Help us shape the future of Go! Take the 2026 Go Developer Survey

check

package
v0.18.0 Latest Latest
Warning

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

Go to latest
Published: Oct 6, 2026 License: Apache-2.0 Imports: 18 Imported by: 0

Documentation

Overview

Package check provides various configuration and health checks that can be run against a sql.DB connection.

Index

Constants

This section is empty.

Variables

View Source
var ErrRefused = errors.New("refused by a check")

ErrRefused matches (with errors.Is) a check failure that is a decision rather than an error: the check ran and found a reason to refuse, so a retry would be refused again. A check error that does not match it, such as a failed query, may be transient. A cutover retries on a transient error from its checks under lock, and fails without a retry on a refusal.

View Source
var ErrReplicaNotHealthy = fmt.Errorf("replica is not healthy")

ErrReplicaNotHealthy is returned when a replica's IO or SQL thread is not running.

Functions

func ChecksInScope added in v0.17.0

func ChecksInScope(scope ScopeFlag) []string

ChecksInScope reports the names of the checks registered for scope, in the order RunChecks runs them.

Scope membership is a curated set, not an accident of registration: each scope states what a check in it must tolerate and what its failure entitles the caller to conclude. A caller relying on that contract can pin the set it was written against, so a check added to the scope later has to be judged against the contract rather than inherit a verdict made about its predecessors.

func RunChecks

func RunChecks(ctx context.Context, r Resources, logger *slog.Logger, scope ScopeFlag) error

RunChecks runs all checks that are registered for the given scope. Checks run in name order so that a statement failing more than one check always reports the same error.

logger may be nil: checks log as they run, and a caller classifying a statement without a logger to hand must get a verdict rather than a panic.

func StatementRefusal added in v0.17.0

func StatementRefusal(ctx context.Context, stmt, currentCreateTable string, logger *slog.Logger) (reason string, refused bool, err error)

StatementRefusal reports whether Spirit deterministically refuses stmt, and the reason it would give.

It runs the ScopeStatement checks: the subset of Spirit's preflight checks that decide from the statement — plus, when currentCreateTable is supplied, the table's existing column definitions — and that MySQL's native DDL cannot complete behind their back. That makes it the entry point for a caller which needs to know an apply's outcome before starting it, such as a planning tool classifying DDL. Checks Spirit adds to the scope later are picked up without any change here.

currentCreateTable is the table's current definition, normally its SHOW CREATE TABLE. It must describe the table stmt alters. Pass an empty string when it is not available: coverage then narrows to the checks that need only the statement — the ENUM/SET checks, which compare a redeclared column against its current type, the missing-primary-key refusal, which reads the current key definition, and the primary key collation prediction, which reads the key columns' current charsets and collations, are skipped. The definition must also reflect the table's true key set: SHOW CREATE TABLE output collected with show_gipk_in_create_table_and_information_schema disabled omits a generated invisible primary key and misreports the table as unkeyed.

logger may be nil, in which case the checks' own logging is discarded.

refused is true only when Spirit will refuse the statement, so a caller may act on it. A statement that is not an ALTER TABLE is never refused: Spirit runs CREATE TABLE and DROP TABLE as native DDL rather than through the copy process. err reports input that cannot be classified — an unparseable statement, more than one statement, a currentCreateTable for a different table, or one that does not describe a column the statement redeclares — and is never itself a refusal.

Types

type Resources

type Resources struct {
	DB                   *sql.DB
	Replicas             []*sql.DB
	Table                *table.TableInfo
	NewTable             *table.TableInfo // the altered shadow table; set from ScopePostSetup onwards
	Statement            *statement.AbstractStatement
	Threads              int
	ReplicaMaxLag        time.Duration
	SkipDropAfterCutover bool
	// The following resources are only used by the
	// pre-run checks
	Host               string
	Username           string
	Password           string
	TLSMode            string
	TLSCertificatePath string
	// contains filtered or unexported fields
}

type ScopeFlag

type ScopeFlag uint8

ScopeFlag scopes a check

const (
	ScopeNone        ScopeFlag = 0
	ScopePreRun      ScopeFlag = 1 << 0
	ScopePreflight   ScopeFlag = 1 << 1
	ScopePostSetup   ScopeFlag = 1 << 2
	ScopeCutover     ScopeFlag = 1 << 3
	ScopePostCutover ScopeFlag = 1 << 4
	ScopeTesting     ScopeFlag = 1 << 5
	// ScopeStatement marks checks a caller can run ahead of an apply to learn
	// that Spirit will refuse a statement. Most are preflight checks that also
	// run as part of a migration; a check registered only here predicts, from
	// the statement, a refusal a later scope makes for every statement it
	// refuses. Callers run them via
	// RunChecks with Resources.Statement set and, optionally,
	// Resources.Table — the table's current metadata, which widens coverage to
	// the checks that compare the statement against the existing column
	// definitions. Neither needs a database connection: Table can be built
	// from the table's DDL with statement.CreateTable.ToTableInfo. A check
	// tagged with this scope must tolerate every Resources field except
	// Statement being unset.
	//
	// A failure here is a refusal the caller can report as certain, so the
	// scope only carries checks that no earlier stage can bypass on any
	// server. The migration runner runs this scope itself, once it has loaded
	// the tables and before it attempts MySQL's native DDL, so it refuses
	// every statement these checks refuse. Spirit attempts that native DDL —
	// ALGORITHM=INSTANT, then a safe-INPLACE subset — before it runs
	// preflight checks, and MySQL decides what that completes, which varies
	// with the server version and the table. A preflight check the native DDL
	// may complete (dropadd, rename) is deliberately excluded: claiming those
	// as refusals would report failure for an apply that succeeds, and the
	// runner, running them here, would then make it fail.
	//
	// That exclusion only ever under-reports, which is the safe direction:
	// passing these checks is not a promise Spirit will accept the statement,
	// only a failure is a claim. An excluded check still refuses at preflight
	// whenever the native attempt does not take the statement — Spirit skips
	// the attempt altogether for a multi-table change, and an older server
	// rejects shapes a newer one completes instantly. Checks that need a live
	// connection (existing foreign keys, triggers, privileges, ...) are not in
	// this scope either: they run at preflight, and the foreign key and
	// trigger checks run again before and during the cutover, to catch one
	// created while the migration runs.
	//
	// A caller that reports these refusals somewhere other than its own logs
	// judges the text by reading the checks that produce it, and a check added
	// to the scope is picked up without any change on the caller's side. Pin
	// the membership with ChecksInScope to keep that judgement attached to the
	// checks it was made about.
	ScopeStatement ScopeFlag = 1 << 6
	// ScopeCutoverLocked runs while the cutover holds its table lock, after
	// the final flush and immediately before the RENAME. LOCK TABLES ... WRITE
	// keeps out any DDL that needs a metadata lock on the table, so a check
	// here sees the state the RENAME acts on. The runner stops acting on
	// schema-change notifications once the cutover starts, so this is the
	// only check of anything created while the cutover waits for its lock.
	// A refusal (an error that matches ErrRefused, see refuse) fails the
	// cutover without a retry. Any other error is retried like any other
	// cutover failure, so a check must return ErrRefused for a permanent
	// condition, or it takes the table lock again on every retry. Keep these
	// checks to fast reads: they run while the application's writes to the
	// table are blocked.
	ScopeCutoverLocked ScopeFlag = 1 << 7
)

Jump to

Keyboard shortcuts

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