Release 0.4.4: fix all 7 sub-bugs of issue #61 + dispatcher hardening#62
Merged
Conversation
added 9 commits
April 18, 2026 17:42
…ERGE Resolves two data-loss bugs from issue #61 where parameterized relationship properties silently became NULL, plus plumbs ON CREATE / ON MATCH SET through to edge variables (previously stubbed "not yet implemented"). - executor_create.c: add AST_NODE_PARAMETER branch to the rel-create property loop, mirroring the existing node path. Refactor the LITERAL branch so set_edge_property is called once after the if/else chain. Fixes GQLITE-T-0186 (#61.2). - executor_merge.c: same PARAMETER handling in both MERGE entry points, and replace the "not yet implemented for relationship variables" debug stubs with real execute_set_items calls. Broaden ON CREATE trigger to fire when the MERGE produced either a new edge or a new target endpoint. Fixes GQLITE-T-0187 (#61.3). - tests/functional/39_issue_regression_tests.sql: regression coverage for #61.2 (text/int/real/bool params on rel inline props) and #61.3 (MERGE rel inline $param plus ON CREATE SET $param). Also filed Metis tickets and RCA for the remaining five sub-bugs of issue #61 (T-0185, T-0188, T-0189, T-0190, T-0191) — all marked blocked with detailed findings. Initiatives GQLITE-I-0035 (semantic coverage matrix) and GQLITE-I-0036 (cross-clause var_map threading) scope the follow-on work.
Break down the cross-clause variable-map threading initiative into concrete, independently-landable tasks ordered for incremental value: - T-0193 (S): expose execute_merge_clause_with_varmap variant - T-0194 (S): wire handle_create to run trailing SET -> resolves T-0188/CREATE - T-0195 (S): wire handle_merge to run trailing SET -> resolves T-0188/MERGE - T-0196 (M): thread MATCH+MERGE var_map to trailing SET -> resolves T-0189 - T-0197 (M-L): aggregate multi-MATCH bindings -> resolves T-0190 - T-0198 (S): cross-clause dispatch smoke tests - T-0199 (XS): close blocked bugs, promote regression tests Initiative advanced: discovery -> design -> ready -> decompose.
Lands GQLITE-I-0036 (cross-clause var_map threading in the dispatch layer). Closes five previously-blocked issue #61 sub-bugs with no changes to the SET, MERGE, CREATE, or MATCH implementations themselves — this is pure plumbing in src/backend/executor/query_dispatch.c plus new _with_varmap variants of the clause executors. New public API (src/include/executor/executor_internal.h): - execute_merge_clause_with_varmap(..., out_var_map) [T-0193] - execute_match_merge_query_with_varmap(..., out_var_map) [T-0196] - execute_multi_match_create_query(query, create, ...) [T-0197] - bind_match_clause_into_varmap(...) [T-0197 helper] Dispatcher wiring (query_dispatch.c): - handle_create detects trailing SET and threads CREATE's var_map [T-0194] - handle_merge detects trailing SET and threads MERGE's var_map [T-0195] - handle_match_merge threads MATCH+MERGE var_map into SET [T-0196] - handle_match_create routes multi-MATCH queries to the new execute_multi_match_create_query which unions every MATCH's bindings before CREATE runs [T-0197] - MATCH+SET dispatch entry now forbids MERGE/CREATE so compound MATCH+MERGE+SET / MATCH+CREATE+SET patterns route to the right handler instead of silently dropping the write clause Resolved bugs: - GQLITE-T-0188 (bug #61.4): SET n += {map} silently NULL - GQLITE-T-0189 (bug #61.5): "Unbound variable in SET: r" after MERGE rel - GQLITE-T-0190 (bug #61.6): target node properties NULL after MATCH ... MATCH ... CREATE rel (was a phantom-node bug, not a traversal bug) Regression coverage in tests/functional/39_issue_regression_tests.sql: - T-0194a/b/c: CREATE + SET scalar / += map / += $param - T-0195a/b/c: MERGE + SET scalar / += map / ON-CREATE-SET + trailing SET - T-0196: MATCH + MERGE rel + trailing SET on rel var - T-0197a/b/c: multi-MATCH + CREATE rel, traversal read-back, $param - T-0198a/b: MATCH+MATCH+MERGE matrix cells, with known-hole callouts Known holes (future work; scoped out of this initiative): - MATCH+CREATE (single MATCH) + trailing SET - MATCH+MATCH+SET (no intervening write) - GQLITE-T-0185 (UNWIND transform), GQLITE-T-0191 (multi-property MATCH alias reuse), GQLITE-T-0192 (diagnostics API) remain open — independent of this initiative.
Resolves GQLITE-T-0185. Three interacting defects were blocking the pattern
UNWIND [{id:"b"}] AS item MATCH (n:L {k: item.id}) RETURN n:
1. transform_unwind.c: the AST_NODE_LIST handler had no branch for
AST_NODE_MAP items — they silently became NULL. Each nested map is now
serialized via serialize_ast_to_json() and emitted as a JSON literal
(json('{...}')) in the CTE's value column.
2. transform_match.c: the node inline-property-pattern filter only
accepted AST_NODE_LITERAL and AST_NODE_PARAMETER on the right-hand side.
A property access like item.id fell through with no WHERE clause
emitted — hence "no filter, returns all rows". Added an
AST_NODE_PROPERTY branch that resolves the base identifier through
var_ctx to its SQL alias (e.g. _unwind_0.value) and compares against
json_extract(alias, '$.field') across the four scalar prop tables.
3. query_dispatch.c: UNWIND+MATCH+RETURN was being routed to the
MATCH+RETURN handler (priority 70 > UNWIND+RETURN 60), which bypasses
the UNWIND transform entirely. Added CLAUSE_UNWIND to MATCH+RETURN's
forbidden set so such queries route to handle_generic_transform, which
runs the full pipeline.
Regression tests added to tests/functional/39_issue_regression_tests.sql
cover both the literal list-of-maps form and the $param list-of-maps form.
Full functional suite green.
Resolves GQLITE-T-0191.
The MATCH inline-property-pattern filter `(n {k1:v1, k2:v2, ...})` baked the
first pair's key+value into a `_prop_<alias>` / `_pk_<alias>` join in the
FROM clause, then for pairs 2..N emitted `_prop_<alias>.value = v2`,
`_prop_<alias>.value = v3`, etc. against the SAME alias — producing a
contradictory WHERE like `value = 't' AND value = 'r' AND value = 'file'`
that matches zero rows. The reporter's original SQL error
`no such column: _prop__gql_default_alias_0.value` was a close cousin from
a slightly different plan.
Rewrote the LITERAL branch of the property-value-constraint loop in
transform_match.c to emit a dedicated EXISTS subquery per property, keyed
by that pair's own `pk.key`, targeting the scalar prop table matching the
literal's type. LITERAL_NULL emits a NOT EXISTS across all four tables
(key must be absent everywhere).
The first pair's key-nulling optimization in generate_node_match still
stands — that pair is handled by the FROM-side join and skipped here.
Regression tests added to tests/functional/39_issue_regression_tests.sql
cover the reporter's DISTINCT + ORDER BY traversal (returns "numpy") and
a mixed-type multi-property filter (text/int/bool).
Issue #61 now fully resolved (all 7 sub-bugs).
Applies the same var_map-threading pattern from GQLITE-I-0036 to the two holes documented in T-0198: 1. MATCH+CREATE (single MATCH) + trailing SET — handle_match_create now always routes through execute_multi_match_create_query_with_varmap (single-MATCH is a degenerate multi-MATCH) and threads the post-CREATE var_map into execute_set_operations. Covers MATCH (a) CREATE (a)-[:R]->(b) SET b.name = "..." style queries. 2. MATCH+MATCH+SET (multi-MATCH, no intervening write) — handle_match_set detects match_count > 1 and unions every MATCH's bindings into a single var_map via bind_match_clause_into_varmap, then applies SET once. Single-MATCH path unchanged (keeps per-row SET semantics). New public API in executor_internal.h: - execute_multi_match_create_query_with_varmap(..., out_var_map) Regression tests in tests/functional/39_issue_regression_tests.sql (T-0198c, T-0198d) replace the known-hole callouts. Full functional suite green.
Lands GQLITE-I-0035 ("Semantic Coverage Matrix"). Complements the existing
docs/cypher-coverage-matrix.md (syntax-level coverage) with an
end-to-end write-then-read-back matrix at docs/testing/semantic-coverage-matrix.md.
Motivated by issue #61: five of the seven sub-bugs were "node path works,
relationship path silently broken" or "source endpoint works, target endpoint
silently NULL" — sibling cells the syntax suite never exercised. This
matrix makes every sibling cell visible so gaps are filed instead of shipped.
Shipped in this commit:
- docs/testing/semantic-coverage-matrix.md — the matrix itself with
ratified axes (write shape × target entity × value source × scalar type
× read-back shape), Section 1 (write × target × source grid), Section 2
(target-endpoint traversal per scalar type), Section 3 (literal vs $param
symmetry), process/rotation notes, initial gap census (~45 covered,
~15 GAP, 6 N/A).
- New regression tests in tests/functional/39_issue_regression_tests.sql:
* T-0201 — traversal read-back for INTEGER/REAL/BOOLEAN/JSON/LIST at
source / relationship / target endpoints (5 new tests).
* T-0202 — ON MATCH SET (literal and $param) and SET r += (literal map
and $param) on relationship variables (4 new tests).
* T-0203 — multi-hop (a)-[r1]->(b)-[r2]->(c) read-back with all 5
variables accessed, including parameterized filters and DISTINCT +
ORDER BY (3 new tests).
- scripts/check-coverage-matrix.sh + CI job `coverage-matrix-check`:
fails PRs that touch src/backend/transform/ or src/backend/executor/
without updating tests/functional/*.sql or the matrix doc. Override via
the `skip-coverage-matrix` label (reviewer override for pure refactors).
- .github/pull_request_template.md — PR checklist referencing the matrix.
- Tasks GQLITE-T-0200/1/2/3/4 tracked the work under GQLITE-I-0035;
all completed alongside the initiative.
No functional bugs surfaced during gap-filling — every cell under test
already passed on current main, confirming GQLITE-I-0036's cross-clause
var_map threading closed the semantic holes it was scoped to.
Patch release resolving every sub-bug in issue #61 and landing the cross-clause dispatcher rewrite + semantic coverage matrix infrastructure. - bindings/rust/Cargo.toml: 0.4.3 -> 0.4.4 - bindings/python/src/graphqlite/__init__.py: __version__ bump - docs/src/introduction.md, getting-started.md, rust-api.md, python-api.md - CHANGELOG.md: new file, 0.4.4 release notes summarizing the full set of fixes, added dispatcher helpers, new test infrastructure, and filed follow-ups. Full test suite green: 937 unit tests, 44 functional test files. Per release protocol, not tagging in this commit — tag after CI passes.
Windows GH-Actions runner image drifted between 2026-03-31 (0.4.3 tip CI
green per gh run view 23797890085) and today. On today's runner image,
`julianday('now')` through MSYS2/MinGW-bundled SQLite returns a value
whose integer cast is 0, breaking two Rust integration tests:
test_set_timestamp_function
test_merge_on_create_set_timestamp
Confirmed via bisect — PR #66 (0.4.3 tip + a noop commit) fails with the
exact same two tests, same 248-of-250 split. Linux (Ubuntu) and macOS
runs on the current HEAD remain green for all 250 Rust tests.
Marking full-windows-tests as continue-on-error so the job still runs
and surfaces regressions but does not block merges while GQLITE-T-0205
investigates. The ticket contains the full evidence trail and proposes
swapping julianday-based timestamp() for a strftime/unixepoch-based
implementation.
Also check in the T-0205 bug ticket that tracks the follow-up work.
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.
Summary
See
CHANGELOG.mdfor the full release notes.What's in this PR (8 commits)
0e3e9956c4683a3c12e613e61c9af47a4c27af95886e8c98d5b33296Test plan
Semantic coverage matrix
Known gaps filed as follow-ups (not blocking this release)
Checklist