flowquery 1.0.41 → 1.0.42
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/node_reference.d.ts.map +1 -1
- package/dist/graph/node_reference.js +7 -3
- package/dist/graph/node_reference.js.map +1 -1
- package/docs/flowquery.min.js +1 -1
- package/flowquery-py/pyproject.toml +1 -1
- package/flowquery-py/src/graph/node_reference.py +5 -4
- package/flowquery-py/tests/compute/test_runner.py +158 -1
- package/flowquery-vscode/flowQueryEngine/flowquery.min.js +1 -1
- package/package.json +1 -1
- package/src/graph/node_reference.ts +5 -1
- package/tests/compute/runner.test.ts +142 -0
|
@@ -32,10 +32,11 @@ class NodeReference(Node):
|
|
|
32
32
|
async def next(self) -> None:
|
|
33
33
|
"""Process next using the referenced node's value."""
|
|
34
34
|
ref_value = self._reference.value()
|
|
35
|
-
if ref_value is
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
if ref_value is None:
|
|
36
|
+
return
|
|
37
|
+
self.set_value(dict(ref_value))
|
|
38
|
+
if self._outgoing and self._value:
|
|
39
|
+
await self._outgoing.find(self._value['id'])
|
|
39
40
|
await self.run_todo_next()
|
|
40
41
|
|
|
41
42
|
async def find(self, id_: str, hop: int = 0) -> None:
|
|
@@ -4394,4 +4394,161 @@ class TestRunner:
|
|
|
4394
4394
|
"mentor": "Bob Jones",
|
|
4395
4395
|
"mentorJobTitle": "Staff Engineer",
|
|
4396
4396
|
"mentorDepartment": "Engineering",
|
|
4397
|
-
}
|
|
4397
|
+
}
|
|
4398
|
+
|
|
4399
|
+
@pytest.mark.asyncio
|
|
4400
|
+
async def test_chained_optional_match_with_null_intermediate_node(self):
|
|
4401
|
+
"""Test chained OPTIONAL MATCH where intermediate node is null doesn't crash."""
|
|
4402
|
+
# Chain: Alice -> Bob -> Charlie (no outgoing)
|
|
4403
|
+
await Runner(
|
|
4404
|
+
"""
|
|
4405
|
+
CREATE VIRTUAL (:ChainEmp) AS {
|
|
4406
|
+
unwind [
|
|
4407
|
+
{id: 1, name: 'Alice'},
|
|
4408
|
+
{id: 2, name: 'Bob'},
|
|
4409
|
+
{id: 3, name: 'Charlie'}
|
|
4410
|
+
] as record
|
|
4411
|
+
RETURN record.id as id, record.name as name
|
|
4412
|
+
}
|
|
4413
|
+
"""
|
|
4414
|
+
).run()
|
|
4415
|
+
await Runner(
|
|
4416
|
+
"""
|
|
4417
|
+
CREATE VIRTUAL (:ChainEmp)-[:REPORTS_TO]-(:ChainEmp) AS {
|
|
4418
|
+
unwind [
|
|
4419
|
+
{left_id: 1, right_id: 2},
|
|
4420
|
+
{left_id: 2, right_id: 3}
|
|
4421
|
+
] as record
|
|
4422
|
+
RETURN record.left_id as left_id, record.right_id as right_id
|
|
4423
|
+
}
|
|
4424
|
+
"""
|
|
4425
|
+
).run()
|
|
4426
|
+
|
|
4427
|
+
# Alice -> Bob -> Charlie -> null -> null
|
|
4428
|
+
runner = Runner(
|
|
4429
|
+
"""
|
|
4430
|
+
MATCH (u:ChainEmp)
|
|
4431
|
+
WHERE u.name = "Alice"
|
|
4432
|
+
OPTIONAL MATCH (u)-[:REPORTS_TO]->(m1:ChainEmp)
|
|
4433
|
+
OPTIONAL MATCH (m1)-[:REPORTS_TO]->(m2:ChainEmp)
|
|
4434
|
+
OPTIONAL MATCH (m2)-[:REPORTS_TO]->(m3:ChainEmp)
|
|
4435
|
+
OPTIONAL MATCH (m3)-[:REPORTS_TO]->(m4:ChainEmp)
|
|
4436
|
+
RETURN
|
|
4437
|
+
u.name AS user,
|
|
4438
|
+
m1.name AS manager1,
|
|
4439
|
+
m2.name AS manager2,
|
|
4440
|
+
m3.name AS manager3,
|
|
4441
|
+
m4.name AS manager4
|
|
4442
|
+
"""
|
|
4443
|
+
)
|
|
4444
|
+
await runner.run()
|
|
4445
|
+
results = runner.results
|
|
4446
|
+
|
|
4447
|
+
assert len(results) == 1
|
|
4448
|
+
assert results[0]["user"] == "Alice"
|
|
4449
|
+
assert results[0]["manager1"] == "Bob"
|
|
4450
|
+
assert results[0]["manager2"] == "Charlie"
|
|
4451
|
+
assert results[0]["manager3"] is None
|
|
4452
|
+
assert results[0]["manager4"] is None
|
|
4453
|
+
|
|
4454
|
+
@pytest.mark.asyncio
|
|
4455
|
+
async def test_chained_optional_match_all_null_from_first(self):
|
|
4456
|
+
"""Test chained OPTIONAL MATCH where first optional returns null propagates nulls."""
|
|
4457
|
+
await Runner(
|
|
4458
|
+
"""
|
|
4459
|
+
CREATE VIRTUAL (:ChainWorker) AS {
|
|
4460
|
+
unwind [
|
|
4461
|
+
{id: 1, name: 'Solo'}
|
|
4462
|
+
] as record
|
|
4463
|
+
RETURN record.id as id, record.name as name
|
|
4464
|
+
}
|
|
4465
|
+
"""
|
|
4466
|
+
).run()
|
|
4467
|
+
await Runner(
|
|
4468
|
+
"""
|
|
4469
|
+
CREATE VIRTUAL (:ChainWorker)-[:MANAGES]-(:ChainWorker) AS {
|
|
4470
|
+
unwind [] as record
|
|
4471
|
+
RETURN record.left_id as left_id, record.right_id as right_id
|
|
4472
|
+
}
|
|
4473
|
+
"""
|
|
4474
|
+
).run()
|
|
4475
|
+
|
|
4476
|
+
# Solo has no MANAGES relationship
|
|
4477
|
+
runner = Runner(
|
|
4478
|
+
"""
|
|
4479
|
+
MATCH (u:ChainWorker)
|
|
4480
|
+
OPTIONAL MATCH (u)-[:MANAGES]->(m1:ChainWorker)
|
|
4481
|
+
OPTIONAL MATCH (m1)-[:MANAGES]->(m2:ChainWorker)
|
|
4482
|
+
OPTIONAL MATCH (m2)-[:MANAGES]->(m3:ChainWorker)
|
|
4483
|
+
RETURN
|
|
4484
|
+
u.name AS user,
|
|
4485
|
+
m1.name AS mgr1,
|
|
4486
|
+
m2.name AS mgr2,
|
|
4487
|
+
m3.name AS mgr3
|
|
4488
|
+
"""
|
|
4489
|
+
)
|
|
4490
|
+
await runner.run()
|
|
4491
|
+
results = runner.results
|
|
4492
|
+
|
|
4493
|
+
assert len(results) == 1
|
|
4494
|
+
assert results[0]["user"] == "Solo"
|
|
4495
|
+
assert results[0]["mgr1"] is None
|
|
4496
|
+
assert results[0]["mgr2"] is None
|
|
4497
|
+
assert results[0]["mgr3"] is None
|
|
4498
|
+
|
|
4499
|
+
@pytest.mark.asyncio
|
|
4500
|
+
async def test_chained_optional_match_mixed_null_and_non_null(self):
|
|
4501
|
+
"""Test chained OPTIONAL MATCH with multiple start nodes having different chain depths."""
|
|
4502
|
+
await Runner(
|
|
4503
|
+
"""
|
|
4504
|
+
CREATE VIRTUAL (:ChainStaff) AS {
|
|
4505
|
+
unwind [
|
|
4506
|
+
{id: 1, name: 'Dev'},
|
|
4507
|
+
{id: 2, name: 'Lead'},
|
|
4508
|
+
{id: 3, name: 'Director'},
|
|
4509
|
+
{id: 4, name: 'Intern'}
|
|
4510
|
+
] as record
|
|
4511
|
+
RETURN record.id as id, record.name as name
|
|
4512
|
+
}
|
|
4513
|
+
"""
|
|
4514
|
+
).run()
|
|
4515
|
+
await Runner(
|
|
4516
|
+
"""
|
|
4517
|
+
CREATE VIRTUAL (:ChainStaff)-[:REPORTS_TO]-(:ChainStaff) AS {
|
|
4518
|
+
unwind [
|
|
4519
|
+
{left_id: 1, right_id: 2},
|
|
4520
|
+
{left_id: 2, right_id: 3}
|
|
4521
|
+
] as record
|
|
4522
|
+
RETURN record.left_id as left_id, record.right_id as right_id
|
|
4523
|
+
}
|
|
4524
|
+
"""
|
|
4525
|
+
).run()
|
|
4526
|
+
|
|
4527
|
+
# Dev -> Lead -> Director -> null
|
|
4528
|
+
# Intern -> null -> null -> null
|
|
4529
|
+
runner = Runner(
|
|
4530
|
+
"""
|
|
4531
|
+
MATCH (u:ChainStaff)
|
|
4532
|
+
WHERE u.name = "Dev" OR u.name = "Intern"
|
|
4533
|
+
OPTIONAL MATCH (u)-[:REPORTS_TO]->(m1:ChainStaff)
|
|
4534
|
+
OPTIONAL MATCH (m1)-[:REPORTS_TO]->(m2:ChainStaff)
|
|
4535
|
+
OPTIONAL MATCH (m2)-[:REPORTS_TO]->(m3:ChainStaff)
|
|
4536
|
+
RETURN
|
|
4537
|
+
u.name AS user,
|
|
4538
|
+
m1.name AS mgr1,
|
|
4539
|
+
m2.name AS mgr2,
|
|
4540
|
+
m3.name AS mgr3
|
|
4541
|
+
"""
|
|
4542
|
+
)
|
|
4543
|
+
await runner.run()
|
|
4544
|
+
results = runner.results
|
|
4545
|
+
|
|
4546
|
+
assert len(results) == 2
|
|
4547
|
+
dev = next(r for r in results if r["user"] == "Dev")
|
|
4548
|
+
assert dev["mgr1"] == "Lead"
|
|
4549
|
+
assert dev["mgr2"] == "Director"
|
|
4550
|
+
assert dev["mgr3"] is None
|
|
4551
|
+
intern = next(r for r in results if r["user"] == "Intern")
|
|
4552
|
+
assert intern["mgr1"] is None
|
|
4553
|
+
assert intern["mgr2"] is None
|
|
4554
|
+
assert intern["mgr3"] is None
|