@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,155 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const d2ts = require("@electric-sql/d2ts");
|
|
4
|
+
const evaluators = require("./evaluators.cjs");
|
|
5
|
+
const extractors = require("./extractors.cjs");
|
|
6
|
+
function processJoinClause(pipeline, query, tables, mainTableAlias, allInputs) {
|
|
7
|
+
if (!query.join) return pipeline;
|
|
8
|
+
const input = allInputs[query.from];
|
|
9
|
+
for (const joinClause of query.join) {
|
|
10
|
+
const joinedTableAlias = joinClause.as || joinClause.from;
|
|
11
|
+
const joinType = joinClause.type === `cross` ? `inner` : joinClause.type;
|
|
12
|
+
const mainPipeline = pipeline.pipe(
|
|
13
|
+
d2ts.map((nestedRow) => {
|
|
14
|
+
const mainRow = nestedRow[mainTableAlias];
|
|
15
|
+
const keyValue = extractors.extractJoinKey(
|
|
16
|
+
mainRow,
|
|
17
|
+
joinClause.on[0],
|
|
18
|
+
mainTableAlias
|
|
19
|
+
);
|
|
20
|
+
return [keyValue, nestedRow];
|
|
21
|
+
})
|
|
22
|
+
);
|
|
23
|
+
let joinedTableInput;
|
|
24
|
+
if (allInputs[joinClause.from]) {
|
|
25
|
+
joinedTableInput = allInputs[joinClause.from];
|
|
26
|
+
} else {
|
|
27
|
+
joinedTableInput = input.graph.newInput();
|
|
28
|
+
}
|
|
29
|
+
tables[joinedTableAlias] = joinedTableInput;
|
|
30
|
+
const joinedPipeline = joinedTableInput.pipe(
|
|
31
|
+
d2ts.map((row) => {
|
|
32
|
+
const nestedRow = { [joinedTableAlias]: row };
|
|
33
|
+
const keyValue = extractors.extractJoinKey(row, joinClause.on[2], joinedTableAlias);
|
|
34
|
+
return [keyValue, nestedRow];
|
|
35
|
+
})
|
|
36
|
+
);
|
|
37
|
+
switch (joinType) {
|
|
38
|
+
case `inner`:
|
|
39
|
+
pipeline = mainPipeline.pipe(
|
|
40
|
+
d2ts.join(joinedPipeline, `inner`),
|
|
41
|
+
d2ts.consolidate(),
|
|
42
|
+
processJoinResults(mainTableAlias, joinedTableAlias, joinClause)
|
|
43
|
+
);
|
|
44
|
+
break;
|
|
45
|
+
case `left`:
|
|
46
|
+
pipeline = mainPipeline.pipe(
|
|
47
|
+
d2ts.join(joinedPipeline, `left`),
|
|
48
|
+
d2ts.consolidate(),
|
|
49
|
+
processJoinResults(mainTableAlias, joinedTableAlias, joinClause)
|
|
50
|
+
);
|
|
51
|
+
break;
|
|
52
|
+
case `right`:
|
|
53
|
+
pipeline = mainPipeline.pipe(
|
|
54
|
+
d2ts.join(joinedPipeline, `right`),
|
|
55
|
+
d2ts.consolidate(),
|
|
56
|
+
processJoinResults(mainTableAlias, joinedTableAlias, joinClause)
|
|
57
|
+
);
|
|
58
|
+
break;
|
|
59
|
+
case `full`:
|
|
60
|
+
pipeline = mainPipeline.pipe(
|
|
61
|
+
d2ts.join(joinedPipeline, `full`),
|
|
62
|
+
d2ts.consolidate(),
|
|
63
|
+
processJoinResults(mainTableAlias, joinedTableAlias, joinClause)
|
|
64
|
+
);
|
|
65
|
+
break;
|
|
66
|
+
default:
|
|
67
|
+
pipeline = mainPipeline.pipe(
|
|
68
|
+
d2ts.join(joinedPipeline, `inner`),
|
|
69
|
+
d2ts.consolidate(),
|
|
70
|
+
processJoinResults(mainTableAlias, joinedTableAlias, joinClause)
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return pipeline;
|
|
75
|
+
}
|
|
76
|
+
function processJoinResults(mainTableAlias, joinedTableAlias, joinClause) {
|
|
77
|
+
return function(pipeline) {
|
|
78
|
+
return pipeline.pipe(
|
|
79
|
+
// Process the join result and handle nulls in the same step
|
|
80
|
+
d2ts.map((result) => {
|
|
81
|
+
const [_key, [mainNestedRow, joinedNestedRow]] = result;
|
|
82
|
+
if (joinClause.type === `inner` || joinClause.type === `cross`) {
|
|
83
|
+
if (!mainNestedRow || !joinedNestedRow) {
|
|
84
|
+
return void 0;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (joinClause.type === `left` && !mainNestedRow) {
|
|
88
|
+
return void 0;
|
|
89
|
+
}
|
|
90
|
+
if (joinClause.type === `right` && !joinedNestedRow) {
|
|
91
|
+
return void 0;
|
|
92
|
+
}
|
|
93
|
+
const mergedNestedRow = {};
|
|
94
|
+
if (mainNestedRow) {
|
|
95
|
+
Object.entries(mainNestedRow).forEach(([tableAlias, tableData]) => {
|
|
96
|
+
mergedNestedRow[tableAlias] = tableData;
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
if (joinedNestedRow) {
|
|
100
|
+
Object.entries(joinedNestedRow).forEach(([tableAlias, tableData]) => {
|
|
101
|
+
mergedNestedRow[tableAlias] = tableData;
|
|
102
|
+
});
|
|
103
|
+
} else if (joinClause.type === `left` || joinClause.type === `full`) {
|
|
104
|
+
mergedNestedRow[joinedTableAlias] = null;
|
|
105
|
+
}
|
|
106
|
+
if (!mainNestedRow && (joinClause.type === `right` || joinClause.type === `full`)) {
|
|
107
|
+
mergedNestedRow[mainTableAlias] = null;
|
|
108
|
+
}
|
|
109
|
+
return mergedNestedRow;
|
|
110
|
+
}),
|
|
111
|
+
// Filter out undefined results
|
|
112
|
+
d2ts.filter(
|
|
113
|
+
(value) => value !== void 0
|
|
114
|
+
),
|
|
115
|
+
// Process the ON condition
|
|
116
|
+
d2ts.filter((nestedRow) => {
|
|
117
|
+
if (!joinClause.on || joinClause.type === `cross`) {
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
if (joinClause.type === `left` && nestedRow[joinedTableAlias] === null) {
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
if (joinClause.type === `right` && nestedRow[mainTableAlias] === null) {
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
if (joinClause.type === `full` && (nestedRow[mainTableAlias] === null || nestedRow[joinedTableAlias] === null)) {
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
const result = evaluators.evaluateConditionOnNestedRow(
|
|
130
|
+
nestedRow,
|
|
131
|
+
joinClause.on,
|
|
132
|
+
mainTableAlias,
|
|
133
|
+
joinedTableAlias
|
|
134
|
+
);
|
|
135
|
+
return result;
|
|
136
|
+
}),
|
|
137
|
+
// Process the WHERE clause for the join if it exists
|
|
138
|
+
d2ts.filter((nestedRow) => {
|
|
139
|
+
if (!joinClause.where) {
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
const result = evaluators.evaluateConditionOnNestedRow(
|
|
143
|
+
nestedRow,
|
|
144
|
+
joinClause.where,
|
|
145
|
+
mainTableAlias,
|
|
146
|
+
joinedTableAlias
|
|
147
|
+
);
|
|
148
|
+
return result;
|
|
149
|
+
})
|
|
150
|
+
);
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
exports.processJoinClause = processJoinClause;
|
|
154
|
+
exports.processJoinResults = processJoinResults;
|
|
155
|
+
//# sourceMappingURL=joins.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"joins.cjs","sources":["../../../src/query/joins.ts"],"sourcesContent":["import {\n consolidate,\n filter,\n join as joinOperator,\n map,\n} from \"@electric-sql/d2ts\"\nimport { evaluateConditionOnNestedRow } from \"./evaluators.js\"\nimport { extractJoinKey } from \"./extractors.js\"\nimport type { Query } from \"./index.js\"\nimport type { IStreamBuilder, JoinType } from \"@electric-sql/d2ts\"\n\n/**\n * Creates a processing pipeline for join clauses\n */\nexport function processJoinClause(\n pipeline: IStreamBuilder<Record<string, unknown>>,\n query: Query,\n tables: Record<string, IStreamBuilder<Record<string, unknown>>>,\n mainTableAlias: string,\n allInputs: Record<string, IStreamBuilder<Record<string, unknown>>>\n) {\n if (!query.join) return pipeline\n const input = allInputs[query.from]\n\n for (const joinClause of query.join) {\n // Create a stream for the joined table\n const joinedTableAlias = joinClause.as || joinClause.from\n\n // Get the right join type for the operator\n const joinType: JoinType =\n joinClause.type === `cross` ? `inner` : joinClause.type\n\n // We need to prepare the main pipeline and the joined pipeline\n // to have the correct key format for joining\n const mainPipeline = pipeline.pipe(\n map((nestedRow: Record<string, unknown>) => {\n // Extract the key from the ON condition left side for the main table\n const mainRow = nestedRow[mainTableAlias] as Record<string, unknown>\n\n // Extract the join key from the main row\n const keyValue = extractJoinKey(\n mainRow,\n joinClause.on[0],\n mainTableAlias\n )\n\n // Return [key, nestedRow] as a KeyValue type\n return [keyValue, nestedRow] as [unknown, Record<string, unknown>]\n })\n )\n\n // Get the joined table input from the inputs map\n let joinedTableInput: IStreamBuilder<Record<string, unknown>>\n\n if (allInputs[joinClause.from]) {\n // Use the provided input if available\n joinedTableInput = allInputs[joinClause.from]!\n } else {\n // Create a new input if not provided\n joinedTableInput = input!.graph.newInput<Record<string, unknown>>()\n }\n\n tables[joinedTableAlias] = joinedTableInput\n\n // Create a pipeline for the joined table\n const joinedPipeline = joinedTableInput.pipe(\n map((row: Record<string, unknown>) => {\n // Wrap the row in an object with the table alias as the key\n const nestedRow = { [joinedTableAlias]: row }\n\n // Extract the key from the ON condition right side for the joined table\n const keyValue = extractJoinKey(row, joinClause.on[2], joinedTableAlias)\n\n // Return [key, nestedRow] as a KeyValue type\n return [keyValue, nestedRow] as [unknown, Record<string, unknown>]\n })\n )\n\n // Apply join with appropriate typings based on join type\n switch (joinType) {\n case `inner`:\n pipeline = mainPipeline.pipe(\n joinOperator(joinedPipeline, `inner`),\n consolidate(),\n processJoinResults(mainTableAlias, joinedTableAlias, joinClause)\n )\n break\n case `left`:\n pipeline = mainPipeline.pipe(\n joinOperator(joinedPipeline, `left`),\n consolidate(),\n processJoinResults(mainTableAlias, joinedTableAlias, joinClause)\n )\n break\n case `right`:\n pipeline = mainPipeline.pipe(\n joinOperator(joinedPipeline, `right`),\n consolidate(),\n processJoinResults(mainTableAlias, joinedTableAlias, joinClause)\n )\n break\n case `full`:\n pipeline = mainPipeline.pipe(\n joinOperator(joinedPipeline, `full`),\n consolidate(),\n processJoinResults(mainTableAlias, joinedTableAlias, joinClause)\n )\n break\n default:\n pipeline = mainPipeline.pipe(\n joinOperator(joinedPipeline, `inner`),\n consolidate(),\n processJoinResults(mainTableAlias, joinedTableAlias, joinClause)\n )\n }\n }\n return pipeline\n}\n\n/**\n * Creates a processing pipeline for join results\n */\nexport function processJoinResults(\n mainTableAlias: string,\n joinedTableAlias: string,\n joinClause: { on: any; where?: any; type: string }\n) {\n return function (\n pipeline: IStreamBuilder<unknown>\n ): IStreamBuilder<Record<string, unknown>> {\n return pipeline.pipe(\n // Process the join result and handle nulls in the same step\n map((result: unknown) => {\n const [_key, [mainNestedRow, joinedNestedRow]] = result as [\n unknown,\n [\n Record<string, unknown> | undefined,\n Record<string, unknown> | undefined,\n ],\n ]\n\n // For inner joins, both sides should be non-null\n if (joinClause.type === `inner` || joinClause.type === `cross`) {\n if (!mainNestedRow || !joinedNestedRow) {\n return undefined // Will be filtered out\n }\n }\n\n // For left joins, the main row must be non-null\n if (joinClause.type === `left` && !mainNestedRow) {\n return undefined // Will be filtered out\n }\n\n // For right joins, the joined row must be non-null\n if (joinClause.type === `right` && !joinedNestedRow) {\n return undefined // Will be filtered out\n }\n\n // Merge the nested rows\n const mergedNestedRow: Record<string, unknown> = {}\n\n // Add main row data if it exists\n if (mainNestedRow) {\n Object.entries(mainNestedRow).forEach(([tableAlias, tableData]) => {\n mergedNestedRow[tableAlias] = tableData\n })\n }\n\n // If we have a joined row, add it to the merged result\n if (joinedNestedRow) {\n Object.entries(joinedNestedRow).forEach(([tableAlias, tableData]) => {\n mergedNestedRow[tableAlias] = tableData\n })\n } else if (joinClause.type === `left` || joinClause.type === `full`) {\n // For left or full joins, add the joined table with null data if missing\n mergedNestedRow[joinedTableAlias] = null\n }\n\n // For right or full joins, add the main table with null data if missing\n if (\n !mainNestedRow &&\n (joinClause.type === `right` || joinClause.type === `full`)\n ) {\n mergedNestedRow[mainTableAlias] = null\n }\n\n return mergedNestedRow\n }),\n // Filter out undefined results\n filter(\n (value: unknown): value is Record<string, unknown> =>\n value !== undefined\n ),\n // Process the ON condition\n filter((nestedRow: Record<string, unknown>) => {\n // If there's no ON condition, or it's a cross join, always return true\n if (!joinClause.on || joinClause.type === `cross`) {\n return true\n }\n\n // For LEFT JOIN, if the right side is null, we should include the row\n if (\n joinClause.type === `left` &&\n nestedRow[joinedTableAlias] === null\n ) {\n return true\n }\n\n // For RIGHT JOIN, if the left side is null, we should include the row\n if (joinClause.type === `right` && nestedRow[mainTableAlias] === null) {\n return true\n }\n\n // For FULL JOIN, if either side is null, we should include the row\n if (\n joinClause.type === `full` &&\n (nestedRow[mainTableAlias] === null ||\n nestedRow[joinedTableAlias] === null)\n ) {\n return true\n }\n\n const result = evaluateConditionOnNestedRow(\n nestedRow,\n joinClause.on,\n mainTableAlias,\n joinedTableAlias\n )\n return result\n }),\n // Process the WHERE clause for the join if it exists\n filter((nestedRow: Record<string, unknown>) => {\n if (!joinClause.where) {\n return true\n }\n\n const result = evaluateConditionOnNestedRow(\n nestedRow,\n joinClause.where,\n mainTableAlias,\n joinedTableAlias\n )\n return result\n })\n )\n }\n}\n"],"names":["map","extractJoinKey","joinOperator","consolidate","filter","evaluateConditionOnNestedRow"],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Query } from './index.js';
|
|
2
|
+
import { IStreamBuilder } from '@electric-sql/d2ts';
|
|
3
|
+
/**
|
|
4
|
+
* Creates a processing pipeline for join clauses
|
|
5
|
+
*/
|
|
6
|
+
export declare function processJoinClause(pipeline: IStreamBuilder<Record<string, unknown>>, query: Query, tables: Record<string, IStreamBuilder<Record<string, unknown>>>, mainTableAlias: string, allInputs: Record<string, IStreamBuilder<Record<string, unknown>>>): IStreamBuilder<Record<string, unknown>>;
|
|
7
|
+
/**
|
|
8
|
+
* Creates a processing pipeline for join results
|
|
9
|
+
*/
|
|
10
|
+
export declare function processJoinResults(mainTableAlias: string, joinedTableAlias: string, joinClause: {
|
|
11
|
+
on: any;
|
|
12
|
+
where?: any;
|
|
13
|
+
type: string;
|
|
14
|
+
}): (pipeline: IStreamBuilder<unknown>) => IStreamBuilder<Record<string, unknown>>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const d2ts = require("@electric-sql/d2ts");
|
|
4
|
+
function processKeyBy(resultPipeline, query) {
|
|
5
|
+
if (!query.keyBy) {
|
|
6
|
+
return resultPipeline;
|
|
7
|
+
}
|
|
8
|
+
const keyByParam = query.keyBy;
|
|
9
|
+
resultPipeline = resultPipeline.pipe(
|
|
10
|
+
d2ts.keyBy((row) => {
|
|
11
|
+
if (Array.isArray(keyByParam)) {
|
|
12
|
+
const keyValues = {};
|
|
13
|
+
for (const keyColumn of keyByParam) {
|
|
14
|
+
const columnName = keyColumn.startsWith(`@`) ? keyColumn.substring(1) : keyColumn;
|
|
15
|
+
if (columnName in row) {
|
|
16
|
+
keyValues[columnName] = row[columnName];
|
|
17
|
+
} else {
|
|
18
|
+
throw new Error(
|
|
19
|
+
`Key column "${columnName}" not found in result set. Make sure it's included in the select clause.`
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return JSON.stringify(keyValues);
|
|
24
|
+
} else {
|
|
25
|
+
const columnName = keyByParam.startsWith(`@`) ? keyByParam.substring(1) : keyByParam;
|
|
26
|
+
if (!(columnName in row)) {
|
|
27
|
+
throw new Error(
|
|
28
|
+
`Key column "${columnName}" not found in result set. Make sure it's included in the select clause.`
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
const keyValue = row[columnName];
|
|
32
|
+
if (typeof keyValue === `string` || typeof keyValue === `number`) {
|
|
33
|
+
return keyValue;
|
|
34
|
+
} else {
|
|
35
|
+
return JSON.stringify(keyValue);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
);
|
|
40
|
+
return resultPipeline;
|
|
41
|
+
}
|
|
42
|
+
exports.processKeyBy = processKeyBy;
|
|
43
|
+
//# sourceMappingURL=key-by.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"key-by.cjs","sources":["../../../src/query/key-by.ts"],"sourcesContent":["import { keyBy } from \"@electric-sql/d2ts\"\nimport type { IStreamBuilder } from \"@electric-sql/d2ts\"\nimport type { Query } from \"./schema\"\n\nexport function processKeyBy(\n resultPipeline: IStreamBuilder<\n Record<string, unknown> | [string | number, Record<string, unknown>]\n >,\n query: Query\n) {\n if (!query.keyBy) {\n return resultPipeline\n }\n const keyByParam = query.keyBy\n\n resultPipeline = resultPipeline.pipe(\n keyBy((row: Record<string, unknown>) => {\n if (Array.isArray(keyByParam)) {\n // Multiple columns - extract values and JSON stringify\n const keyValues: Record<string, unknown> = {}\n for (const keyColumn of keyByParam) {\n // Remove @ prefix if present\n const columnName = (keyColumn as string).startsWith(`@`)\n ? (keyColumn as string).substring(1)\n : (keyColumn as string)\n\n if (columnName in row) {\n keyValues[columnName] = row[columnName]\n } else {\n throw new Error(\n `Key column \"${columnName}\" not found in result set. Make sure it's included in the select clause.`\n )\n }\n }\n return JSON.stringify(keyValues)\n } else {\n // Single column\n // Remove @ prefix if present\n const columnName = (keyByParam as string).startsWith(`@`)\n ? (keyByParam as string).substring(1)\n : (keyByParam as string)\n\n if (!(columnName in row)) {\n throw new Error(\n `Key column \"${columnName}\" not found in result set. Make sure it's included in the select clause.`\n )\n }\n\n const keyValue = row[columnName]\n // Use the value directly if it's a string or number, otherwise JSON stringify\n if (typeof keyValue === `string` || typeof keyValue === `number`) {\n return keyValue\n } else {\n return JSON.stringify(keyValue)\n }\n }\n })\n )\n\n return resultPipeline\n}\n"],"names":["keyBy"],"mappings":";;;AAIgB,SAAA,aACd,gBAGA,OACA;AACI,MAAA,CAAC,MAAM,OAAO;AACT,WAAA;AAAA,EAAA;AAET,QAAM,aAAa,MAAM;AAEzB,mBAAiB,eAAe;AAAA,IAC9BA,KAAA,MAAM,CAAC,QAAiC;AAClC,UAAA,MAAM,QAAQ,UAAU,GAAG;AAE7B,cAAM,YAAqC,CAAC;AAC5C,mBAAW,aAAa,YAAY;AAE5B,gBAAA,aAAc,UAAqB,WAAW,GAAG,IAClD,UAAqB,UAAU,CAAC,IAChC;AAEL,cAAI,cAAc,KAAK;AACX,sBAAA,UAAU,IAAI,IAAI,UAAU;AAAA,UAAA,OACjC;AACL,kBAAM,IAAI;AAAA,cACR,eAAe,UAAU;AAAA,YAC3B;AAAA,UAAA;AAAA,QACF;AAEK,eAAA,KAAK,UAAU,SAAS;AAAA,MAAA,OAC1B;AAGC,cAAA,aAAc,WAAsB,WAAW,GAAG,IACnD,WAAsB,UAAU,CAAC,IACjC;AAED,YAAA,EAAE,cAAc,MAAM;AACxB,gBAAM,IAAI;AAAA,YACR,eAAe,UAAU;AAAA,UAC3B;AAAA,QAAA;AAGI,cAAA,WAAW,IAAI,UAAU;AAE/B,YAAI,OAAO,aAAa,YAAY,OAAO,aAAa,UAAU;AACzD,iBAAA;AAAA,QAAA,OACF;AACE,iBAAA,KAAK,UAAU,QAAQ;AAAA,QAAA;AAAA,MAChC;AAAA,IAEH,CAAA;AAAA,EACH;AAEO,SAAA;AACT;;"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { IStreamBuilder } from '@electric-sql/d2ts';
|
|
2
|
+
import { Query } from './schema.cjs';
|
|
3
|
+
export declare function processKeyBy(resultPipeline: IStreamBuilder<Record<string, unknown> | [string | number, Record<string, unknown>]>, query: Query): IStreamBuilder<Record<string, unknown> | [string | number, Record<string, unknown>]>;
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const d2ts = require("@electric-sql/d2ts");
|
|
4
|
+
const extractors = require("./extractors.cjs");
|
|
5
|
+
const utils = require("./utils.cjs");
|
|
6
|
+
function processOrderBy(resultPipeline, query, mainTableAlias) {
|
|
7
|
+
let hasOrderIndexColumn = false;
|
|
8
|
+
let orderIndexType = `numeric`;
|
|
9
|
+
let orderIndexAlias = ``;
|
|
10
|
+
for (const item of query.select) {
|
|
11
|
+
if (typeof item === `object`) {
|
|
12
|
+
for (const [alias, expr] of Object.entries(item)) {
|
|
13
|
+
if (typeof expr === `object` && utils.isOrderIndexFunctionCall(expr)) {
|
|
14
|
+
hasOrderIndexColumn = true;
|
|
15
|
+
orderIndexAlias = alias;
|
|
16
|
+
orderIndexType = getOrderIndexType(expr);
|
|
17
|
+
break;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
if (hasOrderIndexColumn) break;
|
|
22
|
+
}
|
|
23
|
+
const orderByItems = [];
|
|
24
|
+
if (typeof query.orderBy === `string`) {
|
|
25
|
+
orderByItems.push({
|
|
26
|
+
operand: query.orderBy,
|
|
27
|
+
direction: `asc`
|
|
28
|
+
});
|
|
29
|
+
} else if (Array.isArray(query.orderBy)) {
|
|
30
|
+
for (const item of query.orderBy) {
|
|
31
|
+
if (typeof item === `string`) {
|
|
32
|
+
orderByItems.push({
|
|
33
|
+
operand: item,
|
|
34
|
+
direction: `asc`
|
|
35
|
+
});
|
|
36
|
+
} else if (typeof item === `object`) {
|
|
37
|
+
for (const [column, direction] of Object.entries(item)) {
|
|
38
|
+
orderByItems.push({
|
|
39
|
+
operand: column,
|
|
40
|
+
direction
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
} else if (typeof query.orderBy === `object`) {
|
|
46
|
+
for (const [column, direction] of Object.entries(query.orderBy)) {
|
|
47
|
+
orderByItems.push({
|
|
48
|
+
operand: column,
|
|
49
|
+
direction
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const valueExtractor = (value) => {
|
|
54
|
+
const row = value;
|
|
55
|
+
const nestedRow = { [mainTableAlias]: row };
|
|
56
|
+
if (orderByItems.length > 1) {
|
|
57
|
+
return orderByItems.map((item) => {
|
|
58
|
+
const val = extractors.evaluateOperandOnNestedRow(
|
|
59
|
+
nestedRow,
|
|
60
|
+
item.operand,
|
|
61
|
+
mainTableAlias
|
|
62
|
+
);
|
|
63
|
+
return item.direction === `desc` && typeof val === `number` ? -val : item.direction === `desc` && typeof val === `string` ? String.fromCharCode(
|
|
64
|
+
...[...val].map((c) => 65535 - c.charCodeAt(0))
|
|
65
|
+
) : val;
|
|
66
|
+
});
|
|
67
|
+
} else if (orderByItems.length === 1) {
|
|
68
|
+
const item = orderByItems[0];
|
|
69
|
+
const val = extractors.evaluateOperandOnNestedRow(
|
|
70
|
+
nestedRow,
|
|
71
|
+
item.operand,
|
|
72
|
+
mainTableAlias
|
|
73
|
+
);
|
|
74
|
+
return item.direction === `desc` && typeof val === `number` ? -val : item.direction === `desc` && typeof val === `string` ? String.fromCharCode(
|
|
75
|
+
...[...val].map((c) => 65535 - c.charCodeAt(0))
|
|
76
|
+
) : val;
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
79
|
+
};
|
|
80
|
+
const comparator = (a, b) => {
|
|
81
|
+
if (typeof a === `number` && typeof b === `number`) {
|
|
82
|
+
return a - b;
|
|
83
|
+
}
|
|
84
|
+
if (typeof a === `string` && typeof b === `string`) {
|
|
85
|
+
return a.localeCompare(b);
|
|
86
|
+
}
|
|
87
|
+
if (typeof a === `boolean` && typeof b === `boolean`) {
|
|
88
|
+
return a === b ? 0 : a ? 1 : -1;
|
|
89
|
+
}
|
|
90
|
+
if (a instanceof Date && b instanceof Date) {
|
|
91
|
+
return a.getTime() - b.getTime();
|
|
92
|
+
}
|
|
93
|
+
if (a === null || b === null) {
|
|
94
|
+
return 0;
|
|
95
|
+
}
|
|
96
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
97
|
+
for (let i = 0; i < Math.min(a.length, b.length); i++) {
|
|
98
|
+
const aVal = a[i];
|
|
99
|
+
const bVal = b[i];
|
|
100
|
+
let result;
|
|
101
|
+
if (typeof aVal === `boolean` && typeof bVal === `boolean`) {
|
|
102
|
+
result = aVal === bVal ? 0 : aVal ? 1 : -1;
|
|
103
|
+
} else if (typeof aVal === `number` && typeof bVal === `number`) {
|
|
104
|
+
result = aVal - bVal;
|
|
105
|
+
} else if (typeof aVal === `string` && typeof bVal === `string`) {
|
|
106
|
+
result = aVal.localeCompare(bVal);
|
|
107
|
+
} else {
|
|
108
|
+
result = comparator(aVal, bVal);
|
|
109
|
+
}
|
|
110
|
+
if (result !== 0) {
|
|
111
|
+
return result;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return a.length - b.length;
|
|
115
|
+
}
|
|
116
|
+
if (a == null && b == null) {
|
|
117
|
+
return 0;
|
|
118
|
+
}
|
|
119
|
+
return a.toString().localeCompare(b.toString());
|
|
120
|
+
};
|
|
121
|
+
let topKComparator;
|
|
122
|
+
if (!query.keyBy) {
|
|
123
|
+
topKComparator = (a, b) => {
|
|
124
|
+
const aValue = valueExtractor(a);
|
|
125
|
+
const bValue = valueExtractor(b);
|
|
126
|
+
return comparator(aValue, bValue);
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
if (hasOrderIndexColumn) {
|
|
130
|
+
if (orderIndexType === `numeric`) {
|
|
131
|
+
if (query.keyBy) {
|
|
132
|
+
resultPipeline = resultPipeline.pipe(
|
|
133
|
+
d2ts.orderByWithIndex(valueExtractor, {
|
|
134
|
+
limit: query.limit,
|
|
135
|
+
offset: query.offset,
|
|
136
|
+
comparator
|
|
137
|
+
}),
|
|
138
|
+
d2ts.map(([key, [value, index]]) => {
|
|
139
|
+
const result = {
|
|
140
|
+
...value,
|
|
141
|
+
[orderIndexAlias]: index
|
|
142
|
+
};
|
|
143
|
+
return [key, result];
|
|
144
|
+
})
|
|
145
|
+
);
|
|
146
|
+
} else {
|
|
147
|
+
resultPipeline = resultPipeline.pipe(
|
|
148
|
+
d2ts.map((value) => [null, value]),
|
|
149
|
+
d2ts.topKWithIndex(topKComparator, {
|
|
150
|
+
limit: query.limit,
|
|
151
|
+
offset: query.offset
|
|
152
|
+
}),
|
|
153
|
+
d2ts.map(([_, [value, index]]) => {
|
|
154
|
+
return {
|
|
155
|
+
...value,
|
|
156
|
+
[orderIndexAlias]: index
|
|
157
|
+
};
|
|
158
|
+
})
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
} else {
|
|
162
|
+
if (query.keyBy) {
|
|
163
|
+
resultPipeline = resultPipeline.pipe(
|
|
164
|
+
d2ts.orderByWithFractionalIndex(valueExtractor, {
|
|
165
|
+
limit: query.limit,
|
|
166
|
+
offset: query.offset,
|
|
167
|
+
comparator
|
|
168
|
+
}),
|
|
169
|
+
d2ts.map(([key, [value, index]]) => {
|
|
170
|
+
const result = {
|
|
171
|
+
...value,
|
|
172
|
+
[orderIndexAlias]: index
|
|
173
|
+
};
|
|
174
|
+
return [key, result];
|
|
175
|
+
})
|
|
176
|
+
);
|
|
177
|
+
} else {
|
|
178
|
+
resultPipeline = resultPipeline.pipe(
|
|
179
|
+
d2ts.map((value) => [null, value]),
|
|
180
|
+
d2ts.topKWithFractionalIndex(topKComparator, {
|
|
181
|
+
limit: query.limit,
|
|
182
|
+
offset: query.offset
|
|
183
|
+
}),
|
|
184
|
+
d2ts.map(([_, [value, index]]) => {
|
|
185
|
+
return {
|
|
186
|
+
...value,
|
|
187
|
+
[orderIndexAlias]: index
|
|
188
|
+
};
|
|
189
|
+
})
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
} else {
|
|
194
|
+
if (query.keyBy) {
|
|
195
|
+
resultPipeline = resultPipeline.pipe(
|
|
196
|
+
d2ts.orderBy(valueExtractor, {
|
|
197
|
+
limit: query.limit,
|
|
198
|
+
offset: query.offset,
|
|
199
|
+
comparator
|
|
200
|
+
})
|
|
201
|
+
);
|
|
202
|
+
} else {
|
|
203
|
+
resultPipeline = resultPipeline.pipe(
|
|
204
|
+
d2ts.map((value) => [null, value]),
|
|
205
|
+
d2ts.topK(topKComparator, {
|
|
206
|
+
limit: query.limit,
|
|
207
|
+
offset: query.offset
|
|
208
|
+
}),
|
|
209
|
+
d2ts.map(([_, value]) => value)
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return resultPipeline;
|
|
214
|
+
}
|
|
215
|
+
function getOrderIndexType(obj) {
|
|
216
|
+
if (!utils.isOrderIndexFunctionCall(obj)) {
|
|
217
|
+
throw new Error(`Not an ORDER_INDEX function call`);
|
|
218
|
+
}
|
|
219
|
+
const arg = obj[`ORDER_INDEX`];
|
|
220
|
+
if (arg === `numeric` || arg === true || arg === `default`) {
|
|
221
|
+
return `numeric`;
|
|
222
|
+
} else if (arg === `fractional`) {
|
|
223
|
+
return `fractional`;
|
|
224
|
+
} else {
|
|
225
|
+
throw new Error(`Invalid ORDER_INDEX type: ` + arg);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
exports.processOrderBy = processOrderBy;
|
|
229
|
+
//# sourceMappingURL=order-by.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"order-by.cjs","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":["isOrderIndexFunctionCall","evaluateOperandOnNestedRow","orderByWithIndex","map","topKWithIndex","orderByWithFractionalIndex","topKWithFractionalIndex","orderBy","topK"],"mappings":"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"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { Query } from './schema.cjs';
|
|
2
|
+
import { IStreamBuilder } from '@electric-sql/d2ts';
|
|
3
|
+
export declare function processOrderBy(resultPipeline: IStreamBuilder<Record<string, unknown> | [string | number, Record<string, unknown>]>, query: Query, mainTableAlias: string): IStreamBuilder<Record<string, unknown> | [string | number, Record<string, unknown>]>;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const d2ts = require("@electric-sql/d2ts");
|
|
4
|
+
const evaluators = require("./evaluators.cjs");
|
|
5
|
+
const joins = require("./joins.cjs");
|
|
6
|
+
const groupBy = require("./group-by.cjs");
|
|
7
|
+
const orderBy = require("./order-by.cjs");
|
|
8
|
+
const keyBy = require("./key-by.cjs");
|
|
9
|
+
const select = require("./select.cjs");
|
|
10
|
+
function compileQueryPipeline(query, inputs) {
|
|
11
|
+
const allInputs = { ...inputs };
|
|
12
|
+
if (query.with && query.with.length > 0) {
|
|
13
|
+
for (const withQuery of query.with) {
|
|
14
|
+
if (!withQuery.as) {
|
|
15
|
+
throw new Error(`WITH query must have an "as" property`);
|
|
16
|
+
}
|
|
17
|
+
if (withQuery.keyBy !== void 0) {
|
|
18
|
+
throw new Error(`WITH query cannot have a "keyBy" property`);
|
|
19
|
+
}
|
|
20
|
+
if (allInputs[withQuery.as]) {
|
|
21
|
+
throw new Error(`CTE with name "${withQuery.as}" already exists`);
|
|
22
|
+
}
|
|
23
|
+
const withQueryWithoutWith = { ...withQuery, with: void 0 };
|
|
24
|
+
const compiledWithQuery = compileQueryPipeline(
|
|
25
|
+
withQueryWithoutWith,
|
|
26
|
+
allInputs
|
|
27
|
+
);
|
|
28
|
+
allInputs[withQuery.as] = compiledWithQuery;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const tables = {};
|
|
32
|
+
const mainTableAlias = query.as || query.from;
|
|
33
|
+
const input = allInputs[query.from];
|
|
34
|
+
if (!input) {
|
|
35
|
+
throw new Error(`Input for table "${query.from}" not found in inputs map`);
|
|
36
|
+
}
|
|
37
|
+
tables[mainTableAlias] = input;
|
|
38
|
+
let pipeline = input.pipe(
|
|
39
|
+
d2ts.map((row) => {
|
|
40
|
+
return { [mainTableAlias]: row };
|
|
41
|
+
})
|
|
42
|
+
);
|
|
43
|
+
if (query.join) {
|
|
44
|
+
pipeline = joins.processJoinClause(
|
|
45
|
+
pipeline,
|
|
46
|
+
query,
|
|
47
|
+
tables,
|
|
48
|
+
mainTableAlias,
|
|
49
|
+
allInputs
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
if (query.where) {
|
|
53
|
+
pipeline = pipeline.pipe(
|
|
54
|
+
d2ts.filter((nestedRow) => {
|
|
55
|
+
const result = evaluators.evaluateConditionOnNestedRow(
|
|
56
|
+
nestedRow,
|
|
57
|
+
query.where,
|
|
58
|
+
mainTableAlias
|
|
59
|
+
);
|
|
60
|
+
return result;
|
|
61
|
+
})
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
if (query.groupBy) {
|
|
65
|
+
pipeline = groupBy.processGroupBy(pipeline, query, mainTableAlias);
|
|
66
|
+
}
|
|
67
|
+
if (query.having) {
|
|
68
|
+
pipeline = pipeline.pipe(
|
|
69
|
+
d2ts.filter((row) => {
|
|
70
|
+
const result = evaluators.evaluateConditionOnNestedRow(
|
|
71
|
+
{ [mainTableAlias]: row, ...row },
|
|
72
|
+
query.having,
|
|
73
|
+
mainTableAlias
|
|
74
|
+
);
|
|
75
|
+
return result;
|
|
76
|
+
})
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
pipeline = select.processSelect(pipeline, query, mainTableAlias, allInputs);
|
|
80
|
+
let resultPipeline = pipeline;
|
|
81
|
+
if (query.keyBy) {
|
|
82
|
+
resultPipeline = keyBy.processKeyBy(resultPipeline, query);
|
|
83
|
+
}
|
|
84
|
+
if (query.orderBy) {
|
|
85
|
+
resultPipeline = orderBy.processOrderBy(resultPipeline, query, mainTableAlias);
|
|
86
|
+
} else if (query.limit !== void 0 || query.offset !== void 0) {
|
|
87
|
+
throw new Error(
|
|
88
|
+
`LIMIT and OFFSET require an ORDER BY clause to ensure deterministic results`
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
return resultPipeline;
|
|
92
|
+
}
|
|
93
|
+
exports.compileQueryPipeline = compileQueryPipeline;
|
|
94
|
+
//# sourceMappingURL=pipeline-compiler.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pipeline-compiler.cjs","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":["map","processJoinClause","filter","evaluateConditionOnNestedRow","processGroupBy","processSelect","processKeyBy","processOrderBy"],"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,IACnBA,KAAA,IAAI,CAAC,QAAiB;AAEpB,aAAO,EAAE,CAAC,cAAc,GAAG,IAAI;AAAA,IAChC,CAAA;AAAA,EACH;AAGA,MAAI,MAAM,MAAM;AACH,eAAAC,MAAA;AAAA,MACT;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EAAA;AAIF,MAAI,MAAM,OAAO;AACf,eAAW,SAAS;AAAA,MAClBC,KAAA,OAAO,CAAC,cAAc;AACpB,cAAM,SAASC,WAAA;AAAA,UACb;AAAA,UACA,MAAM;AAAA,UACN;AAAA,QACF;AACO,eAAA;AAAA,MACR,CAAA;AAAA,IACH;AAAA,EAAA;AAIF,MAAI,MAAM,SAAS;AACN,eAAAC,QAAA,eAAe,UAAU,OAAO,cAAc;AAAA,EAAA;AAK3D,MAAI,MAAM,QAAQ;AAChB,eAAW,SAAS;AAAA,MAClBF,KAAA,OAAO,CAAC,QAAQ;AAGd,cAAM,SAASC,WAAA;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,aAAWE,OAAAA,cAAc,UAAU,OAAO,gBAAgB,SAAS;AAEnE,MAAI,iBAEA;AAGJ,MAAI,MAAM,OAAO;AACE,qBAAAC,MAAAA,aAAa,gBAAgB,KAAK;AAAA,EAAA;AAIrD,MAAI,MAAM,SAAS;AACA,qBAAAC,QAAA,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,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;
|