diff --git a/README.md b/README.md index 6bb06fe..dfcdb40 100644 --- a/README.md +++ b/README.md @@ -242,11 +242,10 @@ The provider supports the following databases: ### Frequently Asked Questions -* **Foreign key constraints not generated correctly** - - If a [Customize JoinTable](https://gorm.io/docs/many_to_many.html#Customize-JoinTable) is defined in the schema, - you need to use the provider as a [Go Program](#as-go-file) and set it up using the `WithJoinTable` option. - - for example if those are your models: +* **Foreign key constraints not generated correctly** - When setting up your [Go Program](#as-go-file) and using [Customize JoinTable](https://gorm.io/docs/many_to_many.html#Customize-JoinTable), + you may encounter issues with foreign key constraints. To avoid these issues, ensure that all models, including the join tables, are passed to the `Load` function. + + For example if those are your models: ```go type Person struct { ID int @@ -269,11 +268,7 @@ The provider supports the following databases: you should use the following code: ```go - stmts, err := gormschema.New("mysql", - gormschema.WithJoinTable( - &Models.Person{}, "Addresses", &Models.PersonAddress{}, - ), - ).Load(&Models.Address{}, &Models.Person{}) + stmts, err := gormschema.New("mysql").Load(&Models.Address{}, &Models.Person{}, &Models.PersonAddress{}) ``` ### License diff --git a/gormschema/gorm.go b/gormschema/gorm.go index ad90556..6cfd393 100644 --- a/gormschema/gorm.go +++ b/gormschema/gorm.go @@ -104,17 +104,24 @@ func (l *Loader) Load(models ...any) (string, error) { return "", err } } - if err = db.AutoMigrate(tables...); err != nil { - return "", err - } - db, err = gorm.Open(dialector{Dialector: di}, l.config) + cdb, err := gorm.Open(dialector{Dialector: di}, l.config) if err != nil { return "", err } - cm, ok := db.Migrator().(*migrator) + cm, ok := cdb.Migrator().(*migrator) if !ok { return "", fmt.Errorf("unexpected migrator type: %T", db.Migrator()) } + if err = cm.setupJoinTables(tables...); err != nil { + return "", err + } + orderedTables, err := cm.orderModels(tables...) + if err != nil { + return "", err + } + if err = db.AutoMigrate(orderedTables...); err != nil { + return "", err + } if err = cm.CreateViews(views); err != nil { return "", err } @@ -193,6 +200,39 @@ func (m *migrator) CreateConstraints(models []any) error { return nil } +// setupJoinTables helps to determine custom join tables present in the model list and sets them up. +func (m *migrator) setupJoinTables(models ...any) error { + var dbNameModelMap = make(map[string]any) + for _, model := range models { + err := m.RunWithValue(model, func(stmt *gorm.Statement) error { + dbNameModelMap[stmt.Schema.Table] = model + return nil + }) + if err != nil { + return err + } + } + for _, model := range m.ReorderModels(models, false) { + err := m.RunWithValue(model, func(stmt *gorm.Statement) error { + for _, rel := range stmt.Schema.Relationships.Relations { + if rel.Field.IgnoreMigration || rel.JoinTable == nil { + continue + } + if joinTable, ok := dbNameModelMap[rel.JoinTable.Name]; ok { + if err := m.DB.SetupJoinTable(model, rel.Field.Name, joinTable); err != nil { + return err + } + } + } + return nil + }) + if err != nil { + return err + } + } + return nil +} + // CreateViews creates the given "view-based" models func (m *migrator) CreateViews(views []ViewDefiner) error { for _, view := range views { @@ -217,6 +257,7 @@ func (m *migrator) CreateViews(views []ViewDefiner) error { } // WithJoinTable sets up a join table for the given model and field. +// Deprecated: put the join tables alongside the models in the Load call. func WithJoinTable(model any, field string, jointable any) Option { return func(l *Loader) { l.beforeAutoMigrate = append(l.beforeAutoMigrate, func(db *gorm.DB) error { @@ -269,3 +310,41 @@ func BuildStmt(fn func(db *gorm.DB) *gorm.DB) ViewOption { b.createStmt = fmt.Sprintf("CREATE VIEW %s AS %s", b.viewName, vd) } } + +// orderModels places join tables at the end of the list of models (if any), +// which helps GORM resolve m2m relationships correctly. +func (m *migrator) orderModels(models ...any) ([]any, error) { + var ( + joinTableDBNames = make(map[string]bool) + otherTables []any + joinTables []any + ) + for _, model := range models { + err := m.RunWithValue(model, func(stmt *gorm.Statement) error { + for _, rel := range stmt.Schema.Relationships.Relations { + if rel.JoinTable != nil { + joinTableDBNames[rel.JoinTable.Table] = true + return nil + } + } + return nil + }) + if err != nil { + return nil, err + } + } + for _, model := range models { + err := m.RunWithValue(model, func(stmt *gorm.Statement) error { + if joinTableDBNames[stmt.Schema.Table] { + joinTables = append(joinTables, model) + } else { + otherTables = append(otherTables, model) + } + return nil + }) + if err != nil { + return nil, err + } + } + return append(otherTables, joinTables...), nil +} diff --git a/gormschema/gorm_test.go b/gormschema/gorm_test.go index 9035968..f2d6e36 100644 --- a/gormschema/gorm_test.go +++ b/gormschema/gorm_test.go @@ -16,7 +16,7 @@ import ( func TestSQLiteConfig(t *testing.T) { resetSession() l := gormschema.New("sqlite") - sql, err := l.Load(models.WorkingAgedUsers{}, models.Pet{}, models.User{}, ckmodels.Event{}, ckmodels.Location{}, models.TopPetOwner{}) + sql, err := l.Load(models.WorkingAgedUsers{}, models.Pet{}, ckmodels.Event{}, ckmodels.Location{}, models.TopPetOwner{}) require.NoError(t, err) requireEqualContent(t, sql, "testdata/sqlite_default") resetSession() @@ -65,6 +65,16 @@ func TestMySQLConfig(t *testing.T) { sql, err = l.Load(customjointable.Address{}, customjointable.Person{}, customjointable.TopCrowdedAddresses{}) require.NoError(t, err) requireEqualContent(t, sql, "testdata/mysql_custom_join_table") + resetSession() + l = gormschema.New("mysql") + sql, err = l.Load(customjointable.PersonAddress{}, customjointable.Address{}, customjointable.Person{}, customjointable.TopCrowdedAddresses{}) + require.NoError(t, err) + requireEqualContent(t, sql, "testdata/mysql_custom_join_table") + resetSession() + l = gormschema.New("mysql") + sql, err = l.Load(customjointable.Address{}, customjointable.PersonAddress{}, customjointable.Person{}, customjointable.TopCrowdedAddresses{}) + require.NoError(t, err) + requireEqualContent(t, sql, "testdata/mysql_custom_join_table") // position of tables should not matter } func TestSQLServerConfig(t *testing.T) { diff --git a/gormschema/testdata/mysql_default b/gormschema/testdata/mysql_default index 6f71ff0..2e42336 100644 --- a/gormschema/testdata/mysql_default +++ b/gormschema/testdata/mysql_default @@ -1,9 +1,13 @@ CREATE TABLE `events` (`eventId` varchar(191),`locationId` varchar(191),PRIMARY KEY (`eventId`),UNIQUE INDEX `idx_events_location_id` (`locationId`)); CREATE TABLE `locations` (`locationId` varchar(191),`eventId` varchar(191),PRIMARY KEY (`locationId`),UNIQUE INDEX `idx_locations_event_id` (`eventId`)); CREATE TABLE `users` (`id` bigint unsigned AUTO_INCREMENT,`created_at` datetime(3) NULL,`updated_at` datetime(3) NULL,`deleted_at` datetime(3) NULL,`name` longtext,`age` bigint,PRIMARY KEY (`id`),INDEX `idx_users_deleted_at` (`deleted_at`)); +CREATE TABLE `hobbies` (`id` bigint unsigned AUTO_INCREMENT,`name` longtext,PRIMARY KEY (`id`)); +CREATE TABLE `user_hobbies` (`hobby_id` bigint unsigned,`user_id` bigint unsigned,PRIMARY KEY (`hobby_id`,`user_id`)); CREATE TABLE `pets` (`id` bigint unsigned AUTO_INCREMENT,`created_at` datetime(3) NULL,`updated_at` datetime(3) NULL,`deleted_at` datetime(3) NULL,`name` longtext,`user_id` bigint unsigned,PRIMARY KEY (`id`),INDEX `idx_pets_deleted_at` (`deleted_at`)); CREATE VIEW working_aged_users AS SELECT name, age FROM `users` WHERE age BETWEEN 18 AND 65; CREATE VIEW top_pet_owners AS SELECT user_id, COUNT(id) AS pet_count FROM pets GROUP BY user_id ORDER BY pet_count DESC LIMIT 10; ALTER TABLE `events` ADD CONSTRAINT `fk_locations_event` FOREIGN KEY (`locationId`) REFERENCES `locations`(`locationId`); ALTER TABLE `locations` ADD CONSTRAINT `fk_events_location` FOREIGN KEY (`eventId`) REFERENCES `events`(`eventId`); +ALTER TABLE `user_hobbies` ADD CONSTRAINT `fk_user_hobbies_hobby` FOREIGN KEY (`hobby_id`) REFERENCES `hobbies`(`id`); +ALTER TABLE `user_hobbies` ADD CONSTRAINT `fk_user_hobbies_user` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`); ALTER TABLE `pets` ADD CONSTRAINT `fk_users_pets` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`); diff --git a/gormschema/testdata/mysql_deterministic_output b/gormschema/testdata/mysql_deterministic_output new file mode 100644 index 0000000..9feb1df --- /dev/null +++ b/gormschema/testdata/mysql_deterministic_output @@ -0,0 +1,9 @@ +CREATE TABLE `hobbies` (`id` bigint unsigned AUTO_INCREMENT,`name` longtext,PRIMARY KEY (`id`)); +CREATE TABLE `users` (`id` bigint unsigned AUTO_INCREMENT,`created_at` datetime(3) NULL,`updated_at` datetime(3) NULL,`deleted_at` datetime(3) NULL,`name` longtext,`age` bigint,PRIMARY KEY (`id`),INDEX `idx_users_deleted_at` (`deleted_at`)); +CREATE TABLE `user_hobbies` (`user_id` bigint unsigned,`hobby_id` bigint unsigned,PRIMARY KEY (`user_id`,`hobby_id`)); +CREATE TABLE `pets` (`id` bigint unsigned AUTO_INCREMENT,`created_at` datetime(3) NULL,`updated_at` datetime(3) NULL,`deleted_at` datetime(3) NULL,`name` longtext,`user_id` bigint unsigned,PRIMARY KEY (`id`),INDEX `idx_pets_deleted_at` (`deleted_at`)); +CREATE VIEW top_pet_owners AS SELECT user_id, COUNT(id) AS pet_count FROM pets GROUP BY user_id ORDER BY pet_count DESC LIMIT 10; +CREATE VIEW working_aged_users AS SELECT name, age FROM `users` WHERE age BETWEEN 18 AND 65; +ALTER TABLE `user_hobbies` ADD CONSTRAINT `fk_user_hobbies_hobby` FOREIGN KEY (`hobby_id`) REFERENCES `hobbies`(`id`); +ALTER TABLE `user_hobbies` ADD CONSTRAINT `fk_user_hobbies_user` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`); +ALTER TABLE `pets` ADD CONSTRAINT `fk_users_pets` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`); diff --git a/gormschema/testdata/postgresql_default b/gormschema/testdata/postgresql_default index 6d7db22..d0c022d 100644 --- a/gormschema/testdata/postgresql_default +++ b/gormschema/testdata/postgresql_default @@ -4,10 +4,14 @@ CREATE TABLE "locations" ("locationId" varchar(191),"eventId" varchar(191),PRIMA CREATE UNIQUE INDEX IF NOT EXISTS "idx_locations_event_id" ON "locations" ("eventId"); CREATE TABLE "users" ("id" bigserial,"created_at" timestamptz,"updated_at" timestamptz,"deleted_at" timestamptz,"name" text,"age" bigint,PRIMARY KEY ("id")); CREATE INDEX IF NOT EXISTS "idx_users_deleted_at" ON "users" ("deleted_at"); +CREATE TABLE "hobbies" ("id" bigserial,"name" text,PRIMARY KEY ("id")); +CREATE TABLE "user_hobbies" ("hobby_id" bigint,"user_id" bigint,PRIMARY KEY ("hobby_id","user_id")); CREATE TABLE "pets" ("id" bigserial,"created_at" timestamptz,"updated_at" timestamptz,"deleted_at" timestamptz,"name" text,"user_id" bigint,PRIMARY KEY ("id")); CREATE INDEX IF NOT EXISTS "idx_pets_deleted_at" ON "pets" ("deleted_at"); CREATE VIEW working_aged_users AS SELECT name, age FROM "users" WHERE age BETWEEN 18 AND 65; CREATE VIEW top_pet_owners AS SELECT user_id, COUNT(id) AS pet_count FROM pets GROUP BY user_id ORDER BY pet_count DESC LIMIT 10; ALTER TABLE "events" ADD CONSTRAINT "fk_locations_event" FOREIGN KEY ("locationId") REFERENCES "locations"("locationId"); ALTER TABLE "locations" ADD CONSTRAINT "fk_events_location" FOREIGN KEY ("eventId") REFERENCES "events"("eventId"); +ALTER TABLE "user_hobbies" ADD CONSTRAINT "fk_user_hobbies_hobby" FOREIGN KEY ("hobby_id") REFERENCES "hobbies"("id"); +ALTER TABLE "user_hobbies" ADD CONSTRAINT "fk_user_hobbies_user" FOREIGN KEY ("user_id") REFERENCES "users"("id"); ALTER TABLE "pets" ADD CONSTRAINT "fk_users_pets" FOREIGN KEY ("user_id") REFERENCES "users"("id"); diff --git a/gormschema/testdata/sqlite_no_fk b/gormschema/testdata/sqlite_no_fk index 80a5134..0405a77 100644 --- a/gormschema/testdata/sqlite_no_fk +++ b/gormschema/testdata/sqlite_no_fk @@ -2,3 +2,5 @@ CREATE TABLE `users` (`id` integer,`created_at` datetime,`updated_at` datetime,` CREATE INDEX `idx_users_deleted_at` ON `users`(`deleted_at`); CREATE TABLE `pets` (`id` integer,`created_at` datetime,`updated_at` datetime,`deleted_at` datetime,`name` text,`user_id` integer,PRIMARY KEY (`id`)); CREATE INDEX `idx_pets_deleted_at` ON `pets`(`deleted_at`); +CREATE TABLE `hobbies` (`id` integer,`name` text,PRIMARY KEY (`id`)); +CREATE TABLE `user_hobbies` (`hobby_id` integer,`user_id` integer,PRIMARY KEY (`hobby_id`,`user_id`)); diff --git a/gormschema/testdata/sqlserver_default b/gormschema/testdata/sqlserver_default index a81d09d..1f4c36e 100644 --- a/gormschema/testdata/sqlserver_default +++ b/gormschema/testdata/sqlserver_default @@ -4,10 +4,14 @@ CREATE TABLE "locations" ("locationId" nvarchar(191),"eventId" nvarchar(191),PRI CREATE UNIQUE INDEX "idx_locations_event_id" ON "locations"("eventId"); CREATE TABLE "users" ("id" bigint IDENTITY(1,1),"created_at" datetimeoffset,"updated_at" datetimeoffset,"deleted_at" datetimeoffset,"name" nvarchar(MAX),"age" bigint,PRIMARY KEY ("id")); CREATE INDEX "idx_users_deleted_at" ON "users"("deleted_at"); +CREATE TABLE "hobbies" ("id" bigint IDENTITY(1,1),"name" nvarchar(MAX),PRIMARY KEY ("id")); +CREATE TABLE "user_hobbies" ("hobby_id" bigint,"user_id" bigint,PRIMARY KEY ("hobby_id","user_id")); CREATE TABLE "pets" ("id" bigint IDENTITY(1,1),"created_at" datetimeoffset,"updated_at" datetimeoffset,"deleted_at" datetimeoffset,"name" nvarchar(MAX),"user_id" bigint,PRIMARY KEY ("id")); CREATE INDEX "idx_pets_deleted_at" ON "pets"("deleted_at"); CREATE VIEW working_aged_users AS SELECT name, age FROM "users" WHERE age BETWEEN 18 AND 65; CREATE VIEW top_pet_owners AS SELECT user_id, COUNT(id) AS pet_count FROM pets GROUP BY user_id ORDER BY pet_count DESC OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY; ALTER TABLE "events" ADD CONSTRAINT "fk_locations_event" FOREIGN KEY ("locationId") REFERENCES "locations"("locationId"); ALTER TABLE "locations" ADD CONSTRAINT "fk_events_location" FOREIGN KEY ("eventId") REFERENCES "events"("eventId"); +ALTER TABLE "user_hobbies" ADD CONSTRAINT "fk_user_hobbies_hobby" FOREIGN KEY ("hobby_id") REFERENCES "hobbies"("id"); +ALTER TABLE "user_hobbies" ADD CONSTRAINT "fk_user_hobbies_user" FOREIGN KEY ("user_id") REFERENCES "users"("id"); ALTER TABLE "pets" ADD CONSTRAINT "fk_users_pets" FOREIGN KEY ("user_id") REFERENCES "users"("id"); diff --git a/internal/testdata/customjointable/models.go b/internal/testdata/customjointable/models.go index 1afcde6..b1cf018 100644 --- a/internal/testdata/customjointable/models.go +++ b/internal/testdata/customjointable/models.go @@ -15,7 +15,7 @@ type Person struct { } type Address struct { - ID int + ID int `gorm:"primaryKey"` Name string } diff --git a/internal/testdata/migrations/mysql/20240601122756.sql b/internal/testdata/migrations/mysql/20240601122756.sql new file mode 100644 index 0000000..085b59e --- /dev/null +++ b/internal/testdata/migrations/mysql/20240601122756.sql @@ -0,0 +1,15 @@ +-- Create "hobbies" table +CREATE TABLE `hobbies` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT, + `name` longtext NULL, + PRIMARY KEY (`id`) +) CHARSET utf8mb4 COLLATE utf8mb4_0900_ai_ci; +-- Create "user_hobbies" table +CREATE TABLE `user_hobbies` ( + `user_id` bigint unsigned NOT NULL, + `hobby_id` bigint unsigned NOT NULL, + PRIMARY KEY (`user_id`, `hobby_id`), + INDEX `fk_user_hobbies_hobby` (`hobby_id`), + CONSTRAINT `fk_user_hobbies_hobby` FOREIGN KEY (`hobby_id`) REFERENCES `hobbies` (`id`) ON UPDATE NO ACTION ON DELETE NO ACTION, + CONSTRAINT `fk_user_hobbies_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON UPDATE NO ACTION ON DELETE NO ACTION +) CHARSET utf8mb4 COLLATE utf8mb4_0900_ai_ci; diff --git a/internal/testdata/migrations/mysql/atlas.sum b/internal/testdata/migrations/mysql/atlas.sum index 4287904..925ebbd 100644 --- a/internal/testdata/migrations/mysql/atlas.sum +++ b/internal/testdata/migrations/mysql/atlas.sum @@ -1,4 +1,5 @@ -h1:lKRYkpC/b7av3+2N9ZwAhoInac+KYoj12ZIPaAINpeI= +h1:4neLwABljo1WE/2TflQvcvZCa4xR025viAzNEa4Cp6s= 20230627123246.sql h1:+bgzC3WJyyIR6Rv/FUvaNXJ1gkbKJlYcEMgp69yORIY= 20240512024238.sql h1:2kQL4tE/tAhvXuozmRAJ3oXTo1KRz11QosVDw+0va14= 20240518091603.sql h1:xNClqqRaOjXwg0julpsiPYsmqUcEL/hJel1iqYzi3DM= +20240601122756.sql h1:5+N9UzKTw6qQvJLf8nGw4BdFuHG0fa2JHdHn++G8MuE= diff --git a/internal/testdata/migrations/postgres/20240601122813.sql b/internal/testdata/migrations/postgres/20240601122813.sql new file mode 100644 index 0000000..a18cdd9 --- /dev/null +++ b/internal/testdata/migrations/postgres/20240601122813.sql @@ -0,0 +1,14 @@ +-- Create "hobbies" table +CREATE TABLE "public"."hobbies" ( + "id" bigserial NOT NULL, + "name" text NULL, + PRIMARY KEY ("id") +); +-- Create "user_hobbies" table +CREATE TABLE "public"."user_hobbies" ( + "user_id" bigint NOT NULL, + "hobby_id" bigint NOT NULL, + PRIMARY KEY ("user_id", "hobby_id"), + CONSTRAINT "fk_user_hobbies_hobby" FOREIGN KEY ("hobby_id") REFERENCES "public"."hobbies" ("id") ON UPDATE NO ACTION ON DELETE NO ACTION, + CONSTRAINT "fk_user_hobbies_user" FOREIGN KEY ("user_id") REFERENCES "public"."users" ("id") ON UPDATE NO ACTION ON DELETE NO ACTION +); diff --git a/internal/testdata/migrations/postgres/atlas.sum b/internal/testdata/migrations/postgres/atlas.sum index 05f7e69..fa401b8 100644 --- a/internal/testdata/migrations/postgres/atlas.sum +++ b/internal/testdata/migrations/postgres/atlas.sum @@ -1,4 +1,5 @@ -h1:LR1ImLA5ZOcbBr9yHMReTtaRiM6dVScC6Xefy3kpxWI= +h1:a/bAYykD/mWdu/8WM16vbYqnwg5NsdX2iM3urGrBFUc= 20230627123049.sql h1:1jYJM2+VCr9152vg6gayCrcEvuT/FE7ufOyZ86VLaOE= 20240512024223.sql h1:RY4w148OJBBr5sXpfBq6B48p/1cFrTEpH4TlwD7mUps= 20240518091611.sql h1:3Kv6mYS8ML72H6HE5qr/a2gdVUrfWuHVufP/1wl70vE= +20240601122813.sql h1:ALbhVYVMyOvZSy5jmBVrsk4xjdkx6ro8OxKJcmW0n1U= diff --git a/internal/testdata/migrations/sqlite/20240601122746.sql b/internal/testdata/migrations/sqlite/20240601122746.sql new file mode 100644 index 0000000..fd2e1e5 --- /dev/null +++ b/internal/testdata/migrations/sqlite/20240601122746.sql @@ -0,0 +1,14 @@ +-- Create "hobbies" table +CREATE TABLE `hobbies` ( + `id` integer NULL, + `name` text NULL, + PRIMARY KEY (`id`) +); +-- Create "user_hobbies" table +CREATE TABLE `user_hobbies` ( + `user_id` integer NULL, + `hobby_id` integer NULL, + PRIMARY KEY (`user_id`, `hobby_id`), + CONSTRAINT `fk_user_hobbies_hobby` FOREIGN KEY (`hobby_id`) REFERENCES `hobbies` (`id`) ON UPDATE NO ACTION ON DELETE NO ACTION, + CONSTRAINT `fk_user_hobbies_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON UPDATE NO ACTION ON DELETE NO ACTION +); diff --git a/internal/testdata/migrations/sqlite/atlas.sum b/internal/testdata/migrations/sqlite/atlas.sum index 81a544a..4bbb8ac 100644 --- a/internal/testdata/migrations/sqlite/atlas.sum +++ b/internal/testdata/migrations/sqlite/atlas.sum @@ -1,4 +1,5 @@ -h1:4HTi5BBlSSs2F/Rg17KQCcuEsVY/dDwyF2yzjThRcd0= +h1:n3NYZrFWODZNTYXEomQ/EtMuxc7EsQDZ7jaIgd9olYY= 20230627123228.sql h1:YfwJdN73sWz1G5/0tU2BtGLyzJCfRQr8blTSquUZ+qo= 20240511123637.sql h1:Kbk3wUzTfBbq8mDdTT08hP93ecNU0y5oTL+O8idEcdQ= 20240518091437.sql h1:svMANRZuZDvgzqO3iyNLUjrUrK8BTMEB2f0NV3d5sJo= +20240601122746.sql h1:Jpzp4UdG5EjO8uNtRWU8sZ9fKuP4mRod5ofyFVQwH1A= diff --git a/internal/testdata/migrations/sqlserver/20240601122612.sql b/internal/testdata/migrations/sqlserver/20240601122612.sql new file mode 100644 index 0000000..0da1a0e --- /dev/null +++ b/internal/testdata/migrations/sqlserver/20240601122612.sql @@ -0,0 +1,15 @@ +-- Create "hobbies" table +CREATE TABLE [hobbies] ( + [id] bigint IDENTITY (1, 1) NOT NULL, + [name] nvarchar(MAX) COLLATE SQL_Latin1_General_CP1_CI_AS NULL, + CONSTRAINT [PK_hobbies] PRIMARY KEY CLUSTERED ([id] ASC) +); +-- Create "user_hobbies" table +CREATE TABLE [user_hobbies] ( + [user_id] bigint NOT NULL, + [hobby_id] bigint NOT NULL, + CONSTRAINT [PK_user_hobbies] PRIMARY KEY CLUSTERED ([user_id] ASC, [hobby_id] ASC), + + CONSTRAINT [fk_user_hobbies_hobby] FOREIGN KEY ([hobby_id]) REFERENCES [hobbies] ([id]) ON UPDATE NO ACTION ON DELETE NO ACTION, + CONSTRAINT [fk_user_hobbies_user] FOREIGN KEY ([user_id]) REFERENCES [users] ([id]) ON UPDATE NO ACTION ON DELETE NO ACTION +); diff --git a/internal/testdata/migrations/sqlserver/atlas.sum b/internal/testdata/migrations/sqlserver/atlas.sum index 607d0f4..6ec041b 100644 --- a/internal/testdata/migrations/sqlserver/atlas.sum +++ b/internal/testdata/migrations/sqlserver/atlas.sum @@ -1,4 +1,5 @@ -h1:24mg+pUG3Z4l321YP5s1fRPPaAg6sJC8VR62qPXClTw= +h1:chvbQ1yysFYmq1K82/VfQeLxzMYrhjQ+8xv+2uoXdZw= 20240124151658.sql h1:KaWALlql7BBV3oPVRT4rn+dvZaolhDmgbTgUPxIhauU= 20240512024328.sql h1:IBON1V3jlts+AqxRhejN82SE7/BIXSUWM0SQ0pvw4wc= 20240518091510.sql h1:CCFQHjVI+5dLXCgoPSERCHhyGBZl7QistZlrs1I5170= +20240601122612.sql h1:sCf/0g9oPsaj0Z3l7WO2BjthUwsK9OfPWv80J/L1WCA= diff --git a/internal/testdata/models/user.go b/internal/testdata/models/user.go index ed432c5..1cc44f4 100644 --- a/internal/testdata/models/user.go +++ b/internal/testdata/models/user.go @@ -8,9 +8,16 @@ import ( type User struct { gorm.Model - Name string - Age int - Pets []Pet + Name string + Age int + Pets []Pet + Hobbies []Hobby `gorm:"many2many:user_hobbies;"` +} + +type Hobby struct { + ID uint + Name string + Users []User `gorm:"many2many:user_hobbies;"` } type WorkingAgedUsers struct { diff --git a/main_test.go b/main_test.go index 28de172..37b29e1 100644 --- a/main_test.go +++ b/main_test.go @@ -2,6 +2,7 @@ package main import ( "bytes" + "os" "testing" "github.com/stretchr/testify/require" @@ -27,20 +28,33 @@ func TestLoad(t *testing.T) { } func TestDeterministicOutput(t *testing.T) { - expected := "CREATE TABLE `users` (`id` integer,`created_at` datetime,`updated_at` datetime,`deleted_at` datetime,`name` text,`age` integer,PRIMARY KEY (`id`));\n" + - "CREATE INDEX `idx_users_deleted_at` ON `users`(`deleted_at`);\n" + - "CREATE TABLE `pets` (`id` integer,`created_at` datetime,`updated_at` datetime,`deleted_at` datetime,`name` text,`user_id` integer,PRIMARY KEY (`id`),CONSTRAINT `fk_users_pets` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`));\nCREATE INDEX `idx_pets_deleted_at` ON `pets`(`deleted_at`);\n" + - "CREATE VIEW top_pet_owners AS SELECT user_id, COUNT(id) AS pet_count FROM pets GROUP BY user_id ORDER BY pet_count DESC LIMIT 10;\n" + - "CREATE VIEW working_aged_users AS SELECT name, age FROM `users` WHERE age BETWEEN 18 AND 65;\n\n" + expected, err := os.ReadFile("./gormschema/testdata/mysql_deterministic_output") + require.NoError(t, err) cmd := &LoadCmd{ Path: "./internal/testdata/models", - Dialect: "sqlite", + Dialect: "mysql", } for i := 0; i < 10; i++ { var buf bytes.Buffer cmd.out = &buf err := cmd.Run() require.NoError(t, err) - require.Equal(t, expected, buf.String()) + actual := bytes.TrimSuffix(buf.Bytes(), []byte("\n")) + require.Equal(t, string(expected), string(actual)) } } + +func TestCustomizeTablesLoad(t *testing.T) { + var buf bytes.Buffer + cmd := &LoadCmd{ + Path: "./internal/testdata/customjointable", + Dialect: "mysql", + out: &buf, + } + err := cmd.Run() + require.NoError(t, err) + expected, err := os.ReadFile("./gormschema/testdata/mysql_custom_join_table") + require.NoError(t, err) + actual := bytes.TrimSuffix(buf.Bytes(), []byte("\n")) + require.Equal(t, string(expected), string(actual)) +}