61bf7d841a
PostgreSQL has two string syntaxes, one that allows backslash escapes and one that does not (SQL standard conforming strings). By default PostgreSQL uses standard conforming strings. QuoteString was only designed for use with standard conforming strings. If PostgreSQL was configured with certain combinations of the standard_conforming_strings and backslash_quote settings, QuoteString may not correctly sanitize strings. QuoteString was only used in unprepared queries, bound parameters are used for prepared queries. This commit alters pgx to use always use bound parameters. As a consequence of never doing string interpolation there is no need to have separate Text and Binary encoders. There is now only the Encoder interface. This change had a negative effect on the performance of simple unprepared queries, but prepared statements should already be used for performance. fixes #26 https://github.com/jackc/pgx/issues/26
559 lines
13 KiB
Go
559 lines
13 KiB
Go
package pgx_test
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"github.com/jackc/pgx"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
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: ", 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: ", 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 from generate_series(1,$1) n", 10)
|
|
if err != nil {
|
|
t.Fatalf("conn.Query failed: ", 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) != 3 {
|
|
t.Errorf("Expected rows.Values to return 3 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 %d, but it was %d`, nil, values[2])
|
|
}
|
|
}
|
|
|
|
if rows.Err() != nil {
|
|
t.Fatalf("conn.Query failed: ", 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: ", 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: ", 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)
|
|
}
|
|
|
|
// 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: ", 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")
|
|
}
|
|
|
|
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: ", 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 TestConnQueryUnpreparedScanner(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: ", 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: ", 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 TestConnQueryPreparedScanner(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
conn := mustConnect(t, *defaultConnConfig)
|
|
defer closeConn(t, conn)
|
|
|
|
mustPrepare(t, conn, "scannerTest", "select null::int8, 1::int8")
|
|
|
|
rows, err := conn.Query("scannerTest")
|
|
if err != nil {
|
|
t.Fatalf("conn.Query failed: ", 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: ", 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 TestConnQueryUnpreparedEncoder(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: ", 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: ", 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 TestQueryPreparedEncodeError(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
conn := mustConnect(t, *defaultConnConfig)
|
|
defer closeConn(t, conn)
|
|
|
|
mustPrepare(t, conn, "testTranscode", "select $1::integer")
|
|
defer func() {
|
|
if err := conn.Deallocate("testTranscode"); err != nil {
|
|
t.Fatalf("Unable to deallocate prepared statement: %v", err)
|
|
}
|
|
}()
|
|
|
|
_, err := conn.Query("testTranscode", "wrong")
|
|
switch {
|
|
case err == nil:
|
|
t.Error("Expected transcode error to return error, but it didn't")
|
|
case err.Error() == "Expected integer representable in int32, received string wrong":
|
|
// Correct behavior
|
|
default:
|
|
t.Errorf("Expected transcode error, received %v", 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 TestQueryPreparedEncodeCoreTextFormatError(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
conn := mustConnect(t, *defaultConnConfig)
|
|
defer closeConn(t, conn)
|
|
|
|
mustPrepare(t, conn, "testTranscode", "select $1::integer")
|
|
|
|
var n int32
|
|
err := conn.QueryRow("testTranscode", &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
|
|
}
|
|
|
|
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)}},
|
|
}
|
|
|
|
for i, tt := range tests {
|
|
psName := fmt.Sprintf("success%d", i)
|
|
mustPrepare(t, conn, psName, tt.sql)
|
|
|
|
for _, sql := range []string{tt.sql, psName} {
|
|
actual = zero
|
|
|
|
err := conn.QueryRow(sql, tt.queryArgs...).Scan(tt.scanArgs...)
|
|
if err != nil {
|
|
t.Errorf("%d. Unexpected failure: %v (sql -> %v, queryArgs -> %v)", i, err, sql, tt.queryArgs)
|
|
}
|
|
|
|
if actual != tt.expected {
|
|
t.Errorf("%d. Expected %v, got %v (sql -> %v, queryArgs -> %v)", i, tt.expected, actual, sql, tt.queryArgs)
|
|
}
|
|
|
|
ensureConnValid(t, conn)
|
|
|
|
// Check that Scan errors when a core type is null
|
|
err = conn.QueryRow(sql, nil).Scan(tt.scanArgs...)
|
|
if err == nil {
|
|
t.Errorf("%d. Expected null to cause error, but it didn't (sql -> %v)", i, 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, sql)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestQueryRowCoreBytea(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
conn := mustConnect(t, *defaultConnConfig)
|
|
defer closeConn(t, conn)
|
|
|
|
var actual []byte
|
|
sql := "select $1::bytea"
|
|
queryArg := []byte{0, 15, 255, 17}
|
|
expected := []byte{0, 15, 255, 17}
|
|
|
|
psName := "selectBytea"
|
|
mustPrepare(t, conn, psName, sql)
|
|
|
|
for _, sql := range []string{sql, psName} {
|
|
actual = nil
|
|
|
|
err := conn.QueryRow(sql, queryArg).Scan(&actual)
|
|
if err != nil {
|
|
t.Errorf("Unexpected failure: %v (sql -> %v)", err, sql)
|
|
}
|
|
|
|
if bytes.Compare(actual, expected) != 0 {
|
|
t.Errorf("Expected %v, got %v (sql -> %v)", expected, actual, sql)
|
|
}
|
|
|
|
ensureConnValid(t, conn)
|
|
}
|
|
}
|
|
|
|
func TestQueryRowUnpreparedErrors(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
conn := mustConnect(t, *defaultConnConfig)
|
|
defer closeConn(t, conn)
|
|
|
|
type allTypes struct {
|
|
i16 int16
|
|
}
|
|
|
|
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"},
|
|
}
|
|
|
|
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 TestQueryRowPreparedErrors(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
conn := mustConnect(t, *defaultConnConfig)
|
|
defer closeConn(t, conn)
|
|
|
|
type allTypes struct {
|
|
i16 int16
|
|
}
|
|
|
|
var actual, zero allTypes
|
|
|
|
tests := []struct {
|
|
sql string
|
|
queryArgs []interface{}
|
|
scanArgs []interface{}
|
|
err string
|
|
}{
|
|
{"select $1::text", []interface{}{"Jack"}, []interface{}{&actual.i16}, "Expected type oid 21 but received type oid 25"},
|
|
}
|
|
|
|
for i, tt := range tests {
|
|
psName := fmt.Sprintf("ps%d", i)
|
|
mustPrepare(t, conn, psName, tt.sql)
|
|
|
|
actual = zero
|
|
|
|
err := conn.QueryRow(psName, 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)
|
|
|
|
sql := "select 1 where 1=0"
|
|
psName := "selectNothing"
|
|
mustPrepare(t, conn, psName, sql)
|
|
|
|
for _, sql := range []string{sql, psName} {
|
|
var n int32
|
|
err := conn.QueryRow(sql).Scan(&n)
|
|
if err != pgx.ErrNoRows {
|
|
t.Errorf("Expected pgx.ErrNoRows, got %v", err)
|
|
}
|
|
|
|
ensureConnValid(t, conn)
|
|
}
|
|
}
|