Repository navigation
fix(duckdb): unbreak the build, make --engine duckdb reachable, and green up clippy - #12
LifeInTheWeeds wants to merge 3 commits into
Conversation
duckdb 1.10505.0 marked `types::Value` as #[non_exhaustive]. Cargo.toml
declares `duckdb = "1.10504.0"`, which caret-resolves forward, so a fresh
`cargo build` now fails on the default feature set:
error[E0004]: non-exhaustive patterns: `&_` not covered
--> src/engine/duckdb/mod.rs:758:11
error: could not compile `plenum` (lib) due to 1 previous error
The existing match is complete for every real variant; it only lacks the
wildcard the attribute now requires.
Renders the Debug form rather than the compiler-suggested `todo!()`. This
function runs inside a live MCP server, where an unknown future variant
should stay visible in the JSON rather than panicking the process or being
silently nulled.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016pWGjWtzGf4UvtgiWqYQyR
The DuckDB engine was fully implemented but unreachable from the CLI.
Three clap `value_parser` arrays hardcoded the engine list:
src/main.rs:72 connect
src/main.rs:156 introspect
src/main.rs:283 query
#[arg(long, value_parser = ["postgres", "mysql", "sqlite"])]
clap rejects the value before `parse_engine` is ever called, so every
downstream layer was dead code on this path:
- parse_engine(): "duckdb" => Ok(DatabaseType::DuckDB)
- connection builder: DatabaseType::DuckDB => ConnectionConfig::duckdb(file)
(the "--file is required for duckdb" message
could never fire)
- validate_connection / introspect / execute: all dispatch DuckDbEngine
- is_read_only_duckdb(): already implemented, REF-41 + REF-42
The interactive path did not have this gate -- `engine_choices` at
main.rs:1032 already lists "duckdb" -- so the two entry points disagreed
about which engines exist.
Verified after the change, against a DuckDB file:
connect --test -> {"ok":true,"engine":"duckdb","database_version":"v1.5.5"}
--list-tables -> 7 tables returned
Read-only enforcement re-checked at runtime; enabling the engine does not
widen the write surface. SELECT allowed; DELETE, CTE-hidden DELETE ...
RETURNING, SELECT ... INTO, COPY ... TO <file>, INSTALL and ATTACH all
rejected with CAPABILITY_VIOLATION.
Note for follow-up: the engine list is declared in six places (three
value_parser arrays, the interactive `engine_choices` vec, the
`parse_engine` match, and `parse_engine`'s error string). Deriving
clap::ValueEnum on DatabaseType would collapse all six and make this
class of drift impossible.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016pWGjWtzGf4UvtgiWqYQyR
|
On the red Clippy check — it isn't from this PR. The 6 failures are #14 fixes it in isolation. Everything else here is green, including Suggested order: #12 → #14 → #13. This one unbreaks the build, which the other two need before their CI can even reach the relevant checks. Happy to rebase the others once this lands. |
CI runs `cargo clippy --all-targets --all-features -- -D warnings` against the
`stable` toolchain with nothing pinning it. A newer stable clippy began firing
`unused_async` on async trait-impl methods, producing 6 errors:
src/engine/sqlite/mod.rs:35, 77, 152
src/engine/duckdb/mod.rs:43, 63, 100
That is exactly the 3 `DatabaseEngine` methods (`validate_connection`,
`introspect`, `execute`) in the 2 engines whose drivers are synchronous. The
`postgres` and `mysql` impls are unaffected because they genuinely `.await`.
The `async` cannot be dropped: `DatabaseEngine` declares these methods as
returning futures, so every impl must match that signature regardless of
whether its body awaits. Satisfying the lint would mean hand-rolling
`impl Future` bodies purely to appease it, which is strictly worse code.
`#[allow(clippy::unused_async)]` on the two impl blocks, with a comment saying
why. This matches existing house style -- the tree already carries
`allow(clippy::future_not_send)`, `allow(clippy::too_many_arguments)` and
several others.
Note this is drift, not regression: clippy passed on main in July against this
same code. Pinning the clippy toolchain in CI would address the cause rather
than the symptom, but that is a maintainer call and out of scope here.
Ordering: main does not currently compile against duckdb >= 1.10505.0, so
clippy aborts on that error before it reaches these lints. This branch
therefore cannot go green until the `non_exhaustive` wildcard fix (first commit
of #12) lands. It is branched from main to stay single-purpose.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016pWGjWtzGf4UvtgiWqYQyR
|
Update: folded the clippy fix in here so this PR can prove itself on CI. I originally split the So this branch is now three commits:
Each is independently revertible, and #3 is 12 lines of attribute + comment touching no logic. #14 is now redundant and I'll close it once CI here is green. #13 (the MCP On commit 3: clippy passed on |
d8aee71 to
c6ad88a
Compare
…impls
CI runs `cargo clippy --all-targets --all-features -- -D warnings` against
`dtolnay/rust-toolchain@stable`, with nothing pinning the version. Clippy 0.1.98
(Rust 1.98, 2026-09-01) split `unused_async_trait_impl` out of `unused_async` as
a SEPARATE lint, and it now fires 6 times:
src/engine/sqlite/mod.rs -- validate_connection, introspect, execute
src/engine/duckdb/mod.rs -- validate_connection, introspect, execute
That is exactly the 3 `DatabaseEngine` methods in the 2 engines whose drivers
are synchronous. `postgres` and `mysql` are unaffected because they genuinely
`.await`.
The `async` cannot be dropped: `DatabaseEngine` declares these methods as
returning futures, so every impl must match that signature regardless of whether
its body awaits. Satisfying the lint would mean hand-rolling `impl Future`
bodies purely to appease it, which is strictly worse code.
⚠ The lint name matters, and the error text is the clue: `#[allow(clippy::unused_async)]`
does NOT cover it, because since 0.1.98 the trait-impl case is its own lint. Both
names are listed so the attribute holds either side of that split.
This is drift, not regression: clippy passed on main in July against this same
code. Nothing in the source changed; the toolchain moved underneath it.
⭐ The durable fix is to pin the clippy toolchain in `.github/workflows/ci.yml`
rather than tracking `stable`. As written, any new upstream lint can turn main
red with no repo change -- which is exactly what happened here. That is a
maintainer call, so it is left out of this PR.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016pWGjWtzGf4UvtgiWqYQyR
c6ad88a to
8407dbe
Compare
Two independent DuckDB fixes. The first commit is release-blocking —
maindoes not currently build.1.
mainfails to compile against duckdb >= 1.10505.0Cargo.tomldeclaresduckdb = "1.10504.0", which caret-resolves forward. Upstream markedduckdb::types::Valueas#[non_exhaustive]in1.10505.0, so a freshcargo buildnow fails:The existing match is complete for every real variant; it only lacks the wildcard the attribute requires. I used the Debug form rather than the compiler-suggested
todo!()— this function runs inside a live MCP server, where an unknown future variant should stay visible in the JSON instead of panicking the process.2. DuckDB was unreachable from the CLI
Three
value_parserarrays hardcoded the engine list (src/main.rs:72,:156,:283). clap rejects the value beforeparse_engineruns, so every downstream layer was dead code on this path:parse_engine()"duckdb" => Ok(DatabaseType::DuckDB)— already correctConnectionConfig::duckdb(file)— already correct, including its--file is required for duckdbmessage, which could never firevalidate_connection/introspect/executeDuckDbEngineis_read_only_duckdb()Notably the interactive path never had this gate —
engine_choicesatmain.rs:1032already lists"duckdb". The two entry points disagreed about which engines exist.Verification
Read-only enforcement re-checked at runtime — enabling the engine does not widen the write surface:
SELECT count(*) FROM macro_seriesDELETE FROM macro_seriesWITH x AS (DELETE ... RETURNING *) SELECT * FROM xSELECT * INTO evil FROM macro_seriesCOPY (SELECT 1) TO '...csv'INSTALL httpfsATTACH '...' AS zTest suite: 414 pass. The 4
*_schema_not_stalefailures are pre-existing and unrelated — see the note below.Two follow-ups, not included here
Root cause worth addressing. The engine list is declared in six places: three
value_parserarrays, the interactiveengine_choicesvec, theparse_enginematch, andparse_engine's error string. That is why one drifted unnoticed.#[derive(clap::ValueEnum)]onDatabaseTypewould collapse all six and make this class of bug structurally impossible.Windows checkouts are red.
schemas/*.jsonare checked out CRLF undercore.autocrlf=truewhilegenerate-schemasemits LF, so all 4schema_drifttests fail on any Windows clone. There is no.gitattributes. One line fixes it:I can send either as a separate PR if useful.
🤖 Generated with Claude Code
https://claude.ai/code/session_016pWGjWtzGf4UvtgiWqYQyR