@dbsp/nql 1.0.4 → 1.2.0

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