Two related follow-ups surfaced while auditing PR #1362 / #1359 (Comparator + ORDER BY semantics, meta #1353 item #1). Both are non-blocking for #1362 (already merged-ready) but warrant separate work.
1. cuDF host-bridge gap in order_by list-path
While auditing #1362's stringified-list ORDER BY surface for cuDF compatibility, found a latent host-bridge protocol violation at graphistry/compute/gfql/row/pipeline.py:3964.
Setup. parse_stringified_list_series (graphistry/compute/gfql/row/ordering.py:188-219) is intentionally pandas-only — it ast.literal_evals string-encoded lists, which has no cuDF analog. Its docstring documents this and points at the canonical pattern at _gfql_eval_dynamic_list_subscript (pipeline.py:2369-2401).
The gap. The canonical pattern has two halves:
- Producer (
pipeline.py:2394-2401): emits a pd.Series.
- Consumer (
pipeline.py:2424-2437): detects pd.Series and routes around cudf_df.assign(...) via a fresh pd.DataFrame build.
PR #1362's order_by callsite (pipeline.py:3955-3967) implements the producer half but does work_df = work_df.assign(**{sort_col: parsed}) without the consumer-side to_pandas() bridge that select uses at pipeline.py:3651-3658:
if resolve_engine(EngineAbstract.AUTO, table_df) == Engine.CUDF and any(
isinstance(value, pd.Series) for value in projected.values()
):
out_table_df = table_df.to_pandas()
...
Reachability today. Unreachable in production because the only column dtype that can carry ast.literal_eval-able list strings is pandas string/object, and current upstream gating ensures work_df is already pandas by the time order_by runs. The bug is not a regression in #1362 and not blocking. It is a latent cuDF break that surfaces if either (a) cuDF general object dtype lands or (b) upstream gating regresses.
Coverage gap. Zero _on_cudf tests cover the list / stringified-list / list-comprehension-key ORDER BY surface (52 _on_cudf tests in test_lowering.py, none on this surface). The 8 pin tests at test_lowering.py:7721-7972 are pandas-only by _mk_graph(pd.DataFrame(...)).
Other latent issues found in the same audit (separate, deferrable):
ordering.py:306 — only .pivot( call in the whole repo; no established cuDF-safety pattern. Replace with groupby+merge idiom.
ordering.py:289 — value_str.where(num_mask, None).astype(\"float64\") is cuDF-fragile (None-fill then float cast).
ordering.py:317 — key_frame[col] = key_frame[col].fillna(\"\") is a pure-functional checklist violation per agents/skills/review/SKILL.md.
Proposed work:
- Mirror the
select consumer-side host-bridge at pipeline.py:3964 (single-line guard).
- Add at least one
_on_cudf pin test that covers stringified-list ORDER BY on a cuDF input (asserting clean raise OR success, whichever matches expected behavior).
- Tighten
parse_stringified_list_series docstring to document the caller contract.
- (Optional) refactor
.pivot( away from build_list_sort_columns:306.
- (Optional)
where(_, None).astype(\"float64\") cuDF fragility.
- (Optional)
key_frame[col] = ... mutation fix.
2. Test placement debt: compute/gfql/row/ has no test mirror
PR #1362 added 6 new tests at graphistry/tests/compute/gfql/cypher/test_lowering.py:7781-7972 exercising the cypher → gfql → row-pipeline integration path, but the actual unit-of-change is in graphistry/compute/gfql/row/{ordering,pipeline}.py. These belong in row-pipeline unit tests.
Layout asymmetry
| Source |
Current test home |
Ideal home |
compute/gfql/row/ordering.py |
tests/compute/gfql/test_row_pipeline_ops.py (2615 LOC, single mega-file) |
tests/compute/gfql/row/test_ordering.py |
compute/gfql/row/pipeline.py |
same mega-file |
tests/compute/gfql/row/test_pipeline.py |
compute/gfql/row/entity_props.py |
scattered in same mega-file |
tests/compute/gfql/row/test_entity_props.py |
compute/gfql/row/entity_text.py |
scattered in same mega-file |
tests/compute/gfql/row/test_entity_text.py |
compute/gfql/row/order_expr.py |
scattered in same mega-file |
tests/compute/gfql/row/test_order_expr.py |
compute/gfql/row/dispatch.py |
scattered in same mega-file |
tests/compute/gfql/row/test_dispatch.py |
graphistry/tests/compute/gfql/row/ directory does not exist. Source side has 7 files under row/; test side has zero mirror.
What PR #1362 did
| Test |
Where |
Should be |
test_string_cypher_order_by_python_list_column_uses_list_orderability |
cypher integration |
Redundant — duplicates test_row_pipeline_order_by_list_column_matches_opencypher_prefix_tie_break at test_row_pipeline_ops.py:335 (#1360). Drop. |
test_string_cypher_order_by_stringified_list_column_uses_list_orderability |
cypher integration |
Unit test in test_row_pipeline_ops.py next to :335; keep ONE thin cypher-integration test in test_lowering.py. |
test_string_cypher_order_by_partial_stringified_list_raises_mixed_family |
cypher integration |
Unit test in test_row_pipeline_ops.py next to peers at :450/:475. |
test_string_cypher_order_by_stringified_list_with_nulls_returns_top_k_without_error |
cypher integration |
Unit test in test_row_pipeline_ops.py. |
test_string_cypher_order_by_malformed_stringified_list_falls_back_to_lex_sort |
cypher integration |
Unit test in test_row_pipeline_ops.py. |
test_string_cypher_order_by_multi_key_stringified_list_with_scalar |
cypher integration |
Unit test in test_row_pipeline_ops.py. |
Why this matters
- Wrong abstraction layer. Bug class is "row pipeline mishandles stringified lists." Cypher → gfql lowering is unaffected. Testing through cypher adds parse + lower + project noise to every assertion.
- Test file bloat.
test_lowering.py is 14,732 LOC; test_row_pipeline_ops.py is 2615 LOC. A tests/compute/gfql/row/ subdir splitting per-file would scale better.
- Discoverability. A reviewer scanning
graphistry/compute/gfql/row/ordering.py for tests has no parallel tests/compute/gfql/row/ to look in.
- Coupling to cypher. Row-pipeline tests written through cypher implicitly couple to cypher parser shape; tightening cypher syntax later breaks unrelated row-pipeline pins.
Recommended scope
- Phase A (small): relocate the 5 useful tests from
test_lowering.py:7781-7972 to test_row_pipeline_ops.py (drop the redundant Python-list one); keep one thin cypher-integration smoke test.
- Phase B (medium): create
tests/compute/gfql/row/ subdirectory; split test_row_pipeline_ops.py's 2615 LOC into per-source-file mirrors.
- Phase C (larger): sweep
test_lowering.py for other tests that are really row-pipeline tests and relocate.
Cross-refs
Severity buckets
Status (2026-05-09)
Two related follow-ups surfaced while auditing PR #1362 / #1359 (Comparator + ORDER BY semantics, meta #1353 item #1). Both are non-blocking for #1362 (already merged-ready) but warrant separate work.
1. cuDF host-bridge gap in
order_bylist-pathWhile auditing #1362's stringified-list ORDER BY surface for cuDF compatibility, found a latent host-bridge protocol violation at
graphistry/compute/gfql/row/pipeline.py:3964.Setup.
parse_stringified_list_series(graphistry/compute/gfql/row/ordering.py:188-219) is intentionally pandas-only — itast.literal_evals string-encoded lists, which has no cuDF analog. Its docstring documents this and points at the canonical pattern at_gfql_eval_dynamic_list_subscript(pipeline.py:2369-2401).The gap. The canonical pattern has two halves:
pipeline.py:2394-2401): emits apd.Series.pipeline.py:2424-2437): detectspd.Seriesand routes aroundcudf_df.assign(...)via a freshpd.DataFramebuild.PR #1362's
order_bycallsite (pipeline.py:3955-3967) implements the producer half but doeswork_df = work_df.assign(**{sort_col: parsed})without the consumer-sideto_pandas()bridge thatselectuses atpipeline.py:3651-3658:Reachability today. Unreachable in production because the only column dtype that can carry
ast.literal_eval-able list strings is pandas string/object, and current upstream gating ensureswork_dfis already pandas by the timeorder_byruns. The bug is not a regression in #1362 and not blocking. It is a latent cuDF break that surfaces if either (a) cuDF general object dtype lands or (b) upstream gating regresses.Coverage gap. Zero
_on_cudftests cover the list / stringified-list / list-comprehension-key ORDER BY surface (52_on_cudftests intest_lowering.py, none on this surface). The 8 pin tests attest_lowering.py:7721-7972are pandas-only by_mk_graph(pd.DataFrame(...)).Other latent issues found in the same audit (separate, deferrable):
ordering.py:306— only.pivot(call in the whole repo; no established cuDF-safety pattern. Replace with groupby+merge idiom.ordering.py:289—value_str.where(num_mask, None).astype(\"float64\")is cuDF-fragile (None-fill then float cast).ordering.py:317—key_frame[col] = key_frame[col].fillna(\"\")is a pure-functional checklist violation peragents/skills/review/SKILL.md.Proposed work:
selectconsumer-side host-bridge atpipeline.py:3964(single-line guard)._on_cudfpin test that covers stringified-list ORDER BY on a cuDF input (asserting clean raise OR success, whichever matches expected behavior).parse_stringified_list_seriesdocstring to document the caller contract..pivot(away frombuild_list_sort_columns:306.where(_, None).astype(\"float64\")cuDF fragility.key_frame[col] = ...mutation fix.2. Test placement debt:
compute/gfql/row/has no test mirrorPR #1362 added 6 new tests at
graphistry/tests/compute/gfql/cypher/test_lowering.py:7781-7972exercising the cypher → gfql → row-pipeline integration path, but the actual unit-of-change is ingraphistry/compute/gfql/row/{ordering,pipeline}.py. These belong in row-pipeline unit tests.Layout asymmetry
compute/gfql/row/ordering.pytests/compute/gfql/test_row_pipeline_ops.py(2615 LOC, single mega-file)tests/compute/gfql/row/test_ordering.pycompute/gfql/row/pipeline.pytests/compute/gfql/row/test_pipeline.pycompute/gfql/row/entity_props.pytests/compute/gfql/row/test_entity_props.pycompute/gfql/row/entity_text.pytests/compute/gfql/row/test_entity_text.pycompute/gfql/row/order_expr.pytests/compute/gfql/row/test_order_expr.pycompute/gfql/row/dispatch.pytests/compute/gfql/row/test_dispatch.pygraphistry/tests/compute/gfql/row/directory does not exist. Source side has 7 files underrow/; test side has zero mirror.What PR #1362 did
test_string_cypher_order_by_python_list_column_uses_list_orderabilitytest_row_pipeline_order_by_list_column_matches_opencypher_prefix_tie_breakattest_row_pipeline_ops.py:335(#1360). Drop.test_string_cypher_order_by_stringified_list_column_uses_list_orderabilitytest_row_pipeline_ops.pynext to:335; keep ONE thin cypher-integration test intest_lowering.py.test_string_cypher_order_by_partial_stringified_list_raises_mixed_familytest_row_pipeline_ops.pynext to peers at:450/:475.test_string_cypher_order_by_stringified_list_with_nulls_returns_top_k_without_errortest_row_pipeline_ops.py.test_string_cypher_order_by_malformed_stringified_list_falls_back_to_lex_sorttest_row_pipeline_ops.py.test_string_cypher_order_by_multi_key_stringified_list_with_scalartest_row_pipeline_ops.py.Why this matters
test_lowering.pyis 14,732 LOC;test_row_pipeline_ops.pyis 2615 LOC. Atests/compute/gfql/row/subdir splitting per-file would scale better.graphistry/compute/gfql/row/ordering.pyfor tests has no paralleltests/compute/gfql/row/to look in.Recommended scope
test_lowering.py:7781-7972totest_row_pipeline_ops.py(drop the redundant Python-list one); keep one thin cypher-integration smoke test.tests/compute/gfql/row/subdirectory; splittest_row_pipeline_ops.py's 2615 LOC into per-source-file mirrors.test_lowering.pyfor other tests that are really row-pipeline tests and relocate.Cross-refs
plans/1359-orderby-wrong-rows/audits/cudf-flow-audit.md(local).Severity buckets
order_byhost-bridge guard atpipeline.py:3964; (1.2)_on_cudfpin test for stringified-list ORDER BY; (2.A) test relocation.Status (2026-05-09)
graphistry/tests/compute/gfql/row/now existsrow/test_ordering.pyaddedtest_lowering.pyretains one thin integration smoke