Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- **Collections**: Added `test_collections.py` covering encoding, GFQL Chain/AST normalization, wire-protocol acceptance, validation modes, and helper constructors.
- **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).
- **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.
- **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`.

## [0.54.1 - 2026-04-08]

Expand Down
78 changes: 78 additions & 0 deletions graphistry/tests/compute/gfql/cypher/test_lowering.py
Original file line number Diff line number Diff line change
Expand Up @@ -1694,6 +1694,21 @@ def test_lower_match_query_emits_row_anti_semi_filter_for_negated_where_pattern(
assert [op.get("type") for op in binding_ops] == ["Node", "Edge", "Node"]


def test_lower_match_query_emits_row_anti_semi_filter_for_bound_alias_negated_where_pattern() -> None:
lowered = lower_match_query(
_parse_query("MATCH (a)-[:R]->(b) WHERE NOT (b)-[:R]->(a) RETURN a.id AS a_id, b.id AS b_id")
)

assert len(lowered.row_pre_filters) == 1
anti = lowered.row_pre_filters[0]
assert isinstance(anti, ASTCall)
assert anti.function == "anti_semi_apply"
assert anti.params.get("join_aliases") == ["b", "a"]
binding_ops = anti.params.get("binding_ops")
assert isinstance(binding_ops, list)
assert [op.get("type") for op in binding_ops] == ["Node", "Edge", "Node"]


def test_lower_match_query_rejects_where_pattern_predicate_introducing_new_aliases() -> None:
with pytest.raises(GFQLValidationError, match="cannot introduce new aliases"):
lower_cypher_query(_parse_query("MATCH (n) WHERE (n)-[r]->(a) RETURN n"))
Expand Down Expand Up @@ -5215,6 +5230,69 @@ def test_string_cypher_executes_mixed_row_and_negated_pattern_where_predicate()
assert result._nodes.to_dict(orient="records") == [{"id": "c"}]


def test_string_cypher_executes_bound_alias_negated_pattern_where_predicate() -> None:
graph = _mk_graph(
pd.DataFrame({"id": ["a", "b", "c", "d"]}),
pd.DataFrame(
{
"s": ["a", "b", "c"],
"d": ["b", "a", "d"],
"type": ["R", "R", "R"],
}
),
)

result = graph.gfql(
"MATCH (a)-[:R]->(b) "
"WHERE NOT (b)-[:R]->(a) "
"RETURN a.id AS a_id, b.id AS b_id"
)

assert result._nodes.to_dict(orient="records") == [{"a_id": "c", "b_id": "d"}]


def test_string_cypher_executes_mixed_row_and_bound_alias_negated_pattern_where_predicate() -> None:
graph = _mk_graph(
pd.DataFrame({"id": ["a", "b", "c", "d", "e"]}),
pd.DataFrame(
{
"s": ["a", "b", "c", "d"],
"d": ["b", "a", "d", "e"],
"type": ["R", "R", "R", "R"],
}
),
)

result = graph.gfql(
"MATCH (a)-[:R]->(b) "
"WHERE a.id <> 'd' AND NOT (b)-[:R]->(a) "
"RETURN a.id AS a_id, b.id AS b_id"
)

assert result._nodes.to_dict(orient="records") == [{"a_id": "c", "b_id": "d"}]


def test_string_cypher_executes_ic10_shaped_bound_alias_negated_pattern_where_predicate() -> None:
graph = _mk_graph(
pd.DataFrame({"id": ["a", "b", "c", "d", "e"]}),
pd.DataFrame(
{
"s": ["a", "b", "b", "a", "c"],
"d": ["b", "c", "d", "d", "e"],
"type": ["R", "R", "R", "R", "R"],
}
),
)

result = graph.gfql(
"MATCH (root {id: 'a'})-[:R]->(mid)-[:R]->(cand) "
"WHERE NOT (root)-[:R]->(cand) "
"RETURN cand.id AS cand_id ORDER BY cand_id"
)

assert result._nodes.to_dict(orient="records") == [{"cand_id": "c"}]


def test_string_cypher_failfast_rejects_multi_alias_return_star_projection() -> None:
graph = _mk_empty_graph()

Expand Down
Loading