plantree

package
v0.3.0-alpha.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 17, 2026 License: Apache-2.0 Imports: 13 Imported by: 1

Documentation

Overview

Package plantree provides functionality to render PlanNode as ASCII tree (EXPERIMENTAL).

RowWithPredicates and tree prefix

The tree prefix is exposed without tying callers to a particular struct field shape:

Prefer these methods over reading the TreePart field directly so future internal representations can change with less churn. Typical rendering uses RowWithPredicates.Text, which combines tree prefix and node title.

The TreePart field remains exported for struct literals and cmp.Diff in tests; new code should use the accessors above when not constructing rows by hand. When tests do construct RowWithPredicates values directly, prefer keyed composite literals so future exported fields do not break the call site.

Rows also expose scalar child links in original PlanNode.ChildLinks order via RowWithPredicates.ScalarChildLinks. Callers should group those links at rendering time using the parent row's RowWithPredicates.DisplayName together with each ScalarChildLink.Type, because the same child-link type can have different meanings under different operators.

A []string field would avoid one strings.Join in the renderer and one strings.Split in Text, but it is a breaking API change for modules that build RowWithPredicates literals in tests (for example github.com/apstndb/spanner-mycli). Downstream production code reviewed at the time of this change uses ProcessPlan and Text/FormatID only, not TreePart field access.

Breaking changes in this package are called out in the release / PR description when they affect exported options or types.

Structural signatures

StructuralSignature returns a deterministic, versioned canonical string for comparing visible relational plan structure. It is intentionally separate from any machine-readable PlanTreeNode / ProcessPlanTree API (see issue #30) and does not expose viewer structured-row DTOs.

The signature ignores plan-node IDs and execution statistics, preserves ordered child occurrences and parent link types, and uses the same depth / occurrence budgets and cycle detection as ProcessPlan. Its metadata field set is every present key and recursively typed value except subquery_cluster_node at any metadata struct depth, whose value is a PlanNode ID. This includes operation_type, raw scan_type, scan_method, seekable_key_size, flags (including false), and future optimizer metadata. New metadata therefore intentionally changes the alpha signature instead of being silently ignored.

Equality is meaningful only for signatures made by the same alpha encoding revision; the encoding may change during the alpha and is not a stable interchange contract. Repeated identical operators can collide because plan-node IDs are intentionally excluded, so comparison layers must expose matching ambiguity. See StructuralSignature and plantree/testdata/signature for the encoding and fixture corpus.

Stability

This package is still marked EXPERIMENTAL. The shape of exported row types (including how TreePart is represented) may change in a future version if we adopt a different internal representation—callers should prefer RowWithPredicates.Text and stable Option entrypoints where possible, and pin module versions when upgrading.

Index

Constants

View Source
const (
	// MaxPlantreeDepth counts the root as depth zero. This conservative
	// first-alpha renderer budget bounds recursive tree construction, row
	// collection, and rendering passes even when a plan contains a deep DAG.
	// It may be raised non-breakingly when real capture evidence requires it.
	MaxPlantreeDepth = 256
	// MaxPlantreeOccurrences bounds visible node occurrences, rather than
	// unique PlanNode indexes, because a DAG can expand exponentially when it
	// is rendered as a tree. This conservative first-alpha renderer budget may
	// be raised non-breakingly when real capture evidence requires it.
	MaxPlantreeOccurrences = 4096
)
View Source
const StructuralSignatureVersion = "spannerplan.structural_signature.v1alpha1"

StructuralSignatureVersion identifies the current alpha encoding revision. It may change in a later alpha release when the canonical encoding or its included fields change.

Variables

View Source
var ErrTraversalLimitExceeded = errors.New("plantree: traversal limit exceeded")

ErrTraversalLimitExceeded identifies a plan whose visible rendered tree exceeds Plantree's fixed resource bounds.

Functions

func StructuralSignature

func StructuralSignature(qp *spannerplan.QueryPlan) (string, error)

StructuralSignature returns a deterministic, versioned canonical string that describes the visible relational plan tree for comparison.

Included:

  • operator display name
  • link type in the parent for each child occurrence
  • every present metadata key, recursively preserving value type and value
  • predicate link types and short-representation descriptions, in ChildLinks order
  • ordered visible child occurrences (DAG reuse expands like ProcessPlan)

Excluded:

  • plan-node indexes / IDs
  • subquery_cluster_node keys at any metadata struct depth, because their values are PlanNode IDs
  • execution statistics and other volatile runtime metrics
  • rendered titles, wrapping, and ASCII tree prefixes

Traversal uses the same depth and occurrence budgets as ProcessPlan (MaxPlantreeDepth, MaxPlantreeOccurrences) and the same cycle detection. Cycles return an error. Budget failures return ErrTraversalLimitExceeded via TraversalLimitError. Malformed plans should be rejected by spannerplan.New before calling this function; remaining link/node guards fail with ordinary errors.

Equality is meaningful only for signatures produced by this same alpha encoding revision. The encoding may change during the alpha, so it is not a stable cross-version or cross-language interchange contract.

Collision limitations: the signature deliberately omits node IDs, so repeated identical operators or shared DAG subtrees produce identical fragments. Comparison / matching layers must expose that ambiguity rather than silently pairing nodes. Equal signatures do not prove the plans are the same physical capture; unequal signatures prove a structural difference within this version's included fields.

This API is intentionally not the machine-readable PlanTreeNode surface discussed in issue #30 and does not expose viewer structured-row DTOs.

Types

type Option

type Option func(*options)

Option configures ProcessPlan.

func DisallowUnknownStats

func DisallowUnknownStats() Option

DisallowUnknownStats makes ProcessPlan fail on unknown execution-stat keys.

func EnableCompact

func EnableCompact() Option

EnableCompact enables compact node title mode.

func WithHangingIndent added in v0.1.6

func WithHangingIndent() Option

WithHangingIndent hangs wrapped continuation lines after a node-local prefix such as `[Input] ` or `[Map] ` instead of keeping the default tree-aligned indentation.

func WithQueryPlanOptions

func WithQueryPlanOptions(opts ...spannerplan.Option) Option

WithQueryPlanOptions forwards node-title formatting options to the underlying query plan renderer.

func WithWrapWidth

func WithWrapWidth(width int) Option

WithWrapWidth sets the maximum total rendered line width, including the tree prefix. Node title text is wrapped to the remaining width after accounting for the tree prefix. A value of 0 disables wrapping (consistent with the rendertree CLI default of 0). Negative values make ProcessPlan return an error.

type RowWithPredicates

type RowWithPredicates struct {
	// ID is the Spanner PlanNode index for this row.
	ID int32
	// TreePart stores everything rendered before NodeText on each visual line: the ASCII tree prefix
	// plus any continuation padding inserted by the renderer for wrapping / hanging indent.
	// Prefer [RowWithPredicates.TreePartString] or [RowWithPredicates.TreePartLines] instead of
	// reading this field directly, so callers stay decoupled if the storage shape changes.
	TreePart string
	// NodeText is the rendered operator title, possibly split across visual lines.
	NodeText string
	// DisplayName is the raw Spanner PlanNode display name, before metadata is folded into NodeText.
	DisplayName string
	// Predicates contains filter predicate text associated with this row.
	Predicates []string
	// ExecutionStats contains execution statistics associated with this row.
	ExecutionStats stats.ExecutionStats
	// ScalarChildLinks contains this row's scalar child links in original PlanNode.ChildLinks order.
	ScalarChildLinks []ScalarChildLink
}

RowWithPredicates is one rendered plan row plus predicate and execution metadata.

func ProcessPlan

func ProcessPlan(qp *spannerplan.QueryPlan, opts ...Option) (rows []RowWithPredicates, err error)

ProcessPlan converts a query plan into rendered tree rows with predicate and execution metadata.

func (RowWithPredicates) FormatID

func (r RowWithPredicates) FormatID() string

FormatID returns the display ID, prefixed with "*" when the row has predicates.

func (RowWithPredicates) Text

func (r RowWithPredicates) Text() string

Text returns the full rendered row text, with the tree prefix prepended to each node text line.

func (RowWithPredicates) TreePartLines added in v0.1.5

func (r RowWithPredicates) TreePartLines() []string

TreePartLines splits RowWithPredicates.TreePartString into one prefix per visual line.

func (RowWithPredicates) TreePartString added in v0.1.5

func (r RowWithPredicates) TreePartString() string

TreePartString returns the full tree-prefix string (newline-separated lines), matching the historical field encoding. Use this when you need a single string; use RowWithPredicates.TreePartLines for per-line access.

type ScalarChildLink struct {
	// Type is the ChildLink type, such as "Condition", "Key", or "Agg".
	Type string
	// Variable is the ChildLink variable, when Spanner provides one.
	Variable string
	// Description is the scalar child node's short representation description.
	Description string
	// DisplayName is the scalar child node's raw PlanNode display name.
	DisplayName string
	// ChildIndex is the scalar child node's PlanNode index.
	ChildIndex int32
}

ScalarChildLink is a scalar child link attached to a rendered plan row.

It keeps raw-ish child-link fields so callers can group links by the parent row's DisplayName and the child-link Type. The same Type can have different semantics under different parent operators, for example Sort Key versus Aggregate Key.

type TraversalLimitError

type TraversalLimitError struct {
	Kind      TraversalLimitKind
	Limit     int
	Observed  int
	NodeIndex int32
}

TraversalLimitError describes a rendered-tree resource bound failure. It unwraps to ErrTraversalLimitExceeded so callers can distinguish a valid but too-large plan from malformed-plan and rendering failures.

func (*TraversalLimitError) Error

func (e *TraversalLimitError) Error() string

func (*TraversalLimitError) Unwrap

func (e *TraversalLimitError) Unwrap() error

Unwrap reports the stable traversal-limit sentinel.

type TraversalLimitKind

type TraversalLimitKind string

TraversalLimitKind identifies which rendered-tree resource bound was exceeded.

const (
	// TraversalLimitDepth reports a node below MaxPlantreeDepth.
	TraversalLimitDepth TraversalLimitKind = "depth"
	// TraversalLimitOccurrences reports more than MaxPlantreeOccurrences
	// visible node occurrences.
	TraversalLimitOccurrences TraversalLimitKind = "occurrences"
)

Directories

Path Synopsis
Package reference provides a reference implementation for rendering Spanner query plans as ASCII tables with various formatting options.
Package reference provides a reference implementation for rendering Spanner query plans as ASCII tables with various formatting options.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL