You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(repl): Phase 11.11a REPL .spawn for interactive BEGIN CONCURRENT demos (SQLR-22) (#131)
Lifts the REPL from a single Database to Vec<Connection> so users can mint
sibling handles in-session and step through cross-handle MVCC scenarios.
The prompt now shows the active handle ('sqlrite[A]> ' / 'sqlrite[B]> ')
so it's obvious which connection will execute the next line.
New meta-commands:
- .spawn Mint a sibling Connection sharing the same Arc<Mutex<Database>>
via Connection::connect(), then switch to it. Each handle gets
a stable letter name (A, B, ..., Z, then AA, AB).
- .use NAME Switch the active handle (case-insensitive); errors with the
list of valid names on miss.
- .conns List every handle, mark active with *, tag handles holding
an open BEGIN CONCURRENT with (BEGIN CONCURRENT).
Plumbing:
- New ReplState in src/repl/mod.rs owns Vec<Connection>, parallel
Vec<String> of names, and the active index. Exposes lock_active() for
meta-commands that mutate the underlying Database directly, and
active_conn_mut() for SQL dispatch through Connection.
- src/main.rs migrates the REPL loop from &mut Database to
&mut ReplState. SQL dispatch routes through a new
Connection::execute_with_render which returns CommandOutput (status +
optional rendered prettytable) instead of a bare String — so BEGIN
CONCURRENT / COMMIT / ROLLBACK still hit the per-connection MVCC
state, while SELECTs come back with the prettytable for the REPL to
print above the status line.
- Connection::concurrent_tx_is_open() promoted from private to public
so .conns can render per-handle tx state.
- .open collapses every sibling back to a single handle named A —
siblings pointing at the previous Database would otherwise be
stranded with stale MVCC state.
Tests:
- 8 new cases in src/meta_command/mod.rs cover .spawn / .use / .conns
parse + dispatch behaviour, case-insensitive .use, the error message
on unknown names, multi-handle shared-database visibility,
.open-collapse, and the A → Z → AA naming wrap.
Docs:
- roadmap.md: Phase 11.11a promoted to shipped; the heavier
benchmark workload split out as 11.11b.
- usage.md: new "Multi-handle mode" section with a worked
BEGIN-CONCURRENT-vs-BEGIN-CONCURRENT demo.
- smoke-test.md: prompt example refreshed (sqlrite[A]>), command
count bumped from 5 → 9.
- design-decisions.md: new §12h covering the migration rationale
(why Vec<Connection> over HashMap, why .open collapses siblings,
why execute_with_render instead of pre-parsing).
Workspace: 615/615 Rust tests pass (was 607, +8 new). fmt + clippy
+ doc all clean. Smoke-tested the BEGIN CONCURRENT demo end-to-end
across A/B handles — A's commit hits Busy as expected.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: docs/roadmap.md
+14-2Lines changed: 14 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -685,9 +685,21 @@ MVCC commits now leave a typed log-record frame in the WAL on top of the existin
685
685
686
686
Each secondary-index entry becomes its own `RowVersion`. Turso explicitly punted on this; SQLRite's v0 will reject `CREATE INDEX` while `journal_mode = mvcc`.
REPL `.spawn` meta-command for interactive `BEGIN CONCURRENT` demos. New "N concurrent writers" benchmark workload pitting SQLRite-MVCC against SQLite + DuckDB on disjoint-row write throughput. Plus Go SDK multi-handle work (cross-pool sibling shape).
690
+
Lift the REPL from a single `Database` to a `Vec<Connection>` so users can mint sibling handles in-session and step through cross-handle MVCC scenarios. The prompt now shows the active handle (`sqlrite[A]> ` / `sqlrite[B]> `) so it's always obvious which connection is about to execute the next line.
691
+
692
+
-**`.spawn`** mints a sibling off the active handle (via `Connection::connect`) and switches to it. Each handle gets a stable letter name (`A`, `B`, `C`, …, `Z`, then `AA`, `AB`).
693
+
-**`.use <NAME>`** switches the active handle (case-insensitive); errors with the list of valid names if the target is unknown.
694
+
-**`.conns`** lists every handle, marks the active one with `*`, and tags any handle that holds an open `BEGIN CONCURRENT` so demos can show the conflict-detection state at a glance.
695
+
-**`.open`** collapses every sibling back to a single handle named `A` so the new database doesn't strand siblings pointing at the old one.
696
+
- New [`Connection::execute_with_render`](../src/connection.rs) returns a `CommandOutput` instead of a bare status string, so the REPL's SQL dispatch routes through `Connection` (catching `BEGIN CONCURRENT` / `COMMIT` / `ROLLBACK` and the in-flight tx swap) while still printing the prettytable for `SELECT`. The old non-render `execute` stays for callers that don't need it.
697
+
698
+
The downstream "N concurrent writers" benchmark workload (originally bundled into 11.11) is its own follow-up: it touches the `benchmarks/` harness, links SQLite + DuckDB drivers, and is much heavier than this slice.
New benchmark in [`benchmarks/`](../benchmarks/) that pits SQLRite-MVCC against SQLite + DuckDB on a disjoint-row "N writers, mostly disjoint rows" scenario. Slots into the existing SQLR-16 harness as a Group D differentiator workload. Also includes Go SDK multi-handle work (cross-pool sibling shape) — see the 11.8 note for why that's a separate slice.
Copy file name to clipboardExpand all lines: docs/smoke-test.md
+5-3Lines changed: 5 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -39,9 +39,11 @@ Enter .exit to quit.
39
39
Enter .help for usage hints.
40
40
Connected to a transient in-memory database.
41
41
Use '.open FILENAME' to reopen on a persistent database.
42
-
sqlrite>
42
+
sqlrite[A]>
43
43
```
44
44
45
+
(The `[A]` suffix is the active connection handle — Phase 11.11a added `.spawn` / `.use` / `.conns` for multi-handle demos.)
46
+
45
47
(The version line in the banner tracks the current build — `cargo run` always shows the live value, so don't be surprised if it's later than what's printed here.)
46
48
47
49
Also verify the help text is detailed:
@@ -58,7 +60,7 @@ Should print the project description, the meta-command table, and a summary of s
58
60
sqlrite> .help
59
61
```
60
62
61
-
Expect the 5-command list (`.help`, `.open`, `.save`, `.tables`, `.exit`) and a note that `.read` / `.ast` aren't implemented.
63
+
Expect the 9-command list (`.help`, `.open`, `.save`, `.tables`, `.ask`, `.spawn`, `.use`, `.conns`, `.exit`) and a note that `.read` / `.ast` aren't implemented.
62
64
63
65
### 1.3 Create a table
64
66
@@ -585,7 +587,7 @@ When you want a fast before/after comparison for a change, run this condensed ch
585
587
-[ ]`cargo run --bin sqlrite-mcp -- --help` prints the MCP server CLI without crashing — quick check that the stdio_redirect dance still works
586
588
-[ ]`cargo run -- --help` prints the full description + meta-command table + SQL surface (not just `-h` / `-V`)
587
589
-[ ]`cargo run -- somefile.sqlrite` on a non-existent path creates the file and enters the REPL with auto-save on
588
-
-[ ] REPL launches, `.help` shows 5 commands
590
+
-[ ] REPL launches, `.help` shows 9 commands
589
591
-[ ]`.tables` in a populated DB prints one name per line
590
592
-[ ] CREATE TABLE + INSERT + SELECT `*` work in memory
591
593
-[ ]`SELECT ... WHERE col = literal` on a UNIQUE column returns the right row (index probe path)
Copy file name to clipboardExpand all lines: docs/usage.md
+31-1Lines changed: 31 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -23,6 +23,10 @@ Meta commands start with a dot and don't need a trailing semicolon.
23
23
|`.open FILENAME`| Open (or create) a `.sqlrite` file. From this point on, every committing SQL statement auto-saves. |
24
24
|`.save FILENAME`| Force-flush the current DB to `FILENAME`. Rarely needed — auto-save makes this redundant when it's the active file. Useful for "save as" to a different path. |
25
25
|`.tables`| List tables in the current database, sorted alphabetically |
26
+
|`.ask QUESTION`| Natural-language → SQL via the configured LLM. Requires `SQLRITE_LLM_API_KEY`. |
27
+
|`.spawn`| Mint a sibling connection sharing the same backing database. Switches to it. (Phase 11.11a) |
28
+
|`.use NAME`| Switch the active handle (case-insensitive). (Phase 11.11a) |
29
+
|`.conns`| List every active handle; marks the current one with `*` and flags handles in an open `BEGIN CONCURRENT`. (Phase 11.11a) |
26
30
|`.read` / `.ast`| Not yet implemented |
27
31
28
32
### `.open` semantics
@@ -31,7 +35,33 @@ Meta commands start with a dot and don't need a trailing semicolon.
31
35
- If `FILENAME` doesn't exist: create an empty database at that path (auto-save enabled immediately).
32
36
- If `FILENAME` exists but is not a valid SQLRite database: reject with a `bad magic bytes` error — the REPL stays in its previous state.
33
37
34
-
Only one database is active at a time. A subsequent `.open` replaces the in-memory state.
38
+
Only one database is active at a time. A subsequent `.open` replaces the in-memory state and **collapses every sibling handle minted via `.spawn` back to a single one** (named `A`) — siblings pointing at the previous database would be stranded otherwise.
39
+
40
+
### Multi-handle mode (Phase 11.11a)
41
+
42
+
The REPL holds a vector of `Connection`s; the prompt always shows which one is active: `sqlrite[A]> `, `sqlrite[B]> `, etc.
43
+
44
+
-`.spawn` mints a new sibling off the active handle and switches to it. Each new handle gets the next letter in sequence (`A`, `B`, `C`, …, `Z`, `AA`, `AB`).
45
+
-`.use NAME` switches the active handle. The next SQL line runs on that connection.
46
+
-`.conns` shows the current roster, with `*` next to the active handle and `(BEGIN CONCURRENT)` next to any handle holding an open concurrent transaction.
47
+
48
+
A worked demo (assumes `PRAGMA journal_mode = mvcc;`):
49
+
50
+
```text
51
+
sqlrite[A]> CREATE TABLE t (id INTEGER PRIMARY KEY, v INTEGER);
52
+
sqlrite[A]> INSERT INTO t (id, v) VALUES (1, 0);
53
+
sqlrite[A]> .spawn
54
+
Spawned sibling handle 'B' and switched to it. 2 handles open.
55
+
sqlrite[B]> .use A
56
+
sqlrite[A]> BEGIN CONCURRENT;
57
+
sqlrite[A]> UPDATE t SET v = 100 WHERE id = 1;
58
+
sqlrite[A]> .use B
59
+
sqlrite[B]> BEGIN CONCURRENT;
60
+
sqlrite[B]> UPDATE t SET v = 200 WHERE id = 1;
61
+
sqlrite[B]> COMMIT;
62
+
sqlrite[B]> .use A
63
+
sqlrite[A]> COMMIT; -- Busy: write-write conflict on t/1
0 commit comments