@dbsp/nql 1.1.0 → 1.3.0

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