diff --git a/README.md b/README.md index dbfbedd5..476296f9 100644 --- a/README.md +++ b/README.md @@ -1210,6 +1210,7 @@ db.Where("email = ?", "x@example.org").Attrs(User{RegisteredIp: "111.111.111.111 * Github Pages * AlertColumn, DropColumn * R/W Splitting, Validation +* AddForeignKey compatibility with all dialects # Author diff --git a/field.go b/field.go index 8f5efa6d..a6cb8214 100644 --- a/field.go +++ b/field.go @@ -54,7 +54,25 @@ func (field *Field) Set(value interface{}) error { // Fields get value's fields func (scope *Scope) Fields() map[string]*Field { - if scope.fields == nil { + // Recalculate if fields is empty (nil) or number of fields is LTE 1. + // + // Protect the `.fields' variable state from partial-initialization timing + // issues. + // + // As gorm warms state and makes `.GetStructFields' calls, they lead to a long + // deferred function in model_struct.go. Then the deferred function calls + // `Scope.Fields', leading to another `.GetStructFields' call.. cyclically. + // Once information is cached the cycling ends. + // + // This extra rule evicts incorrect/suspiciously short fields information. + // + // Symptom of incorrect internal state ocurring can manifest as invalid + // insert SQL statement errors generated during `DB.Save', e.g.: + // + // INSERT INTO "your_table" DEFAULT VALUES RETURNING "your_table"."id" + // + // This is why we refresh if nil or only a single field (usually is the ID field). + if scope.fields == nil || len(scope.fields) <= 1 { fields := map[string]*Field{} structFields := scope.GetStructFields() diff --git a/internal_state_test.go b/internal_state_test.go new file mode 100644 index 00000000..94a44ccd --- /dev/null +++ b/internal_state_test.go @@ -0,0 +1,68 @@ +package gorm_test + +import ( + "runtime" + "testing" +) + +type ( + Organization struct { + Id int64 + Name string `sql:"type:varchar(255);not null;"` + } + + App struct { + Id int64 + Organization Organization + OrganizationId int64 `sql:"not null;"` + Name string `sql:"type:varchar(255);not null;"` + } +) + +// TestMultipleSingularTableInvocations runs `DB.SingularTable(true)' at the +// beginning and middle of operation. The `SingularTable' call clears out all +// of the `gorm.modelStructs' internal package state, triggering the conditions +// where gorm must recover, or this test will not pass. +// +// Also see the `Scope.Fields' function in scope.go for additional information +// and context. +func TestMultipleSingularTableInvocations(t *testing.T) { + runtime.GOMAXPROCS(runtime.NumCPU()) + DB.SingularTable(true) // Invocation #1. + defer DB.SingularTable(false) // Restore original plurality. + //DB.LogMode(true) + + entities := []interface{}{ + &Organization{}, + &App{}, + } + for i := len(entities) - 1; i >= 0; i-- { + if err := DB.DropTableIfExists(entities[i]).Error; err != nil { + t.Fatalf("Problem dropping table entity=%+v: %s", entities[i], err) + } + } + if err := DB.AutoMigrate(entities...).Error; err != nil { + t.Fatalf("Auto-migrate failed: %s", err) + } + + createFixtures(t) +} + +func createFixtures(t *testing.T) { + DB.SingularTable(true) // Invocation #2. Clobber/reset internal gorm state. + + org := &Organization{ + Name: "Some Organization for Testing", + } + if err := DB.Save(org).Error; err != nil { + t.Fatal(err) + } + + app := &App{ + OrganizationId: org.Id, + Name: "my-app-for-test-purposes", + } + if err := DB.Save(app).Error; err != nil { + t.Fatal(err) + } +}