From faf56d682e706bfc602acfa0746faaa5394aa0ee Mon Sep 17 00:00:00 2001 From: Dylan Bobby Storey Date: Wed, 3 Jun 2026 21:00:59 -0400 Subject: [PATCH 1/2] =?UTF-8?q?release:=200.6.0=20=E2=80=94=20openCypher?= =?UTF-8?q?=20TCK=2091.5%=E2=86=9297.7%=20(+239)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bump python/rust binding versions to 0.6.0, add CHANGELOG [0.6.0] entry summarizing the conformance work since 0.5.0 (pattern/path matching, temporal, quantifiers, WITH/ORDER BY/aggregation, existential subqueries, validation), and document current openCypher TCK coverage in the README (97.7% overall; expressions 98.0%, clauses 97.2%, use cases 100%). --- CHANGELOG.md | 52 ++++++++++++++++++++++ README.md | 19 ++++++++ bindings/python/src/graphqlite/__init__.py | 2 +- bindings/rust/Cargo.lock | 2 +- bindings/rust/Cargo.toml | 2 +- 5 files changed, 74 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 182b2335..30c8e18b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,58 @@ All notable changes to GraphQLite are documented here. Format loosely follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions follow [Semantic Versioning](https://semver.org/). +## [0.6.0] — 2026-06-03 + +A large openCypher conformance release. TCK pass rate moves from **91.5%** +(3549 executable scenarios) to **97.7%** (3788 / 3876), **+239 scenarios** with +zero regressions among previously-passing scenarios. 944/944 unit tests pass; +functional tests clean. + +### Added — openCypher coverage + +- **Pattern & path matching completeness** — variable-length path hydration, + `relationships()` / `nodes()` on varlen and null paths, path-wide relationship + uniqueness, multi-rel OPTIONAL MATCH with full-pattern (all-or-none) semantics, + bidirectional bracketed relationship patterns, and four-label conjunctions in + expression context. +- **Temporal** — closed the non-DST temporal cluster: duration arithmetic and + ISO round-trip normalization (months not normalized across days; 1 month = + 30.436875 days), `duration.between`, component accessors, `date`/`time`/ + `datetime`/`localdatetime` construction and selection, named-zone handling, and + UTC-instant ordering. Cross-type ordering now uses an orderability comparator + with a NaN sentinel and an overflow-safe `(seconds, nanoseconds)` temporal + comparison (far-future years no longer wrap int64 epoch-nanoseconds). +- **Quantifiers** — `all` / `any` / `none` / `single` over list pipelines now + evaluate consistently; non-deterministic CTEs (`rand()`/`RANDOM()` in WITH + projections) are emitted `AS MATERIALIZED` so a multiply-referenced list is + evaluated once, fixing algebraic-identity scenarios. +- **WITH / ORDER BY / aggregation** — `ORDER BY` on a non-aggregating WITH now + flows to a downstream aggregating WITH (`… ORDER BY x WITH collect(y)` collects + in sorted order); computed (non-aggregate) WITH keys (e.g. `a.n % 3 AS m`) are + now GROUP BY keys; `LIMIT`/`OFFSET` inline into the WITH CTE body. +- **Existential subqueries** — `EXISTS { pattern }` brace form. +- **Validation** — compile-time `SyntaxError` diagnostics (pattern expressions in + `SET` RHS, relationship-uniqueness, label/type 3-valued-logic predicates, and + more). + +### Fixed + +- Multi-row `MATCH … CREATE` / `MATCH … DELETE … CREATE` runs the write per + matched row; inter-pattern variable references in `CREATE` (`{x: a.id}`) + resolve against earlier patterns; `RETURN` is pre-captured before `DELETE`. +- `REMOVE` on a null/unbound variable is a no-op; `RETURN *` excludes synthetic + anonymous aliases. +- General-expression projection in `CREATE … RETURN` and `SET` (list/JSON props). + +### Notes + +Remaining gaps (~2.3%) concentrate in DST-aware timezone arithmetic, nested +existential subqueries, multi-row MERGE binding, and boolean subtype preservation +(SQLite has no native boolean type). See +[`docs/testing/semantic-coverage-matrix.md`](docs/testing/semantic-coverage-matrix.md). +Windows extension tests (`test_*_timestamp*`) remain non-blocking pending an +MSYS2/MinGW `julianday('now')` regression (GQLITE-T-0205). + ## [0.5.0] — 2026-05-22 Significant TCK conformance release plus the first-time Windows extension diff --git a/README.md b/README.md index f5f6bedc..83a1e099 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,25 @@ g.dijkstra("alice", "bob") - **Zero configuration** — Works with any SQLite database, no server required - **Multiple bindings** — Python, Rust, and raw SQL interfaces +## openCypher Conformance + +GraphQLite is validated against the official [openCypher Technology Compatibility +Kit (TCK)](https://github.com/opencypher/openCypher) — the canonical conformance +suite for the Cypher query language. Current coverage: + +| Area | Scenarios | Passing | +|------|-----------|---------| +| **Overall** | **3,876** | **97.7%** | +| Expressions (temporal, lists, maps, comparison, literals, …) | 2,599 | 98.0% | +| Clauses (MATCH, WITH, MERGE, CREATE, SET, DELETE, UNWIND, …) | 1,247 | 97.2% | +| Use cases (triadic selection, subgraph counting) | 30 | 100% | + +Run it yourself with `angreal test tck`. The remaining gaps are tracked in +[`docs/testing/semantic-coverage-matrix.md`](docs/testing/semantic-coverage-matrix.md) +and concentrate in a few deep areas (DST-aware timezone arithmetic, nested +existential subqueries, multi-row MERGE). Booleans, strings, null handling, +CREATE/SET/DELETE/REMOVE, UNION, and SKIP/LIMIT are at 100%. + ## Documentation **[Full Documentation](https://colliery-io.github.io/graphqlite/)** — Tutorials, how-to guides, and API reference diff --git a/bindings/python/src/graphqlite/__init__.py b/bindings/python/src/graphqlite/__init__.py index 195180ed..7c1f448c 100644 --- a/bindings/python/src/graphqlite/__init__.py +++ b/bindings/python/src/graphqlite/__init__.py @@ -8,7 +8,7 @@ from .utils import escape_string, sanitize_rel_type, CYPHER_RESERVED from ._platform import get_loadable_path -__version__ = "0.5.0" +__version__ = "0.6.0" __all__ = [ "BulkInsertResult", "Connection", "connect", "wrap", "load", "loadable_path", diff --git a/bindings/rust/Cargo.lock b/bindings/rust/Cargo.lock index 3a019007..c79c580b 100644 --- a/bindings/rust/Cargo.lock +++ b/bindings/rust/Cargo.lock @@ -103,7 +103,7 @@ dependencies = [ [[package]] name = "graphqlite" -version = "0.5.0" +version = "0.6.0" dependencies = [ "rusqlite", "serde", diff --git a/bindings/rust/Cargo.toml b/bindings/rust/Cargo.toml index ab4e5ab3..439c75b3 100644 --- a/bindings/rust/Cargo.toml +++ b/bindings/rust/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "graphqlite" -version = "0.5.0" +version = "0.6.0" edition = "2021" description = "SQLite extension for graph queries using Cypher" license = "MIT" From f5fa8c54bf18f06c989d0c6858599a5a8af56aeb Mon Sep 17 00:00:00 2001 From: Dylan Bobby Storey Date: Wed, 3 Jun 2026 21:01:07 -0400 Subject: [PATCH 2/2] =?UTF-8?q?metis:=20I-0049=20=E2=80=94=20record=20With?= =?UTF-8?q?OrderBy/GROUP-BY=20wins=20and=20Merge/boolean=20deferrals?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../initiatives/GQLITE-I-0049/initiative.md | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/.metis/initiatives/GQLITE-I-0049/initiative.md b/.metis/initiatives/GQLITE-I-0049/initiative.md index d378271c..c0a96fc7 100644 --- a/.metis/initiatives/GQLITE-I-0049/initiative.md +++ b/.metis/initiatives/GQLITE-I-0049/initiative.md @@ -81,3 +81,34 @@ sub-steps once started (human check-in before decomposing, per Metis HITL). - 2026-05-30: Initiative created. Structure approved (initiative + task-per-cluster). Starting with Temporal (T-A). +- 2026-06-02: **WithOrderBy (T-D) cluster CLOSED (+10, PR #91).** `WithOrderBy1` + [45] (all 10 type examples). Two root causes: (1) a non-aggregating WITH's + ORDER BY over a *kept* input variable was only applied on the outer SELECT, so a + downstream aggregating WITH (`collect`) never saw sorted rows — now also pushed + into the CTE body via `order_expr_all_live_input` gate in `transform_with.c` + (SQLite preserves an ordered subquery's order through `json_group_array`); + (2) `gql_order_cmp_func`'s temporal compare used `parse_temporal_ns` whose + `epoch*1e9` overflows int64 ~year 2262, so year-9999 datetimes sorted *smallest* + — new overflow-safe `cmp_temporal_strings()` compares `(epoch_seconds, sub_ns)`. + Verified zero regressions, 3776→3786, unit 944/944, functional clean. +- 2026-06-02: **Merge (T-C) — deep architectural blockers identified, deferred.** + Investigated the undirected MATCH+MERGE+RETURN cluster (Merge5 [12]/[13]). + Root cause of row-doubling: the RETURN-after-MERGE re-query builds a synthetic + combined MATCH (original MATCH pattern + MERGE pattern). For undirected + `(a)-[r]-(b)` whose endpoints reuse MATCH variables, the transform re-emits the + shared endpoints into a *reset* FROM and drops the MATCH nodes' property + constraints, so the undirected OR matches each edge twice. A property-transplant + fix (copy MATCH node props onto MERGE endpoints, drop redundant standalone nodes) + was prototyped but blocked by a *second* transform bug: a fresh-fresh undirected + connected pattern in the re-query drops endpoint property joins entirely (the + parsed-equivalent query emits them fine — the re-query path resets the builder + mid-emission). Reverted (no commit). Additional Merge blockers: (a) MERGE + processes only the FIRST match row (`break` in `executor_merge.c` ~line 1088), so + multi-row MATCH+MERGE can't bind/return per-row (Merge8 [1] expects +3 rels); + (b) `handle_create_return` is a hand-rolled projection that doesn't handle + aggregate RETURN items (`count(*)`/`count(a)` → `?column?`/null) — gates + Merge5 [4], Merge9 [3]. These need transform/executor rework, not a contained + fix; left for a dedicated effort. +- 2026-06-02: Comparison2 [3] confirmed blocked by boolean-type-preservation + (`WHERE ` and `WHERE b = true` both filter all rows). List12 [1] is the + entity-in-list property access deep item (`x.name` on collected nodes → null).