UNPKG

@tanstack/db

Version:

A reactive client store for building super fast apps on sync

1 lines 64.7 kB
{"version":3,"file":"predicate-utils.cjs","sources":["../../../src/query/predicate-utils.ts"],"sourcesContent":["import { Func, Value } from './ir.js'\nimport type { BasicExpression, OrderBy, PropRef } from './ir.js'\nimport type { LoadSubsetOptions } from '../types.js'\n\n/**\n * Check if one where clause is a logical subset of another.\n * Returns true if the subset predicate is more restrictive than (or equal to) the superset predicate.\n *\n * @example\n * // age > 20 is subset of age > 10 (more restrictive)\n * isWhereSubset(gt(ref('age'), val(20)), gt(ref('age'), val(10))) // true\n *\n * @example\n * // age > 10 AND name = 'X' is subset of age > 10 (more conditions)\n * isWhereSubset(and(gt(ref('age'), val(10)), eq(ref('name'), val('X'))), gt(ref('age'), val(10))) // true\n *\n * @param subset - The potentially more restrictive predicate\n * @param superset - The potentially less restrictive predicate\n * @returns true if subset logically implies superset\n */\nexport function isWhereSubset(\n subset: BasicExpression<boolean> | undefined,\n superset: BasicExpression<boolean> | undefined,\n): boolean {\n // undefined/missing where clause means \"no filter\" (all data)\n // Both undefined means subset relationship holds (all data ⊆ all data)\n if (subset === undefined && superset === undefined) {\n return true\n }\n\n // If subset is undefined but superset is not, we're requesting ALL data\n // but have only loaded SOME data - subset relationship does NOT hold\n if (subset === undefined && superset !== undefined) {\n return false\n }\n\n // If superset is undefined (no filter = all data loaded),\n // then any constrained subset is contained\n if (superset === undefined && subset !== undefined) {\n return true\n }\n\n return isWhereSubsetInternal(subset!, superset!)\n}\n\nfunction makeDisjunction(\n preds: Array<BasicExpression<boolean>>,\n): BasicExpression<boolean> {\n if (preds.length === 0) {\n return new Value(false)\n }\n if (preds.length === 1) {\n return preds[0]!\n }\n return new Func(`or`, preds)\n}\n\nfunction convertInToOr(inField: InField) {\n const equalities = inField.values.map(\n (value) => new Func(`eq`, [inField.ref, new Value(value)]),\n )\n return makeDisjunction(equalities)\n}\n\nfunction isWhereSubsetInternal(\n subset: BasicExpression<boolean>,\n superset: BasicExpression<boolean>,\n): boolean {\n // If subset is false it is requesting no data,\n // thus the result set is empty\n // and the empty set is a subset of any set\n if (subset.type === `val` && subset.value === false) {\n return true\n }\n\n // If expressions are structurally equal, subset relationship holds\n if (areExpressionsEqual(subset, superset)) {\n return true\n }\n\n // Handle superset being an AND: subset must imply ALL conjuncts\n // If superset is (A AND B), then subset ⊆ (A AND B) only if subset ⊆ A AND subset ⊆ B\n // Example: (age > 20) ⊆ (age > 10 AND status = 'active') is false (doesn't imply status condition)\n if (superset.type === `func` && superset.name === `and`) {\n return superset.args.every((arg) =>\n isWhereSubsetInternal(subset, arg as BasicExpression<boolean>),\n )\n }\n\n // Handle subset being an AND: (A AND B) implies both A and B\n if (subset.type === `func` && subset.name === `and`) {\n // For (A AND B) ⊆ C, since (A AND B) implies A, we check if any conjunct implies C\n return subset.args.some((arg) =>\n isWhereSubsetInternal(arg as BasicExpression<boolean>, superset),\n )\n }\n\n // Turn x IN [A, B, C] into x = A OR x = B OR x = C\n // for unified handling of IN and OR\n if (subset.type === `func` && subset.name === `in`) {\n const inField = extractInField(subset)\n if (inField) {\n return isWhereSubsetInternal(convertInToOr(inField), superset)\n }\n }\n\n if (superset.type === `func` && superset.name === `in`) {\n const inField = extractInField(superset)\n if (inField) {\n return isWhereSubsetInternal(subset, convertInToOr(inField))\n }\n }\n\n // Handle OR in subset: (A OR B) is subset of C only if both A and B are subsets of C\n if (subset.type === `func` && subset.name === `or`) {\n return subset.args.every((arg) =>\n isWhereSubsetInternal(arg as BasicExpression<boolean>, superset),\n )\n }\n\n // Handle OR in superset: subset ⊆ (A OR B) if subset ⊆ A or subset ⊆ B\n // (A OR B) as superset means data can satisfy A or B\n // If subset is contained in any disjunct, it's contained in the union\n if (superset.type === `func` && superset.name === `or`) {\n return superset.args.some((arg) =>\n isWhereSubsetInternal(subset, arg as BasicExpression<boolean>),\n )\n }\n\n // Handle comparison operators on the same field\n if (subset.type === `func` && superset.type === `func`) {\n const subsetFunc = subset as Func\n const supersetFunc = superset as Func\n\n // Check if both are comparisons on the same field\n const subsetField = extractComparisonField(subsetFunc)\n const supersetField = extractComparisonField(supersetFunc)\n\n if (\n subsetField &&\n supersetField &&\n areRefsEqual(subsetField.ref, supersetField.ref)\n ) {\n return isComparisonSubset(\n subsetFunc,\n subsetField.value,\n supersetFunc,\n supersetField.value,\n )\n }\n\n /*\n // Handle eq vs in\n if (subsetFunc.name === `eq` && supersetFunc.name === `in`) {\n const subsetFieldEq = extractEqualityField(subsetFunc)\n const supersetFieldIn = extractInField(supersetFunc)\n if (\n subsetFieldEq &&\n supersetFieldIn &&\n areRefsEqual(subsetFieldEq.ref, supersetFieldIn.ref)\n ) {\n // field = X is subset of field IN [X, Y, Z] if X is in the array\n // Use cached primitive set and metadata from extraction\n return arrayIncludesWithSet(\n supersetFieldIn.values,\n subsetFieldEq.value,\n supersetFieldIn.primitiveSet ?? null,\n supersetFieldIn.areAllPrimitives\n )\n }\n }\n\n // Handle in vs in\n if (subsetFunc.name === `in` && supersetFunc.name === `in`) {\n const subsetFieldIn = extractInField(subsetFunc)\n const supersetFieldIn = extractInField(supersetFunc)\n if (\n subsetFieldIn &&\n supersetFieldIn &&\n areRefsEqual(subsetFieldIn.ref, supersetFieldIn.ref)\n ) {\n // field IN [A, B] is subset of field IN [A, B, C] if all values in subset are in superset\n // Use cached primitive set and metadata from extraction\n return subsetFieldIn.values.every((subVal) =>\n arrayIncludesWithSet(\n supersetFieldIn.values,\n subVal,\n supersetFieldIn.primitiveSet ?? null,\n supersetFieldIn.areAllPrimitives\n )\n )\n }\n }\n */\n }\n\n // Conservative: if we can't determine, return false\n return false\n}\n\n/**\n * Helper to combine where predicates with common logic for AND/OR operations\n */\nfunction combineWherePredicates(\n predicates: Array<BasicExpression<boolean>>,\n operation: `and` | `or`,\n simplifyFn: (\n preds: Array<BasicExpression<boolean>>,\n ) => BasicExpression<boolean> | null,\n): BasicExpression<boolean> {\n const emptyValue = operation === `and` ? true : false\n const identityValue = operation === `and` ? true : false\n\n if (predicates.length === 0) {\n return { type: `val`, value: emptyValue } as BasicExpression<boolean>\n }\n\n if (predicates.length === 1) {\n return predicates[0]!\n }\n\n // Flatten nested expressions of the same operation\n const flatPredicates: Array<BasicExpression<boolean>> = []\n for (const pred of predicates) {\n if (pred.type === `func` && pred.name === operation) {\n flatPredicates.push(...pred.args)\n } else {\n flatPredicates.push(pred)\n }\n }\n\n // Group predicates by field for simplification\n const grouped = groupPredicatesByField(flatPredicates)\n\n // Simplify each group\n const simplified: Array<BasicExpression<boolean>> = []\n for (const [field, preds] of grouped.entries()) {\n if (field === null) {\n // Complex predicates that we can't group by field\n simplified.push(...preds)\n } else {\n // Try to simplify same-field predicates\n const result = simplifyFn(preds)\n\n // For intersection: check for empty set (contradiction)\n if (\n operation === `and` &&\n result &&\n result.type === `val` &&\n result.value === false\n ) {\n // Intersection is empty (conflicting constraints) - entire AND is false\n return { type: `val`, value: false } as BasicExpression<boolean>\n }\n\n // For union: result may be null if simplification failed\n if (result) {\n simplified.push(result)\n }\n }\n }\n\n if (simplified.length === 0) {\n return { type: `val`, value: identityValue } as BasicExpression<boolean>\n }\n\n if (simplified.length === 1) {\n return simplified[0]!\n }\n\n // Return combined predicate\n return {\n type: `func`,\n name: operation,\n args: simplified,\n } as BasicExpression<boolean>\n}\n\n/**\n * Combine multiple where predicates with OR logic (union).\n * Returns a predicate that is satisfied when any input predicate is satisfied.\n * Simplifies when possible (e.g., age > 10 OR age > 20 → age > 10).\n *\n * @example\n * // Take least restrictive\n * unionWherePredicates([gt(ref('age'), val(10)), gt(ref('age'), val(20))]) // age > 10\n *\n * @example\n * // Combine equals into IN\n * unionWherePredicates([eq(ref('age'), val(5)), eq(ref('age'), val(10))]) // age IN [5, 10]\n *\n * @param predicates - Array of where predicates to union\n * @returns Combined predicate representing the union\n */\nexport function unionWherePredicates(\n predicates: Array<BasicExpression<boolean>>,\n): BasicExpression<boolean> {\n return combineWherePredicates(predicates, `or`, unionSameFieldPredicates)\n}\n\n/**\n * Compute the difference between two where predicates: `fromPredicate AND NOT(subtractPredicate)`.\n * Returns the simplified predicate, or null if the difference cannot be simplified\n * (in which case the caller should fetch the full fromPredicate).\n *\n * @example\n * // Range difference\n * minusWherePredicates(\n * gt(ref('age'), val(10)), // age > 10\n * gt(ref('age'), val(20)) // age > 20\n * ) // → age > 10 AND age <= 20\n *\n * @example\n * // Set difference\n * minusWherePredicates(\n * inOp(ref('status'), ['A', 'B', 'C', 'D']), // status IN ['A','B','C','D']\n * inOp(ref('status'), ['B', 'C']) // status IN ['B','C']\n * ) // → status IN ['A', 'D']\n *\n * @example\n * // Common conditions\n * minusWherePredicates(\n * and(gt(ref('age'), val(10)), eq(ref('status'), val('active'))), // age > 10 AND status = 'active'\n * and(gt(ref('age'), val(20)), eq(ref('status'), val('active'))) // age > 20 AND status = 'active'\n * ) // → age > 10 AND age <= 20 AND status = 'active'\n *\n * @example\n * // Complete overlap - empty result\n * minusWherePredicates(\n * gt(ref('age'), val(20)), // age > 20\n * gt(ref('age'), val(10)) // age > 10\n * ) // → {type: 'val', value: false} (empty set)\n *\n * @param fromPredicate - The predicate to subtract from\n * @param subtractPredicate - The predicate to subtract\n * @returns The simplified difference, or null if cannot be simplified\n */\nexport function minusWherePredicates(\n fromPredicate: BasicExpression<boolean> | undefined,\n subtractPredicate: BasicExpression<boolean> | undefined,\n): BasicExpression<boolean> | null {\n // If nothing to subtract, return the original\n if (subtractPredicate === undefined) {\n return (\n fromPredicate ??\n ({ type: `val`, value: true } as BasicExpression<boolean>)\n )\n }\n\n // If from is undefined then we are asking for all data\n // so we need to load all data minus what we already loaded\n // i.e. we need to load NOT(subtractPredicate)\n if (fromPredicate === undefined) {\n return {\n type: `func`,\n name: `not`,\n args: [subtractPredicate],\n } as BasicExpression<boolean>\n }\n\n // Check if fromPredicate is entirely contained in subtractPredicate\n // In that case, fromPredicate AND NOT(subtractPredicate) = empty set\n if (isWhereSubset(fromPredicate, subtractPredicate)) {\n return { type: `val`, value: false } as BasicExpression<boolean>\n }\n\n // Try to detect and handle common conditions\n const commonConditions = findCommonConditions(\n fromPredicate,\n subtractPredicate,\n )\n if (commonConditions.length > 0) {\n // Extract predicates without common conditions\n const fromWithoutCommon = removeConditions(fromPredicate, commonConditions)\n const subtractWithoutCommon = removeConditions(\n subtractPredicate,\n commonConditions,\n )\n\n // Recursively compute difference on simplified predicates\n const simplifiedDifference = minusWherePredicates(\n fromWithoutCommon,\n subtractWithoutCommon,\n )\n\n if (simplifiedDifference !== null) {\n // Combine the simplified difference with common conditions\n return combineConditions([...commonConditions, simplifiedDifference])\n }\n }\n\n // Check if they are on the same field - if so, we can try to simplify\n if (fromPredicate.type === `func` && subtractPredicate.type === `func`) {\n const result = minusSameFieldPredicates(fromPredicate, subtractPredicate)\n if (result !== null) {\n return result\n }\n }\n\n // Can't simplify - return null to indicate caller should fetch full fromPredicate\n return null\n}\n\n/**\n * Helper function to compute difference for same-field predicates\n */\nfunction minusSameFieldPredicates(\n fromPred: Func,\n subtractPred: Func,\n): BasicExpression<boolean> | null {\n // Extract field information\n const fromField =\n extractComparisonField(fromPred) ||\n extractEqualityField(fromPred) ||\n extractInField(fromPred)\n const subtractField =\n extractComparisonField(subtractPred) ||\n extractEqualityField(subtractPred) ||\n extractInField(subtractPred)\n\n // Must be on the same field\n if (\n !fromField ||\n !subtractField ||\n !areRefsEqual(fromField.ref, subtractField.ref)\n ) {\n return null\n }\n\n // Handle IN minus IN: status IN [A,B,C,D] - status IN [B,C] = status IN [A,D]\n if (fromPred.name === `in` && subtractPred.name === `in`) {\n const fromInField = fromField as InField\n const subtractInField = subtractField as InField\n\n // Filter out values that are in the subtract set\n const remainingValues = fromInField.values.filter(\n (v) =>\n !arrayIncludesWithSet(\n subtractInField.values,\n v,\n subtractInField.primitiveSet ?? null,\n subtractInField.areAllPrimitives,\n ),\n )\n\n if (remainingValues.length === 0) {\n return { type: `val`, value: false } as BasicExpression<boolean>\n }\n\n if (remainingValues.length === 1) {\n return {\n type: `func`,\n name: `eq`,\n args: [fromField.ref, { type: `val`, value: remainingValues[0] }],\n } as BasicExpression<boolean>\n }\n\n return {\n type: `func`,\n name: `in`,\n args: [fromField.ref, { type: `val`, value: remainingValues }],\n } as BasicExpression<boolean>\n }\n\n // Handle IN minus equality: status IN [A,B,C] - status = B = status IN [A,C]\n if (fromPred.name === `in` && subtractPred.name === `eq`) {\n const fromInField = fromField as InField\n const subtractValue = (subtractField as { ref: PropRef; value: any }).value\n\n const remainingValues = fromInField.values.filter(\n (v) => !areValuesEqual(v, subtractValue),\n )\n\n if (remainingValues.length === 0) {\n return { type: `val`, value: false } as BasicExpression<boolean>\n }\n\n if (remainingValues.length === 1) {\n return {\n type: `func`,\n name: `eq`,\n args: [fromField.ref, { type: `val`, value: remainingValues[0] }],\n } as BasicExpression<boolean>\n }\n\n return {\n type: `func`,\n name: `in`,\n args: [fromField.ref, { type: `val`, value: remainingValues }],\n } as BasicExpression<boolean>\n }\n\n // Handle equality minus equality: age = 15 - age = 15 = empty, age = 15 - age = 20 = age = 15\n if (fromPred.name === `eq` && subtractPred.name === `eq`) {\n const fromValue = (fromField as { ref: PropRef; value: any }).value\n const subtractValue = (subtractField as { ref: PropRef; value: any }).value\n\n if (areValuesEqual(fromValue, subtractValue)) {\n return { type: `val`, value: false } as BasicExpression<boolean>\n }\n\n // No overlap - return original\n return fromPred as BasicExpression<boolean>\n }\n\n // Handle range minus range: age > 10 - age > 20 = age > 10 AND age <= 20\n const fromComp = extractComparisonField(fromPred)\n const subtractComp = extractComparisonField(subtractPred)\n\n if (\n fromComp &&\n subtractComp &&\n areRefsEqual(fromComp.ref, subtractComp.ref)\n ) {\n // Try to compute the difference using range logic\n const result = minusRangePredicates(\n fromPred,\n fromComp.value,\n subtractPred,\n subtractComp.value,\n )\n return result\n }\n\n // Can't simplify\n return null\n}\n\n/**\n * Helper to compute difference between range predicates\n */\nfunction minusRangePredicates(\n fromFunc: Func,\n fromValue: any,\n subtractFunc: Func,\n subtractValue: any,\n): BasicExpression<boolean> | null {\n const fromOp = fromFunc.name as `gt` | `gte` | `lt` | `lte` | `eq`\n const subtractOp = subtractFunc.name as `gt` | `gte` | `lt` | `lte` | `eq`\n const ref = (extractComparisonField(fromFunc) ||\n extractEqualityField(fromFunc))!.ref\n\n // age > 10 - age > 20 = (age > 10 AND age <= 20)\n if (fromOp === `gt` && subtractOp === `gt`) {\n if (fromValue < subtractValue) {\n // Result is: fromValue < field <= subtractValue\n return {\n type: `func`,\n name: `and`,\n args: [\n fromFunc as BasicExpression<boolean>,\n {\n type: `func`,\n name: `lte`,\n args: [ref, { type: `val`, value: subtractValue }],\n } as BasicExpression<boolean>,\n ],\n } as BasicExpression<boolean>\n }\n // fromValue >= subtractValue means no overlap\n return fromFunc as BasicExpression<boolean>\n }\n\n // age >= 10 - age >= 20 = (age >= 10 AND age < 20)\n if (fromOp === `gte` && subtractOp === `gte`) {\n if (fromValue < subtractValue) {\n return {\n type: `func`,\n name: `and`,\n args: [\n fromFunc as BasicExpression<boolean>,\n {\n type: `func`,\n name: `lt`,\n args: [ref, { type: `val`, value: subtractValue }],\n } as BasicExpression<boolean>,\n ],\n } as BasicExpression<boolean>\n }\n return fromFunc as BasicExpression<boolean>\n }\n\n // age > 10 - age >= 20 = (age > 10 AND age < 20)\n if (fromOp === `gt` && subtractOp === `gte`) {\n if (fromValue < subtractValue) {\n return {\n type: `func`,\n name: `and`,\n args: [\n fromFunc as BasicExpression<boolean>,\n {\n type: `func`,\n name: `lt`,\n args: [ref, { type: `val`, value: subtractValue }],\n } as BasicExpression<boolean>,\n ],\n } as BasicExpression<boolean>\n }\n return fromFunc as BasicExpression<boolean>\n }\n\n // age >= 10 - age > 20 = (age >= 10 AND age <= 20)\n if (fromOp === `gte` && subtractOp === `gt`) {\n if (fromValue <= subtractValue) {\n return {\n type: `func`,\n name: `and`,\n args: [\n fromFunc as BasicExpression<boolean>,\n {\n type: `func`,\n name: `lte`,\n args: [ref, { type: `val`, value: subtractValue }],\n } as BasicExpression<boolean>,\n ],\n } as BasicExpression<boolean>\n }\n return fromFunc as BasicExpression<boolean>\n }\n\n // age < 30 - age < 20 = (age >= 20 AND age < 30)\n if (fromOp === `lt` && subtractOp === `lt`) {\n if (fromValue > subtractValue) {\n return {\n type: `func`,\n name: `and`,\n args: [\n {\n type: `func`,\n name: `gte`,\n args: [ref, { type: `val`, value: subtractValue }],\n } as BasicExpression<boolean>,\n fromFunc as BasicExpression<boolean>,\n ],\n } as BasicExpression<boolean>\n }\n return fromFunc as BasicExpression<boolean>\n }\n\n // age <= 30 - age <= 20 = (age > 20 AND age <= 30)\n if (fromOp === `lte` && subtractOp === `lte`) {\n if (fromValue > subtractValue) {\n return {\n type: `func`,\n name: `and`,\n args: [\n {\n type: `func`,\n name: `gt`,\n args: [ref, { type: `val`, value: subtractValue }],\n } as BasicExpression<boolean>,\n fromFunc as BasicExpression<boolean>,\n ],\n } as BasicExpression<boolean>\n }\n return fromFunc as BasicExpression<boolean>\n }\n\n // age < 30 - age <= 20 = (age > 20 AND age < 30)\n if (fromOp === `lt` && subtractOp === `lte`) {\n if (fromValue > subtractValue) {\n return {\n type: `func`,\n name: `and`,\n args: [\n {\n type: `func`,\n name: `gt`,\n args: [ref, { type: `val`, value: subtractValue }],\n } as BasicExpression<boolean>,\n fromFunc as BasicExpression<boolean>,\n ],\n } as BasicExpression<boolean>\n }\n return fromFunc as BasicExpression<boolean>\n }\n\n // age <= 30 - age < 20 = (age >= 20 AND age <= 30)\n if (fromOp === `lte` && subtractOp === `lt`) {\n if (fromValue >= subtractValue) {\n return {\n type: `func`,\n name: `and`,\n args: [\n {\n type: `func`,\n name: `gte`,\n args: [ref, { type: `val`, value: subtractValue }],\n } as BasicExpression<boolean>,\n fromFunc as BasicExpression<boolean>,\n ],\n } as BasicExpression<boolean>\n }\n return fromFunc as BasicExpression<boolean>\n }\n\n // Can't simplify other combinations\n return null\n}\n\n/**\n * Check if one orderBy clause is a subset of another.\n * Returns true if the subset ordering requirements are satisfied by the superset ordering.\n *\n * @example\n * // Subset is prefix of superset\n * isOrderBySubset([{expr: age, asc}], [{expr: age, asc}, {expr: name, desc}]) // true\n *\n * @param subset - The ordering requirements to check\n * @param superset - The ordering that might satisfy the requirements\n * @returns true if subset is satisfied by superset\n */\nexport function isOrderBySubset(\n subset: OrderBy | undefined,\n superset: OrderBy | undefined,\n): boolean {\n // No ordering requirement is always satisfied\n if (!subset || subset.length === 0) {\n return true\n }\n\n // If there's no superset ordering but subset requires ordering, not satisfied\n if (!superset || superset.length === 0) {\n return false\n }\n\n // Check if subset is a prefix of superset with matching expressions and compare options\n if (subset.length > superset.length) {\n return false\n }\n\n for (let i = 0; i < subset.length; i++) {\n const subClause = subset[i]!\n const superClause = superset[i]!\n\n // Check if expressions match\n if (!areExpressionsEqual(subClause.expression, superClause.expression)) {\n return false\n }\n\n // Check if compare options match\n if (\n !areCompareOptionsEqual(\n subClause.compareOptions,\n superClause.compareOptions,\n )\n ) {\n return false\n }\n }\n\n return true\n}\n\n/**\n * Check if one limit is a subset of another.\n * Returns true if the subset limit requirements are satisfied by the superset limit.\n *\n * Note: This function does NOT consider offset. For offset-aware subset checking,\n * use `isOffsetLimitSubset` instead.\n *\n * @example\n * isLimitSubset(10, 20) // true (requesting 10 items when 20 are available)\n * isLimitSubset(20, 10) // false (requesting 20 items when only 10 are available)\n * isLimitSubset(10, undefined) // true (requesting 10 items when unlimited are available)\n *\n * @param subset - The limit requirement to check\n * @param superset - The limit that might satisfy the requirement\n * @returns true if subset is satisfied by superset\n */\nexport function isLimitSubset(\n subset: number | undefined,\n superset: number | undefined,\n): boolean {\n // Unlimited superset satisfies any limit requirement\n if (superset === undefined) {\n return true\n }\n\n // If requesting all data (no limit), we need unlimited data to satisfy it\n // But we know superset is not unlimited so we return false\n if (subset === undefined) {\n return false\n }\n\n // Otherwise, subset must be less than or equal to superset\n return subset <= superset\n}\n\n/**\n * Check if one offset+limit range is a subset of another.\n * Returns true if the subset range is fully contained within the superset range.\n *\n * A query with `{limit: 10, offset: 0}` loads rows [0, 10).\n * A query with `{limit: 10, offset: 20}` loads rows [20, 30).\n *\n * For subset to be satisfied by superset:\n * - Superset must start at or before subset (superset.offset <= subset.offset)\n * - Superset must end at or after subset (superset.offset + superset.limit >= subset.offset + subset.limit)\n *\n * @example\n * isOffsetLimitSubset({ offset: 0, limit: 5 }, { offset: 0, limit: 10 }) // true\n * isOffsetLimitSubset({ offset: 5, limit: 5 }, { offset: 0, limit: 10 }) // true (rows 5-9 within 0-9)\n * isOffsetLimitSubset({ offset: 5, limit: 10 }, { offset: 0, limit: 10 }) // false (rows 5-14 exceed 0-9)\n * isOffsetLimitSubset({ offset: 20, limit: 10 }, { offset: 0, limit: 10 }) // false (rows 20-29 outside 0-9)\n *\n * @param subset - The offset+limit requirements to check\n * @param superset - The offset+limit that might satisfy the requirements\n * @returns true if subset range is fully contained within superset range\n */\nexport function isOffsetLimitSubset(\n subset: { offset?: number; limit?: number },\n superset: { offset?: number; limit?: number },\n): boolean {\n const subsetOffset = subset.offset ?? 0\n const supersetOffset = superset.offset ?? 0\n\n // Superset must start at or before subset\n if (supersetOffset > subsetOffset) {\n return false\n }\n\n // If superset is unlimited, it covers everything from its offset onwards\n if (superset.limit === undefined) {\n return true\n }\n\n // If subset is unlimited but superset has a limit, subset can't be satisfied\n if (subset.limit === undefined) {\n return false\n }\n\n // Both have limits - check if subset range is within superset range\n const subsetEnd = subsetOffset + subset.limit\n const supersetEnd = supersetOffset + superset.limit\n\n return subsetEnd <= supersetEnd\n}\n\n/**\n * Check if one predicate (where + orderBy + limit + offset) is a subset of another.\n * Returns true if all aspects of the subset predicate are satisfied by the superset.\n *\n * @example\n * isPredicateSubset(\n * { where: gt(ref('age'), val(20)), limit: 10 },\n * { where: gt(ref('age'), val(10)), limit: 20 }\n * ) // true\n *\n * @param subset - The predicate requirements to check\n * @param superset - The predicate that might satisfy the requirements\n * @returns true if subset is satisfied by superset\n */\nexport function isPredicateSubset(\n subset: LoadSubsetOptions,\n superset: LoadSubsetOptions,\n): boolean {\n // When the superset has a limit, we can only determine subset relationship\n // if the where clauses are equal (not just subset relationship).\n //\n // This is because a limited query only loads a portion of the matching rows.\n // A more restrictive where clause might require rows outside that portion.\n //\n // Example: superset = {where: undefined, limit: 10, orderBy: desc}\n // subset = {where: LIKE 'search%', limit: 10, orderBy: desc}\n // The top 10 items matching 'search%' might include items outside the overall top 10.\n //\n // However, if the where clauses are equal, then the subset relationship can\n // be determined by orderBy, limit, and offset:\n // Example: superset = {where: status='active', limit: 10, offset: 0, orderBy: desc}\n // subset = {where: status='active', limit: 5, offset: 0, orderBy: desc}\n // The top 5 active items ARE contained in the top 10 active items.\n if (superset.limit !== undefined) {\n // For limited supersets, where clauses must be equal\n if (!areWhereClausesEqual(subset.where, superset.where)) {\n return false\n }\n return (\n isOrderBySubset(subset.orderBy, superset.orderBy) &&\n isOffsetLimitSubset(subset, superset)\n )\n }\n\n // For unlimited supersets, use the normal subset logic\n // Still need to consider offset - an unlimited query with offset only covers\n // rows from that offset onwards\n return (\n isWhereSubset(subset.where, superset.where) &&\n isOrderBySubset(subset.orderBy, superset.orderBy) &&\n isOffsetLimitSubset(subset, superset)\n )\n}\n\n/**\n * Check if two where clauses are structurally equal.\n * Used for limited query subset checks where subset relationship isn't sufficient.\n */\nfunction areWhereClausesEqual(\n a: BasicExpression<boolean> | undefined,\n b: BasicExpression<boolean> | undefined,\n): boolean {\n if (a === undefined && b === undefined) {\n return true\n }\n if (a === undefined || b === undefined) {\n return false\n }\n return areExpressionsEqual(a, b)\n}\n\n// ============================================================================\n// Helper functions\n// ============================================================================\n\n/**\n * Find common conditions between two predicates.\n * Returns an array of conditions that appear in both predicates.\n */\nfunction findCommonConditions(\n predicate1: BasicExpression<boolean>,\n predicate2: BasicExpression<boolean>,\n): Array<BasicExpression<boolean>> {\n const conditions1 = extractAllConditions(predicate1)\n const conditions2 = extractAllConditions(predicate2)\n\n const common: Array<BasicExpression<boolean>> = []\n\n for (const cond1 of conditions1) {\n for (const cond2 of conditions2) {\n if (areExpressionsEqual(cond1, cond2)) {\n // Avoid duplicates\n if (!common.some((c) => areExpressionsEqual(c, cond1))) {\n common.push(cond1)\n }\n break\n }\n }\n }\n\n return common\n}\n\n/**\n * Extract all individual conditions from a predicate, flattening AND operations.\n */\nfunction extractAllConditions(\n predicate: BasicExpression<boolean>,\n): Array<BasicExpression<boolean>> {\n if (predicate.type === `func` && predicate.name === `and`) {\n const conditions: Array<BasicExpression<boolean>> = []\n for (const arg of predicate.args) {\n conditions.push(...extractAllConditions(arg as BasicExpression<boolean>))\n }\n return conditions\n }\n\n return [predicate]\n}\n\n/**\n * Remove specified conditions from a predicate.\n * Returns the predicate with the specified conditions removed, or undefined if all conditions are removed.\n */\nfunction removeConditions(\n predicate: BasicExpression<boolean>,\n conditionsToRemove: Array<BasicExpression<boolean>>,\n): BasicExpression<boolean> | undefined {\n if (predicate.type === `func` && predicate.name === `and`) {\n const remainingArgs = predicate.args.filter(\n (arg) =>\n !conditionsToRemove.some((cond) =>\n areExpressionsEqual(arg as BasicExpression<boolean>, cond),\n ),\n )\n\n if (remainingArgs.length === 0) {\n return undefined\n } else if (remainingArgs.length === 1) {\n return remainingArgs[0]!\n } else {\n return {\n type: `func`,\n name: `and`,\n args: remainingArgs,\n } as BasicExpression<boolean>\n }\n }\n\n // For non-AND predicates, don't remove anything\n return predicate\n}\n\n/**\n * Combine multiple conditions into a single predicate using AND logic.\n * Flattens nested AND operations to avoid unnecessary nesting.\n */\nfunction combineConditions(\n conditions: Array<BasicExpression<boolean>>,\n): BasicExpression<boolean> {\n if (conditions.length === 0) {\n return { type: `val`, value: true } as BasicExpression<boolean>\n } else if (conditions.length === 1) {\n return conditions[0]!\n } else {\n // Flatten all conditions, including those that are already AND operations\n const flattenedConditions: Array<BasicExpression<boolean>> = []\n\n for (const condition of conditions) {\n if (condition.type === `func` && condition.name === `and`) {\n // Flatten nested AND operations\n flattenedConditions.push(...condition.args)\n } else {\n flattenedConditions.push(condition)\n }\n }\n\n if (flattenedConditions.length === 1) {\n return flattenedConditions[0]!\n } else {\n return {\n type: `func`,\n name: `and`,\n args: flattenedConditions,\n } as BasicExpression<boolean>\n }\n }\n}\n\n/**\n * Find a predicate with a specific operator and value\n */\nfunction findPredicateWithOperator(\n predicates: Array<BasicExpression<boolean>>,\n operator: string,\n value: any,\n): BasicExpression<boolean> | undefined {\n return predicates.find((p) => {\n if (p.type === `func`) {\n const f = p as Func\n const field = extractComparisonField(f)\n return f.name === operator && field && areValuesEqual(field.value, value)\n }\n return false\n })\n}\n\nfunction areExpressionsEqual(a: BasicExpression, b: BasicExpression): boolean {\n if (a.type !== b.type) {\n return false\n }\n\n if (a.type === `val` && b.type === `val`) {\n return areValuesEqual(a.value, b.value)\n }\n\n if (a.type === `ref` && b.type === `ref`) {\n return areRefsEqual(a, b)\n }\n\n if (a.type === `func` && b.type === `func`) {\n const aFunc = a\n const bFunc = b\n if (aFunc.name !== bFunc.name) {\n return false\n }\n if (aFunc.args.length !== bFunc.args.length) {\n return false\n }\n return aFunc.args.every((arg, i) =>\n areExpressionsEqual(arg, bFunc.args[i]!),\n )\n }\n\n return false\n}\n\nfunction areValuesEqual(a: any, b: any): boolean {\n // Simple equality check - could be enhanced for deep object comparison\n if (a === b) {\n return true\n }\n\n // Handle NaN\n if (typeof a === `number` && typeof b === `number` && isNaN(a) && isNaN(b)) {\n return true\n }\n\n // Handle Date objects\n if (a instanceof Date && b instanceof Date) {\n return a.getTime() === b.getTime()\n }\n\n // For arrays and objects, use reference equality\n // (In practice, we don't need deep equality for these cases -\n // same object reference means same value for our use case)\n if (\n typeof a === `object` &&\n typeof b === `object` &&\n a !== null &&\n b !== null\n ) {\n return a === b\n }\n\n return false\n}\n\nfunction areRefsEqual(a: PropRef, b: PropRef): boolean {\n if (a.path.length !== b.path.length) {\n return false\n }\n return a.path.every((segment, i) => segment === b.path[i])\n}\n\n/**\n * Check if a value is a primitive (string, number, boolean, null, undefined)\n * Primitives can use Set for fast lookups\n */\nfunction isPrimitive(value: any): boolean {\n return (\n value === null ||\n value === undefined ||\n typeof value === `string` ||\n typeof value === `number` ||\n typeof value === `boolean`\n )\n}\n\n/**\n * Check if all values in an array are primitives\n */\nfunction areAllPrimitives(values: Array<any>): boolean {\n return values.every(isPrimitive)\n}\n\n/**\n * Check if a value is in an array, with optional pre-built Set for optimization.\n * The primitiveSet is cached in InField during extraction and reused for all lookups.\n */\nfunction arrayIncludesWithSet(\n array: Array<any>,\n value: any,\n primitiveSet: Set<any> | null,\n arrayIsAllPrimitives?: boolean,\n): boolean {\n // Fast path: use pre-built Set for O(1) lookup\n if (primitiveSet) {\n // Skip isPrimitive check if we know the value must be primitive for a match\n // (if array is all primitives, only primitives can match)\n if (arrayIsAllPrimitives || isPrimitive(value)) {\n return primitiveSet.has(value)\n }\n return false // Non-primitive can't be in primitive-only set\n }\n\n // Fallback: use areValuesEqual for Dates and objects\n return array.some((v) => areValuesEqual(v, value))\n}\n\n/**\n * Get the maximum of two values, handling both numbers and Dates\n */\nfunction maxValue(a: any, b: any): any {\n if (a instanceof Date && b instanceof Date) {\n return a.getTime() > b.getTime() ? a : b\n }\n return Math.max(a, b)\n}\n\n/**\n * Get the minimum of two values, handling both numbers and Dates\n */\nfunction minValue(a: any, b: any): any {\n if (a instanceof Date && b instanceof Date) {\n return a.getTime() < b.getTime() ? a : b\n }\n return Math.min(a, b)\n}\n\nfunction areCompareOptionsEqual(\n a: { direction?: `asc` | `desc`; [key: string]: any },\n b: { direction?: `asc` | `desc`; [key: string]: any },\n): boolean {\n // For now, just compare direction - could be enhanced for other options\n return a.direction === b.direction\n}\n\ninterface ComparisonField {\n ref: PropRef\n value: any\n}\n\nfunction extractComparisonField(func: Func): ComparisonField | null {\n // Handle comparison operators: eq, gt, gte, lt, lte\n if ([`eq`, `gt`, `gte`, `lt`, `lte`].includes(func.name)) {\n // Assume first arg is ref, second is value\n const firstArg = func.args[0]\n const secondArg = func.args[1]\n\n if (firstArg?.type === `ref` && secondArg?.type === `val`) {\n return {\n ref: firstArg,\n value: secondArg.value,\n }\n }\n }\n\n return null\n}\n\nfunction extractEqualityField(func: Func): ComparisonField | null {\n if (func.name === `eq`) {\n const firstArg = func.args[0]\n const secondArg = func.args[1]\n\n if (firstArg?.type === `ref` && secondArg?.type === `val`) {\n return {\n ref: firstArg,\n value: secondArg.value,\n }\n }\n }\n return null\n}\n\ninterface InField {\n ref: PropRef\n values: Array<any>\n // Cached optimization data (computed once, reused many times)\n areAllPrimitives?: boolean\n primitiveSet?: Set<any> | null\n}\n\nfunction extractInField(func: Func): InField | null {\n if (func.name === `in`) {\n const firstArg = func.args[0]\n const secondArg = func.args[1]\n\n if (\n firstArg?.type === `ref` &&\n secondArg?.type === `val` &&\n Array.isArray(secondArg.value)\n ) {\n let values = secondArg.value\n // Precompute optimization metadata once\n const allPrimitives = areAllPrimitives(values)\n let primitiveSet: Set<any> | null = null\n\n if (allPrimitives && values.length > 10) {\n // Build Set and dedupe values at the same time\n primitiveSet = new Set(values)\n // If we found duplicates, use the deduped array going forward\n if (primitiveSet.size < values.length) {\n values = Array.from(primitiveSet)\n }\n }\n\n return {\n ref: firstArg,\n values,\n areAllPrimitives: allPrimitives,\n primitiveSet,\n }\n }\n }\n return null\n}\n\nfunction isComparisonSubset(\n subsetFunc: Func,\n subsetValue: any,\n supersetFunc: Func,\n supersetValue: any,\n): boolean {\n const subOp = subsetFunc.name\n const superOp = supersetFunc.name\n\n // Handle same operator\n if (subOp === superOp) {\n if (subOp === `eq`) {\n // field = X is subset of field = X only\n // Fast path: primitives can use strict equality\n if (isPrimitive(subsetValue) && isPrimitive(supersetValue)) {\n return subsetValue === supersetValue\n }\n return areValuesEqual(subsetValue, supersetValue)\n } else if (subOp === `gt`) {\n // field > 20 is subset of field > 10 if 20 > 10\n return subsetValue >= supersetValue\n } else if (subOp === `gte`) {\n // field >= 20 is subset of field >= 10 if 20 >= 10\n return subsetValue >= supersetValue\n } else if (subOp === `lt`) {\n // field < 10 is subset of field < 20 if 10 <= 20\n return subsetValue <= supersetValue\n } else if (subOp === `lte`) {\n // field <= 10 is subset of field <= 20 if 10 <= 20\n return subsetValue <= supersetValue\n }\n }\n\n // Handle different operators on same field\n // eq vs gt/gte: field = 15 is subset of field > 10 if 15 > 10\n if (subOp === `eq` && superOp === `gt`) {\n return subsetValue > supersetValue\n }\n if (subOp === `eq` && superOp === `gte`) {\n return subsetValue >= supersetValue\n }\n if (subOp === `eq` && superOp === `lt`) {\n return subsetValue < supersetValue\n }\n if (subOp === `eq` && superOp === `lte`) {\n return subsetValue <= supersetValue\n }\n\n // gt/gte vs gte/gt\n if (subOp === `gt` && superOp === `gte`) {\n // field > 10 is subset of field >= 10 if 10 >= 10 (always true for same value)\n return subsetValue >= supersetValue\n }\n if (subOp === `gte` && superOp === `gt`) {\n // field >= 11 is subset of field > 10 if 11 > 10\n return subsetValue > supersetValue\n }\n\n // lt/lte vs lte/lt\n if (subOp === `lt` && superOp === `lte`) {\n // field < 10 is subset of field <= 10 if 10 <= 10\n return subsetValue <= supersetValue\n }\n if (subOp === `lte` && superOp === `lt`) {\n // field <= 9 is subset of field < 10 if 9 < 10\n return subsetValue < supersetValue\n }\n\n return false\n}\n\nfunction groupPredicatesByField(\n predicates: Array<BasicExpression<boolean>>,\n): Map<string | null, Array<BasicExpression<boolean>>> {\n const groups = new Map<string | null, Array<BasicExpression<boolean>>>()\n\n for (const pred of predicates) {\n let fieldKey: string | null = null\n\n if (pred.type === `func`) {\n const func = pred as Func\n const field =\n extractComparisonField(func) ||\n extractEqualityField(func) ||\n extractInField(func)\n if (field) {\n fieldKey = field.ref.path.join(`.`)\n }\n }\n\n const group = groups.get(fieldKey) || []\n group.push(pred)\n groups.set(fieldKey, group)\n }\n\n return groups\n}\n\nfunction unionSameFieldPredicates(\n predicates: Array<BasicExpression<boolean>>,\n): BasicExpression<boolean> | null {\n if (predicates.length === 1) {\n return predicates[0]!\n }\n\n // Try to extract range constraints\n let maxGt: number | null = null\n let maxGte: number | null = null\n let minLt: number | null = null\n let minLte: number | null = null\n const eqValues: Set<any> = new Set()\n const inValues: Set<any> = new Set()\n const otherPredicates: Array<BasicExpression<boolean>> = []\n\n for (const pred of predicates) {\n if (pred.type === `func`) {\n const func = pred as Func\n const field = extractComparisonField(func)\n\n if (field) {\n const value = field.value\n if (func.name === `gt`) {\n maxGt = maxGt === null ? value : minValue(maxGt, value)\n } else if (func.name === `gte`) {\n maxGte = maxGte === null ? value : minValue(maxGte, value)\n } else if (func.name === `lt`) {\n minLt = minLt === null ? value : maxValue(minLt, value)\n } else if (func.name === `lte`) {\n minLte = minLte === null ? value : maxValue(minLte, value)\n } else if (func.name === `eq`) {\n eqValues.add(value)\n } else {\n otherPredicates.push(pred)\n }\n } else {\n const inField = extractInField(func)\n if (inField) {\n for (const val of inField.values) {\n inValues.add(val)\n }\n } else {\n otherPredicates.push(pred)\n }\n }\n } else {\n otherPredicates.push(pred)\n }\n }\n\n // If we have multiple equality values, combine into IN\n if (eqValues.size > 1 || (eqValues.size > 0 && inValues.size > 0)) {\n const allValues = [...eqValues, ...inValues]\n const ref = predicates.find((p) => {\n if (p.type === `func`) {\n const field =\n extractComparisonField(p as Func) || extractInField(p as Func)\n return field !== null\n }\n return false\n })\n\n if (ref && ref.type === `func`) {\n const field =\n extractComparisonField(ref as Func) || extractInField(ref as Func)\n if (field) {\n return {\n type: `func`,\n name: `in`,\n args: [\n field.ref,\n { type: `val`, value: allValues } as BasicExpression,\n ],\n } as BasicExpression<boolean>\n }\n }\n }\n\n // Build the least restrictive range\n const result: Array<BasicExpression<boolean>> = []\n\n // Choose the least restrictive lower bound\n if (maxGt !== null && maxGte !== null) {\n // Take the smaller one (less restrictive)\n const pred =\n maxGte <= maxGt\n ? findPredicateWithOperator(predicates, `gte`, maxGte)\n : findPredicateWithOperator(predicates, `gt`, maxGt)\n if (pred) result.push(pred)\n } else if (maxGt !== null) {\n const pred = findPredicateWithOperator(predicates, `gt`, maxGt)\n if (pred) result.push(pred)\n } else if (maxGte !== null) {\n const pred = findPredicateWithOperator(predicates, `gte`, maxGte)\n if (pred) result.push(pred)\n }\n\n // Choose the least restrictive upper bound\n if (minLt !== null && minLte !== null) {\n const pred =\n minLte >= minLt\n ? findPredicateWithOperator(predicates, `lte`, minLte)\n : findPredicateWithOperator(predicates, `lt`, minLt)\n if (pred) result.push(pred)\n } else if (minLt !== null) {\n const pred = findPredicateWithOperator(predicates, `lt`, minLt)\n if (pred) result.push(pred)\n } else if (minLte !== null) {\n const pred = findPredicateWithOperator(predicates, `lte`, minLte)\n if (pred) result.push(pred)\n }\n\n // Add single eq value\n if (eqValues.size === 1 && inValues.size === 0) {\n const pred = findPredicateWithOperator(predicates, `eq`, [...eqValues][0])\n if (pred) result.push(pred)\n }\n\n // Add IN if only IN values\n if (eqValues.size === 0 && inValues.size > 0) {\n result.push(\n predicates.find((p) => {\n if (p.type === `func`) {\n return (p as Func).name === `in`\n }\n return false\n })!,\n )\n }\n\n // Add other predicates\n result.push(...otherPredicates)\n\n if (result.length === 0) {\n return { type: `val`, value: true } as BasicExpression<boolean>\n }\n\n if (result.length === 1) {\n return result[0]!\n }\n\n return {\n type: `func`,\n name: `or`,\n args: result,\n } as BasicExpression<boolean>\n}\n"],"names":["Value","Func"],"mappings":"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