diff --git a/Makefile b/Makefile index 4afaa37..334847f 100644 --- a/Makefile +++ b/Makefile @@ -27,7 +27,7 @@ test: @$(GOTEST) . @$(GOTEST) ./migrate @$(GOTEST) ./qb - @$(GOTEST) ./reflectx + @$(GOTEST) ./table .PHONY: bench bench: diff --git a/go.mod b/go.mod index 6872b16..b5f1385 100644 --- a/go.mod +++ b/go.mod @@ -3,4 +3,5 @@ module github.com/scylladb/gocqlx require ( github.com/gocql/gocql v0.0.0-20181124151448-70385f88b28b github.com/google/go-cmp v0.2.0 + github.com/scylladb/go-reflectx v1.0.0 ) diff --git a/gocqlx.go b/gocqlx.go index bc4d55e..8c29069 100644 --- a/gocqlx.go +++ b/gocqlx.go @@ -10,7 +10,7 @@ import ( "reflect" "github.com/gocql/gocql" - "github.com/scylladb/gocqlx/reflectx" + "github.com/scylladb/go-reflectx" ) // structOnlyError returns an error appropriate for type when a non-scannable diff --git a/iterx.go b/iterx.go index b45d837..0fc76bd 100644 --- a/iterx.go +++ b/iterx.go @@ -10,7 +10,7 @@ import ( "reflect" "github.com/gocql/gocql" - "github.com/scylladb/gocqlx/reflectx" + "github.com/scylladb/go-reflectx" ) // Get is a convenience function for creating iterator and calling Get. diff --git a/mapper.go b/mapper.go index 4c40a1e..5b134eb 100644 --- a/mapper.go +++ b/mapper.go @@ -5,40 +5,11 @@ package gocqlx import ( - "fmt" - "unicode" - - "github.com/scylladb/gocqlx/reflectx" + "github.com/scylladb/go-reflectx" ) // DefaultMapper uses `db` tag and automatically converts struct field names to // snake case. It can be set to whatever you want, but it is encouraged to be // set before gocqlx is used as name-to-field mappings are cached after first // use on a type. -var DefaultMapper = reflectx.NewMapperFunc("db", snakeCase) - -// snakeCase converts camel case to snake case. -func snakeCase(s string) string { - buf := []byte(s) - out := make([]byte, 0, len(buf)+3) - - l := len(buf) - for i := 0; i < l; i++ { - if !(allowedBindRune(buf[i]) || buf[i] == '_') { - panic(fmt.Sprint("not allowed name ", s)) - } - - b := rune(buf[i]) - - if unicode.IsUpper(b) { - if i > 0 && buf[i-1] != '_' && (unicode.IsLower(rune(buf[i-1])) || (i+1 < l && unicode.IsLower(rune(buf[i+1])))) { - out = append(out, '_') - } - b = unicode.ToLower(b) - } - - out = append(out, byte(b)) - } - - return string(out) -} +var DefaultMapper = reflectx.NewMapperFunc("db", reflectx.CamelToSnakeASCII) diff --git a/mapper_bench_test.go b/mapper_bench_test.go deleted file mode 100644 index 26f07eb..0000000 --- a/mapper_bench_test.go +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (C) 2017 ScyllaDB -// Use of this source code is governed by a ALv2-style -// license that can be found in the LICENSE file. - -package gocqlx - -import ( - "strings" - "testing" -) - -func BenchmarkSnakeCase(b *testing.B) { - for i := 0; i < b.N; i++ { - snakeCase(snakeTable[b.N%len(snakeTable)].N) - } -} - -func BenchmarkToLower(b *testing.B) { - for i := 0; i < b.N; i++ { - strings.ToLower(snakeTable[b.N%len(snakeTable)].N) - } -} diff --git a/mapper_test.go b/mapper_test.go deleted file mode 100644 index 2ec05db..0000000 --- a/mapper_test.go +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (C) 2017 ScyllaDB -// Use of this source code is governed by a ALv2-style -// license that can be found in the LICENSE file. - -package gocqlx - -import ( - "testing" -) - -var snakeTable = []struct { - N string - V string -}{ - {"a", "a"}, - {"snake", "snake"}, - {"A", "a"}, - {"ID", "id"}, - {"MOTD", "motd"}, - {"Snake", "snake"}, - {"SnakeTest", "snake_test"}, - {"APIResponse", "api_response"}, - {"SnakeID", "snake_id"}, - {"Snake_Id", "snake_id"}, - {"Snake_ID", "snake_id"}, - {"SnakeIDGoogle", "snake_id_google"}, - {"LinuxMOTD", "linux_motd"}, - {"OMGWTFBBQ", "omgwtfbbq"}, - {"omg_wtf_bbq", "omg_wtf_bbq"}, - {"woof_woof", "woof_woof"}, - {"_woof_woof", "_woof_woof"}, - {"woof_woof_", "woof_woof_"}, - {"WOOF", "woof"}, - {"Woof", "woof"}, - {"woof", "woof"}, - {"woof0_woof1", "woof0_woof1"}, - {"_woof0_woof1_2", "_woof0_woof1_2"}, - {"woof0_WOOF1_2", "woof0_woof1_2"}, - {"WOOF0", "woof0"}, - {"Woof1", "woof1"}, - {"woof2", "woof2"}, - {"woofWoof", "woof_woof"}, - {"woofWOOF", "woof_woof"}, - {"woof_WOOF", "woof_woof"}, - {"Woof_WOOF", "woof_woof"}, - {"WOOFWoofWoofWOOFWoofWoof", "woof_woof_woof_woof_woof_woof"}, - {"WOOF_Woof_woof_WOOF_Woof_woof", "woof_woof_woof_woof_woof_woof"}, - {"Woof_W", "woof_w"}, - {"Woof_w", "woof_w"}, - {"WoofW", "woof_w"}, - {"Woof_W_", "woof_w_"}, - {"Woof_w_", "woof_w_"}, - {"WoofW_", "woof_w_"}, - {"WOOF_", "woof_"}, - {"W_Woof", "w_woof"}, - {"w_Woof", "w_woof"}, - {"WWoof", "w_woof"}, - {"_W_Woof", "_w_woof"}, - {"_w_Woof", "_w_woof"}, - {"_WWoof", "_w_woof"}, - {"_WOOF", "_woof"}, - {"_woof", "_woof"}, -} - -func TestSnakeCase(t *testing.T) { - for _, test := range snakeTable { - if actual := snakeCase(test.N); actual != test.V { - t.Error("V", test.V, "got", actual, test) - } - } -} diff --git a/queryx.go b/queryx.go index aea2398..e100645 100644 --- a/queryx.go +++ b/queryx.go @@ -12,7 +12,7 @@ import ( "strconv" "github.com/gocql/gocql" - "github.com/scylladb/gocqlx/reflectx" + "github.com/scylladb/go-reflectx" ) // CompileNamedQuery translates query with named parameters in a form diff --git a/reflectx/README.md b/reflectx/README.md deleted file mode 100644 index f01d3d1..0000000 --- a/reflectx/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# 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/reflectx/reflect.go b/reflectx/reflect.go deleted file mode 100644 index 659782e..0000000 --- a/reflectx/reflect.go +++ /dev/null @@ -1,439 +0,0 @@ -// 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 { // nolint - 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) { // nolint: unparam - 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 { - flds.Paths[fi.Path] = fi - if fi.Name != "" && !fi.Embedded { - flds.Names[fi.Path] = fi - } - } - - return flds -} diff --git a/reflectx/reflect_test.go b/reflectx/reflect_test.go deleted file mode 100644 index d14d023..0000000 --- a/reflectx/reflect_test.go +++ /dev/null @@ -1,971 +0,0 @@ -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.Bar.Foo.A { // the dominant field - t.Errorf("Expecting %d, got %d", z.Bar.Foo.A, ival(v)) - } - v = m.FieldByName(zv, "b") - if ival(v) != z.B { - t.Errorf("Expecting %d, got %d", z.B, 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 - } - - 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.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) - } - } -}