@tanstack/db 0.0.26 → 0.0.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/change-events.cjs +141 -0
- package/dist/cjs/change-events.cjs.map +1 -0
- package/dist/cjs/change-events.d.cts +49 -0
- package/dist/cjs/collection.cjs +236 -90
- package/dist/cjs/collection.cjs.map +1 -1
- package/dist/cjs/collection.d.cts +95 -20
- package/dist/cjs/errors.cjs +509 -1
- package/dist/cjs/errors.cjs.map +1 -1
- package/dist/cjs/errors.d.cts +225 -1
- package/dist/cjs/index.cjs +82 -3
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +5 -1
- package/dist/cjs/indexes/auto-index.cjs +64 -0
- package/dist/cjs/indexes/auto-index.cjs.map +1 -0
- package/dist/cjs/indexes/auto-index.d.cts +9 -0
- package/dist/cjs/indexes/base-index.cjs +46 -0
- package/dist/cjs/indexes/base-index.cjs.map +1 -0
- package/dist/cjs/indexes/base-index.d.cts +54 -0
- package/dist/cjs/indexes/index-options.d.cts +13 -0
- package/dist/cjs/indexes/lazy-index.cjs +193 -0
- package/dist/cjs/indexes/lazy-index.cjs.map +1 -0
- package/dist/cjs/indexes/lazy-index.d.cts +96 -0
- package/dist/cjs/indexes/ordered-index.cjs +227 -0
- package/dist/cjs/indexes/ordered-index.cjs.map +1 -0
- package/dist/cjs/indexes/ordered-index.d.cts +72 -0
- package/dist/cjs/local-storage.cjs +9 -15
- package/dist/cjs/local-storage.cjs.map +1 -1
- package/dist/cjs/query/builder/functions.cjs +11 -0
- package/dist/cjs/query/builder/functions.cjs.map +1 -1
- package/dist/cjs/query/builder/functions.d.cts +4 -0
- package/dist/cjs/query/builder/index.cjs +6 -7
- package/dist/cjs/query/builder/index.cjs.map +1 -1
- package/dist/cjs/query/builder/ref-proxy.cjs +37 -0
- package/dist/cjs/query/builder/ref-proxy.cjs.map +1 -1
- package/dist/cjs/query/builder/ref-proxy.d.cts +12 -0
- package/dist/cjs/query/compiler/evaluators.cjs +83 -58
- package/dist/cjs/query/compiler/evaluators.cjs.map +1 -1
- package/dist/cjs/query/compiler/evaluators.d.cts +8 -0
- package/dist/cjs/query/compiler/expressions.cjs +61 -0
- package/dist/cjs/query/compiler/expressions.cjs.map +1 -0
- package/dist/cjs/query/compiler/expressions.d.cts +25 -0
- package/dist/cjs/query/compiler/group-by.cjs +5 -10
- package/dist/cjs/query/compiler/group-by.cjs.map +1 -1
- package/dist/cjs/query/compiler/index.cjs +23 -17
- package/dist/cjs/query/compiler/index.cjs.map +1 -1
- package/dist/cjs/query/compiler/index.d.cts +12 -3
- package/dist/cjs/query/compiler/joins.cjs +61 -12
- package/dist/cjs/query/compiler/joins.cjs.map +1 -1
- package/dist/cjs/query/compiler/order-by.cjs +4 -34
- package/dist/cjs/query/compiler/order-by.cjs.map +1 -1
- package/dist/cjs/query/compiler/types.d.cts +2 -2
- package/dist/cjs/query/live-query-collection.cjs +54 -12
- package/dist/cjs/query/live-query-collection.cjs.map +1 -1
- package/dist/cjs/query/optimizer.cjs +45 -7
- package/dist/cjs/query/optimizer.cjs.map +1 -1
- package/dist/cjs/query/optimizer.d.cts +13 -3
- package/dist/cjs/transactions.cjs +5 -4
- package/dist/cjs/transactions.cjs.map +1 -1
- package/dist/cjs/types.d.cts +31 -0
- package/dist/cjs/utils/array-utils.cjs +18 -0
- package/dist/cjs/utils/array-utils.cjs.map +1 -0
- package/dist/cjs/utils/array-utils.d.cts +8 -0
- package/dist/cjs/utils/comparison.cjs +52 -0
- package/dist/cjs/utils/comparison.cjs.map +1 -0
- package/dist/cjs/utils/comparison.d.cts +11 -0
- package/dist/cjs/utils/index-optimization.cjs +270 -0
- package/dist/cjs/utils/index-optimization.cjs.map +1 -0
- package/dist/cjs/utils/index-optimization.d.cts +29 -0
- package/dist/esm/change-events.d.ts +49 -0
- package/dist/esm/change-events.js +141 -0
- package/dist/esm/change-events.js.map +1 -0
- package/dist/esm/collection.d.ts +95 -20
- package/dist/esm/collection.js +234 -88
- package/dist/esm/collection.js.map +1 -1
- package/dist/esm/errors.d.ts +225 -1
- package/dist/esm/errors.js +510 -2
- package/dist/esm/errors.js.map +1 -1
- package/dist/esm/index.d.ts +5 -1
- package/dist/esm/index.js +81 -2
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/indexes/auto-index.d.ts +9 -0
- package/dist/esm/indexes/auto-index.js +64 -0
- package/dist/esm/indexes/auto-index.js.map +1 -0
- package/dist/esm/indexes/base-index.d.ts +54 -0
- package/dist/esm/indexes/base-index.js +46 -0
- package/dist/esm/indexes/base-index.js.map +1 -0
- package/dist/esm/indexes/index-options.d.ts +13 -0
- package/dist/esm/indexes/lazy-index.d.ts +96 -0
- package/dist/esm/indexes/lazy-index.js +193 -0
- package/dist/esm/indexes/lazy-index.js.map +1 -0
- package/dist/esm/indexes/ordered-index.d.ts +72 -0
- package/dist/esm/indexes/ordered-index.js +227 -0
- package/dist/esm/indexes/ordered-index.js.map +1 -0
- package/dist/esm/local-storage.js +9 -15
- package/dist/esm/local-storage.js.map +1 -1
- package/dist/esm/query/builder/functions.d.ts +4 -0
- package/dist/esm/query/builder/functions.js +11 -0
- package/dist/esm/query/builder/functions.js.map +1 -1
- package/dist/esm/query/builder/index.js +6 -7
- package/dist/esm/query/builder/index.js.map +1 -1
- package/dist/esm/query/builder/ref-proxy.d.ts +12 -0
- package/dist/esm/query/builder/ref-proxy.js +37 -0
- package/dist/esm/query/builder/ref-proxy.js.map +1 -1
- package/dist/esm/query/compiler/evaluators.d.ts +8 -0
- package/dist/esm/query/compiler/evaluators.js +84 -59
- package/dist/esm/query/compiler/evaluators.js.map +1 -1
- package/dist/esm/query/compiler/expressions.d.ts +25 -0
- package/dist/esm/query/compiler/expressions.js +61 -0
- package/dist/esm/query/compiler/expressions.js.map +1 -0
- package/dist/esm/query/compiler/group-by.js +5 -10
- package/dist/esm/query/compiler/group-by.js.map +1 -1
- package/dist/esm/query/compiler/index.d.ts +12 -3
- package/dist/esm/query/compiler/index.js +23 -17
- package/dist/esm/query/compiler/index.js.map +1 -1
- package/dist/esm/query/compiler/joins.js +61 -12
- package/dist/esm/query/compiler/joins.js.map +1 -1
- package/dist/esm/query/compiler/order-by.js +1 -31
- package/dist/esm/query/compiler/order-by.js.map +1 -1
- package/dist/esm/query/compiler/types.d.ts +2 -2
- package/dist/esm/query/live-query-collection.js +54 -12
- package/dist/esm/query/live-query-collection.js.map +1 -1
- package/dist/esm/query/optimizer.d.ts +13 -3
- package/dist/esm/query/optimizer.js +40 -2
- package/dist/esm/query/optimizer.js.map +1 -1
- package/dist/esm/transactions.js +5 -4
- package/dist/esm/transactions.js.map +1 -1
- package/dist/esm/types.d.ts +31 -0
- package/dist/esm/utils/array-utils.d.ts +8 -0
- package/dist/esm/utils/array-utils.js +18 -0
- package/dist/esm/utils/array-utils.js.map +1 -0
- package/dist/esm/utils/comparison.d.ts +11 -0
- package/dist/esm/utils/comparison.js +52 -0
- package/dist/esm/utils/comparison.js.map +1 -0
- package/dist/esm/utils/index-optimization.d.ts +29 -0
- package/dist/esm/utils/index-optimization.js +270 -0
- package/dist/esm/utils/index-optimization.js.map +1 -0
- package/package.json +3 -2
- package/src/change-events.ts +257 -0
- package/src/collection.ts +321 -110
- package/src/errors.ts +545 -1
- package/src/index.ts +7 -1
- package/src/indexes/auto-index.ts +108 -0
- package/src/indexes/base-index.ts +119 -0
- package/src/indexes/index-options.ts +42 -0
- package/src/indexes/lazy-index.ts +251 -0
- package/src/indexes/ordered-index.ts +305 -0
- package/src/local-storage.ts +16 -17
- package/src/query/builder/functions.ts +14 -0
- package/src/query/builder/index.ts +12 -7
- package/src/query/builder/ref-proxy.ts +65 -0
- package/src/query/compiler/evaluators.ts +114 -62
- package/src/query/compiler/expressions.ts +92 -0
- package/src/query/compiler/group-by.ts +10 -10
- package/src/query/compiler/index.ts +52 -22
- package/src/query/compiler/joins.ts +114 -15
- package/src/query/compiler/order-by.ts +1 -45
- package/src/query/compiler/types.ts +2 -2
- package/src/query/live-query-collection.ts +95 -15
- package/src/query/optimizer.ts +94 -5
- package/src/transactions.ts +10 -4
- package/src/types.ts +38 -0
- package/src/utils/array-utils.ts +28 -0
- package/src/utils/comparison.ts +79 -0
- package/src/utils/index-optimization.ts +546 -0
|
@@ -0,0 +1,546 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* # Index-Based Query Optimization
|
|
3
|
+
*
|
|
4
|
+
* This module provides utilities for optimizing query expressions by leveraging
|
|
5
|
+
* available indexes to quickly find matching keys instead of scanning all data.
|
|
6
|
+
*
|
|
7
|
+
* This is different from the query structure optimizer in `query/optimizer.ts`
|
|
8
|
+
* which rewrites query IR structure. This module focuses on using indexes during
|
|
9
|
+
* query execution to speed up data filtering.
|
|
10
|
+
*
|
|
11
|
+
* ## Key Features:
|
|
12
|
+
* - Uses indexes to find matching keys for WHERE conditions
|
|
13
|
+
* - Supports AND/OR logic with set operations
|
|
14
|
+
* - Handles range queries (eq, gt, gte, lt, lte)
|
|
15
|
+
* - Optimizes IN array expressions
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import type { BaseIndex, IndexOperation } from "../indexes/base-index.js"
|
|
19
|
+
import type { BasicExpression } from "../query/ir.js"
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Result of index-based query optimization
|
|
23
|
+
*/
|
|
24
|
+
export interface OptimizationResult<TKey> {
|
|
25
|
+
canOptimize: boolean
|
|
26
|
+
matchingKeys: Set<TKey>
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Finds an index that matches a given field path
|
|
31
|
+
*/
|
|
32
|
+
export function findIndexForField<TKey extends string | number>(
|
|
33
|
+
indexes: Map<number, BaseIndex<TKey>>,
|
|
34
|
+
fieldPath: Array<string>
|
|
35
|
+
): BaseIndex<TKey> | undefined {
|
|
36
|
+
for (const index of indexes.values()) {
|
|
37
|
+
if (index.matchesField(fieldPath)) {
|
|
38
|
+
return index
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return undefined
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Intersects multiple sets (AND logic)
|
|
46
|
+
*/
|
|
47
|
+
export function intersectSets<T>(sets: Array<Set<T>>): Set<T> {
|
|
48
|
+
if (sets.length === 0) return new Set()
|
|
49
|
+
if (sets.length === 1) return new Set(sets[0])
|
|
50
|
+
|
|
51
|
+
let result = new Set(sets[0])
|
|
52
|
+
for (let i = 1; i < sets.length; i++) {
|
|
53
|
+
const newResult = new Set<T>()
|
|
54
|
+
for (const item of result) {
|
|
55
|
+
if (sets[i]!.has(item)) {
|
|
56
|
+
newResult.add(item)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
result = newResult
|
|
60
|
+
}
|
|
61
|
+
return result
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Unions multiple sets (OR logic)
|
|
66
|
+
*/
|
|
67
|
+
export function unionSets<T>(sets: Array<Set<T>>): Set<T> {
|
|
68
|
+
const result = new Set<T>()
|
|
69
|
+
for (const set of sets) {
|
|
70
|
+
for (const item of set) {
|
|
71
|
+
result.add(item)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return result
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Optimizes a query expression using available indexes to find matching keys
|
|
79
|
+
*/
|
|
80
|
+
export function optimizeExpressionWithIndexes<TKey extends string | number>(
|
|
81
|
+
expression: BasicExpression,
|
|
82
|
+
indexes: Map<number, BaseIndex<TKey>>
|
|
83
|
+
): OptimizationResult<TKey> {
|
|
84
|
+
return optimizeQueryRecursive(expression, indexes)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Recursively optimizes query expressions
|
|
89
|
+
*/
|
|
90
|
+
function optimizeQueryRecursive<TKey extends string | number>(
|
|
91
|
+
expression: BasicExpression,
|
|
92
|
+
indexes: Map<number, BaseIndex<TKey>>
|
|
93
|
+
): OptimizationResult<TKey> {
|
|
94
|
+
if (expression.type === `func`) {
|
|
95
|
+
switch (expression.name) {
|
|
96
|
+
case `eq`:
|
|
97
|
+
case `gt`:
|
|
98
|
+
case `gte`:
|
|
99
|
+
case `lt`:
|
|
100
|
+
case `lte`:
|
|
101
|
+
return optimizeSimpleComparison(expression, indexes)
|
|
102
|
+
|
|
103
|
+
case `and`:
|
|
104
|
+
return optimizeAndExpression(expression, indexes)
|
|
105
|
+
|
|
106
|
+
case `or`:
|
|
107
|
+
return optimizeOrExpression(expression, indexes)
|
|
108
|
+
|
|
109
|
+
case `in`:
|
|
110
|
+
return optimizeInArrayExpression(expression, indexes)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return { canOptimize: false, matchingKeys: new Set() }
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Checks if an expression can be optimized
|
|
119
|
+
*/
|
|
120
|
+
export function canOptimizeExpression<TKey extends string | number>(
|
|
121
|
+
expression: BasicExpression,
|
|
122
|
+
indexes: Map<number, BaseIndex<TKey>>
|
|
123
|
+
): boolean {
|
|
124
|
+
if (expression.type === `func`) {
|
|
125
|
+
switch (expression.name) {
|
|
126
|
+
case `eq`:
|
|
127
|
+
case `gt`:
|
|
128
|
+
case `gte`:
|
|
129
|
+
case `lt`:
|
|
130
|
+
case `lte`:
|
|
131
|
+
return canOptimizeSimpleComparison(expression, indexes)
|
|
132
|
+
|
|
133
|
+
case `and`:
|
|
134
|
+
return canOptimizeAndExpression(expression, indexes)
|
|
135
|
+
|
|
136
|
+
case `or`:
|
|
137
|
+
return canOptimizeOrExpression(expression, indexes)
|
|
138
|
+
|
|
139
|
+
case `in`:
|
|
140
|
+
return canOptimizeInArrayExpression(expression, indexes)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return false
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Optimizes compound range queries on the same field
|
|
149
|
+
* Example: WHERE age > 5 AND age < 10
|
|
150
|
+
*/
|
|
151
|
+
function optimizeCompoundRangeQuery<TKey extends string | number>(
|
|
152
|
+
expression: BasicExpression,
|
|
153
|
+
indexes: Map<number, BaseIndex<TKey>>
|
|
154
|
+
): OptimizationResult<TKey> {
|
|
155
|
+
if (expression.type !== `func` || expression.args.length < 2) {
|
|
156
|
+
return { canOptimize: false, matchingKeys: new Set() }
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Group range operations by field
|
|
160
|
+
const fieldOperations = new Map<
|
|
161
|
+
string,
|
|
162
|
+
Array<{
|
|
163
|
+
operation: `gt` | `gte` | `lt` | `lte`
|
|
164
|
+
value: any
|
|
165
|
+
}>
|
|
166
|
+
>()
|
|
167
|
+
|
|
168
|
+
// Collect all range operations from AND arguments
|
|
169
|
+
for (const arg of expression.args) {
|
|
170
|
+
if (arg.type === `func` && [`gt`, `gte`, `lt`, `lte`].includes(arg.name)) {
|
|
171
|
+
const rangeOp = arg as any
|
|
172
|
+
if (rangeOp.args.length === 2) {
|
|
173
|
+
const leftArg = rangeOp.args[0]!
|
|
174
|
+
const rightArg = rangeOp.args[1]!
|
|
175
|
+
|
|
176
|
+
// Check both directions: field op value AND value op field
|
|
177
|
+
let fieldArg: BasicExpression | null = null
|
|
178
|
+
let valueArg: BasicExpression | null = null
|
|
179
|
+
let operation = rangeOp.name as `gt` | `gte` | `lt` | `lte`
|
|
180
|
+
|
|
181
|
+
if (leftArg.type === `ref` && rightArg.type === `val`) {
|
|
182
|
+
// field op value
|
|
183
|
+
fieldArg = leftArg
|
|
184
|
+
valueArg = rightArg
|
|
185
|
+
} else if (leftArg.type === `val` && rightArg.type === `ref`) {
|
|
186
|
+
// value op field - need to flip the operation
|
|
187
|
+
fieldArg = rightArg
|
|
188
|
+
valueArg = leftArg
|
|
189
|
+
|
|
190
|
+
// Flip the operation for reverse comparison
|
|
191
|
+
switch (operation) {
|
|
192
|
+
case `gt`:
|
|
193
|
+
operation = `lt`
|
|
194
|
+
break
|
|
195
|
+
case `gte`:
|
|
196
|
+
operation = `lte`
|
|
197
|
+
break
|
|
198
|
+
case `lt`:
|
|
199
|
+
operation = `gt`
|
|
200
|
+
break
|
|
201
|
+
case `lte`:
|
|
202
|
+
operation = `gte`
|
|
203
|
+
break
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (fieldArg && valueArg) {
|
|
208
|
+
const fieldPath = (fieldArg as any).path
|
|
209
|
+
const fieldKey = fieldPath.join(`.`)
|
|
210
|
+
const value = (valueArg as any).value
|
|
211
|
+
|
|
212
|
+
if (!fieldOperations.has(fieldKey)) {
|
|
213
|
+
fieldOperations.set(fieldKey, [])
|
|
214
|
+
}
|
|
215
|
+
fieldOperations.get(fieldKey)!.push({ operation, value })
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Check if we have multiple operations on the same field
|
|
222
|
+
for (const [fieldKey, operations] of fieldOperations) {
|
|
223
|
+
if (operations.length >= 2) {
|
|
224
|
+
const fieldPath = fieldKey.split(`.`)
|
|
225
|
+
const index = findIndexForField(indexes, fieldPath)
|
|
226
|
+
|
|
227
|
+
if (index && index.supports(`gt`) && index.supports(`lt`)) {
|
|
228
|
+
// Build range query options
|
|
229
|
+
let from: any = undefined
|
|
230
|
+
let to: any = undefined
|
|
231
|
+
let fromInclusive = true
|
|
232
|
+
let toInclusive = true
|
|
233
|
+
|
|
234
|
+
for (const { operation, value } of operations) {
|
|
235
|
+
switch (operation) {
|
|
236
|
+
case `gt`:
|
|
237
|
+
if (from === undefined || value > from) {
|
|
238
|
+
from = value
|
|
239
|
+
fromInclusive = false
|
|
240
|
+
}
|
|
241
|
+
break
|
|
242
|
+
case `gte`:
|
|
243
|
+
if (from === undefined || value > from) {
|
|
244
|
+
from = value
|
|
245
|
+
fromInclusive = true
|
|
246
|
+
}
|
|
247
|
+
break
|
|
248
|
+
case `lt`:
|
|
249
|
+
if (to === undefined || value < to) {
|
|
250
|
+
to = value
|
|
251
|
+
toInclusive = false
|
|
252
|
+
}
|
|
253
|
+
break
|
|
254
|
+
case `lte`:
|
|
255
|
+
if (to === undefined || value < to) {
|
|
256
|
+
to = value
|
|
257
|
+
toInclusive = true
|
|
258
|
+
}
|
|
259
|
+
break
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const matchingKeys = (index as any).rangeQuery({
|
|
264
|
+
from,
|
|
265
|
+
to,
|
|
266
|
+
fromInclusive,
|
|
267
|
+
toInclusive,
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
return { canOptimize: true, matchingKeys }
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return { canOptimize: false, matchingKeys: new Set() }
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Optimizes simple comparison expressions (eq, gt, gte, lt, lte)
|
|
280
|
+
*/
|
|
281
|
+
function optimizeSimpleComparison<TKey extends string | number>(
|
|
282
|
+
expression: BasicExpression,
|
|
283
|
+
indexes: Map<number, BaseIndex<TKey>>
|
|
284
|
+
): OptimizationResult<TKey> {
|
|
285
|
+
if (expression.type !== `func` || expression.args.length !== 2) {
|
|
286
|
+
return { canOptimize: false, matchingKeys: new Set() }
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const leftArg = expression.args[0]!
|
|
290
|
+
const rightArg = expression.args[1]!
|
|
291
|
+
|
|
292
|
+
// Check both directions: field op value AND value op field
|
|
293
|
+
let fieldArg: BasicExpression | null = null
|
|
294
|
+
let valueArg: BasicExpression | null = null
|
|
295
|
+
let operation = expression.name as `eq` | `gt` | `gte` | `lt` | `lte`
|
|
296
|
+
|
|
297
|
+
if (leftArg.type === `ref` && rightArg.type === `val`) {
|
|
298
|
+
// field op value
|
|
299
|
+
fieldArg = leftArg
|
|
300
|
+
valueArg = rightArg
|
|
301
|
+
} else if (leftArg.type === `val` && rightArg.type === `ref`) {
|
|
302
|
+
// value op field - need to flip the operation
|
|
303
|
+
fieldArg = rightArg
|
|
304
|
+
valueArg = leftArg
|
|
305
|
+
|
|
306
|
+
// Flip the operation for reverse comparison
|
|
307
|
+
switch (operation) {
|
|
308
|
+
case `gt`:
|
|
309
|
+
operation = `lt`
|
|
310
|
+
break
|
|
311
|
+
case `gte`:
|
|
312
|
+
operation = `lte`
|
|
313
|
+
break
|
|
314
|
+
case `lt`:
|
|
315
|
+
operation = `gt`
|
|
316
|
+
break
|
|
317
|
+
case `lte`:
|
|
318
|
+
operation = `gte`
|
|
319
|
+
break
|
|
320
|
+
// eq stays the same
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (fieldArg && valueArg) {
|
|
325
|
+
const fieldPath = (fieldArg as any).path
|
|
326
|
+
const index = findIndexForField(indexes, fieldPath)
|
|
327
|
+
|
|
328
|
+
if (index) {
|
|
329
|
+
const queryValue = (valueArg as any).value
|
|
330
|
+
|
|
331
|
+
// Map operation to IndexOperation enum
|
|
332
|
+
const indexOperation = operation as IndexOperation
|
|
333
|
+
|
|
334
|
+
// Check if the index supports this operation
|
|
335
|
+
if (!index.supports(indexOperation)) {
|
|
336
|
+
return { canOptimize: false, matchingKeys: new Set() }
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const matchingKeys = index.lookup(indexOperation, queryValue)
|
|
340
|
+
return { canOptimize: true, matchingKeys }
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return { canOptimize: false, matchingKeys: new Set() }
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Checks if a simple comparison can be optimized
|
|
349
|
+
*/
|
|
350
|
+
function canOptimizeSimpleComparison<TKey extends string | number>(
|
|
351
|
+
expression: BasicExpression,
|
|
352
|
+
indexes: Map<number, BaseIndex<TKey>>
|
|
353
|
+
): boolean {
|
|
354
|
+
if (expression.type !== `func` || expression.args.length !== 2) {
|
|
355
|
+
return false
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const leftArg = expression.args[0]!
|
|
359
|
+
const rightArg = expression.args[1]!
|
|
360
|
+
|
|
361
|
+
// Check both directions: field op value AND value op field
|
|
362
|
+
let fieldPath: Array<string> | null = null
|
|
363
|
+
|
|
364
|
+
if (leftArg.type === `ref` && rightArg.type === `val`) {
|
|
365
|
+
fieldPath = (leftArg as any).path
|
|
366
|
+
} else if (leftArg.type === `val` && rightArg.type === `ref`) {
|
|
367
|
+
fieldPath = (rightArg as any).path
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (fieldPath) {
|
|
371
|
+
const index = findIndexForField(indexes, fieldPath)
|
|
372
|
+
return index !== undefined
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
return false
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Optimizes AND expressions
|
|
380
|
+
*/
|
|
381
|
+
function optimizeAndExpression<TKey extends string | number>(
|
|
382
|
+
expression: BasicExpression,
|
|
383
|
+
indexes: Map<number, BaseIndex<TKey>>
|
|
384
|
+
): OptimizationResult<TKey> {
|
|
385
|
+
if (expression.type !== `func` || expression.args.length < 2) {
|
|
386
|
+
return { canOptimize: false, matchingKeys: new Set() }
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// First, try to optimize compound range queries on the same field
|
|
390
|
+
const compoundRangeResult = optimizeCompoundRangeQuery(expression, indexes)
|
|
391
|
+
if (compoundRangeResult.canOptimize) {
|
|
392
|
+
return compoundRangeResult
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const results: Array<OptimizationResult<TKey>> = []
|
|
396
|
+
|
|
397
|
+
// Try to optimize each part, keep the optimizable ones
|
|
398
|
+
for (const arg of expression.args) {
|
|
399
|
+
const result = optimizeQueryRecursive(arg, indexes)
|
|
400
|
+
if (result.canOptimize) {
|
|
401
|
+
results.push(result)
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
if (results.length > 0) {
|
|
406
|
+
// Use intersectSets utility for AND logic
|
|
407
|
+
const allMatchingSets = results.map((r) => r.matchingKeys)
|
|
408
|
+
const intersectedKeys = intersectSets(allMatchingSets)
|
|
409
|
+
return { canOptimize: true, matchingKeys: intersectedKeys }
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
return { canOptimize: false, matchingKeys: new Set() }
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Checks if an AND expression can be optimized
|
|
417
|
+
*/
|
|
418
|
+
function canOptimizeAndExpression<TKey extends string | number>(
|
|
419
|
+
expression: BasicExpression,
|
|
420
|
+
indexes: Map<number, BaseIndex<TKey>>
|
|
421
|
+
): boolean {
|
|
422
|
+
if (expression.type !== `func` || expression.args.length < 2) {
|
|
423
|
+
return false
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// If any argument can be optimized, we can gain some speedup
|
|
427
|
+
return expression.args.some((arg) => canOptimizeExpression(arg, indexes))
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Optimizes OR expressions
|
|
432
|
+
*/
|
|
433
|
+
function optimizeOrExpression<TKey extends string | number>(
|
|
434
|
+
expression: BasicExpression,
|
|
435
|
+
indexes: Map<number, BaseIndex<TKey>>
|
|
436
|
+
): OptimizationResult<TKey> {
|
|
437
|
+
if (expression.type !== `func` || expression.args.length < 2) {
|
|
438
|
+
return { canOptimize: false, matchingKeys: new Set() }
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
const results: Array<OptimizationResult<TKey>> = []
|
|
442
|
+
|
|
443
|
+
// Try to optimize each part, keep the optimizable ones
|
|
444
|
+
for (const arg of expression.args) {
|
|
445
|
+
const result = optimizeQueryRecursive(arg, indexes)
|
|
446
|
+
if (result.canOptimize) {
|
|
447
|
+
results.push(result)
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
if (results.length > 0) {
|
|
452
|
+
// Use unionSets utility for OR logic
|
|
453
|
+
const allMatchingSets = results.map((r) => r.matchingKeys)
|
|
454
|
+
const unionedKeys = unionSets(allMatchingSets)
|
|
455
|
+
return { canOptimize: true, matchingKeys: unionedKeys }
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
return { canOptimize: false, matchingKeys: new Set() }
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Checks if an OR expression can be optimized
|
|
463
|
+
*/
|
|
464
|
+
function canOptimizeOrExpression<TKey extends string | number>(
|
|
465
|
+
expression: BasicExpression,
|
|
466
|
+
indexes: Map<number, BaseIndex<TKey>>
|
|
467
|
+
): boolean {
|
|
468
|
+
if (expression.type !== `func` || expression.args.length < 2) {
|
|
469
|
+
return false
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// If any argument can be optimized, we can gain some speedup
|
|
473
|
+
return expression.args.some((arg) => canOptimizeExpression(arg, indexes))
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Optimizes IN array expressions
|
|
478
|
+
*/
|
|
479
|
+
function optimizeInArrayExpression<TKey extends string | number>(
|
|
480
|
+
expression: BasicExpression,
|
|
481
|
+
indexes: Map<number, BaseIndex<TKey>>
|
|
482
|
+
): OptimizationResult<TKey> {
|
|
483
|
+
if (expression.type !== `func` || expression.args.length !== 2) {
|
|
484
|
+
return { canOptimize: false, matchingKeys: new Set() }
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
const fieldArg = expression.args[0]!
|
|
488
|
+
const arrayArg = expression.args[1]!
|
|
489
|
+
|
|
490
|
+
if (
|
|
491
|
+
fieldArg.type === `ref` &&
|
|
492
|
+
arrayArg.type === `val` &&
|
|
493
|
+
Array.isArray((arrayArg as any).value)
|
|
494
|
+
) {
|
|
495
|
+
const fieldPath = (fieldArg as any).path
|
|
496
|
+
const values = (arrayArg as any).value
|
|
497
|
+
const index = findIndexForField(indexes, fieldPath)
|
|
498
|
+
|
|
499
|
+
if (index) {
|
|
500
|
+
// Check if the index supports IN operation
|
|
501
|
+
if (index.supports(`in`)) {
|
|
502
|
+
const matchingKeys = index.lookup(`in`, values)
|
|
503
|
+
return { canOptimize: true, matchingKeys }
|
|
504
|
+
} else if (index.supports(`eq`)) {
|
|
505
|
+
// Fallback to multiple equality lookups
|
|
506
|
+
const matchingKeys = new Set<TKey>()
|
|
507
|
+
for (const value of values) {
|
|
508
|
+
const keysForValue = index.lookup(`eq`, value)
|
|
509
|
+
for (const key of keysForValue) {
|
|
510
|
+
matchingKeys.add(key)
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
return { canOptimize: true, matchingKeys }
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
return { canOptimize: false, matchingKeys: new Set() }
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* Checks if an IN array expression can be optimized
|
|
523
|
+
*/
|
|
524
|
+
function canOptimizeInArrayExpression<TKey extends string | number>(
|
|
525
|
+
expression: BasicExpression,
|
|
526
|
+
indexes: Map<number, BaseIndex<TKey>>
|
|
527
|
+
): boolean {
|
|
528
|
+
if (expression.type !== `func` || expression.args.length !== 2) {
|
|
529
|
+
return false
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
const fieldArg = expression.args[0]!
|
|
533
|
+
const arrayArg = expression.args[1]!
|
|
534
|
+
|
|
535
|
+
if (
|
|
536
|
+
fieldArg.type === `ref` &&
|
|
537
|
+
arrayArg.type === `val` &&
|
|
538
|
+
Array.isArray((arrayArg as any).value)
|
|
539
|
+
) {
|
|
540
|
+
const fieldPath = (fieldArg as any).path
|
|
541
|
+
const index = findIndexForField(indexes, fieldPath)
|
|
542
|
+
return index !== undefined
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
return false
|
|
546
|
+
}
|