@dbsp/nql 1.1.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 +74 -5
- package/dist/index.d.ts +48 -33
- package/dist/index.js +839 -474
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { NQL_INTERNAL_COMPILER_OPTIONS, isNqlBindingRef, getNqlBindingRefName, createNqlBindingRef } from '@dbsp/types/internal';
|
|
2
|
+
import { NQL_SELECT_AGGREGATE_FUNCTIONS, NQL_SELECT_JSON_FUNCTIONS, NQL_SELECT_SCALAR_FUNCTIONS, isParamIntent, isNqlSelectWindowFunctionAllowed, isRankingWindowFunction } from '@dbsp/types';
|
|
2
3
|
import { createToken, Lexer, CstParser } from 'chevrotain';
|
|
3
4
|
|
|
5
|
+
// src/compiler/index.ts
|
|
6
|
+
|
|
4
7
|
// src/errors/types.ts
|
|
5
8
|
var NqlErrorCodes = {
|
|
6
9
|
// Lexer errors
|
|
@@ -107,8 +110,83 @@ var ColumnValidator = class _ColumnValidator {
|
|
|
107
110
|
return rel?.target;
|
|
108
111
|
}
|
|
109
112
|
};
|
|
110
|
-
|
|
111
|
-
|
|
113
|
+
var FORBIDDEN_PARAM_NAMES = /* @__PURE__ */ new Set([
|
|
114
|
+
"__proto__",
|
|
115
|
+
"constructor",
|
|
116
|
+
"prototype"
|
|
117
|
+
]);
|
|
118
|
+
function isReservedAutoParamName(name) {
|
|
119
|
+
return name.startsWith("__p");
|
|
120
|
+
}
|
|
121
|
+
function assertParamNameAllowed(name, ctx) {
|
|
122
|
+
if (FORBIDDEN_PARAM_NAMES.has(name)) {
|
|
123
|
+
throw new NqlSemanticException(
|
|
124
|
+
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
125
|
+
`Named parameter :${name} is reserved and cannot be bound`
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
if (!ctx?.allowInternalParams && isReservedAutoParamName(name)) {
|
|
129
|
+
throw new NqlSemanticException(
|
|
130
|
+
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
131
|
+
`Named parameter :${name} uses the reserved __p namespace`
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function assertParamValueAllowed(name, value) {
|
|
136
|
+
if (value === void 0) {
|
|
137
|
+
throw new NqlSemanticException(
|
|
138
|
+
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
139
|
+
`Named parameter :${name} must not be undefined; use null to bind SQL NULL`
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
if (typeof value === "number" && !Number.isFinite(value)) {
|
|
143
|
+
throw new NqlSemanticException(
|
|
144
|
+
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
145
|
+
`Named parameter :${name} must be a finite number`
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
if (Array.isArray(value)) {
|
|
149
|
+
for (let i = 0; i < value.length; i++) {
|
|
150
|
+
assertParamValueAllowed(`${name}[${i}]`, value[i]);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
function validateParamsMap(params, ctx) {
|
|
155
|
+
for (const key of Object.getOwnPropertyNames(params)) {
|
|
156
|
+
assertParamNameAllowed(key, ctx);
|
|
157
|
+
assertParamValueAllowed(key, params[key]);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
function resolveNamedParam(ctx, name) {
|
|
161
|
+
assertParamNameAllowed(name, ctx);
|
|
162
|
+
if (!Object.hasOwn(ctx.params, name)) {
|
|
163
|
+
throw new NqlSemanticException(
|
|
164
|
+
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
165
|
+
`Named parameter :${name} is not bound`
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
const value = ctx.params[name];
|
|
169
|
+
assertParamValueAllowed(name, value);
|
|
170
|
+
return { kind: "param", value };
|
|
171
|
+
}
|
|
172
|
+
function assertIntegerCount(value, label) {
|
|
173
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
|
|
174
|
+
throw new NqlSemanticException(
|
|
175
|
+
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
176
|
+
`${label} must resolve to a non-negative safe integer`
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
function resolveIntegerCount(count, ctx, label) {
|
|
181
|
+
if (typeof count === "number") {
|
|
182
|
+
assertIntegerCount(count, label);
|
|
183
|
+
return count;
|
|
184
|
+
}
|
|
185
|
+
const param = resolveNamedParam(ctx, count.name);
|
|
186
|
+
const { value } = param;
|
|
187
|
+
assertIntegerCount(value, label);
|
|
188
|
+
return { ...param, value };
|
|
189
|
+
}
|
|
112
190
|
function expressionToField(expr, aliasContext) {
|
|
113
191
|
if (expr.type === "path") {
|
|
114
192
|
const segments = expr.segments;
|
|
@@ -119,8 +197,16 @@ function expressionToField(expr, aliasContext) {
|
|
|
119
197
|
}
|
|
120
198
|
return null;
|
|
121
199
|
}
|
|
122
|
-
function expressionToValue(expr) {
|
|
200
|
+
function expressionToValue(expr, ctx) {
|
|
123
201
|
switch (expr.type) {
|
|
202
|
+
case "namedParam":
|
|
203
|
+
if (!ctx) {
|
|
204
|
+
throw new NqlSemanticException(
|
|
205
|
+
NqlErrorCodes.SEM_UNREACHABLE,
|
|
206
|
+
`Cannot resolve named parameter :${expr.name} without compiler context`
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
return resolveNamedParam(ctx, expr.name);
|
|
124
210
|
case "string":
|
|
125
211
|
return expr.value;
|
|
126
212
|
case "number":
|
|
@@ -130,25 +216,25 @@ function expressionToValue(expr) {
|
|
|
130
216
|
case "null":
|
|
131
217
|
return null;
|
|
132
218
|
case "path":
|
|
133
|
-
return
|
|
219
|
+
return createNqlBindingRef(expr.segments.join("."));
|
|
134
220
|
case "function": {
|
|
135
221
|
return {
|
|
136
222
|
$fn: expr.name,
|
|
137
|
-
$args: expr.args.map((a) => expressionToValue(a))
|
|
223
|
+
$args: expr.args.map((a) => expressionToValue(a, ctx))
|
|
138
224
|
};
|
|
139
225
|
}
|
|
140
226
|
case "binary": {
|
|
141
227
|
const binary = expr;
|
|
142
228
|
return {
|
|
143
229
|
$op: binary.operator,
|
|
144
|
-
$left: expressionToValue(binary.left),
|
|
145
|
-
$right: expressionToValue(binary.right)
|
|
230
|
+
$left: expressionToValue(binary.left, ctx),
|
|
231
|
+
$right: expressionToValue(binary.right, ctx)
|
|
146
232
|
};
|
|
147
233
|
}
|
|
148
234
|
case "unary": {
|
|
149
235
|
const unary = expr;
|
|
150
236
|
if (unary.operator === "-") {
|
|
151
|
-
const operand = expressionToValue(unary.operand);
|
|
237
|
+
const operand = expressionToValue(unary.operand, ctx);
|
|
152
238
|
if (typeof operand === "number") {
|
|
153
239
|
return -operand;
|
|
154
240
|
}
|
|
@@ -174,6 +260,11 @@ function expressionToValue(expr) {
|
|
|
174
260
|
}
|
|
175
261
|
function expressionToSql(expr) {
|
|
176
262
|
switch (expr.type) {
|
|
263
|
+
case "namedParam":
|
|
264
|
+
throw new NqlSemanticException(
|
|
265
|
+
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
266
|
+
`Named parameter :${expr.name} cannot be used as SQL structure; use nqlRaw() for trusted dynamic structure`
|
|
267
|
+
);
|
|
177
268
|
case "path":
|
|
178
269
|
return expr.segments.join(".");
|
|
179
270
|
case "string":
|
|
@@ -217,12 +308,18 @@ function expressionToRangeValue(expr) {
|
|
|
217
308
|
if (expr.type === "string") {
|
|
218
309
|
return expr.value;
|
|
219
310
|
}
|
|
311
|
+
if (expr.type === "namedParam") {
|
|
312
|
+
throw new NqlSemanticException(
|
|
313
|
+
NqlErrorCodes.SEM_UNREACHABLE,
|
|
314
|
+
`Named parameter :${expr.name} must be resolved by the caller`
|
|
315
|
+
);
|
|
316
|
+
}
|
|
220
317
|
throw new Error(
|
|
221
318
|
`Range operator requires a range literal or scalar value, got ${expr.type}`
|
|
222
319
|
);
|
|
223
320
|
}
|
|
224
321
|
function resolveFilterValue(expr, ctx, aliasContext, outerAliases) {
|
|
225
|
-
if (!aliasContext) return expressionToValue(expr);
|
|
322
|
+
if (!aliasContext) return expressionToValue(expr, ctx);
|
|
226
323
|
if (expr.type === "path") {
|
|
227
324
|
const segments = expr.segments;
|
|
228
325
|
if (segments.length > 1 && segments[0] === aliasContext) {
|
|
@@ -259,7 +356,7 @@ function resolveFilterValue(expr, ctx, aliasContext, outerAliases) {
|
|
|
259
356
|
scope: "outer"
|
|
260
357
|
};
|
|
261
358
|
}
|
|
262
|
-
return expressionToValue(expr);
|
|
359
|
+
return expressionToValue(expr, ctx);
|
|
263
360
|
}
|
|
264
361
|
function mapComparisonOperator(op) {
|
|
265
362
|
switch (op) {
|
|
@@ -280,15 +377,9 @@ function mapComparisonOperator(op) {
|
|
|
280
377
|
}
|
|
281
378
|
}
|
|
282
379
|
function isAggregateFunction(name) {
|
|
283
|
-
return
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
"avg",
|
|
287
|
-
"min",
|
|
288
|
-
"max",
|
|
289
|
-
"array_agg",
|
|
290
|
-
"string_agg"
|
|
291
|
-
].includes(name.toLowerCase());
|
|
380
|
+
return NQL_SELECT_AGGREGATE_FUNCTIONS.includes(
|
|
381
|
+
name.toLowerCase()
|
|
382
|
+
);
|
|
292
383
|
}
|
|
293
384
|
function validateWhereField(ctx, field, aliasContext, originalExpr) {
|
|
294
385
|
if (!ctx.validator) return;
|
|
@@ -309,35 +400,325 @@ function validateWhereField(ctx, field, aliasContext, originalExpr) {
|
|
|
309
400
|
ctx.validator.validateColumn(ctx.currentRelationTarget, field);
|
|
310
401
|
return;
|
|
311
402
|
}
|
|
312
|
-
if (ctx.currentFromTable && !field.includes(".")) {
|
|
313
|
-
ctx.validator.validateColumn(ctx.currentFromTable, field);
|
|
403
|
+
if (ctx.currentFromTable && !field.includes(".")) {
|
|
404
|
+
ctx.validator.validateColumn(ctx.currentFromTable, field);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
function coerceToStringKey(expr, contextLabel, ctx) {
|
|
408
|
+
if (expr.type === "path") {
|
|
409
|
+
const segments = expr.segments;
|
|
410
|
+
if (segments.length > 1) {
|
|
411
|
+
throw new NqlSemanticException(
|
|
412
|
+
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
413
|
+
`${contextLabel} must be a string literal or a single identifier, not a dotted path`
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
const key = expressionToField(expr);
|
|
417
|
+
if (!key) {
|
|
418
|
+
throw new NqlSemanticException(
|
|
419
|
+
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
420
|
+
`${contextLabel} must be a string literal or a single identifier`
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
return key;
|
|
424
|
+
}
|
|
425
|
+
if (expr.type === "string") {
|
|
426
|
+
return expr.value;
|
|
427
|
+
}
|
|
428
|
+
if (expr.type === "namedParam") {
|
|
429
|
+
const param = resolveNamedParam(ctx, expr.name);
|
|
430
|
+
const value = param.value;
|
|
431
|
+
if (typeof value !== "string") {
|
|
432
|
+
throw new NqlSemanticException(
|
|
433
|
+
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
434
|
+
`${contextLabel} named parameter :${expr.name} must resolve to a string`
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
return param;
|
|
438
|
+
}
|
|
439
|
+
throw new NqlSemanticException(
|
|
440
|
+
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
441
|
+
`${contextLabel} must be a string literal`
|
|
442
|
+
);
|
|
443
|
+
}
|
|
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;
|
|
314
708
|
}
|
|
709
|
+
return where;
|
|
315
710
|
}
|
|
316
|
-
function
|
|
317
|
-
if (
|
|
318
|
-
const
|
|
319
|
-
|
|
320
|
-
throw new NqlSemanticException(
|
|
321
|
-
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
322
|
-
`${contextLabel} must be a string literal or a single identifier, not a dotted path`
|
|
323
|
-
);
|
|
711
|
+
function extractBindName(stmt) {
|
|
712
|
+
if (stmt.type === "query") {
|
|
713
|
+
for (const clause of stmt.clauses) {
|
|
714
|
+
if (clause.type === "bind") return clause.name;
|
|
324
715
|
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
329
|
-
`${contextLabel} must be a string literal or a single identifier`
|
|
330
|
-
);
|
|
716
|
+
} else if (stmt.type === "mutationPipeline") {
|
|
717
|
+
for (const clause of stmt.clauses) {
|
|
718
|
+
if (clause.type === "bind") return clause.name;
|
|
331
719
|
}
|
|
332
|
-
return key;
|
|
333
|
-
}
|
|
334
|
-
if (expr.type === "string") {
|
|
335
|
-
return expr.value;
|
|
336
720
|
}
|
|
337
|
-
|
|
338
|
-
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
339
|
-
`${contextLabel} must be a string literal`
|
|
340
|
-
);
|
|
721
|
+
return void 0;
|
|
341
722
|
}
|
|
342
723
|
|
|
343
724
|
// src/compiler/include-builder.ts
|
|
@@ -435,10 +816,9 @@ function compileQuery(query, ctx, fns, bindings) {
|
|
|
435
816
|
const clause = query.clauses[i];
|
|
436
817
|
switch (clause.type) {
|
|
437
818
|
case "where": {
|
|
438
|
-
const condition =
|
|
439
|
-
clause.condition,
|
|
440
|
-
|
|
441
|
-
fns
|
|
819
|
+
const condition = resolveBindingsInWhere(
|
|
820
|
+
fns.compileExpression(clause.condition, ctx, fns),
|
|
821
|
+
bindings
|
|
442
822
|
);
|
|
443
823
|
if (groupByIndex >= 0 && i > groupByIndex) {
|
|
444
824
|
havingConditions.push(condition);
|
|
@@ -474,15 +854,25 @@ function compileQuery(query, ctx, fns, bindings) {
|
|
|
474
854
|
break;
|
|
475
855
|
case "limit": {
|
|
476
856
|
const lc = clause;
|
|
857
|
+
const count = resolveIntegerCount(
|
|
858
|
+
lc.count,
|
|
859
|
+
ctx,
|
|
860
|
+
lc.relation ? "per-include limit" : "limit"
|
|
861
|
+
);
|
|
477
862
|
if (lc.relation) {
|
|
478
|
-
|
|
863
|
+
const includeLimit = isParamIntent(count) ? count.value : count;
|
|
864
|
+
includeLimits.set(lc.relation, includeLimit);
|
|
479
865
|
} else {
|
|
480
|
-
limit =
|
|
866
|
+
limit = count;
|
|
481
867
|
}
|
|
482
868
|
break;
|
|
483
869
|
}
|
|
484
870
|
case "offset":
|
|
485
|
-
offset =
|
|
871
|
+
offset = resolveIntegerCount(
|
|
872
|
+
clause.count,
|
|
873
|
+
ctx,
|
|
874
|
+
"offset"
|
|
875
|
+
);
|
|
486
876
|
break;
|
|
487
877
|
case "lock": {
|
|
488
878
|
const lc = clause;
|
|
@@ -647,6 +1037,14 @@ function compileOrderItem(item, ctx) {
|
|
|
647
1037
|
}
|
|
648
1038
|
return { field, direction: item.direction };
|
|
649
1039
|
}
|
|
1040
|
+
if (item.expression.type === "namedParam") {
|
|
1041
|
+
throw new NqlSemanticException(
|
|
1042
|
+
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
1043
|
+
`Named parameter :${item.expression.name} cannot be used as an ORDER BY expression because ORDER BY is query structure, not a value`,
|
|
1044
|
+
void 0,
|
|
1045
|
+
'Choose a trusted structural path for dynamic ordering, such as nqlRaw("order by ...") or the query builder after validating the requested column and direction.'
|
|
1046
|
+
);
|
|
1047
|
+
}
|
|
650
1048
|
const sqlExpr = expressionToSql(item.expression);
|
|
651
1049
|
return { field: sqlExpr, direction: item.direction };
|
|
652
1050
|
}
|
|
@@ -809,6 +1207,9 @@ function getISOWeekCount(year) {
|
|
|
809
1207
|
}
|
|
810
1208
|
|
|
811
1209
|
// src/compiler/compile-expression.ts
|
|
1210
|
+
function paramValue(value) {
|
|
1211
|
+
return value !== null && typeof value === "object" && value.kind === "param" ? value.value : value;
|
|
1212
|
+
}
|
|
812
1213
|
var MAX_ANY_ITEMS = 1e4;
|
|
813
1214
|
function compileLogical(expr, ctx, fns, aliasContext, outerAliases) {
|
|
814
1215
|
if (expr.type === "binary") {
|
|
@@ -872,7 +1273,7 @@ function compileComparison(expr, ctx, _fns, aliasContext, outerAliases) {
|
|
|
872
1273
|
aliasContext,
|
|
873
1274
|
outerAliases
|
|
874
1275
|
);
|
|
875
|
-
|
|
1276
|
+
const intent2 = {
|
|
876
1277
|
kind: "comparison",
|
|
877
1278
|
field: baseField,
|
|
878
1279
|
operator: operator2,
|
|
@@ -880,6 +1281,7 @@ function compileComparison(expr, ctx, _fns, aliasContext, outerAliases) {
|
|
|
880
1281
|
jsonPath: jsonLeft.path,
|
|
881
1282
|
jsonMode: jsonLeft.mode
|
|
882
1283
|
};
|
|
1284
|
+
return intent2;
|
|
883
1285
|
}
|
|
884
1286
|
if (comp.left.type === "function") {
|
|
885
1287
|
const fn = comp.left.name.toLowerCase();
|
|
@@ -897,7 +1299,7 @@ function compileComparison(expr, ctx, _fns, aliasContext, outerAliases) {
|
|
|
897
1299
|
`${fn}() first argument must be a field reference`
|
|
898
1300
|
);
|
|
899
1301
|
}
|
|
900
|
-
const keys = comp.left.args.slice(1).map((a) => coerceToStringKey(a, `${fn}() path argument
|
|
1302
|
+
const keys = comp.left.args.slice(1).map((a) => coerceToStringKey(a, `${fn}() path argument`, ctx));
|
|
901
1303
|
const operator2 = mapComparisonOperator(comp.operator);
|
|
902
1304
|
const value2 = resolveFilterValue(
|
|
903
1305
|
comp.right,
|
|
@@ -905,7 +1307,7 @@ function compileComparison(expr, ctx, _fns, aliasContext, outerAliases) {
|
|
|
905
1307
|
aliasContext,
|
|
906
1308
|
outerAliases
|
|
907
1309
|
);
|
|
908
|
-
|
|
1310
|
+
const intent2 = {
|
|
909
1311
|
kind: "comparison",
|
|
910
1312
|
field: baseField,
|
|
911
1313
|
operator: operator2,
|
|
@@ -913,6 +1315,7 @@ function compileComparison(expr, ctx, _fns, aliasContext, outerAliases) {
|
|
|
913
1315
|
jsonPath: keys,
|
|
914
1316
|
jsonMode: fn === "json_extract" ? "json" : "text"
|
|
915
1317
|
};
|
|
1318
|
+
return intent2;
|
|
916
1319
|
}
|
|
917
1320
|
}
|
|
918
1321
|
const field = expressionToField(comp.left, aliasContext);
|
|
@@ -924,20 +1327,22 @@ function compileComparison(expr, ctx, _fns, aliasContext, outerAliases) {
|
|
|
924
1327
|
}
|
|
925
1328
|
validateWhereField(ctx, field, aliasContext, comp.left);
|
|
926
1329
|
if (comp.operator === "like") {
|
|
1330
|
+
const pattern = coerceToStringKey(comp.right, "LIKE pattern", ctx);
|
|
927
1331
|
return {
|
|
928
1332
|
kind: "like",
|
|
929
1333
|
field,
|
|
930
|
-
pattern
|
|
1334
|
+
pattern
|
|
931
1335
|
};
|
|
932
1336
|
}
|
|
933
1337
|
const operator = mapComparisonOperator(comp.operator);
|
|
934
1338
|
const value = resolveFilterValue(comp.right, ctx, aliasContext, outerAliases);
|
|
935
|
-
|
|
1339
|
+
const intent = {
|
|
936
1340
|
kind: "comparison",
|
|
937
1341
|
field,
|
|
938
1342
|
operator,
|
|
939
1343
|
value
|
|
940
1344
|
};
|
|
1345
|
+
return intent;
|
|
941
1346
|
}
|
|
942
1347
|
function compileRange(expr, ctx, _fns, aliasContext, outerAliases) {
|
|
943
1348
|
const rangeExpr = expr;
|
|
@@ -965,12 +1370,13 @@ function compileRange(expr, ctx, _fns, aliasContext, outerAliases) {
|
|
|
965
1370
|
"Range operator requires either a range literal or scalar value"
|
|
966
1371
|
);
|
|
967
1372
|
}
|
|
968
|
-
|
|
1373
|
+
const result = {
|
|
969
1374
|
kind: "range",
|
|
970
1375
|
field,
|
|
971
1376
|
operator: rangeExpr.operator,
|
|
972
1377
|
value: rangeValue
|
|
973
1378
|
};
|
|
1379
|
+
return result;
|
|
974
1380
|
}
|
|
975
1381
|
function compileMembership(expr, ctx, fns, aliasContext, outerAliases) {
|
|
976
1382
|
if (expr.type === "any") {
|
|
@@ -983,11 +1389,12 @@ function compileMembership(expr, ctx, fns, aliasContext, outerAliases) {
|
|
|
983
1389
|
);
|
|
984
1390
|
}
|
|
985
1391
|
validateWhereField(ctx, field2, aliasContext, anyExpr.column);
|
|
986
|
-
const
|
|
1392
|
+
const valuesParam = resolveNamedParam(ctx, anyExpr.paramName);
|
|
1393
|
+
const rawValues = valuesParam.value;
|
|
987
1394
|
if (!Array.isArray(rawValues)) {
|
|
988
1395
|
throw new NqlSemanticException(
|
|
989
1396
|
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
990
|
-
`ANY(:${anyExpr.paramName}) requires an array argument
|
|
1397
|
+
`ANY(:${anyExpr.paramName}) requires an array argument`
|
|
991
1398
|
);
|
|
992
1399
|
}
|
|
993
1400
|
if (rawValues.length > ctx.maxAnyItems) {
|
|
@@ -996,8 +1403,12 @@ function compileMembership(expr, ctx, fns, aliasContext, outerAliases) {
|
|
|
996
1403
|
`ANY(:${anyExpr.paramName}) array length ${rawValues.length} exceeds maximum of ${ctx.maxAnyItems}`
|
|
997
1404
|
);
|
|
998
1405
|
}
|
|
999
|
-
const
|
|
1000
|
-
|
|
1406
|
+
const result2 = {
|
|
1407
|
+
kind: "any",
|
|
1408
|
+
field: field2,
|
|
1409
|
+
values: valuesParam
|
|
1410
|
+
};
|
|
1411
|
+
return result2;
|
|
1001
1412
|
}
|
|
1002
1413
|
const inExpr = expr;
|
|
1003
1414
|
const field = expressionToField(inExpr.expression, aliasContext);
|
|
@@ -1077,27 +1488,28 @@ function compileBetween(expr, ctx, _fns, aliasContext, outerAliases) {
|
|
|
1077
1488
|
aliasContext,
|
|
1078
1489
|
outerAliases
|
|
1079
1490
|
);
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
);
|
|
1086
|
-
}
|
|
1087
|
-
if (upper !== null && typeof upper !== "number" && typeof upper !== "string" && !(upper instanceof Date)) {
|
|
1088
|
-
const got = typeof upper === "object" ? "path reference" : JSON.stringify(upper);
|
|
1089
|
-
throw new NqlSemanticException(
|
|
1090
|
-
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
1091
|
-
`BETWEEN upper bound must be a literal number, string, or date \u2014 got ${got}. Use a param or hardcoded value.`
|
|
1092
|
-
);
|
|
1093
|
-
}
|
|
1491
|
+
const lowerValue = paramValue(lower);
|
|
1492
|
+
const upperValue = paramValue(upper);
|
|
1493
|
+
assertBetweenBoundValueAllowed("lower", between.low, lowerValue);
|
|
1494
|
+
assertBetweenBoundValueAllowed("upper", between.high, upperValue);
|
|
1495
|
+
const value = { lower, upper };
|
|
1094
1496
|
return {
|
|
1095
1497
|
kind: "range",
|
|
1096
1498
|
field,
|
|
1097
1499
|
operator: "between",
|
|
1098
|
-
value
|
|
1500
|
+
value
|
|
1099
1501
|
};
|
|
1100
1502
|
}
|
|
1503
|
+
function assertBetweenBoundValueAllowed(position, expr, value) {
|
|
1504
|
+
if (value === null || typeof value === "number" || typeof value === "string" || typeof value === "bigint" || value instanceof Date) {
|
|
1505
|
+
return;
|
|
1506
|
+
}
|
|
1507
|
+
const paramSuffix = expr.type === "namedParam" ? `; param :${expr.name}` : "";
|
|
1508
|
+
throw new NqlSemanticException(
|
|
1509
|
+
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
1510
|
+
`BETWEEN ${position} bound must be a literal number, string, or date, or a bigint param; got type ${typeof value}${paramSuffix}.`
|
|
1511
|
+
);
|
|
1512
|
+
}
|
|
1101
1513
|
function compileNull(expr, ctx, aliasContext) {
|
|
1102
1514
|
const isNull = expr;
|
|
1103
1515
|
const field = expressionToField(isNull.expression, aliasContext);
|
|
@@ -1137,12 +1549,13 @@ function compileJson(expr, ctx, _fns, aliasContext, outerAliases) {
|
|
|
1137
1549
|
aliasContext,
|
|
1138
1550
|
outerAliases
|
|
1139
1551
|
);
|
|
1140
|
-
|
|
1552
|
+
const intent2 = {
|
|
1141
1553
|
kind: "jsonContains",
|
|
1142
1554
|
field: jsonField2,
|
|
1143
1555
|
value: jsonValue2,
|
|
1144
1556
|
reversed: fn === "json_contained_by"
|
|
1145
1557
|
};
|
|
1558
|
+
return intent2;
|
|
1146
1559
|
}
|
|
1147
1560
|
if (fn === "json_exists") {
|
|
1148
1561
|
if (expr.args.length < 2) {
|
|
@@ -1158,7 +1571,7 @@ function compileJson(expr, ctx, _fns, aliasContext, outerAliases) {
|
|
|
1158
1571
|
`${fn}() first argument must be a field reference`
|
|
1159
1572
|
);
|
|
1160
1573
|
}
|
|
1161
|
-
const key = coerceToStringKey(expr.args[1], `${fn}() key
|
|
1574
|
+
const key = coerceToStringKey(expr.args[1], `${fn}() key`, ctx);
|
|
1162
1575
|
return {
|
|
1163
1576
|
kind: "jsonExists",
|
|
1164
1577
|
field: jsonField2,
|
|
@@ -1179,7 +1592,7 @@ function compileJson(expr, ctx, _fns, aliasContext, outerAliases) {
|
|
|
1179
1592
|
);
|
|
1180
1593
|
}
|
|
1181
1594
|
if (jsonComp.operator === "?") {
|
|
1182
|
-
const key = coerceToStringKey(jsonComp.right, "? operator key");
|
|
1595
|
+
const key = coerceToStringKey(jsonComp.right, "? operator key", ctx);
|
|
1183
1596
|
return {
|
|
1184
1597
|
kind: "jsonExists",
|
|
1185
1598
|
field: jsonField,
|
|
@@ -1192,12 +1605,13 @@ function compileJson(expr, ctx, _fns, aliasContext, outerAliases) {
|
|
|
1192
1605
|
aliasContext,
|
|
1193
1606
|
outerAliases
|
|
1194
1607
|
);
|
|
1195
|
-
|
|
1608
|
+
const intent = {
|
|
1196
1609
|
kind: "jsonContains",
|
|
1197
1610
|
field: jsonField,
|
|
1198
1611
|
value: jsonValue,
|
|
1199
1612
|
reversed: jsonComp.operator === "<@"
|
|
1200
1613
|
};
|
|
1614
|
+
return intent;
|
|
1201
1615
|
}
|
|
1202
1616
|
function compileRelationFilter(expr, ctx, fns, aliasContext, outerAliases) {
|
|
1203
1617
|
const relFilter = expr;
|
|
@@ -1242,321 +1656,125 @@ function expandDateRangeList(field, patterns, negated) {
|
|
|
1242
1656
|
field,
|
|
1243
1657
|
operator: "lt",
|
|
1244
1658
|
value: end
|
|
1245
|
-
}
|
|
1246
|
-
]
|
|
1247
|
-
};
|
|
1248
|
-
});
|
|
1249
|
-
const result = conditions.length === 1 ? conditions[0] : { kind: "or", conditions };
|
|
1250
|
-
if (negated) {
|
|
1251
|
-
return { kind: "not", condition: result };
|
|
1252
|
-
}
|
|
1253
|
-
return result;
|
|
1254
|
-
}
|
|
1255
|
-
function compileExpression(expr, ctx, fns, aliasContext, outerAliases) {
|
|
1256
|
-
switch (expr.type) {
|
|
1257
|
-
case "binary":
|
|
1258
|
-
case "unary":
|
|
1259
|
-
return compileLogical(expr, ctx, fns, aliasContext, outerAliases);
|
|
1260
|
-
case "comparison":
|
|
1261
|
-
return compileComparison(expr, ctx, fns, aliasContext, outerAliases);
|
|
1262
|
-
case "rangeOp":
|
|
1263
|
-
return compileRange(expr, ctx, fns, aliasContext, outerAliases);
|
|
1264
|
-
case "in":
|
|
1265
|
-
case "any":
|
|
1266
|
-
return compileMembership(expr, ctx, fns, aliasContext, outerAliases);
|
|
1267
|
-
case "between":
|
|
1268
|
-
return compileBetween(expr, ctx, fns, aliasContext, outerAliases);
|
|
1269
|
-
case "isNull":
|
|
1270
|
-
return compileNull(expr, ctx, aliasContext);
|
|
1271
|
-
case "jsonComparison":
|
|
1272
|
-
case "function":
|
|
1273
|
-
return compileJson(expr, ctx, fns, aliasContext, outerAliases);
|
|
1274
|
-
case "relationFilter":
|
|
1275
|
-
return compileRelationFilter(expr, ctx, fns, aliasContext, outerAliases);
|
|
1276
|
-
case "case":
|
|
1277
|
-
throw new NqlSemanticException(
|
|
1278
|
-
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
1279
|
-
"CASE in WHERE not supported. Use a computed column in SELECT or a relation filter instead."
|
|
1280
|
-
);
|
|
1281
|
-
case "exists":
|
|
1282
|
-
throw new NqlSemanticException(
|
|
1283
|
-
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
1284
|
-
"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."
|
|
1285
|
-
);
|
|
1286
|
-
/* v8 ignore next — defensive: all parser-produced expression types are handled above -- @preserve */
|
|
1287
|
-
default:
|
|
1288
|
-
throw new NqlSemanticException(
|
|
1289
|
-
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
1290
|
-
`Unsupported expression type in WHERE: ${expr.type}`
|
|
1291
|
-
);
|
|
1292
|
-
}
|
|
1293
|
-
}
|
|
1294
|
-
|
|
1295
|
-
// src/compiler/compile-mutation.ts
|
|
1296
|
-
function compileMutationPipeline(pipeline, ctx, fns, bindings) {
|
|
1297
|
-
ctx.currentFromTable = pipeline.mutation.table;
|
|
1298
|
-
const mutation = compileMutation(pipeline.mutation, ctx, fns, bindings);
|
|
1299
|
-
let returning;
|
|
1300
|
-
for (const clause of pipeline.clauses) {
|
|
1301
|
-
if (clause.type === "select") {
|
|
1302
|
-
returning = extractReturningColumns(clause, ctx);
|
|
1303
|
-
}
|
|
1304
|
-
}
|
|
1305
|
-
if (returning) {
|
|
1306
|
-
return {
|
|
1307
|
-
mutation: { ...mutation, returning }
|
|
1308
|
-
};
|
|
1309
|
-
}
|
|
1310
|
-
return { mutation };
|
|
1311
|
-
}
|
|
1312
|
-
function compileMutation(mutation, ctx, fns, bindings) {
|
|
1313
|
-
switch (mutation.type) {
|
|
1314
|
-
case "insert":
|
|
1315
|
-
return compileInsert(mutation, ctx);
|
|
1316
|
-
case "insert_from":
|
|
1317
|
-
return compileInsertFrom(mutation, ctx, fns, bindings);
|
|
1318
|
-
case "update":
|
|
1319
|
-
return compileUpdate(mutation, ctx, fns, bindings);
|
|
1320
|
-
case "delete":
|
|
1321
|
-
return compileDelete(mutation, ctx, fns, bindings);
|
|
1322
|
-
case "upsert":
|
|
1323
|
-
return compileUpsert(mutation, ctx);
|
|
1324
|
-
case "upsert_from":
|
|
1325
|
-
return compileUpsertFrom(mutation, ctx, fns, bindings);
|
|
1326
|
-
}
|
|
1327
|
-
}
|
|
1328
|
-
function compileInsert(insert, ctx) {
|
|
1329
|
-
ctx.validator?.validateTable(insert.table);
|
|
1330
|
-
const allColumns = /* @__PURE__ */ new Set();
|
|
1331
|
-
for (const row of insert.rows) {
|
|
1332
|
-
for (const assignment of row) {
|
|
1333
|
-
ctx.validator?.validateColumn(insert.table, assignment.column);
|
|
1334
|
-
allColumns.add(assignment.column);
|
|
1335
|
-
}
|
|
1336
|
-
}
|
|
1337
|
-
const values = [];
|
|
1338
|
-
for (const row of insert.rows) {
|
|
1339
|
-
const rowValues = {};
|
|
1340
|
-
const rowColumns = /* @__PURE__ */ new Set();
|
|
1341
|
-
for (const assignment of row) {
|
|
1342
|
-
rowColumns.add(assignment.column);
|
|
1343
|
-
rowValues[assignment.column] = expressionToValue(assignment.value);
|
|
1344
|
-
}
|
|
1345
|
-
for (const col of allColumns) {
|
|
1346
|
-
if (!rowColumns.has(col)) {
|
|
1347
|
-
rowValues[col] = void 0;
|
|
1348
|
-
}
|
|
1349
|
-
}
|
|
1350
|
-
values.push(rowValues);
|
|
1351
|
-
}
|
|
1352
|
-
return {
|
|
1353
|
-
type: "insert",
|
|
1354
|
-
table: insert.table,
|
|
1355
|
-
values
|
|
1356
|
-
};
|
|
1357
|
-
}
|
|
1358
|
-
function compileInsertFrom(insertFrom, ctx, fns, bindings) {
|
|
1359
|
-
ctx.validator?.validateTable(insertFrom.table);
|
|
1360
|
-
const sourceQuery = bindings?.get(insertFrom.source);
|
|
1361
|
-
if (!sourceQuery) {
|
|
1362
|
-
ctx.validator?.validateTable(insertFrom.source);
|
|
1363
|
-
}
|
|
1364
|
-
ctx.currentFromTable = insertFrom.source;
|
|
1365
|
-
return {
|
|
1366
|
-
type: "insert_from",
|
|
1367
|
-
table: insertFrom.table,
|
|
1368
|
-
source: insertFrom.source,
|
|
1369
|
-
...sourceQuery !== void 0 && { sourceQuery },
|
|
1370
|
-
/* v8 ignore next — NQL grammar does not produce explicit column lists for INSERT FROM -- @preserve */
|
|
1371
|
-
...insertFrom.columns !== void 0 && { columns: insertFrom.columns },
|
|
1372
|
-
...insertFrom.where !== void 0 && {
|
|
1373
|
-
where: fns.compileExpression(insertFrom.where, ctx, fns)
|
|
1374
|
-
},
|
|
1375
|
-
...insertFrom.limit !== void 0 && { limit: insertFrom.limit }
|
|
1376
|
-
};
|
|
1377
|
-
}
|
|
1378
|
-
function compileUpdate(update, ctx, fns, bindings) {
|
|
1379
|
-
ctx.currentFromTable = update.table;
|
|
1380
|
-
ctx.validator?.validateTable(update.table);
|
|
1381
|
-
const set = {};
|
|
1382
|
-
for (const assignment of update.assignments) {
|
|
1383
|
-
ctx.validator?.validateColumn(update.table, assignment.column);
|
|
1384
|
-
set[assignment.column] = expressionToValue(assignment.value);
|
|
1385
|
-
}
|
|
1386
|
-
if (update.where) {
|
|
1387
|
-
return {
|
|
1388
|
-
type: "update",
|
|
1389
|
-
table: update.table,
|
|
1390
|
-
set,
|
|
1391
|
-
where: resolveBindingsInWhere(
|
|
1392
|
-
fns.compileExpression(update.where, ctx, fns),
|
|
1393
|
-
bindings
|
|
1394
|
-
)
|
|
1395
|
-
};
|
|
1396
|
-
}
|
|
1397
|
-
if (!ctx.allowUnfilteredMutations) {
|
|
1398
|
-
throw new Error(
|
|
1399
|
-
"update without a where clause would affect all rows; pass { allowUnfilteredMutations: true } to the compiler to allow an unfiltered update"
|
|
1400
|
-
);
|
|
1401
|
-
}
|
|
1402
|
-
return {
|
|
1403
|
-
type: "update",
|
|
1404
|
-
table: update.table,
|
|
1405
|
-
set,
|
|
1406
|
-
allowAll: true
|
|
1407
|
-
};
|
|
1408
|
-
}
|
|
1409
|
-
function compileDelete(del, ctx, fns, bindings) {
|
|
1410
|
-
ctx.currentFromTable = del.table;
|
|
1411
|
-
ctx.validator?.validateTable(del.table);
|
|
1412
|
-
if (del.where) {
|
|
1413
|
-
return {
|
|
1414
|
-
type: "delete",
|
|
1415
|
-
table: del.table,
|
|
1416
|
-
where: resolveBindingsInWhere(
|
|
1417
|
-
fns.compileExpression(del.where, ctx, fns),
|
|
1418
|
-
bindings
|
|
1419
|
-
)
|
|
1420
|
-
};
|
|
1421
|
-
}
|
|
1422
|
-
if (!ctx.allowUnfilteredMutations) {
|
|
1423
|
-
throw new Error(
|
|
1424
|
-
"delete without a where clause would affect all rows; pass { allowUnfilteredMutations: true } to the compiler to allow an unfiltered delete"
|
|
1425
|
-
);
|
|
1659
|
+
}
|
|
1660
|
+
]
|
|
1661
|
+
};
|
|
1662
|
+
});
|
|
1663
|
+
const result = conditions.length === 1 ? conditions[0] : { kind: "or", conditions };
|
|
1664
|
+
if (negated) {
|
|
1665
|
+
return { kind: "not", condition: result };
|
|
1426
1666
|
}
|
|
1427
|
-
return
|
|
1428
|
-
type: "delete",
|
|
1429
|
-
table: del.table,
|
|
1430
|
-
allowAll: true
|
|
1431
|
-
};
|
|
1667
|
+
return result;
|
|
1432
1668
|
}
|
|
1433
|
-
function
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
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
|
+
);
|
|
1447
1706
|
}
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1707
|
+
}
|
|
1708
|
+
function paramExpressionIntent(param, alias) {
|
|
1709
|
+
const intent = { ...param };
|
|
1710
|
+
if (alias !== void 0) intent.as = alias;
|
|
1711
|
+
return intent;
|
|
1712
|
+
}
|
|
1713
|
+
function literalExpressionIntent(value, alias) {
|
|
1714
|
+
const intent = {
|
|
1715
|
+
kind: "literal",
|
|
1716
|
+
value
|
|
1454
1717
|
};
|
|
1718
|
+
if (alias !== void 0) intent.as = alias;
|
|
1719
|
+
return intent;
|
|
1455
1720
|
}
|
|
1456
|
-
function
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
ctx.validator?.validateTable(upsertFrom.source);
|
|
1721
|
+
function resolveLagLeadOffset(expr, ctx) {
|
|
1722
|
+
if (expr.type === "number") {
|
|
1723
|
+
const resolved = resolveIntegerCount(expr.value, ctx, "lag/lead offset");
|
|
1724
|
+
return isParamIntent(resolved) ? resolved.value : resolved;
|
|
1461
1725
|
}
|
|
1462
|
-
|
|
1463
|
-
|
|
1726
|
+
if (expr.type === "namedParam") {
|
|
1727
|
+
const resolved = resolveIntegerCount(expr, ctx, "lag/lead offset");
|
|
1728
|
+
return isParamIntent(resolved) ? resolved.value : resolved;
|
|
1464
1729
|
}
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
source: upsertFrom.source,
|
|
1470
|
-
conflictColumns: upsertFrom.conflictColumns,
|
|
1471
|
-
...sourceQuery !== void 0 && { sourceQuery },
|
|
1472
|
-
/* v8 ignore start — NQL grammar does not produce explicit column lists for UPSERT FROM -- @preserve */
|
|
1473
|
-
...upsertFrom.columns !== void 0 && {
|
|
1474
|
-
columns: upsertFrom.columns
|
|
1475
|
-
},
|
|
1476
|
-
/* v8 ignore stop -- @preserve */
|
|
1477
|
-
...upsertFrom.where !== void 0 && {
|
|
1478
|
-
where: fns.compileExpression(upsertFrom.where, ctx, fns)
|
|
1479
|
-
},
|
|
1480
|
-
...upsertFrom.limit !== void 0 && { limit: upsertFrom.limit }
|
|
1481
|
-
};
|
|
1730
|
+
throw new NqlSemanticException(
|
|
1731
|
+
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
1732
|
+
"lag/lead offset must resolve to a non-negative safe integer"
|
|
1733
|
+
);
|
|
1482
1734
|
}
|
|
1483
|
-
function
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
}
|
|
1489
|
-
if (item.type === "expression") {
|
|
1490
|
-
const field = expressionToField(item.expression);
|
|
1491
|
-
if (field) {
|
|
1492
|
-
if (ctx.currentFromTable && !field.includes(".")) {
|
|
1493
|
-
ctx.validator?.validateColumn(ctx.currentFromTable, field);
|
|
1494
|
-
}
|
|
1495
|
-
columns.push(item.alias ?? field);
|
|
1496
|
-
}
|
|
1497
|
-
}
|
|
1498
|
-
}
|
|
1499
|
-
return columns;
|
|
1735
|
+
function throwUnsupportedSelectFunction(fn) {
|
|
1736
|
+
throw new NqlSemanticException(
|
|
1737
|
+
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
1738
|
+
`Unsupported function in SELECT context: ${fn}()`
|
|
1739
|
+
);
|
|
1500
1740
|
}
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
const boundSelect = boundQuery.select;
|
|
1513
|
-
const selectFields = boundSelect && "fields" in boundSelect ? boundSelect.fields : void 0;
|
|
1514
|
-
const cteRef = {
|
|
1515
|
-
type: "select",
|
|
1516
|
-
from: ref,
|
|
1517
|
-
...selectFields && {
|
|
1518
|
-
select: {
|
|
1519
|
-
type: "fields",
|
|
1520
|
-
fields: selectFields
|
|
1521
|
-
}
|
|
1522
|
-
}
|
|
1523
|
-
};
|
|
1524
|
-
return {
|
|
1525
|
-
kind: "in",
|
|
1526
|
-
field: inWhere.field,
|
|
1527
|
-
subquery: cteRef
|
|
1528
|
-
};
|
|
1529
|
-
}
|
|
1530
|
-
}
|
|
1531
|
-
}
|
|
1532
|
-
return where;
|
|
1741
|
+
var SELECT_SCALAR_FUNCTION_NAMES = /* @__PURE__ */ new Set([
|
|
1742
|
+
...NQL_SELECT_AGGREGATE_FUNCTIONS,
|
|
1743
|
+
...NQL_SELECT_JSON_FUNCTIONS,
|
|
1744
|
+
...NQL_SELECT_SCALAR_FUNCTIONS
|
|
1745
|
+
]);
|
|
1746
|
+
function validateSelectPathExpression(expr, ctx) {
|
|
1747
|
+
if (!ctx.currentFromTable || !ctx.validator) return;
|
|
1748
|
+
const { segments } = expr;
|
|
1749
|
+
if (segments.length === 1) {
|
|
1750
|
+
ctx.validator.validateColumn(ctx.currentFromTable, segments[0]);
|
|
1751
|
+
return;
|
|
1533
1752
|
}
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1753
|
+
const firstSegmentLower = segments[0].toLowerCase();
|
|
1754
|
+
if (ctx.pseudoColumnKeywords.has(firstSegmentLower)) {
|
|
1755
|
+
let i = 1;
|
|
1756
|
+
while (i < segments.length && ctx.pseudoColumnKeywords.has(segments[i].toLowerCase())) {
|
|
1757
|
+
i++;
|
|
1758
|
+
}
|
|
1759
|
+
if (i < segments.length) {
|
|
1760
|
+
ctx.validator.validateColumn(ctx.currentFromTable, segments[i]);
|
|
1761
|
+
}
|
|
1762
|
+
return;
|
|
1538
1763
|
}
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
return changed ? { kind: compound.kind, conditions: resolved } : where;
|
|
1764
|
+
const targetTable = ctx.validator.resolveRelationTarget(
|
|
1765
|
+
ctx.currentFromTable,
|
|
1766
|
+
segments[0]
|
|
1767
|
+
);
|
|
1768
|
+
if (targetTable) {
|
|
1769
|
+
ctx.validator.validateColumn(targetTable, segments[segments.length - 1]);
|
|
1546
1770
|
}
|
|
1547
|
-
return where;
|
|
1548
1771
|
}
|
|
1549
|
-
function
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
}
|
|
1554
|
-
} else if (stmt.type === "mutationPipeline") {
|
|
1555
|
-
for (const clause of stmt.clauses) {
|
|
1556
|
-
if (clause.type === "bind") return clause.name;
|
|
1557
|
-
}
|
|
1772
|
+
function expressionToValidatedField(expr, ctx) {
|
|
1773
|
+
const field = expressionToField(expr);
|
|
1774
|
+
if (field && expr.type === "path") {
|
|
1775
|
+
validateSelectPathExpression(expr, ctx);
|
|
1558
1776
|
}
|
|
1559
|
-
return
|
|
1777
|
+
return field;
|
|
1560
1778
|
}
|
|
1561
1779
|
function compileSelectClause(clause, ctx, fns) {
|
|
1562
1780
|
if (clause.items.length === 1 && clause.items[0]?.type === "star") {
|
|
@@ -1581,12 +1799,7 @@ function compileSelectClause(clause, ctx, fns) {
|
|
|
1581
1799
|
} else if (item.type === "expression") {
|
|
1582
1800
|
const expr = item.expression;
|
|
1583
1801
|
if (expr.type === "path" && expr.segments.length === 1 && !item.alias) {
|
|
1584
|
-
|
|
1585
|
-
ctx.validator?.validateColumn(
|
|
1586
|
-
ctx.currentFromTable,
|
|
1587
|
-
expr.segments[0]
|
|
1588
|
-
);
|
|
1589
|
-
}
|
|
1802
|
+
validateSelectPathExpression(expr, ctx);
|
|
1590
1803
|
simpleFields.push(expr.segments[0]);
|
|
1591
1804
|
} else {
|
|
1592
1805
|
hasExpressions = true;
|
|
@@ -1599,6 +1812,40 @@ function compileSelectClause(clause, ctx, fns) {
|
|
|
1599
1812
|
}
|
|
1600
1813
|
return { type: "expressions", columns: expressions };
|
|
1601
1814
|
}
|
|
1815
|
+
function expressionToSelectValue(valueExpr, ctx, fns) {
|
|
1816
|
+
const field = expressionToValidatedField(valueExpr, ctx);
|
|
1817
|
+
if (field) return field;
|
|
1818
|
+
if (valueExpr.type === "namedParam") {
|
|
1819
|
+
return expressionToValue(valueExpr, ctx);
|
|
1820
|
+
}
|
|
1821
|
+
if (valueExpr.type === "string" || valueExpr.type === "number" || valueExpr.type === "boolean") {
|
|
1822
|
+
return valueExpr.value;
|
|
1823
|
+
}
|
|
1824
|
+
if (valueExpr.type === "null") {
|
|
1825
|
+
return null;
|
|
1826
|
+
}
|
|
1827
|
+
if (valueExpr.type === "rangeLiteral" || valueExpr.type === "dateRange") {
|
|
1828
|
+
return valueExpr.value;
|
|
1829
|
+
}
|
|
1830
|
+
return compileExpressionToIntent(valueExpr, ctx, fns);
|
|
1831
|
+
}
|
|
1832
|
+
function expressionToFunctionArg(valueExpr, ctx, fns) {
|
|
1833
|
+
const field = expressionToValidatedField(valueExpr, ctx);
|
|
1834
|
+
if (field) return field;
|
|
1835
|
+
if (valueExpr.type === "namedParam") {
|
|
1836
|
+
return expressionToValue(valueExpr, ctx);
|
|
1837
|
+
}
|
|
1838
|
+
if (valueExpr.type === "string" || valueExpr.type === "number" || valueExpr.type === "boolean") {
|
|
1839
|
+
return literalExpressionIntent(valueExpr.value);
|
|
1840
|
+
}
|
|
1841
|
+
if (valueExpr.type === "null") {
|
|
1842
|
+
return literalExpressionIntent(null);
|
|
1843
|
+
}
|
|
1844
|
+
if (valueExpr.type === "rangeLiteral" || valueExpr.type === "dateRange") {
|
|
1845
|
+
return literalExpressionIntent(valueExpr.value);
|
|
1846
|
+
}
|
|
1847
|
+
return compileExpressionToIntent(valueExpr, ctx, fns);
|
|
1848
|
+
}
|
|
1602
1849
|
function compileSelectExpression(item, ctx, fns) {
|
|
1603
1850
|
if (item.type === "star") {
|
|
1604
1851
|
return { kind: "column", column: "*" };
|
|
@@ -1618,6 +1865,7 @@ function compileSelectExpression(item, ctx, fns) {
|
|
|
1618
1865
|
const fn = expr.name.toLowerCase();
|
|
1619
1866
|
if (isAggregateFunction(fn)) {
|
|
1620
1867
|
let field;
|
|
1868
|
+
let firstArgAsExtraArg;
|
|
1621
1869
|
if (expr.args.length === 0) {
|
|
1622
1870
|
if (fn === "count") {
|
|
1623
1871
|
field = "*";
|
|
@@ -1627,56 +1875,72 @@ function compileSelectExpression(item, ctx, fns) {
|
|
|
1627
1875
|
);
|
|
1628
1876
|
}
|
|
1629
1877
|
} else {
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1878
|
+
const firstArg = expr.args[0];
|
|
1879
|
+
const firstField = expressionToValidatedField(firstArg, ctx);
|
|
1880
|
+
if (firstField) {
|
|
1881
|
+
field = firstField;
|
|
1882
|
+
} else if (firstArg.type === "namedParam") {
|
|
1883
|
+
firstArgAsExtraArg = [expressionToValue(firstArg, ctx)];
|
|
1884
|
+
} else {
|
|
1885
|
+
field = expressionToSql(firstArg);
|
|
1633
1886
|
}
|
|
1634
1887
|
}
|
|
1635
|
-
const extraArgs = expr.args.length > 1 ? expr.args.slice(1).map(
|
|
1636
|
-
|
|
1888
|
+
const extraArgs = expr.args.length > 1 ? expr.args.slice(1).map(
|
|
1889
|
+
(a) => expressionToValidatedField(a, ctx) ?? expressionToSelectValue(a, ctx, fns)
|
|
1890
|
+
) : void 0;
|
|
1891
|
+
const aggregateArgs = firstArgAsExtraArg ? [...firstArgAsExtraArg, ...extraArgs ?? []] : extraArgs;
|
|
1892
|
+
const aggregateIntent = {
|
|
1637
1893
|
kind: "aggregate",
|
|
1638
1894
|
function: fn,
|
|
1639
|
-
field,
|
|
1895
|
+
...field !== void 0 && { field },
|
|
1640
1896
|
...exprItem.alias !== void 0 && { as: exprItem.alias },
|
|
1641
1897
|
...expr.distinct && { distinct: true },
|
|
1642
|
-
...
|
|
1898
|
+
...aggregateArgs && { extraArgs: aggregateArgs }
|
|
1643
1899
|
};
|
|
1900
|
+
return aggregateIntent;
|
|
1644
1901
|
}
|
|
1645
|
-
const jsonIntent = compileJsonFunction(fn, expr.args, exprItem.alias);
|
|
1902
|
+
const jsonIntent = compileJsonFunction(fn, expr.args, exprItem.alias, ctx);
|
|
1646
1903
|
if (jsonIntent) return jsonIntent;
|
|
1904
|
+
if (!SELECT_SCALAR_FUNCTION_NAMES.has(fn)) {
|
|
1905
|
+
throwUnsupportedSelectFunction(fn);
|
|
1906
|
+
}
|
|
1647
1907
|
return {
|
|
1648
1908
|
kind: "function",
|
|
1649
|
-
name:
|
|
1650
|
-
args: expr.args.map((a) =>
|
|
1909
|
+
name: fn,
|
|
1910
|
+
args: expr.args.map((a) => expressionToFunctionArg(a, ctx, fns)),
|
|
1651
1911
|
...exprItem.alias !== void 0 && { as: exprItem.alias }
|
|
1652
1912
|
};
|
|
1653
1913
|
}
|
|
1654
1914
|
if (expr.type === "window") {
|
|
1655
1915
|
const windowExpr = expr;
|
|
1656
1916
|
const fn = windowExpr.function.toLowerCase();
|
|
1917
|
+
if (!isNqlSelectWindowFunctionAllowed(fn)) {
|
|
1918
|
+
throwUnsupportedSelectFunction(fn);
|
|
1919
|
+
}
|
|
1920
|
+
const windowFn = fn;
|
|
1657
1921
|
let field;
|
|
1658
1922
|
if (windowExpr.args.length > 0) {
|
|
1659
|
-
field =
|
|
1923
|
+
field = expressionToValidatedField(windowExpr.args[0], ctx) ?? expressionToSql(windowExpr.args[0]);
|
|
1660
1924
|
}
|
|
1661
1925
|
let offset;
|
|
1662
1926
|
let defaultValue;
|
|
1663
|
-
if (
|
|
1927
|
+
if (windowFn === "lag" || windowFn === "lead") {
|
|
1664
1928
|
if (windowExpr.args.length > 1) {
|
|
1665
|
-
offset =
|
|
1929
|
+
offset = resolveLagLeadOffset(windowExpr.args[1], ctx);
|
|
1666
1930
|
}
|
|
1667
1931
|
if (windowExpr.args.length > 2) {
|
|
1668
|
-
defaultValue = expressionToValue(windowExpr.args[2]);
|
|
1932
|
+
defaultValue = expressionToValue(windowExpr.args[2], ctx);
|
|
1669
1933
|
}
|
|
1670
1934
|
}
|
|
1671
1935
|
const partitionBy = windowExpr.partitionBy.length > 0 ? windowExpr.partitionBy.map((e) => {
|
|
1672
|
-
const f =
|
|
1936
|
+
const f = expressionToValidatedField(e, ctx) ?? expressionToSql(e);
|
|
1673
1937
|
if (ctx.currentFromTable && !f.includes(".") && !f.includes("(")) {
|
|
1674
1938
|
ctx.validator?.validateColumn(ctx.currentFromTable, f);
|
|
1675
1939
|
}
|
|
1676
1940
|
return f;
|
|
1677
1941
|
}) : void 0;
|
|
1678
1942
|
const orderBy = windowExpr.orderBy.length > 0 ? windowExpr.orderBy.map((o) => {
|
|
1679
|
-
const f =
|
|
1943
|
+
const f = expressionToValidatedField(o.expression, ctx) ?? expressionToSql(o.expression);
|
|
1680
1944
|
if (ctx.currentFromTable && !f.includes(".") && !f.includes("(")) {
|
|
1681
1945
|
ctx.validator?.validateColumn(ctx.currentFromTable, f);
|
|
1682
1946
|
}
|
|
@@ -1686,19 +1950,19 @@ function compileSelectExpression(item, ctx, fns) {
|
|
|
1686
1950
|
...partitionBy && { partitionBy },
|
|
1687
1951
|
...orderBy && { orderBy }
|
|
1688
1952
|
};
|
|
1689
|
-
const alias = exprItem.alias ??
|
|
1690
|
-
if (isRankingWindowFunction(
|
|
1953
|
+
const alias = exprItem.alias ?? windowFn;
|
|
1954
|
+
if (isRankingWindowFunction(windowFn)) {
|
|
1691
1955
|
return {
|
|
1692
1956
|
kind: "window",
|
|
1693
|
-
function:
|
|
1957
|
+
function: windowFn,
|
|
1694
1958
|
alias,
|
|
1695
1959
|
over
|
|
1696
1960
|
};
|
|
1697
1961
|
}
|
|
1698
|
-
if (
|
|
1962
|
+
if (windowFn === "lag" || windowFn === "lead") {
|
|
1699
1963
|
return {
|
|
1700
1964
|
kind: "window",
|
|
1701
|
-
function:
|
|
1965
|
+
function: windowFn,
|
|
1702
1966
|
field: field ?? "",
|
|
1703
1967
|
alias,
|
|
1704
1968
|
...offset !== void 0 && { offset },
|
|
@@ -1708,7 +1972,7 @@ function compileSelectExpression(item, ctx, fns) {
|
|
|
1708
1972
|
}
|
|
1709
1973
|
return {
|
|
1710
1974
|
kind: "window",
|
|
1711
|
-
function:
|
|
1975
|
+
function: windowFn,
|
|
1712
1976
|
...field !== void 0 && { field },
|
|
1713
1977
|
alias,
|
|
1714
1978
|
over
|
|
@@ -1723,9 +1987,7 @@ function compileSelectExpression(item, ctx, fns) {
|
|
|
1723
1987
|
}
|
|
1724
1988
|
if (expr.type === "path" && expr.segments.length === 1) {
|
|
1725
1989
|
const column = expr.segments[0];
|
|
1726
|
-
|
|
1727
|
-
ctx.validator?.validateColumn(ctx.currentFromTable, column);
|
|
1728
|
-
}
|
|
1990
|
+
validateSelectPathExpression(expr, ctx);
|
|
1729
1991
|
if (exprItem.alias) {
|
|
1730
1992
|
return { kind: "columnAlias", column, alias: exprItem.alias };
|
|
1731
1993
|
}
|
|
@@ -1734,26 +1996,32 @@ function compileSelectExpression(item, ctx, fns) {
|
|
|
1734
1996
|
if (expr.type === "path" && expr.segments.length > 1) {
|
|
1735
1997
|
return compileMultiSegmentPath(expr, exprItem, ctx);
|
|
1736
1998
|
}
|
|
1999
|
+
if (expr.type === "namedParam") {
|
|
2000
|
+
return paramExpressionIntent(
|
|
2001
|
+
expressionToValue(expr, ctx),
|
|
2002
|
+
exprItem.alias
|
|
2003
|
+
);
|
|
2004
|
+
}
|
|
1737
2005
|
if (expr.type === "binary" && ["+", "-", "*", "/", "%"].includes(expr.operator)) {
|
|
1738
|
-
const leftField =
|
|
1739
|
-
const rightField =
|
|
2006
|
+
const leftField = expressionToValidatedField(expr.left, ctx);
|
|
2007
|
+
const rightField = expressionToValidatedField(expr.right, ctx);
|
|
1740
2008
|
return {
|
|
1741
2009
|
kind: "arithmetic",
|
|
1742
|
-
left: leftField ??
|
|
2010
|
+
left: leftField ?? expressionToSelectValue(expr.left, ctx, fns),
|
|
1743
2011
|
operator: expr.operator,
|
|
1744
|
-
right: rightField ??
|
|
2012
|
+
right: rightField ?? expressionToSelectValue(expr.right, ctx, fns),
|
|
1745
2013
|
...exprItem.alias !== void 0 && { as: exprItem.alias }
|
|
1746
2014
|
};
|
|
1747
2015
|
}
|
|
1748
2016
|
if (expr.type === "unary") {
|
|
1749
2017
|
const unary = expr;
|
|
1750
2018
|
if (unary.operator === "-") {
|
|
1751
|
-
const operandField =
|
|
2019
|
+
const operandField = expressionToValidatedField(unary.operand, ctx);
|
|
1752
2020
|
return {
|
|
1753
2021
|
kind: "arithmetic",
|
|
1754
2022
|
left: -1,
|
|
1755
2023
|
operator: "*",
|
|
1756
|
-
right: operandField ??
|
|
2024
|
+
right: operandField ?? expressionToSelectValue(unary.operand, ctx, fns),
|
|
1757
2025
|
...exprItem.alias !== void 0 && { as: exprItem.alias }
|
|
1758
2026
|
};
|
|
1759
2027
|
}
|
|
@@ -1763,7 +2031,7 @@ function compileSelectExpression(item, ctx, fns) {
|
|
|
1763
2031
|
return compileCaseExpression(expr, exprItem, ctx, fns);
|
|
1764
2032
|
}
|
|
1765
2033
|
if (expr.type === "jsonAccess") {
|
|
1766
|
-
const baseField =
|
|
2034
|
+
const baseField = expressionToValidatedField(expr.base, ctx);
|
|
1767
2035
|
if (!baseField) {
|
|
1768
2036
|
throw new Error("JSON access base must be a field reference");
|
|
1769
2037
|
}
|
|
@@ -1775,6 +2043,15 @@ function compileSelectExpression(item, ctx, fns) {
|
|
|
1775
2043
|
...exprItem.alias !== void 0 && { as: exprItem.alias }
|
|
1776
2044
|
};
|
|
1777
2045
|
}
|
|
2046
|
+
if (expr.type === "string" || expr.type === "number" || expr.type === "boolean" || expr.type === "null" || expr.type === "rangeLiteral" || expr.type === "dateRange") {
|
|
2047
|
+
if (expr.type === "null") {
|
|
2048
|
+
return literalExpressionIntent(null, exprItem.alias);
|
|
2049
|
+
}
|
|
2050
|
+
if (expr.type === "rangeLiteral" || expr.type === "dateRange") {
|
|
2051
|
+
return literalExpressionIntent(expr.value, exprItem.alias);
|
|
2052
|
+
}
|
|
2053
|
+
return literalExpressionIntent(expr.value, exprItem.alias);
|
|
2054
|
+
}
|
|
1778
2055
|
throw new Error(
|
|
1779
2056
|
`Unsupported expression type in SELECT: ${expr.type}. This expression cannot be compiled to IntentAST. Consider extending the grammar or using a supported expression.`
|
|
1780
2057
|
);
|
|
@@ -1850,21 +2127,25 @@ function compileMultiSegmentPath(expr, item, ctx) {
|
|
|
1850
2127
|
}
|
|
1851
2128
|
function compileCaseExpression(caseExpr, item, ctx, fns) {
|
|
1852
2129
|
if (caseExpr.subject) {
|
|
1853
|
-
const subjectField =
|
|
2130
|
+
const subjectField = expressionToValidatedField(caseExpr.subject, ctx);
|
|
1854
2131
|
if (!subjectField) {
|
|
1855
2132
|
throw new Error("Simple CASE subject must be a column reference");
|
|
1856
2133
|
}
|
|
2134
|
+
const when = caseExpr.whenClauses.map((wc) => {
|
|
2135
|
+
const condition = {
|
|
2136
|
+
kind: "comparison",
|
|
2137
|
+
field: subjectField,
|
|
2138
|
+
operator: "eq",
|
|
2139
|
+
value: expressionToValue(wc.condition, ctx)
|
|
2140
|
+
};
|
|
2141
|
+
return {
|
|
2142
|
+
condition,
|
|
2143
|
+
result: compileExpressionToIntent(wc.result, ctx, fns)
|
|
2144
|
+
};
|
|
2145
|
+
});
|
|
1857
2146
|
return {
|
|
1858
2147
|
kind: "case",
|
|
1859
|
-
when
|
|
1860
|
-
condition: {
|
|
1861
|
-
kind: "comparison",
|
|
1862
|
-
field: subjectField,
|
|
1863
|
-
operator: "eq",
|
|
1864
|
-
value: expressionToValue(wc.condition)
|
|
1865
|
-
},
|
|
1866
|
-
result: compileExpressionToIntent(wc.result, ctx, fns)
|
|
1867
|
-
})),
|
|
2148
|
+
when,
|
|
1868
2149
|
...caseExpr.elseClause && {
|
|
1869
2150
|
else: compileExpressionToIntent(caseExpr.elseClause, ctx, fns)
|
|
1870
2151
|
},
|
|
@@ -1891,21 +2172,28 @@ function compileExpressionToIntent(expr, ctx, fns) {
|
|
|
1891
2172
|
`CASE WHEN condition left side must be a column path, got ${cmp.left.type}`
|
|
1892
2173
|
);
|
|
1893
2174
|
}
|
|
2175
|
+
validateSelectPathExpression(cmp.left, ctx);
|
|
2176
|
+
if (cmp.right.type === "path") {
|
|
2177
|
+
validateSelectPathExpression(cmp.right, ctx);
|
|
2178
|
+
}
|
|
1894
2179
|
const column = cmp.left.segments.join(".");
|
|
1895
|
-
const value = expressionToValue(cmp.right);
|
|
1896
|
-
|
|
2180
|
+
const value = expressionToValue(cmp.right, ctx);
|
|
2181
|
+
const intent = {
|
|
1897
2182
|
kind: "comparison",
|
|
1898
2183
|
column,
|
|
1899
2184
|
operator: cmp.operator,
|
|
1900
2185
|
value
|
|
1901
2186
|
};
|
|
2187
|
+
return intent;
|
|
1902
2188
|
}
|
|
1903
|
-
if (expr.type === "string" || expr.type === "number" || expr.type === "boolean" || expr.type === "null") {
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
2189
|
+
if (expr.type === "string" || expr.type === "number" || expr.type === "boolean" || expr.type === "null" || expr.type === "namedParam") {
|
|
2190
|
+
if (expr.type === "namedParam") {
|
|
2191
|
+
return expressionToValue(expr, ctx);
|
|
2192
|
+
}
|
|
2193
|
+
if (expr.type === "null") {
|
|
2194
|
+
return literalExpressionIntent(null);
|
|
2195
|
+
}
|
|
2196
|
+
return literalExpressionIntent(expr.value);
|
|
1909
2197
|
}
|
|
1910
2198
|
const selectItem = {
|
|
1911
2199
|
type: "expression",
|
|
@@ -1913,13 +2201,20 @@ function compileExpressionToIntent(expr, ctx, fns) {
|
|
|
1913
2201
|
};
|
|
1914
2202
|
return compileSelectExpression(selectItem, ctx, fns);
|
|
1915
2203
|
}
|
|
1916
|
-
var JSON_FUNCTION_NAMES =
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
]
|
|
1922
|
-
|
|
2204
|
+
var JSON_FUNCTION_NAMES = new Set(
|
|
2205
|
+
NQL_SELECT_JSON_FUNCTIONS
|
|
2206
|
+
);
|
|
2207
|
+
function compileJsonPathArgs(fn, args, ctx) {
|
|
2208
|
+
const keys = args.slice(1).map((a) => coerceToStringKey(a, `${fn}() path argument`, ctx));
|
|
2209
|
+
const firstArg = args[1];
|
|
2210
|
+
const first = keys[0];
|
|
2211
|
+
if (args.length === 2 && firstArg?.type === "string" && typeof first === "string" && first?.startsWith("{") && first.endsWith("}")) {
|
|
2212
|
+
const inner = first.slice(1, -1);
|
|
2213
|
+
return inner.length === 0 ? [] : inner.split(",");
|
|
2214
|
+
}
|
|
2215
|
+
return keys;
|
|
2216
|
+
}
|
|
2217
|
+
function compileJsonFunction(fn, args, alias, ctx) {
|
|
1923
2218
|
if (!JSON_FUNCTION_NAMES.has(fn)) return null;
|
|
1924
2219
|
if (args.length < 2) {
|
|
1925
2220
|
throw new NqlSemanticException(
|
|
@@ -1935,7 +2230,7 @@ function compileJsonFunction(fn, args, alias) {
|
|
|
1935
2230
|
);
|
|
1936
2231
|
}
|
|
1937
2232
|
if (fn === "json_extract" || fn === "json_extract_text") {
|
|
1938
|
-
const keys = args.slice(1).map((a) => coerceToStringKey(a, `${fn}() path argument
|
|
2233
|
+
const keys = args.slice(1).map((a) => coerceToStringKey(a, `${fn}() path argument`, ctx));
|
|
1939
2234
|
return {
|
|
1940
2235
|
kind: "jsonExtract",
|
|
1941
2236
|
field,
|
|
@@ -1945,13 +2240,11 @@ function compileJsonFunction(fn, args, alias) {
|
|
|
1945
2240
|
};
|
|
1946
2241
|
}
|
|
1947
2242
|
if (fn === "json_path" || fn === "json_path_text") {
|
|
1948
|
-
const
|
|
1949
|
-
const first = keys[0];
|
|
1950
|
-
const pathStr = keys.length === 1 && first?.startsWith("{") && first.endsWith("}") ? first : `{${keys.join(",")}}`;
|
|
2243
|
+
const path = compileJsonPathArgs(fn, args, ctx);
|
|
1951
2244
|
return {
|
|
1952
2245
|
kind: "jsonPathExtract",
|
|
1953
2246
|
field,
|
|
1954
|
-
path
|
|
2247
|
+
path,
|
|
1955
2248
|
mode: fn === "json_path" ? "json" : "text",
|
|
1956
2249
|
...alias !== void 0 && { as: alias }
|
|
1957
2250
|
};
|
|
@@ -1972,6 +2265,10 @@ var DEFAULT_RECURSIVE_KEYWORDS = [
|
|
|
1972
2265
|
];
|
|
1973
2266
|
|
|
1974
2267
|
// src/compiler/index.ts
|
|
2268
|
+
function allowsInternalParams(options) {
|
|
2269
|
+
const internalOptions = options?.[NQL_INTERNAL_COMPILER_OPTIONS];
|
|
2270
|
+
return internalOptions?.allowInternalParams === true;
|
|
2271
|
+
}
|
|
1975
2272
|
var NqlCompiler = class {
|
|
1976
2273
|
ctx;
|
|
1977
2274
|
fns;
|
|
@@ -1989,15 +2286,19 @@ var NqlCompiler = class {
|
|
|
1989
2286
|
);
|
|
1990
2287
|
}
|
|
1991
2288
|
}
|
|
2289
|
+
const params = options?.params ?? {};
|
|
2290
|
+
const allowInternalParams = allowsInternalParams(options);
|
|
2291
|
+
validateParamsMap(params, { allowInternalParams });
|
|
1992
2292
|
this.ctx = {
|
|
1993
2293
|
currentFromTable: void 0,
|
|
1994
2294
|
currentRelationTarget: void 0,
|
|
1995
2295
|
pseudoColumnKeywords,
|
|
1996
2296
|
recursiveKeywords,
|
|
1997
2297
|
validator,
|
|
1998
|
-
params
|
|
2298
|
+
params,
|
|
1999
2299
|
maxAnyItems: maxAnyItemsRaw ?? MAX_ANY_ITEMS,
|
|
2000
|
-
allowUnfilteredMutations: options?.allowUnfilteredMutations ?? false
|
|
2300
|
+
allowUnfilteredMutations: options?.allowUnfilteredMutations ?? false,
|
|
2301
|
+
allowInternalParams
|
|
2001
2302
|
};
|
|
2002
2303
|
this.fns = {
|
|
2003
2304
|
compileQuery: (query, ctx) => compileQuery(query, ctx, this.fns),
|
|
@@ -2440,6 +2741,11 @@ var allTokens = [
|
|
|
2440
2741
|
// ? (no conflict with other tokens)
|
|
2441
2742
|
];
|
|
2442
2743
|
var NqlLexer = new Lexer(allTokens);
|
|
2744
|
+
|
|
2745
|
+
// src/lexer/index.ts
|
|
2746
|
+
function tokenize(input) {
|
|
2747
|
+
return NqlLexer.tokenize(input);
|
|
2748
|
+
}
|
|
2443
2749
|
var NqlParser = class extends CstParser {
|
|
2444
2750
|
constructor() {
|
|
2445
2751
|
super(allTokens, {
|
|
@@ -2587,7 +2893,7 @@ var NqlParser = class extends CstParser {
|
|
|
2587
2893
|
this.SUBRULE(this.orderList);
|
|
2588
2894
|
});
|
|
2589
2895
|
/**
|
|
2590
|
-
* limit_clause = "limit" [ident_segment ("." ident_segment)*] NUMBER ;
|
|
2896
|
+
* limit_clause = "limit" [ident_segment ("." ident_segment)*] (NUMBER | NAMED_PARAM) ;
|
|
2591
2897
|
*/
|
|
2592
2898
|
limitClause = this.RULE("limitClause", () => {
|
|
2593
2899
|
this.CONSUME(Limit);
|
|
@@ -2598,14 +2904,29 @@ var NqlParser = class extends CstParser {
|
|
|
2598
2904
|
this.SUBRULE2(this.identSegment);
|
|
2599
2905
|
});
|
|
2600
2906
|
});
|
|
2601
|
-
this.
|
|
2907
|
+
this.SUBRULE(this.numericValueAtom);
|
|
2602
2908
|
});
|
|
2603
2909
|
/**
|
|
2604
|
-
* offset_clause = "offset" NUMBER ;
|
|
2910
|
+
* offset_clause = "offset" (NUMBER | NAMED_PARAM) ;
|
|
2605
2911
|
*/
|
|
2606
2912
|
offsetClause = this.RULE("offsetClause", () => {
|
|
2607
2913
|
this.CONSUME(Offset);
|
|
2608
|
-
this.
|
|
2914
|
+
this.SUBRULE(this.numericValueAtom);
|
|
2915
|
+
});
|
|
2916
|
+
/**
|
|
2917
|
+
* numeric_value_atom = NUMBER | NAMED_PARAM ;
|
|
2918
|
+
*
|
|
2919
|
+
* Structural-position durability rule: value positions that consume scalar
|
|
2920
|
+
* values may add `NamedParam` here and validate the resolved value in the
|
|
2921
|
+
* compiler. Structural positions such as identifiers, directions, lock modes,
|
|
2922
|
+
* and traversal depth hints must not consume this helper; use nqlRaw()/builder
|
|
2923
|
+
* APIs for trusted dynamic structure.
|
|
2924
|
+
*/
|
|
2925
|
+
numericValueAtom = this.RULE("numericValueAtom", () => {
|
|
2926
|
+
this.OR([
|
|
2927
|
+
{ ALT: () => this.CONSUME(NumberLiteral) },
|
|
2928
|
+
{ ALT: () => this.CONSUME(NamedParam) }
|
|
2929
|
+
]);
|
|
2609
2930
|
});
|
|
2610
2931
|
/**
|
|
2611
2932
|
* lock_clause = lock_strength [ lock_wait_policy ] ;
|
|
@@ -2869,7 +3190,7 @@ var NqlParser = class extends CstParser {
|
|
|
2869
3190
|
]);
|
|
2870
3191
|
});
|
|
2871
3192
|
/**
|
|
2872
|
-
* range_op_suffix = range_op (range_literal | literal) ;
|
|
3193
|
+
* range_op_suffix = range_op (range_literal | literal | named_param_expr) ;
|
|
2873
3194
|
* Example: overlaps [1,10) or contains (0,100) or contains 25
|
|
2874
3195
|
* Note: contains can take a scalar value (e.g., contains 25 checks if range contains the value)
|
|
2875
3196
|
*/
|
|
@@ -2877,7 +3198,8 @@ var NqlParser = class extends CstParser {
|
|
|
2877
3198
|
this.SUBRULE(this.rangeOp);
|
|
2878
3199
|
this.OR([
|
|
2879
3200
|
{ ALT: () => this.SUBRULE(this.rangeLiteral) },
|
|
2880
|
-
{ ALT: () => this.SUBRULE2(this.literal) }
|
|
3201
|
+
{ ALT: () => this.SUBRULE2(this.literal) },
|
|
3202
|
+
{ ALT: () => this.SUBRULE(this.namedParamExpr) }
|
|
2881
3203
|
]);
|
|
2882
3204
|
});
|
|
2883
3205
|
/**
|
|
@@ -3118,10 +3440,12 @@ var NqlParser = class extends CstParser {
|
|
|
3118
3440
|
});
|
|
3119
3441
|
});
|
|
3120
3442
|
/**
|
|
3121
|
-
* primary_expr = literal | case_expr | path_expr | func_call | "(" expr ")" | "(" scalar_subquery ")" ;
|
|
3443
|
+
* primary_expr = named_param_expr | literal | case_expr | path_expr | func_call | "(" expr ")" | "(" scalar_subquery ")" ;
|
|
3122
3444
|
*/
|
|
3123
3445
|
primaryExpr = this.RULE("primaryExpr", () => {
|
|
3124
3446
|
this.OR([
|
|
3447
|
+
// Bound value parameter: :paramName
|
|
3448
|
+
{ ALT: () => this.SUBRULE(this.namedParamExpr) },
|
|
3125
3449
|
// Range literal in value context (unambiguous: starts with '[')
|
|
3126
3450
|
{
|
|
3127
3451
|
GATE: () => this.LA(1).tokenType === LBracket,
|
|
@@ -3159,6 +3483,12 @@ var NqlParser = class extends CstParser {
|
|
|
3159
3483
|
}
|
|
3160
3484
|
]);
|
|
3161
3485
|
});
|
|
3486
|
+
/**
|
|
3487
|
+
* named_param_expr = NAMED_PARAM ;
|
|
3488
|
+
*/
|
|
3489
|
+
namedParamExpr = this.RULE("namedParamExpr", () => {
|
|
3490
|
+
this.CONSUME(NamedParam);
|
|
3491
|
+
});
|
|
3162
3492
|
/**
|
|
3163
3493
|
* Check if at start of scalar subquery (ident | ...)
|
|
3164
3494
|
*/
|
|
@@ -3515,7 +3845,7 @@ var NqlParser = class extends CstParser {
|
|
|
3515
3845
|
]);
|
|
3516
3846
|
});
|
|
3517
3847
|
/**
|
|
3518
|
-
* insert_from_stmt = "insert" "into" ident_segment "from" ident_segment [ "where" boolean_expr ] [ "limit"
|
|
3848
|
+
* insert_from_stmt = "insert" "into" ident_segment "from" ident_segment [ "where" boolean_expr ] [ "limit" numeric_value_atom ] ;
|
|
3519
3849
|
* @example insert into archived_users from users where active = false limit 100
|
|
3520
3850
|
*/
|
|
3521
3851
|
insertFromStmt = this.RULE("insertFromStmt", () => {
|
|
@@ -3530,7 +3860,7 @@ var NqlParser = class extends CstParser {
|
|
|
3530
3860
|
});
|
|
3531
3861
|
this.OPTION2(() => {
|
|
3532
3862
|
this.CONSUME(Limit);
|
|
3533
|
-
this.
|
|
3863
|
+
this.SUBRULE(this.numericValueAtom);
|
|
3534
3864
|
});
|
|
3535
3865
|
});
|
|
3536
3866
|
/**
|
|
@@ -3590,7 +3920,7 @@ var NqlParser = class extends CstParser {
|
|
|
3590
3920
|
});
|
|
3591
3921
|
});
|
|
3592
3922
|
/**
|
|
3593
|
-
* upsert_from_stmt = "upsert" "into" ident_segment "on" ( "(" ident_list ")" | ident_segment ) "from" ident_segment [ "where" boolean_expr ] [ "limit"
|
|
3923
|
+
* upsert_from_stmt = "upsert" "into" ident_segment "on" ( "(" ident_list ")" | ident_segment ) "from" ident_segment [ "where" boolean_expr ] [ "limit" numeric_value_atom ] ;
|
|
3594
3924
|
* @example upsert into authors on id from counts
|
|
3595
3925
|
* @example upsert into authors on (id, email) from counts where active = true
|
|
3596
3926
|
*/
|
|
@@ -3621,7 +3951,7 @@ var NqlParser = class extends CstParser {
|
|
|
3621
3951
|
});
|
|
3622
3952
|
this.OPTION2(() => {
|
|
3623
3953
|
this.CONSUME(Limit);
|
|
3624
|
-
this.
|
|
3954
|
+
this.SUBRULE(this.numericValueAtom);
|
|
3625
3955
|
});
|
|
3626
3956
|
});
|
|
3627
3957
|
/**
|
|
@@ -3916,6 +4246,12 @@ function buildRangeOp(left, suffixNode, visit) {
|
|
|
3916
4246
|
const scalar = visit(asCstNode(suffixCtx.literal[0]));
|
|
3917
4247
|
return { type: "rangeOp", operator, left, scalar };
|
|
3918
4248
|
}
|
|
4249
|
+
if (suffixCtx.namedParamExpr) {
|
|
4250
|
+
const scalar = visit(
|
|
4251
|
+
asCstNode(suffixCtx.namedParamExpr[0])
|
|
4252
|
+
);
|
|
4253
|
+
return { type: "rangeOp", operator, left, scalar };
|
|
4254
|
+
}
|
|
3919
4255
|
throw new NqlSemanticException(
|
|
3920
4256
|
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
3921
4257
|
"Range op suffix missing range literal or scalar value"
|
|
@@ -4091,6 +4427,7 @@ function visitUnaryExpr(ctx, visit) {
|
|
|
4091
4427
|
return expr;
|
|
4092
4428
|
}
|
|
4093
4429
|
function visitPrimaryExpr(ctx, visit) {
|
|
4430
|
+
if (ctx.namedParamExpr) return visit(asCstNode(ctx.namedParamExpr[0]));
|
|
4094
4431
|
if (ctx.rangeLiteral) return visit(asCstNode(ctx.rangeLiteral[0]));
|
|
4095
4432
|
if (ctx.literal) return visit(asCstNode(ctx.literal[0]));
|
|
4096
4433
|
if (ctx.caseExpr) return visit(asCstNode(ctx.caseExpr[0]));
|
|
@@ -4105,6 +4442,14 @@ function visitPrimaryExpr(ctx, visit) {
|
|
|
4105
4442
|
"Invalid primary expression"
|
|
4106
4443
|
);
|
|
4107
4444
|
}
|
|
4445
|
+
function visitNamedParamExpr(ctx) {
|
|
4446
|
+
const token = ctx.NamedParam?.[0];
|
|
4447
|
+
const raw = token ? getImage(token) : ":unknown";
|
|
4448
|
+
return {
|
|
4449
|
+
type: "namedParam",
|
|
4450
|
+
name: raw.slice(1)
|
|
4451
|
+
};
|
|
4452
|
+
}
|
|
4108
4453
|
function visitCaseExpr(ctx, visit) {
|
|
4109
4454
|
const whenClauses = [];
|
|
4110
4455
|
let subject;
|
|
@@ -4488,7 +4833,7 @@ function visitInsertFromStmt(ctx, visit) {
|
|
|
4488
4833
|
table: target,
|
|
4489
4834
|
source,
|
|
4490
4835
|
where: ctx.booleanExpr ? visit(asCstNode(ctx.booleanExpr[0])) : void 0,
|
|
4491
|
-
limit: ctx.
|
|
4836
|
+
limit: ctx.numericValueAtom ? visit(asCstNode(ctx.numericValueAtom[0])) : void 0
|
|
4492
4837
|
};
|
|
4493
4838
|
}
|
|
4494
4839
|
function visitUpdateStmt(ctx, visit) {
|
|
@@ -4563,7 +4908,7 @@ function visitUpsertFromStmt(ctx, visit) {
|
|
|
4563
4908
|
conflictColumns,
|
|
4564
4909
|
source,
|
|
4565
4910
|
where: ctx.booleanExpr ? visit(asCstNode(ctx.booleanExpr[0])) : void 0,
|
|
4566
|
-
limit: ctx.
|
|
4911
|
+
limit: ctx.numericValueAtom ? visit(asCstNode(ctx.numericValueAtom[0])) : void 0
|
|
4567
4912
|
};
|
|
4568
4913
|
}
|
|
4569
4914
|
function visitAssignmentList(ctx, visit) {
|
|
@@ -4671,8 +5016,8 @@ function visitOrderClause(ctx, visit) {
|
|
|
4671
5016
|
return { type: "orderBy", items };
|
|
4672
5017
|
}
|
|
4673
5018
|
function visitLimitClause(ctx, visit) {
|
|
4674
|
-
requireFirst(ctx, "
|
|
4675
|
-
const count =
|
|
5019
|
+
requireFirst(ctx, "numericValueAtom", "Limit clause missing number");
|
|
5020
|
+
const count = visit(asCstNode(ctx.numericValueAtom[0]));
|
|
4676
5021
|
const segments = ctx.identSegment;
|
|
4677
5022
|
if (segments && segments.length > 0) {
|
|
4678
5023
|
const parts = [];
|
|
@@ -4683,13 +5028,23 @@ function visitLimitClause(ctx, visit) {
|
|
|
4683
5028
|
}
|
|
4684
5029
|
return { type: "limit", count };
|
|
4685
5030
|
}
|
|
4686
|
-
function visitOffsetClause(ctx) {
|
|
4687
|
-
requireFirst(ctx, "
|
|
5031
|
+
function visitOffsetClause(ctx, visit) {
|
|
5032
|
+
requireFirst(ctx, "numericValueAtom", "Offset clause missing number");
|
|
4688
5033
|
return {
|
|
4689
5034
|
type: "offset",
|
|
4690
|
-
count:
|
|
5035
|
+
count: visit(asCstNode(ctx.numericValueAtom[0]))
|
|
4691
5036
|
};
|
|
4692
5037
|
}
|
|
5038
|
+
function visitNumericValueAtom(ctx) {
|
|
5039
|
+
if (ctx.NumberLiteral) {
|
|
5040
|
+
return parseInt(getImage(ctx.NumberLiteral[0]), 10);
|
|
5041
|
+
}
|
|
5042
|
+
if (ctx.NamedParam) {
|
|
5043
|
+
const raw = getImage(ctx.NamedParam[0]);
|
|
5044
|
+
return { type: "namedParam", name: raw.slice(1) };
|
|
5045
|
+
}
|
|
5046
|
+
unreachable("Invalid numeric value");
|
|
5047
|
+
}
|
|
4693
5048
|
function visitSelectList(ctx, visit) {
|
|
4694
5049
|
const items = [];
|
|
4695
5050
|
if (ctx.selectItem) {
|
|
@@ -4816,7 +5171,10 @@ var NqlCstVisitor = class extends BaseCstVisitor {
|
|
|
4816
5171
|
return visitLimitClause(ctx, this.v);
|
|
4817
5172
|
}
|
|
4818
5173
|
offsetClause(ctx) {
|
|
4819
|
-
return visitOffsetClause(ctx);
|
|
5174
|
+
return visitOffsetClause(ctx, this.v);
|
|
5175
|
+
}
|
|
5176
|
+
numericValueAtom(ctx) {
|
|
5177
|
+
return visitNumericValueAtom(ctx);
|
|
4820
5178
|
}
|
|
4821
5179
|
selectList(ctx) {
|
|
4822
5180
|
return visitSelectList(ctx, this.v);
|
|
@@ -4899,6 +5257,9 @@ var NqlCstVisitor = class extends BaseCstVisitor {
|
|
|
4899
5257
|
primaryExpr(ctx) {
|
|
4900
5258
|
return visitPrimaryExpr(ctx, this.v);
|
|
4901
5259
|
}
|
|
5260
|
+
namedParamExpr(ctx) {
|
|
5261
|
+
return visitNamedParamExpr(ctx);
|
|
5262
|
+
}
|
|
4902
5263
|
// -- CASE --
|
|
4903
5264
|
caseExpr(ctx) {
|
|
4904
5265
|
return visitCaseExpr(ctx, this.v);
|
|
@@ -5087,13 +5448,16 @@ function compile(input, schema, options, compilerOptions) {
|
|
|
5087
5448
|
warnings: parseResult.warnings
|
|
5088
5449
|
};
|
|
5089
5450
|
} catch (err) {
|
|
5090
|
-
const
|
|
5451
|
+
const semanticError = err instanceof NqlSemanticException;
|
|
5452
|
+
const code = semanticError ? err.code : NqlErrorCodes.SEM_UNREACHABLE;
|
|
5091
5453
|
return {
|
|
5092
5454
|
success: false,
|
|
5093
5455
|
errors: [
|
|
5094
5456
|
{
|
|
5095
5457
|
code,
|
|
5096
|
-
message: err instanceof Error ? err.message : String(err)
|
|
5458
|
+
message: err instanceof Error ? err.message : String(err),
|
|
5459
|
+
location: semanticError ? err.location : void 0,
|
|
5460
|
+
suggestion: semanticError ? err.suggestion : void 0
|
|
5097
5461
|
}
|
|
5098
5462
|
],
|
|
5099
5463
|
warnings: []
|
|
@@ -5105,6 +5469,7 @@ function hasColumnValidatorShape(obj) {
|
|
|
5105
5469
|
}
|
|
5106
5470
|
/* v8 ignore next — '*' columns are validated at call-site before reaching here -- @preserve */
|
|
5107
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 */
|
|
5108
5473
|
/* v8 ignore next — defensive: callers always pass valid relation paths -- @preserve */
|
|
5109
5474
|
/* v8 ignore next — not yet reachable: flat + pre-existing includes requires WITH clause -- @preserve */
|
|
5110
5475
|
/* v8 ignore stop -- @preserve */
|
|
@@ -5120,7 +5485,6 @@ function hasColumnValidatorShape(obj) {
|
|
|
5120
5485
|
/* v8 ignore start — defensive: parser guarantees IS NULL LHS is a path -- @preserve */
|
|
5121
5486
|
/* v8 ignore start — defensive: parser guarantees at least 2 args -- @preserve */
|
|
5122
5487
|
/* v8 ignore next — defensive: only json_* functions reach WHERE context -- @preserve */
|
|
5123
|
-
/* v8 ignore next — defensive: bound queries from NQL always have select.fields -- @preserve */
|
|
5124
5488
|
/* v8 ignore start — defensive: star items are handled in compileSelectClause before reaching here -- @preserve */
|
|
5125
5489
|
/* v8 ignore next — defensive: relationStar items are handled in compileSelectClause -- @preserve */
|
|
5126
5490
|
/* v8 ignore start — defensive: all parser-produced SELECT expression types are handled above -- @preserve */
|
|
@@ -5149,8 +5513,9 @@ function hasColumnValidatorShape(obj) {
|
|
|
5149
5513
|
/* v8 ignore start — defensive: parser guarantees target and source identifiers -- @preserve */
|
|
5150
5514
|
/* v8 ignore next — defensive: parser guarantees withQuery, query, or mutationPipeline -- @preserve */
|
|
5151
5515
|
/* v8 ignore next — defensive: parser guarantees one of the clause alternatives -- @preserve */
|
|
5516
|
+
/* v8 ignore next — defensive: parser guarantees NumberLiteral or NamedParam -- @preserve */
|
|
5152
5517
|
/* v8 ignore next — defensive: parser guarantees query or identSegment -- @preserve */
|
|
5153
5518
|
|
|
5154
|
-
export { NqlCompiler, NqlCstVisitor, NqlLexer, NqlParser, allTokens, compile, createCompiler, cstToAst, nqlVisitor, parse, parseCst };
|
|
5519
|
+
export { NqlCompiler, NqlCstVisitor, NqlLexer, NqlParser, allTokens, compile, createCompiler, cstToAst, nqlVisitor, parse, parseCst, tokenize };
|
|
5155
5520
|
//# sourceMappingURL=index.js.map
|
|
5156
5521
|
//# sourceMappingURL=index.js.map
|