Skip to content

Add internal/planrender adapter for query plan rendering - #94

Merged
apstndb merged 1 commit into
mainfrom
feat/plan-render
Sep 8, 2026
Merged

apstndb merged 1 commit into
mainfrom
feat/plan-render

Conversation

@apstndb

@apstndb apstndb commented Sep 8, 2026

Copy link
Copy Markdown
Owner

Summary

  • Library-only slice: new internal/planrender adapter renders Spanner query plans as text, DOT, Mermaid, D2, SVG, or PNG from a rows-less ResultSet envelope (metadata.rowType + stats).
  • No CLI flags and no main.go behavior change. --plan-format=text|dot|mermaid|d2|svg|png wiring waits for the split-output feature.
  • Adds github.com/apstndb/spannerplan v0.3.0 and github.com/apstndb/spannerplanviz v0.11.0. No nographviz build tag in this PR.

API

Package internal/planrender (v0/experimental libraries stay behind this boundary; only spannerpb types leak):

  • type Format string with FormatText, FormatDOT, FormatMermaid, FormatD2, FormatSVG, FormatPNG
  • ParseFormat(string) (Format, error)
  • Format.IsBinary() bool (true for PNG)
  • Format.NeedsGraphviz() bool (true for SVG/PNG)
  • type Options struct { TextStyle, WrapWidth, PrintSections, Full, ShowQuery, ShowQueryStats, Query }
    • Text defaults: TextStyle=current, WrapWidth=0, PrintSections=basic
    • Graph defaults: StructureBuildOptions; Full selects FullBuildOptions
    • Options that do not apply to the chosen format are rejected (not ignored)
  • Render(ctx, w, format, rowType, stats, opts) error
    • Text: reference.RenderTreeTableWithOptions(..., RenderModeAuto, ...)
    • Graphs: visualize.BuildPlan(rowType, stats, buildOpts) then dot/mermaid/d2 renderers, or graphviz.NewRenderer(...).Render for SVG/PNG
    • Typed protobufs only (no ExtractQueryPlan JSON round trip); rowType may be nil
    • Missing/empty plan: ErrNoQueryPlan (--query-mode=PLAN, PROFILE, or WITH_PLAN_AND_STATS)
    • Traversal-budget and cycle errors from spannerplan are surfaced
    • Graphviz runtime is created and closed inside spannerplanviz v0.11.0 Renderer.Render

Dependencies

Module Version Why
github.com/apstndb/spannerplan v0.3.0 Text table renderer; occurrence-aware parent links; traversal budgets/cycle detection
github.com/apstndb/spannerplanviz v0.11.0 Graph builders/renderers; requires Go 1.25 and spannerplan v0.3.0; fixes Apply-with-repeated-child edge labels

v0.11.0 is the published tag (local checkout was v0.10.2). Indirectly pulls goccy/go-graphviz v0.2.10 and tetratelabs/wazero v1.10.1 (pure Go, CGO_ENABLED=0). golang.org/x/text moves v0.37.0v0.38.0 via golang.org/x/image.

Measurements

Scratch CLI builds that keep planrender.Render live (not committed). Baseline is origin/main 5dbad3b in .tmp/baseline-main.

Binary Bytes MiB Delta vs base
origin/main CLI 53,463,906 51.0
CLI + planrender without Graphviz (dot/mermaid/d2/text) 53,590,658 51.1 +126,752 (124 KiB)
CLI + full planrender (SVG/PNG / wazero) 59,873,906 57.1 +6,410,000 (6.11 MiB)
  • spannerplan + spannerplanviz without Graphviz: +124 KiB
  • extra Graphviz/wazero runtime: +6.00 MiB
  • Under the design note's ~10 MiB skip-the-tag heuristic. Maintainer can still add nographviz later.

Cross-compile (CGO_ENABLED=0 go build ./internal/planrender/... and go vet)

Target build vet
linux/amd64 OK OK
linux/386 OK OK
linux/arm64 OK OK
windows/amd64 OK OK
windows/386 OK OK
windows/arm64 OK OK
darwin/amd64 OK OK
darwin/arm64 OK OK

linux/386 and windows/386 build; wazero has no optimizing compiler on 386 (interpreter fallback). Not benchmarked here.

govulncheck

govulncheck ./... exit 1 (findings). Same two call-site vulns on origin/main:

ID Module Branch origin/main Notes
GO-2026-6061 google.golang.org/grpc@v1.81.1 (fixed v1.82.1) yes yes Pre-existing; traces through main.go / internal/grpctest
GO-2026-5970 golang.org/x/text (fixed v0.39.0) v0.38.0 v0.37.0 Pre-existing (< v0.39.0); this PR only moved 0.37.0 → 0.38.0

No new vulnerability IDs. Not upgraded here (out of library-adapter scope).

Tests

Command Exit
go build ./... 0
go vet ./... 0
go test ./internal/planrender ./params/... ./jqresult/... ./resultset/... 0
GOTOOLCHAIN=go1.25.13 golangci-lint run 0
golangci-lint run (host go1.27.1) 1 (toolchain typecheck on samber/lo / math/rand/v2; reran with Go 1.25.13)
govulncheck ./... 1 (findings above)
DOCKER_HOST=unix://$HOME/.colima/default/docker.sock TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE=/var/run/docker.sock go test ./... 0

planrender coverage: PROFILE and PLAN-style AUTO text goldens (testdata/profile/singers_limit3.json); DOT/Mermaid/D2 smoke (root operator); one SVG (<svg) and one PNG (magic bytes) on a 1-node plan, skippable with -short; nil/empty plan, dangling childIndex, DAG repeated child, cycle, Unicode/control predicates, option validation matrix, cancelled graph context.

Follow-up

CLI wiring after split-output lands: --plan-format=text\|dot\|mermaid\|d2\|svg\|png and the text/graph tuning flags from docs/design/plan-rendering.md, dispatching json/yaml locally and everything else to planrender.Render.

Wrap experimental spannerplan v0.3.0 and spannerplanviz v0.11.0 behind
Format/Options/Render so the CLI can later emit text and graph plans
without leaking library types. v0.11.0 is required for Go 1.25 and the
occurrence-aware parent-link API. Linking the adapter into a scratch CLI
adds about 124 KiB without Graphviz and about 6.0 MiB more for wazero;
all eight CGO_ENABLED=0 release targets build.
@apstndb apstndb added the enhancement New feature or request label Sep 8, 2026
@apstndb
apstndb merged commit ba46921 into main Sep 8, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant