From 78e905919fc253332fb032d0f4a76e7753e437e4 Mon Sep 17 00:00:00 2001 From: gleb <47985861+glebarez@users.noreply.github.com> Date: Thu, 26 Oct 2023 06:54:15 +0300 Subject: [PATCH 1/7] tests/sqilte: enable FOREIGN_KEYS inside OpenTestConnection (#6641) --- tests/tests_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/tests_test.go b/tests/tests_test.go index 47c2a7c1..f9c6cab5 100644 --- a/tests/tests_test.go +++ b/tests/tests_test.go @@ -43,9 +43,6 @@ func init() { } RunMigrations() - if DB.Dialector.Name() == "sqlite" { - DB.Exec("PRAGMA foreign_keys = ON") - } } } @@ -89,7 +86,10 @@ func OpenTestConnection(cfg *gorm.Config) (db *gorm.DB, err error) { db, err = gorm.Open(mysql.Open(dbDSN), cfg) default: log.Println("testing sqlite3...") - db, err = gorm.Open(sqlite.Open(filepath.Join(os.TempDir(), "gorm.db?_foreign_keys=on")), cfg) + db, err = gorm.Open(sqlite.Open(filepath.Join(os.TempDir(), "gorm.db")), cfg) + if err == nil { + db.Exec("PRAGMA foreign_keys = ON") + } } if err != nil { From 5adc0ce5f6c8cf97f1f6b9e835750406612c2fe0 Mon Sep 17 00:00:00 2001 From: Cr <631807682@qq.com> Date: Thu, 26 Oct 2023 11:58:13 +0800 Subject: [PATCH 2/7] test: fix TestEmbeddedRelations (#6639) --- tests/embedded_struct_test.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/embedded_struct_test.go b/tests/embedded_struct_test.go index 4314f88c..873bba2a 100644 --- a/tests/embedded_struct_test.go +++ b/tests/embedded_struct_test.go @@ -236,8 +236,15 @@ func TestEmbeddedScanValuer(t *testing.T) { } func TestEmbeddedRelations(t *testing.T) { + type EmbUser struct { + gorm.Model + Name string + Age uint + Languages []Language `gorm:"many2many:EmbUserSpeak;"` + } + type AdvancedUser struct { - User `gorm:"embedded"` + EmbUser `gorm:"embedded"` Advanced bool } From 9fea15ae75fb9ff2bd86dcaa167673c8ed77394f Mon Sep 17 00:00:00 2001 From: black-06 Date: Mon, 30 Oct 2023 17:15:49 +0800 Subject: [PATCH 3/7] feat: add MigrateColumnUnique (#6640) * feat: add MigrateColumnUnique * feat: define new methods * delete debug in test --- migrator.go | 2 ++ migrator/migrator.go | 22 ++++++++++++++++++++++ schema/naming.go | 8 ++++++++ tests/associations_belongs_to_test.go | 2 -- tests/count_test.go | 2 +- tests/preload_test.go | 1 - tests/update_test.go | 2 +- 7 files changed, 34 insertions(+), 5 deletions(-) diff --git a/migrator.go b/migrator.go index 0e01f567..3d2b032b 100644 --- a/migrator.go +++ b/migrator.go @@ -87,6 +87,8 @@ type Migrator interface { DropColumn(dst interface{}, field string) error AlterColumn(dst interface{}, field string) error MigrateColumn(dst interface{}, field *schema.Field, columnType ColumnType) error + // MigrateColumnUnique migrate column's UNIQUE constraint, it's part of MigrateColumn. + MigrateColumnUnique(dst interface{}, field *schema.Field, columnType ColumnType) error HasColumn(dst interface{}, field string) bool RenameColumn(dst interface{}, oldName, field string) error ColumnTypes(dst interface{}) ([]ColumnType, error) diff --git a/migrator/migrator.go b/migrator/migrator.go index 49bc9371..64a5a4b5 100644 --- a/migrator/migrator.go +++ b/migrator/migrator.go @@ -27,6 +27,8 @@ var regFullDataType = regexp.MustCompile(`\D*(\d+)\D?`) // TODO:? Create const vars for raw sql queries ? +var _ gorm.Migrator = (*Migrator)(nil) + // Migrator m struct type Migrator struct { Config @@ -539,6 +541,26 @@ func (m Migrator) MigrateColumn(value interface{}, field *schema.Field, columnTy return nil } +func (m Migrator) MigrateColumnUnique(value interface{}, field *schema.Field, columnType gorm.ColumnType) error { + unique, ok := columnType.Unique() + if !ok || field.PrimaryKey { + return nil // skip primary key + } + // By default, ColumnType's Unique is not affected by UniqueIndex, so we don't care about UniqueIndex. + return m.RunWithValue(value, func(stmt *gorm.Statement) error { + // We're currently only receiving boolean values on `Unique` tag, + // so the UniqueConstraint name is fixed + constraint := m.DB.NamingStrategy.UniqueName(stmt.Table, field.DBName) + if unique && !field.Unique { + return m.DB.Migrator().DropConstraint(value, constraint) + } + if !unique && field.Unique { + return m.DB.Migrator().CreateConstraint(value, constraint) + } + return nil + }) +} + // ColumnTypes return columnTypes []gorm.ColumnType and execErr error func (m Migrator) ColumnTypes(value interface{}) ([]gorm.ColumnType, error) { columnTypes := make([]gorm.ColumnType, 0) diff --git a/schema/naming.go b/schema/naming.go index a2a0150a..e6fb81b2 100644 --- a/schema/naming.go +++ b/schema/naming.go @@ -19,6 +19,7 @@ type Namer interface { RelationshipFKName(Relationship) string CheckerName(table, column string) string IndexName(table, column string) string + UniqueName(table, column string) string } // Replacer replacer interface like strings.Replacer @@ -26,6 +27,8 @@ type Replacer interface { Replace(name string) string } +var _ Namer = (*NamingStrategy)(nil) + // NamingStrategy tables, columns naming strategy type NamingStrategy struct { TablePrefix string @@ -85,6 +88,11 @@ func (ns NamingStrategy) IndexName(table, column string) string { return ns.formatName("idx", table, ns.toDBName(column)) } +// UniqueName generate unique constraint name +func (ns NamingStrategy) UniqueName(table, column string) string { + return ns.formatName("uni", table, ns.toDBName(column)) +} + func (ns NamingStrategy) formatName(prefix, table, name string) string { formattedName := strings.ReplaceAll(strings.Join([]string{ prefix, table, name, diff --git a/tests/associations_belongs_to_test.go b/tests/associations_belongs_to_test.go index 6befb5f2..103da032 100644 --- a/tests/associations_belongs_to_test.go +++ b/tests/associations_belongs_to_test.go @@ -278,8 +278,6 @@ func TestBelongsToAssociationUnscoped(t *testing.T) { t.Fatalf("failed to create items, got error: %v", err) } - tx = tx.Debug() - // test replace if err := tx.Model(&item).Association("ItemParent").Unscoped().Replace(&ItemParent{ Logo: "updated logo", diff --git a/tests/count_test.go b/tests/count_test.go index b0dfb0b5..4449515b 100644 --- a/tests/count_test.go +++ b/tests/count_test.go @@ -29,7 +29,7 @@ func TestCountWithGroup(t *testing.T) { } var count2 int64 - if err := DB.Debug().Model(&Company{}).Where("name in ?", []string{"company_count_group_b", "company_count_group_c"}).Group("name").Count(&count2).Error; err != nil { + if err := DB.Model(&Company{}).Where("name in ?", []string{"company_count_group_b", "company_count_group_c"}).Group("name").Count(&count2).Error; err != nil { t.Errorf(fmt.Sprintf("Count should work, but got err %v", err)) } if count2 != 2 { diff --git a/tests/preload_test.go b/tests/preload_test.go index 7304e350..3ff86492 100644 --- a/tests/preload_test.go +++ b/tests/preload_test.go @@ -429,7 +429,6 @@ func TestEmbedPreload(t *testing.T) { }, } - DB = DB.Debug() for _, test := range tests { t.Run(test.name, func(t *testing.T) { actual := Org{} diff --git a/tests/update_test.go b/tests/update_test.go index a3fb7015..b719cc45 100644 --- a/tests/update_test.go +++ b/tests/update_test.go @@ -838,7 +838,7 @@ func TestSaveWithHooks(t *testing.T) { saveTokenOwner := func(owner *TokenOwner) (*TokenOwner, error) { var newOwner TokenOwner if err := DB.Transaction(func(tx *gorm.DB) error { - if err := tx.Debug().Session(&gorm.Session{FullSaveAssociations: true}).Save(owner).Error; err != nil { + if err := tx.Session(&gorm.Session{FullSaveAssociations: true}).Save(owner).Error; err != nil { return err } if err := tx.Preload("Token").First(&newOwner, owner.ID).Error; err != nil { From d2fb7a942b8d44f9ad7f6f5bc6f9f99ddcebc95a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Flc=E3=82=9B?= Date: Tue, 7 Nov 2023 10:19:41 +0800 Subject: [PATCH 4/7] chore(logger): optimize (#6675) * chore(logger): optimize * chore(logger): optimize --- logger/logger.go | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/logger/logger.go b/logger/logger.go index aa0060bc..253f0325 100644 --- a/logger/logger.go +++ b/logger/logger.go @@ -69,7 +69,7 @@ type Interface interface { } var ( - // Discard Discard logger will print any log to io.Discard + // Discard logger will print any log to io.Discard Discard = New(log.New(io.Discard, "", log.LstdFlags), Config{}) // Default Default logger Default = New(log.New(os.Stdout, "\r\n", log.LstdFlags), Config{ @@ -78,7 +78,7 @@ var ( IgnoreRecordNotFoundError: false, Colorful: true, }) - // Recorder Recorder logger records running SQL into a recorder instance + // Recorder logger records running SQL into a recorder instance Recorder = traceRecorder{Interface: Default, BeginAt: time.Now()} ) @@ -129,28 +129,30 @@ func (l *logger) LogMode(level LogLevel) Interface { } // Info print info -func (l logger) Info(ctx context.Context, msg string, data ...interface{}) { +func (l *logger) Info(ctx context.Context, msg string, data ...interface{}) { if l.LogLevel >= Info { l.Printf(l.infoStr+msg, append([]interface{}{utils.FileWithLineNum()}, data...)...) } } // Warn print warn messages -func (l logger) Warn(ctx context.Context, msg string, data ...interface{}) { +func (l *logger) Warn(ctx context.Context, msg string, data ...interface{}) { if l.LogLevel >= Warn { l.Printf(l.warnStr+msg, append([]interface{}{utils.FileWithLineNum()}, data...)...) } } // Error print error messages -func (l logger) Error(ctx context.Context, msg string, data ...interface{}) { +func (l *logger) Error(ctx context.Context, msg string, data ...interface{}) { if l.LogLevel >= Error { l.Printf(l.errStr+msg, append([]interface{}{utils.FileWithLineNum()}, data...)...) } } // Trace print sql message -func (l logger) Trace(ctx context.Context, begin time.Time, fc func() (string, int64), err error) { +// +//nolint:cyclop +func (l *logger) Trace(ctx context.Context, begin time.Time, fc func() (string, int64), err error) { if l.LogLevel <= Silent { return } @@ -182,8 +184,8 @@ func (l logger) Trace(ctx context.Context, begin time.Time, fc func() (string, i } } -// Trace print sql message -func (l logger) ParamsFilter(ctx context.Context, sql string, params ...interface{}) (string, []interface{}) { +// ParamsFilter filter params +func (l *logger) ParamsFilter(ctx context.Context, sql string, params ...interface{}) (string, []interface{}) { if l.Config.ParameterizedQueries { return sql, nil } @@ -198,8 +200,8 @@ type traceRecorder struct { Err error } -// New new trace recorder -func (l traceRecorder) New() *traceRecorder { +// New trace recorder +func (l *traceRecorder) New() *traceRecorder { return &traceRecorder{Interface: l.Interface, BeginAt: time.Now()} } From 40f4afe8c21d96db63174bd501fb61d6e73c5587 Mon Sep 17 00:00:00 2001 From: Kijima Daigo Date: Tue, 7 Nov 2023 11:20:06 +0900 Subject: [PATCH 5/7] docs: fix broken link (#6673) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 85ad3050..745dad60 100644 --- a/README.md +++ b/README.md @@ -41,4 +41,4 @@ The fantastic ORM library for Golang, aims to be developer friendly. © Jinzhu, 2013~time.Now -Released under the [MIT License](https://github.com/go-gorm/gorm/blob/master/License) +Released under the [MIT License](https://github.com/go-gorm/gorm/blob/master/LICENSE) From c1e911f6ed8d3d929aebbd39985a33c9ebe3bad7 Mon Sep 17 00:00:00 2001 From: Jinzhu Date: Thu, 9 Nov 2023 18:46:39 +0800 Subject: [PATCH 6/7] Update tests/go.mod --- tests/go.mod | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/tests/go.mod b/tests/go.mod index 5a0aeddd..71079050 100644 --- a/tests/go.mod +++ b/tests/go.mod @@ -3,14 +3,14 @@ module gorm.io/gorm/tests go 1.18 require ( - github.com/google/uuid v1.3.1 + github.com/google/uuid v1.4.0 github.com/jinzhu/now v1.1.5 github.com/lib/pq v1.10.9 - gorm.io/driver/mysql v1.5.2-0.20230612053416-48b6526a21f0 - gorm.io/driver/postgres v1.5.3-0.20230607070428-18bc84b75196 + gorm.io/driver/mysql v1.5.2 + gorm.io/driver/postgres v1.5.4 gorm.io/driver/sqlite v1.5.4 - gorm.io/driver/sqlserver v1.5.2-0.20230613072041-6e2cde390b0a - gorm.io/gorm v1.25.4 + gorm.io/driver/sqlserver v1.5.2 + gorm.io/gorm v1.25.5 ) require ( @@ -19,12 +19,14 @@ require ( github.com/golang-sql/sqlexp v0.1.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect - github.com/jackc/pgx/v5 v5.4.3 // indirect + github.com/jackc/pgx/v5 v5.5.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect - github.com/mattn/go-sqlite3 v1.14.17 // indirect + github.com/mattn/go-sqlite3 v1.14.18 // indirect github.com/microsoft/go-mssqldb v1.6.0 // indirect - golang.org/x/crypto v0.14.0 // indirect - golang.org/x/text v0.13.0 // indirect + golang.org/x/crypto v0.15.0 // indirect + golang.org/x/text v0.14.0 // indirect ) replace gorm.io/gorm => ../ + +replace github.com/jackc/pgx/v5 => github.com/jackc/pgx/v5 v5.4.3 From 3207ad6033aad5e76c6c9d578ef663032765e484 Mon Sep 17 00:00:00 2001 From: FangSqing <148066072+FangSqing@users.noreply.github.com> Date: Wed, 15 Nov 2023 21:32:56 +0800 Subject: [PATCH 7/7] map insert support return increment id (#6662) --- callbacks/create.go | 70 +++++++++++++---- schema/field.go | 4 +- tests/create_test.go | 180 ++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 237 insertions(+), 17 deletions(-) diff --git a/callbacks/create.go b/callbacks/create.go index f0b78139..b1488b08 100644 --- a/callbacks/create.go +++ b/callbacks/create.go @@ -103,13 +103,53 @@ func Create(config *Config) func(db *gorm.DB) { } db.RowsAffected, _ = result.RowsAffected() - if db.RowsAffected != 0 && db.Statement.Schema != nil && - db.Statement.Schema.PrioritizedPrimaryField != nil && - db.Statement.Schema.PrioritizedPrimaryField.HasDefaultValue { - insertID, err := result.LastInsertId() - insertOk := err == nil && insertID > 0 - if !insertOk { - db.AddError(err) + if db.RowsAffected == 0 { + return + } + + var ( + pkField *schema.Field + pkFieldName = "@id" + ) + if db.Statement.Schema != nil { + if db.Statement.Schema.PrioritizedPrimaryField == nil || !db.Statement.Schema.PrioritizedPrimaryField.HasDefaultValue { + return + } + pkField = db.Statement.Schema.PrioritizedPrimaryField + pkFieldName = db.Statement.Schema.PrioritizedPrimaryField.DBName + } + + insertID, err := result.LastInsertId() + insertOk := err == nil && insertID > 0 + if !insertOk { + db.AddError(err) + return + } + + // append @id column with value for auto-increment primary key + // the @id value is correct, when: 1. without setting auto-increment primary key, 2. database AutoIncrementIncrement = 1 + switch values := db.Statement.Dest.(type) { + case map[string]interface{}: + values[pkFieldName] = insertID + case *map[string]interface{}: + (*values)[pkFieldName] = insertID + case []map[string]interface{}, *[]map[string]interface{}: + mapValues, ok := values.([]map[string]interface{}) + if !ok { + if v, ok := values.(*[]map[string]interface{}); ok { + if *v != nil { + mapValues = *v + } + } + } + for _, mapValue := range mapValues { + if mapValue != nil { + mapValue[pkFieldName] = insertID + } + insertID += schema.DefaultAutoIncrementIncrement + } + default: + if pkField == nil { return } @@ -122,10 +162,10 @@ func Create(config *Config) func(db *gorm.DB) { break } - _, isZero := db.Statement.Schema.PrioritizedPrimaryField.ValueOf(db.Statement.Context, rv) + _, isZero := pkField.ValueOf(db.Statement.Context, rv) if isZero { - db.AddError(db.Statement.Schema.PrioritizedPrimaryField.Set(db.Statement.Context, rv, insertID)) - insertID -= db.Statement.Schema.PrioritizedPrimaryField.AutoIncrementIncrement + db.AddError(pkField.Set(db.Statement.Context, rv, insertID)) + insertID -= pkField.AutoIncrementIncrement } } } else { @@ -135,16 +175,16 @@ func Create(config *Config) func(db *gorm.DB) { break } - if _, isZero := db.Statement.Schema.PrioritizedPrimaryField.ValueOf(db.Statement.Context, rv); isZero { - db.AddError(db.Statement.Schema.PrioritizedPrimaryField.Set(db.Statement.Context, rv, insertID)) - insertID += db.Statement.Schema.PrioritizedPrimaryField.AutoIncrementIncrement + if _, isZero := pkField.ValueOf(db.Statement.Context, rv); isZero { + db.AddError(pkField.Set(db.Statement.Context, rv, insertID)) + insertID += pkField.AutoIncrementIncrement } } } case reflect.Struct: - _, isZero := db.Statement.Schema.PrioritizedPrimaryField.ValueOf(db.Statement.Context, db.Statement.ReflectValue) + _, isZero := pkField.ValueOf(db.Statement.Context, db.Statement.ReflectValue) if isZero { - db.AddError(db.Statement.Schema.PrioritizedPrimaryField.Set(db.Statement.Context, db.Statement.ReflectValue, insertID)) + db.AddError(pkField.Set(db.Statement.Context, db.Statement.ReflectValue, insertID)) } } } diff --git a/schema/field.go b/schema/field.go index dd08e056..657e0a4b 100644 --- a/schema/field.go +++ b/schema/field.go @@ -49,6 +49,8 @@ const ( Bytes DataType = "bytes" ) +const DefaultAutoIncrementIncrement int64 = 1 + // Field is the representation of model schema's field type Field struct { Name string @@ -119,7 +121,7 @@ func (schema *Schema) ParseField(fieldStruct reflect.StructField) *Field { NotNull: utils.CheckTruth(tagSetting["NOT NULL"], tagSetting["NOTNULL"]), Unique: utils.CheckTruth(tagSetting["UNIQUE"]), Comment: tagSetting["COMMENT"], - AutoIncrementIncrement: 1, + AutoIncrementIncrement: DefaultAutoIncrementIncrement, } for field.IndirectFieldType.Kind() == reflect.Ptr { diff --git a/tests/create_test.go b/tests/create_test.go index 02613b72..d9b54b7f 100644 --- a/tests/create_test.go +++ b/tests/create_test.go @@ -2,6 +2,7 @@ package tests_test import ( "errors" + "fmt" "regexp" "testing" "time" @@ -580,7 +581,7 @@ func TestCreateWithAutoIncrementCompositeKey(t *testing.T) { } } -func TestCreateOnConfilctWithDefalutNull(t *testing.T) { +func TestCreateOnConflictWithDefaultNull(t *testing.T) { type OnConfilctUser struct { ID string Name string `gorm:"default:null"` @@ -615,3 +616,180 @@ func TestCreateOnConfilctWithDefalutNull(t *testing.T) { AssertEqual(t, u2.Email, "on-confilct-user-email-2") AssertEqual(t, u2.Mobile, "133xxxx") } + +func TestCreateFromMapWithoutPK(t *testing.T) { + if !isMysql() { + t.Skipf("This test case skipped, because of only supportting for mysql") + } + + // case 1: one record, create from map[string]interface{} + mapValue1 := map[string]interface{}{"name": "create_from_map_with_schema1", "age": 1} + if err := DB.Model(&User{}).Create(mapValue1).Error; err != nil { + t.Fatalf("failed to create data from map, got error: %v", err) + } + + if _, ok := mapValue1["id"]; !ok { + t.Fatal("failed to create data from map with table, returning map has no primary key") + } + + var result1 User + if err := DB.Where("name = ?", "create_from_map_with_schema1").First(&result1).Error; err != nil || result1.Age != 1 { + t.Fatalf("failed to create from map, got error %v", err) + } + + var idVal int64 + _, ok := mapValue1["id"].(uint) + if ok { + t.Skipf("This test case skipped, because the db supports returning") + } + + idVal, ok = mapValue1["id"].(int64) + if !ok { + t.Fatal("ret result missing id") + } + + if int64(result1.ID) != idVal { + t.Fatal("failed to create data from map with table, @id != id") + } + + // case2: one record, create from *map[string]interface{} + mapValue2 := map[string]interface{}{"name": "create_from_map_with_schema2", "age": 1} + if err := DB.Model(&User{}).Create(&mapValue2).Error; err != nil { + t.Fatalf("failed to create data from map, got error: %v", err) + } + + if _, ok := mapValue2["id"]; !ok { + t.Fatal("failed to create data from map with table, returning map has no primary key") + } + + var result2 User + if err := DB.Where("name = ?", "create_from_map_with_schema2").First(&result2).Error; err != nil || result2.Age != 1 { + t.Fatalf("failed to create from map, got error %v", err) + } + + _, ok = mapValue2["id"].(uint) + if ok { + t.Skipf("This test case skipped, because the db supports returning") + } + + idVal, ok = mapValue2["id"].(int64) + if !ok { + t.Fatal("ret result missing id") + } + + if int64(result2.ID) != idVal { + t.Fatal("failed to create data from map with table, @id != id") + } + + // case 3: records + values := []map[string]interface{}{ + {"name": "create_from_map_with_schema11", "age": 1}, {"name": "create_from_map_with_schema12", "age": 1}, + } + + beforeLen := len(values) + if err := DB.Model(&User{}).Create(&values).Error; err != nil { + t.Fatalf("failed to create data from map, got error: %v", err) + } + + // mariadb with returning, values will be appended with id map + if len(values) == beforeLen*2 { + t.Skipf("This test case skipped, because the db supports returning") + } + + for i := range values { + v, ok := values[i]["id"] + if !ok { + t.Fatal("failed to create data from map with table, returning map has no primary key") + } + + var result User + if err := DB.Where("name = ?", fmt.Sprintf("create_from_map_with_schema1%d", i+1)).First(&result).Error; err != nil || result.Age != 1 { + t.Fatalf("failed to create from map, got error %v", err) + } + if int64(result.ID) != v.(int64) { + t.Fatal("failed to create data from map with table, @id != id") + } + } +} + +func TestCreateFromMapWithTable(t *testing.T) { + if !isMysql() { + t.Skipf("This test case skipped, because of only supportting for mysql") + } + tableDB := DB.Table("`users`") + + // case 1: create from map[string]interface{} + record := map[string]interface{}{"`name`": "create_from_map_with_table", "`age`": 18} + if err := tableDB.Create(record).Error; err != nil { + t.Fatalf("failed to create data from map with table, got error: %v", err) + } + + if _, ok := record["@id"]; !ok { + t.Fatal("failed to create data from map with table, returning map has no key '@id'") + } + + var res map[string]interface{} + if err := tableDB.Select([]string{"id", "name", "age"}).Where("name = ?", "create_from_map_with_table").Find(&res).Error; err != nil || res["age"] != int64(18) { + t.Fatalf("failed to create from map, got error %v", err) + } + + if int64(res["id"].(uint64)) != record["@id"] { + t.Fatal("failed to create data from map with table, @id != id") + } + + // case 2: create from *map[string]interface{} + record1 := map[string]interface{}{"name": "create_from_map_with_table_1", "age": 18} + tableDB2 := DB.Table("users") + if err := tableDB2.Create(&record1).Error; err != nil { + t.Fatalf("failed to create data from map, got error: %v", err) + } + if _, ok := record1["@id"]; !ok { + t.Fatal("failed to create data from map with table, returning map has no key '@id'") + } + + var res1 map[string]interface{} + if err := tableDB2.Select([]string{"id", "name", "age"}).Where("name = ?", "create_from_map_with_table_1").Find(&res1).Error; err != nil || res1["age"] != int64(18) { + t.Fatalf("failed to create from map, got error %v", err) + } + + if int64(res1["id"].(uint64)) != record1["@id"] { + t.Fatal("failed to create data from map with table, @id != id") + } + + // case 3: create from []map[string]interface{} + records := []map[string]interface{}{ + {"name": "create_from_map_with_table_2", "age": 19}, + {"name": "create_from_map_with_table_3", "age": 20}, + } + + tableDB = DB.Table("users") + if err := tableDB.Create(&records).Error; err != nil { + t.Fatalf("failed to create data from slice of map, got error: %v", err) + } + + if _, ok := records[0]["@id"]; !ok { + t.Fatal("failed to create data from map with table, returning map has no key '@id'") + } + + if _, ok := records[1]["@id"]; !ok { + t.Fatal("failed to create data from map with table, returning map has no key '@id'") + } + + var res2 map[string]interface{} + if err := tableDB.Select([]string{"id", "name", "age"}).Where("name = ?", "create_from_map_with_table_2").Find(&res2).Error; err != nil || res2["age"] != int64(19) { + t.Fatalf("failed to query data after create from slice of map, got error %v", err) + } + + var res3 map[string]interface{} + if err := DB.Table("users").Select([]string{"id", "name", "age"}).Where("name = ?", "create_from_map_with_table_3").Find(&res3).Error; err != nil || res3["age"] != int64(20) { + t.Fatalf("failed to query data after create from slice of map, got error %v", err) + } + + if int64(res2["id"].(uint64)) != records[0]["@id"] { + t.Fatal("failed to create data from map with table, @id != id") + } + + if int64(res3["id"].(uint64)) != records[1]["@id"] { + t.Fatal("failed to create data from map with table, @id != id") + } +}