@tanstack/db 0.4.19 → 0.5.0
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/collection/change-events.cjs +10 -12
- package/dist/cjs/collection/change-events.cjs.map +1 -1
- package/dist/cjs/collection/change-events.d.cts +1 -8
- package/dist/cjs/collection/index.cjs +19 -1
- package/dist/cjs/collection/index.cjs.map +1 -1
- package/dist/cjs/collection/index.d.cts +7 -5
- package/dist/cjs/collection/sync.cjs +7 -1
- package/dist/cjs/collection/sync.cjs.map +1 -1
- package/dist/cjs/errors.cjs +9 -4
- package/dist/cjs/errors.cjs.map +1 -1
- package/dist/cjs/errors.d.cts +4 -1
- package/dist/cjs/index.cjs +21 -3
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +2 -0
- package/dist/cjs/indexes/auto-index.cjs +7 -3
- package/dist/cjs/indexes/auto-index.cjs.map +1 -1
- package/dist/cjs/local-storage.cjs.map +1 -1
- package/dist/cjs/local-storage.d.cts +2 -2
- package/dist/cjs/query/builder/functions.cjs +34 -0
- package/dist/cjs/query/builder/functions.cjs.map +1 -1
- package/dist/cjs/query/builder/functions.d.cts +5 -0
- package/dist/cjs/query/builder/index.cjs +2 -2
- package/dist/cjs/query/builder/index.cjs.map +1 -1
- package/dist/cjs/query/builder/types.d.cts +18 -24
- package/dist/cjs/query/compiler/evaluators.cjs +57 -4
- package/dist/cjs/query/compiler/evaluators.cjs.map +1 -1
- package/dist/cjs/query/compiler/evaluators.d.cts +13 -0
- package/dist/cjs/query/compiler/expressions.cjs +4 -1
- package/dist/cjs/query/compiler/expressions.cjs.map +1 -1
- package/dist/cjs/query/compiler/group-by.cjs +3 -3
- package/dist/cjs/query/compiler/group-by.cjs.map +1 -1
- package/dist/cjs/query/compiler/index.cjs +2 -2
- package/dist/cjs/query/compiler/index.cjs.map +1 -1
- package/dist/cjs/query/compiler/order-by.cjs +18 -6
- package/dist/cjs/query/compiler/order-by.cjs.map +1 -1
- package/dist/cjs/query/compiler/order-by.d.cts +7 -1
- package/dist/cjs/query/expression-helpers.cjs +217 -0
- package/dist/cjs/query/expression-helpers.cjs.map +1 -0
- package/dist/cjs/query/expression-helpers.d.cts +216 -0
- package/dist/cjs/query/index.d.cts +2 -0
- package/dist/cjs/query/live/collection-config-builder.cjs +34 -2
- package/dist/cjs/query/live/collection-config-builder.cjs.map +1 -1
- package/dist/cjs/query/live/collection-config-builder.d.cts +7 -1
- package/dist/cjs/query/live/collection-registry.cjs +2 -1
- package/dist/cjs/query/live/collection-registry.cjs.map +1 -1
- package/dist/cjs/query/live/collection-registry.d.cts +1 -1
- package/dist/cjs/query/live/internal.cjs +5 -0
- package/dist/cjs/query/live/internal.cjs.map +1 -0
- package/dist/cjs/query/live/internal.d.cts +13 -0
- package/dist/cjs/query/live/types.d.cts +6 -1
- package/dist/cjs/query/predicate-utils.cjs +816 -0
- package/dist/cjs/query/predicate-utils.cjs.map +1 -0
- package/dist/cjs/query/predicate-utils.d.cts +116 -0
- package/dist/cjs/query/subset-dedupe.cjs +111 -0
- package/dist/cjs/query/subset-dedupe.cjs.map +1 -0
- package/dist/cjs/query/subset-dedupe.d.cts +66 -0
- package/dist/cjs/types.d.cts +31 -2
- package/dist/cjs/utils/comparison.cjs +30 -0
- package/dist/cjs/utils/comparison.cjs.map +1 -1
- package/dist/cjs/utils/comparison.d.cts +7 -1
- package/dist/cjs/utils/index-optimization.cjs +26 -22
- package/dist/cjs/utils/index-optimization.cjs.map +1 -1
- package/dist/cjs/utils/index-optimization.d.cts +5 -4
- package/dist/esm/collection/change-events.d.ts +1 -8
- package/dist/esm/collection/change-events.js +7 -9
- package/dist/esm/collection/change-events.js.map +1 -1
- package/dist/esm/collection/index.d.ts +7 -5
- package/dist/esm/collection/index.js +19 -1
- package/dist/esm/collection/index.js.map +1 -1
- package/dist/esm/collection/sync.js +7 -1
- package/dist/esm/collection/sync.js.map +1 -1
- package/dist/esm/errors.d.ts +4 -1
- package/dist/esm/errors.js +9 -4
- package/dist/esm/errors.js.map +1 -1
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.js +19 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/indexes/auto-index.js +7 -3
- package/dist/esm/indexes/auto-index.js.map +1 -1
- package/dist/esm/local-storage.d.ts +2 -2
- package/dist/esm/local-storage.js.map +1 -1
- package/dist/esm/query/builder/functions.d.ts +5 -0
- package/dist/esm/query/builder/functions.js +34 -0
- package/dist/esm/query/builder/functions.js.map +1 -1
- package/dist/esm/query/builder/index.js +2 -2
- package/dist/esm/query/builder/index.js.map +1 -1
- package/dist/esm/query/builder/types.d.ts +18 -24
- package/dist/esm/query/compiler/evaluators.d.ts +13 -0
- package/dist/esm/query/compiler/evaluators.js +59 -6
- package/dist/esm/query/compiler/evaluators.js.map +1 -1
- package/dist/esm/query/compiler/expressions.js +4 -1
- package/dist/esm/query/compiler/expressions.js.map +1 -1
- package/dist/esm/query/compiler/group-by.js +4 -4
- package/dist/esm/query/compiler/group-by.js.map +1 -1
- package/dist/esm/query/compiler/index.js +3 -3
- package/dist/esm/query/compiler/index.js.map +1 -1
- package/dist/esm/query/compiler/order-by.d.ts +7 -1
- package/dist/esm/query/compiler/order-by.js +18 -6
- package/dist/esm/query/compiler/order-by.js.map +1 -1
- package/dist/esm/query/expression-helpers.d.ts +216 -0
- package/dist/esm/query/expression-helpers.js +217 -0
- package/dist/esm/query/expression-helpers.js.map +1 -0
- package/dist/esm/query/index.d.ts +2 -0
- package/dist/esm/query/live/collection-config-builder.d.ts +7 -1
- package/dist/esm/query/live/collection-config-builder.js +34 -2
- package/dist/esm/query/live/collection-config-builder.js.map +1 -1
- package/dist/esm/query/live/collection-registry.d.ts +1 -1
- package/dist/esm/query/live/collection-registry.js +2 -1
- package/dist/esm/query/live/collection-registry.js.map +1 -1
- package/dist/esm/query/live/internal.d.ts +13 -0
- package/dist/esm/query/live/internal.js +5 -0
- package/dist/esm/query/live/internal.js.map +1 -0
- package/dist/esm/query/live/types.d.ts +6 -1
- package/dist/esm/query/predicate-utils.d.ts +116 -0
- package/dist/esm/query/predicate-utils.js +816 -0
- package/dist/esm/query/predicate-utils.js.map +1 -0
- package/dist/esm/query/subset-dedupe.d.ts +66 -0
- package/dist/esm/query/subset-dedupe.js +111 -0
- package/dist/esm/query/subset-dedupe.js.map +1 -0
- package/dist/esm/types.d.ts +31 -2
- package/dist/esm/utils/comparison.d.ts +7 -1
- package/dist/esm/utils/comparison.js +30 -0
- package/dist/esm/utils/comparison.js.map +1 -1
- package/dist/esm/utils/index-optimization.d.ts +5 -4
- package/dist/esm/utils/index-optimization.js +26 -22
- package/dist/esm/utils/index-optimization.js.map +1 -1
- package/package.json +2 -2
- package/src/collection/change-events.ts +14 -24
- package/src/collection/index.ts +34 -6
- package/src/collection/sync.ts +9 -1
- package/src/errors.ts +20 -4
- package/src/index.ts +4 -0
- package/src/indexes/auto-index.ts +8 -4
- package/src/local-storage.ts +11 -3
- package/src/query/builder/functions.ts +39 -0
- package/src/query/builder/index.ts +2 -2
- package/src/query/builder/types.ts +19 -27
- package/src/query/compiler/evaluators.ts +103 -5
- package/src/query/compiler/expressions.ts +3 -0
- package/src/query/compiler/group-by.ts +4 -4
- package/src/query/compiler/index.ts +3 -3
- package/src/query/compiler/order-by.ts +33 -7
- package/src/query/expression-helpers.ts +522 -0
- package/src/query/index.ts +12 -0
- package/src/query/live/collection-config-builder.ts +54 -2
- package/src/query/live/collection-registry.ts +3 -2
- package/src/query/live/internal.ts +15 -0
- package/src/query/live/types.ts +11 -1
- package/src/query/predicate-utils.ts +1415 -0
- package/src/query/subset-dedupe.ts +243 -0
- package/src/types.ts +41 -2
- package/src/utils/comparison.ts +70 -1
- package/src/utils/index-optimization.ts +77 -63
|
@@ -469,11 +469,11 @@ export class BaseQueryBuilder<TContext extends Context = Context> {
|
|
|
469
469
|
|
|
470
470
|
const opts: CompareOptions =
|
|
471
471
|
typeof options === `string`
|
|
472
|
-
? { direction: options, nulls: `first
|
|
472
|
+
? { direction: options, nulls: `first` }
|
|
473
473
|
: {
|
|
474
474
|
direction: options.direction ?? `asc`,
|
|
475
475
|
nulls: options.nulls ?? `first`,
|
|
476
|
-
stringSort: options.stringSort
|
|
476
|
+
stringSort: options.stringSort,
|
|
477
477
|
locale:
|
|
478
478
|
options.stringSort === `locale` ? options.locale : undefined,
|
|
479
479
|
localeOptions:
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { CollectionImpl } from "../../collection/index.js"
|
|
2
|
-
import type { SingleResult } from "../../types.js"
|
|
2
|
+
import type { SingleResult, StringCollationConfig } from "../../types.js"
|
|
3
3
|
import type {
|
|
4
4
|
Aggregate,
|
|
5
5
|
BasicExpression,
|
|
@@ -303,26 +303,7 @@ export type OrderByCallback<TContext extends Context> = (
|
|
|
303
303
|
export type OrderByOptions = {
|
|
304
304
|
direction?: OrderByDirection
|
|
305
305
|
nulls?: `first` | `last`
|
|
306
|
-
} &
|
|
307
|
-
|
|
308
|
-
/**
|
|
309
|
-
* StringSortOpts - Options for string sorting behavior
|
|
310
|
-
*
|
|
311
|
-
* This discriminated union allows for two types of string sorting:
|
|
312
|
-
* - **Lexical**: Simple character-by-character comparison (default)
|
|
313
|
-
* - **Locale**: Locale-aware sorting with optional customization
|
|
314
|
-
*
|
|
315
|
-
* The union ensures that locale options are only available when locale sorting is selected.
|
|
316
|
-
*/
|
|
317
|
-
export type StringSortOpts =
|
|
318
|
-
| {
|
|
319
|
-
stringSort?: `lexical`
|
|
320
|
-
}
|
|
321
|
-
| {
|
|
322
|
-
stringSort?: `locale`
|
|
323
|
-
locale?: string
|
|
324
|
-
localeOptions?: object
|
|
325
|
-
}
|
|
306
|
+
} & StringCollationConfig
|
|
326
307
|
|
|
327
308
|
/**
|
|
328
309
|
* CompareOptions - Final resolved options for comparison operations
|
|
@@ -331,12 +312,9 @@ export type StringSortOpts =
|
|
|
331
312
|
* to their concrete values. Unlike OrderByOptions, all fields are required
|
|
332
313
|
* since defaults have been applied.
|
|
333
314
|
*/
|
|
334
|
-
export type CompareOptions = {
|
|
315
|
+
export type CompareOptions = StringCollationConfig & {
|
|
335
316
|
direction: OrderByDirection
|
|
336
317
|
nulls: `first` | `last`
|
|
337
|
-
stringSort: `lexical` | `locale`
|
|
338
|
-
locale?: string
|
|
339
|
-
localeOptions?: object
|
|
340
318
|
}
|
|
341
319
|
|
|
342
320
|
/**
|
|
@@ -530,6 +508,20 @@ export type RefLeaf<T = any> = { readonly [RefBrand]?: T }
|
|
|
530
508
|
type WithoutRefBrand<T> =
|
|
531
509
|
T extends Record<string, any> ? Omit<T, typeof RefBrand> : T
|
|
532
510
|
|
|
511
|
+
/**
|
|
512
|
+
* PreserveSingleResultFlag - Conditionally includes the singleResult flag
|
|
513
|
+
*
|
|
514
|
+
* This helper type ensures the singleResult flag is only added to the context when it's
|
|
515
|
+
* explicitly true. It uses a non-distributive conditional (tuple wrapper) to prevent
|
|
516
|
+
* unexpected behavior when TFlag is a union type.
|
|
517
|
+
*
|
|
518
|
+
* @template TFlag - The singleResult flag value to check
|
|
519
|
+
* @returns { singleResult: true } if TFlag is true, otherwise {}
|
|
520
|
+
*/
|
|
521
|
+
type PreserveSingleResultFlag<TFlag> = [TFlag] extends [true]
|
|
522
|
+
? { singleResult: true }
|
|
523
|
+
: {}
|
|
524
|
+
|
|
533
525
|
/**
|
|
534
526
|
* MergeContextWithJoinType - Creates a new context after a join operation
|
|
535
527
|
*
|
|
@@ -551,6 +543,7 @@ type WithoutRefBrand<T> =
|
|
|
551
543
|
* - `hasJoins`: Set to true
|
|
552
544
|
* - `joinTypes`: Updated to track this join type
|
|
553
545
|
* - `result`: Preserved from previous operations
|
|
546
|
+
* - `singleResult`: Preserved only if already true (via PreserveSingleResultFlag)
|
|
554
547
|
*/
|
|
555
548
|
export type MergeContextWithJoinType<
|
|
556
549
|
TContext extends Context,
|
|
@@ -574,8 +567,7 @@ export type MergeContextWithJoinType<
|
|
|
574
567
|
[K in keyof TNewSchema & string]: TJoinType
|
|
575
568
|
}
|
|
576
569
|
result: TContext[`result`]
|
|
577
|
-
|
|
578
|
-
}
|
|
570
|
+
} & PreserveSingleResultFlag<TContext[`singleResult`]>
|
|
579
571
|
|
|
580
572
|
/**
|
|
581
573
|
* ApplyJoinOptionalityToMergedSchema - Applies optionality rules when merging schemas
|
|
@@ -3,10 +3,33 @@ import {
|
|
|
3
3
|
UnknownExpressionTypeError,
|
|
4
4
|
UnknownFunctionError,
|
|
5
5
|
} from "../../errors.js"
|
|
6
|
-
import { normalizeValue } from "../../utils/comparison.js"
|
|
6
|
+
import { areValuesEqual, normalizeValue } from "../../utils/comparison.js"
|
|
7
7
|
import type { BasicExpression, Func, PropRef } from "../ir.js"
|
|
8
8
|
import type { NamespacedRow } from "../../types.js"
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Helper function to check if a value is null or undefined (represents UNKNOWN in 3-valued logic)
|
|
12
|
+
*/
|
|
13
|
+
function isUnknown(value: any): boolean {
|
|
14
|
+
return value === null || value === undefined
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Converts a 3-valued logic result to a boolean for use in WHERE/HAVING filters.
|
|
19
|
+
* In SQL, UNKNOWN (null) values in WHERE clauses exclude rows, matching false behavior.
|
|
20
|
+
*
|
|
21
|
+
* @param result - The 3-valued logic result: true, false, or null (UNKNOWN)
|
|
22
|
+
* @returns true only if result is explicitly true, false otherwise
|
|
23
|
+
*
|
|
24
|
+
* Truth table:
|
|
25
|
+
* - true → true (include row)
|
|
26
|
+
* - false → false (exclude row)
|
|
27
|
+
* - null (UNKNOWN) → false (exclude row, matching SQL behavior)
|
|
28
|
+
*/
|
|
29
|
+
export function toBooleanPredicate(result: boolean | null): boolean {
|
|
30
|
+
return result === true
|
|
31
|
+
}
|
|
32
|
+
|
|
10
33
|
/**
|
|
11
34
|
* Compiled expression evaluator function type
|
|
12
35
|
*/
|
|
@@ -145,7 +168,12 @@ function compileFunction(func: Func, isSingleRow: boolean): (data: any) => any {
|
|
|
145
168
|
return (data) => {
|
|
146
169
|
const a = normalizeValue(argA(data))
|
|
147
170
|
const b = normalizeValue(argB(data))
|
|
148
|
-
|
|
171
|
+
// In 3-valued logic, any comparison with null/undefined returns UNKNOWN
|
|
172
|
+
if (isUnknown(a) || isUnknown(b)) {
|
|
173
|
+
return null
|
|
174
|
+
}
|
|
175
|
+
// Use areValuesEqual for proper Uint8Array/Buffer comparison
|
|
176
|
+
return areValuesEqual(a, b)
|
|
149
177
|
}
|
|
150
178
|
}
|
|
151
179
|
case `gt`: {
|
|
@@ -154,6 +182,10 @@ function compileFunction(func: Func, isSingleRow: boolean): (data: any) => any {
|
|
|
154
182
|
return (data) => {
|
|
155
183
|
const a = argA(data)
|
|
156
184
|
const b = argB(data)
|
|
185
|
+
// In 3-valued logic, any comparison with null/undefined returns UNKNOWN
|
|
186
|
+
if (isUnknown(a) || isUnknown(b)) {
|
|
187
|
+
return null
|
|
188
|
+
}
|
|
157
189
|
return a > b
|
|
158
190
|
}
|
|
159
191
|
}
|
|
@@ -163,6 +195,10 @@ function compileFunction(func: Func, isSingleRow: boolean): (data: any) => any {
|
|
|
163
195
|
return (data) => {
|
|
164
196
|
const a = argA(data)
|
|
165
197
|
const b = argB(data)
|
|
198
|
+
// In 3-valued logic, any comparison with null/undefined returns UNKNOWN
|
|
199
|
+
if (isUnknown(a) || isUnknown(b)) {
|
|
200
|
+
return null
|
|
201
|
+
}
|
|
166
202
|
return a >= b
|
|
167
203
|
}
|
|
168
204
|
}
|
|
@@ -172,6 +208,10 @@ function compileFunction(func: Func, isSingleRow: boolean): (data: any) => any {
|
|
|
172
208
|
return (data) => {
|
|
173
209
|
const a = argA(data)
|
|
174
210
|
const b = argB(data)
|
|
211
|
+
// In 3-valued logic, any comparison with null/undefined returns UNKNOWN
|
|
212
|
+
if (isUnknown(a) || isUnknown(b)) {
|
|
213
|
+
return null
|
|
214
|
+
}
|
|
175
215
|
return a < b
|
|
176
216
|
}
|
|
177
217
|
}
|
|
@@ -181,6 +221,10 @@ function compileFunction(func: Func, isSingleRow: boolean): (data: any) => any {
|
|
|
181
221
|
return (data) => {
|
|
182
222
|
const a = argA(data)
|
|
183
223
|
const b = argB(data)
|
|
224
|
+
// In 3-valued logic, any comparison with null/undefined returns UNKNOWN
|
|
225
|
+
if (isUnknown(a) || isUnknown(b)) {
|
|
226
|
+
return null
|
|
227
|
+
}
|
|
184
228
|
return a <= b
|
|
185
229
|
}
|
|
186
230
|
}
|
|
@@ -188,25 +232,67 @@ function compileFunction(func: Func, isSingleRow: boolean): (data: any) => any {
|
|
|
188
232
|
// Boolean operators
|
|
189
233
|
case `and`:
|
|
190
234
|
return (data) => {
|
|
235
|
+
// 3-valued logic for AND:
|
|
236
|
+
// - false AND anything = false (short-circuit)
|
|
237
|
+
// - null AND false = false
|
|
238
|
+
// - null AND anything (except false) = null
|
|
239
|
+
// - anything (except false) AND null = null
|
|
240
|
+
// - true AND true = true
|
|
241
|
+
let hasUnknown = false
|
|
191
242
|
for (const compiledArg of compiledArgs) {
|
|
192
|
-
|
|
243
|
+
const result = compiledArg(data)
|
|
244
|
+
if (result === false) {
|
|
193
245
|
return false
|
|
194
246
|
}
|
|
247
|
+
if (isUnknown(result)) {
|
|
248
|
+
hasUnknown = true
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
// If we got here, no operand was false
|
|
252
|
+
// If any operand was null, return null (UNKNOWN)
|
|
253
|
+
if (hasUnknown) {
|
|
254
|
+
return null
|
|
195
255
|
}
|
|
256
|
+
|
|
196
257
|
return true
|
|
197
258
|
}
|
|
198
259
|
case `or`:
|
|
199
260
|
return (data) => {
|
|
261
|
+
// 3-valued logic for OR:
|
|
262
|
+
// - true OR anything = true (short-circuit)
|
|
263
|
+
// - null OR anything (except true) = null
|
|
264
|
+
// - false OR false = false
|
|
265
|
+
let hasUnknown = false
|
|
200
266
|
for (const compiledArg of compiledArgs) {
|
|
201
|
-
|
|
267
|
+
const result = compiledArg(data)
|
|
268
|
+
if (result === true) {
|
|
202
269
|
return true
|
|
203
270
|
}
|
|
271
|
+
if (isUnknown(result)) {
|
|
272
|
+
hasUnknown = true
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
// If we got here, no operand was true
|
|
276
|
+
// If any operand was null, return null (UNKNOWN)
|
|
277
|
+
if (hasUnknown) {
|
|
278
|
+
return null
|
|
204
279
|
}
|
|
280
|
+
|
|
205
281
|
return false
|
|
206
282
|
}
|
|
207
283
|
case `not`: {
|
|
208
284
|
const arg = compiledArgs[0]!
|
|
209
|
-
return (data) =>
|
|
285
|
+
return (data) => {
|
|
286
|
+
// 3-valued logic for NOT:
|
|
287
|
+
// - NOT null = null
|
|
288
|
+
// - NOT true = false
|
|
289
|
+
// - NOT false = true
|
|
290
|
+
const result = arg(data)
|
|
291
|
+
if (isUnknown(result)) {
|
|
292
|
+
return null
|
|
293
|
+
}
|
|
294
|
+
return !result
|
|
295
|
+
}
|
|
210
296
|
}
|
|
211
297
|
|
|
212
298
|
// Array operators
|
|
@@ -216,6 +302,10 @@ function compileFunction(func: Func, isSingleRow: boolean): (data: any) => any {
|
|
|
216
302
|
return (data) => {
|
|
217
303
|
const value = valueEvaluator(data)
|
|
218
304
|
const array = arrayEvaluator(data)
|
|
305
|
+
// In 3-valued logic, if the value is null/undefined, return UNKNOWN
|
|
306
|
+
if (isUnknown(value)) {
|
|
307
|
+
return null
|
|
308
|
+
}
|
|
219
309
|
if (!Array.isArray(array)) {
|
|
220
310
|
return false
|
|
221
311
|
}
|
|
@@ -230,6 +320,10 @@ function compileFunction(func: Func, isSingleRow: boolean): (data: any) => any {
|
|
|
230
320
|
return (data) => {
|
|
231
321
|
const value = valueEvaluator(data)
|
|
232
322
|
const pattern = patternEvaluator(data)
|
|
323
|
+
// In 3-valued logic, if value or pattern is null/undefined, return UNKNOWN
|
|
324
|
+
if (isUnknown(value) || isUnknown(pattern)) {
|
|
325
|
+
return null
|
|
326
|
+
}
|
|
233
327
|
return evaluateLike(value, pattern, false)
|
|
234
328
|
}
|
|
235
329
|
}
|
|
@@ -239,6 +333,10 @@ function compileFunction(func: Func, isSingleRow: boolean): (data: any) => any {
|
|
|
239
333
|
return (data) => {
|
|
240
334
|
const value = valueEvaluator(data)
|
|
241
335
|
const pattern = patternEvaluator(data)
|
|
336
|
+
// In 3-valued logic, if value or pattern is null/undefined, return UNKNOWN
|
|
337
|
+
if (isUnknown(value) || isUnknown(pattern)) {
|
|
338
|
+
return null
|
|
339
|
+
}
|
|
242
340
|
return evaluateLike(value, pattern, true)
|
|
243
341
|
}
|
|
244
342
|
}
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
UnknownHavingExpressionTypeError,
|
|
7
7
|
UnsupportedAggregateFunctionError,
|
|
8
8
|
} from "../../errors.js"
|
|
9
|
-
import { compileExpression } from "./evaluators.js"
|
|
9
|
+
import { compileExpression, toBooleanPredicate } from "./evaluators.js"
|
|
10
10
|
import type {
|
|
11
11
|
Aggregate,
|
|
12
12
|
BasicExpression,
|
|
@@ -140,7 +140,7 @@ export function processGroupBy(
|
|
|
140
140
|
filter(([, row]) => {
|
|
141
141
|
// Create a namespaced row structure for HAVING evaluation
|
|
142
142
|
const namespacedRow = { result: (row as any).__select_results }
|
|
143
|
-
return compiledHaving(namespacedRow)
|
|
143
|
+
return toBooleanPredicate(compiledHaving(namespacedRow))
|
|
144
144
|
})
|
|
145
145
|
)
|
|
146
146
|
}
|
|
@@ -153,7 +153,7 @@ export function processGroupBy(
|
|
|
153
153
|
filter(([, row]) => {
|
|
154
154
|
// Create a namespaced row structure for functional HAVING evaluation
|
|
155
155
|
const namespacedRow = { result: (row as any).__select_results }
|
|
156
|
-
return fnHaving(namespacedRow)
|
|
156
|
+
return toBooleanPredicate(fnHaving(namespacedRow))
|
|
157
157
|
})
|
|
158
158
|
)
|
|
159
159
|
}
|
|
@@ -288,7 +288,7 @@ export function processGroupBy(
|
|
|
288
288
|
filter(([, row]) => {
|
|
289
289
|
// Create a namespaced row structure for functional HAVING evaluation
|
|
290
290
|
const namespacedRow = { result: (row as any).__select_results }
|
|
291
|
-
return fnHaving(namespacedRow)
|
|
291
|
+
return toBooleanPredicate(fnHaving(namespacedRow))
|
|
292
292
|
})
|
|
293
293
|
)
|
|
294
294
|
}
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
UnsupportedFromTypeError,
|
|
10
10
|
} from "../../errors.js"
|
|
11
11
|
import { PropRef, Value as ValClass, getWhereExpression } from "../ir.js"
|
|
12
|
-
import { compileExpression } from "./evaluators.js"
|
|
12
|
+
import { compileExpression, toBooleanPredicate } from "./evaluators.js"
|
|
13
13
|
import { processJoins } from "./joins.js"
|
|
14
14
|
import { processGroupBy } from "./group-by.js"
|
|
15
15
|
import { processOrderBy } from "./order-by.js"
|
|
@@ -195,7 +195,7 @@ export function compileQuery(
|
|
|
195
195
|
const compiledWhere = compileExpression(whereExpression)
|
|
196
196
|
pipeline = pipeline.pipe(
|
|
197
197
|
filter(([_key, namespacedRow]) => {
|
|
198
|
-
return compiledWhere(namespacedRow)
|
|
198
|
+
return toBooleanPredicate(compiledWhere(namespacedRow))
|
|
199
199
|
})
|
|
200
200
|
)
|
|
201
201
|
}
|
|
@@ -206,7 +206,7 @@ export function compileQuery(
|
|
|
206
206
|
for (const fnWhere of query.fnWhere) {
|
|
207
207
|
pipeline = pipeline.pipe(
|
|
208
208
|
filter(([_key, namespacedRow]) => {
|
|
209
|
-
return fnWhere(namespacedRow)
|
|
209
|
+
return toBooleanPredicate(fnWhere(namespacedRow))
|
|
210
210
|
})
|
|
211
211
|
)
|
|
212
212
|
}
|
|
@@ -5,10 +5,15 @@ import { ensureIndexForField } from "../../indexes/auto-index.js"
|
|
|
5
5
|
import { findIndexForField } from "../../utils/index-optimization.js"
|
|
6
6
|
import { compileExpression } from "./evaluators.js"
|
|
7
7
|
import { replaceAggregatesByRefs } from "./group-by.js"
|
|
8
|
+
import type { CompareOptions } from "../builder/types.js"
|
|
8
9
|
import type { WindowOptions } from "./types.js"
|
|
9
10
|
import type { CompiledSingleRowExpression } from "./evaluators.js"
|
|
10
11
|
import type { OrderBy, OrderByClause, QueryIR, Select } from "../ir.js"
|
|
11
|
-
import type {
|
|
12
|
+
import type {
|
|
13
|
+
CollectionLike,
|
|
14
|
+
NamespacedAndKeyedStream,
|
|
15
|
+
NamespacedRow,
|
|
16
|
+
} from "../../types.js"
|
|
12
17
|
import type { IStreamBuilder, KeyValue } from "@tanstack/db-ivm"
|
|
13
18
|
import type { IndexInterface } from "../../indexes/base-index.js"
|
|
14
19
|
import type { Collection } from "../../collection/index.js"
|
|
@@ -50,9 +55,10 @@ export function processOrderBy(
|
|
|
50
55
|
selectClause,
|
|
51
56
|
`__select_results`
|
|
52
57
|
)
|
|
58
|
+
|
|
53
59
|
return {
|
|
54
60
|
compiledExpression: compileExpression(clauseWithoutAggregates),
|
|
55
|
-
compareOptions: clause
|
|
61
|
+
compareOptions: buildCompareOptions(clause, collection),
|
|
56
62
|
}
|
|
57
63
|
})
|
|
58
64
|
|
|
@@ -87,7 +93,7 @@ export function processOrderBy(
|
|
|
87
93
|
const arrayA = a as Array<unknown>
|
|
88
94
|
const arrayB = b as Array<unknown>
|
|
89
95
|
for (let i = 0; i < orderByClause.length; i++) {
|
|
90
|
-
const clause =
|
|
96
|
+
const clause = compiledOrderBy[i]!
|
|
91
97
|
const compareFn = makeComparator(clause.compareOptions)
|
|
92
98
|
const result = compareFn(arrayA[i], arrayB[i])
|
|
93
99
|
if (result !== 0) {
|
|
@@ -99,7 +105,7 @@ export function processOrderBy(
|
|
|
99
105
|
|
|
100
106
|
// Single property comparison
|
|
101
107
|
if (orderByClause.length === 1) {
|
|
102
|
-
const clause =
|
|
108
|
+
const clause = compiledOrderBy[0]!
|
|
103
109
|
const compareFn = makeComparator(clause.compareOptions)
|
|
104
110
|
return compareFn(a, b)
|
|
105
111
|
}
|
|
@@ -127,12 +133,13 @@ export function processOrderBy(
|
|
|
127
133
|
|
|
128
134
|
const followRefCollection = followRefResult.collection
|
|
129
135
|
const fieldName = followRefResult.path[0]
|
|
136
|
+
const compareOpts = buildCompareOptions(clause, followRefCollection)
|
|
130
137
|
if (fieldName) {
|
|
131
138
|
ensureIndexForField(
|
|
132
139
|
fieldName,
|
|
133
140
|
followRefResult.path,
|
|
134
141
|
followRefCollection,
|
|
135
|
-
|
|
142
|
+
compareOpts,
|
|
136
143
|
compare
|
|
137
144
|
)
|
|
138
145
|
}
|
|
@@ -153,9 +160,9 @@ export function processOrderBy(
|
|
|
153
160
|
|
|
154
161
|
const index: IndexInterface<string | number> | undefined =
|
|
155
162
|
findIndexForField(
|
|
156
|
-
followRefCollection
|
|
163
|
+
followRefCollection,
|
|
157
164
|
followRefResult.path,
|
|
158
|
-
|
|
165
|
+
compareOpts
|
|
159
166
|
)
|
|
160
167
|
|
|
161
168
|
if (index && index.supports(`gt`)) {
|
|
@@ -217,3 +224,22 @@ export function processOrderBy(
|
|
|
217
224
|
// orderByWithFractionalIndex returns [key, [value, index]] - we keep this format
|
|
218
225
|
)
|
|
219
226
|
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Builds a comparison configuration object that uses the values provided in the orderBy clause.
|
|
230
|
+
* If no string sort configuration is provided it defaults to the collection's string sort configuration.
|
|
231
|
+
*/
|
|
232
|
+
export function buildCompareOptions(
|
|
233
|
+
clause: OrderByClause,
|
|
234
|
+
collection: CollectionLike<any, any>
|
|
235
|
+
): CompareOptions {
|
|
236
|
+
if (clause.compareOptions.stringSort !== undefined) {
|
|
237
|
+
return clause.compareOptions
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return {
|
|
241
|
+
...collection.compareOptions,
|
|
242
|
+
direction: clause.compareOptions.direction,
|
|
243
|
+
nulls: clause.compareOptions.nulls,
|
|
244
|
+
}
|
|
245
|
+
}
|