Documentation
¶
Overview ¶
Package plan defines the machine-readable dry-run plan report: the stable JSON contract an operator or orchestrator consumes to decide whether and how a change would execute. Both front doors emit it — the imperative migrate --dry-run path and the declarative diff path — so a consumer parses one shape regardless of how the plan was derived.
Index ¶
- Constants
- func CreateShapeRefusal(cause executor.CreateShapeCause) (verdict.Refusal, bool)
- func DiscloseGreenfieldExecution(report *Report)
- func Fingerprint(statements []Statement) string
- func PartitionRefusal(cause preflight.PartitionRefusalCause) (verdict.Refusal, bool)
- func RefuseUnsupportedCreateShape(report *Report, refused []error) error
- func RefuseUnsupportedPartitionedParent(report *Report, causes []preflight.PartitionRefusalCause) error
- func RouteRefusal() verdict.Refusal
- type Report
- type Source
- type Statement
Constants ¶
const FormatVersion = 5
FormatVersion identifies the report contract. A consumer must reject a report whose version it does not understand instead of guessing at the field semantics. Version 2 added the guidance field on rewrite-required statements. Version 3 added the cause field on greenfield statements the create path refuses by shape. Version 4 added the class and owner fields on the report and on refused statements, and closed their vocabularies. Version 5 added blocking_passthrough_eligible to refused statements.
Variables ¶
This section is empty.
Functions ¶
func CreateShapeRefusal ¶ added in v0.3.3
func CreateShapeRefusal(cause executor.CreateShapeCause) (verdict.Refusal, bool)
CreateShapeRefusal classifies the create path's shape refusal of a statement in a greenfield plan. The closed key set is executor.CreateShapeCauses(); ok is false for a cause outside it, which callers treat as an invariant violation rather than classify.
func DiscloseGreenfieldExecution ¶ added in v0.3.0
func DiscloseGreenfieldExecution(report *Report)
DiscloseGreenfieldExecution makes executable statements describe the plain, bounded builds used for a table born in this run. The report must describe a table that does not exist yet; the function returns without mutation unless the report establishes that precondition. Each create step commits in its own transaction under the brief lock_timeout and statement_timeout budget, so CREATE TABLE is visible before its indexes build and a concurrent writer that already knows the name makes the step fail fast rather than block. The build is classified metadata-only because its cost is bounded by that budget on a table born in the run. Reclassifying keeps the statement in a state router.Route can produce: an execute disposition never carries a safer-idiom decision without its rewrite.
func Fingerprint ¶
Fingerprint computes the plan's stable identity: "sha256:" plus the hex digest over what would execute — each statement's canonical SQL, route, backend, disposition, and exec_sql, in plan order. Explanatory fields (decisions, kind, destructive, reason, cause, guidance) are excluded, so a reworded reason does not change identity but a rerouted or resequenced plan does. The exact serialization is part of the contract (docs/plan-report.md) and changes only with a format_version bump. This is a plan identity, not a schema fingerprint: it never participates in schema-state comparison.
func PartitionRefusal ¶ added in v0.3.3
func PartitionRefusal(cause preflight.PartitionRefusalCause) (verdict.Refusal, bool)
PartitionRefusal classifies a partitioned-parent refusal by its cause. The closed key set is preflight.PartitionRefusalCauses(); ok is false outside it. The four causes span three classes, which is why the plan carries the cause rather than a bare refused flag. The refusal keeps its typed cause, so every consumer of the proof — both front doors and the accepted-blocking eligibility registry — reads one narrowing instead of re-deriving it from the preflight error.
func RefuseUnsupportedCreateShape ¶ added in v0.3.0
RefuseUnsupportedCreateShape marks the create-path statements whose connection-free shape checks refuse them and stamps each one with the executor's typed cause. refused is positional over report.Statements — one entry per planned statement, nil where the statement is admitted. A length mismatch means the two sides no longer agree on what the plan contains, so no positional marking is safe and the report is left untouched. A refusal the cause vocabulary does not name is a contract violation: the report would carry a refusal it cannot explain, so it fails closed before any statement is marked.
func RefuseUnsupportedPartitionedParent ¶
func RefuseUnsupportedPartitionedParent(report *Report, causes []preflight.PartitionRefusalCause) error
RefuseUnsupportedPartitionedParent marks statements as refused when partition-aware admission rejects their execution steps. causes is positional over report.Statements — one entry per planned statement, empty where the statement is admitted — because the class of the refusal is a property of the cause (docs/refusal-classes.md), not of the reason. A length mismatch, or a cause the classification registry does not name, is a contract violation: the report would carry a refusal it cannot explain, so it fails closed before any statement is marked.
func RouteRefusal ¶ added in v0.3.3
RouteRefusal classifies a routed statement the planner has no safe path for: an admitted ALTER TABLE operation with no route. The key is the site.
Types ¶
type Report ¶
type Report struct {
// FormatVersion is the report contract version; always FormatVersion.
FormatVersion int `json:"format_version"`
// Source is the front door that derived the plan.
Source Source `json:"source"`
// Schema is the target schema; empty when the submitted statement did
// not qualify one.
Schema string `json:"schema,omitempty"`
// Table is the target table; empty when the statement has no single
// table target (index maintenance).
Table string `json:"table,omitempty"`
// ServerVersion is the PostgreSQL server_version the plan was derived
// against. Classification is version-sensitive, so a stored or
// forwarded report names the server whose rules produced it; empty
// only for sources that never connected.
ServerVersion string `json:"server_version,omitempty"`
// TableExists reports whether the live table was found. Set by every
// source that introspects the target (diff, and the alter dry run);
// nil means the plan has no single table target to introspect. For
// diff, false means the statements are the full desired schema; for
// an alter dry run, false means the plan was classified from zero
// facts and executing it would fail.
TableExists *bool `json:"table_exists,omitempty"`
// Disposition is the aggregate disposition across all statements:
// what would happen if the engine executed this plan now.
Disposition router.Disposition `json:"disposition"`
// Reason is the typed refusal cause when target facts make an otherwise
// executable routed plan unsafe.
Reason verdict.Reason `json:"reason,omitempty"`
// Class identifies how a consumer routes an aggregate refusal.
Class verdict.Class `json:"class,omitempty"`
// Owner identifies who owns aggregate work with no online-safety problem.
Owner verdict.Owner `json:"owner,omitempty"`
// Fingerprint is the plan's stable identity (see Fingerprint). An
// approver pins it when the plan is reviewed; an executor recomputes it
// at apply time and refuses on mismatch — that is how "the plan a
// reviewer approves is the plan that executes" is enforced across
// storage and forwarding.
Fingerprint string `json:"fingerprint"`
// Statements is the ordered plan; empty means there is nothing to do.
Statements []Statement `json:"statements"`
}
Report is the dry-run plan for one change against one table.
type Source ¶
type Source string
Source identifies which front door derived the plan.
const ( // SourceAlter marks a plan derived from a submitted DDL statement // (migrate --alter --dry-run): the classify-and-route pipeline with // the diff step skipped. SourceAlter Source = "alter" // SourceDiff marks a plan derived from a desired-state schema diff // (diff --desired): the ordered statements that converge the live // table on the desired schema. SourceDiff Source = "diff" )
type Statement ¶
type Statement struct {
// SQL is the statement in the engine's canonical rendering: parsed and
// reprinted through the PostgreSQL deparser, whichever front door
// derived it. It is never a verbatim echo of the submitted text, so
// the same change carries the same string through either door.
SQL string `json:"sql"`
// Kind classifies a diff-derived statement so a consumer can gate
// whole classes of change (see schemadiff.ChangeKind). Empty for the
// alter source: a submitted statement may carry several operations and
// has no single kind.
Kind schemadiff.ChangeKind `json:"kind,omitempty"`
// Destructive marks statements that discard live structure — a dropped
// column, constraint, index, or NOT NULL. It is derived from the classifier's
// decisions, so both sources report it identically; it is always
// emitted, never omitted, because a safety flag a consumer gates on
// must be explicit even when false.
Destructive bool `json:"destructive"`
// Route is the planner's aggregate route for the statement.
Route planner.Route `json:"route"`
// Backend is the assigned execution strategy; empty for refusals.
Backend router.Backend `json:"backend,omitempty"`
// Disposition is what execution would do with the statement now.
Disposition router.Disposition `json:"disposition"`
// Reason is the typed cause when target facts refuse this statement.
Reason verdict.Reason `json:"reason,omitempty"`
// Class identifies how a consumer routes a refused statement.
Class verdict.Class `json:"class,omitempty"`
// Owner identifies who owns work with no online-safety problem.
Owner verdict.Owner `json:"owner,omitempty"`
// BlockingPassthroughEligible reports whether an operator may request the
// bounded blocking path for this refusal. Present exactly on refusals.
BlockingPassthroughEligible *bool `json:"blocking_passthrough_eligible,omitempty"`
// Cause is the create path's typed shape refusal for a statement of a
// greenfield plan (executor.CreateShapeCause): why a table born in the
// run cannot carry this statement. Present exactly when the create path
// refused the statement by shape — on a diff-source plan whose table
// does not exist, that is every statement with Disposition refuse and
// Reason unsupported-statement. Absent for every other refusal,
// including an alter-source refusal against an absent table. Stored
// rather than derived because a JSON consumer has no desired schema to
// recompute the shape check from; it is the executor's own explanation,
// so a renderer prints Description() instead. Explanatory, so it is
// excluded from the fingerprint.
Cause executor.CreateShapeCause `json:"cause,omitempty"`
// Decisions are the planner's per-operation classifications.
Decisions []planner.Decision `json:"decisions"`
// ExecSQL is the ordered SQL the native backend would run — the safer
// sequence when the planner constructed one. Empty for non-native
// routes.
ExecSQL []string `json:"exec_sql,omitempty"`
// Execution is the typed execution contract for ExecSQL
// (planner.Execution), present exactly when ExecSQL is. A consumer
// that runs the statements itself branches on it — it is what says
// each step runs in its own implicit transaction, never inside an
// enclosing transaction block. It is derived from ExecSQL's presence,
// so it is excluded from the fingerprint like the other explanatory
// fields.
Execution planner.Execution `json:"execution,omitempty"`
// Guidance is the typed manual path for a rewrite-required refusal,
// drawn from the suggest contract's Guidance vocabulary
// (docs/suggest-report.md): the engine will not run the statement, and
// this names what to do instead. Present exactly when Disposition is
// rewrite-required. Explanatory, so it is excluded from the
// fingerprint.
Guidance suggest.Guidance `json:"guidance,omitempty"`
}
Statement is one planned statement: the SQL, its classification, and what execution would do with it now.
func FromRouted ¶
FromRouted converts one routed statement into a plan statement. Destructive is derived from the classifier's decisions — one destructive operation makes the statement destructive — so every source that routes through the planner reports it identically by construction. A rewrite-required statement additionally carries the typed manual path (Guidance), derived through the same mapping the suggest report uses; a rewrite-required decision with no known guidance is a contract violation and fails closed.