@tanstack/db 0.0.13 → 0.0.15
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.cjs +117 -104
- package/dist/cjs/collection.cjs.map +1 -1
- package/dist/cjs/collection.d.cts +19 -22
- package/dist/cjs/index.cjs +35 -13
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +0 -1
- package/dist/cjs/query/builder/functions.cjs +107 -0
- package/dist/cjs/query/builder/functions.cjs.map +1 -0
- package/dist/cjs/query/builder/functions.d.cts +38 -0
- package/dist/cjs/query/builder/index.cjs +499 -0
- package/dist/cjs/query/builder/index.cjs.map +1 -0
- package/dist/cjs/query/builder/index.d.cts +324 -0
- package/dist/cjs/query/builder/ref-proxy.cjs +96 -0
- package/dist/cjs/query/builder/ref-proxy.cjs.map +1 -0
- package/dist/cjs/query/builder/ref-proxy.d.cts +28 -0
- package/dist/cjs/query/builder/types.d.cts +80 -0
- package/dist/cjs/query/compiler/evaluators.cjs +261 -0
- package/dist/cjs/query/compiler/evaluators.cjs.map +1 -0
- package/dist/cjs/query/compiler/evaluators.d.cts +11 -0
- package/dist/cjs/query/compiler/group-by.cjs +271 -0
- package/dist/cjs/query/compiler/group-by.cjs.map +1 -0
- package/dist/cjs/query/compiler/group-by.d.cts +7 -0
- package/dist/cjs/query/compiler/index.cjs +181 -0
- package/dist/cjs/query/compiler/index.cjs.map +1 -0
- package/dist/cjs/query/compiler/index.d.cts +15 -0
- package/dist/cjs/query/compiler/joins.cjs +116 -0
- package/dist/cjs/query/compiler/joins.cjs.map +1 -0
- package/dist/cjs/query/compiler/joins.d.cts +11 -0
- package/dist/cjs/query/compiler/order-by.cjs +89 -0
- package/dist/cjs/query/compiler/order-by.cjs.map +1 -0
- package/dist/cjs/query/compiler/order-by.d.cts +9 -0
- package/dist/cjs/query/compiler/select.cjs +57 -0
- package/dist/cjs/query/compiler/select.cjs.map +1 -0
- package/dist/cjs/query/compiler/select.d.cts +15 -0
- package/dist/cjs/query/index.d.cts +6 -5
- package/dist/cjs/query/ir.cjs +57 -0
- package/dist/cjs/query/ir.cjs.map +1 -0
- package/dist/cjs/query/ir.d.cts +81 -0
- package/dist/cjs/query/live-query-collection.cjs +224 -0
- package/dist/cjs/query/live-query-collection.cjs.map +1 -0
- package/dist/cjs/query/live-query-collection.d.cts +124 -0
- package/dist/cjs/transactions.cjs +20 -13
- package/dist/cjs/transactions.cjs.map +1 -1
- package/dist/cjs/transactions.d.cts +13 -4
- package/dist/cjs/types.d.cts +14 -1
- package/dist/esm/collection.d.ts +19 -22
- package/dist/esm/collection.js +118 -105
- package/dist/esm/collection.js.map +1 -1
- package/dist/esm/index.d.ts +0 -1
- package/dist/esm/index.js +34 -12
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/query/builder/functions.d.ts +38 -0
- package/dist/esm/query/builder/functions.js +107 -0
- package/dist/esm/query/builder/functions.js.map +1 -0
- package/dist/esm/query/builder/index.d.ts +324 -0
- package/dist/esm/query/builder/index.js +499 -0
- package/dist/esm/query/builder/index.js.map +1 -0
- package/dist/esm/query/builder/ref-proxy.d.ts +28 -0
- package/dist/esm/query/builder/ref-proxy.js +96 -0
- package/dist/esm/query/builder/ref-proxy.js.map +1 -0
- package/dist/esm/query/builder/types.d.ts +80 -0
- package/dist/esm/query/compiler/evaluators.d.ts +11 -0
- package/dist/esm/query/compiler/evaluators.js +261 -0
- package/dist/esm/query/compiler/evaluators.js.map +1 -0
- package/dist/esm/query/compiler/group-by.d.ts +7 -0
- package/dist/esm/query/compiler/group-by.js +271 -0
- package/dist/esm/query/compiler/group-by.js.map +1 -0
- package/dist/esm/query/compiler/index.d.ts +15 -0
- package/dist/esm/query/compiler/index.js +181 -0
- package/dist/esm/query/compiler/index.js.map +1 -0
- package/dist/esm/query/compiler/joins.d.ts +11 -0
- package/dist/esm/query/compiler/joins.js +116 -0
- package/dist/esm/query/compiler/joins.js.map +1 -0
- package/dist/esm/query/compiler/order-by.d.ts +9 -0
- package/dist/esm/query/compiler/order-by.js +89 -0
- package/dist/esm/query/compiler/order-by.js.map +1 -0
- package/dist/esm/query/compiler/select.d.ts +15 -0
- package/dist/esm/query/compiler/select.js +57 -0
- package/dist/esm/query/compiler/select.js.map +1 -0
- package/dist/esm/query/index.d.ts +6 -5
- package/dist/esm/query/ir.d.ts +81 -0
- package/dist/esm/query/ir.js +57 -0
- package/dist/esm/query/ir.js.map +1 -0
- package/dist/esm/query/live-query-collection.d.ts +124 -0
- package/dist/esm/query/live-query-collection.js +224 -0
- package/dist/esm/query/live-query-collection.js.map +1 -0
- package/dist/esm/transactions.d.ts +13 -4
- package/dist/esm/transactions.js +20 -13
- package/dist/esm/transactions.js.map +1 -1
- package/dist/esm/types.d.ts +14 -1
- package/package.json +3 -4
- package/src/collection.ts +152 -129
- package/src/index.ts +0 -1
- package/src/query/builder/functions.ts +267 -0
- package/src/query/builder/index.ts +648 -0
- package/src/query/builder/ref-proxy.ts +156 -0
- package/src/query/builder/types.ts +278 -0
- package/src/query/compiler/evaluators.ts +315 -0
- package/src/query/compiler/group-by.ts +428 -0
- package/src/query/compiler/index.ts +276 -0
- package/src/query/compiler/joins.ts +228 -0
- package/src/query/compiler/order-by.ts +139 -0
- package/src/query/compiler/select.ts +173 -0
- package/src/query/index.ts +64 -5
- package/src/query/ir.ts +128 -0
- package/src/query/live-query-collection.ts +509 -0
- package/src/transactions.ts +34 -19
- package/src/types.ts +16 -1
- package/dist/cjs/query/compiled-query.cjs +0 -160
- package/dist/cjs/query/compiled-query.cjs.map +0 -1
- package/dist/cjs/query/compiled-query.d.cts +0 -20
- package/dist/cjs/query/evaluators.cjs +0 -161
- package/dist/cjs/query/evaluators.cjs.map +0 -1
- package/dist/cjs/query/evaluators.d.cts +0 -14
- package/dist/cjs/query/extractors.cjs +0 -122
- package/dist/cjs/query/extractors.cjs.map +0 -1
- package/dist/cjs/query/extractors.d.cts +0 -22
- package/dist/cjs/query/functions.cjs +0 -152
- package/dist/cjs/query/functions.cjs.map +0 -1
- package/dist/cjs/query/functions.d.cts +0 -21
- package/dist/cjs/query/group-by.cjs +0 -88
- package/dist/cjs/query/group-by.cjs.map +0 -1
- package/dist/cjs/query/group-by.d.cts +0 -40
- package/dist/cjs/query/joins.cjs +0 -141
- package/dist/cjs/query/joins.cjs.map +0 -1
- package/dist/cjs/query/joins.d.cts +0 -14
- package/dist/cjs/query/order-by.cjs +0 -185
- package/dist/cjs/query/order-by.cjs.map +0 -1
- package/dist/cjs/query/order-by.d.cts +0 -3
- package/dist/cjs/query/pipeline-compiler.cjs +0 -89
- package/dist/cjs/query/pipeline-compiler.cjs.map +0 -1
- package/dist/cjs/query/pipeline-compiler.d.cts +0 -10
- package/dist/cjs/query/query-builder.cjs +0 -307
- package/dist/cjs/query/query-builder.cjs.map +0 -1
- package/dist/cjs/query/query-builder.d.cts +0 -225
- package/dist/cjs/query/schema.d.cts +0 -100
- package/dist/cjs/query/select.cjs +0 -130
- package/dist/cjs/query/select.cjs.map +0 -1
- package/dist/cjs/query/select.d.cts +0 -3
- package/dist/cjs/query/types.d.cts +0 -189
- package/dist/cjs/query/utils.cjs +0 -154
- package/dist/cjs/query/utils.cjs.map +0 -1
- package/dist/cjs/query/utils.d.cts +0 -37
- package/dist/cjs/utils.cjs +0 -17
- package/dist/cjs/utils.cjs.map +0 -1
- package/dist/cjs/utils.d.cts +0 -3
- package/dist/esm/query/compiled-query.d.ts +0 -20
- package/dist/esm/query/compiled-query.js +0 -160
- package/dist/esm/query/compiled-query.js.map +0 -1
- package/dist/esm/query/evaluators.d.ts +0 -14
- package/dist/esm/query/evaluators.js +0 -161
- package/dist/esm/query/evaluators.js.map +0 -1
- package/dist/esm/query/extractors.d.ts +0 -22
- package/dist/esm/query/extractors.js +0 -122
- package/dist/esm/query/extractors.js.map +0 -1
- package/dist/esm/query/functions.d.ts +0 -21
- package/dist/esm/query/functions.js +0 -152
- package/dist/esm/query/functions.js.map +0 -1
- package/dist/esm/query/group-by.d.ts +0 -40
- package/dist/esm/query/group-by.js +0 -88
- package/dist/esm/query/group-by.js.map +0 -1
- package/dist/esm/query/joins.d.ts +0 -14
- package/dist/esm/query/joins.js +0 -141
- package/dist/esm/query/joins.js.map +0 -1
- package/dist/esm/query/order-by.d.ts +0 -3
- package/dist/esm/query/order-by.js +0 -185
- package/dist/esm/query/order-by.js.map +0 -1
- package/dist/esm/query/pipeline-compiler.d.ts +0 -10
- package/dist/esm/query/pipeline-compiler.js +0 -89
- package/dist/esm/query/pipeline-compiler.js.map +0 -1
- package/dist/esm/query/query-builder.d.ts +0 -225
- package/dist/esm/query/query-builder.js +0 -307
- package/dist/esm/query/query-builder.js.map +0 -1
- package/dist/esm/query/schema.d.ts +0 -100
- package/dist/esm/query/select.d.ts +0 -3
- package/dist/esm/query/select.js +0 -130
- package/dist/esm/query/select.js.map +0 -1
- package/dist/esm/query/types.d.ts +0 -189
- package/dist/esm/query/utils.d.ts +0 -37
- package/dist/esm/query/utils.js +0 -154
- package/dist/esm/query/utils.js.map +0 -1
- package/dist/esm/utils.d.ts +0 -3
- package/dist/esm/utils.js +0 -17
- package/dist/esm/utils.js.map +0 -1
- package/src/query/compiled-query.ts +0 -234
- package/src/query/evaluators.ts +0 -250
- package/src/query/extractors.ts +0 -214
- package/src/query/functions.ts +0 -297
- package/src/query/group-by.ts +0 -139
- package/src/query/joins.ts +0 -260
- package/src/query/order-by.ts +0 -264
- package/src/query/pipeline-compiler.ts +0 -149
- package/src/query/query-builder.ts +0 -902
- package/src/query/schema.ts +0 -268
- package/src/query/select.ts +0 -208
- package/src/query/types.ts +0 -418
- package/src/query/utils.ts +0 -245
- package/src/utils.ts +0 -15
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import {
|
|
2
|
+
consolidate,
|
|
3
|
+
filter,
|
|
4
|
+
join as joinOperator,
|
|
5
|
+
map,
|
|
6
|
+
} from "@electric-sql/d2mini"
|
|
7
|
+
import { compileExpression } from "./evaluators.js"
|
|
8
|
+
import { compileQuery } from "./index.js"
|
|
9
|
+
import type { IStreamBuilder, JoinType } from "@electric-sql/d2mini"
|
|
10
|
+
import type { CollectionRef, JoinClause, QueryIR, QueryRef } from "../ir.js"
|
|
11
|
+
import type {
|
|
12
|
+
KeyedStream,
|
|
13
|
+
NamespacedAndKeyedStream,
|
|
14
|
+
NamespacedRow,
|
|
15
|
+
ResultStream,
|
|
16
|
+
} from "../../types.js"
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Cache for compiled subqueries to avoid duplicate compilation
|
|
20
|
+
*/
|
|
21
|
+
type QueryCache = WeakMap<QueryIR, ResultStream>
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Processes all join clauses in a query
|
|
25
|
+
*/
|
|
26
|
+
export function processJoins(
|
|
27
|
+
pipeline: NamespacedAndKeyedStream,
|
|
28
|
+
joinClauses: Array<JoinClause>,
|
|
29
|
+
tables: Record<string, KeyedStream>,
|
|
30
|
+
mainTableAlias: string,
|
|
31
|
+
allInputs: Record<string, KeyedStream>,
|
|
32
|
+
cache: QueryCache
|
|
33
|
+
): NamespacedAndKeyedStream {
|
|
34
|
+
let resultPipeline = pipeline
|
|
35
|
+
|
|
36
|
+
for (const joinClause of joinClauses) {
|
|
37
|
+
resultPipeline = processJoin(
|
|
38
|
+
resultPipeline,
|
|
39
|
+
joinClause,
|
|
40
|
+
tables,
|
|
41
|
+
mainTableAlias,
|
|
42
|
+
allInputs,
|
|
43
|
+
cache
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return resultPipeline
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Processes a single join clause
|
|
52
|
+
*/
|
|
53
|
+
function processJoin(
|
|
54
|
+
pipeline: NamespacedAndKeyedStream,
|
|
55
|
+
joinClause: JoinClause,
|
|
56
|
+
tables: Record<string, KeyedStream>,
|
|
57
|
+
mainTableAlias: string,
|
|
58
|
+
allInputs: Record<string, KeyedStream>,
|
|
59
|
+
cache: QueryCache
|
|
60
|
+
): NamespacedAndKeyedStream {
|
|
61
|
+
// Get the joined table alias and input stream
|
|
62
|
+
const { alias: joinedTableAlias, input: joinedInput } = processJoinSource(
|
|
63
|
+
joinClause.from,
|
|
64
|
+
allInputs,
|
|
65
|
+
cache
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
// Add the joined table to the tables map
|
|
69
|
+
tables[joinedTableAlias] = joinedInput
|
|
70
|
+
|
|
71
|
+
// Convert join type to D2 join type
|
|
72
|
+
const joinType: JoinType =
|
|
73
|
+
joinClause.type === `cross`
|
|
74
|
+
? `inner`
|
|
75
|
+
: joinClause.type === `outer`
|
|
76
|
+
? `full`
|
|
77
|
+
: (joinClause.type as JoinType)
|
|
78
|
+
|
|
79
|
+
// Pre-compile the join expressions
|
|
80
|
+
const compiledLeftExpr = compileExpression(joinClause.left)
|
|
81
|
+
const compiledRightExpr = compileExpression(joinClause.right)
|
|
82
|
+
|
|
83
|
+
// Prepare the main pipeline for joining
|
|
84
|
+
const mainPipeline = pipeline.pipe(
|
|
85
|
+
map(([currentKey, namespacedRow]) => {
|
|
86
|
+
// Extract the join key from the left side of the join condition
|
|
87
|
+
const leftKey = compiledLeftExpr(namespacedRow)
|
|
88
|
+
|
|
89
|
+
// Return [joinKey, [originalKey, namespacedRow]]
|
|
90
|
+
return [leftKey, [currentKey, namespacedRow]] as [
|
|
91
|
+
unknown,
|
|
92
|
+
[string, typeof namespacedRow],
|
|
93
|
+
]
|
|
94
|
+
})
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
// Prepare the joined pipeline
|
|
98
|
+
const joinedPipeline = joinedInput.pipe(
|
|
99
|
+
map(([currentKey, row]) => {
|
|
100
|
+
// Wrap the row in a namespaced structure
|
|
101
|
+
const namespacedRow: NamespacedRow = { [joinedTableAlias]: row }
|
|
102
|
+
|
|
103
|
+
// Extract the join key from the right side of the join condition
|
|
104
|
+
const rightKey = compiledRightExpr(namespacedRow)
|
|
105
|
+
|
|
106
|
+
// Return [joinKey, [originalKey, namespacedRow]]
|
|
107
|
+
return [rightKey, [currentKey, namespacedRow]] as [
|
|
108
|
+
unknown,
|
|
109
|
+
[string, typeof namespacedRow],
|
|
110
|
+
]
|
|
111
|
+
})
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
// Apply the join operation
|
|
115
|
+
if (![`inner`, `left`, `right`, `full`].includes(joinType)) {
|
|
116
|
+
throw new Error(`Unsupported join type: ${joinClause.type}`)
|
|
117
|
+
}
|
|
118
|
+
return mainPipeline.pipe(
|
|
119
|
+
joinOperator(joinedPipeline, joinType),
|
|
120
|
+
consolidate(),
|
|
121
|
+
processJoinResults(joinClause.type)
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Processes the join source (collection or sub-query)
|
|
127
|
+
*/
|
|
128
|
+
function processJoinSource(
|
|
129
|
+
from: CollectionRef | QueryRef,
|
|
130
|
+
allInputs: Record<string, KeyedStream>,
|
|
131
|
+
cache: QueryCache
|
|
132
|
+
): { alias: string; input: KeyedStream } {
|
|
133
|
+
switch (from.type) {
|
|
134
|
+
case `collectionRef`: {
|
|
135
|
+
const input = allInputs[from.collection.id]
|
|
136
|
+
if (!input) {
|
|
137
|
+
throw new Error(
|
|
138
|
+
`Input for collection "${from.collection.id}" not found in inputs map`
|
|
139
|
+
)
|
|
140
|
+
}
|
|
141
|
+
return { alias: from.alias, input }
|
|
142
|
+
}
|
|
143
|
+
case `queryRef`: {
|
|
144
|
+
// Recursively compile the sub-query with cache
|
|
145
|
+
const subQueryInput = compileQuery(from.query, allInputs, cache)
|
|
146
|
+
|
|
147
|
+
// Subqueries may return [key, [value, orderByIndex]] (with ORDER BY) or [key, value] (without ORDER BY)
|
|
148
|
+
// We need to extract just the value for use in parent queries
|
|
149
|
+
const extractedInput = subQueryInput.pipe(
|
|
150
|
+
map((data: any) => {
|
|
151
|
+
const [key, [value, _orderByIndex]] = data
|
|
152
|
+
return [key, value] as [unknown, any]
|
|
153
|
+
})
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
return { alias: from.alias, input: extractedInput as KeyedStream }
|
|
157
|
+
}
|
|
158
|
+
default:
|
|
159
|
+
throw new Error(`Unsupported join source type: ${(from as any).type}`)
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Processes the results of a join operation
|
|
165
|
+
*/
|
|
166
|
+
function processJoinResults(joinType: string) {
|
|
167
|
+
return function (
|
|
168
|
+
pipeline: IStreamBuilder<
|
|
169
|
+
[
|
|
170
|
+
key: string,
|
|
171
|
+
[
|
|
172
|
+
[string, NamespacedRow] | undefined,
|
|
173
|
+
[string, NamespacedRow] | undefined,
|
|
174
|
+
],
|
|
175
|
+
]
|
|
176
|
+
>
|
|
177
|
+
): NamespacedAndKeyedStream {
|
|
178
|
+
return pipeline.pipe(
|
|
179
|
+
// Process the join result and handle nulls
|
|
180
|
+
filter((result) => {
|
|
181
|
+
const [_key, [main, joined]] = result
|
|
182
|
+
const mainNamespacedRow = main?.[1]
|
|
183
|
+
const joinedNamespacedRow = joined?.[1]
|
|
184
|
+
|
|
185
|
+
// Handle different join types
|
|
186
|
+
if (joinType === `inner`) {
|
|
187
|
+
return !!(mainNamespacedRow && joinedNamespacedRow)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (joinType === `left`) {
|
|
191
|
+
return !!mainNamespacedRow
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (joinType === `right`) {
|
|
195
|
+
return !!joinedNamespacedRow
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// For full joins, always include
|
|
199
|
+
return true
|
|
200
|
+
}),
|
|
201
|
+
map((result) => {
|
|
202
|
+
const [_key, [main, joined]] = result
|
|
203
|
+
const mainKey = main?.[0]
|
|
204
|
+
const mainNamespacedRow = main?.[1]
|
|
205
|
+
const joinedKey = joined?.[0]
|
|
206
|
+
const joinedNamespacedRow = joined?.[1]
|
|
207
|
+
|
|
208
|
+
// Merge the namespaced rows
|
|
209
|
+
const mergedNamespacedRow: NamespacedRow = {}
|
|
210
|
+
|
|
211
|
+
// Add main row data if it exists
|
|
212
|
+
if (mainNamespacedRow) {
|
|
213
|
+
Object.assign(mergedNamespacedRow, mainNamespacedRow)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Add joined row data if it exists
|
|
217
|
+
if (joinedNamespacedRow) {
|
|
218
|
+
Object.assign(mergedNamespacedRow, joinedNamespacedRow)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// We create a composite key that combines the main and joined keys
|
|
222
|
+
const resultKey = `[${mainKey},${joinedKey}]`
|
|
223
|
+
|
|
224
|
+
return [resultKey, mergedNamespacedRow] as [string, NamespacedRow]
|
|
225
|
+
})
|
|
226
|
+
)
|
|
227
|
+
}
|
|
228
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { orderByWithFractionalIndex } from "@electric-sql/d2mini"
|
|
2
|
+
import { compileExpression } from "./evaluators.js"
|
|
3
|
+
import type { OrderByClause } from "../ir.js"
|
|
4
|
+
import type { NamespacedAndKeyedStream, NamespacedRow } from "../../types.js"
|
|
5
|
+
import type { IStreamBuilder, KeyValue } from "@electric-sql/d2mini"
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Processes the ORDER BY clause
|
|
9
|
+
* Works with the new structure that has both namespaced row data and __select_results
|
|
10
|
+
* Always uses fractional indexing and adds the index as __ordering_index to the result
|
|
11
|
+
*/
|
|
12
|
+
export function processOrderBy(
|
|
13
|
+
pipeline: NamespacedAndKeyedStream,
|
|
14
|
+
orderByClause: Array<OrderByClause>,
|
|
15
|
+
limit?: number,
|
|
16
|
+
offset?: number
|
|
17
|
+
): IStreamBuilder<KeyValue<unknown, [NamespacedRow, string]>> {
|
|
18
|
+
// Pre-compile all order by expressions
|
|
19
|
+
const compiledOrderBy = orderByClause.map((clause) => ({
|
|
20
|
+
compiledExpression: compileExpression(clause.expression),
|
|
21
|
+
direction: clause.direction,
|
|
22
|
+
}))
|
|
23
|
+
|
|
24
|
+
// Create a value extractor function for the orderBy operator
|
|
25
|
+
const valueExtractor = (row: NamespacedRow & { __select_results?: any }) => {
|
|
26
|
+
// For ORDER BY expressions, we need to provide access to both:
|
|
27
|
+
// 1. The original namespaced row data (for direct table column references)
|
|
28
|
+
// 2. The __select_results (for SELECT alias references)
|
|
29
|
+
|
|
30
|
+
// Create a merged context for expression evaluation
|
|
31
|
+
const orderByContext = { ...row }
|
|
32
|
+
|
|
33
|
+
// If there are select results, merge them at the top level for alias access
|
|
34
|
+
if (row.__select_results) {
|
|
35
|
+
// Add select results as top-level properties for alias access
|
|
36
|
+
Object.assign(orderByContext, row.__select_results)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (orderByClause.length > 1) {
|
|
40
|
+
// For multiple orderBy columns, create a composite key
|
|
41
|
+
return compiledOrderBy.map((compiled) =>
|
|
42
|
+
compiled.compiledExpression(orderByContext)
|
|
43
|
+
)
|
|
44
|
+
} else if (orderByClause.length === 1) {
|
|
45
|
+
// For a single orderBy column, use the value directly
|
|
46
|
+
const compiled = compiledOrderBy[0]!
|
|
47
|
+
return compiled.compiledExpression(orderByContext)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Default case - no ordering
|
|
51
|
+
return null
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Create comparator functions
|
|
55
|
+
const ascComparator = (a: any, b: any): number => {
|
|
56
|
+
// Handle null/undefined
|
|
57
|
+
if (a == null && b == null) return 0
|
|
58
|
+
if (a == null) return -1
|
|
59
|
+
if (b == null) return 1
|
|
60
|
+
|
|
61
|
+
// if a and b are both strings, compare them based on locale
|
|
62
|
+
if (typeof a === `string` && typeof b === `string`) {
|
|
63
|
+
return a.localeCompare(b)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// if a and b are both arrays, compare them element by element
|
|
67
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
68
|
+
for (let i = 0; i < Math.min(a.length, b.length); i++) {
|
|
69
|
+
const result = ascComparator(a[i], b[i])
|
|
70
|
+
if (result !== 0) {
|
|
71
|
+
return result
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// All elements are equal up to the minimum length
|
|
75
|
+
return a.length - b.length
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// If both are dates, compare them
|
|
79
|
+
if (a instanceof Date && b instanceof Date) {
|
|
80
|
+
return a.getTime() - b.getTime()
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// If at least one of the values is an object, convert to strings
|
|
84
|
+
const bothObjects = typeof a === `object` && typeof b === `object`
|
|
85
|
+
const notNull = a !== null && b !== null
|
|
86
|
+
if (bothObjects && notNull) {
|
|
87
|
+
return a.toString().localeCompare(b.toString())
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (a < b) return -1
|
|
91
|
+
if (a > b) return 1
|
|
92
|
+
return 0
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const descComparator = (a: unknown, b: unknown): number => {
|
|
96
|
+
return ascComparator(b, a)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Create a multi-property comparator that respects the order and direction of each property
|
|
100
|
+
const makeComparator = () => {
|
|
101
|
+
return (a: unknown, b: unknown) => {
|
|
102
|
+
// If we're comparing arrays (multiple properties), compare each property in order
|
|
103
|
+
if (orderByClause.length > 1) {
|
|
104
|
+
const arrayA = a as Array<unknown>
|
|
105
|
+
const arrayB = b as Array<unknown>
|
|
106
|
+
for (let i = 0; i < orderByClause.length; i++) {
|
|
107
|
+
const direction = orderByClause[i]!.direction
|
|
108
|
+
const compareFn =
|
|
109
|
+
direction === `desc` ? descComparator : ascComparator
|
|
110
|
+
const result = compareFn(arrayA[i], arrayB[i])
|
|
111
|
+
if (result !== 0) {
|
|
112
|
+
return result
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return arrayA.length - arrayB.length
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Single property comparison
|
|
119
|
+
if (orderByClause.length === 1) {
|
|
120
|
+
const direction = orderByClause[0]!.direction
|
|
121
|
+
return direction === `desc` ? descComparator(a, b) : ascComparator(a, b)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return ascComparator(a, b)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const comparator = makeComparator()
|
|
129
|
+
|
|
130
|
+
// Use fractional indexing and return the tuple [value, index]
|
|
131
|
+
return pipeline.pipe(
|
|
132
|
+
orderByWithFractionalIndex(valueExtractor, {
|
|
133
|
+
limit,
|
|
134
|
+
offset,
|
|
135
|
+
comparator,
|
|
136
|
+
})
|
|
137
|
+
// orderByWithFractionalIndex returns [key, [value, index]] - we keep this format
|
|
138
|
+
)
|
|
139
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { map } from "@electric-sql/d2mini"
|
|
2
|
+
import { compileExpression } from "./evaluators.js"
|
|
3
|
+
import type { Aggregate, BasicExpression, Select } from "../ir.js"
|
|
4
|
+
import type {
|
|
5
|
+
KeyedStream,
|
|
6
|
+
NamespacedAndKeyedStream,
|
|
7
|
+
NamespacedRow,
|
|
8
|
+
} from "../../types.js"
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Processes the SELECT clause and places results in __select_results
|
|
12
|
+
* while preserving the original namespaced row for ORDER BY access
|
|
13
|
+
*/
|
|
14
|
+
export function processSelectToResults(
|
|
15
|
+
pipeline: NamespacedAndKeyedStream,
|
|
16
|
+
select: Select,
|
|
17
|
+
_allInputs: Record<string, KeyedStream>
|
|
18
|
+
): NamespacedAndKeyedStream {
|
|
19
|
+
// Pre-compile all select expressions
|
|
20
|
+
const compiledSelect: Array<{
|
|
21
|
+
alias: string
|
|
22
|
+
compiledExpression: (row: NamespacedRow) => any
|
|
23
|
+
}> = []
|
|
24
|
+
const spreadAliases: Array<string> = []
|
|
25
|
+
|
|
26
|
+
for (const [alias, expression] of Object.entries(select)) {
|
|
27
|
+
if (alias.startsWith(`__SPREAD_SENTINEL__`)) {
|
|
28
|
+
// Extract the table alias from the sentinel key
|
|
29
|
+
const tableAlias = alias.replace(`__SPREAD_SENTINEL__`, ``)
|
|
30
|
+
spreadAliases.push(tableAlias)
|
|
31
|
+
} else {
|
|
32
|
+
if (isAggregateExpression(expression)) {
|
|
33
|
+
// For aggregates, we'll store the expression info for GROUP BY processing
|
|
34
|
+
// but still compile a placeholder that will be replaced later
|
|
35
|
+
compiledSelect.push({
|
|
36
|
+
alias,
|
|
37
|
+
compiledExpression: () => null, // Placeholder - will be handled by GROUP BY
|
|
38
|
+
})
|
|
39
|
+
} else {
|
|
40
|
+
compiledSelect.push({
|
|
41
|
+
alias,
|
|
42
|
+
compiledExpression: compileExpression(expression as BasicExpression),
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return pipeline.pipe(
|
|
49
|
+
map(([key, namespacedRow]) => {
|
|
50
|
+
const selectResults: Record<string, any> = {}
|
|
51
|
+
|
|
52
|
+
// First pass: spread table data for any spread sentinels
|
|
53
|
+
for (const tableAlias of spreadAliases) {
|
|
54
|
+
const tableData = namespacedRow[tableAlias]
|
|
55
|
+
if (tableData && typeof tableData === `object`) {
|
|
56
|
+
// Spread the table data into the result, but don't overwrite explicit fields
|
|
57
|
+
for (const [fieldName, fieldValue] of Object.entries(tableData)) {
|
|
58
|
+
if (!(fieldName in selectResults)) {
|
|
59
|
+
selectResults[fieldName] = fieldValue
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Second pass: evaluate all compiled select expressions (non-aggregates)
|
|
66
|
+
for (const { alias, compiledExpression } of compiledSelect) {
|
|
67
|
+
selectResults[alias] = compiledExpression(namespacedRow)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Return the namespaced row with __select_results added
|
|
71
|
+
return [
|
|
72
|
+
key,
|
|
73
|
+
{
|
|
74
|
+
...namespacedRow,
|
|
75
|
+
__select_results: selectResults,
|
|
76
|
+
},
|
|
77
|
+
] as [
|
|
78
|
+
string,
|
|
79
|
+
typeof namespacedRow & { __select_results: typeof selectResults },
|
|
80
|
+
]
|
|
81
|
+
})
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Processes the SELECT clause (legacy function - kept for compatibility)
|
|
87
|
+
*/
|
|
88
|
+
export function processSelect(
|
|
89
|
+
pipeline: NamespacedAndKeyedStream,
|
|
90
|
+
select: Select,
|
|
91
|
+
_allInputs: Record<string, KeyedStream>
|
|
92
|
+
): KeyedStream {
|
|
93
|
+
// Pre-compile all select expressions
|
|
94
|
+
const compiledSelect: Array<{
|
|
95
|
+
alias: string
|
|
96
|
+
compiledExpression: (row: NamespacedRow) => any
|
|
97
|
+
}> = []
|
|
98
|
+
const spreadAliases: Array<string> = []
|
|
99
|
+
|
|
100
|
+
for (const [alias, expression] of Object.entries(select)) {
|
|
101
|
+
if (alias.startsWith(`__SPREAD_SENTINEL__`)) {
|
|
102
|
+
// Extract the table alias from the sentinel key
|
|
103
|
+
const tableAlias = alias.replace(`__SPREAD_SENTINEL__`, ``)
|
|
104
|
+
spreadAliases.push(tableAlias)
|
|
105
|
+
} else {
|
|
106
|
+
if (isAggregateExpression(expression)) {
|
|
107
|
+
// Aggregates should be handled by GROUP BY processing, not here
|
|
108
|
+
throw new Error(
|
|
109
|
+
`Aggregate expressions in SELECT clause should be handled by GROUP BY processing`
|
|
110
|
+
)
|
|
111
|
+
}
|
|
112
|
+
compiledSelect.push({
|
|
113
|
+
alias,
|
|
114
|
+
compiledExpression: compileExpression(expression as BasicExpression),
|
|
115
|
+
})
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return pipeline.pipe(
|
|
120
|
+
map(([key, namespacedRow]) => {
|
|
121
|
+
const result: Record<string, any> = {}
|
|
122
|
+
|
|
123
|
+
// First pass: spread table data for any spread sentinels
|
|
124
|
+
for (const tableAlias of spreadAliases) {
|
|
125
|
+
const tableData = namespacedRow[tableAlias]
|
|
126
|
+
if (tableData && typeof tableData === `object`) {
|
|
127
|
+
// Spread the table data into the result, but don't overwrite explicit fields
|
|
128
|
+
for (const [fieldName, fieldValue] of Object.entries(tableData)) {
|
|
129
|
+
if (!(fieldName in result)) {
|
|
130
|
+
result[fieldName] = fieldValue
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Second pass: evaluate all compiled select expressions
|
|
137
|
+
for (const { alias, compiledExpression } of compiledSelect) {
|
|
138
|
+
result[alias] = compiledExpression(namespacedRow)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return [key, result] as [string, typeof result]
|
|
142
|
+
})
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Helper function to check if an expression is an aggregate
|
|
148
|
+
*/
|
|
149
|
+
function isAggregateExpression(
|
|
150
|
+
expr: BasicExpression | Aggregate
|
|
151
|
+
): expr is Aggregate {
|
|
152
|
+
return expr.type === `agg`
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Processes a single argument in a function context
|
|
157
|
+
*/
|
|
158
|
+
export function processArgument(
|
|
159
|
+
arg: BasicExpression | Aggregate,
|
|
160
|
+
namespacedRow: NamespacedRow
|
|
161
|
+
): any {
|
|
162
|
+
if (isAggregateExpression(arg)) {
|
|
163
|
+
throw new Error(
|
|
164
|
+
`Aggregate expressions are not supported in this context. Use GROUP BY clause for aggregates.`
|
|
165
|
+
)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Pre-compile the expression and evaluate immediately
|
|
169
|
+
const compiledExpression = compileExpression(arg)
|
|
170
|
+
const value = compiledExpression(namespacedRow)
|
|
171
|
+
|
|
172
|
+
return value
|
|
173
|
+
}
|
package/src/query/index.ts
CHANGED
|
@@ -1,5 +1,64 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
export
|
|
5
|
-
|
|
1
|
+
// Main exports for the new query builder system
|
|
2
|
+
|
|
3
|
+
// Query builder exports
|
|
4
|
+
export {
|
|
5
|
+
BaseQueryBuilder,
|
|
6
|
+
Query,
|
|
7
|
+
type InitialQueryBuilder,
|
|
8
|
+
type QueryBuilder,
|
|
9
|
+
type Context,
|
|
10
|
+
type Source,
|
|
11
|
+
type GetResult,
|
|
12
|
+
} from "./builder/index.js"
|
|
13
|
+
|
|
14
|
+
// Expression functions exports
|
|
15
|
+
export {
|
|
16
|
+
// Operators
|
|
17
|
+
eq,
|
|
18
|
+
gt,
|
|
19
|
+
gte,
|
|
20
|
+
lt,
|
|
21
|
+
lte,
|
|
22
|
+
and,
|
|
23
|
+
or,
|
|
24
|
+
not,
|
|
25
|
+
inArray,
|
|
26
|
+
like,
|
|
27
|
+
ilike,
|
|
28
|
+
// Functions
|
|
29
|
+
upper,
|
|
30
|
+
lower,
|
|
31
|
+
length,
|
|
32
|
+
concat,
|
|
33
|
+
coalesce,
|
|
34
|
+
add,
|
|
35
|
+
// Aggregates
|
|
36
|
+
count,
|
|
37
|
+
avg,
|
|
38
|
+
sum,
|
|
39
|
+
min,
|
|
40
|
+
max,
|
|
41
|
+
} from "./builder/functions.js"
|
|
42
|
+
|
|
43
|
+
// Ref proxy utilities
|
|
44
|
+
export { val, toExpression, isRefProxy } from "./builder/ref-proxy.js"
|
|
45
|
+
|
|
46
|
+
// IR types (for advanced usage)
|
|
47
|
+
export type {
|
|
48
|
+
QueryIR,
|
|
49
|
+
BasicExpression as Expression,
|
|
50
|
+
Aggregate,
|
|
51
|
+
CollectionRef,
|
|
52
|
+
QueryRef,
|
|
53
|
+
JoinClause,
|
|
54
|
+
} from "./ir.js"
|
|
55
|
+
|
|
56
|
+
// Compiler
|
|
57
|
+
export { compileQuery } from "./compiler/index.js"
|
|
58
|
+
|
|
59
|
+
// Live query collection utilities
|
|
60
|
+
export {
|
|
61
|
+
createLiveQueryCollection,
|
|
62
|
+
liveQueryCollectionOptions,
|
|
63
|
+
type LiveQueryCollectionConfig,
|
|
64
|
+
} from "./live-query-collection.js"
|