spannerplanviz
Cloud Spanner Query Plan Visualizer using goccy/go-graphviz.

(Possibly) remote calls are rendered as dashed lines.
Install
go install github.com/apstndb/spannerplanviz@latest
Usage
It can read various types in JSON and YAML.
PLAN
$ gcloud spanner databases execute-sql --instance=sampleinstance sampledb --query-mode=PLAN --format=yaml \
--sql="SELECT SongName FROM Songs WHERE STARTS_WITH(SongName, @prefix)" |
spannerplanviz --full --type=svg --output plan.svg
PROFILE
You see verbose profile information. (Currently, histogram is not shown.)
$ gcloud spanner databases execute-sql --instance=sampleinstance sampledb --query-mode=PROFILE --format=yaml \
--sql "SELECT * FROM Singers JOIN Songs USING(SingerId) WHERE SongName LIKE 'Th%e'" |
spannerplanviz --full --type=svg --output profile.svg

You can emit Mermaid.js using --type mermaid (EXPERIMENTAL).
spannerplanviz --full --type=mermaid --output profile.mermaid < dca_profile.json
The generated source uses HTML labels and a browser-friendly init block (htmlLabels: true, useMaxWidth: false). See visualize/testdata/dca_profile.golden.mermaid for a full example output.
--type dot emits pure Graphviz DOT source (unlaid-out, no pos/bb layout attributes) via the dot package. Pipe it into dot -Tsvg (or any Graphviz tool) to lay it out:
spannerplanviz --full --type=dot < dca_profile.json | dot -Tsvg -o profile.svg
--type svg and --type png still produce fully laid-out output; they generate the same DOT source internally and render it with the embedded Graphviz runtime.
You can emit D2 source using --type d2 (EXPERIMENTAL). Lay it out with the d2 CLI:
spannerplanviz --full --type=d2 --output profile.d2 < dca_profile.json
d2 profile.d2 profile.svg
Node labels use D2 markdown blocks (|md ... |), tooltips carry the canonical plan YAML in |yaml ... | block strings, possible remote calls are drawn with a dashed stroke, and --show-query/--show-query-stats add a rounded query node linked from the root. See visualize/testdata/dca_profile.golden.d2 for a full example.
Output types
--type |
Output |
svg (default), png |
Fully laid-out Graphviz raster/vector, rendered with the embedded runtime |
dot |
Unlaid-out Graphviz DOT source; lay out with dot -Tsvg |
mermaid |
Mermaid.js flowchart source |
d2 |
D2 source; lay out with d2 in.d2 out.svg |
--show-query and --show-query-stats add a query-text node and are honored by the svg, png, dot, mermaid, and d2 types.
Library usage
Build a diagram model once, then render with the backend of your choice:
stats, rowType, err := spannerplan.ExtractQueryPlan(input)
plan, err := visualize.BuildPlan(rowType, stats, visualize.StructureBuildOptions())
src, err := mermaid.Source(plan)
Presets:
visualize.StructureBuildOptions() — operator structure for interactive viewers (lighter than --full)
visualize.FullBuildOptions() — same detail level as CLI --full
Renderers:
mermaid.Source(plan) — Mermaid.js source using plan.Build
mermaid.SourceWithOptions(plan, opts) — override plan.Build at render time (including disabling flags)
mermaid.NewRenderer(opts).Render(ctx, w, plan) — streaming render
dot.Source(plan) / dot.SourceWithOptions(plan, opts) — DOT source text without a Graphviz runtime (no goccy/go-graphviz dependency); lay it out elsewhere, e.g. with a browser-side Graphviz build. This is the single source of truth for graph construction.
d2.Source(plan) / d2.SourceWithOptions(plan, opts) — D2 source text; lay it out with the d2 CLI
d2.NewRenderer(opts).Render(ctx, w, plan) — streaming render
graphviz.NewRenderer(opts).Render(ctx, w, plan) — SVG/PNG via Graphviz; internally generates the dot package's source and hands it to the Graphviz runtime, so both paths describe an identical graph
The dot, mermaid and d2 backends all consume a shared backend-neutral graph model (visualize.BuildGraph), so they describe the same nodes, edges and labels.
Browser embedding
When rendering Mermaid in the browser (for example from Go WASM):
- Call
visualize.BuildPlan and mermaid.Source(plan).
- Pass the returned source to mermaid.js
render().
- The source includes a
%%{ init: ... }%% block with htmlLabels: true and useMaxWidth: false. Keep your global mermaid.initialize() consistent with those settings if you set defaults separately.
- Prefer
StructureBuildOptions() for large plans; --full output can be slow to lay out in the browser.
BuildOptions fields mirror CLI flags (metadata, execution-stats, hide-metadata, and so on). See option.Options.BuildOptions() for the full mapping.
Stability
- The
spannerplanviz CLI flags and behavior are treated as stable.
- The Go library API (
visualize.BuildPlan, mermaid.Source, graphviz.NewRenderer, dot.Source, and related types) is experimental and may change between releases.
- This module follows v0 semver: breaking changes may appear in minor releases. See GitHub release notes for details.
- Text plan rendering moved to
spannerplan/cmd/rendertree; the deprecated shim in this repository has been removed.
Disclaimer
This tool is Alpha quality.