diff --git a/database/gdb/gdb_model_select.go b/database/gdb/gdb_model_select.go index e71bd2321..9d1dab718 100644 --- a/database/gdb/gdb_model_select.go +++ b/database/gdb/gdb_model_select.go @@ -8,6 +8,7 @@ package gdb import ( "fmt" + "github.com/gogf/gf/errors/gerror" "reflect" "github.com/gogf/gf/container/gset" @@ -195,6 +196,15 @@ func (m *Model) Array(fieldsAndWhere ...interface{}) ([]Value, error) { return all.Array(), nil } +// Struct retrieves one record from table and converts it into given struct. +// The parameter `pointer` should be type of *struct/**struct. If type **struct is given, +// it can create the struct internally during converting. +// +// Deprecated, use Scan instead. +func (m *Model) Struct(pointer interface{}, where ...interface{}) error { + return m.doStruct(pointer, where...) +} + // Struct retrieves one record from table and converts it into given struct. // The parameter `pointer` should be type of *struct/**struct. If type **struct is given, // it can create the struct internally during converting. @@ -202,24 +212,38 @@ func (m *Model) Array(fieldsAndWhere ...interface{}) ([]Value, error) { // The optional parameter `where` is the same as the parameter of Model.Where function, // see Model.Where. // -// Note that it returns sql.ErrNoRows if there's no record retrieved with the given conditions -// from table and `pointer` is not nil. +// Note that it returns sql.ErrNoRows if the given parameter `pointer` pointed to a variable that has +// default value and there's no record retrieved with the given conditions from table. // -// Eg: +// Example: // user := new(User) -// err := db.Model("user").Where("id", 1).Struct(user) +// err := db.Model("user").Where("id", 1).Scan(user) // // user := (*User)(nil) -// err := db.Model("user").Where("id", 1).Struct(&user) -func (m *Model) Struct(pointer interface{}, where ...interface{}) error { - one, err := m.One(where...) +// err := db.Model("user").Where("id", 1).Scan(&user) +func (m *Model) doStruct(pointer interface{}, where ...interface{}) error { + model := m + // Auto selecting fields by struct attributes. + if model.fieldsEx == "" && (model.fields == "" || model.fields == "*") { + model = m.Fields(pointer) + } + one, err := model.One(where...) if err != nil { return err } if err = one.Struct(pointer); err != nil { return err } - return m.doWithScanStruct(pointer) + return model.doWithScanStruct(pointer) +} + +// Structs retrieves records from table and converts them into given struct slice. +// The parameter `pointer` should be type of *[]struct/*[]*struct. It can create and fill the struct +// slice internally during converting. +// +// Deprecated, use Scan instead. +func (m *Model) Structs(pointer interface{}, where ...interface{}) error { + return m.doStructs(pointer, where...) } // Structs retrieves records from table and converts them into given struct slice. @@ -229,37 +253,45 @@ func (m *Model) Struct(pointer interface{}, where ...interface{}) error { // The optional parameter `where` is the same as the parameter of Model.Where function, // see Model.Where. // -// Note that it returns sql.ErrNoRows if there's no record retrieved with the given conditions -// from table and `pointer` is not empty. +// Note that it returns sql.ErrNoRows if the given parameter `pointer` pointed to a variable that has +// default value and there's no record retrieved with the given conditions from table. // -// Eg: +// Example: // users := ([]User)(nil) -// err := db.Model("user").Structs(&users) +// err := db.Model("user").Scan(&users) // // users := ([]*User)(nil) -// err := db.Model("user").Structs(&users) -func (m *Model) Structs(pointer interface{}, where ...interface{}) error { - all, err := m.All(where...) +// err := db.Model("user").Scan(&users) +func (m *Model) doStructs(pointer interface{}, where ...interface{}) error { + model := m + // Auto selecting fields by struct attributes. + if model.fieldsEx == "" && (model.fields == "" || model.fields == "*") { + model = m.Fields( + reflect.New( + reflect.ValueOf(pointer).Elem().Type().Elem(), + ).Interface(), + ) + } + all, err := model.All(where...) if err != nil { return err } if err = all.Structs(pointer); err != nil { return err } - return m.doWithScanStructs(pointer) + return model.doWithScanStructs(pointer) } // Scan automatically calls Struct or Structs function according to the type of parameter `pointer`. -// It calls function Struct if `pointer` is type of *struct/**struct. -// It calls function Structs if `pointer` is type of *[]struct/*[]*struct. +// It calls function doStruct if `pointer` is type of *struct/**struct. +// It calls function doStructs if `pointer` is type of *[]struct/*[]*struct. // -// The optional parameter `where` is the same as the parameter of Model.Where function, -// see Model.Where. +// The optional parameter `where` is the same as the parameter of Model.Where function, see Model.Where. // -// Note that it returns sql.ErrNoRows if there's no record retrieved with the given conditions -// from table. +// Note that it returns sql.ErrNoRows if the given parameter `pointer` pointed to a variable that has +// default value and there's no record retrieved with the given conditions from table. // -// Eg: +// Example: // user := new(User) // err := db.Model("user").Where("id", 1).Scan(user) // @@ -272,16 +304,35 @@ func (m *Model) Structs(pointer interface{}, where ...interface{}) error { // users := ([]*User)(nil) // err := db.Model("user").Scan(&users) func (m *Model) Scan(pointer interface{}, where ...interface{}) error { - var reflectType reflect.Type + var ( + reflectValue reflect.Value + reflectKind reflect.Kind + ) if v, ok := pointer.(reflect.Value); ok { - reflectType = v.Type() + reflectValue = v } else { - reflectType = reflect.TypeOf(pointer) + reflectValue = reflect.ValueOf(pointer) } - if gstr.Contains(reflectType.String(), "[]") { - return m.Structs(pointer, where...) + + reflectKind = reflectValue.Kind() + if reflectKind != reflect.Ptr { + return gerror.New(`the parameter "pointer" for function Scan should type of pointer`) + } + for reflectKind == reflect.Ptr { + reflectValue = reflectValue.Elem() + reflectKind = reflectValue.Kind() + } + + switch reflectKind { + case reflect.Slice, reflect.Array: + return m.doStructs(pointer, where...) + + case reflect.Struct, reflect.Invalid: + return m.doStruct(pointer, where...) + + default: + return gerror.New(`element of parameter "pointer" for function Scan should type of struct/*struct/[]struct/[]*struct`) } - return m.Struct(pointer, where...) } // ScanList converts `r` to struct slice which contains other complex struct attributes. diff --git a/database/gdb/gdb_type_result.go b/database/gdb/gdb_type_result.go index a5800ece9..05e15026c 100644 --- a/database/gdb/gdb_type_result.go +++ b/database/gdb/gdb_type_result.go @@ -153,7 +153,7 @@ func (r Result) MapKeyUint(key string) map[uint]Map { return m } -// RecordKeyInt converts `r` to a map[int]Record of which key is specified by `key`. +// RecordKeyStr converts `r` to a map[string]Record of which key is specified by `key`. func (r Result) RecordKeyStr(key string) map[string]Record { m := make(map[string]Record) for _, item := range r { diff --git a/database/gdb/gdb_z_mysql_model_test.go b/database/gdb/gdb_z_mysql_model_test.go index 90e89a00a..5f96a5c46 100644 --- a/database/gdb/gdb_z_mysql_model_test.go +++ b/database/gdb/gdb_z_mysql_model_test.go @@ -899,7 +899,7 @@ func Test_Model_Struct(t *testing.T) { CreateTime gtime.Time } user := new(User) - err := db.Model(table).Where("id=1").Struct(user) + err := db.Model(table).Where("id=1").Scan(user) t.AssertNil(err) t.Assert(user.NickName, "name_1") t.Assert(user.CreateTime.String(), "2018-10-24 10:00:00") @@ -913,7 +913,7 @@ func Test_Model_Struct(t *testing.T) { CreateTime *gtime.Time } user := new(User) - err := db.Model(table).Where("id=1").Struct(user) + err := db.Model(table).Where("id=1").Scan(user) t.AssertNil(err) t.Assert(user.NickName, "name_1") t.Assert(user.CreateTime.String(), "2018-10-24 10:00:00") @@ -928,7 +928,7 @@ func Test_Model_Struct(t *testing.T) { CreateTime *gtime.Time } user := (*User)(nil) - err := db.Model(table).Where("id=1").Struct(&user) + err := db.Model(table).Where("id=1").Scan(&user) t.AssertNil(err) t.Assert(user.NickName, "name_1") t.Assert(user.CreateTime.String(), "2018-10-24 10:00:00") @@ -960,7 +960,7 @@ func Test_Model_Struct(t *testing.T) { CreateTime *gtime.Time } user := new(User) - err := db.Model(table).Where("id=-1").Struct(user) + err := db.Model(table).Where("id=-1").Scan(user) t.Assert(err, sql.ErrNoRows) }) gtest.C(t, func(t *gtest.T) { @@ -972,7 +972,7 @@ func Test_Model_Struct(t *testing.T) { CreateTime *gtime.Time } var user *User - err := db.Model(table).Where("id=-1").Struct(&user) + err := db.Model(table).Where("id=-1").Scan(&user) t.AssertNil(err) }) } @@ -992,7 +992,7 @@ func Test_Model_Struct_CustomType(t *testing.T) { CreateTime gtime.Time } user := new(User) - err := db.Model(table).Where("id=1").Struct(user) + err := db.Model(table).Where("id=1").Scan(user) t.AssertNil(err) t.Assert(user.NickName, "name_1") t.Assert(user.CreateTime.String(), "2018-10-24 10:00:00") @@ -1012,7 +1012,7 @@ func Test_Model_Structs(t *testing.T) { CreateTime gtime.Time } var users []User - err := db.Model(table).Order("id asc").Structs(&users) + err := db.Model(table).Order("id asc").Scan(&users) if err != nil { gtest.Error(err) } @@ -1035,7 +1035,7 @@ func Test_Model_Structs(t *testing.T) { CreateTime *gtime.Time } var users []*User - err := db.Model(table).Order("id asc").Structs(&users) + err := db.Model(table).Order("id asc").Scan(&users) if err != nil { gtest.Error(err) } @@ -1081,38 +1081,40 @@ func Test_Model_Structs(t *testing.T) { CreateTime *gtime.Time } var users []*User - err := db.Model(table).Where("id<0").Structs(&users) + err := db.Model(table).Where("id<0").Scan(&users) t.AssertNil(err) }) } -func Test_Model_StructsWithJsonTag(t *testing.T) { - table := createInitTable() - defer dropTable(table) - - gtest.C(t, func(t *gtest.T) { - type User struct { - Uid int `json:"id"` - Passport string - Password string - Name string `json:"nick_name"` - Time gtime.Time `json:"create_time"` - } - var users []User - err := db.Model(table).Order("id asc").Structs(&users) - if err != nil { - gtest.Error(err) - } - t.Assert(len(users), TableSize) - t.Assert(users[0].Uid, 1) - t.Assert(users[1].Uid, 2) - t.Assert(users[2].Uid, 3) - t.Assert(users[0].Name, "name_1") - t.Assert(users[1].Name, "name_2") - t.Assert(users[2].Name, "name_3") - t.Assert(users[0].Time.String(), "2018-10-24 10:00:00") - }) -} +// JSON tag is only used for JSON Marshal/Unmarshal, DO NOT use it in multiple purposes! +//func Test_Model_StructsWithJsonTag(t *testing.T) { +// table := createInitTable() +// defer dropTable(table) +// +// db.SetDebug(true) +// gtest.C(t, func(t *gtest.T) { +// type User struct { +// Uid int `json:"id"` +// Passport string +// Password string +// Name string `json:"nick_name"` +// Time gtime.Time `json:"create_time"` +// } +// var users []User +// err := db.Model(table).Order("id asc").Scan(&users) +// if err != nil { +// gtest.Error(err) +// } +// t.Assert(len(users), TableSize) +// t.Assert(users[0].Uid, 1) +// t.Assert(users[1].Uid, 2) +// t.Assert(users[2].Uid, 3) +// t.Assert(users[0].Name, "name_1") +// t.Assert(users[1].Name, "name_2") +// t.Assert(users[2].Name, "name_3") +// t.Assert(users[0].Time.String(), "2018-10-24 10:00:00") +// }) +//} func Test_Model_Scan(t *testing.T) { table := createInitTable() @@ -3098,7 +3100,7 @@ func Test_TimeZoneInsert(t *testing.T) { gtest.C(t, func(t *gtest.T) { _, _ = db.Model(tableName).Unscoped().Insert(u) userEntity := &User{} - err := db.Model(tableName).Where("id", 1).Unscoped().Struct(&userEntity) + err := db.Model(tableName).Where("id", 1).Unscoped().Scan(&userEntity) t.AssertNil(err) t.Assert(userEntity.CreatedAt.String(), "2020-11-22 04:23:45") t.Assert(userEntity.UpdatedAt.String(), "2020-11-22 05:23:45") @@ -3129,7 +3131,7 @@ func Test_Model_Fields_Map_Struct(t *testing.T) { XXX_TYPE int } var a = A{} - err := db.Model(table).Fields(a).Where("id", 1).Struct(&a) + err := db.Model(table).Fields(a).Where("id", 1).Scan(&a) t.AssertNil(err) t.Assert(a.ID, 1) t.Assert(a.PASSPORT, "user_1") @@ -3143,7 +3145,7 @@ func Test_Model_Fields_Map_Struct(t *testing.T) { XXX_TYPE int } var a *A - err := db.Model(table).Fields(a).Where("id", 1).Struct(&a) + err := db.Model(table).Fields(a).Where("id", 1).Scan(&a) t.AssertNil(err) t.Assert(a.ID, 1) t.Assert(a.PASSPORT, "user_1") @@ -3157,7 +3159,7 @@ func Test_Model_Fields_Map_Struct(t *testing.T) { XXX_TYPE int } var a *A - err := db.Model(table).Fields(&a).Where("id", 1).Struct(&a) + err := db.Model(table).Fields(&a).Where("id", 1).Scan(&a) t.AssertNil(err) t.Assert(a.ID, 1) t.Assert(a.PASSPORT, "user_1") diff --git a/database/gdb/gdb_z_mysql_struct_test.go b/database/gdb/gdb_z_mysql_struct_test.go index dbcf48d97..eb895be5a 100644 --- a/database/gdb/gdb_z_mysql_struct_test.go +++ b/database/gdb/gdb_z_mysql_struct_test.go @@ -121,7 +121,7 @@ func Test_Struct_Pointer_Attribute(t *testing.T) { }) gtest.C(t, func(t *gtest.T) { user := new(User) - err := db.Model(table).Struct(user, "id=1") + err := db.Model(table).Scan(user, "id=1") t.AssertNil(err) t.Assert(*user.Id, 1) t.Assert(*user.Passport, "user_1") @@ -130,7 +130,7 @@ func Test_Struct_Pointer_Attribute(t *testing.T) { }) gtest.C(t, func(t *gtest.T) { var user *User - err := db.Model(table).Struct(&user, "id=1") + err := db.Model(table).Scan(&user, "id=1") t.AssertNil(err) t.Assert(*user.Id, 1) t.Assert(*user.Passport, "user_1") @@ -201,7 +201,7 @@ func Test_Structs_Pointer_Attribute(t *testing.T) { // Structs gtest.C(t, func(t *gtest.T) { users := make([]User, 0) - err := db.Model(table).Structs(&users, "id < 3") + err := db.Model(table).Scan(&users, "id < 3") t.AssertNil(err) t.Assert(len(users), 2) t.Assert(*users[0].Id, 1) @@ -211,7 +211,7 @@ func Test_Structs_Pointer_Attribute(t *testing.T) { }) gtest.C(t, func(t *gtest.T) { users := make([]*User, 0) - err := db.Model(table).Structs(&users, "id < 3") + err := db.Model(table).Scan(&users, "id < 3") t.AssertNil(err) t.Assert(len(users), 2) t.Assert(*users[0].Id, 1) @@ -221,7 +221,7 @@ func Test_Structs_Pointer_Attribute(t *testing.T) { }) gtest.C(t, func(t *gtest.T) { var users []User - err := db.Model(table).Structs(&users, "id < 3") + err := db.Model(table).Scan(&users, "id < 3") t.AssertNil(err) t.Assert(len(users), 2) t.Assert(*users[0].Id, 1) @@ -231,7 +231,7 @@ func Test_Structs_Pointer_Attribute(t *testing.T) { }) gtest.C(t, func(t *gtest.T) { var users []*User - err := db.Model(table).Structs(&users, "id < 3") + err := db.Model(table).Scan(&users, "id < 3") t.AssertNil(err) t.Assert(len(users), 2) t.Assert(*users[0].Id, 1) @@ -254,7 +254,7 @@ func Test_Struct_Empty(t *testing.T) { gtest.C(t, func(t *gtest.T) { user := new(User) - err := db.Model(table).Where("id=100").Struct(user) + err := db.Model(table).Where("id=100").Scan(user) t.Assert(err, sql.ErrNoRows) t.AssertNE(user, nil) }) @@ -269,7 +269,7 @@ func Test_Struct_Empty(t *testing.T) { gtest.C(t, func(t *gtest.T) { var user *User - err := db.Model(table).Where("id=100").Struct(&user) + err := db.Model(table).Where("id=100").Scan(&user) t.AssertNil(err) t.Assert(user, nil) }) @@ -452,3 +452,27 @@ func Test_Model_Scan_Map(t *testing.T) { t.Assert(users[9].CreateTime.String(), CreateTime) }) } + +func Test_Scan_AutoFilteringByStructAttributes(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + type User struct { + Id int + Passport string + } + //db.SetDebug(true) + gtest.C(t, func(t *gtest.T) { + var user *User + err := db.Model(table).OrderAsc("id").Scan(&user) + t.AssertNil(err) + t.Assert(user.Id, 1) + }) + gtest.C(t, func(t *gtest.T) { + var users []User + err := db.Model(table).OrderAsc("id").Scan(&users) + t.AssertNil(err) + t.Assert(len(users), TableSize) + t.Assert(users[0].Id, 1) + }) +} diff --git a/database/gdb/gdb_z_mysql_types_test.go b/database/gdb/gdb_z_mysql_types_test.go index e26d763b9..7c02df03e 100644 --- a/database/gdb/gdb_z_mysql_types_test.go +++ b/database/gdb/gdb_z_mysql_types_test.go @@ -87,7 +87,7 @@ func Test_Types(t *testing.T) { TinyInt bool } var obj *T - err = db.Model("types").Struct(&obj) + err = db.Model("types").Scan(&obj) t.AssertNil(err) t.Assert(obj.Id, 1) t.Assert(obj.Blob, data["blob"])