@dbsp/nql 1.1.0 → 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 +48 -33
- package/dist/index.js +511 -152
- 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`
|
|
@@ -474,15 +576,25 @@ function compileQuery(query, ctx, fns, bindings) {
|
|
|
474
576
|
break;
|
|
475
577
|
case "limit": {
|
|
476
578
|
const lc = clause;
|
|
579
|
+
const count = resolveIntegerCount(
|
|
580
|
+
lc.count,
|
|
581
|
+
ctx,
|
|
582
|
+
lc.relation ? "per-include limit" : "limit"
|
|
583
|
+
);
|
|
477
584
|
if (lc.relation) {
|
|
478
|
-
|
|
585
|
+
const includeLimit = isParamIntent(count) ? count.value : count;
|
|
586
|
+
includeLimits.set(lc.relation, includeLimit);
|
|
479
587
|
} else {
|
|
480
|
-
limit =
|
|
588
|
+
limit = count;
|
|
481
589
|
}
|
|
482
590
|
break;
|
|
483
591
|
}
|
|
484
592
|
case "offset":
|
|
485
|
-
offset =
|
|
593
|
+
offset = resolveIntegerCount(
|
|
594
|
+
clause.count,
|
|
595
|
+
ctx,
|
|
596
|
+
"offset"
|
|
597
|
+
);
|
|
486
598
|
break;
|
|
487
599
|
case "lock": {
|
|
488
600
|
const lc = clause;
|
|
@@ -647,6 +759,14 @@ function compileOrderItem(item, ctx) {
|
|
|
647
759
|
}
|
|
648
760
|
return { field, direction: item.direction };
|
|
649
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
|
+
}
|
|
650
770
|
const sqlExpr = expressionToSql(item.expression);
|
|
651
771
|
return { field: sqlExpr, direction: item.direction };
|
|
652
772
|
}
|
|
@@ -809,6 +929,9 @@ function getISOWeekCount(year) {
|
|
|
809
929
|
}
|
|
810
930
|
|
|
811
931
|
// src/compiler/compile-expression.ts
|
|
932
|
+
function paramValue(value) {
|
|
933
|
+
return value !== null && typeof value === "object" && value.kind === "param" ? value.value : value;
|
|
934
|
+
}
|
|
812
935
|
var MAX_ANY_ITEMS = 1e4;
|
|
813
936
|
function compileLogical(expr, ctx, fns, aliasContext, outerAliases) {
|
|
814
937
|
if (expr.type === "binary") {
|
|
@@ -872,7 +995,7 @@ function compileComparison(expr, ctx, _fns, aliasContext, outerAliases) {
|
|
|
872
995
|
aliasContext,
|
|
873
996
|
outerAliases
|
|
874
997
|
);
|
|
875
|
-
|
|
998
|
+
const intent2 = {
|
|
876
999
|
kind: "comparison",
|
|
877
1000
|
field: baseField,
|
|
878
1001
|
operator: operator2,
|
|
@@ -880,6 +1003,7 @@ function compileComparison(expr, ctx, _fns, aliasContext, outerAliases) {
|
|
|
880
1003
|
jsonPath: jsonLeft.path,
|
|
881
1004
|
jsonMode: jsonLeft.mode
|
|
882
1005
|
};
|
|
1006
|
+
return intent2;
|
|
883
1007
|
}
|
|
884
1008
|
if (comp.left.type === "function") {
|
|
885
1009
|
const fn = comp.left.name.toLowerCase();
|
|
@@ -897,7 +1021,7 @@ function compileComparison(expr, ctx, _fns, aliasContext, outerAliases) {
|
|
|
897
1021
|
`${fn}() first argument must be a field reference`
|
|
898
1022
|
);
|
|
899
1023
|
}
|
|
900
|
-
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));
|
|
901
1025
|
const operator2 = mapComparisonOperator(comp.operator);
|
|
902
1026
|
const value2 = resolveFilterValue(
|
|
903
1027
|
comp.right,
|
|
@@ -905,7 +1029,7 @@ function compileComparison(expr, ctx, _fns, aliasContext, outerAliases) {
|
|
|
905
1029
|
aliasContext,
|
|
906
1030
|
outerAliases
|
|
907
1031
|
);
|
|
908
|
-
|
|
1032
|
+
const intent2 = {
|
|
909
1033
|
kind: "comparison",
|
|
910
1034
|
field: baseField,
|
|
911
1035
|
operator: operator2,
|
|
@@ -913,6 +1037,7 @@ function compileComparison(expr, ctx, _fns, aliasContext, outerAliases) {
|
|
|
913
1037
|
jsonPath: keys,
|
|
914
1038
|
jsonMode: fn === "json_extract" ? "json" : "text"
|
|
915
1039
|
};
|
|
1040
|
+
return intent2;
|
|
916
1041
|
}
|
|
917
1042
|
}
|
|
918
1043
|
const field = expressionToField(comp.left, aliasContext);
|
|
@@ -924,20 +1049,22 @@ function compileComparison(expr, ctx, _fns, aliasContext, outerAliases) {
|
|
|
924
1049
|
}
|
|
925
1050
|
validateWhereField(ctx, field, aliasContext, comp.left);
|
|
926
1051
|
if (comp.operator === "like") {
|
|
1052
|
+
const pattern = coerceToStringKey(comp.right, "LIKE pattern", ctx);
|
|
927
1053
|
return {
|
|
928
1054
|
kind: "like",
|
|
929
1055
|
field,
|
|
930
|
-
pattern
|
|
1056
|
+
pattern
|
|
931
1057
|
};
|
|
932
1058
|
}
|
|
933
1059
|
const operator = mapComparisonOperator(comp.operator);
|
|
934
1060
|
const value = resolveFilterValue(comp.right, ctx, aliasContext, outerAliases);
|
|
935
|
-
|
|
1061
|
+
const intent = {
|
|
936
1062
|
kind: "comparison",
|
|
937
1063
|
field,
|
|
938
1064
|
operator,
|
|
939
1065
|
value
|
|
940
1066
|
};
|
|
1067
|
+
return intent;
|
|
941
1068
|
}
|
|
942
1069
|
function compileRange(expr, ctx, _fns, aliasContext, outerAliases) {
|
|
943
1070
|
const rangeExpr = expr;
|
|
@@ -965,12 +1092,13 @@ function compileRange(expr, ctx, _fns, aliasContext, outerAliases) {
|
|
|
965
1092
|
"Range operator requires either a range literal or scalar value"
|
|
966
1093
|
);
|
|
967
1094
|
}
|
|
968
|
-
|
|
1095
|
+
const result = {
|
|
969
1096
|
kind: "range",
|
|
970
1097
|
field,
|
|
971
1098
|
operator: rangeExpr.operator,
|
|
972
1099
|
value: rangeValue
|
|
973
1100
|
};
|
|
1101
|
+
return result;
|
|
974
1102
|
}
|
|
975
1103
|
function compileMembership(expr, ctx, fns, aliasContext, outerAliases) {
|
|
976
1104
|
if (expr.type === "any") {
|
|
@@ -983,11 +1111,12 @@ function compileMembership(expr, ctx, fns, aliasContext, outerAliases) {
|
|
|
983
1111
|
);
|
|
984
1112
|
}
|
|
985
1113
|
validateWhereField(ctx, field2, aliasContext, anyExpr.column);
|
|
986
|
-
const
|
|
1114
|
+
const valuesParam = resolveNamedParam(ctx, anyExpr.paramName);
|
|
1115
|
+
const rawValues = valuesParam.value;
|
|
987
1116
|
if (!Array.isArray(rawValues)) {
|
|
988
1117
|
throw new NqlSemanticException(
|
|
989
1118
|
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
990
|
-
`ANY(:${anyExpr.paramName}) requires an array argument
|
|
1119
|
+
`ANY(:${anyExpr.paramName}) requires an array argument`
|
|
991
1120
|
);
|
|
992
1121
|
}
|
|
993
1122
|
if (rawValues.length > ctx.maxAnyItems) {
|
|
@@ -996,8 +1125,12 @@ function compileMembership(expr, ctx, fns, aliasContext, outerAliases) {
|
|
|
996
1125
|
`ANY(:${anyExpr.paramName}) array length ${rawValues.length} exceeds maximum of ${ctx.maxAnyItems}`
|
|
997
1126
|
);
|
|
998
1127
|
}
|
|
999
|
-
const
|
|
1000
|
-
|
|
1128
|
+
const result2 = {
|
|
1129
|
+
kind: "any",
|
|
1130
|
+
field: field2,
|
|
1131
|
+
values: valuesParam
|
|
1132
|
+
};
|
|
1133
|
+
return result2;
|
|
1001
1134
|
}
|
|
1002
1135
|
const inExpr = expr;
|
|
1003
1136
|
const field = expressionToField(inExpr.expression, aliasContext);
|
|
@@ -1077,27 +1210,28 @@ function compileBetween(expr, ctx, _fns, aliasContext, outerAliases) {
|
|
|
1077
1210
|
aliasContext,
|
|
1078
1211
|
outerAliases
|
|
1079
1212
|
);
|
|
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
|
-
}
|
|
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 };
|
|
1094
1218
|
return {
|
|
1095
1219
|
kind: "range",
|
|
1096
1220
|
field,
|
|
1097
1221
|
operator: "between",
|
|
1098
|
-
value
|
|
1222
|
+
value
|
|
1099
1223
|
};
|
|
1100
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
|
+
}
|
|
1101
1235
|
function compileNull(expr, ctx, aliasContext) {
|
|
1102
1236
|
const isNull = expr;
|
|
1103
1237
|
const field = expressionToField(isNull.expression, aliasContext);
|
|
@@ -1137,12 +1271,13 @@ function compileJson(expr, ctx, _fns, aliasContext, outerAliases) {
|
|
|
1137
1271
|
aliasContext,
|
|
1138
1272
|
outerAliases
|
|
1139
1273
|
);
|
|
1140
|
-
|
|
1274
|
+
const intent2 = {
|
|
1141
1275
|
kind: "jsonContains",
|
|
1142
1276
|
field: jsonField2,
|
|
1143
1277
|
value: jsonValue2,
|
|
1144
1278
|
reversed: fn === "json_contained_by"
|
|
1145
1279
|
};
|
|
1280
|
+
return intent2;
|
|
1146
1281
|
}
|
|
1147
1282
|
if (fn === "json_exists") {
|
|
1148
1283
|
if (expr.args.length < 2) {
|
|
@@ -1158,7 +1293,7 @@ function compileJson(expr, ctx, _fns, aliasContext, outerAliases) {
|
|
|
1158
1293
|
`${fn}() first argument must be a field reference`
|
|
1159
1294
|
);
|
|
1160
1295
|
}
|
|
1161
|
-
const key = coerceToStringKey(expr.args[1], `${fn}() key
|
|
1296
|
+
const key = coerceToStringKey(expr.args[1], `${fn}() key`, ctx);
|
|
1162
1297
|
return {
|
|
1163
1298
|
kind: "jsonExists",
|
|
1164
1299
|
field: jsonField2,
|
|
@@ -1179,7 +1314,7 @@ function compileJson(expr, ctx, _fns, aliasContext, outerAliases) {
|
|
|
1179
1314
|
);
|
|
1180
1315
|
}
|
|
1181
1316
|
if (jsonComp.operator === "?") {
|
|
1182
|
-
const key = coerceToStringKey(jsonComp.right, "? operator key");
|
|
1317
|
+
const key = coerceToStringKey(jsonComp.right, "? operator key", ctx);
|
|
1183
1318
|
return {
|
|
1184
1319
|
kind: "jsonExists",
|
|
1185
1320
|
field: jsonField,
|
|
@@ -1192,12 +1327,13 @@ function compileJson(expr, ctx, _fns, aliasContext, outerAliases) {
|
|
|
1192
1327
|
aliasContext,
|
|
1193
1328
|
outerAliases
|
|
1194
1329
|
);
|
|
1195
|
-
|
|
1330
|
+
const intent = {
|
|
1196
1331
|
kind: "jsonContains",
|
|
1197
1332
|
field: jsonField,
|
|
1198
1333
|
value: jsonValue,
|
|
1199
1334
|
reversed: jsonComp.operator === "<@"
|
|
1200
1335
|
};
|
|
1336
|
+
return intent;
|
|
1201
1337
|
}
|
|
1202
1338
|
function compileRelationFilter(expr, ctx, fns, aliasContext, outerAliases) {
|
|
1203
1339
|
const relFilter = expr;
|
|
@@ -1291,8 +1427,9 @@ function compileExpression(expr, ctx, fns, aliasContext, outerAliases) {
|
|
|
1291
1427
|
);
|
|
1292
1428
|
}
|
|
1293
1429
|
}
|
|
1294
|
-
|
|
1295
|
-
|
|
1430
|
+
function assignMutationValue(target, column, value, ctx) {
|
|
1431
|
+
target[column] = expressionToValue(value, ctx);
|
|
1432
|
+
}
|
|
1296
1433
|
function compileMutationPipeline(pipeline, ctx, fns, bindings) {
|
|
1297
1434
|
ctx.currentFromTable = pipeline.mutation.table;
|
|
1298
1435
|
const mutation = compileMutation(pipeline.mutation, ctx, fns, bindings);
|
|
@@ -1340,7 +1477,7 @@ function compileInsert(insert, ctx) {
|
|
|
1340
1477
|
const rowColumns = /* @__PURE__ */ new Set();
|
|
1341
1478
|
for (const assignment of row) {
|
|
1342
1479
|
rowColumns.add(assignment.column);
|
|
1343
|
-
rowValues
|
|
1480
|
+
assignMutationValue(rowValues, assignment.column, assignment.value, ctx);
|
|
1344
1481
|
}
|
|
1345
1482
|
for (const col of allColumns) {
|
|
1346
1483
|
if (!rowColumns.has(col)) {
|
|
@@ -1372,7 +1509,9 @@ function compileInsertFrom(insertFrom, ctx, fns, bindings) {
|
|
|
1372
1509
|
...insertFrom.where !== void 0 && {
|
|
1373
1510
|
where: fns.compileExpression(insertFrom.where, ctx, fns)
|
|
1374
1511
|
},
|
|
1375
|
-
...insertFrom.limit !== void 0 && {
|
|
1512
|
+
...insertFrom.limit !== void 0 && {
|
|
1513
|
+
limit: resolveIntegerCount(insertFrom.limit, ctx, "insert-from limit")
|
|
1514
|
+
}
|
|
1376
1515
|
};
|
|
1377
1516
|
}
|
|
1378
1517
|
function compileUpdate(update, ctx, fns, bindings) {
|
|
@@ -1381,7 +1520,7 @@ function compileUpdate(update, ctx, fns, bindings) {
|
|
|
1381
1520
|
const set = {};
|
|
1382
1521
|
for (const assignment of update.assignments) {
|
|
1383
1522
|
ctx.validator?.validateColumn(update.table, assignment.column);
|
|
1384
|
-
set
|
|
1523
|
+
assignMutationValue(set, assignment.column, assignment.value, ctx);
|
|
1385
1524
|
}
|
|
1386
1525
|
if (update.where) {
|
|
1387
1526
|
return {
|
|
@@ -1435,7 +1574,7 @@ function compileUpsert(upsert, ctx) {
|
|
|
1435
1574
|
const values = {};
|
|
1436
1575
|
for (const assignment of upsert.assignments) {
|
|
1437
1576
|
ctx.validator?.validateColumn(upsert.table, assignment.column);
|
|
1438
|
-
values
|
|
1577
|
+
assignMutationValue(values, assignment.column, assignment.value, ctx);
|
|
1439
1578
|
}
|
|
1440
1579
|
for (const col of upsert.conflictColumns) {
|
|
1441
1580
|
ctx.validator?.validateColumn(upsert.table, col);
|
|
@@ -1477,7 +1616,9 @@ function compileUpsertFrom(upsertFrom, ctx, fns, bindings) {
|
|
|
1477
1616
|
...upsertFrom.where !== void 0 && {
|
|
1478
1617
|
where: fns.compileExpression(upsertFrom.where, ctx, fns)
|
|
1479
1618
|
},
|
|
1480
|
-
...upsertFrom.limit !== void 0 && {
|
|
1619
|
+
...upsertFrom.limit !== void 0 && {
|
|
1620
|
+
limit: resolveIntegerCount(upsertFrom.limit, ctx, "upsert-from limit")
|
|
1621
|
+
}
|
|
1481
1622
|
};
|
|
1482
1623
|
}
|
|
1483
1624
|
function extractReturningColumns(clause, ctx) {
|
|
@@ -1505,7 +1646,7 @@ function resolveBindingsInWhere(where, bindings) {
|
|
|
1505
1646
|
const inValues = inWhere.subquery ? void 0 : inWhere.values;
|
|
1506
1647
|
if (inValues && inValues.length === 1) {
|
|
1507
1648
|
const val = inValues[0];
|
|
1508
|
-
if (val && typeof val === "object" && "$ref" in val) {
|
|
1649
|
+
if (!isParamIntent(val) && val && typeof val === "object" && "$ref" in val) {
|
|
1509
1650
|
const ref = val.$ref;
|
|
1510
1651
|
if (bindings.has(ref)) {
|
|
1511
1652
|
const boundQuery = bindings.get(ref);
|
|
@@ -1558,6 +1699,77 @@ function extractBindName(stmt) {
|
|
|
1558
1699
|
}
|
|
1559
1700
|
return void 0;
|
|
1560
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
|
+
}
|
|
1561
1773
|
function compileSelectClause(clause, ctx, fns) {
|
|
1562
1774
|
if (clause.items.length === 1 && clause.items[0]?.type === "star") {
|
|
1563
1775
|
return { type: "all" };
|
|
@@ -1581,12 +1793,7 @@ function compileSelectClause(clause, ctx, fns) {
|
|
|
1581
1793
|
} else if (item.type === "expression") {
|
|
1582
1794
|
const expr = item.expression;
|
|
1583
1795
|
if (expr.type === "path" && expr.segments.length === 1 && !item.alias) {
|
|
1584
|
-
|
|
1585
|
-
ctx.validator?.validateColumn(
|
|
1586
|
-
ctx.currentFromTable,
|
|
1587
|
-
expr.segments[0]
|
|
1588
|
-
);
|
|
1589
|
-
}
|
|
1796
|
+
validateSelectPathExpression(expr, ctx);
|
|
1590
1797
|
simpleFields.push(expr.segments[0]);
|
|
1591
1798
|
} else {
|
|
1592
1799
|
hasExpressions = true;
|
|
@@ -1599,6 +1806,40 @@ function compileSelectClause(clause, ctx, fns) {
|
|
|
1599
1806
|
}
|
|
1600
1807
|
return { type: "expressions", columns: expressions };
|
|
1601
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
|
+
}
|
|
1602
1843
|
function compileSelectExpression(item, ctx, fns) {
|
|
1603
1844
|
if (item.type === "star") {
|
|
1604
1845
|
return { kind: "column", column: "*" };
|
|
@@ -1618,6 +1859,7 @@ function compileSelectExpression(item, ctx, fns) {
|
|
|
1618
1859
|
const fn = expr.name.toLowerCase();
|
|
1619
1860
|
if (isAggregateFunction(fn)) {
|
|
1620
1861
|
let field;
|
|
1862
|
+
let firstArgAsExtraArg;
|
|
1621
1863
|
if (expr.args.length === 0) {
|
|
1622
1864
|
if (fn === "count") {
|
|
1623
1865
|
field = "*";
|
|
@@ -1627,56 +1869,72 @@ function compileSelectExpression(item, ctx, fns) {
|
|
|
1627
1869
|
);
|
|
1628
1870
|
}
|
|
1629
1871
|
} else {
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
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);
|
|
1633
1880
|
}
|
|
1634
1881
|
}
|
|
1635
|
-
const extraArgs = expr.args.length > 1 ? expr.args.slice(1).map(
|
|
1636
|
-
|
|
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 = {
|
|
1637
1887
|
kind: "aggregate",
|
|
1638
1888
|
function: fn,
|
|
1639
|
-
field,
|
|
1889
|
+
...field !== void 0 && { field },
|
|
1640
1890
|
...exprItem.alias !== void 0 && { as: exprItem.alias },
|
|
1641
1891
|
...expr.distinct && { distinct: true },
|
|
1642
|
-
...
|
|
1892
|
+
...aggregateArgs && { extraArgs: aggregateArgs }
|
|
1643
1893
|
};
|
|
1894
|
+
return aggregateIntent;
|
|
1644
1895
|
}
|
|
1645
|
-
const jsonIntent = compileJsonFunction(fn, expr.args, exprItem.alias);
|
|
1896
|
+
const jsonIntent = compileJsonFunction(fn, expr.args, exprItem.alias, ctx);
|
|
1646
1897
|
if (jsonIntent) return jsonIntent;
|
|
1898
|
+
if (!SELECT_SCALAR_FUNCTION_NAMES.has(fn)) {
|
|
1899
|
+
throwUnsupportedSelectFunction(fn);
|
|
1900
|
+
}
|
|
1647
1901
|
return {
|
|
1648
1902
|
kind: "function",
|
|
1649
|
-
name:
|
|
1650
|
-
args: expr.args.map((a) =>
|
|
1903
|
+
name: fn,
|
|
1904
|
+
args: expr.args.map((a) => expressionToFunctionArg(a, ctx, fns)),
|
|
1651
1905
|
...exprItem.alias !== void 0 && { as: exprItem.alias }
|
|
1652
1906
|
};
|
|
1653
1907
|
}
|
|
1654
1908
|
if (expr.type === "window") {
|
|
1655
1909
|
const windowExpr = expr;
|
|
1656
1910
|
const fn = windowExpr.function.toLowerCase();
|
|
1911
|
+
if (!isNqlSelectWindowFunctionAllowed(fn)) {
|
|
1912
|
+
throwUnsupportedSelectFunction(fn);
|
|
1913
|
+
}
|
|
1914
|
+
const windowFn = fn;
|
|
1657
1915
|
let field;
|
|
1658
1916
|
if (windowExpr.args.length > 0) {
|
|
1659
|
-
field =
|
|
1917
|
+
field = expressionToValidatedField(windowExpr.args[0], ctx) ?? expressionToSql(windowExpr.args[0]);
|
|
1660
1918
|
}
|
|
1661
1919
|
let offset;
|
|
1662
1920
|
let defaultValue;
|
|
1663
|
-
if (
|
|
1921
|
+
if (windowFn === "lag" || windowFn === "lead") {
|
|
1664
1922
|
if (windowExpr.args.length > 1) {
|
|
1665
|
-
offset =
|
|
1923
|
+
offset = resolveLagLeadOffset(windowExpr.args[1], ctx);
|
|
1666
1924
|
}
|
|
1667
1925
|
if (windowExpr.args.length > 2) {
|
|
1668
|
-
defaultValue = expressionToValue(windowExpr.args[2]);
|
|
1926
|
+
defaultValue = expressionToValue(windowExpr.args[2], ctx);
|
|
1669
1927
|
}
|
|
1670
1928
|
}
|
|
1671
1929
|
const partitionBy = windowExpr.partitionBy.length > 0 ? windowExpr.partitionBy.map((e) => {
|
|
1672
|
-
const f =
|
|
1930
|
+
const f = expressionToValidatedField(e, ctx) ?? expressionToSql(e);
|
|
1673
1931
|
if (ctx.currentFromTable && !f.includes(".") && !f.includes("(")) {
|
|
1674
1932
|
ctx.validator?.validateColumn(ctx.currentFromTable, f);
|
|
1675
1933
|
}
|
|
1676
1934
|
return f;
|
|
1677
1935
|
}) : void 0;
|
|
1678
1936
|
const orderBy = windowExpr.orderBy.length > 0 ? windowExpr.orderBy.map((o) => {
|
|
1679
|
-
const f =
|
|
1937
|
+
const f = expressionToValidatedField(o.expression, ctx) ?? expressionToSql(o.expression);
|
|
1680
1938
|
if (ctx.currentFromTable && !f.includes(".") && !f.includes("(")) {
|
|
1681
1939
|
ctx.validator?.validateColumn(ctx.currentFromTable, f);
|
|
1682
1940
|
}
|
|
@@ -1686,19 +1944,19 @@ function compileSelectExpression(item, ctx, fns) {
|
|
|
1686
1944
|
...partitionBy && { partitionBy },
|
|
1687
1945
|
...orderBy && { orderBy }
|
|
1688
1946
|
};
|
|
1689
|
-
const alias = exprItem.alias ??
|
|
1690
|
-
if (isRankingWindowFunction(
|
|
1947
|
+
const alias = exprItem.alias ?? windowFn;
|
|
1948
|
+
if (isRankingWindowFunction(windowFn)) {
|
|
1691
1949
|
return {
|
|
1692
1950
|
kind: "window",
|
|
1693
|
-
function:
|
|
1951
|
+
function: windowFn,
|
|
1694
1952
|
alias,
|
|
1695
1953
|
over
|
|
1696
1954
|
};
|
|
1697
1955
|
}
|
|
1698
|
-
if (
|
|
1956
|
+
if (windowFn === "lag" || windowFn === "lead") {
|
|
1699
1957
|
return {
|
|
1700
1958
|
kind: "window",
|
|
1701
|
-
function:
|
|
1959
|
+
function: windowFn,
|
|
1702
1960
|
field: field ?? "",
|
|
1703
1961
|
alias,
|
|
1704
1962
|
...offset !== void 0 && { offset },
|
|
@@ -1708,7 +1966,7 @@ function compileSelectExpression(item, ctx, fns) {
|
|
|
1708
1966
|
}
|
|
1709
1967
|
return {
|
|
1710
1968
|
kind: "window",
|
|
1711
|
-
function:
|
|
1969
|
+
function: windowFn,
|
|
1712
1970
|
...field !== void 0 && { field },
|
|
1713
1971
|
alias,
|
|
1714
1972
|
over
|
|
@@ -1723,9 +1981,7 @@ function compileSelectExpression(item, ctx, fns) {
|
|
|
1723
1981
|
}
|
|
1724
1982
|
if (expr.type === "path" && expr.segments.length === 1) {
|
|
1725
1983
|
const column = expr.segments[0];
|
|
1726
|
-
|
|
1727
|
-
ctx.validator?.validateColumn(ctx.currentFromTable, column);
|
|
1728
|
-
}
|
|
1984
|
+
validateSelectPathExpression(expr, ctx);
|
|
1729
1985
|
if (exprItem.alias) {
|
|
1730
1986
|
return { kind: "columnAlias", column, alias: exprItem.alias };
|
|
1731
1987
|
}
|
|
@@ -1734,26 +1990,32 @@ function compileSelectExpression(item, ctx, fns) {
|
|
|
1734
1990
|
if (expr.type === "path" && expr.segments.length > 1) {
|
|
1735
1991
|
return compileMultiSegmentPath(expr, exprItem, ctx);
|
|
1736
1992
|
}
|
|
1993
|
+
if (expr.type === "namedParam") {
|
|
1994
|
+
return paramExpressionIntent(
|
|
1995
|
+
expressionToValue(expr, ctx),
|
|
1996
|
+
exprItem.alias
|
|
1997
|
+
);
|
|
1998
|
+
}
|
|
1737
1999
|
if (expr.type === "binary" && ["+", "-", "*", "/", "%"].includes(expr.operator)) {
|
|
1738
|
-
const leftField =
|
|
1739
|
-
const rightField =
|
|
2000
|
+
const leftField = expressionToValidatedField(expr.left, ctx);
|
|
2001
|
+
const rightField = expressionToValidatedField(expr.right, ctx);
|
|
1740
2002
|
return {
|
|
1741
2003
|
kind: "arithmetic",
|
|
1742
|
-
left: leftField ??
|
|
2004
|
+
left: leftField ?? expressionToSelectValue(expr.left, ctx, fns),
|
|
1743
2005
|
operator: expr.operator,
|
|
1744
|
-
right: rightField ??
|
|
2006
|
+
right: rightField ?? expressionToSelectValue(expr.right, ctx, fns),
|
|
1745
2007
|
...exprItem.alias !== void 0 && { as: exprItem.alias }
|
|
1746
2008
|
};
|
|
1747
2009
|
}
|
|
1748
2010
|
if (expr.type === "unary") {
|
|
1749
2011
|
const unary = expr;
|
|
1750
2012
|
if (unary.operator === "-") {
|
|
1751
|
-
const operandField =
|
|
2013
|
+
const operandField = expressionToValidatedField(unary.operand, ctx);
|
|
1752
2014
|
return {
|
|
1753
2015
|
kind: "arithmetic",
|
|
1754
2016
|
left: -1,
|
|
1755
2017
|
operator: "*",
|
|
1756
|
-
right: operandField ??
|
|
2018
|
+
right: operandField ?? expressionToSelectValue(unary.operand, ctx, fns),
|
|
1757
2019
|
...exprItem.alias !== void 0 && { as: exprItem.alias }
|
|
1758
2020
|
};
|
|
1759
2021
|
}
|
|
@@ -1763,7 +2025,7 @@ function compileSelectExpression(item, ctx, fns) {
|
|
|
1763
2025
|
return compileCaseExpression(expr, exprItem, ctx, fns);
|
|
1764
2026
|
}
|
|
1765
2027
|
if (expr.type === "jsonAccess") {
|
|
1766
|
-
const baseField =
|
|
2028
|
+
const baseField = expressionToValidatedField(expr.base, ctx);
|
|
1767
2029
|
if (!baseField) {
|
|
1768
2030
|
throw new Error("JSON access base must be a field reference");
|
|
1769
2031
|
}
|
|
@@ -1775,6 +2037,15 @@ function compileSelectExpression(item, ctx, fns) {
|
|
|
1775
2037
|
...exprItem.alias !== void 0 && { as: exprItem.alias }
|
|
1776
2038
|
};
|
|
1777
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
|
+
}
|
|
1778
2049
|
throw new Error(
|
|
1779
2050
|
`Unsupported expression type in SELECT: ${expr.type}. This expression cannot be compiled to IntentAST. Consider extending the grammar or using a supported expression.`
|
|
1780
2051
|
);
|
|
@@ -1850,21 +2121,25 @@ function compileMultiSegmentPath(expr, item, ctx) {
|
|
|
1850
2121
|
}
|
|
1851
2122
|
function compileCaseExpression(caseExpr, item, ctx, fns) {
|
|
1852
2123
|
if (caseExpr.subject) {
|
|
1853
|
-
const subjectField =
|
|
2124
|
+
const subjectField = expressionToValidatedField(caseExpr.subject, ctx);
|
|
1854
2125
|
if (!subjectField) {
|
|
1855
2126
|
throw new Error("Simple CASE subject must be a column reference");
|
|
1856
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
|
+
});
|
|
1857
2140
|
return {
|
|
1858
2141
|
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
|
-
})),
|
|
2142
|
+
when,
|
|
1868
2143
|
...caseExpr.elseClause && {
|
|
1869
2144
|
else: compileExpressionToIntent(caseExpr.elseClause, ctx, fns)
|
|
1870
2145
|
},
|
|
@@ -1891,21 +2166,28 @@ function compileExpressionToIntent(expr, ctx, fns) {
|
|
|
1891
2166
|
`CASE WHEN condition left side must be a column path, got ${cmp.left.type}`
|
|
1892
2167
|
);
|
|
1893
2168
|
}
|
|
2169
|
+
validateSelectPathExpression(cmp.left, ctx);
|
|
2170
|
+
if (cmp.right.type === "path") {
|
|
2171
|
+
validateSelectPathExpression(cmp.right, ctx);
|
|
2172
|
+
}
|
|
1894
2173
|
const column = cmp.left.segments.join(".");
|
|
1895
|
-
const value = expressionToValue(cmp.right);
|
|
1896
|
-
|
|
2174
|
+
const value = expressionToValue(cmp.right, ctx);
|
|
2175
|
+
const intent = {
|
|
1897
2176
|
kind: "comparison",
|
|
1898
2177
|
column,
|
|
1899
2178
|
operator: cmp.operator,
|
|
1900
2179
|
value
|
|
1901
2180
|
};
|
|
2181
|
+
return intent;
|
|
1902
2182
|
}
|
|
1903
|
-
if (expr.type === "string" || expr.type === "number" || expr.type === "boolean" || expr.type === "null") {
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
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);
|
|
1909
2191
|
}
|
|
1910
2192
|
const selectItem = {
|
|
1911
2193
|
type: "expression",
|
|
@@ -1913,13 +2195,20 @@ function compileExpressionToIntent(expr, ctx, fns) {
|
|
|
1913
2195
|
};
|
|
1914
2196
|
return compileSelectExpression(selectItem, ctx, fns);
|
|
1915
2197
|
}
|
|
1916
|
-
var JSON_FUNCTION_NAMES =
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
]
|
|
1922
|
-
|
|
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) {
|
|
1923
2212
|
if (!JSON_FUNCTION_NAMES.has(fn)) return null;
|
|
1924
2213
|
if (args.length < 2) {
|
|
1925
2214
|
throw new NqlSemanticException(
|
|
@@ -1935,7 +2224,7 @@ function compileJsonFunction(fn, args, alias) {
|
|
|
1935
2224
|
);
|
|
1936
2225
|
}
|
|
1937
2226
|
if (fn === "json_extract" || fn === "json_extract_text") {
|
|
1938
|
-
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));
|
|
1939
2228
|
return {
|
|
1940
2229
|
kind: "jsonExtract",
|
|
1941
2230
|
field,
|
|
@@ -1945,13 +2234,11 @@ function compileJsonFunction(fn, args, alias) {
|
|
|
1945
2234
|
};
|
|
1946
2235
|
}
|
|
1947
2236
|
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(",")}}`;
|
|
2237
|
+
const path = compileJsonPathArgs(fn, args, ctx);
|
|
1951
2238
|
return {
|
|
1952
2239
|
kind: "jsonPathExtract",
|
|
1953
2240
|
field,
|
|
1954
|
-
path
|
|
2241
|
+
path,
|
|
1955
2242
|
mode: fn === "json_path" ? "json" : "text",
|
|
1956
2243
|
...alias !== void 0 && { as: alias }
|
|
1957
2244
|
};
|
|
@@ -1972,6 +2259,10 @@ var DEFAULT_RECURSIVE_KEYWORDS = [
|
|
|
1972
2259
|
];
|
|
1973
2260
|
|
|
1974
2261
|
// src/compiler/index.ts
|
|
2262
|
+
function allowsInternalParams(options) {
|
|
2263
|
+
const internalOptions = options?.[NQL_INTERNAL_COMPILER_OPTIONS];
|
|
2264
|
+
return internalOptions?.allowInternalParams === true;
|
|
2265
|
+
}
|
|
1975
2266
|
var NqlCompiler = class {
|
|
1976
2267
|
ctx;
|
|
1977
2268
|
fns;
|
|
@@ -1989,15 +2280,19 @@ var NqlCompiler = class {
|
|
|
1989
2280
|
);
|
|
1990
2281
|
}
|
|
1991
2282
|
}
|
|
2283
|
+
const params = options?.params ?? {};
|
|
2284
|
+
const allowInternalParams = allowsInternalParams(options);
|
|
2285
|
+
validateParamsMap(params, { allowInternalParams });
|
|
1992
2286
|
this.ctx = {
|
|
1993
2287
|
currentFromTable: void 0,
|
|
1994
2288
|
currentRelationTarget: void 0,
|
|
1995
2289
|
pseudoColumnKeywords,
|
|
1996
2290
|
recursiveKeywords,
|
|
1997
2291
|
validator,
|
|
1998
|
-
params
|
|
2292
|
+
params,
|
|
1999
2293
|
maxAnyItems: maxAnyItemsRaw ?? MAX_ANY_ITEMS,
|
|
2000
|
-
allowUnfilteredMutations: options?.allowUnfilteredMutations ?? false
|
|
2294
|
+
allowUnfilteredMutations: options?.allowUnfilteredMutations ?? false,
|
|
2295
|
+
allowInternalParams
|
|
2001
2296
|
};
|
|
2002
2297
|
this.fns = {
|
|
2003
2298
|
compileQuery: (query, ctx) => compileQuery(query, ctx, this.fns),
|
|
@@ -2440,6 +2735,11 @@ var allTokens = [
|
|
|
2440
2735
|
// ? (no conflict with other tokens)
|
|
2441
2736
|
];
|
|
2442
2737
|
var NqlLexer = new Lexer(allTokens);
|
|
2738
|
+
|
|
2739
|
+
// src/lexer/index.ts
|
|
2740
|
+
function tokenize(input) {
|
|
2741
|
+
return NqlLexer.tokenize(input);
|
|
2742
|
+
}
|
|
2443
2743
|
var NqlParser = class extends CstParser {
|
|
2444
2744
|
constructor() {
|
|
2445
2745
|
super(allTokens, {
|
|
@@ -2587,7 +2887,7 @@ var NqlParser = class extends CstParser {
|
|
|
2587
2887
|
this.SUBRULE(this.orderList);
|
|
2588
2888
|
});
|
|
2589
2889
|
/**
|
|
2590
|
-
* limit_clause = "limit" [ident_segment ("." ident_segment)*] NUMBER ;
|
|
2890
|
+
* limit_clause = "limit" [ident_segment ("." ident_segment)*] (NUMBER | NAMED_PARAM) ;
|
|
2591
2891
|
*/
|
|
2592
2892
|
limitClause = this.RULE("limitClause", () => {
|
|
2593
2893
|
this.CONSUME(Limit);
|
|
@@ -2598,14 +2898,29 @@ var NqlParser = class extends CstParser {
|
|
|
2598
2898
|
this.SUBRULE2(this.identSegment);
|
|
2599
2899
|
});
|
|
2600
2900
|
});
|
|
2601
|
-
this.
|
|
2901
|
+
this.SUBRULE(this.numericValueAtom);
|
|
2602
2902
|
});
|
|
2603
2903
|
/**
|
|
2604
|
-
* offset_clause = "offset" NUMBER ;
|
|
2904
|
+
* offset_clause = "offset" (NUMBER | NAMED_PARAM) ;
|
|
2605
2905
|
*/
|
|
2606
2906
|
offsetClause = this.RULE("offsetClause", () => {
|
|
2607
2907
|
this.CONSUME(Offset);
|
|
2608
|
-
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
|
+
]);
|
|
2609
2924
|
});
|
|
2610
2925
|
/**
|
|
2611
2926
|
* lock_clause = lock_strength [ lock_wait_policy ] ;
|
|
@@ -2869,7 +3184,7 @@ var NqlParser = class extends CstParser {
|
|
|
2869
3184
|
]);
|
|
2870
3185
|
});
|
|
2871
3186
|
/**
|
|
2872
|
-
* range_op_suffix = range_op (range_literal | literal) ;
|
|
3187
|
+
* range_op_suffix = range_op (range_literal | literal | named_param_expr) ;
|
|
2873
3188
|
* Example: overlaps [1,10) or contains (0,100) or contains 25
|
|
2874
3189
|
* Note: contains can take a scalar value (e.g., contains 25 checks if range contains the value)
|
|
2875
3190
|
*/
|
|
@@ -2877,7 +3192,8 @@ var NqlParser = class extends CstParser {
|
|
|
2877
3192
|
this.SUBRULE(this.rangeOp);
|
|
2878
3193
|
this.OR([
|
|
2879
3194
|
{ ALT: () => this.SUBRULE(this.rangeLiteral) },
|
|
2880
|
-
{ ALT: () => this.SUBRULE2(this.literal) }
|
|
3195
|
+
{ ALT: () => this.SUBRULE2(this.literal) },
|
|
3196
|
+
{ ALT: () => this.SUBRULE(this.namedParamExpr) }
|
|
2881
3197
|
]);
|
|
2882
3198
|
});
|
|
2883
3199
|
/**
|
|
@@ -3118,10 +3434,12 @@ var NqlParser = class extends CstParser {
|
|
|
3118
3434
|
});
|
|
3119
3435
|
});
|
|
3120
3436
|
/**
|
|
3121
|
-
* 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 ")" ;
|
|
3122
3438
|
*/
|
|
3123
3439
|
primaryExpr = this.RULE("primaryExpr", () => {
|
|
3124
3440
|
this.OR([
|
|
3441
|
+
// Bound value parameter: :paramName
|
|
3442
|
+
{ ALT: () => this.SUBRULE(this.namedParamExpr) },
|
|
3125
3443
|
// Range literal in value context (unambiguous: starts with '[')
|
|
3126
3444
|
{
|
|
3127
3445
|
GATE: () => this.LA(1).tokenType === LBracket,
|
|
@@ -3159,6 +3477,12 @@ var NqlParser = class extends CstParser {
|
|
|
3159
3477
|
}
|
|
3160
3478
|
]);
|
|
3161
3479
|
});
|
|
3480
|
+
/**
|
|
3481
|
+
* named_param_expr = NAMED_PARAM ;
|
|
3482
|
+
*/
|
|
3483
|
+
namedParamExpr = this.RULE("namedParamExpr", () => {
|
|
3484
|
+
this.CONSUME(NamedParam);
|
|
3485
|
+
});
|
|
3162
3486
|
/**
|
|
3163
3487
|
* Check if at start of scalar subquery (ident | ...)
|
|
3164
3488
|
*/
|
|
@@ -3515,7 +3839,7 @@ var NqlParser = class extends CstParser {
|
|
|
3515
3839
|
]);
|
|
3516
3840
|
});
|
|
3517
3841
|
/**
|
|
3518
|
-
* 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 ] ;
|
|
3519
3843
|
* @example insert into archived_users from users where active = false limit 100
|
|
3520
3844
|
*/
|
|
3521
3845
|
insertFromStmt = this.RULE("insertFromStmt", () => {
|
|
@@ -3530,7 +3854,7 @@ var NqlParser = class extends CstParser {
|
|
|
3530
3854
|
});
|
|
3531
3855
|
this.OPTION2(() => {
|
|
3532
3856
|
this.CONSUME(Limit);
|
|
3533
|
-
this.
|
|
3857
|
+
this.SUBRULE(this.numericValueAtom);
|
|
3534
3858
|
});
|
|
3535
3859
|
});
|
|
3536
3860
|
/**
|
|
@@ -3590,7 +3914,7 @@ var NqlParser = class extends CstParser {
|
|
|
3590
3914
|
});
|
|
3591
3915
|
});
|
|
3592
3916
|
/**
|
|
3593
|
-
* 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 ] ;
|
|
3594
3918
|
* @example upsert into authors on id from counts
|
|
3595
3919
|
* @example upsert into authors on (id, email) from counts where active = true
|
|
3596
3920
|
*/
|
|
@@ -3621,7 +3945,7 @@ var NqlParser = class extends CstParser {
|
|
|
3621
3945
|
});
|
|
3622
3946
|
this.OPTION2(() => {
|
|
3623
3947
|
this.CONSUME(Limit);
|
|
3624
|
-
this.
|
|
3948
|
+
this.SUBRULE(this.numericValueAtom);
|
|
3625
3949
|
});
|
|
3626
3950
|
});
|
|
3627
3951
|
/**
|
|
@@ -3916,6 +4240,12 @@ function buildRangeOp(left, suffixNode, visit) {
|
|
|
3916
4240
|
const scalar = visit(asCstNode(suffixCtx.literal[0]));
|
|
3917
4241
|
return { type: "rangeOp", operator, left, scalar };
|
|
3918
4242
|
}
|
|
4243
|
+
if (suffixCtx.namedParamExpr) {
|
|
4244
|
+
const scalar = visit(
|
|
4245
|
+
asCstNode(suffixCtx.namedParamExpr[0])
|
|
4246
|
+
);
|
|
4247
|
+
return { type: "rangeOp", operator, left, scalar };
|
|
4248
|
+
}
|
|
3919
4249
|
throw new NqlSemanticException(
|
|
3920
4250
|
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
3921
4251
|
"Range op suffix missing range literal or scalar value"
|
|
@@ -4091,6 +4421,7 @@ function visitUnaryExpr(ctx, visit) {
|
|
|
4091
4421
|
return expr;
|
|
4092
4422
|
}
|
|
4093
4423
|
function visitPrimaryExpr(ctx, visit) {
|
|
4424
|
+
if (ctx.namedParamExpr) return visit(asCstNode(ctx.namedParamExpr[0]));
|
|
4094
4425
|
if (ctx.rangeLiteral) return visit(asCstNode(ctx.rangeLiteral[0]));
|
|
4095
4426
|
if (ctx.literal) return visit(asCstNode(ctx.literal[0]));
|
|
4096
4427
|
if (ctx.caseExpr) return visit(asCstNode(ctx.caseExpr[0]));
|
|
@@ -4105,6 +4436,14 @@ function visitPrimaryExpr(ctx, visit) {
|
|
|
4105
4436
|
"Invalid primary expression"
|
|
4106
4437
|
);
|
|
4107
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
|
+
}
|
|
4108
4447
|
function visitCaseExpr(ctx, visit) {
|
|
4109
4448
|
const whenClauses = [];
|
|
4110
4449
|
let subject;
|
|
@@ -4488,7 +4827,7 @@ function visitInsertFromStmt(ctx, visit) {
|
|
|
4488
4827
|
table: target,
|
|
4489
4828
|
source,
|
|
4490
4829
|
where: ctx.booleanExpr ? visit(asCstNode(ctx.booleanExpr[0])) : void 0,
|
|
4491
|
-
limit: ctx.
|
|
4830
|
+
limit: ctx.numericValueAtom ? visit(asCstNode(ctx.numericValueAtom[0])) : void 0
|
|
4492
4831
|
};
|
|
4493
4832
|
}
|
|
4494
4833
|
function visitUpdateStmt(ctx, visit) {
|
|
@@ -4563,7 +4902,7 @@ function visitUpsertFromStmt(ctx, visit) {
|
|
|
4563
4902
|
conflictColumns,
|
|
4564
4903
|
source,
|
|
4565
4904
|
where: ctx.booleanExpr ? visit(asCstNode(ctx.booleanExpr[0])) : void 0,
|
|
4566
|
-
limit: ctx.
|
|
4905
|
+
limit: ctx.numericValueAtom ? visit(asCstNode(ctx.numericValueAtom[0])) : void 0
|
|
4567
4906
|
};
|
|
4568
4907
|
}
|
|
4569
4908
|
function visitAssignmentList(ctx, visit) {
|
|
@@ -4671,8 +5010,8 @@ function visitOrderClause(ctx, visit) {
|
|
|
4671
5010
|
return { type: "orderBy", items };
|
|
4672
5011
|
}
|
|
4673
5012
|
function visitLimitClause(ctx, visit) {
|
|
4674
|
-
requireFirst(ctx, "
|
|
4675
|
-
const count =
|
|
5013
|
+
requireFirst(ctx, "numericValueAtom", "Limit clause missing number");
|
|
5014
|
+
const count = visit(asCstNode(ctx.numericValueAtom[0]));
|
|
4676
5015
|
const segments = ctx.identSegment;
|
|
4677
5016
|
if (segments && segments.length > 0) {
|
|
4678
5017
|
const parts = [];
|
|
@@ -4683,13 +5022,23 @@ function visitLimitClause(ctx, visit) {
|
|
|
4683
5022
|
}
|
|
4684
5023
|
return { type: "limit", count };
|
|
4685
5024
|
}
|
|
4686
|
-
function visitOffsetClause(ctx) {
|
|
4687
|
-
requireFirst(ctx, "
|
|
5025
|
+
function visitOffsetClause(ctx, visit) {
|
|
5026
|
+
requireFirst(ctx, "numericValueAtom", "Offset clause missing number");
|
|
4688
5027
|
return {
|
|
4689
5028
|
type: "offset",
|
|
4690
|
-
count:
|
|
5029
|
+
count: visit(asCstNode(ctx.numericValueAtom[0]))
|
|
4691
5030
|
};
|
|
4692
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
|
+
}
|
|
4693
5042
|
function visitSelectList(ctx, visit) {
|
|
4694
5043
|
const items = [];
|
|
4695
5044
|
if (ctx.selectItem) {
|
|
@@ -4816,7 +5165,10 @@ var NqlCstVisitor = class extends BaseCstVisitor {
|
|
|
4816
5165
|
return visitLimitClause(ctx, this.v);
|
|
4817
5166
|
}
|
|
4818
5167
|
offsetClause(ctx) {
|
|
4819
|
-
return visitOffsetClause(ctx);
|
|
5168
|
+
return visitOffsetClause(ctx, this.v);
|
|
5169
|
+
}
|
|
5170
|
+
numericValueAtom(ctx) {
|
|
5171
|
+
return visitNumericValueAtom(ctx);
|
|
4820
5172
|
}
|
|
4821
5173
|
selectList(ctx) {
|
|
4822
5174
|
return visitSelectList(ctx, this.v);
|
|
@@ -4899,6 +5251,9 @@ var NqlCstVisitor = class extends BaseCstVisitor {
|
|
|
4899
5251
|
primaryExpr(ctx) {
|
|
4900
5252
|
return visitPrimaryExpr(ctx, this.v);
|
|
4901
5253
|
}
|
|
5254
|
+
namedParamExpr(ctx) {
|
|
5255
|
+
return visitNamedParamExpr(ctx);
|
|
5256
|
+
}
|
|
4902
5257
|
// -- CASE --
|
|
4903
5258
|
caseExpr(ctx) {
|
|
4904
5259
|
return visitCaseExpr(ctx, this.v);
|
|
@@ -5087,13 +5442,16 @@ function compile(input, schema, options, compilerOptions) {
|
|
|
5087
5442
|
warnings: parseResult.warnings
|
|
5088
5443
|
};
|
|
5089
5444
|
} catch (err) {
|
|
5090
|
-
const
|
|
5445
|
+
const semanticError = err instanceof NqlSemanticException;
|
|
5446
|
+
const code = semanticError ? err.code : NqlErrorCodes.SEM_UNREACHABLE;
|
|
5091
5447
|
return {
|
|
5092
5448
|
success: false,
|
|
5093
5449
|
errors: [
|
|
5094
5450
|
{
|
|
5095
5451
|
code,
|
|
5096
|
-
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
|
|
5097
5455
|
}
|
|
5098
5456
|
],
|
|
5099
5457
|
warnings: []
|
|
@@ -5149,8 +5507,9 @@ function hasColumnValidatorShape(obj) {
|
|
|
5149
5507
|
/* v8 ignore start — defensive: parser guarantees target and source identifiers -- @preserve */
|
|
5150
5508
|
/* v8 ignore next — defensive: parser guarantees withQuery, query, or mutationPipeline -- @preserve */
|
|
5151
5509
|
/* v8 ignore next — defensive: parser guarantees one of the clause alternatives -- @preserve */
|
|
5510
|
+
/* v8 ignore next — defensive: parser guarantees NumberLiteral or NamedParam -- @preserve */
|
|
5152
5511
|
/* v8 ignore next — defensive: parser guarantees query or identSegment -- @preserve */
|
|
5153
5512
|
|
|
5154
|
-
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 };
|
|
5155
5514
|
//# sourceMappingURL=index.js.map
|
|
5156
5515
|
//# sourceMappingURL=index.js.map
|