cleanup(engine): make process_command stdout-clean (drop REPL-only prints)#76
Merged
Conversation
…ints)
The engine's `process_command` had three direct stdout writes that
made sense in a REPL but corrupted any other channel an embedder
might be using stdout for:
src/sql/mod.rs:150 — let _ = table.print_table_schema(); (CREATE)
src/sql/mod.rs:208 — db_table.print_table_data(); (INSERT)
src/sql/mod.rs:224 — print!("{rendered}"); (SELECT)
Plus a leftover debug print in src/sql/parser/create.rs:190 dumping
unhandled `sqlparser::ast::TableConstraint` debug reprs to stdout.
This is the issue PR #73 papered over with the dup2(2,1) dance in
sqlrite-mcp/src/stdio_redirect.rs. That fix is still in place
(belt-and-suspenders against future regressions), but the engine now
behaves correctly without it — the SDKs (Python, Node, Go, WASM,
FFI), the Tauri desktop, and any future embedder all benefit.
## How
Refactored process_command to return a richer struct, with a
backwards-compat wrapper:
pub struct CommandOutput {
pub status: String, // "INSERT Statement executed.", etc.
pub rendered: Option<String>, // SELECT prettytable; None otherwise
}
pub fn process_command(query, db) -> Result<String>
// backwards-compat: returns just .status
pub fn process_command_with_render(query, db) -> Result<CommandOutput>
// new: rich return, never writes to stdout
The REPL (`src/main.rs`) calls `_with_render` and prints rendered
above status — same UX as before. The .ask meta-command does the
same and concatenates the two pieces into the single String it
bubbles up to the REPL's outer dispatch loop.
The CREATE-TABLE schema dump and INSERT row dump aren't preserved —
they were small UX niceties that interactive REPL users rarely need
(the schema you just CREATEd, the row you just INSERTed). The INSERT
case was actively bad UX on any non-trivial table — it dumped the
entire table after every insert. Both gone.
`sqlrite::process_command` keeps its existing signature for
backwards compatibility — every existing call site (Connection::execute,
the SDK FFI shims, the .ask handler's inline runner, the engine's
own tests) keeps working unchanged.
## Verified
- `cargo build --workspace` clean
- `cargo test --workspace --exclude sqlrite-{python,nodejs,desktop}` —
330+ tests across engine + sqlrite-mcp + sqlrite-ask + sqlrite-ffi,
all passing (engine's 251-test integration suite covers the
process_command paths most directly affected)
- REPL smoke: CREATE / INSERT / SELECT round-trip prints rendered
table above status, no surprises
- MCP smoke: same round-trip via JSON-RPC over stdio — `cat 2>/dev/null`
filtered stderr is now empty (was prettytable noise before;
stdio_redirect was the only thing keeping it off the protocol channel)
## Docs
- `docs/architecture.md` — updates the engine module map entries for
src/lib.rs and src/sql/mod.rs to mention the new types + the
"never writes to stdout" guarantee
- `docs/sql-engine.md` — entry-point section now shows both functions
+ the CommandOutput shape; explains the rendered-vs-status split
- `docs/mcp.md` — security-notes section reframes stdio_redirect as
defense-in-depth now that the engine is clean
- `sqlrite-mcp/src/stdio_redirect.rs` — module-level comment rewritten
to call out the engine fix + the three concrete future-regression
scenarios the redirect protects against
Co-Authored-By: Claude Opus 4.7 (1M context) <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
The engine's
process_commandhad four direct stdout writes that made sense in a REPL but corrupted any other channel an embedder might be using stdout for:src/sql/mod.rs:150table.print_table_schema()after CREATE TABLEsrc/sql/mod.rs:208db_table.print_table_data()after INSERT (dumps the whole table after every insert — bad UX even before the protocol-channel concern)src/sql/mod.rs:224print!("{rendered}")after SELECTsrc/sql/parser/create.rs:190println!("{constraint:?}")— debug print left in for unhandledTableConstraintvariantsThis is the issue PR #73 papered over with the
dup2(2,1)dance insqlrite-mcp/src/stdio_redirect.rs. The dance is still in place (belt-and-suspenders against future regressions), but the engine now behaves correctly without it. Every other embedder benefits — Tauri desktop, Python/Node/Go/WASM SDKs, FFI consumers, and any future host all get clean stdout.How
Refactored
process_commandto return a richer struct, with a backwards-compat wrapper:The REPL (
src/main.rs) calls_with_renderand prints rendered above status — same UX as before. The.askmeta-command does the same and concatenates the two pieces into the single String it bubbles up to the REPL's outer dispatch loop.The CREATE-TABLE schema dump and INSERT row dump are not preserved — they were small UX niceties the interactive REPL user rarely needs (the schema you just CREATEd, the row you just INSERTed). The INSERT case was actively bad UX on any non-trivial table — it dumped the entire table after every insert.
sqlrite::process_commandkeeps its existing signature for backwards compatibility — every existing call site (Connection::execute, the SDK FFI shims, the .ask handler's inline runner, the engine's own tests) keeps working unchanged.REPL smoke (UX preserved for what matters)
CREATE / INSERT now show just the status line (no schema dump after CREATE, no row dump after INSERT). SELECT still renders the table above the status — same as before.
MCP smoke (the original motivating bug)
Before PR #73, MCP saw prettytable garbage on stdout corrupting JSON-RPC. PR #73's
stdio_redirectpapered over it. With this engine cleanup, the MCP server's stderr is now completely empty during the same round-trip — confirming the engine no longer produces stray stdout writes.Tests
cargo build --workspace— cleancargo test --workspace --exclude sqlrite-{python,nodejs,desktop}— 330+ tests across engine + sqlrite-mcp + sqlrite-ask + sqlrite-ffi, all passing (the engine's 251-test integration suite directly exercises the process_command paths)Docs updated
docs/architecture.md— module-map entries for src/lib.rs and src/sql/mod.rs now mention the new types + the "never writes to stdout" guaranteedocs/sql-engine.md— entry-point section shows both functions + theCommandOutputshape; explains the rendered-vs-status splitdocs/mcp.md— security-notes section reframesstdio_redirectas defense-in-depth now that the engine is cleansqlrite-mcp/src/stdio_redirect.rs— module-level comment rewritten to call out the engine fix + the concrete future-regression scenarios the redirect still protects againstTest plan
🤖 Generated with Claude Code