flowquery 1.0.32 → 1.0.34

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 (103) hide show
  1. package/dist/compute/flowquery.d.ts +43 -0
  2. package/dist/compute/flowquery.d.ts.map +1 -0
  3. package/dist/compute/flowquery.js +30 -0
  4. package/dist/compute/flowquery.js.map +1 -0
  5. package/dist/compute/runner.d.ts +0 -21
  6. package/dist/compute/runner.d.ts.map +1 -1
  7. package/dist/compute/runner.js.map +1 -1
  8. package/dist/flowquery.min.js +1 -1
  9. package/dist/index.browser.d.ts +1 -1
  10. package/dist/index.browser.d.ts.map +1 -1
  11. package/dist/index.browser.js +10 -10
  12. package/dist/index.browser.js.map +1 -1
  13. package/dist/index.node.d.ts +4 -4
  14. package/dist/index.node.d.ts.map +1 -1
  15. package/dist/index.node.js +13 -13
  16. package/dist/index.node.js.map +1 -1
  17. package/dist/parsing/context.d.ts +1 -0
  18. package/dist/parsing/context.d.ts.map +1 -1
  19. package/dist/parsing/context.js +5 -0
  20. package/dist/parsing/context.js.map +1 -1
  21. package/dist/parsing/expressions/operator.d.ts +2 -2
  22. package/dist/parsing/expressions/operator.d.ts.map +1 -1
  23. package/dist/parsing/expressions/operator.js +6 -1
  24. package/dist/parsing/expressions/operator.js.map +1 -1
  25. package/dist/parsing/functions/function_factory.d.ts +2 -0
  26. package/dist/parsing/functions/function_factory.d.ts.map +1 -1
  27. package/dist/parsing/functions/function_factory.js +2 -0
  28. package/dist/parsing/functions/function_factory.js.map +1 -1
  29. package/dist/parsing/functions/to_lower.d.ts +7 -0
  30. package/dist/parsing/functions/to_lower.d.ts.map +1 -0
  31. package/dist/parsing/functions/to_lower.js +37 -0
  32. package/dist/parsing/functions/to_lower.js.map +1 -0
  33. package/dist/parsing/functions/to_string.d.ts +7 -0
  34. package/dist/parsing/functions/to_string.d.ts.map +1 -0
  35. package/dist/parsing/functions/to_string.js +44 -0
  36. package/dist/parsing/functions/to_string.js.map +1 -0
  37. package/dist/parsing/operations/match.d.ts +5 -1
  38. package/dist/parsing/operations/match.d.ts.map +1 -1
  39. package/dist/parsing/operations/match.js +25 -1
  40. package/dist/parsing/operations/match.js.map +1 -1
  41. package/dist/parsing/operations/union.d.ts +36 -0
  42. package/dist/parsing/operations/union.d.ts.map +1 -0
  43. package/dist/parsing/operations/union.js +121 -0
  44. package/dist/parsing/operations/union.js.map +1 -0
  45. package/dist/parsing/operations/union_all.d.ts +10 -0
  46. package/dist/parsing/operations/union_all.d.ts.map +1 -0
  47. package/dist/parsing/operations/union_all.js +17 -0
  48. package/dist/parsing/operations/union_all.js.map +1 -0
  49. package/dist/parsing/parser.d.ts +2 -3
  50. package/dist/parsing/parser.d.ts.map +1 -1
  51. package/dist/parsing/parser.js +72 -24
  52. package/dist/parsing/parser.js.map +1 -1
  53. package/dist/parsing/parser_state.d.ts +13 -0
  54. package/dist/parsing/parser_state.d.ts.map +1 -0
  55. package/dist/parsing/parser_state.js +27 -0
  56. package/dist/parsing/parser_state.js.map +1 -0
  57. package/dist/tokenization/keyword.d.ts +4 -1
  58. package/dist/tokenization/keyword.d.ts.map +1 -1
  59. package/dist/tokenization/keyword.js +3 -0
  60. package/dist/tokenization/keyword.js.map +1 -1
  61. package/dist/tokenization/token.d.ts +6 -0
  62. package/dist/tokenization/token.d.ts.map +1 -1
  63. package/dist/tokenization/token.js +18 -0
  64. package/dist/tokenization/token.js.map +1 -1
  65. package/docs/flowquery.min.js +1 -1
  66. package/flowquery-py/pyproject.toml +1 -1
  67. package/flowquery-py/src/__init__.py +2 -0
  68. package/flowquery-py/src/compute/__init__.py +2 -1
  69. package/flowquery-py/src/compute/flowquery.py +68 -0
  70. package/flowquery-py/src/graph/node.py +1 -1
  71. package/flowquery-py/src/parsing/functions/__init__.py +4 -0
  72. package/flowquery-py/src/parsing/functions/to_lower.py +35 -0
  73. package/flowquery-py/src/parsing/functions/to_string.py +41 -0
  74. package/flowquery-py/src/parsing/operations/__init__.py +4 -0
  75. package/flowquery-py/src/parsing/operations/match.py +24 -2
  76. package/flowquery-py/src/parsing/operations/union.py +115 -0
  77. package/flowquery-py/src/parsing/operations/union_all.py +17 -0
  78. package/flowquery-py/src/parsing/parser.py +68 -24
  79. package/flowquery-py/src/parsing/parser_state.py +26 -0
  80. package/flowquery-py/src/tokenization/keyword.py +3 -0
  81. package/flowquery-py/src/tokenization/token.py +21 -0
  82. package/flowquery-py/tests/compute/test_runner.py +587 -1
  83. package/flowquery-py/tests/parsing/test_parser.py +82 -0
  84. package/flowquery-vscode/flowQueryEngine/flowquery.min.js +1 -1
  85. package/package.json +1 -1
  86. package/src/compute/flowquery.ts +46 -0
  87. package/src/compute/runner.ts +0 -24
  88. package/src/index.browser.ts +17 -14
  89. package/src/index.node.ts +21 -18
  90. package/src/parsing/context.ts +6 -0
  91. package/src/parsing/expressions/operator.ts +8 -3
  92. package/src/parsing/functions/function_factory.ts +2 -0
  93. package/src/parsing/functions/to_lower.ts +25 -0
  94. package/src/parsing/functions/to_string.ts +32 -0
  95. package/src/parsing/operations/match.ts +24 -1
  96. package/src/parsing/operations/union.ts +114 -0
  97. package/src/parsing/operations/union_all.ts +16 -0
  98. package/src/parsing/parser.ts +74 -23
  99. package/src/parsing/parser_state.ts +25 -0
  100. package/src/tokenization/keyword.ts +3 -0
  101. package/src/tokenization/token.ts +24 -0
  102. package/tests/compute/runner.test.ts +507 -0
  103. package/tests/parsing/parser.test.ts +76 -0
@@ -58,9 +58,12 @@ import Load from "./operations/load";
58
58
  import Match from "./operations/match";
59
59
  import Operation from "./operations/operation";
60
60
  import Return from "./operations/return";
61
+ import Union from "./operations/union";
62
+ import UnionAll from "./operations/union_all";
61
63
  import Unwind from "./operations/unwind";
62
64
  import Where from "./operations/where";
63
65
  import With from "./operations/with";
66
+ import ParserState from "./parser_state";
64
67
 
65
68
  /**
66
69
  * Main parser for FlowQuery statements.
@@ -76,9 +79,7 @@ import With from "./operations/with";
76
79
  * ```
77
80
  */
78
81
  class Parser extends BaseParser {
79
- private variables: Map<string, ASTNode> = new Map();
80
- private context: Context = new Context();
81
- private _returns: number = 0;
82
+ private _state: ParserState = new ParserState();
82
83
 
83
84
  /**
84
85
  * Parses a FlowQuery statement into an Abstract Syntax Tree.
@@ -107,13 +108,17 @@ class Parser extends BaseParser {
107
108
  } else {
108
109
  this.skipWhitespaceAndComments();
109
110
  }
111
+ // UNION separates two query pipelines — break and handle after the loop
112
+ if (this.token.isUnion()) {
113
+ break;
114
+ }
110
115
  operation = this.parseOperation();
111
116
  if (operation === null && !isSubQuery) {
112
117
  throw new Error("Expected one of WITH, UNWIND, RETURN, LOAD, OR CALL");
113
118
  } else if (operation === null && isSubQuery) {
114
119
  return root;
115
120
  }
116
- if (this._returns > 1) {
121
+ if (this._state.returns > 1) {
117
122
  throw new Error("Only one RETURN statement is allowed");
118
123
  }
119
124
  if (previous instanceof Call && !previous.hasYield) {
@@ -142,6 +147,24 @@ class Parser extends BaseParser {
142
147
  }
143
148
  previous = operation;
144
149
  }
150
+ // Handle UNION: wrap left and right pipelines into a Union node
151
+ if (!this.token.isEOF() && this.token.isUnion()) {
152
+ if (!(operation instanceof Return) && !(operation instanceof Call)) {
153
+ throw new Error("Each side of UNION must end with a RETURN or CALL statement");
154
+ }
155
+ const union = this.parseUnion()!;
156
+ union.left = root.firstChild() as Operation;
157
+ // Save and reset parser state for right-side scope
158
+ const state = this._state;
159
+ this._state = new ParserState();
160
+ const right = this._parseTokenized(isSubQuery);
161
+ union.right = right.firstChild() as Operation;
162
+ // Restore parser state
163
+ this._state = state;
164
+ const newRoot = new ASTNode();
165
+ newRoot.addChild(union);
166
+ return newRoot;
167
+ }
145
168
  if (
146
169
  !(operation instanceof Return) &&
147
170
  !(operation instanceof Call) &&
@@ -216,7 +239,7 @@ class Parser extends BaseParser {
216
239
  throw new Error("Expected alias");
217
240
  }
218
241
  const unwind = new Unwind(expression);
219
- this.variables.set(alias.getAlias(), unwind);
242
+ this._state.variables.set(alias.getAlias(), unwind);
220
243
  return unwind;
221
244
  }
222
245
 
@@ -239,7 +262,7 @@ class Parser extends BaseParser {
239
262
  if (distinct || expressions.some((expression: Expression) => expression.has_reducers())) {
240
263
  return new AggregatedReturn(expressions);
241
264
  }
242
- this._returns++;
265
+ this._state.incrementReturns();
243
266
  return new Return(expressions);
244
267
  }
245
268
 
@@ -322,7 +345,7 @@ class Parser extends BaseParser {
322
345
  const alias = this.parseAlias();
323
346
  if (alias !== null) {
324
347
  load.addChild(alias);
325
- this.variables.set(alias.getAlias(), load);
348
+ this._state.variables.set(alias.getAlias(), load);
326
349
  } else {
327
350
  throw new Error("Expected alias");
328
351
  }
@@ -418,7 +441,16 @@ class Parser extends BaseParser {
418
441
  }
419
442
 
420
443
  private parseMatch(): Match | null {
444
+ let optional = false;
445
+ if (this.token.isOptional()) {
446
+ optional = true;
447
+ this.setNextToken();
448
+ this.expectAndSkipWhitespaceAndComments();
449
+ }
421
450
  if (!this.token.isMatch()) {
451
+ if (optional) {
452
+ throw new Error("Expected MATCH after OPTIONAL");
453
+ }
422
454
  return null;
423
455
  }
424
456
  this.setNextToken();
@@ -427,7 +459,7 @@ class Parser extends BaseParser {
427
459
  if (patterns.length === 0) {
428
460
  throw new Error("Expected graph pattern");
429
461
  }
430
- return new Match(patterns);
462
+ return new Match(patterns, optional);
431
463
  }
432
464
 
433
465
  private parseNode(): Node | null {
@@ -458,8 +490,8 @@ class Parser extends BaseParser {
458
490
  let node = new Node();
459
491
  node.properties = new Map(this.parseProperties());
460
492
  node.label = label!;
461
- if (identifier !== null && this.variables.has(identifier)) {
462
- let reference = this.variables.get(identifier);
493
+ if (identifier !== null && this._state.variables.has(identifier)) {
494
+ let reference = this._state.variables.get(identifier);
463
495
  // Resolve through Expression -> Reference -> Node (e.g., after WITH)
464
496
  if (reference instanceof Expression && reference.firstChild() instanceof Reference) {
465
497
  const inner = (reference.firstChild() as Reference).referred;
@@ -476,7 +508,7 @@ class Parser extends BaseParser {
476
508
  node = new NodeReference(node, reference);
477
509
  } else if (identifier !== null) {
478
510
  node.identifier = identifier;
479
- this.variables.set(identifier, node);
511
+ this._state.variables.set(identifier, node);
480
512
  }
481
513
  if (!this.token.isRightParenthesis()) {
482
514
  throw new Error("Expected closing parenthesis for node definition");
@@ -543,7 +575,7 @@ class Parser extends BaseParser {
543
575
  if (pattern !== null) {
544
576
  if (identifier !== null) {
545
577
  pattern.identifier = identifier;
546
- this.variables.set(identifier, pattern);
578
+ this._state.variables.set(identifier, pattern);
547
579
  }
548
580
  yield pattern;
549
581
  } else {
@@ -659,8 +691,8 @@ class Parser extends BaseParser {
659
691
  let relationship = new Relationship();
660
692
  relationship.direction = direction;
661
693
  relationship.properties = properties;
662
- if (variable !== null && this.variables.has(variable)) {
663
- let reference = this.variables.get(variable);
694
+ if (variable !== null && this._state.variables.has(variable)) {
695
+ let reference = this._state.variables.get(variable);
664
696
  // Resolve through Expression -> Reference -> Relationship (e.g., after WITH)
665
697
  if (reference instanceof Expression && reference.firstChild() instanceof Reference) {
666
698
  const inner = (reference.firstChild() as Reference).referred;
@@ -674,7 +706,7 @@ class Parser extends BaseParser {
674
706
  relationship = new RelationshipReference(relationship, reference);
675
707
  } else if (variable !== null) {
676
708
  relationship.identifier = variable;
677
- this.variables.set(variable, relationship);
709
+ this._state.variables.set(variable, relationship);
678
710
  }
679
711
  if (hops !== null) {
680
712
  relationship.hops = hops;
@@ -753,7 +785,7 @@ class Parser extends BaseParser {
753
785
  if (expression.firstChild() instanceof Reference && alias === null) {
754
786
  const reference: Reference = expression.firstChild() as Reference;
755
787
  expression.setAlias(reference.identifier);
756
- this.variables.set(reference.identifier, expression);
788
+ this._state.variables.set(reference.identifier, expression);
757
789
  } else if (
758
790
  alias_option === AliasOption.REQUIRED &&
759
791
  alias === null &&
@@ -767,7 +799,7 @@ class Parser extends BaseParser {
767
799
  alias !== null
768
800
  ) {
769
801
  expression.setAlias(alias.getAlias());
770
- this.variables.set(alias.getAlias(), expression);
802
+ this._state.variables.set(alias.getAlias(), expression);
771
803
  }
772
804
  yield expression;
773
805
  } else {
@@ -789,7 +821,7 @@ class Parser extends BaseParser {
789
821
  this.skipWhitespaceAndComments();
790
822
  if (this.token.isIdentifierOrKeyword() && !this.peek()?.isLeftParenthesis()) {
791
823
  const identifier: string = this.token.value || "";
792
- const reference = new Reference(identifier, this.variables.get(identifier));
824
+ const reference = new Reference(identifier, this._state.variables.get(identifier));
793
825
  this.setNextToken();
794
826
  const lookup = this.parseLookup(reference);
795
827
  expression.addNode(lookup);
@@ -1185,10 +1217,13 @@ class Parser extends BaseParser {
1185
1217
  return null;
1186
1218
  }
1187
1219
  const func = FunctionFactory.create(this.token.value);
1188
- if (func instanceof AggregateFunction && this.context.containsType(AggregateFunction)) {
1220
+ if (
1221
+ func instanceof AggregateFunction &&
1222
+ this._state.context.containsType(AggregateFunction)
1223
+ ) {
1189
1224
  throw new Error("Aggregate functions cannot be nested");
1190
1225
  }
1191
- this.context.push(func);
1226
+ this._state.context.push(func);
1192
1227
  this.setNextToken();
1193
1228
  this.setNextToken();
1194
1229
  this.skipWhitespaceAndComments();
@@ -1203,7 +1238,7 @@ class Parser extends BaseParser {
1203
1238
  throw new Error("Expected right parenthesis");
1204
1239
  }
1205
1240
  this.setNextToken();
1206
- this.context.pop();
1241
+ this._state.context.pop();
1207
1242
  return func;
1208
1243
  }
1209
1244
 
@@ -1265,7 +1300,7 @@ class Parser extends BaseParser {
1265
1300
  throw new Error("Expected identifier");
1266
1301
  }
1267
1302
  const reference = new Reference(this.token.value);
1268
- this.variables.set(reference.identifier, reference);
1303
+ this._state.variables.set(reference.identifier, reference);
1269
1304
  func.addChild(reference);
1270
1305
  this.setNextToken();
1271
1306
  this.expectAndSkipWhitespaceAndComments();
@@ -1308,7 +1343,7 @@ class Parser extends BaseParser {
1308
1343
  throw new Error("Expected right parenthesis");
1309
1344
  }
1310
1345
  this.setNextToken();
1311
- this.variables.delete(reference.identifier);
1346
+ this._state.variables.delete(reference.identifier);
1312
1347
  return func;
1313
1348
  }
1314
1349
 
@@ -1421,6 +1456,22 @@ class Parser extends BaseParser {
1421
1456
  return array;
1422
1457
  }
1423
1458
 
1459
+ private parseUnion(): Union | UnionAll | null {
1460
+ if (!this.token.isUnion()) {
1461
+ return null;
1462
+ }
1463
+ this.setNextToken();
1464
+ this.skipWhitespaceAndComments();
1465
+ let union: Union | UnionAll;
1466
+ if (this.token.isAll()) {
1467
+ union = new UnionAll();
1468
+ this.setNextToken();
1469
+ } else {
1470
+ union = new Union();
1471
+ }
1472
+ return union;
1473
+ }
1474
+
1424
1475
  private expectAndSkipWhitespaceAndComments(): void {
1425
1476
  const skipped = this.skipWhitespaceAndComments();
1426
1477
  if (!skipped) {
@@ -0,0 +1,25 @@
1
+ import ASTNode from "./ast_node";
2
+ import Context from "./context";
3
+
4
+ class ParserState {
5
+ private _variables: Map<string, ASTNode> = new Map();
6
+ private _context: Context = new Context();
7
+ private _returns: number = 0;
8
+
9
+ public get variables(): Map<string, ASTNode> {
10
+ return this._variables;
11
+ }
12
+
13
+ public get context(): Context {
14
+ return this._context;
15
+ }
16
+
17
+ public get returns(): number {
18
+ return this._returns;
19
+ }
20
+ public incrementReturns(): void {
21
+ this._returns++;
22
+ }
23
+ }
24
+
25
+ export default ParserState;
@@ -1,6 +1,7 @@
1
1
  enum Keyword {
2
2
  RETURN = "RETURN",
3
3
  MATCH = "MATCH",
4
+ OPTIONAL = "OPTIONAL",
4
5
  WHERE = "WHERE",
5
6
  CREATE = "CREATE",
6
7
  VIRTUAL = "VIRTUAL",
@@ -42,6 +43,8 @@ enum Keyword {
42
43
  CONTAINS = "CONTAINS",
43
44
  STARTS = "STARTS",
44
45
  ENDS = "ENDS",
46
+ UNION = "UNION",
47
+ ALL = "ALL",
45
48
  }
46
49
 
47
50
  export default Keyword;
@@ -507,6 +507,14 @@ class Token {
507
507
  return this._type === TokenType.KEYWORD && this._value === Keyword.MATCH;
508
508
  }
509
509
 
510
+ public static get OPTIONAL(): Token {
511
+ return new Token(TokenType.KEYWORD, Keyword.OPTIONAL);
512
+ }
513
+
514
+ public isOptional(): boolean {
515
+ return this._type === TokenType.KEYWORD && this._value === Keyword.OPTIONAL;
516
+ }
517
+
510
518
  public static get AS(): Token {
511
519
  return new Token(TokenType.KEYWORD, Keyword.AS);
512
520
  }
@@ -675,6 +683,22 @@ class Token {
675
683
  return this._type === TokenType.KEYWORD && this._value === Keyword.LIMIT;
676
684
  }
677
685
 
686
+ public static get UNION(): Token {
687
+ return new Token(TokenType.KEYWORD, Keyword.UNION);
688
+ }
689
+
690
+ public isUnion(): boolean {
691
+ return this._type === TokenType.KEYWORD && this._value === Keyword.UNION;
692
+ }
693
+
694
+ public static get ALL(): Token {
695
+ return new Token(TokenType.KEYWORD, Keyword.ALL);
696
+ }
697
+
698
+ public isAll(): boolean {
699
+ return this._type === TokenType.KEYWORD && this._value === Keyword.ALL;
700
+ }
701
+
678
702
  // End of file token
679
703
 
680
704
  public static get EOF(): Token {