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.
- package/dist/flowquery.min.js +1 -1
- package/dist/graph/database.d.ts +2 -0
- package/dist/graph/database.d.ts.map +1 -1
- package/dist/graph/database.js +12 -0
- package/dist/graph/database.js.map +1 -1
- package/dist/parsing/expressions/operator.js +4 -4
- package/dist/parsing/expressions/operator.js.map +1 -1
- package/dist/parsing/functions/function_factory.d.ts +1 -0
- package/dist/parsing/functions/function_factory.d.ts.map +1 -1
- package/dist/parsing/functions/function_factory.js +1 -0
- package/dist/parsing/functions/function_factory.js.map +1 -1
- package/dist/parsing/functions/substring.d.ts +9 -0
- package/dist/parsing/functions/substring.d.ts.map +1 -0
- package/dist/parsing/functions/substring.js +62 -0
- package/dist/parsing/functions/substring.js.map +1 -0
- package/dist/parsing/operations/aggregated_return.d.ts.map +1 -1
- package/dist/parsing/operations/aggregated_return.js +6 -2
- package/dist/parsing/operations/aggregated_return.js.map +1 -1
- package/dist/parsing/operations/delete_node.d.ts +11 -0
- package/dist/parsing/operations/delete_node.d.ts.map +1 -0
- package/dist/parsing/operations/delete_node.js +46 -0
- package/dist/parsing/operations/delete_node.js.map +1 -0
- package/dist/parsing/operations/delete_relationship.d.ts +11 -0
- package/dist/parsing/operations/delete_relationship.d.ts.map +1 -0
- package/dist/parsing/operations/delete_relationship.js +46 -0
- package/dist/parsing/operations/delete_relationship.js.map +1 -0
- package/dist/parsing/operations/limit.d.ts +1 -0
- package/dist/parsing/operations/limit.d.ts.map +1 -1
- package/dist/parsing/operations/limit.js +3 -0
- package/dist/parsing/operations/limit.js.map +1 -1
- package/dist/parsing/operations/order_by.d.ts +35 -0
- package/dist/parsing/operations/order_by.d.ts.map +1 -0
- package/dist/parsing/operations/order_by.js +87 -0
- package/dist/parsing/operations/order_by.js.map +1 -0
- package/dist/parsing/operations/return.d.ts +3 -0
- package/dist/parsing/operations/return.d.ts.map +1 -1
- package/dist/parsing/operations/return.js +16 -3
- package/dist/parsing/operations/return.js.map +1 -1
- package/dist/parsing/parser.d.ts +2 -0
- package/dist/parsing/parser.d.ts.map +1 -1
- package/dist/parsing/parser.js +116 -2
- package/dist/parsing/parser.js.map +1 -1
- package/dist/tokenization/token.d.ts +8 -0
- package/dist/tokenization/token.d.ts.map +1 -1
- package/dist/tokenization/token.js +24 -0
- package/dist/tokenization/token.js.map +1 -1
- package/docs/flowquery.min.js +1 -1
- package/flowquery-py/pyproject.toml +1 -1
- package/flowquery-py/src/graph/database.py +12 -0
- package/flowquery-py/src/parsing/expressions/operator.py +4 -4
- package/flowquery-py/src/parsing/functions/__init__.py +2 -0
- package/flowquery-py/src/parsing/functions/substring.py +74 -0
- package/flowquery-py/src/parsing/operations/__init__.py +7 -0
- package/flowquery-py/src/parsing/operations/aggregated_return.py +4 -1
- package/flowquery-py/src/parsing/operations/delete_node.py +29 -0
- package/flowquery-py/src/parsing/operations/delete_relationship.py +29 -0
- package/flowquery-py/src/parsing/operations/limit.py +4 -0
- package/flowquery-py/src/parsing/operations/order_by.py +72 -0
- package/flowquery-py/src/parsing/operations/return_op.py +20 -3
- package/flowquery-py/src/parsing/parser.py +98 -3
- package/flowquery-py/src/tokenization/token.py +28 -0
- package/flowquery-py/tests/compute/test_runner.py +329 -1
- package/flowquery-vscode/flowQueryEngine/flowquery.min.js +1 -1
- package/package.json +1 -1
- package/src/graph/database.ts +12 -0
- package/src/parsing/expressions/operator.ts +4 -4
- package/src/parsing/functions/function_factory.ts +1 -0
- package/src/parsing/functions/substring.ts +65 -0
- package/src/parsing/operations/aggregated_return.ts +9 -5
- package/src/parsing/operations/delete_node.ts +33 -0
- package/src/parsing/operations/delete_relationship.ts +32 -0
- package/src/parsing/operations/limit.ts +3 -0
- package/src/parsing/operations/order_by.ts +75 -0
- package/src/parsing/operations/return.ts +17 -3
- package/src/parsing/parser.ts +115 -2
- package/src/tokenization/token.ts +32 -0
- package/tests/compute/runner.test.ts +291 -0
- 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
|
|
524
|
+
"Last statement must be a RETURN, WHERE, CALL, CREATE, or DELETE statement"
|
|
525
525
|
);
|
|
526
526
|
});
|
|
527
527
|
|