Skip to content

Commit 6891d39

Browse files
authored
test(cypher): add IC10-shaped bound-alias NOT-pattern regression (#1243)
* test(cypher): add IC10-shaped bound-alias NOT-pattern regression * docs(changelog): record #1237 IC10-shaped cypher regression coverage
1 parent 58b48dc commit 6891d39

2 files changed

Lines changed: 79 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
100100
- **Collections**: Added `test_collections.py` covering encoding, GFQL Chain/AST normalization, wire-protocol acceptance, validation modes, and helper constructors.
101101
- **GFQL / Cypher binder**: Added PR-4 white-box binder semantic conformance coverage for name resolution success/failure (including unresolved alias errors), WITH scope-reset visibility, OPTIONAL MATCH `null_extended_from` lineage as `frozenset` clause ids, label narrowing from MATCH labels + conjunctive `WHERE alias:Label` checks, and SchemaConfidence rules (min-rule propagation, operand inheritance, and strong literal/`COUNT` behavior). Parser/lowering regression lanes remain green (#1114).
102102
- **Plugins / cuDF**: 14 GPU tests in `TestCpuOnlyPluginsCudfRoundTrip` (`test_call_operations_gpu.py`) verifying real cuDF→pandas→cuDF round-trip for `compute_igraph` (pagerank, spanning_tree Graph-returning path, articulation_points list-return path, edge-attribute merge path), `layout_igraph`, `layout_graphviz`, `render_graphviz`, `execute_call`, `ensure_pandas` nullable dtype preservation, and `restore_engine` conversion. Requires `TEST_CUDF=1` and RAPIDS.
103+
- **GFQL / Cypher**: Added bound-alias `WHERE NOT (pattern)` regression coverage for issue `#1237`, including an IC10-shaped direct-Cypher case (`MATCH (root {id: 'a'})-[:R]->(mid)-[:R]->(cand) WHERE NOT (root)-[:R]->(cand)`) plus compile-shape and mixed row+NOT predicate tests in `graphistry/tests/compute/gfql/cypher/test_lowering.py`.
103104

104105
## [0.54.1 - 2026-04-08]
105106

graphistry/tests/compute/gfql/cypher/test_lowering.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1694,6 +1694,21 @@ def test_lower_match_query_emits_row_anti_semi_filter_for_negated_where_pattern(
16941694
assert [op.get("type") for op in binding_ops] == ["Node", "Edge", "Node"]
16951695

16961696

1697+
def test_lower_match_query_emits_row_anti_semi_filter_for_bound_alias_negated_where_pattern() -> None:
1698+
lowered = lower_match_query(
1699+
_parse_query("MATCH (a)-[:R]->(b) WHERE NOT (b)-[:R]->(a) RETURN a.id AS a_id, b.id AS b_id")
1700+
)
1701+
1702+
assert len(lowered.row_pre_filters) == 1
1703+
anti = lowered.row_pre_filters[0]
1704+
assert isinstance(anti, ASTCall)
1705+
assert anti.function == "anti_semi_apply"
1706+
assert anti.params.get("join_aliases") == ["b", "a"]
1707+
binding_ops = anti.params.get("binding_ops")
1708+
assert isinstance(binding_ops, list)
1709+
assert [op.get("type") for op in binding_ops] == ["Node", "Edge", "Node"]
1710+
1711+
16971712
def test_lower_match_query_rejects_where_pattern_predicate_introducing_new_aliases() -> None:
16981713
with pytest.raises(GFQLValidationError, match="cannot introduce new aliases"):
16991714
lower_cypher_query(_parse_query("MATCH (n) WHERE (n)-[r]->(a) RETURN n"))
@@ -5215,6 +5230,69 @@ def test_string_cypher_executes_mixed_row_and_negated_pattern_where_predicate()
52155230
assert result._nodes.to_dict(orient="records") == [{"id": "c"}]
52165231

52175232

5233+
def test_string_cypher_executes_bound_alias_negated_pattern_where_predicate() -> None:
5234+
graph = _mk_graph(
5235+
pd.DataFrame({"id": ["a", "b", "c", "d"]}),
5236+
pd.DataFrame(
5237+
{
5238+
"s": ["a", "b", "c"],
5239+
"d": ["b", "a", "d"],
5240+
"type": ["R", "R", "R"],
5241+
}
5242+
),
5243+
)
5244+
5245+
result = graph.gfql(
5246+
"MATCH (a)-[:R]->(b) "
5247+
"WHERE NOT (b)-[:R]->(a) "
5248+
"RETURN a.id AS a_id, b.id AS b_id"
5249+
)
5250+
5251+
assert result._nodes.to_dict(orient="records") == [{"a_id": "c", "b_id": "d"}]
5252+
5253+
5254+
def test_string_cypher_executes_mixed_row_and_bound_alias_negated_pattern_where_predicate() -> None:
5255+
graph = _mk_graph(
5256+
pd.DataFrame({"id": ["a", "b", "c", "d", "e"]}),
5257+
pd.DataFrame(
5258+
{
5259+
"s": ["a", "b", "c", "d"],
5260+
"d": ["b", "a", "d", "e"],
5261+
"type": ["R", "R", "R", "R"],
5262+
}
5263+
),
5264+
)
5265+
5266+
result = graph.gfql(
5267+
"MATCH (a)-[:R]->(b) "
5268+
"WHERE a.id <> 'd' AND NOT (b)-[:R]->(a) "
5269+
"RETURN a.id AS a_id, b.id AS b_id"
5270+
)
5271+
5272+
assert result._nodes.to_dict(orient="records") == [{"a_id": "c", "b_id": "d"}]
5273+
5274+
5275+
def test_string_cypher_executes_ic10_shaped_bound_alias_negated_pattern_where_predicate() -> None:
5276+
graph = _mk_graph(
5277+
pd.DataFrame({"id": ["a", "b", "c", "d", "e"]}),
5278+
pd.DataFrame(
5279+
{
5280+
"s": ["a", "b", "b", "a", "c"],
5281+
"d": ["b", "c", "d", "d", "e"],
5282+
"type": ["R", "R", "R", "R", "R"],
5283+
}
5284+
),
5285+
)
5286+
5287+
result = graph.gfql(
5288+
"MATCH (root {id: 'a'})-[:R]->(mid)-[:R]->(cand) "
5289+
"WHERE NOT (root)-[:R]->(cand) "
5290+
"RETURN cand.id AS cand_id ORDER BY cand_id"
5291+
)
5292+
5293+
assert result._nodes.to_dict(orient="records") == [{"cand_id": "c"}]
5294+
5295+
52185296
def test_string_cypher_failfast_rejects_multi_alias_return_star_projection() -> None:
52195297
graph = _mk_empty_graph()
52205298

0 commit comments

Comments
 (0)