flowquery 1.0.38 → 1.0.40

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 (78) hide show
  1. package/dist/flowquery.min.js +1 -1
  2. package/dist/graph/database.d.ts +2 -0
  3. package/dist/graph/database.d.ts.map +1 -1
  4. package/dist/graph/database.js +12 -0
  5. package/dist/graph/database.js.map +1 -1
  6. package/dist/parsing/expressions/operator.js +4 -4
  7. package/dist/parsing/expressions/operator.js.map +1 -1
  8. package/dist/parsing/functions/function_factory.d.ts +1 -0
  9. package/dist/parsing/functions/function_factory.d.ts.map +1 -1
  10. package/dist/parsing/functions/function_factory.js +1 -0
  11. package/dist/parsing/functions/function_factory.js.map +1 -1
  12. package/dist/parsing/functions/substring.d.ts +9 -0
  13. package/dist/parsing/functions/substring.d.ts.map +1 -0
  14. package/dist/parsing/functions/substring.js +62 -0
  15. package/dist/parsing/functions/substring.js.map +1 -0
  16. package/dist/parsing/operations/aggregated_return.d.ts.map +1 -1
  17. package/dist/parsing/operations/aggregated_return.js +6 -2
  18. package/dist/parsing/operations/aggregated_return.js.map +1 -1
  19. package/dist/parsing/operations/delete_node.d.ts +11 -0
  20. package/dist/parsing/operations/delete_node.d.ts.map +1 -0
  21. package/dist/parsing/operations/delete_node.js +46 -0
  22. package/dist/parsing/operations/delete_node.js.map +1 -0
  23. package/dist/parsing/operations/delete_relationship.d.ts +11 -0
  24. package/dist/parsing/operations/delete_relationship.d.ts.map +1 -0
  25. package/dist/parsing/operations/delete_relationship.js +46 -0
  26. package/dist/parsing/operations/delete_relationship.js.map +1 -0
  27. package/dist/parsing/operations/limit.d.ts +1 -0
  28. package/dist/parsing/operations/limit.d.ts.map +1 -1
  29. package/dist/parsing/operations/limit.js +3 -0
  30. package/dist/parsing/operations/limit.js.map +1 -1
  31. package/dist/parsing/operations/order_by.d.ts +35 -0
  32. package/dist/parsing/operations/order_by.d.ts.map +1 -0
  33. package/dist/parsing/operations/order_by.js +87 -0
  34. package/dist/parsing/operations/order_by.js.map +1 -0
  35. package/dist/parsing/operations/return.d.ts +3 -0
  36. package/dist/parsing/operations/return.d.ts.map +1 -1
  37. package/dist/parsing/operations/return.js +16 -3
  38. package/dist/parsing/operations/return.js.map +1 -1
  39. package/dist/parsing/parser.d.ts +2 -0
  40. package/dist/parsing/parser.d.ts.map +1 -1
  41. package/dist/parsing/parser.js +116 -2
  42. package/dist/parsing/parser.js.map +1 -1
  43. package/dist/tokenization/token.d.ts +8 -0
  44. package/dist/tokenization/token.d.ts.map +1 -1
  45. package/dist/tokenization/token.js +24 -0
  46. package/dist/tokenization/token.js.map +1 -1
  47. package/docs/flowquery.min.js +1 -1
  48. package/flowquery-py/pyproject.toml +1 -1
  49. package/flowquery-py/src/graph/database.py +12 -0
  50. package/flowquery-py/src/parsing/expressions/operator.py +4 -4
  51. package/flowquery-py/src/parsing/functions/__init__.py +2 -0
  52. package/flowquery-py/src/parsing/functions/substring.py +74 -0
  53. package/flowquery-py/src/parsing/operations/__init__.py +7 -0
  54. package/flowquery-py/src/parsing/operations/aggregated_return.py +4 -1
  55. package/flowquery-py/src/parsing/operations/delete_node.py +29 -0
  56. package/flowquery-py/src/parsing/operations/delete_relationship.py +29 -0
  57. package/flowquery-py/src/parsing/operations/limit.py +4 -0
  58. package/flowquery-py/src/parsing/operations/order_by.py +72 -0
  59. package/flowquery-py/src/parsing/operations/return_op.py +20 -3
  60. package/flowquery-py/src/parsing/parser.py +98 -3
  61. package/flowquery-py/src/tokenization/token.py +28 -0
  62. package/flowquery-py/tests/compute/test_runner.py +329 -1
  63. package/flowquery-vscode/flowQueryEngine/flowquery.min.js +1 -1
  64. package/package.json +1 -1
  65. package/src/graph/database.ts +12 -0
  66. package/src/parsing/expressions/operator.ts +4 -4
  67. package/src/parsing/functions/function_factory.ts +1 -0
  68. package/src/parsing/functions/substring.ts +65 -0
  69. package/src/parsing/operations/aggregated_return.ts +9 -5
  70. package/src/parsing/operations/delete_node.ts +33 -0
  71. package/src/parsing/operations/delete_relationship.ts +32 -0
  72. package/src/parsing/operations/limit.ts +3 -0
  73. package/src/parsing/operations/order_by.ts +75 -0
  74. package/src/parsing/operations/return.ts +17 -3
  75. package/src/parsing/parser.ts +115 -2
  76. package/src/tokenization/token.ts +32 -0
  77. package/tests/compute/runner.test.ts +291 -0
  78. package/tests/parsing/parser.test.ts +1 -1
@@ -1,6 +1,7 @@
1
1
  import Runner from "../../src/compute/runner";
2
2
  import Database from "../../src/graph/database";
3
3
  import Node from "../../src/graph/node";
4
+ import Relationship from "../../src/graph/relationship";
4
5
  import AsyncFunction from "../../src/parsing/functions/async_function";
5
6
  import { FunctionDef } from "../../src/parsing/functions/function_metadata";
6
7
 
@@ -725,6 +726,38 @@ test("Test trim function with empty string", async () => {
725
726
  expect(results[0]).toEqual({ result: "" });
726
727
  });
727
728
 
729
+ test("Test substring function with start and length", async () => {
730
+ const runner = new Runner('RETURN substring("hello", 1, 3) as result');
731
+ await runner.run();
732
+ const results = runner.results;
733
+ expect(results.length).toBe(1);
734
+ expect(results[0]).toEqual({ result: "ell" });
735
+ });
736
+
737
+ test("Test substring function with start only", async () => {
738
+ const runner = new Runner('RETURN substring("hello", 2) as result');
739
+ await runner.run();
740
+ const results = runner.results;
741
+ expect(results.length).toBe(1);
742
+ expect(results[0]).toEqual({ result: "llo" });
743
+ });
744
+
745
+ test("Test substring function with zero start", async () => {
746
+ const runner = new Runner('RETURN substring("hello", 0, 5) as result');
747
+ await runner.run();
748
+ const results = runner.results;
749
+ expect(results.length).toBe(1);
750
+ expect(results[0]).toEqual({ result: "hello" });
751
+ });
752
+
753
+ test("Test substring function with zero length", async () => {
754
+ const runner = new Runner('RETURN substring("hello", 1, 0) as result');
755
+ await runner.run();
756
+ const results = runner.results;
757
+ expect(results.length).toBe(1);
758
+ expect(results[0]).toEqual({ result: "" });
759
+ });
760
+
728
761
  test("Test associative array with key which is keyword", async () => {
729
762
  const runner = new Runner("RETURN {return: 1} as aa");
730
763
  await runner.run();
@@ -2548,6 +2581,54 @@ test("Test WHERE with IN combined with AND", async () => {
2548
2581
  expect(results.map((r: any) => r.n)).toEqual([10, 15, 20]);
2549
2582
  });
2550
2583
 
2584
+ test("Test WHERE with AND before IN", async () => {
2585
+ const runner = new Runner(`
2586
+ unwind ['expert', 'intermediate', 'beginner'] as proficiency
2587
+ with proficiency where 1=1 and proficiency in ['expert']
2588
+ return proficiency
2589
+ `);
2590
+ await runner.run();
2591
+ const results = runner.results;
2592
+ expect(results.length).toBe(1);
2593
+ expect(results[0]).toEqual({ proficiency: "expert" });
2594
+ });
2595
+
2596
+ test("Test WHERE with AND before NOT IN", async () => {
2597
+ const runner = new Runner(`
2598
+ unwind ['expert', 'intermediate', 'beginner'] as proficiency
2599
+ with proficiency where 1=1 and proficiency not in ['expert']
2600
+ return proficiency
2601
+ `);
2602
+ await runner.run();
2603
+ const results = runner.results;
2604
+ expect(results.length).toBe(2);
2605
+ expect(results.map((r: any) => r.proficiency)).toEqual(["intermediate", "beginner"]);
2606
+ });
2607
+
2608
+ test("Test WHERE with OR before IN", async () => {
2609
+ const runner = new Runner(`
2610
+ unwind range(1, 10) as n
2611
+ with n where 1=0 or n in [3, 7]
2612
+ return n
2613
+ `);
2614
+ await runner.run();
2615
+ const results = runner.results;
2616
+ expect(results.length).toBe(2);
2617
+ expect(results.map((r: any) => r.n)).toEqual([3, 7]);
2618
+ });
2619
+
2620
+ test("Test IN as return expression with AND in WHERE", async () => {
2621
+ const runner = new Runner(`
2622
+ unwind ['expert', 'intermediate', 'beginner'] as proficiency
2623
+ with proficiency where 1=1 and proficiency in ['expert']
2624
+ return proficiency, proficiency in ['expert'] as isExpert
2625
+ `);
2626
+ await runner.run();
2627
+ const results = runner.results;
2628
+ expect(results.length).toBe(1);
2629
+ expect(results[0]).toEqual({ proficiency: "expert", isExpert: 1 });
2630
+ });
2631
+
2551
2632
  test("Test WHERE with CONTAINS", async () => {
2552
2633
  const runner = new Runner(`
2553
2634
  unwind ['apple', 'banana', 'grape', 'pineapple'] as fruit
@@ -3759,3 +3840,213 @@ test("Test duration() with time only", async () => {
3759
3840
  expect(d.totalSeconds).toBe(9000);
3760
3841
  expect(d.formatted).toBe("PT2H30M");
3761
3842
  });
3843
+
3844
+ // ORDER BY tests
3845
+
3846
+ test("Test order by ascending", async () => {
3847
+ const runner = new Runner("unwind [3, 1, 2] as x return x order by x");
3848
+ await runner.run();
3849
+ const results = runner.results;
3850
+ expect(results.length).toBe(3);
3851
+ expect(results[0]).toEqual({ x: 1 });
3852
+ expect(results[1]).toEqual({ x: 2 });
3853
+ expect(results[2]).toEqual({ x: 3 });
3854
+ });
3855
+
3856
+ test("Test order by descending", async () => {
3857
+ const runner = new Runner("unwind [3, 1, 2] as x return x order by x desc");
3858
+ await runner.run();
3859
+ const results = runner.results;
3860
+ expect(results.length).toBe(3);
3861
+ expect(results[0]).toEqual({ x: 3 });
3862
+ expect(results[1]).toEqual({ x: 2 });
3863
+ expect(results[2]).toEqual({ x: 1 });
3864
+ });
3865
+
3866
+ test("Test order by ascending explicit", async () => {
3867
+ const runner = new Runner("unwind [3, 1, 2] as x return x order by x asc");
3868
+ await runner.run();
3869
+ const results = runner.results;
3870
+ expect(results.length).toBe(3);
3871
+ expect(results[0]).toEqual({ x: 1 });
3872
+ expect(results[1]).toEqual({ x: 2 });
3873
+ expect(results[2]).toEqual({ x: 3 });
3874
+ });
3875
+
3876
+ test("Test order by with multiple fields", async () => {
3877
+ const runner = new Runner(`
3878
+ unwind [{name: 'Alice', age: 30}, {name: 'Bob', age: 25}, {name: 'Alice', age: 25}] as person
3879
+ return person.name as name, person.age as age
3880
+ order by name asc, age asc
3881
+ `);
3882
+ await runner.run();
3883
+ const results = runner.results;
3884
+ expect(results.length).toBe(3);
3885
+ expect(results[0]).toEqual({ name: "Alice", age: 25 });
3886
+ expect(results[1]).toEqual({ name: "Alice", age: 30 });
3887
+ expect(results[2]).toEqual({ name: "Bob", age: 25 });
3888
+ });
3889
+
3890
+ test("Test order by with strings", async () => {
3891
+ const runner = new Runner(
3892
+ "unwind ['banana', 'apple', 'cherry'] as fruit return fruit order by fruit"
3893
+ );
3894
+ await runner.run();
3895
+ const results = runner.results;
3896
+ expect(results.length).toBe(3);
3897
+ expect(results[0]).toEqual({ fruit: "apple" });
3898
+ expect(results[1]).toEqual({ fruit: "banana" });
3899
+ expect(results[2]).toEqual({ fruit: "cherry" });
3900
+ });
3901
+
3902
+ test("Test order by with aggregated return", async () => {
3903
+ const runner = new Runner(`
3904
+ unwind [1, 1, 2, 2, 3, 3] as x
3905
+ return x, count(x) as cnt
3906
+ order by x desc
3907
+ `);
3908
+ await runner.run();
3909
+ const results = runner.results;
3910
+ expect(results.length).toBe(3);
3911
+ expect(results[0]).toEqual({ x: 3, cnt: 2 });
3912
+ expect(results[1]).toEqual({ x: 2, cnt: 2 });
3913
+ expect(results[2]).toEqual({ x: 1, cnt: 2 });
3914
+ });
3915
+
3916
+ test("Test order by with limit", async () => {
3917
+ const runner = new Runner("unwind [3, 1, 4, 1, 5, 9, 2, 6] as x return x order by x limit 3");
3918
+ await runner.run();
3919
+ const results = runner.results;
3920
+ expect(results.length).toBe(3);
3921
+ expect(results[0]).toEqual({ x: 1 });
3922
+ expect(results[1]).toEqual({ x: 1 });
3923
+ expect(results[2]).toEqual({ x: 2 });
3924
+ });
3925
+
3926
+ test("Test order by with where", async () => {
3927
+ const runner = new Runner(
3928
+ "unwind [3, 1, 4, 1, 5, 9, 2, 6] as x return x where x > 2 order by x desc"
3929
+ );
3930
+ await runner.run();
3931
+ const results = runner.results;
3932
+ expect(results.length).toBe(5);
3933
+ expect(results[0]).toEqual({ x: 9 });
3934
+ expect(results[1]).toEqual({ x: 6 });
3935
+ expect(results[2]).toEqual({ x: 5 });
3936
+ expect(results[3]).toEqual({ x: 4 });
3937
+ expect(results[4]).toEqual({ x: 3 });
3938
+ });
3939
+
3940
+ test("Test delete virtual node operation", async () => {
3941
+ const db = Database.getInstance();
3942
+ // Create a virtual node first
3943
+ const create = new Runner(`
3944
+ CREATE VIRTUAL (:DeleteTestPerson) AS {
3945
+ unwind [
3946
+ {id: 1, name: 'Person 1'},
3947
+ {id: 2, name: 'Person 2'}
3948
+ ] as record
3949
+ RETURN record.id as id, record.name as name
3950
+ }
3951
+ `);
3952
+ await create.run();
3953
+ expect(db.getNode(new Node(null, "DeleteTestPerson"))).not.toBeNull();
3954
+
3955
+ // Delete the virtual node
3956
+ const del = new Runner("DELETE VIRTUAL (:DeleteTestPerson)");
3957
+ await del.run();
3958
+ expect(del.results.length).toBe(0);
3959
+ expect(db.getNode(new Node(null, "DeleteTestPerson"))).toBeNull();
3960
+ });
3961
+
3962
+ test("Test delete virtual node then match throws", async () => {
3963
+ // Create a virtual node
3964
+ const create = new Runner(`
3965
+ CREATE VIRTUAL (:DeleteMatchPerson) AS {
3966
+ unwind [{id: 1, name: 'Alice'}] as record
3967
+ RETURN record.id as id, record.name as name
3968
+ }
3969
+ `);
3970
+ await create.run();
3971
+
3972
+ // Verify it can be matched
3973
+ const match1 = new Runner("MATCH (n:DeleteMatchPerson) RETURN n");
3974
+ await match1.run();
3975
+ expect(match1.results.length).toBe(1);
3976
+
3977
+ // Delete the virtual node
3978
+ const del = new Runner("DELETE VIRTUAL (:DeleteMatchPerson)");
3979
+ await del.run();
3980
+
3981
+ // Matching should now throw since the node is gone
3982
+ const match2 = new Runner("MATCH (n:DeleteMatchPerson) RETURN n");
3983
+ await expect(match2.run()).rejects.toThrow();
3984
+ });
3985
+
3986
+ test("Test delete virtual relationship operation", async () => {
3987
+ const db = Database.getInstance();
3988
+ // Create virtual nodes and relationship
3989
+ await new Runner(`
3990
+ CREATE VIRTUAL (:DelRelUser) AS {
3991
+ unwind [
3992
+ {id: 1, name: 'Alice'},
3993
+ {id: 2, name: 'Bob'}
3994
+ ] as record
3995
+ RETURN record.id as id, record.name as name
3996
+ }
3997
+ `).run();
3998
+
3999
+ await new Runner(`
4000
+ CREATE VIRTUAL (:DelRelUser)-[:DEL_KNOWS]-(:DelRelUser) AS {
4001
+ unwind [
4002
+ {left_id: 1, right_id: 2}
4003
+ ] as record
4004
+ RETURN record.left_id as left_id, record.right_id as right_id
4005
+ }
4006
+ `).run();
4007
+
4008
+ // Verify relationship exists
4009
+ const rel = new Relationship();
4010
+ rel.type = "DEL_KNOWS";
4011
+ expect(db.getRelationship(rel)).not.toBeNull();
4012
+
4013
+ // Delete the virtual relationship
4014
+ const del = new Runner("DELETE VIRTUAL (:DelRelUser)-[:DEL_KNOWS]-(:DelRelUser)");
4015
+ await del.run();
4016
+ expect(del.results.length).toBe(0);
4017
+ expect(db.getRelationship(rel)).toBeNull();
4018
+ });
4019
+
4020
+ test("Test delete virtual node leaves other nodes intact", async () => {
4021
+ const db = Database.getInstance();
4022
+ // Create two virtual node types
4023
+ await new Runner(`
4024
+ CREATE VIRTUAL (:KeepNode) AS {
4025
+ unwind [{id: 1, name: 'Keep'}] as record
4026
+ RETURN record.id as id, record.name as name
4027
+ }
4028
+ `).run();
4029
+
4030
+ await new Runner(`
4031
+ CREATE VIRTUAL (:RemoveNode) AS {
4032
+ unwind [{id: 2, name: 'Remove'}] as record
4033
+ RETURN record.id as id, record.name as name
4034
+ }
4035
+ `).run();
4036
+
4037
+ expect(db.getNode(new Node(null, "KeepNode"))).not.toBeNull();
4038
+ expect(db.getNode(new Node(null, "RemoveNode"))).not.toBeNull();
4039
+
4040
+ // Delete only one
4041
+ await new Runner("DELETE VIRTUAL (:RemoveNode)").run();
4042
+
4043
+ // The other should still exist
4044
+ expect(db.getNode(new Node(null, "KeepNode"))).not.toBeNull();
4045
+ expect(db.getNode(new Node(null, "RemoveNode"))).toBeNull();
4046
+
4047
+ // The remaining node can still be matched
4048
+ const match = new Runner("MATCH (n:KeepNode) RETURN n");
4049
+ await match.run();
4050
+ expect(match.results.length).toBe(1);
4051
+ expect(match.results[0].n.name).toBe("Keep");
4052
+ });
@@ -521,7 +521,7 @@ test("Test non-well formed statements", () => {
521
521
  "Only one RETURN statement is allowed"
522
522
  );
523
523
  expect(() => new Parser().parse("return 1 with 1 as n")).toThrow(
524
- "Last statement must be a RETURN, WHERE, CALL, or CREATE statement"
524
+ "Last statement must be a RETURN, WHERE, CALL, CREATE, or DELETE statement"
525
525
  );
526
526
  });
527
527