@@ -231,34 +231,6 @@ function combineWherePredicates(
231231 } as BasicExpression < boolean >
232232}
233233
234- /**
235- * Combine multiple where predicates with AND logic (intersection).
236- * Returns a predicate that is satisfied only when all input predicates are satisfied.
237- * Simplifies when possible (e.g., age > 10 AND age > 20 → age > 20).
238- * Returns a false literal if predicates are contradictory (empty set).
239- *
240- * @example
241- * // Take most restrictive
242- * intersectWherePredicates([gt(ref('age'), val(10)), gt(ref('age'), val(20))]) // age > 20
243- *
244- * @example
245- * // Different fields combine with AND
246- * intersectWherePredicates([gt(ref('age'), val(10)), eq(ref('status'), val('active'))])
247- * // age > 10 AND status = 'active'
248- *
249- * @example
250- * // Contradictory predicates return false
251- * intersectWherePredicates([eq(ref('age'), val(5)), eq(ref('age'), val(6))])
252- * // {type: 'val', value: false}
253- *
254- * @param predicates - Array of where predicates to intersect
255- * @returns Combined predicate representing the intersection, or false literal for empty set
256- */
257- export function intersectWherePredicates (
258- predicates : Array < BasicExpression < boolean > >
259- ) : BasicExpression < boolean > {
260- return combineWherePredicates ( predicates , `and` , intersectSameFieldPredicates )
261- }
262234
263235/**
264236 * Combine multiple where predicates with OR logic (union).
@@ -783,84 +755,7 @@ export function isPredicateSubset(
783755 )
784756}
785757
786- /**
787- * Helper to combine predicates (where + orderBy + limit)
788- */
789- function combinePredicates (
790- predicates : Array < LoadSubsetOptions > ,
791- operation : `intersect` | `union`,
792- whereFn : (
793- clauses : Array < BasicExpression < boolean > >
794- ) => BasicExpression < boolean >
795- ) : LoadSubsetOptions {
796- if ( predicates . length === 0 ) {
797- return { }
798- }
799-
800- if ( predicates . length === 1 ) {
801- return predicates [ 0 ] !
802- }
803-
804- // Combine where clauses
805- const whereClauses = predicates
806- . map ( ( p ) => p . where )
807- . filter ( ( w ) : w is BasicExpression < boolean > => w !== undefined )
808-
809- const mergedWhere =
810- whereClauses . length > 0 ? whereFn ( whereClauses ) : undefined
811-
812- // OrderBy logic differs by operation
813- const mergedOrderBy =
814- operation === `intersect`
815- ? predicates . find ( ( p ) => p . orderBy && p . orderBy . length > 0 ) ?. orderBy
816- : undefined // Union: different orderings can't be combined
817-
818- // Limit logic
819- const limits = predicates
820- . map ( ( p ) => p . limit )
821- . filter ( ( l ) : l is number => l !== undefined )
822-
823- const mergedLimit =
824- operation === `intersect`
825- ? limits . length === 0
826- ? undefined
827- : Math . min ( ...limits ) // All unlimited = unlimited, else min
828- : limits . length === predicates . length && limits . length > 0
829- ? Math . min ( ...limits )
830- : undefined // Min only if all have limits
831-
832- return {
833- where : mergedWhere ,
834- orderBy : mergedOrderBy ,
835- limit : mergedLimit ,
836- }
837- }
838-
839- /**
840- * Merge multiple predicates by intersecting their where clauses.
841- * Intersection semantics: returns predicate satisfied by data matching ALL input predicates.
842- * For limits, this means the MINIMUM (most restrictive) limit.
843- *
844- * @param predicates - Array of predicates to merge
845- * @returns Combined predicate representing the intersection
846- */
847- export function intersectPredicates (
848- predicates : Array < LoadSubsetOptions >
849- ) : LoadSubsetOptions {
850- return combinePredicates ( predicates , `intersect` , intersectWherePredicates )
851- }
852758
853- /**
854- * Merge multiple predicates by unioning their where clauses.
855- *
856- * @param predicates - Array of predicates to merge
857- * @returns Combined predicate
858- */
859- export function unionPredicates (
860- predicates : Array < LoadSubsetOptions >
861- ) : LoadSubsetOptions {
862- return combinePredicates ( predicates , `union` , unionWherePredicates )
863- }
864759
865760// ============================================================================
866761// Helper functions
@@ -1107,25 +1002,6 @@ function arrayIncludesWithSet(
11071002 return array . some ( ( v ) => areValuesEqual ( v , value ) )
11081003}
11091004
1110- /**
1111- * Intersect two arrays, with optional pre-built Set for optimization.
1112- * The set2 is cached in InField during extraction and reused for all operations.
1113- */
1114- function intersectArraysWithSet (
1115- arr1 : Array < any > ,
1116- arr2 : Array < any > ,
1117- set2 : Set < any > | null
1118- ) : Array < any > {
1119- // Fast path: use pre-built Set for O(n) intersection
1120- if ( set2 ) {
1121- // If set2 exists, arr2 contains ONLY primitives (that's when we build the Set).
1122- // So we can skip non-primitives in arr1 immediately - they can't be in arr2.
1123- return arr1 . filter ( ( v ) => isPrimitive ( v ) && set2 . has ( v ) )
1124- }
1125-
1126- // Fallback: use areValuesEqual for all comparisons
1127- return arr1 . filter ( ( v ) => arr2 . some ( ( v2 ) => areValuesEqual ( v , v2 ) ) )
1128- }
11291005
11301006/**
11311007 * Get the maximum of two values, handling both numbers and Dates
@@ -1334,202 +1210,6 @@ function groupPredicatesByField(
13341210 return groups
13351211}
13361212
1337- function intersectSameFieldPredicates (
1338- predicates : Array < BasicExpression < boolean > >
1339- ) : BasicExpression < boolean > {
1340- if ( predicates . length === 1 ) {
1341- return predicates [ 0 ] !
1342- }
1343-
1344- // Try to extract range constraints
1345- let minGt : number | null = null
1346- let minGte : number | null = null
1347- let maxLt : number | null = null
1348- let maxLte : number | null = null
1349- const eqValues : Set < any > = new Set ( )
1350- const inFields : Array < InField > = [ ] // Store full InField objects to access cached data
1351- const otherPredicates : Array < BasicExpression < boolean > > = [ ]
1352-
1353- for ( const pred of predicates ) {
1354- if ( pred . type === `func` ) {
1355- const func = pred as Func
1356- const field = extractComparisonField ( func )
1357-
1358- if ( field ) {
1359- const value = field . value
1360- if ( func . name === `gt` ) {
1361- minGt = minGt === null ? value : maxValue ( minGt , value )
1362- } else if ( func . name === `gte` ) {
1363- minGte = minGte === null ? value : maxValue ( minGte , value )
1364- } else if ( func . name === `lt` ) {
1365- maxLt = maxLt === null ? value : minValue ( maxLt , value )
1366- } else if ( func . name === `lte` ) {
1367- maxLte = maxLte === null ? value : minValue ( maxLte , value )
1368- } else if ( func . name === `eq` ) {
1369- eqValues . add ( value )
1370- } else {
1371- otherPredicates . push ( pred )
1372- }
1373- } else {
1374- const inField = extractInField ( func )
1375- if ( inField ) {
1376- inFields . push ( inField ) // Store full InField with cached primitiveSet
1377- } else {
1378- otherPredicates . push ( pred )
1379- }
1380- }
1381- } else {
1382- otherPredicates . push ( pred )
1383- }
1384- }
1385-
1386- // Check for conflicting equality values (field = 5 AND field = 6 → empty set)
1387- // Need to use areValuesEqual for proper Date/object comparison
1388- const uniqueEqValues : Array < any > = [ ]
1389- for ( const value of eqValues ) {
1390- if ( ! uniqueEqValues . some ( ( v ) => areValuesEqual ( v , value ) ) ) {
1391- uniqueEqValues . push ( value )
1392- }
1393- }
1394- if ( uniqueEqValues . length > 1 ) {
1395- return { type : `val` , value : false } as BasicExpression < boolean >
1396- }
1397-
1398- // If we have an equality, that's the most restrictive
1399- const eqValue = uniqueEqValues . length === 1 ? uniqueEqValues [ 0 ] : null
1400- if ( eqValue !== null ) {
1401- // Check if it satisfies the range constraints
1402- if ( minGt !== null && ! ( eqValue > minGt ) ) {
1403- return { type : `val` , value : false } as BasicExpression < boolean >
1404- }
1405- if ( minGte !== null && ! ( eqValue >= minGte ) ) {
1406- return { type : `val` , value : false } as BasicExpression < boolean >
1407- }
1408- if ( maxLt !== null && ! ( eqValue < maxLt ) ) {
1409- return { type : `val` , value : false } as BasicExpression < boolean >
1410- }
1411- if ( maxLte !== null && ! ( eqValue <= maxLte ) ) {
1412- return { type : `val` , value : false } as BasicExpression < boolean >
1413- }
1414-
1415- // Check if it's in all IN sets (use cached primitive sets and metadata)
1416- for ( const inField of inFields ) {
1417- if (
1418- ! arrayIncludesWithSet (
1419- inField . values ,
1420- eqValue ,
1421- inField . primitiveSet ?? null ,
1422- inField . areAllPrimitives
1423- )
1424- ) {
1425- return { type : `val` , value : false } as BasicExpression < boolean >
1426- }
1427- }
1428-
1429- // Return just the equality (use areValuesEqual for Date support)
1430- return predicates . find ( ( p ) => {
1431- if ( p . type === `func` ) {
1432- const f = p as Func
1433- const field = extractComparisonField ( f )
1434- return f . name === `eq` && field && areValuesEqual ( field . value , eqValue )
1435- }
1436- return false
1437- } ) !
1438- }
1439-
1440- // Handle intersection of multiple IN clauses (use cached primitive sets)
1441- let intersectedInValues : Array < any > | null = null
1442- if ( inFields . length > 0 ) {
1443- // All primitive sets already cached in inFields from extraction
1444- intersectedInValues = [ ...inFields [ 0 ] ! . values ]
1445- for ( let i = 1 ; i < inFields . length ; i ++ ) {
1446- const currentField = inFields [ i ] !
1447- intersectedInValues = intersectArraysWithSet (
1448- intersectedInValues ,
1449- currentField . values ,
1450- currentField . primitiveSet ?? null
1451- )
1452- // Early exit if intersection becomes empty
1453- if ( intersectedInValues . length === 0 ) {
1454- return { type : `val` , value : false } as BasicExpression < boolean >
1455- }
1456- }
1457- }
1458-
1459- // Build the most restrictive range
1460- const result : Array < BasicExpression < boolean > > = [ ]
1461-
1462- // Choose the most restrictive lower bound
1463- if ( minGt !== null && minGte !== null ) {
1464- // If we have both > and >=, use > if it's more restrictive
1465- const pred =
1466- minGt >= minGte
1467- ? findPredicateWithOperator ( predicates , `gt` , minGt )
1468- : findPredicateWithOperator ( predicates , `gte` , minGte )
1469- if ( pred ) result . push ( pred )
1470- } else if ( minGt !== null ) {
1471- const pred = findPredicateWithOperator ( predicates , `gt` , minGt )
1472- if ( pred ) result . push ( pred )
1473- } else if ( minGte !== null ) {
1474- const pred = findPredicateWithOperator ( predicates , `gte` , minGte )
1475- if ( pred ) result . push ( pred )
1476- }
1477-
1478- // Choose the most restrictive upper bound
1479- if ( maxLt !== null && maxLte !== null ) {
1480- const pred =
1481- maxLt <= maxLte
1482- ? findPredicateWithOperator ( predicates , `lt` , maxLt )
1483- : findPredicateWithOperator ( predicates , `lte` , maxLte )
1484- if ( pred ) result . push ( pred )
1485- } else if ( maxLt !== null ) {
1486- const pred = findPredicateWithOperator ( predicates , `lt` , maxLt )
1487- if ( pred ) result . push ( pred )
1488- } else if ( maxLte !== null ) {
1489- const pred = findPredicateWithOperator ( predicates , `lte` , maxLte )
1490- if ( pred ) result . push ( pred )
1491- }
1492-
1493- // Add intersected IN values if present
1494- if ( intersectedInValues !== null && intersectedInValues . length > 0 ) {
1495- // Get the ref from one of the original IN predicates
1496- const firstInPred = predicates . find ( ( p ) => {
1497- if ( p . type === `func` ) {
1498- return ( p as Func ) . name === `in`
1499- }
1500- return false
1501- } )
1502-
1503- if ( firstInPred && firstInPred . type === `func` ) {
1504- const ref = ( firstInPred as Func ) . args [ 0 ]
1505- result . push ( {
1506- type : `func` ,
1507- name : `in` ,
1508- args : [
1509- ref ,
1510- { type : `val` , value : intersectedInValues } as BasicExpression ,
1511- ] ,
1512- } as BasicExpression < boolean > )
1513- }
1514- }
1515-
1516- // Add other predicates
1517- result . push ( ...otherPredicates )
1518-
1519- if ( result . length === 0 ) {
1520- return { type : `val` , value : true } as BasicExpression < boolean >
1521- }
1522-
1523- if ( result . length === 1 ) {
1524- return result [ 0 ] !
1525- }
1526-
1527- return {
1528- type : `func` ,
1529- name : `and` ,
1530- args : result ,
1531- } as BasicExpression < boolean >
1532- }
15331213
15341214function unionSameFieldPredicates (
15351215 predicates : Array < BasicExpression < boolean > >
0 commit comments