diff --git a/dialect/sql/schema/atlas.go b/dialect/sql/schema/atlas.go index 0f9f90998..9c5ef7333 100644 --- a/dialect/sql/schema/atlas.go +++ b/dialect/sql/schema/atlas.go @@ -664,6 +664,18 @@ func (a *Atlas) create(ctx context.Context, tables ...*Table) (err error) { if err := a.sqlDialect.init(ctx); err != nil { return err } + a.atDriver, err = a.sqlDialect.atOpen(a.sqlDialect) + if err != nil { + return err + } + defer func() { a.atDriver = nil }() + plan, err := a.planInspect(ctx, a.sqlDialect, "changes", tables) + if err != nil { + return fmt.Errorf("sql/schema: %w", err) + } + if len(plan.Changes) == 0 { + return nil + } // Open a transaction for backwards compatibility, // even if the migration is not transactional. tx, err := a.sqlDialect.Tx(ctx) @@ -674,34 +686,23 @@ func (a *Atlas) create(ctx context.Context, tables ...*Table) (err error) { if err != nil { return err } - defer func() { a.atDriver = nil }() - if err := func() error { - plan, err := a.planInspect(ctx, tx, "changes", tables) - if err != nil { - return err - } - // Apply plan (changes). - var applier Applier = ApplyFunc(func(ctx context.Context, tx dialect.ExecQuerier, plan *migrate.Plan) error { - for _, c := range plan.Changes { - if err := tx.Exec(ctx, c.Cmd, c.Args, nil); err != nil { - if c.Comment != "" { - err = fmt.Errorf("%s: %w", c.Comment, err) - } - return err + // Apply plan (changes). + var applier Applier = ApplyFunc(func(ctx context.Context, tx dialect.ExecQuerier, plan *migrate.Plan) error { + for _, c := range plan.Changes { + if err := tx.Exec(ctx, c.Cmd, c.Args, nil); err != nil { + if c.Comment != "" { + err = fmt.Errorf("%s: %w", c.Comment, err) } + return err } - return nil - }) - for i := len(a.applyHook) - 1; i >= 0; i-- { - applier = a.applyHook[i](applier) } - return applier.Apply(ctx, tx, plan) - }(); err != nil { - err = fmt.Errorf("sql/schema: %w", err) - if rerr := tx.Rollback(); rerr != nil { - err = fmt.Errorf("%w: %v", err, rerr) - } - return err + return nil + }) + for i := len(a.applyHook) - 1; i >= 0; i-- { + applier = a.applyHook[i](applier) + } + if err = applier.Apply(ctx, tx, plan); err != nil { + return errors.Join(fmt.Errorf("sql/schema: %w", err), tx.Rollback()) } return tx.Commit() } diff --git a/entc/integration/migrate/migrate_test.go b/entc/integration/migrate/migrate_test.go index f114e3b78..c4c0b3b59 100644 --- a/entc/integration/migrate/migrate_test.go +++ b/entc/integration/migrate/migrate_test.go @@ -241,6 +241,23 @@ func TestSQLite(t *testing.T) { require.NoError(t, err) defer vdrv.Close() Versioned(t, vdrv, "sqlite3://file?mode=memory&cache=shared&_fk=1", versioned.NewClient(versioned.Driver(vdrv))) + + // In case there are no changes, no transaction should be opened and foreign keys should not be touched. + fkdrv, err := sql.Open("sqlite3", "file:fk_ent?mode=memory&cache=shared&_fk=1") + require.NoError(t, err) + defer fkdrv.Close() + fkclient := entv1.NewClient(entv1.Driver(fkdrv), entv1.Log(func(a ...any) { + s := a[0].(string) + switch { + case strings.HasPrefix(s, "driver.Tx"): + t.Errorf("unexpected transaction, %q", s) + case strings.HasPrefix(s, "driver.Exec") && strings.Contains(s, "PRAGMA foreign_keys"): + t.Errorf("unexpected foreign keys pragma: %q", s) + } + })) + require.NoError(t, fkclient.Schema.Create(ctx)) + // Apply again has no changes -> there should not be any pragma changes and no transaction to be opened. + require.NoError(t, fkclient.Debug().Schema.Create(ctx)) } // https://github.com/ent/ent/issues/2954