flowquery 1.0.68 → 1.0.70

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 (94) hide show
  1. package/README.md +244 -1
  2. package/dist/compute/runner.d.ts +6 -0
  3. package/dist/compute/runner.d.ts.map +1 -1
  4. package/dist/compute/runner.js +26 -0
  5. package/dist/compute/runner.js.map +1 -1
  6. package/dist/flowquery.min.js +1 -1
  7. package/dist/graph/bindings.d.ts +78 -0
  8. package/dist/graph/bindings.d.ts.map +1 -0
  9. package/dist/graph/bindings.js +210 -0
  10. package/dist/graph/bindings.js.map +1 -0
  11. package/dist/graph/database.d.ts +4 -2
  12. package/dist/graph/database.d.ts.map +1 -1
  13. package/dist/graph/database.js +45 -9
  14. package/dist/graph/database.js.map +1 -1
  15. package/dist/graph/physical_node.d.ts +9 -0
  16. package/dist/graph/physical_node.d.ts.map +1 -1
  17. package/dist/graph/physical_node.js +70 -3
  18. package/dist/graph/physical_node.js.map +1 -1
  19. package/dist/graph/physical_relationship.d.ts +9 -0
  20. package/dist/graph/physical_relationship.d.ts.map +1 -1
  21. package/dist/graph/physical_relationship.js +70 -3
  22. package/dist/graph/physical_relationship.js.map +1 -1
  23. package/dist/parsing/ast_node.d.ts +9 -0
  24. package/dist/parsing/ast_node.d.ts.map +1 -1
  25. package/dist/parsing/ast_node.js +21 -4
  26. package/dist/parsing/ast_node.js.map +1 -1
  27. package/dist/parsing/expressions/binding_reference.d.ts +16 -0
  28. package/dist/parsing/expressions/binding_reference.d.ts.map +1 -0
  29. package/dist/parsing/expressions/binding_reference.js +34 -0
  30. package/dist/parsing/expressions/binding_reference.js.map +1 -0
  31. package/dist/parsing/operations/create_node.d.ts +5 -1
  32. package/dist/parsing/operations/create_node.d.ts.map +1 -1
  33. package/dist/parsing/operations/create_node.js +12 -2
  34. package/dist/parsing/operations/create_node.js.map +1 -1
  35. package/dist/parsing/operations/create_relationship.d.ts +5 -1
  36. package/dist/parsing/operations/create_relationship.d.ts.map +1 -1
  37. package/dist/parsing/operations/create_relationship.js +12 -2
  38. package/dist/parsing/operations/create_relationship.js.map +1 -1
  39. package/dist/parsing/operations/drop_binding.d.ts +15 -0
  40. package/dist/parsing/operations/drop_binding.d.ts.map +1 -0
  41. package/dist/parsing/operations/drop_binding.js +42 -0
  42. package/dist/parsing/operations/drop_binding.js.map +1 -0
  43. package/dist/parsing/operations/let.d.ts +36 -0
  44. package/dist/parsing/operations/let.d.ts.map +1 -0
  45. package/dist/parsing/operations/let.js +101 -0
  46. package/dist/parsing/operations/let.js.map +1 -0
  47. package/dist/parsing/operations/load.d.ts +11 -0
  48. package/dist/parsing/operations/load.d.ts.map +1 -1
  49. package/dist/parsing/operations/load.js +31 -2
  50. package/dist/parsing/operations/load.js.map +1 -1
  51. package/dist/parsing/operations/merge.d.ts +158 -0
  52. package/dist/parsing/operations/merge.d.ts.map +1 -0
  53. package/dist/parsing/operations/merge.js +338 -0
  54. package/dist/parsing/operations/merge.js.map +1 -0
  55. package/dist/parsing/operations/refresh_binding.d.ts +15 -0
  56. package/dist/parsing/operations/refresh_binding.d.ts.map +1 -0
  57. package/dist/parsing/operations/refresh_binding.js +42 -0
  58. package/dist/parsing/operations/refresh_binding.js.map +1 -0
  59. package/dist/parsing/operations/refresh_node.d.ts +11 -0
  60. package/dist/parsing/operations/refresh_node.d.ts.map +1 -0
  61. package/dist/parsing/operations/refresh_node.js +46 -0
  62. package/dist/parsing/operations/refresh_node.js.map +1 -0
  63. package/dist/parsing/operations/refresh_relationship.d.ts +11 -0
  64. package/dist/parsing/operations/refresh_relationship.d.ts.map +1 -0
  65. package/dist/parsing/operations/refresh_relationship.js +46 -0
  66. package/dist/parsing/operations/refresh_relationship.js.map +1 -0
  67. package/dist/parsing/operations/return.d.ts.map +1 -1
  68. package/dist/parsing/operations/return.js +7 -1
  69. package/dist/parsing/operations/return.js.map +1 -1
  70. package/dist/parsing/operations/update.d.ts +27 -0
  71. package/dist/parsing/operations/update.d.ts.map +1 -0
  72. package/dist/parsing/operations/update.js +88 -0
  73. package/dist/parsing/operations/update.js.map +1 -0
  74. package/dist/parsing/operations/update_delete.d.ts +43 -0
  75. package/dist/parsing/operations/update_delete.d.ts.map +1 -0
  76. package/dist/parsing/operations/update_delete.js +105 -0
  77. package/dist/parsing/operations/update_delete.js.map +1 -0
  78. package/dist/parsing/parser.d.ts +70 -1
  79. package/dist/parsing/parser.d.ts.map +1 -1
  80. package/dist/parsing/parser.js +704 -10
  81. package/dist/parsing/parser.js.map +1 -1
  82. package/dist/parsing/statement_info_crawler.d.ts +26 -0
  83. package/dist/parsing/statement_info_crawler.d.ts.map +1 -1
  84. package/dist/parsing/statement_info_crawler.js +86 -4
  85. package/dist/parsing/statement_info_crawler.js.map +1 -1
  86. package/dist/tokenization/keyword.d.ts +21 -1
  87. package/dist/tokenization/keyword.d.ts.map +1 -1
  88. package/dist/tokenization/keyword.js +20 -0
  89. package/dist/tokenization/keyword.js.map +1 -1
  90. package/dist/tokenization/token.d.ts +24 -0
  91. package/dist/tokenization/token.d.ts.map +1 -1
  92. package/dist/tokenization/token.js +73 -0
  93. package/dist/tokenization/token.js.map +1 -1
  94. package/package.json +1 -1
@@ -58,6 +58,7 @@ const key_value_pair_1 = __importDefault(require("./data_structures/key_value_pa
58
58
  const list_comprehension_1 = __importDefault(require("./data_structures/list_comprehension"));
59
59
  const lookup_1 = __importDefault(require("./data_structures/lookup"));
60
60
  const range_lookup_1 = __importDefault(require("./data_structures/range_lookup"));
61
+ const binding_reference_1 = __importDefault(require("./expressions/binding_reference"));
61
62
  const expression_1 = __importDefault(require("./expressions/expression"));
62
63
  const f_string_1 = __importDefault(require("./expressions/f_string"));
63
64
  const identifier_1 = __importDefault(require("./expressions/identifier"));
@@ -80,14 +81,22 @@ const create_node_1 = __importDefault(require("./operations/create_node"));
80
81
  const create_relationship_1 = __importDefault(require("./operations/create_relationship"));
81
82
  const delete_node_1 = __importDefault(require("./operations/delete_node"));
82
83
  const delete_relationship_1 = __importDefault(require("./operations/delete_relationship"));
84
+ const drop_binding_1 = __importDefault(require("./operations/drop_binding"));
85
+ const let_1 = __importDefault(require("./operations/let"));
83
86
  const limit_1 = __importDefault(require("./operations/limit"));
84
87
  const load_1 = __importDefault(require("./operations/load"));
85
88
  const match_1 = __importDefault(require("./operations/match"));
89
+ const merge_1 = __importStar(require("./operations/merge"));
86
90
  const order_by_1 = __importDefault(require("./operations/order_by"));
91
+ const refresh_binding_1 = __importDefault(require("./operations/refresh_binding"));
92
+ const refresh_node_1 = __importDefault(require("./operations/refresh_node"));
93
+ const refresh_relationship_1 = __importDefault(require("./operations/refresh_relationship"));
87
94
  const return_1 = __importDefault(require("./operations/return"));
88
95
  const union_1 = __importDefault(require("./operations/union"));
89
96
  const union_all_1 = __importDefault(require("./operations/union_all"));
90
97
  const unwind_1 = __importDefault(require("./operations/unwind"));
98
+ const update_1 = __importDefault(require("./operations/update"));
99
+ const update_delete_1 = __importDefault(require("./operations/update_delete"));
91
100
  const where_1 = __importDefault(require("./operations/where"));
92
101
  const with_1 = __importDefault(require("./operations/with"));
93
102
  const parser_state_1 = __importDefault(require("./parser_state"));
@@ -175,7 +184,9 @@ class Parser extends base_parser_1.default {
175
184
  }
176
185
  }
177
186
  /**
178
- * Validates that all operations in a statement are CREATE or DELETE.
187
+ * Validates that all operations in a statement are CREATE, DELETE,
188
+ * REFRESH, LET, or UPDATE — the declaration kinds that may precede the
189
+ * terminal statement in a multi-statement query.
179
190
  */
180
191
  validateIsCreateOrDelete(root) {
181
192
  let op = root.firstChild();
@@ -183,8 +194,16 @@ class Parser extends base_parser_1.default {
183
194
  if (!(op instanceof create_node_1.default) &&
184
195
  !(op instanceof create_relationship_1.default) &&
185
196
  !(op instanceof delete_node_1.default) &&
186
- !(op instanceof delete_relationship_1.default)) {
187
- throw new Error("Only CREATE and DELETE statements can appear before the last statement in a multi-statement query");
197
+ !(op instanceof delete_relationship_1.default) &&
198
+ !(op instanceof drop_binding_1.default) &&
199
+ !(op instanceof refresh_node_1.default) &&
200
+ !(op instanceof refresh_relationship_1.default) &&
201
+ !(op instanceof refresh_binding_1.default) &&
202
+ !(op instanceof let_1.default) &&
203
+ !(op instanceof update_1.default) &&
204
+ !(op instanceof update_delete_1.default) &&
205
+ !(op instanceof merge_1.default)) {
206
+ throw new Error("Only CREATE, DELETE, DROP, REFRESH, LET, UPDATE, and MERGE statements can appear before the last statement in a multi-statement query");
188
207
  }
189
208
  op = op.next;
190
209
  }
@@ -198,6 +217,9 @@ class Parser extends base_parser_1.default {
198
217
  if (this.token.isSemicolon()) {
199
218
  break;
200
219
  }
220
+ if (isSubQuery && this.token.isClosingBrace()) {
221
+ return root;
222
+ }
201
223
  this.expectAndSkipWhitespaceAndComments();
202
224
  }
203
225
  else {
@@ -292,8 +314,16 @@ class Parser extends base_parser_1.default {
292
314
  !(operation instanceof create_node_1.default) &&
293
315
  !(operation instanceof create_relationship_1.default) &&
294
316
  !(operation instanceof delete_node_1.default) &&
295
- !(operation instanceof delete_relationship_1.default)) {
296
- throw new Error("Last statement must be a RETURN, WHERE, CALL, CREATE, or DELETE statement");
317
+ !(operation instanceof delete_relationship_1.default) &&
318
+ !(operation instanceof drop_binding_1.default) &&
319
+ !(operation instanceof refresh_node_1.default) &&
320
+ !(operation instanceof refresh_relationship_1.default) &&
321
+ !(operation instanceof refresh_binding_1.default) &&
322
+ !(operation instanceof let_1.default) &&
323
+ !(operation instanceof update_1.default) &&
324
+ !(operation instanceof update_delete_1.default) &&
325
+ !(operation instanceof merge_1.default)) {
326
+ throw new Error("Last statement must be a RETURN, WHERE, CALL, CREATE, DELETE, DROP, REFRESH, LET, UPDATE, or MERGE statement");
297
327
  }
298
328
  return root;
299
329
  }
@@ -305,6 +335,10 @@ class Parser extends base_parser_1.default {
305
335
  this.parseCall() ||
306
336
  this.parseCreate() ||
307
337
  this.parseDelete() ||
338
+ this.parseRefresh() ||
339
+ this.parseLet() ||
340
+ this.parseUpdate() ||
341
+ this.parseMerge() ||
308
342
  this.parseMatch());
309
343
  }
310
344
  parseWith() {
@@ -434,6 +468,15 @@ class Parser extends base_parser_1.default {
434
468
  if (expression === null) {
435
469
  throw new Error("Expected expression or async function");
436
470
  }
471
+ // A bare unresolved identifier in `LOAD JSON FROM <name>` is
472
+ // treated as a reference to a LET-bound value, resolved at
473
+ // execution time against the global Bindings store.
474
+ const inner = expression.firstChild();
475
+ if (inner instanceof reference_1.default &&
476
+ inner.referred === undefined &&
477
+ !inner.identifier.startsWith("$")) {
478
+ expression.replaceChild(inner, new binding_reference_1.default(inner.identifier));
479
+ }
437
480
  from.addChild(expression);
438
481
  }
439
482
  this.expectAndSkipWhitespaceAndComments();
@@ -503,6 +546,12 @@ class Parser extends base_parser_1.default {
503
546
  }
504
547
  this.setNextToken();
505
548
  this.expectAndSkipWhitespaceAndComments();
549
+ let isStatic = false;
550
+ if (this.token.isStatic()) {
551
+ isStatic = true;
552
+ this.setNextToken();
553
+ this.expectAndSkipWhitespaceAndComments();
554
+ }
506
555
  if (!this.token.isVirtual()) {
507
556
  throw new Error("Expected VIRTUAL");
508
557
  }
@@ -554,24 +603,91 @@ class Parser extends base_parser_1.default {
554
603
  if (query === null) {
555
604
  throw new Error("Expected sub-query");
556
605
  }
606
+ // Optional trailing REFRESH EVERY <n> <unit> clause. Only allowed
607
+ // for STATIC virtual entities (caching must be enabled to refresh).
608
+ // We deliberately peek past REFRESH to confirm EVERY follows: a bare
609
+ // `REFRESH VIRTUAL ...` is a separate top-level statement.
610
+ let refreshEveryMs = null;
611
+ const savedIndex = this.tokenIndex;
612
+ this.skipWhitespaceAndComments();
613
+ let consumedRefreshClause = false;
614
+ if (this.token.isRefresh()) {
615
+ // Look ahead past whitespace for EVERY.
616
+ const afterRefreshIndex = this.tokenIndex;
617
+ this.setNextToken();
618
+ this.skipWhitespaceAndComments();
619
+ if (this.token.isEvery()) {
620
+ if (!isStatic) {
621
+ throw new Error("REFRESH EVERY requires STATIC (caching must be enabled)");
622
+ }
623
+ this.setNextToken();
624
+ this.expectAndSkipWhitespaceAndComments();
625
+ if (!this.token.isNumber()) {
626
+ throw new Error("Expected number after REFRESH EVERY");
627
+ }
628
+ const amount = Number(this.token.value);
629
+ if (!Number.isFinite(amount) || amount <= 0) {
630
+ throw new Error("REFRESH EVERY interval must be a positive number");
631
+ }
632
+ this.setNextToken();
633
+ this.expectAndSkipWhitespaceAndComments();
634
+ const unit = (this.token.value || "").toUpperCase();
635
+ const unitMs = {
636
+ SECOND: 1000,
637
+ SECONDS: 1000,
638
+ MINUTE: 60000,
639
+ MINUTES: 60000,
640
+ HOUR: 3600000,
641
+ HOURS: 3600000,
642
+ DAY: 86400000,
643
+ DAYS: 86400000,
644
+ };
645
+ if (!(unit in unitMs)) {
646
+ throw new Error("Expected time unit (SECOND[S], MINUTE[S], HOUR[S], DAY[S]) after REFRESH EVERY interval");
647
+ }
648
+ refreshEveryMs = amount * unitMs[unit];
649
+ this.setNextToken();
650
+ consumedRefreshClause = true;
651
+ }
652
+ else {
653
+ // Not REFRESH EVERY — must be the start of a separate
654
+ // REFRESH VIRTUAL statement. Rewind to before REFRESH.
655
+ this.tokenIndex = afterRefreshIndex;
656
+ }
657
+ }
658
+ if (!consumedRefreshClause) {
659
+ // No REFRESH clause present; restore token position so the
660
+ // outer loop sees the original whitespace/next-clause boundary.
661
+ this.tokenIndex = savedIndex;
662
+ }
557
663
  let create;
558
664
  if (relationship !== null) {
559
- create = new create_relationship_1.default(relationship, query);
665
+ create = new create_relationship_1.default(relationship, query, isStatic, refreshEveryMs);
560
666
  }
561
667
  else {
562
- create = new create_node_1.default(node, query);
668
+ create = new create_node_1.default(node, query, isStatic, refreshEveryMs);
563
669
  }
564
670
  return create;
565
671
  }
566
672
  parseDelete() {
567
673
  var _a;
568
- if (!this.token.isDelete()) {
674
+ if (!this.token.isDelete() && !this.token.isDrop()) {
569
675
  return null;
570
676
  }
571
677
  this.setNextToken();
572
678
  this.expectAndSkipWhitespaceAndComments();
679
+ if (this.token.isBinding()) {
680
+ this.setNextToken();
681
+ this.expectAndSkipWhitespaceAndComments();
682
+ if (!this.token.isIdentifierOrKeyword()) {
683
+ throw new Error("Expected binding name");
684
+ }
685
+ const name = this.token.value || "";
686
+ this.setNextToken();
687
+ return new drop_binding_1.default(name);
688
+ }
573
689
  if (!this.token.isVirtual()) {
574
- throw new Error("Expected VIRTUAL");
690
+ throw new Error("Expected VIRTUAL or BINDING");
575
691
  }
576
692
  this.setNextToken();
577
693
  this.expectAndSkipWhitespaceAndComments();
@@ -618,6 +734,584 @@ class Parser extends base_parser_1.default {
618
734
  }
619
735
  return result;
620
736
  }
737
+ parseRefresh() {
738
+ var _a;
739
+ if (!this.token.isRefresh()) {
740
+ return null;
741
+ }
742
+ this.setNextToken();
743
+ this.expectAndSkipWhitespaceAndComments();
744
+ if (this.token.isBinding()) {
745
+ this.setNextToken();
746
+ this.expectAndSkipWhitespaceAndComments();
747
+ if (!this.token.isIdentifierOrKeyword()) {
748
+ throw new Error("Expected binding name");
749
+ }
750
+ const name = this.token.value || "";
751
+ this.setNextToken();
752
+ return new refresh_binding_1.default(name);
753
+ }
754
+ if (!this.token.isVirtual()) {
755
+ throw new Error("Expected VIRTUAL or BINDING");
756
+ }
757
+ this.setNextToken();
758
+ this.expectAndSkipWhitespaceAndComments();
759
+ const node = this.parseNode();
760
+ if (node === null) {
761
+ throw new Error("Expected node definition");
762
+ }
763
+ let relationship = null;
764
+ if (this.token.isSubtract() && ((_a = this.peek()) === null || _a === void 0 ? void 0 : _a.isOpeningBracket())) {
765
+ this.setNextToken();
766
+ this.setNextToken();
767
+ if (!this.token.isColon()) {
768
+ throw new Error("Expected ':' for relationship type");
769
+ }
770
+ this.setNextToken();
771
+ if (!this.token.isIdentifierOrKeyword()) {
772
+ throw new Error("Expected relationship type identifier");
773
+ }
774
+ const type = this.token.value || "";
775
+ this.setNextToken();
776
+ if (!this.token.isClosingBracket()) {
777
+ throw new Error("Expected closing bracket for relationship definition");
778
+ }
779
+ this.setNextToken();
780
+ if (!this.token.isSubtract()) {
781
+ throw new Error("Expected '-' for relationship definition");
782
+ }
783
+ this.setNextToken();
784
+ const target = this.parseNode();
785
+ if (target === null) {
786
+ throw new Error("Expected target node definition");
787
+ }
788
+ relationship = new relationship_1.default();
789
+ relationship.type = type;
790
+ relationship.source = node;
791
+ relationship.target = target;
792
+ }
793
+ if (relationship !== null) {
794
+ return new refresh_relationship_1.default(relationship);
795
+ }
796
+ return new refresh_node_1.default(node);
797
+ }
798
+ /**
799
+ * Returns true when the current token is a clause keyword that
800
+ * starts a query pipeline (the right-hand side of a `LET name =`
801
+ * or `UPDATE name =`). Used to disambiguate between binding a
802
+ * plain expression and binding the rows produced by a sub-query.
803
+ */
804
+ looksLikePipelineStart() {
805
+ return (this.token.isWith() ||
806
+ this.token.isUnwind() ||
807
+ this.token.isLoad() ||
808
+ this.token.isCall() ||
809
+ this.token.isMatch() ||
810
+ this.token.isOptional() ||
811
+ this.token.isReturn());
812
+ }
813
+ /**
814
+ * Peeks past `{` to decide whether the brace opens a sub-query
815
+ * (first significant token is a clause keyword) or a map literal
816
+ * (anything else, e.g. identifier+colon or string key).
817
+ */
818
+ braceOpensSubQuery() {
819
+ if (!this.token.isOpeningBrace()) {
820
+ return false;
821
+ }
822
+ const savedIndex = this.tokenIndex;
823
+ this.setNextToken();
824
+ this.skipWhitespaceAndComments();
825
+ const result = this.token.isWith() ||
826
+ this.token.isUnwind() ||
827
+ this.token.isLoad() ||
828
+ this.token.isCall() ||
829
+ this.token.isMatch() ||
830
+ this.token.isOptional() ||
831
+ this.token.isReturn();
832
+ this.tokenIndex = savedIndex;
833
+ return result;
834
+ }
835
+ /**
836
+ * Parses the right-hand side of a `LET` / `UPDATE`. Returns the
837
+ * expression *or* the sub-query AST that was parsed.
838
+ */
839
+ parseLetUpdateRhs() {
840
+ this.expectAndSkipWhitespaceAndComments();
841
+ // Explicit braces wrap a sub-query only when followed by a
842
+ // clause keyword; otherwise `{ ... }` is a map literal.
843
+ if (this.braceOpensSubQuery()) {
844
+ const subQuery = this.parseSubQuery();
845
+ if (subQuery === null) {
846
+ throw new Error("Expected sub-query");
847
+ }
848
+ return { expression: null, subQuery };
849
+ }
850
+ if (this.looksLikePipelineStart()) {
851
+ // Inline sub-query: parse a pipeline up to ; / EOF.
852
+ const savedState = this._state;
853
+ this._state = new parser_state_1.default();
854
+ const subQuery = this._parseTokenized(true);
855
+ this._state = savedState;
856
+ if (subQuery.firstChild() === null) {
857
+ throw new Error("Expected expression or sub-query");
858
+ }
859
+ return { expression: null, subQuery };
860
+ }
861
+ const expression = this.parseExpression();
862
+ if (expression === null) {
863
+ throw new Error("Expected expression or sub-query");
864
+ }
865
+ return { expression, subQuery: null };
866
+ }
867
+ parseLet() {
868
+ if (!this.token.isLet()) {
869
+ return null;
870
+ }
871
+ this.setNextToken();
872
+ this.expectAndSkipWhitespaceAndComments();
873
+ if (!this.token.isIdentifierOrKeyword() || this.token.value === null) {
874
+ throw new Error("Expected identifier after LET");
875
+ }
876
+ const name = this.token.value;
877
+ this.setNextToken();
878
+ this.skipWhitespaceAndComments();
879
+ if (!this.token.isEquals()) {
880
+ throw new Error("Expected '=' after LET identifier");
881
+ }
882
+ this.setNextToken();
883
+ const { expression, subQuery } = this.parseLetUpdateRhs();
884
+ // Optional trailing REFRESH EVERY <n> <unit> clause turns the
885
+ // binding into a refreshable one. Requires a sub-query RHS
886
+ // (there is nothing to re-evaluate for a literal expression).
887
+ // Peek past REFRESH to confirm EVERY follows: a bare
888
+ // `REFRESH BINDING` is a separate top-level statement.
889
+ let refreshEveryMs = null;
890
+ const savedIndex = this.tokenIndex;
891
+ this.skipWhitespaceAndComments();
892
+ let consumedRefreshClause = false;
893
+ if (this.token.isRefresh()) {
894
+ const afterRefreshIndex = this.tokenIndex;
895
+ this.setNextToken();
896
+ this.skipWhitespaceAndComments();
897
+ if (this.token.isEvery()) {
898
+ if (subQuery === null) {
899
+ throw new Error("LET REFRESH EVERY requires a sub-query right-hand side");
900
+ }
901
+ this.setNextToken();
902
+ this.expectAndSkipWhitespaceAndComments();
903
+ if (!this.token.isNumber()) {
904
+ throw new Error("Expected number after REFRESH EVERY");
905
+ }
906
+ const amount = Number(this.token.value);
907
+ if (!Number.isFinite(amount) || amount <= 0) {
908
+ throw new Error("REFRESH EVERY interval must be a positive number");
909
+ }
910
+ this.setNextToken();
911
+ this.expectAndSkipWhitespaceAndComments();
912
+ const unit = (this.token.value || "").toUpperCase();
913
+ const unitMs = {
914
+ SECOND: 1000,
915
+ SECONDS: 1000,
916
+ MINUTE: 60000,
917
+ MINUTES: 60000,
918
+ HOUR: 3600000,
919
+ HOURS: 3600000,
920
+ DAY: 86400000,
921
+ DAYS: 86400000,
922
+ };
923
+ if (!(unit in unitMs)) {
924
+ throw new Error("Expected time unit (SECOND[S], MINUTE[S], HOUR[S], DAY[S]) after REFRESH EVERY interval");
925
+ }
926
+ refreshEveryMs = amount * unitMs[unit];
927
+ this.setNextToken();
928
+ consumedRefreshClause = true;
929
+ }
930
+ else {
931
+ // Not REFRESH EVERY; must be the start of a separate
932
+ // REFRESH statement. Rewind to before REFRESH.
933
+ this.tokenIndex = afterRefreshIndex;
934
+ }
935
+ }
936
+ if (!consumedRefreshClause) {
937
+ this.tokenIndex = savedIndex;
938
+ }
939
+ return new let_1.default(name, expression, subQuery, refreshEveryMs);
940
+ }
941
+ parseUpdate() {
942
+ if (!this.token.isUpdate()) {
943
+ return null;
944
+ }
945
+ this.setNextToken();
946
+ this.expectAndSkipWhitespaceAndComments();
947
+ if (!this.token.isIdentifierOrKeyword() || this.token.value === null) {
948
+ throw new Error("Expected identifier after UPDATE");
949
+ }
950
+ const name = this.token.value;
951
+ this.setNextToken();
952
+ this.skipWhitespaceAndComments();
953
+ // `UPDATE name AS alias DELETE WHERE <pred>` — row-filtering branch.
954
+ if (this.token.isAs()) {
955
+ return this.parseUpdateDeleteTail(name);
956
+ }
957
+ // `UPDATE name = <rhs>` — assign branch.
958
+ if (!this.token.isEquals()) {
959
+ throw new Error("Expected '=' in UPDATE");
960
+ }
961
+ this.setNextToken();
962
+ const { expression, subQuery } = this.parseLetUpdateRhs();
963
+ return new update_1.default(name, expression, subQuery);
964
+ }
965
+ /**
966
+ * Parses a `MERGE INTO <name> [AS <target>] USING <rhs>
967
+ * [AS <source>] ON <on-clause> <when-clauses>` statement.
968
+ */
969
+ parseMerge() {
970
+ if (!this.token.isMerge()) {
971
+ return null;
972
+ }
973
+ this.setNextToken();
974
+ this.expectAndSkipWhitespaceAndComments();
975
+ if (!this.token.isInto()) {
976
+ throw new Error("Expected INTO after MERGE");
977
+ }
978
+ this.setNextToken();
979
+ this.expectAndSkipWhitespaceAndComments();
980
+ if (!this.token.isIdentifierOrKeyword() || this.token.value === null) {
981
+ throw new Error("Expected binding name after MERGE INTO");
982
+ }
983
+ const name = this.token.value;
984
+ this.setNextToken();
985
+ this.skipWhitespaceAndComments();
986
+ // Optional `AS <target-alias>`.
987
+ let targetAlias = null;
988
+ if (this.token.isAs()) {
989
+ this.setNextToken();
990
+ this.expectAndSkipWhitespaceAndComments();
991
+ if (!this.token.isIdentifierOrKeyword() || this.token.value === null) {
992
+ throw new Error("Expected alias after AS in MERGE INTO");
993
+ }
994
+ targetAlias = this.token.value;
995
+ this.setNextToken();
996
+ this.skipWhitespaceAndComments();
997
+ }
998
+ if (!this.token.isUsing()) {
999
+ throw new Error("Expected USING after MERGE INTO target");
1000
+ }
1001
+ this.setNextToken();
1002
+ const { expression: sourceExpression, subQuery: sourceSubQuery } = this.parseLetUpdateRhs();
1003
+ if (sourceExpression !== null) {
1004
+ // The source RHS lives outside the LET / UPDATE binding-RHS
1005
+ // scope, so a bare identifier here refers to an existing
1006
+ // binding (e.g. `USING incoming AS s`) rather than a
1007
+ // declaration-site name. Convert unresolved references so
1008
+ // they resolve against the global Bindings store at runtime.
1009
+ this.convertUnresolvedReferencesToBindings(sourceExpression);
1010
+ }
1011
+ this.skipWhitespaceAndComments();
1012
+ // Optional `AS <source-alias>`.
1013
+ let sourceAlias = null;
1014
+ if (this.token.isAs()) {
1015
+ this.setNextToken();
1016
+ this.expectAndSkipWhitespaceAndComments();
1017
+ if (!this.token.isIdentifierOrKeyword() || this.token.value === null) {
1018
+ throw new Error("Expected alias after AS in MERGE INTO … USING");
1019
+ }
1020
+ sourceAlias = this.token.value;
1021
+ this.setNextToken();
1022
+ this.skipWhitespaceAndComments();
1023
+ }
1024
+ if (!this.token.isOn()) {
1025
+ throw new Error("Expected ON in MERGE INTO");
1026
+ }
1027
+ this.setNextToken();
1028
+ this.expectAndSkipWhitespaceAndComments();
1029
+ // Pre-create the Merge with placeholders so that aliases can
1030
+ // resolve to the per-row context while we parse the ON clause
1031
+ // and the WHEN expressions. The proxy aliases delegate to
1032
+ // `targetValue()` / `sourceValue()` on this instance.
1033
+ const placeholderOn = { type: "keys", keys: [] };
1034
+ const merge = new merge_1.default(name, targetAlias, sourceAlias, sourceExpression, sourceSubQuery, placeholderOn, null, null);
1035
+ if (targetAlias !== null) {
1036
+ this._state.variables.set(targetAlias, new merge_1.MergeTargetAlias(merge));
1037
+ }
1038
+ if (sourceAlias !== null) {
1039
+ this._state.variables.set(sourceAlias, new merge_1.MergeSourceAlias(merge));
1040
+ }
1041
+ let onClause;
1042
+ try {
1043
+ onClause = this.parseMergeOn();
1044
+ this.skipWhitespaceAndComments();
1045
+ // Build the matched / not-matched actions from one or more
1046
+ // WHEN clauses. Track whether we've already seen each kind
1047
+ // to forbid duplicates.
1048
+ let matched = null;
1049
+ let notMatched = null;
1050
+ while (this.token.isWhen()) {
1051
+ const parsed = this.parseMergeWhenClause();
1052
+ if (parsed.kind === "matched") {
1053
+ if (matched !== null) {
1054
+ throw new Error("Duplicate WHEN MATCHED clause in MERGE INTO");
1055
+ }
1056
+ matched = parsed.action;
1057
+ }
1058
+ else {
1059
+ if (notMatched !== null) {
1060
+ throw new Error("Duplicate WHEN NOT MATCHED clause in MERGE INTO");
1061
+ }
1062
+ notMatched = parsed.action;
1063
+ }
1064
+ this.skipWhitespaceAndComments();
1065
+ }
1066
+ if (matched === null && notMatched === null) {
1067
+ throw new Error("MERGE INTO requires at least one WHEN clause");
1068
+ }
1069
+ merge.setClauses(onClause, matched, notMatched);
1070
+ }
1071
+ finally {
1072
+ if (targetAlias !== null) {
1073
+ this._state.variables.delete(targetAlias);
1074
+ }
1075
+ if (sourceAlias !== null) {
1076
+ this._state.variables.delete(sourceAlias);
1077
+ }
1078
+ }
1079
+ return merge;
1080
+ }
1081
+ /**
1082
+ * Parses the body of the `ON` clause. Distinguishes a short-form
1083
+ * key list (`ON id`, `ON (a, b, c)`) from a full predicate
1084
+ * expression by looking ahead: a bare identifier (or parenthesised
1085
+ * list of identifiers) followed by `WHEN` is the key form;
1086
+ * anything else is parsed as an expression.
1087
+ */
1088
+ parseMergeOn() {
1089
+ // Snapshot tokenizer position so we can rewind after a failed
1090
+ // key-list probe.
1091
+ const savedIndex = this.tokenIndex;
1092
+ const keys = this.tryParseMergeKeyList();
1093
+ if (keys !== null) {
1094
+ return { type: "keys", keys };
1095
+ }
1096
+ // Not a key list — rewind and parse as a predicate expression.
1097
+ this.tokenIndex = savedIndex;
1098
+ const predicate = this.parseExpression();
1099
+ if (predicate === null) {
1100
+ throw new Error("Expected predicate or key list after MERGE INTO … ON");
1101
+ }
1102
+ this.convertUnresolvedReferencesToBindings(predicate);
1103
+ return { type: "predicate", predicate };
1104
+ }
1105
+ /**
1106
+ * Speculatively parses `<id>` or `(<id> [, <id>]*)` followed by
1107
+ * `WHEN`. Returns the key list on success, or `null` if the
1108
+ * lookahead doesn't match (leaving the caller to rewind and try
1109
+ * an expression).
1110
+ */
1111
+ tryParseMergeKeyList() {
1112
+ const keys = [];
1113
+ if (this.token.isLeftParenthesis()) {
1114
+ this.setNextToken();
1115
+ this.skipWhitespaceAndComments();
1116
+ while (!this.token.isRightParenthesis()) {
1117
+ if (!this.token.isIdentifierOrKeyword() || this.token.value === null) {
1118
+ return null;
1119
+ }
1120
+ keys.push(this.token.value);
1121
+ this.setNextToken();
1122
+ this.skipWhitespaceAndComments();
1123
+ if (this.token.isComma()) {
1124
+ this.setNextToken();
1125
+ this.skipWhitespaceAndComments();
1126
+ }
1127
+ else if (!this.token.isRightParenthesis()) {
1128
+ return null;
1129
+ }
1130
+ }
1131
+ this.setNextToken();
1132
+ }
1133
+ else {
1134
+ if (!this.token.isIdentifierOrKeyword() || this.token.value === null) {
1135
+ return null;
1136
+ }
1137
+ keys.push(this.token.value);
1138
+ this.setNextToken();
1139
+ }
1140
+ // Must be followed by `WHEN` (with any whitespace) to be a key list.
1141
+ this.skipWhitespaceAndComments();
1142
+ if (!this.token.isWhen()) {
1143
+ return null;
1144
+ }
1145
+ if (keys.length === 0) {
1146
+ return null;
1147
+ }
1148
+ return keys;
1149
+ }
1150
+ /**
1151
+ * Parses a single `WHEN [NOT] MATCHED THEN UPDATE SET … | DELETE
1152
+ * | INSERT [<expr>]` clause. Caller is positioned on `WHEN`.
1153
+ */
1154
+ parseMergeWhenClause() {
1155
+ this.setNextToken(); // consume WHEN
1156
+ this.expectAndSkipWhitespaceAndComments();
1157
+ let negated = false;
1158
+ if (this.token.isNot()) {
1159
+ negated = true;
1160
+ this.setNextToken();
1161
+ this.expectAndSkipWhitespaceAndComments();
1162
+ }
1163
+ if (!this.token.isMatched()) {
1164
+ throw new Error("Expected MATCHED after WHEN" + (negated ? " NOT" : ""));
1165
+ }
1166
+ this.setNextToken();
1167
+ this.expectAndSkipWhitespaceAndComments();
1168
+ if (!this.token.isThen()) {
1169
+ throw new Error("Expected THEN after WHEN" + (negated ? " NOT" : "") + " MATCHED");
1170
+ }
1171
+ this.setNextToken();
1172
+ this.expectAndSkipWhitespaceAndComments();
1173
+ if (negated) {
1174
+ // WHEN NOT MATCHED — only INSERT is meaningful.
1175
+ if (!this.token.isInsert()) {
1176
+ throw new Error("Expected INSERT after WHEN NOT MATCHED THEN");
1177
+ }
1178
+ this.setNextToken();
1179
+ this.skipWhitespaceAndComments();
1180
+ // Optional expression: anything that isn't a WHEN / EOF /
1181
+ // semicolon / closing brace is the row-builder expression.
1182
+ let expression = null;
1183
+ if (!this.token.isWhen() &&
1184
+ !this.token.isEOF() &&
1185
+ !this.token.isSemicolon() &&
1186
+ !this.token.isClosingBrace()) {
1187
+ expression = this.parseExpression();
1188
+ if (expression === null) {
1189
+ throw new Error("Expected expression after WHEN NOT MATCHED THEN INSERT");
1190
+ }
1191
+ this.convertUnresolvedReferencesToBindings(expression);
1192
+ }
1193
+ return { kind: "notMatched", action: { type: "insert", expression } };
1194
+ }
1195
+ // WHEN MATCHED — accepts UPDATE SET … or DELETE.
1196
+ if (this.token.isUpdate()) {
1197
+ this.setNextToken();
1198
+ this.expectAndSkipWhitespaceAndComments();
1199
+ if (!this.token.isSet()) {
1200
+ throw new Error("Expected SET after WHEN MATCHED THEN UPDATE");
1201
+ }
1202
+ this.setNextToken();
1203
+ this.expectAndSkipWhitespaceAndComments();
1204
+ const setItems = this.parseMergeSetList();
1205
+ return { kind: "matched", action: { type: "update", setItems } };
1206
+ }
1207
+ if (this.token.isDelete()) {
1208
+ this.setNextToken();
1209
+ return { kind: "matched", action: { type: "delete" } };
1210
+ }
1211
+ throw new Error("Expected UPDATE or DELETE after WHEN MATCHED THEN");
1212
+ }
1213
+ /**
1214
+ * Parses `.field [= <expr>] [, .field [= <expr>]]*` — the body of a
1215
+ * `WHEN MATCHED THEN UPDATE SET` clause. An item without an
1216
+ * explicit `= <expr>` defaults to `source.<field>` at run time.
1217
+ */
1218
+ parseMergeSetList() {
1219
+ const items = [];
1220
+ while (true) {
1221
+ if (!this.token.isDot()) {
1222
+ throw new Error("Expected '.' before SET field name");
1223
+ }
1224
+ this.setNextToken();
1225
+ if (!this.token.isIdentifierOrKeyword() || this.token.value === null) {
1226
+ throw new Error("Expected field name after '.'");
1227
+ }
1228
+ const field = this.token.value;
1229
+ this.setNextToken();
1230
+ this.skipWhitespaceAndComments();
1231
+ let expression = null;
1232
+ if (this.token.isEquals()) {
1233
+ this.setNextToken();
1234
+ this.expectAndSkipWhitespaceAndComments();
1235
+ expression = this.parseExpression();
1236
+ if (expression === null) {
1237
+ throw new Error(`Expected expression after SET .${field} =`);
1238
+ }
1239
+ this.convertUnresolvedReferencesToBindings(expression);
1240
+ this.skipWhitespaceAndComments();
1241
+ }
1242
+ items.push({ field, expression });
1243
+ if (!this.token.isComma()) {
1244
+ break;
1245
+ }
1246
+ this.setNextToken();
1247
+ this.skipWhitespaceAndComments();
1248
+ }
1249
+ if (items.length === 0) {
1250
+ throw new Error("Expected at least one SET field");
1251
+ }
1252
+ return items;
1253
+ }
1254
+ /**
1255
+ * Parses the tail of `UPDATE name AS alias DELETE WHERE <pred>`,
1256
+ * with `AS` already consumed by the caller's lookahead.
1257
+ */
1258
+ parseUpdateDeleteTail(name) {
1259
+ this.setNextToken(); // consume AS
1260
+ this.expectAndSkipWhitespaceAndComments();
1261
+ if (!this.token.isIdentifierOrKeyword() || this.token.value === null) {
1262
+ throw new Error("Expected alias after AS");
1263
+ }
1264
+ const alias = this.token.value;
1265
+ this.setNextToken();
1266
+ this.expectAndSkipWhitespaceAndComments();
1267
+ if (!this.token.isDelete()) {
1268
+ throw new Error("Expected DELETE after UPDATE alias");
1269
+ }
1270
+ this.setNextToken();
1271
+ this.expectAndSkipWhitespaceAndComments();
1272
+ if (!this.token.isWhere()) {
1273
+ throw new Error("Expected WHERE after UPDATE … DELETE");
1274
+ }
1275
+ this.setNextToken();
1276
+ this.expectAndSkipWhitespaceAndComments();
1277
+ // Inject the alias into the parser's variable scope while we
1278
+ // parse the predicate so that `<alias>` / `<alias>.field`
1279
+ // references resolve to the UpdateDelete's per-row value.
1280
+ const updateDelete = new update_delete_1.default(name, alias, new expression_1.default());
1281
+ this._state.variables.set(alias, updateDelete);
1282
+ const predicate = this.parseExpression();
1283
+ this._state.variables.delete(alias);
1284
+ if (predicate === null) {
1285
+ throw new Error("Expected predicate expression after WHERE");
1286
+ }
1287
+ // Any other unresolved bare identifiers (e.g. `IN banned`) refer
1288
+ // to LET-bound bindings, which are resolved at run-time against
1289
+ // the global Bindings store.
1290
+ this.convertUnresolvedReferencesToBindings(predicate);
1291
+ updateDelete.setPredicate(predicate);
1292
+ return updateDelete;
1293
+ }
1294
+ /**
1295
+ * Recursively replaces any unresolved {@link Reference} node in
1296
+ * the given subtree with a {@link BindingReference}, which looks up
1297
+ * the value from the global {@link Bindings} store at run-time.
1298
+ *
1299
+ * Used by `UPDATE … AS u DELETE WHERE …` so that predicates can
1300
+ * reference other bindings by name, and to keep `LOAD JSON FROM …`
1301
+ * behaviour consistent at arbitrary depth.
1302
+ */
1303
+ convertUnresolvedReferencesToBindings(node) {
1304
+ for (const child of node.getChildren().slice()) {
1305
+ if (child instanceof reference_1.default &&
1306
+ child.referred === undefined &&
1307
+ !child.identifier.startsWith("$")) {
1308
+ node.replaceChild(child, new binding_reference_1.default(child.identifier));
1309
+ }
1310
+ else {
1311
+ this.convertUnresolvedReferencesToBindings(child);
1312
+ }
1313
+ }
1314
+ }
621
1315
  parseMatch() {
622
1316
  let optional = false;
623
1317
  if (this.token.isOptional()) {
@@ -956,7 +1650,7 @@ class Parser extends base_parser_1.default {
956
1650
  return null;
957
1651
  }
958
1652
  this.setNextToken();
959
- this.expectAndSkipWhitespaceAndComments();
1653
+ this.skipWhitespaceAndComments();
960
1654
  const query = this._parseTokenized(true);
961
1655
  this.skipWhitespaceAndComments();
962
1656
  if (!this.token.isClosingBrace()) {