flowquery 1.0.36 → 1.0.38

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 (73) hide show
  1. package/dist/flowquery.min.js +1 -1
  2. package/dist/parsing/functions/coalesce.d.ts +0 -1
  3. package/dist/parsing/functions/coalesce.d.ts.map +1 -1
  4. package/dist/parsing/functions/coalesce.js +0 -1
  5. package/dist/parsing/functions/coalesce.js.map +1 -1
  6. package/dist/parsing/functions/date.d.ts +0 -2
  7. package/dist/parsing/functions/date.d.ts.map +1 -1
  8. package/dist/parsing/functions/date.js +0 -2
  9. package/dist/parsing/functions/date.js.map +1 -1
  10. package/dist/parsing/functions/datetime.d.ts +0 -2
  11. package/dist/parsing/functions/datetime.d.ts.map +1 -1
  12. package/dist/parsing/functions/datetime.js +0 -2
  13. package/dist/parsing/functions/datetime.js.map +1 -1
  14. package/dist/parsing/functions/localdatetime.d.ts +0 -2
  15. package/dist/parsing/functions/localdatetime.d.ts.map +1 -1
  16. package/dist/parsing/functions/localdatetime.js +0 -2
  17. package/dist/parsing/functions/localdatetime.js.map +1 -1
  18. package/dist/parsing/functions/localtime.d.ts +0 -2
  19. package/dist/parsing/functions/localtime.d.ts.map +1 -1
  20. package/dist/parsing/functions/localtime.js +0 -2
  21. package/dist/parsing/functions/localtime.js.map +1 -1
  22. package/dist/parsing/functions/temporal_utils.js +1 -1
  23. package/dist/parsing/functions/time.d.ts +0 -2
  24. package/dist/parsing/functions/time.d.ts.map +1 -1
  25. package/dist/parsing/functions/time.js +0 -2
  26. package/dist/parsing/functions/time.js.map +1 -1
  27. package/dist/parsing/functions/timestamp.d.ts +0 -2
  28. package/dist/parsing/functions/timestamp.d.ts.map +1 -1
  29. package/dist/parsing/functions/timestamp.js +1 -4
  30. package/dist/parsing/functions/timestamp.js.map +1 -1
  31. package/dist/parsing/operations/group_by.d.ts.map +1 -1
  32. package/dist/parsing/operations/group_by.js +3 -2
  33. package/dist/parsing/operations/group_by.js.map +1 -1
  34. package/dist/parsing/operations/limit.d.ts +2 -0
  35. package/dist/parsing/operations/limit.d.ts.map +1 -1
  36. package/dist/parsing/operations/limit.js +6 -0
  37. package/dist/parsing/operations/limit.js.map +1 -1
  38. package/dist/parsing/operations/return.d.ts +3 -0
  39. package/dist/parsing/operations/return.d.ts.map +1 -1
  40. package/dist/parsing/operations/return.js +10 -0
  41. package/dist/parsing/operations/return.js.map +1 -1
  42. package/dist/parsing/parser.d.ts.map +1 -1
  43. package/dist/parsing/parser.js +13 -10
  44. package/dist/parsing/parser.js.map +1 -1
  45. package/docs/flowquery.min.js +1 -1
  46. package/flowquery-py/pyproject.toml +1 -1
  47. package/flowquery-py/src/parsing/functions/coalesce.py +1 -2
  48. package/flowquery-py/src/parsing/functions/date_.py +0 -2
  49. package/flowquery-py/src/parsing/functions/datetime_.py +0 -2
  50. package/flowquery-py/src/parsing/functions/localdatetime.py +0 -2
  51. package/flowquery-py/src/parsing/functions/localtime.py +0 -2
  52. package/flowquery-py/src/parsing/functions/temporal_utils.py +1 -1
  53. package/flowquery-py/src/parsing/functions/time_.py +0 -2
  54. package/flowquery-py/src/parsing/functions/timestamp.py +1 -3
  55. package/flowquery-py/src/parsing/operations/limit.py +7 -0
  56. package/flowquery-py/src/parsing/operations/return_op.py +14 -0
  57. package/flowquery-py/src/parsing/parser.py +13 -9
  58. package/flowquery-py/tests/compute/test_runner.py +81 -2
  59. package/flowquery-vscode/flowQueryEngine/flowquery.min.js +1 -1
  60. package/package.json +1 -1
  61. package/src/parsing/functions/coalesce.ts +0 -1
  62. package/src/parsing/functions/date.ts +0 -2
  63. package/src/parsing/functions/datetime.ts +0 -2
  64. package/src/parsing/functions/localdatetime.ts +0 -2
  65. package/src/parsing/functions/localtime.ts +0 -2
  66. package/src/parsing/functions/temporal_utils.ts +1 -1
  67. package/src/parsing/functions/time.ts +0 -2
  68. package/src/parsing/functions/timestamp.ts +1 -5
  69. package/src/parsing/operations/group_by.ts +4 -2
  70. package/src/parsing/operations/limit.ts +7 -1
  71. package/src/parsing/operations/return.ts +11 -0
  72. package/src/parsing/parser.ts +12 -10
  73. package/tests/compute/runner.test.ts +69 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flowquery",
3
- "version": "1.0.36",
3
+ "version": "1.0.38",
4
4
  "description": "A declarative query language for data processing pipelines.",
5
5
  "main": "dist/index.node.js",
6
6
  "types": "dist/index.node.d.ts",
@@ -3,7 +3,6 @@ import { FunctionDef } from "./function_metadata";
3
3
 
4
4
  /**
5
5
  * Returns the first non-null value from a list of expressions.
6
- * Equivalent to Neo4j's coalesce() function.
7
6
  *
8
7
  * @example
9
8
  * ```
@@ -8,8 +8,6 @@ import { buildDateObject, parseTemporalArg } from "./temporal_utils";
8
8
  * When called with a string argument, parses it as an ISO 8601 date.
9
9
  * When called with a map argument, constructs a date from components.
10
10
  *
11
- * Equivalent to Neo4j's date() function.
12
- *
13
11
  * @example
14
12
  * ```
15
13
  * RETURN date() AS today
@@ -8,8 +8,6 @@ import { buildDatetimeObject, parseTemporalArg } from "./temporal_utils";
8
8
  * When called with a string argument, parses it as an ISO 8601 datetime.
9
9
  * When called with a map argument, constructs a datetime from components.
10
10
  *
11
- * Equivalent to Neo4j's datetime() function.
12
- *
13
11
  * @example
14
12
  * ```
15
13
  * RETURN datetime() AS now
@@ -8,8 +8,6 @@ import { buildDatetimeObject, parseTemporalArg } from "./temporal_utils";
8
8
  * When called with a string argument, parses it as an ISO 8601 datetime.
9
9
  * When called with a map argument, constructs a datetime from components.
10
10
  *
11
- * Equivalent to Neo4j's localdatetime() function.
12
- *
13
11
  * @example
14
12
  * ```
15
13
  * RETURN localdatetime() AS now
@@ -7,8 +7,6 @@ import { buildTimeObject, parseTemporalArg } from "./temporal_utils";
7
7
  * When called with no arguments, returns the current local time.
8
8
  * When called with a string argument, parses it.
9
9
  *
10
- * Equivalent to Neo4j's localtime() function.
11
- *
12
10
  * @example
13
11
  * ```
14
12
  * RETURN localtime() AS now
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  /**
9
- * Computes the ISO day of the week (1 = Monday, 7 = Sunday) matching Neo4j convention.
9
+ * Computes the ISO day of the week (1 = Monday, 7 = Sunday).
10
10
  */
11
11
  function isoDayOfWeek(d: Date): number {
12
12
  const jsDay = d.getDay(); // 0 = Sunday, 6 = Saturday
@@ -7,8 +7,6 @@ import { buildTimeObject, parseTemporalArg } from "./temporal_utils";
7
7
  * When called with no arguments, returns the current UTC time.
8
8
  * When called with a string argument, parses it as an ISO 8601 time.
9
9
  *
10
- * Equivalent to Neo4j's time() function.
11
- *
12
10
  * @example
13
11
  * ```
14
12
  * RETURN time() AS now
@@ -4,17 +4,13 @@ import { FunctionDef } from "./function_metadata";
4
4
  /**
5
5
  * Returns the number of milliseconds since the Unix epoch (1970-01-01T00:00:00Z).
6
6
  *
7
- * Equivalent to Neo4j's timestamp() function.
8
- *
9
7
  * @example
10
8
  * ```
11
9
  * RETURN timestamp() AS ts
12
10
  * ```
13
11
  */
14
12
  @FunctionDef({
15
- description:
16
- "Returns the number of milliseconds since the Unix epoch (1970-01-01T00:00:00Z). " +
17
- "Equivalent to Neo4j's timestamp() function.",
13
+ description: "Returns the number of milliseconds since the Unix epoch (1970-01-01T00:00:00Z).",
18
14
  category: "scalar",
19
15
  parameters: [],
20
16
  output: {
@@ -52,10 +52,12 @@ class GroupBy extends Projection {
52
52
  let node: Node = this.current;
53
53
  for (const mapper of this.mappers) {
54
54
  const value: any = mapper.value();
55
- let child: Node | undefined = node.children.get(value);
55
+ const key: string =
56
+ typeof value === "object" && value !== null ? JSON.stringify(value) : String(value);
57
+ let child: Node | undefined = node.children.get(key);
56
58
  if (child === undefined) {
57
59
  child = new Node(value);
58
- node.children.set(value, child);
60
+ node.children.set(key, child);
59
61
  }
60
62
  node = child;
61
63
  }
@@ -7,6 +7,12 @@ class Limit extends Operation {
7
7
  super();
8
8
  this.limit = limit;
9
9
  }
10
+ public get isLimitReached(): boolean {
11
+ return this.count >= this.limit;
12
+ }
13
+ public increment(): void {
14
+ this.count++;
15
+ }
10
16
  public async run(): Promise<void> {
11
17
  if (this.count >= this.limit) {
12
18
  return;
@@ -19,4 +25,4 @@ class Limit extends Operation {
19
25
  }
20
26
  }
21
27
 
22
- export default Limit;
28
+ export default Limit;
@@ -1,3 +1,4 @@
1
+ import Limit from "./limit";
1
2
  import Projection from "./projection";
2
3
  import Where from "./where";
3
4
 
@@ -15,6 +16,7 @@ import Where from "./where";
15
16
  class Return extends Projection {
16
17
  protected _where: Where | null = null;
17
18
  protected _results: Record<string, any>[] = [];
19
+ private _limit: Limit | null = null;
18
20
  public set where(where: Where) {
19
21
  this._where = where;
20
22
  }
@@ -24,10 +26,16 @@ class Return extends Projection {
24
26
  }
25
27
  return this._where.value();
26
28
  }
29
+ public set limit(limit: Limit) {
30
+ this._limit = limit;
31
+ }
27
32
  public async run(): Promise<void> {
28
33
  if (!this.where) {
29
34
  return;
30
35
  }
36
+ if (this._limit !== null && this._limit.isLimitReached) {
37
+ return;
38
+ }
31
39
  const record: Map<string, any> = new Map();
32
40
  for (const [expression, alias] of this.expressions()) {
33
41
  const raw = expression.value();
@@ -35,6 +43,9 @@ class Return extends Projection {
35
43
  record.set(alias, value);
36
44
  }
37
45
  this._results.push(Object.fromEntries(record));
46
+ if (this._limit !== null) {
47
+ this._limit.increment();
48
+ }
38
49
  }
39
50
  public async initialize(): Promise<void> {
40
51
  this._results = [];
@@ -112,6 +112,9 @@ class Parser extends BaseParser {
112
112
  if (this.token.isUnion()) {
113
113
  break;
114
114
  }
115
+ if (this.token.isEOF()) {
116
+ break;
117
+ }
115
118
  operation = this.parseOperation();
116
119
  if (operation === null && !isSubQuery) {
117
120
  throw new Error("Expected one of WITH, UNWIND, RETURN, LOAD, OR CALL");
@@ -142,8 +145,12 @@ class Parser extends BaseParser {
142
145
  }
143
146
  const limit = this.parseLimit();
144
147
  if (limit !== null) {
145
- operation!.addSibling(limit);
146
- operation = limit;
148
+ if (operation instanceof Return) {
149
+ (operation as Return).limit = limit;
150
+ } else {
151
+ operation!.addSibling(limit);
152
+ operation = limit;
153
+ }
147
154
  }
148
155
  previous = operation;
149
156
  }
@@ -494,16 +501,11 @@ class Parser extends BaseParser {
494
501
  node.label = label!;
495
502
  if (identifier !== null && this._state.variables.has(identifier)) {
496
503
  let reference = this._state.variables.get(identifier);
497
- // Resolve through Expression -> Reference -> Node (e.g., after WITH)
498
- if (reference instanceof Expression && reference.firstChild() instanceof Reference) {
499
- const inner = (reference.firstChild() as Reference).referred;
500
- if (inner instanceof Node) {
501
- reference = inner;
502
- }
503
- }
504
504
  if (
505
505
  reference === undefined ||
506
- (!(reference instanceof Node) && !(reference instanceof Unwind))
506
+ (!(reference instanceof Node) &&
507
+ !(reference instanceof Unwind) &&
508
+ !(reference instanceof Expression))
507
509
  ) {
508
510
  throw new Error(`Undefined node reference: ${identifier}`);
509
511
  }
@@ -842,6 +842,19 @@ test("Test limit", async () => {
842
842
  expect(results.length).toBe(50);
843
843
  });
844
844
 
845
+ test("Test limit as last operation", async () => {
846
+ const runner = new Runner(
847
+ `
848
+ unwind range(1, 10) as i
849
+ return i
850
+ limit 5
851
+ `
852
+ );
853
+ await runner.run();
854
+ const results = runner.results;
855
+ expect(results.length).toBe(5);
856
+ });
857
+
845
858
  test("Test range lookup", async () => {
846
859
  const runner = new Runner(
847
860
  `
@@ -1327,6 +1340,60 @@ test("Test match with referenced to previous variable", async () => {
1327
1340
  expect(results[1]).toEqual({ name1: "Person 2", name2: "Person 3", name3: "Person 4" });
1328
1341
  });
1329
1342
 
1343
+ test("Test match with aggregated with and subsequent match", async () => {
1344
+ await new Runner(`
1345
+ CREATE VIRTUAL (:User) AS {
1346
+ unwind [
1347
+ {id: 1, name: 'Alice'},
1348
+ {id: 2, name: 'Bob'},
1349
+ {id: 3, name: 'Carol'}
1350
+ ] as record
1351
+ RETURN record.id as id, record.name as name
1352
+ }
1353
+ `).run();
1354
+ await new Runner(`
1355
+ CREATE VIRTUAL (:User)-[:KNOWS]-(:User) AS {
1356
+ unwind [
1357
+ {left_id: 1, right_id: 2},
1358
+ {left_id: 1, right_id: 3}
1359
+ ] as record
1360
+ RETURN record.left_id as left_id, record.right_id as right_id
1361
+ }
1362
+ `).run();
1363
+ await new Runner(`
1364
+ CREATE VIRTUAL (:Project) AS {
1365
+ unwind [
1366
+ {id: 1, name: 'Project A'},
1367
+ {id: 2, name: 'Project B'}
1368
+ ] as record
1369
+ RETURN record.id as id, record.name as name
1370
+ }
1371
+ `).run();
1372
+ await new Runner(`
1373
+ CREATE VIRTUAL (:User)-[:WORKS_ON]-(:Project) AS {
1374
+ unwind [
1375
+ {left_id: 1, right_id: 1},
1376
+ {left_id: 1, right_id: 2}
1377
+ ] as record
1378
+ RETURN record.left_id as left_id, record.right_id as right_id
1379
+ }
1380
+ `).run();
1381
+ const match = new Runner(`
1382
+ MATCH (u:User)-[:KNOWS]->(s:User)
1383
+ WITH u, count(s) as acquaintances
1384
+ MATCH (u)-[:WORKS_ON]->(p:Project)
1385
+ RETURN u.name as name, acquaintances, collect(p.name) as projects
1386
+ `);
1387
+ await match.run();
1388
+ const results = match.results;
1389
+ expect(results.length).toBe(1);
1390
+ expect(results[0]).toEqual({
1391
+ name: "Alice",
1392
+ acquaintances: 2,
1393
+ projects: ["Project A", "Project B"],
1394
+ });
1395
+ });
1396
+
1330
1397
  test("Test match and return full node", async () => {
1331
1398
  await new Runner(`
1332
1399
  CREATE VIRTUAL (:Person) AS {
@@ -2039,7 +2106,7 @@ test("Test optional match property access on null node returns null", async () =
2039
2106
  RETURN record.left_id as left_id, record.right_id as right_id
2040
2107
  }
2041
2108
  `).run();
2042
- // When accessing b.name and b is null (no match), should return null like Neo4j
2109
+ // When accessing b.name and b is null (no match), should return null
2043
2110
  const match = new Runner(`
2044
2111
  MATCH (a:Person)
2045
2112
  OPTIONAL MATCH (a)-[:KNOWS]->(b:Person)
@@ -3246,7 +3313,7 @@ test("Test coalesce with property access", async () => {
3246
3313
  });
3247
3314
 
3248
3315
  // ============================================================
3249
- // Temporal / Time Functions (Neo4j-style)
3316
+ // Temporal / Time Functions
3250
3317
  // ============================================================
3251
3318
 
3252
3319
  test("Test datetime() returns current datetime object", async () => {