Skip to content

Commit

Permalink
gormschema: support customize JoinTable (#40)
Browse files Browse the repository at this point in the history
* feat: support Customize JoinTable

* fix: re-order sql statements output of test cases

* chore: update README for Customize JoinTable usage

* fix: use callback instead of struct for params

* fix: allow WithJoinTable as an option

* fix: set up proper test case for customize table

* chore: update README for Customize JoinTable usage

* refactor: sort relationships map to have a deterministic SQL output

* chore: use slices.Sort instead of sort.Strings
  • Loading branch information
luantranminh authored Apr 25, 2024
1 parent 8be329c commit ed0d712
Show file tree
Hide file tree
Showing 7 changed files with 45 additions and 18 deletions.
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
Expand Down
34 changes: 29 additions & 5 deletions gormschema/gorm.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}
Expand All @@ -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)
})
}
}
4 changes: 2 additions & 2 deletions gormschema/gorm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Expand Down
2 changes: 1 addition & 1 deletion gormschema/testdata/mysql_custom_join_table
Original file line number Diff line number Diff line change
@@ -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`);
4 changes: 2 additions & 2 deletions gormschema/testdata/mysql_default
Original file line number Diff line number Diff line change
Expand Up @@ -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`);
4 changes: 2 additions & 2 deletions gormschema/testdata/postgresql_default
Original file line number Diff line number Diff line change
Expand Up @@ -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");
4 changes: 2 additions & 2 deletions gormschema/testdata/sqlserver_default
Original file line number Diff line number Diff line change
Expand Up @@ -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");

0 comments on commit ed0d712

Please sign in to comment.