Merge "Remove github.com/rubenv/sql-migrate and gopkg.in/yaml.v1"
diff --git a/go/src/github.com/jmoiron/sqlx/README.google b/go/src/github.com/jmoiron/sqlx/README.google
index 85f76b9..00e9c8d 100644
--- a/go/src/github.com/jmoiron/sqlx/README.google
+++ b/go/src/github.com/jmoiron/sqlx/README.google
@@ -1,5 +1,5 @@
-URL: https://github.com/jmoiron/sqlx/archive/69738bd209812c5a381786011aff17944a384554.zip
-Version: 69738bd209812c5a381786011aff17944a384554
+URL: https://github.com/jmoiron/sqlx/archive/398dd5876282499cdfd4cb8ea0f31a672abe9495.zip
+Version: 398dd5876282499cdfd4cb8ea0f31a672abe9495
License: MIT
License File: LICENSE
@@ -7,11 +7,8 @@
sqlx provides a set of extensions on go's standard database/sql library.
Local Modifications:
-
-Added newlines to sqlx_test.go debug output on lines 74 and 82 to prevent it
-from crashing our test runner.
-
-Remove the following imports from sqlx_test.go:
+- Added newlines to sqlx_test.go debug output on lines 77 and 87 to keep
+ our test runner happy
+- Removed the following import from sqlx_test.go:
* github.com/lib/pq
- * github.com/mattn/go-sqlite3
diff --git a/go/src/github.com/jmoiron/sqlx/README.md b/go/src/github.com/jmoiron/sqlx/README.md
index 4e3eb6d..55155d0 100644
--- a/go/src/github.com/jmoiron/sqlx/README.md
+++ b/go/src/github.com/jmoiron/sqlx/README.md
@@ -13,35 +13,32 @@
* Marshal rows into structs (with embedded struct support), maps, and slices
* Named parameter support including prepared statements
* `Get` and `Select` to go quickly from query to struct/slice
-* `LoadFile` for executing statements from a file
-There is now some [fairly comprehensive documentation](http://jmoiron.github.io/sqlx/) for sqlx.
-You can also read the usage below for a quick sample on how sqlx works, or check out the [API
-documentation on godoc](http://godoc.org/github.com/jmoiron/sqlx).
+In addition to the [godoc API documentation](http://godoc.org/github.com/jmoiron/sqlx),
+there is also some [standard documentation](http://jmoiron.github.io/sqlx/) that
+explains how to use `database/sql` along with sqlx.
## Recent Changes
-The ability to use basic types as Select and Get destinations was added. This
-is only valid when there is one column in the result set, and both functions
-return an error if this isn't the case. This allows for much simpler patterns
-of access for single column results:
+* sqlx/types.JsonText has been renamed to JSONText to follow Go naming conventions.
-```go
-var count int
-err := db.Get(&count, "SELECT count(*) FROM person;")
+This breaks backwards compatibility, but it's in a way that is trivially fixable
+(`s/JsonText/JSONText/g`). The `types` package is both experimental and not in
+active development currently.
-var names []string
-err := db.Select(&names, "SELECT name FROM person;")
-```
-
-See the note on Scannability at the bottom of this README for some more info.
+More importantly, [golang bug #13905](https://github.com/golang/go/issues/13905)
+makes `types.JSONText` and `types.GzippedText` _potentially unsafe_, **especially**
+when used with common auto-scan sqlx idioms like `Select` and `Get`.
### Backwards Compatibility
-There is no Go1-like promise of absolute stability, but I take the issue
-seriously and will maintain the library in a compatible state unless vital
-bugs prevent me from doing so. Since [#59](https://github.com/jmoiron/sqlx/issues/59) and [#60](https://github.com/jmoiron/sqlx/issues/60) necessitated
-breaking behavior, a wider API cleanup was done at the time of fixing.
+There is no Go1-like promise of absolute stability, but I take the issue seriously
+and will maintain the library in a compatible state unless vital bugs prevent me
+from doing so. Since [#59](https://github.com/jmoiron/sqlx/issues/59) and
+[#60](https://github.com/jmoiron/sqlx/issues/60) necessitated breaking behavior,
+a wider API cleanup was done at the time of fixing. It's possible this will happen
+in future; if it does, a git tag will be provided for users requiring the old
+behavior to continue to use it until such a time as they can migrate.
## install
@@ -50,21 +47,21 @@
## issues
Row headers can be ambiguous (`SELECT 1 AS a, 2 AS a`), and the result of
-`Columns()` can have duplicate names on queries like:
+`Columns()` does not fully qualify column names in queries like:
```sql
SELECT a.id, a.name, b.id, b.name FROM foos AS a JOIN foos AS b ON a.parent = b.id;
```
making a struct or map destination ambiguous. Use `AS` in your queries
-to give rows distinct names, `rows.Scan` to scan them manually, or
+to give columns distinct names, `rows.Scan` to scan them manually, or
`SliceScan` to get a slice of results.
## usage
Below is an example which shows some common use cases for sqlx. Check
[sqlx_test.go](https://github.com/jmoiron/sqlx/blob/master/sqlx_test.go) for more
-usage.
+usage.
```go
@@ -103,7 +100,7 @@
}
func main() {
- // this connects & tries a simple 'SELECT 1', panics on error
+ // this Pings the database trying to connect, panics on error
// use sqlx.Open() for sql.Open() semantics
db, err := sqlx.Connect("postgres", "user=foo dbname=bar sslmode=disable")
if err != nil {
@@ -186,73 +183,3 @@
}
```
-## Scannability
-
-Get and Select are able to take base types, so the following is now possible:
-
-```go
-var name string
-db.Get(&name, "SELECT first_name FROM person WHERE id=$1", 10)
-
-var ids []int64
-db.Select(&ids, "SELECT id FROM person LIMIT 20;")
-```
-
-This can get complicated with destination types which are structs, like `sql.NullString`. Because of this, straightforward rules for *scannability* had to be developed. Iff something is "Scannable", then it is used directly in `rows.Scan`; if it's not, then the standard sqlx struct rules apply.
-
-Something is scannable if any of the following are true:
-
-* It is not a struct, ie. `reflect.ValueOf(v).Kind() != reflect.Struct`
-* It implements the `sql.Scanner` interface
-* It has no exported fields (eg. `time.Time`)
-
-## embedded structs
-
-Scan targets obey Go attribute rules directly, including nested embedded structs. Older versions of sqlx would attempt to also descend into non-embedded structs, but this is no longer supported.
-
-Go makes *accessing* '[ambiguous selectors](http://play.golang.org/p/MGRxdjLaUc)' a compile time error, defining structs with ambiguous selectors is legal. Sqlx will decide which field to use on a struct based on a breadth first search of the struct and any structs it embeds, as specified by the order of the fields as accessible by `reflect`, which generally means in source-order. This means that sqlx chooses the outer-most, top-most matching name for targets, even when the selector might technically be ambiguous.
-
-## scan safety
-
-By default, scanning into structs requires the structs to have fields for all of the
-columns in the query. This was done for a few reasons:
-
-* A mistake in naming during development could lead you to believe that data is
- being written to a field when actually it can't be found and it is being dropped
-* This behavior mirrors the behavior of the Go compiler with respect to unused
- variables
-* Selecting more data than you need is wasteful (more data on the wire, more time
- marshalling, etc)
-
-Unlike Marshallers in the stdlib, the programmer scanning an sql result into a struct
-will generally have a full understanding of what the underlying data model is *and*
-full control over the SQL statement.
-
-Despite this, there are use cases where it's convenient to be able to ignore unknown
-columns. In most of these cases, you might be better off with `ScanSlice`, but where
-you want to still use structs, there is now the `Unsafe` method. Its usage is most
-simply shown in an example:
-
-```go
- db, err := sqlx.Connect("postgres", "user=foo dbname=bar sslmode=disable")
- if err != nil {
- log.Fatal(err)
- }
-
- type Person {
- Name string
- }
- var p Person
-
- // This fails, because there is no destination for location in Person
- err = db.Get(&p, "SELECT name, location FROM person LIMIT 1")
-
- udb := db.Unsafe()
-
- // This succeeds and just sets `Name` in the p struct
- err = udb.Get(&p, "SELECT name, location FROM person LIMIT 1")
-```
-
-The `Unsafe` method is implemented on `Tx`, `DB`, and `Stmt`. When you use an unsafe
-`Tx` or `DB` to create a new `Tx` or `Stmt`, those inherit its lack of safety.
-
diff --git a/go/src/github.com/jmoiron/sqlx/bind.go b/go/src/github.com/jmoiron/sqlx/bind.go
index 2f1ec2b..564635c 100644
--- a/go/src/github.com/jmoiron/sqlx/bind.go
+++ b/go/src/github.com/jmoiron/sqlx/bind.go
@@ -2,7 +2,12 @@
import (
"bytes"
+ "errors"
+ "reflect"
"strconv"
+ "strings"
+
+ "github.com/jmoiron/sqlx/reflectx"
)
// Bindvar types supported by Rebind, BindMap and BindStruct.
@@ -31,11 +36,10 @@
// FIXME: this should be able to be tolerant of escaped ?'s in queries without
// losing much speed, and should be to avoid confusion.
-// FIXME: this is now produces the wrong results for oracle's NAMED bindtype
-
// Rebind a query from the default bindtype (QUESTION) to the target bindtype.
func Rebind(bindType int, query string) string {
- if bindType != DOLLAR {
+ switch bindType {
+ case QUESTION, UNKNOWN:
return query
}
@@ -45,7 +49,12 @@
j := 1
for _, b := range qb {
if b == '?' {
- rqb = append(rqb, '$')
+ switch bindType {
+ case DOLLAR:
+ rqb = append(rqb, '$')
+ case NAMED:
+ rqb = append(rqb, ':', 'a', 'r', 'g')
+ }
for _, b := range strconv.Itoa(j) {
rqb = append(rqb, byte(b))
}
@@ -61,7 +70,6 @@
// much simpler and should be more resistant to odd unicode, but it is twice as
// slow. Kept here for benchmarking purposes and to possibly replace Rebind if
// problems arise with its somewhat naive handling of unicode.
-
func rebindBuff(bindType int, query string) string {
if bindType != DOLLAR {
return query
@@ -82,3 +90,97 @@
return rqb.String()
}
+
+// In expands slice values in args, returning the modified query string
+// and a new arg list that can be executed by a database. The `query` should
+// use the `?` bindVar. The return value uses the `?` bindVar.
+func In(query string, args ...interface{}) (string, []interface{}, error) {
+ // argMeta stores reflect.Value and length for slices and
+ // the value itself for non-slice arguments
+ type argMeta struct {
+ v reflect.Value
+ i interface{}
+ length int
+ }
+
+ var flatArgsCount int
+ var anySlices bool
+
+ meta := make([]argMeta, len(args))
+
+ for i, arg := range args {
+ v := reflect.ValueOf(arg)
+ t := reflectx.Deref(v.Type())
+
+ if t.Kind() == reflect.Slice {
+ meta[i].length = v.Len()
+ meta[i].v = v
+
+ anySlices = true
+ flatArgsCount += meta[i].length
+
+ if meta[i].length == 0 {
+ return "", nil, errors.New("empty slice passed to 'in' query")
+ }
+ } else {
+ meta[i].i = arg
+ flatArgsCount++
+ }
+ }
+
+ // don't do any parsing if there aren't any slices; note that this means
+ // some errors that we might have caught below will not be returned.
+ if !anySlices {
+ return query, args, nil
+ }
+
+ newArgs := make([]interface{}, 0, flatArgsCount)
+
+ var arg, offset int
+ var buf bytes.Buffer
+
+ for i := strings.IndexByte(query[offset:], '?'); i != -1; i = strings.IndexByte(query[offset:], '?') {
+ if arg >= len(meta) {
+ // if an argument wasn't passed, lets return an error; this is
+ // not actually how database/sql Exec/Query works, but since we are
+ // creating an argument list programmatically, we want to be able
+ // to catch these programmer errors earlier.
+ return "", nil, errors.New("number of bindVars exceeds arguments")
+ }
+
+ argMeta := meta[arg]
+ arg++
+
+ // not a slice, continue.
+ // our questionmark will either be written before the next expansion
+ // of a slice or after the loop when writing the rest of the query
+ if argMeta.length == 0 {
+ offset = offset + i + 1
+ newArgs = append(newArgs, argMeta.i)
+ continue
+ }
+
+ // write everything up to and including our ? character
+ buf.WriteString(query[:offset+i+1])
+
+ newArgs = append(newArgs, argMeta.v.Index(0).Interface())
+
+ for si := 1; si < argMeta.length; si++ {
+ buf.WriteString(", ?")
+ newArgs = append(newArgs, argMeta.v.Index(si).Interface())
+ }
+
+ // slice the query and reset the offset. this avoids some bookkeeping for
+ // the write after the loop
+ query = query[offset+i+1:]
+ offset = 0
+ }
+
+ buf.WriteString(query)
+
+ if arg < len(meta) {
+ return "", nil, errors.New("number of bindVars less than number arguments")
+ }
+
+ return buf.String(), newArgs, nil
+}
diff --git a/go/src/github.com/jmoiron/sqlx/named.go b/go/src/github.com/jmoiron/sqlx/named.go
index d753518..4df8095 100644
--- a/go/src/github.com/jmoiron/sqlx/named.go
+++ b/go/src/github.com/jmoiron/sqlx/named.go
@@ -79,7 +79,7 @@
if err != nil {
return nil, err
}
- return &Rows{Rows: r, Mapper: n.Stmt.Mapper}, err
+ return &Rows{Rows: r, Mapper: n.Stmt.Mapper, unsafe: isUnsafe(n)}, err
}
// QueryRowx this NamedStmt. Because of limitations with QueryRow, this is
@@ -90,7 +90,7 @@
// Select using this NamedStmt
func (n *NamedStmt) Select(dest interface{}, arg interface{}) error {
- rows, err := n.Query(arg)
+ rows, err := n.Queryx(arg)
if err != nil {
return err
}
@@ -105,6 +105,13 @@
return r.scanAny(dest, false)
}
+// Unsafe creates an unsafe version of the NamedStmt
+func (n *NamedStmt) Unsafe() *NamedStmt {
+ r := &NamedStmt{Params: n.Params, Stmt: n.Stmt, QueryString: n.QueryString}
+ r.Stmt.unsafe = true
+ return r
+}
+
// A union interface of preparer and binder, required to be able to prepare
// named statements (as the bindtype must be determined).
type namedPreparer interface {
@@ -286,11 +293,19 @@
return string(rebound), names, err
}
-// Bind binds a struct or a map to a query with named parameters.
+// BindNamed binds a struct or a map to a query with named parameters.
+// DEPRECATED: use sqlx.Named` instead of this, it may be removed in future.
func BindNamed(bindType int, query string, arg interface{}) (string, []interface{}, error) {
return bindNamedMapper(bindType, query, arg, mapper())
}
+// Named takes a query using named parameters and an argument and
+// returns a new query with a list of args that can be executed by
+// a database. The return value uses the `?` bindvar.
+func Named(query string, arg interface{}) (string, []interface{}, error) {
+ return bindNamedMapper(QUESTION, query, arg, mapper())
+}
+
func bindNamedMapper(bindType int, query string, arg interface{}, m *reflectx.Mapper) (string, []interface{}, error) {
if maparg, ok := arg.(map[string]interface{}); ok {
return bindMap(bindType, query, maparg)
diff --git a/go/src/github.com/jmoiron/sqlx/reflectx/reflect.go b/go/src/github.com/jmoiron/sqlx/reflectx/reflect.go
index d49b7bc..04d2080 100644
--- a/go/src/github.com/jmoiron/sqlx/reflectx/reflect.go
+++ b/go/src/github.com/jmoiron/sqlx/reflectx/reflect.go
@@ -1,25 +1,67 @@
-// Package reflect implements extensions to the standard reflect lib suitable
+// Package reflectx implements extensions to the standard reflect lib suitable
// for implementing marshaling and unmarshaling packages. The main Mapper type
-// allows for Go-compatible named atribute access, including accessing embedded
+// 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 "sync"
-
import (
+ "fmt"
"reflect"
"runtime"
+ "strings"
+ "sync"
)
-type fieldMap map[string][]int
+// A FieldInfo is a collection of metadata about 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.
+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, 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]fieldMap
+ cache map[reflect.Type]*StructMap
tagName string
tagMapFunc func(string) string
mapFunc func(string) string
@@ -30,7 +72,7 @@
// by tagName. If tagName is the empty string, it is ignored.
func NewMapper(tagName string) *Mapper {
return &Mapper{
- cache: make(map[reflect.Type]fieldMap),
+ cache: make(map[reflect.Type]*StructMap),
tagName: tagName,
}
}
@@ -40,7 +82,7 @@
// have values like "name,omitempty".
func NewMapperTagFunc(tagName string, mapFunc, tagMapFunc func(string) string) *Mapper {
return &Mapper{
- cache: make(map[reflect.Type]fieldMap),
+ cache: make(map[reflect.Type]*StructMap),
tagName: tagName,
mapFunc: mapFunc,
tagMapFunc: tagMapFunc,
@@ -52,7 +94,7 @@
// 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]fieldMap),
+ cache: make(map[reflect.Type]*StructMap),
tagName: tagName,
mapFunc: f,
}
@@ -60,7 +102,7 @@
// 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) fieldMap {
+func (m *Mapper) TypeMap(t reflect.Type) *StructMap {
m.mutex.Lock()
mapping, ok := m.cache[t]
if !ok {
@@ -78,9 +120,9 @@
mustBe(v, reflect.Struct)
r := map[string]reflect.Value{}
- nm := m.TypeMap(v.Type())
- for tagName, indexes := range nm {
- r[tagName] = FieldByIndexes(v, indexes)
+ tm := m.TypeMap(v.Type())
+ for tagName, fi := range tm.Names {
+ r[tagName] = FieldByIndexes(v, fi.Index)
}
return r
}
@@ -92,12 +134,12 @@
v = reflect.Indirect(v)
mustBe(v, reflect.Struct)
- nm := m.TypeMap(v.Type())
- traversal, ok := nm[name]
+ tm := m.TypeMap(v.Type())
+ fi, ok := tm.Names[name]
if !ok {
- return *new(reflect.Value)
+ return v
}
- return FieldByIndexes(v, traversal)
+ return FieldByIndexes(v, fi.Index)
}
// FieldsByName returns a slice of values corresponding to the slice of names
@@ -107,35 +149,34 @@
v = reflect.Indirect(v)
mustBe(v, reflect.Struct)
- nm := m.TypeMap(v.Type())
-
+ tm := m.TypeMap(v.Type())
vals := make([]reflect.Value, 0, len(names))
for _, name := range names {
- traversal, ok := nm[name]
+ fi, ok := tm.Names[name]
if !ok {
vals = append(vals, *new(reflect.Value))
} else {
- vals = append(vals, FieldByIndexes(v, traversal))
+ vals = append(vals, FieldByIndexes(v, fi.Index))
}
}
return vals
}
-// Traversals by name returns a slice of int slices which represent the struct
+// 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 {
t = Deref(t)
mustBe(t, reflect.Struct)
- nm := m.TypeMap(t)
+ tm := m.TypeMap(t)
r := make([][]int, 0, len(names))
for _, name := range names {
- traversal, ok := nm[name]
+ fi, ok := tm.Names[name]
if !ok {
r = append(r, []int{})
} else {
- r = append(r, traversal)
+ r = append(r, fi.Index)
}
}
return r
@@ -177,13 +218,13 @@
// -- helpers & utilities --
-type Kinder interface {
+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) {
+func mustBe(v kinder, expected reflect.Kind) {
k := v.Kind()
if k != expected {
panic(&reflect.ValueError{Method: methodName(), Kind: k})
@@ -201,8 +242,9 @@
}
type typeQueue struct {
- t reflect.Type
- p []int
+ t reflect.Type
+ fi *FieldInfo
+ pp string // Parent path
}
// A copying append that creates a new slice each time.
@@ -217,27 +259,65 @@
// 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) fieldMap {
+func getMapping(t reflect.Type, tagName string, mapFunc, tagMapFunc func(string) string) *StructMap {
+ m := []*FieldInfo{}
+
+ root := &FieldInfo{}
queue := []typeQueue{}
- queue = append(queue, typeQueue{Deref(t), []int{}})
- m := fieldMap{}
+ queue = append(queue, typeQueue{Deref(t), root, ""})
+
for len(queue) != 0 {
// pop the first item off of the queue
tq := queue[0]
queue = queue[1:]
+ 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 < tq.t.NumField(); fieldPos++ {
+ for fieldPos := 0; fieldPos < nChildren; fieldPos++ {
f := tq.t.Field(fieldPos)
- name := f.Tag.Get(tagName)
- if len(name) == 0 {
+ fi := FieldInfo{}
+ fi.Field = f
+ fi.Zero = reflect.New(f.Type).Elem()
+ fi.Options = map[string]string{}
+
+ var tag, name string
+ if tagName != "" && strings.Contains(string(f.Tag), tagName+":") {
+ tag = f.Tag.Get(tagName)
+ name = tag
+ } else {
if mapFunc != nil {
name = mapFunc(f.Name)
- } else {
- name = f.Name
}
- } else if tagMapFunc != nil {
- name = tagMapFunc(name)
+ }
+
+ parts := strings.Split(name, ",")
+ if len(parts) > 1 {
+ name = parts[0]
+ for _, opt := range parts[1:] {
+ kv := strings.Split(opt, "=")
+ if len(kv) > 1 {
+ fi.Options[kv[0]] = kv[1]
+ } else {
+ fi.Options[kv[0]] = ""
+ }
+ }
+ }
+
+ if tagMapFunc != nil {
+ tag = tagMapFunc(tag)
+ }
+
+ fi.Name = name
+
+ if tq.pp == "" || (tq.pp == "" && tag == "") {
+ fi.Path = fi.Name
+ } else {
+ fi.Path = fmt.Sprintf("%s.%s", tq.pp, fi.Name)
}
// if the name is "-", disabled via a tag, skip it
@@ -246,23 +326,46 @@
}
// skip unexported fields
- if len(f.PkgPath) != 0 {
+ if len(f.PkgPath) != 0 && !f.Anonymous {
continue
}
// bfs search of anonymous embedded structs
if f.Anonymous {
- queue = append(queue, typeQueue{Deref(f.Type), apnd(tq.p, fieldPos)})
- continue
+ 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})
}
- // if the name is shadowed by an earlier identical name in the search, skip it
- if _, ok := m[name]; ok {
- continue
- }
- // add it to the map at the current position
- m[name] = apnd(tq.p, fieldPos)
+ fi.Index = apnd(tq.fi.Index, fieldPos)
+ fi.Parent = tq.fi
+ tq.fi.Children[fieldPos] = &fi
+ m = append(m, &fi)
}
}
- return m
+
+ 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/go/src/github.com/jmoiron/sqlx/reflectx/reflect_test.go b/go/src/github.com/jmoiron/sqlx/reflectx/reflect_test.go
index 1fcba66..8072244 100644
--- a/go/src/github.com/jmoiron/sqlx/reflectx/reflect_test.go
+++ b/go/src/github.com/jmoiron/sqlx/reflectx/reflect_test.go
@@ -19,7 +19,7 @@
f := Foo{1, 2, 3}
fv := reflect.ValueOf(f)
- m := NewMapper("")
+ m := NewMapperFunc("", func(s string) string { return s })
v := m.FieldByName(fv, "A")
if ival(v) != f.A {
@@ -35,57 +35,425 @@
}
}
-func TestEmbedded(t *testing.T) {
+func TestBasicEmbedded(t *testing.T) {
type Foo struct {
A int
}
type Bar struct {
- Foo
- B int
+ Foo // `db:""` is implied for an embedded struct
+ B int
+ C int `db:"-"`
}
type Baz struct {
- A int
- Bar
+ A int
+ Bar `db:"Bar"`
}
- m := NewMapper("")
+ 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)
- v := m.FieldByName(zv, "A")
- if ival(v) != z.A {
- t.Errorf("Expecting %d, got %d", ival(v), z.A)
+ zv := reflect.ValueOf(z)
+ fields := m.TypeMap(reflect.TypeOf(z))
+
+ if len(fields.Index) != 5 {
+ t.Errorf("Expecting 5 fields")
}
- v = m.FieldByName(zv, "B")
+
+ // 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", 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 TestFieldsEmbedded(t *testing.T) {
+ m := NewMapper("db")
+
+ type Person struct {
+ Name string `db:"name"`
+ }
+ 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)
+ }
+
+ 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"`
+ 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]
- } else {
- return value
}
+ return value
})
strategy := Strategy{"1", "Alpah"}
mapping := m.TypeMap(reflect.TypeOf(strategy))
for _, key := range []string{"strategy_id", "STRATEGYNAME"} {
- if _, ok := mapping[key]; !ok {
+ if fi := mapping.GetByPath(key); fi == nil {
t.Errorf("Expecting to find key %s in mapping but did not.", key)
}
}
@@ -103,7 +471,7 @@
mapping := m.TypeMap(reflect.TypeOf(p))
for _, key := range []string{"id", "name", "wears_glasses"} {
- if _, ok := mapping[key]; !ok {
+ if fi := mapping.GetByPath(key); fi == nil {
t.Errorf("Expecting to find key %s in mapping but did not.", key)
}
}
@@ -116,10 +484,9 @@
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 _, ok := mapping[key]; !ok {
+ if fi := mapping.GetByPath(key); fi == nil {
t.Errorf("Expecting to find key %s in mapping but did not.", key)
}
-
}
type RugbyPlayer struct {
@@ -131,33 +498,14 @@
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 _, ok := mapping[key]; !ok {
+ if fi := mapping.GetByPath(key); fi == nil {
t.Errorf("Expecting to find key %s in mapping but did not.", key)
}
}
- if _, ok := mapping["isallblack"]; ok {
+ if fi := mapping.GetByPath("isallblack"); fi != nil {
t.Errorf("Expecting to ignore `IsAllBlack` field")
}
-
- type EmbeddedLiteral struct {
- Embedded struct {
- Person string
- Position int
- }
- IsIntense bool
- }
-
- e := EmbeddedLiteral{}
- mapping = m.TypeMap(reflect.TypeOf(e))
- //fmt.Printf("Mapping: %#v\n", mapping)
-
- //f := FieldByIndexes(reflect.ValueOf(e), mapping["isintense"])
- //fmt.Println(f, f.Interface())
-
- //tbn := m.TraversalsByName(reflect.TypeOf(e), []string{"isintense"})
- //fmt.Printf("%#v\n", tbn)
-
}
type E1 struct {
diff --git a/go/src/github.com/jmoiron/sqlx/sqlx.go b/go/src/github.com/jmoiron/sqlx/sqlx.go
index d195705..74e0a31 100644
--- a/go/src/github.com/jmoiron/sqlx/sqlx.go
+++ b/go/src/github.com/jmoiron/sqlx/sqlx.go
@@ -58,7 +58,7 @@
// it's not important that we use the right mapper for this particular object,
// we're only concerned on how many exported fields this struct has
m := mapper()
- if len(m.TypeMap(t)) == 0 {
+ if len(m.TypeMap(t).Index) == 0 {
return true
}
return false
@@ -105,29 +105,35 @@
// determine if any of our extensions are unsafe
func isUnsafe(i interface{}) bool {
- switch i.(type) {
+ switch v := i.(type) {
case Row:
- return i.(Row).unsafe
+ return v.unsafe
case *Row:
- return i.(*Row).unsafe
+ return v.unsafe
case Rows:
- return i.(Rows).unsafe
+ return v.unsafe
case *Rows:
- return i.(*Rows).unsafe
+ return v.unsafe
+ case NamedStmt:
+ return v.Stmt.unsafe
+ case *NamedStmt:
+ return v.Stmt.unsafe
case Stmt:
- return i.(Stmt).unsafe
+ return v.unsafe
+ case *Stmt:
+ return v.unsafe
case qStmt:
- return i.(qStmt).Stmt.unsafe
+ return v.unsafe
case *qStmt:
- return i.(*qStmt).Stmt.unsafe
+ return v.unsafe
case DB:
- return i.(DB).unsafe
+ return v.unsafe
case *DB:
- return i.(*DB).unsafe
+ return v.unsafe
case Tx:
- return i.(Tx).unsafe
+ return v.unsafe
case *Tx:
- return i.(*Tx).unsafe
+ return v.unsafe
case sql.Rows, *sql.Rows:
return false
default:
@@ -279,7 +285,7 @@
// BindNamed binds a query using the DB driver's bindvar type.
func (db *DB) BindNamed(query string, arg interface{}) (string, []interface{}, error) {
- return BindNamed(BindType(db.driverName), query, arg)
+ return bindNamedMapper(BindType(db.driverName), query, arg, db.Mapper)
}
// NamedQuery using this DB.
@@ -377,7 +383,7 @@
// BindNamed binds a query within a transaction's bindvar type.
func (tx *Tx) BindNamed(query string, arg interface{}) (string, []interface{}, error) {
- return BindNamed(BindType(tx.driverName), query, arg)
+ return bindNamedMapper(BindType(tx.driverName), query, arg, tx.Mapper)
}
// NamedQuery within a transaction.
@@ -428,18 +434,18 @@
// Stmtx returns a version of the prepared statement which runs within a transaction. Provided
// stmt can be either *sql.Stmt or *sqlx.Stmt.
func (tx *Tx) Stmtx(stmt interface{}) *Stmt {
- var st sql.Stmt
var s *sql.Stmt
- switch stmt.(type) {
- case sql.Stmt:
- st = stmt.(sql.Stmt)
- s = &st
+ switch v := stmt.(type) {
case Stmt:
- s = stmt.(Stmt).Stmt
+ s = v.Stmt
case *Stmt:
- s = stmt.(*Stmt).Stmt
+ s = v.Stmt
+ case sql.Stmt:
+ s = &v
case *sql.Stmt:
- s = stmt.(*sql.Stmt)
+ s = v
+ default:
+ panic(fmt.Sprintf("non-statement type %v passed to Stmtx", reflect.ValueOf(stmt).Type()))
}
return &Stmt{Stmt: tx.Stmt(s), Mapper: tx.Mapper}
}
@@ -473,35 +479,35 @@
// Select using the prepared statement.
func (s *Stmt) Select(dest interface{}, args ...interface{}) error {
- return Select(&qStmt{*s}, dest, "", args...)
+ return Select(&qStmt{s}, dest, "", args...)
}
// Get using the prepared statement.
func (s *Stmt) Get(dest interface{}, args ...interface{}) error {
- return Get(&qStmt{*s}, dest, "", args...)
+ return Get(&qStmt{s}, dest, "", args...)
}
// MustExec (panic) using this statement. Note that the query portion of the error
// output will be blank, as Stmt does not expose its query.
func (s *Stmt) MustExec(args ...interface{}) sql.Result {
- return MustExec(&qStmt{*s}, "", args...)
+ return MustExec(&qStmt{s}, "", args...)
}
// QueryRowx using this statement.
func (s *Stmt) QueryRowx(args ...interface{}) *Row {
- qs := &qStmt{*s}
+ qs := &qStmt{s}
return qs.QueryRowx("", args...)
}
// Queryx using this statement.
func (s *Stmt) Queryx(args ...interface{}) (*Rows, error) {
- qs := &qStmt{*s}
+ qs := &qStmt{s}
return qs.Queryx("", args...)
}
// qStmt is an unexposed wrapper which lets you use a Stmt as a Queryer & Execer by
// implementing those interfaces and ignoring the `query` argument.
-type qStmt struct{ Stmt }
+type qStmt struct{ *Stmt }
func (q *qStmt) Query(query string, args ...interface{}) (*sql.Rows, error) {
return q.Stmt.Query(args...)
@@ -738,7 +744,7 @@
}
// SliceScan a row, returning a []interface{} with values similar to MapScan.
-// This function is primarly intended for use where the number of columns
+// This function is primarily intended for use where the number of columns
// is not known. Because you can pass an []interface{} directly to Scan,
// it's recommended that you do that as it will not have to allocate new
// slices per row.
diff --git a/go/src/github.com/jmoiron/sqlx/sqlx_test.go b/go/src/github.com/jmoiron/sqlx/sqlx_test.go
index 2096004..3716c75 100644
--- a/go/src/github.com/jmoiron/sqlx/sqlx_test.go
+++ b/go/src/github.com/jmoiron/sqlx/sqlx_test.go
@@ -24,6 +24,7 @@
_ "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx/reflectx"
+ _ "github.com/mattn/go-sqlite3"
)
/* compile time checks that Db, Tx, Stmt (qStmt) implement expected interfaces */
@@ -56,6 +57,10 @@
TestMysql = mydsn != "skip"
TestSqlite = sqdsn != "skip"
+ if !strings.Contains(mydsn, "parseTime=true") {
+ mydsn += "?parseTime=true"
+ }
+
if TestPostgres {
pgdb, err = Connect("postgres", pgdsn)
if err != nil {
@@ -130,12 +135,20 @@
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;
`,
}
@@ -179,18 +192,6 @@
Person
}
-type Loop1 struct {
- Person
-}
-
-type Loop2 struct {
- Loop1
-}
-
-type Loop3 struct {
- Loop2
-}
-
type SliceMember struct {
Country string
City sql.NullString
@@ -200,7 +201,7 @@
}
// Note that because of field map caching, we need a new type here
-// if we've used Place already soemwhere in sqlx
+// if we've used Place already somewhere in sqlx
type CPlace Place
func MultiExec(e Execer, query string) {
@@ -252,6 +253,9 @@
} 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()
}
@@ -323,10 +327,61 @@
}
rowsx.Close()
+ // test Named stmt
+ if !isUnsafe(db) {
+ t.Error("Expected db to be unsafe, but it isn't")
+ }
+ nstmt, err := db.PrepareNamed(`SELECT * FROM person WHERE first_name != :name`)
+ if err != nil {
+ t.Fatal(err)
+ }
+ // its internal stmt should be marked unsafe
+ if !nstmt.Stmt.unsafe {
+ t.Error("expected NamedStmt to be unsafe but its underlying stmt did not inherit safety")
+ }
+ pps = []PersonPlus{}
+ err = nstmt.Select(&pps, map[string]interface{}{"name": "Jason"})
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(pps) != 1 {
+ t.Errorf("Expected 1 person back, got %d", len(pps))
+ }
+
+ // test it with a safe db
+ db.unsafe = false
+ if isUnsafe(db) {
+ t.Error("expected db to be safe but it isn't")
+ }
+ nstmt, err = db.PrepareNamed(`SELECT * FROM person WHERE first_name != :name`)
+ if err != nil {
+ t.Fatal(err)
+ }
+ // it should be safe
+ if isUnsafe(nstmt) {
+ t.Error("NamedStmt did not inherit safety")
+ }
+ nstmt.Unsafe()
+ if !isUnsafe(nstmt) {
+ t.Error("expected newly unsafed NamedStmt to be unsafe")
+ }
+ pps = []PersonPlus{}
+ err = nstmt.Select(&pps, map[string]interface{}{"name": "Jason"})
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(pps) != 1 {
+ t.Errorf("Expected 1 person back, got %d", len(pps))
+ }
+
})
}
func TestEmbeddedStructs(t *testing.T) {
+ type Loop1 struct{ Person }
+ type Loop2 struct{ Loop1 }
+ type Loop3 struct{ Loop2 }
+
RunWithSchema(defaultSchema, t, func(db *DB, t *testing.T) {
loadDefaultFixture(db, t)
peopleAndPlaces := []PersonPlace{}
@@ -411,6 +466,82 @@
})
}
+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 TestJoinQueryNamedPointerStructs(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 {
+ Emp1 *Employee `db:"emp1"`
+ Emp2 *Employee `db:"emp2"`
+ *Boss `db:"boss"`
+ }
+
+ err := db.Select(
+ &employees,
+ `SELECT emp.name "emp1.name", emp.id "emp1.id", emp.boss_id "emp1.boss_id",
+ emp.name "emp2.name", emp.id "emp2.id", emp.boss_id "emp2.boss_id",
+ boss.id "boss.id", boss.name "boss.name" FROM employees AS emp
+ JOIN employees AS boss ON emp.boss_id = boss.id
+ `)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ for _, em := range employees {
+ if len(em.Emp1.Name) == 0 || len(em.Emp2.Name) == 0 {
+ t.Errorf("Expected non zero lengthed name.")
+ }
+ if em.Emp1.BossID.Int64 != em.Boss.ID || em.Emp2.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)
@@ -483,9 +614,9 @@
}
p := Person{
- FirstName: sql.NullString{"ben", true},
- LastName: sql.NullString{"doe", true},
- Email: sql.NullString{"ben@doe.com", true},
+ FirstName: sql.NullString{String: "ben", Valid: true},
+ LastName: sql.NullString{String: "doe", Valid: true},
+ Email: sql.NullString{String: "ben@doe.com", Valid: true},
}
q1 := `INSERT INTO person (first_name, last_name, email) VALUES (:first_name, :last_name, :email)`
@@ -517,16 +648,16 @@
// queries and NamedStmt queries, which use different code paths internally.
old := *db.Mapper
- type JsonPerson struct {
+ type JSONPerson struct {
FirstName sql.NullString `json:"FIRST"`
LastName sql.NullString `json:"last_name"`
Email sql.NullString
}
- jp := JsonPerson{
- FirstName: sql.NullString{"ben", true},
- LastName: sql.NullString{"smith", true},
- Email: sql.NullString{"ben@smith.com", true},
+ jp := JSONPerson{
+ FirstName: sql.NullString{String: "ben", Valid: true},
+ LastName: sql.NullString{String: "smith", Valid: true},
+ Email: sql.NullString{String: "ben@smith.com", Valid: true},
}
db.Mapper = reflectx.NewMapperFunc("json", strings.ToUpper)
@@ -549,7 +680,7 @@
// Checks that a person pulled out of the db matches the one we put in
check := func(t *testing.T, rows *Rows) {
- jp = JsonPerson{}
+ jp = JSONPerson{}
for rows.Next() {
err = rows.StructScan(&jp)
if err != nil {
@@ -617,7 +748,7 @@
RunWithSchema(schema, t, func(db *DB, t *testing.T) {
type TT struct {
- Id int
+ ID int
Value *string
}
var v, v2 TT
@@ -625,14 +756,14 @@
db.MustExec(r(`INSERT INTO tt (id) VALUES (1)`))
db.Get(&v, r(`SELECT * FROM tt`))
- if v.Id != 1 {
- t.Errorf("Expecting id of 1, got %v", v.Id)
+ if v.ID != 1 {
+ t.Errorf("Expecting id of 1, got %v", v.ID)
}
if v.Value != nil {
- t.Errorf("Expecting NULL to map to nil, got %s", v.Value)
+ t.Errorf("Expecting NULL to map to nil, got %s", *v.Value)
}
- v.Id = 2
+ v.ID = 2
// NOTE: this incidentally uncovered a bug which was that named queries with
// pointer destinations would not work if the passed value here was not addressable,
// as reflectx.FieldByIndexes attempts to allocate nil pointer receivers for
@@ -641,11 +772,11 @@
db.NamedExec(`INSERT INTO tt (id, value) VALUES (:id, :value)`, v)
db.Get(&v2, r(`SELECT * FROM tt WHERE id=2`))
- if v.Id != v2.Id {
- t.Errorf("%v != %v", v.Id, v2.Id)
+ if v.ID != v2.ID {
+ t.Errorf("%v != %v", v.ID, v2.ID)
}
if v2.Value != nil {
- t.Errorf("Expecting NULL to map to nil, got %s", v.Value)
+ t.Errorf("Expecting NULL to map to nil, got %s", *v.Value)
}
})
}
@@ -1035,7 +1166,7 @@
t.Error(err)
}
if *pcount != count {
- t.Error("expected %d = %d", *pcount, count)
+ t.Errorf("expected %d = %d", *pcount, count)
}
// test Select...
@@ -1076,6 +1207,7 @@
t.Errorf("Should return error when using bogus driverName")
}
}
+
func TestRebind(t *testing.T) {
q1 := `INSERT INTO foo (a, b, c, d, e, f, g, h, i) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
q2 := `INSERT INTO foo (a, b, c) VALUES (?, ?, "foo"), ("Hi", ?, ?)`
@@ -1090,6 +1222,20 @@
if s2 != `INSERT INTO foo (a, b, c) VALUES ($1, $2, "foo"), ("Hi", $3, $4)` {
t.Errorf("q2 failed")
}
+
+ s1 = Rebind(NAMED, q1)
+ s2 = Rebind(NAMED, q2)
+
+ ex1 := `INSERT INTO foo (a, b, c, d, e, f, g, h, i) VALUES ` +
+ `(:arg1, :arg2, :arg3, :arg4, :arg5, :arg6, :arg7, :arg8, :arg9, :arg10)`
+ if s1 != ex1 {
+ t.Error("q1 failed on Named params")
+ }
+
+ ex2 := `INSERT INTO foo (a, b, c) VALUES (:arg1, :arg2, "foo"), ("Hi", :arg3, :arg4)`
+ if s2 != ex2 {
+ t.Error("q2 failed on Named params")
+ }
}
func TestBindMap(t *testing.T) {
@@ -1171,7 +1317,7 @@
for _, m := range messages {
_, err := db.NamedExec(q1, m)
if err != nil {
- t.Error(err)
+ t.Fatal(err)
}
}
var count int
@@ -1194,6 +1340,147 @@
})
}
+func TestIssue197(t *testing.T) {
+ // this test actually tests for a bug in database/sql:
+ // https://github.com/golang/go/issues/13905
+ // this potentially makes _any_ named type that is an alias for []byte
+ // unsafe to use in a lot of different ways (basically, unsafe to hold
+ // onto after loading from the database).
+ t.Skip()
+
+ type mybyte []byte
+ type Var struct{ Raw json.RawMessage }
+ type Var2 struct{ Raw []byte }
+ type Var3 struct{ Raw mybyte }
+ RunWithSchema(defaultSchema, t, func(db *DB, t *testing.T) {
+ var err error
+ var v, q Var
+ if err = db.Get(&v, `SELECT '{"a": "b"}' AS raw`); err != nil {
+ t.Fatal(err)
+ }
+ fmt.Printf("%s: v %s\n", db.DriverName(), v.Raw)
+ if err = db.Get(&q, `SELECT 'null' AS raw`); err != nil {
+ t.Fatal(err)
+ }
+ fmt.Printf("%s: v %s\n", db.DriverName(), v.Raw)
+
+ var v2, q2 Var2
+ if err = db.Get(&v2, `SELECT '{"a": "b"}' AS raw`); err != nil {
+ t.Fatal(err)
+ }
+ fmt.Printf("%s: v2 %s\n", db.DriverName(), v2.Raw)
+ if err = db.Get(&q2, `SELECT 'null' AS raw`); err != nil {
+ t.Fatal(err)
+ }
+ fmt.Printf("%s: v2 %s\n", db.DriverName(), v2.Raw)
+
+ var v3, q3 Var3
+ if err = db.QueryRow(`SELECT '{"a": "b"}' AS raw`).Scan(&v3.Raw); err != nil {
+ t.Fatal(err)
+ }
+ fmt.Printf("v3 %s\n", v3.Raw)
+ if err = db.QueryRow(`SELECT '{"c": "d"}' AS raw`).Scan(&q3.Raw); err != nil {
+ t.Fatal(err)
+ }
+ fmt.Printf("v3 %s\n", v3.Raw)
+ t.Fail()
+ })
+}
+
+func TestIn(t *testing.T) {
+ // some quite normal situations
+ type tr struct {
+ q string
+ args []interface{}
+ c int
+ }
+ tests := []tr{
+ {"SELECT * FROM foo WHERE x = ? AND v in (?) AND y = ?",
+ []interface{}{"foo", []int{0, 5, 7, 2, 9}, "bar"},
+ 7},
+ {"SELECT * FROM foo WHERE x in (?)",
+ []interface{}{[]int{1, 2, 3, 4, 5, 6, 7, 8}},
+ 8},
+ }
+ for _, test := range tests {
+ q, a, err := In(test.q, test.args...)
+ if err != nil {
+ t.Error(err)
+ }
+ if len(a) != test.c {
+ t.Errorf("Expected %d args, but got %d (%+v)", test.c, len(a), a)
+ }
+ if strings.Count(q, "?") != test.c {
+ t.Errorf("Expected %d bindVars, got %d", test.c, strings.Count(q, "?"))
+ }
+ }
+
+ // too many bindVars, but no slices, so short circuits parsing
+ // i'm not sure if this is the right behavior; this query/arg combo
+ // might not work, but we shouldn't parse if we don't need to
+ {
+ orig := "SELECT * FROM foo WHERE x = ? AND y = ?"
+ q, a, err := In(orig, "foo", "bar", "baz")
+ if err != nil {
+ t.Error(err)
+ }
+ if len(a) != 3 {
+ t.Errorf("Expected 3 args, but got %d (%+v)", len(a), a)
+ }
+ if q != orig {
+ t.Error("Expected unchanged query.")
+ }
+ }
+
+ tests = []tr{
+ // too many bindvars; slice present so should return error during parse
+ {"SELECT * FROM foo WHERE x = ? and y = ?",
+ []interface{}{"foo", []int{1, 2, 3}, "bar"},
+ 0},
+ // empty slice, should return error before parse
+ {"SELECT * FROM foo WHERE x = ?",
+ []interface{}{[]int{}},
+ 0},
+ // too *few* bindvars, should return an error
+ {"SELECT * FROM foo WHERE x = ? AND y in (?)",
+ []interface{}{[]int{1, 2, 3}},
+ 0},
+ }
+ for _, test := range tests {
+ _, _, err := In(test.q, test.args...)
+ if err == nil {
+ t.Error("Expected an error, but got nil.")
+ }
+ }
+ RunWithSchema(defaultSchema, t, func(db *DB, t *testing.T) {
+ loadDefaultFixture(db, t)
+ //tx.MustExec(tx.Rebind("INSERT INTO place (country, city, telcode) VALUES (?, ?, ?)"), "United States", "New York", "1")
+ //tx.MustExec(tx.Rebind("INSERT INTO place (country, telcode) VALUES (?, ?)"), "Hong Kong", "852")
+ //tx.MustExec(tx.Rebind("INSERT INTO place (country, telcode) VALUES (?, ?)"), "Singapore", "65")
+ telcodes := []int{852, 65}
+ q := "SELECT * FROM place WHERE telcode IN(?) ORDER BY telcode"
+ query, args, err := In(q, telcodes)
+ if err != nil {
+ t.Error(err)
+ }
+ query = db.Rebind(query)
+ places := []Place{}
+ err = db.Select(&places, query, args...)
+ if err != nil {
+ t.Error(err)
+ }
+ if len(places) != 2 {
+ t.Fatalf("Expecting 2 results, got %d", len(places))
+ }
+ if places[0].TelCode != 65 {
+ t.Errorf("Expecting singapore first, but got %#v", places[0])
+ }
+ if places[1].TelCode != 852 {
+ t.Errorf("Expecting hong kong second, but got %#v", places[1])
+ }
+ })
+}
+
func TestBindStruct(t *testing.T) {
var err error
@@ -1319,7 +1606,6 @@
if *target2.K != "one" {
t.Errorf("Expected target2.K to be `one`, got `%v`", target2.K)
}
-
})
}
@@ -1336,7 +1622,6 @@
b.StartTimer()
for i := 0; i < b.N; i++ {
bindStruct(DOLLAR, q1, am, mapper())
- //bindMap(QUESTION, q1, am)
}
}
@@ -1352,7 +1637,14 @@
b.StartTimer()
for i := 0; i < b.N; i++ {
bindMap(DOLLAR, q1, am)
- //bindMap(QUESTION, q1, am)
+ }
+}
+
+func BenchmarkIn(b *testing.B) {
+ q := `SELECT * FROM foo WHERE x = ? AND v in (?) AND y = ?`
+
+ for i := 0; i < b.N; i++ {
+ _, _, _ = In(q, []interface{}{"foo", []int{0, 5, 7, 2, 9}, "bar"}...)
}
}
diff --git a/go/src/github.com/jmoiron/sqlx/types/types.go b/go/src/github.com/jmoiron/sqlx/types/types.go
index f1700b6..53848bc 100644
--- a/go/src/github.com/jmoiron/sqlx/types/types.go
+++ b/go/src/github.com/jmoiron/sqlx/types/types.go
@@ -10,8 +10,12 @@
"io/ioutil"
)
+// GzippedText is a []byte which transparently gzips data being submitted to
+// a database and ungzips data being Scanned from a database.
type GzippedText []byte
+// Value implements the driver.Valuer interface, gzipping the raw value of
+// this GzippedText.
func (g GzippedText) Value() (driver.Value, error) {
b := make([]byte, 0, len(g))
buf := bytes.NewBuffer(b)
@@ -22,6 +26,8 @@
}
+// Scan implements the sql.Scanner interface, ungzipping the value coming off
+// the wire and storing the raw result in the GzippedText.
func (g *GzippedText) Scan(src interface{}) error {
var source []byte
switch src.(type) {
@@ -42,21 +48,21 @@
return nil
}
-// JsonText is a json.RawMessage, which is a []byte underneath.
+// JSONText is a json.RawMessage, which is a []byte underneath.
// Value() validates the json format in the source, and returns an error if
-// the json is not valid. Scan does no validation. JsonText additionally
+// the json is not valid. Scan does no validation. JSONText additionally
// implements `Unmarshal`, which unmarshals the json within to an interface{}
-type JsonText json.RawMessage
+type JSONText json.RawMessage
-// Returns the *j as the JSON encoding of j.
-func (j *JsonText) MarshalJSON() ([]byte, error) {
+// MarshalJSON returns the *j as the JSON encoding of j.
+func (j *JSONText) MarshalJSON() ([]byte, error) {
return *j, nil
}
// UnmarshalJSON sets *j to a copy of data
-func (j *JsonText) UnmarshalJSON(data []byte) error {
+func (j *JSONText) UnmarshalJSON(data []byte) error {
if j == nil {
- return errors.New("JsonText: UnmarshalJSON on nil pointer")
+ return errors.New("JSONText: UnmarshalJSON on nil pointer")
}
*j = append((*j)[0:0], data...)
return nil
@@ -65,7 +71,7 @@
// Value returns j as a value. This does a validating unmarshal into another
// RawMessage. If j is invalid json, it returns an error.
-func (j JsonText) Value() (driver.Value, error) {
+func (j JSONText) Value() (driver.Value, error) {
var m json.RawMessage
var err = j.Unmarshal(&m)
if err != nil {
@@ -75,7 +81,7 @@
}
// Scan stores the src in *j. No validation is done.
-func (j *JsonText) Scan(src interface{}) error {
+func (j *JSONText) Scan(src interface{}) error {
var source []byte
switch src.(type) {
case string:
@@ -83,13 +89,18 @@
case []byte:
source = src.([]byte)
default:
- return errors.New("Incompatible type for JsonText")
+ return errors.New("Incompatible type for JSONText")
}
- *j = JsonText(append((*j)[0:0], source...))
+ *j = JSONText(append((*j)[0:0], source...))
return nil
}
// Unmarshal unmarshal's the json in j to v, as in json.Unmarshal.
-func (j *JsonText) Unmarshal(v interface{}) error {
+func (j *JSONText) Unmarshal(v interface{}) error {
return json.Unmarshal([]byte(*j), v)
}
+
+// Pretty printing for JSONText types
+func (j JSONText) String() string {
+ return string(j)
+}
diff --git a/go/src/github.com/jmoiron/sqlx/types/types_test.go b/go/src/github.com/jmoiron/sqlx/types/types_test.go
index e5c9e1a..78a1ef8 100644
--- a/go/src/github.com/jmoiron/sqlx/types/types_test.go
+++ b/go/src/github.com/jmoiron/sqlx/types/types_test.go
@@ -17,8 +17,8 @@
}
}
-func TestJsonText(t *testing.T) {
- j := JsonText(`{"foo": 1, "bar": 2}`)
+func TestJSONText(t *testing.T) {
+ j := JSONText(`{"foo": 1, "bar": 2}`)
v, err := j.Value()
if err != nil {
t.Errorf("Was not expecting an error")
@@ -34,7 +34,7 @@
t.Errorf("Expected valid json but got some garbage instead? %#v", m)
}
- j = JsonText(`{"foo": 1, invalid, false}`)
+ j = JSONText(`{"foo": 1, invalid, false}`)
v, err = j.Value()
if err == nil {
t.Errorf("Was expecting invalid json to fail!")