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
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
import pytest
|
|
4
4
|
from typing import AsyncIterator
|
|
5
5
|
from flowquery.compute.runner import Runner
|
|
6
|
+
from flowquery.graph.node import Node
|
|
7
|
+
from flowquery.graph.relationship import Relationship
|
|
8
|
+
from flowquery.graph.database import Database
|
|
6
9
|
from flowquery.parsing.functions.async_function import AsyncFunction
|
|
7
10
|
from flowquery.parsing.functions.function_metadata import FunctionDef
|
|
8
11
|
|
|
@@ -810,6 +813,42 @@ class TestRunner:
|
|
|
810
813
|
assert len(results) == 1
|
|
811
814
|
assert results[0] == {"result": ""}
|
|
812
815
|
|
|
816
|
+
@pytest.mark.asyncio
|
|
817
|
+
async def test_substring_function_with_start_and_length(self):
|
|
818
|
+
"""Test substring function with start and length."""
|
|
819
|
+
runner = Runner('RETURN substring("hello", 1, 3) as result')
|
|
820
|
+
await runner.run()
|
|
821
|
+
results = runner.results
|
|
822
|
+
assert len(results) == 1
|
|
823
|
+
assert results[0] == {"result": "ell"}
|
|
824
|
+
|
|
825
|
+
@pytest.mark.asyncio
|
|
826
|
+
async def test_substring_function_with_start_only(self):
|
|
827
|
+
"""Test substring function with start only."""
|
|
828
|
+
runner = Runner('RETURN substring("hello", 2) as result')
|
|
829
|
+
await runner.run()
|
|
830
|
+
results = runner.results
|
|
831
|
+
assert len(results) == 1
|
|
832
|
+
assert results[0] == {"result": "llo"}
|
|
833
|
+
|
|
834
|
+
@pytest.mark.asyncio
|
|
835
|
+
async def test_substring_function_with_zero_start(self):
|
|
836
|
+
"""Test substring function with zero start."""
|
|
837
|
+
runner = Runner('RETURN substring("hello", 0, 5) as result')
|
|
838
|
+
await runner.run()
|
|
839
|
+
results = runner.results
|
|
840
|
+
assert len(results) == 1
|
|
841
|
+
assert results[0] == {"result": "hello"}
|
|
842
|
+
|
|
843
|
+
@pytest.mark.asyncio
|
|
844
|
+
async def test_substring_function_with_zero_length(self):
|
|
845
|
+
"""Test substring function with zero length."""
|
|
846
|
+
runner = Runner('RETURN substring("hello", 1, 0) as result')
|
|
847
|
+
await runner.run()
|
|
848
|
+
results = runner.results
|
|
849
|
+
assert len(results) == 1
|
|
850
|
+
assert results[0] == {"result": ""}
|
|
851
|
+
|
|
813
852
|
@pytest.mark.asyncio
|
|
814
853
|
async def test_associative_array_with_key_which_is_keyword(self):
|
|
815
854
|
"""Test associative array with key which is keyword."""
|
|
@@ -2798,6 +2837,58 @@ class TestRunner:
|
|
|
2798
2837
|
assert len(results) == 3
|
|
2799
2838
|
assert [r["n"] for r in results] == [10, 15, 20]
|
|
2800
2839
|
|
|
2840
|
+
@pytest.mark.asyncio
|
|
2841
|
+
async def test_where_with_and_before_in(self):
|
|
2842
|
+
"""Test WHERE with AND before IN (IN on right side of AND)."""
|
|
2843
|
+
runner = Runner("""
|
|
2844
|
+
unwind ['expert', 'intermediate', 'beginner'] as proficiency
|
|
2845
|
+
with proficiency where 1=1 and proficiency in ['expert']
|
|
2846
|
+
return proficiency
|
|
2847
|
+
""")
|
|
2848
|
+
await runner.run()
|
|
2849
|
+
results = runner.results
|
|
2850
|
+
assert len(results) == 1
|
|
2851
|
+
assert results[0] == {"proficiency": "expert"}
|
|
2852
|
+
|
|
2853
|
+
@pytest.mark.asyncio
|
|
2854
|
+
async def test_where_with_and_before_not_in(self):
|
|
2855
|
+
"""Test WHERE with AND before NOT IN."""
|
|
2856
|
+
runner = Runner("""
|
|
2857
|
+
unwind ['expert', 'intermediate', 'beginner'] as proficiency
|
|
2858
|
+
with proficiency where 1=1 and proficiency not in ['expert']
|
|
2859
|
+
return proficiency
|
|
2860
|
+
""")
|
|
2861
|
+
await runner.run()
|
|
2862
|
+
results = runner.results
|
|
2863
|
+
assert len(results) == 2
|
|
2864
|
+
assert [r["proficiency"] for r in results] == ["intermediate", "beginner"]
|
|
2865
|
+
|
|
2866
|
+
@pytest.mark.asyncio
|
|
2867
|
+
async def test_where_with_or_before_in(self):
|
|
2868
|
+
"""Test WHERE with OR before IN."""
|
|
2869
|
+
runner = Runner("""
|
|
2870
|
+
unwind range(1, 10) as n
|
|
2871
|
+
with n where 1=0 or n in [3, 7]
|
|
2872
|
+
return n
|
|
2873
|
+
""")
|
|
2874
|
+
await runner.run()
|
|
2875
|
+
results = runner.results
|
|
2876
|
+
assert len(results) == 2
|
|
2877
|
+
assert [r["n"] for r in results] == [3, 7]
|
|
2878
|
+
|
|
2879
|
+
@pytest.mark.asyncio
|
|
2880
|
+
async def test_in_as_return_expression_with_and_in_where(self):
|
|
2881
|
+
"""Test IN as return expression with AND in WHERE."""
|
|
2882
|
+
runner = Runner("""
|
|
2883
|
+
unwind ['expert', 'intermediate', 'beginner'] as proficiency
|
|
2884
|
+
with proficiency where 1=1 and proficiency in ['expert']
|
|
2885
|
+
return proficiency, proficiency in ['expert'] as isExpert
|
|
2886
|
+
""")
|
|
2887
|
+
await runner.run()
|
|
2888
|
+
results = runner.results
|
|
2889
|
+
assert len(results) == 1
|
|
2890
|
+
assert results[0] == {"proficiency": "expert", "isExpert": 1}
|
|
2891
|
+
|
|
2801
2892
|
@pytest.mark.asyncio
|
|
2802
2893
|
async def test_where_with_contains(self):
|
|
2803
2894
|
"""Test WHERE with CONTAINS."""
|
|
@@ -4011,4 +4102,241 @@ class TestRunner:
|
|
|
4011
4102
|
assert d["hours"] == 2
|
|
4012
4103
|
assert d["minutes"] == 30
|
|
4013
4104
|
assert d["totalSeconds"] == 9000
|
|
4014
|
-
assert d["formatted"] == "PT2H30M"
|
|
4105
|
+
assert d["formatted"] == "PT2H30M"
|
|
4106
|
+
|
|
4107
|
+
# ORDER BY tests
|
|
4108
|
+
|
|
4109
|
+
@pytest.mark.asyncio
|
|
4110
|
+
async def test_order_by_ascending(self):
|
|
4111
|
+
"""Test ORDER BY ascending (default)."""
|
|
4112
|
+
runner = Runner("unwind [3, 1, 2] as x return x order by x")
|
|
4113
|
+
await runner.run()
|
|
4114
|
+
results = runner.results
|
|
4115
|
+
assert len(results) == 3
|
|
4116
|
+
assert results[0] == {"x": 1}
|
|
4117
|
+
assert results[1] == {"x": 2}
|
|
4118
|
+
assert results[2] == {"x": 3}
|
|
4119
|
+
|
|
4120
|
+
@pytest.mark.asyncio
|
|
4121
|
+
async def test_order_by_descending(self):
|
|
4122
|
+
"""Test ORDER BY descending."""
|
|
4123
|
+
runner = Runner("unwind [3, 1, 2] as x return x order by x desc")
|
|
4124
|
+
await runner.run()
|
|
4125
|
+
results = runner.results
|
|
4126
|
+
assert len(results) == 3
|
|
4127
|
+
assert results[0] == {"x": 3}
|
|
4128
|
+
assert results[1] == {"x": 2}
|
|
4129
|
+
assert results[2] == {"x": 1}
|
|
4130
|
+
|
|
4131
|
+
@pytest.mark.asyncio
|
|
4132
|
+
async def test_order_by_ascending_explicit(self):
|
|
4133
|
+
"""Test ORDER BY with explicit ASC."""
|
|
4134
|
+
runner = Runner("unwind [3, 1, 2] as x return x order by x asc")
|
|
4135
|
+
await runner.run()
|
|
4136
|
+
results = runner.results
|
|
4137
|
+
assert len(results) == 3
|
|
4138
|
+
assert results[0] == {"x": 1}
|
|
4139
|
+
assert results[1] == {"x": 2}
|
|
4140
|
+
assert results[2] == {"x": 3}
|
|
4141
|
+
|
|
4142
|
+
@pytest.mark.asyncio
|
|
4143
|
+
async def test_order_by_with_multiple_fields(self):
|
|
4144
|
+
"""Test ORDER BY with multiple sort fields."""
|
|
4145
|
+
runner = Runner(
|
|
4146
|
+
"unwind [{name: 'Alice', age: 30}, {name: 'Bob', age: 25}, {name: 'Alice', age: 25}] as person "
|
|
4147
|
+
"return person.name as name, person.age as age "
|
|
4148
|
+
"order by name asc, age asc"
|
|
4149
|
+
)
|
|
4150
|
+
await runner.run()
|
|
4151
|
+
results = runner.results
|
|
4152
|
+
assert len(results) == 3
|
|
4153
|
+
assert results[0] == {"name": "Alice", "age": 25}
|
|
4154
|
+
assert results[1] == {"name": "Alice", "age": 30}
|
|
4155
|
+
assert results[2] == {"name": "Bob", "age": 25}
|
|
4156
|
+
|
|
4157
|
+
@pytest.mark.asyncio
|
|
4158
|
+
async def test_order_by_with_strings(self):
|
|
4159
|
+
"""Test ORDER BY with string values."""
|
|
4160
|
+
runner = Runner(
|
|
4161
|
+
"unwind ['banana', 'apple', 'cherry'] as fruit return fruit order by fruit"
|
|
4162
|
+
)
|
|
4163
|
+
await runner.run()
|
|
4164
|
+
results = runner.results
|
|
4165
|
+
assert len(results) == 3
|
|
4166
|
+
assert results[0] == {"fruit": "apple"}
|
|
4167
|
+
assert results[1] == {"fruit": "banana"}
|
|
4168
|
+
assert results[2] == {"fruit": "cherry"}
|
|
4169
|
+
|
|
4170
|
+
@pytest.mark.asyncio
|
|
4171
|
+
async def test_order_by_with_aggregated_return(self):
|
|
4172
|
+
"""Test ORDER BY with aggregated RETURN."""
|
|
4173
|
+
runner = Runner(
|
|
4174
|
+
"unwind [1, 1, 2, 2, 3, 3] as x "
|
|
4175
|
+
"return x, count(x) as cnt "
|
|
4176
|
+
"order by x desc"
|
|
4177
|
+
)
|
|
4178
|
+
await runner.run()
|
|
4179
|
+
results = runner.results
|
|
4180
|
+
assert len(results) == 3
|
|
4181
|
+
assert results[0] == {"x": 3, "cnt": 2}
|
|
4182
|
+
assert results[1] == {"x": 2, "cnt": 2}
|
|
4183
|
+
assert results[2] == {"x": 1, "cnt": 2}
|
|
4184
|
+
|
|
4185
|
+
@pytest.mark.asyncio
|
|
4186
|
+
async def test_order_by_with_limit(self):
|
|
4187
|
+
"""Test ORDER BY combined with LIMIT."""
|
|
4188
|
+
runner = Runner(
|
|
4189
|
+
"unwind [3, 1, 4, 1, 5, 9, 2, 6] as x return x order by x limit 3"
|
|
4190
|
+
)
|
|
4191
|
+
await runner.run()
|
|
4192
|
+
results = runner.results
|
|
4193
|
+
assert len(results) == 3
|
|
4194
|
+
assert results[0] == {"x": 1}
|
|
4195
|
+
assert results[1] == {"x": 1}
|
|
4196
|
+
assert results[2] == {"x": 2}
|
|
4197
|
+
|
|
4198
|
+
@pytest.mark.asyncio
|
|
4199
|
+
async def test_order_by_with_where(self):
|
|
4200
|
+
"""Test ORDER BY combined with WHERE."""
|
|
4201
|
+
runner = Runner(
|
|
4202
|
+
"unwind [3, 1, 4, 1, 5, 9, 2, 6] as x return x where x > 2 order by x desc"
|
|
4203
|
+
)
|
|
4204
|
+
await runner.run()
|
|
4205
|
+
results = runner.results
|
|
4206
|
+
assert len(results) == 5
|
|
4207
|
+
assert results[0] == {"x": 9}
|
|
4208
|
+
assert results[1] == {"x": 6}
|
|
4209
|
+
assert results[2] == {"x": 5}
|
|
4210
|
+
assert results[3] == {"x": 4}
|
|
4211
|
+
assert results[4] == {"x": 3}
|
|
4212
|
+
|
|
4213
|
+
@pytest.mark.asyncio
|
|
4214
|
+
async def test_delete_virtual_node_operation(self):
|
|
4215
|
+
"""Test delete virtual node operation."""
|
|
4216
|
+
db = Database.get_instance()
|
|
4217
|
+
# Create a virtual node first
|
|
4218
|
+
create = Runner(
|
|
4219
|
+
"""
|
|
4220
|
+
CREATE VIRTUAL (:PyDeleteTestPerson) AS {
|
|
4221
|
+
unwind [
|
|
4222
|
+
{id: 1, name: 'Person 1'},
|
|
4223
|
+
{id: 2, name: 'Person 2'}
|
|
4224
|
+
] as record
|
|
4225
|
+
RETURN record.id as id, record.name as name
|
|
4226
|
+
}
|
|
4227
|
+
"""
|
|
4228
|
+
)
|
|
4229
|
+
await create.run()
|
|
4230
|
+
assert db.get_node(Node(None, "PyDeleteTestPerson")) is not None
|
|
4231
|
+
|
|
4232
|
+
# Delete the virtual node
|
|
4233
|
+
del_runner = Runner("DELETE VIRTUAL (:PyDeleteTestPerson)")
|
|
4234
|
+
await del_runner.run()
|
|
4235
|
+
assert len(del_runner.results) == 0
|
|
4236
|
+
assert db.get_node(Node(None, "PyDeleteTestPerson")) is None
|
|
4237
|
+
|
|
4238
|
+
@pytest.mark.asyncio
|
|
4239
|
+
async def test_delete_virtual_node_then_match_throws(self):
|
|
4240
|
+
"""Test that matching a deleted virtual node throws."""
|
|
4241
|
+
# Create a virtual node
|
|
4242
|
+
create = Runner(
|
|
4243
|
+
"""
|
|
4244
|
+
CREATE VIRTUAL (:PyDeleteMatchPerson) AS {
|
|
4245
|
+
unwind [{id: 1, name: 'Alice'}] as record
|
|
4246
|
+
RETURN record.id as id, record.name as name
|
|
4247
|
+
}
|
|
4248
|
+
"""
|
|
4249
|
+
)
|
|
4250
|
+
await create.run()
|
|
4251
|
+
|
|
4252
|
+
# Verify it can be matched
|
|
4253
|
+
match1 = Runner("MATCH (n:PyDeleteMatchPerson) RETURN n")
|
|
4254
|
+
await match1.run()
|
|
4255
|
+
assert len(match1.results) == 1
|
|
4256
|
+
|
|
4257
|
+
# Delete the virtual node
|
|
4258
|
+
del_runner = Runner("DELETE VIRTUAL (:PyDeleteMatchPerson)")
|
|
4259
|
+
await del_runner.run()
|
|
4260
|
+
|
|
4261
|
+
# Matching should now throw since the node is gone
|
|
4262
|
+
match2 = Runner("MATCH (n:PyDeleteMatchPerson) RETURN n")
|
|
4263
|
+
with pytest.raises(ValueError):
|
|
4264
|
+
await match2.run()
|
|
4265
|
+
|
|
4266
|
+
@pytest.mark.asyncio
|
|
4267
|
+
async def test_delete_virtual_relationship_operation(self):
|
|
4268
|
+
"""Test delete virtual relationship operation."""
|
|
4269
|
+
db = Database.get_instance()
|
|
4270
|
+
# Create virtual nodes and relationship
|
|
4271
|
+
await Runner(
|
|
4272
|
+
"""
|
|
4273
|
+
CREATE VIRTUAL (:PyDelRelUser) AS {
|
|
4274
|
+
unwind [
|
|
4275
|
+
{id: 1, name: 'Alice'},
|
|
4276
|
+
{id: 2, name: 'Bob'}
|
|
4277
|
+
] as record
|
|
4278
|
+
RETURN record.id as id, record.name as name
|
|
4279
|
+
}
|
|
4280
|
+
"""
|
|
4281
|
+
).run()
|
|
4282
|
+
|
|
4283
|
+
await Runner(
|
|
4284
|
+
"""
|
|
4285
|
+
CREATE VIRTUAL (:PyDelRelUser)-[:PY_DEL_KNOWS]-(:PyDelRelUser) AS {
|
|
4286
|
+
unwind [
|
|
4287
|
+
{left_id: 1, right_id: 2}
|
|
4288
|
+
] as record
|
|
4289
|
+
RETURN record.left_id as left_id, record.right_id as right_id
|
|
4290
|
+
}
|
|
4291
|
+
"""
|
|
4292
|
+
).run()
|
|
4293
|
+
|
|
4294
|
+
# Verify relationship exists
|
|
4295
|
+
rel = Relationship()
|
|
4296
|
+
rel.type = "PY_DEL_KNOWS"
|
|
4297
|
+
assert db.get_relationship(rel) is not None
|
|
4298
|
+
|
|
4299
|
+
# Delete the virtual relationship
|
|
4300
|
+
del_runner = Runner("DELETE VIRTUAL (:PyDelRelUser)-[:PY_DEL_KNOWS]-(:PyDelRelUser)")
|
|
4301
|
+
await del_runner.run()
|
|
4302
|
+
assert len(del_runner.results) == 0
|
|
4303
|
+
assert db.get_relationship(rel) is None
|
|
4304
|
+
|
|
4305
|
+
@pytest.mark.asyncio
|
|
4306
|
+
async def test_delete_virtual_node_leaves_other_nodes_intact(self):
|
|
4307
|
+
"""Test that deleting one virtual node leaves others intact."""
|
|
4308
|
+
db = Database.get_instance()
|
|
4309
|
+
# Create two virtual node types
|
|
4310
|
+
await Runner(
|
|
4311
|
+
"""
|
|
4312
|
+
CREATE VIRTUAL (:PyKeepNode) AS {
|
|
4313
|
+
unwind [{id: 1, name: 'Keep'}] as record
|
|
4314
|
+
RETURN record.id as id, record.name as name
|
|
4315
|
+
}
|
|
4316
|
+
"""
|
|
4317
|
+
).run()
|
|
4318
|
+
|
|
4319
|
+
await Runner(
|
|
4320
|
+
"""
|
|
4321
|
+
CREATE VIRTUAL (:PyRemoveNode) AS {
|
|
4322
|
+
unwind [{id: 2, name: 'Remove'}] as record
|
|
4323
|
+
RETURN record.id as id, record.name as name
|
|
4324
|
+
}
|
|
4325
|
+
"""
|
|
4326
|
+
).run()
|
|
4327
|
+
|
|
4328
|
+
assert db.get_node(Node(None, "PyKeepNode")) is not None
|
|
4329
|
+
assert db.get_node(Node(None, "PyRemoveNode")) is not None
|
|
4330
|
+
|
|
4331
|
+
# Delete only one
|
|
4332
|
+
await Runner("DELETE VIRTUAL (:PyRemoveNode)").run()
|
|
4333
|
+
|
|
4334
|
+
# The other should still exist
|
|
4335
|
+
assert db.get_node(Node(None, "PyKeepNode")) is not None
|
|
4336
|
+
assert db.get_node(Node(None, "PyRemoveNode")) is None
|
|
4337
|
+
|
|
4338
|
+
# The remaining node can still be matched
|
|
4339
|
+
match = Runner("MATCH (n:PyKeepNode) RETURN n")
|
|
4340
|
+
await match.run()
|
|
4341
|
+
assert len(match.results) == 1
|
|
4342
|
+
assert match.results[0]["n"]["name"] == "Keep"
|