Named queries
This commit is contained in:
parent
2070776cf7
commit
2e35257eef
34
dbc/named.go
34
dbc/named.go
|
@ -3,10 +3,37 @@ package dbc
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/localhots/gobelt/reflect2"
|
"github.com/localhots/gobelt/reflect2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var namedRegexp = regexp.MustCompile("" +
|
||||||
|
"`[^`]+`|" +
|
||||||
|
`'[^']+'|` +
|
||||||
|
`"[^"]+"|` +
|
||||||
|
`@[a-zA-Z][a-zA-Z0-9_]*`)
|
||||||
|
|
||||||
|
func prepareNamedQuery(query string, p namedParams) (newQuery string, args []interface{}, err error) {
|
||||||
|
newQuery = namedRegexp.ReplaceAllStringFunc(query, func(m string) string {
|
||||||
|
if !strings.HasPrefix(m, "@") {
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
val, ok := p.Get(m[1:])
|
||||||
|
if !ok {
|
||||||
|
err = fmt.Errorf("Named parameter %s was not found", m)
|
||||||
|
}
|
||||||
|
args = append(args, val)
|
||||||
|
return "?"
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Params
|
||||||
|
//
|
||||||
|
|
||||||
type namedParams interface {
|
type namedParams interface {
|
||||||
Get(name string) (val interface{}, ok bool)
|
Get(name string) (val interface{}, ok bool)
|
||||||
}
|
}
|
||||||
|
@ -58,9 +85,8 @@ func newNamedParamsStruct(s interface{}) (*namedParamsStruct, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *namedParamsStruct) Get(name string) (val interface{}, ok bool) {
|
func (p *namedParamsStruct) Get(name string) (val interface{}, ok bool) {
|
||||||
i, ok := p.idx[name]
|
if i, ok := p.idx[name]; ok {
|
||||||
if !ok {
|
return p.s.Field(i).Interface(), true
|
||||||
return nil, false
|
|
||||||
}
|
}
|
||||||
return p.s.Field(i).Interface(), true
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,32 @@
|
||||||
package dbc
|
package dbc
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPrepareNamedQuery(t *testing.T) {
|
||||||
|
q := `SELECT id, "@not_param ", '@name', "i", ` + "`password` " +
|
||||||
|
`FROM tbl WHERE name = @name, active = @is_active`
|
||||||
|
p, err := newNamedParamsMap(map[string]interface{}{"name": "Bob", "is_active": 1})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create named params map: %v", err)
|
||||||
|
}
|
||||||
|
q, args, err := prepareNamedQuery(q, p)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to prepare named statement: %v", err)
|
||||||
|
}
|
||||||
|
const expQ = `SELECT id, "@not_param ", '@name', "i", ` + "`password` " +
|
||||||
|
`FROM tbl WHERE name = ?, active = ?`
|
||||||
|
if q != expQ {
|
||||||
|
t.Errorf("Expected query to be\n%s\ngot\n%q", expQ, q)
|
||||||
|
}
|
||||||
|
expA := []interface{}{"Bob", 1}
|
||||||
|
if !cmp.Equal(expA, args) {
|
||||||
|
t.Errorf("Returned arguments are different: %s", cmp.Diff(expA, args))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestNamedParamsMap(t *testing.T) {
|
func TestNamedParamsMap(t *testing.T) {
|
||||||
m, err := newNamedParamsMap(map[string]interface{}{
|
m, err := newNamedParamsMap(map[string]interface{}{
|
||||||
|
@ -55,9 +81,9 @@ func BenchmarkNamedParamsMap(b *testing.B) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatalf("Failed to create named params map: %v", err)
|
b.Fatalf("Failed to create named params map: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
m.Get("foo")
|
m.Get("foo")
|
||||||
}
|
}
|
||||||
|
@ -71,10 +97,40 @@ func BenchmarkNamedParamsStruct(b *testing.B) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatalf("Failed to create named params struct: %v", err)
|
b.Fatalf("Failed to create named params struct: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
m.Get("foo")
|
m.Get("foo")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BenchmarkPrepareNamedOne(b *testing.B) {
|
||||||
|
p, _ := newNamedParamsMap(map[string]interface{}{
|
||||||
|
"name": "Bob",
|
||||||
|
})
|
||||||
|
const q = `SELECT * FROM tbl WHER name = @name`
|
||||||
|
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
prepareNamedQuery(q, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkPrepareNamedQuerySix(b *testing.B) {
|
||||||
|
p, _ := newNamedParamsMap(map[string]interface{}{
|
||||||
|
"name": "Bob",
|
||||||
|
"is_active": 1,
|
||||||
|
"amount": 123.45,
|
||||||
|
})
|
||||||
|
const q = `SELECT "aaa @false1 bbb" as f1, 'ccc @false2 ddd' as f2, ` +
|
||||||
|
"`eee @false3 fff` as f3, name, is_active, amount FROM tbl " +
|
||||||
|
`WHERE name = @name AND is_active = @is_active AND amount = @amount`
|
||||||
|
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
prepareNamedQuery(q, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
20
dbc/query.go
20
dbc/query.go
|
@ -48,7 +48,15 @@ func (c *caller) Exec(ctx context.Context, query string, args ...interface{}) Ex
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *caller) ExecNamed(ctx context.Context, query string, arg interface{}) ExecResult {
|
func (c *caller) ExecNamed(ctx context.Context, query string, arg interface{}) ExecResult {
|
||||||
return nil
|
params, err := newNamedParams(arg)
|
||||||
|
if err != nil {
|
||||||
|
return &execResult{err: err}
|
||||||
|
}
|
||||||
|
preparedQuery, args, err := prepareNamedQuery(query, params)
|
||||||
|
if err != nil {
|
||||||
|
return &execResult{err: err}
|
||||||
|
}
|
||||||
|
return c.Exec(ctx, preparedQuery, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *caller) Query(ctx context.Context, query string, args ...interface{}) Rows {
|
func (c *caller) Query(ctx context.Context, query string, args ...interface{}) Rows {
|
||||||
|
@ -63,5 +71,13 @@ func (c *caller) Query(ctx context.Context, query string, args ...interface{}) R
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *caller) QueryNamed(ctx context.Context, query string, arg interface{}) Rows {
|
func (c *caller) QueryNamed(ctx context.Context, query string, arg interface{}) Rows {
|
||||||
return nil
|
params, err := newNamedParams(arg)
|
||||||
|
if err != nil {
|
||||||
|
return &rows{err: err}
|
||||||
|
}
|
||||||
|
preparedQuery, args, err := prepareNamedQuery(query, params)
|
||||||
|
if err != nil {
|
||||||
|
return &rows{err: err}
|
||||||
|
}
|
||||||
|
return c.Query(ctx, preparedQuery, args...)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue