From 002e3d6aaa832a435a720760556607779f332f72 Mon Sep 17 00:00:00 2001 From: Mal Curtis Date: Fri, 18 Sep 2015 08:32:20 +1200 Subject: [PATCH 1/3] Add NamedStmt.Selectx --- named.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/named.go b/named.go index 5cda2b62b..db6b1afb5 100644 --- a/named.go +++ b/named.go @@ -99,6 +99,17 @@ func (n *NamedStmt) Select(dest interface{}, arg interface{}) error { return scanAll(rows, dest, false) } +// Selectx using this NamedStmt +func (n *NamedStmt) Selectx(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 func (n *NamedStmt) Get(dest interface{}, arg interface{}) error { r := n.QueryRowx(arg) From 2298e7738b4da516b83f1b4b7402597edccb11ed Mon Sep 17 00:00:00 2001 From: Mal Curtis Date: Sat, 5 Mar 2016 12:16:57 +1300 Subject: [PATCH 2/3] Allow fallback tag names --- bind.go | 2 +- named.go | 2 +- reflectx/reflect.go | 37 ++++++++++++++++++++++--------------- reflectx/reflect_test.go | 22 ++++++++++++++++++++++ sqlx.go | 2 +- 5 files changed, 47 insertions(+), 18 deletions(-) diff --git a/bind.go b/bind.go index 564635ca2..d3b2ae44c 100644 --- a/bind.go +++ b/bind.go @@ -7,7 +7,7 @@ import ( "strconv" "strings" - "github.com/jmoiron/sqlx/reflectx" + "github.com/snikch/sqlx/reflectx" ) // Bindvar types supported by Rebind, BindMap and BindStruct. diff --git a/named.go b/named.go index 5cda2b62b..026502c13 100644 --- a/named.go +++ b/named.go @@ -19,7 +19,7 @@ import ( "strconv" "unicode" - "github.com/jmoiron/sqlx/reflectx" + "github.com/snikch/sqlx/reflectx" ) // NamedStmt is a prepared statement that executes named queries. Prepare it diff --git a/reflectx/reflect.go b/reflectx/reflect.go index ad8e93091..13ed86976 100644 --- a/reflectx/reflect.go +++ b/reflectx/reflect.go @@ -62,18 +62,18 @@ func (f StructMap) GetByTraversal(index []int) *FieldInfo { // mapping and a function to provide a basic mapping of fields to names. type Mapper struct { cache map[reflect.Type]*StructMap - tagName string + tagNames []string tagMapFunc func(string) string mapFunc func(string) string mutex sync.Mutex } // NewMapper returns a new mapper which optionally obeys the field tag given -// by tagName. If tagName is the empty string, it is ignored. -func NewMapper(tagName string) *Mapper { +// by tagNames. If tagName is the empty string, it is ignored. +func NewMapper(tagNames ...string) *Mapper { return &Mapper{ - cache: make(map[reflect.Type]*StructMap), - tagName: tagName, + cache: make(map[reflect.Type]*StructMap), + tagNames: tagNames, } } @@ -83,7 +83,7 @@ func NewMapper(tagName string) *Mapper { func NewMapperTagFunc(tagName string, mapFunc, tagMapFunc func(string) string) *Mapper { return &Mapper{ cache: make(map[reflect.Type]*StructMap), - tagName: tagName, + tagNames: []string{tagName}, mapFunc: mapFunc, tagMapFunc: tagMapFunc, } @@ -94,9 +94,9 @@ func NewMapperTagFunc(tagName string, mapFunc, tagMapFunc func(string) string) * // 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, + cache: make(map[reflect.Type]*StructMap), + tagNames: []string{tagName}, + mapFunc: f, } } @@ -106,7 +106,7 @@ 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) + mapping = getMapping(t, m.tagNames, m.mapFunc, m.tagMapFunc) m.cache[t] = mapping } m.mutex.Unlock() @@ -259,7 +259,7 @@ func apnd(is []int, i int) []int { // getMapping returns a mapping for the t type, using the tagName, mapFunc and // tagMapFunc to determine the canonical names of fields. -func getMapping(t reflect.Type, tagName string, mapFunc, tagMapFunc func(string) string) *StructMap { +func getMapping(t reflect.Type, tagNames []string, mapFunc, tagMapFunc func(string) string) *StructMap { m := []*FieldInfo{} root := &FieldInfo{} @@ -282,10 +282,17 @@ func getMapping(t reflect.Type, tagName string, mapFunc, tagMapFunc func(string) fi.Options = map[string]string{} var tag, name string - if tagName != "" && strings.Contains(string(f.Tag), tagName+":") { - tag = f.Tag.Get(tagName) - name = tag - } else { + hasMappedWithTag := false + for _, tagName := range tagNames { + if tagName != "" && strings.Contains(string(f.Tag), tagName+":") { + tag = f.Tag.Get(tagName) + name = tag + // If we find this tag is valid, mark it so and don't keep processing. + hasMappedWithTag = true + break + } + } + if !hasMappedWithTag { if mapFunc != nil { name = mapFunc(f.Name) } diff --git a/reflectx/reflect_test.go b/reflectx/reflect_test.go index 73fcb4a74..03830fc64 100644 --- a/reflectx/reflect_test.go +++ b/reflectx/reflect_test.go @@ -161,6 +161,28 @@ func TestFlatTags(t *testing.T) { } } +func TestMultipleTags(t *testing.T) { + m := NewMapper("db", "json") + + type Foo struct { + Bar string `json:"-" db:"bar"` + Baz string `json:"baz"` + } + // Post columns: (author title) + + foo := Foo{Bar: "a", Baz: "b"} + fv := reflect.ValueOf(foo) + + v := m.FieldByName(fv, "bar") + if v.Interface().(string) != foo.Bar { + t.Errorf("Expecting %s, got %s", foo.Bar, v.Interface().(string)) + } + v = m.FieldByName(fv, "baz") + if v.Interface().(string) != foo.Baz { + t.Errorf("Expecting %s, got %s", foo.Baz, v.Interface().(string)) + } +} + func TestNestedStruct(t *testing.T) { m := NewMapper("db") diff --git a/sqlx.go b/sqlx.go index 05dd8b13f..9037de2d7 100644 --- a/sqlx.go +++ b/sqlx.go @@ -11,7 +11,7 @@ import ( "reflect" "strings" - "github.com/jmoiron/sqlx/reflectx" + "github.com/snikch/sqlx/reflectx" ) // Although the NameMapper is convenient, in practice it should not From 9cee11b570c5871e99ea00dd6f3b36256b2a5b13 Mon Sep 17 00:00:00 2001 From: Mal Curtis Date: Wed, 25 May 2016 17:09:05 +1200 Subject: [PATCH 3/3] Allow periods in named bind vars --- named.go | 2 +- named_test.go | 27 ++++++++++++++++++++++++++- sqlx_test.go | 2 +- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/named.go b/named.go index 026502c13..d6c1ecd81 100644 --- a/named.go +++ b/named.go @@ -243,7 +243,7 @@ func compileNamedQuery(qs []byte, bindType int) (query string, names []string, e inName = true name = []byte{} // if we're in a name, and this is an allowed character, continue - } else if inName && (unicode.IsOneOf(allowedBindRunes, rune(b)) || b == '_') && i != last { + } 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 diff --git a/named_test.go b/named_test.go index d3459a86f..c6a8b9705 100644 --- a/named_test.go +++ b/named_test.go @@ -3,6 +3,7 @@ package sqlx import ( "database/sql" "testing" + "time" ) func TestCompileQuery(t *testing.T) { @@ -126,7 +127,7 @@ func TestNamedQueries(t *testing.T) { test.Error(err) ns, err = db.PrepareNamed(` - SELECT first_name, last_name, email + SELECT first_name, last_name, email FROM person WHERE first_name=:first_name AND email=:email`) test.Error(err) @@ -167,6 +168,20 @@ func TestNamedQueries(t *testing.T) { t.Errorf("got %s, expected %s", p.Email, people[0].Email) } + // test Exec with dot notation + ns, err = db.PrepareNamed(` + INSERT INTO person (first_name, last_name, email) + VALUES (:first.name, :last.name, :email)`) + test.Error(err) + + pn := PersonNest{ + First: Name{Name: "Julien"}, + Last: Name{Name: "Savea"}, + Email: "jsavea@ab.co.nz", + } + _, err = ns.Exec(pn) + test.Error(err) + // test Exec ns, err = db.PrepareNamed(` INSERT INTO person (first_name, last_name, email) @@ -225,3 +240,13 @@ func TestNamedQueries(t *testing.T) { }) } + +type Name struct { + Name string `db:"name"` +} +type PersonNest struct { + First Name `db:"first"` + Last Name `db:"last"` + Email string + AddedAt time.Time `db:"added_at"` +} diff --git a/sqlx_test.go b/sqlx_test.go index c1a125bac..97e3e4e4d 100644 --- a/sqlx_test.go +++ b/sqlx_test.go @@ -23,9 +23,9 @@ import ( "time" _ "github.com/go-sql-driver/mysql" - "github.com/jmoiron/sqlx/reflectx" _ "github.com/lib/pq" _ "github.com/mattn/go-sqlite3" + "github.com/snikch/sqlx/reflectx" ) /* compile time checks that Db, Tx, Stmt (qStmt) implement expected interfaces */