mapper: moved mapper to separate file, added snake case mapper
Performance is comparable to sting.ToLower BenchmarkSnakeCase-4 200000 5922 ns/op 1216 B/op 97 allocs/op BenchmarkToLower-4 300000 5418 ns/op 704 B/op 74 allocs/op
This commit is contained in:
@@ -22,20 +22,22 @@ var placeSchema = `
|
|||||||
CREATE TABLE gocqlx_test.place (
|
CREATE TABLE gocqlx_test.place (
|
||||||
country text,
|
country text,
|
||||||
city text,
|
city text,
|
||||||
telcode int,
|
code int,
|
||||||
PRIMARY KEY(country, city)
|
PRIMARY KEY(country, city)
|
||||||
)`
|
)`
|
||||||
|
|
||||||
|
// Field names are converted to camel case by default, no need to add
|
||||||
|
// `db:"first_name"`, if you want to disable a filed add `db:"-"` tag
|
||||||
type Person struct {
|
type Person struct {
|
||||||
FirstName string `db:"first_name"`
|
FirstName string
|
||||||
LastName string `db:"last_name"`
|
LastName string
|
||||||
Email []string
|
Email []string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Place struct {
|
type Place struct {
|
||||||
Country string
|
Country string
|
||||||
City string
|
City string
|
||||||
TelCode int
|
TelCode int `db:"code"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExample(t *testing.T) {
|
func TestExample(t *testing.T) {
|
||||||
@@ -62,7 +64,7 @@ func TestExample(t *testing.T) {
|
|||||||
{
|
{
|
||||||
mustExec(session.Query(placeSchema))
|
mustExec(session.Query(placeSchema))
|
||||||
|
|
||||||
q := session.Query("INSERT INTO gocqlx_test.place (country, city, telcode) VALUES (?, ?, ?)")
|
q := session.Query("INSERT INTO gocqlx_test.place (country, city, code) VALUES (?, ?, ?)")
|
||||||
mustExec(q.Bind("United States", "New York", 1))
|
mustExec(q.Bind("United States", "New York", 1))
|
||||||
mustExec(q.Bind("Hong Kong", "", 852))
|
mustExec(q.Bind("Hong Kong", "", 852))
|
||||||
mustExec(q.Bind("Singapore", "", 65))
|
mustExec(q.Bind("Singapore", "", 65))
|
||||||
|
|||||||
@@ -4,18 +4,11 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/gocql/gocql"
|
"github.com/gocql/gocql"
|
||||||
"github.com/jmoiron/sqlx/reflectx"
|
"github.com/jmoiron/sqlx/reflectx"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DefaultMapper uses `db` tag and strings.ToLower to lowercase struct field
|
|
||||||
// names. It can be set to whatever you want, but it is encouraged to be set
|
|
||||||
// before gocqlx is used as name-to-field mappings are cached after first
|
|
||||||
// use on a type.
|
|
||||||
var DefaultMapper = reflectx.NewMapperFunc("db", strings.ToLower)
|
|
||||||
|
|
||||||
// structOnlyError returns an error appropriate for type when a non-scannable
|
// structOnlyError returns an error appropriate for type when a non-scannable
|
||||||
// struct is expected but something else is given
|
// struct is expected but something else is given
|
||||||
func structOnlyError(t reflect.Type) error {
|
func structOnlyError(t reflect.Type) error {
|
||||||
|
|||||||
40
mapper.go
Normal file
40
mapper.go
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
package gocqlx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
|
"github.com/jmoiron/sqlx/reflectx"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DefaultMapper uses `db` tag and automatically converts struct field names to
|
||||||
|
// snake case. It can be set to whatever you want, but it is encouraged to be
|
||||||
|
// set before gocqlx is used as name-to-field mappings are cached after first
|
||||||
|
// use on a type.
|
||||||
|
var DefaultMapper = reflectx.NewMapperFunc("db", snakeCase)
|
||||||
|
|
||||||
|
// snakeCase converts camel case to snake case.
|
||||||
|
func snakeCase(s string) string {
|
||||||
|
buf := []byte(s)
|
||||||
|
out := make([]byte, 0, len(buf)+3)
|
||||||
|
|
||||||
|
l := len(buf)
|
||||||
|
for i := 0; i < l; i++ {
|
||||||
|
if !(allowedBindRune(buf[i]) || buf[i] == '_') {
|
||||||
|
panic(fmt.Sprint("not allowed name ", s))
|
||||||
|
}
|
||||||
|
|
||||||
|
b := rune(buf[i])
|
||||||
|
|
||||||
|
if unicode.IsUpper(b) {
|
||||||
|
if i > 0 && buf[i-1] != '_' && (unicode.IsLower(rune(buf[i-1])) || (i+1 < l && unicode.IsLower(rune(buf[i+1])))) {
|
||||||
|
out = append(out, '_')
|
||||||
|
}
|
||||||
|
b = unicode.ToLower(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
out = append(out, byte(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(out)
|
||||||
|
}
|
||||||
84
mapper_test.go
Normal file
84
mapper_test.go
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
package gocqlx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var snakeTests = []struct {
|
||||||
|
name string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{"a", "a"},
|
||||||
|
{"snake", "snake"},
|
||||||
|
{"A", "a"},
|
||||||
|
{"ID", "id"},
|
||||||
|
{"MOTD", "motd"},
|
||||||
|
{"Snake", "snake"},
|
||||||
|
{"SnakeTest", "snake_test"},
|
||||||
|
{"APIResponse", "api_response"},
|
||||||
|
{"SnakeID", "snake_id"},
|
||||||
|
{"Snake_Id", "snake_id"},
|
||||||
|
{"Snake_ID", "snake_id"},
|
||||||
|
{"SnakeIDGoogle", "snake_id_google"},
|
||||||
|
{"LinuxMOTD", "linux_motd"},
|
||||||
|
{"OMGWTFBBQ", "omgwtfbbq"},
|
||||||
|
{"omg_wtf_bbq", "omg_wtf_bbq"},
|
||||||
|
{"woof_woof", "woof_woof"},
|
||||||
|
{"_woof_woof", "_woof_woof"},
|
||||||
|
{"woof_woof_", "woof_woof_"},
|
||||||
|
{"WOOF", "woof"},
|
||||||
|
{"Woof", "woof"},
|
||||||
|
{"woof", "woof"},
|
||||||
|
{"woof0_woof1", "woof0_woof1"},
|
||||||
|
{"_woof0_woof1_2", "_woof0_woof1_2"},
|
||||||
|
{"woof0_WOOF1_2", "woof0_woof1_2"},
|
||||||
|
{"WOOF0", "woof0"},
|
||||||
|
{"Woof1", "woof1"},
|
||||||
|
{"woof2", "woof2"},
|
||||||
|
{"woofWoof", "woof_woof"},
|
||||||
|
{"woofWOOF", "woof_woof"},
|
||||||
|
{"woof_WOOF", "woof_woof"},
|
||||||
|
{"Woof_WOOF", "woof_woof"},
|
||||||
|
{"WOOFWoofWoofWOOFWoofWoof", "woof_woof_woof_woof_woof_woof"},
|
||||||
|
{"WOOF_Woof_woof_WOOF_Woof_woof", "woof_woof_woof_woof_woof_woof"},
|
||||||
|
{"Woof_W", "woof_w"},
|
||||||
|
{"Woof_w", "woof_w"},
|
||||||
|
{"WoofW", "woof_w"},
|
||||||
|
{"Woof_W_", "woof_w_"},
|
||||||
|
{"Woof_w_", "woof_w_"},
|
||||||
|
{"WoofW_", "woof_w_"},
|
||||||
|
{"WOOF_", "woof_"},
|
||||||
|
{"W_Woof", "w_woof"},
|
||||||
|
{"w_Woof", "w_woof"},
|
||||||
|
{"WWoof", "w_woof"},
|
||||||
|
{"_W_Woof", "_w_woof"},
|
||||||
|
{"_w_Woof", "_w_woof"},
|
||||||
|
{"_WWoof", "_w_woof"},
|
||||||
|
{"_WOOF", "_woof"},
|
||||||
|
{"_woof", "_woof"},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSnakeCase(t *testing.T) {
|
||||||
|
for _, tt := range snakeTests {
|
||||||
|
if actual := snakeCase(tt.name); actual != tt.expected {
|
||||||
|
t.Error("expected", tt.expected, "got", actual, tt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkSnakeCase(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
for _, test := range snakeTests {
|
||||||
|
snakeCase(test.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkToLower(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
for _, test := range snakeTests {
|
||||||
|
strings.ToLower(test.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user