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 ¶
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.
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.
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 ¶
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 ¶
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 ¶
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 ¶
func Build(in migrations.Inspection) (SchemaPlan, error)
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" )