Skip to content

feat(gfql/cypher): standard =~ regex + numeric fns + toLower/toUpper (#1673)#1675

Merged
lmeyerov merged 18 commits into
masterfrom
dev/gfql-cypher-viz-ops
Jul 5, 2026
Merged

feat(gfql/cypher): standard =~ regex + numeric fns + toLower/toUpper (#1673)#1675
lmeyerov merged 18 commits into
masterfrom
dev/gfql-cypher-viz-ops

Conversation

@lmeyerov

@lmeyerov lmeyerov commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Implements the streamgl-viz filter-language conformance gaps (#1673) by adopting standard openCypher/neo4j Cypher syntax — not inventing dialect. Based directly on master (the polars contains regex fix it originally stacked on merged via #1660).

Research first

Verified each construct against the neo4j Cypher manual, openCypher 9, and ISO GQL (sources in the issue). Finding: most "gaps" are standard Cypher, so we adopt the standard forms. LIKE/ILIKE/BETWEEN are SQL-only (not in Cypher or GQL) → intentionally not implemented; chained comparison (1 < n.age < 65) already worked.

What's added (all standard openCypher/neo4j)

  • =~ regex-match operator — Java-regex, full/anchored match (n.name =~ 'AB' matches only 'AB'), inline (?i)/(?m)/(?s) flags; lowers to fullmatch. Simple WHERE …=~… on all engines (filter_by_dict); composes through AND/OR/NOT/RETURN on pandas/cuDF (polars declines the complex OR/NOT row-filter form with an honest NotImplementedError — pre-existing where_rows gap). Adds native polars Match/Fullmatch lowering.
  • Numeric functions floor, ceil (≡ceiling), round(x)/round(x, precision) (complementing existing abs/sqrt/sign).
  • toLower/toUpper — the idiomatic case-insensitive compare.

Wired end-to-end: cypher grammar + shared expr_parser (REGEX_MATCH terminal) + allow-lists + evaluators (pandas/cuDF row/pipeline.py+row/dispatch.py; polars predicates.py+row_pipeline.py).

Tests / quality

Differential-parity tested pandas↔polars for =~, Match/Fullmatch, numeric fns, sign, toLower/toUpper. Full polars lane passes; ruff+mypy clean. A focused adversarial review found no BLOCKERs; its two findings are addressed (polars parity now guarded by parametrized tests; polars sign() cast to Int64 to match pandas/neo4j Integer spec).

GPU parity verified on dgx (GB10, --gpus all, 2026-07-05): full polars lane + conformance matrix + test_engine_polars_gpu.py = 1803 passed, 0 skipped/failed — cuDF + polars-gpu (cudf_polars) matrix cases (incl. Match/Fullmatch + the scalar fns) all parity-green vs the pandas oracle.

Review hardening (4-wave converged adversarial review, 2026-07-05)

A multi-wave findings-mode review (artifacts: plans/viz-filter-pipeline/waves/pr1675/) found and fixed, each dgx-repro'd before and re-verified after: composed =~ broken on engine='cudf' (no .str.fullmatch; now delegates to the hardened Fullmatch predicate); cuDF fullmatch alternation mis-anchoring ('ab|cd' silently matched 'abXXX'); the cuDF (?i) lowercase-fold silently inverting escape classes (now a precise decline gate: uppercase escapes / case-crossing ranges / hex escapes / non-ASCII decline, lowercase escapes keep folding); polars Match/Fullmatch missing the Rust-regex guard (lookarounds crashed non-NIE); toLower(numeric col) broadcasting the Series repr on the pandas oracle (now declines like neo4j's type error); polars floor/ceil/round returning Int64 on int columns (now Float64 like neo4j/pandas); NotImplementedError being re-labeled by broad excepts (honest NIEs now survive to the caller); plus matrix coverage for the =~ family, null cells, dtype pins, and discriminating regression fixtures. Waves 3–4 converged at zero BLOCKER/IMPORTANT.

Docs

docs/source/gfql/cypher.rst: =~ in WHERE Forms + a new "Scalar Functions and Operators" section (fns/toLower + why LIKE/BETWEEN aren't provided). CHANGELOG entries for both increments.

Deferred (documented)

🤖 Generated with Claude Code

lmeyerov added a commit that referenced this pull request Jul 2, 2026
…s (no crash)

GPU-parity pass (viz-filter #1673 item 2) on dgx found `MATCH (n) WHERE n.name =~
'(?i)…'` CRASHES on engine='cudf' with libcudf "invalid regex pattern: nothing to
repeat". Root cause: libcudf's regex engine rejects inline flag groups ((?i)/(?m)/
(?s)) at ANY position (verified: '(?i)abc', '^(?i)abc$', '(?i)^abc$' all raise;
only flag-free '^abc$' works) — not merely a position issue.

Fix: the Match/Fullmatch cuDF branches now translate a leading (?i) to the existing
lowercase case-folding workaround (parity with pandas' (?i)), and honestly decline
any other inline flag with NotImplementedError instead of crashing. Shared helper
_cudf_regex_prep. pandas/polars paths untouched.

Validated on dgx (RAPIDS 26.02): cudf =~ '(?i)a.c' == pandas [2,3] (was RuntimeError);
446 regex/match/fullmatch/contains/numeric tests pass across pandas/polars/cudf; +1
cudf-gated regression test (test_regex_cudf_inline_flag_parity). ruff+mypy clean.

Also confirms viz-filter #1673 item 2: cuDF numeric (floor/ceil/round) + toLower/
toUpper parity OK; polars-gpu is N/A on this branch (#1675 is off #1660, below #1655
which introduces polars-gpu).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@lmeyerov
lmeyerov force-pushed the dev/gfql-polars-contains-regex-fix branch from b8f7d2c to 6c68f1c Compare July 2, 2026 16:19
@lmeyerov
lmeyerov force-pushed the dev/gfql-cypher-viz-ops branch from 8b94e1a to d90d7ea Compare July 2, 2026 16:19
lmeyerov added a commit that referenced this pull request Jul 2, 2026
…s (no crash)

GPU-parity pass (viz-filter #1673 item 2) on dgx found `MATCH (n) WHERE n.name =~
'(?i)…'` CRASHES on engine='cudf' with libcudf "invalid regex pattern: nothing to
repeat". Root cause: libcudf's regex engine rejects inline flag groups ((?i)/(?m)/
(?s)) at ANY position (verified: '(?i)abc', '^(?i)abc$', '(?i)^abc$' all raise;
only flag-free '^abc$' works) — not merely a position issue.

Fix: the Match/Fullmatch cuDF branches now translate a leading (?i) to the existing
lowercase case-folding workaround (parity with pandas' (?i)), and honestly decline
any other inline flag with NotImplementedError instead of crashing. Shared helper
_cudf_regex_prep. pandas/polars paths untouched.

Validated on dgx (RAPIDS 26.02): cudf =~ '(?i)a.c' == pandas [2,3] (was RuntimeError);
446 regex/match/fullmatch/contains/numeric tests pass across pandas/polars/cudf; +1
cudf-gated regression test (test_regex_cudf_inline_flag_parity). ruff+mypy clean.

Also confirms viz-filter #1673 item 2: cuDF numeric (floor/ceil/round) + toLower/
toUpper parity OK; polars-gpu is N/A on this branch (#1675 is off #1660, below #1655
which introduces polars-gpu).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
lmeyerov added a commit that referenced this pull request Jul 2, 2026
…in _cudf_regex_prep

The cuDF =~ flags commit annotated _cudf_regex_prep with "tuple[object, bool]",
which mypy rejects on py3.8/3.9 (builtin generics need 3.9+/3.10+); broke the
python-lint-types (3.8, 3.9) lanes on #1675/#1677. Use typing.Tuple.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
lmeyerov added a commit that referenced this pull request Jul 2, 2026
… transform)

_cudf_regex_prep is a pure string transform (no cuDF required), but its only
tests were cuDF-gated, so CPU CI left the new lines uncovered and #1675's
changed-line-coverage gate fell to 65.9%. Add direct unit tests for every
branch: non-str/no-flag passthrough, leading (?i)/(?ii) -> case-fold, other
inline flags -> honest NotImplementedError. Remaining uncovered lines are the
is_cudf execution branches (GPU-only; covered by the dgx parity pass).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@lmeyerov
lmeyerov changed the base branch from dev/gfql-polars-contains-regex-fix to dev/gfql-polars-engine July 2, 2026 23:40
lmeyerov added a commit that referenced this pull request Jul 2, 2026
…s (no crash)

GPU-parity pass (viz-filter #1673 item 2) on dgx found `MATCH (n) WHERE n.name =~
'(?i)…'` CRASHES on engine='cudf' with libcudf "invalid regex pattern: nothing to
repeat". Root cause: libcudf's regex engine rejects inline flag groups ((?i)/(?m)/
(?s)) at ANY position (verified: '(?i)abc', '^(?i)abc$', '(?i)^abc$' all raise;
only flag-free '^abc$' works) — not merely a position issue.

Fix: the Match/Fullmatch cuDF branches now translate a leading (?i) to the existing
lowercase case-folding workaround (parity with pandas' (?i)), and honestly decline
any other inline flag with NotImplementedError instead of crashing. Shared helper
_cudf_regex_prep. pandas/polars paths untouched.

Validated on dgx (RAPIDS 26.02): cudf =~ '(?i)a.c' == pandas [2,3] (was RuntimeError);
446 regex/match/fullmatch/contains/numeric tests pass across pandas/polars/cudf; +1
cudf-gated regression test (test_regex_cudf_inline_flag_parity). ruff+mypy clean.

Also confirms viz-filter #1673 item 2: cuDF numeric (floor/ceil/round) + toLower/
toUpper parity OK; polars-gpu is N/A on this branch (#1675 is off #1660, below #1655
which introduces polars-gpu).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@lmeyerov
lmeyerov force-pushed the dev/gfql-cypher-viz-ops branch from f997217 to 31054a8 Compare July 2, 2026 23:40
lmeyerov added a commit that referenced this pull request Jul 2, 2026
…in _cudf_regex_prep

The cuDF =~ flags commit annotated _cudf_regex_prep with "tuple[object, bool]",
which mypy rejects on py3.8/3.9 (builtin generics need 3.9+/3.10+); broke the
python-lint-types (3.8, 3.9) lanes on #1675/#1677. Use typing.Tuple.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
lmeyerov added a commit that referenced this pull request Jul 2, 2026
… transform)

_cudf_regex_prep is a pure string transform (no cuDF required), but its only
tests were cuDF-gated, so CPU CI left the new lines uncovered and #1675's
changed-line-coverage gate fell to 65.9%. Add direct unit tests for every
branch: non-str/no-flag passthrough, leading (?i)/(?ii) -> case-fold, other
inline flags -> honest NotImplementedError. Remaining uncovered lines are the
is_cudf execution branches (GPU-only; covered by the dgx parity pass).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
lmeyerov added a commit that referenced this pull request Jul 4, 2026
…s (no crash)

GPU-parity pass (viz-filter #1673 item 2) on dgx found `MATCH (n) WHERE n.name =~
'(?i)…'` CRASHES on engine='cudf' with libcudf "invalid regex pattern: nothing to
repeat". Root cause: libcudf's regex engine rejects inline flag groups ((?i)/(?m)/
(?s)) at ANY position (verified: '(?i)abc', '^(?i)abc$', '(?i)^abc$' all raise;
only flag-free '^abc$' works) — not merely a position issue.

Fix: the Match/Fullmatch cuDF branches now translate a leading (?i) to the existing
lowercase case-folding workaround (parity with pandas' (?i)), and honestly decline
any other inline flag with NotImplementedError instead of crashing. Shared helper
_cudf_regex_prep. pandas/polars paths untouched.

Validated on dgx (RAPIDS 26.02): cudf =~ '(?i)a.c' == pandas [2,3] (was RuntimeError);
446 regex/match/fullmatch/contains/numeric tests pass across pandas/polars/cudf; +1
cudf-gated regression test (test_regex_cudf_inline_flag_parity). ruff+mypy clean.

Also confirms viz-filter #1673 item 2: cuDF numeric (floor/ceil/round) + toLower/
toUpper parity OK; polars-gpu is N/A on this branch (#1675 is off #1660, below #1655
which introduces polars-gpu).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@lmeyerov
lmeyerov force-pushed the dev/gfql-cypher-viz-ops branch from 31054a8 to 8843b0f Compare July 4, 2026 20:24
lmeyerov added a commit that referenced this pull request Jul 4, 2026
…in _cudf_regex_prep

The cuDF =~ flags commit annotated _cudf_regex_prep with "tuple[object, bool]",
which mypy rejects on py3.8/3.9 (builtin generics need 3.9+/3.10+); broke the
python-lint-types (3.8, 3.9) lanes on #1675/#1677. Use typing.Tuple.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
lmeyerov added a commit that referenced this pull request Jul 4, 2026
… transform)

_cudf_regex_prep is a pure string transform (no cuDF required), but its only
tests were cuDF-gated, so CPU CI left the new lines uncovered and #1675's
changed-line-coverage gate fell to 65.9%. Add direct unit tests for every
branch: non-str/no-flag passthrough, leading (?i)/(?ii) -> case-fold, other
inline flags -> honest NotImplementedError. Remaining uncovered lines are the
is_cudf execution branches (GPU-only; covered by the dgx parity pass).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
lmeyerov added a commit that referenced this pull request Jul 4, 2026
…s (no crash)

GPU-parity pass (viz-filter #1673 item 2) on dgx found `MATCH (n) WHERE n.name =~
'(?i)…'` CRASHES on engine='cudf' with libcudf "invalid regex pattern: nothing to
repeat". Root cause: libcudf's regex engine rejects inline flag groups ((?i)/(?m)/
(?s)) at ANY position (verified: '(?i)abc', '^(?i)abc$', '(?i)^abc$' all raise;
only flag-free '^abc$' works) — not merely a position issue.

Fix: the Match/Fullmatch cuDF branches now translate a leading (?i) to the existing
lowercase case-folding workaround (parity with pandas' (?i)), and honestly decline
any other inline flag with NotImplementedError instead of crashing. Shared helper
_cudf_regex_prep. pandas/polars paths untouched.

Validated on dgx (RAPIDS 26.02): cudf =~ '(?i)a.c' == pandas [2,3] (was RuntimeError);
446 regex/match/fullmatch/contains/numeric tests pass across pandas/polars/cudf; +1
cudf-gated regression test (test_regex_cudf_inline_flag_parity). ruff+mypy clean.

Also confirms viz-filter #1673 item 2: cuDF numeric (floor/ceil/round) + toLower/
toUpper parity OK; polars-gpu is N/A on this branch (#1675 is off #1660, below #1655
which introduces polars-gpu).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@lmeyerov
lmeyerov force-pushed the dev/gfql-cypher-viz-ops branch from 6d65aad to 17b127c Compare July 4, 2026 22:24
lmeyerov added a commit that referenced this pull request Jul 4, 2026
…in _cudf_regex_prep

The cuDF =~ flags commit annotated _cudf_regex_prep with "tuple[object, bool]",
which mypy rejects on py3.8/3.9 (builtin generics need 3.9+/3.10+); broke the
python-lint-types (3.8, 3.9) lanes on #1675/#1677. Use typing.Tuple.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
lmeyerov added a commit that referenced this pull request Jul 4, 2026
… transform)

_cudf_regex_prep is a pure string transform (no cuDF required), but its only
tests were cuDF-gated, so CPU CI left the new lines uncovered and #1675's
changed-line-coverage gate fell to 65.9%. Add direct unit tests for every
branch: non-str/no-flag passthrough, leading (?i)/(?ii) -> case-fold, other
inline flags -> honest NotImplementedError. Remaining uncovered lines are the
is_cudf execution branches (GPU-only; covered by the dgx parity pass).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
lmeyerov added a commit that referenced this pull request Jul 4, 2026
…s (no crash)

GPU-parity pass (viz-filter #1673 item 2) on dgx found `MATCH (n) WHERE n.name =~
'(?i)…'` CRASHES on engine='cudf' with libcudf "invalid regex pattern: nothing to
repeat". Root cause: libcudf's regex engine rejects inline flag groups ((?i)/(?m)/
(?s)) at ANY position (verified: '(?i)abc', '^(?i)abc$', '(?i)^abc$' all raise;
only flag-free '^abc$' works) — not merely a position issue.

Fix: the Match/Fullmatch cuDF branches now translate a leading (?i) to the existing
lowercase case-folding workaround (parity with pandas' (?i)), and honestly decline
any other inline flag with NotImplementedError instead of crashing. Shared helper
_cudf_regex_prep. pandas/polars paths untouched.

Validated on dgx (RAPIDS 26.02): cudf =~ '(?i)a.c' == pandas [2,3] (was RuntimeError);
446 regex/match/fullmatch/contains/numeric tests pass across pandas/polars/cudf; +1
cudf-gated regression test (test_regex_cudf_inline_flag_parity). ruff+mypy clean.

Also confirms viz-filter #1673 item 2: cuDF numeric (floor/ceil/round) + toLower/
toUpper parity OK; polars-gpu is N/A on this branch (#1675 is off #1660, below #1655
which introduces polars-gpu).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
lmeyerov added a commit that referenced this pull request Jul 4, 2026
…in _cudf_regex_prep

The cuDF =~ flags commit annotated _cudf_regex_prep with "tuple[object, bool]",
which mypy rejects on py3.8/3.9 (builtin generics need 3.9+/3.10+); broke the
python-lint-types (3.8, 3.9) lanes on #1675/#1677. Use typing.Tuple.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
lmeyerov added a commit that referenced this pull request Jul 4, 2026
… transform)

_cudf_regex_prep is a pure string transform (no cuDF required), but its only
tests were cuDF-gated, so CPU CI left the new lines uncovered and #1675's
changed-line-coverage gate fell to 65.9%. Add direct unit tests for every
branch: non-str/no-flag passthrough, leading (?i)/(?ii) -> case-fold, other
inline flags -> honest NotImplementedError. Remaining uncovered lines are the
is_cudf execution branches (GPU-only; covered by the dgx parity pass).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@lmeyerov
lmeyerov force-pushed the dev/gfql-cypher-viz-ops branch from 17b127c to e9ba70f Compare July 4, 2026 22:57
@lmeyerov
lmeyerov changed the base branch from dev/gfql-polars-engine to master July 4, 2026 23:24
lmeyerov added a commit that referenced this pull request Jul 4, 2026
…s (no crash)

GPU-parity pass (viz-filter #1673 item 2) on dgx found `MATCH (n) WHERE n.name =~
'(?i)…'` CRASHES on engine='cudf' with libcudf "invalid regex pattern: nothing to
repeat". Root cause: libcudf's regex engine rejects inline flag groups ((?i)/(?m)/
(?s)) at ANY position (verified: '(?i)abc', '^(?i)abc$', '(?i)^abc$' all raise;
only flag-free '^abc$' works) — not merely a position issue.

Fix: the Match/Fullmatch cuDF branches now translate a leading (?i) to the existing
lowercase case-folding workaround (parity with pandas' (?i)), and honestly decline
any other inline flag with NotImplementedError instead of crashing. Shared helper
_cudf_regex_prep. pandas/polars paths untouched.

Validated on dgx (RAPIDS 26.02): cudf =~ '(?i)a.c' == pandas [2,3] (was RuntimeError);
446 regex/match/fullmatch/contains/numeric tests pass across pandas/polars/cudf; +1
cudf-gated regression test (test_regex_cudf_inline_flag_parity). ruff+mypy clean.

Also confirms viz-filter #1673 item 2: cuDF numeric (floor/ceil/round) + toLower/
toUpper parity OK; polars-gpu is N/A on this branch (#1675 is off #1660, below #1655
which introduces polars-gpu).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@lmeyerov lmeyerov changed the title feat(gfql/cypher): standard =~ regex + numeric fns + ^ power + toLower/toUpper (#1673) feat(gfql/cypher): standard =~ regex + numeric fns + toLower/toUpper (#1673) Jul 5, 2026
prefix = _inline_regex_flag_prefix(case, flags)
return c.str.contains(f"{prefix}{pred.pat}", literal=False)

if name in ("Match", "Fullmatch") and hasattr(pred, "pat") and isinstance(pred.pat, str):

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

avoid dynamic typing patterns..

return args[0].sqrt()
# sqrt of a negative -> NaN on both pandas and polars; Float64 cast so sqrt(int)
# returns float like neo4j/pandas; parity-verified.
return args[0].cast(pl.Float64).sqrt()

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

avoid dangerous dynamic typing patterns..

if hasattr(inner, "astype"):
null_mask = self._gfql_null_mask(table_df, inner)
f = inner.astype(float)
if hasattr(f, "ceil" if use_ceil else "floor"): # cuDF native

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

avoid dynamic typing patterns

Comment thread graphistry/compute/predicates/str.py
lmeyerov and others added 18 commits July 5, 2026 14:39
…lars Match/Fullmatch (I1+I2, #1673)

Adopts the STANDARD openCypher/neo4j `=~` regex-match operator rather than
inventing dialect (research: `=~` is standard Cypher; Java-regex, full/anchored
match, inline (?i)/(?m)/(?s) flags; LIKE/ILIKE are SQL-only and stay unimplemented).

Wired end-to-end:
- Cypher WHERE-predicate grammar + expression grammar (`=~`), lowered to the
  existing `fullmatch` predicate (full/anchored match, not partial).
- Shared expr engine: REGEX_MATCH terminal + regex_op -> BinaryOp("regex")
  (high-priority terminal so `=~` beats `=`), allow-listed, evaluated via a
  fullmatch dispatch (pandas/cuDF series + scalar).
- polars `Match`/`Fullmatch` native lowering added (were NotImplementedError),
  so `=~` and match()/fullmatch() predicates run on polars.

Coverage: simple `WHERE prop =~ '...'` on all engines (filter_by_dict);
composes through AND/OR/NOT/RETURN on pandas/cuDF; polars declines the complex
OR/NOT row-filter form with an honest NotImplementedError (pre-existing polars
`where_rows` gap, not =~-specific). Differential-parity tested vs pandas oracle;
1306 tests pass across expr_parser + cypher lowering + polars chain; ruff+mypy
clean. CHANGELOG + docs (docs/source/gfql/cypher.rst).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…r (I3, #1673)

Adopts standard openCypher/neo4j scalar functions + operator (research-verified;
these are standard Cypher, not gaps to invent):
- floor, ceil (alias ceiling), round(x) / round(x, precision) — numeric, return float
- toLower / toUpper — the idiomatic case-insensitive compare
- `^` exponentiation operator — right-associative, binds tighter than * / %,
  returns float (new power grammar tier in both the cypher parser and shared
  expr_parser; allow-listed; evaluated pandas/cuDF + polars)

(abs/sqrt/sign and chained comparison `1 < x < 9` were already supported.)
Differential-parity tested vs pandas oracle; 1575 tests pass across expr_parser,
row pipeline, polars, and cypher lowering; ruff+mypy clean. CHANGELOG + docs.

Two documented minor divergences: round uses numpy default (half-to-even) and
the 3-arg round(x,prec,mode) form is deferred; `-2 ^ 2` folds to (-2)^2 due to
the pre-existing negative-literal lexer (column base `-n.x ^ 2` is correct).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… sign Int64 (review)

Review findings on the =~/numeric-fns branch:
- IMPORTANT: the numeric/^/toLower tests ran only on pandas; parametrize them
  engine=[pandas, polars] so the differential-parity claim is guarded (adds a
  sign() case too). Regression guard for the polars row_pipeline lowerings.
- SUGGESTION: polars sign() returned float vs pandas int; cast to Int64 to match
  the pandas engine and neo4j's Integer-returning spec.

24 parity cases pass (pandas+polars); ruff+mypy clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…th importorskip

The engine=[pandas, polars] parametrization added in the review-fix commit ran
engine='polars' unconditionally in test_lowering.py (a pandas-oriented file with
no module-level polars guard), so it ERRORED (not skipped) in polars-less CI
lanes (test-gfql-core, test-pandas-compat-gfql). Add pytest.importorskip('polars')
on the polars branch of both tests, matching the repo convention (polars-specific
suites already importorskip). Pandas variants unaffected; polars variants run
where polars is installed, skip where it isn't.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… cover numeric scalar/null

CI surfaced two real issues on the numeric/^ increment:
1. tck-gfql (correct corpus): the openCypher TCK marks exponentiation precedence
   (Precedence2) as reject-expected ("expression evaluation not supported") AND
   its expected `4 ^ (3*2) ^ 3 = 4^18` shows the TCK treats `^` as LEFT-
   associative (my impl was right-assoc per neo4j docs). Implementing `^` both
   broke the corpus xfail-contract (execute vs expected-reject) and had ambiguous
   associativity. Reverted `^` — it needs a coordinated corpus xfail update + a
   settled associativity decision. Kept the clean numeric fns + toLower/toUpper.
2. changed-line-coverage: added scalar + null-scalar test coverage for
   floor/ceil/round/toLower/toUpper (86% of changed lines; remaining uncovered
   are cuDF-native branches + pre-existing shifted lines).

Also guards the polars-parametrized tests with importorskip (fixed separately in
f17bdf8). floor/ceil/ceiling/round/toLower/toUpper + =~ unchanged and green.
1607 tests pass; ruff+mypy clean. CHANGELOG + docs updated to drop `^`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…s (no crash)

GPU-parity pass (viz-filter #1673 item 2) on dgx found `MATCH (n) WHERE n.name =~
'(?i)…'` CRASHES on engine='cudf' with libcudf "invalid regex pattern: nothing to
repeat". Root cause: libcudf's regex engine rejects inline flag groups ((?i)/(?m)/
(?s)) at ANY position (verified: '(?i)abc', '^(?i)abc$', '(?i)^abc$' all raise;
only flag-free '^abc$' works) — not merely a position issue.

Fix: the Match/Fullmatch cuDF branches now translate a leading (?i) to the existing
lowercase case-folding workaround (parity with pandas' (?i)), and honestly decline
any other inline flag with NotImplementedError instead of crashing. Shared helper
_cudf_regex_prep. pandas/polars paths untouched.

Validated on dgx (RAPIDS 26.02): cudf =~ '(?i)a.c' == pandas [2,3] (was RuntimeError);
446 regex/match/fullmatch/contains/numeric tests pass across pandas/polars/cudf; +1
cudf-gated regression test (test_regex_cudf_inline_flag_parity). ruff+mypy clean.

Also confirms viz-filter #1673 item 2: cuDF numeric (floor/ceil/round) + toLower/
toUpper parity OK; polars-gpu is N/A on this branch (#1675 is off #1660, below #1655
which introduces polars-gpu).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…in _cudf_regex_prep

The cuDF =~ flags commit annotated _cudf_regex_prep with "tuple[object, bool]",
which mypy rejects on py3.8/3.9 (builtin generics need 3.9+/3.10+); broke the
python-lint-types (3.8, 3.9) lanes on #1675/#1677. Use typing.Tuple.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… transform)

_cudf_regex_prep is a pure string transform (no cuDF required), but its only
tests were cuDF-gated, so CPU CI left the new lines uncovered and #1675's
changed-line-coverage gate fell to 65.9%. Add direct unit tests for every
branch: non-str/no-flag passthrough, leading (?i)/(?ii) -> case-fold, other
inline flags -> honest NotImplementedError. Remaining uncovered lines are the
is_cudf execution branches (GPU-only; covered by the dgx parity pass).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…lane

The polars-parametrized cypher tests (numeric fns, toLower, =~ parity in
graphistry/tests/compute/gfql/cypher/test_lowering.py) never executed in CI:
the core lane has no polars installed (importorskip -> skip) and the polars
lane's fixed file list excluded the file. So the polars numeric lowerings this
branch adds were CI-untested and their changed lines uncovered (coverage gate
76.9% < 80). Add the file (-k polars) to bin/test-polars.sh and the ci.yml
polars coverage step (--cov-append). Moved from the stacked #1677 to here — it
belongs with the lowerings it tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ceil/ceiling/round/toLower/toUpper)

The coverage ledger (cascaded from #1660) correctly flagged the new
GFQL_SCALAR_FUNCTIONS entries as neither exercised nor waived. Real 4-engine
parity-or-NIE cases beat waivers: each new function gets a cypher expression
case through the standard matrix driver (chain + DAG surfaces). dgx: matrix +
ledger 267 passed (cudf + polars-gpu lanes active).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…n standards vetting; not shipped)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… dgx-verified)

Composed =~ on cudf (Fullmatch delegate; honest NIE pass-through; clear invalid-regex
error); cudf alternation anchoring ^(...)$; cudf (?i)-fold declines escape classes;
cudf lookaround/backref decline; polars Match/Fullmatch rust-regex guard; toLower/
toUpper non-string decline (was Series-repr broadcast on the oracle); polars floor/
ceil/round Float64 cast; matrix: =~ family + null-cells + dtype-pin + decline tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…pass-through one frame up, pinned repros

Casefold gate v2: decline only fold-unsound shapes (uppercase escapes, case-crossing
ranges, non-ASCII) — lowercase escapes keep folding (blanket backslash decline
regressed (?i)\d+ to NIE); NIE survives _gfql_eval_string_expr's broad except;
scalar-path re.error clarity; (?P=/(?P< in cudf decline regex; anchored-pattern
passthrough in the compat shim (ordering.py blast radius); discriminating-fixture
pin tests (generic cases passed on pre-fix code).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e, class-scoped

[?-Z]/[Z-~]/[X-^]/[\x41-Z] style ranges (exactly one uppercase-letter endpoint)
shift or go invalid under fold — decline; check scoped to patterns containing a
class so class-free literal hyphens ((?i)e-MAIL) keep folding; docs wording:
uppercase escape classes only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… all decline shapes (wave-4 suggestions)

\x hex escapes spell uppercase letters invisibly to .lower(); cypher.rst caveat
now lists all four fold-decline shapes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…er getattr/hasattr/cast (Leo steer)

isinstance((Match, Fullmatch)) narrowing + direct pred.case/flags; deleted the
dead pre-existing polars Match/Fullmatch block (identical guard above always
returns); _cudf_regex_prep typed str (pat declared+validated; 4 isinstance dances
dropped); isinstance(Literal) replaces the .value getattr probe in round lowering
(also fixes false-accept of other value-bearing nodes); dispatch typed SeriesT.
Net -15 lines; dgx 4-engine lane green (1802+529). Audit
(research/dynamic-patterns-audit.md): ~480 PRE-EXISTING occurrences = follow-up
debt PR (is_series TypeGuard design recorded), sequenced after the stack lands.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…not version-lag)

Rewrites the comments on _CUDF_REGEX_UNSUPPORTED / _cudf_regex_prep /
_cudf_casefold_or_decline so the limits are re-checkable without re-research.
Comment/docstring-only — zero behavior change.

- Lookaround + pattern backreferences: architectural (libcudf regex is
  non-backtracking / finite-automata); NO RAPIDS version restores them. SAME
  limitation on polars + polars-gpu (Rust regex, also non-backtracking) — cross-
  reference the twin guard _regex_rust_incompatible. Only pandas (backtracking re)
  supports them. Verified vs the cuDF 26.02 regex feature doc.
- Inline (?i)/(?m)/(?s): unsupported inline; and the out-of-band flags arg exposes
  only MULTILINE/DOTALL in the Python str.contains binding, NOT IGNORECASE.
- _cudf_casefold_or_decline: the manual lowercase-fold is the ONLY case-insensitive
  regex mechanism cuDF's Python API offers, so its declines are PERMANENT — dgx
  probe (cudf 26.02.01): flags=re.IGNORECASE -> 'unsupported value for flags'
  (only MULTILINE/DOTALL accepted); case=False -> 'only supported when regex=False'.
  libcudf C++ regex_flags has an IGNORECASE bit but the Python StringMethods binding
  does not expose it. Records the settled negative result so it is not re-litigated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
#1682 switched WHERE parsing to LALR (contextual lexer). The =~ rules
textually merged clean but the contextual lexer tokenized '=~' as COMP_OP '='
+ '~' (Earley's dynamic lexer had tried all terminals), so every =~ query
failed to parse. Add a filtered, higher-priority terminal (_REGEX_MATCH.5) so
the lexer prefers '=~' over '='; leading underscore keeps the op token out of
the tree, preserving regex_where/regex_op arity. Add =~ corpus entries to
satisfy #1682's new rule-coverage gate (regex_op via expr path, regex_where via
the lift chain). Grammar invariants (zero LALR conflicts, rule coverage, Earley
differential) + parser + lowering suites green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@lmeyerov
lmeyerov force-pushed the dev/gfql-cypher-viz-ops branch from c61feb7 to 38be679 Compare July 5, 2026 22:10
@lmeyerov
lmeyerov merged commit 4ce3fc0 into master Jul 5, 2026
77 checks passed
@lmeyerov
lmeyerov deleted the dev/gfql-cypher-viz-ops branch July 5, 2026 23:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

1 participant