feat: support explain over advanced-mode SQL queries

explain now wraps the advanced SQL commands — select, with (CTE),
insert, update, delete — in addition to the DSL show data/update/
delete it already covered, rendering through the same plan tree
(ADR-0039, closing the ADR-0030 OOS-2 gap).

Implemented as a second Advanced `explain` CommandNode under the
shared entry word, reusing the established shared-word dispatch
(SQL-first, DSL-fallback) rather than new grammar machinery.
build_explain_sql slices the inner SQL off the source and reuses the
existing SQL builders; do_explain_plan runs EXPLAIN QUERY PLAN over
the carried text verbatim (never executes, so safe for destructive
verbs). Advanced explain update/delete now route through SQL with an
identical plan; DSL-explain tests pinned to simple mode. Help and
usage text now list the advanced explain forms.
This commit is contained in:
claude@clouddev1
2026-05-30 18:44:05 +00:00
parent f7ca288fe1
commit f62cccec55
8 changed files with 503 additions and 14 deletions
+25
View File
@@ -634,6 +634,13 @@ pub static REGISTRY: &[(&CommandNode, CommandCategory)] = &[
(&data::SQL_INSERT, CommandCategory::Advanced),
(&data::SQL_UPDATE, CommandCategory::Advanced),
(&data::SQL_DELETE, CommandCategory::Advanced),
// Shared entry word `explain` (ADR-0039): the `Simple` DSL
// `data::EXPLAIN` (above) wraps `show data` / `update` / `delete`;
// this `Advanced` node wraps the SQL `select` / `with` / `insert`
// / `update` / `delete`. SQL-first / DSL-fallback in advanced mode
// (so `explain show data …` and DSL-only `--all-rows` still reach
// the DSL node); DSL-only in simple mode.
(&data::EXPLAIN_SQL, CommandCategory::Advanced),
// Shared entry word `create` (ADR-0035 §2): the simple
// `ddl::CREATE` (above) and these advanced SQL nodes. The
// dispatcher tries the advanced candidates first in advanced mode
@@ -782,4 +789,22 @@ mod usage_key_tests {
Some("parse.usage.create_table"),
);
}
#[test]
fn no_two_registered_commands_share_a_help_id() {
// `note_help` emits one help block per `help_id: Some(_)`
// with no dedup, so a duplicate help_id prints the same
// command twice in `help`. Shared-entry-word `Advanced`
// nodes (SQL_INSERT, …, EXPLAIN_SQL) therefore carry
// `help_id: None` and defer to their `Simple` sibling.
let mut seen = std::collections::HashSet::new();
for (command, _category) in super::REGISTRY {
if let Some(id) = command.help_id {
assert!(
seen.insert(id),
"duplicate help_id `{id}` in REGISTRY would print twice in `help`",
);
}
}
}
}