From 056f9e506f935e81beca857cd1702031e46aa8da Mon Sep 17 00:00:00 2001 From: fifsky Date: Thu, 8 Apr 2021 11:12:26 +0800 Subject: [PATCH 1/3] fix: bulk insert regular expression is too strict --- named.go | 2 +- named_test.go | 22 ++++++++++++++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/named.go b/named.go index 11a68e0..32b0059 100644 --- a/named.go +++ b/named.go @@ -224,7 +224,7 @@ func bindStruct(bindType int, query string, arg interface{}, m *reflectx.Mapper) return bound, arglist, nil } -var valueBracketReg = regexp.MustCompile(`VALUES\s+(\([^(]*.[^(]\))`) +var valueBracketReg = regexp.MustCompile(`(?i:VALUES)\s*(\([^(]*.[^(]\))`) func fixBound(bound string, loop int) string { loc := valueBracketReg.FindAllStringSubmatchIndex(bound, -1) diff --git a/named_test.go b/named_test.go index f5545d5..e0a6a68 100644 --- a/named_test.go +++ b/named_test.go @@ -342,6 +342,24 @@ func TestFixBounds(t *testing.T) { expect: `INSERT INTO foo (a,b,c,d) VALUES (:name, :age, :first, :last) VALUES (:name, :age, :first, :last)`, loop: 2, }, + { + name: `lowercase`, + 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: `not blank space`, + 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: `capital letter`, + 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 { @@ -354,7 +372,7 @@ func TestFixBounds(t *testing.T) { } t.Run("regex changed", func(t *testing.T) { - var valueBracketRegChanged = regexp.MustCompile(`(VALUES)\s+(\([^(]*.[^(]\))`) + var valueBracketRegChanged = regexp.MustCompile(`(?i:VALUES)\s*(\([^(]*.[^(]\))`) saveRegexp := valueBracketReg defer func() { valueBracketReg = saveRegexp @@ -362,7 +380,7 @@ func TestFixBounds(t *testing.T) { valueBracketReg = valueBracketRegChanged res := fixBound("VALUES (:a, :b)", 2) - if res != "VALUES (:a, :b)" { + if res != "VALUES (:a, :b),(:a, :b)" { t.Errorf("changed regex should return string") } }) From ac30690b72518ffd1de807b542b62e8ea2a1ca70 Mon Sep 17 00:00:00 2001 From: fifsky Date: Sun, 17 Apr 2022 11:37:30 +0800 Subject: [PATCH 2/3] fix: batch insert bind for ptr slice --- named.go | 7 ++++++- named_test.go | 4 ++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/named.go b/named.go index 728aa04..9bcd780 100644 --- a/named.go +++ b/named.go @@ -277,7 +277,7 @@ func bindArray(bindType int, query string, arg interface{}, m *reflectx.Mapper) if err != nil { return "", []interface{}{}, err } - arrayValue := reflect.ValueOf(arg) + arrayValue := reflect.Indirect(reflect.ValueOf(arg)) arrayLen := arrayValue.Len() if arrayLen == 0 { return "", []interface{}{}, fmt.Errorf("length of array is 0: %#v", arg) @@ -421,6 +421,11 @@ func Named(query string, arg interface{}) (string, []interface{}, error) { func bindNamedMapper(bindType int, query string, arg interface{}, m *reflectx.Mapper) (string, []interface{}, error) { t := reflect.TypeOf(arg) k := t.Kind() + + if k == reflect.Ptr { + k = t.Elem().Kind() + } + switch { case k == reflect.Map && t.Key().Kind() == reflect.String: m, ok := convertMapStringInterface(arg) diff --git a/named_test.go b/named_test.go index 8481b35..2603138 100644 --- a/named_test.go +++ b/named_test.go @@ -211,6 +211,10 @@ func TestNamedQueries(t *testing.T) { _, err = db.NamedExec(insert, sls) test.Error(err) + // and test ptr batch inserts + _, 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"}, From 9994f6186311da89211e70651becb1275d84ff6c Mon Sep 17 00:00:00 2001 From: August Vi Date: Sat, 10 Oct 2026 21:10:01 -0700 Subject: [PATCH 3/3] Bind a pointer to a map in named queries as well. --- named.go | 16 +++++++++++----- named_ptr_test.go | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 5 deletions(-) create mode 100644 named_ptr_test.go diff --git a/named.go b/named.go index d98cf84..795d29a 100644 --- a/named.go +++ b/named.go @@ -153,12 +153,17 @@ func prepareNamed(p namedPreparer, query string) (*NamedStmt, error) { 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) { + rv := reflect.ValueOf(v) + if rv.Kind() == reflect.Ptr { + if rv.IsNil() { + return nil, false + } + rv = rv.Elem() + } + if !rv.Type().ConvertibleTo(mtype) { return nil, false } - return reflect.ValueOf(v).Convert(mtype).Interface().(map[string]interface{}), true - + return rv.Convert(mtype).Interface().(map[string]interface{}), true } func bindAnyArgs(names []string, arg interface{}, m *reflectx.Mapper) ([]interface{}, error) { @@ -442,7 +447,8 @@ func bindNamedMapper(bindType int, query string, arg interface{}, m *reflectx.Ma k := t.Kind() if k == reflect.Ptr { - k = t.Elem().Kind() + t = t.Elem() + k = t.Kind() } switch { diff --git a/named_ptr_test.go b/named_ptr_test.go new file mode 100644 index 0000000..49666e0 --- /dev/null +++ b/named_ptr_test.go @@ -0,0 +1,40 @@ +package sqlx + +import "testing" + +// Named queries accept a pointer to a slice, to a map and to a slice of maps +// (upstream #577, #729). +func TestNamedPtrArgs(t *testing.T) { + RunWithSchema(defaultSchema, t, func(db *DB, t *testing.T, now string) { + const insert = "INSERT INTO person (first_name, last_name, email) VALUES (:first_name, :last_name, :email)" + try := func(label string, arg interface{}, want int64) { + defer func() { + if r := recover(); r != nil { + t.Errorf("%s: panicked: %v", label, r) + } + }() + res, err := db.NamedExec(insert, arg) + if err != nil { + t.Errorf("%s: %v", label, err) + return + } + if n, err := res.RowsAffected(); err == nil && n != want { + t.Errorf("%s: %d rows affected, want %d", label, n, want) + } + } + people := []Person{{FirstName: "A", LastName: "B", Email: "a@b"}, {FirstName: "C", LastName: "D", Email: "c@d"}} + try("*[]struct", &people, 2) + m := map[string]interface{}{"first_name": "E", "last_name": "F", "email": "e@f"} + try("*map", &m, 1) + ms := []map[string]interface{}{{"first_name": "G", "last_name": "H", "email": "g@h"}, {"first_name": "I", "last_name": "J", "email": "i@j"}} + try("*[]map", &ms, 2) + + var count int + if err := db.Get(&count, "SELECT count(*) FROM person"); err != nil { + t.Fatal(err) + } + if count != 5 { + t.Errorf("expected 5 people inserted, got %d", count) + } + }) +}