diff --git a/CHANGELOG.md b/CHANGELOG.md index 933c020029..a6acc52a62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,6 +57,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - **dgx GB10 benchmark safety harness**: `benchmarks/dgx/{safe_run.sh, preflight.py, sitecustomize.py, local_run.sh}` — an RMM device-allocation cap + host-memory watchdog + preflight refusal + hard timeout for running GPU / large-graph benchmarks on the unified-memory GB10 box without OOM-wedging it. Developer/benchmark tooling only; no runtime or library behavior change. - **Docs CI: polars in the doc-test image (pinned)**: the Sphinx doc-example runner now installs `polars` (pinned to cooldown-safe versions) so `engine='polars'` documentation examples execute — and are SKIPPED, not failed, where polars is unavailable (e.g. the minimal-deps job, Python 3.14). Doc-build / CI only; no runtime or library change. +- **openCypher TCK conformance: `EXISTS { }` existential-subquery scenarios (`expr-existentialsubquery1-1`, `1-3`) now pass**, unblocking the `tck-gfql` CI job. No pygraphistry code change — pygraphistry already renders these correctly (a whole-entity `RETURN n` over a simple-pattern `EXISTS { }` yields the expected entity-text rows). The `success_wrong_rows` was purely a property-map display-whitespace convention (`(:A {prop: 1})` vs the TCK oracle's `(:A {prop:1})`; both valid Cypher). Reconciled harness-side in [tck-gfql#193](https://github.com/graphistry/tck-gfql/pull/193): entity-text whitespace normalization + promoting the two scenarios to supported (and deflaking the pre-existing scipy-`svds` `firstparty-networkx-hits-1` fixture). Conformance/CI only; no runtime or library behavior change. ## [0.57.0 - 2026-06-28] diff --git a/graphistry/compute/gfql/cypher/result_postprocess.py b/graphistry/compute/gfql/cypher/result_postprocess.py index 05e4b26d63..b786201475 100644 --- a/graphistry/compute/gfql/cypher/result_postprocess.py +++ b/graphistry/compute/gfql/cypher/result_postprocess.py @@ -186,6 +186,12 @@ def render_entity_text( the Cypher display string (``(:Label {..})`` / ``[:TYPE {..}]``). Used by the conformance/TCK driver and by callers who want the human-readable form. The structured data path itself never pays this cost. + + Property maps render with a space after the key colon (``(:A {prop: 1})``) — + our long-standing display convention. The openCypher TCK oracle omits it + (``(:A {prop:1})``); the whitespace is insignificant Cypher, so the TCK + harness normalizes it rather than us changing this convention across ~350 + tests (see tck-gfql#193 / CHANGELOG). """ rows_df = cast(DataFrameT, result._nodes) if rows_df is None: diff --git a/graphistry/tests/compute/gfql/test_engine_polars_conformance_matrix.py b/graphistry/tests/compute/gfql/test_engine_polars_conformance_matrix.py index 204c95e145..fb2274094d 100644 --- a/graphistry/tests/compute/gfql/test_engine_polars_conformance_matrix.py +++ b/graphistry/tests/compute/gfql/test_engine_polars_conformance_matrix.py @@ -752,6 +752,39 @@ def test_exists_prune_isolated_flavors_all_engines(): _assert_invariant(g, q, f"prune-isolated {q}") +def test_exists_whole_entity_return_renders_entity_text(): + """Regression for openCypher TCK expr-existentialsubquery1-1 (tck-gfql#193): + a WHOLE-ENTITY ``RETURN n`` over a simple-pattern ``EXISTS { }`` renders the + correct entity text (label + property), not leaked internal marker columns. + Graph mirrors the TCK fixture — node ``a`` is the only one with an out-edge — + so the EXISTS filter keeps exactly ``a`` and it renders ``(:A {prop: 1})``. + + ``id`` is a STRING on purpose: a numeric id would render as an extra + property (``{id: 0, prop: 1}``). ``n.id`` is the #1650 flattened + whole-entity projection column, not user-aliased output.""" + import pandas as pd + from graphistry.compute.gfql.cypher.result_postprocess import render_entity_text + nodes = pd.DataFrame({ + "id": ["a", "b", "c", "d"], + "label__A": [True, False, False, False], + "label__B": [False, True, False, False], + "label__C": [False, False, True, False], + "label__D": [False, False, False, True], + "prop": [1, 1, 2, 3], + }) + edges = pd.DataFrame({"s": ["a", "a", "a"], "d": ["b", "c", "d"], "eid": [0, 1, 2]}) + g = graphistry.nodes(nodes, "id").edges(edges, "s", "d").bind(edge="eid") + q = "MATCH (n) WHERE EXISTS { (n)-->() } RETURN n" + res = g.gfql(q, engine="pandas") + pdf = _to_pd(res._nodes) + assert len(pdf) == 1, "EXISTS keeps exactly the one node with an out-edge" + assert pdf["n.id"].tolist() == ["a"] + assert render_entity_text(res, "n", table="nodes").tolist() == ["(:A {prop: 1})"] + # Cross-engine (this file's invariant): non-pandas engines match pandas + # parity or decline honestly (NIE) — whole-entity RETURN declines on polars. + _assert_invariant(g, q, "exists-whole-entity-return") + + def test_tolower_non_string_declines_never_fabricates(): """toLower/toUpper on a non-string column must never return values (neo4j: type error). Regression (#1675 wave-1, dgx-repro'd): the scalar fallback broadcast the lowercased