Documentation
¶
Overview ¶
Package preflight verifies preconditions before the engine writes anything (invariant ST-6). In Phase 1 that is the table-size guard in front of the optimistic attempt: a cancelled rewrite attempt is not a free probe — it holds ACCESS EXCLUSIVE and does real rewrite work for the full statement budget — so above a size threshold the attempt is skipped entirely.
This is a safety-critical core package: see SAFETY.md. It returns proof types with package-private constructors; dangerous downstream APIs accept only the proof.
Index ¶
Constants ¶
const NoSizeLimit int64 = math.MaxInt64
NoSizeLimit is a size limit no PostgreSQL relation can exceed. Callers pass it when the check should prove only existence and kind — the online sequence path, whose long steps are safe on any size by design (the size guard protects blind attempts, not planner-proven online idioms).
Variables ¶
var ErrNotTable = errors.New("not an ordinary or partitioned table")
ErrNotTable is returned when the target exists but is not an ordinary or partitioned table (e.g. a view or foreign table).
var ErrTableNotFound = errors.New("table not found")
ErrTableNotFound is returned when the target table does not exist (or is not visible with the session's search_path).
Functions ¶
func CheckPartitionSupport ¶
func CheckPartitionSupport(table PreflightedTable, serverMajor int, execSQL []string) error
CheckPartitionSupport verifies that the execution steps are safe for the target's relation kind. Ordinary tables and leaf partitions pass unchanged. Supported in-place parent ALTER TABLE operations remain available.
Types ¶
type PartitionRefusalCause ¶
type PartitionRefusalCause string
PartitionRefusalCause identifies which unsupported shape triggered a partitioned-parent refusal. The zero value means the steps are supported.
const ( // PartitionCauseConcurrentIndexBuild means a step attempts a concurrent // index build on the parent. PartitionCauseConcurrentIndexBuild PartitionRefusalCause = "parent-concurrent-index-build" // PartitionCauseBlockingIndexBuild means a step would build an index on // the parent while holding ACCESS EXCLUSIVE. PartitionCauseBlockingIndexBuild PartitionRefusalCause = "parent-blocking-index-build" // PartitionCauseIndexAdoption means a step adopts an existing index as a // primary-key or unique constraint on the parent. PartitionCauseIndexAdoption PartitionRefusalCause = "parent-index-adoption" // PartitionCauseNotValidForeignKey means a step adds a NOT VALID // foreign key, which the server version cannot do on a partitioned // table. PartitionCauseNotValidForeignKey PartitionRefusalCause = "parent-not-valid-foreign-key" )
func RefusesPartitionedParent ¶
func RefusesPartitionedParent(serverMajor int, execSQL []string) (PartitionRefusalCause, error)
RefusesPartitionedParent reports the cause that makes steps unsupported on a partitioned parent, or the zero value when they are supported. It is the shared static policy used by preflight, plan reporting, and executor admission.
type PreflightedTable ¶
type PreflightedTable struct {
// contains filtered or unexported fields
}
PreflightedTable proves the target table exists, is a table, and is under the size threshold for an optimistic attempt. It can only be constructed by CheckTable in this package.
func CheckTable ¶
func CheckTable(ctx context.Context, pool *pgxpool.Pool, schema, table string, limitBytes int64) (PreflightedTable, error)
CheckTable verifies that schema.table (search_path when schema is empty) exists, is an ordinary or partitioned table, and is at most limitBytes on disk. Above the limit it returns a *SizeError; on success it returns the PreflightedTable proof.
func (PreflightedTable) Partitioned ¶
func (t PreflightedTable) Partitioned() bool
Partitioned reports whether the verified target is a partitioned parent. Leaf partitions have relkind 'r' and therefore report false.
func (PreflightedTable) RelTuples ¶
func (t PreflightedTable) RelTuples() float64
RelTuples returns the planner's row estimate (-1 when the table has never been vacuumed or analyzed). Reporting only — the size guard's authority is bytes on disk.
func (PreflightedTable) Schema ¶
func (t PreflightedTable) Schema() string
Schema returns the schema qualification the check ran with (empty when the lookup used the session search_path).
func (PreflightedTable) Table ¶
func (t PreflightedTable) Table() string
Table returns the verified table name.
func (PreflightedTable) TotalBytes ¶
func (t PreflightedTable) TotalBytes() int64
TotalBytes returns the measured on-disk size across all partitions, including indexes and TOAST.
type PrivilegeError ¶
type PrivilegeError struct {
// Tier is the access level the failed check belongs to.
Tier Tier
// Check is the catalog predicate that returned false. It is display
// prose: identifiers appear unquoted, so a renderer embedding it in
// structured output (a markdown table, a PR comment) owns escaping it.
Check string
// Grant is the exact statement that would satisfy the check. Every
// identifier in it is Sanitize()-quoted, so it is safe to echo
// verbatim as executable SQL.
Grant string
// Hint explains the remediation when the Grant alone would surprise
// the operator — for example when its grantee differs from the role
// the Check names. Empty when the Grant speaks for itself.
Hint string
}
PrivilegeError reports a failed access check: the tier that needs it, the catalog check that returned false, and the exact statement that would satisfy it. It is a refusal input, not an operational failure — the same fail-closed posture as every other refusal in the engine. Provisioning rationale lives in docs/engine-role.md.
func (*PrivilegeError) Error ¶
func (e *PrivilegeError) Error() string
Error implements the error interface.
type PrivilegedRole ¶
type PrivilegedRole struct {
// contains filtered or unexported fields
}
PrivilegedRole proves the connected role holds every access the requirement's tier needs against the target table. It can only be constructed by CheckPrivileges in this package. The owning role it carries is the catalog-resolved owner the copy-and-swap path will SET ROLE to for shadow objects.
func CheckPrivileges ¶
func CheckPrivileges(ctx context.Context, pool *pgxpool.Pool, schema, table string, req Requirement) (PrivilegedRole, error)
CheckPrivileges verifies the connected role holds the access the requirement needs against schema.table (search_path resolution when schema is empty), per the tiered contract in docs/engine-role.md. A missing requirement is a *PrivilegeError naming the exact statement that would satisfy it; on success it returns the PrivilegedRole proof.
It runs before CheckTable in the preflight order: a role that cannot see the target would otherwise report "table not found" and mask the real cause.
func (PrivilegedRole) Owner ¶
func (p PrivilegedRole) Owner() string
Owner returns the target table's owning role from the catalog.
func (PrivilegedRole) Role ¶
func (p PrivilegedRole) Role() string
Role returns the connected role the checks ran as.
func (PrivilegedRole) Tier ¶
func (p PrivilegedRole) Tier() Tier
Tier returns the tier the role was verified at.
type Requirement ¶
type Requirement struct {
Tier Tier
// LogicalDecoding requires replication access on top of the tier:
// rds_replication membership where that role exists (Aurora/RDS), the
// REPLICATION role attribute otherwise. Only valid with
// TierCopyAndSwap — no other strategy decodes WAL.
LogicalDecoding bool
}
Requirement states the access a schema change's plan needs: the tier, and whether the strategy decodes WAL (logical-decoding CDC, copy-and-swap only), which additionally requires replication access.
type SizeError ¶
type SizeError struct {
// TotalBytes is the table's measured on-disk size (all partitions,
// including indexes and TOAST).
TotalBytes int64
// LimitBytes is the threshold that was exceeded.
LimitBytes int64
}
SizeError reports that the table exceeds the configured size threshold, so the optimistic attempt must be skipped. It is a refusal input, not an operational failure.
type TargetFacts ¶
type TargetFacts struct {
// contains filtered or unexported fields
}
TargetFacts are the cheap target facts needed by planning and executor admission without measuring the relation or its partition tree.
func LookupTargetFacts ¶
func LookupTargetFacts(ctx context.Context, pool *pgxpool.Pool, schema, table string) (TargetFacts, error)
LookupTargetFacts verifies that the target is an ordinary or partitioned table and returns its relation kind and server major in one catalog query.
func (TargetFacts) Partitioned ¶
func (f TargetFacts) Partitioned() bool
Partitioned reports whether the target is a partitioned parent.
func (TargetFacts) ServerMajor ¶
func (f TargetFacts) ServerMajor() int
ServerMajor returns the PostgreSQL server major version.
type Tier ¶
type Tier int
Tier is the access level a schema change's plan requires from the engine role, per the tiered contract in docs/engine-role.md. Each tier includes everything below it; a change is admitted at the tier its plan requires and nothing higher.
const ( // TierConnect covers connecting and resolving the target: CONNECT on // the database and USAGE on the target schema. The CONNECT rung // documents the contract rather than catching live failures — a role // missing it fails at connection time, before any check runs — while // the USAGE rung is load-bearing: without it a qualified target // masquerades as "table not found". TierConnect Tier = iota // TierAlterInPlace covers owner-gated in-place ALTER TABLE (the // instant and fast native paths): inheritable membership in the // owning role. TierAlterInPlace // TierIndexBuild covers CREATE INDEX [CONCURRENTLY]: CREATE on the // target schema on top of owning-role membership. TierIndexBuild // TierCopyAndSwap covers shadow-object creation: membership usable // with SET ROLE, so shadow objects are born with the correct owner. TierCopyAndSwap )
The contract's tiers, lowest to highest.
func RequiredTier ¶
RequiredTier derives the engine-role tier a routed change's exec SQL needs: an in-place ALTER TABLE step is owner-gated (TierAlterInPlace), and any step that builds a new index — every CREATE INDEX, and the ALTER TABLE shapes that build one as a side effect — additionally needs CREATE on the schema (TierIndexBuild), so the requirement is the most demanding step's tier — the ladder check covers every rung below it. The mapping from step shape to required access lives here, next to Tier and CheckPrivileges, so every consumer of the routed plan derives the same answer. A step shape the engine does not execute fails closed here, before anything runs.
type UnsupportedPartitionedParentError ¶
type UnsupportedPartitionedParentError struct {
// Cause is the unsupported shape that triggered the refusal.
Cause PartitionRefusalCause
}
UnsupportedPartitionedParentError reports that an execution plan contains a step pg-sprite cannot safely run on a partitioned parent. Its rendered message is a fixed English sentence with no interpolated identifiers or server text, so orchestrator-facing surfaces may render it verbatim. This is a deliberate property to preserve.
func (*UnsupportedPartitionedParentError) Error ¶
func (e *UnsupportedPartitionedParentError) Error() string
Error implements the error interface.