gorm/internal_state_test.go
Jay Taylor c53f494ff6 Recalculate fields in `Scope.Fields' when 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).

Included is a unit-test which triggers the scenario and prove that it is
fixed.
2015-08-07 22:17:51 -07:00

69 lines
1.7 KiB
Go

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)
}
}