diff --git a/reflectx/reflect.go b/reflectx/reflect.go index 8c3ce04..5c7ba40 100644 --- a/reflectx/reflect.go +++ b/reflectx/reflect.go @@ -65,6 +65,7 @@ type Mapper struct { tagName string tagMapFunc func(string) string mapFunc func(string) string + colMapFunc func(string) string mutex sync.Mutex } @@ -89,6 +90,19 @@ func NewMapperTagFunc(tagName string, mapFunc, tagMapFunc func(string) string) * } } +// NewMapperTagColFunc returns a new mapper which contains a mapper for field names +// AND a mapper for tag values AND sql column names. This is useful for tags like json which can +// have values like "name,omitempty", or on databases like Sqlite where column casing in queries cause problems. +func NewMapperTagColFunc(tagName string, mapFunc, tagMapFunc, colMapFunc func(string) string) *Mapper { + return &Mapper{ + cache: make(map[reflect.Type]*StructMap), + tagName: tagName, + mapFunc: mapFunc, + tagMapFunc: tagMapFunc, + colMapFunc: colMapFunc, + } +} + // 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) @@ -189,6 +203,9 @@ func (m *Mapper) TraversalsByNameFunc(t reflect.Type, names []string, fn func(in nameCounter := make(map[string]int, len(names)) for i, name := range names { + if m.colMapFunc != nil { + name = m.colMapFunc(name) + } fi, ok := tm.Names[name] if !ok { if leafs, lok := tm.Leafs[name]; lok { diff --git a/sqlx_test.go b/sqlx_test.go index 5133bb3..15eb8fb 100644 --- a/sqlx_test.go +++ b/sqlx_test.go @@ -815,7 +815,7 @@ func TestNamedQuery(t *testing.T) { 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()) + t.Errorf("Expected email of `ben@smith.com`, got `%s` (%s)", jp.Email.String, db.DriverName()) } } } @@ -852,6 +852,24 @@ func TestNamedQuery(t *testing.T) { check(t, rows) + // Test the new col mapper which maps the sql column names also. + db.Mapper = reflectx.NewMapperTagColFunc("json", strings.ToLower, strings.ToLower, strings.ToLower) + + // Sqlite will return the column names with the same case as we wrote in the query. + // So in queries with mixed casing, it will cause problems. + rows, err = db.NamedQuery(pdb(` + SELECT "FIRST" as fIrSt, last_name as LAST_NAME, "EMAIL" as email 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