Skip to content

Commit 5216d82

Browse files
author
Frankee.zhou
committed
add extension for reflectx
1 parent 38398a3 commit 5216d82

12 files changed

Lines changed: 258 additions & 44 deletions

File tree

README.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# sqlx
22

3-
[![Build Status](https://travis-ci.org/jmoiron/sqlx.svg?branch=master)](https://travis-ci.org/jmoiron/sqlx) [![Coverage Status](https://coveralls.io/repos/github/jmoiron/sqlx/badge.svg?branch=master)](https://coveralls.io/github/jmoiron/sqlx?branch=master) [![Godoc](http://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/jmoiron/sqlx) [![license](http://img.shields.io/badge/license-MIT-red.svg?style=flat)](https://raw.githubusercontent.com/jmoiron/sqlx/master/LICENSE)
3+
[![Build Status](https://travis-ci.org/frankee/sqlx.svg?branch=master)](https://travis-ci.org/frankee/sqlx) [![Coverage Status](https://coveralls.io/repos/github/frankee/sqlx/badge.svg?branch=master)](https://coveralls.io/github/frankee/sqlx?branch=master) [![Godoc](http://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/frankee/sqlx) [![license](http://img.shields.io/badge/license-MIT-red.svg?style=flat)](https://raw.githubusercontent.com/frankee/sqlx/master/LICENSE)
44

55
sqlx is a library which provides a set of extensions on go's standard
66
`database/sql` library. The sqlx versions of `sql.DB`, `sql.TX`, `sql.Stmt`,
@@ -14,13 +14,13 @@ Major additional concepts are:
1414
* Named parameter support including prepared statements
1515
* `Get` and `Select` to go quickly from query to struct/slice
1616

17-
In addition to the [godoc API documentation](http://godoc.org/github.com/jmoiron/sqlx),
17+
In addition to the [godoc API documentation](http://godoc.org/github.com/frankee/sqlx),
1818
there is also some [user documentation](http://jmoiron.github.io/sqlx/) that
1919
explains how to use `database/sql` along with sqlx.
2020

2121
## Recent Changes
2222

23-
* The [introduction](https://github.com/jmoiron/sqlx/pull/387) of `sql.ColumnType` sets the required minimum Go version to 1.8.
23+
* The [introduction](https://github.com/frankee/sqlx/pull/387) of `sql.ColumnType` sets the required minimum Go version to 1.8.
2424

2525
* sqlx/types.JsonText has been renamed to JSONText to follow Go naming conventions.
2626

@@ -34,15 +34,15 @@ active development currently.
3434

3535
There is no Go1-like promise of absolute stability, but I take the issue seriously
3636
and will maintain the library in a compatible state unless vital bugs prevent me
37-
from doing so. Since [#59](https://github.com/jmoiron/sqlx/issues/59) and
38-
[#60](https://github.com/jmoiron/sqlx/issues/60) necessitated breaking behavior,
37+
from doing so. Since [#59](https://github.com/frankee/sqlx/issues/59) and
38+
[#60](https://github.com/frankee/sqlx/issues/60) necessitated breaking behavior,
3939
a wider API cleanup was done at the time of fixing. It's possible this will happen
4040
in future; if it does, a git tag will be provided for users requiring the old
4141
behavior to continue to use it until such a time as they can migrate.
4242

4343
## install
4444

45-
go get github.com/jmoiron/sqlx
45+
go get github.com/frankee/sqlx
4646

4747
## issues
4848

@@ -60,7 +60,7 @@ to give columns distinct names, `rows.Scan` to scan them manually, or
6060
## usage
6161

6262
Below is an example which shows some common use cases for sqlx. Check
63-
[sqlx_test.go](https://github.com/jmoiron/sqlx/blob/master/sqlx_test.go) for more
63+
[sqlx_test.go](https://github.com/frankee/sqlx/blob/master/sqlx_test.go) for more
6464
usage.
6565

6666

@@ -73,7 +73,7 @@ import (
7373
"log"
7474

7575
_ "github.com/lib/pq"
76-
"github.com/jmoiron/sqlx"
76+
"github.com/frankee/sqlx"
7777
)
7878

7979
var schema = `

bind.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import (
88
"strconv"
99
"strings"
1010

11-
"github.com/jmoiron/sqlx/reflectx"
11+
"github.com/frankee/sqlx/reflectx"
1212
)
1313

1414
// Bindvar types supported by Rebind, BindMap and BindStruct.

go.mod

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1-
module github.com/jmoiron/sqlx
1+
module github.com/frankee/sqlx
22

33
require (
4+
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869
45
github.com/go-sql-driver/mysql v1.4.0
6+
github.com/kr/pretty v0.1.0 // indirect
57
github.com/lib/pq v1.0.0
68
github.com/mattn/go-sqlite3 v1.9.0
9+
github.com/serenize/snaker v0.0.0-20171204205717-a683aaf2d516 // indirect
710
)

go.sum

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
1+
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
2+
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
13
github.com/go-sql-driver/mysql v1.4.0 h1:7LxgVwFb2hIQtMm87NdgAVfXjnt4OePseqT1tKx+opk=
24
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
5+
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
6+
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
7+
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
8+
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
9+
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
310
github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
411
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
512
github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4=
613
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
14+
github.com/serenize/snaker v0.0.0-20171204205717-a683aaf2d516 h1:ofR1ZdrNSkiWcMsRrubK9tb2/SlZVWttAfqUjJi6QYc=
15+
github.com/serenize/snaker v0.0.0-20171204205717-a683aaf2d516/go.mod h1:Yow6lPLSAXx2ifx470yD/nUe22Dv5vBvxK/UK9UUTVs=

named.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import (
2121
"strconv"
2222
"unicode"
2323

24-
"github.com/jmoiron/sqlx/reflectx"
24+
"github.com/frankee/sqlx/reflectx"
2525
)
2626

2727
// NamedStmt is a prepared statement that executes named queries. Prepare it

reflectx/reflect.go

Lines changed: 105 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ type FieldInfo struct {
2424
Embedded bool
2525
Children []*FieldInfo
2626
Parent *FieldInfo
27+
28+
Virtual bool
29+
VirtualType reflect.Type
2730
}
2831

2932
// A StructMap is an index of field metadata for a struct.
@@ -39,6 +42,11 @@ func (f StructMap) GetByPath(path string) *FieldInfo {
3942
return f.Paths[path]
4043
}
4144

45+
// GetByPath returns a *FieldInfo for a given string name.
46+
func (f StructMap) GetByName(name string) *FieldInfo {
47+
return f.Names[name]
48+
}
49+
4250
// GetByTraversal returns a *FieldInfo for a given integer path. It is
4351
// analogous to reflect.FieldByIndex, but using the cached traversal
4452
// rather than re-executing the reflect machinery each time.
@@ -57,11 +65,15 @@ func (f StructMap) GetByTraversal(index []int) *FieldInfo {
5765
return tree
5866
}
5967

68+
type Register = map[reflect.Type]map[string]reflect.Type
69+
6070
// Mapper is a general purpose mapper of names to struct fields. A Mapper
6171
// behaves like most marshallers in the standard library, obeying a field tag
6272
// for name mapping but also providing a basic transform function.
6373
type Mapper struct {
64-
cache map[reflect.Type]*StructMap
74+
cache map[reflect.Type]*StructMap
75+
registry Register
76+
6577
tagName string
6678
tagMapFunc func(string) string
6779
mapFunc func(string) string
@@ -72,8 +84,9 @@ type Mapper struct {
7284
// If tagName is the empty string, it is ignored.
7385
func NewMapper(tagName string) *Mapper {
7486
return &Mapper{
75-
cache: make(map[reflect.Type]*StructMap),
76-
tagName: tagName,
87+
cache: make(map[reflect.Type]*StructMap),
88+
registry: make(Register),
89+
tagName: tagName,
7790
}
7891
}
7992

@@ -83,6 +96,7 @@ func NewMapper(tagName string) *Mapper {
8396
func NewMapperTagFunc(tagName string, mapFunc, tagMapFunc func(string) string) *Mapper {
8497
return &Mapper{
8598
cache: make(map[reflect.Type]*StructMap),
99+
registry: make(Register),
86100
tagName: tagName,
87101
mapFunc: mapFunc,
88102
tagMapFunc: tagMapFunc,
@@ -94,19 +108,32 @@ func NewMapperTagFunc(tagName string, mapFunc, tagMapFunc func(string) string) *
94108
// for any other field, the mapped name will be f(field.Name)
95109
func NewMapperFunc(tagName string, f func(string) string) *Mapper {
96110
return &Mapper{
97-
cache: make(map[reflect.Type]*StructMap),
98-
tagName: tagName,
99-
mapFunc: f,
111+
cache: make(map[reflect.Type]*StructMap),
112+
registry: make(Register),
113+
tagName: tagName,
114+
mapFunc: f,
100115
}
101116
}
102117

118+
//
119+
//
120+
func (m *Mapper) Register(t reflect.Type, name string, vf reflect.Type) {
121+
vfs := m.registry[t]
122+
if vfs == nil {
123+
vfs = make(map[string]reflect.Type)
124+
m.registry[t] = vfs
125+
}
126+
127+
vfs[name] = vf
128+
}
129+
103130
// TypeMap returns a mapping of field strings to int slices representing
104131
// the traversal down the struct to reach the field.
105132
func (m *Mapper) TypeMap(t reflect.Type) *StructMap {
106133
m.mutex.Lock()
107134
mapping, ok := m.cache[t]
108135
if !ok {
109-
mapping = getMapping(t, m.tagName, m.mapFunc, m.tagMapFunc)
136+
mapping = getMapping(t, m.tagName, m.mapFunc, m.tagMapFunc, m)
110137
m.cache[t] = mapping
111138
}
112139
m.mutex.Unlock()
@@ -122,7 +149,7 @@ func (m *Mapper) FieldMap(v reflect.Value) map[string]reflect.Value {
122149
r := map[string]reflect.Value{}
123150
tm := m.TypeMap(v.Type())
124151
for tagName, fi := range tm.Names {
125-
r[tagName] = FieldByIndexes(v, fi.Index)
152+
r[tagName] = FieldByIndexes(v, fi.Index, m)
126153
}
127154
return r
128155
}
@@ -139,7 +166,7 @@ func (m *Mapper) FieldByName(v reflect.Value, name string) reflect.Value {
139166
if !ok {
140167
return v
141168
}
142-
return FieldByIndexes(v, fi.Index)
169+
return FieldByIndexes(v, fi.Index, m)
143170
}
144171

145172
// FieldsByName returns a slice of values corresponding to the slice of names
@@ -156,7 +183,7 @@ func (m *Mapper) FieldsByName(v reflect.Value, names []string) []reflect.Value {
156183
if !ok {
157184
vals = append(vals, *new(reflect.Value))
158185
} else {
159-
vals = append(vals, FieldByIndexes(v, fi.Index))
186+
vals = append(vals, FieldByIndexes(v, fi.Index, m))
160187
}
161188
}
162189
return vals
@@ -203,16 +230,28 @@ func (m *Mapper) TraversalsByNameFunc(t reflect.Type, names []string, fn func(in
203230

204231
// FieldByIndexes returns a value for the field given by the struct traversal
205232
// for the given value.
206-
func FieldByIndexes(v reflect.Value, indexes []int) reflect.Value {
233+
func FieldByIndexes(v reflect.Value, indexes []int, m *Mapper) reflect.Value {
234+
tm := m.TypeMap(v.Type())
235+
fi := tm.Tree
207236
for _, i := range indexes {
208-
v = reflect.Indirect(v).Field(i)
209-
// if this is a pointer and it's nil, allocate a new value and set it
210-
if v.Kind() == reflect.Ptr && v.IsNil() {
211-
alloc := reflect.New(Deref(v.Type()))
212-
v.Set(alloc)
213-
}
214-
if v.Kind() == reflect.Map && v.IsNil() {
215-
v.Set(reflect.MakeMap(v.Type()))
237+
fi = fi.Children[i]
238+
if !fi.Virtual {
239+
v = reflect.Indirect(v).Field(i)
240+
// if this is a pointer and it's nil, allocate a new value and set it
241+
if v.Kind() == reflect.Ptr && v.IsNil() {
242+
alloc := reflect.New(Deref(v.Type()))
243+
v.Set(alloc)
244+
}
245+
if v.Kind() == reflect.Map && v.IsNil() {
246+
v.Set(reflect.MakeMap(v.Type()))
247+
}
248+
} else {
249+
alloc := reflect.New(Deref(fi.VirtualType))
250+
251+
method := alloc.MethodByName("SetDelegateField")
252+
method.Call([]reflect.Value{v})
253+
254+
return alloc
216255
}
217256
}
218257
return v
@@ -340,7 +379,7 @@ func parseOptions(tag string) map[string]string {
340379

341380
// getMapping returns a mapping for the t type, using the tagName, mapFunc and
342381
// tagMapFunc to determine the canonical names of fields.
343-
func getMapping(t reflect.Type, tagName string, mapFunc, tagMapFunc mapf) *StructMap {
382+
func getMapping(t reflect.Type, tagName string, mapFunc, tagMapFunc mapf, mapper *Mapper) *StructMap {
344383
m := []*FieldInfo{}
345384

346385
root := &FieldInfo{}
@@ -361,6 +400,34 @@ QueueLoop:
361400
}
362401

363402
nChildren := 0
403+
if fields, ok := mapper.registry[tq.t]; ok {
404+
nChildren = len(fields)
405+
tq.fi.Children = make([]*FieldInfo, nChildren)
406+
fieldPos := 0
407+
for name, field := range fields {
408+
//f := tq.t.Field(fieldPos)
409+
fi := FieldInfo{
410+
Field: reflect.StructField{},
411+
Name: name,
412+
Zero: reflect.New(field).Elem(),
413+
Virtual: true,
414+
VirtualType: field,
415+
}
416+
417+
if tq.pp == "" {
418+
fi.Path = fi.Name
419+
} else {
420+
fi.Path = tq.pp + "." + fi.Name
421+
}
422+
423+
fi.Index = apnd(tq.fi.Index, fieldPos)
424+
fi.Parent = tq.fi
425+
tq.fi.Children[fieldPos] = &fi
426+
m = append(m, &fi)
427+
}
428+
continue
429+
}
430+
364431
if tq.t.Kind() == reflect.Struct {
365432
nChildren = tq.t.NumField()
366433
}
@@ -435,5 +502,23 @@ QueueLoop:
435502
}
436503
}
437504

505+
duplicatedNames := make(map[string]bool)
506+
for _, fi := range flds.Index {
507+
if fi.Name != "" && !fi.Embedded {
508+
fld, ok := flds.Names[fi.Name]
509+
if ok {
510+
if fld.Parent.Parent != nil { // not first level field
511+
duplicatedNames[fi.Name] = true
512+
}
513+
} else {
514+
flds.Names[fi.Name] = fi
515+
}
516+
}
517+
}
518+
519+
for key := range duplicatedNames {
520+
delete(flds.Names, key)
521+
}
522+
438523
return flds
439524
}

0 commit comments

Comments
 (0)