pgtype uses pgxtest

Added ValueRoundTripTest to pgxtest
Removed pgtype/testutil

pgtype tests now run with all (applicable) query modes. This gives
better coverage than before and revealed several bugs which are also
fixed in this commit.
This commit is contained in:
Jack Christensen 2022-04-02 14:34:19 -05:00
parent 83e50f21e8
commit ee93440ac1
54 changed files with 2028 additions and 1954 deletions

35
conn.go
View File

@ -558,18 +558,41 @@ func (c *Conn) execSQLParams(ctx context.Context, sql string, args []interface{}
// Given that the whole point of QueryExecModeExec is to operate without having to know the PostgreSQL types there is // Given that the whole point of QueryExecModeExec is to operate without having to know the PostgreSQL types there is
// no way to safely use binary or to specify the parameter OIDs. // no way to safely use binary or to specify the parameter OIDs.
func (c *Conn) appendParamsForQueryExecModeExec(args []interface{}) error { func (c *Conn) appendParamsForQueryExecModeExec(args []interface{}) error {
for i := range args { for _, arg := range args {
if args[i] == nil { if arg == nil {
err := c.eqb.AppendParamFormat(c.typeMap, 0, TextFormatCode, args[i]) err := c.eqb.AppendParamFormat(c.typeMap, 0, TextFormatCode, arg)
if err != nil { if err != nil {
return err return err
} }
} else { } else {
dt, ok := c.TypeMap().TypeForValue(args[i]) dt, ok := c.TypeMap().TypeForValue(arg)
if !ok { if !ok {
return &unknownArgumentTypeQueryExecModeExecError{arg: args[i]} var tv pgtype.TextValuer
if tv, ok = arg.(pgtype.TextValuer); ok {
t, err := tv.TextValue()
if err != nil {
return err
}
dt, ok = c.TypeMap().TypeForOID(pgtype.TextOID)
if ok {
arg = t
}
}
} }
err := c.eqb.AppendParamFormat(c.typeMap, dt.OID, TextFormatCode, args[i]) if !ok {
var str fmt.Stringer
if str, ok = arg.(fmt.Stringer); ok {
dt, ok = c.TypeMap().TypeForOID(pgtype.TextOID)
if ok {
arg = str.String()
}
}
}
if !ok {
return &unknownArgumentTypeQueryExecModeExecError{arg: arg}
}
err := c.eqb.AppendParamFormat(c.typeMap, dt.OID, TextFormatCode, arg)
if err != nil { if err != nil {
return err return err
} }

View File

@ -4,191 +4,184 @@ import (
"context" "context"
"testing" "testing"
"github.com/jackc/pgx/v5/pgtype/testutil" pgx "github.com/jackc/pgx/v5"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestArrayCodec(t *testing.T) { func TestArrayCodec(t *testing.T) {
conn := testutil.MustConnectPgx(t) defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
defer testutil.MustCloseContext(t, conn) for i, tt := range []struct {
expected interface{}
}{
{[]int16(nil)},
{[]int16{}},
{[]int16{1, 2, 3}},
} {
var actual []int16
err := conn.QueryRow(
ctx,
"select $1::smallint[]",
tt.expected,
).Scan(&actual)
assert.NoErrorf(t, err, "%d", i)
assert.Equalf(t, tt.expected, actual, "%d", i)
}
for i, tt := range []struct { newInt16 := func(n int16) *int16 { return &n }
expected interface{}
}{
{[]int16(nil)},
{[]int16{}},
{[]int16{1, 2, 3}},
} {
var actual []int16
err := conn.QueryRow(
context.Background(),
"select $1::smallint[]",
tt.expected,
).Scan(&actual)
assert.NoErrorf(t, err, "%d", i)
assert.Equalf(t, tt.expected, actual, "%d", i)
}
newInt16 := func(n int16) *int16 { return &n } for i, tt := range []struct {
expected interface{}
for i, tt := range []struct { }{
expected interface{} {[]*int16{newInt16(1), nil, newInt16(3), nil, newInt16(5)}},
}{ } {
{[]*int16{newInt16(1), nil, newInt16(3), nil, newInt16(5)}}, var actual []*int16
} { err := conn.QueryRow(
var actual []*int16 ctx,
err := conn.QueryRow( "select $1::smallint[]",
context.Background(), tt.expected,
"select $1::smallint[]", ).Scan(&actual)
tt.expected, assert.NoErrorf(t, err, "%d", i)
).Scan(&actual) assert.Equalf(t, tt.expected, actual, "%d", i)
assert.NoErrorf(t, err, "%d", i) }
assert.Equalf(t, tt.expected, actual, "%d", i) })
}
} }
func TestArrayCodecAnySlice(t *testing.T) { func TestArrayCodecAnySlice(t *testing.T) {
conn := testutil.MustConnectPgx(t) defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
defer testutil.MustCloseContext(t, conn) type _int16Slice []int16
type _int16Slice []int16 for i, tt := range []struct {
expected interface{}
for i, tt := range []struct { }{
expected interface{} {_int16Slice(nil)},
}{ {_int16Slice{}},
{_int16Slice(nil)}, {_int16Slice{1, 2, 3}},
{_int16Slice{}}, } {
{_int16Slice{1, 2, 3}}, var actual _int16Slice
} { err := conn.QueryRow(
var actual _int16Slice ctx,
err := conn.QueryRow( "select $1::smallint[]",
context.Background(), tt.expected,
"select $1::smallint[]", ).Scan(&actual)
tt.expected, assert.NoErrorf(t, err, "%d", i)
).Scan(&actual) assert.Equalf(t, tt.expected, actual, "%d", i)
assert.NoErrorf(t, err, "%d", i) }
assert.Equalf(t, tt.expected, actual, "%d", i) })
}
} }
func TestArrayCodecDecodeValue(t *testing.T) { func TestArrayCodecDecodeValue(t *testing.T) {
conn := testutil.MustConnectPgx(t) defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, _ testing.TB, conn *pgx.Conn) {
defer testutil.MustCloseContext(t, conn) for _, tt := range []struct {
sql string
for _, tt := range []struct { expected interface{}
sql string }{
expected interface{} {
}{ sql: `select '{}'::int4[]`,
{ expected: []interface{}{},
sql: `select '{}'::int4[]`, },
expected: []interface{}{}, {
}, sql: `select '{1,2}'::int8[]`,
{ expected: []interface{}{int64(1), int64(2)},
sql: `select '{1,2}'::int8[]`, },
expected: []interface{}{int64(1), int64(2)}, {
}, sql: `select '{foo,bar}'::text[]`,
{ expected: []interface{}{"foo", "bar"},
sql: `select '{foo,bar}'::text[]`, },
expected: []interface{}{"foo", "bar"}, } {
}, t.Run(tt.sql, func(t *testing.T) {
} { rows, err := conn.Query(ctx, tt.sql)
t.Run(tt.sql, func(t *testing.T) {
rows, err := conn.Query(context.Background(), tt.sql)
require.NoError(t, err)
for rows.Next() {
values, err := rows.Values()
require.NoError(t, err) require.NoError(t, err)
require.Len(t, values, 1)
require.Equal(t, tt.expected, values[0])
}
require.NoError(t, rows.Err()) for rows.Next() {
}) values, err := rows.Values()
} require.NoError(t, err)
require.Len(t, values, 1)
require.Equal(t, tt.expected, values[0])
}
require.NoError(t, rows.Err())
})
}
})
} }
func TestArrayCodecScanMultipleDimensions(t *testing.T) { func TestArrayCodecScanMultipleDimensions(t *testing.T) {
skipCockroachDB(t, "Server does not support nested arrays (https://github.com/cockroachdb/cockroach/issues/36815)") skipCockroachDB(t, "Server does not support nested arrays (https://github.com/cockroachdb/cockroach/issues/36815)")
conn := testutil.MustConnectPgx(t) defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
defer testutil.MustCloseContext(t, conn)
rows, err := conn.Query(context.Background(), `select '{{1,2,3,4}, {5,6,7,8}, {9,10,11,12}}'::int4[]`) rows, err := conn.Query(ctx, `select '{{1,2,3,4}, {5,6,7,8}, {9,10,11,12}}'::int4[]`)
require.NoError(t, err)
for rows.Next() {
var ss [][]int32
err := rows.Scan(&ss)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, [][]int32{{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}}, ss)
}
require.NoError(t, rows.Err()) for rows.Next() {
var ss [][]int32
err := rows.Scan(&ss)
require.NoError(t, err)
require.Equal(t, [][]int32{{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}}, ss)
}
require.NoError(t, rows.Err())
})
} }
func TestArrayCodecScanMultipleDimensionsEmpty(t *testing.T) { func TestArrayCodecScanMultipleDimensionsEmpty(t *testing.T) {
skipCockroachDB(t, "Server does not support nested arrays (https://github.com/cockroachdb/cockroach/issues/36815)") skipCockroachDB(t, "Server does not support nested arrays (https://github.com/cockroachdb/cockroach/issues/36815)")
conn := testutil.MustConnectPgx(t) defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
defer testutil.MustCloseContext(t, conn) rows, err := conn.Query(ctx, `select '{}'::int4[]`)
rows, err := conn.Query(context.Background(), `select '{}'::int4[]`)
require.NoError(t, err)
for rows.Next() {
var ss [][]int32
err := rows.Scan(&ss)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, [][]int32{}, ss)
}
require.NoError(t, rows.Err()) for rows.Next() {
var ss [][]int32
err := rows.Scan(&ss)
require.NoError(t, err)
require.Equal(t, [][]int32{}, ss)
}
require.NoError(t, rows.Err())
})
} }
func TestArrayCodecScanWrongMultipleDimensions(t *testing.T) { func TestArrayCodecScanWrongMultipleDimensions(t *testing.T) {
skipCockroachDB(t, "Server does not support nested arrays (https://github.com/cockroachdb/cockroach/issues/36815)") skipCockroachDB(t, "Server does not support nested arrays (https://github.com/cockroachdb/cockroach/issues/36815)")
conn := testutil.MustConnectPgx(t) defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
defer testutil.MustCloseContext(t, conn) rows, err := conn.Query(ctx, `select '{{1,2,3,4}, {5,6,7,8}, {9,10,11,12}}'::int4[]`)
require.NoError(t, err)
rows, err := conn.Query(context.Background(), `select '{{1,2,3,4}, {5,6,7,8}, {9,10,11,12}}'::int4[]`) for rows.Next() {
require.NoError(t, err) var ss [][][]int32
err := rows.Scan(&ss)
for rows.Next() { require.Error(t, err, "can't scan into dest[0]: PostgreSQL array has 2 dimensions but slice has 3 dimensions")
var ss [][][]int32 }
err := rows.Scan(&ss) })
require.Error(t, err, "can't scan into dest[0]: PostgreSQL array has 2 dimensions but slice has 3 dimensions")
}
} }
func TestArrayCodecEncodeMultipleDimensions(t *testing.T) { func TestArrayCodecEncodeMultipleDimensions(t *testing.T) {
skipCockroachDB(t, "Server does not support nested arrays (https://github.com/cockroachdb/cockroach/issues/36815)") skipCockroachDB(t, "Server does not support nested arrays (https://github.com/cockroachdb/cockroach/issues/36815)")
conn := testutil.MustConnectPgx(t) defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
defer testutil.MustCloseContext(t, conn) rows, err := conn.Query(ctx, `select $1::int4[]`, [][]int32{{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}})
rows, err := conn.Query(context.Background(), `select $1::int4[]`, [][]int32{{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}})
require.NoError(t, err)
for rows.Next() {
var ss [][]int32
err := rows.Scan(&ss)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, [][]int32{{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}}, ss)
}
require.NoError(t, rows.Err()) for rows.Next() {
var ss [][]int32
err := rows.Scan(&ss)
require.NoError(t, err)
require.Equal(t, [][]int32{{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}}, ss)
}
require.NoError(t, rows.Err())
})
} }
func TestArrayCodecEncodeMultipleDimensionsRagged(t *testing.T) { func TestArrayCodecEncodeMultipleDimensionsRagged(t *testing.T) {
skipCockroachDB(t, "Server does not support nested arrays (https://github.com/cockroachdb/cockroach/issues/36815)") skipCockroachDB(t, "Server does not support nested arrays (https://github.com/cockroachdb/cockroach/issues/36815)")
conn := testutil.MustConnectPgx(t) defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
defer testutil.MustCloseContext(t, conn) rows, err := conn.Query(ctx, `select $1::int4[]`, [][]int32{{1, 2, 3, 4}, {5}, {9, 10, 11, 12}})
require.Error(t, err, "cannot convert [][]int32 to ArrayGetter because it is a ragged multi-dimensional")
rows, err := conn.Query(context.Background(), `select $1::int4[]`, [][]int32{{1, 2, 3, 4}, {5}, {9, 10, 11, 12}}) defer rows.Close()
require.Error(t, err, "cannot convert [][]int32 to ArrayGetter because it is a ragged multi-dimensional") })
defer rows.Close()
} }

View File

@ -2,10 +2,11 @@ package pgtype_test
import ( import (
"bytes" "bytes"
"context"
"testing" "testing"
"github.com/jackc/pgx/v5/pgtype" "github.com/jackc/pgx/v5/pgtype"
"github.com/jackc/pgx/v5/pgtype/testutil" "github.com/jackc/pgx/v5/pgxtest"
) )
func isExpectedEqBits(a interface{}) func(interface{}) bool { func isExpectedEqBits(a interface{}) func(interface{}) bool {
@ -17,7 +18,7 @@ func isExpectedEqBits(a interface{}) func(interface{}) bool {
} }
func TestBitsCodecBit(t *testing.T) { func TestBitsCodecBit(t *testing.T) {
testutil.RunTranscodeTests(t, "bit(40)", []testutil.TranscodeTestCase{ pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "bit(40)", []pgxtest.ValueRoundTripTest{
{ {
pgtype.Bits{Bytes: []byte{0, 0, 0, 0, 0}, Len: 40, Valid: true}, pgtype.Bits{Bytes: []byte{0, 0, 0, 0, 0}, Len: 40, Valid: true},
new(pgtype.Bits), new(pgtype.Bits),
@ -34,7 +35,7 @@ func TestBitsCodecBit(t *testing.T) {
} }
func TestBitsCodecVarbit(t *testing.T) { func TestBitsCodecVarbit(t *testing.T) {
testutil.RunTranscodeTests(t, "varbit", []testutil.TranscodeTestCase{ pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "varbit", []pgxtest.ValueRoundTripTest{
{ {
pgtype.Bits{Bytes: []byte{}, Len: 0, Valid: true}, pgtype.Bits{Bytes: []byte{}, Len: 0, Valid: true},
new(pgtype.Bits), new(pgtype.Bits),

View File

@ -1,14 +1,15 @@
package pgtype_test package pgtype_test
import ( import (
"context"
"testing" "testing"
"github.com/jackc/pgx/v5/pgtype" "github.com/jackc/pgx/v5/pgtype"
"github.com/jackc/pgx/v5/pgtype/testutil" "github.com/jackc/pgx/v5/pgxtest"
) )
func TestBoolCodec(t *testing.T) { func TestBoolCodec(t *testing.T) {
testutil.RunTranscodeTests(t, "bool", []testutil.TranscodeTestCase{ pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "bool", []pgxtest.ValueRoundTripTest{
{true, new(bool), isExpectedEq(true)}, {true, new(bool), isExpectedEq(true)},
{false, new(bool), isExpectedEq(false)}, {false, new(bool), isExpectedEq(false)},
{true, new(pgtype.Bool), isExpectedEq(pgtype.Bool{Bool: true, Valid: true})}, {true, new(pgtype.Bool), isExpectedEq(pgtype.Bool{Bool: true, Valid: true})},

View File

@ -1,16 +1,17 @@
package pgtype_test package pgtype_test
import ( import (
"context"
"testing" "testing"
"github.com/jackc/pgx/v5/pgtype" "github.com/jackc/pgx/v5/pgtype"
"github.com/jackc/pgx/v5/pgtype/testutil" "github.com/jackc/pgx/v5/pgxtest"
) )
func TestBoxCodec(t *testing.T) { func TestBoxCodec(t *testing.T) {
skipCockroachDB(t, "Server does not support box type") skipCockroachDB(t, "Server does not support box type")
testutil.RunTranscodeTests(t, "box", []testutil.TranscodeTestCase{ pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "box", []pgxtest.ValueRoundTripTest{
{ {
pgtype.Box{ pgtype.Box{
P: [2]pgtype.Vec2{{7.1, 5.2345678}, {3.14, 1.678}}, P: [2]pgtype.Vec2{{7.1, 5.2345678}, {3.14, 1.678}},

View File

@ -5,8 +5,9 @@ import (
"context" "context"
"testing" "testing"
pgx "github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype" "github.com/jackc/pgx/v5/pgtype"
"github.com/jackc/pgx/v5/pgtype/testutil" "github.com/jackc/pgx/v5/pgxtest"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -28,7 +29,7 @@ func isExpectedEqBytes(a interface{}) func(interface{}) bool {
} }
func TestByteaCodec(t *testing.T) { func TestByteaCodec(t *testing.T) {
testutil.RunTranscodeTests(t, "bytea", []testutil.TranscodeTestCase{ pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "bytea", []pgxtest.ValueRoundTripTest{
{[]byte{1, 2, 3}, new([]byte), isExpectedEqBytes([]byte{1, 2, 3})}, {[]byte{1, 2, 3}, new([]byte), isExpectedEqBytes([]byte{1, 2, 3})},
{[]byte{}, new([]byte), isExpectedEqBytes([]byte{})}, {[]byte{}, new([]byte), isExpectedEqBytes([]byte{})},
{[]byte(nil), new([]byte), isExpectedEqBytes([]byte(nil))}, {[]byte(nil), new([]byte), isExpectedEqBytes([]byte(nil))},
@ -37,91 +38,79 @@ func TestByteaCodec(t *testing.T) {
} }
func TestDriverBytesQueryRow(t *testing.T) { func TestDriverBytesQueryRow(t *testing.T) {
conn := testutil.MustConnectPgx(t) defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
defer testutil.MustCloseContext(t, conn) var buf []byte
err := conn.QueryRow(ctx, `select $1::bytea`, []byte{1, 2}).Scan((*pgtype.DriverBytes)(&buf))
ctx := context.Background() require.EqualError(t, err, "cannot scan into *pgtype.DriverBytes from QueryRow")
})
var buf []byte
err := conn.QueryRow(ctx, `select $1::bytea`, []byte{1, 2}).Scan((*pgtype.DriverBytes)(&buf))
require.EqualError(t, err, "cannot scan into *pgtype.DriverBytes from QueryRow")
} }
func TestDriverBytes(t *testing.T) { func TestDriverBytes(t *testing.T) {
conn := testutil.MustConnectPgx(t) defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
defer testutil.MustCloseContext(t, conn) argBuf := make([]byte, 128)
for i := range argBuf {
ctx := context.Background() argBuf[i] = byte(i)
argBuf := make([]byte, 128)
for i := range argBuf {
argBuf[i] = byte(i)
}
rows, err := conn.Query(ctx, `select $1::bytea from generate_series(1, 1000)`, argBuf)
require.NoError(t, err)
defer rows.Close()
rowCount := 0
resultBuf := argBuf
detectedResultMutation := false
for rows.Next() {
rowCount++
// At some point the buffer should be reused and change.
if bytes.Compare(argBuf, resultBuf) != 0 {
detectedResultMutation = true
} }
err = rows.Scan((*pgtype.DriverBytes)(&resultBuf)) rows, err := conn.Query(ctx, `select $1::bytea from generate_series(1, 1000)`, argBuf)
require.NoError(t, err) require.NoError(t, err)
defer rows.Close()
require.Len(t, resultBuf, len(argBuf)) rowCount := 0
require.Equal(t, resultBuf, argBuf) resultBuf := argBuf
require.Equalf(t, cap(resultBuf), len(resultBuf), "cap(resultBuf) is larger than len(resultBuf)") detectedResultMutation := false
} for rows.Next() {
rowCount++
require.True(t, detectedResultMutation) // At some point the buffer should be reused and change.
if bytes.Compare(argBuf, resultBuf) != 0 {
detectedResultMutation = true
}
err = rows.Err() err = rows.Scan((*pgtype.DriverBytes)(&resultBuf))
require.NoError(t, err) require.NoError(t, err)
require.Len(t, resultBuf, len(argBuf))
require.Equal(t, resultBuf, argBuf)
require.Equalf(t, cap(resultBuf), len(resultBuf), "cap(resultBuf) is larger than len(resultBuf)")
}
require.True(t, detectedResultMutation)
err = rows.Err()
require.NoError(t, err)
})
} }
func TestPreallocBytes(t *testing.T) { func TestPreallocBytes(t *testing.T) {
conn := testutil.MustConnectPgx(t) defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
defer testutil.MustCloseContext(t, conn) origBuf := []byte{5, 6, 7, 8}
buf := origBuf
err := conn.QueryRow(ctx, `select $1::bytea`, []byte{1, 2}).Scan((*pgtype.PreallocBytes)(&buf))
require.NoError(t, err)
ctx := context.Background() require.Len(t, buf, 2)
require.Equal(t, 4, cap(buf))
require.Equal(t, buf, []byte{1, 2})
origBuf := []byte{5, 6, 7, 8} require.Equal(t, []byte{1, 2, 7, 8}, origBuf)
buf := origBuf
err := conn.QueryRow(ctx, `select $1::bytea`, []byte{1, 2}).Scan((*pgtype.PreallocBytes)(&buf))
require.NoError(t, err)
require.Len(t, buf, 2) err = conn.QueryRow(ctx, `select $1::bytea`, []byte{3, 4, 5, 6, 7}).Scan((*pgtype.PreallocBytes)(&buf))
require.Equal(t, 4, cap(buf)) require.NoError(t, err)
require.Equal(t, buf, []byte{1, 2}) require.Len(t, buf, 5)
require.Equal(t, 5, cap(buf))
require.Equal(t, []byte{1, 2, 7, 8}, origBuf) require.Equal(t, []byte{1, 2, 7, 8}, origBuf)
})
err = conn.QueryRow(ctx, `select $1::bytea`, []byte{3, 4, 5, 6, 7}).Scan((*pgtype.PreallocBytes)(&buf))
require.NoError(t, err)
require.Len(t, buf, 5)
require.Equal(t, 5, cap(buf))
require.Equal(t, []byte{1, 2, 7, 8}, origBuf)
} }
func TestUndecodedBytes(t *testing.T) { func TestUndecodedBytes(t *testing.T) {
conn := testutil.MustConnectPgx(t) defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
defer testutil.MustCloseContext(t, conn) var buf []byte
err := conn.QueryRow(ctx, `select 1::int4`).Scan((*pgtype.UndecodedBytes)(&buf))
require.NoError(t, err)
ctx := context.Background() require.Len(t, buf, 4)
require.Equal(t, buf, []byte{0, 0, 0, 1})
var buf []byte })
err := conn.QueryRow(ctx, `select 1::int4`).Scan((*pgtype.UndecodedBytes)(&buf))
require.NoError(t, err)
require.Len(t, buf, 4)
require.Equal(t, buf, []byte{0, 0, 0, 1})
} }

View File

@ -1,16 +1,17 @@
package pgtype_test package pgtype_test
import ( import (
"context"
"testing" "testing"
"github.com/jackc/pgx/v5/pgtype" "github.com/jackc/pgx/v5/pgtype"
"github.com/jackc/pgx/v5/pgtype/testutil" "github.com/jackc/pgx/v5/pgxtest"
) )
func TestCircleTranscode(t *testing.T) { func TestCircleTranscode(t *testing.T) {
skipCockroachDB(t, "Server does not support box type") skipCockroachDB(t, "Server does not support box type")
testutil.RunTranscodeTests(t, "circle", []testutil.TranscodeTestCase{ pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "circle", []pgxtest.ValueRoundTripTest{
{ {
pgtype.Circle{P: pgtype.Vec2{1.234, 5.67890123}, R: 3.5, Valid: true}, pgtype.Circle{P: pgtype.Vec2{1.234, 5.67890123}, R: 3.5, Valid: true},
new(pgtype.Circle), new(pgtype.Circle),

View File

@ -7,50 +7,49 @@ import (
pgx "github.com/jackc/pgx/v5" pgx "github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype" "github.com/jackc/pgx/v5/pgtype"
"github.com/jackc/pgx/v5/pgtype/testutil"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestCompositeCodecTranscode(t *testing.T) { func TestCompositeCodecTranscode(t *testing.T) {
skipCockroachDB(t, "Server does not support composite types (see https://github.com/cockroachdb/cockroach/issues/27792)") skipCockroachDB(t, "Server does not support composite types (see https://github.com/cockroachdb/cockroach/issues/27792)")
conn := testutil.MustConnectPgx(t) defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
defer testutil.MustCloseContext(t, conn)
_, err := conn.Exec(context.Background(), `drop type if exists ct_test; _, err := conn.Exec(ctx, `drop type if exists ct_test;
create type ct_test as ( create type ct_test as (
a text, a text,
b int4 b int4
);`) );`)
require.NoError(t, err) require.NoError(t, err)
defer conn.Exec(context.Background(), "drop type ct_test") defer conn.Exec(ctx, "drop type ct_test")
dt, err := conn.LoadType(context.Background(), "ct_test") dt, err := conn.LoadType(ctx, "ct_test")
require.NoError(t, err) require.NoError(t, err)
conn.TypeMap().RegisterType(dt) conn.TypeMap().RegisterType(dt)
formats := []struct { formats := []struct {
name string name string
code int16 code int16
}{ }{
{name: "TextFormat", code: pgx.TextFormatCode}, {name: "TextFormat", code: pgx.TextFormatCode},
{name: "BinaryFormat", code: pgx.BinaryFormatCode}, {name: "BinaryFormat", code: pgx.BinaryFormatCode},
} }
for _, format := range formats { for _, format := range formats {
var a string var a string
var b int32 var b int32
err := conn.QueryRow(context.Background(), "select $1::ct_test", pgx.QueryResultFormats{format.code}, err := conn.QueryRow(ctx, "select $1::ct_test", pgx.QueryResultFormats{format.code},
pgtype.CompositeFields{"hi", int32(42)}, pgtype.CompositeFields{"hi", int32(42)},
).Scan( ).Scan(
pgtype.CompositeFields{&a, &b}, pgtype.CompositeFields{&a, &b},
) )
require.NoErrorf(t, err, "%v", format.name) require.NoErrorf(t, err, "%v", format.name)
require.EqualValuesf(t, "hi", a, "%v", format.name) require.EqualValuesf(t, "hi", a, "%v", format.name)
require.EqualValuesf(t, 42, b, "%v", format.name) require.EqualValuesf(t, 42, b, "%v", format.name)
} }
})
} }
type point3d struct { type point3d struct {
@ -94,118 +93,118 @@ func (p *point3d) ScanIndex(i int) interface{} {
func TestCompositeCodecTranscodeStruct(t *testing.T) { func TestCompositeCodecTranscodeStruct(t *testing.T) {
skipCockroachDB(t, "Server does not support composite types (see https://github.com/cockroachdb/cockroach/issues/27792)") skipCockroachDB(t, "Server does not support composite types (see https://github.com/cockroachdb/cockroach/issues/27792)")
conn := testutil.MustConnectPgx(t) defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
defer testutil.MustCloseContext(t, conn)
_, err := conn.Exec(context.Background(), `drop type if exists point3d; _, err := conn.Exec(ctx, `drop type if exists point3d;
create type point3d as ( create type point3d as (
x float8, x float8,
y float8, y float8,
z float8 z float8
);`) );`)
require.NoError(t, err) require.NoError(t, err)
defer conn.Exec(context.Background(), "drop type point3d") defer conn.Exec(ctx, "drop type point3d")
dt, err := conn.LoadType(context.Background(), "point3d") dt, err := conn.LoadType(ctx, "point3d")
require.NoError(t, err) require.NoError(t, err)
conn.TypeMap().RegisterType(dt) conn.TypeMap().RegisterType(dt)
formats := []struct { formats := []struct {
name string name string
code int16 code int16
}{ }{
{name: "TextFormat", code: pgx.TextFormatCode}, {name: "TextFormat", code: pgx.TextFormatCode},
{name: "BinaryFormat", code: pgx.BinaryFormatCode}, {name: "BinaryFormat", code: pgx.BinaryFormatCode},
} }
for _, format := range formats { for _, format := range formats {
input := point3d{X: 1, Y: 2, Z: 3} input := point3d{X: 1, Y: 2, Z: 3}
var output point3d var output point3d
err := conn.QueryRow(context.Background(), "select $1::point3d", pgx.QueryResultFormats{format.code}, input).Scan(&output) err := conn.QueryRow(ctx, "select $1::point3d", pgx.QueryResultFormats{format.code}, input).Scan(&output)
require.NoErrorf(t, err, "%v", format.name) require.NoErrorf(t, err, "%v", format.name)
require.Equalf(t, input, output, "%v", format.name) require.Equalf(t, input, output, "%v", format.name)
} }
})
} }
func TestCompositeCodecTranscodeStructWrapper(t *testing.T) { func TestCompositeCodecTranscodeStructWrapper(t *testing.T) {
skipCockroachDB(t, "Server does not support composite types (see https://github.com/cockroachdb/cockroach/issues/27792)") skipCockroachDB(t, "Server does not support composite types (see https://github.com/cockroachdb/cockroach/issues/27792)")
conn := testutil.MustConnectPgx(t) defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
defer testutil.MustCloseContext(t, conn)
_, err := conn.Exec(context.Background(), `drop type if exists point3d; _, err := conn.Exec(ctx, `drop type if exists point3d;
create type point3d as ( create type point3d as (
x float8, x float8,
y float8, y float8,
z float8 z float8
);`) );`)
require.NoError(t, err) require.NoError(t, err)
defer conn.Exec(context.Background(), "drop type point3d") defer conn.Exec(ctx, "drop type point3d")
dt, err := conn.LoadType(context.Background(), "point3d") dt, err := conn.LoadType(ctx, "point3d")
require.NoError(t, err) require.NoError(t, err)
conn.TypeMap().RegisterType(dt) conn.TypeMap().RegisterType(dt)
formats := []struct { formats := []struct {
name string name string
code int16 code int16
}{ }{
{name: "TextFormat", code: pgx.TextFormatCode}, {name: "TextFormat", code: pgx.TextFormatCode},
{name: "BinaryFormat", code: pgx.BinaryFormatCode}, {name: "BinaryFormat", code: pgx.BinaryFormatCode},
} }
type anotherPoint struct { type anotherPoint struct {
X, Y, Z float64 X, Y, Z float64
} }
for _, format := range formats { for _, format := range formats {
input := anotherPoint{X: 1, Y: 2, Z: 3} input := anotherPoint{X: 1, Y: 2, Z: 3}
var output anotherPoint var output anotherPoint
err := conn.QueryRow(context.Background(), "select $1::point3d", pgx.QueryResultFormats{format.code}, input).Scan(&output) err := conn.QueryRow(ctx, "select $1::point3d", pgx.QueryResultFormats{format.code}, input).Scan(&output)
require.NoErrorf(t, err, "%v", format.name) require.NoErrorf(t, err, "%v", format.name)
require.Equalf(t, input, output, "%v", format.name) require.Equalf(t, input, output, "%v", format.name)
} }
})
} }
func TestCompositeCodecDecodeValue(t *testing.T) { func TestCompositeCodecDecodeValue(t *testing.T) {
skipCockroachDB(t, "Server does not support composite types (see https://github.com/cockroachdb/cockroach/issues/27792)") skipCockroachDB(t, "Server does not support composite types (see https://github.com/cockroachdb/cockroach/issues/27792)")
conn := testutil.MustConnectPgx(t) defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
defer testutil.MustCloseContext(t, conn)
_, err := conn.Exec(context.Background(), `drop type if exists point3d; _, err := conn.Exec(ctx, `drop type if exists point3d;
create type point3d as ( create type point3d as (
x float8, x float8,
y float8, y float8,
z float8 z float8
);`) );`)
require.NoError(t, err) require.NoError(t, err)
defer conn.Exec(context.Background(), "drop type point3d") defer conn.Exec(ctx, "drop type point3d")
dt, err := conn.LoadType(context.Background(), "point3d") dt, err := conn.LoadType(ctx, "point3d")
require.NoError(t, err) require.NoError(t, err)
conn.TypeMap().RegisterType(dt) conn.TypeMap().RegisterType(dt)
formats := []struct { formats := []struct {
name string name string
code int16 code int16
}{ }{
{name: "TextFormat", code: pgx.TextFormatCode}, {name: "TextFormat", code: pgx.TextFormatCode},
{name: "BinaryFormat", code: pgx.BinaryFormatCode}, {name: "BinaryFormat", code: pgx.BinaryFormatCode},
} }
for _, format := range formats { for _, format := range formats {
rows, err := conn.Query(context.Background(), "select '(1,2,3)'::point3d", pgx.QueryResultFormats{format.code}) rows, err := conn.Query(ctx, "select '(1,2,3)'::point3d", pgx.QueryResultFormats{format.code})
require.NoErrorf(t, err, "%v", format.name) require.NoErrorf(t, err, "%v", format.name)
require.True(t, rows.Next()) require.True(t, rows.Next())
values, err := rows.Values() values, err := rows.Values()
require.NoErrorf(t, err, "%v", format.name) require.NoErrorf(t, err, "%v", format.name)
require.Lenf(t, values, 1, "%v", format.name) require.Lenf(t, values, 1, "%v", format.name)
require.Equalf(t, map[string]interface{}{"x": 1.0, "y": 2.0, "z": 3.0}, values[0], "%v", format.name) require.Equalf(t, map[string]interface{}{"x": 1.0, "y": 2.0, "z": 3.0}, values[0], "%v", format.name)
require.False(t, rows.Next()) require.False(t, rows.Next())
require.NoErrorf(t, rows.Err(), "%v", format.name) require.NoErrorf(t, rows.Err(), "%v", format.name)
} }
})
} }

View File

@ -1,11 +1,12 @@
package pgtype_test package pgtype_test
import ( import (
"context"
"testing" "testing"
"time" "time"
"github.com/jackc/pgx/v5/pgtype" "github.com/jackc/pgx/v5/pgtype"
"github.com/jackc/pgx/v5/pgtype/testutil" "github.com/jackc/pgx/v5/pgxtest"
) )
func isExpectedEqTime(a interface{}) func(interface{}) bool { func isExpectedEqTime(a interface{}) func(interface{}) bool {
@ -18,7 +19,7 @@ func isExpectedEqTime(a interface{}) func(interface{}) bool {
} }
func TestDateCodec(t *testing.T) { func TestDateCodec(t *testing.T) {
testutil.RunTranscodeTests(t, "date", []testutil.TranscodeTestCase{ pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "date", []pgxtest.ValueRoundTripTest{
{time.Date(-100, 1, 1, 0, 0, 0, 0, time.UTC), new(time.Time), isExpectedEqTime(time.Date(-100, 1, 1, 0, 0, 0, 0, time.UTC))}, {time.Date(-100, 1, 1, 0, 0, 0, 0, time.UTC), new(time.Time), isExpectedEqTime(time.Date(-100, 1, 1, 0, 0, 0, 0, time.UTC))},
{time.Date(-1, 1, 1, 0, 0, 0, 0, time.UTC), new(time.Time), isExpectedEqTime(time.Date(-1, 1, 1, 0, 0, 0, 0, time.UTC))}, {time.Date(-1, 1, 1, 0, 0, 0, 0, time.UTC), new(time.Time), isExpectedEqTime(time.Date(-1, 1, 1, 0, 0, 0, 0, time.UTC))},
{time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), new(time.Time), isExpectedEqTime(time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC))}, {time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), new(time.Time), isExpectedEqTime(time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC))},

View File

@ -4,66 +4,66 @@ import (
"context" "context"
"testing" "testing"
"github.com/jackc/pgx/v5/pgtype/testutil" pgx "github.com/jackc/pgx/v5"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestEnumCodec(t *testing.T) { func TestEnumCodec(t *testing.T) {
conn := testutil.MustConnectPgx(t) defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
defer testutil.MustCloseContext(t, conn)
_, err := conn.Exec(context.Background(), `drop type if exists enum_test; _, err := conn.Exec(ctx, `drop type if exists enum_test;
create type enum_test as enum ('foo', 'bar', 'baz');`) create type enum_test as enum ('foo', 'bar', 'baz');`)
require.NoError(t, err) require.NoError(t, err)
defer conn.Exec(context.Background(), "drop type enum_test") defer conn.Exec(ctx, "drop type enum_test")
dt, err := conn.LoadType(context.Background(), "enum_test") dt, err := conn.LoadType(ctx, "enum_test")
require.NoError(t, err) require.NoError(t, err)
conn.TypeMap().RegisterType(dt) conn.TypeMap().RegisterType(dt)
var s string var s string
err = conn.QueryRow(context.Background(), `select 'foo'::enum_test`).Scan(&s) err = conn.QueryRow(ctx, `select 'foo'::enum_test`).Scan(&s)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, "foo", s) require.Equal(t, "foo", s)
err = conn.QueryRow(context.Background(), `select $1::enum_test`, "bar").Scan(&s) err = conn.QueryRow(ctx, `select $1::enum_test`, "bar").Scan(&s)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, "bar", s) require.Equal(t, "bar", s)
err = conn.QueryRow(context.Background(), `select 'foo'::enum_test`).Scan(&s) err = conn.QueryRow(ctx, `select 'foo'::enum_test`).Scan(&s)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, "foo", s) require.Equal(t, "foo", s)
err = conn.QueryRow(context.Background(), `select $1::enum_test`, "bar").Scan(&s) err = conn.QueryRow(ctx, `select $1::enum_test`, "bar").Scan(&s)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, "bar", s) require.Equal(t, "bar", s)
err = conn.QueryRow(context.Background(), `select 'baz'::enum_test`).Scan(&s) err = conn.QueryRow(ctx, `select 'baz'::enum_test`).Scan(&s)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, "baz", s) require.Equal(t, "baz", s)
})
} }
func TestEnumCodecValues(t *testing.T) { func TestEnumCodecValues(t *testing.T) {
conn := testutil.MustConnectPgx(t) defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
defer testutil.MustCloseContext(t, conn)
_, err := conn.Exec(context.Background(), `drop type if exists enum_test; _, err := conn.Exec(ctx, `drop type if exists enum_test;
create type enum_test as enum ('foo', 'bar', 'baz');`) create type enum_test as enum ('foo', 'bar', 'baz');`)
require.NoError(t, err) require.NoError(t, err)
defer conn.Exec(context.Background(), "drop type enum_test") defer conn.Exec(ctx, "drop type enum_test")
dt, err := conn.LoadType(context.Background(), "enum_test") dt, err := conn.LoadType(ctx, "enum_test")
require.NoError(t, err) require.NoError(t, err)
conn.TypeMap().RegisterType(dt) conn.TypeMap().RegisterType(dt)
rows, err := conn.Query(context.Background(), `select 'foo'::enum_test`) rows, err := conn.Query(ctx, `select 'foo'::enum_test`)
require.NoError(t, err) require.NoError(t, err)
require.True(t, rows.Next()) require.True(t, rows.Next())
values, err := rows.Values() values, err := rows.Values()
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, values, []interface{}{"foo"}) require.Equal(t, values, []interface{}{"foo"})
})
} }

View File

@ -1,14 +1,15 @@
package pgtype_test package pgtype_test
import ( import (
"context"
"testing" "testing"
"github.com/jackc/pgx/v5/pgtype" "github.com/jackc/pgx/v5/pgtype"
"github.com/jackc/pgx/v5/pgtype/testutil" "github.com/jackc/pgx/v5/pgxtest"
) )
func TestFloat4Codec(t *testing.T) { func TestFloat4Codec(t *testing.T) {
testutil.RunTranscodeTests(t, "float4", []testutil.TranscodeTestCase{ pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "float4", []pgxtest.ValueRoundTripTest{
{pgtype.Float4{Float32: -1, Valid: true}, new(pgtype.Float4), isExpectedEq(pgtype.Float4{Float32: -1, Valid: true})}, {pgtype.Float4{Float32: -1, Valid: true}, new(pgtype.Float4), isExpectedEq(pgtype.Float4{Float32: -1, Valid: true})},
{pgtype.Float4{Float32: 0, Valid: true}, new(pgtype.Float4), isExpectedEq(pgtype.Float4{Float32: 0, Valid: true})}, {pgtype.Float4{Float32: 0, Valid: true}, new(pgtype.Float4), isExpectedEq(pgtype.Float4{Float32: 0, Valid: true})},
{pgtype.Float4{Float32: 1, Valid: true}, new(pgtype.Float4), isExpectedEq(pgtype.Float4{Float32: 1, Valid: true})}, {pgtype.Float4{Float32: 1, Valid: true}, new(pgtype.Float4), isExpectedEq(pgtype.Float4{Float32: 1, Valid: true})},

View File

@ -1,14 +1,15 @@
package pgtype_test package pgtype_test
import ( import (
"context"
"testing" "testing"
"github.com/jackc/pgx/v5/pgtype" "github.com/jackc/pgx/v5/pgtype"
"github.com/jackc/pgx/v5/pgtype/testutil" "github.com/jackc/pgx/v5/pgxtest"
) )
func TestFloat8Codec(t *testing.T) { func TestFloat8Codec(t *testing.T) {
testutil.RunTranscodeTests(t, "float8", []testutil.TranscodeTestCase{ pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "float8", []pgxtest.ValueRoundTripTest{
{pgtype.Float8{Float64: -1, Valid: true}, new(pgtype.Float8), isExpectedEq(pgtype.Float8{Float64: -1, Valid: true})}, {pgtype.Float8{Float64: -1, Valid: true}, new(pgtype.Float8), isExpectedEq(pgtype.Float8{Float64: -1, Valid: true})},
{pgtype.Float8{Float64: 0, Valid: true}, new(pgtype.Float8), isExpectedEq(pgtype.Float8{Float64: 0, Valid: true})}, {pgtype.Float8{Float64: 0, Valid: true}, new(pgtype.Float8), isExpectedEq(pgtype.Float8{Float64: 0, Valid: true})},
{pgtype.Float8{Float64: 1, Valid: true}, new(pgtype.Float8), isExpectedEq(pgtype.Float8{Float64: 1, Valid: true})}, {pgtype.Float8{Float64: 1, Valid: true}, new(pgtype.Float8), isExpectedEq(pgtype.Float8{Float64: 1, Valid: true})},

View File

@ -6,7 +6,7 @@ import (
"github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype" "github.com/jackc/pgx/v5/pgtype"
"github.com/jackc/pgx/v5/pgtype/testutil" "github.com/jackc/pgx/v5/pgxtest"
) )
func isExpectedEqMapStringString(a interface{}) func(interface{}) bool { func isExpectedEqMapStringString(a interface{}) func(interface{}) bool {
@ -52,30 +52,22 @@ func isExpectedEqMapStringPointerString(a interface{}) func(interface{}) bool {
} }
func TestHstoreCodec(t *testing.T) { func TestHstoreCodec(t *testing.T) {
conn := testutil.MustConnectPgx(t) ctr := defaultConnTestRunner
defer testutil.MustCloseContext(t, conn) ctr.AfterConnect = func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
var hstoreOID uint32
err := conn.QueryRow(context.Background(), `select oid from pg_type where typname = 'hstore'`).Scan(&hstoreOID)
if err != nil {
t.Skipf("Skipping: cannot find hstore OID")
}
var hstoreOID uint32 conn.TypeMap().RegisterType(&pgtype.Type{Name: "hstore", OID: hstoreOID, Codec: pgtype.HstoreCodec{}})
err := conn.QueryRow(context.Background(), `select oid from pg_type where typname = 'hstore'`).Scan(&hstoreOID)
if err != nil {
t.Skipf("Skipping: cannot find hstore OID")
}
conn.TypeMap().RegisterType(&pgtype.Type{Name: "hstore", OID: hstoreOID, Codec: pgtype.HstoreCodec{}})
formats := []struct {
name string
code int16
}{
{name: "TextFormat", code: pgx.TextFormatCode},
{name: "BinaryFormat", code: pgx.BinaryFormatCode},
} }
fs := func(s string) *string { fs := func(s string) *string {
return &s return &s
} }
tests := []testutil.TranscodeTestCase{ tests := []pgxtest.ValueRoundTripTest{
{ {
map[string]string{}, map[string]string{},
new(map[string]string), new(map[string]string),
@ -134,25 +126,25 @@ func TestHstoreCodec(t *testing.T) {
// Special key values // Special key values
// at beginning // at beginning
tests = append(tests, testutil.TranscodeTestCase{ tests = append(tests, pgxtest.ValueRoundTripTest{
map[string]string{s + "foo": "bar"}, map[string]string{s + "foo": "bar"},
new(map[string]string), new(map[string]string),
isExpectedEqMapStringString(map[string]string{s + "foo": "bar"}), isExpectedEqMapStringString(map[string]string{s + "foo": "bar"}),
}) })
// in middle // in middle
tests = append(tests, testutil.TranscodeTestCase{ tests = append(tests, pgxtest.ValueRoundTripTest{
map[string]string{"foo" + s + "bar": "bar"}, map[string]string{"foo" + s + "bar": "bar"},
new(map[string]string), new(map[string]string),
isExpectedEqMapStringString(map[string]string{"foo" + s + "bar": "bar"}), isExpectedEqMapStringString(map[string]string{"foo" + s + "bar": "bar"}),
}) })
// at end // at end
tests = append(tests, testutil.TranscodeTestCase{ tests = append(tests, pgxtest.ValueRoundTripTest{
map[string]string{"foo" + s: "bar"}, map[string]string{"foo" + s: "bar"},
new(map[string]string), new(map[string]string),
isExpectedEqMapStringString(map[string]string{"foo" + s: "bar"}), isExpectedEqMapStringString(map[string]string{"foo" + s: "bar"}),
}) })
// is key // is key
tests = append(tests, testutil.TranscodeTestCase{ tests = append(tests, pgxtest.ValueRoundTripTest{
map[string]string{s: "bar"}, map[string]string{s: "bar"},
new(map[string]string), new(map[string]string),
isExpectedEqMapStringString(map[string]string{s: "bar"}), isExpectedEqMapStringString(map[string]string{s: "bar"}),
@ -161,32 +153,30 @@ func TestHstoreCodec(t *testing.T) {
// Special value values // Special value values
// at beginning // at beginning
tests = append(tests, testutil.TranscodeTestCase{ tests = append(tests, pgxtest.ValueRoundTripTest{
map[string]string{"foo": s + "bar"}, map[string]string{"foo": s + "bar"},
new(map[string]string), new(map[string]string),
isExpectedEqMapStringString(map[string]string{"foo": s + "bar"}), isExpectedEqMapStringString(map[string]string{"foo": s + "bar"}),
}) })
// in middle // in middle
tests = append(tests, testutil.TranscodeTestCase{ tests = append(tests, pgxtest.ValueRoundTripTest{
map[string]string{"foo": "foo" + s + "bar"}, map[string]string{"foo": "foo" + s + "bar"},
new(map[string]string), new(map[string]string),
isExpectedEqMapStringString(map[string]string{"foo": "foo" + s + "bar"}), isExpectedEqMapStringString(map[string]string{"foo": "foo" + s + "bar"}),
}) })
// at end // at end
tests = append(tests, testutil.TranscodeTestCase{ tests = append(tests, pgxtest.ValueRoundTripTest{
map[string]string{"foo": "foo" + s}, map[string]string{"foo": "foo" + s},
new(map[string]string), new(map[string]string),
isExpectedEqMapStringString(map[string]string{"foo": "foo" + s}), isExpectedEqMapStringString(map[string]string{"foo": "foo" + s}),
}) })
// is key // is key
tests = append(tests, testutil.TranscodeTestCase{ tests = append(tests, pgxtest.ValueRoundTripTest{
map[string]string{"foo": s}, map[string]string{"foo": s},
new(map[string]string), new(map[string]string),
isExpectedEqMapStringString(map[string]string{"foo": s}), isExpectedEqMapStringString(map[string]string{"foo": s}),
}) })
} }
for _, format := range formats { pgxtest.RunValueRoundTripTests(context.Background(), t, ctr, pgxtest.KnownOIDQueryExecModes, "hstore", tests)
testutil.RunTranscodeTestsFormat(t, "hstore", tests, conn, format.name, format.code)
}
} }

View File

@ -1,11 +1,12 @@
package pgtype_test package pgtype_test
import ( import (
"context"
"net" "net"
"testing" "testing"
"github.com/jackc/pgx/v5/pgtype" "github.com/jackc/pgx/v5/pgtype"
"github.com/jackc/pgx/v5/pgtype/testutil" "github.com/jackc/pgx/v5/pgxtest"
) )
func isExpectedEqIPNet(a interface{}) func(interface{}) bool { func isExpectedEqIPNet(a interface{}) func(interface{}) bool {
@ -18,7 +19,7 @@ func isExpectedEqIPNet(a interface{}) func(interface{}) bool {
} }
func TestInetTranscode(t *testing.T) { func TestInetTranscode(t *testing.T) {
testutil.RunTranscodeTests(t, "inet", []testutil.TranscodeTestCase{ pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "inet", []pgxtest.ValueRoundTripTest{
{mustParseInet(t, "0.0.0.0/32"), new(net.IPNet), isExpectedEqIPNet(mustParseInet(t, "0.0.0.0/32"))}, {mustParseInet(t, "0.0.0.0/32"), new(net.IPNet), isExpectedEqIPNet(mustParseInet(t, "0.0.0.0/32"))},
{mustParseInet(t, "127.0.0.1/8"), new(net.IPNet), isExpectedEqIPNet(mustParseInet(t, "127.0.0.1/8"))}, {mustParseInet(t, "127.0.0.1/8"), new(net.IPNet), isExpectedEqIPNet(mustParseInet(t, "127.0.0.1/8"))},
{mustParseInet(t, "12.34.56.65/32"), new(net.IPNet), isExpectedEqIPNet(mustParseInet(t, "12.34.56.65/32"))}, {mustParseInet(t, "12.34.56.65/32"), new(net.IPNet), isExpectedEqIPNet(mustParseInet(t, "12.34.56.65/32"))},
@ -37,7 +38,7 @@ func TestInetTranscode(t *testing.T) {
func TestCidrTranscode(t *testing.T) { func TestCidrTranscode(t *testing.T) {
skipCockroachDB(t, "Server does not support cidr type (see https://github.com/cockroachdb/cockroach/issues/18846)") skipCockroachDB(t, "Server does not support cidr type (see https://github.com/cockroachdb/cockroach/issues/18846)")
testutil.RunTranscodeTests(t, "cidr", []testutil.TranscodeTestCase{ pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "cidr", []pgxtest.ValueRoundTripTest{
{mustParseInet(t, "0.0.0.0/32"), new(net.IPNet), isExpectedEqIPNet(mustParseInet(t, "0.0.0.0/32"))}, {mustParseInet(t, "0.0.0.0/32"), new(net.IPNet), isExpectedEqIPNet(mustParseInet(t, "0.0.0.0/32"))},
{mustParseInet(t, "127.0.0.1/32"), new(net.IPNet), isExpectedEqIPNet(mustParseInet(t, "127.0.0.1/32"))}, {mustParseInet(t, "127.0.0.1/32"), new(net.IPNet), isExpectedEqIPNet(mustParseInet(t, "127.0.0.1/32"))},
{mustParseInet(t, "12.34.56.0/32"), new(net.IPNet), isExpectedEqIPNet(mustParseInet(t, "12.34.56.0/32"))}, {mustParseInet(t, "12.34.56.0/32"), new(net.IPNet), isExpectedEqIPNet(mustParseInet(t, "12.34.56.0/32"))},

View File

@ -2,15 +2,16 @@
package pgtype_test package pgtype_test
import ( import (
"context"
"math" "math"
"testing" "testing"
"github.com/jackc/pgx/v5/pgtype" "github.com/jackc/pgx/v5/pgtype"
"github.com/jackc/pgx/v5/pgtype/testutil" "github.com/jackc/pgx/v5/pgxtest"
) )
func TestInt2Codec(t *testing.T) { func TestInt2Codec(t *testing.T) {
testutil.RunTranscodeTests(t, "int2", []testutil.TranscodeTestCase{ pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "int2", []pgxtest.ValueRoundTripTest{
{int8(1), new(int16), isExpectedEq(int16(1))}, {int8(1), new(int16), isExpectedEq(int16(1))},
{int16(1), new(int16), isExpectedEq(int16(1))}, {int16(1), new(int16), isExpectedEq(int16(1))},
{int32(1), new(int16), isExpectedEq(int16(1))}, {int32(1), new(int16), isExpectedEq(int16(1))},
@ -91,7 +92,7 @@ func TestInt2UnmarshalJSON(t *testing.T) {
} }
func TestInt4Codec(t *testing.T) { func TestInt4Codec(t *testing.T) {
testutil.RunTranscodeTests(t, "int4", []testutil.TranscodeTestCase{ pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "int4", []pgxtest.ValueRoundTripTest{
{int8(1), new(int32), isExpectedEq(int32(1))}, {int8(1), new(int32), isExpectedEq(int32(1))},
{int16(1), new(int32), isExpectedEq(int32(1))}, {int16(1), new(int32), isExpectedEq(int32(1))},
{int32(1), new(int32), isExpectedEq(int32(1))}, {int32(1), new(int32), isExpectedEq(int32(1))},
@ -172,7 +173,7 @@ func TestInt4UnmarshalJSON(t *testing.T) {
} }
func TestInt8Codec(t *testing.T) { func TestInt8Codec(t *testing.T) {
testutil.RunTranscodeTests(t, "int8", []testutil.TranscodeTestCase{ pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "int8", []pgxtest.ValueRoundTripTest{
{int8(1), new(int64), isExpectedEq(int64(1))}, {int8(1), new(int64), isExpectedEq(int64(1))},
{int16(1), new(int64), isExpectedEq(int64(1))}, {int16(1), new(int64), isExpectedEq(int64(1))},
{int32(1), new(int64), isExpectedEq(int64(1))}, {int32(1), new(int64), isExpectedEq(int64(1))},

View File

@ -10,7 +10,7 @@ import (
<% [2, 4, 8].each do |pg_byte_size| %> <% [2, 4, 8].each do |pg_byte_size| %>
<% pg_bit_size = pg_byte_size * 8 %> <% pg_bit_size = pg_byte_size * 8 %>
func TestInt<%= pg_byte_size %>Codec(t *testing.T) { func TestInt<%= pg_byte_size %>Codec(t *testing.T) {
testutil.RunTranscodeTests(t, "int<%= pg_byte_size %>", []testutil.TranscodeTestCase{ pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "int<%= pg_byte_size %>", []pgxtest.ValueRoundTripTest{
{int8(1), new(int<%= pg_bit_size %>), isExpectedEq(int<%= pg_bit_size %>(1))}, {int8(1), new(int<%= pg_bit_size %>), isExpectedEq(int<%= pg_bit_size %>(1))},
{int16(1), new(int<%= pg_bit_size %>), isExpectedEq(int<%= pg_bit_size %>(1))}, {int16(1), new(int<%= pg_bit_size %>), isExpectedEq(int<%= pg_bit_size %>(1))},
{int32(1), new(int<%= pg_bit_size %>), isExpectedEq(int<%= pg_bit_size %>(1))}, {int32(1), new(int<%= pg_bit_size %>), isExpectedEq(int<%= pg_bit_size %>(1))},

File diff suppressed because it is too large Load Diff

View File

@ -18,23 +18,22 @@ import (
<% rows_columns.each do |rows, columns| %> <% rows_columns.each do |rows, columns| %>
<% [["Text", "pgx.TextFormatCode"], ["Binary", "pgx.BinaryFormatCode"]].each do |format_name, format_code| %> <% [["Text", "pgx.TextFormatCode"], ["Binary", "pgx.BinaryFormatCode"]].each do |format_name, format_code| %>
func BenchmarkQuery<%= format_name %>FormatDecode_PG_<%= pg_type %>_to_Go_<%= go_type.gsub(/\W/, "_") %>_<%= rows %>_rows_<%= columns %>_columns(b *testing.B) { func BenchmarkQuery<%= format_name %>FormatDecode_PG_<%= pg_type %>_to_Go_<%= go_type.gsub(/\W/, "_") %>_<%= rows %>_rows_<%= columns %>_columns(b *testing.B) {
conn := testutil.MustConnectPgx(b) defaultConnTestRunner.RunTest(context.Background(), b, func(ctx context.Context, _ testing.TB, conn *pgx.Conn) {
defer testutil.MustCloseContext(b, conn) b.ResetTimer()
var v [<%= columns %>]<%= go_type %>
b.ResetTimer() for i := 0; i < b.N; i++ {
var v [<%= columns %>]<%= go_type %> _, err := conn.QueryFunc(
for i := 0; i < b.N; i++ { ctx,
_, err := conn.QueryFunc( `select <% columns.times do |col_idx| %><% if col_idx != 0 %>, <% end %>n::<%= pg_type %> + <%= col_idx%><% end %> from generate_series(1, <%= rows %>) n`,
context.Background(), []interface{}{pgx.QueryResultFormats{<%= format_code %>}},
`select <% columns.times do |col_idx| %><% if col_idx != 0 %>, <% end %>n::<%= pg_type %> + <%= col_idx%><% end %> from generate_series(1, <%= rows %>) n`, []interface{}{<% columns.times do |col_idx| %><% if col_idx != 0 %>, <% end %>&v[<%= col_idx%>]<% end %>},
[]interface{}{pgx.QueryResultFormats{<%= format_code %>}}, func(pgx.QueryFuncRow) error { return nil },
[]interface{}{<% columns.times do |col_idx| %><% if col_idx != 0 %>, <% end %>&v[<%= col_idx%>]<% end %>}, )
func(pgx.QueryFuncRow) error { return nil }, if err != nil {
) b.Fatal(err)
if err != nil { }
b.Fatal(err) }
} })
}
} }
<% end %> <% end %>
<% end %> <% end %>
@ -44,23 +43,22 @@ func BenchmarkQuery<%= format_name %>FormatDecode_PG_<%= pg_type %>_to_Go_<%= go
<% [10, 100, 1000].each do |array_size| %> <% [10, 100, 1000].each do |array_size| %>
<% [["Text", "pgx.TextFormatCode"], ["Binary", "pgx.BinaryFormatCode"]].each do |format_name, format_code| %> <% [["Text", "pgx.TextFormatCode"], ["Binary", "pgx.BinaryFormatCode"]].each do |format_name, format_code| %>
func BenchmarkQuery<%= format_name %>FormatDecode_PG_Int4Array_With_Go_Int4Array_<%= array_size %>(b *testing.B) { func BenchmarkQuery<%= format_name %>FormatDecode_PG_Int4Array_With_Go_Int4Array_<%= array_size %>(b *testing.B) {
conn := testutil.MustConnectPgx(b) defaultConnTestRunner.RunTest(context.Background(), b, func(ctx context.Context, _ testing.TB, conn *pgx.Conn) {
defer testutil.MustCloseContext(b, conn) b.ResetTimer()
var v []int32
b.ResetTimer() for i := 0; i < b.N; i++ {
var v []int32 _, err := conn.QueryFunc(
for i := 0; i < b.N; i++ { ctx,
_, err := conn.QueryFunc( `select array_agg(n) from generate_series(1, <%= array_size %>) n`,
context.Background(), []interface{}{pgx.QueryResultFormats{<%= format_code %>}},
`select array_agg(n) from generate_series(1, <%= array_size %>) n`, []interface{}{&v},
[]interface{}{pgx.QueryResultFormats{<%= format_code %>}}, func(pgx.QueryFuncRow) error { return nil },
[]interface{}{&v}, )
func(pgx.QueryFuncRow) error { return nil }, if err != nil {
) b.Fatal(err)
if err != nil { }
b.Fatal(err) }
} })
}
} }
<% end %> <% end %>
<% end %> <% end %>

View File

@ -1,15 +1,16 @@
package pgtype_test package pgtype_test
import ( import (
"context"
"testing" "testing"
"time" "time"
"github.com/jackc/pgx/v5/pgtype" "github.com/jackc/pgx/v5/pgtype"
"github.com/jackc/pgx/v5/pgtype/testutil" "github.com/jackc/pgx/v5/pgxtest"
) )
func TestIntervalCodec(t *testing.T) { func TestIntervalCodec(t *testing.T) {
testutil.RunTranscodeTests(t, "interval", []testutil.TranscodeTestCase{ pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "interval", []pgxtest.ValueRoundTripTest{
{ {
pgtype.Interval{Microseconds: 1, Valid: true}, pgtype.Interval{Microseconds: 1, Valid: true},
new(pgtype.Interval), new(pgtype.Interval),

View File

@ -1,9 +1,10 @@
package pgtype_test package pgtype_test
import ( import (
"context"
"testing" "testing"
"github.com/jackc/pgx/v5/pgtype/testutil" "github.com/jackc/pgx/v5/pgxtest"
) )
func isExpectedEqMap(a interface{}) func(interface{}) bool { func isExpectedEqMap(a interface{}) func(interface{}) bool {
@ -39,7 +40,15 @@ func TestJSONCodec(t *testing.T) {
Age int `json:"age"` Age int `json:"age"`
} }
testutil.RunTranscodeTests(t, "json", []testutil.TranscodeTestCase{ pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "json", []pgxtest.ValueRoundTripTest{
{nil, new(*jsonStruct), isExpectedEq((*jsonStruct)(nil))},
{map[string]interface{}(nil), new(*string), isExpectedEq((*string)(nil))},
{map[string]interface{}(nil), new([]byte), isExpectedEqBytes([]byte(nil))},
{[]byte(nil), new([]byte), isExpectedEqBytes([]byte(nil))},
{nil, new([]byte), isExpectedEqBytes([]byte(nil))},
})
pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, pgxtest.KnownOIDQueryExecModes, "json", []pgxtest.ValueRoundTripTest{
{[]byte("{}"), new([]byte), isExpectedEqBytes([]byte("{}"))}, {[]byte("{}"), new([]byte), isExpectedEqBytes([]byte("{}"))},
{[]byte("null"), new([]byte), isExpectedEqBytes([]byte("null"))}, {[]byte("null"), new([]byte), isExpectedEqBytes([]byte("null"))},
{[]byte("42"), new([]byte), isExpectedEqBytes([]byte("42"))}, {[]byte("42"), new([]byte), isExpectedEqBytes([]byte("42"))},
@ -47,10 +56,5 @@ func TestJSONCodec(t *testing.T) {
{[]byte(`"hello"`), new(string), isExpectedEq(`"hello"`)}, {[]byte(`"hello"`), new(string), isExpectedEq(`"hello"`)},
{map[string]interface{}{"foo": "bar"}, new(map[string]interface{}), isExpectedEqMap(map[string]interface{}{"foo": "bar"})}, {map[string]interface{}{"foo": "bar"}, new(map[string]interface{}), isExpectedEqMap(map[string]interface{}{"foo": "bar"})},
{jsonStruct{Name: "Adam", Age: 10}, new(jsonStruct), isExpectedEq(jsonStruct{Name: "Adam", Age: 10})}, {jsonStruct{Name: "Adam", Age: 10}, new(jsonStruct), isExpectedEq(jsonStruct{Name: "Adam", Age: 10})},
{nil, new(*jsonStruct), isExpectedEq((*jsonStruct)(nil))},
{map[string]interface{}(nil), new(*string), isExpectedEq((*string)(nil))},
{map[string]interface{}(nil), new([]byte), isExpectedEqBytes([]byte(nil))},
{[]byte(nil), new([]byte), isExpectedEqBytes([]byte(nil))},
{nil, new([]byte), isExpectedEqBytes([]byte(nil))},
}) })
} }

View File

@ -1,9 +1,10 @@
package pgtype_test package pgtype_test
import ( import (
"context"
"testing" "testing"
"github.com/jackc/pgx/v5/pgtype/testutil" "github.com/jackc/pgx/v5/pgxtest"
) )
func TestJSONBTranscode(t *testing.T) { func TestJSONBTranscode(t *testing.T) {
@ -12,7 +13,15 @@ func TestJSONBTranscode(t *testing.T) {
Age int `json:"age"` Age int `json:"age"`
} }
testutil.RunTranscodeTests(t, "jsonb", []testutil.TranscodeTestCase{ pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "jsonb", []pgxtest.ValueRoundTripTest{
{nil, new(*jsonStruct), isExpectedEq((*jsonStruct)(nil))},
{map[string]interface{}(nil), new(*string), isExpectedEq((*string)(nil))},
{map[string]interface{}(nil), new([]byte), isExpectedEqBytes([]byte(nil))},
{[]byte(nil), new([]byte), isExpectedEqBytes([]byte(nil))},
{nil, new([]byte), isExpectedEqBytes([]byte(nil))},
})
pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, pgxtest.KnownOIDQueryExecModes, "jsonb", []pgxtest.ValueRoundTripTest{
{[]byte("{}"), new([]byte), isExpectedEqBytes([]byte("{}"))}, {[]byte("{}"), new([]byte), isExpectedEqBytes([]byte("{}"))},
{[]byte("null"), new([]byte), isExpectedEqBytes([]byte("null"))}, {[]byte("null"), new([]byte), isExpectedEqBytes([]byte("null"))},
{[]byte("42"), new([]byte), isExpectedEqBytes([]byte("42"))}, {[]byte("42"), new([]byte), isExpectedEqBytes([]byte("42"))},
@ -20,10 +29,5 @@ func TestJSONBTranscode(t *testing.T) {
{[]byte(`"hello"`), new(string), isExpectedEq(`"hello"`)}, {[]byte(`"hello"`), new(string), isExpectedEq(`"hello"`)},
{map[string]interface{}{"foo": "bar"}, new(map[string]interface{}), isExpectedEqMap(map[string]interface{}{"foo": "bar"})}, {map[string]interface{}{"foo": "bar"}, new(map[string]interface{}), isExpectedEqMap(map[string]interface{}{"foo": "bar"})},
{jsonStruct{Name: "Adam", Age: 10}, new(jsonStruct), isExpectedEq(jsonStruct{Name: "Adam", Age: 10})}, {jsonStruct{Name: "Adam", Age: 10}, new(jsonStruct), isExpectedEq(jsonStruct{Name: "Adam", Age: 10})},
{nil, new(*jsonStruct), isExpectedEq((*jsonStruct)(nil))},
{map[string]interface{}(nil), new(*string), isExpectedEq((*string)(nil))},
{map[string]interface{}(nil), new([]byte), isExpectedEqBytes([]byte(nil))},
{[]byte(nil), new([]byte), isExpectedEqBytes([]byte(nil))},
{nil, new([]byte), isExpectedEqBytes([]byte(nil))},
}) })
} }

View File

@ -4,30 +4,32 @@ import (
"context" "context"
"testing" "testing"
pgx "github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype" "github.com/jackc/pgx/v5/pgtype"
"github.com/jackc/pgx/v5/pgtype/testutil" "github.com/jackc/pgx/v5/pgxtest"
) )
func TestLineTranscode(t *testing.T) { func TestLineTranscode(t *testing.T) {
skipCockroachDB(t, "Server does not support type line") ctr := defaultConnTestRunner
ctr.AfterConnect = func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
pgxtest.SkipCockroachDB(t, conn, "Server does not support type line")
conn := testutil.MustConnectPgx(t) if _, ok := conn.TypeMap().TypeForName("line"); !ok {
defer conn.Close(context.Background()) t.Skip("Skipping due to no line type")
if _, ok := conn.TypeMap().TypeForName("line"); !ok { }
t.Skip("Skipping due to no line type")
// line may exist but not be usable on 9.3 :(
var isPG93 bool
err := conn.QueryRow(context.Background(), "select version() ~ '9.3'").Scan(&isPG93)
if err != nil {
t.Fatal(err)
}
if isPG93 {
t.Skip("Skipping due to unimplemented line type in PG 9.3")
}
} }
// line may exist but not be usable on 9.3 :( pgxtest.RunValueRoundTripTests(context.Background(), t, ctr, nil, "line", []pgxtest.ValueRoundTripTest{
var isPG93 bool
err := conn.QueryRow(context.Background(), "select version() ~ '9.3'").Scan(&isPG93)
if err != nil {
t.Fatal(err)
}
if isPG93 {
t.Skip("Skipping due to unimplemented line type in PG 9.3")
}
testutil.RunTranscodeTests(t, "line", []testutil.TranscodeTestCase{
{ {
pgtype.Line{ pgtype.Line{
A: 1.23, B: 4.56, C: 7.89012345, A: 1.23, B: 4.56, C: 7.89012345,

View File

@ -1,16 +1,17 @@
package pgtype_test package pgtype_test
import ( import (
"context"
"testing" "testing"
"github.com/jackc/pgx/v5/pgtype" "github.com/jackc/pgx/v5/pgtype"
"github.com/jackc/pgx/v5/pgtype/testutil" "github.com/jackc/pgx/v5/pgxtest"
) )
func TestLsegTranscode(t *testing.T) { func TestLsegTranscode(t *testing.T) {
skipCockroachDB(t, "Server does not support type lseg") skipCockroachDB(t, "Server does not support type lseg")
testutil.RunTranscodeTests(t, "lseg", []testutil.TranscodeTestCase{ pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "lseg", []pgxtest.ValueRoundTripTest{
{ {
pgtype.Lseg{ pgtype.Lseg{
P: [2]pgtype.Vec2{{3.14, 1.678}, {7.1, 5.2345678901}}, P: [2]pgtype.Vec2{{3.14, 1.678}, {7.1, 5.2345678901}},

View File

@ -2,10 +2,11 @@ package pgtype_test
import ( import (
"bytes" "bytes"
"context"
"net" "net"
"testing" "testing"
"github.com/jackc/pgx/v5/pgtype/testutil" "github.com/jackc/pgx/v5/pgxtest"
) )
func isExpectedEqHardwareAddr(a interface{}) func(interface{}) bool { func isExpectedEqHardwareAddr(a interface{}) func(interface{}) bool {
@ -28,7 +29,8 @@ func isExpectedEqHardwareAddr(a interface{}) func(interface{}) bool {
func TestMacaddrCodec(t *testing.T) { func TestMacaddrCodec(t *testing.T) {
skipCockroachDB(t, "Server does not support type macaddr") skipCockroachDB(t, "Server does not support type macaddr")
testutil.RunTranscodeTests(t, "macaddr", []testutil.TranscodeTestCase{ // Only testing known OID query exec modes as net.HardwareAddr could map to macaddr or macaddr8.
pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, pgxtest.KnownOIDQueryExecModes, "macaddr", []pgxtest.ValueRoundTripTest{
{ {
mustParseMacaddr(t, "01:23:45:67:89:ab"), mustParseMacaddr(t, "01:23:45:67:89:ab"),
new(net.HardwareAddr), new(net.HardwareAddr),

View File

@ -9,8 +9,9 @@ import (
"strconv" "strconv"
"testing" "testing"
pgx "github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype" "github.com/jackc/pgx/v5/pgtype"
"github.com/jackc/pgx/v5/pgtype/testutil" "github.com/jackc/pgx/v5/pgxtest"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -77,7 +78,7 @@ func TestNumericCodec(t *testing.T) {
max.Add(max, big.NewInt(1)) max.Add(max, big.NewInt(1))
longestNumeric := pgtype.Numeric{Int: max, Exp: -16383, Valid: true} longestNumeric := pgtype.Numeric{Int: max, Exp: -16383, Valid: true}
testutil.RunTranscodeTests(t, "numeric", []testutil.TranscodeTestCase{ pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "numeric", []pgxtest.ValueRoundTripTest{
{mustParseNumeric(t, "1"), new(pgtype.Numeric), isExpectedEqNumeric(mustParseNumeric(t, "1"))}, {mustParseNumeric(t, "1"), new(pgtype.Numeric), isExpectedEqNumeric(mustParseNumeric(t, "1"))},
{mustParseNumeric(t, "3.14159"), new(pgtype.Numeric), isExpectedEqNumeric(mustParseNumeric(t, "3.14159"))}, {mustParseNumeric(t, "3.14159"), new(pgtype.Numeric), isExpectedEqNumeric(mustParseNumeric(t, "3.14159"))},
{mustParseNumeric(t, "100010001"), new(pgtype.Numeric), isExpectedEqNumeric(mustParseNumeric(t, "100010001"))}, {mustParseNumeric(t, "100010001"), new(pgtype.Numeric), isExpectedEqNumeric(mustParseNumeric(t, "100010001"))},
@ -118,7 +119,7 @@ func TestNumericCodecInfinity(t *testing.T) {
skipCockroachDB(t, "server formats numeric text format differently") skipCockroachDB(t, "server formats numeric text format differently")
skipPostgreSQLVersionLessThan(t, 14) skipPostgreSQLVersionLessThan(t, 14)
testutil.RunTranscodeTests(t, "numeric", []testutil.TranscodeTestCase{ pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "numeric", []pgxtest.ValueRoundTripTest{
{math.Inf(1), new(float64), isExpectedEq(math.Inf(1))}, {math.Inf(1), new(float64), isExpectedEq(math.Inf(1))},
{float32(math.Inf(1)), new(float32), isExpectedEq(float32(math.Inf(1)))}, {float32(math.Inf(1)), new(float32), isExpectedEq(float32(math.Inf(1)))},
{math.Inf(-1), new(float64), isExpectedEq(math.Inf(-1))}, {math.Inf(-1), new(float64), isExpectedEq(math.Inf(-1))},
@ -159,54 +160,54 @@ func TestNumericCodecFuzz(t *testing.T) {
max := &big.Int{} max := &big.Int{}
max.SetString("9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999", 10) max.SetString("9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999", 10)
tests := make([]testutil.TranscodeTestCase, 0, 2000) tests := make([]pgxtest.ValueRoundTripTest, 0, 2000)
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
for j := -50; j < 50; j++ { for j := -50; j < 50; j++ {
num := (&big.Int{}).Rand(r, max) num := (&big.Int{}).Rand(r, max)
n := pgtype.Numeric{Int: num, Exp: int32(j), Valid: true} n := pgtype.Numeric{Int: num, Exp: int32(j), Valid: true}
tests = append(tests, testutil.TranscodeTestCase{n, new(pgtype.Numeric), isExpectedEqNumeric(n)}) tests = append(tests, pgxtest.ValueRoundTripTest{n, new(pgtype.Numeric), isExpectedEqNumeric(n)})
negNum := &big.Int{} negNum := &big.Int{}
negNum.Neg(num) negNum.Neg(num)
n = pgtype.Numeric{Int: negNum, Exp: int32(j), Valid: true} n = pgtype.Numeric{Int: negNum, Exp: int32(j), Valid: true}
tests = append(tests, testutil.TranscodeTestCase{n, new(pgtype.Numeric), isExpectedEqNumeric(n)}) tests = append(tests, pgxtest.ValueRoundTripTest{n, new(pgtype.Numeric), isExpectedEqNumeric(n)})
} }
} }
testutil.RunTranscodeTests(t, "numeric", tests) pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "numeric", tests)
} }
func TestNumericMarshalJSON(t *testing.T) { func TestNumericMarshalJSON(t *testing.T) {
skipCockroachDB(t, "server formats numeric text format differently") skipCockroachDB(t, "server formats numeric text format differently")
conn := testutil.MustConnectPgx(t) defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
defer testutil.MustCloseContext(t, conn)
for i, tt := range []struct { for i, tt := range []struct {
decString string decString string
}{ }{
{"NaN"}, {"NaN"},
{"0"}, {"0"},
{"1"}, {"1"},
{"-1"}, {"-1"},
{"1000000000000000000"}, {"1000000000000000000"},
{"1234.56789"}, {"1234.56789"},
{"1.56789"}, {"1.56789"},
{"0.00000000000056789"}, {"0.00000000000056789"},
{"0.00123000"}, {"0.00123000"},
{"123e-3"}, {"123e-3"},
{"243723409723490243842378942378901237502734019231380123e23790"}, {"243723409723490243842378942378901237502734019231380123e23790"},
{"3409823409243892349028349023482934092340892390101e-14021"}, {"3409823409243892349028349023482934092340892390101e-14021"},
} { } {
var num pgtype.Numeric var num pgtype.Numeric
var pgJSON string var pgJSON string
err := conn.QueryRow(context.Background(), `select $1::numeric, to_json($1::numeric)`, tt.decString).Scan(&num, &pgJSON) err := conn.QueryRow(ctx, `select $1::numeric, to_json($1::numeric)`, tt.decString).Scan(&num, &pgJSON)
require.NoErrorf(t, err, "%d", i) require.NoErrorf(t, err, "%d", i)
goJSON, err := json.Marshal(num) goJSON, err := json.Marshal(num)
require.NoErrorf(t, err, "%d", i) require.NoErrorf(t, err, "%d", i)
require.Equal(t, pgJSON, string(goJSON)) require.Equal(t, pgJSON, string(goJSON))
} }
})
} }

View File

@ -1,10 +1,11 @@
package pgtype_test package pgtype_test
import ( import (
"context"
"testing" "testing"
"github.com/jackc/pgx/v5/pgtype" "github.com/jackc/pgx/v5/pgtype"
"github.com/jackc/pgx/v5/pgtype/testutil" "github.com/jackc/pgx/v5/pgxtest"
) )
func isExpectedEqPath(a interface{}) func(interface{}) bool { func isExpectedEqPath(a interface{}) func(interface{}) bool {
@ -29,7 +30,7 @@ func isExpectedEqPath(a interface{}) func(interface{}) bool {
func TestPathTranscode(t *testing.T) { func TestPathTranscode(t *testing.T) {
skipCockroachDB(t, "Server does not support type path") skipCockroachDB(t, "Server does not support type path")
testutil.RunTranscodeTests(t, "path", []testutil.TranscodeTestCase{ pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "path", []pgxtest.ValueRoundTripTest{
{ {
pgtype.Path{ pgtype.Path{
P: []pgtype.Vec2{{3.14, 1.678901234}, {7.1, 5.234}}, P: []pgtype.Vec2{{3.14, 1.678901234}, {7.1, 5.234}},

View File

@ -374,7 +374,7 @@ func NewMap() *Map {
registerDefaultPgTypeVariants("daterange", "_daterange", Daterange{}) registerDefaultPgTypeVariants("daterange", "_daterange", Daterange{})
registerDefaultPgTypeVariants("float4", "_float4", Float4{}) registerDefaultPgTypeVariants("float4", "_float4", Float4{})
registerDefaultPgTypeVariants("float8", "_float8", Float8{}) registerDefaultPgTypeVariants("float8", "_float8", Float8{})
registerDefaultPgTypeVariants("float8range", "_float8range", Float8range{}) registerDefaultPgTypeVariants("numrange", "_numrange", Float8range{}) // There is no PostgreSQL builtin float8range so map it to numrange.
registerDefaultPgTypeVariants("inet", "_inet", Inet{}) registerDefaultPgTypeVariants("inet", "_inet", Inet{})
registerDefaultPgTypeVariants("int2", "_int2", Int2{}) registerDefaultPgTypeVariants("int2", "_int2", Int2{})
registerDefaultPgTypeVariants("int4", "_int4", Int4{}) registerDefaultPgTypeVariants("int4", "_int4", Int4{})

View File

@ -1,21 +1,34 @@
package pgtype_test package pgtype_test
import ( import (
"context"
"database/sql" "database/sql"
"errors" "errors"
"net" "net"
"os"
"regexp" "regexp"
"strconv" "strconv"
"testing" "testing"
"github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype" "github.com/jackc/pgx/v5/pgtype"
"github.com/jackc/pgx/v5/pgtype/testutil" "github.com/jackc/pgx/v5/pgxtest"
_ "github.com/jackc/pgx/v5/stdlib" _ "github.com/jackc/pgx/v5/stdlib"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
var defaultConnTestRunner pgxtest.ConnTestRunner
func init() {
defaultConnTestRunner = pgxtest.DefaultConnTestRunner()
defaultConnTestRunner.CreateConfig = func(ctx context.Context, t testing.TB) *pgx.ConnConfig {
config, err := pgx.ParseConfig(os.Getenv("PGX_TEST_DATABASE"))
require.NoError(t, err)
return config
}
}
// Test for renamed types // Test for renamed types
type _string string type _string string
type _bool bool type _bool bool
@ -70,8 +83,11 @@ func mustParseMacaddr(t testing.TB, s string) net.HardwareAddr {
} }
func skipCockroachDB(t testing.TB, msg string) { func skipCockroachDB(t testing.TB, msg string) {
conn := testutil.MustConnectPgx(t) conn, err := pgx.Connect(context.Background(), os.Getenv("PGX_TEST_DATABASE"))
defer testutil.MustCloseContext(t, conn) if err != nil {
t.Fatal(err)
}
defer conn.Close(context.Background())
if conn.PgConn().ParameterStatus("crdb_version") != "" { if conn.PgConn().ParameterStatus("crdb_version") != "" {
t.Skip(msg) t.Skip(msg)
@ -79,8 +95,11 @@ func skipCockroachDB(t testing.TB, msg string) {
} }
func skipPostgreSQLVersionLessThan(t testing.TB, minVersion int64) { func skipPostgreSQLVersionLessThan(t testing.TB, minVersion int64) {
conn := testutil.MustConnectPgx(t) conn, err := pgx.Connect(context.Background(), os.Getenv("PGX_TEST_DATABASE"))
defer testutil.MustCloseContext(t, conn) if err != nil {
t.Fatal(err)
}
defer conn.Close(context.Background())
serverVersionStr := conn.PgConn().ParameterStatus("server_version") serverVersionStr := conn.PgConn().ParameterStatus("server_version")
serverVersionStr = regexp.MustCompile(`^[0-9]+`).FindString(serverVersionStr) serverVersionStr = regexp.MustCompile(`^[0-9]+`).FindString(serverVersionStr)

View File

@ -1,18 +1,19 @@
package pgtype_test package pgtype_test
import ( import (
"context"
"reflect" "reflect"
"testing" "testing"
"github.com/jackc/pgx/v5/pgtype" "github.com/jackc/pgx/v5/pgtype"
"github.com/jackc/pgx/v5/pgtype/testutil" "github.com/jackc/pgx/v5/pgxtest"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestPointCodec(t *testing.T) { func TestPointCodec(t *testing.T) {
skipCockroachDB(t, "Server does not support type point") skipCockroachDB(t, "Server does not support type point")
testutil.RunTranscodeTests(t, "point", []testutil.TranscodeTestCase{ pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "point", []pgxtest.ValueRoundTripTest{
{ {
pgtype.Point{P: pgtype.Vec2{1.234, 5.6789012345}, Valid: true}, pgtype.Point{P: pgtype.Vec2{1.234, 5.6789012345}, Valid: true},
new(pgtype.Point), new(pgtype.Point),

View File

@ -1,10 +1,11 @@
package pgtype_test package pgtype_test
import ( import (
"context"
"testing" "testing"
"github.com/jackc/pgx/v5/pgtype" "github.com/jackc/pgx/v5/pgtype"
"github.com/jackc/pgx/v5/pgtype/testutil" "github.com/jackc/pgx/v5/pgxtest"
) )
func isExpectedEqPolygon(a interface{}) func(interface{}) bool { func isExpectedEqPolygon(a interface{}) func(interface{}) bool {
@ -29,7 +30,7 @@ func isExpectedEqPolygon(a interface{}) func(interface{}) bool {
func TestPolygonTranscode(t *testing.T) { func TestPolygonTranscode(t *testing.T) {
skipCockroachDB(t, "Server does not support type polygon") skipCockroachDB(t, "Server does not support type polygon")
testutil.RunTranscodeTests(t, "polygon", []testutil.TranscodeTestCase{ pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "polygon", []pgxtest.ValueRoundTripTest{
{ {
pgtype.Polygon{ pgtype.Polygon{
P: []pgtype.Vec2{{3.14, 1.678901234}, {7.1, 5.234}, {5.0, 3.234}}, P: []pgtype.Vec2{{3.14, 1.678901234}, {7.1, 5.234}, {5.0, 3.234}},

View File

@ -1,22 +1,24 @@
package pgtype_test package pgtype_test
import ( import (
"context"
"math" "math"
"testing" "testing"
"github.com/jackc/pgx/v5/pgtype/testutil" "github.com/jackc/pgx/v5/pgxtest"
) )
func TestQcharTranscode(t *testing.T) { func TestQcharTranscode(t *testing.T) {
skipCockroachDB(t, "Server does not support qchar") skipCockroachDB(t, "Server does not support qchar")
var tests []testutil.TranscodeTestCase var tests []pgxtest.ValueRoundTripTest
for i := 0; i <= math.MaxUint8; i++ { for i := 0; i <= math.MaxUint8; i++ {
tests = append(tests, testutil.TranscodeTestCase{rune(i), new(rune), isExpectedEq(rune(i))}) tests = append(tests, pgxtest.ValueRoundTripTest{rune(i), new(rune), isExpectedEq(rune(i))})
tests = append(tests, testutil.TranscodeTestCase{byte(i), new(byte), isExpectedEq(byte(i))}) tests = append(tests, pgxtest.ValueRoundTripTest{byte(i), new(byte), isExpectedEq(byte(i))})
} }
tests = append(tests, testutil.TranscodeTestCase{nil, new(*rune), isExpectedEq((*rune)(nil))}) tests = append(tests, pgxtest.ValueRoundTripTest{nil, new(*rune), isExpectedEq((*rune)(nil))})
tests = append(tests, testutil.TranscodeTestCase{nil, new(*byte), isExpectedEq((*byte)(nil))}) tests = append(tests, pgxtest.ValueRoundTripTest{nil, new(*byte), isExpectedEq((*byte)(nil))})
testutil.RunTranscodeTests(t, `"char"`, tests) // Can only test with known OIDs as rune and byte would be considered numbers.
pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, pgxtest.KnownOIDQueryExecModes, `"char"`, tests)
} }

View File

@ -4,15 +4,16 @@ import (
"context" "context"
"testing" "testing"
pgx "github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype" "github.com/jackc/pgx/v5/pgtype"
"github.com/jackc/pgx/v5/pgtype/testutil" "github.com/jackc/pgx/v5/pgxtest"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestRangeCodecTranscode(t *testing.T) { func TestRangeCodecTranscode(t *testing.T) {
skipCockroachDB(t, "Server does not support range types (see https://github.com/cockroachdb/cockroach/issues/27791)") skipCockroachDB(t, "Server does not support range types (see https://github.com/cockroachdb/cockroach/issues/27791)")
testutil.RunTranscodeTests(t, "int4range", []testutil.TranscodeTestCase{ pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "int4range", []pgxtest.ValueRoundTripTest{
{ {
pgtype.Int4range{LowerType: pgtype.Empty, UpperType: pgtype.Empty, Valid: true}, pgtype.Int4range{LowerType: pgtype.Empty, UpperType: pgtype.Empty, Valid: true},
new(pgtype.Int4range), new(pgtype.Int4range),
@ -39,9 +40,12 @@ func TestRangeCodecTranscode(t *testing.T) {
} }
func TestRangeCodecTranscodeCompatibleRangeElementTypes(t *testing.T) { func TestRangeCodecTranscodeCompatibleRangeElementTypes(t *testing.T) {
skipCockroachDB(t, "Server does not support range types (see https://github.com/cockroachdb/cockroach/issues/27791)") ctr := defaultConnTestRunner
ctr.AfterConnect = func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
pgxtest.SkipCockroachDB(t, conn, "Server does not support range types (see https://github.com/cockroachdb/cockroach/issues/27791)")
}
testutil.RunTranscodeTests(t, "numrange", []testutil.TranscodeTestCase{ pgxtest.RunValueRoundTripTests(context.Background(), t, ctr, nil, "numrange", []pgxtest.ValueRoundTripTest{
{ {
pgtype.Float8range{LowerType: pgtype.Empty, UpperType: pgtype.Empty, Valid: true}, pgtype.Float8range{LowerType: pgtype.Empty, UpperType: pgtype.Empty, Valid: true},
new(pgtype.Float8range), new(pgtype.Float8range),
@ -70,90 +74,90 @@ func TestRangeCodecTranscodeCompatibleRangeElementTypes(t *testing.T) {
func TestRangeCodecScanRangeTwiceWithUnbounded(t *testing.T) { func TestRangeCodecScanRangeTwiceWithUnbounded(t *testing.T) {
skipCockroachDB(t, "Server does not support range types (see https://github.com/cockroachdb/cockroach/issues/27791)") skipCockroachDB(t, "Server does not support range types (see https://github.com/cockroachdb/cockroach/issues/27791)")
conn := testutil.MustConnectPgx(t) defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
defer testutil.MustCloseContext(t, conn)
var r pgtype.Int4range var r pgtype.Int4range
err := conn.QueryRow(context.Background(), `select '[1,5)'::int4range`).Scan(&r) err := conn.QueryRow(context.Background(), `select '[1,5)'::int4range`).Scan(&r)
require.NoError(t, err) require.NoError(t, err)
require.Equal( require.Equal(
t, t,
pgtype.Int4range{ pgtype.Int4range{
Lower: pgtype.Int4{Int32: 1, Valid: true}, Lower: pgtype.Int4{Int32: 1, Valid: true},
Upper: pgtype.Int4{Int32: 5, Valid: true}, Upper: pgtype.Int4{Int32: 5, Valid: true},
LowerType: pgtype.Inclusive, LowerType: pgtype.Inclusive,
UpperType: pgtype.Exclusive, UpperType: pgtype.Exclusive,
Valid: true, Valid: true,
}, },
r, r,
) )
err = conn.QueryRow(context.Background(), `select '[1,)'::int4range`).Scan(&r) err = conn.QueryRow(ctx, `select '[1,)'::int4range`).Scan(&r)
require.NoError(t, err) require.NoError(t, err)
require.Equal( require.Equal(
t, t,
pgtype.Int4range{ pgtype.Int4range{
Lower: pgtype.Int4{Int32: 1, Valid: true}, Lower: pgtype.Int4{Int32: 1, Valid: true},
Upper: pgtype.Int4{}, Upper: pgtype.Int4{},
LowerType: pgtype.Inclusive, LowerType: pgtype.Inclusive,
UpperType: pgtype.Unbounded, UpperType: pgtype.Unbounded,
Valid: true, Valid: true,
}, },
r, r,
) )
err = conn.QueryRow(context.Background(), `select 'empty'::int4range`).Scan(&r) err = conn.QueryRow(ctx, `select 'empty'::int4range`).Scan(&r)
require.NoError(t, err) require.NoError(t, err)
require.Equal( require.Equal(
t, t,
pgtype.Int4range{ pgtype.Int4range{
Lower: pgtype.Int4{}, Lower: pgtype.Int4{},
Upper: pgtype.Int4{}, Upper: pgtype.Int4{},
LowerType: pgtype.Empty, LowerType: pgtype.Empty,
UpperType: pgtype.Empty, UpperType: pgtype.Empty,
Valid: true, Valid: true,
}, },
r, r,
) )
})
} }
func TestRangeCodecDecodeValue(t *testing.T) { func TestRangeCodecDecodeValue(t *testing.T) {
skipCockroachDB(t, "Server does not support range types (see https://github.com/cockroachdb/cockroach/issues/27791)") skipCockroachDB(t, "Server does not support range types (see https://github.com/cockroachdb/cockroach/issues/27791)")
conn := testutil.MustConnectPgx(t) defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, _ testing.TB, conn *pgx.Conn) {
defer testutil.MustCloseContext(t, conn)
for _, tt := range []struct { for _, tt := range []struct {
sql string sql string
expected interface{} expected interface{}
}{ }{
{ {
sql: `select '[1,5)'::int4range`, sql: `select '[1,5)'::int4range`,
expected: pgtype.GenericRange{ expected: pgtype.GenericRange{
Lower: int32(1), Lower: int32(1),
Upper: int32(5), Upper: int32(5),
LowerType: pgtype.Inclusive, LowerType: pgtype.Inclusive,
UpperType: pgtype.Exclusive, UpperType: pgtype.Exclusive,
Valid: true, Valid: true,
},
}, },
}, } {
} { t.Run(tt.sql, func(t *testing.T) {
t.Run(tt.sql, func(t *testing.T) { rows, err := conn.Query(ctx, tt.sql)
rows, err := conn.Query(context.Background(), tt.sql)
require.NoError(t, err)
for rows.Next() {
values, err := rows.Values()
require.NoError(t, err) require.NoError(t, err)
require.Len(t, values, 1)
require.Equal(t, tt.expected, values[0])
}
require.NoError(t, rows.Err()) for rows.Next() {
}) values, err := rows.Values()
} require.NoError(t, err)
require.Len(t, values, 1)
require.Equal(t, tt.expected, values[0])
}
require.NoError(t, rows.Err())
})
}
})
} }

View File

@ -4,72 +4,70 @@ import (
"context" "context"
"testing" "testing"
pgx "github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype" "github.com/jackc/pgx/v5/pgtype"
"github.com/jackc/pgx/v5/pgtype/testutil"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestRecordCodec(t *testing.T) { func TestRecordCodec(t *testing.T) {
conn := testutil.MustConnectPgx(t) defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
defer testutil.MustCloseContext(t, conn) var a string
var b int32
err := conn.QueryRow(ctx, `select row('foo'::text, 42::int4)`).Scan(pgtype.CompositeFields{&a, &b})
require.NoError(t, err)
var a string require.Equal(t, "foo", a)
var b int32 require.Equal(t, int32(42), b)
err := conn.QueryRow(context.Background(), `select row('foo'::text, 42::int4)`).Scan(pgtype.CompositeFields{&a, &b}) })
require.NoError(t, err)
require.Equal(t, "foo", a)
require.Equal(t, int32(42), b)
} }
func TestRecordCodecDecodeValue(t *testing.T) { func TestRecordCodecDecodeValue(t *testing.T) {
skipCockroachDB(t, "Server converts row int4 to int8") skipCockroachDB(t, "Server converts row int4 to int8")
conn := testutil.MustConnectPgx(t) defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, _ testing.TB, conn *pgx.Conn) {
defer testutil.MustCloseContext(t, conn) for _, tt := range []struct {
sql string
for _, tt := range []struct { expected interface{}
sql string }{
expected interface{} {
}{ sql: `select row()`,
{ expected: []interface{}{},
sql: `select row()`, },
expected: []interface{}{}, {
}, sql: `select row('foo'::text, 42::int4)`,
{ expected: []interface{}{"foo", int32(42)},
sql: `select row('foo'::text, 42::int4)`, },
expected: []interface{}{"foo", int32(42)}, {
}, sql: `select row(100.0::float4, 1.09::float4)`,
{ expected: []interface{}{float32(100), float32(1.09)},
sql: `select row(100.0::float4, 1.09::float4)`, },
expected: []interface{}{float32(100), float32(1.09)}, {
}, sql: `select row('foo'::text, array[1, 2, null, 4]::int4[], 42::int4)`,
{ expected: []interface{}{"foo", []interface{}{int32(1), int32(2), nil, int32(4)}, int32(42)},
sql: `select row('foo'::text, array[1, 2, null, 4]::int4[], 42::int4)`, },
expected: []interface{}{"foo", []interface{}{int32(1), int32(2), nil, int32(4)}, int32(42)}, {
}, sql: `select row(null)`,
{ expected: []interface{}{nil},
sql: `select row(null)`, },
expected: []interface{}{nil}, {
}, sql: `select null::record`,
{ expected: nil,
sql: `select null::record`, },
expected: nil, } {
}, t.Run(tt.sql, func(t *testing.T) {
} { rows, err := conn.Query(context.Background(), tt.sql)
t.Run(tt.sql, func(t *testing.T) {
rows, err := conn.Query(context.Background(), tt.sql)
require.NoError(t, err)
defer rows.Close()
for rows.Next() {
values, err := rows.Values()
require.NoError(t, err) require.NoError(t, err)
require.Len(t, values, 1) defer rows.Close()
require.Equal(t, tt.expected, values[0])
}
require.NoError(t, rows.Err()) for rows.Next() {
}) values, err := rows.Values()
} require.NoError(t, err)
require.Len(t, values, 1)
require.Equal(t, tt.expected, values[0])
}
require.NoError(t, rows.Err())
})
}
})
} }

View File

@ -1,85 +0,0 @@
package testutil
import (
"context"
"fmt"
"os"
"reflect"
"testing"
"github.com/jackc/pgx/v5"
_ "github.com/jackc/pgx/v5/stdlib"
)
func MustConnectPgx(t testing.TB) *pgx.Conn {
conn, err := pgx.Connect(context.Background(), os.Getenv("PGX_TEST_DATABASE"))
if err != nil {
t.Fatal(err)
}
return conn
}
func MustClose(t testing.TB, conn interface {
Close() error
}) {
err := conn.Close()
if err != nil {
t.Fatal(err)
}
}
func MustCloseContext(t testing.TB, conn interface {
Close(context.Context) error
}) {
err := conn.Close(context.Background())
if err != nil {
t.Fatal(err)
}
}
type TranscodeTestCase struct {
Src interface{}
Dst interface{}
Test func(interface{}) bool
}
func RunTranscodeTests(t testing.TB, pgTypeName string, tests []TranscodeTestCase) {
conn := MustConnectPgx(t)
defer MustCloseContext(t, conn)
formats := []struct {
name string
code int16
}{
{name: "TextFormat", code: pgx.TextFormatCode},
{name: "BinaryFormat", code: pgx.BinaryFormatCode},
}
for _, format := range formats {
RunTranscodeTestsFormat(t, pgTypeName, tests, conn, format.name, format.code)
}
}
func RunTranscodeTestsFormat(t testing.TB, pgTypeName string, tests []TranscodeTestCase, conn *pgx.Conn, formatName string, formatCode int16) {
_, err := conn.Prepare(context.Background(), "test", fmt.Sprintf("select $1::%s", pgTypeName))
if err != nil {
t.Fatal(err)
}
for i, tt := range tests {
err := conn.QueryRow(context.Background(), "test", pgx.QueryResultFormats{formatCode}, tt.Src).Scan(tt.Dst)
if err != nil {
t.Errorf("%s %d: %v", formatName, i, err)
}
dst := reflect.ValueOf(tt.Dst)
if dst.Kind() == reflect.Ptr {
dst = dst.Elem()
}
if !tt.Test(dst.Interface()) {
t.Errorf("%s %d: unexpected result for %v: %v", formatName, i, tt.Src, dst.Interface())
}
}
}

View File

@ -4,8 +4,9 @@ import (
"context" "context"
"testing" "testing"
pgx "github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype" "github.com/jackc/pgx/v5/pgtype"
"github.com/jackc/pgx/v5/pgtype/testutil" "github.com/jackc/pgx/v5/pgxtest"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -17,7 +18,7 @@ func (someFmtStringer) String() string {
func TestTextCodec(t *testing.T) { func TestTextCodec(t *testing.T) {
for _, pgTypeName := range []string{"text", "varchar"} { for _, pgTypeName := range []string{"text", "varchar"} {
testutil.RunTranscodeTests(t, pgTypeName, []testutil.TranscodeTestCase{ pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, pgTypeName, []pgxtest.ValueRoundTripTest{
{ {
pgtype.Text{String: "", Valid: true}, pgtype.Text{String: "", Valid: true},
new(pgtype.Text), new(pgtype.Text),
@ -31,6 +32,10 @@ func TestTextCodec(t *testing.T) {
{nil, new(pgtype.Text), isExpectedEq(pgtype.Text{})}, {nil, new(pgtype.Text), isExpectedEq(pgtype.Text{})},
{"foo", new(string), isExpectedEq("foo")}, {"foo", new(string), isExpectedEq("foo")},
{someFmtStringer{}, new(string), isExpectedEq("some fmt.Stringer")}, {someFmtStringer{}, new(string), isExpectedEq("some fmt.Stringer")},
})
// rune requires known OID because otherwise it is considered an int32.
pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, pgxtest.KnownOIDQueryExecModes, pgTypeName, []pgxtest.ValueRoundTripTest{
{rune('R'), new(rune), isExpectedEq(rune('R'))}, {rune('R'), new(rune), isExpectedEq(rune('R'))},
}) })
} }
@ -47,7 +52,7 @@ func TestTextCodec(t *testing.T) {
// //
// So this is simply a smoke test of the name type. // So this is simply a smoke test of the name type.
func TestTextCodecName(t *testing.T) { func TestTextCodecName(t *testing.T) {
testutil.RunTranscodeTests(t, "name", []testutil.TranscodeTestCase{ pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "name", []pgxtest.ValueRoundTripTest{
{ {
pgtype.Text{String: "", Valid: true}, pgtype.Text{String: "", Valid: true},
new(pgtype.Text), new(pgtype.Text),
@ -67,7 +72,7 @@ func TestTextCodecName(t *testing.T) {
func TestTextCodecBPChar(t *testing.T) { func TestTextCodecBPChar(t *testing.T) {
skipCockroachDB(t, "Server does not properly handle bpchar with multi-byte character") skipCockroachDB(t, "Server does not properly handle bpchar with multi-byte character")
testutil.RunTranscodeTests(t, "char(3)", []testutil.TranscodeTestCase{ pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "char(3)", []pgxtest.ValueRoundTripTest{
{ {
pgtype.Text{String: "a ", Valid: true}, pgtype.Text{String: "a ", Valid: true},
new(pgtype.Text), new(pgtype.Text),
@ -94,12 +99,12 @@ func TestTextCodecBPChar(t *testing.T) {
// //
// It only supports the text format. // It only supports the text format.
func TestTextCodecACLItem(t *testing.T) { func TestTextCodecACLItem(t *testing.T) {
skipCockroachDB(t, "Server does not support type aclitem") ctr := defaultConnTestRunner
ctr.AfterConnect = func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
pgxtest.SkipCockroachDB(t, conn, "Server does not support type aclitem")
}
conn := testutil.MustConnectPgx(t) pgxtest.RunValueRoundTripTests(context.Background(), t, ctr, nil, "aclitem", []pgxtest.ValueRoundTripTest{
defer testutil.MustCloseContext(t, conn)
testutil.RunTranscodeTestsFormat(t, "aclitem", []testutil.TranscodeTestCase{
{ {
pgtype.Text{String: "postgres=arwdDxt/postgres", Valid: true}, pgtype.Text{String: "postgres=arwdDxt/postgres", Valid: true},
new(pgtype.Text), new(pgtype.Text),
@ -107,33 +112,33 @@ func TestTextCodecACLItem(t *testing.T) {
}, },
{pgtype.Text{}, new(pgtype.Text), isExpectedEq(pgtype.Text{})}, {pgtype.Text{}, new(pgtype.Text), isExpectedEq(pgtype.Text{})},
{nil, new(pgtype.Text), isExpectedEq(pgtype.Text{})}, {nil, new(pgtype.Text), isExpectedEq(pgtype.Text{})},
}, conn, "Text", pgtype.TextFormatCode) })
} }
func TestTextCodecACLItemRoleWithSpecialCharacters(t *testing.T) { func TestTextCodecACLItemRoleWithSpecialCharacters(t *testing.T) {
conn := testutil.MustConnectPgx(t) ctr := defaultConnTestRunner
defer testutil.MustCloseContext(t, conn) ctr.AfterConnect = func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
pgxtest.SkipCockroachDB(t, conn, "Server does not support type aclitem")
ctx := context.Background() // The tricky test user, below, has to actually exist so that it can be used in a test
// of aclitem formatting. It turns out aclitems cannot contain non-existing users/roles.
roleWithSpecialCharacters := ` tricky, ' } " \ test user `
// The tricky test user, below, has to actually exist so that it can be used in a test commandTag, err := conn.Exec(ctx, `select * from pg_roles where rolname = $1`, roleWithSpecialCharacters)
// of aclitem formatting. It turns out aclitems cannot contain non-existing users/roles. require.NoError(t, err)
roleWithSpecialCharacters := ` tricky, ' } " \ test user `
commandTag, err := conn.Exec(ctx, `select * from pg_roles where rolname = $1`, roleWithSpecialCharacters) if commandTag.RowsAffected() == 0 {
require.NoError(t, err) t.Skipf("Role with special characters does not exist.")
}
if commandTag.RowsAffected() == 0 {
t.Skipf("Role with special characters does not exist.")
} }
testutil.RunTranscodeTestsFormat(t, "aclitem", []testutil.TranscodeTestCase{ pgxtest.RunValueRoundTripTests(context.Background(), t, ctr, nil, "aclitem", []pgxtest.ValueRoundTripTest{
{ {
pgtype.Text{String: `postgres=arwdDxt/" tricky, ' } "" \ test user "`, Valid: true}, pgtype.Text{String: `postgres=arwdDxt/" tricky, ' } "" \ test user "`, Valid: true},
new(pgtype.Text), new(pgtype.Text),
isExpectedEq(pgtype.Text{String: `postgres=arwdDxt/" tricky, ' } "" \ test user "`, Valid: true}), isExpectedEq(pgtype.Text{String: `postgres=arwdDxt/" tricky, ' } "" \ test user "`, Valid: true}),
}, },
}, conn, "Text", pgtype.TextFormatCode) })
} }
func TestTextMarshalJSON(t *testing.T) { func TestTextMarshalJSON(t *testing.T) {

View File

@ -1,16 +1,17 @@
package pgtype_test package pgtype_test
import ( import (
"context"
"testing" "testing"
"github.com/jackc/pgx/v5/pgtype" "github.com/jackc/pgx/v5/pgtype"
"github.com/jackc/pgx/v5/pgtype/testutil" "github.com/jackc/pgx/v5/pgxtest"
) )
func TestTIDCodec(t *testing.T) { func TestTIDCodec(t *testing.T) {
skipCockroachDB(t, "Server does not support type tid") skipCockroachDB(t, "Server does not support type tid")
testutil.RunTranscodeTests(t, "tid", []testutil.TranscodeTestCase{ pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "tid", []pgxtest.ValueRoundTripTest{
{ {
pgtype.TID{BlockNumber: 42, OffsetNumber: 43, Valid: true}, pgtype.TID{BlockNumber: 42, OffsetNumber: 43, Valid: true},
new(pgtype.TID), new(pgtype.TID),

View File

@ -1,15 +1,16 @@
package pgtype_test package pgtype_test
import ( import (
"context"
"testing" "testing"
"time" "time"
"github.com/jackc/pgx/v5/pgtype" "github.com/jackc/pgx/v5/pgtype"
"github.com/jackc/pgx/v5/pgtype/testutil" "github.com/jackc/pgx/v5/pgxtest"
) )
func TestTimeCodec(t *testing.T) { func TestTimeCodec(t *testing.T) {
testutil.RunTranscodeTests(t, "time", []testutil.TranscodeTestCase{ pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "time", []pgxtest.ValueRoundTripTest{
{ {
pgtype.Time{Microseconds: 0, Valid: true}, pgtype.Time{Microseconds: 0, Valid: true},
new(pgtype.Time), new(pgtype.Time),

View File

@ -4,6 +4,7 @@ import (
"database/sql/driver" "database/sql/driver"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"strings"
"time" "time"
"github.com/jackc/pgx/v5/internal/pgio" "github.com/jackc/pgx/v5/internal/pgio"
@ -127,12 +128,29 @@ func (encodePlanTimestampCodecText) Encode(value interface{}, buf []byte) (newBu
return nil, err return nil, err
} }
if !ts.Valid {
return nil, nil
}
var s string var s string
switch ts.InfinityModifier { switch ts.InfinityModifier {
case Finite: case Finite:
t := discardTimeZone(ts.Time) t := discardTimeZone(ts.Time)
// Year 0000 is 1 BC
bc := false
if year := t.Year(); year <= 0 {
year = -year + 1
t = time.Date(year, t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), time.UTC)
bc = true
}
s = t.Truncate(time.Microsecond).Format(pgTimestampFormat) s = t.Truncate(time.Microsecond).Format(pgTimestampFormat)
if bc {
s = s + " BC"
}
case Infinity: case Infinity:
s = "infinity" s = "infinity"
case NegativeInfinity: case NegativeInfinity:
@ -219,11 +237,21 @@ func (scanPlanTextTimestampToTimestampScanner) Scan(src []byte, dst interface{})
case "-infinity": case "-infinity":
ts = Timestamp{Valid: true, InfinityModifier: -Infinity} ts = Timestamp{Valid: true, InfinityModifier: -Infinity}
default: default:
bc := false
if strings.HasSuffix(sbuf, " BC") {
sbuf = sbuf[:len(sbuf)-3]
bc = true
}
tim, err := time.Parse(pgTimestampFormat, sbuf) tim, err := time.Parse(pgTimestampFormat, sbuf)
if err != nil { if err != nil {
return err return err
} }
if bc {
year := -tim.Year() + 1
tim = time.Date(year, tim.Month(), tim.Day(), tim.Hour(), tim.Minute(), tim.Second(), tim.Nanosecond(), tim.Location())
}
ts = Timestamp{Time: tim, Valid: true} ts = Timestamp{Time: tim, Valid: true}
} }

View File

@ -5,15 +5,21 @@ import (
"testing" "testing"
"time" "time"
pgx "github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype" "github.com/jackc/pgx/v5/pgtype"
"github.com/jackc/pgx/v5/pgtype/testutil" "github.com/jackc/pgx/v5/pgxtest"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestTimestampCodec(t *testing.T) { func TestTimestampCodec(t *testing.T) {
skipCockroachDB(t, "Server does not support infinite timestamps (see https://github.com/cockroachdb/cockroach/issues/41564)") skipCockroachDB(t, "Server does not support infinite timestamps (see https://github.com/cockroachdb/cockroach/issues/41564)")
testutil.RunTranscodeTests(t, "timestamp", []testutil.TranscodeTestCase{ pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "timestamp", []pgxtest.ValueRoundTripTest{
{time.Date(-100, 1, 1, 0, 0, 0, 0, time.UTC), new(time.Time), isExpectedEqTime(time.Date(-100, 1, 1, 0, 0, 0, 0, time.UTC))},
{time.Date(-1, 1, 1, 0, 0, 0, 0, time.UTC), new(time.Time), isExpectedEqTime(time.Date(-1, 1, 1, 0, 0, 0, 0, time.UTC))},
{time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), new(time.Time), isExpectedEqTime(time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC))},
{time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC), new(time.Time), isExpectedEqTime(time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC))},
{time.Date(1900, 1, 1, 0, 0, 0, 0, time.UTC), new(time.Time), isExpectedEqTime(time.Date(1900, 1, 1, 0, 0, 0, 0, time.UTC))}, {time.Date(1900, 1, 1, 0, 0, 0, 0, time.UTC), new(time.Time), isExpectedEqTime(time.Date(1900, 1, 1, 0, 0, 0, 0, time.UTC))},
{time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC), new(time.Time), isExpectedEqTime(time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC))}, {time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC), new(time.Time), isExpectedEqTime(time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC))},
{time.Date(1999, 12, 31, 0, 0, 0, 0, time.UTC), new(time.Time), isExpectedEqTime(time.Date(1999, 12, 31, 0, 0, 0, 0, time.UTC))}, {time.Date(1999, 12, 31, 0, 0, 0, 0, time.UTC), new(time.Time), isExpectedEqTime(time.Date(1999, 12, 31, 0, 0, 0, 0, time.UTC))},
@ -34,19 +40,18 @@ func TestTimestampCodec(t *testing.T) {
// https://github.com/jackc/pgx/v4/pgtype/pull/128 // https://github.com/jackc/pgx/v4/pgtype/pull/128
func TestTimestampTranscodeBigTimeBinary(t *testing.T) { func TestTimestampTranscodeBigTimeBinary(t *testing.T) {
conn := testutil.MustConnectPgx(t) defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
defer testutil.MustCloseContext(t, conn) in := &pgtype.Timestamp{Time: time.Date(294276, 12, 31, 23, 59, 59, 999999000, time.UTC), Valid: true}
var out pgtype.Timestamp
in := &pgtype.Timestamp{Time: time.Date(294276, 12, 31, 23, 59, 59, 999999000, time.UTC), Valid: true} err := conn.QueryRow(ctx, "select $1::timestamp", in).Scan(&out)
var out pgtype.Timestamp if err != nil {
t.Fatal(err)
}
err := conn.QueryRow(context.Background(), "select $1::timestamp", in).Scan(&out) require.Equal(t, in.Valid, out.Valid)
if err != nil { require.Truef(t, in.Time.Equal(out.Time), "expected %v got %v", in.Time, out.Time)
t.Fatal(err) })
}
require.Equal(t, in.Valid, out.Valid)
require.Truef(t, in.Time.Equal(out.Time), "expected %v got %v", in.Time, out.Time)
} }
// https://github.com/jackc/pgtype/issues/74 // https://github.com/jackc/pgtype/issues/74

View File

@ -5,6 +5,7 @@ import (
"encoding/binary" "encoding/binary"
"encoding/json" "encoding/json"
"fmt" "fmt"
"strings"
"time" "time"
"github.com/jackc/pgx/v5/internal/pgio" "github.com/jackc/pgx/v5/internal/pgio"
@ -184,11 +185,30 @@ func (encodePlanTimestamptzCodecText) Encode(value interface{}, buf []byte) (new
return nil, err return nil, err
} }
if !ts.Valid {
return nil, nil
}
var s string var s string
switch ts.InfinityModifier { switch ts.InfinityModifier {
case Finite: case Finite:
s = ts.Time.UTC().Truncate(time.Microsecond).Format(pgTimestamptzSecondFormat)
t := ts.Time.UTC().Truncate(time.Microsecond)
// Year 0000 is 1 BC
bc := false
if year := t.Year(); year <= 0 {
year = -year + 1
t = time.Date(year, t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), time.UTC)
bc = true
}
s = t.Format(pgTimestamptzSecondFormat)
if bc {
s = s + " BC"
}
case Infinity: case Infinity:
s = "infinity" s = "infinity"
case NegativeInfinity: case NegativeInfinity:
@ -267,6 +287,12 @@ func (scanPlanTextTimestamptzToTimestamptzScanner) Scan(src []byte, dst interfac
case "-infinity": case "-infinity":
tstz = Timestamptz{Valid: true, InfinityModifier: -Infinity} tstz = Timestamptz{Valid: true, InfinityModifier: -Infinity}
default: default:
bc := false
if strings.HasSuffix(sbuf, " BC") {
sbuf = sbuf[:len(sbuf)-3]
bc = true
}
var format string var format string
if len(sbuf) >= 9 && (sbuf[len(sbuf)-9] == '-' || sbuf[len(sbuf)-9] == '+') { if len(sbuf) >= 9 && (sbuf[len(sbuf)-9] == '-' || sbuf[len(sbuf)-9] == '+') {
format = pgTimestamptzSecondFormat format = pgTimestamptzSecondFormat
@ -281,6 +307,11 @@ func (scanPlanTextTimestamptzToTimestamptzScanner) Scan(src []byte, dst interfac
return err return err
} }
if bc {
year := -tim.Year() + 1
tim = time.Date(year, tim.Month(), tim.Day(), tim.Hour(), tim.Minute(), tim.Second(), tim.Nanosecond(), tim.Location())
}
tstz = Timestamptz{Time: tim, Valid: true} tstz = Timestamptz{Time: tim, Valid: true}
} }

View File

@ -5,15 +5,21 @@ import (
"testing" "testing"
"time" "time"
pgx "github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype" "github.com/jackc/pgx/v5/pgtype"
"github.com/jackc/pgx/v5/pgtype/testutil" "github.com/jackc/pgx/v5/pgxtest"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestTimestamptzCodec(t *testing.T) { func TestTimestamptzCodec(t *testing.T) {
skipCockroachDB(t, "Server does not support infinite timestamps (see https://github.com/cockroachdb/cockroach/issues/41564)") skipCockroachDB(t, "Server does not support infinite timestamps (see https://github.com/cockroachdb/cockroach/issues/41564)")
testutil.RunTranscodeTests(t, "timestamptz", []testutil.TranscodeTestCase{ pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "timestamptz", []pgxtest.ValueRoundTripTest{
{time.Date(-100, 1, 1, 0, 0, 0, 0, time.Local), new(time.Time), isExpectedEqTime(time.Date(-100, 1, 1, 0, 0, 0, 0, time.Local))},
{time.Date(-1, 1, 1, 0, 0, 0, 0, time.Local), new(time.Time), isExpectedEqTime(time.Date(-1, 1, 1, 0, 0, 0, 0, time.Local))},
{time.Date(0, 1, 1, 0, 0, 0, 0, time.Local), new(time.Time), isExpectedEqTime(time.Date(0, 1, 1, 0, 0, 0, 0, time.Local))},
{time.Date(1, 1, 1, 0, 0, 0, 0, time.Local), new(time.Time), isExpectedEqTime(time.Date(1, 1, 1, 0, 0, 0, 0, time.Local))},
{time.Date(1900, 1, 1, 0, 0, 0, 0, time.Local), new(time.Time), isExpectedEqTime(time.Date(1900, 1, 1, 0, 0, 0, 0, time.Local))}, {time.Date(1900, 1, 1, 0, 0, 0, 0, time.Local), new(time.Time), isExpectedEqTime(time.Date(1900, 1, 1, 0, 0, 0, 0, time.Local))},
{time.Date(1970, 1, 1, 0, 0, 0, 0, time.Local), new(time.Time), isExpectedEqTime(time.Date(1970, 1, 1, 0, 0, 0, 0, time.Local))}, {time.Date(1970, 1, 1, 0, 0, 0, 0, time.Local), new(time.Time), isExpectedEqTime(time.Date(1970, 1, 1, 0, 0, 0, 0, time.Local))},
{time.Date(1999, 12, 31, 0, 0, 0, 0, time.Local), new(time.Time), isExpectedEqTime(time.Date(1999, 12, 31, 0, 0, 0, 0, time.Local))}, {time.Date(1999, 12, 31, 0, 0, 0, 0, time.Local), new(time.Time), isExpectedEqTime(time.Date(1999, 12, 31, 0, 0, 0, 0, time.Local))},
@ -34,19 +40,18 @@ func TestTimestamptzCodec(t *testing.T) {
// https://github.com/jackc/pgx/v4/pgtype/pull/128 // https://github.com/jackc/pgx/v4/pgtype/pull/128
func TestTimestamptzTranscodeBigTimeBinary(t *testing.T) { func TestTimestamptzTranscodeBigTimeBinary(t *testing.T) {
conn := testutil.MustConnectPgx(t) defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
defer testutil.MustCloseContext(t, conn) in := &pgtype.Timestamptz{Time: time.Date(294276, 12, 31, 23, 59, 59, 999999000, time.UTC), Valid: true}
var out pgtype.Timestamptz
in := &pgtype.Timestamptz{Time: time.Date(294276, 12, 31, 23, 59, 59, 999999000, time.UTC), Valid: true} err := conn.QueryRow(ctx, "select $1::timestamptz", in).Scan(&out)
var out pgtype.Timestamptz if err != nil {
t.Fatal(err)
}
err := conn.QueryRow(context.Background(), "select $1::timestamptz", in).Scan(&out) require.Equal(t, in.Valid, out.Valid)
if err != nil { require.Truef(t, in.Time.Equal(out.Time), "expected %v got %v", in.Time, out.Time)
t.Fatal(err) })
}
require.Equal(t, in.Valid, out.Valid)
require.Truef(t, in.Time.Equal(out.Time), "expected %v got %v", in.Time, out.Time)
} }
// https://github.com/jackc/pgtype/issues/74 // https://github.com/jackc/pgtype/issues/74

View File

@ -1,14 +1,15 @@
package pgtype_test package pgtype_test
import ( import (
"context"
"testing" "testing"
"github.com/jackc/pgx/v5/pgtype" "github.com/jackc/pgx/v5/pgtype"
"github.com/jackc/pgx/v5/pgtype/testutil" "github.com/jackc/pgx/v5/pgxtest"
) )
func TestUint32Codec(t *testing.T) { func TestUint32Codec(t *testing.T) {
testutil.RunTranscodeTests(t, "oid", []testutil.TranscodeTestCase{ pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, pgxtest.KnownOIDQueryExecModes, "oid", []pgxtest.ValueRoundTripTest{
{ {
pgtype.Uint32{Uint32: pgtype.TextOID, Valid: true}, pgtype.Uint32{Uint32: pgtype.TextOID, Valid: true},
new(pgtype.Uint32), new(pgtype.Uint32),

View File

@ -1,31 +1,22 @@
package pgtype_test package pgtype_test
import ( import (
"context"
"reflect" "reflect"
"testing" "testing"
"github.com/jackc/pgx/v5/pgtype" "github.com/jackc/pgx/v5/pgtype"
"github.com/jackc/pgx/v5/pgtype/testutil" "github.com/jackc/pgx/v5/pgxtest"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestUUIDCodec(t *testing.T) { func TestUUIDCodec(t *testing.T) {
testutil.RunTranscodeTests(t, "uuid", []testutil.TranscodeTestCase{ pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "uuid", []pgxtest.ValueRoundTripTest{
{ {
pgtype.UUID{Bytes: [16]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, Valid: true}, pgtype.UUID{Bytes: [16]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, Valid: true},
new(pgtype.UUID), new(pgtype.UUID),
isExpectedEq(pgtype.UUID{Bytes: [16]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, Valid: true}), isExpectedEq(pgtype.UUID{Bytes: [16]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, Valid: true}),
}, },
{
[16]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
new(pgtype.UUID),
isExpectedEq(pgtype.UUID{Bytes: [16]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, Valid: true}),
},
{
[]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
new(pgtype.UUID),
isExpectedEq(pgtype.UUID{Bytes: [16]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, Valid: true}),
},
{ {
"00010203-0405-0607-0809-0a0b0c0d0e0f", "00010203-0405-0607-0809-0a0b0c0d0e0f",
new(pgtype.UUID), new(pgtype.UUID),
@ -40,6 +31,19 @@ func TestUUIDCodec(t *testing.T) {
{pgtype.UUID{}, new(pgtype.UUID), isExpectedEq(pgtype.UUID{})}, {pgtype.UUID{}, new(pgtype.UUID), isExpectedEq(pgtype.UUID{})},
{nil, new(pgtype.UUID), isExpectedEq(pgtype.UUID{})}, {nil, new(pgtype.UUID), isExpectedEq(pgtype.UUID{})},
}) })
pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, pgxtest.KnownOIDQueryExecModes, "uuid", []pgxtest.ValueRoundTripTest{
{
[16]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
new(pgtype.UUID),
isExpectedEq(pgtype.UUID{Bytes: [16]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, Valid: true}),
},
{
[]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
new(pgtype.UUID),
isExpectedEq(pgtype.UUID{Bytes: [16]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, Valid: true}),
},
})
} }
func TestUUID_MarshalJSON(t *testing.T) { func TestUUID_MarshalJSON(t *testing.T) {

View File

@ -1,10 +1,11 @@
package zeronull_test package zeronull_test
import ( import (
"context"
"testing" "testing"
"github.com/jackc/pgx/v5/pgtype/testutil"
"github.com/jackc/pgx/v5/pgtype/zeronull" "github.com/jackc/pgx/v5/pgtype/zeronull"
"github.com/jackc/pgx/v5/pgxtest"
) )
func isExpectedEq(a interface{}) func(interface{}) bool { func isExpectedEq(a interface{}) func(interface{}) bool {
@ -14,7 +15,7 @@ func isExpectedEq(a interface{}) func(interface{}) bool {
} }
func TestFloat8Transcode(t *testing.T) { func TestFloat8Transcode(t *testing.T) {
testutil.RunTranscodeTests(t, "float8", []testutil.TranscodeTestCase{ pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "float8", []pgxtest.ValueRoundTripTest{
{ {
(zeronull.Float8)(1), (zeronull.Float8)(1),
new(zeronull.Float8), new(zeronull.Float8),

View File

@ -2,14 +2,15 @@
package zeronull_test package zeronull_test
import ( import (
"context"
"testing" "testing"
"github.com/jackc/pgx/v5/pgtype/testutil"
"github.com/jackc/pgx/v5/pgtype/zeronull" "github.com/jackc/pgx/v5/pgtype/zeronull"
"github.com/jackc/pgx/v5/pgxtest"
) )
func TestInt2Transcode(t *testing.T) { func TestInt2Transcode(t *testing.T) {
testutil.RunTranscodeTests(t, "int2", []testutil.TranscodeTestCase{ pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "int2", []pgxtest.ValueRoundTripTest{
{ {
(zeronull.Int2)(1), (zeronull.Int2)(1),
new(zeronull.Int2), new(zeronull.Int2),
@ -29,7 +30,7 @@ func TestInt2Transcode(t *testing.T) {
} }
func TestInt4Transcode(t *testing.T) { func TestInt4Transcode(t *testing.T) {
testutil.RunTranscodeTests(t, "int4", []testutil.TranscodeTestCase{ pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "int4", []pgxtest.ValueRoundTripTest{
{ {
(zeronull.Int4)(1), (zeronull.Int4)(1),
new(zeronull.Int4), new(zeronull.Int4),
@ -49,7 +50,7 @@ func TestInt4Transcode(t *testing.T) {
} }
func TestInt8Transcode(t *testing.T) { func TestInt8Transcode(t *testing.T) {
testutil.RunTranscodeTests(t, "int8", []testutil.TranscodeTestCase{ pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "int8", []pgxtest.ValueRoundTripTest{
{ {
(zeronull.Int8)(1), (zeronull.Int8)(1),
new(zeronull.Int8), new(zeronull.Int8),

View File

@ -10,7 +10,7 @@ import (
<% [2, 4, 8].each do |pg_byte_size| %> <% [2, 4, 8].each do |pg_byte_size| %>
<% pg_bit_size = pg_byte_size * 8 %> <% pg_bit_size = pg_byte_size * 8 %>
func TestInt<%= pg_byte_size %>Transcode(t *testing.T) { func TestInt<%= pg_byte_size %>Transcode(t *testing.T) {
testutil.RunTranscodeTests(t, "int<%= pg_byte_size %>", []testutil.TranscodeTestCase{ pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "int<%= pg_byte_size %>", []pgxtest.ValueRoundTripTest{
{ {
(zeronull.Int<%= pg_byte_size %>)(1), (zeronull.Int<%= pg_byte_size %>)(1),
new(zeronull.Int<%= pg_byte_size %>), new(zeronull.Int<%= pg_byte_size %>),

View File

@ -1,14 +1,15 @@
package zeronull_test package zeronull_test
import ( import (
"context"
"testing" "testing"
"github.com/jackc/pgx/v5/pgtype/testutil"
"github.com/jackc/pgx/v5/pgtype/zeronull" "github.com/jackc/pgx/v5/pgtype/zeronull"
"github.com/jackc/pgx/v5/pgxtest"
) )
func TestTextTranscode(t *testing.T) { func TestTextTranscode(t *testing.T) {
testutil.RunTranscodeTests(t, "text", []testutil.TranscodeTestCase{ pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "text", []pgxtest.ValueRoundTripTest{
{ {
(zeronull.Text)("foo"), (zeronull.Text)("foo"),
new(zeronull.Text), new(zeronull.Text),

View File

@ -1,11 +1,12 @@
package zeronull_test package zeronull_test
import ( import (
"context"
"testing" "testing"
"time" "time"
"github.com/jackc/pgx/v5/pgtype/testutil"
"github.com/jackc/pgx/v5/pgtype/zeronull" "github.com/jackc/pgx/v5/pgtype/zeronull"
"github.com/jackc/pgx/v5/pgxtest"
) )
func isExpectedEqTimestamp(a interface{}) func(interface{}) bool { func isExpectedEqTimestamp(a interface{}) func(interface{}) bool {
@ -18,7 +19,7 @@ func isExpectedEqTimestamp(a interface{}) func(interface{}) bool {
} }
func TestTimestampTranscode(t *testing.T) { func TestTimestampTranscode(t *testing.T) {
testutil.RunTranscodeTests(t, "timestamp", []testutil.TranscodeTestCase{ pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "timestamp", []pgxtest.ValueRoundTripTest{
{ {
(zeronull.Timestamp)(time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)), (zeronull.Timestamp)(time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)),
new(zeronull.Timestamp), new(zeronull.Timestamp),

View File

@ -1,11 +1,12 @@
package zeronull_test package zeronull_test
import ( import (
"context"
"testing" "testing"
"time" "time"
"github.com/jackc/pgx/v5/pgtype/testutil"
"github.com/jackc/pgx/v5/pgtype/zeronull" "github.com/jackc/pgx/v5/pgtype/zeronull"
"github.com/jackc/pgx/v5/pgxtest"
) )
func isExpectedEqTimestamptz(a interface{}) func(interface{}) bool { func isExpectedEqTimestamptz(a interface{}) func(interface{}) bool {
@ -18,7 +19,7 @@ func isExpectedEqTimestamptz(a interface{}) func(interface{}) bool {
} }
func TestTimestamptzTranscode(t *testing.T) { func TestTimestamptzTranscode(t *testing.T) {
testutil.RunTranscodeTests(t, "timestamptz", []testutil.TranscodeTestCase{ pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "timestamptz", []pgxtest.ValueRoundTripTest{
{ {
(zeronull.Timestamptz)(time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)), (zeronull.Timestamptz)(time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)),
new(zeronull.Timestamptz), new(zeronull.Timestamptz),

View File

@ -1,14 +1,15 @@
package zeronull_test package zeronull_test
import ( import (
"context"
"testing" "testing"
"github.com/jackc/pgx/v5/pgtype/testutil"
"github.com/jackc/pgx/v5/pgtype/zeronull" "github.com/jackc/pgx/v5/pgtype/zeronull"
"github.com/jackc/pgx/v5/pgxtest"
) )
func TestUUIDTranscode(t *testing.T) { func TestUUIDTranscode(t *testing.T) {
testutil.RunTranscodeTests(t, "uuid", []testutil.TranscodeTestCase{ pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "uuid", []pgxtest.ValueRoundTripTest{
{ {
(zeronull.UUID)([16]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}), (zeronull.UUID)([16]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}),
new(zeronull.UUID), new(zeronull.UUID),

View File

@ -0,0 +1,17 @@
package zeronull
import (
"github.com/jackc/pgx/v5/pgtype"
)
// Register registers the zeronull types so they can be used in query exec modes that do not know the server OIDs.
func Register(m *pgtype.Map) {
m.RegisterDefaultPgType(Float8(0), "float8")
m.RegisterDefaultPgType(Int2(0), "int2")
m.RegisterDefaultPgType(Int4(0), "int4")
m.RegisterDefaultPgType(Int8(0), "int8")
m.RegisterDefaultPgType(Text(""), "text")
m.RegisterDefaultPgType(Timestamp{}, "timestamp")
m.RegisterDefaultPgType(Timestamptz{}, "timestamptz")
m.RegisterDefaultPgType(UUID{}, "uuid")
}

View File

@ -0,0 +1,26 @@
package zeronull_test
import (
"context"
"os"
"testing"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype/zeronull"
"github.com/jackc/pgx/v5/pgxtest"
"github.com/stretchr/testify/require"
)
var defaultConnTestRunner pgxtest.ConnTestRunner
func init() {
defaultConnTestRunner = pgxtest.DefaultConnTestRunner()
defaultConnTestRunner.CreateConfig = func(ctx context.Context, t testing.TB) *pgx.ConnConfig {
config, err := pgx.ParseConfig(os.Getenv("PGX_TEST_DATABASE"))
require.NoError(t, err)
return config
}
defaultConnTestRunner.AfterConnect = func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
zeronull.Register(conn.TypeMap())
}
}

View File

@ -3,11 +3,28 @@ package pgxtest
import ( import (
"context" "context"
"fmt"
"reflect"
"testing" "testing"
"github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5"
) )
var AllQueryExecModes = []pgx.QueryExecMode{
pgx.QueryExecModeCacheStatement,
pgx.QueryExecModeCacheDescribe,
pgx.QueryExecModeDescribeExec,
pgx.QueryExecModeExec,
pgx.QueryExecModeSimpleProtocol,
}
// KnownOIDQueryExecModes is a slice of all query exec modes where the param and result OIDs are known before sending the query.
var KnownOIDQueryExecModes = []pgx.QueryExecMode{
pgx.QueryExecModeCacheStatement,
pgx.QueryExecModeCacheDescribe,
pgx.QueryExecModeDescribeExec,
}
// ConnTestRunner controls how a *pgx.Conn is created and closed by tests. All fields are required. Use DefaultConnTestRunner to get a // ConnTestRunner controls how a *pgx.Conn is created and closed by tests. All fields are required. Use DefaultConnTestRunner to get a
// ConnTestRunner with reasonable default values. // ConnTestRunner with reasonable default values.
type ConnTestRunner struct { type ConnTestRunner struct {
@ -46,6 +63,8 @@ func DefaultConnTestRunner() ConnTestRunner {
} }
func (ctr *ConnTestRunner) RunTest(ctx context.Context, t testing.TB, f func(ctx context.Context, t testing.TB, conn *pgx.Conn)) { func (ctr *ConnTestRunner) RunTest(ctx context.Context, t testing.TB, f func(ctx context.Context, t testing.TB, conn *pgx.Conn)) {
t.Helper()
config := ctr.CreateConfig(ctx, t) config := ctr.CreateConfig(ctx, t)
conn, err := pgx.ConnectConfig(ctx, config) conn, err := pgx.ConnectConfig(ctx, config)
if err != nil { if err != nil {
@ -62,13 +81,7 @@ func (ctr *ConnTestRunner) RunTest(ctx context.Context, t testing.TB, f func(ctx
// If modes is nil all pgx.QueryExecModes are tested. // If modes is nil all pgx.QueryExecModes are tested.
func RunWithQueryExecModes(ctx context.Context, t *testing.T, ctr ConnTestRunner, modes []pgx.QueryExecMode, f func(ctx context.Context, t testing.TB, conn *pgx.Conn)) { func RunWithQueryExecModes(ctx context.Context, t *testing.T, ctr ConnTestRunner, modes []pgx.QueryExecMode, f func(ctx context.Context, t testing.TB, conn *pgx.Conn)) {
if modes == nil { if modes == nil {
modes = []pgx.QueryExecMode{ modes = AllQueryExecModes
pgx.QueryExecModeCacheStatement,
pgx.QueryExecModeCacheDescribe,
pgx.QueryExecModeDescribeExec,
pgx.QueryExecModeExec,
pgx.QueryExecModeSimpleProtocol,
}
} }
for _, mode := range modes { for _, mode := range modes {
@ -87,6 +100,51 @@ func RunWithQueryExecModes(ctx context.Context, t *testing.T, ctr ConnTestRunner
} }
} }
type ValueRoundTripTest struct {
Param interface{}
Result interface{}
Test func(interface{}) bool
}
func RunValueRoundTripTests(
ctx context.Context,
t testing.TB,
ctr ConnTestRunner,
modes []pgx.QueryExecMode,
pgTypeName string,
tests []ValueRoundTripTest,
) {
t.Helper()
if modes == nil {
modes = AllQueryExecModes
}
ctr.RunTest(ctx, t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
t.Helper()
sql := fmt.Sprintf("select $1::%s", pgTypeName)
for i, tt := range tests {
for _, mode := range modes {
err := conn.QueryRow(ctx, sql, mode, tt.Param).Scan(tt.Result)
if err != nil {
t.Errorf("%d. %v: %v", i, mode, err)
}
result := reflect.ValueOf(tt.Result)
if result.Kind() == reflect.Ptr {
result = result.Elem()
}
if !tt.Test(result.Interface()) {
t.Errorf("%d. %v: unexpected result for %v: %v", i, mode, tt.Param, result.Interface())
}
}
}
})
}
// SkipCockroachDB calls Skip on t with msg if the connection is to a CockroachDB server. // SkipCockroachDB calls Skip on t with msg if the connection is to a CockroachDB server.
func SkipCockroachDB(t testing.TB, conn *pgx.Conn, msg string) { func SkipCockroachDB(t testing.TB, conn *pgx.Conn, msg string) {
if conn.PgConn().ParameterStatus("crdb_version") != "" { if conn.PgConn().ParameterStatus("crdb_version") != "" {

View File

@ -17,7 +17,7 @@ func convertSimpleArgument(m *pgtype.Map, arg interface{}) (interface{}, error)
return nil, nil return nil, nil
} }
buf, err := m.Encode(0, TextFormatCode, arg, nil) buf, err := m.Encode(0, TextFormatCode, arg, []byte{})
if err != nil { if err != nil {
return nil, err return nil, err
} }