diff --git a/named.go b/named.go index 29d6dcf..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) { @@ -296,7 +301,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) @@ -440,6 +445,12 @@ 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 { + t = t.Elem() + k = t.Kind() + } + switch { case k == reflect.Map && t.Key().Kind() == reflect.String: m, ok := convertMapStringInterface(arg) 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) + } + }) +} diff --git a/named_test.go b/named_test.go index b549539..3111c5e 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"},