dialect/sql/schema: add schema dump command (#4296)

* dialect/sql/schema: add schema dump command

* entc/gen/template: drop build flag from generated globalid.go
This commit is contained in:
Jannik Clausen
2025-01-15 16:24:02 +01:00
committed by GitHub
parent 01e21a2c30
commit 05c977357f
7 changed files with 200 additions and 5 deletions

View File

@@ -525,6 +525,7 @@ func (a *Atlas) StateReader(tables ...*Table) migrate.StateReaderFunc {
}
a.sqlDialect = drv
}
a.setupTables(tables)
ts, err := a.tables(tables)
if err != nil {
return nil, err

View File

@@ -6,10 +6,18 @@
package schema
import (
"context"
"fmt"
"slices"
"strconv"
"strings"
"ariga.io/atlas/sql/migrate"
"ariga.io/atlas/sql/mysql"
"ariga.io/atlas/sql/postgres"
"ariga.io/atlas/sql/schema"
"ariga.io/atlas/sql/sqlite"
entdialect "entgo.io/ent/dialect"
"entgo.io/ent/dialect/entsql"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/schema/field"
@@ -576,3 +584,63 @@ func indexType(idx *Index, d string) (string, bool) {
}
return "", false
}
type driver struct {
sqlDialect
schema.Differ
migrate.PlanApplier
}
var drivers = func(v string) map[string]driver {
return map[string]driver{
entdialect.SQLite: {
&SQLite{WithForeignKeys: true},
sqlite.DefaultDiff,
sqlite.DefaultPlan,
},
entdialect.MySQL: {
&MySQL{version: v},
mysql.DefaultDiff,
mysql.DefaultPlan,
},
entdialect.Postgres: {
&Postgres{version: v},
postgres.DefaultDiff,
postgres.DefaultPlan,
},
}
}
// Dump the schema DDL for the given tables.
func Dump(ctx context.Context, dialect, version string, tables []*Table, opts ...migrate.PlanOption) (string, error) {
opts = append([]migrate.PlanOption{func(o *migrate.PlanOptions) {
o.Mode = migrate.PlanModeDump
o.Indent = " "
}}, opts...)
d, ok := drivers(version)[dialect]
if !ok {
return "", fmt.Errorf("unsupported dialect %q", dialect)
}
r, err := (&Atlas{sqlDialect: d}).StateReader(tables...).ReadState(ctx)
if err != nil {
return "", err
}
var c schema.Changes
if slices.ContainsFunc(tables, func(t *Table) bool { return t.Schema != "" }) {
c, err = d.RealmDiff(&schema.Realm{}, r)
} else {
c, err = d.SchemaDiff(&schema.Schema{}, r.Schemas[0])
}
if err != nil {
return "", err
}
p, err := d.PlanChanges(ctx, "dump", c, opts...)
if err != nil {
return "", err
}
f, err := migrate.DefaultFormatter.FormatFile(p)
if err != nil {
return "", err
}
return string(f.Bytes()), nil
}

View File

@@ -5,8 +5,12 @@
package schema
import (
"context"
"fmt"
"testing"
"ariga.io/atlas/sql/migrate"
"entgo.io/ent/dialect"
"entgo.io/ent/dialect/entsql"
"entgo.io/ent/schema/field"
@@ -149,3 +153,76 @@ func TestCopyTables(t *testing.T) {
require.NoError(t, err)
require.Equal(t, tables, copyT)
}
func TestDump(t *testing.T) {
users := &Table{
Name: "users",
Columns: []*Column{
{Name: "id", Type: field.TypeInt},
{Name: "name", Type: field.TypeString},
{Name: "spouse_id", Type: field.TypeInt},
},
}
users.PrimaryKey = users.Columns[:1]
users.Indexes = append(users.Indexes, &Index{
Name: "name",
Columns: users.Columns[1:2],
})
users.AddForeignKey(&ForeignKey{
Columns: users.Columns[2:],
RefTable: users,
RefColumns: users.Columns[:1],
OnUpdate: SetDefault,
})
users.SetAnnotation(&entsql.Annotation{Table: "Users"})
pets := &Table{
Name: "pets",
Columns: []*Column{
{Name: "id", Type: field.TypeInt},
{Name: "name", Type: field.TypeString},
{Name: "fur_color", Type: field.TypeEnum, Enums: []string{"black", "white"}},
{Name: "owner_id", Type: field.TypeInt},
},
}
pets.Indexes = append(pets.Indexes, &Index{
Name: "name",
Unique: true,
Columns: pets.Columns[1:2],
Annotation: entsql.Desc(),
})
pets.AddForeignKey(&ForeignKey{
Columns: pets.Columns[3:],
RefTable: users,
RefColumns: users.Columns[:1],
OnDelete: SetDefault,
})
tables = []*Table{users, pets}
my := func(length int) string {
return fmt.Sprintf("-- Create \"users\" table\nCREATE TABLE `users` (`id` bigint NOT NULL, `name` varchar(%d) NOT NULL, `spouse_id` bigint NOT NULL, PRIMARY KEY (`id`), INDEX `name` (`name`), FOREIGN KEY (`spouse_id`) REFERENCES `users` (`id`) ON UPDATE SET DEFAULT) CHARSET utf8mb4 COLLATE utf8mb4_bin;\n-- Create \"pets\" table\nCREATE TABLE `pets` (`id` bigint NOT NULL, `name` varchar(%d) NOT NULL, `fur_color` enum('black','white') NOT NULL, `owner_id` bigint NOT NULL, UNIQUE INDEX `name` (`name` DESC), FOREIGN KEY (`owner_id`) REFERENCES `users` (`id`) ON DELETE SET DEFAULT) CHARSET utf8mb4 COLLATE utf8mb4_bin;\n", length, length)
}
pg := "-- Create \"users\" table\nCREATE TABLE \"users\" (\"id\" bigint NOT NULL, \"name\" character varying NOT NULL, \"spouse_id\" bigint NOT NULL, PRIMARY KEY (\"id\"), FOREIGN KEY (\"spouse_id\") REFERENCES \"users\" (\"id\") ON UPDATE SET DEFAULT);\n-- Create index \"name\" to table: \"users\"\nCREATE INDEX \"name\" ON \"users\" (\"name\");\n-- Create \"pets\" table\nCREATE TABLE \"pets\" (\"id\" bigint NOT NULL, \"name\" character varying NOT NULL, \"fur_color\" character varying NOT NULL, \"owner_id\" bigint NOT NULL, FOREIGN KEY (\"owner_id\") REFERENCES \"users\" (\"id\") ON DELETE SET DEFAULT);\n-- Create index \"name\" to table: \"pets\"\nCREATE UNIQUE INDEX \"name\" ON \"pets\" (\"name\" DESC);\n"
for _, tt := range []struct{ dialect, version, expected string }{
{
dialect.SQLite, "",
"-- Create \"users\" table\nCREATE TABLE `users` (`id` integer NOT NULL, `name` text NOT NULL, `spouse_id` integer NOT NULL, PRIMARY KEY (`id`), FOREIGN KEY (`spouse_id`) REFERENCES `users` (`id`) ON UPDATE SET DEFAULT);\n-- Create index \"name\" to table: \"users\"\nCREATE INDEX `name` ON `users` (`name`);\n-- Create \"pets\" table\nCREATE TABLE `pets` (`id` integer NOT NULL, `name` text NOT NULL, `fur_color` text NOT NULL, `owner_id` integer NOT NULL, FOREIGN KEY (`owner_id`) REFERENCES `users` (`id`) ON DELETE SET DEFAULT);\n-- Create index \"name\" to table: \"pets\"\nCREATE UNIQUE INDEX `name` ON `pets` (`name` DESC);\n",
},
{dialect.MySQL, "5.6", my(191)},
{dialect.MySQL, "5.7", my(255)},
{dialect.MySQL, "8", my(255)},
{dialect.Postgres, "12", pg},
{dialect.Postgres, "13", pg},
{dialect.Postgres, "14", pg},
{dialect.Postgres, "15", pg},
} {
t.Run(fmt.Sprintf("%s:%s", tt.dialect, tt.version), func(t *testing.T) {
ac, err := Dump(context.Background(), tt.dialect, tt.version, tables, func(o *migrate.PlanOptions) {
o.Indent = ""
})
require.NoError(t, err)
require.Equal(t, tt.expected, ac)
})
}
}