flowquery 1.0.18 → 1.0.21
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/.gitattributes +3 -0
- package/.github/workflows/python-publish.yml +56 -4
- package/.github/workflows/release.yml +26 -19
- package/.husky/pre-commit +26 -0
- package/README.md +37 -32
- package/dist/flowquery.min.js +1 -1
- package/dist/graph/data.d.ts +5 -4
- package/dist/graph/data.d.ts.map +1 -1
- package/dist/graph/data.js +38 -20
- package/dist/graph/data.js.map +1 -1
- package/dist/graph/node.d.ts +2 -0
- package/dist/graph/node.d.ts.map +1 -1
- package/dist/graph/node.js +23 -0
- package/dist/graph/node.js.map +1 -1
- package/dist/graph/node_data.js +1 -1
- package/dist/graph/node_data.js.map +1 -1
- package/dist/graph/pattern.d.ts.map +1 -1
- package/dist/graph/pattern.js +11 -4
- package/dist/graph/pattern.js.map +1 -1
- package/dist/graph/relationship.d.ts +6 -1
- package/dist/graph/relationship.d.ts.map +1 -1
- package/dist/graph/relationship.js +43 -5
- package/dist/graph/relationship.js.map +1 -1
- package/dist/graph/relationship_data.d.ts +2 -0
- package/dist/graph/relationship_data.d.ts.map +1 -1
- package/dist/graph/relationship_data.js +8 -1
- package/dist/graph/relationship_data.js.map +1 -1
- package/dist/graph/relationship_match_collector.js +2 -2
- package/dist/graph/relationship_match_collector.js.map +1 -1
- package/dist/graph/relationship_reference.d.ts.map +1 -1
- package/dist/graph/relationship_reference.js +2 -1
- package/dist/graph/relationship_reference.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/parsing/parser.d.ts +6 -0
- package/dist/parsing/parser.d.ts.map +1 -1
- package/dist/parsing/parser.js +139 -72
- package/dist/parsing/parser.js.map +1 -1
- package/docs/flowquery.min.js +1 -1
- package/flowquery-py/misc/data/test.json +10 -0
- package/flowquery-py/misc/data/users.json +242 -0
- package/flowquery-py/notebooks/TestFlowQuery.ipynb +440 -0
- package/flowquery-py/pyproject.toml +48 -2
- package/flowquery-py/src/__init__.py +7 -5
- package/flowquery-py/src/compute/runner.py +14 -10
- package/flowquery-py/src/extensibility.py +8 -8
- package/flowquery-py/src/graph/__init__.py +7 -7
- package/flowquery-py/src/graph/data.py +38 -20
- package/flowquery-py/src/graph/database.py +10 -20
- package/flowquery-py/src/graph/node.py +50 -19
- package/flowquery-py/src/graph/node_data.py +1 -1
- package/flowquery-py/src/graph/node_reference.py +10 -11
- package/flowquery-py/src/graph/pattern.py +27 -37
- package/flowquery-py/src/graph/pattern_expression.py +13 -11
- package/flowquery-py/src/graph/patterns.py +2 -2
- package/flowquery-py/src/graph/physical_node.py +4 -3
- package/flowquery-py/src/graph/physical_relationship.py +5 -5
- package/flowquery-py/src/graph/relationship.py +62 -14
- package/flowquery-py/src/graph/relationship_data.py +7 -2
- package/flowquery-py/src/graph/relationship_match_collector.py +15 -10
- package/flowquery-py/src/graph/relationship_reference.py +4 -4
- package/flowquery-py/src/io/command_line.py +13 -14
- package/flowquery-py/src/parsing/__init__.py +2 -2
- package/flowquery-py/src/parsing/alias_option.py +1 -1
- package/flowquery-py/src/parsing/ast_node.py +21 -20
- package/flowquery-py/src/parsing/base_parser.py +7 -7
- package/flowquery-py/src/parsing/components/__init__.py +3 -3
- package/flowquery-py/src/parsing/components/from_.py +3 -1
- package/flowquery-py/src/parsing/components/headers.py +2 -2
- package/flowquery-py/src/parsing/components/null.py +2 -2
- package/flowquery-py/src/parsing/context.py +7 -7
- package/flowquery-py/src/parsing/data_structures/associative_array.py +7 -7
- package/flowquery-py/src/parsing/data_structures/json_array.py +3 -3
- package/flowquery-py/src/parsing/data_structures/key_value_pair.py +4 -4
- package/flowquery-py/src/parsing/data_structures/lookup.py +2 -2
- package/flowquery-py/src/parsing/data_structures/range_lookup.py +2 -2
- package/flowquery-py/src/parsing/expressions/__init__.py +16 -16
- package/flowquery-py/src/parsing/expressions/expression.py +16 -13
- package/flowquery-py/src/parsing/expressions/expression_map.py +9 -9
- package/flowquery-py/src/parsing/expressions/f_string.py +3 -3
- package/flowquery-py/src/parsing/expressions/identifier.py +4 -3
- package/flowquery-py/src/parsing/expressions/number.py +3 -3
- package/flowquery-py/src/parsing/expressions/operator.py +16 -16
- package/flowquery-py/src/parsing/expressions/reference.py +3 -3
- package/flowquery-py/src/parsing/expressions/string.py +2 -2
- package/flowquery-py/src/parsing/functions/__init__.py +17 -17
- package/flowquery-py/src/parsing/functions/aggregate_function.py +8 -8
- package/flowquery-py/src/parsing/functions/async_function.py +12 -9
- package/flowquery-py/src/parsing/functions/avg.py +4 -4
- package/flowquery-py/src/parsing/functions/collect.py +6 -6
- package/flowquery-py/src/parsing/functions/function.py +6 -6
- package/flowquery-py/src/parsing/functions/function_factory.py +31 -34
- package/flowquery-py/src/parsing/functions/function_metadata.py +10 -11
- package/flowquery-py/src/parsing/functions/functions.py +14 -6
- package/flowquery-py/src/parsing/functions/join.py +3 -3
- package/flowquery-py/src/parsing/functions/keys.py +3 -3
- package/flowquery-py/src/parsing/functions/predicate_function.py +8 -7
- package/flowquery-py/src/parsing/functions/predicate_sum.py +12 -7
- package/flowquery-py/src/parsing/functions/rand.py +2 -2
- package/flowquery-py/src/parsing/functions/range_.py +9 -4
- package/flowquery-py/src/parsing/functions/replace.py +2 -2
- package/flowquery-py/src/parsing/functions/round_.py +2 -2
- package/flowquery-py/src/parsing/functions/size.py +2 -2
- package/flowquery-py/src/parsing/functions/split.py +9 -4
- package/flowquery-py/src/parsing/functions/stringify.py +3 -3
- package/flowquery-py/src/parsing/functions/sum.py +4 -4
- package/flowquery-py/src/parsing/functions/to_json.py +2 -2
- package/flowquery-py/src/parsing/functions/type_.py +3 -3
- package/flowquery-py/src/parsing/functions/value_holder.py +1 -1
- package/flowquery-py/src/parsing/logic/__init__.py +2 -2
- package/flowquery-py/src/parsing/logic/case.py +0 -1
- package/flowquery-py/src/parsing/logic/when.py +3 -1
- package/flowquery-py/src/parsing/operations/__init__.py +10 -10
- package/flowquery-py/src/parsing/operations/aggregated_return.py +3 -5
- package/flowquery-py/src/parsing/operations/aggregated_with.py +4 -4
- package/flowquery-py/src/parsing/operations/call.py +6 -7
- package/flowquery-py/src/parsing/operations/create_node.py +5 -4
- package/flowquery-py/src/parsing/operations/create_relationship.py +5 -4
- package/flowquery-py/src/parsing/operations/group_by.py +18 -16
- package/flowquery-py/src/parsing/operations/load.py +21 -19
- package/flowquery-py/src/parsing/operations/match.py +8 -7
- package/flowquery-py/src/parsing/operations/operation.py +3 -3
- package/flowquery-py/src/parsing/operations/projection.py +6 -6
- package/flowquery-py/src/parsing/operations/return_op.py +9 -5
- package/flowquery-py/src/parsing/operations/unwind.py +3 -2
- package/flowquery-py/src/parsing/operations/where.py +9 -7
- package/flowquery-py/src/parsing/operations/with_op.py +2 -2
- package/flowquery-py/src/parsing/parser.py +178 -114
- package/flowquery-py/src/parsing/token_to_node.py +2 -2
- package/flowquery-py/src/tokenization/__init__.py +4 -4
- package/flowquery-py/src/tokenization/keyword.py +1 -1
- package/flowquery-py/src/tokenization/operator.py +1 -1
- package/flowquery-py/src/tokenization/string_walker.py +4 -4
- package/flowquery-py/src/tokenization/symbol.py +1 -1
- package/flowquery-py/src/tokenization/token.py +11 -11
- package/flowquery-py/src/tokenization/token_mapper.py +10 -9
- package/flowquery-py/src/tokenization/token_type.py +1 -1
- package/flowquery-py/src/tokenization/tokenizer.py +19 -19
- package/flowquery-py/src/tokenization/trie.py +18 -17
- package/flowquery-py/src/utils/__init__.py +1 -1
- package/flowquery-py/src/utils/object_utils.py +3 -3
- package/flowquery-py/src/utils/string_utils.py +12 -12
- package/flowquery-py/tests/compute/test_runner.py +214 -7
- package/flowquery-py/tests/parsing/test_parser.py +41 -0
- package/flowquery-vscode/flowQueryEngine/flowquery.min.js +1 -1
- package/package.json +1 -1
- package/src/graph/data.ts +38 -20
- package/src/graph/node.ts +23 -0
- package/src/graph/node_data.ts +1 -1
- package/src/graph/pattern.ts +13 -4
- package/src/graph/relationship.ts +45 -5
- package/src/graph/relationship_data.ts +8 -1
- package/src/graph/relationship_match_collector.ts +1 -1
- package/src/graph/relationship_reference.ts +2 -1
- package/src/index.ts +5 -5
- package/src/parsing/parser.ts +139 -71
- package/tests/compute/runner.test.ts +249 -79
- package/tests/parsing/parser.test.ts +32 -0
|
@@ -871,10 +871,9 @@ class TestRunner:
|
|
|
871
871
|
)
|
|
872
872
|
await match.run()
|
|
873
873
|
results = match.results
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
assert results
|
|
877
|
-
assert results[2] == {"name1": "Person 2", "name2": "Person 3"}
|
|
874
|
+
# With * meaning 0+ hops, each person also matches itself (zero-hop)
|
|
875
|
+
# Person 1→1, 1→2, 1→3, Person 2→2, 2→3, Person 3→3 + bidirectional = 7
|
|
876
|
+
assert len(results) == 7
|
|
878
877
|
|
|
879
878
|
@pytest.mark.asyncio
|
|
880
879
|
async def test_match_with_double_graph_pattern(self):
|
|
@@ -1175,7 +1174,8 @@ class TestRunner:
|
|
|
1175
1174
|
)
|
|
1176
1175
|
await match.run()
|
|
1177
1176
|
results = match.results
|
|
1178
|
-
|
|
1177
|
+
# With *0..3: Person 1 has 4 matches (0,1,2,3 hops), Person 2 has 3, Person 3 has 2, Person 4 has 1 = 10 total
|
|
1178
|
+
assert len(results) == 10
|
|
1179
1179
|
|
|
1180
1180
|
@pytest.mark.asyncio
|
|
1181
1181
|
async def test_return_match_pattern_with_variable_length_relationships(self):
|
|
@@ -1213,7 +1213,8 @@ class TestRunner:
|
|
|
1213
1213
|
)
|
|
1214
1214
|
await match.run()
|
|
1215
1215
|
results = match.results
|
|
1216
|
-
|
|
1216
|
+
# With *0..3: Person 1 has 4 matches (0,1,2,3 hops), Person 2 has 3, Person 3 has 2, Person 4 has 1 = 10 total
|
|
1217
|
+
assert len(results) == 10
|
|
1217
1218
|
|
|
1218
1219
|
@pytest.mark.asyncio
|
|
1219
1220
|
async def test_statement_with_graph_pattern_in_where_clause(self):
|
|
@@ -1332,4 +1333,210 @@ class TestRunner:
|
|
|
1332
1333
|
)
|
|
1333
1334
|
await match.run()
|
|
1334
1335
|
results = match.results
|
|
1335
|
-
|
|
1336
|
+
# With * meaning 0+ hops, Employee 1 (CEO) also matches itself (zero-hop)
|
|
1337
|
+
# Employee 1→1 (zero-hop), 2→1, 3→2→1, 4→2→1 = 4 results
|
|
1338
|
+
assert len(results) == 4
|
|
1339
|
+
|
|
1340
|
+
@pytest.mark.asyncio
|
|
1341
|
+
async def test_match_with_leftward_relationship_direction(self):
|
|
1342
|
+
"""Test match with leftward relationship direction."""
|
|
1343
|
+
await Runner(
|
|
1344
|
+
"""
|
|
1345
|
+
CREATE VIRTUAL (:DirPerson) AS {
|
|
1346
|
+
unwind [
|
|
1347
|
+
{id: 1, name: 'Person 1'},
|
|
1348
|
+
{id: 2, name: 'Person 2'},
|
|
1349
|
+
{id: 3, name: 'Person 3'}
|
|
1350
|
+
] as record
|
|
1351
|
+
RETURN record.id as id, record.name as name
|
|
1352
|
+
}
|
|
1353
|
+
"""
|
|
1354
|
+
).run()
|
|
1355
|
+
await Runner(
|
|
1356
|
+
"""
|
|
1357
|
+
CREATE VIRTUAL (:DirPerson)-[:REPORTS_TO]-(:DirPerson) AS {
|
|
1358
|
+
unwind [
|
|
1359
|
+
{left_id: 2, right_id: 1},
|
|
1360
|
+
{left_id: 3, right_id: 1}
|
|
1361
|
+
] as record
|
|
1362
|
+
RETURN record.left_id as left_id, record.right_id as right_id
|
|
1363
|
+
}
|
|
1364
|
+
"""
|
|
1365
|
+
).run()
|
|
1366
|
+
# Rightward: left_id -> right_id (2->1, 3->1)
|
|
1367
|
+
right_match = Runner(
|
|
1368
|
+
"""
|
|
1369
|
+
MATCH (a:DirPerson)-[:REPORTS_TO]->(b:DirPerson)
|
|
1370
|
+
RETURN a.name AS employee, b.name AS manager
|
|
1371
|
+
"""
|
|
1372
|
+
)
|
|
1373
|
+
await right_match.run()
|
|
1374
|
+
right_results = right_match.results
|
|
1375
|
+
assert len(right_results) == 2
|
|
1376
|
+
assert right_results[0] == {"employee": "Person 2", "manager": "Person 1"}
|
|
1377
|
+
assert right_results[1] == {"employee": "Person 3", "manager": "Person 1"}
|
|
1378
|
+
|
|
1379
|
+
# Leftward: right_id -> left_id (1->2, 1->3) - reverse traversal
|
|
1380
|
+
left_match = Runner(
|
|
1381
|
+
"""
|
|
1382
|
+
MATCH (m:DirPerson)<-[:REPORTS_TO]-(e:DirPerson)
|
|
1383
|
+
RETURN m.name AS manager, e.name AS employee
|
|
1384
|
+
"""
|
|
1385
|
+
)
|
|
1386
|
+
await left_match.run()
|
|
1387
|
+
left_results = left_match.results
|
|
1388
|
+
assert len(left_results) == 2
|
|
1389
|
+
assert left_results[0] == {"manager": "Person 1", "employee": "Person 2"}
|
|
1390
|
+
assert left_results[1] == {"manager": "Person 1", "employee": "Person 3"}
|
|
1391
|
+
|
|
1392
|
+
@pytest.mark.asyncio
|
|
1393
|
+
async def test_match_with_leftward_direction_swapped_data(self):
|
|
1394
|
+
"""Test match with leftward direction produces same results as rightward with swapped data."""
|
|
1395
|
+
await Runner(
|
|
1396
|
+
"""
|
|
1397
|
+
CREATE VIRTUAL (:DirCity) AS {
|
|
1398
|
+
unwind [
|
|
1399
|
+
{id: 1, name: 'New York'},
|
|
1400
|
+
{id: 2, name: 'Boston'},
|
|
1401
|
+
{id: 3, name: 'Chicago'}
|
|
1402
|
+
] as record
|
|
1403
|
+
RETURN record.id as id, record.name as name
|
|
1404
|
+
}
|
|
1405
|
+
"""
|
|
1406
|
+
).run()
|
|
1407
|
+
await Runner(
|
|
1408
|
+
"""
|
|
1409
|
+
CREATE VIRTUAL (:DirCity)-[:ROUTE]-(:DirCity) AS {
|
|
1410
|
+
unwind [
|
|
1411
|
+
{left_id: 1, right_id: 2},
|
|
1412
|
+
{left_id: 1, right_id: 3}
|
|
1413
|
+
] as record
|
|
1414
|
+
RETURN record.left_id as left_id, record.right_id as right_id
|
|
1415
|
+
}
|
|
1416
|
+
"""
|
|
1417
|
+
).run()
|
|
1418
|
+
# Leftward from destination: find where right_id matches, follow left_id
|
|
1419
|
+
match = Runner(
|
|
1420
|
+
"""
|
|
1421
|
+
MATCH (dest:DirCity)<-[:ROUTE]-(origin:DirCity)
|
|
1422
|
+
RETURN dest.name AS destination, origin.name AS origin
|
|
1423
|
+
"""
|
|
1424
|
+
)
|
|
1425
|
+
await match.run()
|
|
1426
|
+
results = match.results
|
|
1427
|
+
assert len(results) == 2
|
|
1428
|
+
assert results[0] == {"destination": "Boston", "origin": "New York"}
|
|
1429
|
+
assert results[1] == {"destination": "Chicago", "origin": "New York"}
|
|
1430
|
+
|
|
1431
|
+
@pytest.mark.asyncio
|
|
1432
|
+
async def test_match_with_leftward_variable_length(self):
|
|
1433
|
+
"""Test match with leftward variable-length relationships."""
|
|
1434
|
+
await Runner(
|
|
1435
|
+
"""
|
|
1436
|
+
CREATE VIRTUAL (:DirVarPerson) AS {
|
|
1437
|
+
unwind [
|
|
1438
|
+
{id: 1, name: 'Person 1'},
|
|
1439
|
+
{id: 2, name: 'Person 2'},
|
|
1440
|
+
{id: 3, name: 'Person 3'}
|
|
1441
|
+
] as record
|
|
1442
|
+
RETURN record.id as id, record.name as name
|
|
1443
|
+
}
|
|
1444
|
+
"""
|
|
1445
|
+
).run()
|
|
1446
|
+
await Runner(
|
|
1447
|
+
"""
|
|
1448
|
+
CREATE VIRTUAL (:DirVarPerson)-[:MANAGES]-(:DirVarPerson) AS {
|
|
1449
|
+
unwind [
|
|
1450
|
+
{left_id: 1, right_id: 2},
|
|
1451
|
+
{left_id: 2, right_id: 3}
|
|
1452
|
+
] as record
|
|
1453
|
+
RETURN record.left_id as left_id, record.right_id as right_id
|
|
1454
|
+
}
|
|
1455
|
+
"""
|
|
1456
|
+
).run()
|
|
1457
|
+
# Leftward variable-length: traverse from right_id to left_id
|
|
1458
|
+
match = Runner(
|
|
1459
|
+
"""
|
|
1460
|
+
MATCH (a:DirVarPerson)<-[:MANAGES*]-(b:DirVarPerson)
|
|
1461
|
+
RETURN a.name AS name1, b.name AS name2
|
|
1462
|
+
"""
|
|
1463
|
+
)
|
|
1464
|
+
await match.run()
|
|
1465
|
+
results = match.results
|
|
1466
|
+
# Leftward indexes on right_id. find(id) looks up right_id=id, follows left_id.
|
|
1467
|
+
# Person 1: zero-hop only (no right_id=1)
|
|
1468
|
+
# Person 2: zero-hop, then left_id=1 (1 hop)
|
|
1469
|
+
# Person 3: zero-hop, then left_id=2 (1 hop), then left_id=1 (2 hops)
|
|
1470
|
+
assert len(results) == 6
|
|
1471
|
+
assert results[0] == {"name1": "Person 1", "name2": "Person 1"}
|
|
1472
|
+
assert results[1] == {"name1": "Person 2", "name2": "Person 2"}
|
|
1473
|
+
assert results[2] == {"name1": "Person 2", "name2": "Person 1"}
|
|
1474
|
+
assert results[3] == {"name1": "Person 3", "name2": "Person 3"}
|
|
1475
|
+
assert results[4] == {"name1": "Person 3", "name2": "Person 2"}
|
|
1476
|
+
assert results[5] == {"name1": "Person 3", "name2": "Person 1"}
|
|
1477
|
+
|
|
1478
|
+
@pytest.mark.asyncio
|
|
1479
|
+
async def test_match_with_leftward_double_graph_pattern(self):
|
|
1480
|
+
"""Test match with leftward double graph pattern."""
|
|
1481
|
+
await Runner(
|
|
1482
|
+
"""
|
|
1483
|
+
CREATE VIRTUAL (:DirDoublePerson) AS {
|
|
1484
|
+
unwind [
|
|
1485
|
+
{id: 1, name: 'Person 1'},
|
|
1486
|
+
{id: 2, name: 'Person 2'},
|
|
1487
|
+
{id: 3, name: 'Person 3'},
|
|
1488
|
+
{id: 4, name: 'Person 4'}
|
|
1489
|
+
] as record
|
|
1490
|
+
RETURN record.id as id, record.name as name
|
|
1491
|
+
}
|
|
1492
|
+
"""
|
|
1493
|
+
).run()
|
|
1494
|
+
await Runner(
|
|
1495
|
+
"""
|
|
1496
|
+
CREATE VIRTUAL (:DirDoublePerson)-[:KNOWS]-(:DirDoublePerson) AS {
|
|
1497
|
+
unwind [
|
|
1498
|
+
{left_id: 1, right_id: 2},
|
|
1499
|
+
{left_id: 2, right_id: 3},
|
|
1500
|
+
{left_id: 3, right_id: 4}
|
|
1501
|
+
] as record
|
|
1502
|
+
RETURN record.left_id as left_id, record.right_id as right_id
|
|
1503
|
+
}
|
|
1504
|
+
"""
|
|
1505
|
+
).run()
|
|
1506
|
+
# Leftward chain: (c)<-[:KNOWS]-(b)<-[:KNOWS]-(a)
|
|
1507
|
+
match = Runner(
|
|
1508
|
+
"""
|
|
1509
|
+
MATCH (c:DirDoublePerson)<-[:KNOWS]-(b:DirDoublePerson)<-[:KNOWS]-(a:DirDoublePerson)
|
|
1510
|
+
RETURN a.name AS name1, b.name AS name2, c.name AS name3
|
|
1511
|
+
"""
|
|
1512
|
+
)
|
|
1513
|
+
await match.run()
|
|
1514
|
+
results = match.results
|
|
1515
|
+
assert len(results) == 2
|
|
1516
|
+
assert results[0] == {"name1": "Person 1", "name2": "Person 2", "name3": "Person 3"}
|
|
1517
|
+
assert results[1] == {"name1": "Person 2", "name2": "Person 3", "name3": "Person 4"}
|
|
1518
|
+
|
|
1519
|
+
async def test_match_with_constraints(self):
|
|
1520
|
+
await Runner(
|
|
1521
|
+
"""
|
|
1522
|
+
CREATE VIRTUAL (:ConstraintEmployee) AS {
|
|
1523
|
+
unwind [
|
|
1524
|
+
{id: 1, name: 'Employee 1'},
|
|
1525
|
+
{id: 2, name: 'Employee 2'},
|
|
1526
|
+
{id: 3, name: 'Employee 3'},
|
|
1527
|
+
{id: 4, name: 'Employee 4'}
|
|
1528
|
+
] as record
|
|
1529
|
+
RETURN record.id as id, record.name as name
|
|
1530
|
+
}
|
|
1531
|
+
"""
|
|
1532
|
+
).run()
|
|
1533
|
+
match = Runner(
|
|
1534
|
+
"""
|
|
1535
|
+
match (e:ConstraintEmployee{name:'Employee 1'})
|
|
1536
|
+
return e.name as name
|
|
1537
|
+
"""
|
|
1538
|
+
)
|
|
1539
|
+
await match.run()
|
|
1540
|
+
results = match.results
|
|
1541
|
+
assert len(results) == 1
|
|
1542
|
+
assert results[0]["name"] == "Employee 1"
|
|
@@ -5,6 +5,9 @@ from typing import AsyncIterator
|
|
|
5
5
|
from flowquery.parsing.parser import Parser
|
|
6
6
|
from flowquery.parsing.functions.async_function import AsyncFunction
|
|
7
7
|
from flowquery.parsing.functions.function_metadata import FunctionDef
|
|
8
|
+
from flowquery.parsing.operations.match import Match
|
|
9
|
+
from flowquery.graph.node import Node
|
|
10
|
+
from flowquery.graph.relationship import Relationship
|
|
8
11
|
|
|
9
12
|
|
|
10
13
|
# Test async function for CALL operation parsing test
|
|
@@ -678,3 +681,41 @@ class TestParser:
|
|
|
678
681
|
parser = Parser()
|
|
679
682
|
with pytest.raises(Exception, match="PatternExpression must contain at least one NodeReference"):
|
|
680
683
|
parser.parse("MATCH (a:Person) WHERE (:Person)-[:KNOWS]->(:Person) RETURN a")
|
|
684
|
+
|
|
685
|
+
def test_node_with_properties(self):
|
|
686
|
+
"""Test node with properties."""
|
|
687
|
+
parser = Parser()
|
|
688
|
+
ast = parser.parse("MATCH (a:Person{value: 'hello'}) return a")
|
|
689
|
+
expected = (
|
|
690
|
+
"ASTNode\n"
|
|
691
|
+
"- Match\n"
|
|
692
|
+
"- Return\n"
|
|
693
|
+
"-- Expression (a)\n"
|
|
694
|
+
"--- Reference (a)"
|
|
695
|
+
)
|
|
696
|
+
assert ast.print() == expected
|
|
697
|
+
match_op = ast.first_child()
|
|
698
|
+
assert isinstance(match_op, Match)
|
|
699
|
+
node = match_op.patterns[0].chain[0]
|
|
700
|
+
assert isinstance(node, Node)
|
|
701
|
+
assert node.properties.get("value") is not None
|
|
702
|
+
assert node.properties["value"].value() == "hello"
|
|
703
|
+
|
|
704
|
+
def test_relationship_with_properties(self):
|
|
705
|
+
"""Test relationship with properties."""
|
|
706
|
+
parser = Parser()
|
|
707
|
+
ast = parser.parse("MATCH (:Person)-[r:LIKES{since: 2022}]->(:Food) return a")
|
|
708
|
+
expected = (
|
|
709
|
+
"ASTNode\n"
|
|
710
|
+
"- Match\n"
|
|
711
|
+
"- Return\n"
|
|
712
|
+
"-- Expression (a)\n"
|
|
713
|
+
"--- Reference (a)"
|
|
714
|
+
)
|
|
715
|
+
assert ast.print() == expected
|
|
716
|
+
match_op = ast.first_child()
|
|
717
|
+
assert isinstance(match_op, Match)
|
|
718
|
+
relationship = match_op.patterns[0].chain[1]
|
|
719
|
+
assert isinstance(relationship, Relationship)
|
|
720
|
+
assert relationship.properties.get("since") is not None
|
|
721
|
+
assert relationship.properties["since"].value() == 2022
|