explain: explain command end to end (ADR-0028 steps 2–3)
Add the `explain` prefix command — `explain show data`,
`explain update`, `explain delete` — from grammar through to a
rendered plan tree.
- Grammar: an `EXPLAIN` CommandNode whose shape is a Choice over
the three explainable query shapes, referenced (not
duplicated) through `Subgrammar`. `Command::Explain { query:
Box<Self> }`; `build_show_data` is extracted so the role-based
builders serve both standalone and explain-wrapped commands.
- Worker: SQL construction is split out of do_query_data /
do_update / do_delete into `build_*_sql`, so EXPLAIN QUERY
PLAN runs the exact same statement. `Request::ExplainPlan` /
`do_explain_plan` capture the plan; `QueryPlan` / `ExplainRow`
carry it back. EXPLAIN QUERY PLAN never executes, so
explaining update/delete changes nothing.
- Display SQL: the executed statement with `?N` parameters
inlined as standard-SQL literals via a quote-aware scan.
- Render: `render_explain_plan` draws the box-drawing plan tree
(plain output; ADR-0028 step 4 adds the styled tree).
- Catalog: `parse.usage.explain` and the `help.data.explain`
entry, so `explain` shows up in the in-app `help` listing.
1151 tests pass (+18); clippy clean.
This commit is contained in:
+14
-1
@@ -30,7 +30,7 @@ use crate::app::App;
|
||||
use crate::cli::Args;
|
||||
use crate::db::{
|
||||
AddColumnResult, ChangeColumnTypeResult, DataResult, Database, DbError, DeleteResult,
|
||||
DropColumnResult, InsertResult, TableDescription, UpdateResult,
|
||||
DropColumnResult, InsertResult, QueryPlan, TableDescription, UpdateResult,
|
||||
};
|
||||
use crate::dsl::Command;
|
||||
use crate::dsl::walker::Severity;
|
||||
@@ -1143,6 +1143,10 @@ fn spawn_dsl_dispatch(
|
||||
command: command.clone(),
|
||||
data,
|
||||
},
|
||||
Ok(CommandOutcome::QueryPlan(plan)) => AppEvent::DslExplainSucceeded {
|
||||
command: command.clone(),
|
||||
plan,
|
||||
},
|
||||
Ok(CommandOutcome::Insert(result)) => AppEvent::DslInsertSucceeded {
|
||||
command: command.clone(),
|
||||
result,
|
||||
@@ -1490,6 +1494,7 @@ fn parse_qualified_target(message: &str) -> Option<(String, String)> {
|
||||
enum CommandOutcome {
|
||||
Schema(Option<TableDescription>),
|
||||
Query(DataResult),
|
||||
QueryPlan(QueryPlan),
|
||||
Insert(InsertResult),
|
||||
Update(UpdateResult),
|
||||
Delete(DeleteResult),
|
||||
@@ -1797,6 +1802,14 @@ async fn execute_command_typed(
|
||||
.query_data(name, filter, limit, src)
|
||||
.await
|
||||
.map(CommandOutcome::Query),
|
||||
// `EXPLAIN QUERY PLAN` never executes the wrapped
|
||||
// statement (ADR-0028 §2), so explaining a destructive
|
||||
// command is safe. `src` is unused here — explain is a
|
||||
// diagnostic and is not written to `history.log`.
|
||||
Command::Explain { query } => database
|
||||
.explain_query_plan(*query)
|
||||
.await
|
||||
.map(CommandOutcome::QueryPlan),
|
||||
// `replay` is parsed as a DSL command but routed by
|
||||
// App::dispatch_dsl as `Action::Replay` rather than
|
||||
// `Action::ExecuteDsl`; it never reaches the worker
|
||||
|
||||
Reference in New Issue
Block a user