Skip to content

feat(explain): rebuild the query plan viewer end to end - #2117

Merged
datlechin merged 20 commits into
mainfrom
feat/query-plan-rewrite
Aug 15, 2026
Merged

feat(explain): rebuild the query plan viewer end to end#2117
datlechin merged 20 commits into
mainfrom
feat/query-plan-rewrite

Conversation

@datlechin

Copy link
Copy Markdown
Member

Summary

Rebuilds the EXPLAIN query plan feature end to end: how a plan is produced, how it is parsed, and how it is shown.

Builds on #2112 and includes its two commits, so @sophiathedev's MySQL TREE parsing and trackpad zoom are preserved with their authorship intact. #2112 can close in favour of this.

Why

The feature had three structural problems, each producing a cluster of symptoms.

Two execution paths that disagreed. The toolbar called runVariantExplain directly, which never consulted ExecutionGateProvider. Safe Mode confirmation was therefore skipped on every database that declares an EXPLAIN variant, and EXPLAIN ANALYZE runs the query. That path also had no stale-result fencing, never stored its task so Stop could not cancel it, wrote errors into the plan text field as "Error: ...", and never recorded to Query History.

Plan format was inferred from DatabaseType, but it is a property of the variant. ExplainVariant carried no format, so QueryPlanParserFactory guessed from a hardcoded switch. PGlite was handed PostgreSQL's EXPLAIN (FORMAT JSON) by the registry merge but had no parser case, so valid JSON rendered as raw text. Cloudflare D1 and libSQL emit exactly SQLite's EXPLAIN QUERY PLAN and were raw-only. A registry-only plugin could never contribute a parser at all. QueryEditorView also dispatched behaviour off variant.label, a localizable display string.

The presentation layer opted out of the app's own patterns. A fixed 180pt detail pane where TriggerDetailView uses AutosavingSplitView; severity colours duplicated in two files with no accessibilityDifferentiateWithoutColor gate; about twenty unlocalized labels; no context menu; no diagram export; no empty or parse-failure state, so a nil plan fell through to a blank rectangle; and no accessibility identifiers, so no UI automation was possible.

What changed

PluginKit (additive, no version bump). New ExplainPlanFormat, a string-based struct like DatabaseType, so a new format never needs a PluginKit release. ExplainVariant gains format through a new four-argument initializer; the three-argument one is kept byte for byte and marked @_disfavoredOverload, the same shape as the PluginQueryResult.columnMeta fix.

Parsing. QueryPlanParserFactory becomes ExplainPlanParserRegistry, keyed by format. Format resolves as variant-declared, then a curated per-DatabaseType default, then plain text, so already-installed Cloudflare D1 and libSQL plugins are fixed on app update with no plugin release.

Execution. runExplainQuery, runVariantExplain and runClickHouseExplain collapse into one gated runExplain(variant:) with shared statement extraction, authorization on every path, claim/isCurrent/settle fencing, a cancellable task, errors to the error banner, and history recording. ClickHouseExplainVariant is deleted.

Views. A shared MagnifiableCanvasView over NSScrollView.allowsMagnification gives both diagrams anchored pinch zoom, smart magnify, Cmd and scroll, real scrollers and elastic scrolling from AppKit. The ER diagram adopts it, deleting ERDiagramCanvasContainer and ERDiagramScrollTranslator. The plan tree becomes an NSOutlineView with labelled, resizable, autosaved columns and sorting that reorders siblings within each parent rather than flattening the tree. A plan is now a ResultSet, so it rides the existing result tab strip and can be pinned.

Also fixes QuerySplitView, which never set sizingOptions = [] on its hosting controllers. That is the divider-pinning trap in CLAUDE.md, latent until something wide was hosted in the results pane.

User-visible changes

  • Running EXPLAIN from the toolbar now asks for confirmation when Safe Mode requires it.
  • Stop can cancel a running EXPLAIN ANALYZE.
  • A plan opens as a result tab next to the data instead of replacing the results pane, and can be pinned.
  • PGlite, Cloudflare D1, libSQL and Turso plans render as a diagram and tree.
  • SQLite's four-column EXPLAIN QUERY PLAN reaches the viewer when typed by hand. MySQL's tabular EXPLAIN still stays in the grid.
  • EXPLAIN errors appear in the usual banner, and Clear Query clears the plan.

Verification

  • xcodebuild build clean, and AllPlugins builds all 30 plugins, which is the only target covering the registry-only Cloudflare D1 and libSQL changes.
  • swiftlint lint --strict: 0 violations across 1,392 files.
  • Full unit suite with the CI quarantine list: 9,019 tests in 1,104 suites, all passing.
  • PluginKit ABI verified additive at the symbol level with nm on both frameworks: no symbol present on main is missing here, and the old three-argument initializer survives unchanged. No currentPluginKitVersion bump and no plugin re-release.
  • Reviewed by a multi-agent adversarial pass; eight findings survived refutation and all eight are fixed in this branch.
  • Driven manually against the bundled Chinook sample: plan arrives as a pinnable result tab, the four-column SQLite plan parses, the diagram lays out one row per depth, and the outline shows its columns with the root pre-selected.

UI automation is included but could not be executed locally: the runner reports Timed out while enabling automation mode, and the pre-existing ResultTabPinUITests fails identically, so this is an environment limitation rather than the new tests.

sophiathedev and others added 19 commits August 14, 2026 14:19
# Conflicts:
#	TablePro/ViewModels/ERDiagramViewModel.swift
#	TablePro/Views/ERDiagram/ERDiagramView.swift
#	TablePro/Views/QueryPlan/QueryPlanDiagramView.swift
@mintlify

mintlify Bot commented Aug 15, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
TablePro 🟢 Ready View Preview Aug 15, 2026, 3:24 AM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@datlechin
datlechin merged commit 678d651 into main Aug 15, 2026
4 checks passed
@datlechin
datlechin deleted the feat/query-plan-rewrite branch August 15, 2026 03:47
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.

2 participants