github.com/systematiccaos/gorm@v1.22.6/schema/schema_test.go (about) 1 package schema_test 2 3 import ( 4 "strings" 5 "sync" 6 "testing" 7 8 "github.com/systematiccaos/gorm" 9 "github.com/systematiccaos/gorm/schema" 10 "github.com/systematiccaos/gorm/utils/tests" 11 ) 12 13 func TestParseSchema(t *testing.T) { 14 user, err := schema.Parse(&tests.User{}, &sync.Map{}, schema.NamingStrategy{}) 15 if err != nil { 16 t.Fatalf("failed to parse user, got error %v", err) 17 } 18 19 checkUserSchema(t, user) 20 } 21 22 func TestParseSchemaWithPointerFields(t *testing.T) { 23 user, err := schema.Parse(&User{}, &sync.Map{}, schema.NamingStrategy{}) 24 if err != nil { 25 t.Fatalf("failed to parse pointer user, got error %v", err) 26 } 27 28 checkUserSchema(t, user) 29 } 30 31 func checkUserSchema(t *testing.T, user *schema.Schema) { 32 // check schema 33 checkSchema(t, user, schema.Schema{Name: "User", Table: "users"}, []string{"ID"}) 34 35 // check fields 36 fields := []schema.Field{ 37 {Name: "ID", DBName: "id", BindNames: []string{"Model", "ID"}, DataType: schema.Uint, PrimaryKey: true, Tag: `gorm:"primarykey"`, TagSettings: map[string]string{"PRIMARYKEY": "PRIMARYKEY"}, Size: 64, HasDefaultValue: true, AutoIncrement: true}, 38 {Name: "CreatedAt", DBName: "created_at", BindNames: []string{"Model", "CreatedAt"}, DataType: schema.Time}, 39 {Name: "UpdatedAt", DBName: "updated_at", BindNames: []string{"Model", "UpdatedAt"}, DataType: schema.Time}, 40 {Name: "DeletedAt", DBName: "deleted_at", BindNames: []string{"Model", "DeletedAt"}, Tag: `gorm:"index"`, DataType: schema.Time}, 41 {Name: "Name", DBName: "name", BindNames: []string{"Name"}, DataType: schema.String}, 42 {Name: "Age", DBName: "age", BindNames: []string{"Age"}, DataType: schema.Uint, Size: 64}, 43 {Name: "Birthday", DBName: "birthday", BindNames: []string{"Birthday"}, DataType: schema.Time}, 44 {Name: "CompanyID", DBName: "company_id", BindNames: []string{"CompanyID"}, DataType: schema.Int, Size: 64}, 45 {Name: "ManagerID", DBName: "manager_id", BindNames: []string{"ManagerID"}, DataType: schema.Uint, Size: 64}, 46 {Name: "Active", DBName: "active", BindNames: []string{"Active"}, DataType: schema.Bool}, 47 } 48 49 for _, f := range fields { 50 checkSchemaField(t, user, &f, func(f *schema.Field) { 51 f.Creatable = true 52 f.Updatable = true 53 f.Readable = true 54 }) 55 } 56 57 // check relations 58 relations := []Relation{ 59 { 60 Name: "Account", Type: schema.HasOne, Schema: "User", FieldSchema: "Account", 61 References: []Reference{{"ID", "User", "UserID", "Account", "", true}}, 62 }, 63 { 64 Name: "Pets", Type: schema.HasMany, Schema: "User", FieldSchema: "Pet", 65 References: []Reference{{"ID", "User", "UserID", "Pet", "", true}}, 66 }, 67 { 68 Name: "Toys", Type: schema.HasMany, Schema: "User", FieldSchema: "Toy", 69 Polymorphic: Polymorphic{ID: "OwnerID", Type: "OwnerType", Value: "users"}, 70 References: []Reference{{"ID", "User", "OwnerID", "Toy", "", true}, {"", "", "OwnerType", "Toy", "users", false}}, 71 }, 72 { 73 Name: "Company", Type: schema.BelongsTo, Schema: "User", FieldSchema: "Company", 74 References: []Reference{{"ID", "Company", "CompanyID", "User", "", false}}, 75 }, 76 { 77 Name: "Manager", Type: schema.BelongsTo, Schema: "User", FieldSchema: "User", 78 References: []Reference{{"ID", "User", "ManagerID", "User", "", false}}, 79 }, 80 { 81 Name: "Team", Type: schema.HasMany, Schema: "User", FieldSchema: "User", 82 References: []Reference{{"ID", "User", "ManagerID", "User", "", true}}, 83 }, 84 { 85 Name: "Languages", Type: schema.Many2Many, Schema: "User", FieldSchema: "Language", 86 JoinTable: JoinTable{Name: "UserSpeak", Table: "user_speaks", Fields: []schema.Field{ 87 { 88 Name: "UserID", DBName: "user_id", BindNames: []string{"UserID"}, DataType: schema.Uint, 89 Tag: `gorm:"primarykey"`, Creatable: true, Updatable: true, Readable: true, PrimaryKey: true, Size: 64, 90 }, 91 { 92 Name: "LanguageCode", DBName: "language_code", BindNames: []string{"LanguageCode"}, DataType: schema.String, 93 Tag: `gorm:"primarykey"`, Creatable: true, Updatable: true, Readable: true, PrimaryKey: true, 94 }, 95 }}, 96 References: []Reference{{"ID", "User", "UserID", "UserSpeak", "", true}, {"Code", "Language", "LanguageCode", "UserSpeak", "", false}}, 97 }, 98 { 99 Name: "Friends", Type: schema.Many2Many, Schema: "User", FieldSchema: "User", 100 JoinTable: JoinTable{Name: "user_friends", Table: "user_friends", Fields: []schema.Field{ 101 { 102 Name: "UserID", DBName: "user_id", BindNames: []string{"UserID"}, DataType: schema.Uint, 103 Tag: `gorm:"primarykey"`, Creatable: true, Updatable: true, Readable: true, PrimaryKey: true, Size: 64, 104 }, 105 { 106 Name: "FriendID", DBName: "friend_id", BindNames: []string{"FriendID"}, DataType: schema.Uint, 107 Tag: `gorm:"primarykey"`, Creatable: true, Updatable: true, Readable: true, PrimaryKey: true, Size: 64, 108 }, 109 }}, 110 References: []Reference{{"ID", "User", "UserID", "user_friends", "", true}, {"ID", "User", "FriendID", "user_friends", "", false}}, 111 }, 112 } 113 114 for _, relation := range relations { 115 checkSchemaRelation(t, user, relation) 116 } 117 } 118 119 func TestParseSchemaWithAdvancedDataType(t *testing.T) { 120 user, err := schema.Parse(&AdvancedDataTypeUser{}, &sync.Map{}, schema.NamingStrategy{}) 121 if err != nil { 122 t.Fatalf("failed to parse pointer user, got error %v", err) 123 } 124 125 // check schema 126 checkSchema(t, user, schema.Schema{Name: "AdvancedDataTypeUser", Table: "advanced_data_type_users"}, []string{"ID"}) 127 128 // check fields 129 fields := []schema.Field{ 130 {Name: "ID", DBName: "id", BindNames: []string{"ID"}, DataType: schema.Int, PrimaryKey: true, Size: 64, HasDefaultValue: true, AutoIncrement: true}, 131 {Name: "Name", DBName: "name", BindNames: []string{"Name"}, DataType: schema.String}, 132 {Name: "Birthday", DBName: "birthday", BindNames: []string{"Birthday"}, DataType: schema.Time}, 133 {Name: "RegisteredAt", DBName: "registered_at", BindNames: []string{"RegisteredAt"}, DataType: schema.Time}, 134 {Name: "DeletedAt", DBName: "deleted_at", BindNames: []string{"DeletedAt"}, DataType: schema.Time}, 135 {Name: "Active", DBName: "active", BindNames: []string{"Active"}, DataType: schema.Bool}, 136 {Name: "Admin", DBName: "admin", BindNames: []string{"Admin"}, DataType: schema.Bool}, 137 } 138 139 for _, f := range fields { 140 checkSchemaField(t, user, &f, func(f *schema.Field) { 141 f.Creatable = true 142 f.Updatable = true 143 f.Readable = true 144 }) 145 } 146 } 147 148 type CustomizeTable struct { 149 } 150 151 func (CustomizeTable) TableName() string { 152 return "customize" 153 } 154 155 func TestCustomizeTableName(t *testing.T) { 156 customize, err := schema.Parse(&CustomizeTable{}, &sync.Map{}, schema.NamingStrategy{}) 157 if err != nil { 158 t.Fatalf("failed to parse pointer user, got error %v", err) 159 } 160 161 if customize.Table != "customize" { 162 t.Errorf("Failed to customize table with TableName method") 163 } 164 } 165 166 func TestNestedModel(t *testing.T) { 167 versionUser, err := schema.Parse(&VersionUser{}, &sync.Map{}, schema.NamingStrategy{}) 168 169 if err != nil { 170 t.Fatalf("failed to parse nested user, got error %v", err) 171 } 172 173 fields := []schema.Field{ 174 {Name: "ID", DBName: "id", BindNames: []string{"VersionModel", "BaseModel", "ID"}, DataType: schema.Uint, PrimaryKey: true, Size: 64, HasDefaultValue: true, AutoIncrement: true}, 175 {Name: "CreatedBy", DBName: "created_by", BindNames: []string{"VersionModel", "BaseModel", "CreatedBy"}, DataType: schema.Uint, Size: 64}, 176 {Name: "Version", DBName: "version", BindNames: []string{"VersionModel", "Version"}, DataType: schema.Int, Size: 64}, 177 } 178 179 for _, f := range fields { 180 checkSchemaField(t, versionUser, &f, func(f *schema.Field) { 181 f.Creatable = true 182 f.Updatable = true 183 f.Readable = true 184 }) 185 } 186 } 187 188 func TestEmbeddedStruct(t *testing.T) { 189 type CorpBase struct { 190 gorm.Model 191 OwnerID string 192 } 193 194 type Company struct { 195 ID int 196 OwnerID int 197 Name string 198 Ignored string `gorm:"-"` 199 } 200 201 type Corp struct { 202 CorpBase 203 Base Company `gorm:"embedded;embeddedPrefix:company_"` 204 } 205 206 cropSchema, err := schema.Parse(&Corp{}, &sync.Map{}, schema.NamingStrategy{}) 207 208 if err != nil { 209 t.Fatalf("failed to parse embedded struct with primary key, got error %v", err) 210 } 211 212 fields := []schema.Field{ 213 {Name: "ID", DBName: "id", BindNames: []string{"CorpBase", "Model", "ID"}, DataType: schema.Uint, PrimaryKey: true, Size: 64, HasDefaultValue: true, AutoIncrement: true, TagSettings: map[string]string{"PRIMARYKEY": "PRIMARYKEY"}}, 214 {Name: "ID", DBName: "company_id", BindNames: []string{"Base", "ID"}, DataType: schema.Int, Size: 64, TagSettings: map[string]string{"EMBEDDED": "EMBEDDED", "EMBEDDEDPREFIX": "company_"}}, 215 {Name: "Name", DBName: "company_name", BindNames: []string{"Base", "Name"}, DataType: schema.String, TagSettings: map[string]string{"EMBEDDED": "EMBEDDED", "EMBEDDEDPREFIX": "company_"}}, 216 {Name: "Ignored", BindNames: []string{"Base", "Ignored"}, TagSettings: map[string]string{"-": "-", "EMBEDDED": "EMBEDDED", "EMBEDDEDPREFIX": "company_"}}, 217 {Name: "OwnerID", DBName: "company_owner_id", BindNames: []string{"Base", "OwnerID"}, DataType: schema.Int, Size: 64, TagSettings: map[string]string{"EMBEDDED": "EMBEDDED", "EMBEDDEDPREFIX": "company_"}}, 218 {Name: "OwnerID", DBName: "owner_id", BindNames: []string{"CorpBase", "OwnerID"}, DataType: schema.String}, 219 } 220 221 for _, f := range fields { 222 checkSchemaField(t, cropSchema, &f, func(f *schema.Field) { 223 if f.Name != "Ignored" { 224 f.Creatable = true 225 f.Updatable = true 226 f.Readable = true 227 } 228 }) 229 } 230 } 231 232 type CustomizedNamingStrategy struct { 233 schema.NamingStrategy 234 } 235 236 func (ns CustomizedNamingStrategy) ColumnName(table, column string) string { 237 baseColumnName := ns.NamingStrategy.ColumnName(table, column) 238 239 if table == "" { 240 return baseColumnName 241 } 242 243 s := strings.Split(table, "_") 244 245 var prefix string 246 switch len(s) { 247 case 1: 248 prefix = s[0][:3] 249 case 2: 250 prefix = s[0][:1] + s[1][:2] 251 default: 252 prefix = s[0][:1] + s[1][:1] + s[2][:1] 253 } 254 return prefix + "_" + baseColumnName 255 } 256 257 func TestEmbeddedStructForCustomizedNamingStrategy(t *testing.T) { 258 type CorpBase struct { 259 gorm.Model 260 OwnerID string 261 } 262 263 type Company struct { 264 ID int 265 OwnerID int 266 Name string 267 Ignored string `gorm:"-"` 268 } 269 270 type Corp struct { 271 CorpBase 272 Base Company `gorm:"embedded;embeddedPrefix:company_"` 273 } 274 275 cropSchema, err := schema.Parse(&Corp{}, &sync.Map{}, CustomizedNamingStrategy{schema.NamingStrategy{}}) 276 277 if err != nil { 278 t.Fatalf("failed to parse embedded struct with primary key, got error %v", err) 279 } 280 281 fields := []schema.Field{ 282 {Name: "ID", DBName: "cor_id", BindNames: []string{"CorpBase", "Model", "ID"}, DataType: schema.Uint, PrimaryKey: true, Size: 64, HasDefaultValue: true, AutoIncrement: true, TagSettings: map[string]string{"PRIMARYKEY": "PRIMARYKEY"}}, 283 {Name: "ID", DBName: "company_cor_id", BindNames: []string{"Base", "ID"}, DataType: schema.Int, Size: 64, TagSettings: map[string]string{"EMBEDDED": "EMBEDDED", "EMBEDDEDPREFIX": "company_"}}, 284 {Name: "Name", DBName: "company_cor_name", BindNames: []string{"Base", "Name"}, DataType: schema.String, TagSettings: map[string]string{"EMBEDDED": "EMBEDDED", "EMBEDDEDPREFIX": "company_"}}, 285 {Name: "Ignored", BindNames: []string{"Base", "Ignored"}, TagSettings: map[string]string{"-": "-", "EMBEDDED": "EMBEDDED", "EMBEDDEDPREFIX": "company_"}}, 286 {Name: "OwnerID", DBName: "company_cor_owner_id", BindNames: []string{"Base", "OwnerID"}, DataType: schema.Int, Size: 64, TagSettings: map[string]string{"EMBEDDED": "EMBEDDED", "EMBEDDEDPREFIX": "company_"}}, 287 {Name: "OwnerID", DBName: "cor_owner_id", BindNames: []string{"CorpBase", "OwnerID"}, DataType: schema.String}, 288 } 289 290 for _, f := range fields { 291 checkSchemaField(t, cropSchema, &f, func(f *schema.Field) { 292 if f.Name != "Ignored" { 293 f.Creatable = true 294 f.Updatable = true 295 f.Readable = true 296 } 297 }) 298 } 299 }