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
|
@@ -636,6 +636,87 @@ class TestRunner:
|
|
|
636
636
|
assert len(results) == 1
|
|
637
637
|
assert results[0] == {"stringify": '{\n "a": 1,\n "b": 2\n}'}
|
|
638
638
|
|
|
639
|
+
@pytest.mark.asyncio
|
|
640
|
+
async def test_to_string_function_with_number(self):
|
|
641
|
+
"""Test toString function with a number."""
|
|
642
|
+
runner = Runner("RETURN toString(42) as result")
|
|
643
|
+
await runner.run()
|
|
644
|
+
results = runner.results
|
|
645
|
+
assert len(results) == 1
|
|
646
|
+
assert results[0] == {"result": "42"}
|
|
647
|
+
|
|
648
|
+
@pytest.mark.asyncio
|
|
649
|
+
async def test_to_string_function_with_boolean(self):
|
|
650
|
+
"""Test toString function with a boolean."""
|
|
651
|
+
runner = Runner("RETURN toString(true) as result")
|
|
652
|
+
await runner.run()
|
|
653
|
+
results = runner.results
|
|
654
|
+
assert len(results) == 1
|
|
655
|
+
assert results[0] == {"result": "true"}
|
|
656
|
+
|
|
657
|
+
@pytest.mark.asyncio
|
|
658
|
+
async def test_to_string_function_with_object(self):
|
|
659
|
+
"""Test toString function with an object."""
|
|
660
|
+
runner = Runner("RETURN toString({a: 1}) as result")
|
|
661
|
+
await runner.run()
|
|
662
|
+
results = runner.results
|
|
663
|
+
assert len(results) == 1
|
|
664
|
+
assert results[0] == {"result": '{"a": 1}'}
|
|
665
|
+
|
|
666
|
+
@pytest.mark.asyncio
|
|
667
|
+
async def test_to_lower_function(self):
|
|
668
|
+
"""Test toLower function."""
|
|
669
|
+
runner = Runner('RETURN toLower("Hello World") as result')
|
|
670
|
+
await runner.run()
|
|
671
|
+
results = runner.results
|
|
672
|
+
assert len(results) == 1
|
|
673
|
+
assert results[0] == {"result": "hello world"}
|
|
674
|
+
|
|
675
|
+
@pytest.mark.asyncio
|
|
676
|
+
async def test_to_lower_function_with_all_uppercase(self):
|
|
677
|
+
"""Test toLower function with all uppercase."""
|
|
678
|
+
runner = Runner('RETURN toLower("FOO BAR") as result')
|
|
679
|
+
await runner.run()
|
|
680
|
+
results = runner.results
|
|
681
|
+
assert len(results) == 1
|
|
682
|
+
assert results[0] == {"result": "foo bar"}
|
|
683
|
+
|
|
684
|
+
@pytest.mark.asyncio
|
|
685
|
+
async def test_trim_function(self):
|
|
686
|
+
"""Test trim function."""
|
|
687
|
+
runner = Runner('RETURN trim(" hello ") as result')
|
|
688
|
+
await runner.run()
|
|
689
|
+
results = runner.results
|
|
690
|
+
assert len(results) == 1
|
|
691
|
+
assert results[0] == {"result": "hello"}
|
|
692
|
+
|
|
693
|
+
@pytest.mark.asyncio
|
|
694
|
+
async def test_trim_function_with_tabs_and_newlines(self):
|
|
695
|
+
"""Test trim function with tabs and newlines."""
|
|
696
|
+
runner = Runner('WITH "\tfoo\n" AS s RETURN trim(s) as result')
|
|
697
|
+
await runner.run()
|
|
698
|
+
results = runner.results
|
|
699
|
+
assert len(results) == 1
|
|
700
|
+
assert results[0] == {"result": "foo"}
|
|
701
|
+
|
|
702
|
+
@pytest.mark.asyncio
|
|
703
|
+
async def test_trim_function_with_no_whitespace(self):
|
|
704
|
+
"""Test trim function with no whitespace."""
|
|
705
|
+
runner = Runner('RETURN trim("hello") as result')
|
|
706
|
+
await runner.run()
|
|
707
|
+
results = runner.results
|
|
708
|
+
assert len(results) == 1
|
|
709
|
+
assert results[0] == {"result": "hello"}
|
|
710
|
+
|
|
711
|
+
@pytest.mark.asyncio
|
|
712
|
+
async def test_trim_function_with_empty_string(self):
|
|
713
|
+
"""Test trim function with empty string."""
|
|
714
|
+
runner = Runner('RETURN trim("") as result')
|
|
715
|
+
await runner.run()
|
|
716
|
+
results = runner.results
|
|
717
|
+
assert len(results) == 1
|
|
718
|
+
assert results[0] == {"result": ""}
|
|
719
|
+
|
|
639
720
|
@pytest.mark.asyncio
|
|
640
721
|
async def test_associative_array_with_key_which_is_keyword(self):
|
|
641
722
|
"""Test associative array with key which is keyword."""
|
|
@@ -2107,20 +2188,24 @@ class TestRunner:
|
|
|
2107
2188
|
).run()
|
|
2108
2189
|
|
|
2109
2190
|
runner = Runner(
|
|
2110
|
-
"CALL schema() YIELD kind, label, type, sample RETURN kind, label, type, sample"
|
|
2191
|
+
"CALL schema() YIELD kind, label, type, from_label, to_label, properties, sample RETURN kind, label, type, from_label, to_label, properties, sample"
|
|
2111
2192
|
)
|
|
2112
2193
|
await runner.run()
|
|
2113
2194
|
results = runner.results
|
|
2114
2195
|
|
|
2115
|
-
animal = next((r for r in results if r.get("kind") == "
|
|
2196
|
+
animal = next((r for r in results if r.get("kind") == "Node" and r.get("label") == "Animal"), None)
|
|
2116
2197
|
assert animal is not None
|
|
2198
|
+
assert animal["properties"] == ["species", "legs"]
|
|
2117
2199
|
assert animal["sample"] is not None
|
|
2118
2200
|
assert "id" not in animal["sample"]
|
|
2119
2201
|
assert "species" in animal["sample"]
|
|
2120
2202
|
assert "legs" in animal["sample"]
|
|
2121
2203
|
|
|
2122
|
-
chases = next((r for r in results if r.get("kind") == "
|
|
2204
|
+
chases = next((r for r in results if r.get("kind") == "Relationship" and r.get("type") == "CHASES"), None)
|
|
2123
2205
|
assert chases is not None
|
|
2206
|
+
assert chases["from_label"] == "Animal"
|
|
2207
|
+
assert chases["to_label"] == "Animal"
|
|
2208
|
+
assert chases["properties"] == ["speed"]
|
|
2124
2209
|
assert chases["sample"] is not None
|
|
2125
2210
|
assert "left_id" not in chases["sample"]
|
|
2126
2211
|
assert "right_id" not in chases["sample"]
|
|
@@ -2504,6 +2589,64 @@ class TestRunner:
|
|
|
2504
2589
|
# Add operator tests
|
|
2505
2590
|
# ============================================================
|
|
2506
2591
|
|
|
2592
|
+
@pytest.mark.asyncio
|
|
2593
|
+
async def test_collected_patterns_and_unwind(self):
|
|
2594
|
+
"""Test collecting graph patterns and unwinding them."""
|
|
2595
|
+
await Runner("""
|
|
2596
|
+
CREATE VIRTUAL (:Person) AS {
|
|
2597
|
+
unwind [
|
|
2598
|
+
{id: 1, name: 'Person 1'},
|
|
2599
|
+
{id: 2, name: 'Person 2'},
|
|
2600
|
+
{id: 3, name: 'Person 3'},
|
|
2601
|
+
{id: 4, name: 'Person 4'}
|
|
2602
|
+
] as record
|
|
2603
|
+
RETURN record.id as id, record.name as name
|
|
2604
|
+
}
|
|
2605
|
+
""").run()
|
|
2606
|
+
await Runner("""
|
|
2607
|
+
CREATE VIRTUAL (:Person)-[:KNOWS]-(:Person) AS {
|
|
2608
|
+
unwind [
|
|
2609
|
+
{left_id: 1, right_id: 2},
|
|
2610
|
+
{left_id: 2, right_id: 3},
|
|
2611
|
+
{left_id: 3, right_id: 4}
|
|
2612
|
+
] as record
|
|
2613
|
+
RETURN record.left_id as left_id, record.right_id as right_id
|
|
2614
|
+
}
|
|
2615
|
+
""").run()
|
|
2616
|
+
runner = Runner("""
|
|
2617
|
+
MATCH p=(a:Person)-[:KNOWS*0..3]->(b:Person)
|
|
2618
|
+
WITH collect(p) AS patterns
|
|
2619
|
+
UNWIND patterns AS pattern
|
|
2620
|
+
RETURN pattern
|
|
2621
|
+
""")
|
|
2622
|
+
await runner.run()
|
|
2623
|
+
results = runner.results
|
|
2624
|
+
assert len(results) == 10
|
|
2625
|
+
# Index 0: Person 1 zero-hop - pattern = [node1] (single node)
|
|
2626
|
+
assert len(results[0]["pattern"]) == 1
|
|
2627
|
+
assert results[0]["pattern"][0]["id"] == 1
|
|
2628
|
+
# Index 1: Person 1 -> Person 2 (1-hop)
|
|
2629
|
+
assert len(results[1]["pattern"]) == 3
|
|
2630
|
+
# Index 2: Person 1 -> Person 2 -> Person 3 (2-hop)
|
|
2631
|
+
assert len(results[2]["pattern"]) == 5
|
|
2632
|
+
# Index 3: Person 1 -> Person 2 -> Person 3 -> Person 4 (3-hop)
|
|
2633
|
+
assert len(results[3]["pattern"]) == 7
|
|
2634
|
+
# Index 4: Person 2 zero-hop
|
|
2635
|
+
assert len(results[4]["pattern"]) == 1
|
|
2636
|
+
assert results[4]["pattern"][0]["id"] == 2
|
|
2637
|
+
# Index 5: Person 2 -> Person 3 (1-hop)
|
|
2638
|
+
assert len(results[5]["pattern"]) == 3
|
|
2639
|
+
# Index 6: Person 2 -> Person 3 -> Person 4 (2-hop)
|
|
2640
|
+
assert len(results[6]["pattern"]) == 5
|
|
2641
|
+
# Index 7: Person 3 zero-hop
|
|
2642
|
+
assert len(results[7]["pattern"]) == 1
|
|
2643
|
+
assert results[7]["pattern"][0]["id"] == 3
|
|
2644
|
+
# Index 8: Person 3 -> Person 4 (1-hop)
|
|
2645
|
+
assert len(results[8]["pattern"]) == 3
|
|
2646
|
+
# Index 9: Person 4 zero-hop
|
|
2647
|
+
assert len(results[9]["pattern"]) == 1
|
|
2648
|
+
assert results[9]["pattern"][0]["id"] == 4
|
|
2649
|
+
|
|
2507
2650
|
@pytest.mark.asyncio
|
|
2508
2651
|
async def test_add_two_integers(self):
|
|
2509
2652
|
"""Test add two integers."""
|
|
@@ -2809,4 +2952,151 @@ class TestRunner:
|
|
|
2809
2952
|
await runner.run()
|
|
2810
2953
|
results = runner.results
|
|
2811
2954
|
assert len(results) == 1
|
|
2812
|
-
assert results == [{"x": 1}]
|
|
2955
|
+
assert results == [{"x": 1}]
|
|
2956
|
+
|
|
2957
|
+
@pytest.mark.asyncio
|
|
2958
|
+
async def test_language_name_hits_query_with_virtual_graph(self):
|
|
2959
|
+
"""Test full language-name-hits query with virtual graph.
|
|
2960
|
+
|
|
2961
|
+
Reproduces the original bug: collect(distinct ...) on MATCH results,
|
|
2962
|
+
then sum(lang IN langs | ...) in a WITH clause, was throwing
|
|
2963
|
+
"Invalid array for sum function" because collect() returned null
|
|
2964
|
+
instead of [] when no rows entered aggregation.
|
|
2965
|
+
"""
|
|
2966
|
+
# Create Language nodes
|
|
2967
|
+
await Runner(
|
|
2968
|
+
"""
|
|
2969
|
+
CREATE VIRTUAL (:Language) AS {
|
|
2970
|
+
UNWIND [
|
|
2971
|
+
{id: 1, name: 'Python'},
|
|
2972
|
+
{id: 2, name: 'JavaScript'},
|
|
2973
|
+
{id: 3, name: 'TypeScript'}
|
|
2974
|
+
] AS record
|
|
2975
|
+
RETURN record.id AS id, record.name AS name
|
|
2976
|
+
}
|
|
2977
|
+
"""
|
|
2978
|
+
).run()
|
|
2979
|
+
|
|
2980
|
+
# Create Chat nodes with messages
|
|
2981
|
+
await Runner(
|
|
2982
|
+
"""
|
|
2983
|
+
CREATE VIRTUAL (:Chat) AS {
|
|
2984
|
+
UNWIND [
|
|
2985
|
+
{id: 1, name: 'Dev Discussion', messages: [
|
|
2986
|
+
{From: 'Alice', SentDateTime: '2025-01-01T10:00:00', Content: 'I love Python and JavaScript'},
|
|
2987
|
+
{From: 'Bob', SentDateTime: '2025-01-01T10:05:00', Content: 'What languages do you prefer?'}
|
|
2988
|
+
]},
|
|
2989
|
+
{id: 2, name: 'General', messages: [
|
|
2990
|
+
{From: 'Charlie', SentDateTime: '2025-01-02T09:00:00', Content: 'The weather is nice today'},
|
|
2991
|
+
{From: 'Alice', SentDateTime: '2025-01-02T09:05:00', Content: 'TypeScript is great for language tooling'}
|
|
2992
|
+
]}
|
|
2993
|
+
] AS record
|
|
2994
|
+
RETURN record.id AS id, record.name AS name, record.messages AS messages
|
|
2995
|
+
}
|
|
2996
|
+
"""
|
|
2997
|
+
).run()
|
|
2998
|
+
|
|
2999
|
+
# Create User nodes
|
|
3000
|
+
await Runner(
|
|
3001
|
+
"""
|
|
3002
|
+
CREATE VIRTUAL (:User) AS {
|
|
3003
|
+
UNWIND [
|
|
3004
|
+
{id: 1, displayName: 'Alice'},
|
|
3005
|
+
{id: 2, displayName: 'Bob'},
|
|
3006
|
+
{id: 3, displayName: 'Charlie'}
|
|
3007
|
+
] AS record
|
|
3008
|
+
RETURN record.id AS id, record.displayName AS displayName
|
|
3009
|
+
}
|
|
3010
|
+
"""
|
|
3011
|
+
).run()
|
|
3012
|
+
|
|
3013
|
+
# Create PARTICIPATES_IN relationships
|
|
3014
|
+
await Runner(
|
|
3015
|
+
"""
|
|
3016
|
+
CREATE VIRTUAL (:User)-[:PARTICIPATES_IN]-(:Chat) AS {
|
|
3017
|
+
UNWIND [
|
|
3018
|
+
{left_id: 1, right_id: 1},
|
|
3019
|
+
{left_id: 2, right_id: 1},
|
|
3020
|
+
{left_id: 3, right_id: 2},
|
|
3021
|
+
{left_id: 1, right_id: 2}
|
|
3022
|
+
] AS record
|
|
3023
|
+
RETURN record.left_id AS left_id, record.right_id AS right_id
|
|
3024
|
+
}
|
|
3025
|
+
"""
|
|
3026
|
+
).run()
|
|
3027
|
+
|
|
3028
|
+
# Run the original query (using 'sender' alias since 'from' is a reserved keyword)
|
|
3029
|
+
runner = Runner(
|
|
3030
|
+
"""
|
|
3031
|
+
MATCH (l:Language)
|
|
3032
|
+
WITH collect(distinct l.name) AS langs
|
|
3033
|
+
MATCH (c:Chat)
|
|
3034
|
+
UNWIND c.messages AS msg
|
|
3035
|
+
WITH c, msg, langs,
|
|
3036
|
+
sum(lang IN langs | 1 where toLower(msg.Content) CONTAINS toLower(lang)) AS langNameHits
|
|
3037
|
+
WHERE toLower(msg.Content) CONTAINS "language"
|
|
3038
|
+
OR toLower(msg.Content) CONTAINS "languages"
|
|
3039
|
+
OR langNameHits > 0
|
|
3040
|
+
OPTIONAL MATCH (u:User)-[:PARTICIPATES_IN]->(c)
|
|
3041
|
+
RETURN
|
|
3042
|
+
c.name AS chat,
|
|
3043
|
+
collect(distinct u.displayName) AS participants,
|
|
3044
|
+
msg.From AS sender,
|
|
3045
|
+
msg.SentDateTime AS sentDateTime,
|
|
3046
|
+
msg.Content AS message
|
|
3047
|
+
"""
|
|
3048
|
+
)
|
|
3049
|
+
await runner.run()
|
|
3050
|
+
results = runner.results
|
|
3051
|
+
|
|
3052
|
+
# Messages that mention a language name or the word "language(s)":
|
|
3053
|
+
# 1. "I love Python and JavaScript" - langNameHits=2
|
|
3054
|
+
# 2. "What languages do you prefer?" - contains "languages"
|
|
3055
|
+
# 3. "TypeScript is great for language tooling" - langNameHits=1, also "language"
|
|
3056
|
+
assert len(results) == 3
|
|
3057
|
+
assert results[0]["chat"] == "Dev Discussion"
|
|
3058
|
+
assert results[0]["message"] == "I love Python and JavaScript"
|
|
3059
|
+
assert results[0]["sender"] == "Alice"
|
|
3060
|
+
assert results[1]["chat"] == "Dev Discussion"
|
|
3061
|
+
assert results[1]["message"] == "What languages do you prefer?"
|
|
3062
|
+
assert results[1]["sender"] == "Bob"
|
|
3063
|
+
assert results[2]["chat"] == "General"
|
|
3064
|
+
assert results[2]["message"] == "TypeScript is great for language tooling"
|
|
3065
|
+
assert results[2]["sender"] == "Alice"
|
|
3066
|
+
|
|
3067
|
+
@pytest.mark.asyncio
|
|
3068
|
+
async def test_sum_with_empty_collected_array(self):
|
|
3069
|
+
"""Reproduces the original bug: collect on empty input should yield []
|
|
3070
|
+
and sum over that empty array should return 0, not throw."""
|
|
3071
|
+
runner = Runner(
|
|
3072
|
+
"""
|
|
3073
|
+
UNWIND [] AS lang
|
|
3074
|
+
WITH collect(distinct lang) AS langs
|
|
3075
|
+
UNWIND ['hello', 'world'] AS msg
|
|
3076
|
+
WITH msg, langs, sum(l IN langs | 1 where toLower(msg) CONTAINS toLower(l)) AS hits
|
|
3077
|
+
RETURN msg, hits
|
|
3078
|
+
"""
|
|
3079
|
+
)
|
|
3080
|
+
await runner.run()
|
|
3081
|
+
results = runner.results
|
|
3082
|
+
assert len(results) == 2
|
|
3083
|
+
assert results[0] == {"msg": "hello", "hits": 0}
|
|
3084
|
+
assert results[1] == {"msg": "world", "hits": 0}
|
|
3085
|
+
|
|
3086
|
+
@pytest.mark.asyncio
|
|
3087
|
+
async def test_sum_where_all_elements_filtered_returns_0(self):
|
|
3088
|
+
"""Test sum returns 0 when where clause filters everything."""
|
|
3089
|
+
runner = Runner("RETURN sum(n in [1, 2, 3] | n where n > 100) as sum")
|
|
3090
|
+
await runner.run()
|
|
3091
|
+
results = runner.results
|
|
3092
|
+
assert len(results) == 1
|
|
3093
|
+
assert results[0] == {"sum": 0}
|
|
3094
|
+
|
|
3095
|
+
@pytest.mark.asyncio
|
|
3096
|
+
async def test_sum_over_empty_array_returns_0(self):
|
|
3097
|
+
"""Test sum over empty array returns 0."""
|
|
3098
|
+
runner = Runner("WITH [] AS arr RETURN sum(n in arr | n) as sum")
|
|
3099
|
+
await runner.run()
|
|
3100
|
+
results = runner.results
|
|
3101
|
+
assert len(results) == 1
|
|
3102
|
+
assert results[0] == {"sum": 0}
|