@dbsp/nql 1.0.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.
@@ -0,0 +1,1055 @@
1
+ import { QueryIntent, CteQueryIntent, MutationIntent, SetOperationIntent } from '@dbsp/types';
2
+ export { DeleteIntent, ExpressionIntent, IncludeIntent, InsertIntent, MutationIntent, OrderByIntent, QueryIntent, SelectAllIntent, SelectFieldsIntent, SelectIntent, SelectWithExpressionsIntent, UpdateIntent, UpsertIntent, WhereAndIntent, WhereComparisonIntent, WhereInIntent, WhereIntent, WhereLikeIntent, WhereNotIntent, WhereNullIntent, WhereOrIntent, WhereRangeIntent } from '@dbsp/types';
3
+ import * as chevrotain from 'chevrotain';
4
+ import { Lexer, CstParser, CstNode, IToken } from 'chevrotain';
5
+
6
+ /**
7
+ * NQL Abstract Syntax Tree Types
8
+ *
9
+ * These types represent the parsed structure of NQL queries,
10
+ * independent of the underlying database schema.
11
+ */
12
+ /**
13
+ * A complete NQL program with let bindings and statements
14
+ * Supports multiple statements for mutation chaining
15
+ */
16
+ interface NqlProgram {
17
+ type: 'program';
18
+ statements: NqlStatement[];
19
+ }
20
+ type NqlStatement = NqlQuery | NqlMutationPipeline | NqlWithQuery;
21
+ interface NqlCteItem {
22
+ type: 'cteItem';
23
+ name: string;
24
+ query: NqlQuery;
25
+ }
26
+ interface NqlWithQuery {
27
+ type: 'withQuery';
28
+ ctes: NqlCteItem[];
29
+ query: NqlQuery;
30
+ }
31
+ interface NqlQuery {
32
+ type: 'query';
33
+ table: string;
34
+ clauses: NqlClause[];
35
+ }
36
+ interface NqlMutationPipeline {
37
+ type: 'mutationPipeline';
38
+ mutation: NqlMutation;
39
+ clauses: NqlMutationClause[];
40
+ }
41
+ type NqlMutation = NqlInsert | NqlInsertFrom | NqlUpdate | NqlDelete | NqlUpsert | NqlUpsertFrom;
42
+ type NqlMutationClause = NqlSelectClause | NqlBindClause;
43
+ type NqlClause = NqlWhereClause | NqlSelectClause | NqlFlatClause | NqlGroupByClause | NqlOrderByClause | NqlLimitClause | NqlOffsetClause | NqlBindClause | NqlSetClause | NqlLockClause;
44
+ /**
45
+ * Where clause - position determines compilation:
46
+ * - Before `group by` → SQL WHERE (aggregates forbidden)
47
+ * - After `group by` → SQL HAVING (aggregates allowed)
48
+ */
49
+ interface NqlWhereClause {
50
+ type: 'where';
51
+ condition: NqlExpression;
52
+ }
53
+ interface NqlSelectClause {
54
+ type: 'select';
55
+ distinct: boolean;
56
+ items: NqlSelectItem[];
57
+ }
58
+ /**
59
+ * NQL v2.1: Forces JOIN strategy instead of json_agg for relation includes
60
+ */
61
+ interface NqlFlatClause {
62
+ type: 'flat';
63
+ }
64
+ interface NqlGroupByClause {
65
+ type: 'groupBy';
66
+ expressions: NqlExpression[];
67
+ }
68
+ interface NqlOrderByClause {
69
+ type: 'orderBy';
70
+ items: NqlOrderItem[];
71
+ }
72
+ interface NqlLimitClause {
73
+ type: 'limit';
74
+ count: number;
75
+ relation?: string;
76
+ }
77
+ interface NqlOffsetClause {
78
+ type: 'offset';
79
+ count: number;
80
+ }
81
+ /**
82
+ * Capture mutation result into a variable (for chained mutations)
83
+ * Used with `bind` keyword: `insert ... | bind result | ...`
84
+ */
85
+ interface NqlBindClause {
86
+ type: 'bind';
87
+ name: string;
88
+ }
89
+ /**
90
+ * Set operation clause: UNION, INTERSECT, EXCEPT
91
+ * The right operand is either an inline query or a bound name reference.
92
+ */
93
+ interface NqlSetClause {
94
+ type: 'setOperation';
95
+ op: 'union' | 'intersect' | 'except';
96
+ all: boolean;
97
+ /** Inline sub-query (parenthesized) */
98
+ right?: NqlQuery;
99
+ /** Bound name reference (via | bind) */
100
+ boundName?: string;
101
+ }
102
+ /**
103
+ * Lock clause (E15): FOR UPDATE | FOR SHARE | FOR NO KEY UPDATE | FOR KEY SHARE
104
+ * with optional wait policy: SKIP LOCKED | NOWAIT
105
+ */
106
+ interface NqlLockClause {
107
+ type: 'lock';
108
+ strength: 'forUpdate' | 'forShare' | 'forNoKeyUpdate' | 'forKeyShare';
109
+ waitPolicy: 'block' | 'skipLocked' | 'noWait';
110
+ }
111
+ type NqlSelectItem = NqlSelectStar | NqlSelectRelationStar | NqlSelectExpression;
112
+ interface NqlSelectStar {
113
+ type: 'star';
114
+ }
115
+ interface NqlSelectRelationStar {
116
+ type: 'relationStar';
117
+ relation: string[];
118
+ }
119
+ interface NqlSelectExpression {
120
+ type: 'expression';
121
+ expression: NqlExpression;
122
+ alias?: string;
123
+ }
124
+ interface NqlJoinSpec {
125
+ relation: string;
126
+ params?: NqlJoinParam[] | undefined;
127
+ via?: string | undefined;
128
+ condition?: NqlExpression | undefined;
129
+ }
130
+ interface NqlJoinParam {
131
+ name: string;
132
+ value: NqlLiteral;
133
+ }
134
+ interface NqlOrderItem {
135
+ expression: NqlExpression;
136
+ direction: 'asc' | 'desc';
137
+ }
138
+ type NqlExpression = NqlBinaryExpression | NqlUnaryExpression | NqlComparisonExpression | NqlRangeOpExpression | NqlInExpression | NqlAnyExpression | NqlBetweenExpression | NqlIsNullExpression | NqlExistsExpression | NqlRelationFilterExpression | NqlFunctionCall | NqlWindowExpression | NqlCaseExpression | NqlPathExpression | NqlJsonAccessExpression | NqlJsonComparisonExpression | NqlLiteral | NqlSubquery | NqlVariableRef;
139
+ interface NqlBinaryExpression {
140
+ type: 'binary';
141
+ operator: 'and' | 'or' | '+' | '-' | '*' | '/' | '%';
142
+ left: NqlExpression;
143
+ right: NqlExpression;
144
+ }
145
+ interface NqlUnaryExpression {
146
+ type: 'unary';
147
+ operator: 'not' | '-';
148
+ operand: NqlExpression;
149
+ }
150
+ interface NqlComparisonExpression {
151
+ type: 'comparison';
152
+ operator: '=' | '!=' | '<' | '>' | '<=' | '>=' | 'like';
153
+ left: NqlExpression;
154
+ right: NqlExpression;
155
+ }
156
+ /**
157
+ * PostgreSQL range operator expression: column RANGE_OP range_literal
158
+ * Examples: dateRange overlaps [2024-01-01,2024-12-31)
159
+ */
160
+ interface NqlRangeOpExpression {
161
+ type: 'rangeOp';
162
+ operator: 'overlaps' | 'contains' | 'containedBy';
163
+ left: NqlExpression;
164
+ range?: NqlRangeLiteral;
165
+ scalar?: NqlLiteral;
166
+ }
167
+ interface NqlInExpression {
168
+ type: 'in';
169
+ negated: boolean;
170
+ expression: NqlExpression;
171
+ values: NqlExpression[] | NqlSubquery | NqlDateRangeLiteral;
172
+ }
173
+ /**
174
+ * BATCH-001: ANY expression — col = ANY(:paramName)
175
+ * Compiles to WhereAnyIntent with values resolved from named parameters.
176
+ */
177
+ interface NqlAnyExpression {
178
+ type: 'any';
179
+ column: NqlExpression;
180
+ paramName: string;
181
+ }
182
+ /**
183
+ * BETWEEN is a ternary operator: expr BETWEEN low AND high
184
+ */
185
+ interface NqlBetweenExpression {
186
+ type: 'between';
187
+ expression: NqlExpression;
188
+ low: NqlExpression;
189
+ high: NqlExpression;
190
+ }
191
+ /**
192
+ * IS NULL / IS NOT NULL check
193
+ */
194
+ interface NqlIsNullExpression {
195
+ type: 'isNull';
196
+ expression: NqlExpression;
197
+ negated: boolean;
198
+ }
199
+ interface NqlExistsExpression {
200
+ type: 'exists';
201
+ negated: boolean;
202
+ subquery: NqlSubquery;
203
+ }
204
+ /**
205
+ * SPEC-002: Relation filter expression for cross-table pseudo-columns
206
+ *
207
+ * Quantifier modes:
208
+ * - 'some' (default): EXISTS - at least one related record matches
209
+ * - 'none': NOT EXISTS - no related record matches
210
+ * - 'every': ALL - every related record matches (vacuous truth if empty)
211
+ *
212
+ * Syntax forms:
213
+ * - Implicit: `posts.featured = true` (some), `not posts.featured = true` (none), `all posts.featured = true` (every)
214
+ * - Explicit: `some(posts).featured = true`, `none(posts).featured = true`, `every(posts).featured = true`
215
+ * - With alias: `posts as p, p.featured = true and p.published = true`
216
+ */
217
+ interface NqlRelationFilterExpression {
218
+ type: 'relationFilter';
219
+ /** Relation path (can be multi-hop, e.g., ['author', 'company']) */
220
+ relation: string[];
221
+ /** The filter condition applied to the relation */
222
+ condition: NqlExpression;
223
+ /** Quantifier mode */
224
+ mode: 'some' | 'none' | 'every';
225
+ /** Optional alias for the relation (for complex conditions) */
226
+ alias?: string;
227
+ }
228
+ interface NqlFunctionCall {
229
+ type: 'function';
230
+ name: string;
231
+ args: NqlExpression[];
232
+ /** Whether DISTINCT modifier was used: count(distinct col) */
233
+ distinct?: boolean;
234
+ }
235
+ /**
236
+ * Window expression: function OVER (PARTITION BY ... ORDER BY ...)
237
+ */
238
+ interface NqlWindowExpression {
239
+ type: 'window';
240
+ function: string;
241
+ args: NqlExpression[];
242
+ partitionBy: NqlExpression[];
243
+ orderBy: NqlOrderItem[];
244
+ }
245
+ /**
246
+ * CASE expression: CASE WHEN cond THEN result [WHEN ...] [ELSE default] END
247
+ */
248
+ interface NqlCaseExpression {
249
+ type: 'case';
250
+ /** Subject expression for simple CASE (CASE expr WHEN val ...) */
251
+ subject?: NqlExpression;
252
+ whenClauses: Array<{
253
+ condition: NqlExpression;
254
+ result: NqlExpression;
255
+ }>;
256
+ elseClause?: NqlExpression;
257
+ }
258
+ /**
259
+ * JSON access expression: col->'a'->'b'->>'c'
260
+ * Chained path extraction with final mode determined by last operator.
261
+ */
262
+ interface NqlJsonAccessExpression {
263
+ type: 'jsonAccess';
264
+ /** Base expression (typically a path/column reference) */
265
+ base: NqlExpression;
266
+ /** Keys to extract in order */
267
+ path: string[];
268
+ /** 'json' = ->, 'text' = ->> (determined by LAST operator) */
269
+ mode: 'json' | 'text';
270
+ }
271
+ /**
272
+ * JSON comparison expression: col @> val, col <@ val, col ? key
273
+ */
274
+ interface NqlJsonComparisonExpression {
275
+ type: 'jsonComparison';
276
+ /** Left expression (column/field) */
277
+ left: NqlExpression;
278
+ /** Operator: @> (contains), <@ (containedBy), ? (exists) */
279
+ operator: '@>' | '<@' | '?';
280
+ /** Right expression (value/key) */
281
+ right: NqlExpression;
282
+ }
283
+ interface NqlPathExpression {
284
+ type: 'path';
285
+ segments: string[];
286
+ /** Optional depth hint for scoped traversal: ascendant[3].column */
287
+ depthHint?: number;
288
+ }
289
+ interface NqlSubquery {
290
+ type: 'subquery';
291
+ query: NqlQuery;
292
+ }
293
+ /**
294
+ * Reference to a let-bound variable
295
+ */
296
+ interface NqlVariableRef {
297
+ type: 'variable';
298
+ name: string;
299
+ }
300
+ type NqlLiteral = NqlStringLiteral | NqlNumberLiteral | NqlBooleanLiteral | NqlNullLiteral | NqlDateRangeLiteral | NqlRangeLiteral;
301
+ interface NqlStringLiteral {
302
+ type: 'string';
303
+ value: string;
304
+ }
305
+ interface NqlNumberLiteral {
306
+ type: 'number';
307
+ value: number;
308
+ }
309
+ interface NqlBooleanLiteral {
310
+ type: 'boolean';
311
+ value: boolean;
312
+ }
313
+ interface NqlNullLiteral {
314
+ type: 'null';
315
+ }
316
+ /**
317
+ * Natural language date range (e.g., 'last 7 days', 'this month')
318
+ * Semantic layer validates and converts to actual dates
319
+ */
320
+ interface NqlDateRangeLiteral {
321
+ type: 'dateRange';
322
+ value: string;
323
+ }
324
+ /**
325
+ * PostgreSQL range literal (e.g., '[2024-01-01,2024-12-31)')
326
+ * Used with range operators: overlaps, contains, containedBy
327
+ */
328
+ interface NqlRangeLiteral {
329
+ type: 'rangeLiteral';
330
+ value: string;
331
+ lowerInclusive: boolean;
332
+ upperInclusive: boolean;
333
+ lower: string;
334
+ upper: string;
335
+ }
336
+ interface NqlInsert {
337
+ type: 'insert';
338
+ table: string;
339
+ /** Multi-row support: each element is a row's assignments */
340
+ rows: NqlAssignment[][];
341
+ }
342
+ /**
343
+ * INSERT INTO target FROM source query.
344
+ * @example `insert into archived_users from users | where active = false`
345
+ */
346
+ interface NqlInsertFrom {
347
+ type: 'insert_from';
348
+ /** Target table to insert into */
349
+ table: string;
350
+ /** Source table to select from */
351
+ source: string;
352
+ /** Optional column mapping (default: same columns) */
353
+ columns?: string[] | undefined;
354
+ /** WHERE clause to filter source rows */
355
+ where?: NqlExpression | undefined;
356
+ /** LIMIT clause to restrict rows */
357
+ limit?: number | undefined;
358
+ }
359
+ interface NqlUpdate {
360
+ type: 'update';
361
+ table: string;
362
+ assignments: NqlAssignment[];
363
+ where?: NqlExpression;
364
+ }
365
+ interface NqlDelete {
366
+ type: 'delete';
367
+ table: string;
368
+ where?: NqlExpression;
369
+ }
370
+ interface NqlUpsert {
371
+ type: 'upsert';
372
+ table: string;
373
+ conflictColumns: string[];
374
+ assignments: NqlAssignment[];
375
+ where?: NqlExpression;
376
+ }
377
+ /**
378
+ * UPSERT INTO target ON conflictColumns FROM source [WHERE ...] [LIMIT ...]
379
+ * Bulk upsert by selecting from another table or bound CTE
380
+ */
381
+ interface NqlUpsertFrom {
382
+ type: 'upsert_from';
383
+ table: string;
384
+ conflictColumns: string[];
385
+ source: string;
386
+ columns?: string[] | undefined;
387
+ where?: NqlExpression | undefined;
388
+ limit?: number | undefined;
389
+ }
390
+ interface NqlAssignment {
391
+ column: string;
392
+ value: NqlExpression;
393
+ }
394
+
395
+ interface CompileResult {
396
+ readonly query?: QueryIntent;
397
+ /** CTE query (WITH clause): wraps outer QueryIntent in CteQueryIntent */
398
+ readonly cteQuery?: CteQueryIntent;
399
+ readonly mutation?: MutationIntent;
400
+ readonly returning?: readonly string[];
401
+ /** Named bindings from `| bind X` clauses (CTE source queries) */
402
+ readonly bindings?: ReadonlyMap<string, QueryIntent>;
403
+ /**
404
+ * Named mutation bindings from `mutation | select cols | bind X` clauses.
405
+ * When a mutation has RETURNING and is bound, the original MutationIntent is stored here
406
+ * so the adapter can compile it as a CTE: `WITH X AS (INSERT ... RETURNING cols) ...`
407
+ * The corresponding synthetic QueryIntent is also stored in `bindings` for reference resolution.
408
+ */
409
+ readonly mutationBindings?: ReadonlyMap<string, MutationIntent>;
410
+ /** Set operation (UNION/INTERSECT/EXCEPT) wrapping two queries */
411
+ readonly setOperation?: SetOperationIntent;
412
+ }
413
+ /**
414
+ * Duck-type interface for schema-based column validation.
415
+ * Loose coupling: ModelIR from @dbsp/core satisfies this shape without direct import.
416
+ */
417
+ interface ColumnValidatorSchema {
418
+ getTable(name: string): {
419
+ readonly columns: readonly {
420
+ readonly name: string;
421
+ }[];
422
+ readonly pseudoColumns?: readonly {
423
+ readonly parentRole: string;
424
+ readonly childRole: string;
425
+ }[];
426
+ } | undefined;
427
+ getRelationsFrom(sourceTable: string): readonly {
428
+ readonly name: string;
429
+ readonly target: string;
430
+ }[];
431
+ }
432
+ /**
433
+ * Options for the NQL compiler.
434
+ * Allows dynamic pseudo-column keywords from schema configuration.
435
+ */
436
+ interface NqlCompilerOptions {
437
+ readonly pseudoColumnKeywords?: readonly string[];
438
+ readonly recursiveKeywords?: readonly string[];
439
+ /** BATCH-001: Named parameters for ANY(:param) expressions */
440
+ readonly params?: Readonly<Record<string, unknown>>;
441
+ }
442
+
443
+ /**
444
+ * NQL Compiler
445
+ *
446
+ * Transforms NQL AST to IntentAST.
447
+ * Thin coordinator: delegates to domain modules for actual compilation.
448
+ *
449
+ * @since ARCH-007: IntentAST types centralized in @dbsp/types to avoid circular dependencies.
450
+ */
451
+
452
+ /**
453
+ * Compiler that transforms NQL AST to IntentAST.
454
+ * Thin coordinator: holds context, wires domain modules via CompilerFns.
455
+ */
456
+ declare class NqlCompiler {
457
+ private readonly ctx;
458
+ private readonly fns;
459
+ constructor(options?: NqlCompilerOptions, schema?: ColumnValidatorSchema);
460
+ /**
461
+ * Compile an NQL program to IntentAST.
462
+ */
463
+ compile(program: NqlProgram): CompileResult;
464
+ private compileSingleStatement;
465
+ }
466
+ /**
467
+ * Create a compiler instance.
468
+ */
469
+ declare function createCompiler(options?: NqlCompilerOptions, schema?: ColumnValidatorSchema): NqlCompiler;
470
+
471
+ /**
472
+ * NQL Error Types
473
+ *
474
+ * Structured error types for lexer, parser, and semantic errors.
475
+ */
476
+ /** Source location for error reporting */
477
+ interface SourceLocation {
478
+ line: number;
479
+ column: number;
480
+ offset: number;
481
+ }
482
+ /** Base error interface for all NQL errors */
483
+ interface NqlError {
484
+ code: string;
485
+ message: string;
486
+ location?: SourceLocation | undefined;
487
+ suggestion?: string | undefined;
488
+ }
489
+ /** Parser errors (syntax errors) */
490
+ interface NqlParseError extends NqlError {
491
+ code: `ERR-PARSE-${string}`;
492
+ expected?: string[] | undefined;
493
+ found?: string | undefined;
494
+ }
495
+ /** Semantic errors (validation failures) */
496
+ interface NqlSemanticError extends NqlError {
497
+ code: `ERR-SEM-${string}`;
498
+ relatedSymbol?: string | undefined;
499
+ }
500
+ /** Warning (non-fatal issues) */
501
+ interface NqlWarning {
502
+ code: string;
503
+ message: string;
504
+ location?: SourceLocation | undefined;
505
+ suggestion?: string | undefined;
506
+ }
507
+
508
+ declare const allTokens: chevrotain.TokenType[];
509
+ declare const NqlLexer: Lexer;
510
+
511
+ /**
512
+ * NQL Parser using Chevrotain CstParser
513
+ */
514
+ declare class NqlParser extends CstParser {
515
+ constructor();
516
+ /**
517
+ * Check if a token type can appear as an identifier segment.
518
+ * This includes regular identifiers, quoted identifiers, and pseudo-column keywords
519
+ * (parent, child, ascendant, descendant) which can be used in paths.
520
+ */
521
+ private isIdentifierLike;
522
+ /**
523
+ * program = { statement } ;
524
+ * Supports multiple statements (queries or mutations)
525
+ */
526
+ program: chevrotain.ParserMethod<[], CstNode>;
527
+ /**
528
+ * statement = withQuery | query | mutation_pipeline ;
529
+ * withQuery is tried first via GATE to avoid ambiguity with 'with' as identifier.
530
+ */
531
+ private statement;
532
+ /**
533
+ * withQuery = "with" cteList query ;
534
+ */
535
+ withQuery: chevrotain.ParserMethod<[], CstNode>;
536
+ /**
537
+ * cteList = cteItem { "," cteItem } ;
538
+ */
539
+ private cteList;
540
+ /**
541
+ * cteItem = identSegment "as" "(" query ")" ;
542
+ */
543
+ private cteItem;
544
+ /**
545
+ * query = table_ref { "|" query_clause } ;
546
+ */
547
+ query: chevrotain.ParserMethod<[], CstNode>;
548
+ /**
549
+ * table_ref = ident_segment ;
550
+ */
551
+ private tableRef;
552
+ /**
553
+ * query_clause = where_clause | select_clause | flat_clause
554
+ * | group_clause | order_clause | limit_clause | offset_clause | bind_clause ;
555
+ */
556
+ private queryClause;
557
+ /**
558
+ * where_clause = "where" boolean_expr ;
559
+ */
560
+ private whereClause;
561
+ /**
562
+ * select_clause = "select" [ "distinct" ] select_list ;
563
+ */
564
+ private selectClause;
565
+ /**
566
+ * flat_clause = "flat" ;
567
+ * NQL v2.1: Forces JOIN strategy instead of json_agg for relation includes
568
+ */
569
+ private flatClause;
570
+ /**
571
+ * group_clause = "group" "by" expr_list ;
572
+ */
573
+ private groupClause;
574
+ /**
575
+ * order_clause = "order" "by" order_list ;
576
+ */
577
+ private orderClause;
578
+ /**
579
+ * limit_clause = "limit" [ident_segment ("." ident_segment)*] NUMBER ;
580
+ */
581
+ private limitClause;
582
+ /**
583
+ * offset_clause = "offset" NUMBER ;
584
+ */
585
+ private offsetClause;
586
+ /**
587
+ * lock_clause = lock_strength [ lock_wait_policy ] ;
588
+ * lock_strength = "for update" | "for share" | "for no key update" | "for key share" ;
589
+ * lock_wait_policy = "skip locked" | "nowait" ;
590
+ */
591
+ private lockClause;
592
+ /**
593
+ * select_list = select_item { "," select_item } ;
594
+ */
595
+ private selectList;
596
+ /**
597
+ * select_item = "*" | path_star | expr [ "as" ident_segment ] ;
598
+ * where path_star = ident { "." ident } "." "*"
599
+ */
600
+ private selectItem;
601
+ /**
602
+ * relation_star_expr = ident { "." ident } "." "*"
603
+ * Parses `relation.*` or `a.b.c.*` patterns
604
+ */
605
+ private relationStarExpr;
606
+ /**
607
+ * Check if current position looks like `path.*`
608
+ */
609
+ private isRelationStar;
610
+ /**
611
+ * order_list = order_item { "," order_item } ;
612
+ */
613
+ private orderList;
614
+ /**
615
+ * order_item = expr [ "asc" | "desc" ] ;
616
+ */
617
+ private orderItem;
618
+ /**
619
+ * boolean_expr = or_expr ;
620
+ */
621
+ private booleanExpr;
622
+ /**
623
+ * or_expr = and_expr { "or" and_expr } ;
624
+ */
625
+ private orExpr;
626
+ /**
627
+ * and_expr = not_expr { "and" not_expr } ;
628
+ */
629
+ private andExpr;
630
+ /**
631
+ * not_expr = [ "not" ] primary_cond ;
632
+ */
633
+ private notExpr;
634
+ /**
635
+ * primary_cond = "(" boolean_expr ")"
636
+ * | quantified_relation_filter -- SPEC-002: some()/none()/every()
637
+ * | all_relation_filter -- SPEC-002: all relation.col = val
638
+ * | exists_check
639
+ * | comparison / between / in / is_null ;
640
+ */
641
+ private primaryCond;
642
+ /**
643
+ * comparison_suffix = comp_op expr ;
644
+ */
645
+ private comparisonSuffix;
646
+ /**
647
+ * json_comparison_suffix = ("@>" | "<@" | "?") expression ;
648
+ * JSONB containment and key-existence operators.
649
+ */
650
+ private jsonComparisonSuffix;
651
+ /**
652
+ * comp_op = "=" | "!=" | "<" | ">" | "<=" | ">=" | "like" ;
653
+ * Note: Range operators (overlaps, contains, containedBy) are handled
654
+ * separately in rangeOpSuffix to allow (value,value) range syntax.
655
+ */
656
+ private compOp;
657
+ /**
658
+ * range_op = "overlaps" | "contains" | "containedBy" ;
659
+ * PostgreSQL range operators - handled separately to support (value,value) syntax.
660
+ */
661
+ private rangeOp;
662
+ /**
663
+ * range_op_suffix = range_op (range_literal | literal) ;
664
+ * Example: overlaps [1,10) or contains (0,100) or contains 25
665
+ * Note: contains can take a scalar value (e.g., contains 25 checks if range contains the value)
666
+ */
667
+ private rangeOpSuffix;
668
+ /**
669
+ * between_suffix = "between" expr "and" expr ;
670
+ */
671
+ private betweenSuffix;
672
+ /**
673
+ * exists_check = [ "not" ] "exists" "(" scalar_subquery ")" ;
674
+ */
675
+ private existsCheck;
676
+ /**
677
+ * SPEC-002: Explicit quantifier function
678
+ * quantified_relation_filter = quantifier "(" path_expr ")" "." ident_segment comp_op expr
679
+ * | quantifier "(" path_expr [ "as" IDENT ] "," boolean_expr ")" ;
680
+ * quantifier = "some" | "none" | "every" ;
681
+ *
682
+ * Examples:
683
+ * some(posts).featured = true
684
+ * none(posts).published = true
685
+ * every(posts).status = 'approved'
686
+ * some(posts as p, p.featured = true and p.published = true)
687
+ */
688
+ private quantifiedRelationFilter;
689
+ /**
690
+ * SPEC-002: ALL quantifier prefix for implicit relation filter
691
+ * all_relation_filter = "all" path_expr comp_op expr ;
692
+ *
693
+ * The path_expr contains both the relation and column, e.g., posts.featured
694
+ * The visitor will split it: relation = all but last segment, column = last segment
695
+ *
696
+ * Examples:
697
+ * all posts.featured = true (relation: posts, column: featured)
698
+ * all author.posts.published = true (relation: author.posts, column: published)
699
+ */
700
+ private allRelationFilter;
701
+ /**
702
+ * in_suffix = [ "not" ] "in" ( "(" value_list ")" | "(" scalar_subquery ")" | date_range_literal ) ;
703
+ */
704
+ /**
705
+ * any_suffix = '=' 'ANY' '(' ':' identifier ')' ;
706
+ * BATCH-001: Parses the ANY(:paramName) suffix after a column expression.
707
+ * Example: id = ANY(:ids)
708
+ */
709
+ private anySuffix;
710
+ private inSuffix;
711
+ /**
712
+ * is_null_suffix = "is" [ "not" ] "null" ;
713
+ */
714
+ private isNullSuffix;
715
+ /**
716
+ * Check if inside parentheses we have a scalar subquery
717
+ * A scalar subquery MUST have at least one pipe
718
+ */
719
+ private isScalarSubqueryInParen;
720
+ /**
721
+ * Check if we're looking at a scalar subquery (for expression context)
722
+ */
723
+ private isScalarSubquery;
724
+ /**
725
+ * expr = add_expr ;
726
+ */
727
+ private expression;
728
+ /**
729
+ * add_expr = mul_expr { ("+" | "-") mul_expr } ;
730
+ */
731
+ private addExpr;
732
+ /**
733
+ * mul_expr = unary_expr { ("*" | "/" | "%") unary_expr } ;
734
+ */
735
+ private mulExpr;
736
+ /**
737
+ * unary_expr = [ "-" ] json_access_expr ;
738
+ */
739
+ private unaryExpr;
740
+ /**
741
+ * json_access_expr = primary_expr { ("->" | "->>") string_literal } ;
742
+ * Chained JSON path access: col->'a'->'b'->>'c'
743
+ */
744
+ private jsonAccessExpr;
745
+ /**
746
+ * primary_expr = literal | case_expr | path_expr | func_call | "(" expr ")" | "(" scalar_subquery ")" ;
747
+ */
748
+ private primaryExpr;
749
+ /**
750
+ * Check if at start of scalar subquery (ident | ...)
751
+ */
752
+ private isScalarSubqueryStart;
753
+ /**
754
+ * scalar_subquery = query ;
755
+ * Delegates to the `query` rule (gate already ensures at least one pipe).
756
+ */
757
+ private scalarSubquery;
758
+ /**
759
+ * path_expr = ident_segment { "." ident_segment } ;
760
+ */
761
+ private pathExpr;
762
+ /**
763
+ * case_expr = "CASE" ( searched_case_body | simple_case_body ) [ else_clause ] "END" ;
764
+ * searched_case_body = "WHEN" boolean_expr "THEN" expression { "WHEN" boolean_expr "THEN" expression } ;
765
+ * simple_case_body = expression "WHEN" expression "THEN" expression { "WHEN" expression "THEN" expression } ;
766
+ * else_clause = "ELSE" expression ;
767
+ */
768
+ private caseExpr;
769
+ /**
770
+ * searched_case_body = "WHEN" boolean_expr "THEN" expression { ... } ;
771
+ */
772
+ private searchedCaseBody;
773
+ /**
774
+ * simple_case_body = expression "WHEN" expression "THEN" expression { ... } ;
775
+ */
776
+ private simpleCaseBody;
777
+ /**
778
+ * func_call = ( window_func | IDENT ) "(" [ func_arg_list ] ")" [ window_clause ] ;
779
+ * window_func = "row_number" | "rank" | "dense_rank" | "lag" | "lead" ;
780
+ * Regular functions can also have OVER (e.g., sum(x) OVER (...))
781
+ */
782
+ private funcCall;
783
+ /**
784
+ * window_clause = "over" "(" [ partition_clause ] [ order_clause_in_window ] ")" ;
785
+ */
786
+ private windowClause;
787
+ /**
788
+ * partition_clause = "partition" "by" expr_list ;
789
+ */
790
+ private partitionClause;
791
+ /**
792
+ * order_clause_in_window = "order" "by" order_list ;
793
+ * Same as orderClause but separate rule for clarity
794
+ */
795
+ private orderClauseInWindow;
796
+ /**
797
+ * Function argument list - handles count(*) and DISTINCT specially
798
+ * func_arg_list = "*" | [ "distinct" ] expr_list ;
799
+ */
800
+ private funcArgList;
801
+ /**
802
+ * expr_list = expr { "," expr } ;
803
+ */
804
+ private exprList;
805
+ /**
806
+ * value_list = expr { "," expr } ;
807
+ */
808
+ private valueList;
809
+ /**
810
+ * literal = STRING | NUMBER | "true" | "false" | "null" ;
811
+ */
812
+ private literal;
813
+ /**
814
+ * range_literal = ( "[" | "(" ) range_value "," range_value ( "]" | ")" ) ;
815
+ * range_value = RANGE_VALUE | NUMBER | "-" NUMBER ;
816
+ *
817
+ * PostgreSQL-style range bounds (full support):
818
+ * - Opening: [ (inclusive) or ( (exclusive)
819
+ * - Closing: ] (inclusive) or ) (exclusive)
820
+ * Examples: [1,10], (0,100), [1,10), (0,10]
821
+ *
822
+ * Note: This is only called from rangeOpSuffix (after overlaps/contains/containedBy),
823
+ * so there's no ambiguity with grouped expressions like (a + b).
824
+ */
825
+ private rangeLiteral;
826
+ /**
827
+ * range_value = RANGE_VALUE | NUMBER | "-" NUMBER ;
828
+ * RANGE_VALUE matches date/time patterns (2024-01-01, 08:00)
829
+ * NUMBER matches plain integers
830
+ */
831
+ private rangeValue;
832
+ /**
833
+ * ident_segment = IDENT | QUOTED_IDENT ;
834
+ */
835
+ private identSegment;
836
+ /**
837
+ * mutation_pipeline = mutation { "|" mutation_clause } ;
838
+ */
839
+ private mutationPipeline;
840
+ /**
841
+ * mutation_clause = select_clause | bind_clause ;
842
+ */
843
+ private mutationClause;
844
+ /**
845
+ * bind_clause = "bind" IDENT ;
846
+ */
847
+ private bindClause;
848
+ /**
849
+ * set_clause = set_op [ "all" ] set_operand ;
850
+ * set_op = "union" | "intersect" | "except" ;
851
+ * set_operand = "(" query ")" | ident_segment ;
852
+ */
853
+ private setClause;
854
+ /**
855
+ * mutation = insert_from_stmt | insert_stmt | update_stmt | delete_stmt | upsert_stmt ;
856
+ * Note: insert_from_stmt must come before insert_stmt because both start with "insert into".
857
+ * We use BACKTRACK to resolve the ambiguity.
858
+ */
859
+ private mutation;
860
+ /**
861
+ * insert_stmt = "insert" "into" ident_segment "set" assignment_list ;
862
+ */
863
+ private insertStmt;
864
+ /**
865
+ * insert_from_stmt = "insert" "into" ident_segment "from" ident_segment [ "where" boolean_expr ] [ "limit" number ] ;
866
+ * @example insert into archived_users from users where active = false limit 100
867
+ */
868
+ private insertFromStmt;
869
+ /**
870
+ * update_stmt = "update" ident_segment "set" assignment_list [ "where" boolean_expr ] ;
871
+ */
872
+ private updateStmt;
873
+ /**
874
+ * delete_stmt = "delete" "from" ident_segment [ "where" boolean_expr ] ;
875
+ * Note: WHERE is checked at semantic layer (to allow proper error messages)
876
+ */
877
+ private deleteStmt;
878
+ /**
879
+ * upsert_stmt = "upsert" "into" ident_segment "on" ( "(" ident_list ")" | ident_segment ) "set" assignment_list [ "where" boolean_expr ] ;
880
+ * Allows both `on (id1, id2)` and `on id` syntax
881
+ */
882
+ private upsertStmt;
883
+ /**
884
+ * upsert_from_stmt = "upsert" "into" ident_segment "on" ( "(" ident_list ")" | ident_segment ) "from" ident_segment [ "where" boolean_expr ] [ "limit" number ] ;
885
+ * @example upsert into authors on id from counts
886
+ * @example upsert into authors on (id, email) from counts where active = true
887
+ */
888
+ private upsertFromStmt;
889
+ /**
890
+ * assignment_list = assignment { "," assignment } ;
891
+ */
892
+ private assignmentList;
893
+ /**
894
+ * SQL-style values tuple: (col1=val1, col2=val2)
895
+ */
896
+ private valuesTuple;
897
+ /**
898
+ * assignment = ident_segment "=" expr ;
899
+ */
900
+ private assignment;
901
+ /**
902
+ * ident_list = ident_segment { "," ident_segment } ;
903
+ */
904
+ private identList;
905
+ }
906
+ /**
907
+ * Parse NQL input and return CST
908
+ */
909
+ declare function parseCst(input: string): {
910
+ cst: CstNode | undefined;
911
+ errors: Array<{
912
+ message: string;
913
+ token?: IToken;
914
+ }>;
915
+ };
916
+
917
+ /**
918
+ * @module semantic/helpers
919
+ * Shared types and utility functions for NQL CST-to-AST visitor.
920
+ */
921
+
922
+ /** CST context type — values are arrays of CstNode or IToken */
923
+ type CstContext = Record<string, (CstNode | IToken)[] | undefined>;
924
+ /**
925
+ * Window specification returned by windowClause visitor
926
+ */
927
+ interface WindowSpec {
928
+ partitionBy: NqlExpression[];
929
+ orderBy: NqlOrderItem[];
930
+ }
931
+
932
+ declare const BaseCstVisitor: new (...args: any[]) => chevrotain.ICstVisitor<any, any>;
933
+ /**
934
+ * CST Visitor that transforms Chevrotain CST to NQL AST.
935
+ * All logic lives in domain modules; this class is a thin dispatcher.
936
+ */
937
+ declare class NqlCstVisitor extends BaseCstVisitor {
938
+ private readonly v;
939
+ readonly warnings: NqlWarning[];
940
+ constructor();
941
+ /** Clear warnings before each parse run (singleton reuse). */
942
+ resetWarnings(): void;
943
+ program(ctx: CstContext): NqlProgram;
944
+ statement(ctx: CstContext): NqlStatement;
945
+ query(ctx: CstContext): NqlQuery;
946
+ tableRef(ctx: CstContext): string;
947
+ queryClause(ctx: CstContext): NqlClause;
948
+ whereClause(ctx: CstContext): NqlClause;
949
+ selectClause(ctx: CstContext): NqlClause;
950
+ flatClause(_ctx: CstContext): NqlClause;
951
+ groupClause(ctx: CstContext): NqlClause;
952
+ orderClause(ctx: CstContext): NqlClause;
953
+ limitClause(ctx: CstContext): NqlClause;
954
+ offsetClause(ctx: CstContext): NqlClause;
955
+ selectList(ctx: CstContext): NqlSelectItem[];
956
+ selectItem(ctx: CstContext): NqlSelectItem;
957
+ relationStarExpr(ctx: CstContext): NqlSelectItem;
958
+ orderList(ctx: CstContext): NqlOrderItem[];
959
+ orderItem(ctx: CstContext): NqlOrderItem;
960
+ lockClause(ctx: CstContext): NqlLockClause;
961
+ booleanExpr(ctx: CstContext): NqlExpression;
962
+ orExpr(ctx: CstContext): NqlExpression;
963
+ andExpr(ctx: CstContext): NqlExpression;
964
+ notExpr(ctx: CstContext): NqlExpression;
965
+ primaryCond(ctx: CstContext): NqlExpression;
966
+ comparisonSuffix(_ctx: CstContext): NqlExpression;
967
+ betweenSuffix(_ctx: CstContext): NqlExpression;
968
+ rangeOpSuffix(_ctx: CstContext): NqlExpression;
969
+ jsonComparisonSuffix(_ctx: CstContext): NqlExpression;
970
+ inSuffix(_ctx: CstContext): NqlExpression;
971
+ anySuffix(_ctx: CstContext): undefined;
972
+ isNullSuffix(_ctx: CstContext): NqlExpression;
973
+ compOp(ctx: CstContext): string;
974
+ rangeOp(ctx: CstContext): "overlaps" | "contains" | "containedBy";
975
+ expression(ctx: CstContext): NqlExpression;
976
+ addExpr(ctx: CstContext): NqlExpression;
977
+ mulExpr(ctx: CstContext): NqlExpression;
978
+ unaryExpr(ctx: CstContext): NqlExpression;
979
+ jsonAccessExpr(ctx: CstContext): NqlExpression;
980
+ primaryExpr(ctx: CstContext): NqlExpression;
981
+ caseExpr(ctx: CstContext): NqlCaseExpression;
982
+ searchedCaseBody(_ctx: CstContext): void;
983
+ simpleCaseBody(_ctx: CstContext): void;
984
+ scalarSubquery(ctx: CstContext): NqlSubquery;
985
+ pathExpr(ctx: CstContext): NqlExpression;
986
+ exprList(ctx: CstContext): NqlExpression[];
987
+ existsCheck(ctx: CstContext): NqlExistsExpression;
988
+ quantifiedRelationFilter(ctx: CstContext): NqlRelationFilterExpression;
989
+ allRelationFilter(ctx: CstContext): NqlRelationFilterExpression;
990
+ funcCall(ctx: CstContext): NqlFunctionCall | NqlWindowExpression;
991
+ windowClause(ctx: CstContext): WindowSpec;
992
+ partitionClause(ctx: CstContext): NqlExpression[];
993
+ orderClauseInWindow(ctx: CstContext): NqlOrderItem[];
994
+ funcArgList(ctx: CstContext): NqlExpression[];
995
+ literal(ctx: CstContext): NqlLiteral;
996
+ rangeLiteral(ctx: CstContext): NqlRangeLiteral;
997
+ rangeValue(ctx: CstContext): string;
998
+ identSegment(ctx: CstContext): string;
999
+ identList(ctx: CstContext): string[];
1000
+ valueList(ctx: CstContext): NqlExpression[];
1001
+ mutationPipeline(ctx: CstContext): NqlMutationPipeline;
1002
+ mutationClause(ctx: CstContext): NqlMutationClause;
1003
+ bindClause(ctx: CstContext): NqlMutationClause;
1004
+ setClause(ctx: CstContext): NqlSetClause;
1005
+ mutation(ctx: CstContext): NqlMutation;
1006
+ insertStmt(ctx: CstContext): NqlMutation;
1007
+ insertFromStmt(ctx: CstContext): NqlMutation;
1008
+ updateStmt(ctx: CstContext): NqlMutation;
1009
+ deleteStmt(ctx: CstContext): NqlMutation;
1010
+ upsertStmt(ctx: CstContext): NqlMutation;
1011
+ upsertFromStmt(ctx: CstContext): NqlMutation;
1012
+ assignmentList(ctx: CstContext): NqlAssignment[];
1013
+ valuesTuple(ctx: CstContext): NqlAssignment[];
1014
+ assignment(ctx: CstContext): NqlAssignment;
1015
+ withQuery(ctx: CstContext): NqlWithQuery;
1016
+ cteList(ctx: CstContext): NqlCteItem[];
1017
+ cteItem(ctx: CstContext): NqlCteItem;
1018
+ }
1019
+ declare const nqlVisitor: NqlCstVisitor;
1020
+ /**
1021
+ * Transform CST to AST, collecting any warnings emitted during traversal.
1022
+ */
1023
+ declare function cstToAst(cst: CstNode): {
1024
+ ast: NqlProgram;
1025
+ warnings: NqlWarning[];
1026
+ };
1027
+
1028
+ interface ParseOptions {
1029
+ /** Reject keyword aliases (default: true) */
1030
+ strictMode?: boolean;
1031
+ /** Maximum subquery nesting depth (default: 10) */
1032
+ maxSubqueryDepth?: number;
1033
+ /** Maximum clauses per query (default: 20) */
1034
+ maxClauses?: number;
1035
+ /** Maximum joins per query (default: 10) */
1036
+ maxJoins?: number;
1037
+ }
1038
+ interface ParseResult<T> {
1039
+ success: boolean;
1040
+ ast?: T;
1041
+ errors: NqlError[];
1042
+ warnings: NqlWarning[];
1043
+ }
1044
+
1045
+ /**
1046
+ * Parse NQL input without schema validation
1047
+ */
1048
+ declare function parse(input: string, _options?: ParseOptions): ParseResult<NqlProgram>;
1049
+ /**
1050
+ * Parse, validate, and compile NQL to IntentAST
1051
+ */
1052
+ declare function compile(input: string, schema: unknown, // ModelIR from @dbsp/core — satisfies ColumnValidatorSchema
1053
+ options?: ParseOptions, compilerOptions?: NqlCompilerOptions): ParseResult<CompileResult>;
1054
+
1055
+ export { type ColumnValidatorSchema, type CompileResult, type NqlAnyExpression, type NqlAssignment, type NqlBetweenExpression, type NqlBinaryExpression, type NqlBindClause, type NqlBooleanLiteral, type NqlCaseExpression, type NqlClause, type NqlComparisonExpression, NqlCompiler, type NqlCompilerOptions, NqlCstVisitor, type NqlCteItem, type NqlDateRangeLiteral, type NqlDelete, type NqlError, type NqlExistsExpression, type NqlExpression, type NqlFlatClause, type NqlFunctionCall, type NqlGroupByClause, type NqlInExpression, type NqlInsert, type NqlInsertFrom, type NqlIsNullExpression, type NqlJoinParam, type NqlJoinSpec, type NqlJsonAccessExpression, type NqlJsonComparisonExpression, NqlLexer, type NqlLimitClause, type NqlLiteral, type NqlLockClause, type NqlMutation, type NqlMutationClause, type NqlMutationPipeline, type NqlNullLiteral, type NqlNumberLiteral, type NqlOffsetClause, type NqlOrderByClause, type NqlOrderItem, type NqlParseError, NqlParser, type NqlPathExpression, type NqlProgram, type NqlQuery, type NqlRangeLiteral, type NqlRangeOpExpression, type NqlRelationFilterExpression, type NqlSelectClause, type NqlSelectExpression, type NqlSelectItem, type NqlSelectRelationStar, type NqlSelectStar, type NqlSemanticError, type NqlSetClause, type NqlStatement, type NqlStringLiteral, type NqlSubquery, type NqlUnaryExpression, type NqlUpdate, type NqlUpsert, type NqlUpsertFrom, type NqlVariableRef, type NqlWarning, type NqlWhereClause, type NqlWindowExpression, type NqlWithQuery, type ParseOptions, type ParseResult, allTokens, compile, createCompiler, cstToAst, nqlVisitor, parse, parseCst };