@dbsp/nql 1.0.4 → 1.2.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 +38 -5
- package/dist/index.d.ts +50 -33
- package/dist/index.js +589 -157
- 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 } 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":
|
|
@@ -134,21 +220,21 @@ function expressionToValue(expr) {
|
|
|
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;
|
|
@@ -313,7 +404,7 @@ function validateWhereField(ctx, field, aliasContext, originalExpr) {
|
|
|
313
404
|
ctx.validator.validateColumn(ctx.currentFromTable, field);
|
|
314
405
|
}
|
|
315
406
|
}
|
|
316
|
-
function coerceToStringKey(expr, contextLabel) {
|
|
407
|
+
function coerceToStringKey(expr, contextLabel, ctx) {
|
|
317
408
|
if (expr.type === "path") {
|
|
318
409
|
const segments = expr.segments;
|
|
319
410
|
if (segments.length > 1) {
|
|
@@ -334,6 +425,17 @@ function coerceToStringKey(expr, contextLabel) {
|
|
|
334
425
|
if (expr.type === "string") {
|
|
335
426
|
return expr.value;
|
|
336
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
|
+
}
|
|
337
439
|
throw new NqlSemanticException(
|
|
338
440
|
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
339
441
|
`${contextLabel} must be a string literal`
|
|
@@ -387,6 +489,21 @@ function applyIncludeLimit(includes, path, limit) {
|
|
|
387
489
|
}
|
|
388
490
|
|
|
389
491
|
// src/compiler/compile-query.ts
|
|
492
|
+
function compileNestedQuery(query, ctx, fns, bindings) {
|
|
493
|
+
const savedContext = {
|
|
494
|
+
currentFromTable: ctx.currentFromTable,
|
|
495
|
+
currentRelationTarget: ctx.currentRelationTarget
|
|
496
|
+
};
|
|
497
|
+
try {
|
|
498
|
+
if (bindings) {
|
|
499
|
+
return compileQuery(query, ctx, fns, bindings);
|
|
500
|
+
}
|
|
501
|
+
return fns.compileQuery(query, ctx);
|
|
502
|
+
} finally {
|
|
503
|
+
ctx.currentFromTable = savedContext.currentFromTable;
|
|
504
|
+
ctx.currentRelationTarget = savedContext.currentRelationTarget;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
390
507
|
function compileQuery(query, ctx, fns, bindings) {
|
|
391
508
|
const setClauseIndex = query.clauses.findIndex(
|
|
392
509
|
(c) => c.type === "setOperation"
|
|
@@ -459,15 +576,25 @@ function compileQuery(query, ctx, fns, bindings) {
|
|
|
459
576
|
break;
|
|
460
577
|
case "limit": {
|
|
461
578
|
const lc = clause;
|
|
579
|
+
const count = resolveIntegerCount(
|
|
580
|
+
lc.count,
|
|
581
|
+
ctx,
|
|
582
|
+
lc.relation ? "per-include limit" : "limit"
|
|
583
|
+
);
|
|
462
584
|
if (lc.relation) {
|
|
463
|
-
|
|
585
|
+
const includeLimit = isParamIntent(count) ? count.value : count;
|
|
586
|
+
includeLimits.set(lc.relation, includeLimit);
|
|
464
587
|
} else {
|
|
465
|
-
limit =
|
|
588
|
+
limit = count;
|
|
466
589
|
}
|
|
467
590
|
break;
|
|
468
591
|
}
|
|
469
592
|
case "offset":
|
|
470
|
-
offset =
|
|
593
|
+
offset = resolveIntegerCount(
|
|
594
|
+
clause.count,
|
|
595
|
+
ctx,
|
|
596
|
+
"offset"
|
|
597
|
+
);
|
|
471
598
|
break;
|
|
472
599
|
case "lock": {
|
|
473
600
|
const lc = clause;
|
|
@@ -569,6 +696,12 @@ function getExplicitColumnCount(intent) {
|
|
|
569
696
|
}
|
|
570
697
|
function compileSetOperation(query, setClauseIndex, ctx, fns, bindings) {
|
|
571
698
|
const setClause = query.clauses[setClauseIndex];
|
|
699
|
+
if (setClauseIndex < query.clauses.length - 1) {
|
|
700
|
+
const trailingTypes = query.clauses.slice(setClauseIndex + 1).map((c) => c.type).join(", ");
|
|
701
|
+
throw new Error(
|
|
702
|
+
`Clauses after a set operation are not supported (found: ${trailingTypes}). Wrap the set operation in a subquery or move the clause before the set operation.`
|
|
703
|
+
);
|
|
704
|
+
}
|
|
572
705
|
const leftQuery = {
|
|
573
706
|
table: query.table,
|
|
574
707
|
clauses: query.clauses.slice(0, setClauseIndex)
|
|
@@ -576,7 +709,7 @@ function compileSetOperation(query, setClauseIndex, ctx, fns, bindings) {
|
|
|
576
709
|
const left = compileQuery(leftQuery, ctx, fns, bindings);
|
|
577
710
|
let right;
|
|
578
711
|
if (setClause.right) {
|
|
579
|
-
right =
|
|
712
|
+
right = compileNestedQuery(setClause.right, ctx, fns, bindings);
|
|
580
713
|
} else if (setClause.boundName) {
|
|
581
714
|
const bound = bindings?.get(setClause.boundName);
|
|
582
715
|
if (!bound) {
|
|
@@ -626,6 +759,14 @@ function compileOrderItem(item, ctx) {
|
|
|
626
759
|
}
|
|
627
760
|
return { field, direction: item.direction };
|
|
628
761
|
}
|
|
762
|
+
if (item.expression.type === "namedParam") {
|
|
763
|
+
throw new NqlSemanticException(
|
|
764
|
+
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
765
|
+
`Named parameter :${item.expression.name} cannot be used as an ORDER BY expression because ORDER BY is query structure, not a value`,
|
|
766
|
+
void 0,
|
|
767
|
+
'Choose a trusted structural path for dynamic ordering, such as nqlRaw("order by ...") or the query builder after validating the requested column and direction.'
|
|
768
|
+
);
|
|
769
|
+
}
|
|
629
770
|
const sqlExpr = expressionToSql(item.expression);
|
|
630
771
|
return { field: sqlExpr, direction: item.direction };
|
|
631
772
|
}
|
|
@@ -646,7 +787,7 @@ function compileWithQuery(astNode, ctx, fns) {
|
|
|
646
787
|
try {
|
|
647
788
|
const ctes = [];
|
|
648
789
|
for (const cte of astNode.ctes) {
|
|
649
|
-
const bodyResult =
|
|
790
|
+
const bodyResult = compileNestedQuery(cte.query, ctx, fns);
|
|
650
791
|
if ("kind" in bodyResult && bodyResult.kind === "setOperation") {
|
|
651
792
|
throw new NqlSemanticException(
|
|
652
793
|
NqlErrorCodes.SEM_UNKNOWN_TABLE,
|
|
@@ -788,6 +929,9 @@ function getISOWeekCount(year) {
|
|
|
788
929
|
}
|
|
789
930
|
|
|
790
931
|
// src/compiler/compile-expression.ts
|
|
932
|
+
function paramValue(value) {
|
|
933
|
+
return value !== null && typeof value === "object" && value.kind === "param" ? value.value : value;
|
|
934
|
+
}
|
|
791
935
|
var MAX_ANY_ITEMS = 1e4;
|
|
792
936
|
function compileLogical(expr, ctx, fns, aliasContext, outerAliases) {
|
|
793
937
|
if (expr.type === "binary") {
|
|
@@ -851,7 +995,7 @@ function compileComparison(expr, ctx, _fns, aliasContext, outerAliases) {
|
|
|
851
995
|
aliasContext,
|
|
852
996
|
outerAliases
|
|
853
997
|
);
|
|
854
|
-
|
|
998
|
+
const intent2 = {
|
|
855
999
|
kind: "comparison",
|
|
856
1000
|
field: baseField,
|
|
857
1001
|
operator: operator2,
|
|
@@ -859,6 +1003,7 @@ function compileComparison(expr, ctx, _fns, aliasContext, outerAliases) {
|
|
|
859
1003
|
jsonPath: jsonLeft.path,
|
|
860
1004
|
jsonMode: jsonLeft.mode
|
|
861
1005
|
};
|
|
1006
|
+
return intent2;
|
|
862
1007
|
}
|
|
863
1008
|
if (comp.left.type === "function") {
|
|
864
1009
|
const fn = comp.left.name.toLowerCase();
|
|
@@ -876,7 +1021,7 @@ function compileComparison(expr, ctx, _fns, aliasContext, outerAliases) {
|
|
|
876
1021
|
`${fn}() first argument must be a field reference`
|
|
877
1022
|
);
|
|
878
1023
|
}
|
|
879
|
-
const keys = comp.left.args.slice(1).map((a) => coerceToStringKey(a, `${fn}() path argument
|
|
1024
|
+
const keys = comp.left.args.slice(1).map((a) => coerceToStringKey(a, `${fn}() path argument`, ctx));
|
|
880
1025
|
const operator2 = mapComparisonOperator(comp.operator);
|
|
881
1026
|
const value2 = resolveFilterValue(
|
|
882
1027
|
comp.right,
|
|
@@ -884,7 +1029,7 @@ function compileComparison(expr, ctx, _fns, aliasContext, outerAliases) {
|
|
|
884
1029
|
aliasContext,
|
|
885
1030
|
outerAliases
|
|
886
1031
|
);
|
|
887
|
-
|
|
1032
|
+
const intent2 = {
|
|
888
1033
|
kind: "comparison",
|
|
889
1034
|
field: baseField,
|
|
890
1035
|
operator: operator2,
|
|
@@ -892,6 +1037,7 @@ function compileComparison(expr, ctx, _fns, aliasContext, outerAliases) {
|
|
|
892
1037
|
jsonPath: keys,
|
|
893
1038
|
jsonMode: fn === "json_extract" ? "json" : "text"
|
|
894
1039
|
};
|
|
1040
|
+
return intent2;
|
|
895
1041
|
}
|
|
896
1042
|
}
|
|
897
1043
|
const field = expressionToField(comp.left, aliasContext);
|
|
@@ -903,20 +1049,22 @@ function compileComparison(expr, ctx, _fns, aliasContext, outerAliases) {
|
|
|
903
1049
|
}
|
|
904
1050
|
validateWhereField(ctx, field, aliasContext, comp.left);
|
|
905
1051
|
if (comp.operator === "like") {
|
|
1052
|
+
const pattern = coerceToStringKey(comp.right, "LIKE pattern", ctx);
|
|
906
1053
|
return {
|
|
907
1054
|
kind: "like",
|
|
908
1055
|
field,
|
|
909
|
-
pattern
|
|
1056
|
+
pattern
|
|
910
1057
|
};
|
|
911
1058
|
}
|
|
912
1059
|
const operator = mapComparisonOperator(comp.operator);
|
|
913
1060
|
const value = resolveFilterValue(comp.right, ctx, aliasContext, outerAliases);
|
|
914
|
-
|
|
1061
|
+
const intent = {
|
|
915
1062
|
kind: "comparison",
|
|
916
1063
|
field,
|
|
917
1064
|
operator,
|
|
918
1065
|
value
|
|
919
1066
|
};
|
|
1067
|
+
return intent;
|
|
920
1068
|
}
|
|
921
1069
|
function compileRange(expr, ctx, _fns, aliasContext, outerAliases) {
|
|
922
1070
|
const rangeExpr = expr;
|
|
@@ -944,12 +1092,13 @@ function compileRange(expr, ctx, _fns, aliasContext, outerAliases) {
|
|
|
944
1092
|
"Range operator requires either a range literal or scalar value"
|
|
945
1093
|
);
|
|
946
1094
|
}
|
|
947
|
-
|
|
1095
|
+
const result = {
|
|
948
1096
|
kind: "range",
|
|
949
1097
|
field,
|
|
950
1098
|
operator: rangeExpr.operator,
|
|
951
1099
|
value: rangeValue
|
|
952
1100
|
};
|
|
1101
|
+
return result;
|
|
953
1102
|
}
|
|
954
1103
|
function compileMembership(expr, ctx, fns, aliasContext, outerAliases) {
|
|
955
1104
|
if (expr.type === "any") {
|
|
@@ -962,11 +1111,12 @@ function compileMembership(expr, ctx, fns, aliasContext, outerAliases) {
|
|
|
962
1111
|
);
|
|
963
1112
|
}
|
|
964
1113
|
validateWhereField(ctx, field2, aliasContext, anyExpr.column);
|
|
965
|
-
const
|
|
1114
|
+
const valuesParam = resolveNamedParam(ctx, anyExpr.paramName);
|
|
1115
|
+
const rawValues = valuesParam.value;
|
|
966
1116
|
if (!Array.isArray(rawValues)) {
|
|
967
1117
|
throw new NqlSemanticException(
|
|
968
1118
|
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
969
|
-
`ANY(:${anyExpr.paramName}) requires an array argument
|
|
1119
|
+
`ANY(:${anyExpr.paramName}) requires an array argument`
|
|
970
1120
|
);
|
|
971
1121
|
}
|
|
972
1122
|
if (rawValues.length > ctx.maxAnyItems) {
|
|
@@ -975,8 +1125,12 @@ function compileMembership(expr, ctx, fns, aliasContext, outerAliases) {
|
|
|
975
1125
|
`ANY(:${anyExpr.paramName}) array length ${rawValues.length} exceeds maximum of ${ctx.maxAnyItems}`
|
|
976
1126
|
);
|
|
977
1127
|
}
|
|
978
|
-
const
|
|
979
|
-
|
|
1128
|
+
const result2 = {
|
|
1129
|
+
kind: "any",
|
|
1130
|
+
field: field2,
|
|
1131
|
+
values: valuesParam
|
|
1132
|
+
};
|
|
1133
|
+
return result2;
|
|
980
1134
|
}
|
|
981
1135
|
const inExpr = expr;
|
|
982
1136
|
const field = expressionToField(inExpr.expression, aliasContext);
|
|
@@ -1005,7 +1159,11 @@ function compileMembership(expr, ctx, fns, aliasContext, outerAliases) {
|
|
|
1005
1159
|
return expandDateRangeList(field, dateRangeValues, inExpr.negated);
|
|
1006
1160
|
}
|
|
1007
1161
|
} else if ("type" in inExpr.values && inExpr.values.type === "subquery") {
|
|
1008
|
-
const subquery =
|
|
1162
|
+
const subquery = compileNestedQuery(
|
|
1163
|
+
inExpr.values.query,
|
|
1164
|
+
ctx,
|
|
1165
|
+
fns
|
|
1166
|
+
);
|
|
1009
1167
|
const result2 = {
|
|
1010
1168
|
kind: "in",
|
|
1011
1169
|
field,
|
|
@@ -1052,27 +1210,28 @@ function compileBetween(expr, ctx, _fns, aliasContext, outerAliases) {
|
|
|
1052
1210
|
aliasContext,
|
|
1053
1211
|
outerAliases
|
|
1054
1212
|
);
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
);
|
|
1061
|
-
}
|
|
1062
|
-
if (upper !== null && typeof upper !== "number" && typeof upper !== "string" && !(upper instanceof Date)) {
|
|
1063
|
-
const got = typeof upper === "object" ? "path reference" : JSON.stringify(upper);
|
|
1064
|
-
throw new NqlSemanticException(
|
|
1065
|
-
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
1066
|
-
`BETWEEN upper bound must be a literal number, string, or date \u2014 got ${got}. Use a param or hardcoded value.`
|
|
1067
|
-
);
|
|
1068
|
-
}
|
|
1213
|
+
const lowerValue = paramValue(lower);
|
|
1214
|
+
const upperValue = paramValue(upper);
|
|
1215
|
+
assertBetweenBoundValueAllowed("lower", between.low, lowerValue);
|
|
1216
|
+
assertBetweenBoundValueAllowed("upper", between.high, upperValue);
|
|
1217
|
+
const value = { lower, upper };
|
|
1069
1218
|
return {
|
|
1070
1219
|
kind: "range",
|
|
1071
1220
|
field,
|
|
1072
1221
|
operator: "between",
|
|
1073
|
-
value
|
|
1222
|
+
value
|
|
1074
1223
|
};
|
|
1075
1224
|
}
|
|
1225
|
+
function assertBetweenBoundValueAllowed(position, expr, value) {
|
|
1226
|
+
if (value === null || typeof value === "number" || typeof value === "string" || typeof value === "bigint" || value instanceof Date) {
|
|
1227
|
+
return;
|
|
1228
|
+
}
|
|
1229
|
+
const paramSuffix = expr.type === "namedParam" ? `; param :${expr.name}` : "";
|
|
1230
|
+
throw new NqlSemanticException(
|
|
1231
|
+
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
1232
|
+
`BETWEEN ${position} bound must be a literal number, string, or date, or a bigint param; got type ${typeof value}${paramSuffix}.`
|
|
1233
|
+
);
|
|
1234
|
+
}
|
|
1076
1235
|
function compileNull(expr, ctx, aliasContext) {
|
|
1077
1236
|
const isNull = expr;
|
|
1078
1237
|
const field = expressionToField(isNull.expression, aliasContext);
|
|
@@ -1112,12 +1271,13 @@ function compileJson(expr, ctx, _fns, aliasContext, outerAliases) {
|
|
|
1112
1271
|
aliasContext,
|
|
1113
1272
|
outerAliases
|
|
1114
1273
|
);
|
|
1115
|
-
|
|
1274
|
+
const intent2 = {
|
|
1116
1275
|
kind: "jsonContains",
|
|
1117
1276
|
field: jsonField2,
|
|
1118
1277
|
value: jsonValue2,
|
|
1119
1278
|
reversed: fn === "json_contained_by"
|
|
1120
1279
|
};
|
|
1280
|
+
return intent2;
|
|
1121
1281
|
}
|
|
1122
1282
|
if (fn === "json_exists") {
|
|
1123
1283
|
if (expr.args.length < 2) {
|
|
@@ -1133,7 +1293,7 @@ function compileJson(expr, ctx, _fns, aliasContext, outerAliases) {
|
|
|
1133
1293
|
`${fn}() first argument must be a field reference`
|
|
1134
1294
|
);
|
|
1135
1295
|
}
|
|
1136
|
-
const key = coerceToStringKey(expr.args[1], `${fn}() key
|
|
1296
|
+
const key = coerceToStringKey(expr.args[1], `${fn}() key`, ctx);
|
|
1137
1297
|
return {
|
|
1138
1298
|
kind: "jsonExists",
|
|
1139
1299
|
field: jsonField2,
|
|
@@ -1154,7 +1314,7 @@ function compileJson(expr, ctx, _fns, aliasContext, outerAliases) {
|
|
|
1154
1314
|
);
|
|
1155
1315
|
}
|
|
1156
1316
|
if (jsonComp.operator === "?") {
|
|
1157
|
-
const key = coerceToStringKey(jsonComp.right, "? operator key");
|
|
1317
|
+
const key = coerceToStringKey(jsonComp.right, "? operator key", ctx);
|
|
1158
1318
|
return {
|
|
1159
1319
|
kind: "jsonExists",
|
|
1160
1320
|
field: jsonField,
|
|
@@ -1167,12 +1327,13 @@ function compileJson(expr, ctx, _fns, aliasContext, outerAliases) {
|
|
|
1167
1327
|
aliasContext,
|
|
1168
1328
|
outerAliases
|
|
1169
1329
|
);
|
|
1170
|
-
|
|
1330
|
+
const intent = {
|
|
1171
1331
|
kind: "jsonContains",
|
|
1172
1332
|
field: jsonField,
|
|
1173
1333
|
value: jsonValue,
|
|
1174
1334
|
reversed: jsonComp.operator === "<@"
|
|
1175
1335
|
};
|
|
1336
|
+
return intent;
|
|
1176
1337
|
}
|
|
1177
1338
|
function compileRelationFilter(expr, ctx, fns, aliasContext, outerAliases) {
|
|
1178
1339
|
const relFilter = expr;
|
|
@@ -1266,8 +1427,9 @@ function compileExpression(expr, ctx, fns, aliasContext, outerAliases) {
|
|
|
1266
1427
|
);
|
|
1267
1428
|
}
|
|
1268
1429
|
}
|
|
1269
|
-
|
|
1270
|
-
|
|
1430
|
+
function assignMutationValue(target, column, value, ctx) {
|
|
1431
|
+
target[column] = expressionToValue(value, ctx);
|
|
1432
|
+
}
|
|
1271
1433
|
function compileMutationPipeline(pipeline, ctx, fns, bindings) {
|
|
1272
1434
|
ctx.currentFromTable = pipeline.mutation.table;
|
|
1273
1435
|
const mutation = compileMutation(pipeline.mutation, ctx, fns, bindings);
|
|
@@ -1315,7 +1477,7 @@ function compileInsert(insert, ctx) {
|
|
|
1315
1477
|
const rowColumns = /* @__PURE__ */ new Set();
|
|
1316
1478
|
for (const assignment of row) {
|
|
1317
1479
|
rowColumns.add(assignment.column);
|
|
1318
|
-
rowValues
|
|
1480
|
+
assignMutationValue(rowValues, assignment.column, assignment.value, ctx);
|
|
1319
1481
|
}
|
|
1320
1482
|
for (const col of allColumns) {
|
|
1321
1483
|
if (!rowColumns.has(col)) {
|
|
@@ -1331,7 +1493,11 @@ function compileInsert(insert, ctx) {
|
|
|
1331
1493
|
};
|
|
1332
1494
|
}
|
|
1333
1495
|
function compileInsertFrom(insertFrom, ctx, fns, bindings) {
|
|
1496
|
+
ctx.validator?.validateTable(insertFrom.table);
|
|
1334
1497
|
const sourceQuery = bindings?.get(insertFrom.source);
|
|
1498
|
+
if (!sourceQuery) {
|
|
1499
|
+
ctx.validator?.validateTable(insertFrom.source);
|
|
1500
|
+
}
|
|
1335
1501
|
ctx.currentFromTable = insertFrom.source;
|
|
1336
1502
|
return {
|
|
1337
1503
|
type: "insert_from",
|
|
@@ -1343,7 +1509,9 @@ function compileInsertFrom(insertFrom, ctx, fns, bindings) {
|
|
|
1343
1509
|
...insertFrom.where !== void 0 && {
|
|
1344
1510
|
where: fns.compileExpression(insertFrom.where, ctx, fns)
|
|
1345
1511
|
},
|
|
1346
|
-
...insertFrom.limit !== void 0 && {
|
|
1512
|
+
...insertFrom.limit !== void 0 && {
|
|
1513
|
+
limit: resolveIntegerCount(insertFrom.limit, ctx, "insert-from limit")
|
|
1514
|
+
}
|
|
1347
1515
|
};
|
|
1348
1516
|
}
|
|
1349
1517
|
function compileUpdate(update, ctx, fns, bindings) {
|
|
@@ -1352,7 +1520,7 @@ function compileUpdate(update, ctx, fns, bindings) {
|
|
|
1352
1520
|
const set = {};
|
|
1353
1521
|
for (const assignment of update.assignments) {
|
|
1354
1522
|
ctx.validator?.validateColumn(update.table, assignment.column);
|
|
1355
|
-
set
|
|
1523
|
+
assignMutationValue(set, assignment.column, assignment.value, ctx);
|
|
1356
1524
|
}
|
|
1357
1525
|
if (update.where) {
|
|
1358
1526
|
return {
|
|
@@ -1365,6 +1533,11 @@ function compileUpdate(update, ctx, fns, bindings) {
|
|
|
1365
1533
|
)
|
|
1366
1534
|
};
|
|
1367
1535
|
}
|
|
1536
|
+
if (!ctx.allowUnfilteredMutations) {
|
|
1537
|
+
throw new Error(
|
|
1538
|
+
"update without a where clause would affect all rows; pass { allowUnfilteredMutations: true } to the compiler to allow an unfiltered update"
|
|
1539
|
+
);
|
|
1540
|
+
}
|
|
1368
1541
|
return {
|
|
1369
1542
|
type: "update",
|
|
1370
1543
|
table: update.table,
|
|
@@ -1385,6 +1558,11 @@ function compileDelete(del, ctx, fns, bindings) {
|
|
|
1385
1558
|
)
|
|
1386
1559
|
};
|
|
1387
1560
|
}
|
|
1561
|
+
if (!ctx.allowUnfilteredMutations) {
|
|
1562
|
+
throw new Error(
|
|
1563
|
+
"delete without a where clause would affect all rows; pass { allowUnfilteredMutations: true } to the compiler to allow an unfiltered delete"
|
|
1564
|
+
);
|
|
1565
|
+
}
|
|
1388
1566
|
return {
|
|
1389
1567
|
type: "delete",
|
|
1390
1568
|
table: del.table,
|
|
@@ -1396,11 +1574,16 @@ function compileUpsert(upsert, ctx) {
|
|
|
1396
1574
|
const values = {};
|
|
1397
1575
|
for (const assignment of upsert.assignments) {
|
|
1398
1576
|
ctx.validator?.validateColumn(upsert.table, assignment.column);
|
|
1399
|
-
values
|
|
1577
|
+
assignMutationValue(values, assignment.column, assignment.value, ctx);
|
|
1400
1578
|
}
|
|
1401
1579
|
for (const col of upsert.conflictColumns) {
|
|
1402
1580
|
ctx.validator?.validateColumn(upsert.table, col);
|
|
1403
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."
|
|
1585
|
+
);
|
|
1586
|
+
}
|
|
1404
1587
|
return {
|
|
1405
1588
|
type: "upsert",
|
|
1406
1589
|
table: upsert.table,
|
|
@@ -1410,7 +1593,14 @@ function compileUpsert(upsert, ctx) {
|
|
|
1410
1593
|
};
|
|
1411
1594
|
}
|
|
1412
1595
|
function compileUpsertFrom(upsertFrom, ctx, fns, bindings) {
|
|
1596
|
+
ctx.validator?.validateTable(upsertFrom.table);
|
|
1413
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
|
+
}
|
|
1414
1604
|
ctx.currentFromTable = upsertFrom.source;
|
|
1415
1605
|
return {
|
|
1416
1606
|
type: "upsert_from",
|
|
@@ -1426,7 +1616,9 @@ function compileUpsertFrom(upsertFrom, ctx, fns, bindings) {
|
|
|
1426
1616
|
...upsertFrom.where !== void 0 && {
|
|
1427
1617
|
where: fns.compileExpression(upsertFrom.where, ctx, fns)
|
|
1428
1618
|
},
|
|
1429
|
-
...upsertFrom.limit !== void 0 && {
|
|
1619
|
+
...upsertFrom.limit !== void 0 && {
|
|
1620
|
+
limit: resolveIntegerCount(upsertFrom.limit, ctx, "upsert-from limit")
|
|
1621
|
+
}
|
|
1430
1622
|
};
|
|
1431
1623
|
}
|
|
1432
1624
|
function extractReturningColumns(clause, ctx) {
|
|
@@ -1454,7 +1646,7 @@ function resolveBindingsInWhere(where, bindings) {
|
|
|
1454
1646
|
const inValues = inWhere.subquery ? void 0 : inWhere.values;
|
|
1455
1647
|
if (inValues && inValues.length === 1) {
|
|
1456
1648
|
const val = inValues[0];
|
|
1457
|
-
if (val && typeof val === "object" && "$ref" in val) {
|
|
1649
|
+
if (!isParamIntent(val) && val && typeof val === "object" && "$ref" in val) {
|
|
1458
1650
|
const ref = val.$ref;
|
|
1459
1651
|
if (bindings.has(ref)) {
|
|
1460
1652
|
const boundQuery = bindings.get(ref);
|
|
@@ -1507,6 +1699,77 @@ function extractBindName(stmt) {
|
|
|
1507
1699
|
}
|
|
1508
1700
|
return void 0;
|
|
1509
1701
|
}
|
|
1702
|
+
function paramExpressionIntent(param, alias) {
|
|
1703
|
+
const intent = { ...param };
|
|
1704
|
+
if (alias !== void 0) intent.as = alias;
|
|
1705
|
+
return intent;
|
|
1706
|
+
}
|
|
1707
|
+
function literalExpressionIntent(value, alias) {
|
|
1708
|
+
const intent = {
|
|
1709
|
+
kind: "literal",
|
|
1710
|
+
value
|
|
1711
|
+
};
|
|
1712
|
+
if (alias !== void 0) intent.as = alias;
|
|
1713
|
+
return intent;
|
|
1714
|
+
}
|
|
1715
|
+
function resolveLagLeadOffset(expr, ctx) {
|
|
1716
|
+
if (expr.type === "number") {
|
|
1717
|
+
const resolved = resolveIntegerCount(expr.value, ctx, "lag/lead offset");
|
|
1718
|
+
return isParamIntent(resolved) ? resolved.value : resolved;
|
|
1719
|
+
}
|
|
1720
|
+
if (expr.type === "namedParam") {
|
|
1721
|
+
const resolved = resolveIntegerCount(expr, ctx, "lag/lead offset");
|
|
1722
|
+
return isParamIntent(resolved) ? resolved.value : resolved;
|
|
1723
|
+
}
|
|
1724
|
+
throw new NqlSemanticException(
|
|
1725
|
+
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
1726
|
+
"lag/lead offset must resolve to a non-negative safe integer"
|
|
1727
|
+
);
|
|
1728
|
+
}
|
|
1729
|
+
function throwUnsupportedSelectFunction(fn) {
|
|
1730
|
+
throw new NqlSemanticException(
|
|
1731
|
+
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
1732
|
+
`Unsupported function in SELECT context: ${fn}()`
|
|
1733
|
+
);
|
|
1734
|
+
}
|
|
1735
|
+
var SELECT_SCALAR_FUNCTION_NAMES = /* @__PURE__ */ new Set([
|
|
1736
|
+
...NQL_SELECT_AGGREGATE_FUNCTIONS,
|
|
1737
|
+
...NQL_SELECT_JSON_FUNCTIONS,
|
|
1738
|
+
...NQL_SELECT_SCALAR_FUNCTIONS
|
|
1739
|
+
]);
|
|
1740
|
+
function validateSelectPathExpression(expr, ctx) {
|
|
1741
|
+
if (!ctx.currentFromTable || !ctx.validator) return;
|
|
1742
|
+
const { segments } = expr;
|
|
1743
|
+
if (segments.length === 1) {
|
|
1744
|
+
ctx.validator.validateColumn(ctx.currentFromTable, segments[0]);
|
|
1745
|
+
return;
|
|
1746
|
+
}
|
|
1747
|
+
const firstSegmentLower = segments[0].toLowerCase();
|
|
1748
|
+
if (ctx.pseudoColumnKeywords.has(firstSegmentLower)) {
|
|
1749
|
+
let i = 1;
|
|
1750
|
+
while (i < segments.length && ctx.pseudoColumnKeywords.has(segments[i].toLowerCase())) {
|
|
1751
|
+
i++;
|
|
1752
|
+
}
|
|
1753
|
+
if (i < segments.length) {
|
|
1754
|
+
ctx.validator.validateColumn(ctx.currentFromTable, segments[i]);
|
|
1755
|
+
}
|
|
1756
|
+
return;
|
|
1757
|
+
}
|
|
1758
|
+
const targetTable = ctx.validator.resolveRelationTarget(
|
|
1759
|
+
ctx.currentFromTable,
|
|
1760
|
+
segments[0]
|
|
1761
|
+
);
|
|
1762
|
+
if (targetTable) {
|
|
1763
|
+
ctx.validator.validateColumn(targetTable, segments[segments.length - 1]);
|
|
1764
|
+
}
|
|
1765
|
+
}
|
|
1766
|
+
function expressionToValidatedField(expr, ctx) {
|
|
1767
|
+
const field = expressionToField(expr);
|
|
1768
|
+
if (field && expr.type === "path") {
|
|
1769
|
+
validateSelectPathExpression(expr, ctx);
|
|
1770
|
+
}
|
|
1771
|
+
return field;
|
|
1772
|
+
}
|
|
1510
1773
|
function compileSelectClause(clause, ctx, fns) {
|
|
1511
1774
|
if (clause.items.length === 1 && clause.items[0]?.type === "star") {
|
|
1512
1775
|
return { type: "all" };
|
|
@@ -1530,12 +1793,7 @@ function compileSelectClause(clause, ctx, fns) {
|
|
|
1530
1793
|
} else if (item.type === "expression") {
|
|
1531
1794
|
const expr = item.expression;
|
|
1532
1795
|
if (expr.type === "path" && expr.segments.length === 1 && !item.alias) {
|
|
1533
|
-
|
|
1534
|
-
ctx.validator?.validateColumn(
|
|
1535
|
-
ctx.currentFromTable,
|
|
1536
|
-
expr.segments[0]
|
|
1537
|
-
);
|
|
1538
|
-
}
|
|
1796
|
+
validateSelectPathExpression(expr, ctx);
|
|
1539
1797
|
simpleFields.push(expr.segments[0]);
|
|
1540
1798
|
} else {
|
|
1541
1799
|
hasExpressions = true;
|
|
@@ -1548,6 +1806,40 @@ function compileSelectClause(clause, ctx, fns) {
|
|
|
1548
1806
|
}
|
|
1549
1807
|
return { type: "expressions", columns: expressions };
|
|
1550
1808
|
}
|
|
1809
|
+
function expressionToSelectValue(valueExpr, ctx, fns) {
|
|
1810
|
+
const field = expressionToValidatedField(valueExpr, ctx);
|
|
1811
|
+
if (field) return field;
|
|
1812
|
+
if (valueExpr.type === "namedParam") {
|
|
1813
|
+
return expressionToValue(valueExpr, ctx);
|
|
1814
|
+
}
|
|
1815
|
+
if (valueExpr.type === "string" || valueExpr.type === "number" || valueExpr.type === "boolean") {
|
|
1816
|
+
return valueExpr.value;
|
|
1817
|
+
}
|
|
1818
|
+
if (valueExpr.type === "null") {
|
|
1819
|
+
return null;
|
|
1820
|
+
}
|
|
1821
|
+
if (valueExpr.type === "rangeLiteral" || valueExpr.type === "dateRange") {
|
|
1822
|
+
return valueExpr.value;
|
|
1823
|
+
}
|
|
1824
|
+
return compileExpressionToIntent(valueExpr, ctx, fns);
|
|
1825
|
+
}
|
|
1826
|
+
function expressionToFunctionArg(valueExpr, ctx, fns) {
|
|
1827
|
+
const field = expressionToValidatedField(valueExpr, ctx);
|
|
1828
|
+
if (field) return field;
|
|
1829
|
+
if (valueExpr.type === "namedParam") {
|
|
1830
|
+
return expressionToValue(valueExpr, ctx);
|
|
1831
|
+
}
|
|
1832
|
+
if (valueExpr.type === "string" || valueExpr.type === "number" || valueExpr.type === "boolean") {
|
|
1833
|
+
return literalExpressionIntent(valueExpr.value);
|
|
1834
|
+
}
|
|
1835
|
+
if (valueExpr.type === "null") {
|
|
1836
|
+
return literalExpressionIntent(null);
|
|
1837
|
+
}
|
|
1838
|
+
if (valueExpr.type === "rangeLiteral" || valueExpr.type === "dateRange") {
|
|
1839
|
+
return literalExpressionIntent(valueExpr.value);
|
|
1840
|
+
}
|
|
1841
|
+
return compileExpressionToIntent(valueExpr, ctx, fns);
|
|
1842
|
+
}
|
|
1551
1843
|
function compileSelectExpression(item, ctx, fns) {
|
|
1552
1844
|
if (item.type === "star") {
|
|
1553
1845
|
return { kind: "column", column: "*" };
|
|
@@ -1567,6 +1859,7 @@ function compileSelectExpression(item, ctx, fns) {
|
|
|
1567
1859
|
const fn = expr.name.toLowerCase();
|
|
1568
1860
|
if (isAggregateFunction(fn)) {
|
|
1569
1861
|
let field;
|
|
1862
|
+
let firstArgAsExtraArg;
|
|
1570
1863
|
if (expr.args.length === 0) {
|
|
1571
1864
|
if (fn === "count") {
|
|
1572
1865
|
field = "*";
|
|
@@ -1576,56 +1869,72 @@ function compileSelectExpression(item, ctx, fns) {
|
|
|
1576
1869
|
);
|
|
1577
1870
|
}
|
|
1578
1871
|
} else {
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1872
|
+
const firstArg = expr.args[0];
|
|
1873
|
+
const firstField = expressionToValidatedField(firstArg, ctx);
|
|
1874
|
+
if (firstField) {
|
|
1875
|
+
field = firstField;
|
|
1876
|
+
} else if (firstArg.type === "namedParam") {
|
|
1877
|
+
firstArgAsExtraArg = [expressionToValue(firstArg, ctx)];
|
|
1878
|
+
} else {
|
|
1879
|
+
field = expressionToSql(firstArg);
|
|
1582
1880
|
}
|
|
1583
1881
|
}
|
|
1584
|
-
const extraArgs = expr.args.length > 1 ? expr.args.slice(1).map(
|
|
1585
|
-
|
|
1882
|
+
const extraArgs = expr.args.length > 1 ? expr.args.slice(1).map(
|
|
1883
|
+
(a) => expressionToValidatedField(a, ctx) ?? expressionToSelectValue(a, ctx, fns)
|
|
1884
|
+
) : void 0;
|
|
1885
|
+
const aggregateArgs = firstArgAsExtraArg ? [...firstArgAsExtraArg, ...extraArgs ?? []] : extraArgs;
|
|
1886
|
+
const aggregateIntent = {
|
|
1586
1887
|
kind: "aggregate",
|
|
1587
1888
|
function: fn,
|
|
1588
|
-
field,
|
|
1889
|
+
...field !== void 0 && { field },
|
|
1589
1890
|
...exprItem.alias !== void 0 && { as: exprItem.alias },
|
|
1590
1891
|
...expr.distinct && { distinct: true },
|
|
1591
|
-
...
|
|
1892
|
+
...aggregateArgs && { extraArgs: aggregateArgs }
|
|
1592
1893
|
};
|
|
1894
|
+
return aggregateIntent;
|
|
1593
1895
|
}
|
|
1594
|
-
const jsonIntent = compileJsonFunction(fn, expr.args, exprItem.alias);
|
|
1896
|
+
const jsonIntent = compileJsonFunction(fn, expr.args, exprItem.alias, ctx);
|
|
1595
1897
|
if (jsonIntent) return jsonIntent;
|
|
1898
|
+
if (!SELECT_SCALAR_FUNCTION_NAMES.has(fn)) {
|
|
1899
|
+
throwUnsupportedSelectFunction(fn);
|
|
1900
|
+
}
|
|
1596
1901
|
return {
|
|
1597
1902
|
kind: "function",
|
|
1598
|
-
name:
|
|
1599
|
-
args: expr.args.map((a) =>
|
|
1903
|
+
name: fn,
|
|
1904
|
+
args: expr.args.map((a) => expressionToFunctionArg(a, ctx, fns)),
|
|
1600
1905
|
...exprItem.alias !== void 0 && { as: exprItem.alias }
|
|
1601
1906
|
};
|
|
1602
1907
|
}
|
|
1603
1908
|
if (expr.type === "window") {
|
|
1604
1909
|
const windowExpr = expr;
|
|
1605
1910
|
const fn = windowExpr.function.toLowerCase();
|
|
1911
|
+
if (!isNqlSelectWindowFunctionAllowed(fn)) {
|
|
1912
|
+
throwUnsupportedSelectFunction(fn);
|
|
1913
|
+
}
|
|
1914
|
+
const windowFn = fn;
|
|
1606
1915
|
let field;
|
|
1607
1916
|
if (windowExpr.args.length > 0) {
|
|
1608
|
-
field =
|
|
1917
|
+
field = expressionToValidatedField(windowExpr.args[0], ctx) ?? expressionToSql(windowExpr.args[0]);
|
|
1609
1918
|
}
|
|
1610
1919
|
let offset;
|
|
1611
1920
|
let defaultValue;
|
|
1612
|
-
if (
|
|
1921
|
+
if (windowFn === "lag" || windowFn === "lead") {
|
|
1613
1922
|
if (windowExpr.args.length > 1) {
|
|
1614
|
-
offset =
|
|
1923
|
+
offset = resolveLagLeadOffset(windowExpr.args[1], ctx);
|
|
1615
1924
|
}
|
|
1616
1925
|
if (windowExpr.args.length > 2) {
|
|
1617
|
-
defaultValue = expressionToValue(windowExpr.args[2]);
|
|
1926
|
+
defaultValue = expressionToValue(windowExpr.args[2], ctx);
|
|
1618
1927
|
}
|
|
1619
1928
|
}
|
|
1620
1929
|
const partitionBy = windowExpr.partitionBy.length > 0 ? windowExpr.partitionBy.map((e) => {
|
|
1621
|
-
const f =
|
|
1930
|
+
const f = expressionToValidatedField(e, ctx) ?? expressionToSql(e);
|
|
1622
1931
|
if (ctx.currentFromTable && !f.includes(".") && !f.includes("(")) {
|
|
1623
1932
|
ctx.validator?.validateColumn(ctx.currentFromTable, f);
|
|
1624
1933
|
}
|
|
1625
1934
|
return f;
|
|
1626
1935
|
}) : void 0;
|
|
1627
1936
|
const orderBy = windowExpr.orderBy.length > 0 ? windowExpr.orderBy.map((o) => {
|
|
1628
|
-
const f =
|
|
1937
|
+
const f = expressionToValidatedField(o.expression, ctx) ?? expressionToSql(o.expression);
|
|
1629
1938
|
if (ctx.currentFromTable && !f.includes(".") && !f.includes("(")) {
|
|
1630
1939
|
ctx.validator?.validateColumn(ctx.currentFromTable, f);
|
|
1631
1940
|
}
|
|
@@ -1635,19 +1944,19 @@ function compileSelectExpression(item, ctx, fns) {
|
|
|
1635
1944
|
...partitionBy && { partitionBy },
|
|
1636
1945
|
...orderBy && { orderBy }
|
|
1637
1946
|
};
|
|
1638
|
-
const alias = exprItem.alias ??
|
|
1639
|
-
if (isRankingWindowFunction(
|
|
1947
|
+
const alias = exprItem.alias ?? windowFn;
|
|
1948
|
+
if (isRankingWindowFunction(windowFn)) {
|
|
1640
1949
|
return {
|
|
1641
1950
|
kind: "window",
|
|
1642
|
-
function:
|
|
1951
|
+
function: windowFn,
|
|
1643
1952
|
alias,
|
|
1644
1953
|
over
|
|
1645
1954
|
};
|
|
1646
1955
|
}
|
|
1647
|
-
if (
|
|
1956
|
+
if (windowFn === "lag" || windowFn === "lead") {
|
|
1648
1957
|
return {
|
|
1649
1958
|
kind: "window",
|
|
1650
|
-
function:
|
|
1959
|
+
function: windowFn,
|
|
1651
1960
|
field: field ?? "",
|
|
1652
1961
|
alias,
|
|
1653
1962
|
...offset !== void 0 && { offset },
|
|
@@ -1657,7 +1966,7 @@ function compileSelectExpression(item, ctx, fns) {
|
|
|
1657
1966
|
}
|
|
1658
1967
|
return {
|
|
1659
1968
|
kind: "window",
|
|
1660
|
-
function:
|
|
1969
|
+
function: windowFn,
|
|
1661
1970
|
...field !== void 0 && { field },
|
|
1662
1971
|
alias,
|
|
1663
1972
|
over
|
|
@@ -1666,15 +1975,13 @@ function compileSelectExpression(item, ctx, fns) {
|
|
|
1666
1975
|
if (expr.type === "subquery") {
|
|
1667
1976
|
return {
|
|
1668
1977
|
kind: "subquery",
|
|
1669
|
-
query:
|
|
1978
|
+
query: compileNestedQuery(expr.query, ctx, fns),
|
|
1670
1979
|
...exprItem.alias !== void 0 && { as: exprItem.alias }
|
|
1671
1980
|
};
|
|
1672
1981
|
}
|
|
1673
1982
|
if (expr.type === "path" && expr.segments.length === 1) {
|
|
1674
1983
|
const column = expr.segments[0];
|
|
1675
|
-
|
|
1676
|
-
ctx.validator?.validateColumn(ctx.currentFromTable, column);
|
|
1677
|
-
}
|
|
1984
|
+
validateSelectPathExpression(expr, ctx);
|
|
1678
1985
|
if (exprItem.alias) {
|
|
1679
1986
|
return { kind: "columnAlias", column, alias: exprItem.alias };
|
|
1680
1987
|
}
|
|
@@ -1683,26 +1990,32 @@ function compileSelectExpression(item, ctx, fns) {
|
|
|
1683
1990
|
if (expr.type === "path" && expr.segments.length > 1) {
|
|
1684
1991
|
return compileMultiSegmentPath(expr, exprItem, ctx);
|
|
1685
1992
|
}
|
|
1993
|
+
if (expr.type === "namedParam") {
|
|
1994
|
+
return paramExpressionIntent(
|
|
1995
|
+
expressionToValue(expr, ctx),
|
|
1996
|
+
exprItem.alias
|
|
1997
|
+
);
|
|
1998
|
+
}
|
|
1686
1999
|
if (expr.type === "binary" && ["+", "-", "*", "/", "%"].includes(expr.operator)) {
|
|
1687
|
-
const leftField =
|
|
1688
|
-
const rightField =
|
|
2000
|
+
const leftField = expressionToValidatedField(expr.left, ctx);
|
|
2001
|
+
const rightField = expressionToValidatedField(expr.right, ctx);
|
|
1689
2002
|
return {
|
|
1690
2003
|
kind: "arithmetic",
|
|
1691
|
-
left: leftField ??
|
|
2004
|
+
left: leftField ?? expressionToSelectValue(expr.left, ctx, fns),
|
|
1692
2005
|
operator: expr.operator,
|
|
1693
|
-
right: rightField ??
|
|
2006
|
+
right: rightField ?? expressionToSelectValue(expr.right, ctx, fns),
|
|
1694
2007
|
...exprItem.alias !== void 0 && { as: exprItem.alias }
|
|
1695
2008
|
};
|
|
1696
2009
|
}
|
|
1697
2010
|
if (expr.type === "unary") {
|
|
1698
2011
|
const unary = expr;
|
|
1699
2012
|
if (unary.operator === "-") {
|
|
1700
|
-
const operandField =
|
|
2013
|
+
const operandField = expressionToValidatedField(unary.operand, ctx);
|
|
1701
2014
|
return {
|
|
1702
2015
|
kind: "arithmetic",
|
|
1703
2016
|
left: -1,
|
|
1704
2017
|
operator: "*",
|
|
1705
|
-
right: operandField ??
|
|
2018
|
+
right: operandField ?? expressionToSelectValue(unary.operand, ctx, fns),
|
|
1706
2019
|
...exprItem.alias !== void 0 && { as: exprItem.alias }
|
|
1707
2020
|
};
|
|
1708
2021
|
}
|
|
@@ -1712,7 +2025,7 @@ function compileSelectExpression(item, ctx, fns) {
|
|
|
1712
2025
|
return compileCaseExpression(expr, exprItem, ctx, fns);
|
|
1713
2026
|
}
|
|
1714
2027
|
if (expr.type === "jsonAccess") {
|
|
1715
|
-
const baseField =
|
|
2028
|
+
const baseField = expressionToValidatedField(expr.base, ctx);
|
|
1716
2029
|
if (!baseField) {
|
|
1717
2030
|
throw new Error("JSON access base must be a field reference");
|
|
1718
2031
|
}
|
|
@@ -1724,6 +2037,15 @@ function compileSelectExpression(item, ctx, fns) {
|
|
|
1724
2037
|
...exprItem.alias !== void 0 && { as: exprItem.alias }
|
|
1725
2038
|
};
|
|
1726
2039
|
}
|
|
2040
|
+
if (expr.type === "string" || expr.type === "number" || expr.type === "boolean" || expr.type === "null" || expr.type === "rangeLiteral" || expr.type === "dateRange") {
|
|
2041
|
+
if (expr.type === "null") {
|
|
2042
|
+
return literalExpressionIntent(null, exprItem.alias);
|
|
2043
|
+
}
|
|
2044
|
+
if (expr.type === "rangeLiteral" || expr.type === "dateRange") {
|
|
2045
|
+
return literalExpressionIntent(expr.value, exprItem.alias);
|
|
2046
|
+
}
|
|
2047
|
+
return literalExpressionIntent(expr.value, exprItem.alias);
|
|
2048
|
+
}
|
|
1727
2049
|
throw new Error(
|
|
1728
2050
|
`Unsupported expression type in SELECT: ${expr.type}. This expression cannot be compiled to IntentAST. Consider extending the grammar or using a supported expression.`
|
|
1729
2051
|
);
|
|
@@ -1799,21 +2121,25 @@ function compileMultiSegmentPath(expr, item, ctx) {
|
|
|
1799
2121
|
}
|
|
1800
2122
|
function compileCaseExpression(caseExpr, item, ctx, fns) {
|
|
1801
2123
|
if (caseExpr.subject) {
|
|
1802
|
-
const subjectField =
|
|
2124
|
+
const subjectField = expressionToValidatedField(caseExpr.subject, ctx);
|
|
1803
2125
|
if (!subjectField) {
|
|
1804
2126
|
throw new Error("Simple CASE subject must be a column reference");
|
|
1805
2127
|
}
|
|
2128
|
+
const when = caseExpr.whenClauses.map((wc) => {
|
|
2129
|
+
const condition = {
|
|
2130
|
+
kind: "comparison",
|
|
2131
|
+
field: subjectField,
|
|
2132
|
+
operator: "eq",
|
|
2133
|
+
value: expressionToValue(wc.condition, ctx)
|
|
2134
|
+
};
|
|
2135
|
+
return {
|
|
2136
|
+
condition,
|
|
2137
|
+
result: compileExpressionToIntent(wc.result, ctx, fns)
|
|
2138
|
+
};
|
|
2139
|
+
});
|
|
1806
2140
|
return {
|
|
1807
2141
|
kind: "case",
|
|
1808
|
-
when
|
|
1809
|
-
condition: {
|
|
1810
|
-
kind: "comparison",
|
|
1811
|
-
field: subjectField,
|
|
1812
|
-
operator: "eq",
|
|
1813
|
-
value: expressionToValue(wc.condition)
|
|
1814
|
-
},
|
|
1815
|
-
result: compileExpressionToIntent(wc.result, ctx, fns)
|
|
1816
|
-
})),
|
|
2142
|
+
when,
|
|
1817
2143
|
...caseExpr.elseClause && {
|
|
1818
2144
|
else: compileExpressionToIntent(caseExpr.elseClause, ctx, fns)
|
|
1819
2145
|
},
|
|
@@ -1840,21 +2166,28 @@ function compileExpressionToIntent(expr, ctx, fns) {
|
|
|
1840
2166
|
`CASE WHEN condition left side must be a column path, got ${cmp.left.type}`
|
|
1841
2167
|
);
|
|
1842
2168
|
}
|
|
2169
|
+
validateSelectPathExpression(cmp.left, ctx);
|
|
2170
|
+
if (cmp.right.type === "path") {
|
|
2171
|
+
validateSelectPathExpression(cmp.right, ctx);
|
|
2172
|
+
}
|
|
1843
2173
|
const column = cmp.left.segments.join(".");
|
|
1844
|
-
const value = expressionToValue(cmp.right);
|
|
1845
|
-
|
|
2174
|
+
const value = expressionToValue(cmp.right, ctx);
|
|
2175
|
+
const intent = {
|
|
1846
2176
|
kind: "comparison",
|
|
1847
2177
|
column,
|
|
1848
2178
|
operator: cmp.operator,
|
|
1849
2179
|
value
|
|
1850
2180
|
};
|
|
2181
|
+
return intent;
|
|
1851
2182
|
}
|
|
1852
|
-
if (expr.type === "string" || expr.type === "number" || expr.type === "boolean" || expr.type === "null") {
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
2183
|
+
if (expr.type === "string" || expr.type === "number" || expr.type === "boolean" || expr.type === "null" || expr.type === "namedParam") {
|
|
2184
|
+
if (expr.type === "namedParam") {
|
|
2185
|
+
return expressionToValue(expr, ctx);
|
|
2186
|
+
}
|
|
2187
|
+
if (expr.type === "null") {
|
|
2188
|
+
return literalExpressionIntent(null);
|
|
2189
|
+
}
|
|
2190
|
+
return literalExpressionIntent(expr.value);
|
|
1858
2191
|
}
|
|
1859
2192
|
const selectItem = {
|
|
1860
2193
|
type: "expression",
|
|
@@ -1862,13 +2195,20 @@ function compileExpressionToIntent(expr, ctx, fns) {
|
|
|
1862
2195
|
};
|
|
1863
2196
|
return compileSelectExpression(selectItem, ctx, fns);
|
|
1864
2197
|
}
|
|
1865
|
-
var JSON_FUNCTION_NAMES =
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
]
|
|
1871
|
-
|
|
2198
|
+
var JSON_FUNCTION_NAMES = new Set(
|
|
2199
|
+
NQL_SELECT_JSON_FUNCTIONS
|
|
2200
|
+
);
|
|
2201
|
+
function compileJsonPathArgs(fn, args, ctx) {
|
|
2202
|
+
const keys = args.slice(1).map((a) => coerceToStringKey(a, `${fn}() path argument`, ctx));
|
|
2203
|
+
const firstArg = args[1];
|
|
2204
|
+
const first = keys[0];
|
|
2205
|
+
if (args.length === 2 && firstArg?.type === "string" && typeof first === "string" && first?.startsWith("{") && first.endsWith("}")) {
|
|
2206
|
+
const inner = first.slice(1, -1);
|
|
2207
|
+
return inner.length === 0 ? [] : inner.split(",");
|
|
2208
|
+
}
|
|
2209
|
+
return keys;
|
|
2210
|
+
}
|
|
2211
|
+
function compileJsonFunction(fn, args, alias, ctx) {
|
|
1872
2212
|
if (!JSON_FUNCTION_NAMES.has(fn)) return null;
|
|
1873
2213
|
if (args.length < 2) {
|
|
1874
2214
|
throw new NqlSemanticException(
|
|
@@ -1884,7 +2224,7 @@ function compileJsonFunction(fn, args, alias) {
|
|
|
1884
2224
|
);
|
|
1885
2225
|
}
|
|
1886
2226
|
if (fn === "json_extract" || fn === "json_extract_text") {
|
|
1887
|
-
const keys = args.slice(1).map((a) => coerceToStringKey(a, `${fn}() path argument
|
|
2227
|
+
const keys = args.slice(1).map((a) => coerceToStringKey(a, `${fn}() path argument`, ctx));
|
|
1888
2228
|
return {
|
|
1889
2229
|
kind: "jsonExtract",
|
|
1890
2230
|
field,
|
|
@@ -1894,13 +2234,11 @@ function compileJsonFunction(fn, args, alias) {
|
|
|
1894
2234
|
};
|
|
1895
2235
|
}
|
|
1896
2236
|
if (fn === "json_path" || fn === "json_path_text") {
|
|
1897
|
-
const
|
|
1898
|
-
const first = keys[0];
|
|
1899
|
-
const pathStr = keys.length === 1 && first?.startsWith("{") && first.endsWith("}") ? first : `{${keys.join(",")}}`;
|
|
2237
|
+
const path = compileJsonPathArgs(fn, args, ctx);
|
|
1900
2238
|
return {
|
|
1901
2239
|
kind: "jsonPathExtract",
|
|
1902
2240
|
field,
|
|
1903
|
-
path
|
|
2241
|
+
path,
|
|
1904
2242
|
mode: fn === "json_path" ? "json" : "text",
|
|
1905
2243
|
...alias !== void 0 && { as: alias }
|
|
1906
2244
|
};
|
|
@@ -1921,6 +2259,10 @@ var DEFAULT_RECURSIVE_KEYWORDS = [
|
|
|
1921
2259
|
];
|
|
1922
2260
|
|
|
1923
2261
|
// src/compiler/index.ts
|
|
2262
|
+
function allowsInternalParams(options) {
|
|
2263
|
+
const internalOptions = options?.[NQL_INTERNAL_COMPILER_OPTIONS];
|
|
2264
|
+
return internalOptions?.allowInternalParams === true;
|
|
2265
|
+
}
|
|
1924
2266
|
var NqlCompiler = class {
|
|
1925
2267
|
ctx;
|
|
1926
2268
|
fns;
|
|
@@ -1938,14 +2280,19 @@ var NqlCompiler = class {
|
|
|
1938
2280
|
);
|
|
1939
2281
|
}
|
|
1940
2282
|
}
|
|
2283
|
+
const params = options?.params ?? {};
|
|
2284
|
+
const allowInternalParams = allowsInternalParams(options);
|
|
2285
|
+
validateParamsMap(params, { allowInternalParams });
|
|
1941
2286
|
this.ctx = {
|
|
1942
2287
|
currentFromTable: void 0,
|
|
1943
2288
|
currentRelationTarget: void 0,
|
|
1944
2289
|
pseudoColumnKeywords,
|
|
1945
2290
|
recursiveKeywords,
|
|
1946
2291
|
validator,
|
|
1947
|
-
params
|
|
1948
|
-
maxAnyItems: maxAnyItemsRaw ?? MAX_ANY_ITEMS
|
|
2292
|
+
params,
|
|
2293
|
+
maxAnyItems: maxAnyItemsRaw ?? MAX_ANY_ITEMS,
|
|
2294
|
+
allowUnfilteredMutations: options?.allowUnfilteredMutations ?? false,
|
|
2295
|
+
allowInternalParams
|
|
1949
2296
|
};
|
|
1950
2297
|
this.fns = {
|
|
1951
2298
|
compileQuery: (query, ctx) => compileQuery(query, ctx, this.fns),
|
|
@@ -1963,15 +2310,27 @@ var NqlCompiler = class {
|
|
|
1963
2310
|
if (program.statements.length === 1) {
|
|
1964
2311
|
return this.compileSingleStatement(program.statements[0]);
|
|
1965
2312
|
}
|
|
2313
|
+
for (let i = 0; i < program.statements.length - 1; i++) {
|
|
2314
|
+
const stmt = program.statements[i];
|
|
2315
|
+
const bindName = extractBindName(stmt);
|
|
2316
|
+
if (!bindName) {
|
|
2317
|
+
throw new Error(
|
|
2318
|
+
`Multiple statements require explicit binding: statement ${i + 1} of ${program.statements.length} has no '| bind <name>' clause. Add a \`| bind <name>\` clause to each statement except the last, or pass a single statement.`
|
|
2319
|
+
);
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
1966
2322
|
const bindings = /* @__PURE__ */ new Map();
|
|
1967
2323
|
const mutationBindings = /* @__PURE__ */ new Map();
|
|
2324
|
+
const materializedBindStatements = /* @__PURE__ */ new Set();
|
|
1968
2325
|
let lastResult = {};
|
|
1969
|
-
for (
|
|
2326
|
+
for (let i = 0; i < program.statements.length; i++) {
|
|
2327
|
+
const stmt = program.statements[i];
|
|
1970
2328
|
lastResult = this.compileSingleStatement(stmt, bindings);
|
|
1971
2329
|
const bindName = extractBindName(stmt);
|
|
1972
2330
|
if (bindName) {
|
|
1973
2331
|
if (lastResult.query) {
|
|
1974
2332
|
bindings.set(bindName, lastResult.query);
|
|
2333
|
+
materializedBindStatements.add(i);
|
|
1975
2334
|
} else if (lastResult.mutation?.returning?.length) {
|
|
1976
2335
|
mutationBindings.set(bindName, lastResult.mutation);
|
|
1977
2336
|
bindings.set(bindName, {
|
|
@@ -1982,9 +2341,18 @@ var NqlCompiler = class {
|
|
|
1982
2341
|
fields: [...lastResult.mutation.returning]
|
|
1983
2342
|
}
|
|
1984
2343
|
});
|
|
2344
|
+
materializedBindStatements.add(i);
|
|
1985
2345
|
}
|
|
1986
2346
|
}
|
|
1987
2347
|
}
|
|
2348
|
+
for (let i = 0; i < program.statements.length - 1; i++) {
|
|
2349
|
+
const bindName = extractBindName(program.statements[i]);
|
|
2350
|
+
if (bindName && !materializedBindStatements.has(i)) {
|
|
2351
|
+
throw new Error(
|
|
2352
|
+
`statement ${i + 1} of ${program.statements.length} binds '${bindName}' but produces no referenceable result \u2014 a mutation used as a binding must include a \`returning\` clause.`
|
|
2353
|
+
);
|
|
2354
|
+
}
|
|
2355
|
+
}
|
|
1988
2356
|
const hasMutationBindings = mutationBindings.size > 0;
|
|
1989
2357
|
if (bindings.size > 0) {
|
|
1990
2358
|
return {
|
|
@@ -2367,6 +2735,11 @@ var allTokens = [
|
|
|
2367
2735
|
// ? (no conflict with other tokens)
|
|
2368
2736
|
];
|
|
2369
2737
|
var NqlLexer = new Lexer(allTokens);
|
|
2738
|
+
|
|
2739
|
+
// src/lexer/index.ts
|
|
2740
|
+
function tokenize(input) {
|
|
2741
|
+
return NqlLexer.tokenize(input);
|
|
2742
|
+
}
|
|
2370
2743
|
var NqlParser = class extends CstParser {
|
|
2371
2744
|
constructor() {
|
|
2372
2745
|
super(allTokens, {
|
|
@@ -2514,7 +2887,7 @@ var NqlParser = class extends CstParser {
|
|
|
2514
2887
|
this.SUBRULE(this.orderList);
|
|
2515
2888
|
});
|
|
2516
2889
|
/**
|
|
2517
|
-
* limit_clause = "limit" [ident_segment ("." ident_segment)*] NUMBER ;
|
|
2890
|
+
* limit_clause = "limit" [ident_segment ("." ident_segment)*] (NUMBER | NAMED_PARAM) ;
|
|
2518
2891
|
*/
|
|
2519
2892
|
limitClause = this.RULE("limitClause", () => {
|
|
2520
2893
|
this.CONSUME(Limit);
|
|
@@ -2525,14 +2898,29 @@ var NqlParser = class extends CstParser {
|
|
|
2525
2898
|
this.SUBRULE2(this.identSegment);
|
|
2526
2899
|
});
|
|
2527
2900
|
});
|
|
2528
|
-
this.
|
|
2901
|
+
this.SUBRULE(this.numericValueAtom);
|
|
2529
2902
|
});
|
|
2530
2903
|
/**
|
|
2531
|
-
* offset_clause = "offset" NUMBER ;
|
|
2904
|
+
* offset_clause = "offset" (NUMBER | NAMED_PARAM) ;
|
|
2532
2905
|
*/
|
|
2533
2906
|
offsetClause = this.RULE("offsetClause", () => {
|
|
2534
2907
|
this.CONSUME(Offset);
|
|
2535
|
-
this.
|
|
2908
|
+
this.SUBRULE(this.numericValueAtom);
|
|
2909
|
+
});
|
|
2910
|
+
/**
|
|
2911
|
+
* numeric_value_atom = NUMBER | NAMED_PARAM ;
|
|
2912
|
+
*
|
|
2913
|
+
* Structural-position durability rule: value positions that consume scalar
|
|
2914
|
+
* values may add `NamedParam` here and validate the resolved value in the
|
|
2915
|
+
* compiler. Structural positions such as identifiers, directions, lock modes,
|
|
2916
|
+
* and traversal depth hints must not consume this helper; use nqlRaw()/builder
|
|
2917
|
+
* APIs for trusted dynamic structure.
|
|
2918
|
+
*/
|
|
2919
|
+
numericValueAtom = this.RULE("numericValueAtom", () => {
|
|
2920
|
+
this.OR([
|
|
2921
|
+
{ ALT: () => this.CONSUME(NumberLiteral) },
|
|
2922
|
+
{ ALT: () => this.CONSUME(NamedParam) }
|
|
2923
|
+
]);
|
|
2536
2924
|
});
|
|
2537
2925
|
/**
|
|
2538
2926
|
* lock_clause = lock_strength [ lock_wait_policy ] ;
|
|
@@ -2796,7 +3184,7 @@ var NqlParser = class extends CstParser {
|
|
|
2796
3184
|
]);
|
|
2797
3185
|
});
|
|
2798
3186
|
/**
|
|
2799
|
-
* range_op_suffix = range_op (range_literal | literal) ;
|
|
3187
|
+
* range_op_suffix = range_op (range_literal | literal | named_param_expr) ;
|
|
2800
3188
|
* Example: overlaps [1,10) or contains (0,100) or contains 25
|
|
2801
3189
|
* Note: contains can take a scalar value (e.g., contains 25 checks if range contains the value)
|
|
2802
3190
|
*/
|
|
@@ -2804,7 +3192,8 @@ var NqlParser = class extends CstParser {
|
|
|
2804
3192
|
this.SUBRULE(this.rangeOp);
|
|
2805
3193
|
this.OR([
|
|
2806
3194
|
{ ALT: () => this.SUBRULE(this.rangeLiteral) },
|
|
2807
|
-
{ ALT: () => this.SUBRULE2(this.literal) }
|
|
3195
|
+
{ ALT: () => this.SUBRULE2(this.literal) },
|
|
3196
|
+
{ ALT: () => this.SUBRULE(this.namedParamExpr) }
|
|
2808
3197
|
]);
|
|
2809
3198
|
});
|
|
2810
3199
|
/**
|
|
@@ -3045,10 +3434,12 @@ var NqlParser = class extends CstParser {
|
|
|
3045
3434
|
});
|
|
3046
3435
|
});
|
|
3047
3436
|
/**
|
|
3048
|
-
* primary_expr = literal | case_expr | path_expr | func_call | "(" expr ")" | "(" scalar_subquery ")" ;
|
|
3437
|
+
* primary_expr = named_param_expr | literal | case_expr | path_expr | func_call | "(" expr ")" | "(" scalar_subquery ")" ;
|
|
3049
3438
|
*/
|
|
3050
3439
|
primaryExpr = this.RULE("primaryExpr", () => {
|
|
3051
3440
|
this.OR([
|
|
3441
|
+
// Bound value parameter: :paramName
|
|
3442
|
+
{ ALT: () => this.SUBRULE(this.namedParamExpr) },
|
|
3052
3443
|
// Range literal in value context (unambiguous: starts with '[')
|
|
3053
3444
|
{
|
|
3054
3445
|
GATE: () => this.LA(1).tokenType === LBracket,
|
|
@@ -3086,6 +3477,12 @@ var NqlParser = class extends CstParser {
|
|
|
3086
3477
|
}
|
|
3087
3478
|
]);
|
|
3088
3479
|
});
|
|
3480
|
+
/**
|
|
3481
|
+
* named_param_expr = NAMED_PARAM ;
|
|
3482
|
+
*/
|
|
3483
|
+
namedParamExpr = this.RULE("namedParamExpr", () => {
|
|
3484
|
+
this.CONSUME(NamedParam);
|
|
3485
|
+
});
|
|
3089
3486
|
/**
|
|
3090
3487
|
* Check if at start of scalar subquery (ident | ...)
|
|
3091
3488
|
*/
|
|
@@ -3442,7 +3839,7 @@ var NqlParser = class extends CstParser {
|
|
|
3442
3839
|
]);
|
|
3443
3840
|
});
|
|
3444
3841
|
/**
|
|
3445
|
-
* insert_from_stmt = "insert" "into" ident_segment "from" ident_segment [ "where" boolean_expr ] [ "limit"
|
|
3842
|
+
* insert_from_stmt = "insert" "into" ident_segment "from" ident_segment [ "where" boolean_expr ] [ "limit" numeric_value_atom ] ;
|
|
3446
3843
|
* @example insert into archived_users from users where active = false limit 100
|
|
3447
3844
|
*/
|
|
3448
3845
|
insertFromStmt = this.RULE("insertFromStmt", () => {
|
|
@@ -3457,7 +3854,7 @@ var NqlParser = class extends CstParser {
|
|
|
3457
3854
|
});
|
|
3458
3855
|
this.OPTION2(() => {
|
|
3459
3856
|
this.CONSUME(Limit);
|
|
3460
|
-
this.
|
|
3857
|
+
this.SUBRULE(this.numericValueAtom);
|
|
3461
3858
|
});
|
|
3462
3859
|
});
|
|
3463
3860
|
/**
|
|
@@ -3517,7 +3914,7 @@ var NqlParser = class extends CstParser {
|
|
|
3517
3914
|
});
|
|
3518
3915
|
});
|
|
3519
3916
|
/**
|
|
3520
|
-
* upsert_from_stmt = "upsert" "into" ident_segment "on" ( "(" ident_list ")" | ident_segment ) "from" ident_segment [ "where" boolean_expr ] [ "limit"
|
|
3917
|
+
* upsert_from_stmt = "upsert" "into" ident_segment "on" ( "(" ident_list ")" | ident_segment ) "from" ident_segment [ "where" boolean_expr ] [ "limit" numeric_value_atom ] ;
|
|
3521
3918
|
* @example upsert into authors on id from counts
|
|
3522
3919
|
* @example upsert into authors on (id, email) from counts where active = true
|
|
3523
3920
|
*/
|
|
@@ -3548,7 +3945,7 @@ var NqlParser = class extends CstParser {
|
|
|
3548
3945
|
});
|
|
3549
3946
|
this.OPTION2(() => {
|
|
3550
3947
|
this.CONSUME(Limit);
|
|
3551
|
-
this.
|
|
3948
|
+
this.SUBRULE(this.numericValueAtom);
|
|
3552
3949
|
});
|
|
3553
3950
|
});
|
|
3554
3951
|
/**
|
|
@@ -3843,6 +4240,12 @@ function buildRangeOp(left, suffixNode, visit) {
|
|
|
3843
4240
|
const scalar = visit(asCstNode(suffixCtx.literal[0]));
|
|
3844
4241
|
return { type: "rangeOp", operator, left, scalar };
|
|
3845
4242
|
}
|
|
4243
|
+
if (suffixCtx.namedParamExpr) {
|
|
4244
|
+
const scalar = visit(
|
|
4245
|
+
asCstNode(suffixCtx.namedParamExpr[0])
|
|
4246
|
+
);
|
|
4247
|
+
return { type: "rangeOp", operator, left, scalar };
|
|
4248
|
+
}
|
|
3846
4249
|
throw new NqlSemanticException(
|
|
3847
4250
|
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
3848
4251
|
"Range op suffix missing range literal or scalar value"
|
|
@@ -4018,6 +4421,7 @@ function visitUnaryExpr(ctx, visit) {
|
|
|
4018
4421
|
return expr;
|
|
4019
4422
|
}
|
|
4020
4423
|
function visitPrimaryExpr(ctx, visit) {
|
|
4424
|
+
if (ctx.namedParamExpr) return visit(asCstNode(ctx.namedParamExpr[0]));
|
|
4021
4425
|
if (ctx.rangeLiteral) return visit(asCstNode(ctx.rangeLiteral[0]));
|
|
4022
4426
|
if (ctx.literal) return visit(asCstNode(ctx.literal[0]));
|
|
4023
4427
|
if (ctx.caseExpr) return visit(asCstNode(ctx.caseExpr[0]));
|
|
@@ -4032,6 +4436,14 @@ function visitPrimaryExpr(ctx, visit) {
|
|
|
4032
4436
|
"Invalid primary expression"
|
|
4033
4437
|
);
|
|
4034
4438
|
}
|
|
4439
|
+
function visitNamedParamExpr(ctx) {
|
|
4440
|
+
const token = ctx.NamedParam?.[0];
|
|
4441
|
+
const raw = token ? getImage(token) : ":unknown";
|
|
4442
|
+
return {
|
|
4443
|
+
type: "namedParam",
|
|
4444
|
+
name: raw.slice(1)
|
|
4445
|
+
};
|
|
4446
|
+
}
|
|
4035
4447
|
function visitCaseExpr(ctx, visit) {
|
|
4036
4448
|
const whenClauses = [];
|
|
4037
4449
|
let subject;
|
|
@@ -4415,7 +4827,7 @@ function visitInsertFromStmt(ctx, visit) {
|
|
|
4415
4827
|
table: target,
|
|
4416
4828
|
source,
|
|
4417
4829
|
where: ctx.booleanExpr ? visit(asCstNode(ctx.booleanExpr[0])) : void 0,
|
|
4418
|
-
limit: ctx.
|
|
4830
|
+
limit: ctx.numericValueAtom ? visit(asCstNode(ctx.numericValueAtom[0])) : void 0
|
|
4419
4831
|
};
|
|
4420
4832
|
}
|
|
4421
4833
|
function visitUpdateStmt(ctx, visit) {
|
|
@@ -4490,7 +4902,7 @@ function visitUpsertFromStmt(ctx, visit) {
|
|
|
4490
4902
|
conflictColumns,
|
|
4491
4903
|
source,
|
|
4492
4904
|
where: ctx.booleanExpr ? visit(asCstNode(ctx.booleanExpr[0])) : void 0,
|
|
4493
|
-
limit: ctx.
|
|
4905
|
+
limit: ctx.numericValueAtom ? visit(asCstNode(ctx.numericValueAtom[0])) : void 0
|
|
4494
4906
|
};
|
|
4495
4907
|
}
|
|
4496
4908
|
function visitAssignmentList(ctx, visit) {
|
|
@@ -4598,8 +5010,8 @@ function visitOrderClause(ctx, visit) {
|
|
|
4598
5010
|
return { type: "orderBy", items };
|
|
4599
5011
|
}
|
|
4600
5012
|
function visitLimitClause(ctx, visit) {
|
|
4601
|
-
requireFirst(ctx, "
|
|
4602
|
-
const count =
|
|
5013
|
+
requireFirst(ctx, "numericValueAtom", "Limit clause missing number");
|
|
5014
|
+
const count = visit(asCstNode(ctx.numericValueAtom[0]));
|
|
4603
5015
|
const segments = ctx.identSegment;
|
|
4604
5016
|
if (segments && segments.length > 0) {
|
|
4605
5017
|
const parts = [];
|
|
@@ -4610,13 +5022,23 @@ function visitLimitClause(ctx, visit) {
|
|
|
4610
5022
|
}
|
|
4611
5023
|
return { type: "limit", count };
|
|
4612
5024
|
}
|
|
4613
|
-
function visitOffsetClause(ctx) {
|
|
4614
|
-
requireFirst(ctx, "
|
|
5025
|
+
function visitOffsetClause(ctx, visit) {
|
|
5026
|
+
requireFirst(ctx, "numericValueAtom", "Offset clause missing number");
|
|
4615
5027
|
return {
|
|
4616
5028
|
type: "offset",
|
|
4617
|
-
count:
|
|
5029
|
+
count: visit(asCstNode(ctx.numericValueAtom[0]))
|
|
4618
5030
|
};
|
|
4619
5031
|
}
|
|
5032
|
+
function visitNumericValueAtom(ctx) {
|
|
5033
|
+
if (ctx.NumberLiteral) {
|
|
5034
|
+
return parseInt(getImage(ctx.NumberLiteral[0]), 10);
|
|
5035
|
+
}
|
|
5036
|
+
if (ctx.NamedParam) {
|
|
5037
|
+
const raw = getImage(ctx.NamedParam[0]);
|
|
5038
|
+
return { type: "namedParam", name: raw.slice(1) };
|
|
5039
|
+
}
|
|
5040
|
+
unreachable("Invalid numeric value");
|
|
5041
|
+
}
|
|
4620
5042
|
function visitSelectList(ctx, visit) {
|
|
4621
5043
|
const items = [];
|
|
4622
5044
|
if (ctx.selectItem) {
|
|
@@ -4743,7 +5165,10 @@ var NqlCstVisitor = class extends BaseCstVisitor {
|
|
|
4743
5165
|
return visitLimitClause(ctx, this.v);
|
|
4744
5166
|
}
|
|
4745
5167
|
offsetClause(ctx) {
|
|
4746
|
-
return visitOffsetClause(ctx);
|
|
5168
|
+
return visitOffsetClause(ctx, this.v);
|
|
5169
|
+
}
|
|
5170
|
+
numericValueAtom(ctx) {
|
|
5171
|
+
return visitNumericValueAtom(ctx);
|
|
4747
5172
|
}
|
|
4748
5173
|
selectList(ctx) {
|
|
4749
5174
|
return visitSelectList(ctx, this.v);
|
|
@@ -4826,6 +5251,9 @@ var NqlCstVisitor = class extends BaseCstVisitor {
|
|
|
4826
5251
|
primaryExpr(ctx) {
|
|
4827
5252
|
return visitPrimaryExpr(ctx, this.v);
|
|
4828
5253
|
}
|
|
5254
|
+
namedParamExpr(ctx) {
|
|
5255
|
+
return visitNamedParamExpr(ctx);
|
|
5256
|
+
}
|
|
4829
5257
|
// -- CASE --
|
|
4830
5258
|
caseExpr(ctx) {
|
|
4831
5259
|
return visitCaseExpr(ctx, this.v);
|
|
@@ -5014,13 +5442,16 @@ function compile(input, schema, options, compilerOptions) {
|
|
|
5014
5442
|
warnings: parseResult.warnings
|
|
5015
5443
|
};
|
|
5016
5444
|
} catch (err) {
|
|
5017
|
-
const
|
|
5445
|
+
const semanticError = err instanceof NqlSemanticException;
|
|
5446
|
+
const code = semanticError ? err.code : NqlErrorCodes.SEM_UNREACHABLE;
|
|
5018
5447
|
return {
|
|
5019
5448
|
success: false,
|
|
5020
5449
|
errors: [
|
|
5021
5450
|
{
|
|
5022
5451
|
code,
|
|
5023
|
-
message: err instanceof Error ? err.message : String(err)
|
|
5452
|
+
message: err instanceof Error ? err.message : String(err),
|
|
5453
|
+
location: semanticError ? err.location : void 0,
|
|
5454
|
+
suggestion: semanticError ? err.suggestion : void 0
|
|
5024
5455
|
}
|
|
5025
5456
|
],
|
|
5026
5457
|
warnings: []
|
|
@@ -5076,8 +5507,9 @@ function hasColumnValidatorShape(obj) {
|
|
|
5076
5507
|
/* v8 ignore start — defensive: parser guarantees target and source identifiers -- @preserve */
|
|
5077
5508
|
/* v8 ignore next — defensive: parser guarantees withQuery, query, or mutationPipeline -- @preserve */
|
|
5078
5509
|
/* v8 ignore next — defensive: parser guarantees one of the clause alternatives -- @preserve */
|
|
5510
|
+
/* v8 ignore next — defensive: parser guarantees NumberLiteral or NamedParam -- @preserve */
|
|
5079
5511
|
/* v8 ignore next — defensive: parser guarantees query or identSegment -- @preserve */
|
|
5080
5512
|
|
|
5081
|
-
export { NqlCompiler, NqlCstVisitor, NqlLexer, NqlParser, allTokens, compile, createCompiler, cstToAst, nqlVisitor, parse, parseCst };
|
|
5513
|
+
export { NqlCompiler, NqlCstVisitor, NqlLexer, NqlParser, allTokens, compile, createCompiler, cstToAst, nqlVisitor, parse, parseCst, tokenize };
|
|
5082
5514
|
//# sourceMappingURL=index.js.map
|
|
5083
5515
|
//# sourceMappingURL=index.js.map
|