DataFusion plans as Excalidraw diagrams. The engine's EXPLAIN and
EXPLAIN ANALYZE output is indented text — an operator per line, metrics
trailing in brackets — which is a poor picture of a join tree. planviz
parses that text (a raw plan cell, or whole terminal output), lays the
tree out in layers, and emits a deterministic .excalidraw file.
Can be used in either of 2 ways:
- as a standalone CLI that eats piped terminal output
(
my-datafusion-tool -c "EXPLAIN ANALYZE ..." | planviz > plan.excalidraw) - as an embedded library call which diagrams the plan the statement just printed, from the same execution. Useful if you're building some kind of CLI tool or wrapper around DataFusion.
Anything that prints an explain table can be piped through the planviz CLI — the whole terminal scroll, banner and closers included:
my-datafusion-tool -c "EXPLAIN ANALYZE SELECT ...;" | planviz > plan.excalidraw
datafusion-cli -q <<'SQL' | planviz --title "Query1" > plan.excalidraw
EXPLAIN SELECT ...;
SQLFlags:
--logicaldiagrams thelogical_planrow instead of the physical one;--title <text>draws a boxed heading above the diagram — long titles fold to one space-separated block, hard-wrapped at 80 columns and capped at three lines with an ellipsis.
Exit codes:
- 0 success
- 1 unparseable input (one
planviz:line on stderr) - 2 usage.
Open the resulting file on excalidraw website or in a local viewer of your choice e.g. in VSCode.
- Single crate, lib + bin: one parser and one fixture suite, shared across both modes of operation.
- The model is datafusion-free and arrow-free: an operator tree with display-grade metrics is the whole contract, so the tree stays portable.
nomfor metric and plan-text parsing — tolerant, combinator-shaped. Manual scanning where the work is positional, right-to-left, nested, or cross-line stateful: docs/nom-vs-manual.md records that boundary and why it holds.- Boxes carry three numbers on their face — rows, time, bytes (plus
spills=Nwhen an operator actually spilled) — because Excalidraw has no tooltip or popup behind which to hide additional metrics, and trying to print all of them inside one node shape just makes it too wide and ugly to be useful so I picked the ones I find most useful. Every other metric an ANALYZE printed lands instead in an appendix below the diagram: three columns of cards, each titled with its operator's numbered name. The number is a live cross-reference: each[nn]suffix and each card title carry an Excalidraw element-link to the other, which jumps with a zoom when the file is viewed on excalidraw.com (because of an excalidraw limitation that the link's host must match the viewing app's, viewing it elsewhere opens that host in a new tab). - Deterministic output: fixed seeds (base + element index), fixed serde
field order, compact JSON, one fixed
updatedstamp (the app rewrites it on first edit) — the same plan always renders byte-identical. - Heat tint ranks nodes by quantile over
elapsed_compute, not by share of a total: parallel branches sum past 1. - The CLI mode will diagram the last plan in the stream you feed into it — the one a piped shell just closed with.
Apache-2.0 — see LICENSE.

