Skip to content

Commit

Permalink
Merge pull request jmoiron#145 from pkieltyka/ptrs
Browse files Browse the repository at this point in the history
Update ptrs branch
  • Loading branch information
jmoiron committed Jun 9, 2015
2 parents 4fdcba9 + 6dda2d0 commit 8a5e7cc
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 34 deletions.
59 changes: 29 additions & 30 deletions reflectx/reflect.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
"sync"
)

type field struct {
type FieldInfo struct {
Index []int
Path string
Field reflect.StructField
Expand All @@ -24,21 +24,21 @@ type field struct {
Embedded bool
}

type fields struct {
Index []*field
Paths map[string]*field
Names map[string]*field
type StructMap struct {
Index []*FieldInfo
Paths map[string]*FieldInfo
Names map[string]*FieldInfo
}

func (f fields) GetByPath(path string) *field {
func (f StructMap) GetByPath(path string) *FieldInfo {
if fi, ok := f.Paths[path]; ok {
return fi
} else {
return nil
}
}

func (f fields) GetByTraversal(index []int) *field {
func (f StructMap) GetByTraversal(index []int) *FieldInfo {
n := len(index)
for _, fi := range f.Index {
if len(fi.Index) != n {
Expand All @@ -62,7 +62,7 @@ func (f fields) GetByTraversal(index []int) *field {
// behaves like most marshallers, optionally obeying a field tag for name
// mapping and a function to provide a basic mapping of fields to names.
type Mapper struct {
cache map[reflect.Type]*fields
cache map[reflect.Type]*StructMap
tagName string
tagMapFunc func(string) string
mapFunc func(string) string
Expand All @@ -73,7 +73,7 @@ type Mapper struct {
// by tagName. If tagName is the empty string, it is ignored.
func NewMapper(tagName string) *Mapper {
return &Mapper{
cache: make(map[reflect.Type]*fields),
cache: make(map[reflect.Type]*StructMap),
tagName: tagName,
}
}
Expand All @@ -83,7 +83,7 @@ func NewMapper(tagName string) *Mapper {
// have values like "name,omitempty".
func NewMapperTagFunc(tagName string, mapFunc, tagMapFunc func(string) string) *Mapper {
return &Mapper{
cache: make(map[reflect.Type]*fields),
cache: make(map[reflect.Type]*StructMap),
tagName: tagName,
mapFunc: mapFunc,
tagMapFunc: tagMapFunc,
Expand All @@ -95,15 +95,15 @@ func NewMapperTagFunc(tagName string, mapFunc, tagMapFunc func(string) string) *
// for any other field, the mapped name will be f(field.Name)
func NewMapperFunc(tagName string, f func(string) string) *Mapper {
return &Mapper{
cache: make(map[reflect.Type]*fields),
cache: make(map[reflect.Type]*StructMap),
tagName: tagName,
mapFunc: f,
}
}

// TypeMap returns a mapping of field strings to int slices representing
// the traversal down the struct to reach the field.
func (m *Mapper) TypeMap(t reflect.Type) *fields {
func (m *Mapper) TypeMap(t reflect.Type) *StructMap {
m.mutex.Lock()
mapping, ok := m.cache[t]
if !ok {
Expand All @@ -116,17 +116,17 @@ func (m *Mapper) TypeMap(t reflect.Type) *fields {

// FieldMap returns the mapper's mapping of field names to reflect values. Panics
// if v's Kind is not Struct, or v is not Indirectable to a struct kind.
// func (m *Mapper) FieldMap(v reflect.Value) map[string]reflect.Value {
// v = reflect.Indirect(v)
// mustBe(v, reflect.Struct)

// r := map[string]reflect.Value{}
// nm := m.TypeMap(v.Type())
// for tagName, fi := range nm {
// r[tagName] = FieldByIndexes(v, fi.Index)
// }
// return r
// }
func (m *Mapper) FieldMap(v reflect.Value) map[string]reflect.Value {
v = reflect.Indirect(v)
mustBe(v, reflect.Struct)

r := map[string]reflect.Value{}
tm := m.TypeMap(v.Type())
for tagName, fi := range tm.Names {
r[tagName] = FieldByIndexes(v, fi.Index)
}
return r
}

// FieldByName returns a field by the its mapped name as a reflect.Value.
// Panics if v's Kind is not Struct or v is not Indirectable to a struct Kind.
Expand Down Expand Up @@ -244,7 +244,7 @@ func methodName() string {

type typeQueue struct {
t reflect.Type
fi *field
fi *FieldInfo
pp string // Parent path
}

Expand All @@ -260,11 +260,11 @@ func apnd(is []int, i int) []int {

// getMapping returns a mapping for the t type, using the tagName, mapFunc and
// tagMapFunc to determine the canonical names of fields.
func getMapping(t reflect.Type, tagName string, mapFunc, tagMapFunc func(string) string) *fields {
m := []*field{}
func getMapping(t reflect.Type, tagName string, mapFunc, tagMapFunc func(string) string) *StructMap {
m := []*FieldInfo{}

queue := []typeQueue{}
queue = append(queue, typeQueue{Deref(t), &field{}, ""})
queue = append(queue, typeQueue{Deref(t), &FieldInfo{}, ""})

for len(queue) != 0 {
// pop the first item off of the queue
Expand All @@ -275,7 +275,7 @@ func getMapping(t reflect.Type, tagName string, mapFunc, tagMapFunc func(string)
for fieldPos := 0; fieldPos < tq.t.NumField(); fieldPos++ {
f := tq.t.Field(fieldPos)

fi := &field{}
fi := &FieldInfo{}
fi.Field = f
fi.Zero = reflect.New(f.Type).Elem()
fi.Options = map[string]string{}
Expand Down Expand Up @@ -303,7 +303,6 @@ func getMapping(t reflect.Type, tagName string, mapFunc, tagMapFunc func(string)
}
}

// TODO: what to do with this...?
if tagMapFunc != nil {
tag = tagMapFunc(tag)
}
Expand Down Expand Up @@ -349,7 +348,7 @@ func getMapping(t reflect.Type, tagName string, mapFunc, tagMapFunc func(string)
}
}

flds := &fields{Index: m, Paths: map[string]*field{}, Names: map[string]*field{}}
flds := &StructMap{Index: m, Paths: map[string]*FieldInfo{}, Names: map[string]*FieldInfo{}}
for _, fi := range flds.Index {
flds.Paths[fi.Path] = fi
if fi.Name != "" && !fi.Embedded {
Expand Down
31 changes: 27 additions & 4 deletions reflectx/reflect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ func TestBasicEmbedded(t *testing.T) {
t.Errorf("Expecting 5 fields")
}

// for _, fi := range fields.index {
// for _, fi := range fields.Index {
// log.Println(fi)
// }

Expand Down Expand Up @@ -233,9 +233,6 @@ func TestInlineStruct(t *testing.T) {
}
}

// TODO: .. question was.. inline struct mapping.. can this be cached..?
// *********** what is the performance hit..?

func TestFieldsEmbedded(t *testing.T) {
m := NewMapper("db")

Expand Down Expand Up @@ -351,6 +348,32 @@ func TestPtrFields(t *testing.T) {
}
}

func TestFieldMap(t *testing.T) {
type Foo struct {
A int
B int
C int
}

f := Foo{1, 2, 3}
m := NewMapperFunc("db", strings.ToLower)

fm := m.FieldMap(reflect.ValueOf(f))

if len(fm) != 3 {
t.Errorf("Expecting %d keys, got %d", 3, len(fm))
}
if fm["a"].Interface().(int) != 1 {
t.Errorf("Expecting %d, got %d", 1, ival(fm["a"]))
}
if fm["b"].Interface().(int) != 2 {
t.Errorf("Expecting %d, got %d", 2, ival(fm["b"]))
}
if fm["c"].Interface().(int) != 3 {
t.Errorf("Expecting %d, got %d", 3, ival(fm["c"]))
}
}

func TestTagNameMapping(t *testing.T) {
type Strategy struct {
StrategyID string `protobuf:"bytes,1,opt,name=strategy_id" json:"strategy_id,omitempty"`
Expand Down
47 changes: 47 additions & 0 deletions sqlx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,12 +132,20 @@ CREATE TABLE nullperson (
last_name text NULL,
email text NULL
);
CREATE TABLE employees (
name text,
id integer,
boss_id integer
);
`,
drop: `
drop table person;
drop table place;
drop table capplace;
drop table nullperson;
drop table employees;
`,
}

Expand Down Expand Up @@ -242,6 +250,9 @@ func loadDefaultFixture(db *DB, t *testing.T) {
} else {
tx.MustExec(tx.Rebind("INSERT INTO capplace (\"COUNTRY\", \"TELCODE\") VALUES (?, ?)"), "Sarf Efrica", "27")
}
tx.MustExec(tx.Rebind("INSERT INTO employees (name, id) VALUES (?, ?)"), "Peter", "4444")
tx.MustExec(tx.Rebind("INSERT INTO employees (name, id, boss_id) VALUES (?, ?, ?)"), "Joe", "1", "4444")
tx.MustExec(tx.Rebind("INSERT INTO employees (name, id, boss_id) VALUES (?, ?, ?)"), "Martin", "2", "4444")
tx.Commit()
}

Expand Down Expand Up @@ -405,6 +416,42 @@ func TestEmbeddedStructs(t *testing.T) {
})
}

func TestJoinQuery(t *testing.T) {
type Employee struct {
Name string
Id int64
// BossId is an id into the employee table
BossId sql.NullInt64 `db:"boss_id"`
}
type Boss Employee

RunWithSchema(defaultSchema, t, func(db *DB, t *testing.T) {
loadDefaultFixture(db, t)

var employees []struct {
Employee
Boss `db:"boss"`
}

err := db.Select(
&employees,
`SELECT employees.*, boss.id "boss.id", boss.name "boss.name" FROM employees
JOIN employees AS boss ON employees.boss_id = boss.id`)
if err != nil {
t.Fatal(err)
}

for _, em := range employees {
if len(em.Employee.Name) == 0 {
t.Errorf("Expected non zero lengthed name.")
}
if em.Employee.BossId.Int64 != em.Boss.Id {
t.Errorf("Expected boss ids to match")
}
}
})
}

func TestSelectSliceMapTime(t *testing.T) {
RunWithSchema(defaultSchema, t, func(db *DB, t *testing.T) {
loadDefaultFixture(db, t)
Expand Down

0 comments on commit 8a5e7cc

Please sign in to comment.