dialect/sql/schema: versioned migrations (#2337)

* dialect/sql/schema: fix go doc

* all: update atlas

* dialect/sql/schema: diff connected DB and defined schema and write changes to migration file

* dialect/sql/schema: use migration directory and formatter directly instead of a planner

* all: update atlas

* cmd: add command to create a new migration file

* entc/gen: generate main.go with migrate diff example

* all: regenerate

* cmd/internal/base: make linter happy

* all: support Go 1.16 in versioned migrations main.go

* entc/gen: put versioned migrations behind feature flag

* all: regenerate

* cmd/ent: driver-prefixed dsn in migrate diff command

* cmd/internal/base: remove prefix from migrate import

* cmd/internal/base: use cobra.CheckErr

* cmd: remove diff command

* entc/gen/template/dialect/sql/feature: remove generated main.go

* all: rebase on master and go mod tidy

* all: regenerate
This commit is contained in:
MasseElch
2022-02-21 11:15:17 +01:00
committed by GitHub
parent f5a5a6ef0e
commit 0239daca7b
37 changed files with 10497 additions and 52 deletions

View File

@@ -12,12 +12,11 @@ import (
"sort"
"strings"
"ariga.io/atlas/sql/migrate"
"ariga.io/atlas/sql/schema"
"entgo.io/ent/dialect"
entsql "entgo.io/ent/dialect/sql"
"entgo.io/ent/schema/field"
"ariga.io/atlas/sql/migrate"
"ariga.io/atlas/sql/schema"
)
type (
@@ -61,8 +60,8 @@ func WithDiffHook(hooks ...DiffHook) MigrateOption {
}
}
// SkipChanges allows skipping/filtering list of changes
// returned by the differ before executing migration planning.
// WithSkipChanges allows skipping/filtering list of changes
// returned by the Differ before executing migration planning.
//
// SkipChanges(schema.DropTable|schema.DropColumn)
//
@@ -72,7 +71,7 @@ func WithSkipChanges(skip ChangeKind) MigrateOption {
}
}
// A Change of schema.
// A ChangeKind denotes the kind of schema change.
type ChangeKind uint
// List of change types.
@@ -173,15 +172,15 @@ func filterChanges(skip ChangeKind) DiffHook {
type (
// Applier is the interface that wraps the Apply method.
Applier interface {
// Diff creates the given tables in the database.
// Apply applies the given migrate.Plan on the database.
Apply(context.Context, dialect.ExecQuerier, *migrate.Plan) error
}
// The ApplyFunc type is an adapter to allow the use of ordinary function as Applier.
// If f is a function with the appropriate signature, ApplyFunc(f) is a Applier that calls f.
// If f is a function with the appropriate signature, ApplyFunc(f) is an Applier that calls f.
ApplyFunc func(context.Context, dialect.ExecQuerier, *migrate.Plan) error
// ApplyHook defines the "migration applying middleware". A function that gets a Applier and returns a Applier.
// ApplyHook defines the "migration applying middleware". A function that gets an Applier and returns an Applier.
ApplyHook func(Applier) Applier
)
@@ -190,7 +189,7 @@ func (f ApplyFunc) Apply(ctx context.Context, conn dialect.ExecQuerier, plan *mi
return f(ctx, conn, plan)
}
// func adds a list of ApplyHook to the schema migration.
// WithApplyHook adds a list of ApplyHook to the schema migration.
//
// schema.WithApplyHook(func(next schema.Applier) schema.Applier {
// return schema.ApplyFunc(func(ctx context.Context, conn dialect.ExecQuerier, plan *migrate.Plan) error {
@@ -221,6 +220,20 @@ func WithAtlas(b bool) MigrateOption {
}
}
// WithDir sets the atlas migration directory to use to store migration files.
func WithDir(dir migrate.Dir) MigrateOption {
return func(m *Migrate) {
m.atlas.dir = dir
}
}
// WithFormatter sets atlas formatter to use to write changes to migration files.
func WithFormatter(fmt migrate.Formatter) MigrateOption {
return func(m *Migrate) {
m.atlas.fmt = fmt
}
}
type (
// atlasOptions describes the options for atlas.
atlasOptions struct {
@@ -228,6 +241,8 @@ type (
diff []DiffHook
apply []ApplyHook
skip ChangeKind
dir migrate.Dir
fmt migrate.Formatter
}
// atBuilder must be implemented by the different drivers in
@@ -245,7 +260,7 @@ type (
func (m *Migrate) setupAtlas() error {
// Using one of the Atlas options, opt-in to Atlas migration.
if !m.atlas.enabled && (m.atlas.skip != NoChange || len(m.atlas.diff) > 0 || len(m.atlas.apply) > 0) {
if !m.atlas.enabled && (m.atlas.skip != NoChange || len(m.atlas.diff) > 0 || len(m.atlas.apply) > 0) || m.atlas.dir != nil {
m.atlas.enabled = true
}
if !m.atlas.enabled {
@@ -270,6 +285,9 @@ func (m *Migrate) setupAtlas() error {
if k == NoChange {
m.atlas.diff = append(m.atlas.diff, filterChanges(k))
}
if m.atlas.dir != nil && m.atlas.fmt == nil {
m.atlas.fmt = migrate.DefaultFormatter
}
return nil
}
@@ -289,36 +307,7 @@ func (m *Migrate) atCreate(ctx context.Context, tables ...*Table) error {
return err
}
}
drv, err := m.atOpen(tx)
if err != nil {
return err
}
current, err := drv.InspectSchema(ctx, "", &schema.InspectOptions{
Tables: func() (t []string) {
for i := range tables {
t = append(t, tables[i].Name)
}
return t
}(),
})
if err != nil {
return err
}
tt, err := m.aTables(ctx, m, tx, tables)
if err != nil {
return err
}
// Diff changes.
var differ Differ = DiffFunc(drv.SchemaDiff)
for i := len(m.atlas.diff) - 1; i >= 0; i-- {
differ = m.atlas.diff[i](differ)
}
changes, err := differ.Diff(current, &schema.Schema{Name: current.Name, Attrs: current.Attrs, Tables: tt})
if err != nil {
return err
}
// Plan changes.
plan, err := drv.PlanChanges(ctx, "plan", changes)
plan, err := m.atDiff(ctx, tx, tables...)
if err != nil {
return err
}
@@ -344,6 +333,39 @@ func (m *Migrate) atCreate(ctx context.Context, tables ...*Table) error {
return tx.Commit()
}
func (m *Migrate) atDiff(ctx context.Context, conn dialect.ExecQuerier, tables ...*Table) (*migrate.Plan, error) {
drv, err := m.atOpen(conn)
if err != nil {
return nil, err
}
current, err := drv.InspectSchema(ctx, "", &schema.InspectOptions{
Tables: func() (t []string) {
for i := range tables {
t = append(t, tables[i].Name)
}
return t
}(),
})
if err != nil {
return nil, err
}
tt, err := m.aTables(ctx, m, conn, tables)
if err != nil {
return nil, err
}
// Diff changes.
var differ Differ = DiffFunc(drv.SchemaDiff)
for i := len(m.atlas.diff) - 1; i >= 0; i-- {
differ = m.atlas.diff[i](differ)
}
changes, err := differ.Diff(current, &schema.Schema{Name: current.Name, Attrs: current.Attrs, Tables: tt})
if err != nil {
return nil, err
}
// Plan changes.
return drv.PlanChanges(ctx, "", changes)
}
type db struct{ dialect.ExecQuerier }
func (d *db) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) {

View File

@@ -7,9 +7,11 @@ package schema
import (
"context"
"crypto/md5"
"errors"
"fmt"
"math"
"ariga.io/atlas/sql/migrate"
"entgo.io/ent/dialect"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/schema/field"
@@ -137,7 +139,7 @@ func NewMigrate(d dialect.Driver, opts ...MigrateOption) (*Migrate, error) {
}
// Create creates all schema resources in the database. It works in an "append-only"
// mode, which means, it only create tables, append column to tables or modifying column type.
// mode, which means, it only creates tables, appends columns to tables or modifies column types.
//
// Column can be modified by turning into a NULL from NOT NULL, or having a type conversion not
// resulting data altering. From example, changing varchar(255) to varchar(120) is invalid, but
@@ -159,6 +161,19 @@ func (m *Migrate) Create(ctx context.Context, tables ...*Table) error {
return creator.Create(ctx, tables...)
}
// Diff compares the state read from the StateReader with the state defined by Ent.
// Changes will be written to migration files by the configures Planner.
func (m *Migrate) Diff(ctx context.Context, tables ...*Table) error {
if m.atlas.dir == nil {
return errors.New("no migration directory given")
}
plan, err := m.atDiff(ctx, m, tables...)
if err != nil {
return err
}
return migrate.New(nil, m.atlas.dir, m.atlas.fmt).WritePlan(plan)
}
func (m *Migrate) create(ctx context.Context, tables ...*Table) error {
tx, err := m.Tx(ctx)
if err != nil {
@@ -383,7 +398,6 @@ func (m *Migrate) changeSet(curr, new *Table) (*changes, error) {
change.column.modify = append(change.column.modify, c1)
}
}
// Drop columns.
for _, c1 := range curr.Columns {
// If a column was dropped, multi-columns indexes that are associated with this column will
@@ -394,7 +408,6 @@ func (m *Migrate) changeSet(curr, new *Table) (*changes, error) {
change.column.drop = append(change.column.drop, c1)
}
}
// Add or modify indexes.
for _, idx1 := range new.Indexes {
switch idx2, ok := curr.index(idx1.Name); {
@@ -413,7 +426,6 @@ func (m *Migrate) changeSet(curr, new *Table) (*changes, error) {
}
}
}
// Drop indexes.
for _, idx := range curr.Indexes {
if _, isFK := new.fk(idx.Name); !isFK && !new.hasIndex(idx.Name, idx.realname) {