@tanstack/db 0.0.1
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/README.md +37 -0
- package/dist/cjs/SortedMap.cjs +140 -0
- package/dist/cjs/SortedMap.cjs.map +1 -0
- package/dist/cjs/SortedMap.d.cts +91 -0
- package/dist/cjs/collection.cjs +597 -0
- package/dist/cjs/collection.cjs.map +1 -0
- package/dist/cjs/collection.d.cts +176 -0
- package/dist/cjs/deferred.cjs +25 -0
- package/dist/cjs/deferred.cjs.map +1 -0
- package/dist/cjs/deferred.d.cts +20 -0
- package/dist/cjs/errors.cjs +10 -0
- package/dist/cjs/errors.cjs.map +1 -0
- package/dist/cjs/errors.d.cts +3 -0
- package/dist/cjs/index.cjs +33 -0
- package/dist/cjs/index.cjs.map +1 -0
- package/dist/cjs/index.d.cts +9 -0
- package/dist/cjs/proxy.cjs +654 -0
- package/dist/cjs/proxy.cjs.map +1 -0
- package/dist/cjs/proxy.d.cts +59 -0
- package/dist/cjs/query/compiled-query.cjs +162 -0
- package/dist/cjs/query/compiled-query.cjs.map +1 -0
- package/dist/cjs/query/compiled-query.d.cts +22 -0
- package/dist/cjs/query/evaluators.cjs +146 -0
- package/dist/cjs/query/evaluators.cjs.map +1 -0
- package/dist/cjs/query/evaluators.d.cts +9 -0
- package/dist/cjs/query/extractors.cjs +122 -0
- package/dist/cjs/query/extractors.cjs.map +1 -0
- package/dist/cjs/query/extractors.d.cts +22 -0
- package/dist/cjs/query/functions.cjs +152 -0
- package/dist/cjs/query/functions.cjs.map +1 -0
- package/dist/cjs/query/functions.d.cts +21 -0
- package/dist/cjs/query/group-by.cjs +91 -0
- package/dist/cjs/query/group-by.cjs.map +1 -0
- package/dist/cjs/query/group-by.d.cts +40 -0
- package/dist/cjs/query/index.d.cts +5 -0
- package/dist/cjs/query/joins.cjs +155 -0
- package/dist/cjs/query/joins.cjs.map +1 -0
- package/dist/cjs/query/joins.d.cts +14 -0
- package/dist/cjs/query/key-by.cjs +43 -0
- package/dist/cjs/query/key-by.cjs.map +1 -0
- package/dist/cjs/query/key-by.d.cts +3 -0
- package/dist/cjs/query/order-by.cjs +229 -0
- package/dist/cjs/query/order-by.cjs.map +1 -0
- package/dist/cjs/query/order-by.d.cts +3 -0
- package/dist/cjs/query/pipeline-compiler.cjs +94 -0
- package/dist/cjs/query/pipeline-compiler.cjs.map +1 -0
- package/dist/cjs/query/pipeline-compiler.d.cts +9 -0
- package/dist/cjs/query/query-builder.cjs +314 -0
- package/dist/cjs/query/query-builder.cjs.map +1 -0
- package/dist/cjs/query/query-builder.d.cts +219 -0
- package/dist/cjs/query/schema.d.cts +98 -0
- package/dist/cjs/query/select.cjs +107 -0
- package/dist/cjs/query/select.cjs.map +1 -0
- package/dist/cjs/query/select.d.cts +3 -0
- package/dist/cjs/query/types.d.cts +188 -0
- package/dist/cjs/query/utils.cjs +154 -0
- package/dist/cjs/query/utils.cjs.map +1 -0
- package/dist/cjs/query/utils.d.cts +37 -0
- package/dist/cjs/transactions.cjs +137 -0
- package/dist/cjs/transactions.cjs.map +1 -0
- package/dist/cjs/transactions.d.cts +27 -0
- package/dist/cjs/types.d.cts +94 -0
- package/dist/cjs/utils.cjs +17 -0
- package/dist/cjs/utils.cjs.map +1 -0
- package/dist/cjs/utils.d.cts +3 -0
- package/dist/esm/SortedMap.d.ts +91 -0
- package/dist/esm/SortedMap.js +140 -0
- package/dist/esm/SortedMap.js.map +1 -0
- package/dist/esm/collection.d.ts +176 -0
- package/dist/esm/collection.js +597 -0
- package/dist/esm/collection.js.map +1 -0
- package/dist/esm/deferred.d.ts +20 -0
- package/dist/esm/deferred.js +25 -0
- package/dist/esm/deferred.js.map +1 -0
- package/dist/esm/errors.d.ts +3 -0
- package/dist/esm/errors.js +10 -0
- package/dist/esm/errors.js.map +1 -0
- package/dist/esm/index.d.ts +9 -0
- package/dist/esm/index.js +33 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/proxy.d.ts +59 -0
- package/dist/esm/proxy.js +654 -0
- package/dist/esm/proxy.js.map +1 -0
- package/dist/esm/query/compiled-query.d.ts +22 -0
- package/dist/esm/query/compiled-query.js +162 -0
- package/dist/esm/query/compiled-query.js.map +1 -0
- package/dist/esm/query/evaluators.d.ts +9 -0
- package/dist/esm/query/evaluators.js +146 -0
- package/dist/esm/query/evaluators.js.map +1 -0
- package/dist/esm/query/extractors.d.ts +22 -0
- package/dist/esm/query/extractors.js +122 -0
- package/dist/esm/query/extractors.js.map +1 -0
- package/dist/esm/query/functions.d.ts +21 -0
- package/dist/esm/query/functions.js +152 -0
- package/dist/esm/query/functions.js.map +1 -0
- package/dist/esm/query/group-by.d.ts +40 -0
- package/dist/esm/query/group-by.js +91 -0
- package/dist/esm/query/group-by.js.map +1 -0
- package/dist/esm/query/index.d.ts +5 -0
- package/dist/esm/query/joins.d.ts +14 -0
- package/dist/esm/query/joins.js +155 -0
- package/dist/esm/query/joins.js.map +1 -0
- package/dist/esm/query/key-by.d.ts +3 -0
- package/dist/esm/query/key-by.js +43 -0
- package/dist/esm/query/key-by.js.map +1 -0
- package/dist/esm/query/order-by.d.ts +3 -0
- package/dist/esm/query/order-by.js +229 -0
- package/dist/esm/query/order-by.js.map +1 -0
- package/dist/esm/query/pipeline-compiler.d.ts +9 -0
- package/dist/esm/query/pipeline-compiler.js +94 -0
- package/dist/esm/query/pipeline-compiler.js.map +1 -0
- package/dist/esm/query/query-builder.d.ts +219 -0
- package/dist/esm/query/query-builder.js +314 -0
- package/dist/esm/query/query-builder.js.map +1 -0
- package/dist/esm/query/schema.d.ts +98 -0
- package/dist/esm/query/select.d.ts +3 -0
- package/dist/esm/query/select.js +107 -0
- package/dist/esm/query/select.js.map +1 -0
- package/dist/esm/query/types.d.ts +188 -0
- package/dist/esm/query/utils.d.ts +37 -0
- package/dist/esm/query/utils.js +154 -0
- package/dist/esm/query/utils.js.map +1 -0
- package/dist/esm/transactions.d.ts +27 -0
- package/dist/esm/transactions.js +137 -0
- package/dist/esm/transactions.js.map +1 -0
- package/dist/esm/types.d.ts +94 -0
- package/dist/esm/utils.d.ts +3 -0
- package/dist/esm/utils.js +17 -0
- package/dist/esm/utils.js.map +1 -0
- package/package.json +57 -0
- package/src/SortedMap.ts +163 -0
- package/src/collection.ts +919 -0
- package/src/deferred.ts +47 -0
- package/src/errors.ts +6 -0
- package/src/index.ts +12 -0
- package/src/proxy.ts +1104 -0
- package/src/query/compiled-query.ts +193 -0
- package/src/query/evaluators.ts +222 -0
- package/src/query/extractors.ts +211 -0
- package/src/query/functions.ts +297 -0
- package/src/query/group-by.ts +137 -0
- package/src/query/index.ts +5 -0
- package/src/query/joins.ts +247 -0
- package/src/query/key-by.ts +61 -0
- package/src/query/order-by.ts +312 -0
- package/src/query/pipeline-compiler.ts +152 -0
- package/src/query/query-builder.ts +898 -0
- package/src/query/schema.ts +255 -0
- package/src/query/select.ts +173 -0
- package/src/query/types.ts +417 -0
- package/src/query/utils.ts +245 -0
- package/src/transactions.ts +198 -0
- package/src/types.ts +125 -0
- package/src/utils.ts +15 -0
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { orderByWithIndex, map, topKWithIndex, orderByWithFractionalIndex, topKWithFractionalIndex, orderBy, topK } from "@electric-sql/d2ts";
|
|
2
|
+
import { evaluateOperandOnNestedRow } from "./extractors.js";
|
|
3
|
+
import { isOrderIndexFunctionCall } from "./utils.js";
|
|
4
|
+
function processOrderBy(resultPipeline, query, mainTableAlias) {
|
|
5
|
+
let hasOrderIndexColumn = false;
|
|
6
|
+
let orderIndexType = `numeric`;
|
|
7
|
+
let orderIndexAlias = ``;
|
|
8
|
+
for (const item of query.select) {
|
|
9
|
+
if (typeof item === `object`) {
|
|
10
|
+
for (const [alias, expr] of Object.entries(item)) {
|
|
11
|
+
if (typeof expr === `object` && isOrderIndexFunctionCall(expr)) {
|
|
12
|
+
hasOrderIndexColumn = true;
|
|
13
|
+
orderIndexAlias = alias;
|
|
14
|
+
orderIndexType = getOrderIndexType(expr);
|
|
15
|
+
break;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
if (hasOrderIndexColumn) break;
|
|
20
|
+
}
|
|
21
|
+
const orderByItems = [];
|
|
22
|
+
if (typeof query.orderBy === `string`) {
|
|
23
|
+
orderByItems.push({
|
|
24
|
+
operand: query.orderBy,
|
|
25
|
+
direction: `asc`
|
|
26
|
+
});
|
|
27
|
+
} else if (Array.isArray(query.orderBy)) {
|
|
28
|
+
for (const item of query.orderBy) {
|
|
29
|
+
if (typeof item === `string`) {
|
|
30
|
+
orderByItems.push({
|
|
31
|
+
operand: item,
|
|
32
|
+
direction: `asc`
|
|
33
|
+
});
|
|
34
|
+
} else if (typeof item === `object`) {
|
|
35
|
+
for (const [column, direction] of Object.entries(item)) {
|
|
36
|
+
orderByItems.push({
|
|
37
|
+
operand: column,
|
|
38
|
+
direction
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
} else if (typeof query.orderBy === `object`) {
|
|
44
|
+
for (const [column, direction] of Object.entries(query.orderBy)) {
|
|
45
|
+
orderByItems.push({
|
|
46
|
+
operand: column,
|
|
47
|
+
direction
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const valueExtractor = (value) => {
|
|
52
|
+
const row = value;
|
|
53
|
+
const nestedRow = { [mainTableAlias]: row };
|
|
54
|
+
if (orderByItems.length > 1) {
|
|
55
|
+
return orderByItems.map((item) => {
|
|
56
|
+
const val = evaluateOperandOnNestedRow(
|
|
57
|
+
nestedRow,
|
|
58
|
+
item.operand,
|
|
59
|
+
mainTableAlias
|
|
60
|
+
);
|
|
61
|
+
return item.direction === `desc` && typeof val === `number` ? -val : item.direction === `desc` && typeof val === `string` ? String.fromCharCode(
|
|
62
|
+
...[...val].map((c) => 65535 - c.charCodeAt(0))
|
|
63
|
+
) : val;
|
|
64
|
+
});
|
|
65
|
+
} else if (orderByItems.length === 1) {
|
|
66
|
+
const item = orderByItems[0];
|
|
67
|
+
const val = evaluateOperandOnNestedRow(
|
|
68
|
+
nestedRow,
|
|
69
|
+
item.operand,
|
|
70
|
+
mainTableAlias
|
|
71
|
+
);
|
|
72
|
+
return item.direction === `desc` && typeof val === `number` ? -val : item.direction === `desc` && typeof val === `string` ? String.fromCharCode(
|
|
73
|
+
...[...val].map((c) => 65535 - c.charCodeAt(0))
|
|
74
|
+
) : val;
|
|
75
|
+
}
|
|
76
|
+
return null;
|
|
77
|
+
};
|
|
78
|
+
const comparator = (a, b) => {
|
|
79
|
+
if (typeof a === `number` && typeof b === `number`) {
|
|
80
|
+
return a - b;
|
|
81
|
+
}
|
|
82
|
+
if (typeof a === `string` && typeof b === `string`) {
|
|
83
|
+
return a.localeCompare(b);
|
|
84
|
+
}
|
|
85
|
+
if (typeof a === `boolean` && typeof b === `boolean`) {
|
|
86
|
+
return a === b ? 0 : a ? 1 : -1;
|
|
87
|
+
}
|
|
88
|
+
if (a instanceof Date && b instanceof Date) {
|
|
89
|
+
return a.getTime() - b.getTime();
|
|
90
|
+
}
|
|
91
|
+
if (a === null || b === null) {
|
|
92
|
+
return 0;
|
|
93
|
+
}
|
|
94
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
95
|
+
for (let i = 0; i < Math.min(a.length, b.length); i++) {
|
|
96
|
+
const aVal = a[i];
|
|
97
|
+
const bVal = b[i];
|
|
98
|
+
let result;
|
|
99
|
+
if (typeof aVal === `boolean` && typeof bVal === `boolean`) {
|
|
100
|
+
result = aVal === bVal ? 0 : aVal ? 1 : -1;
|
|
101
|
+
} else if (typeof aVal === `number` && typeof bVal === `number`) {
|
|
102
|
+
result = aVal - bVal;
|
|
103
|
+
} else if (typeof aVal === `string` && typeof bVal === `string`) {
|
|
104
|
+
result = aVal.localeCompare(bVal);
|
|
105
|
+
} else {
|
|
106
|
+
result = comparator(aVal, bVal);
|
|
107
|
+
}
|
|
108
|
+
if (result !== 0) {
|
|
109
|
+
return result;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return a.length - b.length;
|
|
113
|
+
}
|
|
114
|
+
if (a == null && b == null) {
|
|
115
|
+
return 0;
|
|
116
|
+
}
|
|
117
|
+
return a.toString().localeCompare(b.toString());
|
|
118
|
+
};
|
|
119
|
+
let topKComparator;
|
|
120
|
+
if (!query.keyBy) {
|
|
121
|
+
topKComparator = (a, b) => {
|
|
122
|
+
const aValue = valueExtractor(a);
|
|
123
|
+
const bValue = valueExtractor(b);
|
|
124
|
+
return comparator(aValue, bValue);
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
if (hasOrderIndexColumn) {
|
|
128
|
+
if (orderIndexType === `numeric`) {
|
|
129
|
+
if (query.keyBy) {
|
|
130
|
+
resultPipeline = resultPipeline.pipe(
|
|
131
|
+
orderByWithIndex(valueExtractor, {
|
|
132
|
+
limit: query.limit,
|
|
133
|
+
offset: query.offset,
|
|
134
|
+
comparator
|
|
135
|
+
}),
|
|
136
|
+
map(([key, [value, index]]) => {
|
|
137
|
+
const result = {
|
|
138
|
+
...value,
|
|
139
|
+
[orderIndexAlias]: index
|
|
140
|
+
};
|
|
141
|
+
return [key, result];
|
|
142
|
+
})
|
|
143
|
+
);
|
|
144
|
+
} else {
|
|
145
|
+
resultPipeline = resultPipeline.pipe(
|
|
146
|
+
map((value) => [null, value]),
|
|
147
|
+
topKWithIndex(topKComparator, {
|
|
148
|
+
limit: query.limit,
|
|
149
|
+
offset: query.offset
|
|
150
|
+
}),
|
|
151
|
+
map(([_, [value, index]]) => {
|
|
152
|
+
return {
|
|
153
|
+
...value,
|
|
154
|
+
[orderIndexAlias]: index
|
|
155
|
+
};
|
|
156
|
+
})
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
} else {
|
|
160
|
+
if (query.keyBy) {
|
|
161
|
+
resultPipeline = resultPipeline.pipe(
|
|
162
|
+
orderByWithFractionalIndex(valueExtractor, {
|
|
163
|
+
limit: query.limit,
|
|
164
|
+
offset: query.offset,
|
|
165
|
+
comparator
|
|
166
|
+
}),
|
|
167
|
+
map(([key, [value, index]]) => {
|
|
168
|
+
const result = {
|
|
169
|
+
...value,
|
|
170
|
+
[orderIndexAlias]: index
|
|
171
|
+
};
|
|
172
|
+
return [key, result];
|
|
173
|
+
})
|
|
174
|
+
);
|
|
175
|
+
} else {
|
|
176
|
+
resultPipeline = resultPipeline.pipe(
|
|
177
|
+
map((value) => [null, value]),
|
|
178
|
+
topKWithFractionalIndex(topKComparator, {
|
|
179
|
+
limit: query.limit,
|
|
180
|
+
offset: query.offset
|
|
181
|
+
}),
|
|
182
|
+
map(([_, [value, index]]) => {
|
|
183
|
+
return {
|
|
184
|
+
...value,
|
|
185
|
+
[orderIndexAlias]: index
|
|
186
|
+
};
|
|
187
|
+
})
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
} else {
|
|
192
|
+
if (query.keyBy) {
|
|
193
|
+
resultPipeline = resultPipeline.pipe(
|
|
194
|
+
orderBy(valueExtractor, {
|
|
195
|
+
limit: query.limit,
|
|
196
|
+
offset: query.offset,
|
|
197
|
+
comparator
|
|
198
|
+
})
|
|
199
|
+
);
|
|
200
|
+
} else {
|
|
201
|
+
resultPipeline = resultPipeline.pipe(
|
|
202
|
+
map((value) => [null, value]),
|
|
203
|
+
topK(topKComparator, {
|
|
204
|
+
limit: query.limit,
|
|
205
|
+
offset: query.offset
|
|
206
|
+
}),
|
|
207
|
+
map(([_, value]) => value)
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return resultPipeline;
|
|
212
|
+
}
|
|
213
|
+
function getOrderIndexType(obj) {
|
|
214
|
+
if (!isOrderIndexFunctionCall(obj)) {
|
|
215
|
+
throw new Error(`Not an ORDER_INDEX function call`);
|
|
216
|
+
}
|
|
217
|
+
const arg = obj[`ORDER_INDEX`];
|
|
218
|
+
if (arg === `numeric` || arg === true || arg === `default`) {
|
|
219
|
+
return `numeric`;
|
|
220
|
+
} else if (arg === `fractional`) {
|
|
221
|
+
return `fractional`;
|
|
222
|
+
} else {
|
|
223
|
+
throw new Error(`Invalid ORDER_INDEX type: ` + arg);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
export {
|
|
227
|
+
processOrderBy
|
|
228
|
+
};
|
|
229
|
+
//# sourceMappingURL=order-by.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"order-by.js","sources":["../../../src/query/order-by.ts"],"sourcesContent":["import {\n map,\n orderBy,\n orderByWithFractionalIndex,\n orderByWithIndex,\n topK,\n topKWithFractionalIndex,\n topKWithIndex,\n} from \"@electric-sql/d2ts\"\nimport { evaluateOperandOnNestedRow } from \"./extractors\"\nimport { isOrderIndexFunctionCall } from \"./utils\"\nimport type { ConditionOperand, Query } from \"./schema\"\nimport type { IStreamBuilder } from \"@electric-sql/d2ts\"\n\nexport function processOrderBy(\n resultPipeline: IStreamBuilder<\n Record<string, unknown> | [string | number, Record<string, unknown>]\n >,\n query: Query,\n mainTableAlias: string\n) {\n // Check if any column in the SELECT clause is an ORDER_INDEX function call\n let hasOrderIndexColumn = false\n let orderIndexType: `numeric` | `fractional` = `numeric`\n let orderIndexAlias = ``\n\n // Scan the SELECT clause for ORDER_INDEX functions\n for (const item of query.select) {\n if (typeof item === `object`) {\n for (const [alias, expr] of Object.entries(item)) {\n if (typeof expr === `object` && isOrderIndexFunctionCall(expr)) {\n hasOrderIndexColumn = true\n orderIndexAlias = alias\n orderIndexType = getOrderIndexType(expr)\n break\n }\n }\n }\n if (hasOrderIndexColumn) break\n }\n\n // Normalize orderBy to an array of objects\n const orderByItems: Array<{\n operand: ConditionOperand\n direction: `asc` | `desc`\n }> = []\n\n if (typeof query.orderBy === `string`) {\n // Handle string format: '@column'\n orderByItems.push({\n operand: query.orderBy,\n direction: `asc`,\n })\n } else if (Array.isArray(query.orderBy)) {\n // Handle array format: ['@column1', { '@column2': 'desc' }]\n for (const item of query.orderBy) {\n if (typeof item === `string`) {\n orderByItems.push({\n operand: item,\n direction: `asc`,\n })\n } else if (typeof item === `object`) {\n for (const [column, direction] of Object.entries(item)) {\n orderByItems.push({\n operand: column,\n direction: direction as `asc` | `desc`,\n })\n }\n }\n }\n } else if (typeof query.orderBy === `object`) {\n // Handle object format: { '@column': 'desc' }\n for (const [column, direction] of Object.entries(query.orderBy)) {\n orderByItems.push({\n operand: column,\n direction: direction as `asc` | `desc`,\n })\n }\n }\n\n // Create a value extractor function for the orderBy operator\n const valueExtractor = (value: unknown) => {\n const row = value as Record<string, unknown>\n\n // Create a nested row structure for evaluateOperandOnNestedRow\n const nestedRow: Record<string, unknown> = { [mainTableAlias]: row }\n\n // For multiple orderBy columns, create a composite key\n if (orderByItems.length > 1) {\n return orderByItems.map((item) => {\n const val = evaluateOperandOnNestedRow(\n nestedRow,\n item.operand,\n mainTableAlias\n )\n\n // Reverse the value for 'desc' ordering\n return item.direction === `desc` && typeof val === `number`\n ? -val\n : item.direction === `desc` && typeof val === `string`\n ? String.fromCharCode(\n ...[...val].map((c) => 0xffff - c.charCodeAt(0))\n )\n : val\n })\n } else if (orderByItems.length === 1) {\n // For a single orderBy column, use the value directly\n const item = orderByItems[0]\n const val = evaluateOperandOnNestedRow(\n nestedRow,\n item!.operand,\n mainTableAlias\n )\n\n // Reverse the value for 'desc' ordering\n return item!.direction === `desc` && typeof val === `number`\n ? -val\n : item!.direction === `desc` && typeof val === `string`\n ? String.fromCharCode(\n ...[...val].map((c) => 0xffff - c.charCodeAt(0))\n )\n : val\n }\n\n // Default case - no ordering\n return null\n }\n\n const comparator = (a: unknown, b: unknown): number => {\n // if a and b are both numbers compare them directly\n if (typeof a === `number` && typeof b === `number`) {\n return a - b\n }\n // if a and b are both strings, compare them lexicographically\n if (typeof a === `string` && typeof b === `string`) {\n return a.localeCompare(b)\n }\n // if a and b are both booleans, compare them\n if (typeof a === `boolean` && typeof b === `boolean`) {\n return a === b ? 0 : a ? 1 : -1\n }\n // if a and b are both dates, compare them\n if (a instanceof Date && b instanceof Date) {\n return a.getTime() - b.getTime()\n }\n // if a and b are both null, return 0\n if (a === null || b === null) {\n return 0\n }\n\n // if a and b are both arrays, compare them element by element\n if (Array.isArray(a) && Array.isArray(b)) {\n for (let i = 0; i < Math.min(a.length, b.length); i++) {\n // Get the values from the array\n const aVal = a[i]\n const bVal = b[i]\n\n // Compare the values\n let result: number\n\n if (typeof aVal === `boolean` && typeof bVal === `boolean`) {\n // Special handling for booleans - false comes before true\n result = aVal === bVal ? 0 : aVal ? 1 : -1\n } else if (typeof aVal === `number` && typeof bVal === `number`) {\n // Numeric comparison\n result = aVal - bVal\n } else if (typeof aVal === `string` && typeof bVal === `string`) {\n // String comparison\n result = aVal.localeCompare(bVal)\n } else {\n // Default comparison using the general comparator\n result = comparator(aVal, bVal)\n }\n\n if (result !== 0) {\n return result\n }\n }\n // All elements are equal up to the minimum length\n return a.length - b.length\n }\n // if a and b are both null/undefined, return 0\n if (a == null && b == null) {\n return 0\n }\n // Fallback to string comparison for all other cases\n return (a as any).toString().localeCompare((b as any).toString())\n }\n\n let topKComparator: (a: unknown, b: unknown) => number\n if (!query.keyBy) {\n topKComparator = (a, b) => {\n const aValue = valueExtractor(a)\n const bValue = valueExtractor(b)\n return comparator(aValue, bValue)\n }\n }\n\n // Apply the appropriate orderBy operator based on whether an ORDER_INDEX column is requested\n if (hasOrderIndexColumn) {\n if (orderIndexType === `numeric`) {\n if (query.keyBy) {\n // Use orderByWithIndex for numeric indices\n resultPipeline = resultPipeline.pipe(\n orderByWithIndex(valueExtractor, {\n limit: query.limit,\n offset: query.offset,\n comparator,\n }),\n map(([key, [value, index]]) => {\n // Add the index to the result\n const result = {\n ...(value as Record<string, unknown>),\n [orderIndexAlias]: index,\n }\n return [key, result]\n })\n )\n } else {\n // Use topKWithIndex for numeric indices\n resultPipeline = resultPipeline.pipe(\n map((value) => [null, value]),\n topKWithIndex(topKComparator!, {\n limit: query.limit,\n offset: query.offset,\n }),\n map(([_, [value, index]]) => {\n // Add the index to the result\n return {\n ...(value as Record<string, unknown>),\n [orderIndexAlias]: index,\n }\n })\n )\n }\n } else {\n if (query.keyBy) {\n // Use orderByWithFractionalIndex for fractional indices\n resultPipeline = resultPipeline.pipe(\n orderByWithFractionalIndex(valueExtractor, {\n limit: query.limit,\n offset: query.offset,\n comparator,\n }),\n map(([key, [value, index]]) => {\n // Add the index to the result\n const result = {\n ...(value as Record<string, unknown>),\n [orderIndexAlias]: index,\n }\n return [key, result]\n })\n )\n } else {\n // Use topKWithFractionalIndex for fractional indices\n resultPipeline = resultPipeline.pipe(\n map((value) => [null, value]),\n topKWithFractionalIndex(topKComparator!, {\n limit: query.limit,\n offset: query.offset,\n }),\n map(([_, [value, index]]) => {\n // Add the index to the result\n return {\n ...(value as Record<string, unknown>),\n [orderIndexAlias]: index,\n }\n })\n )\n }\n }\n } else {\n if (query.keyBy) {\n // Use regular orderBy if no index column is requested and but a keyBy is requested\n resultPipeline = resultPipeline.pipe(\n orderBy(valueExtractor, {\n limit: query.limit,\n offset: query.offset,\n comparator,\n })\n )\n } else {\n // Use topK if no index column is requested and no keyBy is requested\n resultPipeline = resultPipeline.pipe(\n map((value) => [null, value]),\n topK(topKComparator!, {\n limit: query.limit,\n offset: query.offset,\n }),\n map(([_, value]) => value as Record<string, unknown>)\n )\n }\n }\n\n return resultPipeline\n}\n\n// Helper function to extract the ORDER_INDEX type from a function call\nfunction getOrderIndexType(obj: any): `numeric` | `fractional` {\n if (!isOrderIndexFunctionCall(obj)) {\n throw new Error(`Not an ORDER_INDEX function call`)\n }\n\n const arg = obj[`ORDER_INDEX`]\n if (arg === `numeric` || arg === true || arg === `default`) {\n return `numeric`\n } else if (arg === `fractional`) {\n return `fractional`\n } else {\n throw new Error(`Invalid ORDER_INDEX type: ` + arg)\n }\n}\n"],"names":[],"mappings":";;;AAcgB,SAAA,eACd,gBAGA,OACA,gBACA;AAEA,MAAI,sBAAsB;AAC1B,MAAI,iBAA2C;AAC/C,MAAI,kBAAkB;AAGX,aAAA,QAAQ,MAAM,QAAQ;AAC3B,QAAA,OAAO,SAAS,UAAU;AAC5B,iBAAW,CAAC,OAAO,IAAI,KAAK,OAAO,QAAQ,IAAI,GAAG;AAChD,YAAI,OAAO,SAAS,YAAY,yBAAyB,IAAI,GAAG;AACxC,gCAAA;AACJ,4BAAA;AAClB,2BAAiB,kBAAkB,IAAI;AACvC;AAAA,QAAA;AAAA,MACF;AAAA,IACF;AAEF,QAAI,oBAAqB;AAAA,EAAA;AAI3B,QAAM,eAGD,CAAC;AAEF,MAAA,OAAO,MAAM,YAAY,UAAU;AAErC,iBAAa,KAAK;AAAA,MAChB,SAAS,MAAM;AAAA,MACf,WAAW;AAAA,IAAA,CACZ;AAAA,EACQ,WAAA,MAAM,QAAQ,MAAM,OAAO,GAAG;AAE5B,eAAA,QAAQ,MAAM,SAAS;AAC5B,UAAA,OAAO,SAAS,UAAU;AAC5B,qBAAa,KAAK;AAAA,UAChB,SAAS;AAAA,UACT,WAAW;AAAA,QAAA,CACZ;AAAA,MACH,WAAW,OAAO,SAAS,UAAU;AACnC,mBAAW,CAAC,QAAQ,SAAS,KAAK,OAAO,QAAQ,IAAI,GAAG;AACtD,uBAAa,KAAK;AAAA,YAChB,SAAS;AAAA,YACT;AAAA,UAAA,CACD;AAAA,QAAA;AAAA,MACH;AAAA,IACF;AAAA,EAEO,WAAA,OAAO,MAAM,YAAY,UAAU;AAEjC,eAAA,CAAC,QAAQ,SAAS,KAAK,OAAO,QAAQ,MAAM,OAAO,GAAG;AAC/D,mBAAa,KAAK;AAAA,QAChB,SAAS;AAAA,QACT;AAAA,MAAA,CACD;AAAA,IAAA;AAAA,EACH;AAII,QAAA,iBAAiB,CAAC,UAAmB;AACzC,UAAM,MAAM;AAGZ,UAAM,YAAqC,EAAE,CAAC,cAAc,GAAG,IAAI;AAG/D,QAAA,aAAa,SAAS,GAAG;AACpB,aAAA,aAAa,IAAI,CAAC,SAAS;AAChC,cAAM,MAAM;AAAA,UACV;AAAA,UACA,KAAK;AAAA,UACL;AAAA,QACF;AAGA,eAAO,KAAK,cAAc,UAAU,OAAO,QAAQ,WAC/C,CAAC,MACD,KAAK,cAAc,UAAU,OAAO,QAAQ,WAC1C,OAAO;AAAA,UACL,GAAG,CAAC,GAAG,GAAG,EAAE,IAAI,CAAC,MAAM,QAAS,EAAE,WAAW,CAAC,CAAC;AAAA,QAAA,IAEjD;AAAA,MAAA,CACP;AAAA,IAAA,WACQ,aAAa,WAAW,GAAG;AAE9B,YAAA,OAAO,aAAa,CAAC;AAC3B,YAAM,MAAM;AAAA,QACV;AAAA,QACA,KAAM;AAAA,QACN;AAAA,MACF;AAGA,aAAO,KAAM,cAAc,UAAU,OAAO,QAAQ,WAChD,CAAC,MACD,KAAM,cAAc,UAAU,OAAO,QAAQ,WAC3C,OAAO;AAAA,QACL,GAAG,CAAC,GAAG,GAAG,EAAE,IAAI,CAAC,MAAM,QAAS,EAAE,WAAW,CAAC,CAAC;AAAA,MAAA,IAEjD;AAAA,IAAA;AAID,WAAA;AAAA,EACT;AAEM,QAAA,aAAa,CAAC,GAAY,MAAuB;AAErD,QAAI,OAAO,MAAM,YAAY,OAAO,MAAM,UAAU;AAClD,aAAO,IAAI;AAAA,IAAA;AAGb,QAAI,OAAO,MAAM,YAAY,OAAO,MAAM,UAAU;AAC3C,aAAA,EAAE,cAAc,CAAC;AAAA,IAAA;AAG1B,QAAI,OAAO,MAAM,aAAa,OAAO,MAAM,WAAW;AACpD,aAAO,MAAM,IAAI,IAAI,IAAI,IAAI;AAAA,IAAA;AAG3B,QAAA,aAAa,QAAQ,aAAa,MAAM;AAC1C,aAAO,EAAE,YAAY,EAAE,QAAQ;AAAA,IAAA;AAG7B,QAAA,MAAM,QAAQ,MAAM,MAAM;AACrB,aAAA;AAAA,IAAA;AAIT,QAAI,MAAM,QAAQ,CAAC,KAAK,MAAM,QAAQ,CAAC,GAAG;AAC/B,eAAA,IAAI,GAAG,IAAI,KAAK,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,KAAK;AAE/C,cAAA,OAAO,EAAE,CAAC;AACV,cAAA,OAAO,EAAE,CAAC;AAGZ,YAAA;AAEJ,YAAI,OAAO,SAAS,aAAa,OAAO,SAAS,WAAW;AAE1D,mBAAS,SAAS,OAAO,IAAI,OAAO,IAAI;AAAA,QAAA,WAC/B,OAAO,SAAS,YAAY,OAAO,SAAS,UAAU;AAE/D,mBAAS,OAAO;AAAA,QAAA,WACP,OAAO,SAAS,YAAY,OAAO,SAAS,UAAU;AAEtD,mBAAA,KAAK,cAAc,IAAI;AAAA,QAAA,OAC3B;AAEI,mBAAA,WAAW,MAAM,IAAI;AAAA,QAAA;AAGhC,YAAI,WAAW,GAAG;AACT,iBAAA;AAAA,QAAA;AAAA,MACT;AAGK,aAAA,EAAE,SAAS,EAAE;AAAA,IAAA;AAGlB,QAAA,KAAK,QAAQ,KAAK,MAAM;AACnB,aAAA;AAAA,IAAA;AAGT,WAAQ,EAAU,SAAS,EAAE,cAAe,EAAU,UAAU;AAAA,EAClE;AAEI,MAAA;AACA,MAAA,CAAC,MAAM,OAAO;AACC,qBAAA,CAAC,GAAG,MAAM;AACnB,YAAA,SAAS,eAAe,CAAC;AACzB,YAAA,SAAS,eAAe,CAAC;AACxB,aAAA,WAAW,QAAQ,MAAM;AAAA,IAClC;AAAA,EAAA;AAIF,MAAI,qBAAqB;AACvB,QAAI,mBAAmB,WAAW;AAChC,UAAI,MAAM,OAAO;AAEf,yBAAiB,eAAe;AAAA,UAC9B,iBAAiB,gBAAgB;AAAA,YAC/B,OAAO,MAAM;AAAA,YACb,QAAQ,MAAM;AAAA,YACd;AAAA,UAAA,CACD;AAAA,UACD,IAAI,CAAC,CAAC,KAAK,CAAC,OAAO,KAAK,CAAC,MAAM;AAE7B,kBAAM,SAAS;AAAA,cACb,GAAI;AAAA,cACJ,CAAC,eAAe,GAAG;AAAA,YACrB;AACO,mBAAA,CAAC,KAAK,MAAM;AAAA,UACpB,CAAA;AAAA,QACH;AAAA,MAAA,OACK;AAEL,yBAAiB,eAAe;AAAA,UAC9B,IAAI,CAAC,UAAU,CAAC,MAAM,KAAK,CAAC;AAAA,UAC5B,cAAc,gBAAiB;AAAA,YAC7B,OAAO,MAAM;AAAA,YACb,QAAQ,MAAM;AAAA,UAAA,CACf;AAAA,UACD,IAAI,CAAC,CAAC,GAAG,CAAC,OAAO,KAAK,CAAC,MAAM;AAEpB,mBAAA;AAAA,cACL,GAAI;AAAA,cACJ,CAAC,eAAe,GAAG;AAAA,YACrB;AAAA,UACD,CAAA;AAAA,QACH;AAAA,MAAA;AAAA,IACF,OACK;AACL,UAAI,MAAM,OAAO;AAEf,yBAAiB,eAAe;AAAA,UAC9B,2BAA2B,gBAAgB;AAAA,YACzC,OAAO,MAAM;AAAA,YACb,QAAQ,MAAM;AAAA,YACd;AAAA,UAAA,CACD;AAAA,UACD,IAAI,CAAC,CAAC,KAAK,CAAC,OAAO,KAAK,CAAC,MAAM;AAE7B,kBAAM,SAAS;AAAA,cACb,GAAI;AAAA,cACJ,CAAC,eAAe,GAAG;AAAA,YACrB;AACO,mBAAA,CAAC,KAAK,MAAM;AAAA,UACpB,CAAA;AAAA,QACH;AAAA,MAAA,OACK;AAEL,yBAAiB,eAAe;AAAA,UAC9B,IAAI,CAAC,UAAU,CAAC,MAAM,KAAK,CAAC;AAAA,UAC5B,wBAAwB,gBAAiB;AAAA,YACvC,OAAO,MAAM;AAAA,YACb,QAAQ,MAAM;AAAA,UAAA,CACf;AAAA,UACD,IAAI,CAAC,CAAC,GAAG,CAAC,OAAO,KAAK,CAAC,MAAM;AAEpB,mBAAA;AAAA,cACL,GAAI;AAAA,cACJ,CAAC,eAAe,GAAG;AAAA,YACrB;AAAA,UACD,CAAA;AAAA,QACH;AAAA,MAAA;AAAA,IACF;AAAA,EACF,OACK;AACL,QAAI,MAAM,OAAO;AAEf,uBAAiB,eAAe;AAAA,QAC9B,QAAQ,gBAAgB;AAAA,UACtB,OAAO,MAAM;AAAA,UACb,QAAQ,MAAM;AAAA,UACd;AAAA,QACD,CAAA;AAAA,MACH;AAAA,IAAA,OACK;AAEL,uBAAiB,eAAe;AAAA,QAC9B,IAAI,CAAC,UAAU,CAAC,MAAM,KAAK,CAAC;AAAA,QAC5B,KAAK,gBAAiB;AAAA,UACpB,OAAO,MAAM;AAAA,UACb,QAAQ,MAAM;AAAA,QAAA,CACf;AAAA,QACD,IAAI,CAAC,CAAC,GAAG,KAAK,MAAM,KAAgC;AAAA,MACtD;AAAA,IAAA;AAAA,EACF;AAGK,SAAA;AACT;AAGA,SAAS,kBAAkB,KAAoC;AACzD,MAAA,CAAC,yBAAyB,GAAG,GAAG;AAC5B,UAAA,IAAI,MAAM,kCAAkC;AAAA,EAAA;AAG9C,QAAA,MAAM,IAAI,aAAa;AAC7B,MAAI,QAAQ,aAAa,QAAQ,QAAQ,QAAQ,WAAW;AACnD,WAAA;AAAA,EAAA,WACE,QAAQ,cAAc;AACxB,WAAA;AAAA,EAAA,OACF;AACC,UAAA,IAAI,MAAM,+BAA+B,GAAG;AAAA,EAAA;AAEtD;"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Query } from './schema.js';
|
|
2
|
+
import { IStreamBuilder } from '@electric-sql/d2ts';
|
|
3
|
+
/**
|
|
4
|
+
* Compiles a query into a D2 pipeline
|
|
5
|
+
* @param query The query to compile
|
|
6
|
+
* @param inputs Mapping of table names to input streams
|
|
7
|
+
* @returns A stream builder representing the compiled query
|
|
8
|
+
*/
|
|
9
|
+
export declare function compileQueryPipeline<T extends IStreamBuilder<unknown>>(query: Query, inputs: Record<string, IStreamBuilder<Record<string, unknown>>>): T;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { map, filter } from "@electric-sql/d2ts";
|
|
2
|
+
import { evaluateConditionOnNestedRow } from "./evaluators.js";
|
|
3
|
+
import { processJoinClause } from "./joins.js";
|
|
4
|
+
import { processGroupBy } from "./group-by.js";
|
|
5
|
+
import { processOrderBy } from "./order-by.js";
|
|
6
|
+
import { processKeyBy } from "./key-by.js";
|
|
7
|
+
import { processSelect } from "./select.js";
|
|
8
|
+
function compileQueryPipeline(query, inputs) {
|
|
9
|
+
const allInputs = { ...inputs };
|
|
10
|
+
if (query.with && query.with.length > 0) {
|
|
11
|
+
for (const withQuery of query.with) {
|
|
12
|
+
if (!withQuery.as) {
|
|
13
|
+
throw new Error(`WITH query must have an "as" property`);
|
|
14
|
+
}
|
|
15
|
+
if (withQuery.keyBy !== void 0) {
|
|
16
|
+
throw new Error(`WITH query cannot have a "keyBy" property`);
|
|
17
|
+
}
|
|
18
|
+
if (allInputs[withQuery.as]) {
|
|
19
|
+
throw new Error(`CTE with name "${withQuery.as}" already exists`);
|
|
20
|
+
}
|
|
21
|
+
const withQueryWithoutWith = { ...withQuery, with: void 0 };
|
|
22
|
+
const compiledWithQuery = compileQueryPipeline(
|
|
23
|
+
withQueryWithoutWith,
|
|
24
|
+
allInputs
|
|
25
|
+
);
|
|
26
|
+
allInputs[withQuery.as] = compiledWithQuery;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const tables = {};
|
|
30
|
+
const mainTableAlias = query.as || query.from;
|
|
31
|
+
const input = allInputs[query.from];
|
|
32
|
+
if (!input) {
|
|
33
|
+
throw new Error(`Input for table "${query.from}" not found in inputs map`);
|
|
34
|
+
}
|
|
35
|
+
tables[mainTableAlias] = input;
|
|
36
|
+
let pipeline = input.pipe(
|
|
37
|
+
map((row) => {
|
|
38
|
+
return { [mainTableAlias]: row };
|
|
39
|
+
})
|
|
40
|
+
);
|
|
41
|
+
if (query.join) {
|
|
42
|
+
pipeline = processJoinClause(
|
|
43
|
+
pipeline,
|
|
44
|
+
query,
|
|
45
|
+
tables,
|
|
46
|
+
mainTableAlias,
|
|
47
|
+
allInputs
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
if (query.where) {
|
|
51
|
+
pipeline = pipeline.pipe(
|
|
52
|
+
filter((nestedRow) => {
|
|
53
|
+
const result = evaluateConditionOnNestedRow(
|
|
54
|
+
nestedRow,
|
|
55
|
+
query.where,
|
|
56
|
+
mainTableAlias
|
|
57
|
+
);
|
|
58
|
+
return result;
|
|
59
|
+
})
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
if (query.groupBy) {
|
|
63
|
+
pipeline = processGroupBy(pipeline, query, mainTableAlias);
|
|
64
|
+
}
|
|
65
|
+
if (query.having) {
|
|
66
|
+
pipeline = pipeline.pipe(
|
|
67
|
+
filter((row) => {
|
|
68
|
+
const result = evaluateConditionOnNestedRow(
|
|
69
|
+
{ [mainTableAlias]: row, ...row },
|
|
70
|
+
query.having,
|
|
71
|
+
mainTableAlias
|
|
72
|
+
);
|
|
73
|
+
return result;
|
|
74
|
+
})
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
pipeline = processSelect(pipeline, query, mainTableAlias, allInputs);
|
|
78
|
+
let resultPipeline = pipeline;
|
|
79
|
+
if (query.keyBy) {
|
|
80
|
+
resultPipeline = processKeyBy(resultPipeline, query);
|
|
81
|
+
}
|
|
82
|
+
if (query.orderBy) {
|
|
83
|
+
resultPipeline = processOrderBy(resultPipeline, query, mainTableAlias);
|
|
84
|
+
} else if (query.limit !== void 0 || query.offset !== void 0) {
|
|
85
|
+
throw new Error(
|
|
86
|
+
`LIMIT and OFFSET require an ORDER BY clause to ensure deterministic results`
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
return resultPipeline;
|
|
90
|
+
}
|
|
91
|
+
export {
|
|
92
|
+
compileQueryPipeline
|
|
93
|
+
};
|
|
94
|
+
//# sourceMappingURL=pipeline-compiler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pipeline-compiler.js","sources":["../../../src/query/pipeline-compiler.ts"],"sourcesContent":["import { filter, map } from \"@electric-sql/d2ts\"\nimport { evaluateConditionOnNestedRow } from \"./evaluators.js\"\nimport { processJoinClause } from \"./joins.js\"\nimport { processGroupBy } from \"./group-by.js\"\nimport { processOrderBy } from \"./order-by.js\"\nimport { processKeyBy } from \"./key-by.js\"\nimport { processSelect } from \"./select.js\"\nimport type { Condition, Query } from \"./schema.js\"\nimport type { IStreamBuilder } from \"@electric-sql/d2ts\"\n\n/**\n * Compiles a query into a D2 pipeline\n * @param query The query to compile\n * @param inputs Mapping of table names to input streams\n * @returns A stream builder representing the compiled query\n */\nexport function compileQueryPipeline<T extends IStreamBuilder<unknown>>(\n query: Query,\n inputs: Record<string, IStreamBuilder<Record<string, unknown>>>\n): T {\n // Create a copy of the inputs map to avoid modifying the original\n const allInputs = { ...inputs }\n\n // Process WITH queries if they exist\n if (query.with && query.with.length > 0) {\n // Process each WITH query in order\n for (const withQuery of query.with) {\n // Ensure the WITH query has an alias\n if (!withQuery.as) {\n throw new Error(`WITH query must have an \"as\" property`)\n }\n\n // Ensure the WITH query is not keyed\n if ((withQuery as Query).keyBy !== undefined) {\n throw new Error(`WITH query cannot have a \"keyBy\" property`)\n }\n\n // Check if this CTE name already exists in the inputs\n if (allInputs[withQuery.as]) {\n throw new Error(`CTE with name \"${withQuery.as}\" already exists`)\n }\n\n // Create a new query without the 'with' property to avoid circular references\n const withQueryWithoutWith = { ...withQuery, with: undefined }\n\n // Compile the WITH query using the current set of inputs\n // (which includes previously compiled WITH queries)\n const compiledWithQuery = compileQueryPipeline(\n withQueryWithoutWith,\n allInputs\n )\n\n // Add the compiled query to the inputs map using its alias\n allInputs[withQuery.as] = compiledWithQuery as IStreamBuilder<\n Record<string, unknown>\n >\n }\n }\n\n // Create a map of table aliases to inputs\n const tables: Record<string, IStreamBuilder<Record<string, unknown>>> = {}\n\n // The main table is the one in the FROM clause\n const mainTableAlias = query.as || query.from\n\n // Get the main input from the inputs map (now including CTEs)\n const input = allInputs[query.from]\n if (!input) {\n throw new Error(`Input for table \"${query.from}\" not found in inputs map`)\n }\n\n tables[mainTableAlias] = input\n\n // Prepare the initial pipeline with the main table wrapped in its alias\n let pipeline = input.pipe(\n map((row: unknown) => {\n // Initialize the record with a nested structure\n return { [mainTableAlias]: row } as Record<string, unknown>\n })\n )\n\n // Process JOIN clauses if they exist\n if (query.join) {\n pipeline = processJoinClause(\n pipeline,\n query,\n tables,\n mainTableAlias,\n allInputs\n )\n }\n\n // Process the WHERE clause if it exists\n if (query.where) {\n pipeline = pipeline.pipe(\n filter((nestedRow) => {\n const result = evaluateConditionOnNestedRow(\n nestedRow,\n query.where as Condition,\n mainTableAlias\n )\n return result\n })\n )\n }\n\n // Process the GROUP BY clause if it exists\n if (query.groupBy) {\n pipeline = processGroupBy(pipeline, query, mainTableAlias)\n }\n\n // Process the HAVING clause if it exists\n // This works similarly to WHERE but is applied after any aggregations\n if (query.having) {\n pipeline = pipeline.pipe(\n filter((row) => {\n // For HAVING, we're working with the flattened row that contains both\n // the group by keys and the aggregate results directly\n const result = evaluateConditionOnNestedRow(\n { [mainTableAlias]: row, ...row } as Record<string, unknown>,\n query.having as Condition,\n mainTableAlias\n )\n return result\n })\n )\n }\n\n // Process the SELECT clause - this is where we flatten the structure\n pipeline = processSelect(pipeline, query, mainTableAlias, allInputs)\n\n let resultPipeline: IStreamBuilder<\n Record<string, unknown> | [string | number, Record<string, unknown>]\n > = pipeline\n\n // Process keyBy parameter if it exists\n if (query.keyBy) {\n resultPipeline = processKeyBy(resultPipeline, query)\n }\n\n // Process orderBy parameter if it exists\n if (query.orderBy) {\n resultPipeline = processOrderBy(resultPipeline, query, mainTableAlias)\n } else if (query.limit !== undefined || query.offset !== undefined) {\n // If there's a limit or offset without orderBy, throw an error\n throw new Error(\n `LIMIT and OFFSET require an ORDER BY clause to ensure deterministic results`\n )\n }\n\n return resultPipeline as T\n}\n"],"names":[],"mappings":";;;;;;;AAgBgB,SAAA,qBACd,OACA,QACG;AAEG,QAAA,YAAY,EAAE,GAAG,OAAO;AAG9B,MAAI,MAAM,QAAQ,MAAM,KAAK,SAAS,GAAG;AAE5B,eAAA,aAAa,MAAM,MAAM;AAE9B,UAAA,CAAC,UAAU,IAAI;AACX,cAAA,IAAI,MAAM,uCAAuC;AAAA,MAAA;AAIpD,UAAA,UAAoB,UAAU,QAAW;AACtC,cAAA,IAAI,MAAM,2CAA2C;AAAA,MAAA;AAIzD,UAAA,UAAU,UAAU,EAAE,GAAG;AAC3B,cAAM,IAAI,MAAM,kBAAkB,UAAU,EAAE,kBAAkB;AAAA,MAAA;AAIlE,YAAM,uBAAuB,EAAE,GAAG,WAAW,MAAM,OAAU;AAI7D,YAAM,oBAAoB;AAAA,QACxB;AAAA,QACA;AAAA,MACF;AAGU,gBAAA,UAAU,EAAE,IAAI;AAAA,IAAA;AAAA,EAG5B;AAIF,QAAM,SAAkE,CAAC;AAGnE,QAAA,iBAAiB,MAAM,MAAM,MAAM;AAGnC,QAAA,QAAQ,UAAU,MAAM,IAAI;AAClC,MAAI,CAAC,OAAO;AACV,UAAM,IAAI,MAAM,oBAAoB,MAAM,IAAI,2BAA2B;AAAA,EAAA;AAG3E,SAAO,cAAc,IAAI;AAGzB,MAAI,WAAW,MAAM;AAAA,IACnB,IAAI,CAAC,QAAiB;AAEpB,aAAO,EAAE,CAAC,cAAc,GAAG,IAAI;AAAA,IAChC,CAAA;AAAA,EACH;AAGA,MAAI,MAAM,MAAM;AACH,eAAA;AAAA,MACT;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EAAA;AAIF,MAAI,MAAM,OAAO;AACf,eAAW,SAAS;AAAA,MAClB,OAAO,CAAC,cAAc;AACpB,cAAM,SAAS;AAAA,UACb;AAAA,UACA,MAAM;AAAA,UACN;AAAA,QACF;AACO,eAAA;AAAA,MACR,CAAA;AAAA,IACH;AAAA,EAAA;AAIF,MAAI,MAAM,SAAS;AACN,eAAA,eAAe,UAAU,OAAO,cAAc;AAAA,EAAA;AAK3D,MAAI,MAAM,QAAQ;AAChB,eAAW,SAAS;AAAA,MAClB,OAAO,CAAC,QAAQ;AAGd,cAAM,SAAS;AAAA,UACb,EAAE,CAAC,cAAc,GAAG,KAAK,GAAG,IAAI;AAAA,UAChC,MAAM;AAAA,UACN;AAAA,QACF;AACO,eAAA;AAAA,MACR,CAAA;AAAA,IACH;AAAA,EAAA;AAIF,aAAW,cAAc,UAAU,OAAO,gBAAgB,SAAS;AAEnE,MAAI,iBAEA;AAGJ,MAAI,MAAM,OAAO;AACE,qBAAA,aAAa,gBAAgB,KAAK;AAAA,EAAA;AAIrD,MAAI,MAAM,SAAS;AACA,qBAAA,eAAe,gBAAgB,OAAO,cAAc;AAAA,EAAA,WAC5D,MAAM,UAAU,UAAa,MAAM,WAAW,QAAW;AAElE,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EAAA;AAGK,SAAA;AACT;"}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { Collection } from '../collection.js';
|
|
2
|
+
import { Comparator, Condition, Limit, LiteralValue, Offset, OrderBy, Query, Select } from './schema.js';
|
|
3
|
+
import { Context, Flatten, InferResultTypeFromSelectTuple, Input, InputReference, PropertyReference, PropertyReferenceString, RemoveIndexSignature, Schema } from './types.js';
|
|
4
|
+
type CollectionRef = {
|
|
5
|
+
[K: string]: Collection<any>;
|
|
6
|
+
};
|
|
7
|
+
export declare class BaseQueryBuilder<TContext extends Context<Schema>> {
|
|
8
|
+
private readonly query;
|
|
9
|
+
/**
|
|
10
|
+
* Create a new QueryBuilder instance.
|
|
11
|
+
*/
|
|
12
|
+
constructor(query?: Partial<Query<TContext>>);
|
|
13
|
+
from<TCollectionRef extends CollectionRef>(collectionRef: TCollectionRef): QueryBuilder<{
|
|
14
|
+
baseSchema: Flatten<TContext[`baseSchema`] & {
|
|
15
|
+
[K in keyof TCollectionRef & string]: RemoveIndexSignature<(TCollectionRef[keyof TCollectionRef] extends Collection<infer T> ? T : never) & Input>;
|
|
16
|
+
}>;
|
|
17
|
+
schema: Flatten<{
|
|
18
|
+
[K in keyof TCollectionRef & string]: RemoveIndexSignature<(TCollectionRef[keyof TCollectionRef] extends Collection<infer T> ? T : never) & Input>;
|
|
19
|
+
}>;
|
|
20
|
+
default: keyof TCollectionRef & string;
|
|
21
|
+
}>;
|
|
22
|
+
from<T extends InputReference<{
|
|
23
|
+
baseSchema: TContext[`baseSchema`];
|
|
24
|
+
schema: TContext[`baseSchema`];
|
|
25
|
+
}>>(collection: T): QueryBuilder<{
|
|
26
|
+
baseSchema: TContext[`baseSchema`];
|
|
27
|
+
schema: {
|
|
28
|
+
[K in T]: RemoveIndexSignature<TContext[`baseSchema`][T]>;
|
|
29
|
+
};
|
|
30
|
+
default: T;
|
|
31
|
+
}>;
|
|
32
|
+
from<T extends InputReference<{
|
|
33
|
+
baseSchema: TContext[`baseSchema`];
|
|
34
|
+
schema: TContext[`baseSchema`];
|
|
35
|
+
}>, TAs extends string>(collection: T, as: TAs): QueryBuilder<{
|
|
36
|
+
baseSchema: TContext[`baseSchema`];
|
|
37
|
+
schema: {
|
|
38
|
+
[K in TAs]: RemoveIndexSignature<TContext[`baseSchema`][T]>;
|
|
39
|
+
};
|
|
40
|
+
default: TAs;
|
|
41
|
+
}>;
|
|
42
|
+
private fromCollectionRef;
|
|
43
|
+
private fromInputReference;
|
|
44
|
+
/**
|
|
45
|
+
* Specify what columns to select.
|
|
46
|
+
* Overwrites any previous select clause.
|
|
47
|
+
*
|
|
48
|
+
* @param selects The columns to select
|
|
49
|
+
* @returns A new QueryBuilder with the select clause set
|
|
50
|
+
*/
|
|
51
|
+
select<TSelects extends Array<Select<TContext>>>(this: QueryBuilder<TContext>, ...selects: TSelects): QueryBuilder<Flatten<Omit<TContext, `result`> & {
|
|
52
|
+
result: InferResultTypeFromSelectTuple<TContext, TSelects>;
|
|
53
|
+
}>>;
|
|
54
|
+
/**
|
|
55
|
+
* Add a where clause comparing two values.
|
|
56
|
+
*/
|
|
57
|
+
where(left: PropertyReferenceString<TContext> | LiteralValue, operator: Comparator, right: PropertyReferenceString<TContext> | LiteralValue): QueryBuilder<TContext>;
|
|
58
|
+
/**
|
|
59
|
+
* Add a where clause with a complete condition object.
|
|
60
|
+
*/
|
|
61
|
+
where(condition: Condition<TContext>): QueryBuilder<TContext>;
|
|
62
|
+
/**
|
|
63
|
+
* Add a having clause comparing two values.
|
|
64
|
+
* For filtering results after they have been grouped.
|
|
65
|
+
*/
|
|
66
|
+
having(left: PropertyReferenceString<TContext> | LiteralValue, operator: Comparator, right: PropertyReferenceString<TContext> | LiteralValue): QueryBuilder<TContext>;
|
|
67
|
+
/**
|
|
68
|
+
* Add a having clause with a complete condition object.
|
|
69
|
+
* For filtering results after they have been grouped.
|
|
70
|
+
*/
|
|
71
|
+
having(condition: Condition<TContext>): QueryBuilder<TContext>;
|
|
72
|
+
/**
|
|
73
|
+
* Add a join clause to the query using a CollectionRef.
|
|
74
|
+
*/
|
|
75
|
+
join<TCollectionRef extends CollectionRef>(joinClause: {
|
|
76
|
+
type: `inner` | `left` | `right` | `full` | `cross`;
|
|
77
|
+
from: TCollectionRef;
|
|
78
|
+
on: Condition<Flatten<{
|
|
79
|
+
baseSchema: TContext[`baseSchema`];
|
|
80
|
+
schema: TContext[`schema`] & {
|
|
81
|
+
[K in keyof TCollectionRef & string]: RemoveIndexSignature<(TCollectionRef[keyof TCollectionRef] extends Collection<infer T> ? T : never) & Input>;
|
|
82
|
+
};
|
|
83
|
+
}>>;
|
|
84
|
+
where?: Condition<Flatten<{
|
|
85
|
+
baseSchema: TContext[`baseSchema`];
|
|
86
|
+
schema: {
|
|
87
|
+
[K in keyof TCollectionRef & string]: RemoveIndexSignature<(TCollectionRef[keyof TCollectionRef] extends Collection<infer T> ? T : never) & Input>;
|
|
88
|
+
};
|
|
89
|
+
}>>;
|
|
90
|
+
}): QueryBuilder<Flatten<Omit<TContext, `schema`> & {
|
|
91
|
+
schema: TContext[`schema`] & {
|
|
92
|
+
[K in keyof TCollectionRef & string]: RemoveIndexSignature<(TCollectionRef[keyof TCollectionRef] extends Collection<infer T> ? T : never) & Input>;
|
|
93
|
+
};
|
|
94
|
+
}>>;
|
|
95
|
+
/**
|
|
96
|
+
* Add a join clause to the query without specifying an alias.
|
|
97
|
+
* The collection name will be used as the default alias.
|
|
98
|
+
*/
|
|
99
|
+
join<T extends InputReference<{
|
|
100
|
+
baseSchema: TContext[`baseSchema`];
|
|
101
|
+
schema: TContext[`baseSchema`];
|
|
102
|
+
}>>(joinClause: {
|
|
103
|
+
type: `inner` | `left` | `right` | `full` | `cross`;
|
|
104
|
+
from: T;
|
|
105
|
+
on: Condition<Flatten<{
|
|
106
|
+
baseSchema: TContext[`baseSchema`];
|
|
107
|
+
schema: TContext[`schema`] & {
|
|
108
|
+
[K in T]: RemoveIndexSignature<TContext[`baseSchema`][T]>;
|
|
109
|
+
};
|
|
110
|
+
}>>;
|
|
111
|
+
where?: Condition<Flatten<{
|
|
112
|
+
baseSchema: TContext[`baseSchema`];
|
|
113
|
+
schema: {
|
|
114
|
+
[K in T]: RemoveIndexSignature<TContext[`baseSchema`][T]>;
|
|
115
|
+
};
|
|
116
|
+
}>>;
|
|
117
|
+
}): QueryBuilder<Flatten<Omit<TContext, `schema`> & {
|
|
118
|
+
schema: TContext[`schema`] & {
|
|
119
|
+
[K in T]: RemoveIndexSignature<TContext[`baseSchema`][T]>;
|
|
120
|
+
};
|
|
121
|
+
}>>;
|
|
122
|
+
/**
|
|
123
|
+
* Add a join clause to the query with a specified alias.
|
|
124
|
+
*/
|
|
125
|
+
join<TFrom extends InputReference<{
|
|
126
|
+
baseSchema: TContext[`baseSchema`];
|
|
127
|
+
schema: TContext[`baseSchema`];
|
|
128
|
+
}>, TAs extends string>(joinClause: {
|
|
129
|
+
type: `inner` | `left` | `right` | `full` | `cross`;
|
|
130
|
+
from: TFrom;
|
|
131
|
+
as: TAs;
|
|
132
|
+
on: Condition<Flatten<{
|
|
133
|
+
baseSchema: TContext[`baseSchema`];
|
|
134
|
+
schema: TContext[`schema`] & {
|
|
135
|
+
[K in TAs]: RemoveIndexSignature<TContext[`baseSchema`][TFrom]>;
|
|
136
|
+
};
|
|
137
|
+
}>>;
|
|
138
|
+
where?: Condition<Flatten<{
|
|
139
|
+
baseSchema: TContext[`baseSchema`];
|
|
140
|
+
schema: {
|
|
141
|
+
[K in TAs]: RemoveIndexSignature<TContext[`baseSchema`][TFrom]>;
|
|
142
|
+
};
|
|
143
|
+
}>>;
|
|
144
|
+
}): QueryBuilder<Flatten<Omit<TContext, `schema`> & {
|
|
145
|
+
schema: TContext[`schema`] & {
|
|
146
|
+
[K in TAs]: RemoveIndexSignature<TContext[`baseSchema`][TFrom]>;
|
|
147
|
+
};
|
|
148
|
+
}>>;
|
|
149
|
+
private joinCollectionRef;
|
|
150
|
+
private joinInputReference;
|
|
151
|
+
/**
|
|
152
|
+
* Add an orderBy clause to sort the results.
|
|
153
|
+
* Overwrites any previous orderBy clause.
|
|
154
|
+
*
|
|
155
|
+
* @param orderBy The order specification
|
|
156
|
+
* @returns A new QueryBuilder with the orderBy clause set
|
|
157
|
+
*/
|
|
158
|
+
orderBy(orderBy: OrderBy<TContext>): QueryBuilder<TContext>;
|
|
159
|
+
/**
|
|
160
|
+
* Set a limit on the number of results returned.
|
|
161
|
+
*
|
|
162
|
+
* @param limit Maximum number of results to return
|
|
163
|
+
* @returns A new QueryBuilder with the limit set
|
|
164
|
+
*/
|
|
165
|
+
limit(limit: Limit<TContext>): QueryBuilder<TContext>;
|
|
166
|
+
/**
|
|
167
|
+
* Set an offset to skip a number of results.
|
|
168
|
+
*
|
|
169
|
+
* @param offset Number of results to skip
|
|
170
|
+
* @returns A new QueryBuilder with the offset set
|
|
171
|
+
*/
|
|
172
|
+
offset(offset: Offset<TContext>): QueryBuilder<TContext>;
|
|
173
|
+
/**
|
|
174
|
+
* Specify which column(s) to use as keys in the output keyed stream.
|
|
175
|
+
*
|
|
176
|
+
* @param keyBy The column(s) to use as keys
|
|
177
|
+
* @returns A new QueryBuilder with the keyBy clause set
|
|
178
|
+
*/
|
|
179
|
+
keyBy(keyBy: PropertyReference<TContext> | Array<PropertyReference<TContext>>): QueryBuilder<TContext>;
|
|
180
|
+
/**
|
|
181
|
+
* Add a groupBy clause to group the results by one or more columns.
|
|
182
|
+
*
|
|
183
|
+
* @param groupBy The column(s) to group by
|
|
184
|
+
* @returns A new QueryBuilder with the groupBy clause set
|
|
185
|
+
*/
|
|
186
|
+
groupBy(groupBy: PropertyReference<TContext> | Array<PropertyReference<TContext>>): QueryBuilder<TContext>;
|
|
187
|
+
/**
|
|
188
|
+
* Define a Common Table Expression (CTE) that can be referenced in the main query.
|
|
189
|
+
* This allows referencing the CTE by name in subsequent from/join clauses.
|
|
190
|
+
*
|
|
191
|
+
* @param name The name of the CTE
|
|
192
|
+
* @param queryBuilderCallback A function that builds the CTE query
|
|
193
|
+
* @returns A new QueryBuilder with the CTE added
|
|
194
|
+
*/
|
|
195
|
+
with<TName extends string, TResult = Record<string, unknown>>(name: TName, queryBuilderCallback: (builder: InitialQueryBuilder<{
|
|
196
|
+
baseSchema: TContext[`baseSchema`];
|
|
197
|
+
schema: {};
|
|
198
|
+
}>) => QueryBuilder<any>): InitialQueryBuilder<{
|
|
199
|
+
baseSchema: TContext[`baseSchema`] & {
|
|
200
|
+
[K in TName]: TResult;
|
|
201
|
+
};
|
|
202
|
+
schema: TContext[`schema`];
|
|
203
|
+
}>;
|
|
204
|
+
get _query(): Query<TContext>;
|
|
205
|
+
}
|
|
206
|
+
export type InitialQueryBuilder<TContext extends Context<Schema>> = Pick<BaseQueryBuilder<TContext>, `from` | `with`>;
|
|
207
|
+
export type QueryBuilder<TContext extends Context<Schema>> = Omit<BaseQueryBuilder<TContext>, `from`>;
|
|
208
|
+
/**
|
|
209
|
+
* Create a new query builder with the given schema
|
|
210
|
+
*/
|
|
211
|
+
export declare function queryBuilder<TBaseSchema extends Schema = {}>(): InitialQueryBuilder<{
|
|
212
|
+
baseSchema: TBaseSchema;
|
|
213
|
+
schema: {};
|
|
214
|
+
}>;
|
|
215
|
+
export type ResultsFromContext<TContext extends Context<Schema>> = Flatten<TContext[`result`] extends object ? TContext[`result`] : TContext[`result`] extends undefined ? TContext[`schema`] : object>;
|
|
216
|
+
export type ResultFromQueryBuilder<TQueryBuilder> = Flatten<TQueryBuilder extends QueryBuilder<infer C> ? C extends {
|
|
217
|
+
result: infer R;
|
|
218
|
+
} ? R : never : never>;
|
|
219
|
+
export {};
|