summaryrefslogtreecommitdiffstats
path: root/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/reflectx
diff options
context:
space:
mode:
Diffstat (limited to 'dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/reflectx')
-rw-r--r--dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/reflectx/README.md17
-rw-r--r--dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/reflectx/reflect.go444
-rw-r--r--dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/reflectx/reflect_test.go1013
3 files changed, 1474 insertions, 0 deletions
diff --git a/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/reflectx/README.md b/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/reflectx/README.md
new file mode 100644
index 0000000..f01d3d1
--- /dev/null
+++ b/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/reflectx/README.md
@@ -0,0 +1,17 @@
+# reflectx
+
+The sqlx package has special reflect needs. In particular, it needs to:
+
+* be able to map a name to a field
+* understand embedded structs
+* understand mapping names to fields by a particular tag
+* user specified name -> field mapping functions
+
+These behaviors mimic the behaviors by the standard library marshallers and also the
+behavior of standard Go accessors.
+
+The first two are amply taken care of by `Reflect.Value.FieldByName`, and the third is
+addressed by `Reflect.Value.FieldByNameFunc`, but these don't quite understand struct
+tags in the ways that are vital to most marshallers, and they are slow.
+
+This reflectx package extends reflect to achieve these goals.
diff --git a/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/reflectx/reflect.go b/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/reflectx/reflect.go
new file mode 100644
index 0000000..0b10994
--- /dev/null
+++ b/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/reflectx/reflect.go
@@ -0,0 +1,444 @@
+// Package reflectx implements extensions to the standard reflect lib suitable
+// for implementing marshalling and unmarshalling packages. The main Mapper type
+// allows for Go-compatible named attribute access, including accessing embedded
+// struct attributes and the ability to use functions and struct tags to
+// customize field names.
+//
+package reflectx
+
+import (
+ "reflect"
+ "runtime"
+ "strings"
+ "sync"
+)
+
+// A FieldInfo is metadata for a struct field.
+type FieldInfo struct {
+ Index []int
+ Path string
+ Field reflect.StructField
+ Zero reflect.Value
+ Name string
+ Options map[string]string
+ Embedded bool
+ Children []*FieldInfo
+ Parent *FieldInfo
+}
+
+// A StructMap is an index of field metadata for a struct.
+type StructMap struct {
+ Tree *FieldInfo
+ Index []*FieldInfo
+ Paths map[string]*FieldInfo
+ Names map[string]*FieldInfo
+}
+
+// GetByPath returns a *FieldInfo for a given string path.
+func (f StructMap) GetByPath(path string) *FieldInfo {
+ return f.Paths[path]
+}
+
+// GetByTraversal returns a *FieldInfo for a given integer path. It is
+// analogous to reflect.FieldByIndex, but using the cached traversal
+// rather than re-executing the reflect machinery each time.
+func (f StructMap) GetByTraversal(index []int) *FieldInfo {
+ if len(index) == 0 {
+ return nil
+ }
+
+ tree := f.Tree
+ for _, i := range index {
+ if i >= len(tree.Children) || tree.Children[i] == nil {
+ return nil
+ }
+ tree = tree.Children[i]
+ }
+ return tree
+}
+
+// Mapper is a general purpose mapper of names to struct fields. A Mapper
+// behaves like most marshallers in the standard library, obeying a field tag
+// for name mapping but also providing a basic transform function.
+type Mapper struct {
+ cache map[reflect.Type]*StructMap
+ tagName string
+ tagMapFunc func(string) string
+ mapFunc func(string) string
+ mutex sync.Mutex
+}
+
+// NewMapper returns a new mapper using the tagName as its struct field tag.
+// If tagName is the empty string, it is ignored.
+func NewMapper(tagName string) *Mapper {
+ return &Mapper{
+ cache: make(map[reflect.Type]*StructMap),
+ tagName: tagName,
+ }
+}
+
+// NewMapperTagFunc returns a new mapper which contains a mapper for field names
+// AND a mapper for tag values. This is useful for tags like json which can
+// have values like "name,omitempty".
+func NewMapperTagFunc(tagName string, mapFunc, tagMapFunc func(string) string) *Mapper {
+ return &Mapper{
+ cache: make(map[reflect.Type]*StructMap),
+ tagName: tagName,
+ mapFunc: mapFunc,
+ tagMapFunc: tagMapFunc,
+ }
+}
+
+// NewMapperFunc returns a new mapper which optionally obeys a field tag and
+// a struct field name mapper func given by f. Tags will take precedence, but
+// 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]*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) *StructMap {
+ m.mutex.Lock()
+ mapping, ok := m.cache[t]
+ if !ok {
+ mapping = getMapping(t, m.tagName, m.mapFunc, m.tagMapFunc)
+ m.cache[t] = mapping
+ }
+ m.mutex.Unlock()
+ return mapping
+}
+
+// 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{}
+ tm := m.TypeMap(v.Type())
+ for tagName, fi := range tm.Names {
+ r[tagName] = FieldByIndexes(v, fi.Index)
+ }
+ return r
+}
+
+// FieldByName returns a field by its mapped name as a reflect.Value.
+// Panics if v's Kind is not Struct or v is not Indirectable to a struct Kind.
+// Returns zero Value if the name is not found.
+func (m *Mapper) FieldByName(v reflect.Value, name string) reflect.Value {
+ v = reflect.Indirect(v)
+ mustBe(v, reflect.Struct)
+
+ tm := m.TypeMap(v.Type())
+ fi, ok := tm.Names[name]
+ if !ok {
+ return v
+ }
+ return FieldByIndexes(v, fi.Index)
+}
+
+// FieldsByName returns a slice of values corresponding to the slice of names
+// for the value. Panics if v's Kind is not Struct or v is not Indirectable
+// to a struct Kind. Returns zero Value for each name not found.
+func (m *Mapper) FieldsByName(v reflect.Value, names []string) []reflect.Value {
+ v = reflect.Indirect(v)
+ mustBe(v, reflect.Struct)
+
+ tm := m.TypeMap(v.Type())
+ vals := make([]reflect.Value, 0, len(names))
+ for _, name := range names {
+ fi, ok := tm.Names[name]
+ if !ok {
+ vals = append(vals, *new(reflect.Value))
+ } else {
+ vals = append(vals, FieldByIndexes(v, fi.Index))
+ }
+ }
+ return vals
+}
+
+// TraversalsByName returns a slice of int slices which represent the struct
+// traversals for each mapped name. Panics if t is not a struct or Indirectable
+// to a struct. Returns empty int slice for each name not found.
+func (m *Mapper) TraversalsByName(t reflect.Type, names []string) [][]int {
+ r := make([][]int, 0, len(names))
+ m.TraversalsByNameFunc(t, names, func(_ int, i []int) error {
+ if i == nil {
+ r = append(r, []int{})
+ } else {
+ r = append(r, i)
+ }
+
+ return nil
+ })
+ return r
+}
+
+// TraversalsByNameFunc traverses the mapped names and calls fn with the index of
+// each name and the struct traversal represented by that name. Panics if t is not
+// a struct or Indirectable to a struct. Returns the first error returned by fn or nil.
+func (m *Mapper) TraversalsByNameFunc(t reflect.Type, names []string, fn func(int, []int) error) error {
+ t = Deref(t)
+ mustBe(t, reflect.Struct)
+ tm := m.TypeMap(t)
+ for i, name := range names {
+ fi, ok := tm.Names[name]
+ if !ok {
+ if err := fn(i, nil); err != nil {
+ return err
+ }
+ } else {
+ if err := fn(i, fi.Index); err != nil {
+ return err
+ }
+ }
+ }
+ return nil
+}
+
+// FieldByIndexes returns a value for the field given by the struct traversal
+// for the given value.
+func FieldByIndexes(v reflect.Value, indexes []int) reflect.Value {
+ for _, i := range indexes {
+ v = reflect.Indirect(v).Field(i)
+ // if this is a pointer and it's nil, allocate a new value and set it
+ if v.Kind() == reflect.Ptr && v.IsNil() {
+ alloc := reflect.New(Deref(v.Type()))
+ v.Set(alloc)
+ }
+ if v.Kind() == reflect.Map && v.IsNil() {
+ v.Set(reflect.MakeMap(v.Type()))
+ }
+ }
+ return v
+}
+
+// FieldByIndexesReadOnly returns a value for a particular struct traversal,
+// but is not concerned with allocating nil pointers because the value is
+// going to be used for reading and not setting.
+func FieldByIndexesReadOnly(v reflect.Value, indexes []int) reflect.Value {
+ for _, i := range indexes {
+ v = reflect.Indirect(v).Field(i)
+ }
+ return v
+}
+
+// Deref is Indirect for reflect.Types
+func Deref(t reflect.Type) reflect.Type {
+ if t.Kind() == reflect.Ptr {
+ t = t.Elem()
+ }
+ return t
+}
+
+// -- helpers & utilities --
+
+type kinder interface {
+ Kind() reflect.Kind
+}
+
+// mustBe checks a value against a kind, panicing with a reflect.ValueError
+// if the kind isn't that which is required.
+func mustBe(v kinder, expected reflect.Kind) {
+ if k := v.Kind(); k != expected {
+ panic(&reflect.ValueError{Method: methodName(), Kind: k})
+ }
+}
+
+// methodName returns the caller of the function calling methodName
+func methodName() string {
+ pc, _, _, _ := runtime.Caller(2)
+ f := runtime.FuncForPC(pc)
+ if f == nil {
+ return "unknown method"
+ }
+ return f.Name()
+}
+
+type typeQueue struct {
+ t reflect.Type
+ fi *FieldInfo
+ pp string // Parent path
+}
+
+// A copying append that creates a new slice each time.
+func apnd(is []int, i int) []int {
+ x := make([]int, len(is)+1)
+ copy(x, is)
+ x[len(x)-1] = i
+ return x
+}
+
+type mapf func(string) string
+
+// parseName parses the tag and the target name for the given field using
+// the tagName (eg 'json' for `json:"foo"` tags), mapFunc for mapping the
+// field's name to a target name, and tagMapFunc for mapping the tag to
+// a target name.
+func parseName(field reflect.StructField, tagName string, mapFunc, tagMapFunc mapf) (tag, fieldName string) {
+ // first, set the fieldName to the field's name
+ fieldName = field.Name
+ // if a mapFunc is set, use that to override the fieldName
+ if mapFunc != nil {
+ fieldName = mapFunc(fieldName)
+ }
+
+ // if there's no tag to look for, return the field name
+ if tagName == "" {
+ return "", fieldName
+ }
+
+ // if this tag is not set using the normal convention in the tag,
+ // then return the fieldname.. this check is done because according
+ // to the reflect documentation:
+ // If the tag does not have the conventional format,
+ // the value returned by Get is unspecified.
+ // which doesn't sound great.
+ if !strings.Contains(string(field.Tag), tagName+":") {
+ return "", fieldName
+ }
+
+ // at this point we're fairly sure that we have a tag, so lets pull it out
+ tag = field.Tag.Get(tagName)
+
+ // if we have a mapper function, call it on the whole tag
+ // XXX: this is a change from the old version, which pulled out the name
+ // before the tagMapFunc could be run, but I think this is the right way
+ if tagMapFunc != nil {
+ tag = tagMapFunc(tag)
+ }
+
+ // finally, split the options from the name
+ parts := strings.Split(tag, ",")
+ fieldName = parts[0]
+
+ return tag, fieldName
+}
+
+// parseOptions parses options out of a tag string, skipping the name
+func parseOptions(tag string) map[string]string {
+ parts := strings.Split(tag, ",")
+ options := make(map[string]string, len(parts))
+ if len(parts) > 1 {
+ for _, opt := range parts[1:] {
+ // short circuit potentially expensive split op
+ if strings.Contains(opt, "=") {
+ kv := strings.Split(opt, "=")
+ options[kv[0]] = kv[1]
+ continue
+ }
+ options[opt] = ""
+ }
+ }
+ return options
+}
+
+// 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 mapf) *StructMap {
+ m := []*FieldInfo{}
+
+ root := &FieldInfo{}
+ queue := []typeQueue{}
+ queue = append(queue, typeQueue{Deref(t), root, ""})
+
+QueueLoop:
+ for len(queue) != 0 {
+ // pop the first item off of the queue
+ tq := queue[0]
+ queue = queue[1:]
+
+ // ignore recursive field
+ for p := tq.fi.Parent; p != nil; p = p.Parent {
+ if tq.fi.Field.Type == p.Field.Type {
+ continue QueueLoop
+ }
+ }
+
+ nChildren := 0
+ if tq.t.Kind() == reflect.Struct {
+ nChildren = tq.t.NumField()
+ }
+ tq.fi.Children = make([]*FieldInfo, nChildren)
+
+ // iterate through all of its fields
+ for fieldPos := 0; fieldPos < nChildren; fieldPos++ {
+
+ f := tq.t.Field(fieldPos)
+
+ // parse the tag and the target name using the mapping options for this field
+ tag, name := parseName(f, tagName, mapFunc, tagMapFunc)
+
+ // if the name is "-", disabled via a tag, skip it
+ if name == "-" {
+ continue
+ }
+
+ fi := FieldInfo{
+ Field: f,
+ Name: name,
+ Zero: reflect.New(f.Type).Elem(),
+ Options: parseOptions(tag),
+ }
+
+ // if the path is empty this path is just the name
+ if tq.pp == "" {
+ fi.Path = fi.Name
+ } else {
+ fi.Path = tq.pp + "." + fi.Name
+ }
+
+ // skip unexported fields
+ if len(f.PkgPath) != 0 && !f.Anonymous {
+ continue
+ }
+
+ // bfs search of anonymous embedded structs
+ if f.Anonymous {
+ pp := tq.pp
+ if tag != "" {
+ pp = fi.Path
+ }
+
+ fi.Embedded = true
+ fi.Index = apnd(tq.fi.Index, fieldPos)
+ nChildren := 0
+ ft := Deref(f.Type)
+ if ft.Kind() == reflect.Struct {
+ nChildren = ft.NumField()
+ }
+ fi.Children = make([]*FieldInfo, nChildren)
+ queue = append(queue, typeQueue{Deref(f.Type), &fi, pp})
+ } else if fi.Zero.Kind() == reflect.Struct || (fi.Zero.Kind() == reflect.Ptr && fi.Zero.Type().Elem().Kind() == reflect.Struct) {
+ fi.Index = apnd(tq.fi.Index, fieldPos)
+ fi.Children = make([]*FieldInfo, Deref(f.Type).NumField())
+ queue = append(queue, typeQueue{Deref(f.Type), &fi, fi.Path})
+ }
+
+ fi.Index = apnd(tq.fi.Index, fieldPos)
+ fi.Parent = tq.fi
+ tq.fi.Children[fieldPos] = &fi
+ m = append(m, &fi)
+ }
+ }
+
+ flds := &StructMap{Index: m, Tree: root, Paths: map[string]*FieldInfo{}, Names: map[string]*FieldInfo{}}
+ for _, fi := range flds.Index {
+ // check if nothing has already been pushed with the same path
+ // sometimes you can choose to override a type using embedded struct
+ fld, ok := flds.Paths[fi.Path]
+ if !ok || fld.Embedded {
+ flds.Paths[fi.Path] = fi
+ if fi.Name != "" && !fi.Embedded {
+ flds.Names[fi.Path] = fi
+ }
+ }
+ }
+
+ return flds
+}
diff --git a/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/reflectx/reflect_test.go b/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/reflectx/reflect_test.go
new file mode 100644
index 0000000..e73af5b
--- /dev/null
+++ b/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/reflectx/reflect_test.go
@@ -0,0 +1,1013 @@
+package reflectx
+
+import (
+ "reflect"
+ "strings"
+ "testing"
+)
+
+func ival(v reflect.Value) int {
+ return v.Interface().(int)
+}
+
+func TestBasic(t *testing.T) {
+ type Foo struct {
+ A int
+ B int
+ C int
+ }
+
+ f := Foo{1, 2, 3}
+ fv := reflect.ValueOf(f)
+ m := NewMapperFunc("", func(s string) string { return s })
+
+ v := m.FieldByName(fv, "A")
+ if ival(v) != f.A {
+ t.Errorf("Expecting %d, got %d", ival(v), f.A)
+ }
+ v = m.FieldByName(fv, "B")
+ if ival(v) != f.B {
+ t.Errorf("Expecting %d, got %d", f.B, ival(v))
+ }
+ v = m.FieldByName(fv, "C")
+ if ival(v) != f.C {
+ t.Errorf("Expecting %d, got %d", f.C, ival(v))
+ }
+}
+
+func TestBasicEmbedded(t *testing.T) {
+ type Foo struct {
+ A int
+ }
+
+ type Bar struct {
+ Foo // `db:""` is implied for an embedded struct
+ B int
+ C int `db:"-"`
+ }
+
+ type Baz struct {
+ A int
+ Bar `db:"Bar"`
+ }
+
+ m := NewMapperFunc("db", func(s string) string { return s })
+
+ z := Baz{}
+ z.A = 1
+ z.B = 2
+ z.C = 4
+ z.Bar.Foo.A = 3
+
+ zv := reflect.ValueOf(z)
+ fields := m.TypeMap(reflect.TypeOf(z))
+
+ if len(fields.Index) != 5 {
+ t.Errorf("Expecting 5 fields")
+ }
+
+ // for _, fi := range fields.Index {
+ // log.Println(fi)
+ // }
+
+ v := m.FieldByName(zv, "A")
+ if ival(v) != z.A {
+ t.Errorf("Expecting %d, got %d", z.A, ival(v))
+ }
+ v = m.FieldByName(zv, "Bar.B")
+ if ival(v) != z.Bar.B {
+ t.Errorf("Expecting %d, got %d", z.Bar.B, ival(v))
+ }
+ v = m.FieldByName(zv, "Bar.A")
+ if ival(v) != z.Bar.Foo.A {
+ t.Errorf("Expecting %d, got %d", z.Bar.Foo.A, ival(v))
+ }
+ v = m.FieldByName(zv, "Bar.C")
+ if _, ok := v.Interface().(int); ok {
+ t.Errorf("Expecting Bar.C to not exist")
+ }
+
+ fi := fields.GetByPath("Bar.C")
+ if fi != nil {
+ t.Errorf("Bar.C should not exist")
+ }
+}
+
+func TestEmbeddedSimple(t *testing.T) {
+ type UUID [16]byte
+ type MyID struct {
+ UUID
+ }
+ type Item struct {
+ ID MyID
+ }
+ z := Item{}
+
+ m := NewMapper("db")
+ m.TypeMap(reflect.TypeOf(z))
+}
+
+func TestBasicEmbeddedWithTags(t *testing.T) {
+ type Foo struct {
+ A int `db:"a"`
+ }
+
+ type Bar struct {
+ Foo // `db:""` is implied for an embedded struct
+ B int `db:"b"`
+ }
+
+ type Baz struct {
+ A int `db:"a"`
+ Bar // `db:""` is implied for an embedded struct
+ }
+
+ m := NewMapper("db")
+
+ z := Baz{}
+ z.A = 1
+ z.B = 2
+ z.Bar.Foo.A = 3
+
+ zv := reflect.ValueOf(z)
+ fields := m.TypeMap(reflect.TypeOf(z))
+
+ if len(fields.Index) != 5 {
+ t.Errorf("Expecting 5 fields")
+ }
+
+ // for _, fi := range fields.index {
+ // log.Println(fi)
+ // }
+
+ v := m.FieldByName(zv, "a")
+ if ival(v) != z.A { // the dominant field
+ t.Errorf("Expecting %d, got %d", z.A, ival(v))
+ }
+ v = m.FieldByName(zv, "b")
+ if ival(v) != z.B {
+ t.Errorf("Expecting %d, got %d", z.B, ival(v))
+ }
+}
+
+func TestBasicEmbeddedWithSameName(t *testing.T) {
+ type Foo struct {
+ A int `db:"a"`
+ Foo int `db:"Foo"` // Same name as the embedded struct
+ }
+
+ type FooExt struct {
+ Foo
+ B int `db:"b"`
+ }
+
+ m := NewMapper("db")
+
+ z := FooExt{}
+ z.A = 1
+ z.B = 2
+ z.Foo.Foo = 3
+
+ zv := reflect.ValueOf(z)
+ fields := m.TypeMap(reflect.TypeOf(z))
+
+ if len(fields.Index) != 4 {
+ t.Errorf("Expecting 3 fields, found %d", len(fields.Index))
+ }
+
+ v := m.FieldByName(zv, "a")
+ if ival(v) != z.A { // the dominant field
+ t.Errorf("Expecting %d, got %d", z.A, ival(v))
+ }
+ v = m.FieldByName(zv, "b")
+ if ival(v) != z.B {
+ t.Errorf("Expecting %d, got %d", z.B, ival(v))
+ }
+ v = m.FieldByName(zv, "Foo")
+ if ival(v) != z.Foo.Foo {
+ t.Errorf("Expecting %d, got %d", z.Foo.Foo, ival(v))
+ }
+}
+
+func TestFlatTags(t *testing.T) {
+ m := NewMapper("db")
+
+ type Asset struct {
+ Title string `db:"title"`
+ }
+ type Post struct {
+ Author string `db:"author,required"`
+ Asset Asset `db:""`
+ }
+ // Post columns: (author title)
+
+ post := Post{Author: "Joe", Asset: Asset{Title: "Hello"}}
+ pv := reflect.ValueOf(post)
+
+ v := m.FieldByName(pv, "author")
+ if v.Interface().(string) != post.Author {
+ t.Errorf("Expecting %s, got %s", post.Author, v.Interface().(string))
+ }
+ v = m.FieldByName(pv, "title")
+ if v.Interface().(string) != post.Asset.Title {
+ t.Errorf("Expecting %s, got %s", post.Asset.Title, v.Interface().(string))
+ }
+}
+
+func TestNestedStruct(t *testing.T) {
+ m := NewMapper("db")
+
+ type Details struct {
+ Active bool `db:"active"`
+ }
+ type Asset struct {
+ Title string `db:"title"`
+ Details Details `db:"details"`
+ }
+ type Post struct {
+ Author string `db:"author,required"`
+ Asset `db:"asset"`
+ }
+ // Post columns: (author asset.title asset.details.active)
+
+ post := Post{
+ Author: "Joe",
+ Asset: Asset{Title: "Hello", Details: Details{Active: true}},
+ }
+ pv := reflect.ValueOf(post)
+
+ v := m.FieldByName(pv, "author")
+ if v.Interface().(string) != post.Author {
+ t.Errorf("Expecting %s, got %s", post.Author, v.Interface().(string))
+ }
+ v = m.FieldByName(pv, "title")
+ if _, ok := v.Interface().(string); ok {
+ t.Errorf("Expecting field to not exist")
+ }
+ v = m.FieldByName(pv, "asset.title")
+ if v.Interface().(string) != post.Asset.Title {
+ t.Errorf("Expecting %s, got %s", post.Asset.Title, v.Interface().(string))
+ }
+ v = m.FieldByName(pv, "asset.details.active")
+ if v.Interface().(bool) != post.Asset.Details.Active {
+ t.Errorf("Expecting %v, got %v", post.Asset.Details.Active, v.Interface().(bool))
+ }
+}
+
+func TestInlineStruct(t *testing.T) {
+ m := NewMapperTagFunc("db", strings.ToLower, nil)
+
+ type Employee struct {
+ Name string
+ ID int
+ }
+ type Boss Employee
+ type person struct {
+ Employee `db:"employee"`
+ Boss `db:"boss"`
+ }
+ // employees columns: (employee.name employee.id boss.name boss.id)
+
+ em := person{Employee: Employee{Name: "Joe", ID: 2}, Boss: Boss{Name: "Dick", ID: 1}}
+ ev := reflect.ValueOf(em)
+
+ fields := m.TypeMap(reflect.TypeOf(em))
+ if len(fields.Index) != 6 {
+ t.Errorf("Expecting 6 fields")
+ }
+
+ v := m.FieldByName(ev, "employee.name")
+ if v.Interface().(string) != em.Employee.Name {
+ t.Errorf("Expecting %s, got %s", em.Employee.Name, v.Interface().(string))
+ }
+ v = m.FieldByName(ev, "boss.id")
+ if ival(v) != em.Boss.ID {
+ t.Errorf("Expecting %v, got %v", em.Boss.ID, ival(v))
+ }
+}
+
+func TestRecursiveStruct(t *testing.T) {
+ type Person struct {
+ Parent *Person
+ }
+ m := NewMapperFunc("db", strings.ToLower)
+ var p *Person
+ m.TypeMap(reflect.TypeOf(p))
+}
+
+func TestFieldsEmbedded(t *testing.T) {
+ m := NewMapper("db")
+
+ type Person struct {
+ Name string `db:"name,size=64"`
+ }
+ type Place struct {
+ Name string `db:"name"`
+ }
+ type Article struct {
+ Title string `db:"title"`
+ }
+ type PP struct {
+ Person `db:"person,required"`
+ Place `db:",someflag"`
+ Article `db:",required"`
+ }
+ // PP columns: (person.name name title)
+
+ pp := PP{}
+ pp.Person.Name = "Peter"
+ pp.Place.Name = "Toronto"
+ pp.Article.Title = "Best city ever"
+
+ fields := m.TypeMap(reflect.TypeOf(pp))
+ // for i, f := range fields {
+ // log.Println(i, f)
+ // }
+
+ ppv := reflect.ValueOf(pp)
+
+ v := m.FieldByName(ppv, "person.name")
+ if v.Interface().(string) != pp.Person.Name {
+ t.Errorf("Expecting %s, got %s", pp.Person.Name, v.Interface().(string))
+ }
+
+ v = m.FieldByName(ppv, "name")
+ if v.Interface().(string) != pp.Place.Name {
+ t.Errorf("Expecting %s, got %s", pp.Place.Name, v.Interface().(string))
+ }
+
+ v = m.FieldByName(ppv, "title")
+ if v.Interface().(string) != pp.Article.Title {
+ t.Errorf("Expecting %s, got %s", pp.Article.Title, v.Interface().(string))
+ }
+
+ fi := fields.GetByPath("person")
+ if _, ok := fi.Options["required"]; !ok {
+ t.Errorf("Expecting required option to be set")
+ }
+ if !fi.Embedded {
+ t.Errorf("Expecting field to be embedded")
+ }
+ if len(fi.Index) != 1 || fi.Index[0] != 0 {
+ t.Errorf("Expecting index to be [0]")
+ }
+
+ fi = fields.GetByPath("person.name")
+ if fi == nil {
+ t.Errorf("Expecting person.name to exist")
+ }
+ if fi.Path != "person.name" {
+ t.Errorf("Expecting %s, got %s", "person.name", fi.Path)
+ }
+ if fi.Options["size"] != "64" {
+ t.Errorf("Expecting %s, got %s", "64", fi.Options["size"])
+ }
+
+ fi = fields.GetByTraversal([]int{1, 0})
+ if fi == nil {
+ t.Errorf("Expecting traveral to exist")
+ }
+ if fi.Path != "name" {
+ t.Errorf("Expecting %s, got %s", "name", fi.Path)
+ }
+
+ fi = fields.GetByTraversal([]int{2})
+ if fi == nil {
+ t.Errorf("Expecting traversal to exist")
+ }
+ if _, ok := fi.Options["required"]; !ok {
+ t.Errorf("Expecting required option to be set")
+ }
+
+ trs := m.TraversalsByName(reflect.TypeOf(pp), []string{"person.name", "name", "title"})
+ if !reflect.DeepEqual(trs, [][]int{{0, 0}, {1, 0}, {2, 0}}) {
+ t.Errorf("Expecting traversal: %v", trs)
+ }
+}
+
+func TestPtrFields(t *testing.T) {
+ m := NewMapperTagFunc("db", strings.ToLower, nil)
+ type Asset struct {
+ Title string
+ }
+ type Post struct {
+ *Asset `db:"asset"`
+ Author string
+ }
+
+ post := &Post{Author: "Joe", Asset: &Asset{Title: "Hiyo"}}
+ pv := reflect.ValueOf(post)
+
+ fields := m.TypeMap(reflect.TypeOf(post))
+ if len(fields.Index) != 3 {
+ t.Errorf("Expecting 3 fields")
+ }
+
+ v := m.FieldByName(pv, "asset.title")
+ if v.Interface().(string) != post.Asset.Title {
+ t.Errorf("Expecting %s, got %s", post.Asset.Title, v.Interface().(string))
+ }
+ v = m.FieldByName(pv, "author")
+ if v.Interface().(string) != post.Author {
+ t.Errorf("Expecting %s, got %s", post.Author, v.Interface().(string))
+ }
+}
+
+func TestNamedPtrFields(t *testing.T) {
+ m := NewMapperTagFunc("db", strings.ToLower, nil)
+
+ type User struct {
+ Name string
+ }
+
+ type Asset struct {
+ Title string
+
+ Owner *User `db:"owner"`
+ }
+ type Post struct {
+ Author string
+
+ Asset1 *Asset `db:"asset1"`
+ Asset2 *Asset `db:"asset2"`
+ }
+
+ post := &Post{Author: "Joe", Asset1: &Asset{Title: "Hiyo", Owner: &User{"Username"}}} // Let Asset2 be nil
+ pv := reflect.ValueOf(post)
+
+ fields := m.TypeMap(reflect.TypeOf(post))
+ if len(fields.Index) != 9 {
+ t.Errorf("Expecting 9 fields")
+ }
+
+ v := m.FieldByName(pv, "asset1.title")
+ if v.Interface().(string) != post.Asset1.Title {
+ t.Errorf("Expecting %s, got %s", post.Asset1.Title, v.Interface().(string))
+ }
+ v = m.FieldByName(pv, "asset1.owner.name")
+ if v.Interface().(string) != post.Asset1.Owner.Name {
+ t.Errorf("Expecting %s, got %s", post.Asset1.Owner.Name, v.Interface().(string))
+ }
+ v = m.FieldByName(pv, "asset2.title")
+ if v.Interface().(string) != post.Asset2.Title {
+ t.Errorf("Expecting %s, got %s", post.Asset2.Title, v.Interface().(string))
+ }
+ v = m.FieldByName(pv, "asset2.owner.name")
+ if v.Interface().(string) != post.Asset2.Owner.Name {
+ t.Errorf("Expecting %s, got %s", post.Asset2.Owner.Name, v.Interface().(string))
+ }
+ v = m.FieldByName(pv, "author")
+ if v.Interface().(string) != post.Author {
+ t.Errorf("Expecting %s, got %s", post.Author, v.Interface().(string))
+ }
+}
+
+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"`
+ StrategyName string
+ }
+
+ m := NewMapperTagFunc("json", strings.ToUpper, func(value string) string {
+ if strings.Contains(value, ",") {
+ return strings.Split(value, ",")[0]
+ }
+ return value
+ })
+ strategy := Strategy{"1", "Alpah"}
+ mapping := m.TypeMap(reflect.TypeOf(strategy))
+
+ for _, key := range []string{"strategy_id", "STRATEGYNAME"} {
+ if fi := mapping.GetByPath(key); fi == nil {
+ t.Errorf("Expecting to find key %s in mapping but did not.", key)
+ }
+ }
+}
+
+func TestMapping(t *testing.T) {
+ type Person struct {
+ ID int
+ Name string
+ WearsGlasses bool `db:"wears_glasses"`
+ }
+
+ m := NewMapperFunc("db", strings.ToLower)
+ p := Person{1, "Jason", true}
+ mapping := m.TypeMap(reflect.TypeOf(p))
+
+ for _, key := range []string{"id", "name", "wears_glasses"} {
+ if fi := mapping.GetByPath(key); fi == nil {
+ t.Errorf("Expecting to find key %s in mapping but did not.", key)
+ }
+ }
+
+ type SportsPerson struct {
+ Weight int
+ Age int
+ Person
+ }
+ s := SportsPerson{Weight: 100, Age: 30, Person: p}
+ mapping = m.TypeMap(reflect.TypeOf(s))
+ for _, key := range []string{"id", "name", "wears_glasses", "weight", "age"} {
+ if fi := mapping.GetByPath(key); fi == nil {
+ t.Errorf("Expecting to find key %s in mapping but did not.", key)
+ }
+ }
+
+ type RugbyPlayer struct {
+ Position int
+ IsIntense bool `db:"is_intense"`
+ IsAllBlack bool `db:"-"`
+ SportsPerson
+ }
+ r := RugbyPlayer{12, true, false, s}
+ mapping = m.TypeMap(reflect.TypeOf(r))
+ for _, key := range []string{"id", "name", "wears_glasses", "weight", "age", "position", "is_intense"} {
+ if fi := mapping.GetByPath(key); fi == nil {
+ t.Errorf("Expecting to find key %s in mapping but did not.", key)
+ }
+ }
+
+ if fi := mapping.GetByPath("isallblack"); fi != nil {
+ t.Errorf("Expecting to ignore `IsAllBlack` field")
+ }
+}
+
+func TestGetByTraversal(t *testing.T) {
+ type C struct {
+ C0 int
+ C1 int
+ }
+ type B struct {
+ B0 string
+ B1 *C
+ }
+ type A struct {
+ A0 int
+ A1 B
+ }
+
+ testCases := []struct {
+ Index []int
+ ExpectedName string
+ ExpectNil bool
+ }{
+ {
+ Index: []int{0},
+ ExpectedName: "A0",
+ },
+ {
+ Index: []int{1, 0},
+ ExpectedName: "B0",
+ },
+ {
+ Index: []int{1, 1, 1},
+ ExpectedName: "C1",
+ },
+ {
+ Index: []int{3, 4, 5},
+ ExpectNil: true,
+ },
+ {
+ Index: []int{},
+ ExpectNil: true,
+ },
+ {
+ Index: nil,
+ ExpectNil: true,
+ },
+ }
+
+ m := NewMapperFunc("db", func(n string) string { return n })
+ tm := m.TypeMap(reflect.TypeOf(A{}))
+
+ for i, tc := range testCases {
+ fi := tm.GetByTraversal(tc.Index)
+ if tc.ExpectNil {
+ if fi != nil {
+ t.Errorf("%d: expected nil, got %v", i, fi)
+ }
+ continue
+ }
+
+ if fi == nil {
+ t.Errorf("%d: expected %s, got nil", i, tc.ExpectedName)
+ continue
+ }
+
+ if fi.Name != tc.ExpectedName {
+ t.Errorf("%d: expected %s, got %s", i, tc.ExpectedName, fi.Name)
+ }
+ }
+}
+
+// TestMapperMethodsByName tests Mapper methods FieldByName and TraversalsByName
+func TestMapperMethodsByName(t *testing.T) {
+ type C struct {
+ C0 string
+ C1 int
+ }
+ type B struct {
+ B0 *C `db:"B0"`
+ B1 C `db:"B1"`
+ B2 string `db:"B2"`
+ }
+ type A struct {
+ A0 *B `db:"A0"`
+ B `db:"A1"`
+ A2 int
+ a3 int
+ }
+
+ val := &A{
+ A0: &B{
+ B0: &C{C0: "0", C1: 1},
+ B1: C{C0: "2", C1: 3},
+ B2: "4",
+ },
+ B: B{
+ B0: nil,
+ B1: C{C0: "5", C1: 6},
+ B2: "7",
+ },
+ A2: 8,
+ }
+
+ testCases := []struct {
+ Name string
+ ExpectInvalid bool
+ ExpectedValue interface{}
+ ExpectedIndexes []int
+ }{
+ {
+ Name: "A0.B0.C0",
+ ExpectedValue: "0",
+ ExpectedIndexes: []int{0, 0, 0},
+ },
+ {
+ Name: "A0.B0.C1",
+ ExpectedValue: 1,
+ ExpectedIndexes: []int{0, 0, 1},
+ },
+ {
+ Name: "A0.B1.C0",
+ ExpectedValue: "2",
+ ExpectedIndexes: []int{0, 1, 0},
+ },
+ {
+ Name: "A0.B1.C1",
+ ExpectedValue: 3,
+ ExpectedIndexes: []int{0, 1, 1},
+ },
+ {
+ Name: "A0.B2",
+ ExpectedValue: "4",
+ ExpectedIndexes: []int{0, 2},
+ },
+ {
+ Name: "A1.B0.C0",
+ ExpectedValue: "",
+ ExpectedIndexes: []int{1, 0, 0},
+ },
+ {
+ Name: "A1.B0.C1",
+ ExpectedValue: 0,
+ ExpectedIndexes: []int{1, 0, 1},
+ },
+ {
+ Name: "A1.B1.C0",
+ ExpectedValue: "5",
+ ExpectedIndexes: []int{1, 1, 0},
+ },
+ {
+ Name: "A1.B1.C1",
+ ExpectedValue: 6,
+ ExpectedIndexes: []int{1, 1, 1},
+ },
+ {
+ Name: "A1.B2",
+ ExpectedValue: "7",
+ ExpectedIndexes: []int{1, 2},
+ },
+ {
+ Name: "A2",
+ ExpectedValue: 8,
+ ExpectedIndexes: []int{2},
+ },
+ {
+ Name: "XYZ",
+ ExpectInvalid: true,
+ ExpectedIndexes: []int{},
+ },
+ {
+ Name: "a3",
+ ExpectInvalid: true,
+ ExpectedIndexes: []int{},
+ },
+ }
+
+ // build the names array from the test cases
+ names := make([]string, len(testCases))
+ for i, tc := range testCases {
+ names[i] = tc.Name
+ }
+ m := NewMapperFunc("db", func(n string) string { return n })
+ v := reflect.ValueOf(val)
+ values := m.FieldsByName(v, names)
+ if len(values) != len(testCases) {
+ t.Errorf("expected %d values, got %d", len(testCases), len(values))
+ t.FailNow()
+ }
+ indexes := m.TraversalsByName(v.Type(), names)
+ if len(indexes) != len(testCases) {
+ t.Errorf("expected %d traversals, got %d", len(testCases), len(indexes))
+ t.FailNow()
+ }
+ for i, val := range values {
+ tc := testCases[i]
+ traversal := indexes[i]
+ if !reflect.DeepEqual(tc.ExpectedIndexes, traversal) {
+ t.Errorf("expected %v, got %v", tc.ExpectedIndexes, traversal)
+ t.FailNow()
+ }
+ val = reflect.Indirect(val)
+ if tc.ExpectInvalid {
+ if val.IsValid() {
+ t.Errorf("%d: expected zero value, got %v", i, val)
+ }
+ continue
+ }
+ if !val.IsValid() {
+ t.Errorf("%d: expected valid value, got %v", i, val)
+ continue
+ }
+ actualValue := reflect.Indirect(val).Interface()
+ if !reflect.DeepEqual(tc.ExpectedValue, actualValue) {
+ t.Errorf("%d: expected %v, got %v", i, tc.ExpectedValue, actualValue)
+ }
+ }
+}
+
+func TestFieldByIndexes(t *testing.T) {
+ type C struct {
+ C0 bool
+ C1 string
+ C2 int
+ C3 map[string]int
+ }
+ type B struct {
+ B1 C
+ B2 *C
+ }
+ type A struct {
+ A1 B
+ A2 *B
+ }
+ testCases := []struct {
+ value interface{}
+ indexes []int
+ expectedValue interface{}
+ readOnly bool
+ }{
+ {
+ value: A{
+ A1: B{B1: C{C0: true}},
+ },
+ indexes: []int{0, 0, 0},
+ expectedValue: true,
+ readOnly: true,
+ },
+ {
+ value: A{
+ A2: &B{B2: &C{C1: "answer"}},
+ },
+ indexes: []int{1, 1, 1},
+ expectedValue: "answer",
+ readOnly: true,
+ },
+ {
+ value: &A{},
+ indexes: []int{1, 1, 3},
+ expectedValue: map[string]int{},
+ },
+ }
+
+ for i, tc := range testCases {
+ checkResults := func(v reflect.Value) {
+ if tc.expectedValue == nil {
+ if !v.IsNil() {
+ t.Errorf("%d: expected nil, actual %v", i, v.Interface())
+ }
+ } else {
+ if !reflect.DeepEqual(tc.expectedValue, v.Interface()) {
+ t.Errorf("%d: expected %v, actual %v", i, tc.expectedValue, v.Interface())
+ }
+ }
+ }
+
+ checkResults(FieldByIndexes(reflect.ValueOf(tc.value), tc.indexes))
+ if tc.readOnly {
+ checkResults(FieldByIndexesReadOnly(reflect.ValueOf(tc.value), tc.indexes))
+ }
+ }
+}
+
+func TestMustBe(t *testing.T) {
+ typ := reflect.TypeOf(E1{})
+ mustBe(typ, reflect.Struct)
+
+ defer func() {
+ if r := recover(); r != nil {
+ valueErr, ok := r.(*reflect.ValueError)
+ if !ok {
+ t.Errorf("unexpected Method: %s", valueErr.Method)
+ t.Error("expected panic with *reflect.ValueError")
+ return
+ }
+ if valueErr.Method != "github.com/jmoiron/sqlx/reflectx.TestMustBe" {
+ }
+ if valueErr.Kind != reflect.String {
+ t.Errorf("unexpected Kind: %s", valueErr.Kind)
+ }
+ } else {
+ t.Error("expected panic")
+ }
+ }()
+
+ typ = reflect.TypeOf("string")
+ mustBe(typ, reflect.Struct)
+ t.Error("got here, didn't expect to")
+}
+
+type E1 struct {
+ A int
+}
+type E2 struct {
+ E1
+ B int
+}
+type E3 struct {
+ E2
+ C int
+}
+type E4 struct {
+ E3
+ D int
+}
+
+func BenchmarkFieldNameL1(b *testing.B) {
+ e4 := E4{D: 1}
+ for i := 0; i < b.N; i++ {
+ v := reflect.ValueOf(e4)
+ f := v.FieldByName("D")
+ if f.Interface().(int) != 1 {
+ b.Fatal("Wrong value.")
+ }
+ }
+}
+
+func BenchmarkFieldNameL4(b *testing.B) {
+ e4 := E4{}
+ e4.A = 1
+ for i := 0; i < b.N; i++ {
+ v := reflect.ValueOf(e4)
+ f := v.FieldByName("A")
+ if f.Interface().(int) != 1 {
+ b.Fatal("Wrong value.")
+ }
+ }
+}
+
+func BenchmarkFieldPosL1(b *testing.B) {
+ e4 := E4{D: 1}
+ for i := 0; i < b.N; i++ {
+ v := reflect.ValueOf(e4)
+ f := v.Field(1)
+ if f.Interface().(int) != 1 {
+ b.Fatal("Wrong value.")
+ }
+ }
+}
+
+func BenchmarkFieldPosL4(b *testing.B) {
+ e4 := E4{}
+ e4.A = 1
+ for i := 0; i < b.N; i++ {
+ v := reflect.ValueOf(e4)
+ f := v.Field(0)
+ f = f.Field(0)
+ f = f.Field(0)
+ f = f.Field(0)
+ if f.Interface().(int) != 1 {
+ b.Fatal("Wrong value.")
+ }
+ }
+}
+
+func BenchmarkFieldByIndexL4(b *testing.B) {
+ e4 := E4{}
+ e4.A = 1
+ idx := []int{0, 0, 0, 0}
+ for i := 0; i < b.N; i++ {
+ v := reflect.ValueOf(e4)
+ f := FieldByIndexes(v, idx)
+ if f.Interface().(int) != 1 {
+ b.Fatal("Wrong value.")
+ }
+ }
+}
+
+func BenchmarkTraversalsByName(b *testing.B) {
+ type A struct {
+ Value int
+ }
+
+ type B struct {
+ A A
+ }
+
+ type C struct {
+ B B
+ }
+
+ type D struct {
+ C C
+ }
+
+ m := NewMapper("")
+ t := reflect.TypeOf(D{})
+ names := []string{"C", "B", "A", "Value"}
+
+ b.ResetTimer()
+
+ for i := 0; i < b.N; i++ {
+ if l := len(m.TraversalsByName(t, names)); l != len(names) {
+ b.Errorf("expected %d values, got %d", len(names), l)
+ }
+ }
+}
+
+func BenchmarkTraversalsByNameFunc(b *testing.B) {
+ type A struct {
+ Z int
+ }
+
+ type B struct {
+ A A
+ }
+
+ type C struct {
+ B B
+ }
+
+ type D struct {
+ C C
+ }
+
+ m := NewMapper("")
+ t := reflect.TypeOf(D{})
+ names := []string{"C", "B", "A", "Z", "Y"}
+
+ b.ResetTimer()
+
+ for i := 0; i < b.N; i++ {
+ var l int
+
+ if err := m.TraversalsByNameFunc(t, names, func(_ int, _ []int) error {
+ l++
+ return nil
+ }); err != nil {
+ b.Errorf("unexpected error %s", err)
+ }
+
+ if l != len(names) {
+ b.Errorf("expected %d values, got %d", len(names), l)
+ }
+ }
+}