Easy-wins sprint: unique constraints, $N parameter binding, indexed range scans, truthful EXPLAIN, multi-line REPL, agent-eval harness#85
Merged
Conversation
… outside string literals Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ed tasks, model-agnostic offline runner Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ws the executed plan EXPLAIN previously printed the planner's speculative IndexScan even when no index existed and execution fell back to a filtered scan. The lowering pass (renamed lower_unindexed_scans) now rewrites unindexed IndexScan to Filter(SeqScan), same as it already did for RangeScan. Lowering unindexed `.col = lit` updates now routes them through the fused scan+update path, which was silently swallowing update_hinted errors with .ok(); propagate them as StorageError so the oversized-row guard fires. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…exes BTree::range_rids walks composite (value,rid) keys between prefix bounds; the executor fetches rows from the heap by rid and rechecks bounds (which also preserves null-exclusion semantics). Plan lowering now keeps RangeScan whenever ANY index exists on the column. Adds range_scan_indexed criterion workload; docs updated to match. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ique, enforced on insert/update/upsert Declaring unique auto-creates a unique B+tree index; enforcement is a storage-layer pre-check before any heap write, so plain, prepared, and upsert paths share one choke point. upsert on .col now requires .col to be unique (fixes upsert-then-insert duplicate-id bug). alter T add unique .col scans for existing duplicates first. Constraint survives restart via persisted IndexedColMeta + WAL replay index rebuild. Audit: every write that can touch an indexed column funnels through Table::insert / Table::update_hinted; the byte-patch fast paths (plan_exec.rs no_indexed guards at 895/967/2590/2651, prepared.rs:225) and scan_patch_matching_with_hook are unreachable for indexed columns, so the two pre-checks form a complete choke point. IndexedColMeta.unique already round-trips through catalog v3 persist/open; rebuild_indexes_from_heap (catalog.rs:450) restores it after WAL replay. No DropIndex statement exists, so alter add unique on an already-indexed column is a clean error. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…n level, QueryWithParams wire message Params are substituted as literal tokens before parsing (never re-lexed, never string-interpolated), so untrusted input cannot change query shape. $N placeholders are 1-based and bound via parser::parse_with_params; the plain parse() path now rejects a bare placeholder with the standard unexpected-token error. New MSG_QUERY_PARAMS (0x04) is a pure protocol addition with a WireParam tag-per-value encoding; existing messages and old clients are untouched, and dispatch_query_with_params routes through the same role-enforcement + readonly escalation as dispatch_query. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…re message query() gains an optional positional-params array (`$1`..`$N`), disambiguated from the legacy `query(q, opts)` form via Array.isArray. Params are sent in the new MSG_QUERY_PARAMS (0x04) frame with a tag-per-value encoding; the server binds them at the token level so injection-shaped strings are inert. Numbers bind as int when integral and float otherwise, bigint always int, null binds PowQL null. Adds protocol round-trip + unknown-tag-rejection tests and live-server integration tests (byte-faithful injection string, null/bool/int/float binding, old no-params path). README documents the new params form and the >= 0.4.7 server requirement. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ures Documents the [Unreleased] feature set: parameter binding ($N), unique constraints (incl. the breaking upsert-requires-unique change), indexed range scans, truthful EXPLAIN, multi-line REPL, and the agent-eval harness; plus the oversized-row-guard regression fix from the EXPLAIN lowering work. Gate: cargo test --workspace 41 suites green, clippy -D warnings clean, fmt clean; TS client 85 tests green (test+protocol+pool); powdb-compare shows no regression (PowDB 1.3-11.7x vs SQLite). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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
Six features from
docs/superpowers/plans/2026-06-09-easy-wins-sprint.md, each landed TDD-first as its own commit. Targets v0.4.7.>,>=,<,<=,betweentraverse the B+tree (newBTree::range_ridsfor non-unique composite keys) instead of full-scanning — ~7× faster on a selective range over 100K rows; NULLs excluded, exclusive bounds honored.uniquefield modifier +alter T add unique .col(scans for existing dups first). Auto-creates a unique index; enforced as a storage-layer pre-check across plain/prepared/upsert paths, before any heap write or WAL append; survives restart.QueryWithParams(0x04) wire message (pure addition; old clients unaffected) + TSclient.query(q, params?).scripts/agent-eval/— offline, model-agnostic PowQL eval (10-table schema, 26 tasks) + SQLite baseline. Not in CI.upsert <T> on .colnow requires.colto beunique. This fixes a real bug:upsert ... on .idthen a plaininsertof the same id used to silently create duplicate rows. Declare the columnuniqueoralter T add unique .col.Test plan (integration pass, T7)
cargo test --workspace— 41 suites, 0 failurescargo clippy --workspace --all-targets -- -D warningsclean;cargo fmt --all --checkcleanpowdb-compare— no regression (PowDB 1.3–11.7× vs SQLite);baseline/main.jsonuntouchedNotes
🤖 Generated with Claude Code