Skip to content

feat: pgbot sql explain — a query plan, read in plain English - #123

Merged
alexshapalov merged 1 commit into
mainfrom
feat/sql-explain
Oct 19, 2026
Merged

alexshapalov merged 1 commit into
mainfrom
feat/sql-explain

Conversation

@alexshapalov

Copy link
Copy Markdown
Contributor

What

A new sql command group with explain: the planner's plan for one SELECT, read back in plain English.

pgbot sql explain "SELECT c.name, count(*) FROM orders o JOIN customers c ON c.id = o.customer_id WHERE o.status = $1 GROUP BY c.name ORDER BY 2 DESC"
cat slow.sql | pgbot sql explain
pgbot SQL EXPLAIN "$DATABASE_URL" "SELECT …"   # case-insensitive command names
  • Plan-only EXPLAIN (GENERIC_PLAN on PG16+, so $1 needs no value) inside a READ ONLY transaction, through the same SELECT-only sanitizer as advise and the MCP tool. The query is never executed — an integration test plans SELECT pg_sleep(30) and asserts it returns at once.
  • Output: the plan tree with a one-line plain-English gloss per node and the planner's cost/row estimates, then What stands out — deterministic observations (large seq scans, nested loops repeating a scan per outer row, sorts an index could avoid, filters applied after an index lookup), then the estimate caveat. Offline, no model.
  • --json for the raw EXPLAIN (FORMAT JSON); --ai appends a model's prose reading of the same rendered text behind a disclosure prompt that names what is sent (SQL text + plan, which unlike findings can carry literals).
  • Only a single plain SELECT is accepted (exit 64 otherwise).

Internals

  • New internal/plan (Prepare / Fetch / Parse / Gloss / Observe / HasPlaceholders), internal/render/plan.go, internal/ai/plan.go, cmd/pgbot/sql.go.
  • The MCP explain_plan tool now goes through plan.Fetch so the two paths cannot drift, and returns the same observations plus generic_plan.
  • confirmDisclosure gained a payload-naming variant; existing callers unchanged.
  • cobra.EnableCaseInsensitive = true (no two pgbot commands differ only by case).

Verification

  • go build, go vet, golangci-lint run: clean. go test -race ./... green, including the new integration tests against PG16 (PGBOT_TEST_DSN).
  • CI's "no EXPLAIN ANALYZE" grep: clean.
  • Manual run against a PG16 container: join query, PK lookup, stdin, --json, write refused with exit 64, --ai without a key fails before connecting.

🤖 Generated with Claude Code

Add a `sql` command group with `explain`: the plan-only form of EXPLAIN for
one SELECT (GENERIC_PLAN on PG16+), run inside a READ ONLY transaction and
never executed, rendered as a tree with a plain-English gloss per node, the
planner's estimates, and a deterministic "What stands out" list (large seq
scans, nested loops repeating a scan, sorts an index could avoid, filters
applied after an index lookup). `--json` prints the raw plan; `--ai` appends
a model's prose reading behind a disclosure prompt that names what is sent.

The new internal/plan package (Prepare/Fetch/Parse/Gloss/Observe) is shared
with the MCP explain_plan tool, which now also returns the observations.
Cobra command matching is case-insensitive so `pgbot SQL EXPLAIN` works.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@alexshapalov
alexshapalov merged commit ccf1b47 into main Oct 19, 2026
21 checks passed
@alexshapalov
alexshapalov deleted the feat/sql-explain branch October 19, 2026 01:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant