TCK batch: exists + SET-entity + NaN + ORDER BY comparator + Any-type funcs (+18) - #87
Merged
Conversation
added 2 commits
May 29, 2026 07:46
`MATCH (n) WHERE exists { (n)-->(m) WHERE n.prop = m.prop } RETURN n`
(ExistentialSubquery1 [2]/[4]) needs the brace form to (a) parse an inner
WHERE and (b) allow fresh inner pattern variables (`m`, `r`).
- cypher_exists_expr gains `where_clause` (inner predicate) and
`is_subquery` (brace vs paren pattern-predicate). Grammar rule
`EXISTS '{' pattern_list WHERE expr '}'` populates both.
- The EXISTS_TYPE_PATTERN emitter now registers the inner pattern's NEW
node/rel variables against their subquery aliases (n%d / e%d) before
transforming the inner WHERE, folds it in as ` AND (<expr>)`, then
transform_var_truncate_to restores the outer scope (mirrors the
pattern-comprehension save/restore).
- The WHERE fresh-variable validator skips is_subquery EXISTS nodes: the
brace form legitimately scopes fresh vars; the paren pattern-predicate
keeps the stricter rule.
Struct field added -> angreal dev clean. No bison conflicts (still
`%expect 15` / `%expect-rr 3`).
Fixes ExistentialSubquery1 [2] and [4] (all of ExistentialSubquery1 now
green). Zero TCK regressions. 3710 -> 3712. Unit 944/944, functional clean.
`MERGE (a)-[r:TYPE]->(b) ON CREATE SET r = a` (Merge6 [6]) and the ON MATCH
variant (Merge7 [4]) copy all of node `a`'s properties onto rel `r`. The
bulk-SET handler only accepted a map literal or JSON parameter as the RHS and
errored ("Bulk SET value must be a map literal or parameter") on an entity
identifier, so the copy silently produced no properties.
Added `copy_entity_properties()`: reads the source entity's five
property-type tables (text/int/real/bool/json) joined to property_keys and
re-sets each on the destination via cypher_schema_set_{node,edge}_property,
incrementing result->properties_set. Wired into the bulk-SET path as a new
RHS case (AST_NODE_IDENTIFIER); replace mode (`=`) reuses the existing
delete-all-first step, `+=` merges.
Fixes Merge6 [6] and Merge7 [4]. Merge8 [1] / Merge9 [3] still fail on the
separate multi-row MATCH+MERGE cartesian-iteration gap (deferred). Zero TCK
regressions. 3712 -> 3714. Unit 944/944, functional clean.
SQLite collapses float division-by-zero to NULL at the operator level, so a NaN value can neither survive as a native double nor be distinguished from null at runtime. Every NaN TCK scenario produces NaN via the literal constant `0.0 / 0.0`, so detect that shape at compile time and emit the correct Cypher comparison result directly. is_nan_const() recognizes `DIV(0-lit, 0-lit)`. classify_nan_other() buckets the other operand (number / string / null / non-null / unknown). For a NaN operand the comparison emits a raw SQL truth value (1/0/NULL — the same shape a native comparison yields, so an enclosing _gql_bool_str(CASE WHEN ...) or WHERE filter evaluates it correctly; a tagged 'true'/'false' text would be re-read as falsy): NaN = x -> false (x non-null; vs null -> null) NaN <> x -> true (x non-null; vs null -> null) NaN </<=/>/>= number-or-NaN -> false; vs other type -> null Falls through untouched when the other operand is not a compile-time literal (so non-NaN comparisons are unaffected — verified by a rigorous full pass-set diff: zero regressions, exactly 7 newly passing). Fixes Comparison1 [8] (4 examples) and Comparison2 [5] (3 examples). NaN via a variable (ReturnOrderBy1 [11]/[12], Comparison2 [3]) needs the cross-type total-ordering comparator and is deferred. 3714 -> 3721. Unit 944/944, functional clean.
added 5 commits
May 29, 2026 14:18
…ectness, +0 TCK)
`MATCH p = (n)-[r]->() UNWIND [n, r, p, ...] AS x` crashed with
`no such column: _gql_default_alias_0.id`. The UNWIND LIST branch emitted a
per-UNION-arm FROM clause only when a WITH projection was being carried
(has_carry); pre-WITH MATCH node/edge variables are deliberately excluded from
carry (their alias is a table alias, not an id column ref), so a list whose
elements reference bound entities produced arms like
`SELECT json_object('id', _gql_default_alias_0.id, ...) AS value` with no FROM
— the aliases were unbound.
The LIST branch now splices the prior MATCH's FROM tables (and re-attaches its
WHERE) into each UNION arm when inner_sql is a splicable `SELECT * FROM ...`,
mirroring the existing function-call/subscript/binary-op branch. Element
expressions keep referencing the original aliases, which are now in scope, and
cardinality correctly tracks the surrounding MATCH.
Prerequisite for the ORDER-BY total-ordering scenarios (ReturnOrderBy1
[11]/[12], WithOrderBy1 [21]/[22]): they no longer crash (error -> fail) but
still need a Cypher orderability key in _gql_order_key, a distinguishable NaN
value, and path-through-UNWIND hydration — all deferred.
Rigorous full pass-set diff vs prior HEAD: zero regressions, zero newly
passing (the 4 scenarios move error -> fail). 3721 pass unchanged. Unit
944/944, functional clean.
…ness, +0 TCK) ORDER BY over heterogeneous values used SQLite's native storage-class order (null < number < text < blob), which is wrong for Cypher. Cypher orderability is map < node < rel < list < path < string < bool < number < NaN < null. New `_gql_order_rank(value)` UDF returns the integer type rank 0..9 (JSON entity/map/path shapes distinguished by their distinctive keys: nodes&rels -> path, labels -> node, startNode* -> rel, else map). `sql_order_by` (sql_builder.c) and the WITH ORDER-BY path (transform_with.c) now emit `_gql_order_rank(e) <dir>, _gql_order_key(e) <dir>`: the rank groups by type for the correct cross-type order, and the existing `_gql_order_key` sorts within each (now homogeneous) rank. test_sql_builder.c assertions updated to the two-key form. This is the GQLITE-T-0340 comparator. It is standalone correctness groundwork: the mixed-type ORDER-BY TCK scenarios (ReturnOrderBy1 [11]/[12], WithOrderBy1 [21]/[22]) additionally require a renderable NaN value and path-through-UNWIND hydration, both deferred (tracked in GQLITE-T-0340). The rank UDF already detects the planned NaN sentinel (rank 8) for forward-compat. Rigorous full pass-set diff: zero regressions, zero newly passing. Mixed-type ORDER BY now verifiably sorts map<node<rel<list<string<bool<number. Unit 944/944, functional clean. 3721 pass unchanged.
…ure B) SQLite collapses float division-by-zero to NULL and drops result subtypes across CTE boundaries, so a runtime NaN can neither survive as a native double nor be distinguished from null. Carry NaN as the private string GQL_NAN_SENTINEL (0x01 'N' 'a' 'N') — recognized by content, collision-proof (the leading control byte can't begin a real Cypher string). - transform_expr_ops.c: standalone `0.0/0.0` emits `(CHAR(1) || 'NaN')` (the comparison-operand case is still folded at compile time by the earlier is_cmp NaN block, so this only fires for non-comparison NaN constants). - executor_match.c create_property_agtype_value: map the sentinel to a float NaN agtype so entity/agtype result rendering emits the bare token. - agtype.c AGTV_FLOAT serializer: render isnan() as `NaN` (not "nan"). - extension.c plain formatter: print the sentinel as `NaN`. Combined with the orderability rank (sub-feature A), this fixes WithOrderBy1 [22]. ReturnOrderBy1 [11]/[12] and WithOrderBy1 [21] now order correctly and fail only on path-as-list-element rendering (sub-feature C, deferred). Rigorous full pass-set diff: zero regressions, +1 (WithOrderBy1 [22]). 3721 -> 3722. Unit 944/944, functional clean.
…340 sub-feature C)
A path variable used as a list element (`UNWIND [n, r, p, ...]`) rendered as
the raw elem_ids array `[1,1,2]` instead of the `{nodes,rels}` path object,
because the executor's elem_ids post-hydration (build_path_from_ids) only
reaches top-level RETURN columns — not a value buried inside an UNWIND row.
Added a transform-context flag `emit_hydrated_path`. When set, the path
projection in transform_expression emits the self-contained fully-hydrated path
JSON (reusing the pattern-comprehension builder: json_object('nodes',
json_array(...), 'rels', json_array(...))) for non-single-varlen paths instead
of elem_ids. transform_unwind sets the flag around each list-element expression
transform and restores it after.
Completes the GQLITE-T-0340 type-ordering stack:
A (orderability rank, 22b00a7) + B (renderable NaN, d74210e) + C (this).
Mixed-type ORDER BY now fully follows Cypher orderability
map<node<rel<list<path<string<bool<number<NaN<null.
Fixes ReturnOrderBy1 [11]/[12] and WithOrderBy1 [21] (WithOrderBy1 [22] landed
with B). Struct field added -> angreal dev clean. 3722 -> 3725. Unit 944/944,
functional clean.
`labels(list[0])`, `type(list[0])`, `keys($param)` were rejected at compile time because these functions only accepted a bare node/rel/map identifier argument. openCypher types a list/subscript element (and a parameter) as Any, resolved at runtime. - labels()/type() on a non-identifier now emit new `_gql_labels` / `_gql_type` UDFs. They inspect the runtime value: a node/relationship JSON object yields its labels/type; null yields null; anything else raises `TypeError: InvalidArgumentValue` (sqlite3_result_error, which the harness classifies as TypeError). This satisfies both the accept-Any scenarios (Graph3 [6], Graph4 [5]) and the fail-on-invalid scenario (Graph3 [9]), which a naive json_extract would have regressed. - keys() on a parameter/expression emits a single-eval subquery over json_each, using the value's `properties` object when present (node/rel) else the value's own keys (map). Fixes Map3 [2]. Rigorous full pass-set diff: zero regressions, +3. 3725 -> 3728. Unit 944/944, functional clean.
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.
A batch of independent openCypher TCK conformance fixes, stacked on one branch for a single squash-merge. 3710 → 3728 (+18 net), zero regressions — every fix verified with a rigorous full pass-set diff.
Fixes (TCK delta)
SET r = acopies entity properties (Merge6 [6], Merge7 [4])0.0/0.0(Comparison1 [8], Comparison2 [5])_gql_order_rankUDF + two-key ORDER BY →map<node<rel<list<path<string<bool<number<NaN<nullNaN) — WithOrderBy1 [22]emit_hydrated_pathflag) — ReturnOrderBy1 [11]/[12], WithOrderBy1 [21]labels()/type()/keys()accept type Any via_gql_labels/_gql_typeUDFs that raise a runtime TypeError on invalid values (Graph3 [6], Graph4 [5], Map3 [2]; Graph3 [9] still correctly errors)Verification
Unit 944/944, functional clean throughout. Two struct fields added →
angreal dev clean. No bison conflicts. Each fix has a rigorous per-fix pass-set diff showing zero regressions.Design + findings for the comparator stack recorded in Metis GQLITE-T-0340.
Deferred (each a larger feature, surveyed): smallest-int literal (
-INT64_MIN, needs scanner/token/AST flag), boolean type preservation through params/lists (List1 [9], TypeConversion4 [5]), varlen path-length tracking (Path3 [1]), full/nested existential subqueries, multi-row MATCH+MERGE, WITH-WHERE input-scope (Comparison2 [3]).