Skip to content

GFQL Cypher dialect: host the streamgl-viz filter language (regex/case, floor/ceil/round/pow, prune-isolated, two-mask via GRAPH{}) #1673

Description

@lmeyerov

Goal

Extend the GFQL Cypher dialect so it can be the single target for a (net-new) streamgl-viz filter/exclusion → Cypher translation layer, running on all four engines (pandas, cudf, polars, polars-gpu). Strategy: extend the Cypher flavor (we own it — GRAPH { … } constructor, CALL … YIELD procedures incl. graph-level, growable WHERE grammar + scalar-fn allow-list), rather than route around it to raw GFQL-AST.

Grounded in a per-engine conformance analysis of the viz filter AST (~/Work/graphistry2/apps/core/viz + forge shaping) vs the GFQL engine.

Frontend (dialect) gaps

  • G1 — regex operator =~ (openCypher-standard; + optional LIKE/ILIKE) → lower to match/fullmatch/contains(regex=True); case-insensitivity via inline (?i) and/or a case flag. Flagship viz gap — Cypher WHERE has no regex/LIKE/ILIKE/case-insensitivity today.
  • G2 — scalar functions floor / ceil / round / pow → add to GFQL_SCALAR_FUNCTIONS + evaluator (all engines). Currently absent from the GFQL language entirely (rejected on every engine).
  • G3 — graph-level CALL graphistry.pruneIsolated() + graphistry.pruneEdgelist() (graph_only procedures, like the existing networkx.* graph algos). Match viz semantics: pruneEdgelist = unconditional both-endpoint induced subgraph; pruneIsolated = opt-in, undirected degree, drops self-loop-only nodes, re-prunes. (No "prune isolated" op exists today; a [n,e,n] chain drops orphans but that's the opposite of viz's keep-by-default default and isn't attribute-preserving.)
  • G4 — two-mask composition via GRAPH { … } — viz produces two independent row masks (node ⟂ edge) + prune; express as a GRAPH pipeline. May require closing the "GRAPH bindings + UNION" gap (parser) — scope carefully; the prune CALLs may make a single-pipeline form sufficient.

Backend (per-engine execution) prerequisites — so the extended Cypher actually runs everywhere

  • B1 — polars contains ignores regex=/flags= (proven: contains('B.R', regex=False) matched 'BAR.baz'). Literal filters with regex metacharacters over-match on polars. Correctness bug — fix first.
  • B2 — polars Match/Fullmatch → NotImplementedError. Add native lowering (needed for G1 on polars).
  • B3 — polars CASE / sqrt / sign → NotImplementedError. Add native lowering.
  • B4 — GPU verify (cudf + polars-gpu): regex + inline (?i) execution, and B1–B3 parity. dgx-only; keep tiny.

Semantics to preserve

  • S1 — null inclusion: viz filters keep nulls by default; GFQL predicates default na=False (exclude). Translation must OR-in IS NULL (or add a dialect knob). Easy to miss.
  • S2 — regex dialect: viz emits JavaScript RegExp; engines use Python re / cuDF / Rust regex. Common patterns port; validate/reject JS-only constructs (lookbehind, named-group syntax).

Engine support today (summary)

Comparisons, AND/OR/NOT, arithmetic, IS NULL, IN, BETWEEN, Contains/StartsWith/EndsWith (incl. case-insensitive) — native on pandas/cudf/polars. Match/Fullmatch, CASE, sqrt, sign — pandas/cudf only (polars NIE). floor/ceil/round/pow — not in GFQL at all. GPU regex/case execution unverified.

Approach

Incremental stacked PRs, each CPU-green + differential-parity-tested (pandas oracle), honest NotImplementedError (no silent bridges); GPU parity deferred to a small dgx pass. Suggested order: B1 → B2/B3 → G1 → G3 → G2 → G4 → S1/S2 → dgx verify.

Full working plan + file:line extension points tracked locally (plan skill).


SPEC ADDENDUM — standards vetting vs neo4j Cypher 25 + ISO GQL (2026-07-02)

Deep re-vet of every shipped/planned extension against primary sources: the current neo4j Cypher manual, neo4j's shipped Cypher 5/25 parser source, the openCypher 9 EBNF + TCK, and the ISO-derived GQL grammar (opengql/grammar) + neo4j's GQL-conformance appendix. Verdicts:

construct neo4j Cypher (current) ISO GQL 39075:2024 our verdict
=~ regex ✅ standard, not deprecated; Java regex, inline (?i)(?m)(?s); full-string match ❌ (listed by neo4j as beyond-GQL) CONFORMANT (Cypher extension) — keep
round() ties precision 0: ties toward +∞ (round(-1.5)=-1.0, manual Ex. 8/10); precision >0: ties away from zero (HALF_UP, round(-1.55,1)=-1.6, Ex. 11). (Args-table "(default: UP)" contradicts Ex. 10 → neo4j docs bug.) ❌ no ROUND in §20.22 DIVERGED → FIXED (we shipped numpy half-to-even; round(2.5) gave 2.0 vs neo4j 3.0)
ceil/ceiling ceiling = Cypher 25 GQL-conformance alias ✅ grammar (CEIL|CEILING) CONFORMANT — keep both
toLower/toUpper + lower/upper ✅ both; lower/upper are GQL-conformance aliases (present since Cypher 5 manual) lower/upper mandatory §20.24 ADDED aliases lower/upper
^ power Manual says "right to left" but the shipped parser left-folds Pow (Cypher25Parser.g4 expression4: expression3 (POW expression3)* + astBinaryFold=foldLeft) → 2^3^2 = 64. openCypher TCK also pins left (4^(3*2)^3 = (4^6)^3) and unary-tighter (-3^2 = 9.0) ❌ no ^; POWER(x,y) fn (GF03) SETTLED: left-associative. Manual's "right to left" = neo4j docs bug. Re-add = coordinated tck-corpus xfail→pass change (deferred)
chained a<b<c CONFORMANT (already worked)
XOR GQL HAS XOR (optional GE07; precedence groups with OR) CORRECTION to earlier note ("GQL lacks XOR" was wrong)
LIKE/ILIKE/BETWEEN ❌ re-verified hard: GQL's only LIKE is CREATE GRAPH TYPE … LIKE g (DDL false-positive); only BETWEEN token is DURATION_BETWEEN() DECLINE STANDS — viz layer lowers (LIKE=~/STARTS WITH, BETWEENa<=x AND x<=b)
GRAPH g = GRAPH { MATCH … } n/a GQL §10.1 GRAPH g = <graph expression> exists but takes references only (graphRef / CURRENT_GRAPH); no inline anonymous constructor with a MATCH body NON-STANDARD EXTENSION — outer GRAPH g = … surface matches GQL §10.1; the { MATCH … } literal body is ours. Document as extension (closest std: CREATE GRAPH … AS COPY OF + INSERT)
CALL ns.proc() YIELD ✅ standard ✅ GQL §15.1–15.3 named procedure calls incl. YIELD (GP04), plus OPTIONAL CALL CONFORMANT
CREATE GFQL INDEX neo4j's own CREATE INDEX is listed beyond-GQL GQL defines no index DDL at all NON-STANDARD, nothing to align to — vendor DDL (same posture as neo4j)

Actions taken

Full citations in the working notes (neo4j manual pages, Cypher25Parser.g4/ExpressionBuilder.scala, TCK Precedence2.feature, GQL.g4, GQL-conformance appendix).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions