mirror of
https://github.com/jackc/pgx.git
synced 2025-04-27 13:14:32 +00:00
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
205 lines
8.0 KiB
Go
205 lines
8.0 KiB
Go
package pgx_test
|
|
|
|
import (
|
|
"fmt"
|
|
"github.com/jackc/pgx"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestDateTranscode(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
conn := mustConnect(t, *defaultConnConfig)
|
|
defer closeConn(t, conn)
|
|
|
|
mustPrepare(t, conn, "testTranscode", "select $1::date")
|
|
defer func() {
|
|
if err := conn.Deallocate("testTranscode"); err != nil {
|
|
t.Fatalf("Unable to deallocate prepared statement: %v", err)
|
|
}
|
|
}()
|
|
|
|
dates := []time.Time{
|
|
time.Date(1990, 1, 1, 0, 0, 0, 0, time.Local),
|
|
time.Date(1999, 12, 31, 0, 0, 0, 0, time.Local),
|
|
time.Date(2000, 1, 1, 0, 0, 0, 0, time.Local),
|
|
time.Date(2001, 1, 2, 0, 0, 0, 0, time.Local),
|
|
time.Date(2004, 2, 29, 0, 0, 0, 0, time.Local),
|
|
time.Date(2013, 7, 4, 0, 0, 0, 0, time.Local),
|
|
time.Date(2013, 12, 25, 0, 0, 0, 0, time.Local),
|
|
}
|
|
|
|
for _, actualDate := range dates {
|
|
var d time.Time
|
|
|
|
// Test text format
|
|
err := conn.QueryRow("select $1::date", actualDate).Scan(&d)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected failure on QueryRow Scan: %v", err)
|
|
}
|
|
if !actualDate.Equal(d) {
|
|
t.Errorf("Did not transcode date successfully: %v is not %v", d, actualDate)
|
|
}
|
|
|
|
// Test binary format
|
|
err = conn.QueryRow("testTranscode", actualDate).Scan(&d)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected failure on QueryRow Scan: %v", err)
|
|
}
|
|
if !actualDate.Equal(d) {
|
|
t.Errorf("Did not transcode date successfully: %v is not %v", d, actualDate)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestTimestampTzTranscode(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
conn := mustConnect(t, *defaultConnConfig)
|
|
defer closeConn(t, conn)
|
|
|
|
inputTime := time.Date(2013, 1, 2, 3, 4, 5, 6000, time.Local)
|
|
|
|
var outputTime time.Time
|
|
|
|
err := conn.QueryRow("select $1::timestamptz", inputTime).Scan(&outputTime)
|
|
if err != nil {
|
|
t.Fatalf("QueryRow Scan failed: %v", err)
|
|
}
|
|
if !inputTime.Equal(outputTime) {
|
|
t.Errorf("Did not transcode time successfully: %v is not %v", outputTime, inputTime)
|
|
}
|
|
|
|
mustPrepare(t, conn, "testTranscode", "select $1::timestamptz")
|
|
defer func() {
|
|
if err := conn.Deallocate("testTranscode"); err != nil {
|
|
t.Fatalf("Unable to deallocate prepared statement: %v", err)
|
|
}
|
|
}()
|
|
|
|
err = conn.QueryRow("testTranscode", inputTime).Scan(&outputTime)
|
|
if err != nil {
|
|
t.Fatalf("QueryRow Scan failed: %v", err)
|
|
}
|
|
if !inputTime.Equal(outputTime) {
|
|
t.Errorf("Did not transcode time successfully: %v is not %v", outputTime, inputTime)
|
|
}
|
|
}
|
|
|
|
func TestNullX(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
conn := mustConnect(t, *defaultConnConfig)
|
|
defer closeConn(t, conn)
|
|
|
|
type allTypes struct {
|
|
s pgx.NullString
|
|
i16 pgx.NullInt16
|
|
i32 pgx.NullInt32
|
|
i64 pgx.NullInt64
|
|
f32 pgx.NullFloat32
|
|
f64 pgx.NullFloat64
|
|
b pgx.NullBool
|
|
t pgx.NullTime
|
|
}
|
|
|
|
var actual, zero allTypes
|
|
|
|
tests := []struct {
|
|
sql string
|
|
queryArgs []interface{}
|
|
scanArgs []interface{}
|
|
expected allTypes
|
|
}{
|
|
{"select $1::text", []interface{}{pgx.NullString{String: "foo", Valid: true}}, []interface{}{&actual.s}, allTypes{s: pgx.NullString{String: "foo", Valid: true}}},
|
|
{"select $1::text", []interface{}{pgx.NullString{String: "foo", Valid: false}}, []interface{}{&actual.s}, allTypes{s: pgx.NullString{String: "", Valid: false}}},
|
|
{"select $1::int2", []interface{}{pgx.NullInt16{Int16: 1, Valid: true}}, []interface{}{&actual.i16}, allTypes{i16: pgx.NullInt16{Int16: 1, Valid: true}}},
|
|
{"select $1::int2", []interface{}{pgx.NullInt16{Int16: 1, Valid: false}}, []interface{}{&actual.i16}, allTypes{i16: pgx.NullInt16{Int16: 0, Valid: false}}},
|
|
{"select $1::int4", []interface{}{pgx.NullInt32{Int32: 1, Valid: true}}, []interface{}{&actual.i32}, allTypes{i32: pgx.NullInt32{Int32: 1, Valid: true}}},
|
|
{"select $1::int4", []interface{}{pgx.NullInt32{Int32: 1, Valid: false}}, []interface{}{&actual.i32}, allTypes{i32: pgx.NullInt32{Int32: 0, Valid: false}}},
|
|
{"select $1::int8", []interface{}{pgx.NullInt64{Int64: 1, Valid: true}}, []interface{}{&actual.i64}, allTypes{i64: pgx.NullInt64{Int64: 1, Valid: true}}},
|
|
{"select $1::int8", []interface{}{pgx.NullInt64{Int64: 1, Valid: false}}, []interface{}{&actual.i64}, allTypes{i64: pgx.NullInt64{Int64: 0, Valid: false}}},
|
|
{"select $1::float4", []interface{}{pgx.NullFloat32{Float32: 1.23, Valid: true}}, []interface{}{&actual.f32}, allTypes{f32: pgx.NullFloat32{Float32: 1.23, Valid: true}}},
|
|
{"select $1::float4", []interface{}{pgx.NullFloat32{Float32: 1.23, Valid: false}}, []interface{}{&actual.f32}, allTypes{f32: pgx.NullFloat32{Float32: 0, Valid: false}}},
|
|
{"select $1::float8", []interface{}{pgx.NullFloat64{Float64: 1.23, Valid: true}}, []interface{}{&actual.f64}, allTypes{f64: pgx.NullFloat64{Float64: 1.23, Valid: true}}},
|
|
{"select $1::float8", []interface{}{pgx.NullFloat64{Float64: 1.23, Valid: false}}, []interface{}{&actual.f64}, allTypes{f64: pgx.NullFloat64{Float64: 0, Valid: false}}},
|
|
{"select $1::bool", []interface{}{pgx.NullBool{Bool: true, Valid: true}}, []interface{}{&actual.b}, allTypes{b: pgx.NullBool{Bool: true, Valid: true}}},
|
|
{"select $1::bool", []interface{}{pgx.NullBool{Bool: true, Valid: false}}, []interface{}{&actual.b}, allTypes{b: pgx.NullBool{Bool: false, Valid: false}}},
|
|
{"select $1::timestamptz", []interface{}{pgx.NullTime{Time: time.Unix(123, 5000), Valid: true}}, []interface{}{&actual.t}, allTypes{t: pgx.NullTime{Time: time.Unix(123, 5000), Valid: true}}},
|
|
{"select $1::timestamptz", []interface{}{pgx.NullTime{Time: time.Unix(123, 5000), Valid: false}}, []interface{}{&actual.t}, allTypes{t: pgx.NullTime{Time: time.Time{}, Valid: false}}},
|
|
{"select 42::int4, $1::float8", []interface{}{pgx.NullFloat64{Float64: 1.23, Valid: true}}, []interface{}{&actual.i32, &actual.f64}, allTypes{i32: pgx.NullInt32{Int32: 42, Valid: true}, f64: pgx.NullFloat64{Float64: 1.23, Valid: true}}},
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestNullXMismatch(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
conn := mustConnect(t, *defaultConnConfig)
|
|
defer closeConn(t, conn)
|
|
|
|
type allTypes struct {
|
|
s pgx.NullString
|
|
i16 pgx.NullInt16
|
|
i32 pgx.NullInt32
|
|
i64 pgx.NullInt64
|
|
f32 pgx.NullFloat32
|
|
f64 pgx.NullFloat64
|
|
b pgx.NullBool
|
|
t pgx.NullTime
|
|
}
|
|
|
|
var actual, zero allTypes
|
|
|
|
tests := []struct {
|
|
sql string
|
|
queryArgs []interface{}
|
|
scanArgs []interface{}
|
|
err string
|
|
}{
|
|
{"select $1::date", []interface{}{pgx.NullString{String: "foo", Valid: true}}, []interface{}{&actual.s}, "invalid input syntax for type date"},
|
|
{"select $1::date", []interface{}{pgx.NullInt16{Int16: 1, Valid: true}}, []interface{}{&actual.i16}, "cannot encode into OID 1082"},
|
|
{"select $1::date", []interface{}{pgx.NullInt32{Int32: 1, Valid: true}}, []interface{}{&actual.i32}, "cannot encode into OID 1082"},
|
|
{"select $1::date", []interface{}{pgx.NullInt64{Int64: 1, Valid: true}}, []interface{}{&actual.i64}, "cannot encode into OID 1082"},
|
|
{"select $1::date", []interface{}{pgx.NullFloat32{Float32: 1.23, Valid: true}}, []interface{}{&actual.f32}, "cannot encode into OID 1082"},
|
|
{"select $1::date", []interface{}{pgx.NullFloat64{Float64: 1.23, Valid: true}}, []interface{}{&actual.f64}, "cannot encode into OID 1082"},
|
|
{"select $1::date", []interface{}{pgx.NullBool{Bool: true, Valid: true}}, []interface{}{&actual.b}, "cannot encode into OID 1082"},
|
|
{"select $1::date", []interface{}{pgx.NullTime{Time: time.Unix(123, 5000), Valid: true}}, []interface{}{&actual.t}, "cannot encode into OID 1082"},
|
|
}
|
|
|
|
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 || !strings.Contains(err.Error(), tt.err) {
|
|
t.Errorf(`%d. Expected error to contain "%s", but it didn't: %v`, i, tt.err, err)
|
|
}
|
|
|
|
ensureConnValid(t, conn)
|
|
}
|
|
}
|