Skip to content

Commit

Permalink
Fixes #13: fields of time.Time are not populated correctly
Browse files Browse the repository at this point in the history
  • Loading branch information
qiangxue committed Jul 28, 2016
1 parent bc7979c commit 32028d5
Show file tree
Hide file tree
Showing 5 changed files with 49 additions and 6 deletions.
16 changes: 13 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand All @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion db_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import (
)

const (
TestDSN = "travis:@/ozzo_dbx_test"
TestDSN = "travis:@/ozzo_dbx_test?parseTime=true"
FixtureFile = "testdata/mysql.sql"
)

Expand Down
17 changes: 16 additions & 1 deletion query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
ss "database/sql"
"encoding/json"
"testing"
"time"

"github.com/stretchr/testify/assert"
)
Expand Down Expand Up @@ -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)
}
9 changes: 8 additions & 1 deletion struct.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 != "" {
Expand All @@ -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
Expand Down
11 changes: 11 additions & 0 deletions testdata/mysql.sql
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -51,6 +59,9 @@ INSERT INTO `customer` (email, name, address, status) VALUES ('[email protected]
INSERT INTO `customer` (email, name, address, status) VALUES ('[email protected]', 'user2', NULL, 1);
INSERT INTO `customer` (email, name, address, status) VALUES ('[email protected]', 'user3', 'address3', 2);

INSERT INTO `user` (email, created) VALUES ('[email protected]', '2015-01-02');
INSERT INTO `user` (email, created) VALUES ('[email protected]', 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');
Expand Down

0 comments on commit 32028d5

Please sign in to comment.