diff --git a/README.md b/README.md index 015ff10..9323b5d 100644 --- a/README.md +++ b/README.md @@ -184,9 +184,8 @@ The provider supports the following databases: * **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 pass to the `Load` method the tables in their dependency order. i.e., - Join tables after their parent tables. - + 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: ```go type Person struct { @@ -210,7 +209,11 @@ The provider supports the following databases: you should use the following code: ```go - stmts, err := gormschema.New("mysql").Load(&models.Person{}, &models.Address{}, &models.PersonAddress{}) + stmts, err := gormschema.New("mysql", + gormschema.WithJoinTable( + &Models.Person{}, "Addresses", &Models.PersonAddress{}, + ), + ).Load(&Models.Address{}, &Models.Person{}) ``` ### License diff --git a/gormschema/gorm.go b/gormschema/gorm.go index 12d3abc..08d5d7c 100644 --- a/gormschema/gorm.go +++ b/gormschema/gorm.go @@ -28,8 +28,9 @@ func New(dialect string, opts ...Option) *Loader { type ( // Loader is a Loader for gorm schema. Loader struct { - dialect string - config *gorm.Config + dialect string + config *gorm.Config + beforeAutoMigrate []func(*gorm.DB) error } // Option configures the Loader. Option func(*Loader) @@ -85,6 +86,11 @@ func (l *Loader) Load(models ...any) (string, error) { if l.dialect != "sqlite" { db.Config.DisableForeignKeyConstraintWhenMigrating = true } + for _, cb := range l.beforeAutoMigrate { + if err = cb(db); err != nil { + return "", err + } + } if err = db.AutoMigrate(models...); err != nil { return "", err } @@ -140,11 +146,20 @@ func (m *migrator) HasTable(dst any) bool { // CreateConstraints detects constraints on the given model and creates them using `m.dialectMigrator`. func (m *migrator) CreateConstraints(models []any) error { - // Reverse the order of models to ensure many 2 many tables constraints are created first, assuming they are at the end. - slices.Reverse(models) for _, model := range m.ReorderModels(models, true) { err := m.Migrator.RunWithValue(model, func(stmt *gorm.Statement) error { - for _, rel := range stmt.Schema.Relationships.Relations { + + relationNames := make([]string, 0, len(stmt.Schema.Relationships.Relations)) + for name := range stmt.Schema.Relationships.Relations { + relationNames = append(relationNames, name) + } + // since Relations is a map, the order of the keys is not guaranteed + // so we sort the keys to make the sql output deterministic + slices.Sort(relationNames) + + for _, name := range relationNames { + rel := stmt.Schema.Relationships.Relations[name] + if rel.Field.IgnoreMigration { continue } @@ -163,3 +178,12 @@ func (m *migrator) CreateConstraints(models []any) error { } return nil } + +// WithJoinTable sets up a join table for the given model and field. +func WithJoinTable(model any, field string, jointable any) Option { + return func(l *Loader) { + l.beforeAutoMigrate = append(l.beforeAutoMigrate, func(db *gorm.DB) error { + return db.SetupJoinTable(model, field, jointable) + }) + } +} diff --git a/gormschema/gorm_test.go b/gormschema/gorm_test.go index 6b1d2ac..3907b7d 100644 --- a/gormschema/gorm_test.go +++ b/gormschema/gorm_test.go @@ -60,8 +60,8 @@ func TestMySQLConfig(t *testing.T) { require.NoError(t, err) requireEqualContent(t, sql, "testdata/mysql_no_fk") resetSession() - l = New("mysql") - sql, err = l.Load(customjointable.Address{}, customjointable.Person{}, customjointable.PersonAddress{}) + l = New("mysql", WithJoinTable(&customjointable.Person{}, "Addresses", &customjointable.PersonAddress{})) + sql, err = l.Load(customjointable.Address{}, customjointable.Person{}) require.NoError(t, err) requireEqualContent(t, sql, "testdata/mysql_custom_join_table") } diff --git a/gormschema/testdata/mysql_custom_join_table b/gormschema/testdata/mysql_custom_join_table index 66b505f..eb9f4fd 100644 --- a/gormschema/testdata/mysql_custom_join_table +++ b/gormschema/testdata/mysql_custom_join_table @@ -1,5 +1,5 @@ CREATE TABLE `addresses` (`id` bigint AUTO_INCREMENT,`name` longtext,PRIMARY KEY (`id`)); CREATE TABLE `people` (`id` bigint AUTO_INCREMENT,`name` longtext,PRIMARY KEY (`id`)); CREATE TABLE `person_addresses` (`person_id` bigint,`address_id` bigint,`created_at` datetime(3) NULL,`deleted_at` datetime(3) NULL,PRIMARY KEY (`person_id`,`address_id`)); -ALTER TABLE `person_addresses` ADD CONSTRAINT `fk_person_addresses_person` FOREIGN KEY (`person_id`) REFERENCES `people`(`id`); ALTER TABLE `person_addresses` ADD CONSTRAINT `fk_person_addresses_address` FOREIGN KEY (`address_id`) REFERENCES `addresses`(`id`); +ALTER TABLE `person_addresses` ADD CONSTRAINT `fk_person_addresses_person` FOREIGN KEY (`person_id`) REFERENCES `people`(`id`); diff --git a/gormschema/testdata/mysql_default b/gormschema/testdata/mysql_default index af32bfa..b786753 100644 --- a/gormschema/testdata/mysql_default +++ b/gormschema/testdata/mysql_default @@ -2,6 +2,6 @@ CREATE TABLE `events` (`eventId` varchar(191),`locationId` varchar(191),PRIMARY 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,PRIMARY KEY (`id`),INDEX `idx_users_deleted_at` (`deleted_at`)); 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`)); -ALTER TABLE `pets` ADD CONSTRAINT `fk_users_pets` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`); -ALTER TABLE `locations` ADD CONSTRAINT `fk_events_location` FOREIGN KEY (`eventId`) REFERENCES `events`(`eventId`); 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 `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 f1d0f16..7f30ad5 100644 --- a/gormschema/testdata/postgresql_default +++ b/gormschema/testdata/postgresql_default @@ -6,6 +6,6 @@ CREATE TABLE "users" ("id" bigserial,"created_at" timestamptz,"updated_at" times CREATE INDEX IF NOT EXISTS "idx_users_deleted_at" ON "users" ("deleted_at"); 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"); -ALTER TABLE "pets" ADD CONSTRAINT "fk_users_pets" FOREIGN KEY ("user_id") REFERENCES "users"("id"); -ALTER TABLE "locations" ADD CONSTRAINT "fk_events_location" FOREIGN KEY ("eventId") REFERENCES "events"("eventId"); 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 "pets" ADD CONSTRAINT "fk_users_pets" FOREIGN KEY ("user_id") REFERENCES "users"("id"); diff --git a/gormschema/testdata/sqlserver_default b/gormschema/testdata/sqlserver_default index 00da7a9..2811bf2 100644 --- a/gormschema/testdata/sqlserver_default +++ b/gormschema/testdata/sqlserver_default @@ -6,6 +6,6 @@ CREATE TABLE "users" ("id" bigint IDENTITY(1,1),"created_at" datetimeoffset,"upd CREATE INDEX "idx_users_deleted_at" ON "users"("deleted_at"); 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"); -ALTER TABLE "pets" ADD CONSTRAINT "fk_users_pets" FOREIGN KEY ("user_id") REFERENCES "users"("id"); -ALTER TABLE "locations" ADD CONSTRAINT "fk_events_location" FOREIGN KEY ("eventId") REFERENCES "events"("eventId"); 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 "pets" ADD CONSTRAINT "fk_users_pets" FOREIGN KEY ("user_id") REFERENCES "users"("id");