schemaplan

package
v0.7.0 Latest Latest
Warning

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

Go to latest
Published: Oct 4, 2026 License: MIT Imports: 19 Imported by: 0

Documentation

Overview

Package schemaplan classifies the schema change a migration would make before it is written (SCHEMA-2, #309), and lints the migrations already written (SCHEMA-4, #311). It evaluates the HCL that `atlas schema inspect` prints, diffs two states with Atlas's own differ, and ranks every change by what it can do to existing rows. The plan uses only that diff. Lint also reads each migration's statements, with the fail-safe HOST-3 classifier (manifest.Classify), for data loss the diff cannot show, and its declared renames.

It is a separate package so the Atlas SQL libraries stay out of package migrations, which generated apps' loaders compile.

Index

Constants

View Source
const (
	StepAddTable         = "add_table"
	StepDropTable        = "drop_table"
	StepAddColumn        = "add_column"
	StepDropColumn       = "drop_column"
	StepAddNotNull       = "add_not_null"
	StepSetNotNull       = "set_not_null"
	StepDropNotNull      = "drop_not_null"
	StepNarrowType       = "narrow_type"
	StepChangeType       = "change_type"
	StepWidenType        = "widen_type"
	StepChangeDefault    = "change_default"
	StepAddIndex         = "add_index"
	StepDropIndex        = "drop_index"
	StepAddUnique        = "add_unique"
	StepAddForeignKey    = "add_foreign_key"
	StepDropForeignKey   = "drop_foreign_key"
	StepChangeForeignKey = "change_foreign_key"
	StepAddCheck         = "add_check"
	StepDropCheck        = "drop_check"
	StepChangePrimaryKey = "change_primary_key"
	StepTableRebuild     = "table_rebuild"
	StepChangeCharset    = "change_charset"
	StepChangeCollation  = "change_collation"
	StepChangeComment    = "change_comment"
	StepChangeGenerated  = "change_generated"
	StepRenameIndex      = "rename_index"
	StepRenameForeignKey = "rename_foreign_key"
	StepRenameCheck      = "rename_check"
	StepOther            = "other"
)

Step codes. A step ID is "<code>:<table>" or "<code>:<table>.<name>", where name is a column, index, or constraint. --allow accepts an ID or a bare code.

View Source
const (
	StepExtraSchema   = "extra_schema"
	StepMissingSchema = "missing_schema"
)

Schema-level drift codes. They exist only in DatabaseDrift: a migration plan never creates or drops a schema.

View Source
const (
	StepRenameTable  = "rename_table"
	StepRenameColumn = "rename_column"
	// Statement-level findings: SQL the migration runs whose effect on rows
	// the before/after schema diff cannot show.
	StepDataChange      = "data_change"
	StepAlterColumn     = "alter_column"
	StepUnclassifiedSQL = "unclassified_sql"
)

Step codes for renames a migration declares in its own SQL.

Variables

This section is empty.

Functions

func Gate

func Gate(allow []string, stderr io.Writer) migrations.Gate

Gate is the migrations.Gate the gombit CLI installs on makemigrations and make resource. It classifies the migration Atlas just wrote exactly as `gombit db lint` will: the schema diff with the renames the SQL declares, plus what the SQL's statements do to rows. It refuses a migration with a destructive or unsafe step that allow (or --forget-model, for its table) does not acknowledge, printing the plan to stderr, and returns the acknowledged step IDs for the migration to record. The persisted acknowledgements and lint's gate are therefore one set.

func WriteLint

func WriteLint(w io.Writer, r LintReport)

WriteLint renders a lint report for a terminal.

func WritePlan

func WritePlan(w io.Writer, p SchemaPlan)

WritePlan renders a plan for a terminal. Destructive and unsafe steps come with the ID to acknowledge them by.

Types

type DeclaredRename

type DeclaredRename struct {
	Table  string
	Column string
	To     string
}

DeclaredRename is a rename a migration states in SQL: a table rename when Column is empty, otherwise Column on Table (the table's name at that point in the migration) becomes To.

func DeclaredRenames

func DeclaredRenames(sql string) []DeclaredRename

DeclaredRenames reads the table and column renames a migration's SQL states, in statement order. Comments are ignored.

type LintFinding

type LintFinding struct {
	File string
	Step PlanStep
}

LintFinding is a destructive or unsafe step in a migration file that no `-- gombit:allow` line covers.

type LintOptions

type LintOptions struct {
	WorkDir      string
	Driver       config.DatabaseDriver
	MigrationDir string
	AtlasBinary  string
	// Latest, when positive, classifies only the N newest migrations (a local
	// shortcut on slow dev databases). Zero classifies every migration, which
	// is what CI should run. The integrity check always covers the whole
	// directory.
	Latest int
	Stderr io.Writer
}

LintOptions configures Lint.

type LintReport

type LintReport struct {
	Driver config.DatabaseDriver `json:"driver"`
	// Integrity is the atlas.sum / replay problem, if any. The migrations are
	// not classified while it stands.
	Integrity string `json:"integrity,omitempty"`
	// Misplaced are *.sql files in the migration directory that are not up
	// migrations (a down file outside downs/, a bad name).
	Misplaced  []string        `json:"misplaced,omitempty"`
	Migrations []MigrationLint `json:"migrations"`
}

LintReport is the result of Lint.

func Lint

func Lint(ctx context.Context, opts LintOptions) (LintReport, error)

Lint checks a migration directory: its integrity (atlas.sum and a clean replay, through `atlas migrate validate`), its layout, and the safety of every migration (or the Latest newest, when set). Each migration is classified from the schema before and after it, with the renames it declares applied, and a destructive or unsafe step passes only when the migration carries a `-- gombit:allow` line for it.

func (LintReport) Failed

func (r LintReport) Failed() bool

Failed reports whether the directory needs fixing.

func (LintReport) Unacknowledged

func (r LintReport) Unacknowledged() []LintFinding

Unacknowledged returns every destructive or unsafe step no `-- gombit:allow` line covers.

type MigrationLint

type MigrationLint struct {
	Version string     `json:"version"`
	Name    string     `json:"name"`
	File    string     `json:"file"`
	Steps   []PlanStep `json:"steps"`
}

MigrationLint is one migration's classified changes.

type PlanStep

type PlanStep struct {
	ID       string   `json:"id"`
	Code     string   `json:"code"`
	Severity Severity `json:"severity"`
	Table    string   `json:"table"`
	// Name is the column, index, or constraint the step changes. It is empty
	// for a table-level step.
	Name   string `json:"name,omitempty"`
	Detail string `json:"detail"`
	// Hint is a safer alternative or a way to make the change apply.
	Hint string `json:"hint,omitempty"`
	// Acknowledged is set by SchemaPlan.Acknowledge for a step an --allow
	// entry (or --forget-model, for a dropped table) covers.
	Acknowledged bool `json:"acknowledged"`
	// contains filtered or unexported fields
}

PlanStep is one classified schema change.

func DatabaseDrift

func DatabaseDrift(driver config.DatabaseDriver, expected, live []byte, ignore []string) ([]PlanStep, error)

DatabaseDrift compares the live application database (live, the HCL `atlas schema inspect` prints for it) with the schema its applied migrations build (expected, the migration directory inspected at the last applied version). Each step is something the database has that the migrations do not say, or the reverse: a column added by hand, an index dropped outside a migration, a table moved to another schema. No steps means no drift.

Two things are set aside, and nothing else. The ignore tables (Gombit's and Atlas's bookkeeping) are dropped by name, along with Atlas's own revisions schema once it holds nothing else. On MySQL, where a schema is the database itself, the one schema on each side is compared as one: the dev database the migrations are replayed on is named "dev", the live one after the application database, and its charset and collation are the server's.

func (PlanStep) NeedsAcknowledgement

func (s PlanStep) NeedsAcknowledgement() bool

NeedsAcknowledgement reports whether the step is destructive or unsafe and nothing has acknowledged it.

type SchemaPlan

type SchemaPlan struct {
	Driver config.DatabaseDriver `json:"driver"`
	Steps  []PlanStep            `json:"steps"`
	// contains filtered or unexported fields
}

SchemaPlan is the classified difference between the schema the migration directory builds and the schema the models declare: what the next makemigrations would write.

func Build

Build classifies an inspection.

func BuildWithRenames

func BuildWithRenames(in migrations.Inspection, renames []DeclaredRename) (SchemaPlan, error)

BuildWithRenames classifies an inspection whose desired state follows from the current one through the given renames, as a migration that declares them does. The renames are applied to the current schema first, so the diff shows what else changed (a narrowed type on a renamed column still classifies), and each rename is a safe step: the rows stay.

func (*SchemaPlan) Acknowledge

func (p *SchemaPlan) Acknowledge(allow []string) (unmatched []string)

Acknowledge marks the steps an --allow entry covers. An entry is a step ID (drop_column:products.name) or a bare code (drop_column) that covers every step with that code. The drop of a forgotten model's table is acknowledged too. It returns the entries that matched no step, which usually means a typo.

func (SchemaPlan) Unacknowledged

func (p SchemaPlan) Unacknowledged() []PlanStep

Unacknowledged returns the destructive or unsafe steps nothing acknowledged.

type Severity

type Severity string

Severity ranks a planned schema change by what it can do to existing rows.

const (
	// SeveritySafe changes add structure or relax a rule. Existing rows keep
	// their data and the migration applies to a populated table.
	SeveritySafe Severity = "safe"
	// SeverityReview changes apply, but change behavior a reviewer should
	// confirm (a delete rule, a rebuilt table, a widened type).
	SeverityReview Severity = "review"
	// SeverityUnsafe changes can fail to apply to a table that already has
	// rows (a new NOT NULL column with no default, a new unique index).
	SeverityUnsafe Severity = "unsafe"
	// SeverityDestructive changes lose data (a dropped table or column, a
	// narrowed type).
	SeverityDestructive Severity = "destructive"
)

Jump to

Keyboard shortcuts

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