Documentation
¶
Overview ¶
Package check provides various configuration and health checks that can be run against a sql.DB connection.
Index ¶
- Variables
- func ChecksInScope(scope ScopeFlag) []string
- func RunChecks(ctx context.Context, r Resources, logger *slog.Logger, scope ScopeFlag) error
- func StatementRefusal(ctx context.Context, stmt, currentCreateTable string, logger *slog.Logger) (reason string, refused bool, err error)
- type Resources
- type ScopeFlag
Constants ¶
This section is empty.
Variables ¶
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.
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
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 ¶
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 )
Source Files
¶
- check.go
- configuration.go
- dropadd.go
- enumreorder.go
- enumsetremoval.go
- enumsetutil.go
- foreignkey.go
- illegalclause.go
- primarykey.go
- primarykeybit.go
- primarykeycollation.go
- primarykeycollation_statement.go
- primarykeyexists.go
- primarykeyfloat.go
- privileges.go
- refusal.go
- rename.go
- replicahealth.go
- replicaprivileges.go
- setreorder.go
- settings.go
- statement_refusal.go
- tableidentifier.go
- tablename.go
- triggers.go
- version.go