Convert named argument types and add regression tests
This commit is contained in:
20
callback.go
20
callback.go
@@ -260,6 +260,16 @@ func callbackArgGeneric(v *C.sqlite3_value) (reflect.Value, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// callbackArgConvert returns conv as-is when the parameter type is the
|
||||||
|
// canonical type conv produces, and wraps it with a cast for named types
|
||||||
|
// (e.g. time.Duration), which reflect.Call would otherwise panic on.
|
||||||
|
func callbackArgConvert(conv callbackArgConverter, typ, canonical reflect.Type) callbackArgConverter {
|
||||||
|
if typ == canonical {
|
||||||
|
return conv
|
||||||
|
}
|
||||||
|
return callbackArgCast{conv, typ}.Run
|
||||||
|
}
|
||||||
|
|
||||||
func callbackArg(typ reflect.Type) (callbackArgConverter, error) {
|
func callbackArg(typ reflect.Type) (callbackArgConverter, error) {
|
||||||
switch typ.Kind() {
|
switch typ.Kind() {
|
||||||
case reflect.Interface:
|
case reflect.Interface:
|
||||||
@@ -271,18 +281,18 @@ func callbackArg(typ reflect.Type) (callbackArgConverter, error) {
|
|||||||
if typ.Elem().Kind() != reflect.Uint8 {
|
if typ.Elem().Kind() != reflect.Uint8 {
|
||||||
return nil, errors.New("the only supported slice type is []byte")
|
return nil, errors.New("the only supported slice type is []byte")
|
||||||
}
|
}
|
||||||
return callbackArgBytes, nil
|
return callbackArgConvert(callbackArgBytes, typ, reflect.TypeOf([]byte(nil))), nil
|
||||||
case reflect.String:
|
case reflect.String:
|
||||||
return callbackArgString, nil
|
return callbackArgConvert(callbackArgString, typ, reflect.TypeOf("")), nil
|
||||||
case reflect.Bool:
|
case reflect.Bool:
|
||||||
return callbackArgBool, nil
|
return callbackArgConvert(callbackArgBool, typ, reflect.TypeOf(false)), nil
|
||||||
case reflect.Int64:
|
case reflect.Int64:
|
||||||
return callbackArgInt64, nil
|
return callbackArgConvert(callbackArgInt64, typ, reflect.TypeOf(int64(0))), nil
|
||||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Int, reflect.Uint:
|
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Int, reflect.Uint:
|
||||||
c := callbackArgCast{callbackArgInt64, typ}
|
c := callbackArgCast{callbackArgInt64, typ}
|
||||||
return c.Run, nil
|
return c.Run, nil
|
||||||
case reflect.Float64:
|
case reflect.Float64:
|
||||||
return callbackArgFloat64, nil
|
return callbackArgConvert(callbackArgFloat64, typ, reflect.TypeOf(float64(0))), nil
|
||||||
case reflect.Float32:
|
case reflect.Float32:
|
||||||
c := callbackArgCast{callbackArgFloat64, typ}
|
c := callbackArgCast{callbackArgFloat64, typ}
|
||||||
return c.Run, nil
|
return c.Run, nil
|
||||||
|
|||||||
@@ -1439,6 +1439,71 @@ func TestFunctionRegistration(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFunctionRegistrationNamedTypes(t *testing.T) {
|
||||||
|
type NInt int64
|
||||||
|
type NFloat float64
|
||||||
|
type NString string
|
||||||
|
type NBlob []byte
|
||||||
|
type NBool bool
|
||||||
|
|
||||||
|
dur := func(n int64) time.Duration { return time.Duration(n) }
|
||||||
|
nint := func(a, b NInt) NInt { return a + b }
|
||||||
|
nfloat := func(a, b NFloat) NFloat { return a + b }
|
||||||
|
nstring := func(s NString) NString { return s + "!" }
|
||||||
|
nblob := func(s string) NBlob { return NBlob(s) }
|
||||||
|
nbool := func(b NBool) NBool { return !b }
|
||||||
|
|
||||||
|
sql.Register("sqlite3_FunctionRegistrationNamedTypes", &SQLiteDriver{
|
||||||
|
ConnectHook: func(conn *SQLiteConn) error {
|
||||||
|
if err := conn.RegisterFunc("dur", dur, true); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := conn.RegisterFunc("nint", nint, true); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := conn.RegisterFunc("nfloat", nfloat, true); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := conn.RegisterFunc("nstring", nstring, true); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := conn.RegisterFunc("nblob", nblob, true); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return conn.RegisterFunc("nbool", nbool, true)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
db, err := sql.Open("sqlite3_FunctionRegistrationNamedTypes", ":memory:")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Failed to open database:", err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
ops := []struct {
|
||||||
|
query string
|
||||||
|
expected any
|
||||||
|
}{
|
||||||
|
{"SELECT dur(42)", int64(42)},
|
||||||
|
{"SELECT nint(1,2)", int64(3)},
|
||||||
|
{"SELECT nfloat(1.5,1.5)", float64(3)},
|
||||||
|
{`SELECT nstring('foo')`, "foo!"},
|
||||||
|
{`SELECT nblob('xy')`, []byte("xy")},
|
||||||
|
// An empty blob result is mapped to SQL NULL.
|
||||||
|
{`SELECT nblob('') IS NULL`, true},
|
||||||
|
{"SELECT nbool(0)", true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, op := range ops {
|
||||||
|
ret := reflect.New(reflect.TypeOf(op.expected))
|
||||||
|
err = db.QueryRow(op.query).Scan(ret.Interface())
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Query %q failed: %s", op.query, err)
|
||||||
|
} else if !reflect.DeepEqual(ret.Elem().Interface(), op.expected) {
|
||||||
|
t.Errorf("Query %q returned wrong value: got %v (%T), want %v (%T)", op.query, ret.Elem().Interface(), ret.Elem().Interface(), op.expected, op.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestFunctionArgStringContainingZero(t *testing.T) {
|
func TestFunctionArgStringContainingZero(t *testing.T) {
|
||||||
sql.Register("sqlite3_FunctionArgZero", &SQLiteDriver{
|
sql.Register("sqlite3_FunctionArgZero", &SQLiteDriver{
|
||||||
ConnectHook: func(conn *SQLiteConn) error {
|
ConnectHook: func(conn *SQLiteConn) error {
|
||||||
|
|||||||
Reference in New Issue
Block a user