flowquery 1.0.33 → 1.0.35
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 +1 -0
- package/dist/graph/database.d.ts.map +1 -1
- package/dist/graph/database.js +43 -6
- package/dist/graph/database.js.map +1 -1
- package/dist/graph/relationship.d.ts +3 -1
- package/dist/graph/relationship.d.ts.map +1 -1
- package/dist/graph/relationship.js +12 -4
- package/dist/graph/relationship.js.map +1 -1
- package/dist/graph/relationship_data.js +1 -1
- package/dist/graph/relationship_data.js.map +1 -1
- package/dist/graph/relationship_match_collector.d.ts.map +1 -1
- package/dist/graph/relationship_match_collector.js +6 -3
- package/dist/graph/relationship_match_collector.js.map +1 -1
- package/dist/graph/relationship_reference.js +1 -1
- package/dist/graph/relationship_reference.js.map +1 -1
- package/dist/parsing/functions/function_factory.d.ts +3 -0
- package/dist/parsing/functions/function_factory.d.ts.map +1 -1
- package/dist/parsing/functions/function_factory.js +3 -0
- package/dist/parsing/functions/function_factory.js.map +1 -1
- package/dist/parsing/functions/predicate_sum.d.ts.map +1 -1
- package/dist/parsing/functions/predicate_sum.js +13 -10
- package/dist/parsing/functions/predicate_sum.js.map +1 -1
- package/dist/parsing/functions/schema.d.ts +5 -2
- package/dist/parsing/functions/schema.d.ts.map +1 -1
- package/dist/parsing/functions/schema.js +7 -4
- package/dist/parsing/functions/schema.js.map +1 -1
- package/dist/parsing/functions/to_lower.d.ts +7 -0
- package/dist/parsing/functions/to_lower.d.ts.map +1 -0
- package/dist/parsing/functions/to_lower.js +37 -0
- package/dist/parsing/functions/to_lower.js.map +1 -0
- package/dist/parsing/functions/to_string.d.ts +7 -0
- package/dist/parsing/functions/to_string.d.ts.map +1 -0
- package/dist/parsing/functions/to_string.js +44 -0
- package/dist/parsing/functions/to_string.js.map +1 -0
- package/dist/parsing/functions/trim.d.ts +7 -0
- package/dist/parsing/functions/trim.d.ts.map +1 -0
- package/dist/parsing/functions/trim.js +37 -0
- package/dist/parsing/functions/trim.js.map +1 -0
- package/dist/parsing/operations/group_by.d.ts.map +1 -1
- package/dist/parsing/operations/group_by.js +4 -2
- package/dist/parsing/operations/group_by.js.map +1 -1
- package/dist/parsing/parser.d.ts.map +1 -1
- package/dist/parsing/parser.js +15 -2
- package/dist/parsing/parser.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 +44 -11
- package/flowquery-py/src/graph/relationship.py +11 -3
- package/flowquery-py/src/graph/relationship_data.py +2 -1
- package/flowquery-py/src/graph/relationship_match_collector.py +7 -1
- package/flowquery-py/src/graph/relationship_reference.py +2 -2
- package/flowquery-py/src/parsing/functions/__init__.py +6 -0
- package/flowquery-py/src/parsing/functions/predicate_sum.py +3 -6
- package/flowquery-py/src/parsing/functions/schema.py +9 -5
- package/flowquery-py/src/parsing/functions/to_lower.py +35 -0
- package/flowquery-py/src/parsing/functions/to_string.py +41 -0
- package/flowquery-py/src/parsing/functions/trim.py +35 -0
- package/flowquery-py/src/parsing/operations/group_by.py +2 -0
- package/flowquery-py/src/parsing/parser.py +12 -2
- package/flowquery-py/tests/compute/test_runner.py +294 -4
- package/flowquery-vscode/flowQueryEngine/flowquery.min.js +1 -1
- package/package.json +1 -1
- package/src/graph/database.ts +42 -4
- package/src/graph/relationship.ts +12 -4
- package/src/graph/relationship_data.ts +1 -1
- package/src/graph/relationship_match_collector.ts +6 -2
- package/src/graph/relationship_reference.ts +1 -1
- package/src/parsing/functions/function_factory.ts +3 -0
- package/src/parsing/functions/predicate_sum.ts +17 -12
- package/src/parsing/functions/schema.ts +7 -4
- package/src/parsing/functions/to_lower.ts +25 -0
- package/src/parsing/functions/to_string.ts +32 -0
- package/src/parsing/functions/trim.ts +25 -0
- package/src/parsing/operations/group_by.ts +4 -1
- package/src/parsing/parser.ts +15 -2
- package/tests/compute/runner.test.ts +319 -3
- package/tests/parsing/parser.test.ts +37 -0
|
@@ -573,6 +573,78 @@ test("Test stringify function", async () => {
|
|
|
573
573
|
});
|
|
574
574
|
});
|
|
575
575
|
|
|
576
|
+
test("Test toString function with number", async () => {
|
|
577
|
+
const runner = new Runner("RETURN toString(42) as result");
|
|
578
|
+
await runner.run();
|
|
579
|
+
const results = runner.results;
|
|
580
|
+
expect(results.length).toBe(1);
|
|
581
|
+
expect(results[0]).toEqual({ result: "42" });
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
test("Test toString function with boolean", async () => {
|
|
585
|
+
const runner = new Runner("RETURN toString(true) as result");
|
|
586
|
+
await runner.run();
|
|
587
|
+
const results = runner.results;
|
|
588
|
+
expect(results.length).toBe(1);
|
|
589
|
+
expect(results[0]).toEqual({ result: "true" });
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
test("Test toString function with object", async () => {
|
|
593
|
+
const runner = new Runner("RETURN toString({a: 1}) as result");
|
|
594
|
+
await runner.run();
|
|
595
|
+
const results = runner.results;
|
|
596
|
+
expect(results.length).toBe(1);
|
|
597
|
+
expect(results[0]).toEqual({ result: '{"a":1}' });
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
test("Test toLower function", async () => {
|
|
601
|
+
const runner = new Runner('RETURN toLower("Hello World") as result');
|
|
602
|
+
await runner.run();
|
|
603
|
+
const results = runner.results;
|
|
604
|
+
expect(results.length).toBe(1);
|
|
605
|
+
expect(results[0]).toEqual({ result: "hello world" });
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
test("Test toLower function with all uppercase", async () => {
|
|
609
|
+
const runner = new Runner('RETURN toLower("FOO BAR") as result');
|
|
610
|
+
await runner.run();
|
|
611
|
+
const results = runner.results;
|
|
612
|
+
expect(results.length).toBe(1);
|
|
613
|
+
expect(results[0]).toEqual({ result: "foo bar" });
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
test("Test trim function", async () => {
|
|
617
|
+
const runner = new Runner('RETURN trim(" hello ") as result');
|
|
618
|
+
await runner.run();
|
|
619
|
+
const results = runner.results;
|
|
620
|
+
expect(results.length).toBe(1);
|
|
621
|
+
expect(results[0]).toEqual({ result: "hello" });
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
test("Test trim function with tabs and newlines", async () => {
|
|
625
|
+
const runner = new Runner('WITH "\tfoo\n" AS s RETURN trim(s) as result');
|
|
626
|
+
await runner.run();
|
|
627
|
+
const results = runner.results;
|
|
628
|
+
expect(results.length).toBe(1);
|
|
629
|
+
expect(results[0]).toEqual({ result: "foo" });
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
test("Test trim function with no whitespace", async () => {
|
|
633
|
+
const runner = new Runner('RETURN trim("hello") as result');
|
|
634
|
+
await runner.run();
|
|
635
|
+
const results = runner.results;
|
|
636
|
+
expect(results.length).toBe(1);
|
|
637
|
+
expect(results[0]).toEqual({ result: "hello" });
|
|
638
|
+
});
|
|
639
|
+
|
|
640
|
+
test("Test trim function with empty string", async () => {
|
|
641
|
+
const runner = new Runner('RETURN trim("") as result');
|
|
642
|
+
await runner.run();
|
|
643
|
+
const results = runner.results;
|
|
644
|
+
expect(results.length).toBe(1);
|
|
645
|
+
expect(results[0]).toEqual({ result: "" });
|
|
646
|
+
});
|
|
647
|
+
|
|
576
648
|
test("Test associative array with key which is keyword", async () => {
|
|
577
649
|
const runner = new Runner("RETURN {return: 1} as aa");
|
|
578
650
|
await runner.run();
|
|
@@ -1924,20 +1996,24 @@ test("Test schema() returns nodes and relationships with sample data", async ()
|
|
|
1924
1996
|
`).run();
|
|
1925
1997
|
|
|
1926
1998
|
const runner = new Runner(
|
|
1927
|
-
"CALL schema() YIELD kind, label, type, sample RETURN kind, label, type, sample"
|
|
1999
|
+
"CALL schema() YIELD kind, label, type, from_label, to_label, properties, sample RETURN kind, label, type, from_label, to_label, properties, sample"
|
|
1928
2000
|
);
|
|
1929
2001
|
await runner.run();
|
|
1930
2002
|
const results = runner.results;
|
|
1931
2003
|
|
|
1932
|
-
const animal = results.find((r: any) => r.kind === "
|
|
2004
|
+
const animal = results.find((r: any) => r.kind === "Node" && r.label === "Animal");
|
|
1933
2005
|
expect(animal).toBeDefined();
|
|
2006
|
+
expect(animal.properties).toEqual(["species", "legs"]);
|
|
1934
2007
|
expect(animal.sample).toBeDefined();
|
|
1935
2008
|
expect(animal.sample).not.toHaveProperty("id");
|
|
1936
2009
|
expect(animal.sample).toHaveProperty("species");
|
|
1937
2010
|
expect(animal.sample).toHaveProperty("legs");
|
|
1938
2011
|
|
|
1939
|
-
const chases = results.find((r: any) => r.kind === "
|
|
2012
|
+
const chases = results.find((r: any) => r.kind === "Relationship" && r.type === "CHASES");
|
|
1940
2013
|
expect(chases).toBeDefined();
|
|
2014
|
+
expect(chases.from_label).toBe("Animal");
|
|
2015
|
+
expect(chases.to_label).toBe("Animal");
|
|
2016
|
+
expect(chases.properties).toEqual(["speed"]);
|
|
1941
2017
|
expect(chases.sample).toBeDefined();
|
|
1942
2018
|
expect(chases.sample).not.toHaveProperty("left_id");
|
|
1943
2019
|
expect(chases.sample).not.toHaveProperty("right_id");
|
|
@@ -2650,3 +2726,243 @@ test("Test UNION with empty right side", async () => {
|
|
|
2650
2726
|
expect(results.length).toBe(1);
|
|
2651
2727
|
expect(results).toEqual([{ x: 1 }]);
|
|
2652
2728
|
});
|
|
2729
|
+
|
|
2730
|
+
test("Test language name hits query with virtual graph", async () => {
|
|
2731
|
+
// Create Language nodes
|
|
2732
|
+
await new Runner(`
|
|
2733
|
+
CREATE VIRTUAL (:Language) AS {
|
|
2734
|
+
UNWIND [
|
|
2735
|
+
{id: 1, name: 'Python'},
|
|
2736
|
+
{id: 2, name: 'JavaScript'},
|
|
2737
|
+
{id: 3, name: 'TypeScript'}
|
|
2738
|
+
] AS record
|
|
2739
|
+
RETURN record.id AS id, record.name AS name
|
|
2740
|
+
}
|
|
2741
|
+
`).run();
|
|
2742
|
+
|
|
2743
|
+
// Create Chat nodes with messages
|
|
2744
|
+
await new Runner(`
|
|
2745
|
+
CREATE VIRTUAL (:Chat) AS {
|
|
2746
|
+
UNWIND [
|
|
2747
|
+
{id: 1, name: 'Dev Discussion', messages: [
|
|
2748
|
+
{From: 'Alice', SentDateTime: '2025-01-01T10:00:00', Content: 'I love Python and JavaScript'},
|
|
2749
|
+
{From: 'Bob', SentDateTime: '2025-01-01T10:05:00', Content: 'What languages do you prefer?'}
|
|
2750
|
+
]},
|
|
2751
|
+
{id: 2, name: 'General', messages: [
|
|
2752
|
+
{From: 'Charlie', SentDateTime: '2025-01-02T09:00:00', Content: 'The weather is nice today'},
|
|
2753
|
+
{From: 'Alice', SentDateTime: '2025-01-02T09:05:00', Content: 'TypeScript is great for language tooling'}
|
|
2754
|
+
]}
|
|
2755
|
+
] AS record
|
|
2756
|
+
RETURN record.id AS id, record.name AS name, record.messages AS messages
|
|
2757
|
+
}
|
|
2758
|
+
`).run();
|
|
2759
|
+
|
|
2760
|
+
// Create User nodes
|
|
2761
|
+
await new Runner(`
|
|
2762
|
+
CREATE VIRTUAL (:User) AS {
|
|
2763
|
+
UNWIND [
|
|
2764
|
+
{id: 1, displayName: 'Alice'},
|
|
2765
|
+
{id: 2, displayName: 'Bob'},
|
|
2766
|
+
{id: 3, displayName: 'Charlie'}
|
|
2767
|
+
] AS record
|
|
2768
|
+
RETURN record.id AS id, record.displayName AS displayName
|
|
2769
|
+
}
|
|
2770
|
+
`).run();
|
|
2771
|
+
|
|
2772
|
+
// Create PARTICIPATES_IN relationships
|
|
2773
|
+
await new Runner(`
|
|
2774
|
+
CREATE VIRTUAL (:User)-[:PARTICIPATES_IN]-(:Chat) AS {
|
|
2775
|
+
UNWIND [
|
|
2776
|
+
{left_id: 1, right_id: 1},
|
|
2777
|
+
{left_id: 2, right_id: 1},
|
|
2778
|
+
{left_id: 3, right_id: 2},
|
|
2779
|
+
{left_id: 1, right_id: 2}
|
|
2780
|
+
] AS record
|
|
2781
|
+
RETURN record.left_id AS left_id, record.right_id AS right_id
|
|
2782
|
+
}
|
|
2783
|
+
`).run();
|
|
2784
|
+
|
|
2785
|
+
// Run the original query (using 'sender' alias since 'from' is a reserved keyword)
|
|
2786
|
+
const runner = new Runner(`
|
|
2787
|
+
MATCH (l:Language)
|
|
2788
|
+
WITH collect(distinct l.name) AS langs
|
|
2789
|
+
MATCH (c:Chat)
|
|
2790
|
+
UNWIND c.messages AS msg
|
|
2791
|
+
WITH c, msg, langs,
|
|
2792
|
+
sum(lang IN langs | 1 where toLower(msg.Content) CONTAINS toLower(lang)) AS langNameHits
|
|
2793
|
+
WHERE toLower(msg.Content) CONTAINS "language"
|
|
2794
|
+
OR toLower(msg.Content) CONTAINS "languages"
|
|
2795
|
+
OR langNameHits > 0
|
|
2796
|
+
OPTIONAL MATCH (u:User)-[:PARTICIPATES_IN]->(c)
|
|
2797
|
+
RETURN
|
|
2798
|
+
c.name AS chat,
|
|
2799
|
+
collect(distinct u.displayName) AS participants,
|
|
2800
|
+
msg.From AS sender,
|
|
2801
|
+
msg.SentDateTime AS sentDateTime,
|
|
2802
|
+
msg.Content AS message
|
|
2803
|
+
`);
|
|
2804
|
+
await runner.run();
|
|
2805
|
+
const results = runner.results;
|
|
2806
|
+
|
|
2807
|
+
// Messages that mention a language name or the word "language(s)":
|
|
2808
|
+
// 1. "I love Python and JavaScript" - langNameHits=2 (matches Python and JavaScript)
|
|
2809
|
+
// 2. "What languages do you prefer?" - contains "languages"
|
|
2810
|
+
// 3. "TypeScript is great for language tooling" - langNameHits=1, also contains "language"
|
|
2811
|
+
expect(results.length).toBe(3);
|
|
2812
|
+
expect(results[0].chat).toBe("Dev Discussion");
|
|
2813
|
+
expect(results[0].message).toBe("I love Python and JavaScript");
|
|
2814
|
+
expect(results[0].sender).toBe("Alice");
|
|
2815
|
+
expect(results[1].chat).toBe("Dev Discussion");
|
|
2816
|
+
expect(results[1].message).toBe("What languages do you prefer?");
|
|
2817
|
+
expect(results[1].sender).toBe("Bob");
|
|
2818
|
+
expect(results[2].chat).toBe("General");
|
|
2819
|
+
expect(results[2].message).toBe("TypeScript is great for language tooling");
|
|
2820
|
+
expect(results[2].sender).toBe("Alice");
|
|
2821
|
+
});
|
|
2822
|
+
|
|
2823
|
+
test("Test sum with empty collected array", async () => {
|
|
2824
|
+
// Reproduces the original bug: collect on empty input should yield []
|
|
2825
|
+
// and sum over that empty array should return 0, not throw
|
|
2826
|
+
const runner = new Runner(`
|
|
2827
|
+
UNWIND [] AS lang
|
|
2828
|
+
WITH collect(distinct lang) AS langs
|
|
2829
|
+
UNWIND ['hello', 'world'] AS msg
|
|
2830
|
+
WITH msg, langs, sum(l IN langs | 1 where toLower(msg) CONTAINS toLower(l)) AS hits
|
|
2831
|
+
RETURN msg, hits
|
|
2832
|
+
`);
|
|
2833
|
+
await runner.run();
|
|
2834
|
+
const results = runner.results;
|
|
2835
|
+
expect(results.length).toBe(2);
|
|
2836
|
+
expect(results[0]).toEqual({ msg: "hello", hits: 0 });
|
|
2837
|
+
expect(results[1]).toEqual({ msg: "world", hits: 0 });
|
|
2838
|
+
});
|
|
2839
|
+
|
|
2840
|
+
test("Test sum where all elements filtered returns 0", async () => {
|
|
2841
|
+
const runner = new Runner("RETURN sum(n in [1, 2, 3] | n where n > 100) as sum");
|
|
2842
|
+
await runner.run();
|
|
2843
|
+
const results = runner.results;
|
|
2844
|
+
expect(results.length).toBe(1);
|
|
2845
|
+
expect(results[0]).toEqual({ sum: 0 });
|
|
2846
|
+
});
|
|
2847
|
+
|
|
2848
|
+
test("Test sum over empty array returns 0", async () => {
|
|
2849
|
+
const runner = new Runner("WITH [] AS arr RETURN sum(n in arr | n) as sum");
|
|
2850
|
+
await runner.run();
|
|
2851
|
+
const results = runner.results;
|
|
2852
|
+
expect(results.length).toBe(1);
|
|
2853
|
+
expect(results[0]).toEqual({ sum: 0 });
|
|
2854
|
+
});
|
|
2855
|
+
|
|
2856
|
+
test("Test match with ORed relationship types", async () => {
|
|
2857
|
+
await new Runner(`
|
|
2858
|
+
CREATE VIRTUAL (:Person) AS {
|
|
2859
|
+
unwind [
|
|
2860
|
+
{id: 1, name: 'Alice'},
|
|
2861
|
+
{id: 2, name: 'Bob'},
|
|
2862
|
+
{id: 3, name: 'Charlie'}
|
|
2863
|
+
] as record
|
|
2864
|
+
RETURN record.id as id, record.name as name
|
|
2865
|
+
}
|
|
2866
|
+
`).run();
|
|
2867
|
+
await new Runner(`
|
|
2868
|
+
CREATE VIRTUAL (:Person)-[:KNOWS]-(:Person) AS {
|
|
2869
|
+
unwind [
|
|
2870
|
+
{left_id: 1, right_id: 2}
|
|
2871
|
+
] as record
|
|
2872
|
+
RETURN record.left_id as left_id, record.right_id as right_id
|
|
2873
|
+
}
|
|
2874
|
+
`).run();
|
|
2875
|
+
await new Runner(`
|
|
2876
|
+
CREATE VIRTUAL (:Person)-[:FOLLOWS]-(:Person) AS {
|
|
2877
|
+
unwind [
|
|
2878
|
+
{left_id: 2, right_id: 3}
|
|
2879
|
+
] as record
|
|
2880
|
+
RETURN record.left_id as left_id, record.right_id as right_id
|
|
2881
|
+
}
|
|
2882
|
+
`).run();
|
|
2883
|
+
const match = new Runner(`
|
|
2884
|
+
MATCH (a:Person)-[:KNOWS|FOLLOWS]->(b:Person)
|
|
2885
|
+
RETURN a.name AS name1, b.name AS name2
|
|
2886
|
+
`);
|
|
2887
|
+
await match.run();
|
|
2888
|
+
const results = match.results;
|
|
2889
|
+
expect(results.length).toBe(2);
|
|
2890
|
+
expect(results[0]).toEqual({ name1: "Alice", name2: "Bob" });
|
|
2891
|
+
expect(results[1]).toEqual({ name1: "Bob", name2: "Charlie" });
|
|
2892
|
+
});
|
|
2893
|
+
|
|
2894
|
+
test("Test match with ORed relationship types with optional colon syntax", async () => {
|
|
2895
|
+
await new Runner(`
|
|
2896
|
+
CREATE VIRTUAL (:Animal) AS {
|
|
2897
|
+
unwind [
|
|
2898
|
+
{id: 1, name: 'Cat'},
|
|
2899
|
+
{id: 2, name: 'Dog'},
|
|
2900
|
+
{id: 3, name: 'Fish'}
|
|
2901
|
+
] as record
|
|
2902
|
+
RETURN record.id as id, record.name as name
|
|
2903
|
+
}
|
|
2904
|
+
`).run();
|
|
2905
|
+
await new Runner(`
|
|
2906
|
+
CREATE VIRTUAL (:Animal)-[:CHASES]-(:Animal) AS {
|
|
2907
|
+
unwind [
|
|
2908
|
+
{left_id: 1, right_id: 2}
|
|
2909
|
+
] as record
|
|
2910
|
+
RETURN record.left_id as left_id, record.right_id as right_id
|
|
2911
|
+
}
|
|
2912
|
+
`).run();
|
|
2913
|
+
await new Runner(`
|
|
2914
|
+
CREATE VIRTUAL (:Animal)-[:EATS]-(:Animal) AS {
|
|
2915
|
+
unwind [
|
|
2916
|
+
{left_id: 1, right_id: 3}
|
|
2917
|
+
] as record
|
|
2918
|
+
RETURN record.left_id as left_id, record.right_id as right_id
|
|
2919
|
+
}
|
|
2920
|
+
`).run();
|
|
2921
|
+
const match = new Runner(`
|
|
2922
|
+
MATCH (a:Animal)-[:CHASES|:EATS]->(b:Animal)
|
|
2923
|
+
RETURN a.name AS name1, b.name AS name2
|
|
2924
|
+
`);
|
|
2925
|
+
await match.run();
|
|
2926
|
+
const results = match.results;
|
|
2927
|
+
expect(results.length).toBe(2);
|
|
2928
|
+
expect(results[0]).toEqual({ name1: "Cat", name2: "Dog" });
|
|
2929
|
+
expect(results[1]).toEqual({ name1: "Cat", name2: "Fish" });
|
|
2930
|
+
});
|
|
2931
|
+
|
|
2932
|
+
test("Test match with ORed relationship types returns correct type in relationship variable", async () => {
|
|
2933
|
+
await new Runner(`
|
|
2934
|
+
CREATE VIRTUAL (:City) AS {
|
|
2935
|
+
unwind [
|
|
2936
|
+
{id: 1, name: 'NYC'},
|
|
2937
|
+
{id: 2, name: 'LA'},
|
|
2938
|
+
{id: 3, name: 'Chicago'}
|
|
2939
|
+
] as record
|
|
2940
|
+
RETURN record.id as id, record.name as name
|
|
2941
|
+
}
|
|
2942
|
+
`).run();
|
|
2943
|
+
await new Runner(`
|
|
2944
|
+
CREATE VIRTUAL (:City)-[:FLIGHT]-(:City) AS {
|
|
2945
|
+
unwind [
|
|
2946
|
+
{left_id: 1, right_id: 2, airline: 'Delta'}
|
|
2947
|
+
] as record
|
|
2948
|
+
RETURN record.left_id as left_id, record.right_id as right_id, record.airline as airline
|
|
2949
|
+
}
|
|
2950
|
+
`).run();
|
|
2951
|
+
await new Runner(`
|
|
2952
|
+
CREATE VIRTUAL (:City)-[:TRAIN]-(:City) AS {
|
|
2953
|
+
unwind [
|
|
2954
|
+
{left_id: 1, right_id: 3, line: 'Amtrak'}
|
|
2955
|
+
] as record
|
|
2956
|
+
RETURN record.left_id as left_id, record.right_id as right_id, record.line as line
|
|
2957
|
+
}
|
|
2958
|
+
`).run();
|
|
2959
|
+
const match = new Runner(`
|
|
2960
|
+
MATCH (a:City)-[r:FLIGHT|TRAIN]->(b:City)
|
|
2961
|
+
RETURN a.name AS from, b.name AS to, r.type AS type
|
|
2962
|
+
`);
|
|
2963
|
+
await match.run();
|
|
2964
|
+
const results = match.results;
|
|
2965
|
+
expect(results.length).toBe(2);
|
|
2966
|
+
expect(results[0]).toEqual({ from: "NYC", to: "LA", type: "FLIGHT" });
|
|
2967
|
+
expect(results[1]).toEqual({ from: "NYC", to: "Chicago", type: "TRAIN" });
|
|
2968
|
+
});
|
|
@@ -750,6 +750,43 @@ test("Match with graph pattern including relationships", () => {
|
|
|
750
750
|
expect(target.label).toBe("Person");
|
|
751
751
|
});
|
|
752
752
|
|
|
753
|
+
test("Match with ORed relationship types", () => {
|
|
754
|
+
const parser = new Parser();
|
|
755
|
+
const ast = parser.parse("MATCH (a:Person)-[:KNOWS|FOLLOWS]->(b:Person) RETURN a, b");
|
|
756
|
+
const match = ast.firstChild() as Match;
|
|
757
|
+
expect(match.patterns[0].chain.length).toBe(3);
|
|
758
|
+
const relationship = match.patterns[0].chain[1] as Relationship;
|
|
759
|
+
expect(relationship.types).toEqual(["KNOWS", "FOLLOWS"]);
|
|
760
|
+
expect(relationship.type).toBe("KNOWS");
|
|
761
|
+
});
|
|
762
|
+
|
|
763
|
+
test("Match with ORed relationship types with optional colons", () => {
|
|
764
|
+
const parser = new Parser();
|
|
765
|
+
const ast = parser.parse("MATCH (a:Person)-[:KNOWS|:FOLLOWS|:LIKES]->(b:Person) RETURN a, b");
|
|
766
|
+
const match = ast.firstChild() as Match;
|
|
767
|
+
const relationship = match.patterns[0].chain[1] as Relationship;
|
|
768
|
+
expect(relationship.types).toEqual(["KNOWS", "FOLLOWS", "LIKES"]);
|
|
769
|
+
});
|
|
770
|
+
|
|
771
|
+
test("Match with ORed relationship types and variable", () => {
|
|
772
|
+
const parser = new Parser();
|
|
773
|
+
const ast = parser.parse("MATCH (a:Person)-[r:KNOWS|FOLLOWS]->(b:Person) RETURN a, r, b");
|
|
774
|
+
const match = ast.firstChild() as Match;
|
|
775
|
+
const relationship = match.patterns[0].chain[1] as Relationship;
|
|
776
|
+
expect(relationship.identifier).toBe("r");
|
|
777
|
+
expect(relationship.types).toEqual(["KNOWS", "FOLLOWS"]);
|
|
778
|
+
});
|
|
779
|
+
|
|
780
|
+
test("Match with ORed relationship types and hops", () => {
|
|
781
|
+
const parser = new Parser();
|
|
782
|
+
const ast = parser.parse("MATCH (a:Person)-[:KNOWS|FOLLOWS*1..3]->(b:Person) RETURN a, b");
|
|
783
|
+
const match = ast.firstChild() as Match;
|
|
784
|
+
const relationship = match.patterns[0].chain[1] as Relationship;
|
|
785
|
+
expect(relationship.types).toEqual(["KNOWS", "FOLLOWS"]);
|
|
786
|
+
expect(relationship.hops!.min).toBe(1);
|
|
787
|
+
expect(relationship.hops!.max).toBe(3);
|
|
788
|
+
});
|
|
789
|
+
|
|
753
790
|
test("Test not equal operator", () => {
|
|
754
791
|
const parser = new Parser();
|
|
755
792
|
const ast = parser.parse("RETURN 1 <> 2");
|