Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions docs/testing/semantic-coverage-matrix.md
Original file line number Diff line number Diff line change
Expand Up @@ -624,3 +624,35 @@ transition elapsed time), Temporal3 [10] / Temporal2 [6] (offset resolution on a
DST-transition date). `named_tz_offset`'s month approximation is load-bearing
(an accurate last-Sunday-rule swap regressed -29), so DST needs a careful,
empirical, per-zone effort + across-transition interval math. Deferred.

## Coverage update (2026-06-01) — MATERIALIZE non-deterministic WITH/pre CTEs (Quantifier +18)

`sql_builder.c`. Verified via the TCK harness (3758 -> 3776, rigorous full
pass-set diff: zero regressions, +18; unit 944/944; functional clean):

- **A WITH-projected value built with `rand()` is now stable across references.**
SQLite treats a multiply-referenced CTE as a view and re-evaluates its body per
reference, so a `rand()`-derived list got a DIFFERENT value at each use — e.g.
`none(x IN list WHERE p)` and `any(x IN list WHERE p)` over the same `list` saw
different random lists, breaking algebraic identities and yielding inconsistent
rows. `sql_cte`/`sql_pre_cte` now emit `AS MATERIALIZED (...)` when the CTE body
calls a non-deterministic function (detected by `RANDOM(`), forcing single
evaluation. Gated on `RANDOM(` and non-recursive only, so recursive/varlen and
ordinary CTEs are untouched. Fixes Quantifier9/11/12 algebraic-identity cluster
and related (+18 across Quantifier1-12).

## Coverage update (2026-06-01) — property access on an entity inside a list (groundwork)

`transform_expr_ops.c`. Verified via the TCK harness (rigorous full pass-set diff:
zero regressions; unit 944/944; functional clean):

- **`r.name` on a node/relationship that is a list element now reads `.properties`.**
A projected/list-element JSON value (e.g. the quantifier variable in
`any(r IN relationships(p) WHERE r.name = 'a')`) used top-level
`json_extract($.name)`, which is null for an entity (its props live under
`.properties`). The projected-JSON property branch now routes through
`_gql_dyn_prop`, which picks `$.properties.<key>` for entity-shaped objects and
`$.<key>` for plain maps. Correct standalone (the entity-in-list quantifier now
evaluates), though the Quantifier1-4 [8]/[9] scenarios additionally need varlen
`relationships(p)`/`nodes(p)` through an aggregating WITH + GROUP-BY-on-list
(deeper, deferred).
14 changes: 12 additions & 2 deletions src/backend/transform/sql_builder.c
Original file line number Diff line number Diff line change
Expand Up @@ -537,7 +537,15 @@ void sql_cte(sql_builder *b, const char *name, const char *query, bool recursive
dbuf_append(&b->cte, ", ");
}

dbuf_appendf(&b->cte, "%s AS (%s)", name, query);
/* A CTE whose body calls a non-deterministic function (rand() ->
* RANDOM()) must be MATERIALIZED: SQLite otherwise treats a multiply-
* referenced CTE as a view and re-evaluates RANDOM() per reference, so a
* WITH-projected random list yields a DIFFERENT value at each use (e.g.
* none()/any() over it see different lists — Quantifier9/11/12). Gating on
* RANDOM() keeps recursive/UNWIND CTEs untouched. Non-recursive only
* (MATERIALIZED is invalid on a recursive CTE). */
bool materialize = !recursive && strstr(query, "RANDOM(") != NULL;
dbuf_appendf(&b->cte, "%s AS %s(%s)", name, materialize ? "MATERIALIZED " : "", query);
b->cte_count++;
}

Expand All @@ -555,7 +563,9 @@ void sql_pre_cte(sql_builder *b, const char *name, const char *query, bool recur
if (b->pre_cte_count > 0) {
dbuf_append(&b->pre_cte, ", ");
}
dbuf_appendf(&b->pre_cte, "%s AS (%s)", name, query);
/* MATERIALIZED when the body is non-deterministic — see sql_cte(). */
bool materialize = !recursive && strstr(query, "RANDOM(") != NULL;
dbuf_appendf(&b->pre_cte, "%s AS %s(%s)", name, materialize ? "MATERIALIZED " : "", query);
b->pre_cte_count++;
if (recursive) b->pre_cte_recursive = true;
}
Expand Down
2 changes: 1 addition & 1 deletion src/backend/transform/transform_expr_ops.c
Original file line number Diff line number Diff line change
Expand Up @@ -803,7 +803,7 @@ int transform_property_access(cypher_transform_context *ctx, cypher_property *pr
* parser. (Temporal5 [7].) */
append_sql(ctx,
"CASE WHEN json_valid(%s) AND instr(%s, '\"_iso8601\"') > 0 THEN _gql_duration_field(%s, '%s')"
" WHEN json_valid(%s) THEN json_extract(%s, '$.%s')"
" WHEN json_valid(%s) THEN _gql_dyn_prop(%s, '%s')"
" ELSE _gql_temporal_field(%s, '%s') END",
alias, alias, alias, pn,
alias, alias, pn,
Expand Down
Loading