diff options
Diffstat (limited to 'vendor/github.com/uptrace/bun/migrate/auto.go')
| -rw-r--r-- | vendor/github.com/uptrace/bun/migrate/auto.go | 473 |
1 files changed, 0 insertions, 473 deletions
diff --git a/vendor/github.com/uptrace/bun/migrate/auto.go b/vendor/github.com/uptrace/bun/migrate/auto.go deleted file mode 100644 index 51868d50d..000000000 --- a/vendor/github.com/uptrace/bun/migrate/auto.go +++ /dev/null @@ -1,473 +0,0 @@ -package migrate - -import ( - "bytes" - "context" - "errors" - "fmt" - "io" - "os" - "path/filepath" - - "github.com/uptrace/bun" - "github.com/uptrace/bun/internal" - "github.com/uptrace/bun/migrate/sqlschema" - "github.com/uptrace/bun/schema" -) - -type AutoMigratorOption func(m *AutoMigrator) - -// WithModel adds a bun.Model to the migration scope. -func WithModel(models ...interface{}) AutoMigratorOption { - return func(m *AutoMigrator) { - m.includeModels = append(m.includeModels, models...) - } -} - -// WithExcludeTable tells AutoMigrator to exclude database tables from the migration scope. -// This prevents AutoMigrator from dropping tables which may exist in the schema -// but which are not used by the application. -// -// Expressions may make use of the wildcards supported by the SQL LIKE operator: -// - % as a wildcard -// - _ as a single character -// -// Do not exclude tables included via WithModel, as BunModelInspector ignores this setting. -func WithExcludeTable(tables ...string) AutoMigratorOption { - return func(m *AutoMigrator) { - m.excludeTables = append(m.excludeTables, tables...) - } -} - -// WithExcludeForeignKeys tells AutoMigrator to exclude a foreign key constaint -// from the migration scope. This prevents AutoMigrator from dropping foreign keys -// that are defined manually via CreateTableQuery.ForeignKey(). -func WithExcludeForeignKeys(fks ...sqlschema.ForeignKey) AutoMigratorOption { - return func(m *AutoMigrator) { - m.excludeForeignKeys = append(m.excludeForeignKeys, fks...) - } -} - -// WithSchemaName sets the database schema to migrate objects in. -// By default, dialects' default schema is used. -func WithSchemaName(schemaName string) AutoMigratorOption { - return func(m *AutoMigrator) { - m.schemaName = schemaName - } -} - -// WithTableNameAuto overrides default migrations table name. -func WithTableNameAuto(table string) AutoMigratorOption { - return func(m *AutoMigrator) { - m.table = table - m.migratorOpts = append(m.migratorOpts, WithTableName(table)) - } -} - -// WithLocksTableNameAuto overrides default migration locks table name. -func WithLocksTableNameAuto(table string) AutoMigratorOption { - return func(m *AutoMigrator) { - m.locksTable = table - m.migratorOpts = append(m.migratorOpts, WithLocksTableName(table)) - } -} - -// WithMarkAppliedOnSuccessAuto sets the migrator to only mark migrations as applied/unapplied -// when their up/down is successful. -func WithMarkAppliedOnSuccessAuto(enabled bool) AutoMigratorOption { - return func(m *AutoMigrator) { - m.migratorOpts = append(m.migratorOpts, WithMarkAppliedOnSuccess(enabled)) - } -} - -// WithMigrationsDirectoryAuto overrides the default directory for migration files. -func WithMigrationsDirectoryAuto(directory string) AutoMigratorOption { - return func(m *AutoMigrator) { - m.migrationsOpts = append(m.migrationsOpts, WithMigrationsDirectory(directory)) - } -} - -// AutoMigrator performs automated schema migrations. -// -// It is designed to be a drop-in replacement for some Migrator functionality and supports all existing -// configuration options. -// Similarly to Migrator, it has methods to create SQL migrations, write them to a file, and apply them. -// Unlike Migrator, it detects the differences between the state defined by bun models and the current -// database schema automatically. -// -// Usage: -// 1. Generate migrations and apply them at once with AutoMigrator.Migrate(). -// 2. Create up- and down-SQL migration files and apply migrations using Migrator.Migrate(). -// -// While both methods produce complete, reversible migrations (with entries in the database -// and SQL migration files), prefer creating migrations and applying them separately for -// any non-trivial cases to ensure AutoMigrator detects expected changes correctly. -// -// Limitations: -// - AutoMigrator only supports a subset of the possible ALTER TABLE modifications. -// - Some changes are not automatically reversible. For example, you would need to manually -// add a CREATE TABLE query to the .down migration file to revert a DROP TABLE migration. -// - Does not validate most dialect-specific constraints. For example, when changing column -// data type, make sure the data con be auto-casted to the new type. -// - Due to how the schema-state diff is calculated, it is not possible to rename a table and -// modify any of its columns' _data type_ in a single run. This will cause the AutoMigrator -// to drop and re-create the table under a different name; it is better to apply this change in 2 steps. -// Renaming a table and renaming its columns at the same time is possible. -// - Renaming table/column to an existing name, i.e. like this [A->B] [B->C], is not possible due to how -// AutoMigrator distinguishes "rename" and "unchanged" columns. -// -// Dialect must implement both sqlschema.Inspector and sqlschema.Migrator to be used with AutoMigrator. -type AutoMigrator struct { - db *bun.DB - - // dbInspector creates the current state for the target database. - dbInspector sqlschema.Inspector - - // modelInspector creates the desired state based on the model definitions. - modelInspector sqlschema.Inspector - - // dbMigrator executes ALTER TABLE queries. - dbMigrator sqlschema.Migrator - - table string // Migrations table (excluded from database inspection) - locksTable string // Migration locks table (excluded from database inspection) - - // schemaName is the database schema considered for migration. - schemaName string - - // includeModels define the migration scope. - includeModels []interface{} - - excludeTables []string // excludeTables are excluded from database inspection. - excludeForeignKeys []sqlschema.ForeignKey // excludeForeignKeys are excluded from database inspection. - - // diffOpts are passed to detector constructor. - diffOpts []diffOption - - // migratorOpts are passed to Migrator constructor. - migratorOpts []MigratorOption - - // migrationsOpts are passed to Migrations constructor. - migrationsOpts []MigrationsOption -} - -func NewAutoMigrator(db *bun.DB, opts ...AutoMigratorOption) (*AutoMigrator, error) { - am := &AutoMigrator{ - db: db, - table: defaultTable, - locksTable: defaultLocksTable, - schemaName: db.Dialect().DefaultSchema(), - } - - for _, opt := range opts { - opt(am) - } - am.excludeTables = append(am.excludeTables, am.table, am.locksTable) - - dbInspector, err := sqlschema.NewInspector(db, - sqlschema.WithSchemaName(am.schemaName), - sqlschema.WithExcludeTables(am.excludeTables...), - sqlschema.WithExcludeForeignKeys(am.excludeForeignKeys...), - ) - if err != nil { - return nil, err - } - am.dbInspector = dbInspector - am.diffOpts = append(am.diffOpts, withCompareTypeFunc(db.Dialect().(sqlschema.InspectorDialect).CompareType)) - - dbMigrator, err := sqlschema.NewMigrator(db, am.schemaName) - if err != nil { - return nil, err - } - am.dbMigrator = dbMigrator - - tables := schema.NewTables(db.Dialect()) - tables.Register(am.includeModels...) - am.modelInspector = sqlschema.NewBunModelInspector(tables, sqlschema.WithSchemaName(am.schemaName)) - - return am, nil -} - -func (am *AutoMigrator) plan(ctx context.Context) (*changeset, error) { - var err error - - got, err := am.dbInspector.Inspect(ctx) - if err != nil { - return nil, err - } - - want, err := am.modelInspector.Inspect(ctx) - if err != nil { - return nil, err - } - - changes := diff(got, want, am.diffOpts...) - if err := changes.ResolveDependencies(); err != nil { - return nil, fmt.Errorf("plan migrations: %w", err) - } - return changes, nil -} - -// Migrate writes required changes to a new migration file and runs the migration. -// This will create an entry in the migrations table, making it possible to revert -// the changes with Migrator.Rollback(). MigrationOptions are passed on to Migrator.Migrate(). -func (am *AutoMigrator) Migrate(ctx context.Context, opts ...MigrationOption) (*MigrationGroup, error) { - migrations, _, err := am.createSQLMigrations(ctx, false) - if err != nil { - if err == errNothingToMigrate { - return new(MigrationGroup), nil - } - return nil, fmt.Errorf("auto migrate: %w", err) - } - - migrator := NewMigrator(am.db, migrations, am.migratorOpts...) - if err := migrator.Init(ctx); err != nil { - return nil, fmt.Errorf("auto migrate: %w", err) - } - - group, err := migrator.Migrate(ctx, opts...) - if err != nil { - return nil, fmt.Errorf("auto migrate: %w", err) - } - return group, nil -} - -// CreateSQLMigration writes required changes to a new migration file. -// Use migrate.Migrator to apply the generated migrations. -func (am *AutoMigrator) CreateSQLMigrations(ctx context.Context) ([]*MigrationFile, error) { - _, files, err := am.createSQLMigrations(ctx, false) - if err == errNothingToMigrate { - return files, nil - } - return files, err -} - -// CreateTxSQLMigration writes required changes to a new migration file making sure they will be executed -// in a transaction when applied. Use migrate.Migrator to apply the generated migrations. -func (am *AutoMigrator) CreateTxSQLMigrations(ctx context.Context) ([]*MigrationFile, error) { - _, files, err := am.createSQLMigrations(ctx, true) - if err == errNothingToMigrate { - return files, nil - } - return files, err -} - -// errNothingToMigrate is a sentinel error which means the database is already in a desired state. -// Should not be returned to the user -- return a nil-error instead. -var errNothingToMigrate = errors.New("nothing to migrate") - -func (am *AutoMigrator) createSQLMigrations(ctx context.Context, transactional bool) (*Migrations, []*MigrationFile, error) { - changes, err := am.plan(ctx) - if err != nil { - return nil, nil, fmt.Errorf("create sql migrations: %w", err) - } - - if changes.Len() == 0 { - return nil, nil, errNothingToMigrate - } - - name, _ := genMigrationName(am.schemaName + "_auto") - migrations := NewMigrations(am.migrationsOpts...) - migrations.Add(Migration{ - Name: name, - Up: wrapMigrationFunc(changes.Up(am.dbMigrator)), - Down: wrapMigrationFunc(changes.Down(am.dbMigrator)), - Comment: "Changes detected by bun.AutoMigrator", - }) - - // Append .tx.up.sql or .up.sql to migration name, depending if it should be transactional. - fname := func(direction string) string { - return name + map[bool]string{true: ".tx.", false: "."}[transactional] + direction + ".sql" - } - - up, err := am.createSQL(ctx, migrations, fname("up"), changes, transactional) - if err != nil { - return nil, nil, fmt.Errorf("create sql migration up: %w", err) - } - - down, err := am.createSQL(ctx, migrations, fname("down"), changes.GetReverse(), transactional) - if err != nil { - return nil, nil, fmt.Errorf("create sql migration down: %w", err) - } - return migrations, []*MigrationFile{up, down}, nil -} - -func (am *AutoMigrator) createSQL(_ context.Context, migrations *Migrations, fname string, changes *changeset, transactional bool) (*MigrationFile, error) { - var buf bytes.Buffer - - if transactional { - buf.WriteString("SET statement_timeout = 0;") - } - - if err := changes.WriteTo(&buf, am.dbMigrator); err != nil { - return nil, err - } - content := buf.Bytes() - - fpath := filepath.Join(migrations.getDirectory(), fname) - if err := os.WriteFile(fpath, content, 0o644); err != nil { - return nil, err - } - - mf := &MigrationFile{ - Name: fname, - Path: fpath, - Content: string(content), - } - return mf, nil -} - -func (c *changeset) Len() int { - return len(c.operations) -} - -// Func creates a MigrationFunc that applies all operations all the changeset. -func (c *changeset) Func(m sqlschema.Migrator) MigrationFunc { - return func(ctx context.Context, db *bun.DB) error { - return c.apply(ctx, db, m) - } -} - -// GetReverse returns a new changeset with each operation in it "reversed" and in reverse order. -func (c *changeset) GetReverse() *changeset { - var reverse changeset - for i := len(c.operations) - 1; i >= 0; i-- { - reverse.Add(c.operations[i].GetReverse()) - } - return &reverse -} - -// Up is syntactic sugar. -func (c *changeset) Up(m sqlschema.Migrator) MigrationFunc { - return c.Func(m) -} - -// Down is syntactic sugar. -func (c *changeset) Down(m sqlschema.Migrator) MigrationFunc { - return c.GetReverse().Func(m) -} - -// apply generates SQL for each operation and executes it. -func (c *changeset) apply(ctx context.Context, db *bun.DB, m sqlschema.Migrator) error { - if len(c.operations) == 0 { - return nil - } - - for _, op := range c.operations { - if _, skip := op.(*Unimplemented); skip { - continue - } - - b := internal.MakeQueryBytes() - b, err := m.AppendSQL(b, op) - if err != nil { - return fmt.Errorf("apply changes: %w", err) - } - - query := internal.String(b) - if _, err = db.ExecContext(ctx, query); err != nil { - return fmt.Errorf("apply changes: %w", err) - } - } - return nil -} - -func (c *changeset) WriteTo(w io.Writer, m sqlschema.Migrator) error { - var err error - - b := internal.MakeQueryBytes() - for _, op := range c.operations { - if comment, isComment := op.(*Unimplemented); isComment { - b = append(b, "/*\n"...) - b = append(b, *comment...) - b = append(b, "\n*/"...) - continue - } - - // Append each query separately, merge later. - // Dialects assume that the []byte only holds - // the contents of a single query and may be misled. - queryBytes := internal.MakeQueryBytes() - queryBytes, err = m.AppendSQL(queryBytes, op) - if err != nil { - return fmt.Errorf("write changeset: %w", err) - } - b = append(b, queryBytes...) - b = append(b, ";\n"...) - } - if _, err := w.Write(b); err != nil { - return fmt.Errorf("write changeset: %w", err) - } - return nil -} - -func (c *changeset) ResolveDependencies() error { - if len(c.operations) <= 1 { - return nil - } - - const ( - unvisited = iota - current - visited - ) - - status := make(map[Operation]int, len(c.operations)) - for _, op := range c.operations { - status[op] = unvisited - } - - var resolved []Operation - var nextOp Operation - var visit func(op Operation) error - - next := func() bool { - for op, s := range status { - if s == unvisited { - nextOp = op - return true - } - } - return false - } - - // visit iterates over c.operations until it finds all operations that depend on the current one - // or runs into circular dependency, in which case it will return an error. - visit = func(op Operation) error { - switch status[op] { - case visited: - return nil - case current: - // TODO: add details (circle) to the error message - return errors.New("detected circular dependency") - } - - status[op] = current - - for _, another := range c.operations { - if dop, hasDeps := another.(interface { - DependsOn(Operation) bool - }); another == op || !hasDeps || !dop.DependsOn(op) { - continue - } - if err := visit(another); err != nil { - return err - } - } - - status[op] = visited - - // Any dependent nodes would've already been added to the list by now, so we prepend. - resolved = append([]Operation{op}, resolved...) - return nil - } - - for next() { - if err := visit(nextOp); err != nil { - return err - } - } - - c.operations = resolved - return nil -} |
