@dbsp/nql 1.0.4 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -15,16 +15,22 @@ pnpm add @dbsp/nql
15
15
 
16
16
  ```typescript
17
17
  // doctest: skip — exec-only operation; compile from @dbsp/nql is not in doctest preamble and orm.from(intent).all() requires a real PostgreSQL connection
18
+ import { createPgsqlCompileOnlyAdapter } from '@dbsp/adapter-pgsql';
18
19
  import { compile } from '@dbsp/nql';
19
20
 
20
- // Compile an NQL query to IntentAST
21
- const intent = compile(
21
+ // Compile an NQL query to a public intent bundle
22
+ const compiled = compile(
22
23
  "users | where active = true | select name, email | order name asc | limit 20",
23
- schema
24
+ db.model
24
25
  );
25
26
 
26
- // Pass the intent to the ORM
27
- const users = await orm.from(intent).all();
27
+ if (!compiled.success || !compiled.ast?.query) {
28
+ throw new Error(compiled.errors.map((e) => e.message).join(', '));
29
+ }
30
+
31
+ // Pass the whole bundle to the adapter; bound params are explicit public IR nodes
32
+ const adapter = createPgsqlCompileOnlyAdapter();
33
+ const query = adapter.compile(compiled.ast, { model: db.model });
28
34
  ```
29
35
 
30
36
  ## Syntax overview
@@ -51,12 +57,39 @@ orders | group customerId | select customerId, sum(total) as revenue
51
57
 
52
58
  - **Pipe syntax** — Readable left-to-right data flow (`table | filter | select | order`)
53
59
  - **SQL-style literals** — Single-quoted strings (`'value'`), not double-quoted
60
+ - **Named parameters** — Bind runtime values with `:name` in expression positions
54
61
  - **CTE support** — `WITH name AS (subquery)` for named subqueries
55
62
  - **Schema-aware** — Validates column names and relation paths against `ModelIR` at parse time
56
63
  - **LLM-friendly** — Concise syntax designed for AI-generated queries
57
64
  - **Chevrotain-based** — Robust lexer + parser with structured error recovery
58
65
  - **Composable** — Output `IntentAST` is the same type used by the TypeScript fluent builders
59
66
 
67
+ ## Named parameters
68
+
69
+ Use `:name` placeholders for runtime values and pass a `params` map to the compiler:
70
+
71
+ ```typescript
72
+ // doctest: skip — illustrative direct compiler params example
73
+ import { createPgsqlCompileOnlyAdapter } from '@dbsp/adapter-pgsql';
74
+ import { compile } from '@dbsp/nql';
75
+
76
+ const compiled = compile(
77
+ 'users | where id = :id and active = :active | limit :limit',
78
+ db.model,
79
+ undefined,
80
+ { params: { id: 42, active: true, limit: 10 } },
81
+ );
82
+
83
+ if (!compiled.success || !compiled.ast?.query) {
84
+ throw new Error(compiled.errors.map((e) => e.message).join(', '));
85
+ }
86
+
87
+ const adapter = createPgsqlCompileOnlyAdapter();
88
+ const query = adapter.compile(compiled.ast, { model: db.model });
89
+ ```
90
+
91
+ Missing params fail compilation. `null` binds SQL `NULL`; `undefined`, `NaN`, and `Infinity` are rejected. The `@dbsp/core` `orm.nql` template tag builds on the same mechanism for `${value}` interpolation. See [Named Parameters and Template Binding](https://oorabona.github.io/db-semantic-planner/nql/#named-parameters-and-template-binding) for the full contract.
92
+
60
93
  ## Documentation
61
94
 
62
95
  - [Guides](https://oorabona.github.io/db-semantic-planner/guide/)
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
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';
1
+ import { CompiledNqlQuery } from '@dbsp/types';
2
+ export { CompiledNqlQuery, 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
3
  import * as chevrotain from 'chevrotain';
4
4
  import { Lexer, CstParser, CstNode, IToken } from 'chevrotain';
5
5
 
@@ -71,13 +71,14 @@ interface NqlOrderByClause {
71
71
  }
72
72
  interface NqlLimitClause {
73
73
  type: 'limit';
74
- count: number;
74
+ count: NqlLimitCount;
75
75
  relation?: string;
76
76
  }
77
77
  interface NqlOffsetClause {
78
78
  type: 'offset';
79
- count: number;
79
+ count: NqlLimitCount;
80
80
  }
81
+ type NqlLimitCount = number | NqlNamedParamExpr;
81
82
  /**
82
83
  * Capture mutation result into a variable (for chained mutations)
83
84
  * Used with `bind` keyword: `insert ... | bind result | ...`
@@ -135,7 +136,7 @@ interface NqlOrderItem {
135
136
  expression: NqlExpression;
136
137
  direction: 'asc' | 'desc';
137
138
  }
138
- type NqlExpression = NqlBinaryExpression | NqlUnaryExpression | NqlComparisonExpression | NqlRangeOpExpression | NqlInExpression | NqlAnyExpression | NqlBetweenExpression | NqlIsNullExpression | NqlExistsExpression | NqlRelationFilterExpression | NqlFunctionCall | NqlWindowExpression | NqlCaseExpression | NqlPathExpression | NqlJsonAccessExpression | NqlJsonComparisonExpression | NqlLiteral | NqlSubquery | NqlVariableRef;
139
+ type NqlExpression = NqlBinaryExpression | NqlUnaryExpression | NqlComparisonExpression | NqlRangeOpExpression | NqlInExpression | NqlAnyExpression | NqlBetweenExpression | NqlIsNullExpression | NqlExistsExpression | NqlRelationFilterExpression | NqlFunctionCall | NqlWindowExpression | NqlCaseExpression | NqlPathExpression | NqlJsonAccessExpression | NqlJsonComparisonExpression | NqlLiteral | NqlSubquery | NqlVariableRef | NqlNamedParamExpr;
139
140
  interface NqlBinaryExpression {
140
141
  type: 'binary';
141
142
  operator: 'and' | 'or' | '+' | '-' | '*' | '/' | '%';
@@ -162,7 +163,7 @@ interface NqlRangeOpExpression {
162
163
  operator: 'overlaps' | 'contains' | 'containedBy';
163
164
  left: NqlExpression;
164
165
  range?: NqlRangeLiteral;
165
- scalar?: NqlLiteral;
166
+ scalar?: NqlLiteral | NqlNamedParamExpr;
166
167
  }
167
168
  interface NqlInExpression {
168
169
  type: 'in';
@@ -297,6 +298,15 @@ interface NqlVariableRef {
297
298
  type: 'variable';
298
299
  name: string;
299
300
  }
301
+ /**
302
+ * Bound parameter reference (e.g. `:tenantId`).
303
+ * The leading colon is not part of `name`; compiler resolution uses
304
+ * `CompilerContext.params`.
305
+ */
306
+ interface NqlNamedParamExpr {
307
+ type: 'namedParam';
308
+ name: string;
309
+ }
300
310
  type NqlLiteral = NqlStringLiteral | NqlNumberLiteral | NqlBooleanLiteral | NqlNullLiteral | NqlDateRangeLiteral | NqlRangeLiteral;
301
311
  interface NqlStringLiteral {
302
312
  type: 'string';
@@ -354,7 +364,7 @@ interface NqlInsertFrom {
354
364
  /** WHERE clause to filter source rows */
355
365
  where?: NqlExpression | undefined;
356
366
  /** LIMIT clause to restrict rows */
357
- limit?: number | undefined;
367
+ limit?: NqlLimitCount | undefined;
358
368
  }
359
369
  interface NqlUpdate {
360
370
  type: 'update';
@@ -385,31 +395,15 @@ interface NqlUpsertFrom {
385
395
  source: string;
386
396
  columns?: string[] | undefined;
387
397
  where?: NqlExpression | undefined;
388
- limit?: number | undefined;
398
+ limit?: NqlLimitCount | undefined;
389
399
  }
390
400
  interface NqlAssignment {
391
401
  column: string;
392
402
  value: NqlExpression;
393
403
  }
394
404
 
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
- }
405
+ type CompileResult = CompiledNqlQuery;
406
+
413
407
  /**
414
408
  * Duck-type interface for schema-based column validation.
415
409
  * Loose coupling: ModelIR from @dbsp/core satisfies this shape without direct import.
@@ -440,6 +434,8 @@ interface NqlCompilerOptions {
440
434
  readonly params?: Readonly<Record<string, unknown>>;
441
435
  /** Maximum number of items allowed in an ANY(:param) array. Defaults to MAX_ANY_ITEMS (10000). */
442
436
  readonly maxAnyItems?: number;
437
+ /** Permit a where-less update/delete to compile to an unfiltered, all-rows mutation. Default false — a where-less mutation throws unless this is set. */
438
+ readonly allowUnfilteredMutations?: boolean;
443
439
  }
444
440
 
445
441
  /**
@@ -510,6 +506,11 @@ interface NqlWarning {
510
506
  declare const allTokens: chevrotain.TokenType[];
511
507
  declare const NqlLexer: Lexer;
512
508
 
509
+ /**
510
+ * Tokenize NQL source with the public lexer.
511
+ */
512
+ declare function tokenize(input: string): ReturnType<typeof NqlLexer.tokenize>;
513
+
513
514
  /**
514
515
  * NQL Parser using Chevrotain CstParser
515
516
  */
@@ -578,13 +579,23 @@ declare class NqlParser extends CstParser {
578
579
  */
579
580
  private orderClause;
580
581
  /**
581
- * limit_clause = "limit" [ident_segment ("." ident_segment)*] NUMBER ;
582
+ * limit_clause = "limit" [ident_segment ("." ident_segment)*] (NUMBER | NAMED_PARAM) ;
582
583
  */
583
584
  private limitClause;
584
585
  /**
585
- * offset_clause = "offset" NUMBER ;
586
+ * offset_clause = "offset" (NUMBER | NAMED_PARAM) ;
586
587
  */
587
588
  private offsetClause;
589
+ /**
590
+ * numeric_value_atom = NUMBER | NAMED_PARAM ;
591
+ *
592
+ * Structural-position durability rule: value positions that consume scalar
593
+ * values may add `NamedParam` here and validate the resolved value in the
594
+ * compiler. Structural positions such as identifiers, directions, lock modes,
595
+ * and traversal depth hints must not consume this helper; use nqlRaw()/builder
596
+ * APIs for trusted dynamic structure.
597
+ */
598
+ private numericValueAtom;
588
599
  /**
589
600
  * lock_clause = lock_strength [ lock_wait_policy ] ;
590
601
  * lock_strength = "for update" | "for share" | "for no key update" | "for key share" ;
@@ -662,7 +673,7 @@ declare class NqlParser extends CstParser {
662
673
  */
663
674
  private rangeOp;
664
675
  /**
665
- * range_op_suffix = range_op (range_literal | literal) ;
676
+ * range_op_suffix = range_op (range_literal | literal | named_param_expr) ;
666
677
  * Example: overlaps [1,10) or contains (0,100) or contains 25
667
678
  * Note: contains can take a scalar value (e.g., contains 25 checks if range contains the value)
668
679
  */
@@ -745,9 +756,13 @@ declare class NqlParser extends CstParser {
745
756
  */
746
757
  private jsonAccessExpr;
747
758
  /**
748
- * primary_expr = literal | case_expr | path_expr | func_call | "(" expr ")" | "(" scalar_subquery ")" ;
759
+ * primary_expr = named_param_expr | literal | case_expr | path_expr | func_call | "(" expr ")" | "(" scalar_subquery ")" ;
749
760
  */
750
761
  private primaryExpr;
762
+ /**
763
+ * named_param_expr = NAMED_PARAM ;
764
+ */
765
+ private namedParamExpr;
751
766
  /**
752
767
  * Check if at start of scalar subquery (ident | ...)
753
768
  */
@@ -864,7 +879,7 @@ declare class NqlParser extends CstParser {
864
879
  */
865
880
  private insertStmt;
866
881
  /**
867
- * insert_from_stmt = "insert" "into" ident_segment "from" ident_segment [ "where" boolean_expr ] [ "limit" number ] ;
882
+ * insert_from_stmt = "insert" "into" ident_segment "from" ident_segment [ "where" boolean_expr ] [ "limit" numeric_value_atom ] ;
868
883
  * @example insert into archived_users from users where active = false limit 100
869
884
  */
870
885
  private insertFromStmt;
@@ -883,7 +898,7 @@ declare class NqlParser extends CstParser {
883
898
  */
884
899
  private upsertStmt;
885
900
  /**
886
- * upsert_from_stmt = "upsert" "into" ident_segment "on" ( "(" ident_list ")" | ident_segment ) "from" ident_segment [ "where" boolean_expr ] [ "limit" number ] ;
901
+ * upsert_from_stmt = "upsert" "into" ident_segment "on" ( "(" ident_list ")" | ident_segment ) "from" ident_segment [ "where" boolean_expr ] [ "limit" numeric_value_atom ] ;
887
902
  * @example upsert into authors on id from counts
888
903
  * @example upsert into authors on (id, email) from counts where active = true
889
904
  */
@@ -954,6 +969,7 @@ declare class NqlCstVisitor extends BaseCstVisitor {
954
969
  orderClause(ctx: CstContext): NqlClause;
955
970
  limitClause(ctx: CstContext): NqlClause;
956
971
  offsetClause(ctx: CstContext): NqlClause;
972
+ numericValueAtom(ctx: CstContext): NqlLimitCount;
957
973
  selectList(ctx: CstContext): NqlSelectItem[];
958
974
  selectItem(ctx: CstContext): NqlSelectItem;
959
975
  relationStarExpr(ctx: CstContext): NqlSelectItem;
@@ -980,6 +996,7 @@ declare class NqlCstVisitor extends BaseCstVisitor {
980
996
  unaryExpr(ctx: CstContext): NqlExpression;
981
997
  jsonAccessExpr(ctx: CstContext): NqlExpression;
982
998
  primaryExpr(ctx: CstContext): NqlExpression;
999
+ namedParamExpr(ctx: CstContext): NqlNamedParamExpr;
983
1000
  caseExpr(ctx: CstContext): NqlCaseExpression;
984
1001
  searchedCaseBody(_ctx: CstContext): void;
985
1002
  simpleCaseBody(_ctx: CstContext): void;
@@ -1054,4 +1071,4 @@ declare function parse(input: string, _options?: ParseOptions): ParseResult<NqlP
1054
1071
  declare function compile(input: string, schema: unknown, // ModelIR from @dbsp/core — satisfies ColumnValidatorSchema
1055
1072
  options?: ParseOptions, compilerOptions?: NqlCompilerOptions): ParseResult<CompileResult>;
1056
1073
 
1057
- 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 };
1074
+ 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 NqlLimitCount, type NqlLiteral, type NqlLockClause, type NqlMutation, type NqlMutationClause, type NqlMutationPipeline, type NqlNamedParamExpr, 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, tokenize };