sqlql 0.1.0
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/LICENSE +21 -0
- package/dist/chunk-DQk6qfdC.mjs +18 -0
- package/dist/index.cjs +613 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +304 -0
- package/dist/index.d.mts +304 -0
- package/dist/index.mjs +559 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +28 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Andy Ingram
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
//#region \0rolldown/runtime.js
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __exportAll = (all, no_symbols) => {
|
|
4
|
+
let target = {};
|
|
5
|
+
for (var name in all) {
|
|
6
|
+
__defProp(target, name, {
|
|
7
|
+
get: all[name],
|
|
8
|
+
enumerable: true
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
if (!no_symbols) {
|
|
12
|
+
__defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
13
|
+
}
|
|
14
|
+
return target;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
//#endregion
|
|
18
|
+
export { __exportAll as t };
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,613 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
//#region \0rolldown/runtime.js
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __exportAll = (all, no_symbols) => {
|
|
10
|
+
let target = {};
|
|
11
|
+
for (var name in all) {
|
|
12
|
+
__defProp(target, name, {
|
|
13
|
+
get: all[name],
|
|
14
|
+
enumerable: true
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
if (!no_symbols) {
|
|
18
|
+
__defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
19
|
+
}
|
|
20
|
+
return target;
|
|
21
|
+
};
|
|
22
|
+
var __copyProps = (to, from, except, desc) => {
|
|
23
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
24
|
+
for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
25
|
+
key = keys[i];
|
|
26
|
+
if (!__hasOwnProp.call(to, key) && key !== except) {
|
|
27
|
+
__defProp(to, key, {
|
|
28
|
+
get: ((k) => from[k]).bind(null, key),
|
|
29
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return to;
|
|
35
|
+
};
|
|
36
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
37
|
+
value: mod,
|
|
38
|
+
enumerable: true
|
|
39
|
+
}) : target, mod));
|
|
40
|
+
|
|
41
|
+
//#endregion
|
|
42
|
+
let node_sql_parser = require("node-sql-parser");
|
|
43
|
+
node_sql_parser = __toESM(node_sql_parser);
|
|
44
|
+
|
|
45
|
+
//#region ../core/src/planning.ts
|
|
46
|
+
var planning_exports = /* @__PURE__ */ __exportAll({ defineTableResolver: () => defineTableResolver });
|
|
47
|
+
function defineTableResolver(resolver) {
|
|
48
|
+
return resolver;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
//#endregion
|
|
52
|
+
//#region ../core/src/index.ts
|
|
53
|
+
const DEFAULT_QUERY_BEHAVIOR = {
|
|
54
|
+
filterable: "all",
|
|
55
|
+
sortable: "all",
|
|
56
|
+
maxRows: null
|
|
57
|
+
};
|
|
58
|
+
function defineSchema(schema) {
|
|
59
|
+
return schema;
|
|
60
|
+
}
|
|
61
|
+
function getTable(schema, tableName) {
|
|
62
|
+
const table = schema.tables[tableName];
|
|
63
|
+
if (!table) throw new Error(`Unknown table: ${tableName}`);
|
|
64
|
+
return table;
|
|
65
|
+
}
|
|
66
|
+
function resolveTableQueryBehavior(schema, tableName) {
|
|
67
|
+
const table = getTable(schema, tableName);
|
|
68
|
+
const defaults = schema.defaults?.query;
|
|
69
|
+
return {
|
|
70
|
+
filterable: table.query?.filterable ?? defaults?.filterable ?? DEFAULT_QUERY_BEHAVIOR.filterable,
|
|
71
|
+
sortable: table.query?.sortable ?? defaults?.sortable ?? DEFAULT_QUERY_BEHAVIOR.sortable,
|
|
72
|
+
maxRows: table.query?.maxRows ?? defaults?.maxRows ?? DEFAULT_QUERY_BEHAVIOR.maxRows
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function defineTableMethods(methods) {
|
|
76
|
+
return methods;
|
|
77
|
+
}
|
|
78
|
+
function toSqlDDL(schema, options = {}) {
|
|
79
|
+
const createPrefix = options.ifNotExists ? "CREATE TABLE IF NOT EXISTS" : "CREATE TABLE";
|
|
80
|
+
const statements = [];
|
|
81
|
+
for (const [tableName, table] of Object.entries(schema.tables)) {
|
|
82
|
+
const columnEntries = Object.entries(table.columns);
|
|
83
|
+
if (columnEntries.length === 0) throw new Error(`Cannot generate DDL for table ${tableName} with no columns.`);
|
|
84
|
+
const columnsSql = columnEntries.map(([columnName, columnType]) => ` ${escapeIdentifier(columnName)} ${toSqlType(columnType)}`).join(",\n");
|
|
85
|
+
statements.push(`${createPrefix} ${escapeIdentifier(tableName)} (\n${columnsSql}\n);`);
|
|
86
|
+
}
|
|
87
|
+
return statements.join("\n\n");
|
|
88
|
+
}
|
|
89
|
+
function toSqlType(type) {
|
|
90
|
+
switch (type) {
|
|
91
|
+
case "text": return "TEXT";
|
|
92
|
+
case "integer": return "INTEGER";
|
|
93
|
+
case "boolean": return "BOOLEAN";
|
|
94
|
+
case "timestamp": return "TIMESTAMP";
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function escapeIdentifier(name) {
|
|
98
|
+
return `"${name.replaceAll("\"", "\"\"")}"`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
//#endregion
|
|
102
|
+
//#region ../sql/src/index.ts
|
|
103
|
+
const { Parser } = node_sql_parser.default;
|
|
104
|
+
const parser = new Parser();
|
|
105
|
+
function parseSql(query, schema) {
|
|
106
|
+
const parsed = parseSelectAst(query.text, schema);
|
|
107
|
+
const source = parsed.bindings[0];
|
|
108
|
+
if (!source) throw new Error("SELECT queries must include a FROM clause.");
|
|
109
|
+
return {
|
|
110
|
+
source: source.table,
|
|
111
|
+
selectAll: parsed.selectAll
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
async function query(input) {
|
|
115
|
+
const parsed = parseSelectAst(input.sql, input.schema);
|
|
116
|
+
const rootBinding = parsed.bindings[0];
|
|
117
|
+
if (!rootBinding) throw new Error("SELECT queries must include a FROM clause.");
|
|
118
|
+
for (const binding of parsed.bindings) {
|
|
119
|
+
getTable(input.schema, binding.table);
|
|
120
|
+
if (!input.methods[binding.table]) throw new Error(`No table methods registered for table: ${binding.table}`);
|
|
121
|
+
}
|
|
122
|
+
const projectionByAlias = buildProjection(parsed, input.schema);
|
|
123
|
+
const filtersByAlias = groupFiltersByAlias(parsed.filters);
|
|
124
|
+
const executionOrder = buildExecutionOrder(parsed.bindings, parsed.joinEdges, filtersByAlias);
|
|
125
|
+
const rowsByAlias = /* @__PURE__ */ new Map();
|
|
126
|
+
for (const alias of executionOrder) {
|
|
127
|
+
const binding = parsed.bindings.find((candidate) => candidate.alias === alias);
|
|
128
|
+
if (!binding) throw new Error(`Unknown alias in execution order: ${alias}`);
|
|
129
|
+
const dependencyFilters = buildDependencyFilters(alias, parsed.joinEdges, rowsByAlias);
|
|
130
|
+
const localFilters = filtersByAlias.get(alias) ?? [];
|
|
131
|
+
if (dependencyFilters.some((filter) => filter.op === "in" && filter.values.length === 0)) {
|
|
132
|
+
rowsByAlias.set(alias, []);
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
const defaultMaxRows = resolveTableQueryBehavior(input.schema, binding.table).maxRows;
|
|
136
|
+
const requestWhere = [...localFilters, ...dependencyFilters];
|
|
137
|
+
const requestOrderBy = parsed.bindings.length === 1 && parsed.orderBy.every((term) => term.alias === alias) ? parsed.orderBy.map((term) => ({
|
|
138
|
+
column: term.column,
|
|
139
|
+
direction: term.direction
|
|
140
|
+
})) : void 0;
|
|
141
|
+
let requestLimit = parsed.bindings.length === 1 ? parsed.limit : void 0;
|
|
142
|
+
if (requestLimit == null && defaultMaxRows != null) requestLimit = defaultMaxRows;
|
|
143
|
+
if (requestLimit != null && defaultMaxRows != null && requestLimit > defaultMaxRows) throw new Error(`Requested limit ${requestLimit} exceeds maxRows ${defaultMaxRows} for table ${binding.table}`);
|
|
144
|
+
const method = input.methods[binding.table];
|
|
145
|
+
if (!method) throw new Error(`No table methods registered for table: ${binding.table}`);
|
|
146
|
+
const projection = projectionByAlias.get(alias);
|
|
147
|
+
if (!projection) throw new Error(`Unable to resolve projection columns for alias: ${alias}`);
|
|
148
|
+
const request = {
|
|
149
|
+
table: binding.table,
|
|
150
|
+
alias,
|
|
151
|
+
select: [...projection]
|
|
152
|
+
};
|
|
153
|
+
if (requestWhere.length > 0) request.where = requestWhere;
|
|
154
|
+
if (requestOrderBy && requestOrderBy.length > 0) request.orderBy = requestOrderBy;
|
|
155
|
+
if (requestLimit != null) request.limit = requestLimit;
|
|
156
|
+
const rows = await runScan(method, request, input.context);
|
|
157
|
+
rowsByAlias.set(alias, rows);
|
|
158
|
+
}
|
|
159
|
+
let joinedRows = initializeJoinedRows(rowsByAlias, rootBinding.alias);
|
|
160
|
+
for (const join of parsed.joins) joinedRows = applyInnerJoin(joinedRows, join, rowsByAlias);
|
|
161
|
+
if (parsed.orderBy.length > 0) joinedRows = applyFinalSort(joinedRows, parsed.orderBy);
|
|
162
|
+
if (parsed.limit != null && parsed.bindings.length > 1) joinedRows = joinedRows.slice(0, parsed.limit);
|
|
163
|
+
return projectResultRows(joinedRows, parsed);
|
|
164
|
+
}
|
|
165
|
+
async function runScan(method, request, context) {
|
|
166
|
+
const dependencyFilters = request.where?.filter((clause) => clause.op === "in") ?? [];
|
|
167
|
+
if (dependencyFilters.length === 1 && method.lookup && dependencyFilters[0] && dependencyFilters[0].values.length > 0 && request.orderBy == null && request.limit == null) {
|
|
168
|
+
const lookup = dependencyFilters[0];
|
|
169
|
+
if (!lookup) return method.scan(request, context);
|
|
170
|
+
const nonDependencyFilters = request.where?.filter((clause) => clause !== lookup);
|
|
171
|
+
const fullLookupRequest = {
|
|
172
|
+
table: request.table,
|
|
173
|
+
key: lookup.column,
|
|
174
|
+
values: lookup.values,
|
|
175
|
+
select: request.select
|
|
176
|
+
};
|
|
177
|
+
if (request.alias) fullLookupRequest.alias = request.alias;
|
|
178
|
+
if (nonDependencyFilters && nonDependencyFilters.length > 0) fullLookupRequest.where = nonDependencyFilters;
|
|
179
|
+
return method.lookup(fullLookupRequest, context);
|
|
180
|
+
}
|
|
181
|
+
return method.scan(request, context);
|
|
182
|
+
}
|
|
183
|
+
function parseSelectAst(sql, _schema) {
|
|
184
|
+
const astRaw = parser.astify(sql);
|
|
185
|
+
if (Array.isArray(astRaw)) throw new Error("Only a single SQL statement is supported.");
|
|
186
|
+
const ast = astRaw;
|
|
187
|
+
if (ast.type !== "select") throw new Error("Only SELECT statements are currently supported.");
|
|
188
|
+
const rawFrom = Array.isArray(ast.from) ? ast.from : ast.from ? [ast.from] : [];
|
|
189
|
+
if (rawFrom.length === 0) throw new Error("SELECT queries must include a FROM clause.");
|
|
190
|
+
const bindings = rawFrom.map((entry, index) => {
|
|
191
|
+
if (!entry || typeof entry !== "object" || !("table" in entry)) throw new Error("Unsupported FROM clause entry.");
|
|
192
|
+
const table = entry.table;
|
|
193
|
+
const alias = entry.as;
|
|
194
|
+
if (typeof table !== "string" || table.length === 0) throw new Error("Unable to resolve table name from query.");
|
|
195
|
+
return {
|
|
196
|
+
table,
|
|
197
|
+
alias: typeof alias === "string" && alias.length > 0 ? alias : table,
|
|
198
|
+
index
|
|
199
|
+
};
|
|
200
|
+
});
|
|
201
|
+
const aliasToTable = new Map(bindings.map((binding) => [binding.alias, binding.table]));
|
|
202
|
+
const joins = [];
|
|
203
|
+
const joinEdges = [];
|
|
204
|
+
for (let i = 1; i < rawFrom.length; i += 1) {
|
|
205
|
+
const entry = rawFrom[i];
|
|
206
|
+
const joinType = typeof entry.join === "string" ? entry.join.toUpperCase() : "";
|
|
207
|
+
if (joinType !== "INNER JOIN" && joinType !== "JOIN") throw new Error(`Unsupported join type: ${String(entry.join ?? "unknown")}`);
|
|
208
|
+
const parsedJoin = parseJoinCondition(entry.on);
|
|
209
|
+
if (!aliasToTable.has(parsedJoin.leftAlias) || !aliasToTable.has(parsedJoin.rightAlias)) throw new Error("JOIN condition references an unknown table alias.");
|
|
210
|
+
const joinedAlias = typeof entry.as === "string" && entry.as.length > 0 ? entry.as : String(entry.table);
|
|
211
|
+
joins.push({
|
|
212
|
+
alias: joinedAlias,
|
|
213
|
+
join: "inner",
|
|
214
|
+
condition: parsedJoin
|
|
215
|
+
});
|
|
216
|
+
joinEdges.push(parsedJoin);
|
|
217
|
+
}
|
|
218
|
+
const whereParts = flattenAndConditions(ast.where);
|
|
219
|
+
const filters = [];
|
|
220
|
+
for (const part of whereParts) {
|
|
221
|
+
if (!part || typeof part !== "object") throw new Error("Unsupported WHERE clause.");
|
|
222
|
+
const binary = part;
|
|
223
|
+
if (binary.type !== "binary_expr") throw new Error("Only binary predicates are supported in WHERE clauses.");
|
|
224
|
+
const operator = normalizeBinaryOperator(binary.operator);
|
|
225
|
+
if (operator === "in") {
|
|
226
|
+
const colRef = toColumnRef(binary.left);
|
|
227
|
+
if (!colRef) throw new Error("IN predicates must use a column on the left-hand side.");
|
|
228
|
+
const values = parseExpressionList(binary.right);
|
|
229
|
+
filters.push({
|
|
230
|
+
alias: colRef.alias,
|
|
231
|
+
clause: {
|
|
232
|
+
op: "in",
|
|
233
|
+
column: colRef.column,
|
|
234
|
+
values
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
const leftCol = toColumnRef(binary.left);
|
|
240
|
+
const rightCol = toColumnRef(binary.right);
|
|
241
|
+
if (operator === "eq" && leftCol && rightCol) {
|
|
242
|
+
joinEdges.push({
|
|
243
|
+
leftAlias: leftCol.alias,
|
|
244
|
+
leftColumn: leftCol.column,
|
|
245
|
+
rightAlias: rightCol.alias,
|
|
246
|
+
rightColumn: rightCol.column
|
|
247
|
+
});
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
const leftLiteral = parseLiteral(binary.left);
|
|
251
|
+
const rightLiteral = parseLiteral(binary.right);
|
|
252
|
+
if (leftCol && rightLiteral !== void 0) {
|
|
253
|
+
filters.push({
|
|
254
|
+
alias: leftCol.alias,
|
|
255
|
+
clause: {
|
|
256
|
+
op: operator,
|
|
257
|
+
column: leftCol.column,
|
|
258
|
+
value: rightLiteral
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
if (rightCol && leftLiteral !== void 0) {
|
|
264
|
+
filters.push({
|
|
265
|
+
alias: rightCol.alias,
|
|
266
|
+
clause: {
|
|
267
|
+
op: invertOperator(operator),
|
|
268
|
+
column: rightCol.column,
|
|
269
|
+
value: leftLiteral
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
throw new Error("WHERE predicates must compare columns to literals (or column equality joins).");
|
|
275
|
+
}
|
|
276
|
+
const selectColumnsRaw = ast.columns;
|
|
277
|
+
const selectAll = selectColumnsRaw === "*" || Array.isArray(selectColumnsRaw) && selectColumnsRaw.length === 1 && isStarColumn(selectColumnsRaw[0]);
|
|
278
|
+
const selectColumns = [];
|
|
279
|
+
if (!selectAll) {
|
|
280
|
+
if (!Array.isArray(selectColumnsRaw)) throw new Error("Unsupported SELECT clause.");
|
|
281
|
+
for (const item of selectColumnsRaw) {
|
|
282
|
+
if (!item || typeof item !== "object") throw new Error("Unsupported SELECT item.");
|
|
283
|
+
const expr = item.expr;
|
|
284
|
+
const colRef = toColumnRef(expr);
|
|
285
|
+
if (!colRef) throw new Error("Only direct column references are currently supported in SELECT.");
|
|
286
|
+
const as = item.as;
|
|
287
|
+
selectColumns.push({
|
|
288
|
+
alias: colRef.alias,
|
|
289
|
+
column: colRef.column,
|
|
290
|
+
output: typeof as === "string" && as.length > 0 ? as : selectColumns.some((existing) => existing.column === colRef.column) ? `${colRef.alias}.${colRef.column}` : colRef.column
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
const orderBy = [];
|
|
295
|
+
if (Array.isArray(ast.orderby)) for (const item of ast.orderby) {
|
|
296
|
+
const colRef = toColumnRef(item.expr);
|
|
297
|
+
if (!colRef) throw new Error("Only column references are currently supported in ORDER BY.");
|
|
298
|
+
const rawType = item.type;
|
|
299
|
+
orderBy.push({
|
|
300
|
+
alias: colRef.alias,
|
|
301
|
+
column: colRef.column,
|
|
302
|
+
direction: rawType === "DESC" ? "desc" : "asc"
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
let limit;
|
|
306
|
+
const rawLimit = ast.limit;
|
|
307
|
+
if (rawLimit && Array.isArray(rawLimit.value) && rawLimit.value.length > 0) {
|
|
308
|
+
const first = rawLimit.value[0]?.value;
|
|
309
|
+
if (typeof first === "number") limit = first;
|
|
310
|
+
else if (typeof first === "string") {
|
|
311
|
+
const parsed = Number(first);
|
|
312
|
+
if (Number.isFinite(parsed)) limit = parsed;
|
|
313
|
+
}
|
|
314
|
+
if (limit == null) throw new Error("Unable to parse LIMIT value.");
|
|
315
|
+
}
|
|
316
|
+
if (selectAll && bindings.length > 1) throw new Error("SELECT * is only supported for single-table queries.");
|
|
317
|
+
const parsedQuery = {
|
|
318
|
+
bindings,
|
|
319
|
+
joins,
|
|
320
|
+
joinEdges: uniqueJoinEdges(joinEdges),
|
|
321
|
+
filters,
|
|
322
|
+
selectAll,
|
|
323
|
+
selectColumns,
|
|
324
|
+
orderBy
|
|
325
|
+
};
|
|
326
|
+
if (limit != null) parsedQuery.limit = limit;
|
|
327
|
+
return parsedQuery;
|
|
328
|
+
}
|
|
329
|
+
function buildProjection(parsed, schema) {
|
|
330
|
+
const projections = /* @__PURE__ */ new Map();
|
|
331
|
+
for (const binding of parsed.bindings) projections.set(binding.alias, /* @__PURE__ */ new Set());
|
|
332
|
+
if (parsed.selectAll) {
|
|
333
|
+
const base = parsed.bindings[0];
|
|
334
|
+
if (!base) throw new Error("SELECT queries must include a FROM clause.");
|
|
335
|
+
const allColumns = Object.keys(getTable(schema, base.table).columns);
|
|
336
|
+
for (const column of allColumns) projections.get(base.alias)?.add(column);
|
|
337
|
+
} else for (const item of parsed.selectColumns) projections.get(item.alias)?.add(item.column);
|
|
338
|
+
for (const join of parsed.joinEdges) {
|
|
339
|
+
projections.get(join.leftAlias)?.add(join.leftColumn);
|
|
340
|
+
projections.get(join.rightAlias)?.add(join.rightColumn);
|
|
341
|
+
}
|
|
342
|
+
for (const filter of parsed.filters) projections.get(filter.alias)?.add(filter.clause.column);
|
|
343
|
+
for (const term of parsed.orderBy) projections.get(term.alias)?.add(term.column);
|
|
344
|
+
for (const [alias, cols] of projections) if (cols.size === 0) {
|
|
345
|
+
const binding = parsed.bindings.find((candidate) => candidate.alias === alias);
|
|
346
|
+
if (binding) {
|
|
347
|
+
const firstColumn = Object.keys(getTable(schema, binding.table).columns)[0];
|
|
348
|
+
if (!firstColumn) throw new Error(`Table ${binding.table} has no columns.`);
|
|
349
|
+
cols.add(firstColumn);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
return projections;
|
|
353
|
+
}
|
|
354
|
+
function groupFiltersByAlias(filters) {
|
|
355
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
356
|
+
for (const filter of filters) {
|
|
357
|
+
const existing = grouped.get(filter.alias) ?? [];
|
|
358
|
+
existing.push(filter.clause);
|
|
359
|
+
grouped.set(filter.alias, existing);
|
|
360
|
+
}
|
|
361
|
+
return grouped;
|
|
362
|
+
}
|
|
363
|
+
function buildExecutionOrder(bindings, joinEdges, filtersByAlias) {
|
|
364
|
+
const score = /* @__PURE__ */ new Map();
|
|
365
|
+
for (const binding of bindings) score.set(binding.alias, filtersByAlias.get(binding.alias)?.length ?? 0);
|
|
366
|
+
const unvisited = new Set(bindings.map((binding) => binding.alias));
|
|
367
|
+
const visited = /* @__PURE__ */ new Set();
|
|
368
|
+
const order = [];
|
|
369
|
+
while (unvisited.size > 0) {
|
|
370
|
+
const candidates = [...unvisited].filter((alias) => {
|
|
371
|
+
if (visited.size === 0) return true;
|
|
372
|
+
return joinEdges.some((edge) => edge.leftAlias === alias && visited.has(edge.rightAlias) || edge.rightAlias === alias && visited.has(edge.leftAlias));
|
|
373
|
+
});
|
|
374
|
+
const pool = candidates.length > 0 ? candidates : [...unvisited];
|
|
375
|
+
pool.sort((a, b) => {
|
|
376
|
+
const aScore = score.get(a) ?? 0;
|
|
377
|
+
const bScore = score.get(b) ?? 0;
|
|
378
|
+
if (aScore !== bScore) return bScore - aScore;
|
|
379
|
+
const aIndex = bindings.find((binding) => binding.alias === a)?.index ?? 0;
|
|
380
|
+
return (bindings.find((binding) => binding.alias === b)?.index ?? 0) - aIndex;
|
|
381
|
+
});
|
|
382
|
+
const next = pool[0];
|
|
383
|
+
if (!next) break;
|
|
384
|
+
order.push(next);
|
|
385
|
+
visited.add(next);
|
|
386
|
+
unvisited.delete(next);
|
|
387
|
+
}
|
|
388
|
+
return order;
|
|
389
|
+
}
|
|
390
|
+
function buildDependencyFilters(alias, joinEdges, rowsByAlias) {
|
|
391
|
+
const clauses = [];
|
|
392
|
+
for (const edge of joinEdges) {
|
|
393
|
+
if (edge.leftAlias === alias && rowsByAlias.has(edge.rightAlias)) {
|
|
394
|
+
clauses.push({
|
|
395
|
+
op: "in",
|
|
396
|
+
column: edge.leftColumn,
|
|
397
|
+
values: uniqueValues(rowsByAlias.get(edge.rightAlias) ?? [], edge.rightColumn)
|
|
398
|
+
});
|
|
399
|
+
continue;
|
|
400
|
+
}
|
|
401
|
+
if (edge.rightAlias === alias && rowsByAlias.has(edge.leftAlias)) clauses.push({
|
|
402
|
+
op: "in",
|
|
403
|
+
column: edge.rightColumn,
|
|
404
|
+
values: uniqueValues(rowsByAlias.get(edge.leftAlias) ?? [], edge.leftColumn)
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
return dedupeInClauses(clauses);
|
|
408
|
+
}
|
|
409
|
+
function initializeJoinedRows(rowsByAlias, baseAlias) {
|
|
410
|
+
return (rowsByAlias.get(baseAlias) ?? []).map((row) => ({ [baseAlias]: row }));
|
|
411
|
+
}
|
|
412
|
+
function applyInnerJoin(existing, join, rowsByAlias) {
|
|
413
|
+
const rightRows = rowsByAlias.get(join.alias) ?? [];
|
|
414
|
+
const isJoinAliasLeft = join.condition.leftAlias === join.alias;
|
|
415
|
+
const joinAliasColumn = isJoinAliasLeft ? join.condition.leftColumn : join.condition.rightColumn;
|
|
416
|
+
const existingAlias = isJoinAliasLeft ? join.condition.rightAlias : join.condition.leftAlias;
|
|
417
|
+
const existingColumn = isJoinAliasLeft ? join.condition.rightColumn : join.condition.leftColumn;
|
|
418
|
+
const index = /* @__PURE__ */ new Map();
|
|
419
|
+
for (const row of rightRows) {
|
|
420
|
+
const key = row[joinAliasColumn];
|
|
421
|
+
const bucket = index.get(key) ?? [];
|
|
422
|
+
bucket.push(row);
|
|
423
|
+
index.set(key, bucket);
|
|
424
|
+
}
|
|
425
|
+
const joined = [];
|
|
426
|
+
for (const bundle of existing) {
|
|
427
|
+
const leftRow = bundle[existingAlias];
|
|
428
|
+
if (!leftRow) continue;
|
|
429
|
+
const key = leftRow[existingColumn];
|
|
430
|
+
const matches = index.get(key) ?? [];
|
|
431
|
+
for (const match of matches) joined.push({
|
|
432
|
+
...bundle,
|
|
433
|
+
[join.alias]: match
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
return joined;
|
|
437
|
+
}
|
|
438
|
+
function applyFinalSort(rows, orderBy) {
|
|
439
|
+
const sorted = [...rows];
|
|
440
|
+
sorted.sort((left, right) => {
|
|
441
|
+
for (const term of orderBy) {
|
|
442
|
+
const leftValue = left[term.alias]?.[term.column];
|
|
443
|
+
const rightValue = right[term.alias]?.[term.column];
|
|
444
|
+
if (leftValue === rightValue) continue;
|
|
445
|
+
const comparison = compareNullableValues(leftValue ?? null, rightValue ?? null);
|
|
446
|
+
return term.direction === "asc" ? comparison : -comparison;
|
|
447
|
+
}
|
|
448
|
+
return 0;
|
|
449
|
+
});
|
|
450
|
+
return sorted;
|
|
451
|
+
}
|
|
452
|
+
function projectResultRows(rows, parsed) {
|
|
453
|
+
if (parsed.selectAll) {
|
|
454
|
+
const baseAlias = parsed.bindings[0]?.alias;
|
|
455
|
+
if (!baseAlias) return [];
|
|
456
|
+
return rows.map((row) => {
|
|
457
|
+
const baseRow = row[baseAlias];
|
|
458
|
+
return baseRow ? { ...baseRow } : {};
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
return rows.map((bundle) => {
|
|
462
|
+
const out = {};
|
|
463
|
+
for (const item of parsed.selectColumns) out[item.output] = bundle[item.alias]?.[item.column] ?? null;
|
|
464
|
+
return out;
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
function parseJoinCondition(raw) {
|
|
468
|
+
const expr = raw;
|
|
469
|
+
if (expr?.type !== "binary_expr" || expr.operator !== "=") throw new Error("Only equality join conditions are currently supported.");
|
|
470
|
+
const left = toColumnRef(expr.left);
|
|
471
|
+
const right = toColumnRef(expr.right);
|
|
472
|
+
if (!left || !right) throw new Error("JOIN conditions must compare two columns.");
|
|
473
|
+
return {
|
|
474
|
+
leftAlias: left.alias,
|
|
475
|
+
leftColumn: left.column,
|
|
476
|
+
rightAlias: right.alias,
|
|
477
|
+
rightColumn: right.column
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
function flattenAndConditions(where) {
|
|
481
|
+
if (!where) return [];
|
|
482
|
+
const expr = where;
|
|
483
|
+
if (expr.type === "binary_expr" && expr.operator === "AND") return [...flattenAndConditions(expr.left), ...flattenAndConditions(expr.right)];
|
|
484
|
+
if (expr.type === "binary_expr" && expr.operator === "OR") throw new Error("OR predicates are not yet supported.");
|
|
485
|
+
return [where];
|
|
486
|
+
}
|
|
487
|
+
function normalizeBinaryOperator(raw) {
|
|
488
|
+
switch (raw) {
|
|
489
|
+
case "=": return "eq";
|
|
490
|
+
case "!=":
|
|
491
|
+
case "<>": return "neq";
|
|
492
|
+
case ">": return "gt";
|
|
493
|
+
case ">=": return "gte";
|
|
494
|
+
case "<": return "lt";
|
|
495
|
+
case "<=": return "lte";
|
|
496
|
+
case "IN": return "in";
|
|
497
|
+
default: throw new Error(`Unsupported operator: ${String(raw)}`);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
function invertOperator(op) {
|
|
501
|
+
switch (op) {
|
|
502
|
+
case "eq": return "eq";
|
|
503
|
+
case "neq": return "neq";
|
|
504
|
+
case "gt": return "lt";
|
|
505
|
+
case "gte": return "lte";
|
|
506
|
+
case "lt": return "gt";
|
|
507
|
+
case "lte": return "gte";
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
function toColumnRef(raw) {
|
|
511
|
+
const expr = raw;
|
|
512
|
+
if (expr?.type !== "column_ref") return;
|
|
513
|
+
if (typeof expr.column !== "string" || expr.column.length === 0) return;
|
|
514
|
+
if (typeof expr.table !== "string" || expr.table.length === 0) throw new Error(`Ambiguous unqualified column reference: ${expr.column}`);
|
|
515
|
+
return {
|
|
516
|
+
alias: expr.table,
|
|
517
|
+
column: expr.column
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
function isStarColumn(raw) {
|
|
521
|
+
const expr = raw.expr;
|
|
522
|
+
return expr?.type === "column_ref" && expr.column === "*";
|
|
523
|
+
}
|
|
524
|
+
function parseLiteral(raw) {
|
|
525
|
+
const expr = raw;
|
|
526
|
+
switch (expr?.type) {
|
|
527
|
+
case "single_quote_string":
|
|
528
|
+
case "double_quote_string":
|
|
529
|
+
case "string": return String(expr.value ?? "");
|
|
530
|
+
case "number": {
|
|
531
|
+
const value = expr.value;
|
|
532
|
+
if (typeof value === "number") return value;
|
|
533
|
+
if (typeof value === "string") {
|
|
534
|
+
const parsed = Number(value);
|
|
535
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
536
|
+
}
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
case "bool": return Boolean(expr.value);
|
|
540
|
+
case "null": return null;
|
|
541
|
+
default: return;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
function parseExpressionList(raw) {
|
|
545
|
+
const expr = raw;
|
|
546
|
+
if (expr?.type !== "expr_list" || !Array.isArray(expr.value)) throw new Error("IN predicates must use literal lists.");
|
|
547
|
+
const values = expr.value.map((entry) => parseLiteral(entry));
|
|
548
|
+
if (values.some((value) => value === void 0)) throw new Error("IN predicates must contain only literal values.");
|
|
549
|
+
return values;
|
|
550
|
+
}
|
|
551
|
+
function uniqueJoinEdges(edges) {
|
|
552
|
+
const seen = /* @__PURE__ */ new Set();
|
|
553
|
+
const out = [];
|
|
554
|
+
for (const edge of edges) {
|
|
555
|
+
const key = `${edge.leftAlias}.${edge.leftColumn}=${edge.rightAlias}.${edge.rightColumn}`;
|
|
556
|
+
const reverseKey = `${edge.rightAlias}.${edge.rightColumn}=${edge.leftAlias}.${edge.leftColumn}`;
|
|
557
|
+
if (seen.has(key) || seen.has(reverseKey)) continue;
|
|
558
|
+
seen.add(key);
|
|
559
|
+
out.push(edge);
|
|
560
|
+
}
|
|
561
|
+
return out;
|
|
562
|
+
}
|
|
563
|
+
function uniqueValues(rows, column) {
|
|
564
|
+
const seen = /* @__PURE__ */ new Set();
|
|
565
|
+
const out = [];
|
|
566
|
+
for (const row of rows) {
|
|
567
|
+
const value = row[column] ?? null;
|
|
568
|
+
if (seen.has(value)) continue;
|
|
569
|
+
seen.add(value);
|
|
570
|
+
out.push(value);
|
|
571
|
+
}
|
|
572
|
+
return out;
|
|
573
|
+
}
|
|
574
|
+
function compareNullableValues(left, right) {
|
|
575
|
+
if (left === right) return 0;
|
|
576
|
+
if (left == null) return -1;
|
|
577
|
+
if (right == null) return 1;
|
|
578
|
+
if (typeof left === "number" && typeof right === "number") return left < right ? -1 : 1;
|
|
579
|
+
if (typeof left === "boolean" && typeof right === "boolean") return Number(left) < Number(right) ? -1 : 1;
|
|
580
|
+
return String(left) < String(right) ? -1 : 1;
|
|
581
|
+
}
|
|
582
|
+
function dedupeInClauses(clauses) {
|
|
583
|
+
const out = [];
|
|
584
|
+
const seen = /* @__PURE__ */ new Set();
|
|
585
|
+
for (const clause of clauses) {
|
|
586
|
+
if (clause.op !== "in") {
|
|
587
|
+
out.push(clause);
|
|
588
|
+
continue;
|
|
589
|
+
}
|
|
590
|
+
const key = `${clause.column}:${JSON.stringify(clause.values)}`;
|
|
591
|
+
if (seen.has(key)) continue;
|
|
592
|
+
seen.add(key);
|
|
593
|
+
out.push(clause);
|
|
594
|
+
}
|
|
595
|
+
return out;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
//#endregion
|
|
599
|
+
exports.DEFAULT_QUERY_BEHAVIOR = DEFAULT_QUERY_BEHAVIOR;
|
|
600
|
+
exports.defineSchema = defineSchema;
|
|
601
|
+
exports.defineTableMethods = defineTableMethods;
|
|
602
|
+
exports.getTable = getTable;
|
|
603
|
+
exports.parseSql = parseSql;
|
|
604
|
+
Object.defineProperty(exports, 'planning', {
|
|
605
|
+
enumerable: true,
|
|
606
|
+
get: function () {
|
|
607
|
+
return planning_exports;
|
|
608
|
+
}
|
|
609
|
+
});
|
|
610
|
+
exports.query = query;
|
|
611
|
+
exports.resolveTableQueryBehavior = resolveTableQueryBehavior;
|
|
612
|
+
exports.toSqlDDL = toSqlDDL;
|
|
613
|
+
//# sourceMappingURL=index.cjs.map
|