@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/dist/index.js CHANGED
@@ -1,6 +1,9 @@
1
- import { isRankingWindowFunction } from '@dbsp/types';
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
- // src/compiler/expression-utils.ts
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
- "count",
285
- "sum",
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
- includeLimits.set(lc.relation, lc.count);
585
+ const includeLimit = isParamIntent(count) ? count.value : count;
586
+ includeLimits.set(lc.relation, includeLimit);
479
587
  } else {
480
- limit = lc.count;
588
+ limit = count;
481
589
  }
482
590
  break;
483
591
  }
484
592
  case "offset":
485
- offset = clause.count;
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
- return {
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
- return {
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: coerceToStringKey(comp.right, "LIKE 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
- return {
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
- return {
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 rawValues = ctx.params[anyExpr.paramName];
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 but received ${rawValues === void 0 ? "undefined (parameter not bound)" : typeof rawValues}`
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 values2 = rawValues;
1000
- return { kind: "any", field: field2, values: values2 };
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
- if (lower !== null && typeof lower !== "number" && typeof lower !== "string" && !(lower instanceof Date)) {
1081
- const got = typeof lower === "object" ? "path reference" : JSON.stringify(lower);
1082
- throw new NqlSemanticException(
1083
- NqlErrorCodes.SEM_INVALID_SYNTAX,
1084
- `BETWEEN lower bound must be a literal number, string, or date \u2014 got ${got}. Use a param or hardcoded value.`
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: { lower, upper }
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
- return {
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
- return {
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
- // src/compiler/compile-mutation.ts
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[assignment.column] = expressionToValue(assignment.value);
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 && { limit: insertFrom.limit }
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[assignment.column] = expressionToValue(assignment.value);
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[assignment.column] = expressionToValue(assignment.value);
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 && { limit: upsertFrom.limit }
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
- if (ctx.currentFromTable) {
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
- field = expressionToField(expr.args[0]) ?? expressionToSql(expr.args[0]);
1631
- if (ctx.currentFromTable && field !== "*" && !field.includes(".")) {
1632
- ctx.validator?.validateColumn(ctx.currentFromTable, field);
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((a) => expressionToField(a) ?? expressionToValue(a)) : void 0;
1636
- return {
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
- ...extraArgs && { extraArgs }
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: expr.name,
1650
- args: expr.args.map((a) => expressionToField(a) ?? expressionToValue(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 = expressionToField(windowExpr.args[0]) ?? expressionToSql(windowExpr.args[0]);
1917
+ field = expressionToValidatedField(windowExpr.args[0], ctx) ?? expressionToSql(windowExpr.args[0]);
1660
1918
  }
1661
1919
  let offset;
1662
1920
  let defaultValue;
1663
- if (fn === "lag" || fn === "lead") {
1921
+ if (windowFn === "lag" || windowFn === "lead") {
1664
1922
  if (windowExpr.args.length > 1) {
1665
- offset = expressionToValue(windowExpr.args[1]);
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 = expressionToField(e) ?? expressionToSql(e);
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 = expressionToField(o.expression) ?? expressionToSql(o.expression);
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 ?? fn;
1690
- if (isRankingWindowFunction(fn)) {
1947
+ const alias = exprItem.alias ?? windowFn;
1948
+ if (isRankingWindowFunction(windowFn)) {
1691
1949
  return {
1692
1950
  kind: "window",
1693
- function: fn,
1951
+ function: windowFn,
1694
1952
  alias,
1695
1953
  over
1696
1954
  };
1697
1955
  }
1698
- if (fn === "lag" || fn === "lead") {
1956
+ if (windowFn === "lag" || windowFn === "lead") {
1699
1957
  return {
1700
1958
  kind: "window",
1701
- function: fn,
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: fn,
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
- if (ctx.currentFromTable) {
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 = expressionToField(expr.left);
1739
- const rightField = expressionToField(expr.right);
2000
+ const leftField = expressionToValidatedField(expr.left, ctx);
2001
+ const rightField = expressionToValidatedField(expr.right, ctx);
1740
2002
  return {
1741
2003
  kind: "arithmetic",
1742
- left: leftField ?? expressionToValue(expr.left),
2004
+ left: leftField ?? expressionToSelectValue(expr.left, ctx, fns),
1743
2005
  operator: expr.operator,
1744
- right: rightField ?? expressionToValue(expr.right),
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 = expressionToField(unary.operand);
2013
+ const operandField = expressionToValidatedField(unary.operand, ctx);
1752
2014
  return {
1753
2015
  kind: "arithmetic",
1754
2016
  left: -1,
1755
2017
  operator: "*",
1756
- right: operandField ?? expressionToValue(unary.operand),
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 = expressionToField(expr.base);
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 = expressionToField(caseExpr.subject);
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: caseExpr.whenClauses.map((wc) => ({
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
- return {
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
- const value = expr.type === "null" ? null : expr.value;
1905
- return {
1906
- kind: "literal",
1907
- value
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 = /* @__PURE__ */ new Set([
1917
- "json_extract",
1918
- "json_extract_text",
1919
- "json_path",
1920
- "json_path_text"
1921
- ]);
1922
- function compileJsonFunction(fn, args, alias) {
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 keys = args.slice(1).map((a) => coerceToStringKey(a, `${fn}() path argument`));
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: pathStr,
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: options?.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.CONSUME(NumberLiteral);
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.CONSUME(NumberLiteral);
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" number ] ;
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.CONSUME(NumberLiteral);
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" number ] ;
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.CONSUME(NumberLiteral);
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.NumberLiteral ? parseInt(getImage(ctx.NumberLiteral[0]), 10) : void 0
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.NumberLiteral ? parseInt(getImage(ctx.NumberLiteral[0]), 10) : void 0
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, "NumberLiteral", "Limit clause missing number");
4675
- const count = parseInt(getImage(ctx.NumberLiteral[0]), 10);
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, "NumberLiteral", "Offset clause missing number");
5025
+ function visitOffsetClause(ctx, visit) {
5026
+ requireFirst(ctx, "numericValueAtom", "Offset clause missing number");
4688
5027
  return {
4689
5028
  type: "offset",
4690
- count: parseInt(getImage(ctx.NumberLiteral[0]), 10)
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 code = err instanceof NqlSemanticException ? err.code : NqlErrorCodes.SEM_UNREACHABLE;
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