explain

package
v0.13.0 Latest Latest
Warning

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

Go to latest
Published: Sep 5, 2026 License: MIT Imports: 10 Imported by: 0

Documentation

Overview

Package explain renders Cypher execution plans as human-readable text (EXPLAIN mode) and instruments them with per-operator execution statistics (PROFILE mode).

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func FormatPlanTable added in v0.13.0

func FormatPlanTable(rows []PlanRow) string

FormatPlanTable renders rows as a Neo4j-style columnar plan table:

+-----------------------+----------+----------+
| Operator              | Est.Rows | Vars     |
+-----------------------+----------+----------+
| ProduceResults        |      100 | n        |
| └─ NodeByLabelScan    |      100 | n:Person |
+-----------------------+----------+----------+

Each column is as wide as its widest cell (never narrower than its header), measured in RUNES rather than bytes: the tree connectors (└─, ├─, │) are multi-byte UTF-8, so a byte-width measurement pads a deeply indented row short and the right-hand border walks left as the tree descends. The measurement is rune count, which is the correct width for every character this renderer emits (ASCII plus the box-drawing set, all single-width); a caller who puts a double-width character — CJK, an emoji — into a cell will still see that cell render narrow, because no terminal-width table can be correct for every font without measuring the terminal.

The Operator and Vars columns are left-aligned, Est.Rows right-aligned. Output ends with a trailing newline. Rendering is a pure function of rows, so it is safe for concurrent use and deterministic.

func FormatReport

func FormatReport(r ProfileReport) string

FormatReport formats r as a Neo4j-style table:

+--------------------------+--------+---------+-----------+
| Operator                 |   Rows | DbHits  | Time (ms) |
+--------------------------+--------+---------+-----------+
| NodeByLabelScan          |    100 |     100 |     0.012 |
| ProduceResults           |    100 |       0 |     0.001 |
+--------------------------+--------+---------+-----------+
| Total                    |    200 |     100 |     0.013 |
+--------------------------+--------+---------+-----------+

func TextTree

func TextTree(plan ir.LogicalPlan) string

TextTree renders a logical plan in Neo4j-style columnar text:

+-------------------------+----------+----------+
| Operator                | Est.Rows | Vars     |
+-------------------------+----------+----------+
| ProduceResults          |        - | n        |
| └─ NodeByLabelScan      |        - | n        |
+-------------------------+----------+----------+

It walks plan depth-first into PlanRow values and hands them to FormatPlanTable. Operator names come from ir.OperatorName, the same naming the engine's own plan renderers use, so a plain Apply reads as CartesianProduct and an Expand with a bound destination as ExpandInto exactly as they do in EXPLAIN. A node type ir.OperatorName does not know falls back to its concrete Go type name rather than to "Unknown", which keeps an out-of-tree plan node legible.

Est.Rows comes from the optional [estimator] interface. No in-tree plan node implements it, so every cell of a plan built by this module's planner renders "-"; the engine's cardinality estimates reach a plan table through FormatPlanTable instead. Vars lists the variables returned by ir.LogicalPlan.Vars.

Output is stable across runs: no map iteration order is relied upon. Children appear in ir.LogicalPlan.Children order.

Example

ExampleTextTree renders a logical plan as a columnar text table. The tree follows the plan's child order, so the output is stable across runs.

package main

import (
	"fmt"
	"strings"

	"github.com/FlavioCFOliveira/GoGraph/cypher/explain"
	"github.com/FlavioCFOliveira/GoGraph/cypher/ir"
)

func main() {
	plan := ir.NewProduceResults(
		[]string{"n"},
		ir.NewNodeByLabelScan("n", "Person"),
	)

	out := explain.TextTree(plan)

	// The rendered table is a fixed-width box; assert its structure rather
	// than embedding the exact padding so the example stays readable.
	fmt.Println("has header:", strings.Contains(out, "Operator") && strings.Contains(out, "Est.Rows"))
	fmt.Println("has root:", strings.Contains(out, "ProduceResults"))
	fmt.Println("has child:", strings.Contains(out, "NodeByLabelScan"))
	fmt.Println("child is nested:", strings.Contains(out, "└─ NodeByLabelScan"))
}
Output:
has header: true
has root: true
has child: true
child is nested: true

Types

type DbHitsCounter

type DbHitsCounter struct {
	// contains filtered or unexported fields
}

DbHitsCounter is a per-pipeline counter for logical storage accesses (index lookups, property reads, CSR neighbour scans). It is incremented by instrumented operator wrappers and read by the PROFILE reporter.

DbHitsCounter is safe for concurrent use.

func (*DbHitsCounter) Add

func (c *DbHitsCounter) Add(delta uint64)

Add increments the counter by delta.

func (*DbHitsCounter) Load

func (c *DbHitsCounter) Load() uint64

Load returns the current counter value.

func (*DbHitsCounter) Reset

func (c *DbHitsCounter) Reset()

Reset sets the counter back to zero.

type InstrumentedScan

type InstrumentedScan struct {
	// contains filtered or unexported fields
}

InstrumentedScan is a thin wrapper that counts dbHits per Next call. It adds 1 dbHit per row fetched from the underlying scan operator.

InstrumentedScan is NOT safe for concurrent use.

func NewInstrumentedScan

func NewInstrumentedScan(op exec.Operator, counter *DbHitsCounter) *InstrumentedScan

NewInstrumentedScan wraps op with a dbHit counter. Each successful Next call adds 1 to counter.

func (*InstrumentedScan) Close

func (s *InstrumentedScan) Close() error

Close implements exec.Operator. It delegates to the inner operator.

func (*InstrumentedScan) Init

func (s *InstrumentedScan) Init(ctx context.Context) error

Init implements exec.Operator. It delegates to the inner operator.

func (*InstrumentedScan) Next

func (s *InstrumentedScan) Next(out *exec.Row) (bool, error)

Next implements exec.Operator. It delegates to the inner operator and on a successful (true, nil) return increments the dbHits counter by 1.

type OperatorStats

type OperatorStats struct {
	// Name is the display name assigned when the operator was wrapped.
	Name string
	// Rows is the number of rows produced by successful Next calls.
	Rows uint64
	// DbHits is the number of logical storage accesses (see [DbHitsCounter]).
	DbHits uint64
	// ElapsedNs is the total nanoseconds spent inside Next across all calls.
	ElapsedNs int64
}

OperatorStats accumulates execution statistics for one operator.

The instance a ProfiledOperator embeds is mutated in place by every ProfiledOperator.Next call, without synchronisation, so it is NOT safe for concurrent use while its pipeline is draining: reading it from another goroutine — including through ProfiledOperator.Stats — races the accumulation. ProfiledOperator.Stats returns a copy, so once the pipeline has been drained that snapshot is an ordinary value and may be shared and read freely.

type PlanRow added in v0.13.0

type PlanRow struct {
	// Operator is the operator's display name, prefixed with its tree indent.
	Operator string
	// EstRows is the estimated row count cell, right-aligned when rendered.
	EstRows string
	// Vars is the comma-joined list of variables the operator exposes.
	Vars string
}

PlanRow is one operator line of a rendered plan table.

Operator already carries the tree indentation and connector for the node's depth, so a row is rendered verbatim into the Operator column; EstRows and Vars are the remaining two cells. A caller building rows by hand is free to leave EstRows empty, which renders as an empty cell rather than "-": "-" is the renderer's marker for "this node offers no estimate", and inventing it for a caller who simply did not fill the field would be a fabrication.

Concurrency

A PlanRow is a plain value carrying no shared state and no synchronisation of its own. Distinct rows are safe for concurrent use; a SINGLE row is not — it must not be written by one goroutine while another reads it, exactly as for any struct. FormatPlanTable only reads the rows it is handed, so a slice of rows no longer being mutated is safe for concurrent rendering.

type ProfileReport

type ProfileReport struct {
	// Operators holds per-operator statistics in the order they were added.
	Operators []OperatorStats
	// TotalRows is the sum of all operator row counts.
	TotalRows uint64
	// TotalDbHits is the sum of all operator dbHits.
	TotalDbHits uint64
	// ElapsedMs is the total wall-clock time in milliseconds.
	ElapsedMs float64
}

ProfileReport is the textual PROFILE output collected after draining a pipeline instrumented with ProfiledOperator wrappers.

A report is assembled once, after the drain, and nothing here mutates it afterwards — FormatReport takes it by value and only reads it — so a finished report is safe for concurrent reads by any number of goroutines. The one caveat is Operators: every copy of the report shares that single backing array, so a caller must not append to it or overwrite its elements while another goroutine reads the report.

type ProfiledOperator

type ProfiledOperator struct {
	// contains filtered or unexported fields
}

ProfiledOperator wraps an exec.Operator and records per-call statistics. It implements exec.Operator.

ProfiledOperator is NOT safe for concurrent use.

func NewProfiledOperator

func NewProfiledOperator(op exec.Operator, name string) *ProfiledOperator

NewProfiledOperator wraps op, assigning it the display name given by name.

func (*ProfiledOperator) Close

func (p *ProfiledOperator) Close() error

Close implements exec.Operator. It delegates to the inner operator.

func (*ProfiledOperator) Init

func (p *ProfiledOperator) Init(ctx context.Context) error

Init implements exec.Operator. It delegates to the inner operator.

func (*ProfiledOperator) Next

func (p *ProfiledOperator) Next(out *exec.Row) (bool, error)

Next implements exec.Operator. It delegates to the inner operator, incrementing Rows on each (true, nil) return and accumulating elapsed time.

func (*ProfiledOperator) Stats

func (p *ProfiledOperator) Stats() OperatorStats

Stats returns the accumulated statistics for this operator.

Jump to

Keyboard shortcuts

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