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
// no way to safely use binary or to specify the parameter OIDs.
func (c *Conn) appendParamsForQueryExecModeExec(args []interface{}) error {
for i := range args {
if args[i] == nil {
err := c.eqb.AppendParamFormat(c.typeMap, 0, TextFormatCode, args[i])
for _, arg := range args {
if arg == nil {
err := c.eqb.AppendParamFormat(c.typeMap, 0, TextFormatCode, arg)
if err != nil {
return err
}
} else {
dt, ok := c.TypeMap().TypeForValue(args[i])
dt, ok := c.TypeMap().TypeForValue(arg)
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 {
return err
}

View File

@ -4,191 +4,184 @@ import (
"context"
"testing"
"github.com/jackc/pgx/v5/pgtype/testutil"
pgx "github.com/jackc/pgx/v5"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestArrayCodec(t *testing.T) {
conn := testutil.MustConnectPgx(t)
defer testutil.MustCloseContext(t, conn)
defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.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 {
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 }
newInt16 := func(n int16) *int16 { return &n }
for i, tt := range []struct {
expected interface{}
}{
{[]*int16{newInt16(1), nil, newInt16(3), nil, newInt16(5)}},
} {
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)
}
for i, tt := range []struct {
expected interface{}
}{
{[]*int16{newInt16(1), nil, newInt16(3), nil, newInt16(5)}},
} {
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)
}
})
}
func TestArrayCodecAnySlice(t *testing.T) {
conn := testutil.MustConnectPgx(t)
defer testutil.MustCloseContext(t, conn)
defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
type _int16Slice []int16
type _int16Slice []int16
for i, tt := range []struct {
expected interface{}
}{
{_int16Slice(nil)},
{_int16Slice{}},
{_int16Slice{1, 2, 3}},
} {
var actual _int16Slice
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)
}
for i, tt := range []struct {
expected interface{}
}{
{_int16Slice(nil)},
{_int16Slice{}},
{_int16Slice{1, 2, 3}},
} {
var actual _int16Slice
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)
}
})
}
func TestArrayCodecDecodeValue(t *testing.T) {
conn := testutil.MustConnectPgx(t)
defer testutil.MustCloseContext(t, conn)
for _, tt := range []struct {
sql string
expected interface{}
}{
{
sql: `select '{}'::int4[]`,
expected: []interface{}{},
},
{
sql: `select '{1,2}'::int8[]`,
expected: []interface{}{int64(1), int64(2)},
},
{
sql: `select '{foo,bar}'::text[]`,
expected: []interface{}{"foo", "bar"},
},
} {
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()
defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, _ testing.TB, conn *pgx.Conn) {
for _, tt := range []struct {
sql string
expected interface{}
}{
{
sql: `select '{}'::int4[]`,
expected: []interface{}{},
},
{
sql: `select '{1,2}'::int8[]`,
expected: []interface{}{int64(1), int64(2)},
},
{
sql: `select '{foo,bar}'::text[]`,
expected: []interface{}{"foo", "bar"},
},
} {
t.Run(tt.sql, func(t *testing.T) {
rows, err := conn.Query(ctx, tt.sql)
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) {
skipCockroachDB(t, "Server does not support nested arrays (https://github.com/cockroachdb/cockroach/issues/36815)")
conn := testutil.MustConnectPgx(t)
defer testutil.MustCloseContext(t, conn)
defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
rows, err := conn.Query(context.Background(), `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)
rows, err := conn.Query(ctx, `select '{{1,2,3,4}, {5,6,7,8}, {9,10,11,12}}'::int4[]`)
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) {
skipCockroachDB(t, "Server does not support nested arrays (https://github.com/cockroachdb/cockroach/issues/36815)")
conn := testutil.MustConnectPgx(t)
defer testutil.MustCloseContext(t, conn)
rows, err := conn.Query(context.Background(), `select '{}'::int4[]`)
require.NoError(t, err)
for rows.Next() {
var ss [][]int32
err := rows.Scan(&ss)
defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
rows, err := conn.Query(ctx, `select '{}'::int4[]`)
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) {
skipCockroachDB(t, "Server does not support nested arrays (https://github.com/cockroachdb/cockroach/issues/36815)")
conn := testutil.MustConnectPgx(t)
defer testutil.MustCloseContext(t, conn)
defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.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[]`)
require.NoError(t, err)
for rows.Next() {
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")
}
for rows.Next() {
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) {
skipCockroachDB(t, "Server does not support nested arrays (https://github.com/cockroachdb/cockroach/issues/36815)")
conn := testutil.MustConnectPgx(t)
defer testutil.MustCloseContext(t, conn)
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)
defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
rows, err := conn.Query(ctx, `select $1::int4[]`, [][]int32{{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}})
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) {
skipCockroachDB(t, "Server does not support nested arrays (https://github.com/cockroachdb/cockroach/issues/36815)")
conn := testutil.MustConnectPgx(t)
defer testutil.MustCloseContext(t, conn)
rows, err := conn.Query(context.Background(), `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")
defer rows.Close()
defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.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")
defer rows.Close()
})
}

View File

@ -2,10 +2,11 @@ package pgtype_test
import (
"bytes"
"context"
"testing"
"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 {
@ -17,7 +18,7 @@ func isExpectedEqBits(a interface{}) func(interface{}) bool {
}
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},
new(pgtype.Bits),
@ -34,7 +35,7 @@ func TestBitsCodecBit(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},
new(pgtype.Bits),

View File

@ -1,14 +1,15 @@
package pgtype_test
import (
"context"
"testing"
"github.com/jackc/pgx/v5/pgtype"
"github.com/jackc/pgx/v5/pgtype/testutil"
"github.com/jackc/pgx/v5/pgxtest"
)
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)},
{false, new(bool), isExpectedEq(false)},
{true, new(pgtype.Bool), isExpectedEq(pgtype.Bool{Bool: true, Valid: true})},

View File

@ -1,16 +1,17 @@
package pgtype_test
import (
"context"
"testing"
"github.com/jackc/pgx/v5/pgtype"
"github.com/jackc/pgx/v5/pgtype/testutil"
"github.com/jackc/pgx/v5/pgxtest"
)
func TestBoxCodec(t *testing.T) {
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{
P: [2]pgtype.Vec2{{7.1, 5.2345678}, {3.14, 1.678}},

View File

@ -5,8 +5,9 @@ import (
"context"
"testing"
pgx "github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype"
"github.com/jackc/pgx/v5/pgtype/testutil"
"github.com/jackc/pgx/v5/pgxtest"
"github.com/stretchr/testify/require"
)
@ -28,7 +29,7 @@ func isExpectedEqBytes(a interface{}) func(interface{}) bool {
}
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{}, new([]byte), isExpectedEqBytes([]byte{})},
{[]byte(nil), new([]byte), isExpectedEqBytes([]byte(nil))},
@ -37,91 +38,79 @@ func TestByteaCodec(t *testing.T) {
}
func TestDriverBytesQueryRow(t *testing.T) {
conn := testutil.MustConnectPgx(t)
defer testutil.MustCloseContext(t, conn)
ctx := context.Background()
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")
defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
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) {
conn := testutil.MustConnectPgx(t)
defer testutil.MustCloseContext(t, conn)
ctx := context.Background()
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
defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
argBuf := make([]byte, 128)
for i := range argBuf {
argBuf[i] = byte(i)
}
err = rows.Scan((*pgtype.DriverBytes)(&resultBuf))
rows, err := conn.Query(ctx, `select $1::bytea from generate_series(1, 1000)`, argBuf)
require.NoError(t, err)
defer rows.Close()
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)")
}
rowCount := 0
resultBuf := argBuf
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()
require.NoError(t, err)
err = rows.Scan((*pgtype.DriverBytes)(&resultBuf))
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) {
conn := testutil.MustConnectPgx(t)
defer testutil.MustCloseContext(t, conn)
defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.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}
buf := origBuf
err := conn.QueryRow(ctx, `select $1::bytea`, []byte{1, 2}).Scan((*pgtype.PreallocBytes)(&buf))
require.NoError(t, err)
require.Equal(t, []byte{1, 2, 7, 8}, origBuf)
require.Len(t, buf, 2)
require.Equal(t, 4, cap(buf))
require.Equal(t, buf, []byte{1, 2})
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)
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)
require.Equal(t, []byte{1, 2, 7, 8}, origBuf)
})
}
func TestUndecodedBytes(t *testing.T) {
conn := testutil.MustConnectPgx(t)
defer testutil.MustCloseContext(t, conn)
defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
var buf []byte
err := conn.QueryRow(ctx, `select 1::int4`).Scan((*pgtype.UndecodedBytes)(&buf))
require.NoError(t, err)
ctx := context.Background()
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})
require.Len(t, buf, 4)
require.Equal(t, buf, []byte{0, 0, 0, 1})
})
}

View File

@ -1,16 +1,17 @@
package pgtype_test
import (
"context"
"testing"
"github.com/jackc/pgx/v5/pgtype"
"github.com/jackc/pgx/v5/pgtype/testutil"
"github.com/jackc/pgx/v5/pgxtest"
)
func TestCircleTranscode(t *testing.T) {
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},
new(pgtype.Circle),

View File

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

View File

@ -1,11 +1,12 @@
package pgtype_test
import (
"context"
"testing"
"time"
"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 {
@ -18,7 +19,7 @@ func isExpectedEqTime(a interface{}) func(interface{}) bool {
}
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(-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))},

View File

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

View File

@ -1,14 +1,15 @@
package pgtype_test
import (
"context"
"testing"
"github.com/jackc/pgx/v5/pgtype"
"github.com/jackc/pgx/v5/pgtype/testutil"
"github.com/jackc/pgx/v5/pgxtest"
)
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: 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})},

View File

@ -1,14 +1,15 @@
package pgtype_test
import (
"context"
"testing"
"github.com/jackc/pgx/v5/pgtype"
"github.com/jackc/pgx/v5/pgtype/testutil"
"github.com/jackc/pgx/v5/pgxtest"
)
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: 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})},

View File

@ -6,7 +6,7 @@ import (
"github.com/jackc/pgx/v5"
"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 {
@ -52,30 +52,22 @@ func isExpectedEqMapStringPointerString(a interface{}) func(interface{}) bool {
}
func TestHstoreCodec(t *testing.T) {
conn := testutil.MustConnectPgx(t)
defer testutil.MustCloseContext(t, conn)
ctr := defaultConnTestRunner
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
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},
conn.TypeMap().RegisterType(&pgtype.Type{Name: "hstore", OID: hstoreOID, Codec: pgtype.HstoreCodec{}})
}
fs := func(s string) *string {
return &s
}
tests := []testutil.TranscodeTestCase{
tests := []pgxtest.ValueRoundTripTest{
{
map[string]string{},
new(map[string]string),
@ -134,25 +126,25 @@ func TestHstoreCodec(t *testing.T) {
// Special key values
// at beginning
tests = append(tests, testutil.TranscodeTestCase{
tests = append(tests, pgxtest.ValueRoundTripTest{
map[string]string{s + "foo": "bar"},
new(map[string]string),
isExpectedEqMapStringString(map[string]string{s + "foo": "bar"}),
})
// in middle
tests = append(tests, testutil.TranscodeTestCase{
tests = append(tests, pgxtest.ValueRoundTripTest{
map[string]string{"foo" + s + "bar": "bar"},
new(map[string]string),
isExpectedEqMapStringString(map[string]string{"foo" + s + "bar": "bar"}),
})
// at end
tests = append(tests, testutil.TranscodeTestCase{
tests = append(tests, pgxtest.ValueRoundTripTest{
map[string]string{"foo" + s: "bar"},
new(map[string]string),
isExpectedEqMapStringString(map[string]string{"foo" + s: "bar"}),
})
// is key
tests = append(tests, testutil.TranscodeTestCase{
tests = append(tests, pgxtest.ValueRoundTripTest{
map[string]string{s: "bar"},
new(map[string]string),
isExpectedEqMapStringString(map[string]string{s: "bar"}),
@ -161,32 +153,30 @@ func TestHstoreCodec(t *testing.T) {
// Special value values
// at beginning
tests = append(tests, testutil.TranscodeTestCase{
tests = append(tests, pgxtest.ValueRoundTripTest{
map[string]string{"foo": s + "bar"},
new(map[string]string),
isExpectedEqMapStringString(map[string]string{"foo": s + "bar"}),
})
// in middle
tests = append(tests, testutil.TranscodeTestCase{
tests = append(tests, pgxtest.ValueRoundTripTest{
map[string]string{"foo": "foo" + s + "bar"},
new(map[string]string),
isExpectedEqMapStringString(map[string]string{"foo": "foo" + s + "bar"}),
})
// at end
tests = append(tests, testutil.TranscodeTestCase{
tests = append(tests, pgxtest.ValueRoundTripTest{
map[string]string{"foo": "foo" + s},
new(map[string]string),
isExpectedEqMapStringString(map[string]string{"foo": "foo" + s}),
})
// is key
tests = append(tests, testutil.TranscodeTestCase{
tests = append(tests, pgxtest.ValueRoundTripTest{
map[string]string{"foo": s},
new(map[string]string),
isExpectedEqMapStringString(map[string]string{"foo": s}),
})
}
for _, format := range formats {
testutil.RunTranscodeTestsFormat(t, "hstore", tests, conn, format.name, format.code)
}
pgxtest.RunValueRoundTripTests(context.Background(), t, ctr, pgxtest.KnownOIDQueryExecModes, "hstore", tests)
}

View File

@ -1,11 +1,12 @@
package pgtype_test
import (
"context"
"net"
"testing"
"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 {
@ -18,7 +19,7 @@ func isExpectedEqIPNet(a interface{}) func(interface{}) bool {
}
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, "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"))},
@ -37,7 +38,7 @@ func TestInetTranscode(t *testing.T) {
func TestCidrTranscode(t *testing.T) {
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, "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"))},

View File

@ -2,15 +2,16 @@
package pgtype_test
import (
"context"
"math"
"testing"
"github.com/jackc/pgx/v5/pgtype"
"github.com/jackc/pgx/v5/pgtype/testutil"
"github.com/jackc/pgx/v5/pgxtest"
)
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))},
{int16(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) {
testutil.RunTranscodeTests(t, "int4", []testutil.TranscodeTestCase{
pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "int4", []pgxtest.ValueRoundTripTest{
{int8(1), new(int32), isExpectedEq(int32(1))},
{int16(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) {
testutil.RunTranscodeTests(t, "int8", []testutil.TranscodeTestCase{
pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "int8", []pgxtest.ValueRoundTripTest{
{int8(1), new(int64), isExpectedEq(int64(1))},
{int16(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| %>
<% pg_bit_size = pg_byte_size * 8 %>
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))},
{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))},

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -1,15 +1,16 @@
package pgtype_test
import (
"context"
"testing"
"time"
"github.com/jackc/pgx/v5/pgtype"
"github.com/jackc/pgx/v5/pgtype/testutil"
"github.com/jackc/pgx/v5/pgxtest"
)
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},
new(pgtype.Interval),

View File

@ -1,9 +1,10 @@
package pgtype_test
import (
"context"
"testing"
"github.com/jackc/pgx/v5/pgtype/testutil"
"github.com/jackc/pgx/v5/pgxtest"
)
func isExpectedEqMap(a interface{}) func(interface{}) bool {
@ -39,7 +40,15 @@ func TestJSONCodec(t *testing.T) {
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("null"), new([]byte), isExpectedEqBytes([]byte("null"))},
{[]byte("42"), new([]byte), isExpectedEqBytes([]byte("42"))},
@ -47,10 +56,5 @@ func TestJSONCodec(t *testing.T) {
{[]byte(`"hello"`), new(string), isExpectedEq(`"hello"`)},
{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})},
{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
import (
"context"
"testing"
"github.com/jackc/pgx/v5/pgtype/testutil"
"github.com/jackc/pgx/v5/pgxtest"
)
func TestJSONBTranscode(t *testing.T) {
@ -12,7 +13,15 @@ func TestJSONBTranscode(t *testing.T) {
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("null"), new([]byte), isExpectedEqBytes([]byte("null"))},
{[]byte("42"), new([]byte), isExpectedEqBytes([]byte("42"))},
@ -20,10 +29,5 @@ func TestJSONBTranscode(t *testing.T) {
{[]byte(`"hello"`), new(string), isExpectedEq(`"hello"`)},
{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})},
{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"
"testing"
pgx "github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype"
"github.com/jackc/pgx/v5/pgtype/testutil"
"github.com/jackc/pgx/v5/pgxtest"
)
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)
defer conn.Close(context.Background())
if _, ok := conn.TypeMap().TypeForName("line"); !ok {
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 :(
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{
pgxtest.RunValueRoundTripTests(context.Background(), t, ctr, nil, "line", []pgxtest.ValueRoundTripTest{
{
pgtype.Line{
A: 1.23, B: 4.56, C: 7.89012345,

View File

@ -1,16 +1,17 @@
package pgtype_test
import (
"context"
"testing"
"github.com/jackc/pgx/v5/pgtype"
"github.com/jackc/pgx/v5/pgtype/testutil"
"github.com/jackc/pgx/v5/pgxtest"
)
func TestLsegTranscode(t *testing.T) {
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{
P: [2]pgtype.Vec2{{3.14, 1.678}, {7.1, 5.2345678901}},

View File

@ -2,10 +2,11 @@ package pgtype_test
import (
"bytes"
"context"
"net"
"testing"
"github.com/jackc/pgx/v5/pgtype/testutil"
"github.com/jackc/pgx/v5/pgxtest"
)
func isExpectedEqHardwareAddr(a interface{}) func(interface{}) bool {
@ -28,7 +29,8 @@ func isExpectedEqHardwareAddr(a interface{}) func(interface{}) bool {
func TestMacaddrCodec(t *testing.T) {
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"),
new(net.HardwareAddr),

View File

@ -9,8 +9,9 @@ import (
"strconv"
"testing"
pgx "github.com/jackc/pgx/v5"
"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/require"
)
@ -77,7 +78,7 @@ func TestNumericCodec(t *testing.T) {
max.Add(max, big.NewInt(1))
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, "3.14159"), new(pgtype.Numeric), isExpectedEqNumeric(mustParseNumeric(t, "3.14159"))},
{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")
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))},
{float32(math.Inf(1)), new(float32), isExpectedEq(float32(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.SetString("9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999", 10)
tests := make([]testutil.TranscodeTestCase, 0, 2000)
tests := make([]pgxtest.ValueRoundTripTest, 0, 2000)
for i := 0; i < 10; i++ {
for j := -50; j < 50; j++ {
num := (&big.Int{}).Rand(r, max)
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.Neg(num)
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) {
skipCockroachDB(t, "server formats numeric text format differently")
conn := testutil.MustConnectPgx(t)
defer testutil.MustCloseContext(t, conn)
defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
for i, tt := range []struct {
decString string
}{
{"NaN"},
{"0"},
{"1"},
{"-1"},
{"1000000000000000000"},
{"1234.56789"},
{"1.56789"},
{"0.00000000000056789"},
{"0.00123000"},
{"123e-3"},
{"243723409723490243842378942378901237502734019231380123e23790"},
{"3409823409243892349028349023482934092340892390101e-14021"},
} {
var num pgtype.Numeric
var pgJSON string
err := conn.QueryRow(context.Background(), `select $1::numeric, to_json($1::numeric)`, tt.decString).Scan(&num, &pgJSON)
require.NoErrorf(t, err, "%d", i)
for i, tt := range []struct {
decString string
}{
{"NaN"},
{"0"},
{"1"},
{"-1"},
{"1000000000000000000"},
{"1234.56789"},
{"1.56789"},
{"0.00000000000056789"},
{"0.00123000"},
{"123e-3"},
{"243723409723490243842378942378901237502734019231380123e23790"},
{"3409823409243892349028349023482934092340892390101e-14021"},
} {
var num pgtype.Numeric
var pgJSON string
err := conn.QueryRow(ctx, `select $1::numeric, to_json($1::numeric)`, tt.decString).Scan(&num, &pgJSON)
require.NoErrorf(t, err, "%d", i)
goJSON, err := json.Marshal(num)
require.NoErrorf(t, err, "%d", i)
goJSON, err := json.Marshal(num)
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
import (
"context"
"testing"
"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 {
@ -29,7 +30,7 @@ func isExpectedEqPath(a interface{}) func(interface{}) bool {
func TestPathTranscode(t *testing.T) {
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{
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("float4", "_float4", Float4{})
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("int2", "_int2", Int2{})
registerDefaultPgTypeVariants("int4", "_int4", Int4{})

View File

@ -1,21 +1,34 @@
package pgtype_test
import (
"context"
"database/sql"
"errors"
"net"
"os"
"regexp"
"strconv"
"testing"
"github.com/jackc/pgx/v5"
"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/stretchr/testify/assert"
"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
type _string string
type _bool bool
@ -70,8 +83,11 @@ func mustParseMacaddr(t testing.TB, s string) net.HardwareAddr {
}
func skipCockroachDB(t testing.TB, msg string) {
conn := testutil.MustConnectPgx(t)
defer testutil.MustCloseContext(t, conn)
conn, err := pgx.Connect(context.Background(), os.Getenv("PGX_TEST_DATABASE"))
if err != nil {
t.Fatal(err)
}
defer conn.Close(context.Background())
if conn.PgConn().ParameterStatus("crdb_version") != "" {
t.Skip(msg)
@ -79,8 +95,11 @@ func skipCockroachDB(t testing.TB, msg string) {
}
func skipPostgreSQLVersionLessThan(t testing.TB, minVersion int64) {
conn := testutil.MustConnectPgx(t)
defer testutil.MustCloseContext(t, conn)
conn, err := pgx.Connect(context.Background(), os.Getenv("PGX_TEST_DATABASE"))
if err != nil {
t.Fatal(err)
}
defer conn.Close(context.Background())
serverVersionStr := conn.PgConn().ParameterStatus("server_version")
serverVersionStr = regexp.MustCompile(`^[0-9]+`).FindString(serverVersionStr)

View File

@ -1,18 +1,19 @@
package pgtype_test
import (
"context"
"reflect"
"testing"
"github.com/jackc/pgx/v5/pgtype"
"github.com/jackc/pgx/v5/pgtype/testutil"
"github.com/jackc/pgx/v5/pgxtest"
"github.com/stretchr/testify/require"
)
func TestPointCodec(t *testing.T) {
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},
new(pgtype.Point),

View File

@ -1,10 +1,11 @@
package pgtype_test
import (
"context"
"testing"
"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 {
@ -29,7 +30,7 @@ func isExpectedEqPolygon(a interface{}) func(interface{}) bool {
func TestPolygonTranscode(t *testing.T) {
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{
P: []pgtype.Vec2{{3.14, 1.678901234}, {7.1, 5.234}, {5.0, 3.234}},

View File

@ -1,22 +1,24 @@
package pgtype_test
import (
"context"
"math"
"testing"
"github.com/jackc/pgx/v5/pgtype/testutil"
"github.com/jackc/pgx/v5/pgxtest"
)
func TestQcharTranscode(t *testing.T) {
skipCockroachDB(t, "Server does not support qchar")
var tests []testutil.TranscodeTestCase
var tests []pgxtest.ValueRoundTripTest
for i := 0; i <= math.MaxUint8; i++ {
tests = append(tests, testutil.TranscodeTestCase{rune(i), new(rune), isExpectedEq(rune(i))})
tests = append(tests, testutil.TranscodeTestCase{byte(i), new(byte), isExpectedEq(byte(i))})
tests = append(tests, pgxtest.ValueRoundTripTest{rune(i), new(rune), isExpectedEq(rune(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, testutil.TranscodeTestCase{nil, new(*byte), isExpectedEq((*byte)(nil))})
tests = append(tests, pgxtest.ValueRoundTripTest{nil, new(*rune), isExpectedEq((*rune)(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"
"testing"
pgx "github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype"
"github.com/jackc/pgx/v5/pgtype/testutil"
"github.com/jackc/pgx/v5/pgxtest"
"github.com/stretchr/testify/require"
)
func TestRangeCodecTranscode(t *testing.T) {
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},
new(pgtype.Int4range),
@ -39,9 +40,12 @@ func TestRangeCodecTranscode(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},
new(pgtype.Float8range),
@ -70,90 +74,90 @@ func TestRangeCodecTranscodeCompatibleRangeElementTypes(t *testing.T) {
func TestRangeCodecScanRangeTwiceWithUnbounded(t *testing.T) {
skipCockroachDB(t, "Server does not support range types (see https://github.com/cockroachdb/cockroach/issues/27791)")
conn := testutil.MustConnectPgx(t)
defer testutil.MustCloseContext(t, conn)
defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
var r pgtype.Int4range
var r pgtype.Int4range
err := conn.QueryRow(context.Background(), `select '[1,5)'::int4range`).Scan(&r)
require.NoError(t, err)
err := conn.QueryRow(context.Background(), `select '[1,5)'::int4range`).Scan(&r)
require.NoError(t, err)
require.Equal(
t,
pgtype.Int4range{
Lower: pgtype.Int4{Int32: 1, Valid: true},
Upper: pgtype.Int4{Int32: 5, Valid: true},
LowerType: pgtype.Inclusive,
UpperType: pgtype.Exclusive,
Valid: true,
},
r,
)
require.Equal(
t,
pgtype.Int4range{
Lower: pgtype.Int4{Int32: 1, Valid: true},
Upper: pgtype.Int4{Int32: 5, Valid: true},
LowerType: pgtype.Inclusive,
UpperType: pgtype.Exclusive,
Valid: true,
},
r,
)
err = conn.QueryRow(context.Background(), `select '[1,)'::int4range`).Scan(&r)
require.NoError(t, err)
err = conn.QueryRow(ctx, `select '[1,)'::int4range`).Scan(&r)
require.NoError(t, err)
require.Equal(
t,
pgtype.Int4range{
Lower: pgtype.Int4{Int32: 1, Valid: true},
Upper: pgtype.Int4{},
LowerType: pgtype.Inclusive,
UpperType: pgtype.Unbounded,
Valid: true,
},
r,
)
require.Equal(
t,
pgtype.Int4range{
Lower: pgtype.Int4{Int32: 1, Valid: true},
Upper: pgtype.Int4{},
LowerType: pgtype.Inclusive,
UpperType: pgtype.Unbounded,
Valid: true,
},
r,
)
err = conn.QueryRow(context.Background(), `select 'empty'::int4range`).Scan(&r)
require.NoError(t, err)
err = conn.QueryRow(ctx, `select 'empty'::int4range`).Scan(&r)
require.NoError(t, err)
require.Equal(
t,
pgtype.Int4range{
Lower: pgtype.Int4{},
Upper: pgtype.Int4{},
LowerType: pgtype.Empty,
UpperType: pgtype.Empty,
Valid: true,
},
r,
)
require.Equal(
t,
pgtype.Int4range{
Lower: pgtype.Int4{},
Upper: pgtype.Int4{},
LowerType: pgtype.Empty,
UpperType: pgtype.Empty,
Valid: true,
},
r,
)
})
}
func TestRangeCodecDecodeValue(t *testing.T) {
skipCockroachDB(t, "Server does not support range types (see https://github.com/cockroachdb/cockroach/issues/27791)")
conn := testutil.MustConnectPgx(t)
defer testutil.MustCloseContext(t, conn)
defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, _ testing.TB, conn *pgx.Conn) {
for _, tt := range []struct {
sql string
expected interface{}
}{
{
sql: `select '[1,5)'::int4range`,
expected: pgtype.GenericRange{
Lower: int32(1),
Upper: int32(5),
LowerType: pgtype.Inclusive,
UpperType: pgtype.Exclusive,
Valid: true,
for _, tt := range []struct {
sql string
expected interface{}
}{
{
sql: `select '[1,5)'::int4range`,
expected: pgtype.GenericRange{
Lower: int32(1),
Upper: int32(5),
LowerType: pgtype.Inclusive,
UpperType: pgtype.Exclusive,
Valid: true,
},
},
},
} {
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()
} {
t.Run(tt.sql, func(t *testing.T) {
rows, err := conn.Query(ctx, tt.sql)
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"
"testing"
pgx "github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype"
"github.com/jackc/pgx/v5/pgtype/testutil"
"github.com/stretchr/testify/require"
)
func TestRecordCodec(t *testing.T) {
conn := testutil.MustConnectPgx(t)
defer testutil.MustCloseContext(t, conn)
defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.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
var b int32
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)
require.Equal(t, "foo", a)
require.Equal(t, int32(42), b)
})
}
func TestRecordCodecDecodeValue(t *testing.T) {
skipCockroachDB(t, "Server converts row int4 to int8")
conn := testutil.MustConnectPgx(t)
defer testutil.MustCloseContext(t, conn)
for _, tt := range []struct {
sql string
expected interface{}
}{
{
sql: `select row()`,
expected: []interface{}{},
},
{
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('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 null::record`,
expected: nil,
},
} {
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()
defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, _ testing.TB, conn *pgx.Conn) {
for _, tt := range []struct {
sql string
expected interface{}
}{
{
sql: `select row()`,
expected: []interface{}{},
},
{
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('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 null::record`,
expected: nil,
},
} {
t.Run(tt.sql, func(t *testing.T) {
rows, err := conn.Query(context.Background(), tt.sql)
require.NoError(t, err)
require.Len(t, values, 1)
require.Equal(t, tt.expected, values[0])
}
defer rows.Close()
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"
"testing"
pgx "github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype"
"github.com/jackc/pgx/v5/pgtype/testutil"
"github.com/jackc/pgx/v5/pgxtest"
"github.com/stretchr/testify/require"
)
@ -17,7 +18,7 @@ func (someFmtStringer) String() string {
func TestTextCodec(t *testing.T) {
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},
new(pgtype.Text),
@ -31,6 +32,10 @@ func TestTextCodec(t *testing.T) {
{nil, new(pgtype.Text), isExpectedEq(pgtype.Text{})},
{"foo", new(string), isExpectedEq("foo")},
{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'))},
})
}
@ -47,7 +52,7 @@ func TestTextCodec(t *testing.T) {
//
// So this is simply a smoke test of the name type.
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},
new(pgtype.Text),
@ -67,7 +72,7 @@ func TestTextCodecName(t *testing.T) {
func TestTextCodecBPChar(t *testing.T) {
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},
new(pgtype.Text),
@ -94,12 +99,12 @@ func TestTextCodecBPChar(t *testing.T) {
//
// It only supports the text format.
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)
defer testutil.MustCloseContext(t, conn)
testutil.RunTranscodeTestsFormat(t, "aclitem", []testutil.TranscodeTestCase{
pgxtest.RunValueRoundTripTests(context.Background(), t, ctr, nil, "aclitem", []pgxtest.ValueRoundTripTest{
{
pgtype.Text{String: "postgres=arwdDxt/postgres", Valid: true},
new(pgtype.Text),
@ -107,33 +112,33 @@ func TestTextCodecACLItem(t *testing.T) {
},
{pgtype.Text{}, new(pgtype.Text), isExpectedEq(pgtype.Text{})},
{nil, new(pgtype.Text), isExpectedEq(pgtype.Text{})},
}, conn, "Text", pgtype.TextFormatCode)
})
}
func TestTextCodecACLItemRoleWithSpecialCharacters(t *testing.T) {
conn := testutil.MustConnectPgx(t)
defer testutil.MustCloseContext(t, conn)
ctr := defaultConnTestRunner
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
// of aclitem formatting. It turns out aclitems cannot contain non-existing users/roles.
roleWithSpecialCharacters := ` tricky, ' } " \ test user `
commandTag, err := conn.Exec(ctx, `select * from pg_roles where rolname = $1`, roleWithSpecialCharacters)
require.NoError(t, err)
commandTag, err := conn.Exec(ctx, `select * from pg_roles where rolname = $1`, roleWithSpecialCharacters)
require.NoError(t, err)
if commandTag.RowsAffected() == 0 {
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},
new(pgtype.Text),
isExpectedEq(pgtype.Text{String: `postgres=arwdDxt/" tricky, ' } "" \ test user "`, Valid: true}),
},
}, conn, "Text", pgtype.TextFormatCode)
})
}
func TestTextMarshalJSON(t *testing.T) {

View File

@ -1,16 +1,17 @@
package pgtype_test
import (
"context"
"testing"
"github.com/jackc/pgx/v5/pgtype"
"github.com/jackc/pgx/v5/pgtype/testutil"
"github.com/jackc/pgx/v5/pgxtest"
)
func TestTIDCodec(t *testing.T) {
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},
new(pgtype.TID),

View File

@ -1,15 +1,16 @@
package pgtype_test
import (
"context"
"testing"
"time"
"github.com/jackc/pgx/v5/pgtype"
"github.com/jackc/pgx/v5/pgtype/testutil"
"github.com/jackc/pgx/v5/pgxtest"
)
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},
new(pgtype.Time),

View File

@ -4,6 +4,7 @@ import (
"database/sql/driver"
"encoding/binary"
"fmt"
"strings"
"time"
"github.com/jackc/pgx/v5/internal/pgio"
@ -127,12 +128,29 @@ func (encodePlanTimestampCodecText) Encode(value interface{}, buf []byte) (newBu
return nil, err
}
if !ts.Valid {
return nil, nil
}
var s string
switch ts.InfinityModifier {
case Finite:
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)
if bc {
s = s + " BC"
}
case Infinity:
s = "infinity"
case NegativeInfinity:
@ -219,11 +237,21 @@ func (scanPlanTextTimestampToTimestampScanner) Scan(src []byte, dst interface{})
case "-infinity":
ts = Timestamp{Valid: true, InfinityModifier: -Infinity}
default:
bc := false
if strings.HasSuffix(sbuf, " BC") {
sbuf = sbuf[:len(sbuf)-3]
bc = true
}
tim, err := time.Parse(pgTimestampFormat, sbuf)
if err != nil {
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}
}

View File

@ -5,15 +5,21 @@ import (
"testing"
"time"
pgx "github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype"
"github.com/jackc/pgx/v5/pgtype/testutil"
"github.com/jackc/pgx/v5/pgxtest"
"github.com/stretchr/testify/require"
)
func TestTimestampCodec(t *testing.T) {
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(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))},
@ -34,19 +40,18 @@ func TestTimestampCodec(t *testing.T) {
// https://github.com/jackc/pgx/v4/pgtype/pull/128
func TestTimestampTranscodeBigTimeBinary(t *testing.T) {
conn := testutil.MustConnectPgx(t)
defer testutil.MustCloseContext(t, conn)
defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.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}
var out pgtype.Timestamp
err := conn.QueryRow(ctx, "select $1::timestamp", in).Scan(&out)
if err != nil {
t.Fatal(err)
}
err := conn.QueryRow(context.Background(), "select $1::timestamp", in).Scan(&out)
if err != nil {
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)
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

View File

@ -5,6 +5,7 @@ import (
"encoding/binary"
"encoding/json"
"fmt"
"strings"
"time"
"github.com/jackc/pgx/v5/internal/pgio"
@ -184,11 +185,30 @@ func (encodePlanTimestamptzCodecText) Encode(value interface{}, buf []byte) (new
return nil, err
}
if !ts.Valid {
return nil, nil
}
var s string
switch ts.InfinityModifier {
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:
s = "infinity"
case NegativeInfinity:
@ -267,6 +287,12 @@ func (scanPlanTextTimestamptzToTimestamptzScanner) Scan(src []byte, dst interfac
case "-infinity":
tstz = Timestamptz{Valid: true, InfinityModifier: -Infinity}
default:
bc := false
if strings.HasSuffix(sbuf, " BC") {
sbuf = sbuf[:len(sbuf)-3]
bc = true
}
var format string
if len(sbuf) >= 9 && (sbuf[len(sbuf)-9] == '-' || sbuf[len(sbuf)-9] == '+') {
format = pgTimestamptzSecondFormat
@ -281,6 +307,11 @@ func (scanPlanTextTimestamptzToTimestamptzScanner) Scan(src []byte, dst interfac
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}
}

View File

@ -5,15 +5,21 @@ import (
"testing"
"time"
pgx "github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype"
"github.com/jackc/pgx/v5/pgtype/testutil"
"github.com/jackc/pgx/v5/pgxtest"
"github.com/stretchr/testify/require"
)
func TestTimestamptzCodec(t *testing.T) {
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(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))},
@ -34,19 +40,18 @@ func TestTimestamptzCodec(t *testing.T) {
// https://github.com/jackc/pgx/v4/pgtype/pull/128
func TestTimestamptzTranscodeBigTimeBinary(t *testing.T) {
conn := testutil.MustConnectPgx(t)
defer testutil.MustCloseContext(t, conn)
defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.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}
var out pgtype.Timestamptz
err := conn.QueryRow(ctx, "select $1::timestamptz", in).Scan(&out)
if err != nil {
t.Fatal(err)
}
err := conn.QueryRow(context.Background(), "select $1::timestamptz", in).Scan(&out)
if err != nil {
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)
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

View File

@ -1,14 +1,15 @@
package pgtype_test
import (
"context"
"testing"
"github.com/jackc/pgx/v5/pgtype"
"github.com/jackc/pgx/v5/pgtype/testutil"
"github.com/jackc/pgx/v5/pgxtest"
)
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},
new(pgtype.Uint32),

View File

@ -1,31 +1,22 @@
package pgtype_test
import (
"context"
"reflect"
"testing"
"github.com/jackc/pgx/v5/pgtype"
"github.com/jackc/pgx/v5/pgtype/testutil"
"github.com/jackc/pgx/v5/pgxtest"
"github.com/stretchr/testify/require"
)
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},
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}),
},
{
[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",
new(pgtype.UUID),
@ -40,6 +31,19 @@ func TestUUIDCodec(t *testing.T) {
{pgtype.UUID{}, 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) {

View File

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

View File

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

View File

@ -10,7 +10,7 @@ import (
<% [2, 4, 8].each do |pg_byte_size| %>
<% pg_bit_size = pg_byte_size * 8 %>
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),
new(zeronull.Int<%= pg_byte_size %>),

View File

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

View File

@ -1,11 +1,12 @@
package zeronull_test
import (
"context"
"testing"
"time"
"github.com/jackc/pgx/v5/pgtype/testutil"
"github.com/jackc/pgx/v5/pgtype/zeronull"
"github.com/jackc/pgx/v5/pgxtest"
)
func isExpectedEqTimestamp(a interface{}) func(interface{}) bool {
@ -18,7 +19,7 @@ func isExpectedEqTimestamp(a interface{}) func(interface{}) bool {
}
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)),
new(zeronull.Timestamp),

View File

@ -1,11 +1,12 @@
package zeronull_test
import (
"context"
"testing"
"time"
"github.com/jackc/pgx/v5/pgtype/testutil"
"github.com/jackc/pgx/v5/pgtype/zeronull"
"github.com/jackc/pgx/v5/pgxtest"
)
func isExpectedEqTimestamptz(a interface{}) func(interface{}) bool {
@ -18,7 +19,7 @@ func isExpectedEqTimestamptz(a interface{}) func(interface{}) bool {
}
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)),
new(zeronull.Timestamptz),

View File

@ -1,14 +1,15 @@
package zeronull_test
import (
"context"
"testing"
"github.com/jackc/pgx/v5/pgtype/testutil"
"github.com/jackc/pgx/v5/pgtype/zeronull"
"github.com/jackc/pgx/v5/pgxtest"
)
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}),
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 (
"context"
"fmt"
"reflect"
"testing"
"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 with reasonable default values.
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)) {
t.Helper()
config := ctr.CreateConfig(ctx, t)
conn, err := pgx.ConnectConfig(ctx, config)
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.
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 {
modes = []pgx.QueryExecMode{
pgx.QueryExecModeCacheStatement,
pgx.QueryExecModeCacheDescribe,
pgx.QueryExecModeDescribeExec,
pgx.QueryExecModeExec,
pgx.QueryExecModeSimpleProtocol,
}
modes = AllQueryExecModes
}
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.
func SkipCockroachDB(t testing.TB, conn *pgx.Conn, msg string) {
if conn.PgConn().ParameterStatus("crdb_version") != "" {

View File

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