flowquery 1.0.37 → 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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flowquery",
3
- "version": "1.0.37",
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",
@@ -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 {