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