statement

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Aug 19, 2026 License: Apache-2.0 Imports: 7 Imported by: 0

Documentation

Overview

Package statement parses SQL through the real PostgreSQL grammar (wasilibs/go-pgquery, Wasm libpg_query) and reports the facts the engine's front door needs. In Phase 1 that is a statement-type gate only: which kind of statement this is and, for ALTER TABLE, which table it targets. No schema model, no classification.

Two canonical forms coexist deliberately: this package's deparser prints grammar-canonical SQL (e.g. varchar(50)) for formatting, while pkg/schemadiff models carry server-decompiled text (character varying(50)) for comparison. The two canons never mix: models only ever compare server output against server output, and deparser output must not feed a model comparison or a schema fingerprint.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrEmptyDesired is returned when the input contains no statements.
	ErrEmptyDesired = errors.New("desired schema contains no statements")
	// ErrNoCreateTable is returned when the input has no CREATE TABLE.
	ErrNoCreateTable = errors.New("desired schema must contain a CREATE TABLE")
	// ErrMultipleCreateTables is returned for more than one CREATE TABLE:
	// the engine is single-table scoped.
	ErrMultipleCreateTables = errors.New("desired schema must contain exactly one CREATE TABLE")
	// ErrDisallowedStatement is returned for any statement kind other than
	// CREATE TABLE / CREATE INDEX. The desired file is executed verbatim on
	// a scratch schema, so only pure schema definition is admitted.
	ErrDisallowedStatement = errors.New("statement kind not allowed in a desired schema")
	// ErrQualifiedName is returned when a statement schema-qualifies its
	// target. Desired files are schema-relative; the live schema comes from
	// the caller, and qualification could escape the scratch schema.
	ErrQualifiedName = errors.New("desired schema statements must use unqualified names")
	// ErrConcurrentIndex is returned for CREATE INDEX CONCURRENTLY, which
	// cannot run inside the scratch transaction.
	ErrConcurrentIndex = errors.New("CONCURRENTLY cannot be used in a desired schema")
	// ErrForeignKey is returned when the CREATE TABLE carries a REFERENCES
	// clause. The scratch transaction cannot faithfully execute a foreign
	// key: an unqualified reference resolves against the scratch
	// search_path, not the target schema, so it either fails or silently
	// binds to the wrong table. Foreign-key support needs its own design
	// (cross-file ordering, lock behavior, qualification policy); until
	// then the admission gate refuses it.
	ErrForeignKey = errors.New("foreign keys are not supported in a desired schema")
	// ErrWrongIndexTarget is returned when an index targets a table other
	// than the desired CREATE TABLE.
	ErrWrongIndexTarget = errors.New("index must target the desired table")
)

Typed refusals for desired-state schema files. Each names one rule of the declarative front door; the caller branches with errors.Is, never on text.

View Source
var (
	// ErrNotRewritable is returned when the statement kind has no
	// CONCURRENTLY form to rewrite to.
	ErrNotRewritable = errors.New("statement has no concurrent form")
	// ErrNotValidNotApplicable is returned when the statement is not a
	// single named ADD CHECK / ADD FOREIGN KEY that could take NOT VALID.
	ErrNotValidNotApplicable = errors.New("statement cannot take NOT VALID")
)

Typed refusals for advisory rewrites. The rewriters flip one syntactic flag and deparse — no semantics are derived; a statement that cannot be rewritten that way is refused with one of these.

View Source
var ErrCommentLoss = errors.New("input contains comments, which formatting would discard")

ErrCommentLoss is returned when an operation that reprints SQL through the deparser would silently discard comments. The parser drops comments at parse time, so a formatter cannot carry them; refusing is the fail-closed alternative to destroying documentation in a source-of-truth file.

View Source
var ErrNotOneStatement = errors.New("input must contain exactly one SQL statement")

ErrNotOneStatement is returned by ParseOne when the input does not contain exactly one SQL statement.

Functions

func AddNotValid

func AddNotValid(sql string) (rewritten, constraint string, err error)

AddNotValid rewrites a single-command ALTER TABLE ... ADD CONSTRAINT (named CHECK or FOREIGN KEY) to its NOT VALID form and returns the rewritten statement plus the constraint name for the follow-up VALIDATE CONSTRAINT step.

func Canonical

func Canonical(sql string) (string, error)

Canonical reprints one statement through the PostgreSQL deparser: grammar-canonical spelling, unnecessary quoting dropped. Commented input is refused (ErrCommentLoss): the parser drops comments, and reprinting must never silently discard content. It is the rendering the plan report carries, so both front doors describe the same change with the same string. Deparser output never feeds a model comparison or a schema fingerprint (see the package comment); the plan report's fingerprint is a plan identity, not a schema fingerprint.

func CheckNoComments

func CheckNoComments(sql string) error

CheckNoComments scans sql with the PostgreSQL lexer and returns ErrCommentLoss when it contains any SQL (--) or C-style (/* */) comment. A scan failure is surfaced to the caller, never guessed around.

func Concurrently

func Concurrently(sql string) (string, error)

Concurrently returns sql rewritten to its CONCURRENTLY form: CREATE INDEX, DROP INDEX, REINDEX, or ALTER TABLE ... DETACH PARTITION. The rewrite flips the grammar's concurrency flag and deparses — nothing else changes. A statement already concurrent comes back canonicalized.

func Qualify

func Qualify(sql, schema string) (string, error)

Qualify returns sql with its target relation qualified by schema; an empty schema strips an existing qualification instead. It supports exactly one CREATE TABLE, CREATE INDEX, or ALTER TABLE statement. This touches qualification only — no semantics are ever derived or transformed at the AST level (that is the scratch database's job).

Types

type ConstraintKind

type ConstraintKind int

ConstraintKind names the constraint families the classifier routes differently.

const (
	ConstraintUnrecognized ConstraintKind = iota
	ConstraintPrimaryKey
	ConstraintUnique
	ConstraintCheck
	ConstraintForeignKey
	ConstraintNotNull
)

The constraint families ParseOps distinguishes. ConstraintUnrecognized (e.g. EXCLUDE) has no known safe pattern and is refused.

type DefaultKind

type DefaultKind int

DefaultKind classifies the DEFAULT expression shape of an added column. Only a provable constant qualifies for PostgreSQL's fast default; any other expression is treated as volatile, conservatively.

const (
	// DefaultNone: no DEFAULT clause.
	DefaultNone DefaultKind = iota
	// DefaultConstant: a literal (possibly type-cast) — fast-default safe.
	DefaultConstant
	// DefaultExpression: anything else — function calls, identity, serial.
	// The engine does not evaluate volatility offline; it assumes the worst.
	DefaultExpression
)

The default shapes an added column can carry.

type DesiredSchema

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

DesiredSchema is a validated desired-state schema file: exactly one CREATE TABLE plus any number of CREATE INDEX statements on that table. Statement SQL is canonical (parsed and deparsed through the PostgreSQL grammar), in input order, one statement per entry.

Only ParseDesired produces a non-zero value, so holding one is proof the set-level admission rules held: a single unqualified CREATE TABLE, every index on that table, none of them CONCURRENTLY.

func ParseDesired

func ParseDesired(sql string) (DesiredSchema, error)

ParseDesired parses a desired-state schema file and admits only what the declarative front door can execute on a scratch schema: one unqualified CREATE TABLE and unqualified, non-concurrent CREATE INDEX statements on it. Anything else is refused with a typed error.

func (DesiredSchema) Statements

func (ds DesiredSchema) Statements() []Statement

Statements returns the admitted statements in input order, the CREATE TABLE among them. The slice is a copy: mutating it cannot invalidate the admission proof the value carries.

func (DesiredSchema) Table

func (ds DesiredSchema) Table() string

Table returns the unqualified name of the single CREATE TABLE target.

type Kind

type Kind int

Kind is the statement-type bucket the Phase 1 gate branches on.

const (
	KindOther Kind = iota
	KindAlterTable
	KindCreateIndex
	KindDropIndex
	KindReindex
	KindCreateTable
)

The kinds the gate distinguishes. Everything the engine does not recognize as one of the named kinds is KindOther and is refused by the front door.

func (Kind) String

func (k Kind) String() string

String returns the human-readable name of the kind.

type Op

type Op struct {
	// Kind is the operation shape.
	Kind OpKind
	// Column is the target column for column operations.
	Column string
	// Name is the constraint or index name where the operation has one,
	// or the new name for renames.
	Name string
	// Columns are the plain key columns of an ADD PRIMARY KEY / UNIQUE;
	// empty when the keys are expressions.
	Columns []string
	// Constraint is the constraint family for OpAddConstraint.
	Constraint ConstraintKind
	// NotValid is true for ADD CONSTRAINT ... NOT VALID.
	NotValid bool
	// UsingIndex is true for ADD CONSTRAINT ... USING INDEX.
	UsingIndex bool
	// Concurrent is true when the statement carries CONCURRENTLY.
	Concurrent bool
	// Unique is true for CREATE UNIQUE INDEX.
	Unique bool
	// IfNotExists is true for CREATE INDEX IF NOT EXISTS.
	IfNotExists bool
	// GeneratedStored is true for ADD COLUMN ... GENERATED ... STORED.
	GeneratedStored bool
	// InlineConstraints are the table-constraint families an added column
	// carries inline (UNIQUE, PRIMARY KEY, REFERENCES, CHECK) — each does
	// the same index build or validation scan as its ADD CONSTRAINT form.
	// An inline constraint the engine does not model is reported as
	// ConstraintUnrecognized so the classifier can refuse it.
	InlineConstraints []ConstraintKind
	// PartitionOf is true for CREATE TABLE ... PARTITION OF, which locks
	// the partitioned parent, not just the new relation.
	PartitionOf bool
	// Default is the DEFAULT shape for OpAddColumn.
	Default DefaultKind
	// NewType is the target type for OpAlterColumnType and the column type
	// for OpAddColumn, as the bare grammar type name (e.g. "varchar",
	// "numeric") without the pg_catalog qualification.
	NewType string
	// NewTypeMods are the target type's modifiers (e.g. 50 in varchar(50),
	// 12 and 2 in numeric(12,2)); empty when unconstrained.
	NewTypeMods []int32
	// HasUsing is true for ALTER COLUMN TYPE ... USING <expr>, which always
	// means a conversion, never a binary-coercible relabel.
	HasUsing bool
}

Op is one parsed operation: the shape facts the classifier needs, nothing executable. Fields beyond Kind are populated only where meaningful for that kind; see each field's comment.

func ParseOps

func ParseOps(sql string) ([]Op, error)

ParseOps parses one SQL statement and returns its typed operations. An ALTER TABLE yields one Op per subcommand; every other supported statement yields exactly one. Statements and subcommands the engine does not recognize come back as OpUnrecognized — never an error — so the classifier can refuse them with context. A parse failure is surfaced to the caller.

func (Op) Describe

func (o Op) Describe() string

Describe returns a short operator-facing label for the operation, e.g. "ADD COLUMN age" — for plan rendering, never for branching.

type OpKind

type OpKind int

OpKind names one operation shape the classifier distinguishes. A single ALTER TABLE statement yields one Op per subcommand; index, rename, and schema statements yield exactly one.

const (
	OpUnrecognized OpKind = iota
	OpCreateTable
	OpAddColumn
	OpDropColumn
	OpAlterColumnType
	OpSetDefault
	OpDropDefault
	OpSetNotNull
	OpDropNotNull
	OpSetColumnOptions
	OpRenameColumn
	OpRenameTable
	OpRenameIndex
	OpSetSchema
	OpSetTablespace
	OpSetRelOptions
	OpAddConstraint
	OpValidateConstraint
	OpDropConstraint
	OpAttachPartition
	OpDetachPartition
	OpCreateIndex
	OpDropIndex
	OpReindex
)

The operation shapes ParseOps reports. OpUnrecognized is everything the engine does not recognize; the classifier refuses it.

type SourceStatement

type SourceStatement struct {
	// SQL is the statement's verbatim source text, without the trailing
	// semicolon, so it can be found in the source by exact match.
	SQL string
	// Line is the 1-based source line of the statement's first token.
	Line int
	// Column is the 1-based source column of the statement's first token.
	Column int
}

SourceStatement is one script statement located in its source: the verbatim text plus the position of its first token, so a consumer can point a finding back at the exact place in the file it came from.

func Split

func Split(sql string) ([]SourceStatement, error)

Split parses a script with the PostgreSQL grammar and returns each statement with its verbatim text and source position, in input order. Every statement is also reprinted through the deparser as a validation gate: a statement the grammar cannot roundtrip is an error, never a guess. An empty script returns no statements; a parse failure anywhere in the script is surfaced to the caller, never guessed around.

type Statement

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

Statement is one parsed SQL statement plus the facts the gate needs. It can only be constructed by ParseOne, so holding one proves the SQL parsed as exactly one statement through the PostgreSQL grammar — the proof the executor requires before running anything (invariant ST-7).

func ParseOne

func ParseOne(sql string) (Statement, error)

ParseOne parses sql with the PostgreSQL grammar and requires exactly one statement. A parse failure is surfaced to the caller, never guessed around.

func (Statement) BuildsIndex

func (s Statement) BuildsIndex() bool

BuildsIndex reports whether executing the statement creates a new index: every CREATE INDEX, and the ALTER TABLE shapes that build one as a side effect — ADD CONSTRAINT UNIQUE / PRIMARY KEY / EXCLUDE without USING INDEX, and ADD COLUMN with an inline UNIQUE or PRIMARY KEY. The server requires CREATE on the schema for these (the engine-role contract's index build tier), unlike in-place ALTERs — including USING INDEX adoption and rewrites that rebuild existing indexes, which are owner-gated only.

func (Statement) Concurrent

func (s Statement) Concurrent() bool

Concurrent reports whether an index statement used its CONCURRENTLY form. It is always false for non-index kinds.

func (Statement) Kind

func (s Statement) Kind() Kind

Kind returns the statement-type bucket.

func (Statement) SQL

func (s Statement) SQL() string

SQL returns the original statement text as submitted.

func (Statement) Schema

func (s Statement) Schema() string

Schema returns the target table's schema qualification for ALTER TABLE, CREATE TABLE, and CREATE INDEX statements; empty when the statement was unqualified (search_path resolves it) or when the kind has no single table target.

func (Statement) Table

func (s Statement) Table() string

Table returns the target table name for ALTER TABLE, CREATE TABLE, and CREATE INDEX statements; empty for other kinds.

Jump to

Keyboard shortcuts

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