@tanstack/db 0.0.27 → 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 +234 -86
- 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 +232 -84
- 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 +1 -1
- package/src/change-events.ts +257 -0
- package/src/collection.ts +318 -105
- 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
package/src/local-storage.ts
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
import {
|
|
2
|
+
InvalidStorageDataFormatError,
|
|
3
|
+
InvalidStorageObjectFormatError,
|
|
4
|
+
NoStorageAvailableError,
|
|
5
|
+
NoStorageEventApiError,
|
|
6
|
+
SerializationError,
|
|
7
|
+
StorageKeyRequiredError,
|
|
8
|
+
} from "./errors"
|
|
1
9
|
import type {
|
|
2
10
|
CollectionConfig,
|
|
3
11
|
DeleteMutationFnParams,
|
|
@@ -136,10 +144,9 @@ function validateJsonSerializable(value: any, operation: string): void {
|
|
|
136
144
|
try {
|
|
137
145
|
JSON.stringify(value)
|
|
138
146
|
} catch (error) {
|
|
139
|
-
throw new
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
}`
|
|
147
|
+
throw new SerializationError(
|
|
148
|
+
operation,
|
|
149
|
+
error instanceof Error ? error.message : String(error)
|
|
143
150
|
)
|
|
144
151
|
}
|
|
145
152
|
}
|
|
@@ -204,7 +211,7 @@ export function localStorageCollectionOptions<
|
|
|
204
211
|
|
|
205
212
|
// Validate required parameters
|
|
206
213
|
if (!config.storageKey) {
|
|
207
|
-
throw new
|
|
214
|
+
throw new StorageKeyRequiredError()
|
|
208
215
|
}
|
|
209
216
|
|
|
210
217
|
// Default to window.localStorage if no storage is provided
|
|
@@ -213,9 +220,7 @@ export function localStorageCollectionOptions<
|
|
|
213
220
|
(typeof window !== `undefined` ? window.localStorage : null)
|
|
214
221
|
|
|
215
222
|
if (!storage) {
|
|
216
|
-
throw new
|
|
217
|
-
`[LocalStorageCollection] No storage available. Please provide a storage option or ensure window.localStorage is available.`
|
|
218
|
-
)
|
|
223
|
+
throw new NoStorageAvailableError()
|
|
219
224
|
}
|
|
220
225
|
|
|
221
226
|
// Default to window for storage events if not provided
|
|
@@ -223,9 +228,7 @@ export function localStorageCollectionOptions<
|
|
|
223
228
|
config.storageEventApi || (typeof window !== `undefined` ? window : null)
|
|
224
229
|
|
|
225
230
|
if (!storageEventApi) {
|
|
226
|
-
throw new
|
|
227
|
-
`[LocalStorageCollection] No storage event API available. Please provide a storageEventApi option or ensure window is available.`
|
|
228
|
-
)
|
|
231
|
+
throw new NoStorageEventApiError()
|
|
229
232
|
}
|
|
230
233
|
|
|
231
234
|
// Track the last known state to detect changes
|
|
@@ -471,15 +474,11 @@ function loadFromStorage<T extends object>(
|
|
|
471
474
|
const storedItem = value as StoredItem<T>
|
|
472
475
|
dataMap.set(key, storedItem)
|
|
473
476
|
} else {
|
|
474
|
-
throw new
|
|
475
|
-
`[LocalStorageCollection] Invalid data format in storage key "${storageKey}" for key "${key}".`
|
|
476
|
-
)
|
|
477
|
+
throw new InvalidStorageDataFormatError(storageKey, key)
|
|
477
478
|
}
|
|
478
479
|
})
|
|
479
480
|
} else {
|
|
480
|
-
throw new
|
|
481
|
-
`[LocalStorageCollection] Invalid data format in storage key "${storageKey}". Expected object format.`
|
|
482
|
-
)
|
|
481
|
+
throw new InvalidStorageObjectFormatError(storageKey)
|
|
483
482
|
}
|
|
484
483
|
|
|
485
484
|
return dataMap
|
|
@@ -265,3 +265,17 @@ export function max(
|
|
|
265
265
|
): Aggregate<number> {
|
|
266
266
|
return new Aggregate(`max`, [toExpression(arg)])
|
|
267
267
|
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* List of comparison function names that can be used with indexes
|
|
271
|
+
*/
|
|
272
|
+
export const comparisonFunctions = [
|
|
273
|
+
`eq`,
|
|
274
|
+
`gt`,
|
|
275
|
+
`gte`,
|
|
276
|
+
`lt`,
|
|
277
|
+
`lte`,
|
|
278
|
+
`in`,
|
|
279
|
+
`like`,
|
|
280
|
+
`ilike`,
|
|
281
|
+
] as const
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { CollectionImpl } from "../../collection.js"
|
|
2
2
|
import { CollectionRef, QueryRef } from "../ir.js"
|
|
3
|
+
import {
|
|
4
|
+
InvalidSourceError,
|
|
5
|
+
JoinConditionMustBeEqualityError,
|
|
6
|
+
OnlyOneSourceAllowedError,
|
|
7
|
+
QueryMustHaveFromClauseError,
|
|
8
|
+
SubQueryMustHaveFromClauseError,
|
|
9
|
+
} from "../../errors.js"
|
|
3
10
|
import { createRefProxy, isRefProxy, toExpression } from "./ref-proxy.js"
|
|
4
11
|
import type { NamespacedRow } from "../../types.js"
|
|
5
12
|
import type {
|
|
@@ -45,7 +52,7 @@ export class BaseQueryBuilder<TContext extends Context = Context> {
|
|
|
45
52
|
context: string
|
|
46
53
|
): [string, CollectionRef | QueryRef] {
|
|
47
54
|
if (Object.keys(source).length !== 1) {
|
|
48
|
-
throw new
|
|
55
|
+
throw new OnlyOneSourceAllowedError(context)
|
|
49
56
|
}
|
|
50
57
|
|
|
51
58
|
const alias = Object.keys(source)[0]!
|
|
@@ -58,13 +65,11 @@ export class BaseQueryBuilder<TContext extends Context = Context> {
|
|
|
58
65
|
} else if (sourceValue instanceof BaseQueryBuilder) {
|
|
59
66
|
const subQuery = sourceValue._getQuery()
|
|
60
67
|
if (!(subQuery as Partial<QueryIR>).from) {
|
|
61
|
-
throw new
|
|
62
|
-
`A sub query passed to a ${context} must have a from clause itself`
|
|
63
|
-
)
|
|
68
|
+
throw new SubQueryMustHaveFromClauseError(context)
|
|
64
69
|
}
|
|
65
70
|
ref = new QueryRef(subQuery, alias)
|
|
66
71
|
} else {
|
|
67
|
-
throw new
|
|
72
|
+
throw new InvalidSourceError()
|
|
68
73
|
}
|
|
69
74
|
|
|
70
75
|
return [alias, ref]
|
|
@@ -166,7 +171,7 @@ export class BaseQueryBuilder<TContext extends Context = Context> {
|
|
|
166
171
|
left = onExpression.args[0]!
|
|
167
172
|
right = onExpression.args[1]!
|
|
168
173
|
} else {
|
|
169
|
-
throw new
|
|
174
|
+
throw new JoinConditionMustBeEqualityError()
|
|
170
175
|
}
|
|
171
176
|
|
|
172
177
|
const joinClause: JoinClause = {
|
|
@@ -725,7 +730,7 @@ export class BaseQueryBuilder<TContext extends Context = Context> {
|
|
|
725
730
|
|
|
726
731
|
_getQuery(): QueryIR {
|
|
727
732
|
if (!this.query.from) {
|
|
728
|
-
throw new
|
|
733
|
+
throw new QueryMustHaveFromClauseError()
|
|
729
734
|
}
|
|
730
735
|
return this.query as QueryIR
|
|
731
736
|
}
|
|
@@ -10,6 +10,71 @@ export interface RefProxy<T = any> {
|
|
|
10
10
|
readonly __type: T
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Type for creating a RefProxy for a single row/type without namespacing
|
|
15
|
+
* Used in collection indexes and where clauses
|
|
16
|
+
*/
|
|
17
|
+
export type SingleRowRefProxy<T> =
|
|
18
|
+
T extends Record<string, any>
|
|
19
|
+
? {
|
|
20
|
+
[K in keyof T]: T[K] extends Record<string, any>
|
|
21
|
+
? SingleRowRefProxy<T[K]> & RefProxy<T[K]>
|
|
22
|
+
: RefProxy<T[K]>
|
|
23
|
+
} & RefProxy<T>
|
|
24
|
+
: RefProxy<T>
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Creates a proxy object that records property access paths for a single row
|
|
28
|
+
* Used in collection indexes and where clauses
|
|
29
|
+
*/
|
|
30
|
+
export function createSingleRowRefProxy<
|
|
31
|
+
T extends Record<string, any>,
|
|
32
|
+
>(): SingleRowRefProxy<T> {
|
|
33
|
+
const cache = new Map<string, any>()
|
|
34
|
+
|
|
35
|
+
function createProxy(path: Array<string>): any {
|
|
36
|
+
const pathKey = path.join(`.`)
|
|
37
|
+
if (cache.has(pathKey)) {
|
|
38
|
+
return cache.get(pathKey)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const proxy = new Proxy({} as any, {
|
|
42
|
+
get(target, prop, receiver) {
|
|
43
|
+
if (prop === `__refProxy`) return true
|
|
44
|
+
if (prop === `__path`) return path
|
|
45
|
+
if (prop === `__type`) return undefined // Type is only for TypeScript inference
|
|
46
|
+
if (typeof prop === `symbol`) return Reflect.get(target, prop, receiver)
|
|
47
|
+
|
|
48
|
+
const newPath = [...path, String(prop)]
|
|
49
|
+
return createProxy(newPath)
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
has(target, prop) {
|
|
53
|
+
if (prop === `__refProxy` || prop === `__path` || prop === `__type`)
|
|
54
|
+
return true
|
|
55
|
+
return Reflect.has(target, prop)
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
ownKeys(target) {
|
|
59
|
+
return Reflect.ownKeys(target)
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
getOwnPropertyDescriptor(target, prop) {
|
|
63
|
+
if (prop === `__refProxy` || prop === `__path` || prop === `__type`) {
|
|
64
|
+
return { enumerable: false, configurable: true }
|
|
65
|
+
}
|
|
66
|
+
return Reflect.getOwnPropertyDescriptor(target, prop)
|
|
67
|
+
},
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
cache.set(pathKey, proxy)
|
|
71
|
+
return proxy
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Return the root proxy that starts with an empty path
|
|
75
|
+
return createProxy([]) as SingleRowRefProxy<T>
|
|
76
|
+
}
|
|
77
|
+
|
|
13
78
|
/**
|
|
14
79
|
* Creates a proxy object that records property access paths
|
|
15
80
|
* Used in callbacks like where, select, etc. to create type-safe references
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import {
|
|
2
|
+
EmptyReferencePathError,
|
|
3
|
+
UnknownExpressionTypeError,
|
|
4
|
+
UnknownFunctionError,
|
|
5
|
+
} from "../../errors.js"
|
|
1
6
|
import type { BasicExpression, Func, PropRef } from "../ir.js"
|
|
2
7
|
import type { NamespacedRow } from "../../types.js"
|
|
3
8
|
|
|
@@ -6,11 +11,37 @@ import type { NamespacedRow } from "../../types.js"
|
|
|
6
11
|
*/
|
|
7
12
|
export type CompiledExpression = (namespacedRow: NamespacedRow) => any
|
|
8
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Compiled single-row expression evaluator function type
|
|
16
|
+
*/
|
|
17
|
+
export type CompiledSingleRowExpression = (item: Record<string, unknown>) => any
|
|
18
|
+
|
|
9
19
|
/**
|
|
10
20
|
* Compiles an expression into an optimized evaluator function.
|
|
11
21
|
* This eliminates branching during evaluation by pre-compiling the expression structure.
|
|
12
22
|
*/
|
|
13
23
|
export function compileExpression(expr: BasicExpression): CompiledExpression {
|
|
24
|
+
const compiledFn = compileExpressionInternal(expr, false)
|
|
25
|
+
return compiledFn as CompiledExpression
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Compiles a single-row expression into an optimized evaluator function.
|
|
30
|
+
*/
|
|
31
|
+
export function compileSingleRowExpression(
|
|
32
|
+
expr: BasicExpression
|
|
33
|
+
): CompiledSingleRowExpression {
|
|
34
|
+
const compiledFn = compileExpressionInternal(expr, true)
|
|
35
|
+
return compiledFn as CompiledSingleRowExpression
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Internal unified expression compiler that handles both namespaced and single-row evaluation
|
|
40
|
+
*/
|
|
41
|
+
function compileExpressionInternal(
|
|
42
|
+
expr: BasicExpression,
|
|
43
|
+
isSingleRow: boolean
|
|
44
|
+
): (data: any) => any {
|
|
14
45
|
switch (expr.type) {
|
|
15
46
|
case `val`: {
|
|
16
47
|
// For constant values, return a function that just returns the value
|
|
@@ -19,17 +50,17 @@ export function compileExpression(expr: BasicExpression): CompiledExpression {
|
|
|
19
50
|
}
|
|
20
51
|
|
|
21
52
|
case `ref`: {
|
|
22
|
-
// For references,
|
|
23
|
-
return compileRef(expr)
|
|
53
|
+
// For references, compile based on evaluation mode
|
|
54
|
+
return isSingleRow ? compileSingleRowRef(expr) : compileRef(expr)
|
|
24
55
|
}
|
|
25
56
|
|
|
26
57
|
case `func`: {
|
|
27
|
-
// For functions,
|
|
28
|
-
return compileFunction(expr)
|
|
58
|
+
// For functions, use the unified compiler
|
|
59
|
+
return compileFunction(expr, isSingleRow)
|
|
29
60
|
}
|
|
30
61
|
|
|
31
62
|
default:
|
|
32
|
-
throw new
|
|
63
|
+
throw new UnknownExpressionTypeError((expr as any).type)
|
|
33
64
|
}
|
|
34
65
|
}
|
|
35
66
|
|
|
@@ -40,7 +71,7 @@ function compileRef(ref: PropRef): CompiledExpression {
|
|
|
40
71
|
const [tableAlias, ...propertyPath] = ref.path
|
|
41
72
|
|
|
42
73
|
if (!tableAlias) {
|
|
43
|
-
throw new
|
|
74
|
+
throw new EmptyReferencePathError()
|
|
44
75
|
}
|
|
45
76
|
|
|
46
77
|
// Pre-compile the property path navigation
|
|
@@ -75,74 +106,95 @@ function compileRef(ref: PropRef): CompiledExpression {
|
|
|
75
106
|
}
|
|
76
107
|
|
|
77
108
|
/**
|
|
78
|
-
* Compiles a
|
|
109
|
+
* Compiles a reference expression for single-row evaluation
|
|
110
|
+
*/
|
|
111
|
+
function compileSingleRowRef(ref: PropRef): CompiledSingleRowExpression {
|
|
112
|
+
const propertyPath = ref.path
|
|
113
|
+
|
|
114
|
+
// This function works for all path lengths including empty path
|
|
115
|
+
return (item) => {
|
|
116
|
+
let value: any = item
|
|
117
|
+
for (const prop of propertyPath) {
|
|
118
|
+
if (value == null) {
|
|
119
|
+
return value
|
|
120
|
+
}
|
|
121
|
+
value = value[prop]
|
|
122
|
+
}
|
|
123
|
+
return value
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Compiles a function expression for both namespaced and single-row evaluation
|
|
79
129
|
*/
|
|
80
|
-
function compileFunction(func: Func):
|
|
81
|
-
// Pre-compile all arguments
|
|
82
|
-
const compiledArgs = func.args.map(
|
|
130
|
+
function compileFunction(func: Func, isSingleRow: boolean): (data: any) => any {
|
|
131
|
+
// Pre-compile all arguments using the appropriate compiler
|
|
132
|
+
const compiledArgs = func.args.map((arg) =>
|
|
133
|
+
compileExpressionInternal(arg, isSingleRow)
|
|
134
|
+
)
|
|
83
135
|
|
|
84
136
|
switch (func.name) {
|
|
85
137
|
// Comparison operators
|
|
86
138
|
case `eq`: {
|
|
87
139
|
const argA = compiledArgs[0]!
|
|
88
140
|
const argB = compiledArgs[1]!
|
|
89
|
-
return (
|
|
90
|
-
const a = argA(
|
|
91
|
-
const b = argB(
|
|
141
|
+
return (data) => {
|
|
142
|
+
const a = argA(data)
|
|
143
|
+
const b = argB(data)
|
|
92
144
|
return a === b
|
|
93
145
|
}
|
|
94
146
|
}
|
|
95
147
|
case `gt`: {
|
|
96
148
|
const argA = compiledArgs[0]!
|
|
97
149
|
const argB = compiledArgs[1]!
|
|
98
|
-
return (
|
|
99
|
-
const a = argA(
|
|
100
|
-
const b = argB(
|
|
150
|
+
return (data) => {
|
|
151
|
+
const a = argA(data)
|
|
152
|
+
const b = argB(data)
|
|
101
153
|
return a > b
|
|
102
154
|
}
|
|
103
155
|
}
|
|
104
156
|
case `gte`: {
|
|
105
157
|
const argA = compiledArgs[0]!
|
|
106
158
|
const argB = compiledArgs[1]!
|
|
107
|
-
return (
|
|
108
|
-
const a = argA(
|
|
109
|
-
const b = argB(
|
|
159
|
+
return (data) => {
|
|
160
|
+
const a = argA(data)
|
|
161
|
+
const b = argB(data)
|
|
110
162
|
return a >= b
|
|
111
163
|
}
|
|
112
164
|
}
|
|
113
165
|
case `lt`: {
|
|
114
166
|
const argA = compiledArgs[0]!
|
|
115
167
|
const argB = compiledArgs[1]!
|
|
116
|
-
return (
|
|
117
|
-
const a = argA(
|
|
118
|
-
const b = argB(
|
|
168
|
+
return (data) => {
|
|
169
|
+
const a = argA(data)
|
|
170
|
+
const b = argB(data)
|
|
119
171
|
return a < b
|
|
120
172
|
}
|
|
121
173
|
}
|
|
122
174
|
case `lte`: {
|
|
123
175
|
const argA = compiledArgs[0]!
|
|
124
176
|
const argB = compiledArgs[1]!
|
|
125
|
-
return (
|
|
126
|
-
const a = argA(
|
|
127
|
-
const b = argB(
|
|
177
|
+
return (data) => {
|
|
178
|
+
const a = argA(data)
|
|
179
|
+
const b = argB(data)
|
|
128
180
|
return a <= b
|
|
129
181
|
}
|
|
130
182
|
}
|
|
131
183
|
|
|
132
184
|
// Boolean operators
|
|
133
185
|
case `and`:
|
|
134
|
-
return (
|
|
186
|
+
return (data) => {
|
|
135
187
|
for (const compiledArg of compiledArgs) {
|
|
136
|
-
if (!compiledArg(
|
|
188
|
+
if (!compiledArg(data)) {
|
|
137
189
|
return false
|
|
138
190
|
}
|
|
139
191
|
}
|
|
140
192
|
return true
|
|
141
193
|
}
|
|
142
194
|
case `or`:
|
|
143
|
-
return (
|
|
195
|
+
return (data) => {
|
|
144
196
|
for (const compiledArg of compiledArgs) {
|
|
145
|
-
if (compiledArg(
|
|
197
|
+
if (compiledArg(data)) {
|
|
146
198
|
return true
|
|
147
199
|
}
|
|
148
200
|
}
|
|
@@ -150,16 +202,16 @@ function compileFunction(func: Func): CompiledExpression {
|
|
|
150
202
|
}
|
|
151
203
|
case `not`: {
|
|
152
204
|
const arg = compiledArgs[0]!
|
|
153
|
-
return (
|
|
205
|
+
return (data) => !arg(data)
|
|
154
206
|
}
|
|
155
207
|
|
|
156
208
|
// Array operators
|
|
157
209
|
case `in`: {
|
|
158
210
|
const valueEvaluator = compiledArgs[0]!
|
|
159
211
|
const arrayEvaluator = compiledArgs[1]!
|
|
160
|
-
return (
|
|
161
|
-
const value = valueEvaluator(
|
|
162
|
-
const array = arrayEvaluator(
|
|
212
|
+
return (data) => {
|
|
213
|
+
const value = valueEvaluator(data)
|
|
214
|
+
const array = arrayEvaluator(data)
|
|
163
215
|
if (!Array.isArray(array)) {
|
|
164
216
|
return false
|
|
165
217
|
}
|
|
@@ -171,18 +223,18 @@ function compileFunction(func: Func): CompiledExpression {
|
|
|
171
223
|
case `like`: {
|
|
172
224
|
const valueEvaluator = compiledArgs[0]!
|
|
173
225
|
const patternEvaluator = compiledArgs[1]!
|
|
174
|
-
return (
|
|
175
|
-
const value = valueEvaluator(
|
|
176
|
-
const pattern = patternEvaluator(
|
|
226
|
+
return (data) => {
|
|
227
|
+
const value = valueEvaluator(data)
|
|
228
|
+
const pattern = patternEvaluator(data)
|
|
177
229
|
return evaluateLike(value, pattern, false)
|
|
178
230
|
}
|
|
179
231
|
}
|
|
180
232
|
case `ilike`: {
|
|
181
233
|
const valueEvaluator = compiledArgs[0]!
|
|
182
234
|
const patternEvaluator = compiledArgs[1]!
|
|
183
|
-
return (
|
|
184
|
-
const value = valueEvaluator(
|
|
185
|
-
const pattern = patternEvaluator(
|
|
235
|
+
return (data) => {
|
|
236
|
+
const value = valueEvaluator(data)
|
|
237
|
+
const pattern = patternEvaluator(data)
|
|
186
238
|
return evaluateLike(value, pattern, true)
|
|
187
239
|
}
|
|
188
240
|
}
|
|
@@ -190,22 +242,22 @@ function compileFunction(func: Func): CompiledExpression {
|
|
|
190
242
|
// String functions
|
|
191
243
|
case `upper`: {
|
|
192
244
|
const arg = compiledArgs[0]!
|
|
193
|
-
return (
|
|
194
|
-
const value = arg(
|
|
245
|
+
return (data) => {
|
|
246
|
+
const value = arg(data)
|
|
195
247
|
return typeof value === `string` ? value.toUpperCase() : value
|
|
196
248
|
}
|
|
197
249
|
}
|
|
198
250
|
case `lower`: {
|
|
199
251
|
const arg = compiledArgs[0]!
|
|
200
|
-
return (
|
|
201
|
-
const value = arg(
|
|
252
|
+
return (data) => {
|
|
253
|
+
const value = arg(data)
|
|
202
254
|
return typeof value === `string` ? value.toLowerCase() : value
|
|
203
255
|
}
|
|
204
256
|
}
|
|
205
257
|
case `length`: {
|
|
206
258
|
const arg = compiledArgs[0]!
|
|
207
|
-
return (
|
|
208
|
-
const value = arg(
|
|
259
|
+
return (data) => {
|
|
260
|
+
const value = arg(data)
|
|
209
261
|
if (typeof value === `string`) {
|
|
210
262
|
return value.length
|
|
211
263
|
}
|
|
@@ -216,10 +268,10 @@ function compileFunction(func: Func): CompiledExpression {
|
|
|
216
268
|
}
|
|
217
269
|
}
|
|
218
270
|
case `concat`:
|
|
219
|
-
return (
|
|
271
|
+
return (data) => {
|
|
220
272
|
return compiledArgs
|
|
221
273
|
.map((evaluator) => {
|
|
222
|
-
const arg = evaluator(
|
|
274
|
+
const arg = evaluator(data)
|
|
223
275
|
try {
|
|
224
276
|
return String(arg ?? ``)
|
|
225
277
|
} catch {
|
|
@@ -233,9 +285,9 @@ function compileFunction(func: Func): CompiledExpression {
|
|
|
233
285
|
.join(``)
|
|
234
286
|
}
|
|
235
287
|
case `coalesce`:
|
|
236
|
-
return (
|
|
288
|
+
return (data) => {
|
|
237
289
|
for (const evaluator of compiledArgs) {
|
|
238
|
-
const value = evaluator(
|
|
290
|
+
const value = evaluator(data)
|
|
239
291
|
if (value !== null && value !== undefined) {
|
|
240
292
|
return value
|
|
241
293
|
}
|
|
@@ -247,43 +299,43 @@ function compileFunction(func: Func): CompiledExpression {
|
|
|
247
299
|
case `add`: {
|
|
248
300
|
const argA = compiledArgs[0]!
|
|
249
301
|
const argB = compiledArgs[1]!
|
|
250
|
-
return (
|
|
251
|
-
const a = argA(
|
|
252
|
-
const b = argB(
|
|
302
|
+
return (data) => {
|
|
303
|
+
const a = argA(data)
|
|
304
|
+
const b = argB(data)
|
|
253
305
|
return (a ?? 0) + (b ?? 0)
|
|
254
306
|
}
|
|
255
307
|
}
|
|
256
308
|
case `subtract`: {
|
|
257
309
|
const argA = compiledArgs[0]!
|
|
258
310
|
const argB = compiledArgs[1]!
|
|
259
|
-
return (
|
|
260
|
-
const a = argA(
|
|
261
|
-
const b = argB(
|
|
311
|
+
return (data) => {
|
|
312
|
+
const a = argA(data)
|
|
313
|
+
const b = argB(data)
|
|
262
314
|
return (a ?? 0) - (b ?? 0)
|
|
263
315
|
}
|
|
264
316
|
}
|
|
265
317
|
case `multiply`: {
|
|
266
318
|
const argA = compiledArgs[0]!
|
|
267
319
|
const argB = compiledArgs[1]!
|
|
268
|
-
return (
|
|
269
|
-
const a = argA(
|
|
270
|
-
const b = argB(
|
|
320
|
+
return (data) => {
|
|
321
|
+
const a = argA(data)
|
|
322
|
+
const b = argB(data)
|
|
271
323
|
return (a ?? 0) * (b ?? 0)
|
|
272
324
|
}
|
|
273
325
|
}
|
|
274
326
|
case `divide`: {
|
|
275
327
|
const argA = compiledArgs[0]!
|
|
276
328
|
const argB = compiledArgs[1]!
|
|
277
|
-
return (
|
|
278
|
-
const a = argA(
|
|
279
|
-
const b = argB(
|
|
329
|
+
return (data) => {
|
|
330
|
+
const a = argA(data)
|
|
331
|
+
const b = argB(data)
|
|
280
332
|
const divisor = b ?? 0
|
|
281
333
|
return divisor !== 0 ? (a ?? 0) / divisor : null
|
|
282
334
|
}
|
|
283
335
|
}
|
|
284
336
|
|
|
285
337
|
default:
|
|
286
|
-
throw new
|
|
338
|
+
throw new UnknownFunctionError(func.name)
|
|
287
339
|
}
|
|
288
340
|
}
|
|
289
341
|
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { Func, PropRef, Value } from "../ir.js"
|
|
2
|
+
import type { BasicExpression } from "../ir.js"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Functions supported by the collection index system.
|
|
6
|
+
* These are the only functions that can be used in WHERE clauses
|
|
7
|
+
* that are pushed down to collection subscriptions for index optimization.
|
|
8
|
+
*/
|
|
9
|
+
export const SUPPORTED_COLLECTION_FUNCS = new Set([
|
|
10
|
+
`eq`,
|
|
11
|
+
`gt`,
|
|
12
|
+
`lt`,
|
|
13
|
+
`gte`,
|
|
14
|
+
`lte`,
|
|
15
|
+
`and`,
|
|
16
|
+
`or`,
|
|
17
|
+
`in`,
|
|
18
|
+
])
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Determines if a WHERE clause can be converted to collection-compatible BasicExpression format.
|
|
22
|
+
* This checks if the expression only uses functions supported by the collection index system.
|
|
23
|
+
*
|
|
24
|
+
* @param whereClause - The WHERE clause to check
|
|
25
|
+
* @returns True if the clause can be converted for collection index optimization
|
|
26
|
+
*/
|
|
27
|
+
export function isConvertibleToCollectionFilter(
|
|
28
|
+
whereClause: BasicExpression<boolean>
|
|
29
|
+
): boolean {
|
|
30
|
+
const tpe = whereClause.type
|
|
31
|
+
if (tpe === `func`) {
|
|
32
|
+
// Check if this function is supported
|
|
33
|
+
if (!SUPPORTED_COLLECTION_FUNCS.has(whereClause.name)) {
|
|
34
|
+
return false
|
|
35
|
+
}
|
|
36
|
+
// Recursively check all arguments
|
|
37
|
+
return whereClause.args.every((arg) =>
|
|
38
|
+
isConvertibleToCollectionFilter(arg as BasicExpression<boolean>)
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
return [`val`, `ref`].includes(tpe)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Converts a WHERE clause to BasicExpression format compatible with collection indexes.
|
|
46
|
+
* This function creates proper BasicExpression class instances that the collection
|
|
47
|
+
* index system can understand.
|
|
48
|
+
*
|
|
49
|
+
* @param whereClause - The WHERE clause to convert
|
|
50
|
+
* @param collectionAlias - The alias of the collection being filtered
|
|
51
|
+
* @returns The converted BasicExpression or null if conversion fails
|
|
52
|
+
*/
|
|
53
|
+
export function convertToBasicExpression(
|
|
54
|
+
whereClause: BasicExpression<boolean>,
|
|
55
|
+
collectionAlias: string
|
|
56
|
+
): BasicExpression<boolean> | null {
|
|
57
|
+
const tpe = whereClause.type
|
|
58
|
+
if (tpe === `val`) {
|
|
59
|
+
return new Value(whereClause.value)
|
|
60
|
+
} else if (tpe === `ref`) {
|
|
61
|
+
const path = whereClause.path
|
|
62
|
+
if (Array.isArray(path)) {
|
|
63
|
+
if (path[0] === collectionAlias && path.length > 1) {
|
|
64
|
+
// Remove the table alias from the path for single-collection queries
|
|
65
|
+
return new PropRef(path.slice(1))
|
|
66
|
+
} else if (path.length === 1 && path[0] !== undefined) {
|
|
67
|
+
// Single field reference
|
|
68
|
+
return new PropRef([path[0]])
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// Fallback for non-array paths
|
|
72
|
+
return new PropRef(Array.isArray(path) ? path : [String(path)])
|
|
73
|
+
} else {
|
|
74
|
+
// Check if this function is supported
|
|
75
|
+
if (!SUPPORTED_COLLECTION_FUNCS.has(whereClause.name)) {
|
|
76
|
+
return null
|
|
77
|
+
}
|
|
78
|
+
// Recursively convert all arguments
|
|
79
|
+
const args: Array<BasicExpression> = []
|
|
80
|
+
for (const arg of whereClause.args) {
|
|
81
|
+
const convertedArg = convertToBasicExpression(
|
|
82
|
+
arg as BasicExpression<boolean>,
|
|
83
|
+
collectionAlias
|
|
84
|
+
)
|
|
85
|
+
if (convertedArg == null) {
|
|
86
|
+
return null
|
|
87
|
+
}
|
|
88
|
+
args.push(convertedArg)
|
|
89
|
+
}
|
|
90
|
+
return new Func(whereClause.name, args)
|
|
91
|
+
}
|
|
92
|
+
}
|