@quereus/quereus 1.3.1 → 2.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.
Files changed (72) hide show
  1. package/dist/src/core/database.js +2 -2
  2. package/dist/src/core/database.js.map +1 -1
  3. package/dist/src/emit/ast-stringify.d.ts.map +1 -1
  4. package/dist/src/emit/ast-stringify.js +36 -2
  5. package/dist/src/emit/ast-stringify.js.map +1 -1
  6. package/dist/src/func/builtins/index.d.ts.map +1 -1
  7. package/dist/src/func/builtins/index.js +2 -1
  8. package/dist/src/func/builtins/index.js.map +1 -1
  9. package/dist/src/func/builtins/schema.d.ts +1 -0
  10. package/dist/src/func/builtins/schema.d.ts.map +1 -1
  11. package/dist/src/func/builtins/schema.js +83 -9
  12. package/dist/src/func/builtins/schema.js.map +1 -1
  13. package/dist/src/index.d.ts +1 -0
  14. package/dist/src/index.d.ts.map +1 -1
  15. package/dist/src/index.js +1 -0
  16. package/dist/src/index.js.map +1 -1
  17. package/dist/src/parser/ast.d.ts +7 -1
  18. package/dist/src/parser/ast.d.ts.map +1 -1
  19. package/dist/src/parser/parser.d.ts +7 -0
  20. package/dist/src/parser/parser.d.ts.map +1 -1
  21. package/dist/src/parser/parser.js +196 -52
  22. package/dist/src/parser/parser.js.map +1 -1
  23. package/dist/src/planner/building/create-view.d.ts.map +1 -1
  24. package/dist/src/planner/building/create-view.js +1 -1
  25. package/dist/src/planner/building/create-view.js.map +1 -1
  26. package/dist/src/planner/building/foreign-key-builder.d.ts.map +1 -1
  27. package/dist/src/planner/building/foreign-key-builder.js +7 -3
  28. package/dist/src/planner/building/foreign-key-builder.js.map +1 -1
  29. package/dist/src/planner/framework/pass.d.ts.map +1 -1
  30. package/dist/src/planner/framework/pass.js +2 -0
  31. package/dist/src/planner/framework/pass.js.map +1 -1
  32. package/dist/src/planner/framework/registry.d.ts.map +1 -1
  33. package/dist/src/planner/framework/registry.js +3 -0
  34. package/dist/src/planner/framework/registry.js.map +1 -1
  35. package/dist/src/planner/nodes/create-view-node.d.ts +3 -1
  36. package/dist/src/planner/nodes/create-view-node.d.ts.map +1 -1
  37. package/dist/src/planner/nodes/create-view-node.js +3 -1
  38. package/dist/src/planner/nodes/create-view-node.js.map +1 -1
  39. package/dist/src/planner/optimizer-tuning.d.ts +2 -0
  40. package/dist/src/planner/optimizer-tuning.d.ts.map +1 -1
  41. package/dist/src/planner/optimizer-tuning.js.map +1 -1
  42. package/dist/src/runtime/cache/shared-cache.d.ts.map +1 -1
  43. package/dist/src/runtime/cache/shared-cache.js +4 -2
  44. package/dist/src/runtime/cache/shared-cache.js.map +1 -1
  45. package/dist/src/runtime/emit/create-view.d.ts.map +1 -1
  46. package/dist/src/runtime/emit/create-view.js +2 -1
  47. package/dist/src/runtime/emit/create-view.js.map +1 -1
  48. package/dist/src/runtime/foreign-key-actions.js +1 -1
  49. package/dist/src/runtime/foreign-key-actions.js.map +1 -1
  50. package/dist/src/schema/catalog.d.ts.map +1 -1
  51. package/dist/src/schema/catalog.js +24 -1
  52. package/dist/src/schema/catalog.js.map +1 -1
  53. package/dist/src/schema/column.d.ts +3 -0
  54. package/dist/src/schema/column.d.ts.map +1 -1
  55. package/dist/src/schema/column.js.map +1 -1
  56. package/dist/src/schema/manager.d.ts +16 -0
  57. package/dist/src/schema/manager.d.ts.map +1 -1
  58. package/dist/src/schema/manager.js +48 -6
  59. package/dist/src/schema/manager.js.map +1 -1
  60. package/dist/src/schema/schema-hasher.d.ts.map +1 -1
  61. package/dist/src/schema/schema-hasher.js +46 -2
  62. package/dist/src/schema/schema-hasher.js.map +1 -1
  63. package/dist/src/schema/table.d.ts +12 -2
  64. package/dist/src/schema/table.d.ts.map +1 -1
  65. package/dist/src/schema/table.js +4 -0
  66. package/dist/src/schema/table.js.map +1 -1
  67. package/dist/src/schema/view.d.ts +3 -0
  68. package/dist/src/schema/view.d.ts.map +1 -1
  69. package/dist/src/util/comparison.d.ts.map +1 -1
  70. package/dist/src/util/comparison.js +14 -21
  71. package/dist/src/util/comparison.js.map +1 -1
  72. package/package.json +5 -3
@@ -29,6 +29,13 @@ function _createLoc(startToken, endToken) {
29
29
  },
30
30
  };
31
31
  }
32
+ /**
33
+ * IMPORTANT: Any changes to parsed syntax must also be reflected in the corresponding emitters:
34
+ * - packages/quereus/src/emit/ast-stringify.ts (AST-to-SQL string conversion)
35
+ * - packages/quereus/src/schema/catalog.ts (DDL generation for catalog/hashing)
36
+ * - packages/quereus-store/src/common/ddl-generator.ts (DDL generation for persistence)
37
+ * If only the parser is updated, SQL round-trips and persisted schemas will silently lose the new syntax.
38
+ */
32
39
  export class Parser {
33
40
  tokens = [];
34
41
  current = 0;
@@ -2051,15 +2058,26 @@ export class Parser {
2051
2058
  this.consume(TokenType.RPAREN, "Expected ')' after module arguments.");
2052
2059
  }
2053
2060
  }
2054
- // Parse mutation context definitions if present
2061
+ // Parse trailing WITH clauses (CONTEXT, TAGS) in any order
2055
2062
  let contextDefinitions;
2056
- if (this.matchKeyword('WITH')) {
2063
+ let tags;
2064
+ while (this.matchKeyword('WITH')) {
2057
2065
  if (this.matchKeyword('CONTEXT')) {
2066
+ if (contextDefinitions) {
2067
+ throw this.error(this.previous(), "Duplicate WITH CONTEXT clause");
2068
+ }
2058
2069
  contextDefinitions = this.parseMutationContextDefinitions();
2059
2070
  }
2071
+ else if (this.matchKeyword('TAGS')) {
2072
+ if (tags) {
2073
+ throw this.error(this.previous(), "Duplicate WITH TAGS clause");
2074
+ }
2075
+ tags = this.parseTags();
2076
+ }
2060
2077
  else {
2061
- // Not a WITH CONTEXT clause, backtrack
2078
+ // Not a recognized WITH clause, backtrack
2062
2079
  this.current--;
2080
+ break;
2063
2081
  }
2064
2082
  }
2065
2083
  return {
@@ -2072,6 +2090,7 @@ export class Parser {
2072
2090
  moduleName,
2073
2091
  moduleArgs,
2074
2092
  contextDefinitions,
2093
+ tags,
2075
2094
  loc: _createLoc(startToken, this.previous()),
2076
2095
  };
2077
2096
  }
@@ -2101,6 +2120,16 @@ export class Parser {
2101
2120
  if (this.matchKeyword('WHERE')) {
2102
2121
  where = this.expression();
2103
2122
  }
2123
+ // Parse optional WITH TAGS
2124
+ let tags;
2125
+ if (this.matchKeyword('WITH')) {
2126
+ if (this.matchKeyword('TAGS')) {
2127
+ tags = this.parseTags();
2128
+ }
2129
+ else {
2130
+ this.current--;
2131
+ }
2132
+ }
2104
2133
  return {
2105
2134
  type: 'createIndex',
2106
2135
  index,
@@ -2109,6 +2138,7 @@ export class Parser {
2109
2138
  columns,
2110
2139
  where,
2111
2140
  isUnique,
2141
+ tags,
2112
2142
  loc: _createLoc(startToken, this.previous()),
2113
2143
  };
2114
2144
  }
@@ -2144,6 +2174,16 @@ export class Parser {
2144
2174
  this.consumeKeyword('AS', "Expected 'AS' before SELECT statement for CREATE VIEW.");
2145
2175
  const selectStartToken = this.consume(TokenType.SELECT, "Expected 'SELECT' after 'AS' in CREATE VIEW.");
2146
2176
  const select = this.selectStatement(selectStartToken, withClause);
2177
+ // Parse optional WITH TAGS
2178
+ let tags;
2179
+ if (this.matchKeyword('WITH')) {
2180
+ if (this.matchKeyword('TAGS')) {
2181
+ tags = this.parseTags();
2182
+ }
2183
+ else {
2184
+ this.current--;
2185
+ }
2186
+ }
2147
2187
  return {
2148
2188
  type: 'createView',
2149
2189
  view,
@@ -2151,6 +2191,7 @@ export class Parser {
2151
2191
  columns,
2152
2192
  select,
2153
2193
  isTemporary,
2194
+ tags,
2154
2195
  loc: _createLoc(startToken, this.previous()),
2155
2196
  };
2156
2197
  }
@@ -2478,15 +2519,25 @@ export class Parser {
2478
2519
  else {
2479
2520
  this.consume(TokenType.RPAREN, "Expected ')' after table definition.");
2480
2521
  }
2481
- // Parse mutation context definitions if present (WITH CONTEXT clause)
2522
+ // Parse trailing WITH clauses (CONTEXT, TAGS) in any order
2482
2523
  let contextDefinitions;
2483
- if (this.matchKeyword('WITH')) {
2524
+ let tags;
2525
+ while (this.matchKeyword('WITH')) {
2484
2526
  if (this.matchKeyword('CONTEXT')) {
2527
+ if (contextDefinitions) {
2528
+ throw this.error(this.previous(), "Duplicate WITH CONTEXT clause");
2529
+ }
2485
2530
  contextDefinitions = this.parseMutationContextDefinitions();
2486
2531
  }
2532
+ else if (this.matchKeyword('TAGS')) {
2533
+ if (tags) {
2534
+ throw this.error(this.previous(), "Duplicate WITH TAGS clause");
2535
+ }
2536
+ tags = this.parseTags();
2537
+ }
2487
2538
  else {
2488
- // Not a WITH CONTEXT clause, backtrack
2489
2539
  this.current--;
2540
+ break;
2490
2541
  }
2491
2542
  }
2492
2543
  // Build the CREATE TABLE AST node for this declared table
@@ -2499,7 +2550,8 @@ export class Parser {
2499
2550
  isTemporary: false,
2500
2551
  moduleName,
2501
2552
  moduleArgs,
2502
- contextDefinitions
2553
+ contextDefinitions,
2554
+ tags
2503
2555
  };
2504
2556
  return { type: 'declaredTable', tableStmt };
2505
2557
  }
@@ -2510,13 +2562,24 @@ export class Parser {
2510
2562
  this.consume(TokenType.LPAREN, "Expected '(' before index columns.");
2511
2563
  const columns = this.indexedColumnList();
2512
2564
  this.consume(TokenType.RPAREN, "Expected ')' after index columns.");
2565
+ // Parse optional WITH TAGS
2566
+ let tags;
2567
+ if (this.matchKeyword('WITH')) {
2568
+ if (this.matchKeyword('TAGS')) {
2569
+ tags = this.parseTags();
2570
+ }
2571
+ else {
2572
+ this.current--;
2573
+ }
2574
+ }
2513
2575
  const indexStmt = {
2514
2576
  type: 'createIndex',
2515
2577
  index: { type: 'identifier', name: indexName },
2516
2578
  table: { type: 'identifier', name: tableName },
2517
2579
  ifNotExists: false,
2518
2580
  columns,
2519
- isUnique: false
2581
+ isUnique: false,
2582
+ tags
2520
2583
  };
2521
2584
  return { type: 'declaredIndex', indexStmt };
2522
2585
  }
@@ -2530,13 +2593,24 @@ export class Parser {
2530
2593
  this.consumeKeyword('AS', "Expected AS before SELECT in view declaration.");
2531
2594
  const selTok = this.consume(TokenType.SELECT, "Expected SELECT after AS in view declaration.");
2532
2595
  const select = this.selectStatement(selTok);
2596
+ // Parse optional WITH TAGS
2597
+ let tags;
2598
+ if (this.matchKeyword('WITH')) {
2599
+ if (this.matchKeyword('TAGS')) {
2600
+ tags = this.parseTags();
2601
+ }
2602
+ else {
2603
+ this.current--;
2604
+ }
2605
+ }
2533
2606
  const viewStmt = {
2534
2607
  type: 'createView',
2535
2608
  view: { type: 'identifier', name: viewName },
2536
2609
  ifNotExists: false,
2537
2610
  columns,
2538
2611
  select,
2539
- isTemporary: false
2612
+ isTemporary: false,
2613
+ tags
2540
2614
  };
2541
2615
  return { type: 'declaredView', viewStmt };
2542
2616
  }
@@ -2842,7 +2916,17 @@ export class Parser {
2842
2916
  }
2843
2917
  }
2844
2918
  const constraints = this.columnConstraintList();
2845
- return { name, dataType, constraints };
2919
+ // Parse optional column-level WITH TAGS
2920
+ let tags;
2921
+ if (this.matchKeyword('WITH')) {
2922
+ if (this.matchKeyword('TAGS')) {
2923
+ tags = this.parseTags();
2924
+ }
2925
+ else {
2926
+ this.current--;
2927
+ }
2928
+ }
2929
+ return { name, dataType, constraints, tags };
2846
2930
  }
2847
2931
  /** @internal Parses mutation context variable definitions: WITH CONTEXT (var type [NULL], ...) */
2848
2932
  parseMutationContextDefinitions() {
@@ -2914,6 +2998,7 @@ export class Parser {
2914
2998
  parseTrailingWithClauses() {
2915
2999
  let contextValues;
2916
3000
  let schemaPath;
3001
+ let tags;
2917
3002
  // Keep trying to parse WITH clauses until we don't find any more
2918
3003
  while (this.matchKeyword('WITH')) {
2919
3004
  if (this.matchKeyword('CONTEXT')) {
@@ -2933,13 +3018,64 @@ export class Parser {
2933
3018
  } while (this.match(TokenType.COMMA));
2934
3019
  schemaPath = schemas;
2935
3020
  }
3021
+ else if (this.matchKeyword('TAGS')) {
3022
+ if (tags) {
3023
+ throw this.error(this.previous(), "Duplicate WITH TAGS clause");
3024
+ }
3025
+ tags = this.parseTags();
3026
+ }
2936
3027
  else {
2937
- // Not a WITH CONTEXT or WITH SCHEMA clause, backtrack
3028
+ // Not a recognized WITH clause, backtrack
2938
3029
  this.current--;
2939
3030
  break;
2940
3031
  }
2941
3032
  }
2942
- return { contextValues, schemaPath };
3033
+ return { contextValues, schemaPath, tags };
3034
+ }
3035
+ /**
3036
+ * @internal Parses a tags list: (key = value, ...)
3037
+ * Called after the TAGS keyword has been consumed.
3038
+ * Keys are identifiers, values are literals (string, number, boolean via TRUE/FALSE, NULL).
3039
+ */
3040
+ parseTags() {
3041
+ this.consume(TokenType.LPAREN, "Expected '(' after TAGS.");
3042
+ const tags = {};
3043
+ if (!this.check(TokenType.RPAREN)) {
3044
+ do {
3045
+ const key = this.consumeIdentifier("Expected tag key identifier.");
3046
+ this.consume(TokenType.EQUAL, `Expected '=' after tag key '${key}'.`);
3047
+ const value = this.parseTagValue();
3048
+ tags[key] = value;
3049
+ } while (this.match(TokenType.COMMA));
3050
+ }
3051
+ this.consume(TokenType.RPAREN, "Expected ')' after tag list.");
3052
+ return tags;
3053
+ }
3054
+ /** @internal Parses a tag value: string, number, TRUE, FALSE, or NULL */
3055
+ parseTagValue() {
3056
+ if (this.match(TokenType.STRING)) {
3057
+ return this.previous().literal;
3058
+ }
3059
+ if (this.match(TokenType.INTEGER) || this.match(TokenType.FLOAT)) {
3060
+ return this.previous().literal;
3061
+ }
3062
+ if (this.match(TokenType.TRUE)) {
3063
+ return true;
3064
+ }
3065
+ if (this.match(TokenType.FALSE)) {
3066
+ return false;
3067
+ }
3068
+ if (this.match(TokenType.NULL)) {
3069
+ return null;
3070
+ }
3071
+ // Allow negative numbers
3072
+ if (this.match(TokenType.MINUS)) {
3073
+ if (this.match(TokenType.INTEGER) || this.match(TokenType.FLOAT)) {
3074
+ return -this.previous().literal;
3075
+ }
3076
+ throw this.error(this.peek(), "Expected number after '-' in tag value.");
3077
+ }
3078
+ throw this.error(this.peek(), "Expected tag value (string, number, true, false, or null).");
2943
3079
  }
2944
3080
  /** @internal Parses column constraints */
2945
3081
  columnConstraintList() {
@@ -2971,6 +3107,7 @@ export class Parser {
2971
3107
  name = this.consumeIdentifier("Expected constraint name after CONSTRAINT.");
2972
3108
  endToken = this.previous();
2973
3109
  }
3110
+ let result;
2974
3111
  if (this.match(TokenType.PRIMARY)) {
2975
3112
  this.consume(TokenType.KEY, "Expected KEY after PRIMARY.");
2976
3113
  const direction = this.match(TokenType.ASC) ? 'asc' : this.match(TokenType.DESC) ? 'desc' : undefined;
@@ -2978,57 +3115,48 @@ export class Parser {
2978
3115
  endToken = this.previous();
2979
3116
  const onConflict = this.parseConflictClause();
2980
3117
  if (onConflict)
2981
- endToken = this.previous(); // Update endToken if conflict clause was parsed
3118
+ endToken = this.previous();
2982
3119
  if (this.check(TokenType.AUTOINCREMENT)) {
2983
3120
  throw this.error(this.peek(), 'AUTOINCREMENT is not supported. Quereus uses key-based addressing without implicit side-effects.');
2984
3121
  }
2985
- return { type: 'primaryKey', name, onConflict, direction, loc: _createLoc(startToken, endToken) };
3122
+ result = { type: 'primaryKey', name, onConflict, direction, loc: _createLoc(startToken, endToken) };
2986
3123
  }
2987
3124
  else if (this.match(TokenType.NOT)) {
2988
3125
  this.consume(TokenType.NULL, "Expected NULL after NOT.");
2989
3126
  endToken = this.previous();
2990
3127
  const onConflict = this.parseConflictClause();
2991
3128
  if (onConflict)
2992
- endToken = this.previous(); // Update endToken if conflict clause was parsed
2993
- return { type: 'notNull', name, onConflict, loc: _createLoc(startToken, endToken) };
3129
+ endToken = this.previous();
3130
+ result = { type: 'notNull', name, onConflict, loc: _createLoc(startToken, endToken) };
2994
3131
  }
2995
3132
  else if (this.match(TokenType.NULL)) {
2996
3133
  endToken = this.previous();
2997
3134
  const onConflict = this.parseConflictClause();
2998
3135
  if (onConflict)
2999
- endToken = this.previous(); // Update endToken if conflict clause was parsed
3000
- return { type: 'null', name, onConflict, loc: _createLoc(startToken, endToken) };
3136
+ endToken = this.previous();
3137
+ result = { type: 'null', name, onConflict, loc: _createLoc(startToken, endToken) };
3001
3138
  }
3002
3139
  else if (this.match(TokenType.UNIQUE)) {
3003
3140
  endToken = this.previous();
3004
3141
  const onConflict = this.parseConflictClause();
3005
3142
  if (onConflict)
3006
- endToken = this.previous(); // Update endToken if conflict clause was parsed
3007
- return { type: 'unique', name, onConflict, loc: _createLoc(startToken, endToken) };
3143
+ endToken = this.previous();
3144
+ result = { type: 'unique', name, onConflict, loc: _createLoc(startToken, endToken) };
3008
3145
  }
3009
3146
  else if (this.match(TokenType.CHECK)) {
3010
- // --- Parse optional ON clause before parentheses --- //
3011
3147
  let operations;
3012
3148
  if (this.matchKeyword('ON')) {
3013
3149
  operations = this.parseRowOpList();
3014
3150
  }
3015
- // --- End Parse ON clause --- //
3016
3151
  this.consume(TokenType.LPAREN, "Expected '(' after CHECK.");
3017
3152
  const expr = this.expression();
3018
3153
  endToken = this.consume(TokenType.RPAREN, "Expected ')' after CHECK expression.");
3019
- // No DEFERRABLE syntax supported; deferral is auto-detected by the planner
3020
- return {
3021
- type: 'check',
3022
- name,
3023
- expr,
3024
- operations,
3025
- loc: _createLoc(startToken, endToken)
3026
- };
3154
+ result = { type: 'check', name, expr, operations, loc: _createLoc(startToken, endToken) };
3027
3155
  }
3028
3156
  else if (this.match(TokenType.DEFAULT)) {
3029
3157
  const expr = this.expression();
3030
3158
  endToken = this.previous();
3031
- return { type: 'default', name, expr, loc: _createLoc(startToken, endToken) };
3159
+ result = { type: 'default', name, expr, loc: _createLoc(startToken, endToken) };
3032
3160
  }
3033
3161
  else if (this.match(TokenType.COLLATE)) {
3034
3162
  if (!this.check(TokenType.IDENTIFIER)) {
@@ -3036,12 +3164,12 @@ export class Parser {
3036
3164
  }
3037
3165
  const collation = this.getIdentifierValue(this.advance());
3038
3166
  endToken = this.previous();
3039
- return { type: 'collate', name, collation, loc: _createLoc(startToken, endToken) };
3167
+ result = { type: 'collate', name, collation, loc: _createLoc(startToken, endToken) };
3040
3168
  }
3041
3169
  else if (this.match(TokenType.REFERENCES)) {
3042
3170
  const fkClause = this.foreignKeyClause();
3043
- endToken = this.previous(); // End token is end of FK clause
3044
- return { type: 'foreignKey', name, foreignKey: fkClause, loc: _createLoc(startToken, endToken) };
3171
+ endToken = this.previous();
3172
+ result = { type: 'foreignKey', name, foreignKey: fkClause, loc: _createLoc(startToken, endToken) };
3045
3173
  }
3046
3174
  else if (this.match(TokenType.GENERATED)) {
3047
3175
  this.consume(TokenType.ALWAYS, "Expected ALWAYS after GENERATED.");
@@ -3058,9 +3186,21 @@ export class Parser {
3058
3186
  else if (this.match(TokenType.VIRTUAL)) {
3059
3187
  endToken = this.previous();
3060
3188
  }
3061
- return { type: 'generated', name, generated: { expr, stored }, loc: _createLoc(startToken, endToken) };
3189
+ result = { type: 'generated', name, generated: { expr, stored }, loc: _createLoc(startToken, endToken) };
3190
+ }
3191
+ else {
3192
+ throw this.error(this.peek(), "Expected column constraint type (PRIMARY KEY, NOT NULL, UNIQUE, CHECK, DEFAULT, COLLATE, REFERENCES, GENERATED).");
3193
+ }
3194
+ // Parse optional trailing WITH TAGS for the constraint
3195
+ if (this.matchKeyword('WITH')) {
3196
+ if (this.matchKeyword('TAGS')) {
3197
+ result.tags = this.parseTags();
3198
+ }
3199
+ else {
3200
+ this.current--;
3201
+ }
3062
3202
  }
3063
- throw this.error(this.peek(), "Expected column constraint type (PRIMARY KEY, NOT NULL, UNIQUE, CHECK, DEFAULT, COLLATE, REFERENCES, GENERATED).");
3203
+ return result;
3064
3204
  }
3065
3205
  /** @internal Parses a table constraint */
3066
3206
  tableConstraint() {
@@ -3071,6 +3211,7 @@ export class Parser {
3071
3211
  name = this.consumeIdentifier("Expected constraint name after CONSTRAINT.");
3072
3212
  endToken = this.previous();
3073
3213
  }
3214
+ let result;
3074
3215
  if (this.match(TokenType.PRIMARY)) {
3075
3216
  this.consume(TokenType.KEY, "Expected KEY after PRIMARY.");
3076
3217
  this.consume(TokenType.LPAREN, "Expected '(' before PRIMARY KEY columns.");
@@ -3083,7 +3224,7 @@ export class Parser {
3083
3224
  const onConflict = this.parseConflictClause();
3084
3225
  if (onConflict)
3085
3226
  endToken = this.previous();
3086
- return { type: 'primaryKey', name, columns, onConflict, loc: _createLoc(startToken, endToken) };
3227
+ result = { type: 'primaryKey', name, columns, onConflict, loc: _createLoc(startToken, endToken) };
3087
3228
  }
3088
3229
  else if (this.match(TokenType.UNIQUE)) {
3089
3230
  this.consume(TokenType.LPAREN, "Expected '(' before UNIQUE columns.");
@@ -3093,26 +3234,17 @@ export class Parser {
3093
3234
  const onConflict = this.parseConflictClause();
3094
3235
  if (onConflict)
3095
3236
  endToken = this.previous();
3096
- return { type: 'unique', name, columns, onConflict, loc: _createLoc(startToken, endToken) };
3237
+ result = { type: 'unique', name, columns, onConflict, loc: _createLoc(startToken, endToken) };
3097
3238
  }
3098
3239
  else if (this.match(TokenType.CHECK)) {
3099
- // --- Parse optional ON clause before parentheses --- //
3100
3240
  let operations;
3101
3241
  if (this.matchKeyword('ON')) {
3102
3242
  operations = this.parseRowOpList();
3103
3243
  }
3104
- // --- End Parse ON clause --- //
3105
3244
  this.consume(TokenType.LPAREN, "Expected '(' after CHECK.");
3106
3245
  const expr = this.expression();
3107
3246
  endToken = this.consume(TokenType.RPAREN, "Expected ')' after CHECK expression.");
3108
- // No DEFERRABLE syntax supported; deferral is auto-detected by the planner
3109
- return {
3110
- type: 'check',
3111
- name,
3112
- expr,
3113
- operations,
3114
- loc: _createLoc(startToken, endToken)
3115
- };
3247
+ result = { type: 'check', name, expr, operations, loc: _createLoc(startToken, endToken) };
3116
3248
  }
3117
3249
  else if (this.match(TokenType.FOREIGN)) {
3118
3250
  this.consume(TokenType.KEY, "Expected KEY after FOREIGN.");
@@ -3120,10 +3252,22 @@ export class Parser {
3120
3252
  const columns = this.identifierList().map(name => ({ name }));
3121
3253
  this.consume(TokenType.RPAREN, "Expected ')' after FOREIGN KEY columns.");
3122
3254
  const fkClause = this.foreignKeyClause();
3123
- endToken = this.previous(); // End token is end of FK clause
3124
- return { type: 'foreignKey', name, columns, foreignKey: fkClause, loc: _createLoc(startToken, endToken) };
3255
+ endToken = this.previous();
3256
+ result = { type: 'foreignKey', name, columns, foreignKey: fkClause, loc: _createLoc(startToken, endToken) };
3257
+ }
3258
+ else {
3259
+ throw this.error(this.peek(), "Expected table constraint type (PRIMARY KEY, UNIQUE, CHECK, FOREIGN KEY).");
3260
+ }
3261
+ // Parse optional trailing WITH TAGS for the constraint
3262
+ if (this.matchKeyword('WITH')) {
3263
+ if (this.matchKeyword('TAGS')) {
3264
+ result.tags = this.parseTags();
3265
+ }
3266
+ else {
3267
+ this.current--;
3268
+ }
3125
3269
  }
3126
- throw this.error(this.peek(), "Expected table constraint type (PRIMARY KEY, UNIQUE, CHECK, FOREIGN KEY).");
3270
+ return result;
3127
3271
  }
3128
3272
  /** @internal Parses a foreign key clause (REFERENCES may already be consumed by caller) */
3129
3273
  foreignKeyClause() {
@@ -3223,7 +3367,7 @@ export class Parser {
3223
3367
  }
3224
3368
  else if (this.match(TokenType.NO)) {
3225
3369
  this.consume(TokenType.ACTION, "Expected ACTION after NO.");
3226
- return 'noAction';
3370
+ return 'ignore';
3227
3371
  }
3228
3372
  throw this.error(this.peek(), "Expected foreign key action (SET NULL, SET DEFAULT, CASCADE, RESTRICT, NO ACTION).");
3229
3373
  }