aboutsummaryrefslogtreecommitdiff
path: root/vendor/github.com/jackc/pgx/query_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/jackc/pgx/query_test.go')
-rw-r--r--vendor/github.com/jackc/pgx/query_test.go1414
1 files changed, 1414 insertions, 0 deletions
diff --git a/vendor/github.com/jackc/pgx/query_test.go b/vendor/github.com/jackc/pgx/query_test.go
new file mode 100644
index 0000000..f08887b
--- /dev/null
+++ b/vendor/github.com/jackc/pgx/query_test.go
@@ -0,0 +1,1414 @@
+package pgx_test
+
+import (
+ "bytes"
+ "database/sql"
+ "fmt"
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/jackc/pgx"
+
+ "github.com/shopspring/decimal"
+)
+
+func TestConnQueryScan(t *testing.T) {
+ t.Parallel()
+
+ conn := mustConnect(t, *defaultConnConfig)
+ defer closeConn(t, conn)
+
+ var sum, rowCount int32
+
+ rows, err := conn.Query("select generate_series(1,$1)", 10)
+ if err != nil {
+ t.Fatalf("conn.Query failed: %v", err)
+ }
+ defer rows.Close()
+
+ for rows.Next() {
+ var n int32
+ rows.Scan(&n)
+ sum += n
+ rowCount++
+ }
+
+ if rows.Err() != nil {
+ t.Fatalf("conn.Query failed: %v", err)
+ }
+
+ if rowCount != 10 {
+ t.Error("Select called onDataRow wrong number of times")
+ }
+ if sum != 55 {
+ t.Error("Wrong values returned")
+ }
+}
+
+func TestConnQueryValues(t *testing.T) {
+ t.Parallel()
+
+ conn := mustConnect(t, *defaultConnConfig)
+ defer closeConn(t, conn)
+
+ var rowCount int32
+
+ rows, err := conn.Query("select 'foo'::text, 'bar'::varchar, n, null, n::oid from generate_series(1,$1) n", 10)
+ if err != nil {
+ t.Fatalf("conn.Query failed: %v", err)
+ }
+ defer rows.Close()
+
+ for rows.Next() {
+ rowCount++
+
+ values, err := rows.Values()
+ if err != nil {
+ t.Fatalf("rows.Values failed: %v", err)
+ }
+ if len(values) != 5 {
+ t.Errorf("Expected rows.Values to return 5 values, but it returned %d", len(values))
+ }
+ if values[0] != "foo" {
+ t.Errorf(`Expected values[0] to be "foo", but it was %v`, values[0])
+ }
+ if values[1] != "bar" {
+ t.Errorf(`Expected values[1] to be "bar", but it was %v`, values[1])
+ }
+
+ if values[2] != rowCount {
+ t.Errorf(`Expected values[2] to be %d, but it was %d`, rowCount, values[2])
+ }
+
+ if values[3] != nil {
+ t.Errorf(`Expected values[3] to be %v, but it was %d`, nil, values[3])
+ }
+
+ if values[4] != pgx.Oid(rowCount) {
+ t.Errorf(`Expected values[4] to be %d, but it was %d`, rowCount, values[4])
+ }
+ }
+
+ if rows.Err() != nil {
+ t.Fatalf("conn.Query failed: %v", err)
+ }
+
+ if rowCount != 10 {
+ t.Error("Select called onDataRow wrong number of times")
+ }
+}
+
+// Test that a connection stays valid when query results are closed early
+func TestConnQueryCloseEarly(t *testing.T) {
+ t.Parallel()
+
+ conn := mustConnect(t, *defaultConnConfig)
+ defer closeConn(t, conn)
+
+ // Immediately close query without reading any rows
+ rows, err := conn.Query("select generate_series(1,$1)", 10)
+ if err != nil {
+ t.Fatalf("conn.Query failed: %v", err)
+ }
+ rows.Close()
+
+ ensureConnValid(t, conn)
+
+ // Read partial response then close
+ rows, err = conn.Query("select generate_series(1,$1)", 10)
+ if err != nil {
+ t.Fatalf("conn.Query failed: %v", err)
+ }
+
+ ok := rows.Next()
+ if !ok {
+ t.Fatal("rows.Next terminated early")
+ }
+
+ var n int32
+ rows.Scan(&n)
+ if n != 1 {
+ t.Fatalf("Expected 1 from first row, but got %v", n)
+ }
+
+ rows.Close()
+
+ ensureConnValid(t, conn)
+}
+
+func TestConnQueryCloseEarlyWithErrorOnWire(t *testing.T) {
+ t.Parallel()
+
+ conn := mustConnect(t, *defaultConnConfig)
+ defer closeConn(t, conn)
+
+ rows, err := conn.Query("select 1/(10-n) from generate_series(1,10) n")
+ if err != nil {
+ t.Fatalf("conn.Query failed: %v", err)
+ }
+ rows.Close()
+
+ ensureConnValid(t, conn)
+}
+
+// Test that a connection stays valid when query results read incorrectly
+func TestConnQueryReadWrongTypeError(t *testing.T) {
+ t.Parallel()
+
+ conn := mustConnect(t, *defaultConnConfig)
+ defer closeConn(t, conn)
+
+ // Read a single value incorrectly
+ rows, err := conn.Query("select generate_series(1,$1)", 10)
+ if err != nil {
+ t.Fatalf("conn.Query failed: %v", err)
+ }
+
+ rowsRead := 0
+
+ for rows.Next() {
+ var t time.Time
+ rows.Scan(&t)
+ rowsRead++
+ }
+
+ if rowsRead != 1 {
+ t.Fatalf("Expected error to cause only 1 row to be read, but %d were read", rowsRead)
+ }
+
+ if rows.Err() == nil {
+ t.Fatal("Expected Rows to have an error after an improper read but it didn't")
+ }
+
+ if rows.Err().Error() != "can't scan into dest[0]: Can't convert OID 23 to time.Time" {
+ t.Fatalf("Expected different Rows.Err(): %v", rows.Err())
+ }
+
+ ensureConnValid(t, conn)
+}
+
+// Test that a connection stays valid when query results read incorrectly
+func TestConnQueryReadTooManyValues(t *testing.T) {
+ t.Parallel()
+
+ conn := mustConnect(t, *defaultConnConfig)
+ defer closeConn(t, conn)
+
+ // Read too many values
+ rows, err := conn.Query("select generate_series(1,$1)", 10)
+ if err != nil {
+ t.Fatalf("conn.Query failed: %v", err)
+ }
+
+ rowsRead := 0
+
+ for rows.Next() {
+ var n, m int32
+ rows.Scan(&n, &m)
+ rowsRead++
+ }
+
+ if rowsRead != 1 {
+ t.Fatalf("Expected error to cause only 1 row to be read, but %d were read", rowsRead)
+ }
+
+ if rows.Err() == nil {
+ t.Fatal("Expected Rows to have an error after an improper read but it didn't")
+ }
+
+ ensureConnValid(t, conn)
+}
+
+func TestConnQueryScanIgnoreColumn(t *testing.T) {
+ t.Parallel()
+
+ conn := mustConnect(t, *defaultConnConfig)
+ defer closeConn(t, conn)
+
+ rows, err := conn.Query("select 1::int8, 2::int8, 3::int8")
+ if err != nil {
+ t.Fatalf("conn.Query failed: %v", err)
+ }
+
+ ok := rows.Next()
+ if !ok {
+ t.Fatal("rows.Next terminated early")
+ }
+
+ var n, m int64
+ err = rows.Scan(&n, nil, &m)
+ if err != nil {
+ t.Fatalf("rows.Scan failed: %v", err)
+ }
+ rows.Close()
+
+ if n != 1 {
+ t.Errorf("Expected n to equal 1, but it was %d", n)
+ }
+
+ if m != 3 {
+ t.Errorf("Expected n to equal 3, but it was %d", m)
+ }
+
+ ensureConnValid(t, conn)
+}
+
+func TestConnQueryScanner(t *testing.T) {
+ t.Parallel()
+
+ conn := mustConnect(t, *defaultConnConfig)
+ defer closeConn(t, conn)
+
+ rows, err := conn.Query("select null::int8, 1::int8")
+ if err != nil {
+ t.Fatalf("conn.Query failed: %v", err)
+ }
+
+ ok := rows.Next()
+ if !ok {
+ t.Fatal("rows.Next terminated early")
+ }
+
+ var n, m pgx.NullInt64
+ err = rows.Scan(&n, &m)
+ if err != nil {
+ t.Fatalf("rows.Scan failed: %v", err)
+ }
+ rows.Close()
+
+ if n.Valid {
+ t.Error("Null should not be valid, but it was")
+ }
+
+ if !m.Valid {
+ t.Error("1 should be valid, but it wasn't")
+ }
+
+ if m.Int64 != 1 {
+ t.Errorf("m.Int64 should have been 1, but it was %v", m.Int64)
+ }
+
+ ensureConnValid(t, conn)
+}
+
+type pgxNullInt64 struct {
+ Int64 int64
+ Valid bool // Valid is true if Int64 is not NULL
+}
+
+func (n *pgxNullInt64) ScanPgx(vr *pgx.ValueReader) error {
+ if vr.Type().DataType != pgx.Int8Oid {
+ return pgx.SerializationError(fmt.Sprintf("pgxNullInt64.Scan cannot decode OID %d", vr.Type().DataType))
+ }
+
+ if vr.Len() == -1 {
+ n.Int64, n.Valid = 0, false
+ return nil
+ }
+ n.Valid = true
+
+ err := pgx.Decode(vr, &n.Int64)
+ if err != nil {
+ return err
+ }
+ return vr.Err()
+}
+
+func TestConnQueryPgxScanner(t *testing.T) {
+ t.Parallel()
+
+ conn := mustConnect(t, *defaultConnConfig)
+ defer closeConn(t, conn)
+
+ rows, err := conn.Query("select null::int8, 1::int8")
+ if err != nil {
+ t.Fatalf("conn.Query failed: %v", err)
+ }
+
+ ok := rows.Next()
+ if !ok {
+ t.Fatal("rows.Next terminated early")
+ }
+
+ var n, m pgxNullInt64
+ err = rows.Scan(&n, &m)
+ if err != nil {
+ t.Fatalf("rows.Scan failed: %v", err)
+ }
+ rows.Close()
+
+ if n.Valid {
+ t.Error("Null should not be valid, but it was")
+ }
+
+ if !m.Valid {
+ t.Error("1 should be valid, but it wasn't")
+ }
+
+ if m.Int64 != 1 {
+ t.Errorf("m.Int64 should have been 1, but it was %v", m.Int64)
+ }
+
+ ensureConnValid(t, conn)
+}
+
+func TestConnQueryErrorWhileReturningRows(t *testing.T) {
+ t.Parallel()
+
+ conn := mustConnect(t, *defaultConnConfig)
+ defer closeConn(t, conn)
+
+ for i := 0; i < 100; i++ {
+ func() {
+ sql := `select 42 / (random() * 20)::integer from generate_series(1,100000)`
+
+ rows, err := conn.Query(sql)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer rows.Close()
+
+ for rows.Next() {
+ var n int32
+ rows.Scan(&n)
+ }
+
+ if err, ok := rows.Err().(pgx.PgError); !ok {
+ t.Fatalf("Expected pgx.PgError, got %v", err)
+ }
+
+ ensureConnValid(t, conn)
+ }()
+ }
+
+}
+
+func TestConnQueryEncoder(t *testing.T) {
+ t.Parallel()
+
+ conn := mustConnect(t, *defaultConnConfig)
+ defer closeConn(t, conn)
+
+ n := pgx.NullInt64{Int64: 1, Valid: true}
+
+ rows, err := conn.Query("select $1::int8", &n)
+ if err != nil {
+ t.Fatalf("conn.Query failed: %v", err)
+ }
+
+ ok := rows.Next()
+ if !ok {
+ t.Fatal("rows.Next terminated early")
+ }
+
+ var m pgx.NullInt64
+ err = rows.Scan(&m)
+ if err != nil {
+ t.Fatalf("rows.Scan failed: %v", err)
+ }
+ rows.Close()
+
+ if !m.Valid {
+ t.Error("m should be valid, but it wasn't")
+ }
+
+ if m.Int64 != 1 {
+ t.Errorf("m.Int64 should have been 1, but it was %v", m.Int64)
+ }
+
+ ensureConnValid(t, conn)
+}
+
+func TestQueryEncodeError(t *testing.T) {
+ t.Parallel()
+
+ conn := mustConnect(t, *defaultConnConfig)
+ defer closeConn(t, conn)
+
+ rows, err := conn.Query("select $1::integer", "wrong")
+ if err != nil {
+ t.Errorf("conn.Query failure: %v", err)
+ }
+ defer rows.Close()
+
+ rows.Next()
+
+ if rows.Err() == nil {
+ t.Error("Expected rows.Err() to return error, but it didn't")
+ }
+ if rows.Err().Error() != `ERROR: invalid input syntax for integer: "wrong" (SQLSTATE 22P02)` {
+ t.Error("Expected rows.Err() to return different error:", rows.Err())
+ }
+}
+
+// Ensure that an argument that implements Encoder works when the parameter type
+// is a core type.
+type coreEncoder struct{}
+
+func (n coreEncoder) FormatCode() int16 { return pgx.TextFormatCode }
+
+func (n *coreEncoder) Encode(w *pgx.WriteBuf, oid pgx.Oid) error {
+ w.WriteInt32(int32(2))
+ w.WriteBytes([]byte("42"))
+ return nil
+}
+
+func TestQueryEncodeCoreTextFormatError(t *testing.T) {
+ t.Parallel()
+
+ conn := mustConnect(t, *defaultConnConfig)
+ defer closeConn(t, conn)
+
+ var n int32
+ err := conn.QueryRow("select $1::integer", &coreEncoder{}).Scan(&n)
+ if err != nil {
+ t.Fatalf("Unexpected conn.QueryRow error: %v", err)
+ }
+
+ if n != 42 {
+ t.Errorf("Expected 42, got %v", n)
+ }
+}
+
+func TestQueryRowCoreTypes(t *testing.T) {
+ t.Parallel()
+
+ conn := mustConnect(t, *defaultConnConfig)
+ defer closeConn(t, conn)
+
+ type allTypes struct {
+ s string
+ f32 float32
+ f64 float64
+ b bool
+ t time.Time
+ oid pgx.Oid
+ }
+
+ var actual, zero allTypes
+
+ tests := []struct {
+ sql string
+ queryArgs []interface{}
+ scanArgs []interface{}
+ expected allTypes
+ }{
+ {"select $1::text", []interface{}{"Jack"}, []interface{}{&actual.s}, allTypes{s: "Jack"}},
+ {"select $1::float4", []interface{}{float32(1.23)}, []interface{}{&actual.f32}, allTypes{f32: 1.23}},
+ {"select $1::float8", []interface{}{float64(1.23)}, []interface{}{&actual.f64}, allTypes{f64: 1.23}},
+ {"select $1::bool", []interface{}{true}, []interface{}{&actual.b}, allTypes{b: true}},
+ {"select $1::timestamptz", []interface{}{time.Unix(123, 5000)}, []interface{}{&actual.t}, allTypes{t: time.Unix(123, 5000)}},
+ {"select $1::timestamp", []interface{}{time.Date(2010, 1, 2, 3, 4, 5, 0, time.Local)}, []interface{}{&actual.t}, allTypes{t: time.Date(2010, 1, 2, 3, 4, 5, 0, time.Local)}},
+ {"select $1::date", []interface{}{time.Date(1987, 1, 2, 0, 0, 0, 0, time.Local)}, []interface{}{&actual.t}, allTypes{t: time.Date(1987, 1, 2, 0, 0, 0, 0, time.Local)}},
+ {"select $1::oid", []interface{}{pgx.Oid(42)}, []interface{}{&actual.oid}, allTypes{oid: 42}},
+ }
+
+ for i, tt := range tests {
+ actual = zero
+
+ err := conn.QueryRow(tt.sql, tt.queryArgs...).Scan(tt.scanArgs...)
+ if err != nil {
+ t.Errorf("%d. Unexpected failure: %v (sql -> %v, queryArgs -> %v)", i, err, tt.sql, tt.queryArgs)
+ }
+
+ if actual != tt.expected {
+ t.Errorf("%d. Expected %v, got %v (sql -> %v, queryArgs -> %v)", i, tt.expected, actual, tt.sql, tt.queryArgs)
+ }
+
+ ensureConnValid(t, conn)
+
+ // Check that Scan errors when a core type is null
+ err = conn.QueryRow(tt.sql, nil).Scan(tt.scanArgs...)
+ if err == nil {
+ t.Errorf("%d. Expected null to cause error, but it didn't (sql -> %v)", i, tt.sql)
+ }
+ if err != nil && !strings.Contains(err.Error(), "Cannot decode null") {
+ t.Errorf(`%d. Expected null to cause error "Cannot decode null..." but it was %v (sql -> %v)`, i, err, tt.sql)
+ }
+
+ ensureConnValid(t, conn)
+ }
+}
+
+func TestQueryRowCoreIntegerEncoding(t *testing.T) {
+ t.Parallel()
+
+ conn := mustConnect(t, *defaultConnConfig)
+ defer closeConn(t, conn)
+
+ type allTypes struct {
+ ui uint
+ ui8 uint8
+ ui16 uint16
+ ui32 uint32
+ ui64 uint64
+ i int
+ i8 int8
+ i16 int16
+ i32 int32
+ i64 int64
+ }
+
+ var actual, zero allTypes
+
+ successfulEncodeTests := []struct {
+ sql string
+ queryArg interface{}
+ scanArg interface{}
+ expected allTypes
+ }{
+ // Check any integer type where value is within int2 range can be encoded
+ {"select $1::int2", int(42), &actual.i16, allTypes{i16: 42}},
+ {"select $1::int2", int8(42), &actual.i16, allTypes{i16: 42}},
+ {"select $1::int2", int16(42), &actual.i16, allTypes{i16: 42}},
+ {"select $1::int2", int32(42), &actual.i16, allTypes{i16: 42}},
+ {"select $1::int2", int64(42), &actual.i16, allTypes{i16: 42}},
+ {"select $1::int2", uint(42), &actual.i16, allTypes{i16: 42}},
+ {"select $1::int2", uint8(42), &actual.i16, allTypes{i16: 42}},
+ {"select $1::int2", uint16(42), &actual.i16, allTypes{i16: 42}},
+ {"select $1::int2", uint32(42), &actual.i16, allTypes{i16: 42}},
+ {"select $1::int2", uint64(42), &actual.i16, allTypes{i16: 42}},
+
+ // Check any integer type where value is within int4 range can be encoded
+ {"select $1::int4", int(42), &actual.i32, allTypes{i32: 42}},
+ {"select $1::int4", int8(42), &actual.i32, allTypes{i32: 42}},
+ {"select $1::int4", int16(42), &actual.i32, allTypes{i32: 42}},
+ {"select $1::int4", int32(42), &actual.i32, allTypes{i32: 42}},
+ {"select $1::int4", int64(42), &actual.i32, allTypes{i32: 42}},
+ {"select $1::int4", uint(42), &actual.i32, allTypes{i32: 42}},
+ {"select $1::int4", uint8(42), &actual.i32, allTypes{i32: 42}},
+ {"select $1::int4", uint16(42), &actual.i32, allTypes{i32: 42}},
+ {"select $1::int4", uint32(42), &actual.i32, allTypes{i32: 42}},
+ {"select $1::int4", uint64(42), &actual.i32, allTypes{i32: 42}},
+
+ // Check any integer type where value is within int8 range can be encoded
+ {"select $1::int8", int(42), &actual.i64, allTypes{i64: 42}},
+ {"select $1::int8", int8(42), &actual.i64, allTypes{i64: 42}},
+ {"select $1::int8", int16(42), &actual.i64, allTypes{i64: 42}},
+ {"select $1::int8", int32(42), &actual.i64, allTypes{i64: 42}},
+ {"select $1::int8", int64(42), &actual.i64, allTypes{i64: 42}},
+ {"select $1::int8", uint(42), &actual.i64, allTypes{i64: 42}},
+ {"select $1::int8", uint8(42), &actual.i64, allTypes{i64: 42}},
+ {"select $1::int8", uint16(42), &actual.i64, allTypes{i64: 42}},
+ {"select $1::int8", uint32(42), &actual.i64, allTypes{i64: 42}},
+ {"select $1::int8", uint64(42), &actual.i64, allTypes{i64: 42}},
+ }
+
+ for i, tt := range successfulEncodeTests {
+ actual = zero
+
+ err := conn.QueryRow(tt.sql, tt.queryArg).Scan(tt.scanArg)
+ if err != nil {
+ t.Errorf("%d. Unexpected failure: %v (sql -> %v, queryArg -> %v)", i, err, tt.sql, tt.queryArg)
+ continue
+ }
+
+ if actual != tt.expected {
+ t.Errorf("%d. Expected %v, got %v (sql -> %v, queryArg -> %v)", i, tt.expected, actual, tt.sql, tt.queryArg)
+ }
+
+ ensureConnValid(t, conn)
+ }
+
+ failedEncodeTests := []struct {
+ sql string
+ queryArg interface{}
+ }{
+ // Check any integer type where value is outside pg:int2 range cannot be encoded
+ {"select $1::int2", int(32769)},
+ {"select $1::int2", int32(32769)},
+ {"select $1::int2", int32(32769)},
+ {"select $1::int2", int64(32769)},
+ {"select $1::int2", uint(32769)},
+ {"select $1::int2", uint16(32769)},
+ {"select $1::int2", uint32(32769)},
+ {"select $1::int2", uint64(32769)},
+
+ // Check any integer type where value is outside pg:int4 range cannot be encoded
+ {"select $1::int4", int64(2147483649)},
+ {"select $1::int4", uint32(2147483649)},
+ {"select $1::int4", uint64(2147483649)},
+
+ // Check any integer type where value is outside pg:int8 range cannot be encoded
+ {"select $1::int8", uint64(9223372036854775809)},
+ }
+
+ for i, tt := range failedEncodeTests {
+ err := conn.QueryRow(tt.sql, tt.queryArg).Scan(nil)
+ if err == nil {
+ t.Errorf("%d. Expected failure to encode, but unexpectedly succeeded: %v (sql -> %v, queryArg -> %v)", i, err, tt.sql, tt.queryArg)
+ } else if !strings.Contains(err.Error(), "is greater than") {
+ t.Errorf("%d. Expected failure to encode, but got: %v (sql -> %v, queryArg -> %v)", i, err, tt.sql, tt.queryArg)
+ }
+
+ ensureConnValid(t, conn)
+ }
+}
+
+func TestQueryRowCoreIntegerDecoding(t *testing.T) {
+ t.Parallel()
+
+ conn := mustConnect(t, *defaultConnConfig)
+ defer closeConn(t, conn)
+
+ type allTypes struct {
+ ui uint
+ ui8 uint8
+ ui16 uint16
+ ui32 uint32
+ ui64 uint64
+ i int
+ i8 int8
+ i16 int16
+ i32 int32
+ i64 int64
+ }
+
+ var actual, zero allTypes
+
+ successfulDecodeTests := []struct {
+ sql string
+ scanArg interface{}
+ expected allTypes
+ }{
+ // Check any integer type where value is within Go:int range can be decoded
+ {"select 42::int2", &actual.i, allTypes{i: 42}},
+ {"select 42::int4", &actual.i, allTypes{i: 42}},
+ {"select 42::int8", &actual.i, allTypes{i: 42}},
+ {"select -42::int2", &actual.i, allTypes{i: -42}},
+ {"select -42::int4", &actual.i, allTypes{i: -42}},
+ {"select -42::int8", &actual.i, allTypes{i: -42}},
+
+ // Check any integer type where value is within Go:int8 range can be decoded
+ {"select 42::int2", &actual.i8, allTypes{i8: 42}},
+ {"select 42::int4", &actual.i8, allTypes{i8: 42}},
+ {"select 42::int8", &actual.i8, allTypes{i8: 42}},
+ {"select -42::int2", &actual.i8, allTypes{i8: -42}},
+ {"select -42::int4", &actual.i8, allTypes{i8: -42}},
+ {"select -42::int8", &actual.i8, allTypes{i8: -42}},
+
+ // Check any integer type where value is within Go:int16 range can be decoded
+ {"select 42::int2", &actual.i16, allTypes{i16: 42}},
+ {"select 42::int4", &actual.i16, allTypes{i16: 42}},
+ {"select 42::int8", &actual.i16, allTypes{i16: 42}},
+ {"select -42::int2", &actual.i16, allTypes{i16: -42}},
+ {"select -42::int4", &actual.i16, allTypes{i16: -42}},
+ {"select -42::int8", &actual.i16, allTypes{i16: -42}},
+
+ // Check any integer type where value is within Go:int32 range can be decoded
+ {"select 42::int2", &actual.i32, allTypes{i32: 42}},
+ {"select 42::int4", &actual.i32, allTypes{i32: 42}},
+ {"select 42::int8", &actual.i32, allTypes{i32: 42}},
+ {"select -42::int2", &actual.i32, allTypes{i32: -42}},
+ {"select -42::int4", &actual.i32, allTypes{i32: -42}},
+ {"select -42::int8", &actual.i32, allTypes{i32: -42}},
+
+ // Check any integer type where value is within Go:int64 range can be decoded
+ {"select 42::int2", &actual.i64, allTypes{i64: 42}},
+ {"select 42::int4", &actual.i64, allTypes{i64: 42}},
+ {"select 42::int8", &actual.i64, allTypes{i64: 42}},
+ {"select -42::int2", &actual.i64, allTypes{i64: -42}},
+ {"select -42::int4", &actual.i64, allTypes{i64: -42}},
+ {"select -42::int8", &actual.i64, allTypes{i64: -42}},
+
+ // Check any integer type where value is within Go:uint range can be decoded
+ {"select 128::int2", &actual.ui, allTypes{ui: 128}},
+ {"select 128::int4", &actual.ui, allTypes{ui: 128}},
+ {"select 128::int8", &actual.ui, allTypes{ui: 128}},
+
+ // Check any integer type where value is within Go:uint8 range can be decoded
+ {"select 128::int2", &actual.ui8, allTypes{ui8: 128}},
+ {"select 128::int4", &actual.ui8, allTypes{ui8: 128}},
+ {"select 128::int8", &actual.ui8, allTypes{ui8: 128}},
+
+ // Check any integer type where value is within Go:uint16 range can be decoded
+ {"select 42::int2", &actual.ui16, allTypes{ui16: 42}},
+ {"select 32768::int4", &actual.ui16, allTypes{ui16: 32768}},
+ {"select 32768::int8", &actual.ui16, allTypes{ui16: 32768}},
+
+ // Check any integer type where value is within Go:uint32 range can be decoded
+ {"select 42::int2", &actual.ui32, allTypes{ui32: 42}},
+ {"select 42::int4", &actual.ui32, allTypes{ui32: 42}},
+ {"select 2147483648::int8", &actual.ui32, allTypes{ui32: 2147483648}},
+
+ // Check any integer type where value is within Go:uint64 range can be decoded
+ {"select 42::int2", &actual.ui64, allTypes{ui64: 42}},
+ {"select 42::int4", &actual.ui64, allTypes{ui64: 42}},
+ {"select 42::int8", &actual.ui64, allTypes{ui64: 42}},
+ }
+
+ for i, tt := range successfulDecodeTests {
+ actual = zero
+
+ err := conn.QueryRow(tt.sql).Scan(tt.scanArg)
+ if err != nil {
+ t.Errorf("%d. Unexpected failure: %v (sql -> %v)", i, err, tt.sql)
+ continue
+ }
+
+ if actual != tt.expected {
+ t.Errorf("%d. Expected %v, got %v (sql -> %v)", i, tt.expected, actual, tt.sql)
+ }
+
+ ensureConnValid(t, conn)
+ }
+
+ failedDecodeTests := []struct {
+ sql string
+ scanArg interface{}
+ expectedErr string
+ }{
+ // Check any integer type where value is outside Go:int8 range cannot be decoded
+ {"select 128::int2", &actual.i8, "is greater than"},
+ {"select 128::int4", &actual.i8, "is greater than"},
+ {"select 128::int8", &actual.i8, "is greater than"},
+ {"select -129::int2", &actual.i8, "is less than"},
+ {"select -129::int4", &actual.i8, "is less than"},
+ {"select -129::int8", &actual.i8, "is less than"},
+
+ // Check any integer type where value is outside Go:int16 range cannot be decoded
+ {"select 32768::int4", &actual.i16, "is greater than"},
+ {"select 32768::int8", &actual.i16, "is greater than"},
+ {"select -32769::int4", &actual.i16, "is less than"},
+ {"select -32769::int8", &actual.i16, "is less than"},
+
+ // Check any integer type where value is outside Go:int32 range cannot be decoded
+ {"select 2147483648::int8", &actual.i32, "is greater than"},
+ {"select -2147483649::int8", &actual.i32, "is less than"},
+
+ // Check any integer type where value is outside Go:uint range cannot be decoded
+ {"select -1::int2", &actual.ui, "is less than"},
+ {"select -1::int4", &actual.ui, "is less than"},
+ {"select -1::int8", &actual.ui, "is less than"},
+
+ // Check any integer type where value is outside Go:uint8 range cannot be decoded
+ {"select 256::int2", &actual.ui8, "is greater than"},
+ {"select 256::int4", &actual.ui8, "is greater than"},
+ {"select 256::int8", &actual.ui8, "is greater than"},
+ {"select -1::int2", &actual.ui8, "is less than"},
+ {"select -1::int4", &actual.ui8, "is less than"},
+ {"select -1::int8", &actual.ui8, "is less than"},
+
+ // Check any integer type where value is outside Go:uint16 cannot be decoded
+ {"select 65536::int4", &actual.ui16, "is greater than"},
+ {"select 65536::int8", &actual.ui16, "is greater than"},
+ {"select -1::int2", &actual.ui16, "is less than"},
+ {"select -1::int4", &actual.ui16, "is less than"},
+ {"select -1::int8", &actual.ui16, "is less than"},
+
+ // Check any integer type where value is outside Go:uint32 range cannot be decoded
+ {"select 4294967296::int8", &actual.ui32, "is greater than"},
+ {"select -1::int2", &actual.ui32, "is less than"},
+ {"select -1::int4", &actual.ui32, "is less than"},
+ {"select -1::int8", &actual.ui32, "is less than"},
+
+ // Check any integer type where value is outside Go:uint64 range cannot be decoded
+ {"select -1::int2", &actual.ui64, "is less than"},
+ {"select -1::int4", &actual.ui64, "is less than"},
+ {"select -1::int8", &actual.ui64, "is less than"},
+ }
+
+ for i, tt := range failedDecodeTests {
+ err := conn.QueryRow(tt.sql).Scan(tt.scanArg)
+ if err == nil {
+ t.Errorf("%d. Expected failure to decode, but unexpectedly succeeded: %v (sql -> %v)", i, err, tt.sql)
+ } else if !strings.Contains(err.Error(), tt.expectedErr) {
+ t.Errorf("%d. Expected failure to decode, but got: %v (sql -> %v)", i, err, tt.sql)
+ }
+
+ ensureConnValid(t, conn)
+ }
+}
+
+func TestQueryRowCoreByteSlice(t *testing.T) {
+ t.Parallel()
+
+ conn := mustConnect(t, *defaultConnConfig)
+ defer closeConn(t, conn)
+
+ tests := []struct {
+ sql string
+ queryArg interface{}
+ expected []byte
+ }{
+ {"select $1::text", "Jack", []byte("Jack")},
+ {"select $1::text", []byte("Jack"), []byte("Jack")},
+ {"select $1::int4", int32(239023409), []byte{14, 63, 53, 49}},
+ {"select $1::varchar", []byte("Jack"), []byte("Jack")},
+ {"select $1::bytea", []byte{0, 15, 255, 17}, []byte{0, 15, 255, 17}},
+ }
+
+ for i, tt := range tests {
+ var actual []byte
+
+ err := conn.QueryRow(tt.sql, tt.queryArg).Scan(&actual)
+ if err != nil {
+ t.Errorf("%d. Unexpected failure: %v (sql -> %v)", i, err, tt.sql)
+ }
+
+ if !bytes.Equal(actual, tt.expected) {
+ t.Errorf("%d. Expected %v, got %v (sql -> %v)", i, tt.expected, actual, tt.sql)
+ }
+
+ ensureConnValid(t, conn)
+ }
+}
+
+func TestQueryRowByteSliceArgument(t *testing.T) {
+ t.Parallel()
+
+ conn := mustConnect(t, *defaultConnConfig)
+ defer closeConn(t, conn)
+
+ sql := "select $1::int4"
+ queryArg := []byte{14, 63, 53, 49}
+ expected := int32(239023409)
+
+ var actual int32
+
+ err := conn.QueryRow(sql, queryArg).Scan(&actual)
+ if err != nil {
+ t.Errorf("Unexpected failure: %v (sql -> %v)", err, sql)
+ }
+
+ if expected != actual {
+ t.Errorf("Expected %v, got %v (sql -> %v)", expected, actual, sql)
+ }
+
+ ensureConnValid(t, conn)
+}
+
+func TestQueryRowUnknownType(t *testing.T) {
+ t.Parallel()
+
+ conn := mustConnect(t, *defaultConnConfig)
+ defer closeConn(t, conn)
+
+ sql := "select $1::point"
+ expected := "(1,0)"
+ var actual string
+
+ err := conn.QueryRow(sql, expected).Scan(&actual)
+ if err != nil {
+ t.Errorf("Unexpected failure: %v (sql -> %v)", err, sql)
+ }
+
+ if actual != expected {
+ t.Errorf(`Expected "%v", got "%v" (sql -> %v)`, expected, actual, sql)
+
+ }
+
+ ensureConnValid(t, conn)
+}
+
+func TestQueryRowErrors(t *testing.T) {
+ t.Parallel()
+
+ conn := mustConnect(t, *defaultConnConfig)
+ defer closeConn(t, conn)
+
+ type allTypes struct {
+ i16 int16
+ i int
+ s string
+ }
+
+ var actual, zero allTypes
+
+ tests := []struct {
+ sql string
+ queryArgs []interface{}
+ scanArgs []interface{}
+ err string
+ }{
+ {"select $1", []interface{}{"Jack"}, []interface{}{&actual.i16}, "could not determine data type of parameter $1 (SQLSTATE 42P18)"},
+ {"select $1::badtype", []interface{}{"Jack"}, []interface{}{&actual.i16}, `type "badtype" does not exist`},
+ {"SYNTAX ERROR", []interface{}{}, []interface{}{&actual.i16}, "SQLSTATE 42601"},
+ {"select $1::text", []interface{}{"Jack"}, []interface{}{&actual.i16}, "Cannot decode oid 25 into any integer type"},
+ {"select $1::point", []interface{}{int(705)}, []interface{}{&actual.s}, "cannot encode int8 into oid 600"},
+ }
+
+ for i, tt := range tests {
+ actual = zero
+
+ err := conn.QueryRow(tt.sql, tt.queryArgs...).Scan(tt.scanArgs...)
+ if err == nil {
+ t.Errorf("%d. Unexpected success (sql -> %v, queryArgs -> %v)", i, tt.sql, tt.queryArgs)
+ }
+ if err != nil && !strings.Contains(err.Error(), tt.err) {
+ t.Errorf("%d. Expected error to contain %s, but got %v (sql -> %v, queryArgs -> %v)", i, tt.err, err, tt.sql, tt.queryArgs)
+ }
+
+ ensureConnValid(t, conn)
+ }
+}
+
+func TestQueryRowNoResults(t *testing.T) {
+ t.Parallel()
+
+ conn := mustConnect(t, *defaultConnConfig)
+ defer closeConn(t, conn)
+
+ var n int32
+ err := conn.QueryRow("select 1 where 1=0").Scan(&n)
+ if err != pgx.ErrNoRows {
+ t.Errorf("Expected pgx.ErrNoRows, got %v", err)
+ }
+
+ ensureConnValid(t, conn)
+}
+
+func TestQueryRowCoreInt16Slice(t *testing.T) {
+ t.Parallel()
+
+ conn := mustConnect(t, *defaultConnConfig)
+ defer closeConn(t, conn)
+
+ var actual []int16
+
+ tests := []struct {
+ sql string
+ expected []int16
+ }{
+ {"select $1::int2[]", []int16{1, 2, 3, 4, 5}},
+ {"select $1::int2[]", []int16{}},
+ }
+
+ for i, tt := range tests {
+ err := conn.QueryRow(tt.sql, tt.expected).Scan(&actual)
+ if err != nil {
+ t.Errorf("%d. Unexpected failure: %v", i, err)
+ }
+
+ if len(actual) != len(tt.expected) {
+ t.Errorf("%d. Expected %v, got %v", i, tt.expected, actual)
+ }
+
+ for j := 0; j < len(actual); j++ {
+ if actual[j] != tt.expected[j] {
+ t.Errorf("%d. Expected actual[%d] to be %v, got %v", i, j, tt.expected[j], actual[j])
+ }
+ }
+
+ ensureConnValid(t, conn)
+ }
+
+ // Check that Scan errors when an array with a null is scanned into a core slice type
+ err := conn.QueryRow("select '{1, 2, 3, 4, 5, null}'::int2[];").Scan(&actual)
+ if err == nil {
+ t.Error("Expected null to cause error when scanned into slice, but it didn't")
+ }
+ if err != nil && !strings.Contains(err.Error(), "Cannot decode null") {
+ t.Errorf(`Expected null to cause error "Cannot decode null..." but it was %v`, err)
+ }
+
+ ensureConnValid(t, conn)
+}
+
+func TestQueryRowCoreInt32Slice(t *testing.T) {
+ t.Parallel()
+
+ conn := mustConnect(t, *defaultConnConfig)
+ defer closeConn(t, conn)
+
+ var actual []int32
+
+ tests := []struct {
+ sql string
+ expected []int32
+ }{
+ {"select $1::int4[]", []int32{1, 2, 3, 4, 5}},
+ {"select $1::int4[]", []int32{}},
+ }
+
+ for i, tt := range tests {
+ err := conn.QueryRow(tt.sql, tt.expected).Scan(&actual)
+ if err != nil {
+ t.Errorf("%d. Unexpected failure: %v", i, err)
+ }
+
+ if len(actual) != len(tt.expected) {
+ t.Errorf("%d. Expected %v, got %v", i, tt.expected, actual)
+ }
+
+ for j := 0; j < len(actual); j++ {
+ if actual[j] != tt.expected[j] {
+ t.Errorf("%d. Expected actual[%d] to be %v, got %v", i, j, tt.expected[j], actual[j])
+ }
+ }
+
+ ensureConnValid(t, conn)
+ }
+
+ // Check that Scan errors when an array with a null is scanned into a core slice type
+ err := conn.QueryRow("select '{1, 2, 3, 4, 5, null}'::int4[];").Scan(&actual)
+ if err == nil {
+ t.Error("Expected null to cause error when scanned into slice, but it didn't")
+ }
+ if err != nil && !strings.Contains(err.Error(), "Cannot decode null") {
+ t.Errorf(`Expected null to cause error "Cannot decode null..." but it was %v`, err)
+ }
+
+ ensureConnValid(t, conn)
+}
+
+func TestQueryRowCoreInt64Slice(t *testing.T) {
+ t.Parallel()
+
+ conn := mustConnect(t, *defaultConnConfig)
+ defer closeConn(t, conn)
+
+ var actual []int64
+
+ tests := []struct {
+ sql string
+ expected []int64
+ }{
+ {"select $1::int8[]", []int64{1, 2, 3, 4, 5}},
+ {"select $1::int8[]", []int64{}},
+ }
+
+ for i, tt := range tests {
+ err := conn.QueryRow(tt.sql, tt.expected).Scan(&actual)
+ if err != nil {
+ t.Errorf("%d. Unexpected failure: %v", i, err)
+ }
+
+ if len(actual) != len(tt.expected) {
+ t.Errorf("%d. Expected %v, got %v", i, tt.expected, actual)
+ }
+
+ for j := 0; j < len(actual); j++ {
+ if actual[j] != tt.expected[j] {
+ t.Errorf("%d. Expected actual[%d] to be %v, got %v", i, j, tt.expected[j], actual[j])
+ }
+ }
+
+ ensureConnValid(t, conn)
+ }
+
+ // Check that Scan errors when an array with a null is scanned into a core slice type
+ err := conn.QueryRow("select '{1, 2, 3, 4, 5, null}'::int8[];").Scan(&actual)
+ if err == nil {
+ t.Error("Expected null to cause error when scanned into slice, but it didn't")
+ }
+ if err != nil && !strings.Contains(err.Error(), "Cannot decode null") {
+ t.Errorf(`Expected null to cause error "Cannot decode null..." but it was %v`, err)
+ }
+
+ ensureConnValid(t, conn)
+}
+
+func TestQueryRowCoreFloat32Slice(t *testing.T) {
+ t.Parallel()
+
+ conn := mustConnect(t, *defaultConnConfig)
+ defer closeConn(t, conn)
+
+ var actual []float32
+
+ tests := []struct {
+ sql string
+ expected []float32
+ }{
+ {"select $1::float4[]", []float32{1.5, 2.0, 3.5}},
+ {"select $1::float4[]", []float32{}},
+ }
+
+ for i, tt := range tests {
+ err := conn.QueryRow(tt.sql, tt.expected).Scan(&actual)
+ if err != nil {
+ t.Errorf("%d. Unexpected failure: %v", i, err)
+ }
+
+ if len(actual) != len(tt.expected) {
+ t.Errorf("%d. Expected %v, got %v", i, tt.expected, actual)
+ }
+
+ for j := 0; j < len(actual); j++ {
+ if actual[j] != tt.expected[j] {
+ t.Errorf("%d. Expected actual[%d] to be %v, got %v", i, j, tt.expected[j], actual[j])
+ }
+ }
+
+ ensureConnValid(t, conn)
+ }
+
+ // Check that Scan errors when an array with a null is scanned into a core slice type
+ err := conn.QueryRow("select '{1.5, 2.0, 3.5, null}'::float4[];").Scan(&actual)
+ if err == nil {
+ t.Error("Expected null to cause error when scanned into slice, but it didn't")
+ }
+ if err != nil && !strings.Contains(err.Error(), "Cannot decode null") {
+ t.Errorf(`Expected null to cause error "Cannot decode null..." but it was %v`, err)
+ }
+
+ ensureConnValid(t, conn)
+}
+
+func TestQueryRowCoreFloat64Slice(t *testing.T) {
+ t.Parallel()
+
+ conn := mustConnect(t, *defaultConnConfig)
+ defer closeConn(t, conn)
+
+ var actual []float64
+
+ tests := []struct {
+ sql string
+ expected []float64
+ }{
+ {"select $1::float8[]", []float64{1.5, 2.0, 3.5}},
+ {"select $1::float8[]", []float64{}},
+ }
+
+ for i, tt := range tests {
+ err := conn.QueryRow(tt.sql, tt.expected).Scan(&actual)
+ if err != nil {
+ t.Errorf("%d. Unexpected failure: %v", i, err)
+ }
+
+ if len(actual) != len(tt.expected) {
+ t.Errorf("%d. Expected %v, got %v", i, tt.expected, actual)
+ }
+
+ for j := 0; j < len(actual); j++ {
+ if actual[j] != tt.expected[j] {
+ t.Errorf("%d. Expected actual[%d] to be %v, got %v", i, j, tt.expected[j], actual[j])
+ }
+ }
+
+ ensureConnValid(t, conn)
+ }
+
+ // Check that Scan errors when an array with a null is scanned into a core slice type
+ err := conn.QueryRow("select '{1.5, 2.0, 3.5, null}'::float8[];").Scan(&actual)
+ if err == nil {
+ t.Error("Expected null to cause error when scanned into slice, but it didn't")
+ }
+ if err != nil && !strings.Contains(err.Error(), "Cannot decode null") {
+ t.Errorf(`Expected null to cause error "Cannot decode null..." but it was %v`, err)
+ }
+
+ ensureConnValid(t, conn)
+}
+
+func TestQueryRowCoreStringSlice(t *testing.T) {
+ t.Parallel()
+
+ conn := mustConnect(t, *defaultConnConfig)
+ defer closeConn(t, conn)
+
+ var actual []string
+
+ tests := []struct {
+ sql string
+ expected []string
+ }{
+ {"select $1::text[]", []string{"Adam", "Eve", "UTF-8 Characters Å Æ Ë Ͽ"}},
+ {"select $1::text[]", []string{}},
+ {"select $1::varchar[]", []string{"Adam", "Eve", "UTF-8 Characters Å Æ Ë Ͽ"}},
+ {"select $1::varchar[]", []string{}},
+ }
+
+ for i, tt := range tests {
+ err := conn.QueryRow(tt.sql, tt.expected).Scan(&actual)
+ if err != nil {
+ t.Errorf("%d. Unexpected failure: %v", i, err)
+ }
+
+ if len(actual) != len(tt.expected) {
+ t.Errorf("%d. Expected %v, got %v", i, tt.expected, actual)
+ }
+
+ for j := 0; j < len(actual); j++ {
+ if actual[j] != tt.expected[j] {
+ t.Errorf("%d. Expected actual[%d] to be %v, got %v", i, j, tt.expected[j], actual[j])
+ }
+ }
+
+ ensureConnValid(t, conn)
+ }
+
+ // Check that Scan errors when an array with a null is scanned into a core slice type
+ err := conn.QueryRow("select '{Adam,Eve,NULL}'::text[];").Scan(&actual)
+ if err == nil {
+ t.Error("Expected null to cause error when scanned into slice, but it didn't")
+ }
+ if err != nil && !strings.Contains(err.Error(), "Cannot decode null") {
+ t.Errorf(`Expected null to cause error "Cannot decode null..." but it was %v`, err)
+ }
+
+ ensureConnValid(t, conn)
+}
+
+func TestReadingValueAfterEmptyArray(t *testing.T) {
+ conn := mustConnect(t, *defaultConnConfig)
+ defer closeConn(t, conn)
+
+ var a []string
+ var b int32
+ err := conn.QueryRow("select '{}'::text[], 42::integer").Scan(&a, &b)
+ if err != nil {
+ t.Fatalf("conn.QueryRow failed: %v", err)
+ }
+
+ if len(a) != 0 {
+ t.Errorf("Expected 'a' to have length 0, but it was: %d", len(a))
+ }
+
+ if b != 42 {
+ t.Errorf("Expected 'b' to 42, but it was: %d", b)
+ }
+}
+
+func TestReadingNullByteArray(t *testing.T) {
+ conn := mustConnect(t, *defaultConnConfig)
+ defer closeConn(t, conn)
+
+ var a []byte
+ err := conn.QueryRow("select null::text").Scan(&a)
+ if err != nil {
+ t.Fatalf("conn.QueryRow failed: %v", err)
+ }
+
+ if a != nil {
+ t.Errorf("Expected 'a' to be nil, but it was: %v", a)
+ }
+}
+
+func TestReadingNullByteArrays(t *testing.T) {
+ conn := mustConnect(t, *defaultConnConfig)
+ defer closeConn(t, conn)
+
+ rows, err := conn.Query("select null::text union all select null::text")
+ if err != nil {
+ t.Fatalf("conn.Query failed: %v", err)
+ }
+
+ count := 0
+ for rows.Next() {
+ count++
+ var a []byte
+ if err := rows.Scan(&a); err != nil {
+ t.Fatalf("failed to scan row: %v", err)
+ }
+ if a != nil {
+ t.Errorf("Expected 'a' to be nil, but it was: %v", a)
+ }
+ }
+ if count != 2 {
+ t.Errorf("Expected to read 2 rows, read: %d", count)
+ }
+}
+
+// Use github.com/shopspring/decimal as real-world database/sql custom type
+// to test against.
+func TestConnQueryDatabaseSQLScanner(t *testing.T) {
+ t.Parallel()
+
+ conn := mustConnect(t, *defaultConnConfig)
+ defer closeConn(t, conn)
+
+ var num decimal.Decimal
+
+ err := conn.QueryRow("select '1234.567'::decimal").Scan(&num)
+ if err != nil {
+ t.Fatalf("Scan failed: %v", err)
+ }
+
+ expected, err := decimal.NewFromString("1234.567")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if !num.Equals(expected) {
+ t.Errorf("Expected num to be %v, but it was %v", expected, num)
+ }
+
+ ensureConnValid(t, conn)
+}
+
+// Use github.com/shopspring/decimal as real-world database/sql custom type
+// to test against.
+func TestConnQueryDatabaseSQLDriverValuer(t *testing.T) {
+ t.Parallel()
+
+ conn := mustConnect(t, *defaultConnConfig)
+ defer closeConn(t, conn)
+
+ expected, err := decimal.NewFromString("1234.567")
+ if err != nil {
+ t.Fatal(err)
+ }
+ var num decimal.Decimal
+
+ err = conn.QueryRow("select $1::decimal", &expected).Scan(&num)
+ if err != nil {
+ t.Fatalf("Scan failed: %v", err)
+ }
+
+ if !num.Equals(expected) {
+ t.Errorf("Expected num to be %v, but it was %v", expected, num)
+ }
+
+ ensureConnValid(t, conn)
+}
+
+func TestConnQueryDatabaseSQLNullX(t *testing.T) {
+ t.Parallel()
+
+ conn := mustConnect(t, *defaultConnConfig)
+ defer closeConn(t, conn)
+
+ type row struct {
+ boolValid sql.NullBool
+ boolNull sql.NullBool
+ int64Valid sql.NullInt64
+ int64Null sql.NullInt64
+ float64Valid sql.NullFloat64
+ float64Null sql.NullFloat64
+ stringValid sql.NullString
+ stringNull sql.NullString
+ }
+
+ expected := row{
+ boolValid: sql.NullBool{Bool: true, Valid: true},
+ int64Valid: sql.NullInt64{Int64: 123, Valid: true},
+ float64Valid: sql.NullFloat64{Float64: 3.14, Valid: true},
+ stringValid: sql.NullString{String: "pgx", Valid: true},
+ }
+
+ var actual row
+
+ err := conn.QueryRow(
+ "select $1::bool, $2::bool, $3::int8, $4::int8, $5::float8, $6::float8, $7::text, $8::text",
+ expected.boolValid,
+ expected.boolNull,
+ expected.int64Valid,
+ expected.int64Null,
+ expected.float64Valid,
+ expected.float64Null,
+ expected.stringValid,
+ expected.stringNull,
+ ).Scan(
+ &actual.boolValid,
+ &actual.boolNull,
+ &actual.int64Valid,
+ &actual.int64Null,
+ &actual.float64Valid,
+ &actual.float64Null,
+ &actual.stringValid,
+ &actual.stringNull,
+ )
+ if err != nil {
+ t.Fatalf("Scan failed: %v", err)
+ }
+
+ if expected != actual {
+ t.Errorf("Expected %v, but got %v", expected, actual)
+ }
+
+ ensureConnValid(t, conn)
+}