@tanstack/db 0.0.4 → 0.0.6
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 +182 -113
- package/dist/cjs/collection.cjs.map +1 -1
- package/dist/cjs/collection.d.cts +43 -15
- package/dist/cjs/index.cjs +1 -0
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/proxy.cjs +87 -248
- package/dist/cjs/proxy.cjs.map +1 -1
- package/dist/cjs/proxy.d.cts +5 -5
- package/dist/cjs/query/compiled-query.cjs +23 -14
- package/dist/cjs/query/compiled-query.cjs.map +1 -1
- package/dist/cjs/query/compiled-query.d.cts +3 -1
- package/dist/cjs/query/evaluators.cjs +35 -20
- package/dist/cjs/query/evaluators.cjs.map +1 -1
- package/dist/cjs/query/evaluators.d.cts +8 -3
- package/dist/cjs/query/extractors.cjs +20 -20
- package/dist/cjs/query/extractors.cjs.map +1 -1
- package/dist/cjs/query/extractors.d.cts +3 -3
- package/dist/cjs/query/group-by.cjs +12 -15
- package/dist/cjs/query/group-by.cjs.map +1 -1
- package/dist/cjs/query/group-by.d.cts +7 -7
- package/dist/cjs/query/joins.cjs +41 -55
- package/dist/cjs/query/joins.cjs.map +1 -1
- package/dist/cjs/query/joins.d.cts +3 -3
- package/dist/cjs/query/order-by.cjs +37 -84
- package/dist/cjs/query/order-by.cjs.map +1 -1
- package/dist/cjs/query/order-by.d.cts +2 -2
- package/dist/cjs/query/pipeline-compiler.cjs +13 -18
- package/dist/cjs/query/pipeline-compiler.cjs.map +1 -1
- package/dist/cjs/query/pipeline-compiler.d.cts +2 -1
- package/dist/cjs/query/query-builder.cjs +22 -29
- package/dist/cjs/query/query-builder.cjs.map +1 -1
- package/dist/cjs/query/query-builder.d.cts +16 -10
- package/dist/cjs/query/schema.d.cts +12 -11
- package/dist/cjs/query/select.cjs +47 -24
- package/dist/cjs/query/select.cjs.map +1 -1
- package/dist/cjs/query/select.d.cts +2 -2
- package/dist/cjs/query/types.d.cts +1 -0
- package/dist/cjs/transactions.cjs +20 -9
- package/dist/cjs/transactions.cjs.map +1 -1
- package/dist/cjs/types.d.cts +66 -7
- package/dist/esm/collection.d.ts +43 -15
- package/dist/esm/collection.js +183 -114
- package/dist/esm/collection.js.map +1 -1
- package/dist/esm/index.js +2 -1
- package/dist/esm/proxy.d.ts +5 -5
- package/dist/esm/proxy.js +87 -248
- package/dist/esm/proxy.js.map +1 -1
- package/dist/esm/query/compiled-query.d.ts +3 -1
- package/dist/esm/query/compiled-query.js +23 -14
- package/dist/esm/query/compiled-query.js.map +1 -1
- package/dist/esm/query/evaluators.d.ts +8 -3
- package/dist/esm/query/evaluators.js +36 -21
- package/dist/esm/query/evaluators.js.map +1 -1
- package/dist/esm/query/extractors.d.ts +3 -3
- package/dist/esm/query/extractors.js +20 -20
- package/dist/esm/query/extractors.js.map +1 -1
- package/dist/esm/query/group-by.d.ts +7 -7
- package/dist/esm/query/group-by.js +14 -17
- package/dist/esm/query/group-by.js.map +1 -1
- package/dist/esm/query/joins.d.ts +3 -3
- package/dist/esm/query/joins.js +42 -56
- package/dist/esm/query/joins.js.map +1 -1
- package/dist/esm/query/order-by.d.ts +2 -2
- package/dist/esm/query/order-by.js +39 -86
- package/dist/esm/query/order-by.js.map +1 -1
- package/dist/esm/query/pipeline-compiler.d.ts +2 -1
- package/dist/esm/query/pipeline-compiler.js +14 -19
- package/dist/esm/query/pipeline-compiler.js.map +1 -1
- package/dist/esm/query/query-builder.d.ts +16 -10
- package/dist/esm/query/query-builder.js +22 -29
- package/dist/esm/query/query-builder.js.map +1 -1
- package/dist/esm/query/schema.d.ts +12 -11
- package/dist/esm/query/select.d.ts +2 -2
- package/dist/esm/query/select.js +48 -25
- package/dist/esm/query/select.js.map +1 -1
- package/dist/esm/query/types.d.ts +1 -0
- package/dist/esm/transactions.js +20 -9
- package/dist/esm/transactions.js.map +1 -1
- package/dist/esm/types.d.ts +66 -7
- package/package.json +2 -2
- package/src/collection.ts +286 -146
- package/src/proxy.ts +141 -358
- package/src/query/compiled-query.ts +30 -15
- package/src/query/evaluators.ts +49 -21
- package/src/query/extractors.ts +24 -21
- package/src/query/group-by.ts +24 -22
- package/src/query/joins.ts +88 -75
- package/src/query/order-by.ts +56 -106
- package/src/query/pipeline-compiler.ts +34 -37
- package/src/query/query-builder.ts +49 -46
- package/src/query/schema.ts +18 -15
- package/src/query/select.ts +68 -33
- package/src/query/types.ts +1 -0
- package/src/transactions.ts +30 -14
- package/src/types.ts +76 -7
- package/dist/cjs/query/key-by.cjs +0 -43
- package/dist/cjs/query/key-by.cjs.map +0 -1
- package/dist/cjs/query/key-by.d.cts +0 -3
- package/dist/esm/query/key-by.d.ts +0 -3
- package/dist/esm/query/key-by.js +0 -43
- package/dist/esm/query/key-by.js.map +0 -1
- package/src/query/key-by.ts +0 -61
|
@@ -14,7 +14,9 @@ import type { Context, Schema } from "./types.js"
|
|
|
14
14
|
export function compileQuery<TContext extends Context<Schema>>(
|
|
15
15
|
queryBuilder: QueryBuilder<TContext>
|
|
16
16
|
) {
|
|
17
|
-
return new CompiledQuery<
|
|
17
|
+
return new CompiledQuery<
|
|
18
|
+
ResultsFromContext<TContext> & { _key?: string | number }
|
|
19
|
+
>(queryBuilder)
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
export class CompiledQuery<TResults extends object = Record<string, unknown>> {
|
|
@@ -67,24 +69,21 @@ export class CompiledQuery<TResults extends object = Record<string, unknown>> {
|
|
|
67
69
|
return acc
|
|
68
70
|
}, new Map<unknown, { deletes: number; inserts: number; value: TResults }>())
|
|
69
71
|
.forEach((changes, rawKey) => {
|
|
70
|
-
const key = (rawKey as any).toString()
|
|
71
72
|
const { deletes, inserts, value } = changes
|
|
73
|
+
const valueWithKey = { ...value, _key: rawKey }
|
|
72
74
|
if (inserts && !deletes) {
|
|
73
75
|
write({
|
|
74
|
-
|
|
75
|
-
value: value,
|
|
76
|
+
value: valueWithKey,
|
|
76
77
|
type: `insert`,
|
|
77
78
|
})
|
|
78
79
|
} else if (inserts >= deletes) {
|
|
79
80
|
write({
|
|
80
|
-
|
|
81
|
-
value: value,
|
|
81
|
+
value: valueWithKey,
|
|
82
82
|
type: `update`,
|
|
83
83
|
})
|
|
84
84
|
} else if (deletes > 0) {
|
|
85
85
|
write({
|
|
86
|
-
|
|
87
|
-
value: value,
|
|
86
|
+
value: valueWithKey,
|
|
88
87
|
type: `delete`,
|
|
89
88
|
})
|
|
90
89
|
}
|
|
@@ -100,6 +99,9 @@ export class CompiledQuery<TResults extends object = Record<string, unknown>> {
|
|
|
100
99
|
this.inputs = inputs
|
|
101
100
|
this.resultCollection = new Collection<TResults>({
|
|
102
101
|
id: crypto.randomUUID(), // TODO: remove when we don't require any more
|
|
102
|
+
getId: (val) => {
|
|
103
|
+
return (val as any)._key
|
|
104
|
+
},
|
|
103
105
|
sync: {
|
|
104
106
|
sync,
|
|
105
107
|
},
|
|
@@ -110,18 +112,23 @@ export class CompiledQuery<TResults extends object = Record<string, unknown>> {
|
|
|
110
112
|
return this.resultCollection
|
|
111
113
|
}
|
|
112
114
|
|
|
113
|
-
private sendChangesToInput(
|
|
115
|
+
private sendChangesToInput(
|
|
116
|
+
inputKey: string,
|
|
117
|
+
changes: Array<ChangeMessage>,
|
|
118
|
+
getId: (item: ChangeMessage[`value`]) => any
|
|
119
|
+
) {
|
|
114
120
|
const input = this.inputs[inputKey]!
|
|
115
121
|
const multiSetArray: MultiSetArray<unknown> = []
|
|
116
122
|
for (const change of changes) {
|
|
123
|
+
const key = getId(change.value)
|
|
117
124
|
if (change.type === `insert`) {
|
|
118
|
-
multiSetArray.push([change.value, 1])
|
|
125
|
+
multiSetArray.push([[key, change.value], 1])
|
|
119
126
|
} else if (change.type === `update`) {
|
|
120
|
-
multiSetArray.push([change.previousValue, -1])
|
|
121
|
-
multiSetArray.push([change.value, 1])
|
|
127
|
+
multiSetArray.push([[key, change.previousValue], -1])
|
|
128
|
+
multiSetArray.push([[key, change.value], 1])
|
|
122
129
|
} else {
|
|
123
130
|
// change.type === `delete`
|
|
124
|
-
multiSetArray.push([change.value, -1])
|
|
131
|
+
multiSetArray.push([[key, change.value], -1])
|
|
125
132
|
}
|
|
126
133
|
}
|
|
127
134
|
input.sendData(this.version, new MultiSet(multiSetArray))
|
|
@@ -155,7 +162,11 @@ export class CompiledQuery<TResults extends object = Record<string, unknown>> {
|
|
|
155
162
|
|
|
156
163
|
batch(() => {
|
|
157
164
|
Object.entries(this.inputCollections).forEach(([key, collection]) => {
|
|
158
|
-
this.sendChangesToInput(
|
|
165
|
+
this.sendChangesToInput(
|
|
166
|
+
key,
|
|
167
|
+
collection.currentStateAsChanges(),
|
|
168
|
+
collection.config.getId
|
|
169
|
+
)
|
|
159
170
|
})
|
|
160
171
|
this.incrementVersion()
|
|
161
172
|
this.sendFrontierToAllInputs()
|
|
@@ -166,7 +177,11 @@ export class CompiledQuery<TResults extends object = Record<string, unknown>> {
|
|
|
166
177
|
fn: () => {
|
|
167
178
|
batch(() => {
|
|
168
179
|
Object.entries(this.inputCollections).forEach(([key, collection]) => {
|
|
169
|
-
this.sendChangesToInput(
|
|
180
|
+
this.sendChangesToInput(
|
|
181
|
+
key,
|
|
182
|
+
collection.derivedChanges.state,
|
|
183
|
+
collection.config.getId
|
|
184
|
+
)
|
|
170
185
|
})
|
|
171
186
|
this.incrementVersion()
|
|
172
187
|
this.sendFrontierToAllInputs()
|
package/src/query/evaluators.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { evaluateOperandOnNamespacedRow } from "./extractors.js"
|
|
2
2
|
import { compareValues, convertLikeToRegex, isValueInArray } from "./utils.js"
|
|
3
3
|
import type {
|
|
4
4
|
Comparator,
|
|
@@ -6,13 +6,41 @@ import type {
|
|
|
6
6
|
ConditionOperand,
|
|
7
7
|
LogicalOperator,
|
|
8
8
|
SimpleCondition,
|
|
9
|
+
Where,
|
|
10
|
+
WhereCallback,
|
|
9
11
|
} from "./schema.js"
|
|
12
|
+
import type { NamespacedRow } from "../types.js"
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Evaluates a Where clause (which is always an array of conditions and/or callbacks) against a nested row structure
|
|
16
|
+
*/
|
|
17
|
+
export function evaluateWhereOnNamespacedRow(
|
|
18
|
+
namespacedRow: NamespacedRow,
|
|
19
|
+
where: Where,
|
|
20
|
+
mainTableAlias?: string,
|
|
21
|
+
joinedTableAlias?: string
|
|
22
|
+
): boolean {
|
|
23
|
+
// Where is always an array of conditions and/or callbacks
|
|
24
|
+
// Evaluate all items and combine with AND logic
|
|
25
|
+
return where.every((item) => {
|
|
26
|
+
if (typeof item === `function`) {
|
|
27
|
+
return (item as WhereCallback)(namespacedRow)
|
|
28
|
+
} else {
|
|
29
|
+
return evaluateConditionOnNamespacedRow(
|
|
30
|
+
namespacedRow,
|
|
31
|
+
item as Condition,
|
|
32
|
+
mainTableAlias,
|
|
33
|
+
joinedTableAlias
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
})
|
|
37
|
+
}
|
|
10
38
|
|
|
11
39
|
/**
|
|
12
40
|
* Evaluates a condition against a nested row structure
|
|
13
41
|
*/
|
|
14
|
-
export function
|
|
15
|
-
|
|
42
|
+
export function evaluateConditionOnNamespacedRow(
|
|
43
|
+
namespacedRow: NamespacedRow,
|
|
16
44
|
condition: Condition,
|
|
17
45
|
mainTableAlias?: string,
|
|
18
46
|
joinedTableAlias?: string
|
|
@@ -20,8 +48,8 @@ export function evaluateConditionOnNestedRow(
|
|
|
20
48
|
// Handle simple conditions with exactly 3 elements
|
|
21
49
|
if (condition.length === 3 && !Array.isArray(condition[0])) {
|
|
22
50
|
const [left, comparator, right] = condition as SimpleCondition
|
|
23
|
-
return
|
|
24
|
-
|
|
51
|
+
return evaluateSimpleConditionOnNamespacedRow(
|
|
52
|
+
namespacedRow,
|
|
25
53
|
left,
|
|
26
54
|
comparator,
|
|
27
55
|
right,
|
|
@@ -38,8 +66,8 @@ export function evaluateConditionOnNestedRow(
|
|
|
38
66
|
![`and`, `or`].includes(condition[1] as string)
|
|
39
67
|
) {
|
|
40
68
|
// Start with the first condition (first 3 elements)
|
|
41
|
-
let result =
|
|
42
|
-
|
|
69
|
+
let result = evaluateSimpleConditionOnNamespacedRow(
|
|
70
|
+
namespacedRow,
|
|
43
71
|
condition[0],
|
|
44
72
|
condition[1] as Comparator,
|
|
45
73
|
condition[2],
|
|
@@ -53,8 +81,8 @@ export function evaluateConditionOnNestedRow(
|
|
|
53
81
|
|
|
54
82
|
// Make sure we have a complete condition to evaluate
|
|
55
83
|
if (i + 3 <= condition.length) {
|
|
56
|
-
const nextResult =
|
|
57
|
-
|
|
84
|
+
const nextResult = evaluateSimpleConditionOnNamespacedRow(
|
|
85
|
+
namespacedRow,
|
|
58
86
|
condition[i + 1],
|
|
59
87
|
condition[i + 2] as Comparator,
|
|
60
88
|
condition[i + 3],
|
|
@@ -78,8 +106,8 @@ export function evaluateConditionOnNestedRow(
|
|
|
78
106
|
// Handle nested composite conditions where the first element is an array
|
|
79
107
|
if (condition.length > 0 && Array.isArray(condition[0])) {
|
|
80
108
|
// Start with the first condition
|
|
81
|
-
let result =
|
|
82
|
-
|
|
109
|
+
let result = evaluateConditionOnNamespacedRow(
|
|
110
|
+
namespacedRow,
|
|
83
111
|
condition[0] as Condition,
|
|
84
112
|
mainTableAlias,
|
|
85
113
|
joinedTableAlias
|
|
@@ -96,8 +124,8 @@ export function evaluateConditionOnNestedRow(
|
|
|
96
124
|
if (operator === `and`) {
|
|
97
125
|
result =
|
|
98
126
|
result &&
|
|
99
|
-
|
|
100
|
-
|
|
127
|
+
evaluateConditionOnNamespacedRow(
|
|
128
|
+
namespacedRow,
|
|
101
129
|
nextCondition,
|
|
102
130
|
mainTableAlias,
|
|
103
131
|
joinedTableAlias
|
|
@@ -106,8 +134,8 @@ export function evaluateConditionOnNestedRow(
|
|
|
106
134
|
// logicalOp === `or`
|
|
107
135
|
result =
|
|
108
136
|
result ||
|
|
109
|
-
|
|
110
|
-
|
|
137
|
+
evaluateConditionOnNamespacedRow(
|
|
138
|
+
namespacedRow,
|
|
111
139
|
nextCondition,
|
|
112
140
|
mainTableAlias,
|
|
113
141
|
joinedTableAlias
|
|
@@ -125,23 +153,23 @@ export function evaluateConditionOnNestedRow(
|
|
|
125
153
|
/**
|
|
126
154
|
* Evaluates a simple condition against a nested row structure
|
|
127
155
|
*/
|
|
128
|
-
export function
|
|
129
|
-
|
|
156
|
+
export function evaluateSimpleConditionOnNamespacedRow(
|
|
157
|
+
namespacedRow: Record<string, unknown>,
|
|
130
158
|
left: ConditionOperand,
|
|
131
159
|
comparator: Comparator,
|
|
132
160
|
right: ConditionOperand,
|
|
133
161
|
mainTableAlias?: string,
|
|
134
162
|
joinedTableAlias?: string
|
|
135
163
|
): boolean {
|
|
136
|
-
const leftValue =
|
|
137
|
-
|
|
164
|
+
const leftValue = evaluateOperandOnNamespacedRow(
|
|
165
|
+
namespacedRow,
|
|
138
166
|
left,
|
|
139
167
|
mainTableAlias,
|
|
140
168
|
joinedTableAlias
|
|
141
169
|
)
|
|
142
170
|
|
|
143
|
-
const rightValue =
|
|
144
|
-
|
|
171
|
+
const rightValue = evaluateOperandOnNamespacedRow(
|
|
172
|
+
namespacedRow,
|
|
145
173
|
right,
|
|
146
174
|
mainTableAlias,
|
|
147
175
|
joinedTableAlias
|
package/src/query/extractors.ts
CHANGED
|
@@ -3,14 +3,14 @@ import type { AllowedFunctionName, ConditionOperand } from "./schema.js"
|
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Extracts a value from a nested row structure
|
|
6
|
-
* @param
|
|
6
|
+
* @param namespacedRow The nested row structure
|
|
7
7
|
* @param columnRef The column reference (may include table.column format)
|
|
8
8
|
* @param mainTableAlias The main table alias to check first for columns without table reference
|
|
9
9
|
* @param joinedTableAlias The joined table alias to check second for columns without table reference
|
|
10
10
|
* @returns The extracted value or undefined if not found
|
|
11
11
|
*/
|
|
12
|
-
export function
|
|
13
|
-
|
|
12
|
+
export function extractValueFromNamespacedRow(
|
|
13
|
+
namespacedRow: Record<string, unknown>,
|
|
14
14
|
columnRef: string,
|
|
15
15
|
mainTableAlias?: string,
|
|
16
16
|
joinedTableAlias?: string
|
|
@@ -20,7 +20,7 @@ export function extractValueFromNestedRow(
|
|
|
20
20
|
const [tableAlias, colName] = columnRef.split(`.`) as [string, string]
|
|
21
21
|
|
|
22
22
|
// Get the table data
|
|
23
|
-
const tableData =
|
|
23
|
+
const tableData = namespacedRow[tableAlias] as
|
|
24
24
|
| Record<string, unknown>
|
|
25
25
|
| null
|
|
26
26
|
| undefined
|
|
@@ -34,16 +34,19 @@ export function extractValueFromNestedRow(
|
|
|
34
34
|
return value
|
|
35
35
|
} else {
|
|
36
36
|
// If no table is specified, first try to find in the main table if provided
|
|
37
|
-
if (mainTableAlias &&
|
|
38
|
-
const mainTableData =
|
|
37
|
+
if (mainTableAlias && namespacedRow[mainTableAlias]) {
|
|
38
|
+
const mainTableData = namespacedRow[mainTableAlias] as Record<
|
|
39
|
+
string,
|
|
40
|
+
unknown
|
|
41
|
+
>
|
|
39
42
|
if (typeof mainTableData === `object` && columnRef in mainTableData) {
|
|
40
43
|
return mainTableData[columnRef]
|
|
41
44
|
}
|
|
42
45
|
}
|
|
43
46
|
|
|
44
47
|
// Then try the joined table if provided
|
|
45
|
-
if (joinedTableAlias &&
|
|
46
|
-
const joinedTableData =
|
|
48
|
+
if (joinedTableAlias && namespacedRow[joinedTableAlias]) {
|
|
49
|
+
const joinedTableData = namespacedRow[joinedTableAlias] as Record<
|
|
47
50
|
string,
|
|
48
51
|
unknown
|
|
49
52
|
>
|
|
@@ -53,7 +56,7 @@ export function extractValueFromNestedRow(
|
|
|
53
56
|
}
|
|
54
57
|
|
|
55
58
|
// If not found in main or joined table, try to find the column in any table
|
|
56
|
-
for (const [_tableAlias, tableData] of Object.entries(
|
|
59
|
+
for (const [_tableAlias, tableData] of Object.entries(namespacedRow)) {
|
|
57
60
|
if (
|
|
58
61
|
tableData &&
|
|
59
62
|
typeof tableData === `object` &&
|
|
@@ -69,8 +72,8 @@ export function extractValueFromNestedRow(
|
|
|
69
72
|
/**
|
|
70
73
|
* Evaluates an operand against a nested row structure
|
|
71
74
|
*/
|
|
72
|
-
export function
|
|
73
|
-
|
|
75
|
+
export function evaluateOperandOnNamespacedRow(
|
|
76
|
+
namespacedRow: Record<string, unknown>,
|
|
74
77
|
operand: ConditionOperand,
|
|
75
78
|
mainTableAlias?: string,
|
|
76
79
|
joinedTableAlias?: string
|
|
@@ -78,8 +81,8 @@ export function evaluateOperandOnNestedRow(
|
|
|
78
81
|
// Handle column references
|
|
79
82
|
if (typeof operand === `string` && operand.startsWith(`@`)) {
|
|
80
83
|
const columnRef = operand.substring(1)
|
|
81
|
-
return
|
|
82
|
-
|
|
84
|
+
return extractValueFromNamespacedRow(
|
|
85
|
+
namespacedRow,
|
|
83
86
|
columnRef,
|
|
84
87
|
mainTableAlias,
|
|
85
88
|
joinedTableAlias
|
|
@@ -92,8 +95,8 @@ export function evaluateOperandOnNestedRow(
|
|
|
92
95
|
|
|
93
96
|
if (typeof colRef === `string`) {
|
|
94
97
|
// First try to extract from nested row structure
|
|
95
|
-
const nestedValue =
|
|
96
|
-
|
|
98
|
+
const nestedValue = extractValueFromNamespacedRow(
|
|
99
|
+
namespacedRow,
|
|
97
100
|
colRef,
|
|
98
101
|
mainTableAlias,
|
|
99
102
|
joinedTableAlias
|
|
@@ -101,8 +104,8 @@ export function evaluateOperandOnNestedRow(
|
|
|
101
104
|
|
|
102
105
|
// If not found in nested structure, check if it's a direct property of the row
|
|
103
106
|
// This is important for HAVING clauses that reference aggregated values
|
|
104
|
-
if (nestedValue === undefined && colRef in
|
|
105
|
-
return
|
|
107
|
+
if (nestedValue === undefined && colRef in namespacedRow) {
|
|
108
|
+
return namespacedRow[colRef]
|
|
106
109
|
}
|
|
107
110
|
|
|
108
111
|
return nestedValue
|
|
@@ -121,15 +124,15 @@ export function evaluateOperandOnNestedRow(
|
|
|
121
124
|
// If the arguments are a reference or another expression, evaluate them first
|
|
122
125
|
const evaluatedArgs = Array.isArray(args)
|
|
123
126
|
? args.map((arg) =>
|
|
124
|
-
|
|
125
|
-
|
|
127
|
+
evaluateOperandOnNamespacedRow(
|
|
128
|
+
namespacedRow,
|
|
126
129
|
arg as ConditionOperand,
|
|
127
130
|
mainTableAlias,
|
|
128
131
|
joinedTableAlias
|
|
129
132
|
)
|
|
130
133
|
)
|
|
131
|
-
:
|
|
132
|
-
|
|
134
|
+
: evaluateOperandOnNamespacedRow(
|
|
135
|
+
namespacedRow,
|
|
133
136
|
args as ConditionOperand,
|
|
134
137
|
mainTableAlias,
|
|
135
138
|
joinedTableAlias
|
package/src/query/group-by.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { groupBy, groupByOperators
|
|
1
|
+
import { groupBy, groupByOperators } from "@electric-sql/d2ts"
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
evaluateOperandOnNamespacedRow,
|
|
4
|
+
extractValueFromNamespacedRow,
|
|
5
5
|
} from "./extractors"
|
|
6
6
|
import { isAggregateFunctionCall } from "./utils"
|
|
7
7
|
import type { ConditionOperand, FunctionCall, Query } from "./schema"
|
|
8
|
-
import type {
|
|
8
|
+
import type { NamespacedAndKeyedStream } from "../types.js"
|
|
9
9
|
|
|
10
10
|
const { sum, count, avg, min, max, median, mode } = groupByOperators
|
|
11
11
|
|
|
@@ -13,7 +13,7 @@ const { sum, count, avg, min, max, median, mode } = groupByOperators
|
|
|
13
13
|
* Process the groupBy clause in a D2QL query
|
|
14
14
|
*/
|
|
15
15
|
export function processGroupBy(
|
|
16
|
-
pipeline:
|
|
16
|
+
pipeline: NamespacedAndKeyedStream,
|
|
17
17
|
query: Query,
|
|
18
18
|
mainTableAlias: string
|
|
19
19
|
) {
|
|
@@ -23,7 +23,10 @@ export function processGroupBy(
|
|
|
23
23
|
: [query.groupBy]
|
|
24
24
|
|
|
25
25
|
// Create a key extractor function for the groupBy operator
|
|
26
|
-
const keyExtractor = (
|
|
26
|
+
const keyExtractor = ([_oldKey, namespacedRow]: [
|
|
27
|
+
string,
|
|
28
|
+
Record<string, unknown>,
|
|
29
|
+
]) => {
|
|
27
30
|
const key: Record<string, unknown> = {}
|
|
28
31
|
|
|
29
32
|
// Extract each groupBy column value
|
|
@@ -34,8 +37,8 @@ export function processGroupBy(
|
|
|
34
37
|
? columnRef.split(`.`)[1]
|
|
35
38
|
: columnRef
|
|
36
39
|
|
|
37
|
-
key[columnName!] =
|
|
38
|
-
|
|
40
|
+
key[columnName!] = extractValueFromNamespacedRow(
|
|
41
|
+
namespacedRow,
|
|
39
42
|
columnRef,
|
|
40
43
|
mainTableAlias
|
|
41
44
|
)
|
|
@@ -48,6 +51,10 @@ export function processGroupBy(
|
|
|
48
51
|
// Create aggregate functions for any aggregated columns in the SELECT clause
|
|
49
52
|
const aggregates: Record<string, any> = {}
|
|
50
53
|
|
|
54
|
+
if (!query.select) {
|
|
55
|
+
throw new Error(`SELECT clause is required for GROUP BY`)
|
|
56
|
+
}
|
|
57
|
+
|
|
51
58
|
// Scan the SELECT clause for aggregate functions
|
|
52
59
|
for (const item of query.select) {
|
|
53
60
|
if (typeof item === `object`) {
|
|
@@ -73,15 +80,7 @@ export function processGroupBy(
|
|
|
73
80
|
|
|
74
81
|
// Apply the groupBy operator if we have any aggregates
|
|
75
82
|
if (Object.keys(aggregates).length > 0) {
|
|
76
|
-
pipeline = pipeline.pipe(
|
|
77
|
-
groupBy(keyExtractor, aggregates),
|
|
78
|
-
// Convert KeyValue<string, ResultType> to Record<string, unknown>
|
|
79
|
-
map(([_key, value]) => {
|
|
80
|
-
// After groupBy, the value already contains both the key fields and aggregate results
|
|
81
|
-
// We need to return it as is, not wrapped in a nested structure
|
|
82
|
-
return value as Record<string, unknown>
|
|
83
|
-
})
|
|
84
|
-
)
|
|
83
|
+
pipeline = pipeline.pipe(groupBy(keyExtractor, aggregates))
|
|
85
84
|
}
|
|
86
85
|
|
|
87
86
|
return pipeline
|
|
@@ -96,17 +95,20 @@ export function getAggregateFunction(
|
|
|
96
95
|
mainTableAlias: string
|
|
97
96
|
) {
|
|
98
97
|
// Create a value extractor function for the column to aggregate
|
|
99
|
-
const valueExtractor = (
|
|
98
|
+
const valueExtractor = ([_oldKey, namespacedRow]: [
|
|
99
|
+
string,
|
|
100
|
+
Record<string, unknown>,
|
|
101
|
+
]) => {
|
|
100
102
|
let value: unknown
|
|
101
103
|
if (typeof columnRef === `string` && columnRef.startsWith(`@`)) {
|
|
102
|
-
value =
|
|
103
|
-
|
|
104
|
+
value = extractValueFromNamespacedRow(
|
|
105
|
+
namespacedRow,
|
|
104
106
|
columnRef.substring(1),
|
|
105
107
|
mainTableAlias
|
|
106
108
|
)
|
|
107
109
|
} else {
|
|
108
|
-
value =
|
|
109
|
-
|
|
110
|
+
value = evaluateOperandOnNamespacedRow(
|
|
111
|
+
namespacedRow,
|
|
110
112
|
columnRef as ConditionOperand,
|
|
111
113
|
mainTableAlias
|
|
112
114
|
)
|