@tanstack/db 0.4.2 → 0.4.4
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 +1 -1
- package/dist/cjs/collection/change-events.cjs.map +1 -1
- package/dist/cjs/collection/changes.cjs +7 -3
- package/dist/cjs/collection/changes.cjs.map +1 -1
- package/dist/cjs/collection/events.cjs +3 -6
- package/dist/cjs/collection/events.cjs.map +1 -1
- package/dist/cjs/collection/index.cjs +4 -1
- package/dist/cjs/collection/index.cjs.map +1 -1
- package/dist/cjs/collection/index.d.cts +16 -4
- package/dist/cjs/collection/mutations.cjs +13 -20
- package/dist/cjs/collection/mutations.cjs.map +1 -1
- package/dist/cjs/collection/state.cjs +9 -2
- package/dist/cjs/collection/state.cjs.map +1 -1
- package/dist/cjs/collection/subscription.cjs +4 -5
- package/dist/cjs/collection/subscription.cjs.map +1 -1
- package/dist/cjs/collection/subscription.d.cts +2 -2
- package/dist/cjs/collection/sync.cjs +10 -2
- package/dist/cjs/collection/sync.cjs.map +1 -1
- package/dist/cjs/indexes/auto-index.cjs +4 -3
- package/dist/cjs/indexes/auto-index.cjs.map +1 -1
- package/dist/cjs/indexes/auto-index.d.cts +2 -1
- package/dist/cjs/indexes/base-index.cjs +26 -0
- package/dist/cjs/indexes/base-index.cjs.map +1 -1
- package/dist/cjs/indexes/base-index.d.cts +47 -2
- package/dist/cjs/indexes/btree-index.cjs +45 -9
- package/dist/cjs/indexes/btree-index.cjs.map +1 -1
- package/dist/cjs/indexes/btree-index.d.cts +15 -0
- package/dist/cjs/indexes/lazy-index.cjs +3 -6
- package/dist/cjs/indexes/lazy-index.cjs.map +1 -1
- package/dist/cjs/indexes/reverse-index.cjs +78 -0
- package/dist/cjs/indexes/reverse-index.cjs.map +1 -0
- package/dist/cjs/indexes/reverse-index.d.cts +30 -0
- package/dist/cjs/proxy.cjs +1 -1
- package/dist/cjs/proxy.cjs.map +1 -1
- package/dist/cjs/query/builder/index.cjs +21 -0
- package/dist/cjs/query/builder/index.cjs.map +1 -1
- package/dist/cjs/query/builder/index.d.cts +16 -1
- package/dist/cjs/query/builder/types.d.cts +7 -0
- package/dist/cjs/query/compiler/evaluators.cjs +1 -1
- package/dist/cjs/query/compiler/evaluators.cjs.map +1 -1
- package/dist/cjs/query/compiler/group-by.cjs +2 -10
- package/dist/cjs/query/compiler/group-by.cjs.map +1 -1
- package/dist/cjs/query/compiler/index.cjs +2 -39
- package/dist/cjs/query/compiler/index.cjs.map +1 -1
- package/dist/cjs/query/compiler/index.d.cts +1 -0
- package/dist/cjs/query/compiler/joins.cjs +15 -14
- package/dist/cjs/query/compiler/joins.cjs.map +1 -1
- package/dist/cjs/query/compiler/joins.d.cts +2 -1
- package/dist/cjs/query/compiler/order-by.cjs +8 -10
- package/dist/cjs/query/compiler/order-by.cjs.map +1 -1
- package/dist/cjs/query/compiler/order-by.d.cts +2 -2
- package/dist/cjs/query/compiler/select.cjs +1 -1
- package/dist/cjs/query/compiler/select.cjs.map +1 -1
- package/dist/cjs/query/index.d.cts +1 -1
- package/dist/cjs/query/ir.cjs +38 -0
- package/dist/cjs/query/ir.cjs.map +1 -1
- package/dist/cjs/query/ir.d.cts +11 -1
- package/dist/cjs/query/live/collection-config-builder.cjs +3 -2
- package/dist/cjs/query/live/collection-config-builder.cjs.map +1 -1
- package/dist/cjs/query/live/collection-config-builder.d.cts +2 -2
- package/dist/cjs/query/live/collection-subscriber.cjs +2 -3
- package/dist/cjs/query/live/collection-subscriber.cjs.map +1 -1
- package/dist/cjs/query/live/types.d.cts +4 -0
- package/dist/cjs/query/live-query-collection.cjs.map +1 -1
- package/dist/cjs/query/live-query-collection.d.cts +7 -4
- package/dist/cjs/query/optimizer.cjs +2 -4
- package/dist/cjs/query/optimizer.cjs.map +1 -1
- package/dist/cjs/transactions.cjs +2 -3
- package/dist/cjs/transactions.cjs.map +1 -1
- package/dist/cjs/types.d.cts +13 -0
- package/dist/cjs/utils/btree.cjs +1 -1
- package/dist/cjs/utils/btree.cjs.map +1 -1
- package/dist/cjs/utils/index-optimization.cjs +7 -2
- package/dist/cjs/utils/index-optimization.cjs.map +1 -1
- package/dist/cjs/utils/index-optimization.d.cts +3 -2
- package/dist/cjs/utils.cjs +6 -0
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/cjs/utils.d.cts +2 -3
- package/dist/esm/collection/change-events.js +1 -1
- package/dist/esm/collection/change-events.js.map +1 -1
- package/dist/esm/collection/changes.js +7 -3
- package/dist/esm/collection/changes.js.map +1 -1
- package/dist/esm/collection/events.js +3 -6
- package/dist/esm/collection/events.js.map +1 -1
- package/dist/esm/collection/index.d.ts +16 -4
- package/dist/esm/collection/index.js +4 -1
- package/dist/esm/collection/index.js.map +1 -1
- package/dist/esm/collection/mutations.js +13 -20
- package/dist/esm/collection/mutations.js.map +1 -1
- package/dist/esm/collection/state.js +9 -2
- package/dist/esm/collection/state.js.map +1 -1
- package/dist/esm/collection/subscription.d.ts +2 -2
- package/dist/esm/collection/subscription.js +4 -5
- package/dist/esm/collection/subscription.js.map +1 -1
- package/dist/esm/collection/sync.js +10 -2
- package/dist/esm/collection/sync.js.map +1 -1
- package/dist/esm/indexes/auto-index.d.ts +2 -1
- package/dist/esm/indexes/auto-index.js +4 -3
- package/dist/esm/indexes/auto-index.js.map +1 -1
- package/dist/esm/indexes/base-index.d.ts +47 -2
- package/dist/esm/indexes/base-index.js +26 -0
- package/dist/esm/indexes/base-index.js.map +1 -1
- package/dist/esm/indexes/btree-index.d.ts +15 -0
- package/dist/esm/indexes/btree-index.js +45 -9
- package/dist/esm/indexes/btree-index.js.map +1 -1
- package/dist/esm/indexes/lazy-index.js +3 -6
- package/dist/esm/indexes/lazy-index.js.map +1 -1
- package/dist/esm/indexes/reverse-index.d.ts +30 -0
- package/dist/esm/indexes/reverse-index.js +78 -0
- package/dist/esm/indexes/reverse-index.js.map +1 -0
- package/dist/esm/proxy.js +1 -1
- package/dist/esm/proxy.js.map +1 -1
- package/dist/esm/query/builder/index.d.ts +16 -1
- package/dist/esm/query/builder/index.js +21 -0
- package/dist/esm/query/builder/index.js.map +1 -1
- package/dist/esm/query/builder/types.d.ts +7 -0
- package/dist/esm/query/compiler/evaluators.js +1 -1
- package/dist/esm/query/compiler/evaluators.js.map +1 -1
- package/dist/esm/query/compiler/group-by.js +3 -11
- package/dist/esm/query/compiler/group-by.js.map +1 -1
- package/dist/esm/query/compiler/index.d.ts +1 -0
- package/dist/esm/query/compiler/index.js +4 -41
- package/dist/esm/query/compiler/index.js.map +1 -1
- package/dist/esm/query/compiler/joins.d.ts +2 -1
- package/dist/esm/query/compiler/joins.js +15 -14
- package/dist/esm/query/compiler/joins.js.map +1 -1
- package/dist/esm/query/compiler/order-by.d.ts +2 -2
- package/dist/esm/query/compiler/order-by.js +5 -7
- package/dist/esm/query/compiler/order-by.js.map +1 -1
- package/dist/esm/query/compiler/select.js +1 -1
- package/dist/esm/query/compiler/select.js.map +1 -1
- package/dist/esm/query/index.d.ts +1 -1
- package/dist/esm/query/ir.d.ts +11 -1
- package/dist/esm/query/ir.js +38 -0
- package/dist/esm/query/ir.js.map +1 -1
- package/dist/esm/query/live/collection-config-builder.d.ts +2 -2
- package/dist/esm/query/live/collection-config-builder.js +3 -2
- package/dist/esm/query/live/collection-config-builder.js.map +1 -1
- package/dist/esm/query/live/collection-subscriber.js +2 -3
- package/dist/esm/query/live/collection-subscriber.js.map +1 -1
- package/dist/esm/query/live/types.d.ts +4 -0
- package/dist/esm/query/live-query-collection.d.ts +7 -4
- package/dist/esm/query/live-query-collection.js.map +1 -1
- package/dist/esm/query/optimizer.js +2 -4
- package/dist/esm/query/optimizer.js.map +1 -1
- package/dist/esm/transactions.js +2 -3
- package/dist/esm/transactions.js.map +1 -1
- package/dist/esm/types.d.ts +13 -0
- package/dist/esm/utils/btree.js +1 -1
- package/dist/esm/utils/btree.js.map +1 -1
- package/dist/esm/utils/index-optimization.d.ts +3 -2
- package/dist/esm/utils/index-optimization.js +7 -2
- package/dist/esm/utils/index-optimization.js.map +1 -1
- package/dist/esm/utils.d.ts +2 -3
- package/dist/esm/utils.js +6 -0
- package/dist/esm/utils.js.map +1 -1
- package/package.json +1 -1
- package/src/collection/changes.ts +10 -4
- package/src/collection/index.ts +38 -5
- package/src/collection/state.ts +22 -5
- package/src/collection/subscription.ts +4 -4
- package/src/collection/sync.ts +17 -2
- package/src/indexes/auto-index.ts +8 -3
- package/src/indexes/base-index.ts +94 -4
- package/src/indexes/btree-index.ts +58 -7
- package/src/indexes/reverse-index.ts +120 -0
- package/src/query/builder/index.ts +30 -2
- package/src/query/builder/types.ts +12 -0
- package/src/query/compiler/group-by.ts +1 -10
- package/src/query/compiler/index.ts +4 -1
- package/src/query/compiler/joins.ts +13 -8
- package/src/query/compiler/order-by.ts +16 -20
- package/src/query/index.ts +1 -0
- package/src/query/ir.ts +68 -1
- package/src/query/live/collection-config-builder.ts +3 -2
- package/src/query/live/types.ts +5 -0
- package/src/query/live-query-collection.ts +34 -8
- package/src/types.ts +22 -0
- package/src/utils/index-optimization.ts +19 -5
- package/src/utils.ts +8 -0
package/src/collection/state.ts
CHANGED
|
@@ -217,7 +217,11 @@ export class CollectionStateManager<
|
|
|
217
217
|
triggeredByUserAction: boolean = false
|
|
218
218
|
): void {
|
|
219
219
|
// Skip redundant recalculations when we're in the middle of committing sync transactions
|
|
220
|
-
|
|
220
|
+
// While the sync pipeline is replaying a large batch we still want to honour
|
|
221
|
+
// fresh optimistic mutations from the UI. Only skip recompute for the
|
|
222
|
+
// internal sync-driven redraws; user-triggered work (triggeredByUserAction)
|
|
223
|
+
// must run so live queries stay responsive during long commits.
|
|
224
|
+
if (this.isCommittingSyncTransactions && !triggeredByUserAction) {
|
|
221
225
|
return
|
|
222
226
|
}
|
|
223
227
|
|
|
@@ -708,10 +712,23 @@ export class CollectionStateManager<
|
|
|
708
712
|
|
|
709
713
|
// Check if this sync operation is redundant with a completed optimistic operation
|
|
710
714
|
const completedOp = completedOptimisticOps.get(key)
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
+
let isRedundantSync = false
|
|
716
|
+
|
|
717
|
+
if (completedOp) {
|
|
718
|
+
if (
|
|
719
|
+
completedOp.type === `delete` &&
|
|
720
|
+
previousVisibleValue !== undefined &&
|
|
721
|
+
newVisibleValue === undefined &&
|
|
722
|
+
deepEquals(completedOp.value, previousVisibleValue)
|
|
723
|
+
) {
|
|
724
|
+
isRedundantSync = true
|
|
725
|
+
} else if (
|
|
726
|
+
newVisibleValue !== undefined &&
|
|
727
|
+
deepEquals(completedOp.value, newVisibleValue)
|
|
728
|
+
) {
|
|
729
|
+
isRedundantSync = true
|
|
730
|
+
}
|
|
731
|
+
}
|
|
715
732
|
|
|
716
733
|
if (!isRedundantSync) {
|
|
717
734
|
if (
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { ensureIndexForExpression } from "../indexes/auto-index.js"
|
|
2
|
-
import { and } from "../query/
|
|
2
|
+
import { and } from "../query/builder/functions.js"
|
|
3
3
|
import {
|
|
4
4
|
createFilterFunctionFromExpression,
|
|
5
5
|
createFilteredCallback,
|
|
6
6
|
} from "./change-events.js"
|
|
7
7
|
import type { BasicExpression } from "../query/ir.js"
|
|
8
|
-
import type {
|
|
8
|
+
import type { IndexInterface } from "../indexes/base-index.js"
|
|
9
9
|
import type { ChangeMessage } from "../types.js"
|
|
10
10
|
import type { CollectionImpl } from "./index.js"
|
|
11
11
|
|
|
@@ -38,7 +38,7 @@ export class CollectionSubscription {
|
|
|
38
38
|
|
|
39
39
|
private filteredCallback: (changes: Array<ChangeMessage<any, any>>) => void
|
|
40
40
|
|
|
41
|
-
private orderByIndex:
|
|
41
|
+
private orderByIndex: IndexInterface<string | number> | undefined
|
|
42
42
|
|
|
43
43
|
constructor(
|
|
44
44
|
private collection: CollectionImpl<any, any, any, any, any>,
|
|
@@ -65,7 +65,7 @@ export class CollectionSubscription {
|
|
|
65
65
|
: this.callback
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
setOrderByIndex(index:
|
|
68
|
+
setOrderByIndex(index: IndexInterface<any>) {
|
|
69
69
|
this.orderByIndex = index
|
|
70
70
|
}
|
|
71
71
|
|
package/src/collection/sync.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
SyncTransactionAlreadyCommittedError,
|
|
8
8
|
SyncTransactionAlreadyCommittedWriteError,
|
|
9
9
|
} from "../errors"
|
|
10
|
+
import { deepEquals } from "../utils"
|
|
10
11
|
import type { StandardSchemaV1 } from "@standard-schema/spec"
|
|
11
12
|
import type { ChangeMessage, CollectionConfig } from "../types"
|
|
12
13
|
import type { CollectionImpl } from "./index.js"
|
|
@@ -84,6 +85,8 @@ export class CollectionSyncManager<
|
|
|
84
85
|
}
|
|
85
86
|
const key = this.config.getKey(messageWithoutKey.value)
|
|
86
87
|
|
|
88
|
+
let messageType = messageWithoutKey.type
|
|
89
|
+
|
|
87
90
|
// Check if an item with this key already exists when inserting
|
|
88
91
|
if (messageWithoutKey.type === `insert`) {
|
|
89
92
|
const insertingIntoExistingSynced = state.syncedData.has(key)
|
|
@@ -96,17 +99,29 @@ export class CollectionSyncManager<
|
|
|
96
99
|
!hasPendingDeleteForKey &&
|
|
97
100
|
!isTruncateTransaction
|
|
98
101
|
) {
|
|
99
|
-
|
|
102
|
+
const existingValue = state.syncedData.get(key)
|
|
103
|
+
if (
|
|
104
|
+
existingValue !== undefined &&
|
|
105
|
+
deepEquals(existingValue, messageWithoutKey.value)
|
|
106
|
+
) {
|
|
107
|
+
// The "insert" is an echo of a value we already have locally.
|
|
108
|
+
// Treat it as an update so we preserve optimistic intent without
|
|
109
|
+
// throwing a duplicate-key error during reconciliation.
|
|
110
|
+
messageType = `update`
|
|
111
|
+
} else {
|
|
112
|
+
throw new DuplicateKeySyncError(key, this.id)
|
|
113
|
+
}
|
|
100
114
|
}
|
|
101
115
|
}
|
|
102
116
|
|
|
103
117
|
const message: ChangeMessage<TOutput> = {
|
|
104
118
|
...messageWithoutKey,
|
|
119
|
+
type: messageType,
|
|
105
120
|
key,
|
|
106
121
|
}
|
|
107
122
|
pendingTransaction.operations.push(message)
|
|
108
123
|
|
|
109
|
-
if (
|
|
124
|
+
if (messageType === `delete`) {
|
|
110
125
|
pendingTransaction.deletedKeys.add(key)
|
|
111
126
|
}
|
|
112
127
|
},
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { DEFAULT_COMPARE_OPTIONS } from "../utils"
|
|
1
2
|
import { BTreeIndex } from "./btree-index"
|
|
3
|
+
import type { CompareOptions } from "../query/builder/types"
|
|
2
4
|
import type { BasicExpression } from "../query/ir"
|
|
3
5
|
import type { CollectionImpl } from "../collection/index.js"
|
|
4
6
|
|
|
@@ -30,6 +32,7 @@ export function ensureIndexForField<
|
|
|
30
32
|
fieldName: string,
|
|
31
33
|
fieldPath: Array<string>,
|
|
32
34
|
collection: CollectionImpl<T, TKey, any, any, any>,
|
|
35
|
+
compareOptions: CompareOptions = DEFAULT_COMPARE_OPTIONS,
|
|
33
36
|
compareFn?: (a: any, b: any) => number
|
|
34
37
|
) {
|
|
35
38
|
if (!shouldAutoIndex(collection)) {
|
|
@@ -37,8 +40,10 @@ export function ensureIndexForField<
|
|
|
37
40
|
}
|
|
38
41
|
|
|
39
42
|
// Check if we already have an index for this field
|
|
40
|
-
const existingIndex = Array.from(collection.indexes.values()).find(
|
|
41
|
-
index
|
|
43
|
+
const existingIndex = Array.from(collection.indexes.values()).find(
|
|
44
|
+
(index) =>
|
|
45
|
+
index.matchesField(fieldPath) &&
|
|
46
|
+
index.matchesCompareOptions(compareOptions)
|
|
42
47
|
)
|
|
43
48
|
|
|
44
49
|
if (existingIndex) {
|
|
@@ -50,7 +55,7 @@ export function ensureIndexForField<
|
|
|
50
55
|
collection.createIndex((row) => (row as any)[fieldName], {
|
|
51
56
|
name: `auto_${fieldName}`,
|
|
52
57
|
indexType: BTreeIndex,
|
|
53
|
-
options: compareFn ? { compareFn } : {},
|
|
58
|
+
options: compareFn ? { compareFn, compareOptions } : {},
|
|
54
59
|
})
|
|
55
60
|
} catch (error) {
|
|
56
61
|
console.warn(`Failed to create auto-index for field "${fieldName}":`, error)
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { compileSingleRowExpression } from "../query/compiler/evaluators.js"
|
|
2
2
|
import { comparisonFunctions } from "../query/builder/functions.js"
|
|
3
|
-
import
|
|
3
|
+
import { DEFAULT_COMPARE_OPTIONS, deepEquals } from "../utils.js"
|
|
4
|
+
import type { RangeQueryOptions } from "./btree-index.js"
|
|
5
|
+
import type { CompareOptions } from "../query/builder/types.js"
|
|
6
|
+
import type { BasicExpression, OrderByDirection } from "../query/ir.js"
|
|
4
7
|
|
|
5
8
|
/**
|
|
6
9
|
* Operations that indexes can support, imported from available comparison functions
|
|
@@ -22,12 +25,57 @@ export interface IndexStats {
|
|
|
22
25
|
readonly lastUpdated: Date
|
|
23
26
|
}
|
|
24
27
|
|
|
28
|
+
export interface IndexInterface<
|
|
29
|
+
TKey extends string | number = string | number,
|
|
30
|
+
> {
|
|
31
|
+
add: (key: TKey, item: any) => void
|
|
32
|
+
remove: (key: TKey, item: any) => void
|
|
33
|
+
update: (key: TKey, oldItem: any, newItem: any) => void
|
|
34
|
+
|
|
35
|
+
build: (entries: Iterable<[TKey, any]>) => void
|
|
36
|
+
clear: () => void
|
|
37
|
+
|
|
38
|
+
lookup: (operation: IndexOperation, value: any) => Set<TKey>
|
|
39
|
+
|
|
40
|
+
equalityLookup: (value: any) => Set<TKey>
|
|
41
|
+
inArrayLookup: (values: Array<any>) => Set<TKey>
|
|
42
|
+
|
|
43
|
+
rangeQuery: (options: RangeQueryOptions) => Set<TKey>
|
|
44
|
+
rangeQueryReversed: (options: RangeQueryOptions) => Set<TKey>
|
|
45
|
+
|
|
46
|
+
take: (
|
|
47
|
+
n: number,
|
|
48
|
+
from?: TKey,
|
|
49
|
+
filterFn?: (key: TKey) => boolean
|
|
50
|
+
) => Array<TKey>
|
|
51
|
+
takeReversed: (
|
|
52
|
+
n: number,
|
|
53
|
+
from?: TKey,
|
|
54
|
+
filterFn?: (key: TKey) => boolean
|
|
55
|
+
) => Array<TKey>
|
|
56
|
+
|
|
57
|
+
get keyCount(): number
|
|
58
|
+
get orderedEntriesArray(): Array<[any, Set<TKey>]>
|
|
59
|
+
get orderedEntriesArrayReversed(): Array<[any, Set<TKey>]>
|
|
60
|
+
|
|
61
|
+
get indexedKeysSet(): Set<TKey>
|
|
62
|
+
get valueMapData(): Map<any, Set<TKey>>
|
|
63
|
+
|
|
64
|
+
supports: (operation: IndexOperation) => boolean
|
|
65
|
+
|
|
66
|
+
matchesField: (fieldPath: Array<string>) => boolean
|
|
67
|
+
matchesCompareOptions: (compareOptions: CompareOptions) => boolean
|
|
68
|
+
matchesDirection: (direction: OrderByDirection) => boolean
|
|
69
|
+
|
|
70
|
+
getStats: () => IndexStats
|
|
71
|
+
}
|
|
72
|
+
|
|
25
73
|
/**
|
|
26
74
|
* Base abstract class that all index types extend
|
|
27
75
|
*/
|
|
28
|
-
export abstract class BaseIndex<
|
|
29
|
-
TKey
|
|
30
|
-
|
|
76
|
+
export abstract class BaseIndex<TKey extends string | number = string | number>
|
|
77
|
+
implements IndexInterface<TKey>
|
|
78
|
+
{
|
|
31
79
|
public readonly id: number
|
|
32
80
|
public readonly name?: string
|
|
33
81
|
public readonly expression: BasicExpression
|
|
@@ -36,6 +84,7 @@ export abstract class BaseIndex<
|
|
|
36
84
|
protected lookupCount = 0
|
|
37
85
|
protected totalLookupTime = 0
|
|
38
86
|
protected lastUpdated = new Date()
|
|
87
|
+
protected compareOptions: CompareOptions
|
|
39
88
|
|
|
40
89
|
constructor(
|
|
41
90
|
id: number,
|
|
@@ -45,6 +94,7 @@ export abstract class BaseIndex<
|
|
|
45
94
|
) {
|
|
46
95
|
this.id = id
|
|
47
96
|
this.expression = expression
|
|
97
|
+
this.compareOptions = DEFAULT_COMPARE_OPTIONS
|
|
48
98
|
this.name = name
|
|
49
99
|
this.initialize(options)
|
|
50
100
|
}
|
|
@@ -61,7 +111,20 @@ export abstract class BaseIndex<
|
|
|
61
111
|
from?: TKey,
|
|
62
112
|
filterFn?: (key: TKey) => boolean
|
|
63
113
|
): Array<TKey>
|
|
114
|
+
abstract takeReversed(
|
|
115
|
+
n: number,
|
|
116
|
+
from?: TKey,
|
|
117
|
+
filterFn?: (key: TKey) => boolean
|
|
118
|
+
): Array<TKey>
|
|
64
119
|
abstract get keyCount(): number
|
|
120
|
+
abstract equalityLookup(value: any): Set<TKey>
|
|
121
|
+
abstract inArrayLookup(values: Array<any>): Set<TKey>
|
|
122
|
+
abstract rangeQuery(options: RangeQueryOptions): Set<TKey>
|
|
123
|
+
abstract rangeQueryReversed(options: RangeQueryOptions): Set<TKey>
|
|
124
|
+
abstract get orderedEntriesArray(): Array<[any, Set<TKey>]>
|
|
125
|
+
abstract get orderedEntriesArrayReversed(): Array<[any, Set<TKey>]>
|
|
126
|
+
abstract get indexedKeysSet(): Set<TKey>
|
|
127
|
+
abstract get valueMapData(): Map<any, Set<TKey>>
|
|
65
128
|
|
|
66
129
|
// Common methods
|
|
67
130
|
supports(operation: IndexOperation): boolean {
|
|
@@ -76,6 +139,33 @@ export abstract class BaseIndex<
|
|
|
76
139
|
)
|
|
77
140
|
}
|
|
78
141
|
|
|
142
|
+
/**
|
|
143
|
+
* Checks if the compare options match the index's compare options.
|
|
144
|
+
* The direction is ignored because the index can be reversed if the direction is different.
|
|
145
|
+
*/
|
|
146
|
+
matchesCompareOptions(compareOptions: CompareOptions): boolean {
|
|
147
|
+
const thisCompareOptionsWithoutDirection = {
|
|
148
|
+
...this.compareOptions,
|
|
149
|
+
direction: undefined,
|
|
150
|
+
}
|
|
151
|
+
const compareOptionsWithoutDirection = {
|
|
152
|
+
...compareOptions,
|
|
153
|
+
direction: undefined,
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return deepEquals(
|
|
157
|
+
thisCompareOptionsWithoutDirection,
|
|
158
|
+
compareOptionsWithoutDirection
|
|
159
|
+
)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Checks if the index matches the provided direction.
|
|
164
|
+
*/
|
|
165
|
+
matchesDirection(direction: OrderByDirection): boolean {
|
|
166
|
+
return this.compareOptions.direction === direction
|
|
167
|
+
}
|
|
168
|
+
|
|
79
169
|
getStats(): IndexStats {
|
|
80
170
|
return {
|
|
81
171
|
entryCount: this.keyCount,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { BTree } from "../utils/btree.js"
|
|
2
2
|
import { defaultComparator, normalizeValue } from "../utils/comparison.js"
|
|
3
3
|
import { BaseIndex } from "./base-index.js"
|
|
4
|
+
import type { CompareOptions } from "../query/builder/types.js"
|
|
4
5
|
import type { BasicExpression } from "../query/ir.js"
|
|
5
6
|
import type { IndexOperation } from "./base-index.js"
|
|
6
7
|
|
|
@@ -9,6 +10,7 @@ import type { IndexOperation } from "./base-index.js"
|
|
|
9
10
|
*/
|
|
10
11
|
export interface BTreeIndexOptions {
|
|
11
12
|
compareFn?: (a: any, b: any) => number
|
|
13
|
+
compareOptions?: CompareOptions
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
/**
|
|
@@ -53,6 +55,9 @@ export class BTreeIndex<
|
|
|
53
55
|
) {
|
|
54
56
|
super(id, expression, name, options)
|
|
55
57
|
this.compareFn = options?.compareFn ?? defaultComparator
|
|
58
|
+
if (options?.compareOptions) {
|
|
59
|
+
this.compareOptions = options!.compareOptions
|
|
60
|
+
}
|
|
56
61
|
this.orderedEntries = new BTree(this.compareFn)
|
|
57
62
|
}
|
|
58
63
|
|
|
@@ -240,18 +245,31 @@ export class BTreeIndex<
|
|
|
240
245
|
}
|
|
241
246
|
|
|
242
247
|
/**
|
|
243
|
-
*
|
|
244
|
-
* @param n - The number of items to return
|
|
245
|
-
* @param from - The item to start from (exclusive). Starts from the smallest item (inclusive) if not provided.
|
|
246
|
-
* @returns The next n items after the provided key. Returns the first n items if no from item is provided.
|
|
248
|
+
* Performs a reversed range query
|
|
247
249
|
*/
|
|
248
|
-
|
|
250
|
+
rangeQueryReversed(options: RangeQueryOptions = {}): Set<TKey> {
|
|
251
|
+
const { from, to, fromInclusive = true, toInclusive = true } = options
|
|
252
|
+
return this.rangeQuery({
|
|
253
|
+
from: to ?? this.orderedEntries.maxKey(),
|
|
254
|
+
to: from ?? this.orderedEntries.minKey(),
|
|
255
|
+
fromInclusive: toInclusive,
|
|
256
|
+
toInclusive: fromInclusive,
|
|
257
|
+
})
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
private takeInternal(
|
|
261
|
+
n: number,
|
|
262
|
+
nextPair: (k?: any) => [any, any] | undefined,
|
|
263
|
+
from?: any,
|
|
264
|
+
filterFn?: (key: TKey) => boolean
|
|
265
|
+
): Array<TKey> {
|
|
249
266
|
const keysInResult: Set<TKey> = new Set()
|
|
250
267
|
const result: Array<TKey> = []
|
|
251
|
-
|
|
268
|
+
let pair: [any, any] | undefined
|
|
252
269
|
let key = normalizeValue(from)
|
|
253
270
|
|
|
254
|
-
while ((
|
|
271
|
+
while ((pair = nextPair(key)) !== undefined && result.length < n) {
|
|
272
|
+
key = pair[0]
|
|
255
273
|
const keys = this.valueMap.get(key)
|
|
256
274
|
if (keys) {
|
|
257
275
|
const it = keys.values()
|
|
@@ -268,6 +286,32 @@ export class BTreeIndex<
|
|
|
268
286
|
return result
|
|
269
287
|
}
|
|
270
288
|
|
|
289
|
+
/**
|
|
290
|
+
* Returns the next n items after the provided item or the first n items if no from item is provided.
|
|
291
|
+
* @param n - The number of items to return
|
|
292
|
+
* @param from - The item to start from (exclusive). Starts from the smallest item (inclusive) if not provided.
|
|
293
|
+
* @returns The next n items after the provided key. Returns the first n items if no from item is provided.
|
|
294
|
+
*/
|
|
295
|
+
take(n: number, from?: any, filterFn?: (key: TKey) => boolean): Array<TKey> {
|
|
296
|
+
const nextPair = (k?: any) => this.orderedEntries.nextHigherPair(k)
|
|
297
|
+
return this.takeInternal(n, nextPair, from, filterFn)
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Returns the next n items **before** the provided item (in descending order) or the last n items if no from item is provided.
|
|
302
|
+
* @param n - The number of items to return
|
|
303
|
+
* @param from - The item to start from (exclusive). Starts from the largest item (inclusive) if not provided.
|
|
304
|
+
* @returns The next n items **before** the provided key. Returns the last n items if no from item is provided.
|
|
305
|
+
*/
|
|
306
|
+
takeReversed(
|
|
307
|
+
n: number,
|
|
308
|
+
from?: any,
|
|
309
|
+
filterFn?: (key: TKey) => boolean
|
|
310
|
+
): Array<TKey> {
|
|
311
|
+
const nextPair = (k?: any) => this.orderedEntries.nextLowerPair(k)
|
|
312
|
+
return this.takeInternal(n, nextPair, from, filterFn)
|
|
313
|
+
}
|
|
314
|
+
|
|
271
315
|
/**
|
|
272
316
|
* Performs an IN array lookup
|
|
273
317
|
*/
|
|
@@ -296,6 +340,13 @@ export class BTreeIndex<
|
|
|
296
340
|
.map((key) => [key, this.valueMap.get(key) ?? new Set()])
|
|
297
341
|
}
|
|
298
342
|
|
|
343
|
+
get orderedEntriesArrayReversed(): Array<[any, Set<TKey>]> {
|
|
344
|
+
return this.takeReversed(this.orderedEntries.size).map((key) => [
|
|
345
|
+
key,
|
|
346
|
+
this.valueMap.get(key) ?? new Set(),
|
|
347
|
+
])
|
|
348
|
+
}
|
|
349
|
+
|
|
299
350
|
get valueMapData(): Map<any, Set<TKey>> {
|
|
300
351
|
return this.valueMap
|
|
301
352
|
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import type { CompareOptions } from "../query/builder/types"
|
|
2
|
+
import type { OrderByDirection } from "../query/ir"
|
|
3
|
+
import type { IndexInterface, IndexOperation, IndexStats } from "./base-index"
|
|
4
|
+
import type { RangeQueryOptions } from "./btree-index"
|
|
5
|
+
|
|
6
|
+
export class ReverseIndex<TKey extends string | number>
|
|
7
|
+
implements IndexInterface<TKey>
|
|
8
|
+
{
|
|
9
|
+
private originalIndex: IndexInterface<TKey>
|
|
10
|
+
|
|
11
|
+
constructor(index: IndexInterface<TKey>) {
|
|
12
|
+
this.originalIndex = index
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Define the reversed operations
|
|
16
|
+
|
|
17
|
+
lookup(operation: IndexOperation, value: any): Set<TKey> {
|
|
18
|
+
const reverseOperation =
|
|
19
|
+
operation === `gt`
|
|
20
|
+
? `lt`
|
|
21
|
+
: operation === `gte`
|
|
22
|
+
? `lte`
|
|
23
|
+
: operation === `lt`
|
|
24
|
+
? `gt`
|
|
25
|
+
: operation === `lte`
|
|
26
|
+
? `gte`
|
|
27
|
+
: operation
|
|
28
|
+
return this.originalIndex.lookup(reverseOperation, value)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
rangeQuery(options: RangeQueryOptions = {}): Set<TKey> {
|
|
32
|
+
return this.originalIndex.rangeQueryReversed(options)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
rangeQueryReversed(options: RangeQueryOptions = {}): Set<TKey> {
|
|
36
|
+
return this.originalIndex.rangeQuery(options)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
take(n: number, from?: any, filterFn?: (key: TKey) => boolean): Array<TKey> {
|
|
40
|
+
return this.originalIndex.takeReversed(n, from, filterFn)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
takeReversed(
|
|
44
|
+
n: number,
|
|
45
|
+
from?: any,
|
|
46
|
+
filterFn?: (key: TKey) => boolean
|
|
47
|
+
): Array<TKey> {
|
|
48
|
+
return this.originalIndex.take(n, from, filterFn)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
get orderedEntriesArray(): Array<[any, Set<TKey>]> {
|
|
52
|
+
return this.originalIndex.orderedEntriesArrayReversed
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
get orderedEntriesArrayReversed(): Array<[any, Set<TKey>]> {
|
|
56
|
+
return this.originalIndex.orderedEntriesArray
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// All operations below delegate to the original index
|
|
60
|
+
|
|
61
|
+
supports(operation: IndexOperation): boolean {
|
|
62
|
+
return this.originalIndex.supports(operation)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
matchesField(fieldPath: Array<string>): boolean {
|
|
66
|
+
return this.originalIndex.matchesField(fieldPath)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
matchesCompareOptions(compareOptions: CompareOptions): boolean {
|
|
70
|
+
return this.originalIndex.matchesCompareOptions(compareOptions)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
matchesDirection(direction: OrderByDirection): boolean {
|
|
74
|
+
return this.originalIndex.matchesDirection(direction)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
getStats(): IndexStats {
|
|
78
|
+
return this.originalIndex.getStats()
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
add(key: TKey, item: any): void {
|
|
82
|
+
this.originalIndex.add(key, item)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
remove(key: TKey, item: any): void {
|
|
86
|
+
this.originalIndex.remove(key, item)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
update(key: TKey, oldItem: any, newItem: any): void {
|
|
90
|
+
this.originalIndex.update(key, oldItem, newItem)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
build(entries: Iterable<[TKey, any]>): void {
|
|
94
|
+
this.originalIndex.build(entries)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
clear(): void {
|
|
98
|
+
this.originalIndex.clear()
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
get keyCount(): number {
|
|
102
|
+
return this.originalIndex.keyCount
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
equalityLookup(value: any): Set<TKey> {
|
|
106
|
+
return this.originalIndex.equalityLookup(value)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
inArrayLookup(values: Array<any>): Set<TKey> {
|
|
110
|
+
return this.originalIndex.inArrayLookup(values)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
get indexedKeysSet(): Set<TKey> {
|
|
114
|
+
return this.originalIndex.indexedKeysSet
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
get valueMapData(): Map<any, Set<TKey>> {
|
|
118
|
+
return this.originalIndex.valueMapData
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
SubQueryMustHaveFromClauseError,
|
|
17
17
|
} from "../../errors.js"
|
|
18
18
|
import { createRefProxy, toExpression } from "./ref-proxy.js"
|
|
19
|
-
import type { NamespacedRow } from "../../types.js"
|
|
19
|
+
import type { NamespacedRow, SingleResult } from "../../types.js"
|
|
20
20
|
import type {
|
|
21
21
|
Aggregate,
|
|
22
22
|
BasicExpression,
|
|
@@ -615,6 +615,28 @@ export class BaseQueryBuilder<TContext extends Context = Context> {
|
|
|
615
615
|
}) as any
|
|
616
616
|
}
|
|
617
617
|
|
|
618
|
+
/**
|
|
619
|
+
* Specify that the query should return a single result
|
|
620
|
+
* @returns A QueryBuilder that returns the first result
|
|
621
|
+
*
|
|
622
|
+
* @example
|
|
623
|
+
* ```ts
|
|
624
|
+
* // Get the user matching the query
|
|
625
|
+
* query
|
|
626
|
+
* .from({ users: usersCollection })
|
|
627
|
+
* .where(({users}) => eq(users.id, 1))
|
|
628
|
+
* .findOne()
|
|
629
|
+
*```
|
|
630
|
+
*/
|
|
631
|
+
findOne(): QueryBuilder<TContext & SingleResult> {
|
|
632
|
+
return new BaseQueryBuilder({
|
|
633
|
+
...this.query,
|
|
634
|
+
// TODO: enforcing return only one result with also a default orderBy if none is specified
|
|
635
|
+
// limit: 1,
|
|
636
|
+
singleResult: true,
|
|
637
|
+
})
|
|
638
|
+
}
|
|
639
|
+
|
|
618
640
|
// Helper methods
|
|
619
641
|
private _getCurrentAliases(): Array<string> {
|
|
620
642
|
const aliases: Array<string> = []
|
|
@@ -817,4 +839,10 @@ export type ExtractContext<T> =
|
|
|
817
839
|
: never
|
|
818
840
|
|
|
819
841
|
// Export the types from types.ts for convenience
|
|
820
|
-
export type {
|
|
842
|
+
export type {
|
|
843
|
+
Context,
|
|
844
|
+
Source,
|
|
845
|
+
GetResult,
|
|
846
|
+
RefLeaf as Ref,
|
|
847
|
+
InferResultType,
|
|
848
|
+
} from "./types.js"
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { CollectionImpl } from "../../collection/index.js"
|
|
2
|
+
import type { SingleResult } from "../../types.js"
|
|
2
3
|
import type {
|
|
3
4
|
Aggregate,
|
|
4
5
|
BasicExpression,
|
|
@@ -47,6 +48,8 @@ export interface Context {
|
|
|
47
48
|
>
|
|
48
49
|
// The result type after select (if select has been called)
|
|
49
50
|
result?: any
|
|
51
|
+
// Single result only (if findOne has been called)
|
|
52
|
+
singleResult?: boolean
|
|
50
53
|
}
|
|
51
54
|
|
|
52
55
|
/**
|
|
@@ -571,6 +574,7 @@ export type MergeContextWithJoinType<
|
|
|
571
574
|
[K in keyof TNewSchema & string]: TJoinType
|
|
572
575
|
}
|
|
573
576
|
result: TContext[`result`]
|
|
577
|
+
singleResult: TContext[`singleResult`] extends true ? true : false
|
|
574
578
|
}
|
|
575
579
|
|
|
576
580
|
/**
|
|
@@ -621,6 +625,14 @@ export type ApplyJoinOptionalityToMergedSchema<
|
|
|
621
625
|
TNewSchema[K]
|
|
622
626
|
}
|
|
623
627
|
|
|
628
|
+
/**
|
|
629
|
+
* Utility type to infer the query result size (single row or an array)
|
|
630
|
+
*/
|
|
631
|
+
export type InferResultType<TContext extends Context> =
|
|
632
|
+
TContext extends SingleResult
|
|
633
|
+
? GetResult<TContext> | undefined
|
|
634
|
+
: Array<GetResult<TContext>>
|
|
635
|
+
|
|
624
636
|
/**
|
|
625
637
|
* GetResult - Determines the final result type of a query
|
|
626
638
|
*
|
|
@@ -417,16 +417,7 @@ export function replaceAggregatesByRefs(
|
|
|
417
417
|
}
|
|
418
418
|
|
|
419
419
|
case `ref`: {
|
|
420
|
-
|
|
421
|
-
// Check if this is a direct reference to a SELECT alias
|
|
422
|
-
if (refExpr.path.length === 1) {
|
|
423
|
-
const alias = refExpr.path[0]!
|
|
424
|
-
if (selectClause[alias]) {
|
|
425
|
-
// This is a reference to a SELECT alias, convert to result.alias
|
|
426
|
-
return new PropRef([resultAlias, alias])
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
// Return as-is for other refs
|
|
420
|
+
// Non-aggregate refs are passed through unchanged (they reference table columns)
|
|
430
421
|
return havingExpr as BasicExpression
|
|
431
422
|
}
|
|
432
423
|
|
|
@@ -127,7 +127,8 @@ export function compileQuery(
|
|
|
127
127
|
callbacks,
|
|
128
128
|
lazyCollections,
|
|
129
129
|
optimizableOrderByCollections,
|
|
130
|
-
rawQuery
|
|
130
|
+
rawQuery,
|
|
131
|
+
compileQuery
|
|
131
132
|
)
|
|
132
133
|
}
|
|
133
134
|
|
|
@@ -512,3 +513,5 @@ export function followRef(
|
|
|
512
513
|
}
|
|
513
514
|
}
|
|
514
515
|
}
|
|
516
|
+
|
|
517
|
+
export type CompileQueryFn = typeof compileQuery
|