Skip to content

fix(gfql): require explicit epoch-ms comparisons - #2197

Merged
lmeyerov merged 1 commit into
masterfrom
fix/gfql-json-epoch-compat
Oct 15, 2026
Merged

lmeyerov merged 1 commit into
masterfrom
fix/gfql-json-epoch-compat

Conversation

@lmeyerov

@lmeyerov lmeyerov commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Fixes GFQL: int epoch-ms comparison against a datetime64 column silently returns 0 rows since 0.58.0 (was a loud error before) #2194 by rejecting bare numeric comparisons against datetime columns with structured GFQLSchemaError(E302).
  • Adds explicit UTC epoch datetime values through the existing DateTimeValue model: DateTimeValue.from_epoch(value, *, unit), where unit is required and must be "s" or "ms".
  • Extends the existing datetime wire input with an explicit epoch variant: {"type":"datetime","value":<integer>,"unit":"s"|"ms"}.
  • Rejects epoch_ms payloads as unsupported E201; there is no predicate-level epoch flag, legacy alias, default unit, or separate epoch value class.
  • Canonical serialization for epoch-created values remains the existing typed ISO UTC datetime. Existing ISO-created values retain their established timezone behavior.
  • Validates booleans, fractions, nonfinite values, overflow, invalid units, and conflicting fields without rounding or NaT coercion. Numeric JSON epoch values are bounded to ±(2^53−1); native Python/NumPy integers remain exact.
  • Adds native Polars lowering for typed datetime values, including timezone-aware columns, while preserving the pandas and cuDF UTC/timezone behavior.
ge(DateTimeValue.from_epoch(1578528000000, unit="ms"))
between(
    DateTimeValue.from_epoch(1577836800, unit="s"),
    DateTimeValue.from_epoch(1578614400000, unit="ms"),
)

Evidence

Review round R1-R3 is resolved: Polars no longer narrows wide bounds through nanoseconds; invalid epoch representations return structured E201; NumPy extended-precision values are validated before narrowing. At the exact head below, the adapted regressions and positive controls pass 63/63 on both DGX cuDF lanes.
Exact head: b557280e96bbf58095d442a9517e27afb23b05d6
Exact tree: a7c11dece5f65950028b205bc519535938e32b31
Base: master@0c71cc4bb8281ecc1f07de8657c4ec45c737a5c1

CPU / Polars at the exact head:

Full GFQL core suite (CI-equivalent, CPU):          17,405 passed, 4,661 skipped
R1-R3 regressions + lane completeness:              27+ passed, incl. Polars-only
Ruff: all changed files pass
Type-hygiene and comment-density guards: no growth
ast_temporal.py coverage at/above the CI per-file floor (97% local replication)
Regression file registered in the Polars test lane; Polars-only cases
use a local importorskip so core lanes still run the CPU tests.
Hosted gates at the exact head: CI Tests SUCCESS (run 38729623415),
RTD docs/readthedocs.org:pygraphistry SUCCESS.

DGX cuDF at the exact head:

RAPIDS 26.02: 63 passed + typed DateTimeValue cuDF smoke PASS
receipt: /tmp/dgx-gfql-receipts/pr2197-epoch-b557280e-dgx-v19
RAPIDS 25.02: 63 passed + typed DateTimeValue cuDF smoke PASS
receipt: /tmp/dgx-gfql-receipts/pr2197-epoch-b557280e-dgx-v20

Coverage includes all six comparisons plus Between, mixed s/ms bounds, Python and JSON entrances, semantic round-trips, epoch zero/negative values, invalid inputs, exact-ms boundaries against finer-resolution columns, timezone/naive and DST cases, empty/all-null frames, genuine no-match controls, numeric-column controls, and existing ISO compatibility.

Notes

  • Epoch counts denote UTC instants; no timezone field is accepted on the epoch wire variant.
  • Naive datetime columns retain GFQL's documented UTC assumption.
  • Date, time, duration conversion, temporal arithmetic, and broader Cypher mixed-type semantics remain out of scope.
  • Combined API change is a 0.61.0 candidate; a rejection-only 0.60.1 split is optional and not part of this PR.

@lmeyerov
lmeyerov force-pushed the fix/gfql-json-epoch-compat branch 5 times, most recently from 97b258b to 3328369 Compare October 14, 2026 16:02
@lmeyerov lmeyerov self-assigned this Oct 14, 2026
@lmeyerov
lmeyerov force-pushed the fix/gfql-json-epoch-compat branch 9 times, most recently from 86c7606 to 286f0f2 Compare October 15, 2026 11:04
A bare numeric bound is never implicitly an instant: comparison predicates and
Between raise a structured E302 against datetime columns. Explicit UTC epoch
instants are typed datetime values: DateTimeValue.from_epoch(count, unit='s'|'ms')
on the Python side and {"type":"datetime","value":<count>,"unit":"s"|"ms"} on the
wire, with required unit, JSON-safe range on wire numerics, exact integral-float
handling, representability checks, and canonical ISO-UTC serialization. The
unreleased epoch_ms predicate flag is removed everywhere (API, wire, docs) and
rejected clearly on decode. Polars gains native DateTimeValue comparison lowering
(exact integer-epoch bounds at column resolution, naive-UTC instant semantics,
honest declines for unrepresentable bounds).
@lmeyerov
lmeyerov force-pushed the fix/gfql-json-epoch-compat branch from 286f0f2 to b557280 Compare October 15, 2026 11:45
@lmeyerov
lmeyerov merged commit 20cf65b into master Oct 15, 2026
93 checks passed
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: int epoch-ms comparison against a datetime64 column silently returns 0 rows since 0.58.0 (was a loud error before)

1 participant