diff options
Diffstat (limited to 'dependencies/pkg/mod/github.com/jmoiron')
23 files changed, 8414 insertions, 0 deletions
diff --git a/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/.gitignore b/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/.gitignore new file mode 100644 index 0000000..b2be23c --- /dev/null +++ b/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/.gitignore @@ -0,0 +1,25 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test +.idea + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +tags +environ diff --git a/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/.travis.yml b/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/.travis.yml new file mode 100644 index 0000000..1cfa28c --- /dev/null +++ b/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/.travis.yml @@ -0,0 +1,26 @@ +# vim: ft=yaml sw=2 ts=2 + +language: go + +# enable database services +services: + - mysql + - postgresql + +# create test database +before_install: + - mysql -e 'CREATE DATABASE IF NOT EXISTS sqlxtest;' + - psql -c 'create database sqlxtest;' -U postgres + - go get github.com/mattn/goveralls + - export SQLX_MYSQL_DSN="travis:@/sqlxtest?parseTime=true" + - export SQLX_POSTGRES_DSN="postgres://postgres:@localhost/sqlxtest?sslmode=disable" + - export SQLX_SQLITE_DSN="$HOME/sqlxtest.db" + +# go versions to test +go: + - "1.15.x" + - "1.16.x" + +# run tests w/ coverage +script: + - travis_retry $GOPATH/bin/goveralls -service=travis-ci diff --git a/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/LICENSE b/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/LICENSE new file mode 100644 index 0000000..0d31edf --- /dev/null +++ b/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/LICENSE @@ -0,0 +1,23 @@ + Copyright (c) 2013, Jason Moiron + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + diff --git a/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/README.md b/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/README.md new file mode 100644 index 0000000..0d71592 --- /dev/null +++ b/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/README.md @@ -0,0 +1,213 @@ +# sqlx + +[![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) + +sqlx is a library which provides a set of extensions on go's standard +`database/sql` library. The sqlx versions of `sql.DB`, `sql.TX`, `sql.Stmt`, +et al. all leave the underlying interfaces untouched, so that their interfaces +are a superset on the standard ones. This makes it relatively painless to +integrate existing codebases using database/sql with sqlx. + +Major additional concepts are: + +* 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 + +In addition to the [godoc API documentation](http://godoc.org/github.com/jmoiron/sqlx), +there is also some [user documentation](http://jmoiron.github.io/sqlx/) that +explains how to use `database/sql` along with sqlx. + +## Recent Changes + +1.3.0: + +* `sqlx.DB.Connx(context.Context) *sqlx.Conn` +* `sqlx.BindDriver(driverName, bindType)` +* support for `[]map[string]interface{}` to do "batch" insertions +* allocation & perf improvements for `sqlx.In` + +DB.Connx returns an `sqlx.Conn`, which is an `sql.Conn`-alike consistent with +sqlx's wrapping of other types. + +`BindDriver` allows users to control the bindvars that sqlx will use for drivers, +and add new drivers at runtime. This results in a very slight performance hit +when resolving the driver into a bind type (~40ns per call), but it allows users +to specify what bindtype their driver uses even when sqlx has not been updated +to know about it by default. + +### Backwards Compatibility + +Compatibility with the most recent two versions of Go is a requirement for any +new changes. Compatibility beyond that is not guaranteed. + +Versioning is done with Go modules. Breaking changes (eg. removing deprecated API) +will get major version number bumps. + +## install + + go get github.com/jmoiron/sqlx + +## issues + +Row headers can be ambiguous (`SELECT 1 AS a, 2 AS a`), and the result of +`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 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. + + +```go +package main + +import ( + "database/sql" + "fmt" + "log" + + _ "github.com/lib/pq" + "github.com/jmoiron/sqlx" +) + +var schema = ` +CREATE TABLE person ( + first_name text, + last_name text, + email text +); + +CREATE TABLE place ( + country text, + city text NULL, + telcode integer +)` + +type Person struct { + FirstName string `db:"first_name"` + LastName string `db:"last_name"` + Email string +} + +type Place struct { + Country string + City sql.NullString + TelCode int +} + +func main() { + // this Pings the database trying to connect + // use sqlx.Open() for sql.Open() semantics + db, err := sqlx.Connect("postgres", "user=foo dbname=bar sslmode=disable") + if err != nil { + log.Fatalln(err) + } + + // exec the schema or fail; multi-statement Exec behavior varies between + // database drivers; pq will exec them all, sqlite3 won't, ymmv + db.MustExec(schema) + + tx := db.MustBegin() + tx.MustExec("INSERT INTO person (first_name, last_name, email) VALUES ($1, $2, $3)", "Jason", "Moiron", "jmoiron@jmoiron.net") + tx.MustExec("INSERT INTO person (first_name, last_name, email) VALUES ($1, $2, $3)", "John", "Doe", "johndoeDNE@gmail.net") + tx.MustExec("INSERT INTO place (country, city, telcode) VALUES ($1, $2, $3)", "United States", "New York", "1") + tx.MustExec("INSERT INTO place (country, telcode) VALUES ($1, $2)", "Hong Kong", "852") + tx.MustExec("INSERT INTO place (country, telcode) VALUES ($1, $2)", "Singapore", "65") + // Named queries can use structs, so if you have an existing struct (i.e. person := &Person{}) that you have populated, you can pass it in as &person + tx.NamedExec("INSERT INTO person (first_name, last_name, email) VALUES (:first_name, :last_name, :email)", &Person{"Jane", "Citizen", "jane.citzen@example.com"}) + tx.Commit() + + // Query the database, storing results in a []Person (wrapped in []interface{}) + people := []Person{} + db.Select(&people, "SELECT * FROM person ORDER BY first_name ASC") + jason, john := people[0], people[1] + + fmt.Printf("%#v\n%#v", jason, john) + // Person{FirstName:"Jason", LastName:"Moiron", Email:"jmoiron@jmoiron.net"} + // Person{FirstName:"John", LastName:"Doe", Email:"johndoeDNE@gmail.net"} + + // You can also get a single result, a la QueryRow + jason = Person{} + err = db.Get(&jason, "SELECT * FROM person WHERE first_name=$1", "Jason") + fmt.Printf("%#v\n", jason) + // Person{FirstName:"Jason", LastName:"Moiron", Email:"jmoiron@jmoiron.net"} + + // if you have null fields and use SELECT *, you must use sql.Null* in your struct + places := []Place{} + err = db.Select(&places, "SELECT * FROM place ORDER BY telcode ASC") + if err != nil { + fmt.Println(err) + return + } + usa, singsing, honkers := places[0], places[1], places[2] + + fmt.Printf("%#v\n%#v\n%#v\n", usa, singsing, honkers) + // Place{Country:"United States", City:sql.NullString{String:"New York", Valid:true}, TelCode:1} + // Place{Country:"Singapore", City:sql.NullString{String:"", Valid:false}, TelCode:65} + // Place{Country:"Hong Kong", City:sql.NullString{String:"", Valid:false}, TelCode:852} + + // Loop through rows using only one struct + place := Place{} + rows, err := db.Queryx("SELECT * FROM place") + for rows.Next() { + err := rows.StructScan(&place) + if err != nil { + log.Fatalln(err) + } + fmt.Printf("%#v\n", place) + } + // Place{Country:"United States", City:sql.NullString{String:"New York", Valid:true}, TelCode:1} + // Place{Country:"Hong Kong", City:sql.NullString{String:"", Valid:false}, TelCode:852} + // Place{Country:"Singapore", City:sql.NullString{String:"", Valid:false}, TelCode:65} + + // Named queries, using `:name` as the bindvar. Automatic bindvar support + // which takes into account the dbtype based on the driverName on sqlx.Open/Connect + _, err = db.NamedExec(`INSERT INTO person (first_name,last_name,email) VALUES (:first,:last,:email)`, + map[string]interface{}{ + "first": "Bin", + "last": "Smuth", + "email": "bensmith@allblacks.nz", + }) + + // Selects Mr. Smith from the database + rows, err = db.NamedQuery(`SELECT * FROM person WHERE first_name=:fn`, map[string]interface{}{"fn": "Bin"}) + + // Named queries can also use structs. Their bind names follow the same rules + // as the name -> db mapping, so struct fields are lowercased and the `db` tag + // is taken into consideration. + rows, err = db.NamedQuery(`SELECT * FROM person WHERE first_name=:first_name`, jason) + + + // batch insert + + // batch insert with structs + personStructs := []Person{ + {FirstName: "Ardie", LastName: "Savea", Email: "asavea@ab.co.nz"}, + {FirstName: "Sonny Bill", LastName: "Williams", Email: "sbw@ab.co.nz"}, + {FirstName: "Ngani", LastName: "Laumape", Email: "nlaumape@ab.co.nz"}, + } + + _, err = db.NamedExec(`INSERT INTO person (first_name, last_name, email) + VALUES (:first_name, :last_name, :email)`, personStructs) + + // batch insert with maps + personMaps := []map[string]interface{}{ + {"first_name": "Ardie", "last_name": "Savea", "email": "asavea@ab.co.nz"}, + {"first_name": "Sonny Bill", "last_name": "Williams", "email": "sbw@ab.co.nz"}, + {"first_name": "Ngani", "last_name": "Laumape", "email": "nlaumape@ab.co.nz"}, + } + + _, err = db.NamedExec(`INSERT INTO person (first_name, last_name, email) + VALUES (:first_name, :last_name, :email)`, personMaps) +} +``` diff --git a/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/bind.go b/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/bind.go new file mode 100644 index 0000000..ec0da4e --- /dev/null +++ b/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/bind.go @@ -0,0 +1,265 @@ +package sqlx + +import ( + "bytes" + "database/sql/driver" + "errors" + "reflect" + "strconv" + "strings" + "sync" + + "github.com/jmoiron/sqlx/reflectx" +) + +// Bindvar types supported by Rebind, BindMap and BindStruct. +const ( + UNKNOWN = iota + QUESTION + DOLLAR + NAMED + AT +) + +var defaultBinds = map[int][]string{ + DOLLAR: []string{"postgres", "pgx", "pq-timeouts", "cloudsqlpostgres", "ql", "nrpostgres", "cockroach"}, + QUESTION: []string{"mysql", "sqlite3", "nrmysql", "nrsqlite3"}, + NAMED: []string{"oci8", "ora", "goracle", "godror"}, + AT: []string{"sqlserver"}, +} + +var binds sync.Map + +func init() { + for bind, drivers := range defaultBinds { + for _, driver := range drivers { + BindDriver(driver, bind) + } + } + +} + +// BindType returns the bindtype for a given database given a drivername. +func BindType(driverName string) int { + itype, ok := binds.Load(driverName) + if !ok { + return UNKNOWN + } + return itype.(int) +} + +// BindDriver sets the BindType for driverName to bindType. +func BindDriver(driverName string, bindType int) { + binds.Store(driverName, bindType) +} + +// FIXME: this should be able to be tolerant of escaped ?'s in queries without +// losing much speed, and should be to avoid confusion. + +// Rebind a query from the default bindtype (QUESTION) to the target bindtype. +func Rebind(bindType int, query string) string { + switch bindType { + case QUESTION, UNKNOWN: + return query + } + + // Add space enough for 10 params before we have to allocate + rqb := make([]byte, 0, len(query)+10) + + var i, j int + + for i = strings.Index(query, "?"); i != -1; i = strings.Index(query, "?") { + rqb = append(rqb, query[:i]...) + + switch bindType { + case DOLLAR: + rqb = append(rqb, '$') + case NAMED: + rqb = append(rqb, ':', 'a', 'r', 'g') + case AT: + rqb = append(rqb, '@', 'p') + } + + j++ + rqb = strconv.AppendInt(rqb, int64(j), 10) + + query = query[i+1:] + } + + return string(append(rqb, query...)) +} + +// Experimental implementation of Rebind which uses a bytes.Buffer. The code is +// 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 + } + + b := make([]byte, 0, len(query)) + rqb := bytes.NewBuffer(b) + j := 1 + for _, r := range query { + if r == '?' { + rqb.WriteRune('$') + rqb.WriteString(strconv.Itoa(j)) + j++ + } else { + rqb.WriteRune(r) + } + } + + return rqb.String() +} + +func asSliceForIn(i interface{}) (v reflect.Value, ok bool) { + if i == nil { + return reflect.Value{}, false + } + + v = reflect.ValueOf(i) + t := reflectx.Deref(v.Type()) + + // Only expand slices + if t.Kind() != reflect.Slice { + return reflect.Value{}, false + } + + // []byte is a driver.Value type so it should not be expanded + if t == reflect.TypeOf([]byte{}) { + return reflect.Value{}, false + + } + + return v, true +} + +// 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 + + var stackMeta [32]argMeta + + var meta []argMeta + if len(args) <= len(stackMeta) { + meta = stackMeta[:len(args)] + } else { + meta = make([]argMeta, len(args)) + } + + for i, arg := range args { + if a, ok := arg.(driver.Valuer); ok { + var err error + arg, err = a.Value() + if err != nil { + return "", nil, err + } + } + + if v, ok := asSliceForIn(arg); ok { + 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 buf strings.Builder + buf.Grow(len(query) + len(", ?")*flatArgsCount) + + var arg, offset int + + 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]) + + for si := 1; si < argMeta.length; si++ { + buf.WriteString(", ?") + } + + newArgs = appendReflectSlice(newArgs, argMeta.v, argMeta.length) + + // 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 +} + +func appendReflectSlice(args []interface{}, v reflect.Value, vlen int) []interface{} { + switch val := v.Interface().(type) { + case []interface{}: + args = append(args, val...) + case []int: + for i := range val { + args = append(args, val[i]) + } + case []string: + for i := range val { + args = append(args, val[i]) + } + default: + for si := 0; si < vlen; si++ { + args = append(args, v.Index(si).Interface()) + } + } + + return args +} diff --git a/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/bind_test.go b/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/bind_test.go new file mode 100644 index 0000000..dfa590e --- /dev/null +++ b/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/bind_test.go @@ -0,0 +1,79 @@ +package sqlx + +import ( + "math/rand" + "testing" +) + +func oldBindType(driverName string) int { + switch driverName { + case "postgres", "pgx", "pq-timeouts", "cloudsqlpostgres", "ql": + return DOLLAR + case "mysql": + return QUESTION + case "sqlite3": + return QUESTION + case "oci8", "ora", "goracle", "godror": + return NAMED + case "sqlserver": + return AT + } + return UNKNOWN +} + +/* +sync.Map implementation: + +goos: linux +goarch: amd64 +pkg: github.com/jmoiron/sqlx +BenchmarkBindSpeed/old-4 100000000 11.0 ns/op +BenchmarkBindSpeed/new-4 24575726 50.8 ns/op + + +async.Value map implementation: + +goos: linux +goarch: amd64 +pkg: github.com/jmoiron/sqlx +BenchmarkBindSpeed/old-4 100000000 11.0 ns/op +BenchmarkBindSpeed/new-4 42535839 27.5 ns/op +*/ + +func BenchmarkBindSpeed(b *testing.B) { + testDrivers := []string{ + "postgres", "pgx", "mysql", "sqlite3", "ora", "sqlserver", + } + + b.Run("old", func(b *testing.B) { + b.StopTimer() + var seq []int + for i := 0; i < b.N; i++ { + seq = append(seq, rand.Intn(len(testDrivers))) + } + b.StartTimer() + for i := 0; i < b.N; i++ { + s := oldBindType(testDrivers[seq[i]]) + if s == UNKNOWN { + b.Error("unknown driver") + } + } + + }) + + b.Run("new", func(b *testing.B) { + b.StopTimer() + var seq []int + for i := 0; i < b.N; i++ { + seq = append(seq, rand.Intn(len(testDrivers))) + } + b.StartTimer() + for i := 0; i < b.N; i++ { + s := BindType(testDrivers[seq[i]]) + if s == UNKNOWN { + b.Error("unknown driver") + } + } + + }) +} diff --git a/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/doc.go b/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/doc.go new file mode 100644 index 0000000..e2b4e60 --- /dev/null +++ b/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/doc.go @@ -0,0 +1,12 @@ +// Package sqlx provides general purpose extensions to database/sql. +// +// It is intended to seamlessly wrap database/sql and provide convenience +// methods which are useful in the development of database driven applications. +// None of the underlying database/sql methods are changed. Instead all extended +// behavior is implemented through new methods defined on wrapper types. +// +// Additions include scanning into structs, named query support, rebinding +// queries for different drivers, convenient shorthands for common error handling +// and more. +// +package sqlx diff --git a/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/go.mod b/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/go.mod new file mode 100644 index 0000000..8d5aeba --- /dev/null +++ b/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/go.mod @@ -0,0 +1,9 @@ +module github.com/jmoiron/sqlx + +go 1.10 + +require ( + github.com/go-sql-driver/mysql v1.6.0 + github.com/lib/pq v1.2.0 + github.com/mattn/go-sqlite3 v1.14.6 +) diff --git a/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/go.sum b/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/go.sum new file mode 100644 index 0000000..7aac5d8 --- /dev/null +++ b/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/go.sum @@ -0,0 +1,6 @@ +github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= +github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= diff --git a/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/named.go b/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/named.go new file mode 100644 index 0000000..728aa04 --- /dev/null +++ b/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/named.go @@ -0,0 +1,458 @@ +package sqlx + +// Named Query Support +// +// * BindMap - bind query bindvars to map/struct args +// * NamedExec, NamedQuery - named query w/ struct or map +// * NamedStmt - a pre-compiled named query which is a prepared statement +// +// Internal Interfaces: +// +// * compileNamedQuery - rebind a named query, returning a query and list of names +// * bindArgs, bindMapArgs, bindAnyArgs - given a list of names, return an arglist +// +import ( + "bytes" + "database/sql" + "errors" + "fmt" + "reflect" + "regexp" + "strconv" + "unicode" + + "github.com/jmoiron/sqlx/reflectx" +) + +// NamedStmt is a prepared statement that executes named queries. Prepare it +// how you would execute a NamedQuery, but pass in a struct or map when executing. +type NamedStmt struct { + Params []string + QueryString string + Stmt *Stmt +} + +// Close closes the named statement. +func (n *NamedStmt) Close() error { + return n.Stmt.Close() +} + +// Exec executes a named statement using the struct passed. +// Any named placeholder parameters are replaced with fields from arg. +func (n *NamedStmt) Exec(arg interface{}) (sql.Result, error) { + args, err := bindAnyArgs(n.Params, arg, n.Stmt.Mapper) + if err != nil { + return *new(sql.Result), err + } + return n.Stmt.Exec(args...) +} + +// Query executes a named statement using the struct argument, returning rows. +// Any named placeholder parameters are replaced with fields from arg. +func (n *NamedStmt) Query(arg interface{}) (*sql.Rows, error) { + args, err := bindAnyArgs(n.Params, arg, n.Stmt.Mapper) + if err != nil { + return nil, err + } + return n.Stmt.Query(args...) +} + +// QueryRow executes a named statement against the database. Because sqlx cannot +// create a *sql.Row with an error condition pre-set for binding errors, sqlx +// returns a *sqlx.Row instead. +// Any named placeholder parameters are replaced with fields from arg. +func (n *NamedStmt) QueryRow(arg interface{}) *Row { + args, err := bindAnyArgs(n.Params, arg, n.Stmt.Mapper) + if err != nil { + return &Row{err: err} + } + return n.Stmt.QueryRowx(args...) +} + +// MustExec execs a NamedStmt, panicing on error +// Any named placeholder parameters are replaced with fields from arg. +func (n *NamedStmt) MustExec(arg interface{}) sql.Result { + res, err := n.Exec(arg) + if err != nil { + panic(err) + } + return res +} + +// Queryx using this NamedStmt +// Any named placeholder parameters are replaced with fields from arg. +func (n *NamedStmt) Queryx(arg interface{}) (*Rows, error) { + r, err := n.Query(arg) + if err != nil { + return nil, err + } + return &Rows{Rows: r, Mapper: n.Stmt.Mapper, unsafe: isUnsafe(n)}, err +} + +// QueryRowx this NamedStmt. Because of limitations with QueryRow, this is +// an alias for QueryRow. +// Any named placeholder parameters are replaced with fields from arg. +func (n *NamedStmt) QueryRowx(arg interface{}) *Row { + return n.QueryRow(arg) +} + +// Select using this NamedStmt +// Any named placeholder parameters are replaced with fields from arg. +func (n *NamedStmt) Select(dest interface{}, arg interface{}) error { + rows, err := n.Queryx(arg) + if err != nil { + return err + } + // if something happens here, we want to make sure the rows are Closed + defer rows.Close() + return scanAll(rows, dest, false) +} + +// Get using this NamedStmt +// Any named placeholder parameters are replaced with fields from arg. +func (n *NamedStmt) Get(dest interface{}, arg interface{}) error { + r := n.QueryRowx(arg) + 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 { + Preparer + binder +} + +func prepareNamed(p namedPreparer, query string) (*NamedStmt, error) { + bindType := BindType(p.DriverName()) + q, args, err := compileNamedQuery([]byte(query), bindType) + if err != nil { + return nil, err + } + stmt, err := Preparex(p, q) + if err != nil { + return nil, err + } + return &NamedStmt{ + QueryString: q, + Params: args, + Stmt: stmt, + }, nil +} + +// convertMapStringInterface attempts to convert v to map[string]interface{}. +// Unlike v.(map[string]interface{}), this function works on named types that +// are convertible to map[string]interface{} as well. +func convertMapStringInterface(v interface{}) (map[string]interface{}, bool) { + var m map[string]interface{} + mtype := reflect.TypeOf(m) + t := reflect.TypeOf(v) + if !t.ConvertibleTo(mtype) { + return nil, false + } + return reflect.ValueOf(v).Convert(mtype).Interface().(map[string]interface{}), true + +} + +func bindAnyArgs(names []string, arg interface{}, m *reflectx.Mapper) ([]interface{}, error) { + if maparg, ok := convertMapStringInterface(arg); ok { + return bindMapArgs(names, maparg) + } + return bindArgs(names, arg, m) +} + +// private interface to generate a list of interfaces from a given struct +// type, given a list of names to pull out of the struct. Used by public +// BindStruct interface. +func bindArgs(names []string, arg interface{}, m *reflectx.Mapper) ([]interface{}, error) { + arglist := make([]interface{}, 0, len(names)) + + // grab the indirected value of arg + v := reflect.ValueOf(arg) + for v = reflect.ValueOf(arg); v.Kind() == reflect.Ptr; { + v = v.Elem() + } + + err := m.TraversalsByNameFunc(v.Type(), names, func(i int, t []int) error { + if len(t) == 0 { + return fmt.Errorf("could not find name %s in %#v", names[i], arg) + } + + val := reflectx.FieldByIndexesReadOnly(v, t) + arglist = append(arglist, val.Interface()) + + return nil + }) + + return arglist, err +} + +// like bindArgs, but for maps. +func bindMapArgs(names []string, arg map[string]interface{}) ([]interface{}, error) { + arglist := make([]interface{}, 0, len(names)) + + for _, name := range names { + val, ok := arg[name] + if !ok { + return arglist, fmt.Errorf("could not find name %s in %#v", name, arg) + } + arglist = append(arglist, val) + } + return arglist, nil +} + +// bindStruct binds a named parameter query with fields from a struct argument. +// The rules for binding field names to parameter names follow the same +// conventions as for StructScan, including obeying the `db` struct tags. +func bindStruct(bindType int, query string, arg interface{}, m *reflectx.Mapper) (string, []interface{}, error) { + bound, names, err := compileNamedQuery([]byte(query), bindType) + if err != nil { + return "", []interface{}{}, err + } + + arglist, err := bindAnyArgs(names, arg, m) + if err != nil { + return "", []interface{}{}, err + } + + return bound, arglist, nil +} + +var valuesReg = regexp.MustCompile(`\)\s*(?i)VALUES\s*\(`) + +func findMatchingClosingBracketIndex(s string) int { + count := 0 + for i, ch := range s { + if ch == '(' { + count++ + } + if ch == ')' { + count-- + if count == 0 { + return i + } + } + } + return 0 +} + +func fixBound(bound string, loop int) string { + loc := valuesReg.FindStringIndex(bound) + // defensive guard when "VALUES (...)" not found + if len(loc) < 2 { + return bound + } + + openingBracketIndex := loc[1] - 1 + index := findMatchingClosingBracketIndex(bound[openingBracketIndex:]) + // defensive guard. must have closing bracket + if index == 0 { + return bound + } + closingBracketIndex := openingBracketIndex + index + 1 + + var buffer bytes.Buffer + + buffer.WriteString(bound[0:closingBracketIndex]) + for i := 0; i < loop-1; i++ { + buffer.WriteString(",") + buffer.WriteString(bound[openingBracketIndex:closingBracketIndex]) + } + buffer.WriteString(bound[closingBracketIndex:]) + return buffer.String() +} + +// bindArray binds a named parameter query with fields from an array or slice of +// structs argument. +func bindArray(bindType int, query string, arg interface{}, m *reflectx.Mapper) (string, []interface{}, error) { + // do the initial binding with QUESTION; if bindType is not question, + // we can rebind it at the end. + bound, names, err := compileNamedQuery([]byte(query), QUESTION) + if err != nil { + return "", []interface{}{}, err + } + arrayValue := reflect.ValueOf(arg) + arrayLen := arrayValue.Len() + if arrayLen == 0 { + return "", []interface{}{}, fmt.Errorf("length of array is 0: %#v", arg) + } + var arglist = make([]interface{}, 0, len(names)*arrayLen) + for i := 0; i < arrayLen; i++ { + elemArglist, err := bindAnyArgs(names, arrayValue.Index(i).Interface(), m) + if err != nil { + return "", []interface{}{}, err + } + arglist = append(arglist, elemArglist...) + } + if arrayLen > 1 { + bound = fixBound(bound, arrayLen) + } + // adjust binding type if we weren't on question + if bindType != QUESTION { + bound = Rebind(bindType, bound) + } + return bound, arglist, nil +} + +// bindMap binds a named parameter query with a map of arguments. +func bindMap(bindType int, query string, args map[string]interface{}) (string, []interface{}, error) { + bound, names, err := compileNamedQuery([]byte(query), bindType) + if err != nil { + return "", []interface{}{}, err + } + + arglist, err := bindMapArgs(names, args) + return bound, arglist, err +} + +// -- Compilation of Named Queries + +// Allow digits and letters in bind params; additionally runes are +// checked against underscores, meaning that bind params can have be +// alphanumeric with underscores. Mind the difference between unicode +// digits and numbers, where '5' is a digit but '五' is not. +var allowedBindRunes = []*unicode.RangeTable{unicode.Letter, unicode.Digit} + +// FIXME: this function isn't safe for unicode named params, as a failing test +// can testify. This is not a regression but a failure of the original code +// as well. It should be modified to range over runes in a string rather than +// bytes, even though this is less convenient and slower. Hopefully the +// addition of the prepared NamedStmt (which will only do this once) will make +// up for the slightly slower ad-hoc NamedExec/NamedQuery. + +// compile a NamedQuery into an unbound query (using the '?' bindvar) and +// a list of names. +func compileNamedQuery(qs []byte, bindType int) (query string, names []string, err error) { + names = make([]string, 0, 10) + rebound := make([]byte, 0, len(qs)) + + inName := false + last := len(qs) - 1 + currentVar := 1 + name := make([]byte, 0, 10) + + for i, b := range qs { + // a ':' while we're in a name is an error + if b == ':' { + // if this is the second ':' in a '::' escape sequence, append a ':' + if inName && i > 0 && qs[i-1] == ':' { + rebound = append(rebound, ':') + inName = false + continue + } else if inName { + err = errors.New("unexpected `:` while reading named param at " + strconv.Itoa(i)) + return query, names, err + } + inName = true + name = []byte{} + } else if inName && i > 0 && b == '=' && len(name) == 0 { + rebound = append(rebound, ':', '=') + inName = false + continue + // if we're in a name, and this is an allowed character, continue + } else if inName && (unicode.IsOneOf(allowedBindRunes, rune(b)) || b == '_' || b == '.') && i != last { + // append the byte to the name if we are in a name and not on the last byte + name = append(name, b) + // if we're in a name and it's not an allowed character, the name is done + } else if inName { + inName = false + // if this is the final byte of the string and it is part of the name, then + // make sure to add it to the name + if i == last && unicode.IsOneOf(allowedBindRunes, rune(b)) { + name = append(name, b) + } + // add the string representation to the names list + names = append(names, string(name)) + // add a proper bindvar for the bindType + switch bindType { + // oracle only supports named type bind vars even for positional + case NAMED: + rebound = append(rebound, ':') + rebound = append(rebound, name...) + case QUESTION, UNKNOWN: + rebound = append(rebound, '?') + case DOLLAR: + rebound = append(rebound, '$') + for _, b := range strconv.Itoa(currentVar) { + rebound = append(rebound, byte(b)) + } + currentVar++ + case AT: + rebound = append(rebound, '@', 'p') + for _, b := range strconv.Itoa(currentVar) { + rebound = append(rebound, byte(b)) + } + currentVar++ + } + // add this byte to string unless it was not part of the name + if i != last { + rebound = append(rebound, b) + } else if !unicode.IsOneOf(allowedBindRunes, rune(b)) { + rebound = append(rebound, b) + } + } else { + // this is a normal byte and should just go onto the rebound query + rebound = append(rebound, b) + } + } + + return string(rebound), names, err +} + +// 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) { + t := reflect.TypeOf(arg) + k := t.Kind() + switch { + case k == reflect.Map && t.Key().Kind() == reflect.String: + m, ok := convertMapStringInterface(arg) + if !ok { + return "", nil, fmt.Errorf("sqlx.bindNamedMapper: unsupported map type: %T", arg) + } + return bindMap(bindType, query, m) + case k == reflect.Array || k == reflect.Slice: + return bindArray(bindType, query, arg, m) + default: + return bindStruct(bindType, query, arg, m) + } +} + +// NamedQuery binds a named query and then runs Query on the result using the +// provided Ext (sqlx.Tx, sqlx.Db). It works with both structs and with +// map[string]interface{} types. +func NamedQuery(e Ext, query string, arg interface{}) (*Rows, error) { + q, args, err := bindNamedMapper(BindType(e.DriverName()), query, arg, mapperFor(e)) + if err != nil { + return nil, err + } + return e.Queryx(q, args...) +} + +// NamedExec uses BindStruct to get a query executable by the driver and +// then runs Exec on the result. Returns an error from the binding +// or the query execution itself. +func NamedExec(e Ext, query string, arg interface{}) (sql.Result, error) { + q, args, err := bindNamedMapper(BindType(e.DriverName()), query, arg, mapperFor(e)) + if err != nil { + return nil, err + } + return e.Exec(q, args...) +} diff --git a/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/named_context.go b/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/named_context.go new file mode 100644 index 0000000..07ad216 --- /dev/null +++ b/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/named_context.go @@ -0,0 +1,132 @@ +// +build go1.8 + +package sqlx + +import ( + "context" + "database/sql" +) + +// A union interface of contextPreparer and binder, required to be able to +// prepare named statements with context (as the bindtype must be determined). +type namedPreparerContext interface { + PreparerContext + binder +} + +func prepareNamedContext(ctx context.Context, p namedPreparerContext, query string) (*NamedStmt, error) { + bindType := BindType(p.DriverName()) + q, args, err := compileNamedQuery([]byte(query), bindType) + if err != nil { + return nil, err + } + stmt, err := PreparexContext(ctx, p, q) + if err != nil { + return nil, err + } + return &NamedStmt{ + QueryString: q, + Params: args, + Stmt: stmt, + }, nil +} + +// ExecContext executes a named statement using the struct passed. +// Any named placeholder parameters are replaced with fields from arg. +func (n *NamedStmt) ExecContext(ctx context.Context, arg interface{}) (sql.Result, error) { + args, err := bindAnyArgs(n.Params, arg, n.Stmt.Mapper) + if err != nil { + return *new(sql.Result), err + } + return n.Stmt.ExecContext(ctx, args...) +} + +// QueryContext executes a named statement using the struct argument, returning rows. +// Any named placeholder parameters are replaced with fields from arg. +func (n *NamedStmt) QueryContext(ctx context.Context, arg interface{}) (*sql.Rows, error) { + args, err := bindAnyArgs(n.Params, arg, n.Stmt.Mapper) + if err != nil { + return nil, err + } + return n.Stmt.QueryContext(ctx, args...) +} + +// QueryRowContext executes a named statement against the database. Because sqlx cannot +// create a *sql.Row with an error condition pre-set for binding errors, sqlx +// returns a *sqlx.Row instead. +// Any named placeholder parameters are replaced with fields from arg. +func (n *NamedStmt) QueryRowContext(ctx context.Context, arg interface{}) *Row { + args, err := bindAnyArgs(n.Params, arg, n.Stmt.Mapper) + if err != nil { + return &Row{err: err} + } + return n.Stmt.QueryRowxContext(ctx, args...) +} + +// MustExecContext execs a NamedStmt, panicing on error +// Any named placeholder parameters are replaced with fields from arg. +func (n *NamedStmt) MustExecContext(ctx context.Context, arg interface{}) sql.Result { + res, err := n.ExecContext(ctx, arg) + if err != nil { + panic(err) + } + return res +} + +// QueryxContext using this NamedStmt +// Any named placeholder parameters are replaced with fields from arg. +func (n *NamedStmt) QueryxContext(ctx context.Context, arg interface{}) (*Rows, error) { + r, err := n.QueryContext(ctx, arg) + if err != nil { + return nil, err + } + return &Rows{Rows: r, Mapper: n.Stmt.Mapper, unsafe: isUnsafe(n)}, err +} + +// QueryRowxContext this NamedStmt. Because of limitations with QueryRow, this is +// an alias for QueryRow. +// Any named placeholder parameters are replaced with fields from arg. +func (n *NamedStmt) QueryRowxContext(ctx context.Context, arg interface{}) *Row { + return n.QueryRowContext(ctx, arg) +} + +// SelectContext using this NamedStmt +// Any named placeholder parameters are replaced with fields from arg. +func (n *NamedStmt) SelectContext(ctx context.Context, dest interface{}, arg interface{}) error { + rows, err := n.QueryxContext(ctx, arg) + if err != nil { + return err + } + // if something happens here, we want to make sure the rows are Closed + defer rows.Close() + return scanAll(rows, dest, false) +} + +// GetContext using this NamedStmt +// Any named placeholder parameters are replaced with fields from arg. +func (n *NamedStmt) GetContext(ctx context.Context, dest interface{}, arg interface{}) error { + r := n.QueryRowxContext(ctx, arg) + return r.scanAny(dest, false) +} + +// NamedQueryContext binds a named query and then runs Query on the result using the +// provided Ext (sqlx.Tx, sqlx.Db). It works with both structs and with +// map[string]interface{} types. +func NamedQueryContext(ctx context.Context, e ExtContext, query string, arg interface{}) (*Rows, error) { + q, args, err := bindNamedMapper(BindType(e.DriverName()), query, arg, mapperFor(e)) + if err != nil { + return nil, err + } + return e.QueryxContext(ctx, q, args...) +} + +// NamedExecContext uses BindStruct to get a query executable by the driver and +// then runs Exec on the result. Returns an error from the binding +// or the query execution itself. +func NamedExecContext(ctx context.Context, e ExtContext, query string, arg interface{}) (sql.Result, error) { + q, args, err := bindNamedMapper(BindType(e.DriverName()), query, arg, mapperFor(e)) + if err != nil { + return nil, err + } + return e.ExecContext(ctx, q, args...) +} diff --git a/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/named_context_test.go b/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/named_context_test.go new file mode 100644 index 0000000..fd1d851 --- /dev/null +++ b/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/named_context_test.go @@ -0,0 +1,136 @@ +// +build go1.8 + +package sqlx + +import ( + "context" + "database/sql" + "testing" +) + +func TestNamedContextQueries(t *testing.T) { + RunWithSchema(defaultSchema, t, func(db *DB, t *testing.T, now string) { + loadDefaultFixture(db, t) + test := Test{t} + var ns *NamedStmt + var err error + + ctx := context.Background() + + // Check that invalid preparations fail + ns, err = db.PrepareNamedContext(ctx, "SELECT * FROM person WHERE first_name=:first:name") + if err == nil { + t.Error("Expected an error with invalid prepared statement.") + } + + ns, err = db.PrepareNamedContext(ctx, "invalid sql") + if err == nil { + t.Error("Expected an error with invalid prepared statement.") + } + + // Check closing works as anticipated + ns, err = db.PrepareNamedContext(ctx, "SELECT * FROM person WHERE first_name=:first_name") + test.Error(err) + err = ns.Close() + test.Error(err) + + ns, err = db.PrepareNamedContext(ctx, ` + SELECT first_name, last_name, email + FROM person WHERE first_name=:first_name AND email=:email`) + test.Error(err) + + // test Queryx w/ uses Query + p := Person{FirstName: "Jason", LastName: "Moiron", Email: "jmoiron@jmoiron.net"} + + rows, err := ns.QueryxContext(ctx, p) + test.Error(err) + for rows.Next() { + var p2 Person + rows.StructScan(&p2) + if p.FirstName != p2.FirstName { + t.Errorf("got %s, expected %s", p.FirstName, p2.FirstName) + } + if p.LastName != p2.LastName { + t.Errorf("got %s, expected %s", p.LastName, p2.LastName) + } + if p.Email != p2.Email { + t.Errorf("got %s, expected %s", p.Email, p2.Email) + } + } + + // test Select + people := make([]Person, 0, 5) + err = ns.SelectContext(ctx, &people, p) + test.Error(err) + + if len(people) != 1 { + t.Errorf("got %d results, expected %d", len(people), 1) + } + if p.FirstName != people[0].FirstName { + t.Errorf("got %s, expected %s", p.FirstName, people[0].FirstName) + } + if p.LastName != people[0].LastName { + t.Errorf("got %s, expected %s", p.LastName, people[0].LastName) + } + if p.Email != people[0].Email { + t.Errorf("got %s, expected %s", p.Email, people[0].Email) + } + + // test Exec + ns, err = db.PrepareNamedContext(ctx, ` + INSERT INTO person (first_name, last_name, email) + VALUES (:first_name, :last_name, :email)`) + test.Error(err) + + js := Person{ + FirstName: "Julien", + LastName: "Savea", + Email: "jsavea@ab.co.nz", + } + _, err = ns.ExecContext(ctx, js) + test.Error(err) + + // Make sure we can pull him out again + p2 := Person{} + db.GetContext(ctx, &p2, db.Rebind("SELECT * FROM person WHERE email=?"), js.Email) + if p2.Email != js.Email { + t.Errorf("expected %s, got %s", js.Email, p2.Email) + } + + // test Txn NamedStmts + tx := db.MustBeginTx(ctx, nil) + txns := tx.NamedStmtContext(ctx, ns) + + // We're going to add Steven in this txn + sl := Person{ + FirstName: "Steven", + LastName: "Luatua", + Email: "sluatua@ab.co.nz", + } + + _, err = txns.ExecContext(ctx, sl) + test.Error(err) + // then rollback... + tx.Rollback() + // looking for Steven after a rollback should fail + err = db.GetContext(ctx, &p2, db.Rebind("SELECT * FROM person WHERE email=?"), sl.Email) + if err != sql.ErrNoRows { + t.Errorf("expected no rows error, got %v", err) + } + + // now do the same, but commit + tx = db.MustBeginTx(ctx, nil) + txns = tx.NamedStmtContext(ctx, ns) + _, err = txns.ExecContext(ctx, sl) + test.Error(err) + tx.Commit() + + // looking for Steven after a Commit should succeed + err = db.GetContext(ctx, &p2, db.Rebind("SELECT * FROM person WHERE email=?"), sl.Email) + test.Error(err) + if p2.Email != sl.Email { + t.Errorf("expected %s, got %s", sl.Email, p2.Email) + } + + }) +} diff --git a/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/named_test.go b/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/named_test.go new file mode 100644 index 0000000..8481b35 --- /dev/null +++ b/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/named_test.go @@ -0,0 +1,435 @@ +package sqlx + +import ( + "database/sql" + "fmt" + "testing" +) + +func TestCompileQuery(t *testing.T) { + table := []struct { + Q, R, D, T, N string + V []string + }{ + // basic test for named parameters, invalid char ',' terminating + { + Q: `INSERT INTO foo (a,b,c,d) VALUES (:name, :age, :first, :last)`, + R: `INSERT INTO foo (a,b,c,d) VALUES (?, ?, ?, ?)`, + D: `INSERT INTO foo (a,b,c,d) VALUES ($1, $2, $3, $4)`, + T: `INSERT INTO foo (a,b,c,d) VALUES (@p1, @p2, @p3, @p4)`, + N: `INSERT INTO foo (a,b,c,d) VALUES (:name, :age, :first, :last)`, + V: []string{"name", "age", "first", "last"}, + }, + // This query tests a named parameter ending the string as well as numbers + { + Q: `SELECT * FROM a WHERE first_name=:name1 AND last_name=:name2`, + R: `SELECT * FROM a WHERE first_name=? AND last_name=?`, + D: `SELECT * FROM a WHERE first_name=$1 AND last_name=$2`, + T: `SELECT * FROM a WHERE first_name=@p1 AND last_name=@p2`, + N: `SELECT * FROM a WHERE first_name=:name1 AND last_name=:name2`, + V: []string{"name1", "name2"}, + }, + { + Q: `SELECT "::foo" FROM a WHERE first_name=:name1 AND last_name=:name2`, + R: `SELECT ":foo" FROM a WHERE first_name=? AND last_name=?`, + D: `SELECT ":foo" FROM a WHERE first_name=$1 AND last_name=$2`, + T: `SELECT ":foo" FROM a WHERE first_name=@p1 AND last_name=@p2`, + N: `SELECT ":foo" FROM a WHERE first_name=:name1 AND last_name=:name2`, + V: []string{"name1", "name2"}, + }, + { + Q: `SELECT 'a::b::c' || first_name, '::::ABC::_::' FROM person WHERE first_name=:first_name AND last_name=:last_name`, + R: `SELECT 'a:b:c' || first_name, '::ABC:_:' FROM person WHERE first_name=? AND last_name=?`, + D: `SELECT 'a:b:c' || first_name, '::ABC:_:' FROM person WHERE first_name=$1 AND last_name=$2`, + T: `SELECT 'a:b:c' || first_name, '::ABC:_:' FROM person WHERE first_name=@p1 AND last_name=@p2`, + N: `SELECT 'a:b:c' || first_name, '::ABC:_:' FROM person WHERE first_name=:first_name AND last_name=:last_name`, + V: []string{"first_name", "last_name"}, + }, + { + Q: `SELECT @name := "name", :age, :first, :last`, + R: `SELECT @name := "name", ?, ?, ?`, + D: `SELECT @name := "name", $1, $2, $3`, + N: `SELECT @name := "name", :age, :first, :last`, + T: `SELECT @name := "name", @p1, @p2, @p3`, + V: []string{"age", "first", "last"}, + }, + /* This unicode awareness test sadly fails, because of our byte-wise worldview. + * We could certainly iterate by Rune instead, though it's a great deal slower, + * it's probably the RightWay(tm) + { + Q: `INSERT INTO foo (a,b,c,d) VALUES (:あ, :b, :キコ, :名前)`, + R: `INSERT INTO foo (a,b,c,d) VALUES (?, ?, ?, ?)`, + D: `INSERT INTO foo (a,b,c,d) VALUES ($1, $2, $3, $4)`, + N: []string{"name", "age", "first", "last"}, + }, + */ + } + + for _, test := range table { + qr, names, err := compileNamedQuery([]byte(test.Q), QUESTION) + if err != nil { + t.Error(err) + } + if qr != test.R { + t.Errorf("expected %s, got %s", test.R, qr) + } + if len(names) != len(test.V) { + t.Errorf("expected %#v, got %#v", test.V, names) + } else { + for i, name := range names { + if name != test.V[i] { + t.Errorf("expected %dth name to be %s, got %s", i+1, test.V[i], name) + } + } + } + qd, _, _ := compileNamedQuery([]byte(test.Q), DOLLAR) + if qd != test.D { + t.Errorf("\nexpected: `%s`\ngot: `%s`", test.D, qd) + } + + qt, _, _ := compileNamedQuery([]byte(test.Q), AT) + if qt != test.T { + t.Errorf("\nexpected: `%s`\ngot: `%s`", test.T, qt) + } + + qq, _, _ := compileNamedQuery([]byte(test.Q), NAMED) + if qq != test.N { + t.Errorf("\nexpected: `%s`\ngot: `%s`\n(len: %d vs %d)", test.N, qq, len(test.N), len(qq)) + } + } +} + +type Test struct { + t *testing.T +} + +func (t Test) Error(err error, msg ...interface{}) { + t.t.Helper() + if err != nil { + if len(msg) == 0 { + t.t.Error(err) + } else { + t.t.Error(msg...) + } + } +} + +func (t Test) Errorf(err error, format string, args ...interface{}) { + t.t.Helper() + if err != nil { + t.t.Errorf(format, args...) + } +} + +func TestEscapedColons(t *testing.T) { + t.Skip("not sure it is possible to support this in general case without an SQL parser") + var qs = `SELECT * FROM testtable WHERE timeposted BETWEEN (now() AT TIME ZONE 'utc') AND + (now() AT TIME ZONE 'utc') - interval '01:30:00') AND name = '\'this is a test\'' and id = :id` + _, _, err := compileNamedQuery([]byte(qs), DOLLAR) + if err != nil { + t.Error("Didn't handle colons correctly when inside a string") + } +} + +func TestNamedQueries(t *testing.T) { + RunWithSchema(defaultSchema, t, func(db *DB, t *testing.T, now string) { + loadDefaultFixture(db, t) + test := Test{t} + var ns *NamedStmt + var err error + + // Check that invalid preparations fail + ns, err = db.PrepareNamed("SELECT * FROM person WHERE first_name=:first:name") + if err == nil { + t.Error("Expected an error with invalid prepared statement.") + } + + ns, err = db.PrepareNamed("invalid sql") + if err == nil { + t.Error("Expected an error with invalid prepared statement.") + } + + // Check closing works as anticipated + ns, err = db.PrepareNamed("SELECT * FROM person WHERE first_name=:first_name") + test.Error(err) + err = ns.Close() + test.Error(err) + + ns, err = db.PrepareNamed(` + SELECT first_name, last_name, email + FROM person WHERE first_name=:first_name AND email=:email`) + test.Error(err) + + // test Queryx w/ uses Query + p := Person{FirstName: "Jason", LastName: "Moiron", Email: "jmoiron@jmoiron.net"} + + rows, err := ns.Queryx(p) + test.Error(err) + for rows.Next() { + var p2 Person + rows.StructScan(&p2) + if p.FirstName != p2.FirstName { + t.Errorf("got %s, expected %s", p.FirstName, p2.FirstName) + } + if p.LastName != p2.LastName { + t.Errorf("got %s, expected %s", p.LastName, p2.LastName) + } + if p.Email != p2.Email { + t.Errorf("got %s, expected %s", p.Email, p2.Email) + } + } + + // test Select + people := make([]Person, 0, 5) + err = ns.Select(&people, p) + test.Error(err) + + if len(people) != 1 { + t.Errorf("got %d results, expected %d", len(people), 1) + } + if p.FirstName != people[0].FirstName { + t.Errorf("got %s, expected %s", p.FirstName, people[0].FirstName) + } + if p.LastName != people[0].LastName { + t.Errorf("got %s, expected %s", p.LastName, people[0].LastName) + } + if p.Email != people[0].Email { + t.Errorf("got %s, expected %s", p.Email, people[0].Email) + } + + // test struct batch inserts + sls := []Person{ + {FirstName: "Ardie", LastName: "Savea", Email: "asavea@ab.co.nz"}, + {FirstName: "Sonny Bill", LastName: "Williams", Email: "sbw@ab.co.nz"}, + {FirstName: "Ngani", LastName: "Laumape", Email: "nlaumape@ab.co.nz"}, + } + + insert := fmt.Sprintf( + "INSERT INTO person (first_name, last_name, email, added_at) VALUES (:first_name, :last_name, :email, %v)\n", + now, + ) + _, err = db.NamedExec(insert, sls) + test.Error(err) + + // test map batch inserts + slsMap := []map[string]interface{}{ + {"first_name": "Ardie", "last_name": "Savea", "email": "asavea@ab.co.nz"}, + {"first_name": "Sonny Bill", "last_name": "Williams", "email": "sbw@ab.co.nz"}, + {"first_name": "Ngani", "last_name": "Laumape", "email": "nlaumape@ab.co.nz"}, + } + + _, err = db.NamedExec(`INSERT INTO person (first_name, last_name, email) + VALUES (:first_name, :last_name, :email) ;--`, slsMap) + test.Error(err) + + type A map[string]interface{} + + typedMap := []A{ + {"first_name": "Ardie", "last_name": "Savea", "email": "asavea@ab.co.nz"}, + {"first_name": "Sonny Bill", "last_name": "Williams", "email": "sbw@ab.co.nz"}, + {"first_name": "Ngani", "last_name": "Laumape", "email": "nlaumape@ab.co.nz"}, + } + + _, err = db.NamedExec(`INSERT INTO person (first_name, last_name, email) + VALUES (:first_name, :last_name, :email) ;--`, typedMap) + test.Error(err) + + for _, p := range sls { + dest := Person{} + err = db.Get(&dest, db.Rebind("SELECT * FROM person WHERE email=?"), p.Email) + test.Error(err) + if dest.Email != p.Email { + t.Errorf("expected %s, got %s", p.Email, dest.Email) + } + } + + // test Exec + ns, err = db.PrepareNamed(` + INSERT INTO person (first_name, last_name, email) + VALUES (:first_name, :last_name, :email)`) + test.Error(err) + + js := Person{ + FirstName: "Julien", + LastName: "Savea", + Email: "jsavea@ab.co.nz", + } + _, err = ns.Exec(js) + test.Error(err) + + // Make sure we can pull him out again + p2 := Person{} + db.Get(&p2, db.Rebind("SELECT * FROM person WHERE email=?"), js.Email) + if p2.Email != js.Email { + t.Errorf("expected %s, got %s", js.Email, p2.Email) + } + + // test Txn NamedStmts + tx := db.MustBegin() + txns := tx.NamedStmt(ns) + + // We're going to add Steven in this txn + sl := Person{ + FirstName: "Steven", + LastName: "Luatua", + Email: "sluatua@ab.co.nz", + } + + _, err = txns.Exec(sl) + test.Error(err) + // then rollback... + tx.Rollback() + // looking for Steven after a rollback should fail + err = db.Get(&p2, db.Rebind("SELECT * FROM person WHERE email=?"), sl.Email) + if err != sql.ErrNoRows { + t.Errorf("expected no rows error, got %v", err) + } + + // now do the same, but commit + tx = db.MustBegin() + txns = tx.NamedStmt(ns) + _, err = txns.Exec(sl) + test.Error(err) + tx.Commit() + + // looking for Steven after a Commit should succeed + err = db.Get(&p2, db.Rebind("SELECT * FROM person WHERE email=?"), sl.Email) + test.Error(err) + if p2.Email != sl.Email { + t.Errorf("expected %s, got %s", sl.Email, p2.Email) + } + + }) +} + +func TestFixBounds(t *testing.T) { + table := []struct { + name, query, expect string + loop int + }{ + { + name: `named syntax`, + query: `INSERT INTO foo (a,b,c,d) VALUES (:name, :age, :first, :last)`, + expect: `INSERT INTO foo (a,b,c,d) VALUES (:name, :age, :first, :last),(:name, :age, :first, :last)`, + loop: 2, + }, + { + name: `mysql syntax`, + query: `INSERT INTO foo (a,b,c,d) VALUES (?, ?, ?, ?)`, + expect: `INSERT INTO foo (a,b,c,d) VALUES (?, ?, ?, ?),(?, ?, ?, ?)`, + loop: 2, + }, + { + name: `named syntax w/ trailer`, + query: `INSERT INTO foo (a,b,c,d) VALUES (:name, :age, :first, :last) ;--`, + expect: `INSERT INTO foo (a,b,c,d) VALUES (:name, :age, :first, :last),(:name, :age, :first, :last) ;--`, + loop: 2, + }, + { + name: `mysql syntax w/ trailer`, + query: `INSERT INTO foo (a,b,c,d) VALUES (?, ?, ?, ?) ;--`, + expect: `INSERT INTO foo (a,b,c,d) VALUES (?, ?, ?, ?),(?, ?, ?, ?) ;--`, + loop: 2, + }, + { + name: `not found test`, + query: `INSERT INTO foo (a,b,c,d) (:name, :age, :first, :last)`, + expect: `INSERT INTO foo (a,b,c,d) (:name, :age, :first, :last)`, + loop: 2, + }, + { + name: `found twice test`, + query: `INSERT INTO foo (a,b,c,d) VALUES (:name, :age, :first, :last) VALUES (:name, :age, :first, :last)`, + expect: `INSERT INTO foo (a,b,c,d) VALUES (:name, :age, :first, :last),(:name, :age, :first, :last) VALUES (:name, :age, :first, :last)`, + loop: 2, + }, + { + name: `nospace`, + query: `INSERT INTO foo (a,b) VALUES(:a, :b)`, + expect: `INSERT INTO foo (a,b) VALUES(:a, :b),(:a, :b)`, + loop: 2, + }, + { + name: `lowercase`, + query: `INSERT INTO foo (a,b) values(:a, :b)`, + expect: `INSERT INTO foo (a,b) values(:a, :b),(:a, :b)`, + loop: 2, + }, + { + name: `on duplicate key using VALUES`, + query: `INSERT INTO foo (a,b) VALUES (:a, :b) ON DUPLICATE KEY UPDATE a=VALUES(a)`, + expect: `INSERT INTO foo (a,b) VALUES (:a, :b),(:a, :b) ON DUPLICATE KEY UPDATE a=VALUES(a)`, + loop: 2, + }, + { + name: `single column`, + query: `INSERT INTO foo (a) VALUES (:a)`, + expect: `INSERT INTO foo (a) VALUES (:a),(:a)`, + loop: 2, + }, + { + name: `call now`, + query: `INSERT INTO foo (a, b) VALUES (:a, NOW())`, + expect: `INSERT INTO foo (a, b) VALUES (:a, NOW()),(:a, NOW())`, + loop: 2, + }, + { + name: `two level depth function call`, + query: `INSERT INTO foo (a, b) VALUES (:a, YEAR(NOW()))`, + expect: `INSERT INTO foo (a, b) VALUES (:a, YEAR(NOW())),(:a, YEAR(NOW()))`, + loop: 2, + }, + { + name: `missing closing bracket`, + query: `INSERT INTO foo (a, b) VALUES (:a, YEAR(NOW())`, + expect: `INSERT INTO foo (a, b) VALUES (:a, YEAR(NOW())`, + loop: 2, + }, + { + name: `table with "values" at the end`, + query: `INSERT INTO table_values (a, b) VALUES (:a, :b)`, + expect: `INSERT INTO table_values (a, b) VALUES (:a, :b),(:a, :b)`, + loop: 2, + }, + { + name: `multiline indented query`, + query: `INSERT INTO foo ( + a, + b, + c, + d + ) VALUES ( + :name, + :age, + :first, + :last + )`, + expect: `INSERT INTO foo ( + a, + b, + c, + d + ) VALUES ( + :name, + :age, + :first, + :last + ),( + :name, + :age, + :first, + :last + )`, + loop: 2, + }, + } + + for _, tc := range table { + t.Run(tc.name, func(t *testing.T) { + res := fixBound(tc.query, tc.loop) + if res != tc.expect { + t.Errorf("mismatched results") + } + }) + } +} diff --git a/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/reflectx/README.md b/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/reflectx/README.md new file mode 100644 index 0000000..f01d3d1 --- /dev/null +++ b/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/reflectx/README.md @@ -0,0 +1,17 @@ +# reflectx + +The sqlx package has special reflect needs. In particular, it needs to: + +* be able to map a name to a field +* understand embedded structs +* understand mapping names to fields by a particular tag +* user specified name -> field mapping functions + +These behaviors mimic the behaviors by the standard library marshallers and also the +behavior of standard Go accessors. + +The first two are amply taken care of by `Reflect.Value.FieldByName`, and the third is +addressed by `Reflect.Value.FieldByNameFunc`, but these don't quite understand struct +tags in the ways that are vital to most marshallers, and they are slow. + +This reflectx package extends reflect to achieve these goals. diff --git a/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/reflectx/reflect.go b/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/reflectx/reflect.go new file mode 100644 index 0000000..0b10994 --- /dev/null +++ b/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/reflectx/reflect.go @@ -0,0 +1,444 @@ +// Package reflectx implements extensions to the standard reflect lib suitable +// for implementing marshalling and unmarshalling packages. The main Mapper type +// allows for Go-compatible named attribute access, including accessing embedded +// struct attributes and the ability to use functions and struct tags to +// customize field names. +// +package reflectx + +import ( + "reflect" + "runtime" + "strings" + "sync" +) + +// A FieldInfo is metadata for a struct field. +type FieldInfo struct { + Index []int + Path string + Field reflect.StructField + Zero reflect.Value + Name string + Options map[string]string + Embedded bool + Children []*FieldInfo + Parent *FieldInfo +} + +// A StructMap is an index of field metadata for a struct. +type StructMap struct { + Tree *FieldInfo + Index []*FieldInfo + Paths map[string]*FieldInfo + Names map[string]*FieldInfo +} + +// GetByPath returns a *FieldInfo for a given string path. +func (f StructMap) GetByPath(path string) *FieldInfo { + return f.Paths[path] +} + +// GetByTraversal returns a *FieldInfo for a given integer path. It is +// analogous to reflect.FieldByIndex, but using the cached traversal +// rather than re-executing the reflect machinery each time. +func (f StructMap) GetByTraversal(index []int) *FieldInfo { + if len(index) == 0 { + return nil + } + + tree := f.Tree + for _, i := range index { + if i >= len(tree.Children) || tree.Children[i] == nil { + return nil + } + tree = tree.Children[i] + } + return tree +} + +// Mapper is a general purpose mapper of names to struct fields. A Mapper +// behaves like most marshallers in the standard library, obeying a field tag +// for name mapping but also providing a basic transform function. +type Mapper struct { + cache map[reflect.Type]*StructMap + tagName string + tagMapFunc func(string) string + mapFunc func(string) string + mutex sync.Mutex +} + +// NewMapper returns a new mapper using the tagName as its struct field tag. +// If tagName is the empty string, it is ignored. +func NewMapper(tagName string) *Mapper { + return &Mapper{ + cache: make(map[reflect.Type]*StructMap), + tagName: tagName, + } +} + +// NewMapperTagFunc returns a new mapper which contains a mapper for field names +// AND a mapper for tag values. This is useful for tags like json which can +// have values like "name,omitempty". +func NewMapperTagFunc(tagName string, mapFunc, tagMapFunc func(string) string) *Mapper { + return &Mapper{ + cache: make(map[reflect.Type]*StructMap), + tagName: tagName, + mapFunc: mapFunc, + tagMapFunc: tagMapFunc, + } +} + +// NewMapperFunc returns a new mapper which optionally obeys a field tag and +// a struct field name mapper func given by f. Tags will take precedence, but +// for any other field, the mapped name will be f(field.Name) +func NewMapperFunc(tagName string, f func(string) string) *Mapper { + return &Mapper{ + cache: make(map[reflect.Type]*StructMap), + tagName: tagName, + mapFunc: f, + } +} + +// TypeMap returns a mapping of field strings to int slices representing +// the traversal down the struct to reach the field. +func (m *Mapper) TypeMap(t reflect.Type) *StructMap { + m.mutex.Lock() + mapping, ok := m.cache[t] + if !ok { + mapping = getMapping(t, m.tagName, m.mapFunc, m.tagMapFunc) + m.cache[t] = mapping + } + m.mutex.Unlock() + return mapping +} + +// FieldMap returns the mapper's mapping of field names to reflect values. Panics +// if v's Kind is not Struct, or v is not Indirectable to a struct kind. +func (m *Mapper) FieldMap(v reflect.Value) map[string]reflect.Value { + v = reflect.Indirect(v) + mustBe(v, reflect.Struct) + + r := map[string]reflect.Value{} + tm := m.TypeMap(v.Type()) + for tagName, fi := range tm.Names { + r[tagName] = FieldByIndexes(v, fi.Index) + } + return r +} + +// FieldByName returns a field by its mapped name as a reflect.Value. +// Panics if v's Kind is not Struct or v is not Indirectable to a struct Kind. +// Returns zero Value if the name is not found. +func (m *Mapper) FieldByName(v reflect.Value, name string) reflect.Value { + v = reflect.Indirect(v) + mustBe(v, reflect.Struct) + + tm := m.TypeMap(v.Type()) + fi, ok := tm.Names[name] + if !ok { + return v + } + return FieldByIndexes(v, fi.Index) +} + +// FieldsByName returns a slice of values corresponding to the slice of names +// for the value. Panics if v's Kind is not Struct or v is not Indirectable +// to a struct Kind. Returns zero Value for each name not found. +func (m *Mapper) FieldsByName(v reflect.Value, names []string) []reflect.Value { + v = reflect.Indirect(v) + mustBe(v, reflect.Struct) + + tm := m.TypeMap(v.Type()) + vals := make([]reflect.Value, 0, len(names)) + for _, name := range names { + fi, ok := tm.Names[name] + if !ok { + vals = append(vals, *new(reflect.Value)) + } else { + vals = append(vals, FieldByIndexes(v, fi.Index)) + } + } + return vals +} + +// TraversalsByName returns a slice of int slices which represent the struct +// traversals for each mapped name. Panics if t is not a struct or Indirectable +// to a struct. Returns empty int slice for each name not found. +func (m *Mapper) TraversalsByName(t reflect.Type, names []string) [][]int { + r := make([][]int, 0, len(names)) + m.TraversalsByNameFunc(t, names, func(_ int, i []int) error { + if i == nil { + r = append(r, []int{}) + } else { + r = append(r, i) + } + + return nil + }) + return r +} + +// TraversalsByNameFunc traverses the mapped names and calls fn with the index of +// each name and the struct traversal represented by that name. Panics if t is not +// a struct or Indirectable to a struct. Returns the first error returned by fn or nil. +func (m *Mapper) TraversalsByNameFunc(t reflect.Type, names []string, fn func(int, []int) error) error { + t = Deref(t) + mustBe(t, reflect.Struct) + tm := m.TypeMap(t) + for i, name := range names { + fi, ok := tm.Names[name] + if !ok { + if err := fn(i, nil); err != nil { + return err + } + } else { + if err := fn(i, fi.Index); err != nil { + return err + } + } + } + return nil +} + +// FieldByIndexes returns a value for the field given by the struct traversal +// for the given value. +func FieldByIndexes(v reflect.Value, indexes []int) reflect.Value { + for _, i := range indexes { + v = reflect.Indirect(v).Field(i) + // if this is a pointer and it's nil, allocate a new value and set it + if v.Kind() == reflect.Ptr && v.IsNil() { + alloc := reflect.New(Deref(v.Type())) + v.Set(alloc) + } + if v.Kind() == reflect.Map && v.IsNil() { + v.Set(reflect.MakeMap(v.Type())) + } + } + return v +} + +// FieldByIndexesReadOnly returns a value for a particular struct traversal, +// but is not concerned with allocating nil pointers because the value is +// going to be used for reading and not setting. +func FieldByIndexesReadOnly(v reflect.Value, indexes []int) reflect.Value { + for _, i := range indexes { + v = reflect.Indirect(v).Field(i) + } + return v +} + +// Deref is Indirect for reflect.Types +func Deref(t reflect.Type) reflect.Type { + if t.Kind() == reflect.Ptr { + t = t.Elem() + } + return t +} + +// -- helpers & utilities -- + +type kinder interface { + Kind() reflect.Kind +} + +// mustBe checks a value against a kind, panicing with a reflect.ValueError +// if the kind isn't that which is required. +func mustBe(v kinder, expected reflect.Kind) { + if k := v.Kind(); k != expected { + panic(&reflect.ValueError{Method: methodName(), Kind: k}) + } +} + +// methodName returns the caller of the function calling methodName +func methodName() string { + pc, _, _, _ := runtime.Caller(2) + f := runtime.FuncForPC(pc) + if f == nil { + return "unknown method" + } + return f.Name() +} + +type typeQueue struct { + t reflect.Type + fi *FieldInfo + pp string // Parent path +} + +// A copying append that creates a new slice each time. +func apnd(is []int, i int) []int { + x := make([]int, len(is)+1) + copy(x, is) + x[len(x)-1] = i + return x +} + +type mapf func(string) string + +// parseName parses the tag and the target name for the given field using +// the tagName (eg 'json' for `json:"foo"` tags), mapFunc for mapping the +// field's name to a target name, and tagMapFunc for mapping the tag to +// a target name. +func parseName(field reflect.StructField, tagName string, mapFunc, tagMapFunc mapf) (tag, fieldName string) { + // first, set the fieldName to the field's name + fieldName = field.Name + // if a mapFunc is set, use that to override the fieldName + if mapFunc != nil { + fieldName = mapFunc(fieldName) + } + + // if there's no tag to look for, return the field name + if tagName == "" { + return "", fieldName + } + + // if this tag is not set using the normal convention in the tag, + // then return the fieldname.. this check is done because according + // to the reflect documentation: + // If the tag does not have the conventional format, + // the value returned by Get is unspecified. + // which doesn't sound great. + if !strings.Contains(string(field.Tag), tagName+":") { + return "", fieldName + } + + // at this point we're fairly sure that we have a tag, so lets pull it out + tag = field.Tag.Get(tagName) + + // if we have a mapper function, call it on the whole tag + // XXX: this is a change from the old version, which pulled out the name + // before the tagMapFunc could be run, but I think this is the right way + if tagMapFunc != nil { + tag = tagMapFunc(tag) + } + + // finally, split the options from the name + parts := strings.Split(tag, ",") + fieldName = parts[0] + + return tag, fieldName +} + +// parseOptions parses options out of a tag string, skipping the name +func parseOptions(tag string) map[string]string { + parts := strings.Split(tag, ",") + options := make(map[string]string, len(parts)) + if len(parts) > 1 { + for _, opt := range parts[1:] { + // short circuit potentially expensive split op + if strings.Contains(opt, "=") { + kv := strings.Split(opt, "=") + options[kv[0]] = kv[1] + continue + } + options[opt] = "" + } + } + return options +} + +// getMapping returns a mapping for the t type, using the tagName, mapFunc and +// tagMapFunc to determine the canonical names of fields. +func getMapping(t reflect.Type, tagName string, mapFunc, tagMapFunc mapf) *StructMap { + m := []*FieldInfo{} + + root := &FieldInfo{} + queue := []typeQueue{} + queue = append(queue, typeQueue{Deref(t), root, ""}) + +QueueLoop: + for len(queue) != 0 { + // pop the first item off of the queue + tq := queue[0] + queue = queue[1:] + + // ignore recursive field + for p := tq.fi.Parent; p != nil; p = p.Parent { + if tq.fi.Field.Type == p.Field.Type { + continue QueueLoop + } + } + + nChildren := 0 + if tq.t.Kind() == reflect.Struct { + nChildren = tq.t.NumField() + } + tq.fi.Children = make([]*FieldInfo, nChildren) + + // iterate through all of its fields + for fieldPos := 0; fieldPos < nChildren; fieldPos++ { + + f := tq.t.Field(fieldPos) + + // parse the tag and the target name using the mapping options for this field + tag, name := parseName(f, tagName, mapFunc, tagMapFunc) + + // if the name is "-", disabled via a tag, skip it + if name == "-" { + continue + } + + fi := FieldInfo{ + Field: f, + Name: name, + Zero: reflect.New(f.Type).Elem(), + Options: parseOptions(tag), + } + + // if the path is empty this path is just the name + if tq.pp == "" { + fi.Path = fi.Name + } else { + fi.Path = tq.pp + "." + fi.Name + } + + // skip unexported fields + if len(f.PkgPath) != 0 && !f.Anonymous { + continue + } + + // bfs search of anonymous embedded structs + if f.Anonymous { + pp := tq.pp + if tag != "" { + pp = fi.Path + } + + fi.Embedded = true + fi.Index = apnd(tq.fi.Index, fieldPos) + nChildren := 0 + ft := Deref(f.Type) + if ft.Kind() == reflect.Struct { + nChildren = ft.NumField() + } + fi.Children = make([]*FieldInfo, nChildren) + queue = append(queue, typeQueue{Deref(f.Type), &fi, pp}) + } else if fi.Zero.Kind() == reflect.Struct || (fi.Zero.Kind() == reflect.Ptr && fi.Zero.Type().Elem().Kind() == reflect.Struct) { + fi.Index = apnd(tq.fi.Index, fieldPos) + fi.Children = make([]*FieldInfo, Deref(f.Type).NumField()) + queue = append(queue, typeQueue{Deref(f.Type), &fi, fi.Path}) + } + + fi.Index = apnd(tq.fi.Index, fieldPos) + fi.Parent = tq.fi + tq.fi.Children[fieldPos] = &fi + m = append(m, &fi) + } + } + + flds := &StructMap{Index: m, Tree: root, Paths: map[string]*FieldInfo{}, Names: map[string]*FieldInfo{}} + for _, fi := range flds.Index { + // check if nothing has already been pushed with the same path + // sometimes you can choose to override a type using embedded struct + fld, ok := flds.Paths[fi.Path] + if !ok || fld.Embedded { + flds.Paths[fi.Path] = fi + if fi.Name != "" && !fi.Embedded { + flds.Names[fi.Path] = fi + } + } + } + + return flds +} diff --git a/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/reflectx/reflect_test.go b/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/reflectx/reflect_test.go new file mode 100644 index 0000000..e73af5b --- /dev/null +++ b/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/reflectx/reflect_test.go @@ -0,0 +1,1013 @@ +package reflectx + +import ( + "reflect" + "strings" + "testing" +) + +func ival(v reflect.Value) int { + return v.Interface().(int) +} + +func TestBasic(t *testing.T) { + type Foo struct { + A int + B int + C int + } + + f := Foo{1, 2, 3} + fv := reflect.ValueOf(f) + m := NewMapperFunc("", func(s string) string { return s }) + + v := m.FieldByName(fv, "A") + if ival(v) != f.A { + t.Errorf("Expecting %d, got %d", ival(v), f.A) + } + v = m.FieldByName(fv, "B") + if ival(v) != f.B { + t.Errorf("Expecting %d, got %d", f.B, ival(v)) + } + v = m.FieldByName(fv, "C") + if ival(v) != f.C { + t.Errorf("Expecting %d, got %d", f.C, ival(v)) + } +} + +func TestBasicEmbedded(t *testing.T) { + type Foo struct { + A int + } + + type Bar struct { + Foo // `db:""` is implied for an embedded struct + B int + C int `db:"-"` + } + + type Baz struct { + A int + Bar `db:"Bar"` + } + + m := NewMapperFunc("db", func(s string) string { return s }) + + z := Baz{} + z.A = 1 + z.B = 2 + z.C = 4 + z.Bar.Foo.A = 3 + + zv := reflect.ValueOf(z) + fields := m.TypeMap(reflect.TypeOf(z)) + + if len(fields.Index) != 5 { + t.Errorf("Expecting 5 fields") + } + + // for _, fi := range fields.Index { + // log.Println(fi) + // } + + v := m.FieldByName(zv, "A") + if ival(v) != z.A { + t.Errorf("Expecting %d, got %d", z.A, ival(v)) + } + v = m.FieldByName(zv, "Bar.B") + if ival(v) != z.Bar.B { + t.Errorf("Expecting %d, got %d", z.Bar.B, ival(v)) + } + v = m.FieldByName(zv, "Bar.A") + if ival(v) != z.Bar.Foo.A { + t.Errorf("Expecting %d, got %d", z.Bar.Foo.A, ival(v)) + } + v = m.FieldByName(zv, "Bar.C") + if _, ok := v.Interface().(int); ok { + t.Errorf("Expecting Bar.C to not exist") + } + + fi := fields.GetByPath("Bar.C") + if fi != nil { + t.Errorf("Bar.C should not exist") + } +} + +func TestEmbeddedSimple(t *testing.T) { + type UUID [16]byte + type MyID struct { + UUID + } + type Item struct { + ID MyID + } + z := Item{} + + m := NewMapper("db") + m.TypeMap(reflect.TypeOf(z)) +} + +func TestBasicEmbeddedWithTags(t *testing.T) { + type Foo struct { + A int `db:"a"` + } + + type Bar struct { + Foo // `db:""` is implied for an embedded struct + B int `db:"b"` + } + + type Baz struct { + A int `db:"a"` + Bar // `db:""` is implied for an embedded struct + } + + m := NewMapper("db") + + z := Baz{} + z.A = 1 + z.B = 2 + z.Bar.Foo.A = 3 + + zv := reflect.ValueOf(z) + fields := m.TypeMap(reflect.TypeOf(z)) + + if len(fields.Index) != 5 { + t.Errorf("Expecting 5 fields") + } + + // for _, fi := range fields.index { + // log.Println(fi) + // } + + v := m.FieldByName(zv, "a") + if ival(v) != z.A { // the dominant field + t.Errorf("Expecting %d, got %d", z.A, ival(v)) + } + v = m.FieldByName(zv, "b") + if ival(v) != z.B { + t.Errorf("Expecting %d, got %d", z.B, ival(v)) + } +} + +func TestBasicEmbeddedWithSameName(t *testing.T) { + type Foo struct { + A int `db:"a"` + Foo int `db:"Foo"` // Same name as the embedded struct + } + + type FooExt struct { + Foo + B int `db:"b"` + } + + m := NewMapper("db") + + z := FooExt{} + z.A = 1 + z.B = 2 + z.Foo.Foo = 3 + + zv := reflect.ValueOf(z) + fields := m.TypeMap(reflect.TypeOf(z)) + + if len(fields.Index) != 4 { + t.Errorf("Expecting 3 fields, found %d", len(fields.Index)) + } + + v := m.FieldByName(zv, "a") + if ival(v) != z.A { // the dominant field + t.Errorf("Expecting %d, got %d", z.A, ival(v)) + } + v = m.FieldByName(zv, "b") + if ival(v) != z.B { + t.Errorf("Expecting %d, got %d", z.B, ival(v)) + } + v = m.FieldByName(zv, "Foo") + if ival(v) != z.Foo.Foo { + t.Errorf("Expecting %d, got %d", z.Foo.Foo, ival(v)) + } +} + +func TestFlatTags(t *testing.T) { + m := NewMapper("db") + + type Asset struct { + Title string `db:"title"` + } + type Post struct { + Author string `db:"author,required"` + Asset Asset `db:""` + } + // Post columns: (author title) + + post := Post{Author: "Joe", Asset: Asset{Title: "Hello"}} + pv := reflect.ValueOf(post) + + v := m.FieldByName(pv, "author") + if v.Interface().(string) != post.Author { + t.Errorf("Expecting %s, got %s", post.Author, v.Interface().(string)) + } + v = m.FieldByName(pv, "title") + if v.Interface().(string) != post.Asset.Title { + t.Errorf("Expecting %s, got %s", post.Asset.Title, v.Interface().(string)) + } +} + +func TestNestedStruct(t *testing.T) { + m := NewMapper("db") + + type Details struct { + Active bool `db:"active"` + } + type Asset struct { + Title string `db:"title"` + Details Details `db:"details"` + } + type Post struct { + Author string `db:"author,required"` + Asset `db:"asset"` + } + // Post columns: (author asset.title asset.details.active) + + post := Post{ + Author: "Joe", + Asset: Asset{Title: "Hello", Details: Details{Active: true}}, + } + pv := reflect.ValueOf(post) + + v := m.FieldByName(pv, "author") + if v.Interface().(string) != post.Author { + t.Errorf("Expecting %s, got %s", post.Author, v.Interface().(string)) + } + v = m.FieldByName(pv, "title") + if _, ok := v.Interface().(string); ok { + t.Errorf("Expecting field to not exist") + } + v = m.FieldByName(pv, "asset.title") + if v.Interface().(string) != post.Asset.Title { + t.Errorf("Expecting %s, got %s", post.Asset.Title, v.Interface().(string)) + } + v = m.FieldByName(pv, "asset.details.active") + if v.Interface().(bool) != post.Asset.Details.Active { + t.Errorf("Expecting %v, got %v", post.Asset.Details.Active, v.Interface().(bool)) + } +} + +func TestInlineStruct(t *testing.T) { + m := NewMapperTagFunc("db", strings.ToLower, nil) + + type Employee struct { + Name string + ID int + } + type Boss Employee + type person struct { + Employee `db:"employee"` + Boss `db:"boss"` + } + // employees columns: (employee.name employee.id boss.name boss.id) + + em := person{Employee: Employee{Name: "Joe", ID: 2}, Boss: Boss{Name: "Dick", ID: 1}} + ev := reflect.ValueOf(em) + + fields := m.TypeMap(reflect.TypeOf(em)) + if len(fields.Index) != 6 { + t.Errorf("Expecting 6 fields") + } + + v := m.FieldByName(ev, "employee.name") + if v.Interface().(string) != em.Employee.Name { + t.Errorf("Expecting %s, got %s", em.Employee.Name, v.Interface().(string)) + } + v = m.FieldByName(ev, "boss.id") + if ival(v) != em.Boss.ID { + t.Errorf("Expecting %v, got %v", em.Boss.ID, ival(v)) + } +} + +func TestRecursiveStruct(t *testing.T) { + type Person struct { + Parent *Person + } + m := NewMapperFunc("db", strings.ToLower) + var p *Person + m.TypeMap(reflect.TypeOf(p)) +} + +func TestFieldsEmbedded(t *testing.T) { + m := NewMapper("db") + + type Person struct { + Name string `db:"name,size=64"` + } + type Place struct { + Name string `db:"name"` + } + type Article struct { + Title string `db:"title"` + } + type PP struct { + Person `db:"person,required"` + Place `db:",someflag"` + Article `db:",required"` + } + // PP columns: (person.name name title) + + pp := PP{} + pp.Person.Name = "Peter" + pp.Place.Name = "Toronto" + pp.Article.Title = "Best city ever" + + fields := m.TypeMap(reflect.TypeOf(pp)) + // for i, f := range fields { + // log.Println(i, f) + // } + + ppv := reflect.ValueOf(pp) + + v := m.FieldByName(ppv, "person.name") + if v.Interface().(string) != pp.Person.Name { + t.Errorf("Expecting %s, got %s", pp.Person.Name, v.Interface().(string)) + } + + v = m.FieldByName(ppv, "name") + if v.Interface().(string) != pp.Place.Name { + t.Errorf("Expecting %s, got %s", pp.Place.Name, v.Interface().(string)) + } + + v = m.FieldByName(ppv, "title") + if v.Interface().(string) != pp.Article.Title { + t.Errorf("Expecting %s, got %s", pp.Article.Title, v.Interface().(string)) + } + + fi := fields.GetByPath("person") + if _, ok := fi.Options["required"]; !ok { + t.Errorf("Expecting required option to be set") + } + if !fi.Embedded { + t.Errorf("Expecting field to be embedded") + } + if len(fi.Index) != 1 || fi.Index[0] != 0 { + t.Errorf("Expecting index to be [0]") + } + + fi = fields.GetByPath("person.name") + if fi == nil { + t.Errorf("Expecting person.name to exist") + } + if fi.Path != "person.name" { + t.Errorf("Expecting %s, got %s", "person.name", fi.Path) + } + if fi.Options["size"] != "64" { + t.Errorf("Expecting %s, got %s", "64", fi.Options["size"]) + } + + fi = fields.GetByTraversal([]int{1, 0}) + if fi == nil { + t.Errorf("Expecting traveral to exist") + } + if fi.Path != "name" { + t.Errorf("Expecting %s, got %s", "name", fi.Path) + } + + fi = fields.GetByTraversal([]int{2}) + if fi == nil { + t.Errorf("Expecting traversal to exist") + } + if _, ok := fi.Options["required"]; !ok { + t.Errorf("Expecting required option to be set") + } + + trs := m.TraversalsByName(reflect.TypeOf(pp), []string{"person.name", "name", "title"}) + if !reflect.DeepEqual(trs, [][]int{{0, 0}, {1, 0}, {2, 0}}) { + t.Errorf("Expecting traversal: %v", trs) + } +} + +func TestPtrFields(t *testing.T) { + m := NewMapperTagFunc("db", strings.ToLower, nil) + type Asset struct { + Title string + } + type Post struct { + *Asset `db:"asset"` + Author string + } + + post := &Post{Author: "Joe", Asset: &Asset{Title: "Hiyo"}} + pv := reflect.ValueOf(post) + + fields := m.TypeMap(reflect.TypeOf(post)) + if len(fields.Index) != 3 { + t.Errorf("Expecting 3 fields") + } + + v := m.FieldByName(pv, "asset.title") + if v.Interface().(string) != post.Asset.Title { + t.Errorf("Expecting %s, got %s", post.Asset.Title, v.Interface().(string)) + } + v = m.FieldByName(pv, "author") + if v.Interface().(string) != post.Author { + t.Errorf("Expecting %s, got %s", post.Author, v.Interface().(string)) + } +} + +func TestNamedPtrFields(t *testing.T) { + m := NewMapperTagFunc("db", strings.ToLower, nil) + + type User struct { + Name string + } + + type Asset struct { + Title string + + Owner *User `db:"owner"` + } + type Post struct { + Author string + + Asset1 *Asset `db:"asset1"` + Asset2 *Asset `db:"asset2"` + } + + post := &Post{Author: "Joe", Asset1: &Asset{Title: "Hiyo", Owner: &User{"Username"}}} // Let Asset2 be nil + pv := reflect.ValueOf(post) + + fields := m.TypeMap(reflect.TypeOf(post)) + if len(fields.Index) != 9 { + t.Errorf("Expecting 9 fields") + } + + v := m.FieldByName(pv, "asset1.title") + if v.Interface().(string) != post.Asset1.Title { + t.Errorf("Expecting %s, got %s", post.Asset1.Title, v.Interface().(string)) + } + v = m.FieldByName(pv, "asset1.owner.name") + if v.Interface().(string) != post.Asset1.Owner.Name { + t.Errorf("Expecting %s, got %s", post.Asset1.Owner.Name, v.Interface().(string)) + } + v = m.FieldByName(pv, "asset2.title") + if v.Interface().(string) != post.Asset2.Title { + t.Errorf("Expecting %s, got %s", post.Asset2.Title, v.Interface().(string)) + } + v = m.FieldByName(pv, "asset2.owner.name") + if v.Interface().(string) != post.Asset2.Owner.Name { + t.Errorf("Expecting %s, got %s", post.Asset2.Owner.Name, v.Interface().(string)) + } + v = m.FieldByName(pv, "author") + if v.Interface().(string) != post.Author { + t.Errorf("Expecting %s, got %s", post.Author, v.Interface().(string)) + } +} + +func TestFieldMap(t *testing.T) { + type Foo struct { + A int + B int + C int + } + + f := Foo{1, 2, 3} + m := NewMapperFunc("db", strings.ToLower) + + fm := m.FieldMap(reflect.ValueOf(f)) + + if len(fm) != 3 { + t.Errorf("Expecting %d keys, got %d", 3, len(fm)) + } + if fm["a"].Interface().(int) != 1 { + t.Errorf("Expecting %d, got %d", 1, ival(fm["a"])) + } + if fm["b"].Interface().(int) != 2 { + t.Errorf("Expecting %d, got %d", 2, ival(fm["b"])) + } + if fm["c"].Interface().(int) != 3 { + t.Errorf("Expecting %d, got %d", 3, ival(fm["c"])) + } +} + +func TestTagNameMapping(t *testing.T) { + type Strategy struct { + StrategyID string `protobuf:"bytes,1,opt,name=strategy_id" json:"strategy_id,omitempty"` + StrategyName string + } + + m := NewMapperTagFunc("json", strings.ToUpper, func(value string) string { + if strings.Contains(value, ",") { + return strings.Split(value, ",")[0] + } + return value + }) + strategy := Strategy{"1", "Alpah"} + mapping := m.TypeMap(reflect.TypeOf(strategy)) + + for _, key := range []string{"strategy_id", "STRATEGYNAME"} { + if fi := mapping.GetByPath(key); fi == nil { + t.Errorf("Expecting to find key %s in mapping but did not.", key) + } + } +} + +func TestMapping(t *testing.T) { + type Person struct { + ID int + Name string + WearsGlasses bool `db:"wears_glasses"` + } + + m := NewMapperFunc("db", strings.ToLower) + p := Person{1, "Jason", true} + mapping := m.TypeMap(reflect.TypeOf(p)) + + for _, key := range []string{"id", "name", "wears_glasses"} { + if fi := mapping.GetByPath(key); fi == nil { + t.Errorf("Expecting to find key %s in mapping but did not.", key) + } + } + + type SportsPerson struct { + Weight int + Age int + Person + } + s := SportsPerson{Weight: 100, Age: 30, Person: p} + mapping = m.TypeMap(reflect.TypeOf(s)) + for _, key := range []string{"id", "name", "wears_glasses", "weight", "age"} { + if fi := mapping.GetByPath(key); fi == nil { + t.Errorf("Expecting to find key %s in mapping but did not.", key) + } + } + + type RugbyPlayer struct { + Position int + IsIntense bool `db:"is_intense"` + IsAllBlack bool `db:"-"` + SportsPerson + } + r := RugbyPlayer{12, true, false, s} + mapping = m.TypeMap(reflect.TypeOf(r)) + for _, key := range []string{"id", "name", "wears_glasses", "weight", "age", "position", "is_intense"} { + if fi := mapping.GetByPath(key); fi == nil { + t.Errorf("Expecting to find key %s in mapping but did not.", key) + } + } + + if fi := mapping.GetByPath("isallblack"); fi != nil { + t.Errorf("Expecting to ignore `IsAllBlack` field") + } +} + +func TestGetByTraversal(t *testing.T) { + type C struct { + C0 int + C1 int + } + type B struct { + B0 string + B1 *C + } + type A struct { + A0 int + A1 B + } + + testCases := []struct { + Index []int + ExpectedName string + ExpectNil bool + }{ + { + Index: []int{0}, + ExpectedName: "A0", + }, + { + Index: []int{1, 0}, + ExpectedName: "B0", + }, + { + Index: []int{1, 1, 1}, + ExpectedName: "C1", + }, + { + Index: []int{3, 4, 5}, + ExpectNil: true, + }, + { + Index: []int{}, + ExpectNil: true, + }, + { + Index: nil, + ExpectNil: true, + }, + } + + m := NewMapperFunc("db", func(n string) string { return n }) + tm := m.TypeMap(reflect.TypeOf(A{})) + + for i, tc := range testCases { + fi := tm.GetByTraversal(tc.Index) + if tc.ExpectNil { + if fi != nil { + t.Errorf("%d: expected nil, got %v", i, fi) + } + continue + } + + if fi == nil { + t.Errorf("%d: expected %s, got nil", i, tc.ExpectedName) + continue + } + + if fi.Name != tc.ExpectedName { + t.Errorf("%d: expected %s, got %s", i, tc.ExpectedName, fi.Name) + } + } +} + +// TestMapperMethodsByName tests Mapper methods FieldByName and TraversalsByName +func TestMapperMethodsByName(t *testing.T) { + type C struct { + C0 string + C1 int + } + type B struct { + B0 *C `db:"B0"` + B1 C `db:"B1"` + B2 string `db:"B2"` + } + type A struct { + A0 *B `db:"A0"` + B `db:"A1"` + A2 int + a3 int + } + + val := &A{ + A0: &B{ + B0: &C{C0: "0", C1: 1}, + B1: C{C0: "2", C1: 3}, + B2: "4", + }, + B: B{ + B0: nil, + B1: C{C0: "5", C1: 6}, + B2: "7", + }, + A2: 8, + } + + testCases := []struct { + Name string + ExpectInvalid bool + ExpectedValue interface{} + ExpectedIndexes []int + }{ + { + Name: "A0.B0.C0", + ExpectedValue: "0", + ExpectedIndexes: []int{0, 0, 0}, + }, + { + Name: "A0.B0.C1", + ExpectedValue: 1, + ExpectedIndexes: []int{0, 0, 1}, + }, + { + Name: "A0.B1.C0", + ExpectedValue: "2", + ExpectedIndexes: []int{0, 1, 0}, + }, + { + Name: "A0.B1.C1", + ExpectedValue: 3, + ExpectedIndexes: []int{0, 1, 1}, + }, + { + Name: "A0.B2", + ExpectedValue: "4", + ExpectedIndexes: []int{0, 2}, + }, + { + Name: "A1.B0.C0", + ExpectedValue: "", + ExpectedIndexes: []int{1, 0, 0}, + }, + { + Name: "A1.B0.C1", + ExpectedValue: 0, + ExpectedIndexes: []int{1, 0, 1}, + }, + { + Name: "A1.B1.C0", + ExpectedValue: "5", + ExpectedIndexes: []int{1, 1, 0}, + }, + { + Name: "A1.B1.C1", + ExpectedValue: 6, + ExpectedIndexes: []int{1, 1, 1}, + }, + { + Name: "A1.B2", + ExpectedValue: "7", + ExpectedIndexes: []int{1, 2}, + }, + { + Name: "A2", + ExpectedValue: 8, + ExpectedIndexes: []int{2}, + }, + { + Name: "XYZ", + ExpectInvalid: true, + ExpectedIndexes: []int{}, + }, + { + Name: "a3", + ExpectInvalid: true, + ExpectedIndexes: []int{}, + }, + } + + // build the names array from the test cases + names := make([]string, len(testCases)) + for i, tc := range testCases { + names[i] = tc.Name + } + m := NewMapperFunc("db", func(n string) string { return n }) + v := reflect.ValueOf(val) + values := m.FieldsByName(v, names) + if len(values) != len(testCases) { + t.Errorf("expected %d values, got %d", len(testCases), len(values)) + t.FailNow() + } + indexes := m.TraversalsByName(v.Type(), names) + if len(indexes) != len(testCases) { + t.Errorf("expected %d traversals, got %d", len(testCases), len(indexes)) + t.FailNow() + } + for i, val := range values { + tc := testCases[i] + traversal := indexes[i] + if !reflect.DeepEqual(tc.ExpectedIndexes, traversal) { + t.Errorf("expected %v, got %v", tc.ExpectedIndexes, traversal) + t.FailNow() + } + val = reflect.Indirect(val) + if tc.ExpectInvalid { + if val.IsValid() { + t.Errorf("%d: expected zero value, got %v", i, val) + } + continue + } + if !val.IsValid() { + t.Errorf("%d: expected valid value, got %v", i, val) + continue + } + actualValue := reflect.Indirect(val).Interface() + if !reflect.DeepEqual(tc.ExpectedValue, actualValue) { + t.Errorf("%d: expected %v, got %v", i, tc.ExpectedValue, actualValue) + } + } +} + +func TestFieldByIndexes(t *testing.T) { + type C struct { + C0 bool + C1 string + C2 int + C3 map[string]int + } + type B struct { + B1 C + B2 *C + } + type A struct { + A1 B + A2 *B + } + testCases := []struct { + value interface{} + indexes []int + expectedValue interface{} + readOnly bool + }{ + { + value: A{ + A1: B{B1: C{C0: true}}, + }, + indexes: []int{0, 0, 0}, + expectedValue: true, + readOnly: true, + }, + { + value: A{ + A2: &B{B2: &C{C1: "answer"}}, + }, + indexes: []int{1, 1, 1}, + expectedValue: "answer", + readOnly: true, + }, + { + value: &A{}, + indexes: []int{1, 1, 3}, + expectedValue: map[string]int{}, + }, + } + + for i, tc := range testCases { + checkResults := func(v reflect.Value) { + if tc.expectedValue == nil { + if !v.IsNil() { + t.Errorf("%d: expected nil, actual %v", i, v.Interface()) + } + } else { + if !reflect.DeepEqual(tc.expectedValue, v.Interface()) { + t.Errorf("%d: expected %v, actual %v", i, tc.expectedValue, v.Interface()) + } + } + } + + checkResults(FieldByIndexes(reflect.ValueOf(tc.value), tc.indexes)) + if tc.readOnly { + checkResults(FieldByIndexesReadOnly(reflect.ValueOf(tc.value), tc.indexes)) + } + } +} + +func TestMustBe(t *testing.T) { + typ := reflect.TypeOf(E1{}) + mustBe(typ, reflect.Struct) + + defer func() { + if r := recover(); r != nil { + valueErr, ok := r.(*reflect.ValueError) + if !ok { + t.Errorf("unexpected Method: %s", valueErr.Method) + t.Error("expected panic with *reflect.ValueError") + return + } + if valueErr.Method != "github.com/jmoiron/sqlx/reflectx.TestMustBe" { + } + if valueErr.Kind != reflect.String { + t.Errorf("unexpected Kind: %s", valueErr.Kind) + } + } else { + t.Error("expected panic") + } + }() + + typ = reflect.TypeOf("string") + mustBe(typ, reflect.Struct) + t.Error("got here, didn't expect to") +} + +type E1 struct { + A int +} +type E2 struct { + E1 + B int +} +type E3 struct { + E2 + C int +} +type E4 struct { + E3 + D int +} + +func BenchmarkFieldNameL1(b *testing.B) { + e4 := E4{D: 1} + for i := 0; i < b.N; i++ { + v := reflect.ValueOf(e4) + f := v.FieldByName("D") + if f.Interface().(int) != 1 { + b.Fatal("Wrong value.") + } + } +} + +func BenchmarkFieldNameL4(b *testing.B) { + e4 := E4{} + e4.A = 1 + for i := 0; i < b.N; i++ { + v := reflect.ValueOf(e4) + f := v.FieldByName("A") + if f.Interface().(int) != 1 { + b.Fatal("Wrong value.") + } + } +} + +func BenchmarkFieldPosL1(b *testing.B) { + e4 := E4{D: 1} + for i := 0; i < b.N; i++ { + v := reflect.ValueOf(e4) + f := v.Field(1) + if f.Interface().(int) != 1 { + b.Fatal("Wrong value.") + } + } +} + +func BenchmarkFieldPosL4(b *testing.B) { + e4 := E4{} + e4.A = 1 + for i := 0; i < b.N; i++ { + v := reflect.ValueOf(e4) + f := v.Field(0) + f = f.Field(0) + f = f.Field(0) + f = f.Field(0) + if f.Interface().(int) != 1 { + b.Fatal("Wrong value.") + } + } +} + +func BenchmarkFieldByIndexL4(b *testing.B) { + e4 := E4{} + e4.A = 1 + idx := []int{0, 0, 0, 0} + for i := 0; i < b.N; i++ { + v := reflect.ValueOf(e4) + f := FieldByIndexes(v, idx) + if f.Interface().(int) != 1 { + b.Fatal("Wrong value.") + } + } +} + +func BenchmarkTraversalsByName(b *testing.B) { + type A struct { + Value int + } + + type B struct { + A A + } + + type C struct { + B B + } + + type D struct { + C C + } + + m := NewMapper("") + t := reflect.TypeOf(D{}) + names := []string{"C", "B", "A", "Value"} + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + if l := len(m.TraversalsByName(t, names)); l != len(names) { + b.Errorf("expected %d values, got %d", len(names), l) + } + } +} + +func BenchmarkTraversalsByNameFunc(b *testing.B) { + type A struct { + Z int + } + + type B struct { + A A + } + + type C struct { + B B + } + + type D struct { + C C + } + + m := NewMapper("") + t := reflect.TypeOf(D{}) + names := []string{"C", "B", "A", "Z", "Y"} + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + var l int + + if err := m.TraversalsByNameFunc(t, names, func(_ int, _ []int) error { + l++ + return nil + }); err != nil { + b.Errorf("unexpected error %s", err) + } + + if l != len(names) { + b.Errorf("expected %d values, got %d", len(names), l) + } + } +} diff --git a/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/sqlx.go b/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/sqlx.go new file mode 100644 index 0000000..f7b2876 --- /dev/null +++ b/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/sqlx.go @@ -0,0 +1,1051 @@ +package sqlx + +import ( + "database/sql" + "database/sql/driver" + "errors" + "fmt" + + "io/ioutil" + "path/filepath" + "reflect" + "strings" + "sync" + + "github.com/jmoiron/sqlx/reflectx" +) + +// Although the NameMapper is convenient, in practice it should not +// be relied on except for application code. If you are writing a library +// that uses sqlx, you should be aware that the name mappings you expect +// can be overridden by your user's application. + +// NameMapper is used to map column names to struct field names. By default, +// it uses strings.ToLower to lowercase struct field names. It can be set +// to whatever you want, but it is encouraged to be set before sqlx is used +// as name-to-field mappings are cached after first use on a type. +var NameMapper = strings.ToLower +var origMapper = reflect.ValueOf(NameMapper) + +// Rather than creating on init, this is created when necessary so that +// importers have time to customize the NameMapper. +var mpr *reflectx.Mapper + +// mprMu protects mpr. +var mprMu sync.Mutex + +// mapper returns a valid mapper using the configured NameMapper func. +func mapper() *reflectx.Mapper { + mprMu.Lock() + defer mprMu.Unlock() + + if mpr == nil { + mpr = reflectx.NewMapperFunc("db", NameMapper) + } else if origMapper != reflect.ValueOf(NameMapper) { + // if NameMapper has changed, create a new mapper + mpr = reflectx.NewMapperFunc("db", NameMapper) + origMapper = reflect.ValueOf(NameMapper) + } + return mpr +} + +// isScannable takes the reflect.Type and the actual dest value and returns +// whether or not it's Scannable. Something is scannable if: +// * it is not a struct +// * it implements sql.Scanner +// * it has no exported fields +func isScannable(t reflect.Type) bool { + if reflect.PtrTo(t).Implements(_scannerInterface) { + return true + } + if t.Kind() != reflect.Struct { + return true + } + + // 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 + return len(mapper().TypeMap(t).Index) == 0 +} + +// ColScanner is an interface used by MapScan and SliceScan +type ColScanner interface { + Columns() ([]string, error) + Scan(dest ...interface{}) error + Err() error +} + +// Queryer is an interface used by Get and Select +type Queryer interface { + Query(query string, args ...interface{}) (*sql.Rows, error) + Queryx(query string, args ...interface{}) (*Rows, error) + QueryRowx(query string, args ...interface{}) *Row +} + +// Execer is an interface used by MustExec and LoadFile +type Execer interface { + Exec(query string, args ...interface{}) (sql.Result, error) +} + +// Binder is an interface for something which can bind queries (Tx, DB) +type binder interface { + DriverName() string + Rebind(string) string + BindNamed(string, interface{}) (string, []interface{}, error) +} + +// Ext is a union interface which can bind, query, and exec, used by +// NamedQuery and NamedExec. +type Ext interface { + binder + Queryer + Execer +} + +// Preparer is an interface used by Preparex. +type Preparer interface { + Prepare(query string) (*sql.Stmt, error) +} + +// determine if any of our extensions are unsafe +func isUnsafe(i interface{}) bool { + switch v := i.(type) { + case Row: + return v.unsafe + case *Row: + return v.unsafe + case Rows: + return v.unsafe + case *Rows: + return v.unsafe + case NamedStmt: + return v.Stmt.unsafe + case *NamedStmt: + return v.Stmt.unsafe + case Stmt: + return v.unsafe + case *Stmt: + return v.unsafe + case qStmt: + return v.unsafe + case *qStmt: + return v.unsafe + case DB: + return v.unsafe + case *DB: + return v.unsafe + case Tx: + return v.unsafe + case *Tx: + return v.unsafe + case sql.Rows, *sql.Rows: + return false + default: + return false + } +} + +func mapperFor(i interface{}) *reflectx.Mapper { + switch i := i.(type) { + case DB: + return i.Mapper + case *DB: + return i.Mapper + case Tx: + return i.Mapper + case *Tx: + return i.Mapper + default: + return mapper() + } +} + +var _scannerInterface = reflect.TypeOf((*sql.Scanner)(nil)).Elem() +var _valuerInterface = reflect.TypeOf((*driver.Valuer)(nil)).Elem() + +// Row is a reimplementation of sql.Row in order to gain access to the underlying +// sql.Rows.Columns() data, necessary for StructScan. +type Row struct { + err error + unsafe bool + rows *sql.Rows + Mapper *reflectx.Mapper +} + +// Scan is a fixed implementation of sql.Row.Scan, which does not discard the +// underlying error from the internal rows object if it exists. +func (r *Row) Scan(dest ...interface{}) error { + if r.err != nil { + return r.err + } + + // TODO(bradfitz): for now we need to defensively clone all + // []byte that the driver returned (not permitting + // *RawBytes in Rows.Scan), since we're about to close + // the Rows in our defer, when we return from this function. + // the contract with the driver.Next(...) interface is that it + // can return slices into read-only temporary memory that's + // only valid until the next Scan/Close. But the TODO is that + // for a lot of drivers, this copy will be unnecessary. We + // should provide an optional interface for drivers to + // implement to say, "don't worry, the []bytes that I return + // from Next will not be modified again." (for instance, if + // they were obtained from the network anyway) But for now we + // don't care. + defer r.rows.Close() + for _, dp := range dest { + if _, ok := dp.(*sql.RawBytes); ok { + return errors.New("sql: RawBytes isn't allowed on Row.Scan") + } + } + + if !r.rows.Next() { + if err := r.rows.Err(); err != nil { + return err + } + return sql.ErrNoRows + } + err := r.rows.Scan(dest...) + if err != nil { + return err + } + // Make sure the query can be processed to completion with no errors. + if err := r.rows.Close(); err != nil { + return err + } + return nil +} + +// Columns returns the underlying sql.Rows.Columns(), or the deferred error usually +// returned by Row.Scan() +func (r *Row) Columns() ([]string, error) { + if r.err != nil { + return []string{}, r.err + } + return r.rows.Columns() +} + +// ColumnTypes returns the underlying sql.Rows.ColumnTypes(), or the deferred error +func (r *Row) ColumnTypes() ([]*sql.ColumnType, error) { + if r.err != nil { + return []*sql.ColumnType{}, r.err + } + return r.rows.ColumnTypes() +} + +// Err returns the error encountered while scanning. +func (r *Row) Err() error { + return r.err +} + +// DB is a wrapper around sql.DB which keeps track of the driverName upon Open, +// used mostly to automatically bind named queries using the right bindvars. +type DB struct { + *sql.DB + driverName string + unsafe bool + Mapper *reflectx.Mapper +} + +// NewDb returns a new sqlx DB wrapper for a pre-existing *sql.DB. The +// driverName of the original database is required for named query support. +func NewDb(db *sql.DB, driverName string) *DB { + return &DB{DB: db, driverName: driverName, Mapper: mapper()} +} + +// DriverName returns the driverName passed to the Open function for this DB. +func (db *DB) DriverName() string { + return db.driverName +} + +// Open is the same as sql.Open, but returns an *sqlx.DB instead. +func Open(driverName, dataSourceName string) (*DB, error) { + db, err := sql.Open(driverName, dataSourceName) + if err != nil { + return nil, err + } + return &DB{DB: db, driverName: driverName, Mapper: mapper()}, err +} + +// MustOpen is the same as sql.Open, but returns an *sqlx.DB instead and panics on error. +func MustOpen(driverName, dataSourceName string) *DB { + db, err := Open(driverName, dataSourceName) + if err != nil { + panic(err) + } + return db +} + +// MapperFunc sets a new mapper for this db using the default sqlx struct tag +// and the provided mapper function. +func (db *DB) MapperFunc(mf func(string) string) { + db.Mapper = reflectx.NewMapperFunc("db", mf) +} + +// Rebind transforms a query from QUESTION to the DB driver's bindvar type. +func (db *DB) Rebind(query string) string { + return Rebind(BindType(db.driverName), query) +} + +// Unsafe returns a version of DB which will silently succeed to scan when +// columns in the SQL result have no fields in the destination struct. +// sqlx.Stmt and sqlx.Tx which are created from this DB will inherit its +// safety behavior. +func (db *DB) Unsafe() *DB { + return &DB{DB: db.DB, driverName: db.driverName, unsafe: true, Mapper: db.Mapper} +} + +// BindNamed binds a query using the DB driver's bindvar type. +func (db *DB) BindNamed(query string, arg interface{}) (string, []interface{}, error) { + return bindNamedMapper(BindType(db.driverName), query, arg, db.Mapper) +} + +// NamedQuery using this DB. +// Any named placeholder parameters are replaced with fields from arg. +func (db *DB) NamedQuery(query string, arg interface{}) (*Rows, error) { + return NamedQuery(db, query, arg) +} + +// NamedExec using this DB. +// Any named placeholder parameters are replaced with fields from arg. +func (db *DB) NamedExec(query string, arg interface{}) (sql.Result, error) { + return NamedExec(db, query, arg) +} + +// Select using this DB. +// Any placeholder parameters are replaced with supplied args. +func (db *DB) Select(dest interface{}, query string, args ...interface{}) error { + return Select(db, dest, query, args...) +} + +// Get using this DB. +// Any placeholder parameters are replaced with supplied args. +// An error is returned if the result set is empty. +func (db *DB) Get(dest interface{}, query string, args ...interface{}) error { + return Get(db, dest, query, args...) +} + +// MustBegin starts a transaction, and panics on error. Returns an *sqlx.Tx instead +// of an *sql.Tx. +func (db *DB) MustBegin() *Tx { + tx, err := db.Beginx() + if err != nil { + panic(err) + } + return tx +} + +// Beginx begins a transaction and returns an *sqlx.Tx instead of an *sql.Tx. +func (db *DB) Beginx() (*Tx, error) { + tx, err := db.DB.Begin() + if err != nil { + return nil, err + } + return &Tx{Tx: tx, driverName: db.driverName, unsafe: db.unsafe, Mapper: db.Mapper}, err +} + +// Queryx queries the database and returns an *sqlx.Rows. +// Any placeholder parameters are replaced with supplied args. +func (db *DB) Queryx(query string, args ...interface{}) (*Rows, error) { + r, err := db.DB.Query(query, args...) + if err != nil { + return nil, err + } + return &Rows{Rows: r, unsafe: db.unsafe, Mapper: db.Mapper}, err +} + +// QueryRowx queries the database and returns an *sqlx.Row. +// Any placeholder parameters are replaced with supplied args. +func (db *DB) QueryRowx(query string, args ...interface{}) *Row { + rows, err := db.DB.Query(query, args...) + return &Row{rows: rows, err: err, unsafe: db.unsafe, Mapper: db.Mapper} +} + +// MustExec (panic) runs MustExec using this database. +// Any placeholder parameters are replaced with supplied args. +func (db *DB) MustExec(query string, args ...interface{}) sql.Result { + return MustExec(db, query, args...) +} + +// Preparex returns an sqlx.Stmt instead of a sql.Stmt +func (db *DB) Preparex(query string) (*Stmt, error) { + return Preparex(db, query) +} + +// PrepareNamed returns an sqlx.NamedStmt +func (db *DB) PrepareNamed(query string) (*NamedStmt, error) { + return prepareNamed(db, query) +} + +// Conn is a wrapper around sql.Conn with extra functionality +type Conn struct { + *sql.Conn + driverName string + unsafe bool + Mapper *reflectx.Mapper +} + +// Tx is an sqlx wrapper around sql.Tx with extra functionality +type Tx struct { + *sql.Tx + driverName string + unsafe bool + Mapper *reflectx.Mapper +} + +// DriverName returns the driverName used by the DB which began this transaction. +func (tx *Tx) DriverName() string { + return tx.driverName +} + +// Rebind a query within a transaction's bindvar type. +func (tx *Tx) Rebind(query string) string { + return Rebind(BindType(tx.driverName), query) +} + +// Unsafe returns a version of Tx which will silently succeed to scan when +// columns in the SQL result have no fields in the destination struct. +func (tx *Tx) Unsafe() *Tx { + return &Tx{Tx: tx.Tx, driverName: tx.driverName, unsafe: true, Mapper: tx.Mapper} +} + +// BindNamed binds a query within a transaction's bindvar type. +func (tx *Tx) BindNamed(query string, arg interface{}) (string, []interface{}, error) { + return bindNamedMapper(BindType(tx.driverName), query, arg, tx.Mapper) +} + +// NamedQuery within a transaction. +// Any named placeholder parameters are replaced with fields from arg. +func (tx *Tx) NamedQuery(query string, arg interface{}) (*Rows, error) { + return NamedQuery(tx, query, arg) +} + +// NamedExec a named query within a transaction. +// Any named placeholder parameters are replaced with fields from arg. +func (tx *Tx) NamedExec(query string, arg interface{}) (sql.Result, error) { + return NamedExec(tx, query, arg) +} + +// Select within a transaction. +// Any placeholder parameters are replaced with supplied args. +func (tx *Tx) Select(dest interface{}, query string, args ...interface{}) error { + return Select(tx, dest, query, args...) +} + +// Queryx within a transaction. +// Any placeholder parameters are replaced with supplied args. +func (tx *Tx) Queryx(query string, args ...interface{}) (*Rows, error) { + r, err := tx.Tx.Query(query, args...) + if err != nil { + return nil, err + } + return &Rows{Rows: r, unsafe: tx.unsafe, Mapper: tx.Mapper}, err +} + +// QueryRowx within a transaction. +// Any placeholder parameters are replaced with supplied args. +func (tx *Tx) QueryRowx(query string, args ...interface{}) *Row { + rows, err := tx.Tx.Query(query, args...) + return &Row{rows: rows, err: err, unsafe: tx.unsafe, Mapper: tx.Mapper} +} + +// Get within a transaction. +// Any placeholder parameters are replaced with supplied args. +// An error is returned if the result set is empty. +func (tx *Tx) Get(dest interface{}, query string, args ...interface{}) error { + return Get(tx, dest, query, args...) +} + +// MustExec runs MustExec within a transaction. +// Any placeholder parameters are replaced with supplied args. +func (tx *Tx) MustExec(query string, args ...interface{}) sql.Result { + return MustExec(tx, query, args...) +} + +// Preparex a statement within a transaction. +func (tx *Tx) Preparex(query string) (*Stmt, error) { + return Preparex(tx, query) +} + +// 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 s *sql.Stmt + switch v := stmt.(type) { + case Stmt: + s = v.Stmt + case *Stmt: + s = v.Stmt + case *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} +} + +// NamedStmt returns a version of the prepared statement which runs within a transaction. +func (tx *Tx) NamedStmt(stmt *NamedStmt) *NamedStmt { + return &NamedStmt{ + QueryString: stmt.QueryString, + Params: stmt.Params, + Stmt: tx.Stmtx(stmt.Stmt), + } +} + +// PrepareNamed returns an sqlx.NamedStmt +func (tx *Tx) PrepareNamed(query string) (*NamedStmt, error) { + return prepareNamed(tx, query) +} + +// Stmt is an sqlx wrapper around sql.Stmt with extra functionality +type Stmt struct { + *sql.Stmt + unsafe bool + Mapper *reflectx.Mapper +} + +// Unsafe returns a version of Stmt which will silently succeed to scan when +// columns in the SQL result have no fields in the destination struct. +func (s *Stmt) Unsafe() *Stmt { + return &Stmt{Stmt: s.Stmt, unsafe: true, Mapper: s.Mapper} +} + +// Select using the prepared statement. +// Any placeholder parameters are replaced with supplied args. +func (s *Stmt) Select(dest interface{}, args ...interface{}) error { + return Select(&qStmt{s}, dest, "", args...) +} + +// Get using the prepared statement. +// Any placeholder parameters are replaced with supplied args. +// An error is returned if the result set is empty. +func (s *Stmt) Get(dest interface{}, args ...interface{}) error { + 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. +// Any placeholder parameters are replaced with supplied args. +func (s *Stmt) MustExec(args ...interface{}) sql.Result { + return MustExec(&qStmt{s}, "", args...) +} + +// QueryRowx using this statement. +// Any placeholder parameters are replaced with supplied args. +func (s *Stmt) QueryRowx(args ...interface{}) *Row { + qs := &qStmt{s} + return qs.QueryRowx("", args...) +} + +// Queryx using this statement. +// Any placeholder parameters are replaced with supplied args. +func (s *Stmt) Queryx(args ...interface{}) (*Rows, error) { + 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 } + +func (q *qStmt) Query(query string, args ...interface{}) (*sql.Rows, error) { + return q.Stmt.Query(args...) +} + +func (q *qStmt) Queryx(query string, args ...interface{}) (*Rows, error) { + r, err := q.Stmt.Query(args...) + if err != nil { + return nil, err + } + return &Rows{Rows: r, unsafe: q.Stmt.unsafe, Mapper: q.Stmt.Mapper}, err +} + +func (q *qStmt) QueryRowx(query string, args ...interface{}) *Row { + rows, err := q.Stmt.Query(args...) + return &Row{rows: rows, err: err, unsafe: q.Stmt.unsafe, Mapper: q.Stmt.Mapper} +} + +func (q *qStmt) Exec(query string, args ...interface{}) (sql.Result, error) { + return q.Stmt.Exec(args...) +} + +// Rows is a wrapper around sql.Rows which caches costly reflect operations +// during a looped StructScan +type Rows struct { + *sql.Rows + unsafe bool + Mapper *reflectx.Mapper + // these fields cache memory use for a rows during iteration w/ structScan + started bool + fields [][]int + values []interface{} +} + +// SliceScan using this Rows. +func (r *Rows) SliceScan() ([]interface{}, error) { + return SliceScan(r) +} + +// MapScan using this Rows. +func (r *Rows) MapScan(dest map[string]interface{}) error { + return MapScan(r, dest) +} + +// StructScan is like sql.Rows.Scan, but scans a single Row into a single Struct. +// Use this and iterate over Rows manually when the memory load of Select() might be +// prohibitive. *Rows.StructScan caches the reflect work of matching up column +// positions to fields to avoid that overhead per scan, which means it is not safe +// to run StructScan on the same Rows instance with different struct types. +func (r *Rows) StructScan(dest interface{}) error { + v := reflect.ValueOf(dest) + + if v.Kind() != reflect.Ptr { + return errors.New("must pass a pointer, not a value, to StructScan destination") + } + + v = v.Elem() + + if !r.started { + columns, err := r.Columns() + if err != nil { + return err + } + m := r.Mapper + + r.fields = m.TraversalsByName(v.Type(), columns) + // if we are not unsafe and are missing fields, return an error + if f, err := missingFields(r.fields); err != nil && !r.unsafe { + return fmt.Errorf("missing destination name %s in %T", columns[f], dest) + } + r.values = make([]interface{}, len(columns)) + r.started = true + } + + err := fieldsByTraversal(v, r.fields, r.values, true) + if err != nil { + return err + } + // scan into the struct field pointers and append to our results + err = r.Scan(r.values...) + if err != nil { + return err + } + return r.Err() +} + +// Connect to a database and verify with a ping. +func Connect(driverName, dataSourceName string) (*DB, error) { + db, err := Open(driverName, dataSourceName) + if err != nil { + return nil, err + } + err = db.Ping() + if err != nil { + db.Close() + return nil, err + } + return db, nil +} + +// MustConnect connects to a database and panics on error. +func MustConnect(driverName, dataSourceName string) *DB { + db, err := Connect(driverName, dataSourceName) + if err != nil { + panic(err) + } + return db +} + +// Preparex prepares a statement. +func Preparex(p Preparer, query string) (*Stmt, error) { + s, err := p.Prepare(query) + if err != nil { + return nil, err + } + return &Stmt{Stmt: s, unsafe: isUnsafe(p), Mapper: mapperFor(p)}, err +} + +// Select executes a query using the provided Queryer, and StructScans each row +// into dest, which must be a slice. If the slice elements are scannable, then +// the result set must have only one column. Otherwise, StructScan is used. +// The *sql.Rows are closed automatically. +// Any placeholder parameters are replaced with supplied args. +func Select(q Queryer, dest interface{}, query string, args ...interface{}) error { + rows, err := q.Queryx(query, args...) + if err != nil { + return err + } + // if something happens here, we want to make sure the rows are Closed + defer rows.Close() + return scanAll(rows, dest, false) +} + +// Get does a QueryRow using the provided Queryer, and scans the resulting row +// to dest. If dest is scannable, the result must only have one column. Otherwise, +// StructScan is used. Get will return sql.ErrNoRows like row.Scan would. +// Any placeholder parameters are replaced with supplied args. +// An error is returned if the result set is empty. +func Get(q Queryer, dest interface{}, query string, args ...interface{}) error { + r := q.QueryRowx(query, args...) + return r.scanAny(dest, false) +} + +// LoadFile exec's every statement in a file (as a single call to Exec). +// LoadFile may return a nil *sql.Result if errors are encountered locating or +// reading the file at path. LoadFile reads the entire file into memory, so it +// is not suitable for loading large data dumps, but can be useful for initializing +// schemas or loading indexes. +// +// FIXME: this does not really work with multi-statement files for mattn/go-sqlite3 +// or the go-mysql-driver/mysql drivers; pq seems to be an exception here. Detecting +// this by requiring something with DriverName() and then attempting to split the +// queries will be difficult to get right, and its current driver-specific behavior +// is deemed at least not complex in its incorrectness. +func LoadFile(e Execer, path string) (*sql.Result, error) { + realpath, err := filepath.Abs(path) + if err != nil { + return nil, err + } + contents, err := ioutil.ReadFile(realpath) + if err != nil { + return nil, err + } + res, err := e.Exec(string(contents)) + return &res, err +} + +// MustExec execs the query using e and panics if there was an error. +// Any placeholder parameters are replaced with supplied args. +func MustExec(e Execer, query string, args ...interface{}) sql.Result { + res, err := e.Exec(query, args...) + if err != nil { + panic(err) + } + return res +} + +// SliceScan using this Rows. +func (r *Row) SliceScan() ([]interface{}, error) { + return SliceScan(r) +} + +// MapScan using this Rows. +func (r *Row) MapScan(dest map[string]interface{}) error { + return MapScan(r, dest) +} + +func (r *Row) scanAny(dest interface{}, structOnly bool) error { + if r.err != nil { + return r.err + } + if r.rows == nil { + r.err = sql.ErrNoRows + return r.err + } + defer r.rows.Close() + + v := reflect.ValueOf(dest) + if v.Kind() != reflect.Ptr { + return errors.New("must pass a pointer, not a value, to StructScan destination") + } + if v.IsNil() { + return errors.New("nil pointer passed to StructScan destination") + } + + base := reflectx.Deref(v.Type()) + scannable := isScannable(base) + + if structOnly && scannable { + return structOnlyError(base) + } + + columns, err := r.Columns() + if err != nil { + return err + } + + if scannable && len(columns) > 1 { + return fmt.Errorf("scannable dest type %s with >1 columns (%d) in result", base.Kind(), len(columns)) + } + + if scannable { + return r.Scan(dest) + } + + m := r.Mapper + + fields := m.TraversalsByName(v.Type(), columns) + // if we are not unsafe and are missing fields, return an error + if f, err := missingFields(fields); err != nil && !r.unsafe { + return fmt.Errorf("missing destination name %s in %T", columns[f], dest) + } + values := make([]interface{}, len(columns)) + + err = fieldsByTraversal(v, fields, values, true) + if err != nil { + return err + } + // scan into the struct field pointers and append to our results + return r.Scan(values...) +} + +// StructScan a single Row into dest. +func (r *Row) StructScan(dest interface{}) error { + return r.scanAny(dest, true) +} + +// SliceScan a row, returning a []interface{} with values similar to MapScan. +// 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. +func SliceScan(r ColScanner) ([]interface{}, error) { + // ignore r.started, since we needn't use reflect for anything. + columns, err := r.Columns() + if err != nil { + return []interface{}{}, err + } + + values := make([]interface{}, len(columns)) + for i := range values { + values[i] = new(interface{}) + } + + err = r.Scan(values...) + + if err != nil { + return values, err + } + + for i := range columns { + values[i] = *(values[i].(*interface{})) + } + + return values, r.Err() +} + +// MapScan scans a single Row into the dest map[string]interface{}. +// Use this to get results for SQL that might not be under your control +// (for instance, if you're building an interface for an SQL server that +// executes SQL from input). Please do not use this as a primary interface! +// This will modify the map sent to it in place, so reuse the same map with +// care. Columns which occur more than once in the result will overwrite +// each other! +func MapScan(r ColScanner, dest map[string]interface{}) error { + // ignore r.started, since we needn't use reflect for anything. + columns, err := r.Columns() + if err != nil { + return err + } + + values := make([]interface{}, len(columns)) + for i := range values { + values[i] = new(interface{}) + } + + err = r.Scan(values...) + if err != nil { + return err + } + + for i, column := range columns { + dest[column] = *(values[i].(*interface{})) + } + + return r.Err() +} + +type rowsi interface { + Close() error + Columns() ([]string, error) + Err() error + Next() bool + Scan(...interface{}) error +} + +// structOnlyError returns an error appropriate for type when a non-scannable +// struct is expected but something else is given +func structOnlyError(t reflect.Type) error { + isStruct := t.Kind() == reflect.Struct + isScanner := reflect.PtrTo(t).Implements(_scannerInterface) + if !isStruct { + return fmt.Errorf("expected %s but got %s", reflect.Struct, t.Kind()) + } + if isScanner { + return fmt.Errorf("structscan expects a struct dest but the provided struct type %s implements scanner", t.Name()) + } + return fmt.Errorf("expected a struct, but struct %s has no exported fields", t.Name()) +} + +// scanAll scans all rows into a destination, which must be a slice of any +// type. It resets the slice length to zero before appending each element to +// the slice. If the destination slice type is a Struct, then StructScan will +// be used on each row. If the destination is some other kind of base type, +// then each row must only have one column which can scan into that type. This +// allows you to do something like: +// +// rows, _ := db.Query("select id from people;") +// var ids []int +// scanAll(rows, &ids, false) +// +// and ids will be a list of the id results. I realize that this is a desirable +// interface to expose to users, but for now it will only be exposed via changes +// to `Get` and `Select`. The reason that this has been implemented like this is +// this is the only way to not duplicate reflect work in the new API while +// maintaining backwards compatibility. +func scanAll(rows rowsi, dest interface{}, structOnly bool) error { + var v, vp reflect.Value + + value := reflect.ValueOf(dest) + + // json.Unmarshal returns errors for these + if value.Kind() != reflect.Ptr { + return errors.New("must pass a pointer, not a value, to StructScan destination") + } + if value.IsNil() { + return errors.New("nil pointer passed to StructScan destination") + } + direct := reflect.Indirect(value) + + slice, err := baseType(value.Type(), reflect.Slice) + if err != nil { + return err + } + direct.SetLen(0) + + isPtr := slice.Elem().Kind() == reflect.Ptr + base := reflectx.Deref(slice.Elem()) + scannable := isScannable(base) + + if structOnly && scannable { + return structOnlyError(base) + } + + columns, err := rows.Columns() + if err != nil { + return err + } + + // if it's a base type make sure it only has 1 column; if not return an error + if scannable && len(columns) > 1 { + return fmt.Errorf("non-struct dest type %s with >1 columns (%d)", base.Kind(), len(columns)) + } + + if !scannable { + var values []interface{} + var m *reflectx.Mapper + + switch rows.(type) { + case *Rows: + m = rows.(*Rows).Mapper + default: + m = mapper() + } + + fields := m.TraversalsByName(base, columns) + // if we are not unsafe and are missing fields, return an error + if f, err := missingFields(fields); err != nil && !isUnsafe(rows) { + return fmt.Errorf("missing destination name %s in %T", columns[f], dest) + } + values = make([]interface{}, len(columns)) + + for rows.Next() { + // create a new struct type (which returns PtrTo) and indirect it + vp = reflect.New(base) + v = reflect.Indirect(vp) + + err = fieldsByTraversal(v, fields, values, true) + if err != nil { + return err + } + + // scan into the struct field pointers and append to our results + err = rows.Scan(values...) + if err != nil { + return err + } + + if isPtr { + direct.Set(reflect.Append(direct, vp)) + } else { + direct.Set(reflect.Append(direct, v)) + } + } + } else { + for rows.Next() { + vp = reflect.New(base) + err = rows.Scan(vp.Interface()) + if err != nil { + return err + } + // append + if isPtr { + direct.Set(reflect.Append(direct, vp)) + } else { + direct.Set(reflect.Append(direct, reflect.Indirect(vp))) + } + } + } + + return rows.Err() +} + +// FIXME: StructScan was the very first bit of API in sqlx, and now unfortunately +// it doesn't really feel like it's named properly. There is an incongruency +// between this and the way that StructScan (which might better be ScanStruct +// anyway) works on a rows object. + +// StructScan all rows from an sql.Rows or an sqlx.Rows into the dest slice. +// StructScan will scan in the entire rows result, so if you do not want to +// allocate structs for the entire result, use Queryx and see sqlx.Rows.StructScan. +// If rows is sqlx.Rows, it will use its mapper, otherwise it will use the default. +func StructScan(rows rowsi, dest interface{}) error { + return scanAll(rows, dest, true) + +} + +// reflect helpers + +func baseType(t reflect.Type, expected reflect.Kind) (reflect.Type, error) { + t = reflectx.Deref(t) + if t.Kind() != expected { + return nil, fmt.Errorf("expected %s but got %s", expected, t.Kind()) + } + return t, nil +} + +// fieldsByName fills a values interface with fields from the passed value based +// on the traversals in int. If ptrs is true, return addresses instead of values. +// We write this instead of using FieldsByName to save allocations and map lookups +// when iterating over many rows. Empty traversals will get an interface pointer. +// Because of the necessity of requesting ptrs or values, it's considered a bit too +// specialized for inclusion in reflectx itself. +func fieldsByTraversal(v reflect.Value, traversals [][]int, values []interface{}, ptrs bool) error { + v = reflect.Indirect(v) + if v.Kind() != reflect.Struct { + return errors.New("argument not a struct") + } + + for i, traversal := range traversals { + if len(traversal) == 0 { + values[i] = new(interface{}) + continue + } + f := reflectx.FieldByIndexes(v, traversal) + if ptrs { + values[i] = f.Addr().Interface() + } else { + values[i] = f.Interface() + } + } + return nil +} + +func missingFields(transversals [][]int) (field int, err error) { + for i, t := range transversals { + if len(t) == 0 { + return i, errors.New("missing field") + } + } + return 0, nil +} diff --git a/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/sqlx_context.go b/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/sqlx_context.go new file mode 100644 index 0000000..7aa4dd0 --- /dev/null +++ b/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/sqlx_context.go @@ -0,0 +1,414 @@ +// +build go1.8 + +package sqlx + +import ( + "context" + "database/sql" + "fmt" + "io/ioutil" + "path/filepath" + "reflect" +) + +// ConnectContext to a database and verify with a ping. +func ConnectContext(ctx context.Context, driverName, dataSourceName string) (*DB, error) { + db, err := Open(driverName, dataSourceName) + if err != nil { + return db, err + } + err = db.PingContext(ctx) + return db, err +} + +// QueryerContext is an interface used by GetContext and SelectContext +type QueryerContext interface { + QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) + QueryxContext(ctx context.Context, query string, args ...interface{}) (*Rows, error) + QueryRowxContext(ctx context.Context, query string, args ...interface{}) *Row +} + +// PreparerContext is an interface used by PreparexContext. +type PreparerContext interface { + PrepareContext(ctx context.Context, query string) (*sql.Stmt, error) +} + +// ExecerContext is an interface used by MustExecContext and LoadFileContext +type ExecerContext interface { + ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) +} + +// ExtContext is a union interface which can bind, query, and exec, with Context +// used by NamedQueryContext and NamedExecContext. +type ExtContext interface { + binder + QueryerContext + ExecerContext +} + +// SelectContext executes a query using the provided Queryer, and StructScans +// each row into dest, which must be a slice. If the slice elements are +// scannable, then the result set must have only one column. Otherwise, +// StructScan is used. The *sql.Rows are closed automatically. +// Any placeholder parameters are replaced with supplied args. +func SelectContext(ctx context.Context, q QueryerContext, dest interface{}, query string, args ...interface{}) error { + rows, err := q.QueryxContext(ctx, query, args...) + if err != nil { + return err + } + // if something happens here, we want to make sure the rows are Closed + defer rows.Close() + return scanAll(rows, dest, false) +} + +// PreparexContext prepares a statement. +// +// The provided context is used for the preparation of the statement, not for +// the execution of the statement. +func PreparexContext(ctx context.Context, p PreparerContext, query string) (*Stmt, error) { + s, err := p.PrepareContext(ctx, query) + if err != nil { + return nil, err + } + return &Stmt{Stmt: s, unsafe: isUnsafe(p), Mapper: mapperFor(p)}, err +} + +// GetContext does a QueryRow using the provided Queryer, and scans the +// resulting row to dest. If dest is scannable, the result must only have one +// column. Otherwise, StructScan is used. Get will return sql.ErrNoRows like +// row.Scan would. Any placeholder parameters are replaced with supplied args. +// An error is returned if the result set is empty. +func GetContext(ctx context.Context, q QueryerContext, dest interface{}, query string, args ...interface{}) error { + r := q.QueryRowxContext(ctx, query, args...) + return r.scanAny(dest, false) +} + +// LoadFileContext exec's every statement in a file (as a single call to Exec). +// LoadFileContext may return a nil *sql.Result if errors are encountered +// locating or reading the file at path. LoadFile reads the entire file into +// memory, so it is not suitable for loading large data dumps, but can be useful +// for initializing schemas or loading indexes. +// +// FIXME: this does not really work with multi-statement files for mattn/go-sqlite3 +// or the go-mysql-driver/mysql drivers; pq seems to be an exception here. Detecting +// this by requiring something with DriverName() and then attempting to split the +// queries will be difficult to get right, and its current driver-specific behavior +// is deemed at least not complex in its incorrectness. +func LoadFileContext(ctx context.Context, e ExecerContext, path string) (*sql.Result, error) { + realpath, err := filepath.Abs(path) + if err != nil { + return nil, err + } + contents, err := ioutil.ReadFile(realpath) + if err != nil { + return nil, err + } + res, err := e.ExecContext(ctx, string(contents)) + return &res, err +} + +// MustExecContext execs the query using e and panics if there was an error. +// Any placeholder parameters are replaced with supplied args. +func MustExecContext(ctx context.Context, e ExecerContext, query string, args ...interface{}) sql.Result { + res, err := e.ExecContext(ctx, query, args...) + if err != nil { + panic(err) + } + return res +} + +// PrepareNamedContext returns an sqlx.NamedStmt +func (db *DB) PrepareNamedContext(ctx context.Context, query string) (*NamedStmt, error) { + return prepareNamedContext(ctx, db, query) +} + +// NamedQueryContext using this DB. +// Any named placeholder parameters are replaced with fields from arg. +func (db *DB) NamedQueryContext(ctx context.Context, query string, arg interface{}) (*Rows, error) { + return NamedQueryContext(ctx, db, query, arg) +} + +// NamedExecContext using this DB. +// Any named placeholder parameters are replaced with fields from arg. +func (db *DB) NamedExecContext(ctx context.Context, query string, arg interface{}) (sql.Result, error) { + return NamedExecContext(ctx, db, query, arg) +} + +// SelectContext using this DB. +// Any placeholder parameters are replaced with supplied args. +func (db *DB) SelectContext(ctx context.Context, dest interface{}, query string, args ...interface{}) error { + return SelectContext(ctx, db, dest, query, args...) +} + +// GetContext using this DB. +// Any placeholder parameters are replaced with supplied args. +// An error is returned if the result set is empty. +func (db *DB) GetContext(ctx context.Context, dest interface{}, query string, args ...interface{}) error { + return GetContext(ctx, db, dest, query, args...) +} + +// PreparexContext returns an sqlx.Stmt instead of a sql.Stmt. +// +// The provided context is used for the preparation of the statement, not for +// the execution of the statement. +func (db *DB) PreparexContext(ctx context.Context, query string) (*Stmt, error) { + return PreparexContext(ctx, db, query) +} + +// QueryxContext queries the database and returns an *sqlx.Rows. +// Any placeholder parameters are replaced with supplied args. +func (db *DB) QueryxContext(ctx context.Context, query string, args ...interface{}) (*Rows, error) { + r, err := db.DB.QueryContext(ctx, query, args...) + if err != nil { + return nil, err + } + return &Rows{Rows: r, unsafe: db.unsafe, Mapper: db.Mapper}, err +} + +// QueryRowxContext queries the database and returns an *sqlx.Row. +// Any placeholder parameters are replaced with supplied args. +func (db *DB) QueryRowxContext(ctx context.Context, query string, args ...interface{}) *Row { + rows, err := db.DB.QueryContext(ctx, query, args...) + return &Row{rows: rows, err: err, unsafe: db.unsafe, Mapper: db.Mapper} +} + +// MustBeginTx starts a transaction, and panics on error. Returns an *sqlx.Tx instead +// of an *sql.Tx. +// +// The provided context is used until the transaction is committed or rolled +// back. If the context is canceled, the sql package will roll back the +// transaction. Tx.Commit will return an error if the context provided to +// MustBeginContext is canceled. +func (db *DB) MustBeginTx(ctx context.Context, opts *sql.TxOptions) *Tx { + tx, err := db.BeginTxx(ctx, opts) + if err != nil { + panic(err) + } + return tx +} + +// MustExecContext (panic) runs MustExec using this database. +// Any placeholder parameters are replaced with supplied args. +func (db *DB) MustExecContext(ctx context.Context, query string, args ...interface{}) sql.Result { + return MustExecContext(ctx, db, query, args...) +} + +// BeginTxx begins a transaction and returns an *sqlx.Tx instead of an +// *sql.Tx. +// +// The provided context is used until the transaction is committed or rolled +// back. If the context is canceled, the sql package will roll back the +// transaction. Tx.Commit will return an error if the context provided to +// BeginxContext is canceled. +func (db *DB) BeginTxx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) { + tx, err := db.DB.BeginTx(ctx, opts) + if err != nil { + return nil, err + } + return &Tx{Tx: tx, driverName: db.driverName, unsafe: db.unsafe, Mapper: db.Mapper}, err +} + +// Connx returns an *sqlx.Conn instead of an *sql.Conn. +func (db *DB) Connx(ctx context.Context) (*Conn, error) { + conn, err := db.DB.Conn(ctx) + if err != nil { + return nil, err + } + + return &Conn{Conn: conn, driverName: db.driverName, unsafe: db.unsafe, Mapper: db.Mapper}, nil +} + +// BeginTxx begins a transaction and returns an *sqlx.Tx instead of an +// *sql.Tx. +// +// The provided context is used until the transaction is committed or rolled +// back. If the context is canceled, the sql package will roll back the +// transaction. Tx.Commit will return an error if the context provided to +// BeginxContext is canceled. +func (c *Conn) BeginTxx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) { + tx, err := c.Conn.BeginTx(ctx, opts) + if err != nil { + return nil, err + } + return &Tx{Tx: tx, driverName: c.driverName, unsafe: c.unsafe, Mapper: c.Mapper}, err +} + +// SelectContext using this Conn. +// Any placeholder parameters are replaced with supplied args. +func (c *Conn) SelectContext(ctx context.Context, dest interface{}, query string, args ...interface{}) error { + return SelectContext(ctx, c, dest, query, args...) +} + +// GetContext using this Conn. +// Any placeholder parameters are replaced with supplied args. +// An error is returned if the result set is empty. +func (c *Conn) GetContext(ctx context.Context, dest interface{}, query string, args ...interface{}) error { + return GetContext(ctx, c, dest, query, args...) +} + +// PreparexContext returns an sqlx.Stmt instead of a sql.Stmt. +// +// The provided context is used for the preparation of the statement, not for +// the execution of the statement. +func (c *Conn) PreparexContext(ctx context.Context, query string) (*Stmt, error) { + return PreparexContext(ctx, c, query) +} + +// QueryxContext queries the database and returns an *sqlx.Rows. +// Any placeholder parameters are replaced with supplied args. +func (c *Conn) QueryxContext(ctx context.Context, query string, args ...interface{}) (*Rows, error) { + r, err := c.Conn.QueryContext(ctx, query, args...) + if err != nil { + return nil, err + } + return &Rows{Rows: r, unsafe: c.unsafe, Mapper: c.Mapper}, err +} + +// QueryRowxContext queries the database and returns an *sqlx.Row. +// Any placeholder parameters are replaced with supplied args. +func (c *Conn) QueryRowxContext(ctx context.Context, query string, args ...interface{}) *Row { + rows, err := c.Conn.QueryContext(ctx, query, args...) + return &Row{rows: rows, err: err, unsafe: c.unsafe, Mapper: c.Mapper} +} + +// Rebind a query within a Conn's bindvar type. +func (c *Conn) Rebind(query string) string { + return Rebind(BindType(c.driverName), query) +} + +// StmtxContext 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) StmtxContext(ctx context.Context, stmt interface{}) *Stmt { + var s *sql.Stmt + switch v := stmt.(type) { + case Stmt: + s = v.Stmt + case *Stmt: + s = v.Stmt + case *sql.Stmt: + s = v + default: + panic(fmt.Sprintf("non-statement type %v passed to Stmtx", reflect.ValueOf(stmt).Type())) + } + return &Stmt{Stmt: tx.StmtContext(ctx, s), Mapper: tx.Mapper} +} + +// NamedStmtContext returns a version of the prepared statement which runs +// within a transaction. +func (tx *Tx) NamedStmtContext(ctx context.Context, stmt *NamedStmt) *NamedStmt { + return &NamedStmt{ + QueryString: stmt.QueryString, + Params: stmt.Params, + Stmt: tx.StmtxContext(ctx, stmt.Stmt), + } +} + +// PreparexContext returns an sqlx.Stmt instead of a sql.Stmt. +// +// The provided context is used for the preparation of the statement, not for +// the execution of the statement. +func (tx *Tx) PreparexContext(ctx context.Context, query string) (*Stmt, error) { + return PreparexContext(ctx, tx, query) +} + +// PrepareNamedContext returns an sqlx.NamedStmt +func (tx *Tx) PrepareNamedContext(ctx context.Context, query string) (*NamedStmt, error) { + return prepareNamedContext(ctx, tx, query) +} + +// MustExecContext runs MustExecContext within a transaction. +// Any placeholder parameters are replaced with supplied args. +func (tx *Tx) MustExecContext(ctx context.Context, query string, args ...interface{}) sql.Result { + return MustExecContext(ctx, tx, query, args...) +} + +// QueryxContext within a transaction and context. +// Any placeholder parameters are replaced with supplied args. +func (tx *Tx) QueryxContext(ctx context.Context, query string, args ...interface{}) (*Rows, error) { + r, err := tx.Tx.QueryContext(ctx, query, args...) + if err != nil { + return nil, err + } + return &Rows{Rows: r, unsafe: tx.unsafe, Mapper: tx.Mapper}, err +} + +// SelectContext within a transaction and context. +// Any placeholder parameters are replaced with supplied args. +func (tx *Tx) SelectContext(ctx context.Context, dest interface{}, query string, args ...interface{}) error { + return SelectContext(ctx, tx, dest, query, args...) +} + +// GetContext within a transaction and context. +// Any placeholder parameters are replaced with supplied args. +// An error is returned if the result set is empty. +func (tx *Tx) GetContext(ctx context.Context, dest interface{}, query string, args ...interface{}) error { + return GetContext(ctx, tx, dest, query, args...) +} + +// QueryRowxContext within a transaction and context. +// Any placeholder parameters are replaced with supplied args. +func (tx *Tx) QueryRowxContext(ctx context.Context, query string, args ...interface{}) *Row { + rows, err := tx.Tx.QueryContext(ctx, query, args...) + return &Row{rows: rows, err: err, unsafe: tx.unsafe, Mapper: tx.Mapper} +} + +// NamedExecContext using this Tx. +// Any named placeholder parameters are replaced with fields from arg. +func (tx *Tx) NamedExecContext(ctx context.Context, query string, arg interface{}) (sql.Result, error) { + return NamedExecContext(ctx, tx, query, arg) +} + +// SelectContext using the prepared statement. +// Any placeholder parameters are replaced with supplied args. +func (s *Stmt) SelectContext(ctx context.Context, dest interface{}, args ...interface{}) error { + return SelectContext(ctx, &qStmt{s}, dest, "", args...) +} + +// GetContext using the prepared statement. +// Any placeholder parameters are replaced with supplied args. +// An error is returned if the result set is empty. +func (s *Stmt) GetContext(ctx context.Context, dest interface{}, args ...interface{}) error { + return GetContext(ctx, &qStmt{s}, dest, "", args...) +} + +// MustExecContext (panic) using this statement. Note that the query portion of +// the error output will be blank, as Stmt does not expose its query. +// Any placeholder parameters are replaced with supplied args. +func (s *Stmt) MustExecContext(ctx context.Context, args ...interface{}) sql.Result { + return MustExecContext(ctx, &qStmt{s}, "", args...) +} + +// QueryRowxContext using this statement. +// Any placeholder parameters are replaced with supplied args. +func (s *Stmt) QueryRowxContext(ctx context.Context, args ...interface{}) *Row { + qs := &qStmt{s} + return qs.QueryRowxContext(ctx, "", args...) +} + +// QueryxContext using this statement. +// Any placeholder parameters are replaced with supplied args. +func (s *Stmt) QueryxContext(ctx context.Context, args ...interface{}) (*Rows, error) { + qs := &qStmt{s} + return qs.QueryxContext(ctx, "", args...) +} + +func (q *qStmt) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) { + return q.Stmt.QueryContext(ctx, args...) +} + +func (q *qStmt) QueryxContext(ctx context.Context, query string, args ...interface{}) (*Rows, error) { + r, err := q.Stmt.QueryContext(ctx, args...) + if err != nil { + return nil, err + } + return &Rows{Rows: r, unsafe: q.Stmt.unsafe, Mapper: q.Stmt.Mapper}, err +} + +func (q *qStmt) QueryRowxContext(ctx context.Context, query string, args ...interface{}) *Row { + rows, err := q.Stmt.QueryContext(ctx, args...) + return &Row{rows: rows, err: err, unsafe: q.Stmt.unsafe, Mapper: q.Stmt.Mapper} +} + +func (q *qStmt) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) { + return q.Stmt.ExecContext(ctx, args...) +} diff --git a/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/sqlx_context_test.go b/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/sqlx_context_test.go new file mode 100644 index 0000000..e49ab8b --- /dev/null +++ b/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/sqlx_context_test.go @@ -0,0 +1,1426 @@ +// +build go1.8 + +// The following environment variables, if set, will be used: +// +// * SQLX_SQLITE_DSN +// * SQLX_POSTGRES_DSN +// * SQLX_MYSQL_DSN +// +// Set any of these variables to 'skip' to skip them. Note that for MySQL, +// the string '?parseTime=True' will be appended to the DSN if it's not there +// already. +// +package sqlx + +import ( + "context" + "database/sql" + "encoding/json" + "fmt" + "log" + "strings" + "testing" + "time" + + _ "github.com/go-sql-driver/mysql" + "github.com/jmoiron/sqlx/reflectx" + _ "github.com/lib/pq" + _ "github.com/mattn/go-sqlite3" +) + +func MultiExecContext(ctx context.Context, e ExecerContext, query string) { + stmts := strings.Split(query, ";\n") + if len(strings.Trim(stmts[len(stmts)-1], " \n\t\r")) == 0 { + stmts = stmts[:len(stmts)-1] + } + for _, s := range stmts { + _, err := e.ExecContext(ctx, s) + if err != nil { + fmt.Println(err, s) + } + } +} + +func RunWithSchemaContext(ctx context.Context, schema Schema, t *testing.T, test func(ctx context.Context, db *DB, t *testing.T)) { + runner := func(ctx context.Context, db *DB, t *testing.T, create, drop, now string) { + defer func() { + MultiExecContext(ctx, db, drop) + }() + + MultiExecContext(ctx, db, create) + test(ctx, db, t) + } + + if TestPostgres { + create, drop, now := schema.Postgres() + runner(ctx, pgdb, t, create, drop, now) + } + if TestSqlite { + create, drop, now := schema.Sqlite3() + runner(ctx, sldb, t, create, drop, now) + } + if TestMysql { + create, drop, now := schema.MySQL() + runner(ctx, mysqldb, t, create, drop, now) + } +} + +func loadDefaultFixtureContext(ctx context.Context, db *DB, t *testing.T) { + tx := db.MustBeginTx(ctx, nil) + tx.MustExecContext(ctx, tx.Rebind("INSERT INTO person (first_name, last_name, email) VALUES (?, ?, ?)"), "Jason", "Moiron", "jmoiron@jmoiron.net") + tx.MustExecContext(ctx, tx.Rebind("INSERT INTO person (first_name, last_name, email) VALUES (?, ?, ?)"), "John", "Doe", "johndoeDNE@gmail.net") + tx.MustExecContext(ctx, tx.Rebind("INSERT INTO place (country, city, telcode) VALUES (?, ?, ?)"), "United States", "New York", "1") + tx.MustExecContext(ctx, tx.Rebind("INSERT INTO place (country, telcode) VALUES (?, ?)"), "Hong Kong", "852") + tx.MustExecContext(ctx, tx.Rebind("INSERT INTO place (country, telcode) VALUES (?, ?)"), "Singapore", "65") + if db.DriverName() == "mysql" { + tx.MustExecContext(ctx, tx.Rebind("INSERT INTO capplace (`COUNTRY`, `TELCODE`) VALUES (?, ?)"), "Sarf Efrica", "27") + } else { + tx.MustExecContext(ctx, tx.Rebind("INSERT INTO capplace (\"COUNTRY\", \"TELCODE\") VALUES (?, ?)"), "Sarf Efrica", "27") + } + tx.MustExecContext(ctx, tx.Rebind("INSERT INTO employees (name, id) VALUES (?, ?)"), "Peter", "4444") + tx.MustExecContext(ctx, tx.Rebind("INSERT INTO employees (name, id, boss_id) VALUES (?, ?, ?)"), "Joe", "1", "4444") + tx.MustExecContext(ctx, tx.Rebind("INSERT INTO employees (name, id, boss_id) VALUES (?, ?, ?)"), "Martin", "2", "4444") + tx.Commit() +} + +// Test a new backwards compatible feature, that missing scan destinations +// will silently scan into sql.RawText rather than failing/panicing +func TestMissingNamesContextContext(t *testing.T) { + RunWithSchemaContext(context.Background(), defaultSchema, t, func(ctx context.Context, db *DB, t *testing.T) { + loadDefaultFixtureContext(ctx, db, t) + type PersonPlus struct { + FirstName string `db:"first_name"` + LastName string `db:"last_name"` + Email string + //AddedAt time.Time `db:"added_at"` + } + + // test Select first + pps := []PersonPlus{} + // pps lacks added_at destination + err := db.SelectContext(ctx, &pps, "SELECT * FROM person") + if err == nil { + t.Error("Expected missing name from Select to fail, but it did not.") + } + + // test Get + pp := PersonPlus{} + err = db.GetContext(ctx, &pp, "SELECT * FROM person LIMIT 1") + if err == nil { + t.Error("Expected missing name Get to fail, but it did not.") + } + + // test naked StructScan + pps = []PersonPlus{} + rows, err := db.QueryContext(ctx, "SELECT * FROM person LIMIT 1") + if err != nil { + t.Fatal(err) + } + rows.Next() + err = StructScan(rows, &pps) + if err == nil { + t.Error("Expected missing name in StructScan to fail, but it did not.") + } + rows.Close() + + // now try various things with unsafe set. + db = db.Unsafe() + pps = []PersonPlus{} + err = db.SelectContext(ctx, &pps, "SELECT * FROM person") + if err != nil { + t.Error(err) + } + + // test Get + pp = PersonPlus{} + err = db.GetContext(ctx, &pp, "SELECT * FROM person LIMIT 1") + if err != nil { + t.Error(err) + } + + // test naked StructScan + pps = []PersonPlus{} + rowsx, err := db.QueryxContext(ctx, "SELECT * FROM person LIMIT 1") + if err != nil { + t.Fatal(err) + } + rowsx.Next() + err = StructScan(rowsx, &pps) + if err != nil { + t.Error(err) + } + rowsx.Close() + + // test Named stmt + if !isUnsafe(db) { + t.Error("Expected db to be unsafe, but it isn't") + } + nstmt, err := db.PrepareNamedContext(ctx, `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.SelectContext(ctx, &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.PrepareNamedContext(ctx, `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.SelectContext(ctx, &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 TestEmbeddedStructsContextContext(t *testing.T) { + type Loop1 struct{ Person } + type Loop2 struct{ Loop1 } + type Loop3 struct{ Loop2 } + + RunWithSchemaContext(context.Background(), defaultSchema, t, func(ctx context.Context, db *DB, t *testing.T) { + loadDefaultFixtureContext(ctx, db, t) + peopleAndPlaces := []PersonPlace{} + err := db.SelectContext( + ctx, + &peopleAndPlaces, + `SELECT person.*, place.* FROM + person natural join place`) + if err != nil { + t.Fatal(err) + } + for _, pp := range peopleAndPlaces { + if len(pp.Person.FirstName) == 0 { + t.Errorf("Expected non zero lengthed first name.") + } + if len(pp.Place.Country) == 0 { + t.Errorf("Expected non zero lengthed country.") + } + } + + // test embedded structs with StructScan + rows, err := db.QueryxContext( + ctx, + `SELECT person.*, place.* FROM + person natural join place`) + if err != nil { + t.Error(err) + } + + perp := PersonPlace{} + rows.Next() + err = rows.StructScan(&perp) + if err != nil { + t.Error(err) + } + + if len(perp.Person.FirstName) == 0 { + t.Errorf("Expected non zero lengthed first name.") + } + if len(perp.Place.Country) == 0 { + t.Errorf("Expected non zero lengthed country.") + } + + rows.Close() + + // test the same for embedded pointer structs + peopleAndPlacesPtrs := []PersonPlacePtr{} + err = db.SelectContext( + ctx, + &peopleAndPlacesPtrs, + `SELECT person.*, place.* FROM + person natural join place`) + if err != nil { + t.Fatal(err) + } + for _, pp := range peopleAndPlacesPtrs { + if len(pp.Person.FirstName) == 0 { + t.Errorf("Expected non zero lengthed first name.") + } + if len(pp.Place.Country) == 0 { + t.Errorf("Expected non zero lengthed country.") + } + } + + // test "deep nesting" + l3s := []Loop3{} + err = db.SelectContext(ctx, &l3s, `select * from person`) + if err != nil { + t.Fatal(err) + } + for _, l3 := range l3s { + if len(l3.Loop2.Loop1.Person.FirstName) == 0 { + t.Errorf("Expected non zero lengthed first name.") + } + } + + // test "embed conflicts" + ec := []EmbedConflict{} + err = db.SelectContext(ctx, &ec, `select * from person`) + // I'm torn between erroring here or having some kind of working behavior + // in order to allow for more flexibility in destination structs + if err != nil { + t.Errorf("Was not expecting an error on embed conflicts.") + } + }) +} + +func TestJoinQueryContext(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 + + RunWithSchemaContext(context.Background(), defaultSchema, t, func(ctx context.Context, db *DB, t *testing.T) { + loadDefaultFixtureContext(ctx, db, t) + + var employees []struct { + Employee + Boss `db:"boss"` + } + + err := db.SelectContext(ctx, + &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 TestJoinQueryNamedPointerStructsContext(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 + + RunWithSchemaContext(context.Background(), defaultSchema, t, func(ctx context.Context, db *DB, t *testing.T) { + loadDefaultFixtureContext(ctx, db, t) + + var employees []struct { + Emp1 *Employee `db:"emp1"` + Emp2 *Employee `db:"emp2"` + *Boss `db:"boss"` + } + + err := db.SelectContext(ctx, + &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 TestSelectSliceMapTimeContext(t *testing.T) { + RunWithSchemaContext(context.Background(), defaultSchema, t, func(ctx context.Context, db *DB, t *testing.T) { + loadDefaultFixtureContext(ctx, db, t) + rows, err := db.QueryxContext(ctx, "SELECT * FROM person") + if err != nil { + t.Fatal(err) + } + for rows.Next() { + _, err := rows.SliceScan() + if err != nil { + t.Error(err) + } + } + + rows, err = db.QueryxContext(ctx, "SELECT * FROM person") + if err != nil { + t.Fatal(err) + } + for rows.Next() { + m := map[string]interface{}{} + err := rows.MapScan(m) + if err != nil { + t.Error(err) + } + } + + }) +} + +func TestNilReceiverContext(t *testing.T) { + RunWithSchemaContext(context.Background(), defaultSchema, t, func(ctx context.Context, db *DB, t *testing.T) { + loadDefaultFixtureContext(ctx, db, t) + var p *Person + err := db.GetContext(ctx, p, "SELECT * FROM person LIMIT 1") + if err == nil { + t.Error("Expected error when getting into nil struct ptr.") + } + var pp *[]Person + err = db.SelectContext(ctx, pp, "SELECT * FROM person") + if err == nil { + t.Error("Expected an error when selecting into nil slice ptr.") + } + }) +} + +func TestNamedQueryContext(t *testing.T) { + var schema = Schema{ + create: ` + CREATE TABLE place ( + id integer PRIMARY KEY, + name text NULL + ); + CREATE TABLE person ( + first_name text NULL, + last_name text NULL, + email text NULL + ); + CREATE TABLE placeperson ( + first_name text NULL, + last_name text NULL, + email text NULL, + place_id integer NULL + ); + CREATE TABLE jsperson ( + "FIRST" text NULL, + last_name text NULL, + "EMAIL" text NULL + );`, + drop: ` + drop table person; + drop table jsperson; + drop table place; + drop table placeperson; + `, + } + + RunWithSchemaContext(context.Background(), schema, t, func(ctx context.Context, db *DB, t *testing.T) { + type Person struct { + FirstName sql.NullString `db:"first_name"` + LastName sql.NullString `db:"last_name"` + Email sql.NullString + } + + p := Person{ + 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)` + _, err := db.NamedExecContext(ctx, q1, p) + if err != nil { + log.Fatal(err) + } + + p2 := &Person{} + rows, err := db.NamedQueryContext(ctx, "SELECT * FROM person WHERE first_name=:first_name", p) + if err != nil { + log.Fatal(err) + } + for rows.Next() { + err = rows.StructScan(p2) + if err != nil { + t.Error(err) + } + if p2.FirstName.String != "ben" { + t.Error("Expected first name of `ben`, got " + p2.FirstName.String) + } + if p2.LastName.String != "doe" { + t.Error("Expected first name of `doe`, got " + p2.LastName.String) + } + } + + // these are tests for #73; they verify that named queries work if you've + // changed the db mapper. This code checks both NamedQuery "ad-hoc" style + // queries and NamedStmt queries, which use different code paths internally. + old := *db.Mapper + + type JSONPerson struct { + FirstName sql.NullString `json:"FIRST"` + LastName sql.NullString `json:"last_name"` + Email sql.NullString + } + + 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) + + // prepare queries for case sensitivity to test our ToUpper function. + // postgres and sqlite accept "", but mysql uses ``; since Go's multi-line + // strings are `` we use "" by default and swap out for MySQL + pdb := func(s string, db *DB) string { + if db.DriverName() == "mysql" { + return strings.Replace(s, `"`, "`", -1) + } + return s + } + + q1 = `INSERT INTO jsperson ("FIRST", last_name, "EMAIL") VALUES (:FIRST, :last_name, :EMAIL)` + _, err = db.NamedExecContext(ctx, pdb(q1, db), jp) + if err != nil { + t.Fatal(err, db.DriverName()) + } + + // Checks that a person pulled out of the db matches the one we put in + check := func(t *testing.T, rows *Rows) { + jp = JSONPerson{} + for rows.Next() { + err = rows.StructScan(&jp) + if err != nil { + t.Error(err) + } + if jp.FirstName.String != "ben" { + t.Errorf("Expected first name of `ben`, got `%s` (%s) ", jp.FirstName.String, db.DriverName()) + } + if jp.LastName.String != "smith" { + t.Errorf("Expected LastName of `smith`, got `%s` (%s)", jp.LastName.String, db.DriverName()) + } + if jp.Email.String != "ben@smith.com" { + t.Errorf("Expected first name of `doe`, got `%s` (%s)", jp.Email.String, db.DriverName()) + } + } + } + + ns, err := db.PrepareNamed(pdb(` + SELECT * FROM jsperson + WHERE + "FIRST"=:FIRST AND + last_name=:last_name AND + "EMAIL"=:EMAIL + `, db)) + + if err != nil { + t.Fatal(err) + } + rows, err = ns.QueryxContext(ctx, jp) + if err != nil { + t.Fatal(err) + } + + check(t, rows) + + // Check exactly the same thing, but with db.NamedQuery, which does not go + // through the PrepareNamed/NamedStmt path. + rows, err = db.NamedQueryContext(ctx, pdb(` + SELECT * FROM jsperson + WHERE + "FIRST"=:FIRST AND + last_name=:last_name AND + "EMAIL"=:EMAIL + `, db), jp) + if err != nil { + t.Fatal(err) + } + + check(t, rows) + + db.Mapper = &old + + // Test nested structs + type Place struct { + ID int `db:"id"` + Name sql.NullString `db:"name"` + } + type PlacePerson struct { + FirstName sql.NullString `db:"first_name"` + LastName sql.NullString `db:"last_name"` + Email sql.NullString + Place Place `db:"place"` + } + + pl := Place{ + Name: sql.NullString{String: "myplace", Valid: true}, + } + + pp := PlacePerson{ + FirstName: sql.NullString{String: "ben", Valid: true}, + LastName: sql.NullString{String: "doe", Valid: true}, + Email: sql.NullString{String: "ben@doe.com", Valid: true}, + } + + q2 := `INSERT INTO place (id, name) VALUES (1, :name)` + _, err = db.NamedExecContext(ctx, q2, pl) + if err != nil { + log.Fatal(err) + } + + id := 1 + pp.Place.ID = id + + q3 := `INSERT INTO placeperson (first_name, last_name, email, place_id) VALUES (:first_name, :last_name, :email, :place.id)` + _, err = db.NamedExecContext(ctx, q3, pp) + if err != nil { + log.Fatal(err) + } + + pp2 := &PlacePerson{} + rows, err = db.NamedQueryContext(ctx, ` + SELECT + first_name, + last_name, + email, + place.id AS "place.id", + place.name AS "place.name" + FROM placeperson + INNER JOIN place ON place.id = placeperson.place_id + WHERE + place.id=:place.id`, pp) + if err != nil { + log.Fatal(err) + } + for rows.Next() { + err = rows.StructScan(pp2) + if err != nil { + t.Error(err) + } + if pp2.FirstName.String != "ben" { + t.Error("Expected first name of `ben`, got " + pp2.FirstName.String) + } + if pp2.LastName.String != "doe" { + t.Error("Expected first name of `doe`, got " + pp2.LastName.String) + } + if pp2.Place.Name.String != "myplace" { + t.Error("Expected place name of `myplace`, got " + pp2.Place.Name.String) + } + if pp2.Place.ID != pp.Place.ID { + t.Errorf("Expected place name of %v, got %v", pp.Place.ID, pp2.Place.ID) + } + } + }) +} + +func TestNilInsertsContext(t *testing.T) { + var schema = Schema{ + create: ` + CREATE TABLE tt ( + id integer, + value text NULL DEFAULT NULL + );`, + drop: "drop table tt;", + } + + RunWithSchemaContext(context.Background(), schema, t, func(ctx context.Context, db *DB, t *testing.T) { + type TT struct { + ID int + Value *string + } + var v, v2 TT + r := db.Rebind + + db.MustExecContext(ctx, r(`INSERT INTO tt (id) VALUES (1)`)) + db.GetContext(ctx, &v, r(`SELECT * FROM tt`)) + 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) + } + + 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 + // writing. This was fixed by creating & using the reflectx.FieldByIndexesReadOnly + // function. This next line is important as it provides the only coverage for this. + db.NamedExecContext(ctx, `INSERT INTO tt (id, value) VALUES (:id, :value)`, v) + + db.GetContext(ctx, &v2, r(`SELECT * FROM tt WHERE id=2`)) + 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) + } + }) +} + +func TestScanErrorContext(t *testing.T) { + var schema = Schema{ + create: ` + CREATE TABLE kv ( + k text, + v integer + );`, + drop: `drop table kv;`, + } + + RunWithSchemaContext(context.Background(), schema, t, func(ctx context.Context, db *DB, t *testing.T) { + type WrongTypes struct { + K int + V string + } + _, err := db.Exec(db.Rebind("INSERT INTO kv (k, v) VALUES (?, ?)"), "hi", 1) + if err != nil { + t.Error(err) + } + + rows, err := db.QueryxContext(ctx, "SELECT * FROM kv") + if err != nil { + t.Error(err) + } + for rows.Next() { + var wt WrongTypes + err := rows.StructScan(&wt) + if err == nil { + t.Errorf("%s: Scanning wrong types into keys should have errored.", db.DriverName()) + } + } + }) +} + +// FIXME: this function is kinda big but it slows things down to be constantly +// loading and reloading the schema.. + +func TestUsageContext(t *testing.T) { + RunWithSchemaContext(context.Background(), defaultSchema, t, func(ctx context.Context, db *DB, t *testing.T) { + loadDefaultFixtureContext(ctx, db, t) + slicemembers := []SliceMember{} + err := db.SelectContext(ctx, &slicemembers, "SELECT * FROM place ORDER BY telcode ASC") + if err != nil { + t.Fatal(err) + } + + people := []Person{} + + err = db.SelectContext(ctx, &people, "SELECT * FROM person ORDER BY first_name ASC") + if err != nil { + t.Fatal(err) + } + + jason, john := people[0], people[1] + if jason.FirstName != "Jason" { + t.Errorf("Expecting FirstName of Jason, got %s", jason.FirstName) + } + if jason.LastName != "Moiron" { + t.Errorf("Expecting LastName of Moiron, got %s", jason.LastName) + } + if jason.Email != "jmoiron@jmoiron.net" { + t.Errorf("Expecting Email of jmoiron@jmoiron.net, got %s", jason.Email) + } + if john.FirstName != "John" || john.LastName != "Doe" || john.Email != "johndoeDNE@gmail.net" { + t.Errorf("John Doe's person record not what expected: Got %v\n", john) + } + + jason = Person{} + err = db.GetContext(ctx, &jason, db.Rebind("SELECT * FROM person WHERE first_name=?"), "Jason") + + if err != nil { + t.Fatal(err) + } + if jason.FirstName != "Jason" { + t.Errorf("Expecting to get back Jason, but got %v\n", jason.FirstName) + } + + err = db.GetContext(ctx, &jason, db.Rebind("SELECT * FROM person WHERE first_name=?"), "Foobar") + if err == nil { + t.Errorf("Expecting an error, got nil\n") + } + if err != sql.ErrNoRows { + t.Errorf("Expected sql.ErrNoRows, got %v\n", err) + } + + // The following tests check statement reuse, which was actually a problem + // due to copying being done when creating Stmt's which was eventually removed + stmt1, err := db.PreparexContext(ctx, db.Rebind("SELECT * FROM person WHERE first_name=?")) + if err != nil { + t.Fatal(err) + } + jason = Person{} + + row := stmt1.QueryRowx("DoesNotExist") + row.Scan(&jason) + row = stmt1.QueryRowx("DoesNotExist") + row.Scan(&jason) + + err = stmt1.GetContext(ctx, &jason, "DoesNotExist User") + if err == nil { + t.Error("Expected an error") + } + err = stmt1.GetContext(ctx, &jason, "DoesNotExist User 2") + if err == nil { + t.Fatal(err) + } + + stmt2, err := db.PreparexContext(ctx, db.Rebind("SELECT * FROM person WHERE first_name=?")) + if err != nil { + t.Fatal(err) + } + jason = Person{} + tx, err := db.Beginx() + if err != nil { + t.Fatal(err) + } + tstmt2 := tx.Stmtx(stmt2) + row2 := tstmt2.QueryRowx("Jason") + err = row2.StructScan(&jason) + if err != nil { + t.Error(err) + } + tx.Commit() + + places := []*Place{} + err = db.SelectContext(ctx, &places, "SELECT telcode FROM place ORDER BY telcode ASC") + if err != nil { + t.Fatal(err) + } + + usa, singsing, honkers := places[0], places[1], places[2] + + if usa.TelCode != 1 || honkers.TelCode != 852 || singsing.TelCode != 65 { + t.Errorf("Expected integer telcodes to work, got %#v", places) + } + + placesptr := []PlacePtr{} + err = db.SelectContext(ctx, &placesptr, "SELECT * FROM place ORDER BY telcode ASC") + if err != nil { + t.Error(err) + } + //fmt.Printf("%#v\n%#v\n%#v\n", placesptr[0], placesptr[1], placesptr[2]) + + // if you have null fields and use SELECT *, you must use sql.Null* in your struct + // this test also verifies that you can use either a []Struct{} or a []*Struct{} + places2 := []Place{} + err = db.SelectContext(ctx, &places2, "SELECT * FROM place ORDER BY telcode ASC") + if err != nil { + t.Fatal(err) + } + + usa, singsing, honkers = &places2[0], &places2[1], &places2[2] + + // this should return a type error that &p is not a pointer to a struct slice + p := Place{} + err = db.SelectContext(ctx, &p, "SELECT * FROM place ORDER BY telcode ASC") + if err == nil { + t.Errorf("Expected an error, argument to select should be a pointer to a struct slice") + } + + // this should be an error + pl := []Place{} + err = db.SelectContext(ctx, pl, "SELECT * FROM place ORDER BY telcode ASC") + if err == nil { + t.Errorf("Expected an error, argument to select should be a pointer to a struct slice, not a slice.") + } + + if usa.TelCode != 1 || honkers.TelCode != 852 || singsing.TelCode != 65 { + t.Errorf("Expected integer telcodes to work, got %#v", places) + } + + stmt, err := db.PreparexContext(ctx, db.Rebind("SELECT country, telcode FROM place WHERE telcode > ? ORDER BY telcode ASC")) + if err != nil { + t.Error(err) + } + + places = []*Place{} + err = stmt.SelectContext(ctx, &places, 10) + if len(places) != 2 { + t.Error("Expected 2 places, got 0.") + } + if err != nil { + t.Fatal(err) + } + singsing, honkers = places[0], places[1] + if singsing.TelCode != 65 || honkers.TelCode != 852 { + t.Errorf("Expected the right telcodes, got %#v", places) + } + + rows, err := db.QueryxContext(ctx, "SELECT * FROM place") + if err != nil { + t.Fatal(err) + } + place := Place{} + for rows.Next() { + err = rows.StructScan(&place) + if err != nil { + t.Fatal(err) + } + } + + rows, err = db.QueryxContext(ctx, "SELECT * FROM place") + if err != nil { + t.Fatal(err) + } + m := map[string]interface{}{} + for rows.Next() { + err = rows.MapScan(m) + if err != nil { + t.Fatal(err) + } + _, ok := m["country"] + if !ok { + t.Errorf("Expected key `country` in map but could not find it (%#v)\n", m) + } + } + + rows, err = db.QueryxContext(ctx, "SELECT * FROM place") + if err != nil { + t.Fatal(err) + } + for rows.Next() { + s, err := rows.SliceScan() + if err != nil { + t.Error(err) + } + if len(s) != 3 { + t.Errorf("Expected 3 columns in result, got %d\n", len(s)) + } + } + + // test advanced querying + // test that NamedExec works with a map as well as a struct + _, err = db.NamedExecContext(ctx, "INSERT INTO person (first_name, last_name, email) VALUES (:first, :last, :email)", map[string]interface{}{ + "first": "Bin", + "last": "Smuth", + "email": "bensmith@allblacks.nz", + }) + if err != nil { + t.Fatal(err) + } + + // ensure that if the named param happens right at the end it still works + // ensure that NamedQuery works with a map[string]interface{} + rows, err = db.NamedQueryContext(ctx, "SELECT * FROM person WHERE first_name=:first", map[string]interface{}{"first": "Bin"}) + if err != nil { + t.Fatal(err) + } + + ben := &Person{} + for rows.Next() { + err = rows.StructScan(ben) + if err != nil { + t.Fatal(err) + } + if ben.FirstName != "Bin" { + t.Fatal("Expected first name of `Bin`, got " + ben.FirstName) + } + if ben.LastName != "Smuth" { + t.Fatal("Expected first name of `Smuth`, got " + ben.LastName) + } + } + + ben.FirstName = "Ben" + ben.LastName = "Smith" + ben.Email = "binsmuth@allblacks.nz" + + // Insert via a named query using the struct + _, err = db.NamedExecContext(ctx, "INSERT INTO person (first_name, last_name, email) VALUES (:first_name, :last_name, :email)", ben) + + if err != nil { + t.Fatal(err) + } + + rows, err = db.NamedQueryContext(ctx, "SELECT * FROM person WHERE first_name=:first_name", ben) + if err != nil { + t.Fatal(err) + } + for rows.Next() { + err = rows.StructScan(ben) + if err != nil { + t.Fatal(err) + } + if ben.FirstName != "Ben" { + t.Fatal("Expected first name of `Ben`, got " + ben.FirstName) + } + if ben.LastName != "Smith" { + t.Fatal("Expected first name of `Smith`, got " + ben.LastName) + } + } + // ensure that Get does not panic on emppty result set + person := &Person{} + err = db.GetContext(ctx, person, "SELECT * FROM person WHERE first_name=$1", "does-not-exist") + if err == nil { + t.Fatal("Should have got an error for Get on non-existent row.") + } + + // lets test prepared statements some more + + stmt, err = db.PreparexContext(ctx, db.Rebind("SELECT * FROM person WHERE first_name=?")) + if err != nil { + t.Fatal(err) + } + rows, err = stmt.QueryxContext(ctx, "Ben") + if err != nil { + t.Fatal(err) + } + for rows.Next() { + err = rows.StructScan(ben) + if err != nil { + t.Fatal(err) + } + if ben.FirstName != "Ben" { + t.Fatal("Expected first name of `Ben`, got " + ben.FirstName) + } + if ben.LastName != "Smith" { + t.Fatal("Expected first name of `Smith`, got " + ben.LastName) + } + } + + john = Person{} + stmt, err = db.PreparexContext(ctx, db.Rebind("SELECT * FROM person WHERE first_name=?")) + if err != nil { + t.Error(err) + } + err = stmt.GetContext(ctx, &john, "John") + if err != nil { + t.Error(err) + } + + // test name mapping + // THIS USED TO WORK BUT WILL NO LONGER WORK. + db.MapperFunc(strings.ToUpper) + rsa := CPlace{} + err = db.GetContext(ctx, &rsa, "SELECT * FROM capplace;") + if err != nil { + t.Error(err, "in db:", db.DriverName()) + } + db.MapperFunc(strings.ToLower) + + // create a copy and change the mapper, then verify the copy behaves + // differently from the original. + dbCopy := NewDb(db.DB, db.DriverName()) + dbCopy.MapperFunc(strings.ToUpper) + err = dbCopy.GetContext(ctx, &rsa, "SELECT * FROM capplace;") + if err != nil { + fmt.Println(db.DriverName()) + t.Error(err) + } + + err = db.GetContext(ctx, &rsa, "SELECT * FROM cappplace;") + if err == nil { + t.Error("Expected no error, got ", err) + } + + // test base type slices + var sdest []string + rows, err = db.QueryxContext(ctx, "SELECT email FROM person ORDER BY email ASC;") + if err != nil { + t.Error(err) + } + err = scanAll(rows, &sdest, false) + if err != nil { + t.Error(err) + } + + // test Get with base types + var count int + err = db.GetContext(ctx, &count, "SELECT count(*) FROM person;") + if err != nil { + t.Error(err) + } + if count != len(sdest) { + t.Errorf("Expected %d == %d (count(*) vs len(SELECT ..)", count, len(sdest)) + } + + // test Get and Select with time.Time, #84 + var addedAt time.Time + err = db.GetContext(ctx, &addedAt, "SELECT added_at FROM person LIMIT 1;") + if err != nil { + t.Error(err) + } + + var addedAts []time.Time + err = db.SelectContext(ctx, &addedAts, "SELECT added_at FROM person;") + if err != nil { + t.Error(err) + } + + // test it on a double pointer + var pcount *int + err = db.GetContext(ctx, &pcount, "SELECT count(*) FROM person;") + if err != nil { + t.Error(err) + } + if *pcount != count { + t.Errorf("expected %d = %d", *pcount, count) + } + + // test Select... + sdest = []string{} + err = db.SelectContext(ctx, &sdest, "SELECT first_name FROM person ORDER BY first_name ASC;") + if err != nil { + t.Error(err) + } + expected := []string{"Ben", "Bin", "Jason", "John"} + for i, got := range sdest { + if got != expected[i] { + t.Errorf("Expected %d result to be %s, but got %s", i, expected[i], got) + } + } + + var nsdest []sql.NullString + err = db.SelectContext(ctx, &nsdest, "SELECT city FROM place ORDER BY city ASC") + if err != nil { + t.Error(err) + } + for _, val := range nsdest { + if val.Valid && val.String != "New York" { + t.Errorf("expected single valid result to be `New York`, but got %s", val.String) + } + } + }) +} + +// tests that sqlx will not panic when the wrong driver is passed because +// of an automatic nil dereference in sqlx.Open(), which was fixed. +func TestDoNotPanicOnConnectContext(t *testing.T) { + _, err := ConnectContext(context.Background(), "bogus", "hehe") + if err == nil { + t.Errorf("Should return error when using bogus driverName") + } +} + +func TestEmbeddedMapsContext(t *testing.T) { + var schema = Schema{ + create: ` + CREATE TABLE message ( + string text, + properties text + );`, + drop: `drop table message;`, + } + + RunWithSchemaContext(context.Background(), schema, t, func(ctx context.Context, db *DB, t *testing.T) { + messages := []Message{ + {"Hello, World", PropertyMap{"one": "1", "two": "2"}}, + {"Thanks, Joy", PropertyMap{"pull": "request"}}, + } + q1 := `INSERT INTO message (string, properties) VALUES (:string, :properties);` + for _, m := range messages { + _, err := db.NamedExecContext(ctx, q1, m) + if err != nil { + t.Fatal(err) + } + } + var count int + err := db.GetContext(ctx, &count, "SELECT count(*) FROM message") + if err != nil { + t.Fatal(err) + } + if count != len(messages) { + t.Fatalf("Expected %d messages in DB, found %d", len(messages), count) + } + + var m Message + err = db.GetContext(ctx, &m, "SELECT * FROM message LIMIT 1;") + if err != nil { + t.Fatal(err) + } + if m.Properties == nil { + t.Fatal("Expected m.Properties to not be nil, but it was.") + } + }) +} + +func TestIssue197Context(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 } + RunWithSchemaContext(context.Background(), defaultSchema, t, func(ctx context.Context, db *DB, t *testing.T) { + var err error + var v, q Var + if err = db.GetContext(ctx, &v, `SELECT '{"a": "b"}' AS raw`); err != nil { + t.Fatal(err) + } + if err = db.GetContext(ctx, &q, `SELECT 'null' AS raw`); err != nil { + t.Fatal(err) + } + + var v2, q2 Var2 + if err = db.GetContext(ctx, &v2, `SELECT '{"a": "b"}' AS raw`); err != nil { + t.Fatal(err) + } + if err = db.GetContext(ctx, &q2, `SELECT 'null' AS raw`); err != nil { + t.Fatal(err) + } + + var v3, q3 Var3 + if err = db.QueryRowContext(ctx, `SELECT '{"a": "b"}' AS raw`).Scan(&v3.Raw); err != nil { + t.Fatal(err) + } + if err = db.QueryRowContext(ctx, `SELECT '{"c": "d"}' AS raw`).Scan(&q3.Raw); err != nil { + t.Fatal(err) + } + t.Fail() + }) +} + +func TestInContext(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.") + } + } + RunWithSchemaContext(context.Background(), defaultSchema, t, func(ctx context.Context, db *DB, t *testing.T) { + loadDefaultFixtureContext(ctx, db, t) + //tx.MustExecContext(ctx, tx.Rebind("INSERT INTO place (country, city, telcode) VALUES (?, ?, ?)"), "United States", "New York", "1") + //tx.MustExecContext(ctx, tx.Rebind("INSERT INTO place (country, telcode) VALUES (?, ?)"), "Hong Kong", "852") + //tx.MustExecContext(ctx, 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.SelectContext(ctx, &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 TestEmbeddedLiteralsContext(t *testing.T) { + var schema = Schema{ + create: ` + CREATE TABLE x ( + k text + );`, + drop: `drop table x;`, + } + + RunWithSchemaContext(context.Background(), schema, t, func(ctx context.Context, db *DB, t *testing.T) { + type t1 struct { + K *string + } + type t2 struct { + Inline struct { + F string + } + K *string + } + + db.MustExecContext(ctx, db.Rebind("INSERT INTO x (k) VALUES (?), (?), (?);"), "one", "two", "three") + + target := t1{} + err := db.GetContext(ctx, &target, db.Rebind("SELECT * FROM x WHERE k=?"), "one") + if err != nil { + t.Error(err) + } + if *target.K != "one" { + t.Error("Expected target.K to be `one`, got ", target.K) + } + + target2 := t2{} + err = db.GetContext(ctx, &target2, db.Rebind("SELECT * FROM x WHERE k=?"), "one") + if err != nil { + t.Error(err) + } + if *target2.K != "one" { + t.Errorf("Expected target2.K to be `one`, got `%v`", target2.K) + } + }) +} + +func TestConn(t *testing.T) { + var schema = Schema{ + create: ` + CREATE TABLE tt_conn ( + id integer, + value text NULL DEFAULT NULL + );`, + drop: "drop table tt_conn;", + } + + RunWithSchemaContext(context.Background(), schema, t, func(ctx context.Context, db *DB, t *testing.T) { + conn, err := db.Connx(ctx) + defer conn.Close() + if err != nil { + t.Fatal(err) + } + + _, err = conn.ExecContext(ctx, conn.Rebind(`INSERT INTO tt_conn (id, value) VALUES (?, ?), (?, ?)`), 1, "a", 2, "b") + if err != nil { + t.Fatal(err) + } + + type s struct { + ID int `db:"id"` + Value string `db:"value"` + } + + v := []s{} + + err = conn.SelectContext(ctx, &v, "SELECT * FROM tt_conn ORDER BY id ASC") + if err != nil { + t.Fatal(err) + } + + if v[0].ID != 1 { + t.Errorf("Expecting ID of 1, got %d", v[0].ID) + } + + v1 := s{} + err = conn.GetContext(ctx, &v1, conn.Rebind("SELECT * FROM tt_conn WHERE id=?"), 1) + + if err != nil { + t.Fatal(err) + } + if v1.ID != 1 { + t.Errorf("Expecting to get back 1, but got %v\n", v1.ID) + } + + stmt, err := conn.PreparexContext(ctx, conn.Rebind("SELECT * FROM tt_conn WHERE id=?")) + if err != nil { + t.Fatal(err) + } + v1 = s{} + tx, err := conn.BeginTxx(ctx, nil) + if err != nil { + t.Fatal(err) + } + tstmt := tx.Stmtx(stmt) + row := tstmt.QueryRowx(1) + err = row.StructScan(&v1) + if err != nil { + t.Error(err) + } + tx.Commit() + if v1.ID != 1 { + t.Errorf("Expecting to get back 1, but got %v\n", v1.ID) + } + + rows, err := conn.QueryxContext(ctx, "SELECT * FROM tt_conn") + if err != nil { + t.Fatal(err) + } + + for rows.Next() { + err = rows.StructScan(&v1) + if err != nil { + t.Fatal(err) + } + } + }) +} diff --git a/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/sqlx_test.go b/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/sqlx_test.go new file mode 100644 index 0000000..1d4aa20 --- /dev/null +++ b/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/sqlx_test.go @@ -0,0 +1,1926 @@ +// The following environment variables, if set, will be used: +// +// * SQLX_SQLITE_DSN +// * SQLX_POSTGRES_DSN +// * SQLX_MYSQL_DSN +// +// Set any of these variables to 'skip' to skip them. Note that for MySQL, +// the string '?parseTime=True' will be appended to the DSN if it's not there +// already. +// +package sqlx + +import ( + "database/sql" + "database/sql/driver" + "encoding/json" + "fmt" + "log" + "os" + "reflect" + "strings" + "testing" + "time" + + _ "github.com/go-sql-driver/mysql" + "github.com/jmoiron/sqlx/reflectx" + _ "github.com/lib/pq" + _ "github.com/mattn/go-sqlite3" +) + +/* compile time checks that Db, Tx, Stmt (qStmt) implement expected interfaces */ +var _, _ Ext = &DB{}, &Tx{} +var _, _ ColScanner = &Row{}, &Rows{} +var _ Queryer = &qStmt{} +var _ Execer = &qStmt{} + +var TestPostgres = true +var TestSqlite = true +var TestMysql = true + +var sldb *DB +var pgdb *DB +var mysqldb *DB +var active = []*DB{} + +func init() { + ConnectAll() +} + +func ConnectAll() { + var err error + + pgdsn := os.Getenv("SQLX_POSTGRES_DSN") + mydsn := os.Getenv("SQLX_MYSQL_DSN") + sqdsn := os.Getenv("SQLX_SQLITE_DSN") + + TestPostgres = pgdsn != "skip" + 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 { + fmt.Printf("Disabling PG tests:\n %v\n", err) + TestPostgres = false + } + } else { + fmt.Println("Disabling Postgres tests.") + } + + if TestMysql { + mysqldb, err = Connect("mysql", mydsn) + if err != nil { + fmt.Printf("Disabling MySQL tests:\n %v", err) + TestMysql = false + } + } else { + fmt.Println("Disabling MySQL tests.") + } + + if TestSqlite { + sldb, err = Connect("sqlite3", sqdsn) + if err != nil { + fmt.Printf("Disabling SQLite:\n %v", err) + TestSqlite = false + } + } else { + fmt.Println("Disabling SQLite tests.") + } +} + +type Schema struct { + create string + drop string +} + +func (s Schema) Postgres() (string, string, string) { + return s.create, s.drop, `now()` +} + +func (s Schema) MySQL() (string, string, string) { + return strings.Replace(s.create, `"`, "`", -1), s.drop, `now()` +} + +func (s Schema) Sqlite3() (string, string, string) { + return strings.Replace(s.create, `now()`, `CURRENT_TIMESTAMP`, -1), s.drop, `CURRENT_TIMESTAMP` +} + +var defaultSchema = Schema{ + create: ` +CREATE TABLE person ( + first_name text, + last_name text, + email text, + added_at timestamp default now() +); + +CREATE TABLE place ( + country text, + city text NULL, + telcode integer +); + +CREATE TABLE capplace ( + "COUNTRY" text, + "CITY" text NULL, + "TELCODE" integer +); + +CREATE TABLE nullperson ( + first_name text NULL, + 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; +`, +} + +type Person struct { + FirstName string `db:"first_name"` + LastName string `db:"last_name"` + Email string + AddedAt time.Time `db:"added_at"` +} + +type Person2 struct { + FirstName sql.NullString `db:"first_name"` + LastName sql.NullString `db:"last_name"` + Email sql.NullString +} + +type Place struct { + Country string + City sql.NullString + TelCode int +} + +type PlacePtr struct { + Country string + City *string + TelCode int +} + +type PersonPlace struct { + Person + Place +} + +type PersonPlacePtr struct { + *Person + *Place +} + +type EmbedConflict struct { + FirstName string `db:"first_name"` + Person +} + +type SliceMember struct { + Country string + City sql.NullString + TelCode int + People []Person `db:"-"` + Addresses []Place `db:"-"` +} + +// Note that because of field map caching, we need a new type here +// if we've used Place already somewhere in sqlx +type CPlace Place + +func MultiExec(e Execer, query string) { + stmts := strings.Split(query, ";\n") + if len(strings.Trim(stmts[len(stmts)-1], " \n\t\r")) == 0 { + stmts = stmts[:len(stmts)-1] + } + for _, s := range stmts { + _, err := e.Exec(s) + if err != nil { + fmt.Println(err, s) + } + } +} + +func RunWithSchema(schema Schema, t *testing.T, test func(db *DB, t *testing.T, now string)) { + runner := func(db *DB, t *testing.T, create, drop, now string) { + defer func() { + MultiExec(db, drop) + }() + + MultiExec(db, create) + test(db, t, now) + } + + if TestPostgres { + create, drop, now := schema.Postgres() + runner(pgdb, t, create, drop, now) + } + if TestSqlite { + create, drop, now := schema.Sqlite3() + runner(sldb, t, create, drop, now) + } + if TestMysql { + create, drop, now := schema.MySQL() + runner(mysqldb, t, create, drop, now) + } +} + +func loadDefaultFixture(db *DB, t *testing.T) { + tx := db.MustBegin() + tx.MustExec(tx.Rebind("INSERT INTO person (first_name, last_name, email) VALUES (?, ?, ?)"), "Jason", "Moiron", "jmoiron@jmoiron.net") + tx.MustExec(tx.Rebind("INSERT INTO person (first_name, last_name, email) VALUES (?, ?, ?)"), "John", "Doe", "johndoeDNE@gmail.net") + 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") + if db.DriverName() == "mysql" { + tx.MustExec(tx.Rebind("INSERT INTO capplace (`COUNTRY`, `TELCODE`) VALUES (?, ?)"), "Sarf Efrica", "27") + } 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() +} + +// Test a new backwards compatible feature, that missing scan destinations +// will silently scan into sql.RawText rather than failing/panicing +func TestMissingNames(t *testing.T) { + RunWithSchema(defaultSchema, t, func(db *DB, t *testing.T, now string) { + loadDefaultFixture(db, t) + type PersonPlus struct { + FirstName string `db:"first_name"` + LastName string `db:"last_name"` + Email string + //AddedAt time.Time `db:"added_at"` + } + + // test Select first + pps := []PersonPlus{} + // pps lacks added_at destination + err := db.Select(&pps, "SELECT * FROM person") + if err == nil { + t.Error("Expected missing name from Select to fail, but it did not.") + } + + // test Get + pp := PersonPlus{} + err = db.Get(&pp, "SELECT * FROM person LIMIT 1") + if err == nil { + t.Error("Expected missing name Get to fail, but it did not.") + } + + // test naked StructScan + pps = []PersonPlus{} + rows, err := db.Query("SELECT * FROM person LIMIT 1") + if err != nil { + t.Fatal(err) + } + rows.Next() + err = StructScan(rows, &pps) + if err == nil { + t.Error("Expected missing name in StructScan to fail, but it did not.") + } + rows.Close() + + // now try various things with unsafe set. + db = db.Unsafe() + pps = []PersonPlus{} + err = db.Select(&pps, "SELECT * FROM person") + if err != nil { + t.Error(err) + } + + // test Get + pp = PersonPlus{} + err = db.Get(&pp, "SELECT * FROM person LIMIT 1") + if err != nil { + t.Error(err) + } + + // test naked StructScan + pps = []PersonPlus{} + rowsx, err := db.Queryx("SELECT * FROM person LIMIT 1") + if err != nil { + t.Fatal(err) + } + rowsx.Next() + err = StructScan(rowsx, &pps) + if err != nil { + t.Error(err) + } + 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, now string) { + loadDefaultFixture(db, t) + peopleAndPlaces := []PersonPlace{} + err := db.Select( + &peopleAndPlaces, + `SELECT person.*, place.* FROM + person natural join place`) + if err != nil { + t.Fatal(err) + } + for _, pp := range peopleAndPlaces { + if len(pp.Person.FirstName) == 0 { + t.Errorf("Expected non zero lengthed first name.") + } + if len(pp.Place.Country) == 0 { + t.Errorf("Expected non zero lengthed country.") + } + } + + // test embedded structs with StructScan + rows, err := db.Queryx( + `SELECT person.*, place.* FROM + person natural join place`) + if err != nil { + t.Error(err) + } + + perp := PersonPlace{} + rows.Next() + err = rows.StructScan(&perp) + if err != nil { + t.Error(err) + } + + if len(perp.Person.FirstName) == 0 { + t.Errorf("Expected non zero lengthed first name.") + } + if len(perp.Place.Country) == 0 { + t.Errorf("Expected non zero lengthed country.") + } + + rows.Close() + + // test the same for embedded pointer structs + peopleAndPlacesPtrs := []PersonPlacePtr{} + err = db.Select( + &peopleAndPlacesPtrs, + `SELECT person.*, place.* FROM + person natural join place`) + if err != nil { + t.Fatal(err) + } + for _, pp := range peopleAndPlacesPtrs { + if len(pp.Person.FirstName) == 0 { + t.Errorf("Expected non zero lengthed first name.") + } + if len(pp.Place.Country) == 0 { + t.Errorf("Expected non zero lengthed country.") + } + } + + // test "deep nesting" + l3s := []Loop3{} + err = db.Select(&l3s, `select * from person`) + if err != nil { + t.Fatal(err) + } + for _, l3 := range l3s { + if len(l3.Loop2.Loop1.Person.FirstName) == 0 { + t.Errorf("Expected non zero lengthed first name.") + } + } + + // test "embed conflicts" + ec := []EmbedConflict{} + err = db.Select(&ec, `select * from person`) + // I'm torn between erroring here or having some kind of working behavior + // in order to allow for more flexibility in destination structs + if err != nil { + t.Errorf("Was not expecting an error on embed conflicts.") + } + }) +} + +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, now string) { + 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, now string) { + 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, now string) { + loadDefaultFixture(db, t) + rows, err := db.Queryx("SELECT * FROM person") + if err != nil { + t.Fatal(err) + } + for rows.Next() { + _, err := rows.SliceScan() + if err != nil { + t.Error(err) + } + } + + rows, err = db.Queryx("SELECT * FROM person") + if err != nil { + t.Fatal(err) + } + for rows.Next() { + m := map[string]interface{}{} + err := rows.MapScan(m) + if err != nil { + t.Error(err) + } + } + + }) +} + +func TestNilReceiver(t *testing.T) { + RunWithSchema(defaultSchema, t, func(db *DB, t *testing.T, now string) { + loadDefaultFixture(db, t) + var p *Person + err := db.Get(p, "SELECT * FROM person LIMIT 1") + if err == nil { + t.Error("Expected error when getting into nil struct ptr.") + } + var pp *[]Person + err = db.Select(pp, "SELECT * FROM person") + if err == nil { + t.Error("Expected an error when selecting into nil slice ptr.") + } + }) +} + +func TestNamedQuery(t *testing.T) { + var schema = Schema{ + create: ` + CREATE TABLE place ( + id integer PRIMARY KEY, + name text NULL + ); + CREATE TABLE person ( + first_name text NULL, + last_name text NULL, + email text NULL + ); + CREATE TABLE placeperson ( + first_name text NULL, + last_name text NULL, + email text NULL, + place_id integer NULL + ); + CREATE TABLE jsperson ( + "FIRST" text NULL, + last_name text NULL, + "EMAIL" text NULL + );`, + drop: ` + drop table person; + drop table jsperson; + drop table place; + drop table placeperson; + `, + } + + RunWithSchema(schema, t, func(db *DB, t *testing.T, now string) { + type Person struct { + FirstName sql.NullString `db:"first_name"` + LastName sql.NullString `db:"last_name"` + Email sql.NullString + } + + p := Person{ + 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)` + _, err := db.NamedExec(q1, p) + if err != nil { + log.Fatal(err) + } + + p2 := &Person{} + rows, err := db.NamedQuery("SELECT * FROM person WHERE first_name=:first_name", p) + if err != nil { + log.Fatal(err) + } + for rows.Next() { + err = rows.StructScan(p2) + if err != nil { + t.Error(err) + } + if p2.FirstName.String != "ben" { + t.Error("Expected first name of `ben`, got " + p2.FirstName.String) + } + if p2.LastName.String != "doe" { + t.Error("Expected first name of `doe`, got " + p2.LastName.String) + } + } + + // these are tests for #73; they verify that named queries work if you've + // changed the db mapper. This code checks both NamedQuery "ad-hoc" style + // queries and NamedStmt queries, which use different code paths internally. + old := *db.Mapper + + type JSONPerson struct { + FirstName sql.NullString `json:"FIRST"` + LastName sql.NullString `json:"last_name"` + Email sql.NullString + } + + 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) + + // prepare queries for case sensitivity to test our ToUpper function. + // postgres and sqlite accept "", but mysql uses ``; since Go's multi-line + // strings are `` we use "" by default and swap out for MySQL + pdb := func(s string, db *DB) string { + if db.DriverName() == "mysql" { + return strings.Replace(s, `"`, "`", -1) + } + return s + } + + q1 = `INSERT INTO jsperson ("FIRST", last_name, "EMAIL") VALUES (:FIRST, :last_name, :EMAIL)` + _, err = db.NamedExec(pdb(q1, db), jp) + if err != nil { + t.Fatal(err, db.DriverName()) + } + + // Checks that a person pulled out of the db matches the one we put in + check := func(t *testing.T, rows *Rows) { + jp = JSONPerson{} + for rows.Next() { + err = rows.StructScan(&jp) + if err != nil { + t.Error(err) + } + if jp.FirstName.String != "ben" { + t.Errorf("Expected first name of `ben`, got `%s` (%s) ", jp.FirstName.String, db.DriverName()) + } + if jp.LastName.String != "smith" { + t.Errorf("Expected LastName of `smith`, got `%s` (%s)", jp.LastName.String, db.DriverName()) + } + if jp.Email.String != "ben@smith.com" { + t.Errorf("Expected first name of `doe`, got `%s` (%s)", jp.Email.String, db.DriverName()) + } + } + } + + ns, err := db.PrepareNamed(pdb(` + SELECT * FROM jsperson + WHERE + "FIRST"=:FIRST AND + last_name=:last_name AND + "EMAIL"=:EMAIL + `, db)) + + if err != nil { + t.Fatal(err) + } + rows, err = ns.Queryx(jp) + if err != nil { + t.Fatal(err) + } + + check(t, rows) + + // Check exactly the same thing, but with db.NamedQuery, which does not go + // through the PrepareNamed/NamedStmt path. + rows, err = db.NamedQuery(pdb(` + SELECT * FROM jsperson + WHERE + "FIRST"=:FIRST AND + last_name=:last_name AND + "EMAIL"=:EMAIL + `, db), jp) + if err != nil { + t.Fatal(err) + } + + check(t, rows) + + db.Mapper = &old + + // Test nested structs + type Place struct { + ID int `db:"id"` + Name sql.NullString `db:"name"` + } + type PlacePerson struct { + FirstName sql.NullString `db:"first_name"` + LastName sql.NullString `db:"last_name"` + Email sql.NullString + Place Place `db:"place"` + } + + pl := Place{ + Name: sql.NullString{String: "myplace", Valid: true}, + } + + pp := PlacePerson{ + FirstName: sql.NullString{String: "ben", Valid: true}, + LastName: sql.NullString{String: "doe", Valid: true}, + Email: sql.NullString{String: "ben@doe.com", Valid: true}, + } + + q2 := `INSERT INTO place (id, name) VALUES (1, :name)` + _, err = db.NamedExec(q2, pl) + if err != nil { + log.Fatal(err) + } + + id := 1 + pp.Place.ID = id + + q3 := `INSERT INTO placeperson (first_name, last_name, email, place_id) VALUES (:first_name, :last_name, :email, :place.id)` + _, err = db.NamedExec(q3, pp) + if err != nil { + log.Fatal(err) + } + + pp2 := &PlacePerson{} + rows, err = db.NamedQuery(` + SELECT + first_name, + last_name, + email, + place.id AS "place.id", + place.name AS "place.name" + FROM placeperson + INNER JOIN place ON place.id = placeperson.place_id + WHERE + place.id=:place.id`, pp) + if err != nil { + log.Fatal(err) + } + for rows.Next() { + err = rows.StructScan(pp2) + if err != nil { + t.Error(err) + } + if pp2.FirstName.String != "ben" { + t.Error("Expected first name of `ben`, got " + pp2.FirstName.String) + } + if pp2.LastName.String != "doe" { + t.Error("Expected first name of `doe`, got " + pp2.LastName.String) + } + if pp2.Place.Name.String != "myplace" { + t.Error("Expected place name of `myplace`, got " + pp2.Place.Name.String) + } + if pp2.Place.ID != pp.Place.ID { + t.Errorf("Expected place name of %v, got %v", pp.Place.ID, pp2.Place.ID) + } + } + }) +} + +func TestNilInserts(t *testing.T) { + var schema = Schema{ + create: ` + CREATE TABLE tt ( + id integer, + value text NULL DEFAULT NULL + );`, + drop: "drop table tt;", + } + + RunWithSchema(schema, t, func(db *DB, t *testing.T, now string) { + type TT struct { + ID int + Value *string + } + var v, v2 TT + r := db.Rebind + + 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.Value != nil { + t.Errorf("Expecting NULL to map to nil, got %s", *v.Value) + } + + 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 + // writing. This was fixed by creating & using the reflectx.FieldByIndexesReadOnly + // function. This next line is important as it provides the only coverage for this. + 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 v2.Value != nil { + t.Errorf("Expecting NULL to map to nil, got %s", *v.Value) + } + }) +} + +func TestScanError(t *testing.T) { + var schema = Schema{ + create: ` + CREATE TABLE kv ( + k text, + v integer + );`, + drop: `drop table kv;`, + } + + RunWithSchema(schema, t, func(db *DB, t *testing.T, now string) { + type WrongTypes struct { + K int + V string + } + _, err := db.Exec(db.Rebind("INSERT INTO kv (k, v) VALUES (?, ?)"), "hi", 1) + if err != nil { + t.Error(err) + } + + rows, err := db.Queryx("SELECT * FROM kv") + if err != nil { + t.Error(err) + } + for rows.Next() { + var wt WrongTypes + err := rows.StructScan(&wt) + if err == nil { + t.Errorf("%s: Scanning wrong types into keys should have errored.", db.DriverName()) + } + } + }) +} + +func TestMultiInsert(t *testing.T) { + RunWithSchema(defaultSchema, t, func(db *DB, t *testing.T, now string) { + loadDefaultFixture(db, t) + q := db.Rebind(`INSERT INTO employees (name, id) VALUES (?, ?), (?, ?);`) + db.MustExec(q, + "Name1", 400, + "name2", 500, + ) + }) +} + +// FIXME: this function is kinda big but it slows things down to be constantly +// loading and reloading the schema.. + +func TestUsage(t *testing.T) { + RunWithSchema(defaultSchema, t, func(db *DB, t *testing.T, now string) { + loadDefaultFixture(db, t) + slicemembers := []SliceMember{} + err := db.Select(&slicemembers, "SELECT * FROM place ORDER BY telcode ASC") + if err != nil { + t.Fatal(err) + } + + people := []Person{} + + err = db.Select(&people, "SELECT * FROM person ORDER BY first_name ASC") + if err != nil { + t.Fatal(err) + } + + jason, john := people[0], people[1] + if jason.FirstName != "Jason" { + t.Errorf("Expecting FirstName of Jason, got %s", jason.FirstName) + } + if jason.LastName != "Moiron" { + t.Errorf("Expecting LastName of Moiron, got %s", jason.LastName) + } + if jason.Email != "jmoiron@jmoiron.net" { + t.Errorf("Expecting Email of jmoiron@jmoiron.net, got %s", jason.Email) + } + if john.FirstName != "John" || john.LastName != "Doe" || john.Email != "johndoeDNE@gmail.net" { + t.Errorf("John Doe's person record not what expected: Got %v\n", john) + } + + jason = Person{} + err = db.Get(&jason, db.Rebind("SELECT * FROM person WHERE first_name=?"), "Jason") + + if err != nil { + t.Fatal(err) + } + if jason.FirstName != "Jason" { + t.Errorf("Expecting to get back Jason, but got %v\n", jason.FirstName) + } + + err = db.Get(&jason, db.Rebind("SELECT * FROM person WHERE first_name=?"), "Foobar") + if err == nil { + t.Errorf("Expecting an error, got nil\n") + } + if err != sql.ErrNoRows { + t.Errorf("Expected sql.ErrNoRows, got %v\n", err) + } + + // The following tests check statement reuse, which was actually a problem + // due to copying being done when creating Stmt's which was eventually removed + stmt1, err := db.Preparex(db.Rebind("SELECT * FROM person WHERE first_name=?")) + if err != nil { + t.Fatal(err) + } + jason = Person{} + + row := stmt1.QueryRowx("DoesNotExist") + row.Scan(&jason) + row = stmt1.QueryRowx("DoesNotExist") + row.Scan(&jason) + + err = stmt1.Get(&jason, "DoesNotExist User") + if err == nil { + t.Error("Expected an error") + } + err = stmt1.Get(&jason, "DoesNotExist User 2") + if err == nil { + t.Fatal(err) + } + + stmt2, err := db.Preparex(db.Rebind("SELECT * FROM person WHERE first_name=?")) + if err != nil { + t.Fatal(err) + } + jason = Person{} + tx, err := db.Beginx() + if err != nil { + t.Fatal(err) + } + tstmt2 := tx.Stmtx(stmt2) + row2 := tstmt2.QueryRowx("Jason") + err = row2.StructScan(&jason) + if err != nil { + t.Error(err) + } + tx.Commit() + + places := []*Place{} + err = db.Select(&places, "SELECT telcode FROM place ORDER BY telcode ASC") + if err != nil { + t.Fatal(err) + } + + usa, singsing, honkers := places[0], places[1], places[2] + + if usa.TelCode != 1 || honkers.TelCode != 852 || singsing.TelCode != 65 { + t.Errorf("Expected integer telcodes to work, got %#v", places) + } + + placesptr := []PlacePtr{} + err = db.Select(&placesptr, "SELECT * FROM place ORDER BY telcode ASC") + if err != nil { + t.Error(err) + } + //fmt.Printf("%#v\n%#v\n%#v\n", placesptr[0], placesptr[1], placesptr[2]) + + // if you have null fields and use SELECT *, you must use sql.Null* in your struct + // this test also verifies that you can use either a []Struct{} or a []*Struct{} + places2 := []Place{} + err = db.Select(&places2, "SELECT * FROM place ORDER BY telcode ASC") + if err != nil { + t.Fatal(err) + } + + usa, singsing, honkers = &places2[0], &places2[1], &places2[2] + + // this should return a type error that &p is not a pointer to a struct slice + p := Place{} + err = db.Select(&p, "SELECT * FROM place ORDER BY telcode ASC") + if err == nil { + t.Errorf("Expected an error, argument to select should be a pointer to a struct slice") + } + + // this should be an error + pl := []Place{} + err = db.Select(pl, "SELECT * FROM place ORDER BY telcode ASC") + if err == nil { + t.Errorf("Expected an error, argument to select should be a pointer to a struct slice, not a slice.") + } + + if usa.TelCode != 1 || honkers.TelCode != 852 || singsing.TelCode != 65 { + t.Errorf("Expected integer telcodes to work, got %#v", places) + } + + stmt, err := db.Preparex(db.Rebind("SELECT country, telcode FROM place WHERE telcode > ? ORDER BY telcode ASC")) + if err != nil { + t.Error(err) + } + + places = []*Place{} + err = stmt.Select(&places, 10) + if len(places) != 2 { + t.Error("Expected 2 places, got 0.") + } + if err != nil { + t.Fatal(err) + } + singsing, honkers = places[0], places[1] + if singsing.TelCode != 65 || honkers.TelCode != 852 { + t.Errorf("Expected the right telcodes, got %#v", places) + } + + rows, err := db.Queryx("SELECT * FROM place") + if err != nil { + t.Fatal(err) + } + place := Place{} + for rows.Next() { + err = rows.StructScan(&place) + if err != nil { + t.Fatal(err) + } + } + + rows, err = db.Queryx("SELECT * FROM place") + if err != nil { + t.Fatal(err) + } + m := map[string]interface{}{} + for rows.Next() { + err = rows.MapScan(m) + if err != nil { + t.Fatal(err) + } + _, ok := m["country"] + if !ok { + t.Errorf("Expected key `country` in map but could not find it (%#v)\n", m) + } + } + + rows, err = db.Queryx("SELECT * FROM place") + if err != nil { + t.Fatal(err) + } + for rows.Next() { + s, err := rows.SliceScan() + if err != nil { + t.Error(err) + } + if len(s) != 3 { + t.Errorf("Expected 3 columns in result, got %d\n", len(s)) + } + } + + // test advanced querying + // test that NamedExec works with a map as well as a struct + _, err = db.NamedExec("INSERT INTO person (first_name, last_name, email) VALUES (:first, :last, :email)", map[string]interface{}{ + "first": "Bin", + "last": "Smuth", + "email": "bensmith@allblacks.nz", + }) + if err != nil { + t.Fatal(err) + } + + // ensure that if the named param happens right at the end it still works + // ensure that NamedQuery works with a map[string]interface{} + rows, err = db.NamedQuery("SELECT * FROM person WHERE first_name=:first", map[string]interface{}{"first": "Bin"}) + if err != nil { + t.Fatal(err) + } + + ben := &Person{} + for rows.Next() { + err = rows.StructScan(ben) + if err != nil { + t.Fatal(err) + } + if ben.FirstName != "Bin" { + t.Fatal("Expected first name of `Bin`, got " + ben.FirstName) + } + if ben.LastName != "Smuth" { + t.Fatal("Expected first name of `Smuth`, got " + ben.LastName) + } + } + + ben.FirstName = "Ben" + ben.LastName = "Smith" + ben.Email = "binsmuth@allblacks.nz" + + // Insert via a named query using the struct + _, err = db.NamedExec("INSERT INTO person (first_name, last_name, email) VALUES (:first_name, :last_name, :email)", ben) + + if err != nil { + t.Fatal(err) + } + + rows, err = db.NamedQuery("SELECT * FROM person WHERE first_name=:first_name", ben) + if err != nil { + t.Fatal(err) + } + for rows.Next() { + err = rows.StructScan(ben) + if err != nil { + t.Fatal(err) + } + if ben.FirstName != "Ben" { + t.Fatal("Expected first name of `Ben`, got " + ben.FirstName) + } + if ben.LastName != "Smith" { + t.Fatal("Expected first name of `Smith`, got " + ben.LastName) + } + } + // ensure that Get does not panic on emppty result set + person := &Person{} + err = db.Get(person, "SELECT * FROM person WHERE first_name=$1", "does-not-exist") + if err == nil { + t.Fatal("Should have got an error for Get on non-existent row.") + } + + // lets test prepared statements some more + + stmt, err = db.Preparex(db.Rebind("SELECT * FROM person WHERE first_name=?")) + if err != nil { + t.Fatal(err) + } + rows, err = stmt.Queryx("Ben") + if err != nil { + t.Fatal(err) + } + for rows.Next() { + err = rows.StructScan(ben) + if err != nil { + t.Fatal(err) + } + if ben.FirstName != "Ben" { + t.Fatal("Expected first name of `Ben`, got " + ben.FirstName) + } + if ben.LastName != "Smith" { + t.Fatal("Expected first name of `Smith`, got " + ben.LastName) + } + } + + john = Person{} + stmt, err = db.Preparex(db.Rebind("SELECT * FROM person WHERE first_name=?")) + if err != nil { + t.Error(err) + } + err = stmt.Get(&john, "John") + if err != nil { + t.Error(err) + } + + // test name mapping + // THIS USED TO WORK BUT WILL NO LONGER WORK. + db.MapperFunc(strings.ToUpper) + rsa := CPlace{} + err = db.Get(&rsa, "SELECT * FROM capplace;") + if err != nil { + t.Error(err, "in db:", db.DriverName()) + } + db.MapperFunc(strings.ToLower) + + // create a copy and change the mapper, then verify the copy behaves + // differently from the original. + dbCopy := NewDb(db.DB, db.DriverName()) + dbCopy.MapperFunc(strings.ToUpper) + err = dbCopy.Get(&rsa, "SELECT * FROM capplace;") + if err != nil { + fmt.Println(db.DriverName()) + t.Error(err) + } + + err = db.Get(&rsa, "SELECT * FROM cappplace;") + if err == nil { + t.Error("Expected no error, got ", err) + } + + // test base type slices + var sdest []string + rows, err = db.Queryx("SELECT email FROM person ORDER BY email ASC;") + if err != nil { + t.Error(err) + } + err = scanAll(rows, &sdest, false) + if err != nil { + t.Error(err) + } + + // test Get with base types + var count int + err = db.Get(&count, "SELECT count(*) FROM person;") + if err != nil { + t.Error(err) + } + if count != len(sdest) { + t.Errorf("Expected %d == %d (count(*) vs len(SELECT ..)", count, len(sdest)) + } + + // test Get and Select with time.Time, #84 + var addedAt time.Time + err = db.Get(&addedAt, "SELECT added_at FROM person LIMIT 1;") + if err != nil { + t.Error(err) + } + + var addedAts []time.Time + err = db.Select(&addedAts, "SELECT added_at FROM person;") + if err != nil { + t.Error(err) + } + + // test it on a double pointer + var pcount *int + err = db.Get(&pcount, "SELECT count(*) FROM person;") + if err != nil { + t.Error(err) + } + if *pcount != count { + t.Errorf("expected %d = %d", *pcount, count) + } + + // test Select... + sdest = []string{} + err = db.Select(&sdest, "SELECT first_name FROM person ORDER BY first_name ASC;") + if err != nil { + t.Error(err) + } + expected := []string{"Ben", "Bin", "Jason", "John"} + for i, got := range sdest { + if got != expected[i] { + t.Errorf("Expected %d result to be %s, but got %s", i, expected[i], got) + } + } + + var nsdest []sql.NullString + err = db.Select(&nsdest, "SELECT city FROM place ORDER BY city ASC") + if err != nil { + t.Error(err) + } + for _, val := range nsdest { + if val.Valid && val.String != "New York" { + t.Errorf("expected single valid result to be `New York`, but got %s", val.String) + } + } + }) +} + +type Product struct { + ProductID int +} + +// tests that sqlx will not panic when the wrong driver is passed because +// of an automatic nil dereference in sqlx.Open(), which was fixed. +func TestDoNotPanicOnConnect(t *testing.T) { + db, err := Connect("bogus", "hehe") + if err == nil { + t.Errorf("Should return error when using bogus driverName") + } + if db != nil { + t.Errorf("Should not return the db on a connect failure") + } +} + +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", ?, ?)` + + s1 := Rebind(DOLLAR, q1) + s2 := Rebind(DOLLAR, q2) + + if s1 != `INSERT INTO foo (a, b, c, d, e, f, g, h, i) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)` { + t.Errorf("q1 failed") + } + + if s2 != `INSERT INTO foo (a, b, c) VALUES ($1, $2, "foo"), ("Hi", $3, $4)` { + t.Errorf("q2 failed") + } + + s1 = Rebind(AT, q1) + s2 = Rebind(AT, q2) + + if s1 != `INSERT INTO foo (a, b, c, d, e, f, g, h, i) VALUES (@p1, @p2, @p3, @p4, @p5, @p6, @p7, @p8, @p9, @p10)` { + t.Errorf("q1 failed") + } + + if s2 != `INSERT INTO foo (a, b, c) VALUES (@p1, @p2, "foo"), ("Hi", @p3, @p4)` { + 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) { + // Test that it works.. + q1 := `INSERT INTO foo (a, b, c, d) VALUES (:name, :age, :first, :last)` + am := map[string]interface{}{ + "name": "Jason Moiron", + "age": 30, + "first": "Jason", + "last": "Moiron", + } + + bq, args, _ := bindMap(QUESTION, q1, am) + expect := `INSERT INTO foo (a, b, c, d) VALUES (?, ?, ?, ?)` + if bq != expect { + t.Errorf("Interpolation of query failed: got `%v`, expected `%v`\n", bq, expect) + } + + if args[0].(string) != "Jason Moiron" { + t.Errorf("Expected `Jason Moiron`, got %v\n", args[0]) + } + + if args[1].(int) != 30 { + t.Errorf("Expected 30, got %v\n", args[1]) + } + + if args[2].(string) != "Jason" { + t.Errorf("Expected Jason, got %v\n", args[2]) + } + + if args[3].(string) != "Moiron" { + t.Errorf("Expected Moiron, got %v\n", args[3]) + } +} + +// Test for #117, embedded nil maps + +type Message struct { + Text string `db:"string"` + Properties PropertyMap `db:"properties"` // Stored as JSON in the database +} + +type PropertyMap map[string]string + +// Implement driver.Valuer and sql.Scanner interfaces on PropertyMap +func (p PropertyMap) Value() (driver.Value, error) { + if len(p) == 0 { + return nil, nil + } + return json.Marshal(p) +} + +func (p PropertyMap) Scan(src interface{}) error { + v := reflect.ValueOf(src) + if !v.IsValid() || v.CanAddr() && v.IsNil() { + return nil + } + switch ts := src.(type) { + case []byte: + return json.Unmarshal(ts, &p) + case string: + return json.Unmarshal([]byte(ts), &p) + default: + return fmt.Errorf("Could not not decode type %T -> %T", src, p) + } +} + +func TestEmbeddedMaps(t *testing.T) { + var schema = Schema{ + create: ` + CREATE TABLE message ( + string text, + properties text + );`, + drop: `drop table message;`, + } + + RunWithSchema(schema, t, func(db *DB, t *testing.T, now string) { + messages := []Message{ + {"Hello, World", PropertyMap{"one": "1", "two": "2"}}, + {"Thanks, Joy", PropertyMap{"pull": "request"}}, + } + q1 := `INSERT INTO message (string, properties) VALUES (:string, :properties);` + for _, m := range messages { + _, err := db.NamedExec(q1, m) + if err != nil { + t.Fatal(err) + } + } + var count int + err := db.Get(&count, "SELECT count(*) FROM message") + if err != nil { + t.Fatal(err) + } + if count != len(messages) { + t.Fatalf("Expected %d messages in DB, found %d", len(messages), count) + } + + var m Message + err = db.Get(&m, "SELECT * FROM message LIMIT 1;") + if err != nil { + t.Fatal(err) + } + if m.Properties == nil { + t.Fatal("Expected m.Properties to not be nil, but it was.") + } + }) +} + +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, now string) { + var err error + var v, q Var + if err = db.Get(&v, `SELECT '{"a": "b"}' AS raw`); err != nil { + t.Fatal(err) + } + if err = db.Get(&q, `SELECT 'null' AS raw`); err != nil { + t.Fatal(err) + } + + var v2, q2 Var2 + if err = db.Get(&v2, `SELECT '{"a": "b"}' AS raw`); err != nil { + t.Fatal(err) + } + if err = db.Get(&q2, `SELECT 'null' AS raw`); err != nil { + t.Fatal(err) + } + + var v3, q3 Var3 + if err = db.QueryRow(`SELECT '{"a": "b"}' AS raw`).Scan(&v3.Raw); err != nil { + t.Fatal(err) + } + if err = db.QueryRow(`SELECT '{"c": "d"}' AS raw`).Scan(&q3.Raw); err != nil { + t.Fatal(err) + } + 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}, + {"SELECT * FROM foo WHERE x = ? AND y in (?)", + []interface{}{[]byte("foo"), []int{0, 5, 3}}, + 4}, + {"SELECT * FROM foo WHERE x = ? AND y IN (?)", + []interface{}{sql.NullString{Valid: false}, []string{"a", "b"}}, + 3}, + } + 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, now string) { + 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 + + q1 := `INSERT INTO foo (a, b, c, d) VALUES (:name, :age, :first, :last)` + + type tt struct { + Name string + Age int + First string + Last string + } + + type tt2 struct { + Field1 string `db:"field_1"` + Field2 string `db:"field_2"` + } + + type tt3 struct { + tt2 + Name string + } + + am := tt{"Jason Moiron", 30, "Jason", "Moiron"} + + bq, args, _ := bindStruct(QUESTION, q1, am, mapper()) + expect := `INSERT INTO foo (a, b, c, d) VALUES (?, ?, ?, ?)` + if bq != expect { + t.Errorf("Interpolation of query failed: got `%v`, expected `%v`\n", bq, expect) + } + + if args[0].(string) != "Jason Moiron" { + t.Errorf("Expected `Jason Moiron`, got %v\n", args[0]) + } + + if args[1].(int) != 30 { + t.Errorf("Expected 30, got %v\n", args[1]) + } + + if args[2].(string) != "Jason" { + t.Errorf("Expected Jason, got %v\n", args[2]) + } + + if args[3].(string) != "Moiron" { + t.Errorf("Expected Moiron, got %v\n", args[3]) + } + + am2 := tt2{"Hello", "World"} + bq, args, _ = bindStruct(QUESTION, "INSERT INTO foo (a, b) VALUES (:field_2, :field_1)", am2, mapper()) + expect = `INSERT INTO foo (a, b) VALUES (?, ?)` + if bq != expect { + t.Errorf("Interpolation of query failed: got `%v`, expected `%v`\n", bq, expect) + } + + if args[0].(string) != "World" { + t.Errorf("Expected 'World', got %s\n", args[0].(string)) + } + if args[1].(string) != "Hello" { + t.Errorf("Expected 'Hello', got %s\n", args[1].(string)) + } + + am3 := tt3{Name: "Hello!"} + am3.Field1 = "Hello" + am3.Field2 = "World" + + bq, args, err = bindStruct(QUESTION, "INSERT INTO foo (a, b, c) VALUES (:name, :field_1, :field_2)", am3, mapper()) + + if err != nil { + t.Fatal(err) + } + + expect = `INSERT INTO foo (a, b, c) VALUES (?, ?, ?)` + if bq != expect { + t.Errorf("Interpolation of query failed: got `%v`, expected `%v`\n", bq, expect) + } + + if args[0].(string) != "Hello!" { + t.Errorf("Expected 'Hello!', got %s\n", args[0].(string)) + } + if args[1].(string) != "Hello" { + t.Errorf("Expected 'Hello', got %s\n", args[1].(string)) + } + if args[2].(string) != "World" { + t.Errorf("Expected 'World', got %s\n", args[0].(string)) + } +} + +func TestEmbeddedLiterals(t *testing.T) { + var schema = Schema{ + create: ` + CREATE TABLE x ( + k text + );`, + drop: `drop table x;`, + } + + RunWithSchema(schema, t, func(db *DB, t *testing.T, now string) { + type t1 struct { + K *string + } + type t2 struct { + Inline struct { + F string + } + K *string + } + + db.MustExec(db.Rebind("INSERT INTO x (k) VALUES (?), (?), (?);"), "one", "two", "three") + + target := t1{} + err := db.Get(&target, db.Rebind("SELECT * FROM x WHERE k=?"), "one") + if err != nil { + t.Error(err) + } + if *target.K != "one" { + t.Error("Expected target.K to be `one`, got ", target.K) + } + + target2 := t2{} + err = db.Get(&target2, db.Rebind("SELECT * FROM x WHERE k=?"), "one") + if err != nil { + t.Error(err) + } + if *target2.K != "one" { + t.Errorf("Expected target2.K to be `one`, got `%v`", target2.K) + } + }) +} + +func BenchmarkBindStruct(b *testing.B) { + b.StopTimer() + q1 := `INSERT INTO foo (a, b, c, d) VALUES (:name, :age, :first, :last)` + type t struct { + Name string + Age int + First string + Last string + } + am := t{"Jason Moiron", 30, "Jason", "Moiron"} + b.StartTimer() + for i := 0; i < b.N; i++ { + bindStruct(DOLLAR, q1, am, mapper()) + } +} + +func TestBindNamedMapper(t *testing.T) { + type A map[string]interface{} + m := reflectx.NewMapperFunc("db", NameMapper) + query, args, err := bindNamedMapper(DOLLAR, `select :x`, A{ + "x": "X!", + }, m) + if err != nil { + t.Fatal(err) + } + + got := fmt.Sprintf("%s %s", query, args) + want := `select $1 [X!]` + if got != want { + t.Errorf("\ngot: %q\nwant: %q", got, want) + } + + _, _, err = bindNamedMapper(DOLLAR, `select :x`, map[string]string{ + "x": "X!", + }, m) + if err == nil { + t.Fatal("err is nil") + } + if !strings.Contains(err.Error(), "unsupported map type") { + t.Errorf("wrong error: %s", err) + } +} + +func BenchmarkBindMap(b *testing.B) { + b.StopTimer() + q1 := `INSERT INTO foo (a, b, c, d) VALUES (:name, :age, :first, :last)` + am := map[string]interface{}{ + "name": "Jason Moiron", + "age": 30, + "first": "Jason", + "last": "Moiron", + } + b.StartTimer() + for i := 0; i < b.N; i++ { + bindMap(DOLLAR, 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"}...) + } +} + +func BenchmarkIn1k(b *testing.B) { + q := `SELECT * FROM foo WHERE x = ? AND v in (?) AND y = ?` + + var vals [1000]interface{} + + for i := 0; i < b.N; i++ { + _, _, _ = In(q, []interface{}{"foo", vals[:], "bar"}...) + } +} + +func BenchmarkIn1kInt(b *testing.B) { + q := `SELECT * FROM foo WHERE x = ? AND v in (?) AND y = ?` + + var vals [1000]int + + for i := 0; i < b.N; i++ { + _, _, _ = In(q, []interface{}{"foo", vals[:], "bar"}...) + } +} + +func BenchmarkIn1kString(b *testing.B) { + q := `SELECT * FROM foo WHERE x = ? AND v in (?) AND y = ?` + + var vals [1000]string + + for i := 0; i < b.N; i++ { + _, _, _ = In(q, []interface{}{"foo", vals[:], "bar"}...) + } +} + +func BenchmarkRebind(b *testing.B) { + b.StopTimer() + q1 := `INSERT INTO foo (a, b, c, d, e, f, g, h, i) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)` + q2 := `INSERT INTO foo (a, b, c) VALUES (?, ?, "foo"), ("Hi", ?, ?)` + b.StartTimer() + + for i := 0; i < b.N; i++ { + Rebind(DOLLAR, q1) + Rebind(DOLLAR, q2) + } +} + +func BenchmarkRebindBuffer(b *testing.B) { + b.StopTimer() + q1 := `INSERT INTO foo (a, b, c, d, e, f, g, h, i) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)` + q2 := `INSERT INTO foo (a, b, c) VALUES (?, ?, "foo"), ("Hi", ?, ?)` + b.StartTimer() + + for i := 0; i < b.N; i++ { + rebindBuff(DOLLAR, q1) + rebindBuff(DOLLAR, q2) + } +} + +func TestIn130Regression(t *testing.T) { + t.Run("[]interface{}{}", func(t *testing.T) { + q, args, err := In("SELECT * FROM people WHERE name IN (?)", []interface{}{[]string{"gopher"}}...) + if err != nil { + t.Fatal(err) + } + if q != "SELECT * FROM people WHERE name IN (?)" { + t.Errorf("got=%v", q) + } + t.Log(args) + for _, a := range args { + switch a.(type) { + case string: + t.Log("ok: string", a) + case *string: + t.Error("ng: string pointer", a, *a.(*string)) + } + } + }) + + t.Run("[]string{}", func(t *testing.T) { + q, args, err := In("SELECT * FROM people WHERE name IN (?)", []string{"gopher"}) + if err != nil { + t.Fatal(err) + } + if q != "SELECT * FROM people WHERE name IN (?)" { + t.Errorf("got=%v", q) + } + t.Log(args) + for _, a := range args { + switch a.(type) { + case string: + t.Log("ok: string", a) + case *string: + t.Error("ng: string pointer", a, *a.(*string)) + } + } + }) +} + +func TestSelectReset(t *testing.T) { + RunWithSchema(defaultSchema, t, func(db *DB, t *testing.T, now string) { + loadDefaultFixture(db, t) + + filledDest := []string{"a", "b", "c"} + err := db.Select(&filledDest, "SELECT first_name FROM person ORDER BY first_name ASC;") + if err != nil { + t.Fatal(err) + } + if len(filledDest) != 2 { + t.Errorf("Expected 2 first names, got %d.", len(filledDest)) + } + expected := []string{"Jason", "John"} + for i, got := range filledDest { + if got != expected[i] { + t.Errorf("Expected %d result to be %s, but got %s.", i, expected[i], got) + } + } + + var emptyDest []string + err = db.Select(&emptyDest, "SELECT first_name FROM person WHERE first_name = 'Jack';") + if err != nil { + t.Fatal(err) + } + // Verify that selecting 0 rows into a nil target didn't create a + // non-nil slice. + if emptyDest != nil { + t.Error("Expected emptyDest to be nil") + } + }) +} diff --git a/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/types/README.md b/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/types/README.md new file mode 100644 index 0000000..713abe5 --- /dev/null +++ b/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/types/README.md @@ -0,0 +1,5 @@ +# types + +The types package provides some useful types which implement the `sql.Scanner` +and `driver.Valuer` interfaces, suitable for use as scan and value targets with +database/sql. diff --git a/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/types/types.go b/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/types/types.go new file mode 100644 index 0000000..808f583 --- /dev/null +++ b/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/types/types.go @@ -0,0 +1,172 @@ +package types + +import ( + "bytes" + "compress/gzip" + "database/sql/driver" + "encoding/json" + "errors" + + "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) + w := gzip.NewWriter(buf) + w.Write(g) + w.Close() + return buf.Bytes(), nil + +} + +// 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 := src.(type) { + case string: + source = []byte(src) + case []byte: + source = src + default: + return errors.New("Incompatible type for GzippedText") + } + reader, err := gzip.NewReader(bytes.NewReader(source)) + if err != nil { + return err + } + defer reader.Close() + b, err := ioutil.ReadAll(reader) + if err != nil { + return err + } + *g = GzippedText(b) + return nil +} + +// 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 +// implements `Unmarshal`, which unmarshals the json within to an interface{} +type JSONText json.RawMessage + +var emptyJSON = JSONText("{}") + +// MarshalJSON returns the *j as the JSON encoding of j. +func (j JSONText) MarshalJSON() ([]byte, error) { + if len(j) == 0 { + return emptyJSON, nil + } + return j, nil +} + +// UnmarshalJSON sets *j to a copy of data +func (j *JSONText) UnmarshalJSON(data []byte) error { + if j == nil { + return errors.New("JSONText: UnmarshalJSON on nil pointer") + } + *j = append((*j)[0:0], data...) + return nil +} + +// 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) { + var m json.RawMessage + var err = j.Unmarshal(&m) + if err != nil { + return []byte{}, err + } + return []byte(j), nil +} + +// Scan stores the src in *j. No validation is done. +func (j *JSONText) Scan(src interface{}) error { + var source []byte + switch t := src.(type) { + case string: + source = []byte(t) + case []byte: + if len(t) == 0 { + source = emptyJSON + } else { + source = t + } + case nil: + *j = emptyJSON + default: + return errors.New("Incompatible type for JSONText") + } + *j = 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 { + if len(*j) == 0 { + *j = emptyJSON + } + return json.Unmarshal([]byte(*j), v) +} + +// String supports pretty printing for JSONText types. +func (j JSONText) String() string { + return string(j) +} + +// NullJSONText represents a JSONText that may be null. +// NullJSONText implements the scanner interface so +// it can be used as a scan destination, similar to NullString. +type NullJSONText struct { + JSONText + Valid bool // Valid is true if JSONText is not NULL +} + +// Scan implements the Scanner interface. +func (n *NullJSONText) Scan(value interface{}) error { + if value == nil { + n.JSONText, n.Valid = emptyJSON, false + return nil + } + n.Valid = true + return n.JSONText.Scan(value) +} + +// Value implements the driver Valuer interface. +func (n NullJSONText) Value() (driver.Value, error) { + if !n.Valid { + return nil, nil + } + return n.JSONText.Value() +} + +// BitBool is an implementation of a bool for the MySQL type BIT(1). +// This type allows you to avoid wasting an entire byte for MySQL's boolean type TINYINT. +type BitBool bool + +// Value implements the driver.Valuer interface, +// and turns the BitBool into a bitfield (BIT(1)) for MySQL storage. +func (b BitBool) Value() (driver.Value, error) { + if b { + return []byte{1}, nil + } + return []byte{0}, nil +} + +// Scan implements the sql.Scanner interface, +// and turns the bitfield incoming from MySQL into a BitBool +func (b *BitBool) Scan(src interface{}) error { + v, ok := src.([]byte) + if !ok { + return errors.New("bad []byte type assertion") + } + *b = v[0] == 1 + return nil +} diff --git a/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/types/types_test.go b/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/types/types_test.go new file mode 100644 index 0000000..29813d1 --- /dev/null +++ b/dependencies/pkg/mod/github.com/jmoiron/sqlx@v1.3.5/types/types_test.go @@ -0,0 +1,127 @@ +package types + +import "testing" + +func TestGzipText(t *testing.T) { + g := GzippedText("Hello, world") + v, err := g.Value() + if err != nil { + t.Errorf("Was not expecting an error") + } + err = (&g).Scan(v) + if err != nil { + t.Errorf("Was not expecting an error") + } + if string(g) != "Hello, world" { + t.Errorf("Was expecting the string we sent in (Hello World), got %s", string(g)) + } +} + +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") + } + err = (&j).Scan(v) + if err != nil { + t.Errorf("Was not expecting an error") + } + m := map[string]interface{}{} + j.Unmarshal(&m) + + if m["foo"].(float64) != 1 || m["bar"].(float64) != 2 { + t.Errorf("Expected valid json but got some garbage instead? %#v", m) + } + + j = JSONText(`{"foo": 1, invalid, false}`) + v, err = j.Value() + if err == nil { + t.Errorf("Was expecting invalid json to fail!") + } + + j = JSONText("") + v, err = j.Value() + if err != nil { + t.Errorf("Was not expecting an error") + } + + err = (&j).Scan(v) + if err != nil { + t.Errorf("Was not expecting an error") + } + + j = JSONText(nil) + v, err = j.Value() + if err != nil { + t.Errorf("Was not expecting an error") + } + + err = (&j).Scan(v) + if err != nil { + t.Errorf("Was not expecting an error") + } +} + +func TestNullJSONText(t *testing.T) { + j := NullJSONText{} + err := j.Scan(`{"foo": 1, "bar": 2}`) + if err != nil { + t.Errorf("Was not expecting an error") + } + v, err := j.Value() + if err != nil { + t.Errorf("Was not expecting an error") + } + err = (&j).Scan(v) + if err != nil { + t.Errorf("Was not expecting an error") + } + m := map[string]interface{}{} + j.Unmarshal(&m) + + if m["foo"].(float64) != 1 || m["bar"].(float64) != 2 { + t.Errorf("Expected valid json but got some garbage instead? %#v", m) + } + + j = NullJSONText{} + err = j.Scan(nil) + if err != nil { + t.Errorf("Was not expecting an error") + } + if j.Valid != false { + t.Errorf("Expected valid to be false, but got true") + } +} + +func TestBitBool(t *testing.T) { + // Test true value + var b BitBool = true + + v, err := b.Value() + if err != nil { + t.Errorf("Cannot return error") + } + err = (&b).Scan(v) + if err != nil { + t.Errorf("Was not expecting an error") + } + if !b { + t.Errorf("Was expecting the bool we sent in (true), got %v", b) + } + + // Test false value + b = false + + v, err = b.Value() + if err != nil { + t.Errorf("Cannot return error") + } + err = (&b).Scan(v) + if err != nil { + t.Errorf("Was not expecting an error") + } + if b { + t.Errorf("Was expecting the bool we sent in (false), got %v", b) + } +} |