Repository navigation
GFQL: track open native-Polars NIE surfaces + a per-engine capability/semantics doc matrix #1665
Description
Activity
- added a commit that references this issue
on Jul 3, 2026 Registering a deliberate, now-TYPED divergence:
sum/avgoverBOOLEANFiling this against the per-engine capability/semantics matrix (item 2) so it lands there as a choice with a contract, not an oversight, per the owner's 2026-07-28 verdict on #1820. Implemented in #1982.
The extension
sum()/avg()over aBOOLEANcolumn is a type error in Cypher (Neo4j 5.26.26: "expected Float, Integer or Duration but was Boolean"; Kuzu rejects at bind time) and is served on every GFQL engine. It is a strict superset — it only accepts input Cypher rejects outright, so no Cypher-valid query changes meaning under it.The contract the matrix should carry (values AND return types, all four engines)
sum(BOOLEAN) -> INTEGER (int64) count of true, nulls skipped; 0 over zero non-null avg(BOOLEAN) -> FLOAT (float64) true_count / non_null_count; NULL over zero non-null min(BOOLEAN) -> BOOLEAN ordering false < true; NULL over zero non-null max(BOOLEAN) -> BOOLEAN ordering false < true; NULL over zero non-null count(BOOLEAN) -> INTEGER (int64) non-null countmin/maxare an ordering, not an AND/OR fold. The fold reading agrees on populated input but predicts the conventional empty identities (true/false) where every engine answers NULL.Divergences this closed (all were cross-engine, all now conformed)
Measured on this repo's four-arm sweep, dtypes read natively (a
to_pandas()round-trip turns a cuDFbool-with-nulls column intoobjectand fabricates a divergence — worth noting for anyone else building a matrix):divergence engine was now sum(BOOLEAN)return typepolars UInt32Int64count(<any type>)return typepolars UInt32Int64all-null sumsubstitution literalpolars Int32(barepl.lit(0))Int64count(DISTINCT ...)return typecuDF int32int64sumover a group with no non-null valuescuDF NULL0The last one is a wrong VALUE, not a wrong type, and it was not boolean-specific — cuDF answered
NULLforBOOLEAN,Int64andfloat64alike, where Cypher says0and pandas/polars already said0. It surfaced only because the verdict required exercising the cuDF arm, which had never been probed.Engine-arm coverage of the new pins
arm status pandas exercised polars 1.42 exercised cuDF 25.10 exercised on a real GPU (RTX 3080 Ti, cupy 13.6, compiled kernel verified) polars-gpu NOT covered — cudf_polarsabsent on the box; every param skips with the named reason. Needs a run on the RAPIDS 26.02 image.Still open, and belongs on this matrix rather than in that PR
The 0-row ungrouped identity row carries no type evidence.
avg/min/maxover an empty result land on pandasobject/ polarsNullinstead of their contract dtypes. The values are correct and pinned on every engine (sum0,avg/min/maxNULL,count0); the dtypes are not. This is not boolean-specific —avgover an empty INTEGER column loses its dtype identically — and closing it means plumbing the aggregate's contract dtype through the identity-row fill.Related: cuDF's 0-row grouped schema types every aggregate output column with the input dtype (so
avgover an empty boolean group reportsbool), where pandas and polars keep the per-aggregate dtype.Executable form of all of the above:
graphistry/tests/compute/gfql/test_aggregate_type_contract.py; single source of the rule:graphistry/compute/gfql/agg_types.py.
Summary
Two related gaps to track:
NotImplementedError(NIE) surfaces in the native Polars GFQL engine — features that currently raiseNotImplementedErroronengine='polars'(and'polars-gpu', which inherits them) and fall to "useengine='pandas'". They're honest declines (parity-or-NIE, never silent wrong answers), but they're coverage gaps to close where feasible.pandas,cudf,polars,polars-gpu) with different coverage and (until the openCypher-conformance work in GFQL: conform to openCypher/SQL semantics — track, fix & document violations (3VL nulls, …) #1664 lands) different semantics. There is no user-facing matrix of "what each engine supports / declines / diverges on." We need one.Context: found while building/extending the native polars engine.
pandasis the differential oracle and the most complete;cudfmostly mirrors pandas (with the cross-engine divergences in #1663);polarsis parity-or-NIE;polars-gpu(cudf_polars) inherits the polars engine's coverage. Recently made native (so NOT on this list): fixed multi-hop, forward/reverseto_fixed_point, undirected fixed multi-hop, forward/reversemin_hops>1(inchain()),toFloat,collect/collect_distinct,WHERE … IN.Open polars-engine NIE surfaces
Feasible to implement (port from the pandas path; rank ~MEDIUM)
include_zero_hop_seed— emit seed nodes at hop 0 (pandascompute/hop.pyseed-union).label_node_hops/label_edge_hops/label_seeds— emit hop-number columns. The min_hops path already computes per-edge hop labels internally; needs exact label-VALUE + NA-dtype parity (see GFQL: cuDF cross-engine result divergences (list-literal order, toString(float), min_hops seed hop-label, group_by Series-truthiness) #1663 Document API with pydoc #3 for the dtype contract — a prerequisite).output_min_hops/output_max_hopsoutput slicing — post-traversal hop-range mask; depends on the labels above.unwindof a list COLUMN / expression (literal-list unwind is already native) —explodewith null/empty-element semantics to prove vs pandas.head/tail/reverse/range— element order/repr parity care.rows(binding_ops=…), edge entity-text) —n.x-prefixed multi-entity columns.call()as a chain PREFIX (row-table → graph re-entry) — polars traversal can't currently consume a row-table input.hop(min_hops>1)— native inchain()/gfql()but NIE as a barehop()(it needs pandas' separate un-labeled direct-hop node output +target_wave_frontthreading; validated this session that it silently diverges otherwise, e.g. drops a genuinely-reachable node)..selectrejects dup names pandas tolerates.DateTimeValue/TimeValuepredicates — localize/convert semantics (naiveDatetimeDateValueis already native).Keep-NIE (genuinely blocked or a correct decline)
to_fixed_pointand undirectedmin_hops>1— need connected-components + 2-core seed retention (compute/hop.py:817-887); no vectorized polars primitive.*_query/ nodequery=— pandasdf.query()eval syntax (pandas-specific).labels) — pandas compares the whole list; a contains-membership port would be wrong.structured=Falsefloat/temporal/nested entity-text — pandas float repr diverges (1e+20);structured=True(default) already flattens any dtype.QuantifierExpr(any/all/none/single),ListComprehension,Match/Fullmatchcustom regex flags — HARD / narrow.(Each has a file:line in the engine source; happy to expand any into its own task.)
Per-engine documentation deliverable
A user-facing capability + semantics matrix across
pandas/cudf/polars/polars-gpu:toString/propertiesfloat repr, NA-dtype) — cross-link GFQL: cuDF cross-engine result divergences (list-literal order, toString(float), min_hops seed hop-label, group_by Series-truthiness) #1663.This is the documentation half of the openCypher-conformance effort (#1664) plus the polars-engine coverage story; the matrix should be generated/maintained alongside the conformance ledger so it can't silently drift.
Related: #1664 (openCypher conformance), #1663 (cuDF cross-engine divergences).