Documentation
¶
Overview ¶
Package migrate checks a schema-change draft, .aicoded/migration.sql, and applies an app's migrations to its database as the database's owner.
A draft holds at most 64 KiB of UTF-8 and 100 statements. Check splits it into statements and allows only those that change the schema of the app's own tables (Classify): Expand ones, which add to it, CREATE TABLE, CREATE INDEX, and ALTER TABLE with only ADD clauses, where a new NOT NULL column has a DEFAULT; and Destructive ones, in a fixed set of DROP, RENAME, TRUNCATE and ALTER TABLE forms, which Notes names with E-MIG-003. Everything else is refused with E-MIG-002. Each publish adds its draft's statements to the app's list of steps, and Apply runs the steps that the database's journal, the table aicoded_migrations, lacks, a destructive one only when its caller allows it.
The aicoded CLI and the platform import it. App code may not: E-LINT-001 refuses the import.
Index ¶
- Constants
- func Check(draft []byte) ([]string, error)
- func Destructives(stmts []string) ([]int, error)
- func Extends(ran, steps []Step) error
- func IsJournal(name string) bool
- func Normalize(stmt string) string
- func Notes(draft []byte) []error
- func Split(draft []byte) ([]string, error)
- type Kind
- type Step
Examples ¶
Constants ¶
const CodeRollback = "E-MIG-007"
CodeRollback is the code that refuses a rollback whose release would leave behind steps that ran in production and hold a destructive one.
Variables ¶
This section is empty.
Functions ¶
func Check ¶
Check splits a draft into its statements and classifies each. It returns the statements and one error that joins every problem, each at the line of the draft its statement starts on. A destructive statement is not a problem; Notes names it.
Example ¶
package main
import (
"fmt"
"aicoded.dev/framework/migrate"
)
func main() {
statements, err := migrate.Check([]byte("CREATE TABLE notes (id BIGINT AUTO_INCREMENT PRIMARY KEY, body TEXT);\n" +
"ALTER TABLE notes ADD COLUMN author VARCHAR(100) NULL;\n"))
fmt.Println(len(statements), err)
}
Output: 2 <nil>
func Destructives ¶ added in v0.7.0
Destructives returns the indexes in stmts of the destructive statements, or the first error of Classify.
func Extends ¶
Extends returns E-MIG-004 unless ran, the steps a database's journal holds, starts steps, or steps starts ran (a rollback). Steps match by Seq, Publish, Index and SHA256.
func IsJournal ¶
IsJournal reports whether name, a table name without quotes, names the journal of migrations, aicoded_migrations, under any case folding MySQL may apply to table names: Unicode simple folding, and İ and ı to i. Lint uses it to keep app code away from the journal.
func Normalize ¶
Normalize returns the form of stmt, a statement as Split returns it, in which two statements that MySQL reads alike are the same: its tokens with one space between each two, without comments or a final ;, and each word outside quotes in upper case, but for a table's name, after TABLE, ON or REFERENCES, and a word that starts with a digit, such as 0x1f. MySQL reads keywords and the names of columns and indexes in any case, while a table's name keeps its case. Strings and names in backquotes are kept as they are. Text the checks cannot read is returned without surrounding space.
func Notes ¶ added in v0.7.0
Notes returns E-MIG-003, at the line of the draft it starts on, for each destructive statement of a draft, in order. It returns nil for a draft that Split refuses, and leaves out the statements that Check refuses.
Types ¶
type Kind ¶
type Kind int
Kind is the kind of a statement that Classify allows.
const ( // Expand is a statement that adds to the schema and changes or removes nothing in it. Expand Kind = 1 // Destructive is a statement that removes, renames or changes what the schema of the app's own // tables has. A release applies it only with an approval, while the app is stopped. Destructive Kind = 2 )
func Classify ¶
Classify returns Expand for a statement that only adds to the schema of the app's own database: CREATE TABLE, CREATE INDEX, and ALTER TABLE with only ADD clauses. It returns Destructive for one that removes, renames or changes what is there, on the app's own tables only: DROP TABLE [IF EXISTS], DROP INDEX … ON, RENAME TABLE, TRUNCATE [TABLE], and ALTER TABLE whose clauses add or are DROP [COLUMN], DROP INDEX or KEY, DROP FOREIGN KEY, DROP PRIMARY KEY, DROP CHECK or CONSTRAINT, MODIFY, CHANGE, RENAME TO, AS, COLUMN, INDEX or KEY, or ALTER [COLUMN] … SET DEFAULT, DROP DEFAULT, SET VISIBLE or SET INVISIBLE. It refuses every other statement, and any it cannot read, with E-MIG-002.
Example ¶
package main
import (
"fmt"
"aicoded.dev/framework/internal/errs"
"aicoded.dev/framework/migrate"
)
func main() {
kind, err := migrate.Classify("ALTER TABLE notes DROP COLUMN author")
fmt.Println(kind == migrate.Destructive, err)
_, err = migrate.Classify("DROP DATABASE notes")
fmt.Println(errs.Code(err))
}
Output: true <nil> E-MIG-002
type Step ¶
type Step struct {
// Seq is the step's place in the app's list, from 1.
Seq int `json:"seq"`
// Publish is the id of the publish that added the step, or "draft" locally.
Publish string `json:"publish"`
// Index is the step's place within its publish, from 1.
Index int `json:"index"`
// SHA256 is the lower-case hex SHA-256 of Statement.
SHA256 string `json:"sha256"`
// Statement is the SQL statement.
Statement string `json:"statement"`
}
Step is one statement of an app's migrations.
func Apply ¶
func Apply(ctx context.Context, db *sql.DB, steps []Step, destructive bool) (applied []Step, err error)
Apply runs on db the steps that its journal, the table aicoded_migrations, lacks, in order, and returns them. It holds the database's migration lock meanwhile, a named lock of aicoded_migrations: and the hex SHA-256 of the database's name cut to 64 characters, on one connection with lock_wait_timeout 30, and runs each step under a 5-minute context, then adds its row to the journal.
It refuses, before running anything, a list whose steps are not numbered from 1 or whose hash does not match its statement (E-MIG-004), a list the journal does not agree with (E-MIG-004, see Extends), a step to run that Classify refuses, and, unless destructive is true, a step to run that Classify finds Destructive (E-MIG-003). A caller passes true only for an approved release while the app is stopped, or for a database of test data. A step that fails stops Apply with E-MIG-005; the journal keeps the steps that ran. So does a step that ran but whose row could not be added to the journal, even on a new connection; its message says it ran but was not recorded.