Feat/iso temporal values#77
Merged
Merged
Conversation
MVP of the ISO temporal subsystem, staged like Path values were:
- Value::Date (days since epoch, proleptic Gregorian) and
Value::LocalDatetime (millis, no timezone), with dependency-free
civil<->days conversion, strict ISO-8601 parsing (leap years
validated) and round-trippable formatting.
- §20.27 constructors as soft keywords (call form only): DATE()/
LOCAL_DATETIME() = the CURRENT_* equivalents per SR 1-3, one-arg
forms take a <date/datetime string> or a field record. Malformed
input degrades to Null, the CAST failure-as-null convention.
- Typechecker: SimpleType::Date / LocalDatetime are base types,
orderable per §22.14 without GA04; provably wrong constructor
arguments are hard errors.
- Runtime: total order within each type (WHERE / ORDER BY / MIN/MAX),
equality + hashing for GROUP BY and DISTINCT, cross-type pairs never
compare, and §20.26 MVP arithmetic (LOCAL_DATETIME ± integer millis,
the form the DURATION({...}) desugaring produces).
- The generic Binop dispatch now routes temporal operand pairs past
the numeric-cast coercion (which silently nulled them) and inlines
the delta cast checks without re-evaluating operands.
- Out of scope, documented: ZONED_* (timezones), typed durations, and
temporal property storage (value_to_prop rejects them explicitly,
same treatment as Path).
Python/Node/WASM bindings return ISO-8601 strings; REPL and dumps
print constructor syntax. 13 integration tests in temporal_test.rs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
# Conflicts: # src/runtime/engine.rs
…rción de operandos El merge de la 3VL (#73) rompía pushdown_null_test: el despacho de la rama temporal inlineó el cast de operandos pero omitió el caso 'null inhabits every type' que la 3VL agregó al brazo AS. Un operando null devolvía Failure en vez de fluir como Null a la lógica 3VL, de modo que 'x.a = 999 OR true' con x.a ausente tiraba la fila en vez de conservarla. Ahora la coerción inline trata null explícitamente, idéntico al brazo AS de main; los temporales conservan su bypass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
matiastoro
added a commit
that referenced
this pull request
Jul 9, 2026
Bindings open() now creates the DB when the path is absent (issue raised via JS/VS Code usage). Python and Node open() switched from LazyGraphStore::open to open_or_create, matching the CLI REPL and sqlite3.connect: a missing path yields an empty DB (DEFAULT active) ready for INSERT + save(). Existing-path behaviour unchanged. Migrate node/ to @napi-rs/cli 3.x (root fix for the VS Code extension-host crash). The 2.x-generated loader read process.report.getReport().header unguarded; getReport() returns undefined in the Linux extension host, so .header threw 'Cannot read properties of undefined' and aborted activation before any frogql call. napi-cli 2.x (2.18.4, the last 2.x) still emits the unguarded form, so the fix is the 3.x toolchain whose loader detects musl through a guarded filesystem -> process.report -> child-process chain, falling back to glibc when the report is unavailable (frogql ships only linux-*-gnu). Migration details: - crates napi 3.10 / napi-derive 3.5 (napi-build stays 2 — no 3.x). Pin >=3.10: napi-derive 3.5's generated code references bindgen_prelude symbols absent from napi 3.8. Binding source (607 lines) unchanged. - package.json config napi.binaryName + napi.targets (2.x name + triples deprecated); build scripts drop the stopgap loader patch. - release-npm.yml: create-npm-dir -> create-npm-dirs, artifacts gets explicit --output-dir/--npm-dir, aarch64-linux --zig -> --use-napi-cross (zig setup step removed). Verified: cargo fmt + clippy clean, full cargo test sweep green, node smoke 8/8, typecheck OK, loader loads with getReport()->undefined, open_or_create create-on-open confirmed end-to-end. Bundles the already-merged ISO temporal values (PR #77) into the release. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
matiastoro
added a commit
that referenced
this pull request
Jul 9, 2026
… 1.97) Pre-existing lint inherited from the ISO temporal PR (#77), which merged with a red CI. clippy 1.97's question_mark lint flags the `match Option { Some(v) => v.len(), None => return None }` in the btree-ltj-real top-k path; `?` is semantically identical. Local clippy was 1.95 (didn't flag it); bumped the toolchain to 1.97 to match CI. No behaviour change — the already-published v0.2.8 artifact compiles fine (clippy is a lint, not a compile error); this just greens main. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
feat(temporal): valores ISO §20.27
DATEyLOCAL DATETIMEcomo valores de consultaRama:
feat/iso-temporal-values→mainAlcance: primer avance del subsistema temporal ISO/IEC 39075:2024
(§4.16.6, §20.27), completo y probado dentro de su alcance. Query-time only.
Resumen
Agrega los tipos temporales
DATEyLOCAL DATETIMEcomo valores deconsulta, con la misma disciplina por etapas que tuvieron los paths: todo lo
que queda fuera falla con un error explícito en vez de funcionar a medias.
Ninguna consulta devuelve resultados incorrectos por usar temporales; algunas
dicen "esto aún no".
Motivación
Salió de la evaluación de conformidad: LDBC IC10 queda fuera porque su
predicado de cumpleaños necesita extraer el mes y el día de una fecha, y
GQL define constructores de valores temporales pero no extractores. Esta rama
comprueba esa lectura con código: implementa los constructores §20.27, y
confirma que aun con ellos IC10 sigue sin forma ISO-conforme (lo que falta son
los extractores, que el estándar no tiene).
Qué cambia
Modelo de valores (
src/model/value.rs)Value::Date(i32)= días desde 1970-01-01 (Gregoriano proléptico).Value::LocalDatetime(i64)= milisegundos desde esa medianoche, sin zonahoraria.
clásica; el Gregoriano se repite exacto cada 400 años). Parseo ISO-8601
estricto con validación de calendario (años bisiestos incluidos) y formato
de vuelta.
Constructores §20.27 (
src/parser/grammar.rs), soft keywords (sólo laforma de llamada es especial;
datesigue usable como variable/label):DATE('2010-01-05')/LOCAL_DATETIME('2010-01-05T08:30:15')desde string.DATE({year:, month:, day:})desde record de campos (sintaxis del estándar).DATE()/LOCAL_DATETIME()sin argumento = los equivalentes deCURRENT_DATE/LOCAL_TIMESTAMP(SR 1-3).Null(convención failure-as-null deCAST).Typechecker (
src/typing/)SimpleType::Date/LocalDatetimecomo tipos base: meet, refine yunión funcionan; ordenables per §22.14 sin requerir GA04, por lo que son
llaves válidas de
ORDER BY,GROUP BYyDISTINCT.DATE(x) : DATE); un argumentoprobadamente incorrecto (
DATE(true)) es error duro de compilación.Distinción tipo-vs-valor:
DATE(true)falla por tipo (rechazo estático),DATE('hola')falla por valor (→Nullen runtime, comoCAST).Runtime (
src/runtime/)WHERE,ORDER BY,MIN/MAX); igualdady hashing para
GROUP BYyDISTINCT; tipos distintos nunca comparan.LOCAL_DATETIME(...) ± <enteros de milisegundos>, laforma que produce el desazucarado existente de
DURATION({...}).ambos operandos a numérico antes de evaluar, lo que degradaba silenciosamente
los temporales a
Null. Se agrega un camino tipado para pares temporales yse deja la coerción intacta para el resto (de paso se elimina una doble
evaluación de operandos en esa ruta).
Bindings y utilidades
DATE('...')), round-trippable.Fuera de alcance (documentado, con error explícito)
ZONED_*(zonas horarias reales: IANA + horario de verano).DURATIONcomo tipo propio (su álgebra separa año-mes de día-tiempo, que noconmutan).
DURATION_BETWEEN).value_to_proplo rechaza conerror explícito, el mismo trato que
Path.Tests
tests/temporal_test.rs(13 casos): las tres formas de construcción, ordencronológico en
ORDER BY, agrupación por fecha, no comparación entre tiposdistintos, validación de calendario (
DATE('2023-02-29')→Null; el bisiesto2024-02-29sí válido), aritmética con duraciones, y los dos comportamientosdel typechecker (aceptar fechas como llave de orden, rechazar
DATE(true)).Diámetro del cambio
17 archivos, +655 líneas. Rama desde
origin/main(v0.2.7); mergea limpio.Pre-commit
cargo fmt --all·cargo clippy --workspace --all-targets -- -D clippy::all· sweep completo de tests + suites sensibles a expresiones
(floor/cast, división, records, ic7, aggregates): verde, cero regresiones.
Notas para el revisor
que comparar, ordenar y agrupar fechas reutilice toda la maquinaria existente
sin cambios.
decisión
★ = base-onlydel chequeo deORDER BY.