package pgx_test import ( "bytes" "database/sql" "github.com/jackc/pgx" "strings" "testing" "time" "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', 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) != 4 { t.Errorf("Expected rows.Values to return 4 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[0] != "foo" { t.Errorf(`Expected values[0] to be "foo", but it was %v`, values[0]) } if values[1] != rowCount { t.Errorf(`Expected values[1] to be %d, but it was %d`, rowCount, values[1]) } if values[2] != nil { t.Errorf(`Expected values[2] to be %v, but it was %d`, nil, values[2]) } if values[3] != pgx.Oid(rowCount) { t.Errorf(`Expected values[3] to be %d, but it was %d`, rowCount, values[3]) } } 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) } 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 i16 int16 i32 int32 i64 int64 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::int2", []interface{}{int16(42)}, []interface{}{&actual.i16}, allTypes{i16: 42}}, {"select $1::int4", []interface{}{int32(42)}, []interface{}{&actual.i32}, allTypes{i32: 42}}, {"select $1::int8", []interface{}{int64(42)}, []interface{}{&actual.i64}, allTypes{i64: 42}}, {"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 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.Compare(actual, tt.expected) != 0 { 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 int16"}, {"select $1::point", []interface{}{int(705)}, []interface{}{&actual.s}, "cannot encode uint64 into oid 600"}, {"select 42::int4", []interface{}{}, []interface{}{&actual.i}, "Scan cannot decode into *int"}, } 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) }