Skip to content

chore: release v0.0.5#6

Open
MagicalTux wants to merge 1 commit into
masterfrom
release-plz-2026-06-19T23-15-35Z
Open

chore: release v0.0.5#6
MagicalTux wants to merge 1 commit into
masterfrom
release-plz-2026-06-19T23-15-35Z

Conversation

@MagicalTux

@MagicalTux MagicalTux commented Jun 19, 2026

Copy link
Copy Markdown
Member

🤖 New release

  • graphitesql: 0.0.4 -> 0.0.5 (✓ API compatible changes)
Changelog

0.0.5 - 2026-06-20

Other

  • Support PRAGMA table-valued functions in FROM
  • Inf prints as Inf in text output, 9.0e+999 only in quote()
  • Print infinities as ±9.0e+999 and map NaN arithmetic to NULL
  • Persist writable PRAGMA user_version / application_id
  • Add last_insert_rowid(), changes(), and total_changes()
  • Add printf '*' width/precision, unhex(x,ignore), and sign() NULL semantics
  • Support ORDER BY / LIMIT on DELETE and UPDATE
  • Fix table_info notnull for rowid; add table_xinfo and index_xinfo
  • Parse REINDEX as a no-op
  • Add CREATE TEMP TABLE and ALTER TABLE DROP COLUMN
  • Fix IS TRUE/IS FALSE truthiness and abs() of text
  • Fix compound dedup to keep the last occurrence's representation
  • Apply FILTER on window aggregates; reject DISTINCT windows
  • mark frame EXCLUDE and RANGE value-offset done in ROADMAP
  • Implement window frame EXCLUDE clause
  • Implement RANGE window frames with value offsets
  • Fix strftime %j off-by-one; add ISO week-date %G/%V/%g
  • Allow HAVING to reference SELECT-output aliases
  • Implement SQLite's bare-column min/max rule
  • Parse sized / multi-word type names in CAST
  • Fix printf %g notation, %f half-away rounding, and float sign flags
  • Fix negative LIMIT to mean "no limit"
  • Support _ digit separators in numeric literals
  • Validate and normalize date/time components
  • Fix round() to round the true decimal value (half away from zero)
  • Add correlated-subquery / EXISTS differential test
  • Add distinct-aggregate and window-function differential test
  • Fix substr() on blobs to slice bytes and return a blob
  • Fix CAST: blob reinterpretation and NUMERIC text reduction
  • Fix quote() blob format and CAST AS BLOB
  • Add REAL formatting differential test
  • Add mixed-affinity differential test
  • Fix NUMERIC storage affinity: reduce integral reals to integers
  • Phase 9: broaden differential corpus with scalar/date edge cases
  • Phase 9: hash join for equi-join ON conditions
  • Fix pre-comparison affinity: NONE column vs TEXT column
  • Phase 9: EXPLAIN QUERY PLAN emits MULTI-INDEX OR
  • Phase 9: broaden differential corpus over planner seek paths
  • Phase 9: OR-by-union index optimization
  • Phase 9: rowid range scans over the table b-tree
  • Phase 9: EXPLAIN QUERY PLAN reports range/IN index seeks
  • Phase 9: IN-list driven index seeks
  • Phase 9: index range scans (< <= > >= BETWEEN)
  • Phase 9: add octet_length() and glob() scalar functions
  • Phase 9: VDBE single-table GROUP BY
  • Phase 9: VDBE whole-table aggregates
  • Phase 9: VDBE SELECT DISTINCT
  • Phase 9: VDBE ORDER BY via a sorter
  • Phase 9: VDBE OFFSET on single-table scans
  • Track B: VDBE LIMIT
  • Track B: VDBE WHERE filtering
  • Track B: VDBE table scans + Connection::query_vdbe
  • Track B: VDBE CAST op; refresh module overview
  • Track B: VDBE control flow (Goto/IfFalse) and CASE compilation
  • Track B: extend VDBE IR with comparison and boolean ops
  • Track B: VDBE bytecode IR spike (exec::vdbe)
  • Track A: RIGHT and FULL OUTER JOIN
  • Track A: LIKE ... ESCAPE, like() function form, likely/unlikely/likelihood
  • Track C: in-engine PRAGMA integrity_check / quick_check
  • Track D: json_each / json_tree table-valued functions
  • Track D: table-valued functions — generate_series
  • Track A: CREATE TABLE ... AS SELECT (CTAS)
  • Track A: INDEXED BY / NOT INDEXED query hints
  • Track A: ordered aggregates (group_concat(x ORDER BY y))
  • Track A: percent_rank() and cume_dist() window functions
  • Track A: expression indexes (CREATE INDEX ... (expr))
  • Track A: named windows (WINDOW w AS ... / OVER w)
  • Track C: PRAGMA foreign_key_check
  • Track C: introspection PRAGMAs (index_list/info, foreign_key_list, ...)
  • Track A: partial indexes (CREATE INDEX ... WHERE)
  • Track A: VALUES as a statement and table source
  • Track A: row-value IN (SELECT ...)
  • Track A: aggregate FILTER (WHERE ...) clause
  • Track C: SAVEPOINT / RELEASE / ROLLBACK TO nested transactions
  • Track A: row-value expressions (=, ordering, IN)
  • Track A: JSON ->/->> operators and json_set/insert/replace/remove/patch
  • Track A: ORDER BY NULLS FIRST/LAST and IS [NOT] DISTINCT FROM
  • Track C: VFS advisory-locking contract + writer serialization
  • Track B: ANALYZE + sqlite_stat1 + cost-based index selection
  • Track A: SQLite JSON functions (pure-core parser/serializer)
  • Track A: SQLite math functions (pure-core, no libm)
  • Track A: UPSERT (ON CONFLICT DO UPDATE/NOTHING) and RETURNING
  • Track A: collating sequences (BINARY/NOCASE/RTRIM)
  • Track A: generated columns (STORED / VIRTUAL)
  • re-plan ROADMAP toward full SQLite parity
  • Phase 9: b-tree page merging on delete (last roadmap item)

Other

  • Track B: hash join. A two-table join with an equi-join left.col = right.col
    in its ON now builds a hash index on the joined table and probes it per left
    row (the full ON is still re-evaluated on each candidate, so semantics are
    unchanged), turning the O(n·m) nested loop into a probe. Numeric keys collide
    across INTEGER/REAL (5 and 5.0) and across affinity (5/'5') via
    multi-keying; non-BINARY collations fall back to the nested loop. Verified
    against sqlite3 (numeric/text/NOCASE/duplicate-key/NULL/outer/self joins).

  • Fix: pre-comparison type affinity no longer text-coerces a typeless (BLOB/NONE)
    column against a TEXT column — none_col = text_col now matches SQLite (e.g.
    integer 1 vs '1' is false). expr_affinity distinguishes a literal's
    absence of affinity from a column's BLOB affinity.

  • Track B: IN-list index seeks. A single-table query with column IN (c1, c2, …) now seeks each constant through an index on that column (or the rowid
    b-tree for an INTEGER PRIMARY KEY), unions the rowids, and fetches the rows,
    instead of scanning. Returns a superset (full WHERE re-applied). Verified
    against sqlite3.

  • Track B: OR-by-union. A single-table query whose WHERE is a top-level OR
    of individually index/rowid-seekable predicates (equality, IN, range, or an
    AND containing one) now seeks each disjunct, unions the rowids, and fetches the
    rows once, instead of scanning. If any disjunct is not seekable it falls back to
    a scan. Superset semantics keep it correct (full WHERE re-applied). Verified
    against sqlite3, including ORs spanning two different indexes. EXPLAIN QUERY PLAN reports these as SQLite's nested MULTI-INDEX OR / INDEX 1 / SEARCH …
    structure.

  • Track B: EXPLAIN QUERY PLAN now reports the index range and IN-list seeks as
    SEARCH … USING INDEX … (a>? AND a<?) / (a=?) (and rowid IN as
    … INTEGER PRIMARY KEY (rowid=?)), matching SQLite's format and reflecting what
    the executor actually does.

  • Track B: index range scans. A single-table query whose WHERE constrains an
    indexed column by </<=/>/>=/BETWEEN now seeks the index between those
    bounds (btree::index_range_rowids, an in-order traversal that stops once the
    upper bound is passed) instead of scanning the whole table, then re-applies the
    full WHERE. The lookup returns a superset, so correctness is preserved
    regardless of bound edge cases. A range on the INTEGER PRIMARY KEY rowid walks
    the table b-tree directly between integer bounds (seeking the lower bound, then
    iterating until the upper). Both verified against sqlite3, and reported by
    EXPLAIN QUERY PLAN as SEARCH … (rowid>? AND rowid<?) etc.

  • Track A: octet_length(X) (byte length of a value's encoding — blob bytes, else
    the UTF-8 length of its text form) and the glob(pattern, text) function form of
    the GLOB operator. Both verified against sqlite3 in the differential corpus.

  • Track D: table-valued functions — generate_series(start, stop[, step]),
    json_each, and json_tree as FROM sources (sole source or joined).
    json_each yields the direct children, json_tree the full depth-first tree,
    each with the key/value/type/atom/id/parent/fullkey/path columns
    (the id/parent numbering is graphitesql's own; the rest match SQLite).
    Establishes the TVF mechanism (TableRef.tvf_args). Verified against sqlite3.

  • Track A: RIGHT [OUTER] JOIN and FULL [OUTER] JOIN. The nested-loop join now
    tracks matched right rows and emits the unmatched ones with NULL left columns
    (and unmatched left rows for FULL/LEFT). Verified against sqlite3.

  • Track A: LIKE … ESCAPE, the like(pattern, text[, escape]) function form, and
    the likely/unlikely/likelihood optimizer-hint functions (identity at the
    value level). Verified against sqlite3.

  • Track A: CREATE TABLE … AS SELECT … (CTAS). The new table's columns are the
    query's output labels (untyped), populated with the query's rows via the normal
    insert path. Verified against sqlite3.

  • Track A: INDEXED BY name / NOT INDEXED query hints. NOT INDEXED forces a
    table scan; INDEXED BY restricts the planner to the named index (and errors if
    it does not exist). Results are identical to the unhinted query. Verified.

  • Track A: ordered aggregates — group_concat(x ORDER BY y [DESC]) (and any
    aggregate with an inner ORDER BY) sorts the group's rows before folding,
    honoring DESC/NULLS and collation. Verified against sqlite3.

  • Track A: percent_rank() and cume_dist() window functions. Verified against
    sqlite3.

  • Track A: named windows — WINDOW w AS (…) definitions with OVER w references
    and OVER (w ORDER BY …) extension (a base window supplies PARTITION BY; the
    use may add ORDER BY/frame). Verified against sqlite3.

  • Track C: in-engine PRAGMA integrity_check / quick_check. Walks every table
    and index b-tree and verifies each index holds exactly the entries its table
    implies (honoring partial-index predicates), returning ok when consistent or
    one row per problem — no longer delegated to sqlite3. Agrees with sqlite3 on
    valid databases (rowid/WITHOUT ROWID, multi-column/unique/partial/expression
    indexes).

  • Track C: introspection PRAGMAs — index_list, index_info,
    foreign_key_list, foreign_key_check, freelist_count, application_id,
    data_version. Output matches SQLite's column layout and ordering;
    foreign_key_check reports (table, rowid, parent, fkid) for each dangling
    reference. Verified against sqlite3.

  • Track A: expression indexes — CREATE INDEX … (lower(x)), (a + b), etc. The
    index key is the per-row evaluation of the term expressions; entries are
    maintained on insert/update/delete and rebuild, so sqlite3 integrity_check
    (which recomputes the expressions) passes. The planner scans rather than
    seeking an expression index. (Not supported on WITHOUT ROWID tables yet.)
    Verified against sqlite3.

  • Track A: partial indexes — CREATE INDEX … WHERE <predicate>. The index stores
    only rows satisfying the predicate; entries are added/removed as rows cross the
    boundary on insert/update/delete, so sqlite3 integrity_check passes. The
    planner conservatively scans rather than seeking a partial index (always
    correct). Verified against sqlite3.

  • Track A: VALUES as a query — standalone (VALUES (1,2),(3,4)) and as a table
    source (SELECT … FROM (VALUES …)). Desugared to a UNION ALL of single-row
    selects with SQLite's column1/column2/… naming. Verified against sqlite3.

  • Track A: aggregate FILTER (WHERE …). count/sum/avg/total/
    group_concat/… accept a FILTER (WHERE predicate) that restricts which rows
    of the group they consume, grouped or ungrouped. Verified against sqlite3.

  • Track C: SAVEPOINT / RELEASE / ROLLBACK TO nested transactions. The write
    pager snapshots its staged state on SAVEPOINT; ROLLBACK TO restores it
    (keeping the savepoint open and repeatable), RELEASE discards it keeping the
    changes, and releasing the outermost savepoint of an implicit transaction
    commits. Savepoints nest inside BEGIN, revert schema changes, and persist to
    disk on release. Verified against sqlite3 semantics.

  • Track A: row-value expressions — (a,b) = (c,d), lexicographic ordering
    (</<=/>/>=), (a,b) IN ((…),(…)), and (a,b) IN (SELECT …), with
    SQLite's three-valued NULL semantics (an undecided element yields NULL; a
    decisive earlier element still resolves). Verified against sqlite3.

  • Track A: JSON ->/->> operators and mutators. -> returns the extracted
    node as JSON, ->> as a SQL value; a bare-label or integer right operand is
    normalized to $.label/$[n]. Added json_set, json_insert,
    json_replace, json_remove, and RFC-7396 json_patch; nested
    json_array/json_object arguments embed as JSON. Verified against sqlite3.

  • Track A: ORDER BY … NULLS FIRST/LAST and IS [NOT] DISTINCT FROM. NULL
    placement in sorts is now controllable (default stays SQLite's: NULLs first
    under ASC, last under DESC); IS DISTINCT FROM/IS NOT DISTINCT FROM are
    the null-aware (in)equality operators. Verified against sqlite3.

  • Track C: VFS advisory-locking contract and writer serialization. A new
    LockState encodes SQLite's SHARED/RESERVED/PENDING/EXCLUSIVE
    compatibility rules; MemoryVfs and StdVfs now share one lock state per path
    across all open handles (process-local). The write pager takes the write-intent
    lock when staging a transaction and upgrades to exclusive while flushing, so a
    second connection writing the same database is rejected with Error::Busy
    while another holds an open write transaction — and the lock is released on
    commit, rollback, and autocommit. (Reads buffer per-connection so they stay
    isolated from uncommitted writes; cross-process OS locks remain a host-VFS
    concern.)

  • Track B: VDBE bytecode IR spike. A new exec::vdbe module defines a
    register-machine instruction set (Op), a Program, a compiler for constant
    SELECT projections, and an interpreter — built alongside the tree-walking
    executor (not replacing it) so the IR can grow incrementally toward cursors and
    filters. The compiled+interpreted output matches both the tree-walker and
    sqlite3 for arithmetic, concatenation, comparison, three-valued AND/OR/
    NOT, IS [NOT] NULL, CASE (via Goto/IfFalse control flow on a
    program-counter interpreter), and CAST projections; unsupported queries
    cleanly report Unsupported for fallback. The IR also scans a single plain
    table with an optional WHERE filter (Rewind/Column/Next cursor ops with
    an IfFalse row skip), wired into the engine via the new
    Connection::query_vdbe, matching the tree-walker and sqlite3 for
    SELECT <exprs> FROM <table> [WHERE …] [ORDER BY …] [LIMIT n [OFFSET m]] (a
    DecrJumpZero counter caps the row count; an IfPosDecr counter skips the
    leading OFFSET rows). ORDER BY compiles to a sorter: the scan stages each
    projected row plus its key columns (SorterInsert), then after the scan the
    rows are sorted (SorterSort, honoring DESC/NULLS FIRST/LAST) and a
    second cursor loop (SorterRewind/SorterRow/SorterNext) emits them with
    OFFSET/LIMIT applied to the sorted output. Output-column ordinals
    (ORDER BY 2) and aliases (ORDER BY d) resolve to their projection.
    SELECT DISTINCT compiles to a DistinctCheck gate (NULLs compare equal) that
    drops duplicate output rows before OFFSET/LIMIT, composing with ORDER BY
    (dedup, then sort). Whole-table aggregates (count/sum/total/avg/min/
    max/group_concat, no GROUP BY) compile to AggStep/AggFinal: the scan
    folds each slot (counting rows for count(*), collecting non-NULL arguments
    otherwise) and a single ResultRow emits the finalized values, reproducing the
    tree-walker's exact semantics (integer-sum overflow promotes to real, empty
    group yields 0/NULL per function). GROUP BY <columns> over a single table
    compiles to GroupStep/GroupEmit: the scan folds per-group accumulators
    (groups kept in first-seen order, NULLs grouping together, matching the
    tree-walker) and one row per group is emitted, where each output column is
    either a grouping-key value or a finalized aggregate. HAVING/ORDER BY/
    non-grouped output expressions fall back to the tree-walker.

  • Track B: ANALYZE and cost-based index selection. ANALYZE [name] gathers
    index selectivity into a sqlite_stat1(tbl,idx,stat) table, byte-compatible
    with SQLite's nRow avgEq1 avgEq2 … format (avgEqK = (nRow + dK/2)/dK);
    no-index tables get a (tbl, NULL, nRow) row, empty indexes are skipped, and
    re-analyzing replaces a table's rows. The planner (both execution and
    EXPLAIN QUERY PLAN) now prefers the most selective usable index per those
    statistics, falling back to the longest-prefix heuristic when unanalyzed.
    Verified against sqlite3 incl. integrity_check.

  • Track A: SQLite JSON functions — json, json_valid, json_quote,
    json_type, json_array_length, json_extract, json_array, json_object.
    Includes a pure-core RFC-8259 parser/serializer and $/.key/[n] path
    navigation; JSON scalars map back to SQL values (true/false→1/0,
    null→NULL), objects/arrays return minified JSON text, and nested
    json_array/json_object calls embed as JSON (subtype propagation by call
    origin). Verified against sqlite3. (Mutators json_set/json_remove/…, the
    ->/->> operators, and json_each/json_tree are not yet implemented.)

  • Track A: SQLite math functions — pi, sqrt, exp, ln, log/log10/
    log2, pow/power, mod, ceil/ceiling, floor, trunc, sin/cos/
    tan, asin/acos/atan/atan2, sinh/cosh/tanh,
    asinh/acosh/atanh, degrees, radians. Implemented in pure core
    arithmetic (no libm dependency): sqrt is correctly rounded; the transcendentals
    are accurate to ~1 ULP. NULL/domain errors return NULL. Verified against sqlite3.

  • Track A: UPSERT and RETURNING. INSERT … ON CONFLICT [(target)] DO NOTHING
    skips the conflicting row; DO UPDATE SET … [WHERE …] updates the existing
    row, exposing the would-be-inserted values via the excluded pseudo-table and
    honoring a vetoing WHERE. INSERT/UPDATE/DELETE … RETURNING <cols|*>
    projects the affected rows; drained via the new Connection::execute_returning.
    Verified against sqlite3. (WITHOUT ROWID upsert/returning not yet supported.)

  • Track A: collating sequences — BINARY/NOCASE/RTRIM honored in comparisons,
    ORDER BY, GROUP BY, DISTINCT, count(DISTINCT …), UNIQUE enforcement, and
    index b-tree ordering/seek. Resolution follows SQLite: explicit COLLATE (left
    precedence) > column collation (left precedence) > BINARY. NOCASE/RTRIM indexes
    order their keys by the collation so sqlite3 integrity_check passes, and
    index-driven equality lookups find case-variant rows. Verified against sqlite3.

  • Track A: generated columns — … AS (expr) [STORED|VIRTUAL]. VIRTUAL columns
    are computed on read and not stored; STORED ones are materialized on write;
    writes to a generated column are rejected; indexes over generated columns work;
    table_info hides them. Verified against sqlite3 incl. integrity_check.

  • Phase 9: b-tree page merging on delete — a delete that empties table leaf pages
    now compacts the b-tree in place (root preserved), returning the slack to the
    freelist for reuse so the file no longer grows unboundedly across delete/insert
    cycles; verified valid across heavy/scattered/full deletes by sqlite3
    integrity_check. This clears the last named Phase 9 deliverable.


This PR was generated with release-plz.

@MagicalTux MagicalTux force-pushed the release-plz-2026-06-19T23-15-35Z branch 30 times, most recently from 79e9999 to 76fa734 Compare June 20, 2026 03:14
@MagicalTux MagicalTux force-pushed the release-plz-2026-06-19T23-15-35Z branch 29 times, most recently from ff6867a to 37210d7 Compare June 20, 2026 06:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant