diff --git a/README.md b/README.md index 4fe80867..8e36dd03 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,7 @@ skip tests for connection types that are not configured. To setup the normal test environment, first install these dependencies: + go get github.com/cockroachdb/apd go get github.com/hashicorp/go-version go get github.com/jackc/fake go get github.com/lib/pq diff --git a/go_stdlib.go b/go_stdlib.go new file mode 100644 index 00000000..9372f9ef --- /dev/null +++ b/go_stdlib.go @@ -0,0 +1,61 @@ +package pgx + +import ( + "database/sql/driver" + "reflect" +) + +// This file contains code copied from the Go standard library due to the +// required function not being public. + +// Copyright (c) 2009 The Go Authors. All rights reserved. + +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: + +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// From database/sql/convert.go + +var valuerReflectType = reflect.TypeOf((*driver.Valuer)(nil)).Elem() + +// callValuerValue returns vr.Value(), with one exception: +// If vr.Value is an auto-generated method on a pointer type and the +// pointer is nil, it would panic at runtime in the panicwrap +// method. Treat it like nil instead. +// Issue 8415. +// +// This is so people can implement driver.Value on value types and +// still use nil pointers to those types to mean nil/NULL, just like +// string/*string. +// +// This function is mirrored in the database/sql/driver package. +func callValuerValue(vr driver.Valuer) (v driver.Value, err error) { + if rv := reflect.ValueOf(vr); rv.Kind() == reflect.Ptr && + rv.IsNil() && + rv.Type().Elem().Implements(valuerReflectType) { + return nil, nil + } + return vr.Value() +} diff --git a/query_test.go b/query_test.go index 02aaeff0..e268b520 100644 --- a/query_test.go +++ b/query_test.go @@ -9,6 +9,7 @@ import ( "testing" "time" + "github.com/cockroachdb/apd" "github.com/jackc/pgx" "github.com/jackc/pgx/pgtype" satori "github.com/jackc/pgx/pgtype/ext/satori-uuid" @@ -1001,6 +1002,27 @@ func TestConnQueryDatabaseSQLDriverValuer(t *testing.T) { ensureConnValid(t, conn) } +// https://github.com/jackc/pgx/issues/339 +func TestConnQueryDatabaseSQLDriverValuerWithAutoGeneratedPointerReceiver(t *testing.T) { + t.Parallel() + + conn := mustConnect(t, *defaultConnConfig) + defer closeConn(t, conn) + + mustExec(t, conn, "create temporary table t(n numeric)") + + var d *apd.Decimal + commandTag, err := conn.Exec(`insert into t(n) values($1)`, d) + if err != nil { + t.Fatal(err) + } + if commandTag != "INSERT 0 1" { + t.Fatalf("want %s, got %s", "INSERT 0 1", commandTag) + } + + ensureConnValid(t, conn) +} + func TestConnQueryDatabaseSQLDriverValuerWithBinaryPgTypeThatAcceptsSameType(t *testing.T) { t.Parallel() diff --git a/values.go b/values.go index efbf5573..6a1c4f08 100644 --- a/values.go +++ b/values.go @@ -32,7 +32,7 @@ func convertSimpleArgument(ci *pgtype.ConnInfo, arg interface{}) (interface{}, e switch arg := arg.(type) { case driver.Valuer: - return arg.Value() + return callValuerValue(arg) case pgtype.TextEncoder: buf, err := arg.EncodeText(ci, nil) if err != nil { @@ -150,7 +150,7 @@ func encodePreparedStatementArgument(ci *pgtype.ConnInfo, buf []byte, oid pgtype if err != nil { { if arg, ok := arg.(driver.Valuer); ok { - v, err := arg.Value() + v, err := callValuerValue(arg) if err != nil { return nil, err } @@ -175,7 +175,7 @@ func encodePreparedStatementArgument(ci *pgtype.ConnInfo, buf []byte, oid pgtype } if arg, ok := arg.(driver.Valuer); ok { - v, err := arg.Value() + v, err := callValuerValue(arg) if err != nil { return nil, err } @@ -203,7 +203,7 @@ func chooseParameterFormatCode(ci *pgtype.ConnInfo, oid pgtype.OID, arg interfac if _, ok := dt.Value.(pgtype.BinaryEncoder); ok { if arg, ok := arg.(driver.Valuer); ok { if err := dt.Value.Set(arg); err != nil { - if value, err := arg.Value(); err == nil { + if value, err := callValuerValue(arg); err == nil { if _, ok := value.(string); ok { return TextFormatCode }