@dbsp/nql 1.2.0 → 1.3.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/README.md +36 -0
- package/dist/index.js +396 -390
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { NQL_INTERNAL_COMPILER_OPTIONS } from '@dbsp/types/internal';
|
|
1
|
+
import { NQL_INTERNAL_COMPILER_OPTIONS, isNqlBindingRef, getNqlBindingRefName, createNqlBindingRef } from '@dbsp/types/internal';
|
|
2
2
|
import { NQL_SELECT_AGGREGATE_FUNCTIONS, NQL_SELECT_JSON_FUNCTIONS, NQL_SELECT_SCALAR_FUNCTIONS, isParamIntent, isNqlSelectWindowFunctionAllowed, isRankingWindowFunction } from '@dbsp/types';
|
|
3
3
|
import { createToken, Lexer, CstParser } from 'chevrotain';
|
|
4
4
|
|
|
@@ -216,7 +216,7 @@ function expressionToValue(expr, ctx) {
|
|
|
216
216
|
case "null":
|
|
217
217
|
return null;
|
|
218
218
|
case "path":
|
|
219
|
-
return
|
|
219
|
+
return createNqlBindingRef(expr.segments.join("."));
|
|
220
220
|
case "function": {
|
|
221
221
|
return {
|
|
222
222
|
$fn: expr.name,
|
|
@@ -442,6 +442,285 @@ function coerceToStringKey(expr, contextLabel, ctx) {
|
|
|
442
442
|
);
|
|
443
443
|
}
|
|
444
444
|
|
|
445
|
+
// src/compiler/compile-mutation.ts
|
|
446
|
+
function assignMutationValue(target, column, value, ctx) {
|
|
447
|
+
target[column] = expressionToValue(value, ctx);
|
|
448
|
+
}
|
|
449
|
+
function compileMutationPipeline(pipeline, ctx, fns, bindings) {
|
|
450
|
+
ctx.currentFromTable = pipeline.mutation.table;
|
|
451
|
+
const mutation = compileMutation(pipeline.mutation, ctx, fns, bindings);
|
|
452
|
+
let returning;
|
|
453
|
+
for (const clause of pipeline.clauses) {
|
|
454
|
+
if (clause.type === "select") {
|
|
455
|
+
returning = extractReturningColumns(clause, ctx);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
if (returning) {
|
|
459
|
+
return {
|
|
460
|
+
mutation: { ...mutation, returning }
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
return { mutation };
|
|
464
|
+
}
|
|
465
|
+
function compileMutation(mutation, ctx, fns, bindings) {
|
|
466
|
+
switch (mutation.type) {
|
|
467
|
+
case "insert":
|
|
468
|
+
return compileInsert(mutation, ctx);
|
|
469
|
+
case "insert_from":
|
|
470
|
+
return compileInsertFrom(mutation, ctx, fns, bindings);
|
|
471
|
+
case "update":
|
|
472
|
+
return compileUpdate(mutation, ctx, fns, bindings);
|
|
473
|
+
case "delete":
|
|
474
|
+
return compileDelete(mutation, ctx, fns, bindings);
|
|
475
|
+
case "upsert":
|
|
476
|
+
return compileUpsert(mutation, ctx, fns, bindings);
|
|
477
|
+
case "upsert_from":
|
|
478
|
+
return compileUpsertFrom(mutation, ctx, fns, bindings);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
function compileInsert(insert, ctx) {
|
|
482
|
+
ctx.validator?.validateTable(insert.table);
|
|
483
|
+
const allColumns = /* @__PURE__ */ new Set();
|
|
484
|
+
for (const row of insert.rows) {
|
|
485
|
+
for (const assignment of row) {
|
|
486
|
+
ctx.validator?.validateColumn(insert.table, assignment.column);
|
|
487
|
+
allColumns.add(assignment.column);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
const values = [];
|
|
491
|
+
for (const row of insert.rows) {
|
|
492
|
+
const rowValues = {};
|
|
493
|
+
const rowColumns = /* @__PURE__ */ new Set();
|
|
494
|
+
for (const assignment of row) {
|
|
495
|
+
rowColumns.add(assignment.column);
|
|
496
|
+
assignMutationValue(rowValues, assignment.column, assignment.value, ctx);
|
|
497
|
+
}
|
|
498
|
+
for (const col of allColumns) {
|
|
499
|
+
if (!rowColumns.has(col)) {
|
|
500
|
+
rowValues[col] = void 0;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
values.push(rowValues);
|
|
504
|
+
}
|
|
505
|
+
return {
|
|
506
|
+
type: "insert",
|
|
507
|
+
table: insert.table,
|
|
508
|
+
values
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
function compileInsertFrom(insertFrom, ctx, fns, bindings) {
|
|
512
|
+
ctx.validator?.validateTable(insertFrom.table);
|
|
513
|
+
const sourceQuery = bindings?.get(insertFrom.source);
|
|
514
|
+
if (!sourceQuery) {
|
|
515
|
+
ctx.validator?.validateTable(insertFrom.source);
|
|
516
|
+
}
|
|
517
|
+
ctx.currentFromTable = insertFrom.source;
|
|
518
|
+
return {
|
|
519
|
+
type: "insert_from",
|
|
520
|
+
table: insertFrom.table,
|
|
521
|
+
source: insertFrom.source,
|
|
522
|
+
...sourceQuery !== void 0 && { sourceQuery },
|
|
523
|
+
/* v8 ignore next — NQL grammar does not produce explicit column lists for INSERT FROM -- @preserve */
|
|
524
|
+
...insertFrom.columns !== void 0 && { columns: insertFrom.columns },
|
|
525
|
+
...insertFrom.where !== void 0 && {
|
|
526
|
+
where: fns.compileExpression(insertFrom.where, ctx, fns)
|
|
527
|
+
},
|
|
528
|
+
...insertFrom.limit !== void 0 && {
|
|
529
|
+
limit: resolveIntegerCount(insertFrom.limit, ctx, "insert-from limit")
|
|
530
|
+
}
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
function compileUpdate(update, ctx, fns, bindings) {
|
|
534
|
+
ctx.currentFromTable = update.table;
|
|
535
|
+
ctx.validator?.validateTable(update.table);
|
|
536
|
+
const set = {};
|
|
537
|
+
for (const assignment of update.assignments) {
|
|
538
|
+
ctx.validator?.validateColumn(update.table, assignment.column);
|
|
539
|
+
assignMutationValue(set, assignment.column, assignment.value, ctx);
|
|
540
|
+
}
|
|
541
|
+
if (update.where) {
|
|
542
|
+
return {
|
|
543
|
+
type: "update",
|
|
544
|
+
table: update.table,
|
|
545
|
+
set,
|
|
546
|
+
where: resolveBindingsInWhere(
|
|
547
|
+
fns.compileExpression(update.where, ctx, fns),
|
|
548
|
+
bindings
|
|
549
|
+
)
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
if (!ctx.allowUnfilteredMutations) {
|
|
553
|
+
throw new Error(
|
|
554
|
+
"update without a where clause would affect all rows; pass { allowUnfilteredMutations: true } to the compiler to allow an unfiltered update"
|
|
555
|
+
);
|
|
556
|
+
}
|
|
557
|
+
return {
|
|
558
|
+
type: "update",
|
|
559
|
+
table: update.table,
|
|
560
|
+
set,
|
|
561
|
+
allowAll: true
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
function compileDelete(del, ctx, fns, bindings) {
|
|
565
|
+
ctx.currentFromTable = del.table;
|
|
566
|
+
ctx.validator?.validateTable(del.table);
|
|
567
|
+
if (del.where) {
|
|
568
|
+
return {
|
|
569
|
+
type: "delete",
|
|
570
|
+
table: del.table,
|
|
571
|
+
where: resolveBindingsInWhere(
|
|
572
|
+
fns.compileExpression(del.where, ctx, fns),
|
|
573
|
+
bindings
|
|
574
|
+
)
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
if (!ctx.allowUnfilteredMutations) {
|
|
578
|
+
throw new Error(
|
|
579
|
+
"delete without a where clause would affect all rows; pass { allowUnfilteredMutations: true } to the compiler to allow an unfiltered delete"
|
|
580
|
+
);
|
|
581
|
+
}
|
|
582
|
+
return {
|
|
583
|
+
type: "delete",
|
|
584
|
+
table: del.table,
|
|
585
|
+
allowAll: true
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
function compileUpsert(upsert, ctx, fns, bindings) {
|
|
589
|
+
ctx.currentFromTable = upsert.table;
|
|
590
|
+
ctx.validator?.validateTable(upsert.table);
|
|
591
|
+
const values = {};
|
|
592
|
+
for (const assignment of upsert.assignments) {
|
|
593
|
+
ctx.validator?.validateColumn(upsert.table, assignment.column);
|
|
594
|
+
assignMutationValue(values, assignment.column, assignment.value, ctx);
|
|
595
|
+
}
|
|
596
|
+
for (const col of upsert.conflictColumns) {
|
|
597
|
+
ctx.validator?.validateColumn(upsert.table, col);
|
|
598
|
+
}
|
|
599
|
+
return {
|
|
600
|
+
type: "upsert",
|
|
601
|
+
table: upsert.table,
|
|
602
|
+
values: [values],
|
|
603
|
+
onConflict: { columns: upsert.conflictColumns },
|
|
604
|
+
action: {
|
|
605
|
+
type: "doUpdate",
|
|
606
|
+
set: values,
|
|
607
|
+
...upsert.where !== void 0 && {
|
|
608
|
+
where: resolveBindingsInWhere(
|
|
609
|
+
fns.compileExpression(upsert.where, ctx, fns),
|
|
610
|
+
bindings
|
|
611
|
+
)
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
function compileUpsertFrom(upsertFrom, ctx, fns, bindings) {
|
|
617
|
+
ctx.validator?.validateTable(upsertFrom.table);
|
|
618
|
+
const sourceQuery = bindings?.get(upsertFrom.source);
|
|
619
|
+
if (!sourceQuery) {
|
|
620
|
+
ctx.validator?.validateTable(upsertFrom.source);
|
|
621
|
+
}
|
|
622
|
+
for (const col of upsertFrom.conflictColumns) {
|
|
623
|
+
ctx.validator?.validateColumn(upsertFrom.table, col);
|
|
624
|
+
}
|
|
625
|
+
ctx.currentFromTable = upsertFrom.source;
|
|
626
|
+
return {
|
|
627
|
+
type: "upsert_from",
|
|
628
|
+
table: upsertFrom.table,
|
|
629
|
+
source: upsertFrom.source,
|
|
630
|
+
conflictColumns: upsertFrom.conflictColumns,
|
|
631
|
+
...sourceQuery !== void 0 && { sourceQuery },
|
|
632
|
+
/* v8 ignore start — NQL grammar does not produce explicit column lists for UPSERT FROM -- @preserve */
|
|
633
|
+
...upsertFrom.columns !== void 0 && {
|
|
634
|
+
columns: upsertFrom.columns
|
|
635
|
+
},
|
|
636
|
+
/* v8 ignore stop -- @preserve */
|
|
637
|
+
...upsertFrom.where !== void 0 && {
|
|
638
|
+
where: fns.compileExpression(upsertFrom.where, ctx, fns)
|
|
639
|
+
},
|
|
640
|
+
...upsertFrom.limit !== void 0 && {
|
|
641
|
+
limit: resolveIntegerCount(upsertFrom.limit, ctx, "upsert-from limit")
|
|
642
|
+
}
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
function extractReturningColumns(clause, ctx) {
|
|
646
|
+
const columns = [];
|
|
647
|
+
for (const item of clause.items) {
|
|
648
|
+
if (item.type === "star") {
|
|
649
|
+
return ["*"];
|
|
650
|
+
}
|
|
651
|
+
if (item.type === "expression") {
|
|
652
|
+
const field = expressionToField(item.expression);
|
|
653
|
+
if (field) {
|
|
654
|
+
if (ctx.currentFromTable && !field.includes(".")) {
|
|
655
|
+
ctx.validator?.validateColumn(ctx.currentFromTable, field);
|
|
656
|
+
}
|
|
657
|
+
columns.push(item.alias ?? field);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
return columns;
|
|
662
|
+
}
|
|
663
|
+
function resolveBindingsInWhere(where, bindings) {
|
|
664
|
+
if (!bindings || bindings.size === 0) return where;
|
|
665
|
+
if (where.kind === "in") {
|
|
666
|
+
const inWhere = where;
|
|
667
|
+
const inValues = inWhere.subquery ? void 0 : inWhere.values;
|
|
668
|
+
if (inValues && inValues.length === 1) {
|
|
669
|
+
const val = inValues[0];
|
|
670
|
+
if (!isParamIntent(val) && isNqlBindingRef(val)) {
|
|
671
|
+
const ref = getNqlBindingRefName(val);
|
|
672
|
+
if (bindings.has(ref)) {
|
|
673
|
+
const boundQuery = bindings.get(ref);
|
|
674
|
+
const boundSelect = boundQuery.select;
|
|
675
|
+
const selectFields = boundSelect && "fields" in boundSelect ? boundSelect.fields : void 0;
|
|
676
|
+
const cteRef = {
|
|
677
|
+
type: "select",
|
|
678
|
+
from: ref,
|
|
679
|
+
...selectFields && {
|
|
680
|
+
select: {
|
|
681
|
+
type: "fields",
|
|
682
|
+
fields: selectFields
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
};
|
|
686
|
+
return {
|
|
687
|
+
kind: "in",
|
|
688
|
+
field: inWhere.field,
|
|
689
|
+
subquery: cteRef
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
return where;
|
|
695
|
+
}
|
|
696
|
+
if (where.kind === "not") {
|
|
697
|
+
const notWhere = where;
|
|
698
|
+
const resolved = resolveBindingsInWhere(notWhere.condition, bindings);
|
|
699
|
+
return resolved === notWhere.condition ? where : { kind: "not", condition: resolved };
|
|
700
|
+
}
|
|
701
|
+
if (where.kind === "and" || where.kind === "or") {
|
|
702
|
+
const compound = where;
|
|
703
|
+
const resolved = compound.conditions.map(
|
|
704
|
+
(c) => resolveBindingsInWhere(c, bindings)
|
|
705
|
+
);
|
|
706
|
+
const changed = resolved.some((r, i) => r !== compound.conditions[i]);
|
|
707
|
+
return changed ? { kind: compound.kind, conditions: resolved } : where;
|
|
708
|
+
}
|
|
709
|
+
return where;
|
|
710
|
+
}
|
|
711
|
+
function extractBindName(stmt) {
|
|
712
|
+
if (stmt.type === "query") {
|
|
713
|
+
for (const clause of stmt.clauses) {
|
|
714
|
+
if (clause.type === "bind") return clause.name;
|
|
715
|
+
}
|
|
716
|
+
} else if (stmt.type === "mutationPipeline") {
|
|
717
|
+
for (const clause of stmt.clauses) {
|
|
718
|
+
if (clause.type === "bind") return clause.name;
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
return void 0;
|
|
722
|
+
}
|
|
723
|
+
|
|
445
724
|
// src/compiler/include-builder.ts
|
|
446
725
|
function buildNestedIncludes(paths, flatMode) {
|
|
447
726
|
const root = { children: /* @__PURE__ */ new Map() };
|
|
@@ -537,10 +816,9 @@ function compileQuery(query, ctx, fns, bindings) {
|
|
|
537
816
|
const clause = query.clauses[i];
|
|
538
817
|
switch (clause.type) {
|
|
539
818
|
case "where": {
|
|
540
|
-
const condition =
|
|
541
|
-
clause.condition,
|
|
542
|
-
|
|
543
|
-
fns
|
|
819
|
+
const condition = resolveBindingsInWhere(
|
|
820
|
+
fns.compileExpression(clause.condition, ctx, fns),
|
|
821
|
+
bindings
|
|
544
822
|
);
|
|
545
823
|
if (groupByIndex >= 0 && i > groupByIndex) {
|
|
546
824
|
havingConditions.push(condition);
|
|
@@ -1298,406 +1576,134 @@ function compileJson(expr, ctx, _fns, aliasContext, outerAliases) {
|
|
|
1298
1576
|
kind: "jsonExists",
|
|
1299
1577
|
field: jsonField2,
|
|
1300
1578
|
key
|
|
1301
|
-
};
|
|
1302
|
-
}
|
|
1303
|
-
throw new NqlSemanticException(
|
|
1304
|
-
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
1305
|
-
`Unsupported function in WHERE context: ${fn}()`
|
|
1306
|
-
);
|
|
1307
|
-
}
|
|
1308
|
-
const jsonComp = expr;
|
|
1309
|
-
const jsonField = expressionToField(jsonComp.left, aliasContext);
|
|
1310
|
-
if (!jsonField) {
|
|
1311
|
-
throw new NqlSemanticException(
|
|
1312
|
-
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
1313
|
-
"Left side of JSON comparison must be a field reference"
|
|
1314
|
-
);
|
|
1315
|
-
}
|
|
1316
|
-
if (jsonComp.operator === "?") {
|
|
1317
|
-
const key = coerceToStringKey(jsonComp.right, "? operator key", ctx);
|
|
1318
|
-
return {
|
|
1319
|
-
kind: "jsonExists",
|
|
1320
|
-
field: jsonField,
|
|
1321
|
-
key
|
|
1322
|
-
};
|
|
1323
|
-
}
|
|
1324
|
-
const jsonValue = resolveFilterValue(
|
|
1325
|
-
jsonComp.right,
|
|
1326
|
-
ctx,
|
|
1327
|
-
aliasContext,
|
|
1328
|
-
outerAliases
|
|
1329
|
-
);
|
|
1330
|
-
const intent = {
|
|
1331
|
-
kind: "jsonContains",
|
|
1332
|
-
field: jsonField,
|
|
1333
|
-
value: jsonValue,
|
|
1334
|
-
reversed: jsonComp.operator === "<@"
|
|
1335
|
-
};
|
|
1336
|
-
return intent;
|
|
1337
|
-
}
|
|
1338
|
-
function compileRelationFilter(expr, ctx, fns, aliasContext, outerAliases) {
|
|
1339
|
-
const relFilter = expr;
|
|
1340
|
-
const nestedOuterAliases = aliasContext ? [...outerAliases ?? [], aliasContext] : outerAliases ?? [];
|
|
1341
|
-
const prevRelationTarget = ctx.currentRelationTarget;
|
|
1342
|
-
if (ctx.currentFromTable && ctx.validator && relFilter.relation[0]) {
|
|
1343
|
-
ctx.currentRelationTarget = ctx.validator.resolveRelationTarget(
|
|
1344
|
-
ctx.currentFromTable,
|
|
1345
|
-
relFilter.relation[0]
|
|
1346
|
-
);
|
|
1347
|
-
}
|
|
1348
|
-
const where = compileExpression(
|
|
1349
|
-
relFilter.condition,
|
|
1350
|
-
ctx,
|
|
1351
|
-
fns,
|
|
1352
|
-
relFilter.alias,
|
|
1353
|
-
nestedOuterAliases
|
|
1354
|
-
);
|
|
1355
|
-
ctx.currentRelationTarget = prevRelationTarget;
|
|
1356
|
-
return {
|
|
1357
|
-
kind: "relationFilter",
|
|
1358
|
-
relation: relFilter.relation,
|
|
1359
|
-
where,
|
|
1360
|
-
mode: relFilter.mode,
|
|
1361
|
-
...relFilter.alias !== void 0 && { alias: relFilter.alias }
|
|
1362
|
-
};
|
|
1363
|
-
}
|
|
1364
|
-
function expandDateRangeList(field, patterns, negated) {
|
|
1365
|
-
const conditions = patterns.map((pattern) => {
|
|
1366
|
-
const { start, end } = expandDateRange(pattern);
|
|
1367
|
-
return {
|
|
1368
|
-
kind: "and",
|
|
1369
|
-
conditions: [
|
|
1370
|
-
{
|
|
1371
|
-
kind: "comparison",
|
|
1372
|
-
field,
|
|
1373
|
-
operator: "gte",
|
|
1374
|
-
value: start
|
|
1375
|
-
},
|
|
1376
|
-
{
|
|
1377
|
-
kind: "comparison",
|
|
1378
|
-
field,
|
|
1379
|
-
operator: "lt",
|
|
1380
|
-
value: end
|
|
1381
|
-
}
|
|
1382
|
-
]
|
|
1383
|
-
};
|
|
1384
|
-
});
|
|
1385
|
-
const result = conditions.length === 1 ? conditions[0] : { kind: "or", conditions };
|
|
1386
|
-
if (negated) {
|
|
1387
|
-
return { kind: "not", condition: result };
|
|
1388
|
-
}
|
|
1389
|
-
return result;
|
|
1390
|
-
}
|
|
1391
|
-
function compileExpression(expr, ctx, fns, aliasContext, outerAliases) {
|
|
1392
|
-
switch (expr.type) {
|
|
1393
|
-
case "binary":
|
|
1394
|
-
case "unary":
|
|
1395
|
-
return compileLogical(expr, ctx, fns, aliasContext, outerAliases);
|
|
1396
|
-
case "comparison":
|
|
1397
|
-
return compileComparison(expr, ctx, fns, aliasContext, outerAliases);
|
|
1398
|
-
case "rangeOp":
|
|
1399
|
-
return compileRange(expr, ctx, fns, aliasContext, outerAliases);
|
|
1400
|
-
case "in":
|
|
1401
|
-
case "any":
|
|
1402
|
-
return compileMembership(expr, ctx, fns, aliasContext, outerAliases);
|
|
1403
|
-
case "between":
|
|
1404
|
-
return compileBetween(expr, ctx, fns, aliasContext, outerAliases);
|
|
1405
|
-
case "isNull":
|
|
1406
|
-
return compileNull(expr, ctx, aliasContext);
|
|
1407
|
-
case "jsonComparison":
|
|
1408
|
-
case "function":
|
|
1409
|
-
return compileJson(expr, ctx, fns, aliasContext, outerAliases);
|
|
1410
|
-
case "relationFilter":
|
|
1411
|
-
return compileRelationFilter(expr, ctx, fns, aliasContext, outerAliases);
|
|
1412
|
-
case "case":
|
|
1413
|
-
throw new NqlSemanticException(
|
|
1414
|
-
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
1415
|
-
"CASE in WHERE not supported. Use a computed column in SELECT or a relation filter instead."
|
|
1416
|
-
);
|
|
1417
|
-
case "exists":
|
|
1418
|
-
throw new NqlSemanticException(
|
|
1419
|
-
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
1420
|
-
"EXISTS (subquery) is not supported in NQL. Use relation filters instead:\n orders | with customer | where customer.active = true\n orders | where exists(customer, active = true)\nThese compile to efficient EXISTS subqueries automatically."
|
|
1421
|
-
);
|
|
1422
|
-
/* v8 ignore next — defensive: all parser-produced expression types are handled above -- @preserve */
|
|
1423
|
-
default:
|
|
1424
|
-
throw new NqlSemanticException(
|
|
1425
|
-
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
1426
|
-
`Unsupported expression type in WHERE: ${expr.type}`
|
|
1427
|
-
);
|
|
1428
|
-
}
|
|
1429
|
-
}
|
|
1430
|
-
function assignMutationValue(target, column, value, ctx) {
|
|
1431
|
-
target[column] = expressionToValue(value, ctx);
|
|
1432
|
-
}
|
|
1433
|
-
function compileMutationPipeline(pipeline, ctx, fns, bindings) {
|
|
1434
|
-
ctx.currentFromTable = pipeline.mutation.table;
|
|
1435
|
-
const mutation = compileMutation(pipeline.mutation, ctx, fns, bindings);
|
|
1436
|
-
let returning;
|
|
1437
|
-
for (const clause of pipeline.clauses) {
|
|
1438
|
-
if (clause.type === "select") {
|
|
1439
|
-
returning = extractReturningColumns(clause, ctx);
|
|
1440
|
-
}
|
|
1441
|
-
}
|
|
1442
|
-
if (returning) {
|
|
1443
|
-
return {
|
|
1444
|
-
mutation: { ...mutation, returning }
|
|
1445
|
-
};
|
|
1446
|
-
}
|
|
1447
|
-
return { mutation };
|
|
1448
|
-
}
|
|
1449
|
-
function compileMutation(mutation, ctx, fns, bindings) {
|
|
1450
|
-
switch (mutation.type) {
|
|
1451
|
-
case "insert":
|
|
1452
|
-
return compileInsert(mutation, ctx);
|
|
1453
|
-
case "insert_from":
|
|
1454
|
-
return compileInsertFrom(mutation, ctx, fns, bindings);
|
|
1455
|
-
case "update":
|
|
1456
|
-
return compileUpdate(mutation, ctx, fns, bindings);
|
|
1457
|
-
case "delete":
|
|
1458
|
-
return compileDelete(mutation, ctx, fns, bindings);
|
|
1459
|
-
case "upsert":
|
|
1460
|
-
return compileUpsert(mutation, ctx);
|
|
1461
|
-
case "upsert_from":
|
|
1462
|
-
return compileUpsertFrom(mutation, ctx, fns, bindings);
|
|
1463
|
-
}
|
|
1464
|
-
}
|
|
1465
|
-
function compileInsert(insert, ctx) {
|
|
1466
|
-
ctx.validator?.validateTable(insert.table);
|
|
1467
|
-
const allColumns = /* @__PURE__ */ new Set();
|
|
1468
|
-
for (const row of insert.rows) {
|
|
1469
|
-
for (const assignment of row) {
|
|
1470
|
-
ctx.validator?.validateColumn(insert.table, assignment.column);
|
|
1471
|
-
allColumns.add(assignment.column);
|
|
1472
|
-
}
|
|
1473
|
-
}
|
|
1474
|
-
const values = [];
|
|
1475
|
-
for (const row of insert.rows) {
|
|
1476
|
-
const rowValues = {};
|
|
1477
|
-
const rowColumns = /* @__PURE__ */ new Set();
|
|
1478
|
-
for (const assignment of row) {
|
|
1479
|
-
rowColumns.add(assignment.column);
|
|
1480
|
-
assignMutationValue(rowValues, assignment.column, assignment.value, ctx);
|
|
1481
|
-
}
|
|
1482
|
-
for (const col of allColumns) {
|
|
1483
|
-
if (!rowColumns.has(col)) {
|
|
1484
|
-
rowValues[col] = void 0;
|
|
1485
|
-
}
|
|
1486
|
-
}
|
|
1487
|
-
values.push(rowValues);
|
|
1488
|
-
}
|
|
1489
|
-
return {
|
|
1490
|
-
type: "insert",
|
|
1491
|
-
table: insert.table,
|
|
1492
|
-
values
|
|
1493
|
-
};
|
|
1494
|
-
}
|
|
1495
|
-
function compileInsertFrom(insertFrom, ctx, fns, bindings) {
|
|
1496
|
-
ctx.validator?.validateTable(insertFrom.table);
|
|
1497
|
-
const sourceQuery = bindings?.get(insertFrom.source);
|
|
1498
|
-
if (!sourceQuery) {
|
|
1499
|
-
ctx.validator?.validateTable(insertFrom.source);
|
|
1500
|
-
}
|
|
1501
|
-
ctx.currentFromTable = insertFrom.source;
|
|
1502
|
-
return {
|
|
1503
|
-
type: "insert_from",
|
|
1504
|
-
table: insertFrom.table,
|
|
1505
|
-
source: insertFrom.source,
|
|
1506
|
-
...sourceQuery !== void 0 && { sourceQuery },
|
|
1507
|
-
/* v8 ignore next — NQL grammar does not produce explicit column lists for INSERT FROM -- @preserve */
|
|
1508
|
-
...insertFrom.columns !== void 0 && { columns: insertFrom.columns },
|
|
1509
|
-
...insertFrom.where !== void 0 && {
|
|
1510
|
-
where: fns.compileExpression(insertFrom.where, ctx, fns)
|
|
1511
|
-
},
|
|
1512
|
-
...insertFrom.limit !== void 0 && {
|
|
1513
|
-
limit: resolveIntegerCount(insertFrom.limit, ctx, "insert-from limit")
|
|
1514
|
-
}
|
|
1515
|
-
};
|
|
1516
|
-
}
|
|
1517
|
-
function compileUpdate(update, ctx, fns, bindings) {
|
|
1518
|
-
ctx.currentFromTable = update.table;
|
|
1519
|
-
ctx.validator?.validateTable(update.table);
|
|
1520
|
-
const set = {};
|
|
1521
|
-
for (const assignment of update.assignments) {
|
|
1522
|
-
ctx.validator?.validateColumn(update.table, assignment.column);
|
|
1523
|
-
assignMutationValue(set, assignment.column, assignment.value, ctx);
|
|
1524
|
-
}
|
|
1525
|
-
if (update.where) {
|
|
1526
|
-
return {
|
|
1527
|
-
type: "update",
|
|
1528
|
-
table: update.table,
|
|
1529
|
-
set,
|
|
1530
|
-
where: resolveBindingsInWhere(
|
|
1531
|
-
fns.compileExpression(update.where, ctx, fns),
|
|
1532
|
-
bindings
|
|
1533
|
-
)
|
|
1534
|
-
};
|
|
1579
|
+
};
|
|
1580
|
+
}
|
|
1581
|
+
throw new NqlSemanticException(
|
|
1582
|
+
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
1583
|
+
`Unsupported function in WHERE context: ${fn}()`
|
|
1584
|
+
);
|
|
1535
1585
|
}
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1586
|
+
const jsonComp = expr;
|
|
1587
|
+
const jsonField = expressionToField(jsonComp.left, aliasContext);
|
|
1588
|
+
if (!jsonField) {
|
|
1589
|
+
throw new NqlSemanticException(
|
|
1590
|
+
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
1591
|
+
"Left side of JSON comparison must be a field reference"
|
|
1539
1592
|
);
|
|
1540
1593
|
}
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
table: update.table,
|
|
1544
|
-
set,
|
|
1545
|
-
allowAll: true
|
|
1546
|
-
};
|
|
1547
|
-
}
|
|
1548
|
-
function compileDelete(del, ctx, fns, bindings) {
|
|
1549
|
-
ctx.currentFromTable = del.table;
|
|
1550
|
-
ctx.validator?.validateTable(del.table);
|
|
1551
|
-
if (del.where) {
|
|
1594
|
+
if (jsonComp.operator === "?") {
|
|
1595
|
+
const key = coerceToStringKey(jsonComp.right, "? operator key", ctx);
|
|
1552
1596
|
return {
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
fns.compileExpression(del.where, ctx, fns),
|
|
1557
|
-
bindings
|
|
1558
|
-
)
|
|
1597
|
+
kind: "jsonExists",
|
|
1598
|
+
field: jsonField,
|
|
1599
|
+
key
|
|
1559
1600
|
};
|
|
1560
1601
|
}
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1602
|
+
const jsonValue = resolveFilterValue(
|
|
1603
|
+
jsonComp.right,
|
|
1604
|
+
ctx,
|
|
1605
|
+
aliasContext,
|
|
1606
|
+
outerAliases
|
|
1607
|
+
);
|
|
1608
|
+
const intent = {
|
|
1609
|
+
kind: "jsonContains",
|
|
1610
|
+
field: jsonField,
|
|
1611
|
+
value: jsonValue,
|
|
1612
|
+
reversed: jsonComp.operator === "<@"
|
|
1570
1613
|
};
|
|
1614
|
+
return intent;
|
|
1571
1615
|
}
|
|
1572
|
-
function
|
|
1573
|
-
|
|
1574
|
-
const
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
ctx.validator?.validateColumn(upsert.table, col);
|
|
1581
|
-
}
|
|
1582
|
-
if (upsert.where) {
|
|
1583
|
-
throw new Error(
|
|
1584
|
-
"conditional upsert is not yet supported: a WHERE clause on `upsert` (ON CONFLICT DO UPDATE ... WHERE) is parsed but cannot be honored by the SQL generator yet. Remove the WHERE, or use a plain conditional update. Tracked for a future release."
|
|
1616
|
+
function compileRelationFilter(expr, ctx, fns, aliasContext, outerAliases) {
|
|
1617
|
+
const relFilter = expr;
|
|
1618
|
+
const nestedOuterAliases = aliasContext ? [...outerAliases ?? [], aliasContext] : outerAliases ?? [];
|
|
1619
|
+
const prevRelationTarget = ctx.currentRelationTarget;
|
|
1620
|
+
if (ctx.currentFromTable && ctx.validator && relFilter.relation[0]) {
|
|
1621
|
+
ctx.currentRelationTarget = ctx.validator.resolveRelationTarget(
|
|
1622
|
+
ctx.currentFromTable,
|
|
1623
|
+
relFilter.relation[0]
|
|
1585
1624
|
);
|
|
1586
1625
|
}
|
|
1626
|
+
const where = compileExpression(
|
|
1627
|
+
relFilter.condition,
|
|
1628
|
+
ctx,
|
|
1629
|
+
fns,
|
|
1630
|
+
relFilter.alias,
|
|
1631
|
+
nestedOuterAliases
|
|
1632
|
+
);
|
|
1633
|
+
ctx.currentRelationTarget = prevRelationTarget;
|
|
1587
1634
|
return {
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
};
|
|
1594
|
-
}
|
|
1595
|
-
function compileUpsertFrom(upsertFrom, ctx, fns, bindings) {
|
|
1596
|
-
ctx.validator?.validateTable(upsertFrom.table);
|
|
1597
|
-
const sourceQuery = bindings?.get(upsertFrom.source);
|
|
1598
|
-
if (!sourceQuery) {
|
|
1599
|
-
ctx.validator?.validateTable(upsertFrom.source);
|
|
1600
|
-
}
|
|
1601
|
-
for (const col of upsertFrom.conflictColumns) {
|
|
1602
|
-
ctx.validator?.validateColumn(upsertFrom.table, col);
|
|
1603
|
-
}
|
|
1604
|
-
ctx.currentFromTable = upsertFrom.source;
|
|
1605
|
-
return {
|
|
1606
|
-
type: "upsert_from",
|
|
1607
|
-
table: upsertFrom.table,
|
|
1608
|
-
source: upsertFrom.source,
|
|
1609
|
-
conflictColumns: upsertFrom.conflictColumns,
|
|
1610
|
-
...sourceQuery !== void 0 && { sourceQuery },
|
|
1611
|
-
/* v8 ignore start — NQL grammar does not produce explicit column lists for UPSERT FROM -- @preserve */
|
|
1612
|
-
...upsertFrom.columns !== void 0 && {
|
|
1613
|
-
columns: upsertFrom.columns
|
|
1614
|
-
},
|
|
1615
|
-
/* v8 ignore stop -- @preserve */
|
|
1616
|
-
...upsertFrom.where !== void 0 && {
|
|
1617
|
-
where: fns.compileExpression(upsertFrom.where, ctx, fns)
|
|
1618
|
-
},
|
|
1619
|
-
...upsertFrom.limit !== void 0 && {
|
|
1620
|
-
limit: resolveIntegerCount(upsertFrom.limit, ctx, "upsert-from limit")
|
|
1621
|
-
}
|
|
1635
|
+
kind: "relationFilter",
|
|
1636
|
+
relation: relFilter.relation,
|
|
1637
|
+
where,
|
|
1638
|
+
mode: relFilter.mode,
|
|
1639
|
+
...relFilter.alias !== void 0 && { alias: relFilter.alias }
|
|
1622
1640
|
};
|
|
1623
1641
|
}
|
|
1624
|
-
function
|
|
1625
|
-
const
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
}
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
}
|
|
1642
|
-
function resolveBindingsInWhere(where, bindings) {
|
|
1643
|
-
if (!bindings || bindings.size === 0) return where;
|
|
1644
|
-
if (where.kind === "in") {
|
|
1645
|
-
const inWhere = where;
|
|
1646
|
-
const inValues = inWhere.subquery ? void 0 : inWhere.values;
|
|
1647
|
-
if (inValues && inValues.length === 1) {
|
|
1648
|
-
const val = inValues[0];
|
|
1649
|
-
if (!isParamIntent(val) && val && typeof val === "object" && "$ref" in val) {
|
|
1650
|
-
const ref = val.$ref;
|
|
1651
|
-
if (bindings.has(ref)) {
|
|
1652
|
-
const boundQuery = bindings.get(ref);
|
|
1653
|
-
const boundSelect = boundQuery.select;
|
|
1654
|
-
const selectFields = boundSelect && "fields" in boundSelect ? boundSelect.fields : void 0;
|
|
1655
|
-
const cteRef = {
|
|
1656
|
-
type: "select",
|
|
1657
|
-
from: ref,
|
|
1658
|
-
...selectFields && {
|
|
1659
|
-
select: {
|
|
1660
|
-
type: "fields",
|
|
1661
|
-
fields: selectFields
|
|
1662
|
-
}
|
|
1663
|
-
}
|
|
1664
|
-
};
|
|
1665
|
-
return {
|
|
1666
|
-
kind: "in",
|
|
1667
|
-
field: inWhere.field,
|
|
1668
|
-
subquery: cteRef
|
|
1669
|
-
};
|
|
1642
|
+
function expandDateRangeList(field, patterns, negated) {
|
|
1643
|
+
const conditions = patterns.map((pattern) => {
|
|
1644
|
+
const { start, end } = expandDateRange(pattern);
|
|
1645
|
+
return {
|
|
1646
|
+
kind: "and",
|
|
1647
|
+
conditions: [
|
|
1648
|
+
{
|
|
1649
|
+
kind: "comparison",
|
|
1650
|
+
field,
|
|
1651
|
+
operator: "gte",
|
|
1652
|
+
value: start
|
|
1653
|
+
},
|
|
1654
|
+
{
|
|
1655
|
+
kind: "comparison",
|
|
1656
|
+
field,
|
|
1657
|
+
operator: "lt",
|
|
1658
|
+
value: end
|
|
1670
1659
|
}
|
|
1671
|
-
|
|
1672
|
-
}
|
|
1673
|
-
|
|
1674
|
-
}
|
|
1675
|
-
if (
|
|
1676
|
-
|
|
1677
|
-
const resolved = resolveBindingsInWhere(notWhere.condition, bindings);
|
|
1678
|
-
return resolved === notWhere.condition ? where : { kind: "not", condition: resolved };
|
|
1679
|
-
}
|
|
1680
|
-
if (where.kind === "and" || where.kind === "or") {
|
|
1681
|
-
const compound = where;
|
|
1682
|
-
const resolved = compound.conditions.map(
|
|
1683
|
-
(c) => resolveBindingsInWhere(c, bindings)
|
|
1684
|
-
);
|
|
1685
|
-
const changed = resolved.some((r, i) => r !== compound.conditions[i]);
|
|
1686
|
-
return changed ? { kind: compound.kind, conditions: resolved } : where;
|
|
1660
|
+
]
|
|
1661
|
+
};
|
|
1662
|
+
});
|
|
1663
|
+
const result = conditions.length === 1 ? conditions[0] : { kind: "or", conditions };
|
|
1664
|
+
if (negated) {
|
|
1665
|
+
return { kind: "not", condition: result };
|
|
1687
1666
|
}
|
|
1688
|
-
return
|
|
1667
|
+
return result;
|
|
1689
1668
|
}
|
|
1690
|
-
function
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1669
|
+
function compileExpression(expr, ctx, fns, aliasContext, outerAliases) {
|
|
1670
|
+
switch (expr.type) {
|
|
1671
|
+
case "binary":
|
|
1672
|
+
case "unary":
|
|
1673
|
+
return compileLogical(expr, ctx, fns, aliasContext, outerAliases);
|
|
1674
|
+
case "comparison":
|
|
1675
|
+
return compileComparison(expr, ctx, fns, aliasContext, outerAliases);
|
|
1676
|
+
case "rangeOp":
|
|
1677
|
+
return compileRange(expr, ctx, fns, aliasContext, outerAliases);
|
|
1678
|
+
case "in":
|
|
1679
|
+
case "any":
|
|
1680
|
+
return compileMembership(expr, ctx, fns, aliasContext, outerAliases);
|
|
1681
|
+
case "between":
|
|
1682
|
+
return compileBetween(expr, ctx, fns, aliasContext, outerAliases);
|
|
1683
|
+
case "isNull":
|
|
1684
|
+
return compileNull(expr, ctx, aliasContext);
|
|
1685
|
+
case "jsonComparison":
|
|
1686
|
+
case "function":
|
|
1687
|
+
return compileJson(expr, ctx, fns, aliasContext, outerAliases);
|
|
1688
|
+
case "relationFilter":
|
|
1689
|
+
return compileRelationFilter(expr, ctx, fns, aliasContext, outerAliases);
|
|
1690
|
+
case "case":
|
|
1691
|
+
throw new NqlSemanticException(
|
|
1692
|
+
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
1693
|
+
"CASE in WHERE not supported. Use a computed column in SELECT or a relation filter instead."
|
|
1694
|
+
);
|
|
1695
|
+
case "exists":
|
|
1696
|
+
throw new NqlSemanticException(
|
|
1697
|
+
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
1698
|
+
"EXISTS (subquery) is not supported in NQL. Use relation filters instead:\n orders | with customer | where customer.active = true\n orders | where exists(customer, active = true)\nThese compile to efficient EXISTS subqueries automatically."
|
|
1699
|
+
);
|
|
1700
|
+
/* v8 ignore next — defensive: all parser-produced expression types are handled above -- @preserve */
|
|
1701
|
+
default:
|
|
1702
|
+
throw new NqlSemanticException(
|
|
1703
|
+
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
1704
|
+
`Unsupported expression type in WHERE: ${expr.type}`
|
|
1705
|
+
);
|
|
1699
1706
|
}
|
|
1700
|
-
return void 0;
|
|
1701
1707
|
}
|
|
1702
1708
|
function paramExpressionIntent(param, alias) {
|
|
1703
1709
|
const intent = { ...param };
|
|
@@ -5463,6 +5469,7 @@ function hasColumnValidatorShape(obj) {
|
|
|
5463
5469
|
}
|
|
5464
5470
|
/* v8 ignore next — '*' columns are validated at call-site before reaching here -- @preserve */
|
|
5465
5471
|
/* v8 ignore next — graceful degradation: unknown tables skip validation -- @preserve */
|
|
5472
|
+
/* v8 ignore next — defensive: bound queries from NQL always have select.fields -- @preserve */
|
|
5466
5473
|
/* v8 ignore next — defensive: callers always pass valid relation paths -- @preserve */
|
|
5467
5474
|
/* v8 ignore next — not yet reachable: flat + pre-existing includes requires WITH clause -- @preserve */
|
|
5468
5475
|
/* v8 ignore stop -- @preserve */
|
|
@@ -5478,7 +5485,6 @@ function hasColumnValidatorShape(obj) {
|
|
|
5478
5485
|
/* v8 ignore start — defensive: parser guarantees IS NULL LHS is a path -- @preserve */
|
|
5479
5486
|
/* v8 ignore start — defensive: parser guarantees at least 2 args -- @preserve */
|
|
5480
5487
|
/* v8 ignore next — defensive: only json_* functions reach WHERE context -- @preserve */
|
|
5481
|
-
/* v8 ignore next — defensive: bound queries from NQL always have select.fields -- @preserve */
|
|
5482
5488
|
/* v8 ignore start — defensive: star items are handled in compileSelectClause before reaching here -- @preserve */
|
|
5483
5489
|
/* v8 ignore next — defensive: relationStar items are handled in compileSelectClause -- @preserve */
|
|
5484
5490
|
/* v8 ignore start — defensive: all parser-produced SELECT expression types are handled above -- @preserve */
|