diff --git a/README.md b/README.md index 9201d7e..ebb8ca0 100644 --- a/README.md +++ b/README.md @@ -232,6 +232,10 @@ When populating a struct, the following rules are used to determine which column according to the rules described above. * For named fields that are of struct type, they will also be expanded. But their component fields will be prefixed with the struct names when being populated. + +An exception to the above struct expansion is that when a struct type implements `sql.Scanner` or when it is `time.Time`. +In this case, the field will be populated as a whole by the DB driver. Also, if a field is a pointer to some type, +the field will be allocated memory and populated with the query result if it is not null. The following example shows how fields are populated according to the rules above: @@ -240,19 +244,25 @@ type User struct { id int Type int `db:"-"` MyName string `db:"name"` - Prof Profile + Profile + Address Address `db:"addr"` } type Profile struct { Age int } + +type Address struct { + City string +} ``` * `User.id`: not populated because the field is not exported; * `User.Type`: not populated because the `db` tag is `-`; * `User.MyName`: to be populated from the `name` column, according to the `db` tag; -* `Profile.Age`: to be populated from the `prof.age` column, since `Prof` is a named field of struct type - and its fields will be prefixed with `prof.`. +* `Profile.Age`: to be populated from the `age` column, since `Profile` is an anonymous field; +* `Address.City`: to be populated from the `addr.city` column, since `Address` is a named field of struct type + and its fields will be prefixed with `addr.` according to the `db` tag. Note that if a column in the result does not have a corresponding struct field, it will be ignored. Similarly, if a struct field does not have a corresponding column in the result, it will not be populated. diff --git a/db_test.go b/db_test.go index 6b8673c..6d7d5f8 100644 --- a/db_test.go +++ b/db_test.go @@ -15,7 +15,7 @@ import ( ) const ( - TestDSN = "travis:@/ozzo_dbx_test" + TestDSN = "travis:@/ozzo_dbx_test?parseTime=true" FixtureFile = "testdata/mysql.sql" ) diff --git a/query_test.go b/query_test.go index f6f8c2b..c4da1f9 100644 --- a/query_test.go +++ b/query_test.go @@ -8,6 +8,7 @@ import ( ss "database/sql" "encoding/json" "testing" + "time" "github.com/stretchr/testify/assert" ) @@ -307,9 +308,23 @@ func TestReplacePlaceholders(t *testing.T) { } func TestIssue6(t *testing.T) { - db := getDB() + db := getPreparedDB() q := db.Select("*").From("customer").Where(HashExp{"id": 1}) var customer Customer assert.Equal(t, q.One(&customer), nil) assert.Equal(t, 1, customer.ID) } + +type User struct { + ID int64 + Email string + Created time.Time +} + +func TestIssue13(t *testing.T) { + db := getPreparedDB() + var user User + err := db.Select().From("user").Where(HashExp{"id": 1}).One(&user) + assert.Nil(t, err) + assert.NotZero(t, user.Created) +} diff --git a/struct.go b/struct.go index 569a69b..807b2fd 100644 --- a/struct.go +++ b/struct.go @@ -176,7 +176,7 @@ func (si *structInfo) build(a reflect.Type, path []int, namePrefix, dbNamePrefix name = "" } - if ft.Kind() == reflect.Struct && !reflect.PtrTo(ft).Implements(scannerType) { + if isNestedStruct(ft) { // dive into non-scanner struct si.build(ft, path2, concat(namePrefix, name), concat(dbNamePrefix, dbName), mapper) } else if dbName != "" { @@ -202,6 +202,13 @@ func (si *structInfo) build(a reflect.Type, path []int, namePrefix, dbNamePrefix } } +func isNestedStruct(t reflect.Type) bool { + if t.PkgPath() == "time" && t.Name() == "Time" { + return false + } + return t.Kind() == reflect.Struct && !reflect.PtrTo(t).Implements(scannerType) +} + func parseTag(tag string) (string, bool) { if tag == "pk" { return "", true diff --git a/testdata/mysql.sql b/testdata/mysql.sql index 8c8d165..52bedc8 100644 --- a/testdata/mysql.sql +++ b/testdata/mysql.sql @@ -11,6 +11,7 @@ DROP TABLE IF EXISTS `order_item` CASCADE; DROP TABLE IF EXISTS `item` CASCADE; DROP TABLE IF EXISTS `order` CASCADE; DROP TABLE IF EXISTS `customer` CASCADE; +DROP TABLE IF EXISTS `user` CASCADE; CREATE TABLE `customer` ( `id` int(11) NOT NULL AUTO_INCREMENT, @@ -21,6 +22,13 @@ CREATE TABLE `customer` ( PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +CREATE TABLE `user` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `email` varchar(128) NOT NULL, + `created` date, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + CREATE TABLE `item` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(128) NOT NULL, @@ -51,6 +59,9 @@ INSERT INTO `customer` (email, name, address, status) VALUES ('user1@example.com INSERT INTO `customer` (email, name, address, status) VALUES ('user2@example.com', 'user2', NULL, 1); INSERT INTO `customer` (email, name, address, status) VALUES ('user3@example.com', 'user3', 'address3', 2); +INSERT INTO `user` (email, created) VALUES ('user1@example.com', '2015-01-02'); +INSERT INTO `user` (email, created) VALUES ('user2@example.com', now()); + INSERT INTO `item` (name) VALUES ('The Go Programming Language'); INSERT INTO `item` (name) VALUES ('Go in Action'); INSERT INTO `item` (name) VALUES ('Go Programming Blueprints');