mirror of
https://github.com/jackc/pgx.git
synced 2025-05-01 21:19:54 +00:00
Tests should timeout in a reasonable time if something is stuck. In particular this is important when testing deadlock conditions such as can occur with the copy protocol if both the client and the server are blocked writing until the other side does a read.
1092 lines
32 KiB
Go
1092 lines
32 KiB
Go
package pgx_test
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"net"
|
|
"os"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/jackc/pgx/v5"
|
|
"github.com/jackc/pgx/v5/pgxtest"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestDateTranscode(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
|
|
pgxtest.RunWithQueryExecModes(ctx, t, defaultConnTestRunner, nil, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
|
|
dates := []time.Time{
|
|
time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC),
|
|
time.Date(1000, 1, 1, 0, 0, 0, 0, time.UTC),
|
|
time.Date(1600, 1, 1, 0, 0, 0, 0, time.UTC),
|
|
time.Date(1700, 1, 1, 0, 0, 0, 0, time.UTC),
|
|
time.Date(1800, 1, 1, 0, 0, 0, 0, time.UTC),
|
|
time.Date(1900, 1, 1, 0, 0, 0, 0, time.UTC),
|
|
time.Date(1990, 1, 1, 0, 0, 0, 0, time.UTC),
|
|
time.Date(1999, 12, 31, 0, 0, 0, 0, time.UTC),
|
|
time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC),
|
|
time.Date(2001, 1, 2, 0, 0, 0, 0, time.UTC),
|
|
time.Date(2004, 2, 29, 0, 0, 0, 0, time.UTC),
|
|
time.Date(2013, 7, 4, 0, 0, 0, 0, time.UTC),
|
|
time.Date(2013, 12, 25, 0, 0, 0, 0, time.UTC),
|
|
time.Date(2029, 1, 1, 0, 0, 0, 0, time.UTC),
|
|
time.Date(2081, 1, 1, 0, 0, 0, 0, time.UTC),
|
|
time.Date(2096, 2, 29, 0, 0, 0, 0, time.UTC),
|
|
time.Date(2550, 1, 1, 0, 0, 0, 0, time.UTC),
|
|
time.Date(9999, 12, 31, 0, 0, 0, 0, time.UTC),
|
|
}
|
|
|
|
for _, actualDate := range dates {
|
|
var d time.Time
|
|
|
|
err := conn.QueryRow(context.Background(), "select $1::date", actualDate).Scan(&d)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected failure on QueryRow Scan: %v", err)
|
|
}
|
|
if !actualDate.Equal(d) {
|
|
t.Errorf("Did not transcode date successfully: %v is not %v", d, actualDate)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestTimestampTzTranscode(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
|
|
pgxtest.RunWithQueryExecModes(ctx, t, defaultConnTestRunner, nil, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
|
|
inputTime := time.Date(2013, 1, 2, 3, 4, 5, 6000, time.Local)
|
|
|
|
var outputTime time.Time
|
|
|
|
err := conn.QueryRow(context.Background(), "select $1::timestamptz", inputTime).Scan(&outputTime)
|
|
if err != nil {
|
|
t.Fatalf("QueryRow Scan failed: %v", err)
|
|
}
|
|
if !inputTime.Equal(outputTime) {
|
|
t.Errorf("Did not transcode time successfully: %v is not %v", outputTime, inputTime)
|
|
}
|
|
})
|
|
}
|
|
|
|
// TODO - move these tests to pgtype
|
|
|
|
func TestJSONAndJSONBTranscode(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
|
|
pgxtest.RunWithQueryExecModes(ctx, t, defaultConnTestRunner, nil, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
|
|
for _, typename := range []string{"json", "jsonb"} {
|
|
if _, ok := conn.TypeMap().TypeForName(typename); !ok {
|
|
continue // No JSON/JSONB type -- must be running against old PostgreSQL
|
|
}
|
|
|
|
testJSONString(t, conn, typename)
|
|
testJSONStringPointer(t, conn, typename)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestJSONAndJSONBTranscodeExtendedOnly(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
conn := mustConnectString(t, os.Getenv("PGX_TEST_DATABASE"))
|
|
defer closeConn(t, conn)
|
|
|
|
for _, typename := range []string{"json", "jsonb"} {
|
|
if _, ok := conn.TypeMap().TypeForName(typename); !ok {
|
|
continue // No JSON/JSONB type -- must be running against old PostgreSQL
|
|
}
|
|
testJSONSingleLevelStringMap(t, conn, typename)
|
|
testJSONNestedMap(t, conn, typename)
|
|
testJSONStringArray(t, conn, typename)
|
|
testJSONInt64Array(t, conn, typename)
|
|
testJSONInt16ArrayFailureDueToOverflow(t, conn, typename)
|
|
testJSONStruct(t, conn, typename)
|
|
}
|
|
|
|
}
|
|
|
|
func testJSONString(t testing.TB, conn *pgx.Conn, typename string) {
|
|
input := `{"key": "value"}`
|
|
expectedOutput := map[string]string{"key": "value"}
|
|
var output map[string]string
|
|
err := conn.QueryRow(context.Background(), "select $1::"+typename, input).Scan(&output)
|
|
if err != nil {
|
|
t.Errorf("%s: QueryRow Scan failed: %v", typename, err)
|
|
return
|
|
}
|
|
|
|
if !reflect.DeepEqual(expectedOutput, output) {
|
|
t.Errorf("%s: Did not transcode map[string]string successfully: %v is not %v", typename, expectedOutput, output)
|
|
return
|
|
}
|
|
}
|
|
|
|
func testJSONStringPointer(t testing.TB, conn *pgx.Conn, typename string) {
|
|
input := `{"key": "value"}`
|
|
expectedOutput := map[string]string{"key": "value"}
|
|
var output map[string]string
|
|
err := conn.QueryRow(context.Background(), "select $1::"+typename, &input).Scan(&output)
|
|
if err != nil {
|
|
t.Errorf("%s: QueryRow Scan failed: %v", typename, err)
|
|
return
|
|
}
|
|
|
|
if !reflect.DeepEqual(expectedOutput, output) {
|
|
t.Errorf("%s: Did not transcode map[string]string successfully: %v is not %v", typename, expectedOutput, output)
|
|
return
|
|
}
|
|
}
|
|
|
|
func testJSONSingleLevelStringMap(t *testing.T, conn *pgx.Conn, typename string) {
|
|
input := map[string]string{"key": "value"}
|
|
var output map[string]string
|
|
err := conn.QueryRow(context.Background(), "select $1::"+typename, input).Scan(&output)
|
|
if err != nil {
|
|
t.Errorf("%s: QueryRow Scan failed: %v", typename, err)
|
|
return
|
|
}
|
|
|
|
if !reflect.DeepEqual(input, output) {
|
|
t.Errorf("%s: Did not transcode map[string]string successfully: %v is not %v", typename, input, output)
|
|
return
|
|
}
|
|
}
|
|
|
|
func testJSONNestedMap(t *testing.T, conn *pgx.Conn, typename string) {
|
|
input := map[string]any{
|
|
"name": "Uncanny",
|
|
"stats": map[string]any{"hp": float64(107), "maxhp": float64(150)},
|
|
"inventory": []any{"phone", "key"},
|
|
}
|
|
var output map[string]any
|
|
err := conn.QueryRow(context.Background(), "select $1::"+typename, input).Scan(&output)
|
|
if err != nil {
|
|
t.Errorf("%s: QueryRow Scan failed: %v", typename, err)
|
|
return
|
|
}
|
|
|
|
if !reflect.DeepEqual(input, output) {
|
|
t.Errorf("%s: Did not transcode map[string]any successfully: %v is not %v", typename, input, output)
|
|
return
|
|
}
|
|
}
|
|
|
|
func testJSONStringArray(t *testing.T, conn *pgx.Conn, typename string) {
|
|
input := []string{"foo", "bar", "baz"}
|
|
var output []string
|
|
err := conn.QueryRow(context.Background(), "select $1::"+typename, input).Scan(&output)
|
|
if err != nil {
|
|
t.Errorf("%s: QueryRow Scan failed: %v", typename, err)
|
|
}
|
|
|
|
if !reflect.DeepEqual(input, output) {
|
|
t.Errorf("%s: Did not transcode []string successfully: %v is not %v", typename, input, output)
|
|
}
|
|
}
|
|
|
|
func testJSONInt64Array(t *testing.T, conn *pgx.Conn, typename string) {
|
|
input := []int64{1, 2, 234432}
|
|
var output []int64
|
|
err := conn.QueryRow(context.Background(), "select $1::"+typename, input).Scan(&output)
|
|
if err != nil {
|
|
t.Errorf("%s: QueryRow Scan failed: %v", typename, err)
|
|
}
|
|
|
|
if !reflect.DeepEqual(input, output) {
|
|
t.Errorf("%s: Did not transcode []int64 successfully: %v is not %v", typename, input, output)
|
|
}
|
|
}
|
|
|
|
func testJSONInt16ArrayFailureDueToOverflow(t *testing.T, conn *pgx.Conn, typename string) {
|
|
input := []int{1, 2, 234432}
|
|
var output []int16
|
|
err := conn.QueryRow(context.Background(), "select $1::"+typename, input).Scan(&output)
|
|
if err == nil || err.Error() != "can't scan into dest[0]: json: cannot unmarshal number 234432 into Go value of type int16" {
|
|
t.Errorf("%s: Expected *json.UnmarkalTypeError, but got %v", typename, err)
|
|
}
|
|
}
|
|
|
|
func testJSONStruct(t *testing.T, conn *pgx.Conn, typename string) {
|
|
type person struct {
|
|
Name string `json:"name"`
|
|
Age int `json:"age"`
|
|
}
|
|
|
|
input := person{
|
|
Name: "John",
|
|
Age: 42,
|
|
}
|
|
|
|
var output person
|
|
|
|
err := conn.QueryRow(context.Background(), "select $1::"+typename, input).Scan(&output)
|
|
if err != nil {
|
|
t.Errorf("%s: QueryRow Scan failed: %v", typename, err)
|
|
}
|
|
|
|
if !reflect.DeepEqual(input, output) {
|
|
t.Errorf("%s: Did not transcode struct successfully: %v is not %v", typename, input, output)
|
|
}
|
|
}
|
|
|
|
func mustParseCIDR(t testing.TB, s string) *net.IPNet {
|
|
_, ipnet, err := net.ParseCIDR(s)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
return ipnet
|
|
}
|
|
|
|
func TestInetCIDRTranscodeIPNet(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
|
|
pgxtest.RunWithQueryExecModes(ctx, t, defaultConnTestRunner, nil, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
|
|
tests := []struct {
|
|
sql string
|
|
value *net.IPNet
|
|
}{
|
|
{"select $1::inet", mustParseCIDR(t, "0.0.0.0/32")},
|
|
{"select $1::inet", mustParseCIDR(t, "127.0.0.1/32")},
|
|
{"select $1::inet", mustParseCIDR(t, "12.34.56.0/32")},
|
|
{"select $1::inet", mustParseCIDR(t, "192.168.1.0/24")},
|
|
{"select $1::inet", mustParseCIDR(t, "255.0.0.0/8")},
|
|
{"select $1::inet", mustParseCIDR(t, "255.255.255.255/32")},
|
|
{"select $1::inet", mustParseCIDR(t, "::/128")},
|
|
{"select $1::inet", mustParseCIDR(t, "::/0")},
|
|
{"select $1::inet", mustParseCIDR(t, "::1/128")},
|
|
{"select $1::inet", mustParseCIDR(t, "2607:f8b0:4009:80b::200e/128")},
|
|
{"select $1::cidr", mustParseCIDR(t, "0.0.0.0/32")},
|
|
{"select $1::cidr", mustParseCIDR(t, "127.0.0.1/32")},
|
|
{"select $1::cidr", mustParseCIDR(t, "12.34.56.0/32")},
|
|
{"select $1::cidr", mustParseCIDR(t, "192.168.1.0/24")},
|
|
{"select $1::cidr", mustParseCIDR(t, "255.0.0.0/8")},
|
|
{"select $1::cidr", mustParseCIDR(t, "255.255.255.255/32")},
|
|
{"select $1::cidr", mustParseCIDR(t, "::/128")},
|
|
{"select $1::cidr", mustParseCIDR(t, "::/0")},
|
|
{"select $1::cidr", mustParseCIDR(t, "::1/128")},
|
|
{"select $1::cidr", mustParseCIDR(t, "2607:f8b0:4009:80b::200e/128")},
|
|
}
|
|
|
|
for i, tt := range tests {
|
|
if conn.PgConn().ParameterStatus("crdb_version") != "" && strings.Contains(tt.sql, "cidr") {
|
|
t.Log("Server does not support cidr type (https://github.com/cockroachdb/cockroach/issues/18846)")
|
|
continue
|
|
}
|
|
|
|
var actual net.IPNet
|
|
|
|
err := conn.QueryRow(context.Background(), tt.sql, tt.value).Scan(&actual)
|
|
if err != nil {
|
|
t.Errorf("%d. Unexpected failure: %v (sql -> %v, value -> %v)", i, err, tt.sql, tt.value)
|
|
continue
|
|
}
|
|
|
|
if actual.String() != tt.value.String() {
|
|
t.Errorf("%d. Expected %v, got %v (sql -> %v)", i, tt.value, actual, tt.sql)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestInetCIDRTranscodeIP(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
|
|
pgxtest.RunWithQueryExecModes(ctx, t, defaultConnTestRunner, nil, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
|
|
tests := []struct {
|
|
sql string
|
|
value net.IP
|
|
}{
|
|
{"select $1::inet", net.ParseIP("0.0.0.0")},
|
|
{"select $1::inet", net.ParseIP("127.0.0.1")},
|
|
{"select $1::inet", net.ParseIP("12.34.56.0")},
|
|
{"select $1::inet", net.ParseIP("255.255.255.255")},
|
|
{"select $1::inet", net.ParseIP("::1")},
|
|
{"select $1::inet", net.ParseIP("2607:f8b0:4009:80b::200e")},
|
|
{"select $1::cidr", net.ParseIP("0.0.0.0")},
|
|
{"select $1::cidr", net.ParseIP("127.0.0.1")},
|
|
{"select $1::cidr", net.ParseIP("12.34.56.0")},
|
|
{"select $1::cidr", net.ParseIP("255.255.255.255")},
|
|
{"select $1::cidr", net.ParseIP("::1")},
|
|
{"select $1::cidr", net.ParseIP("2607:f8b0:4009:80b::200e")},
|
|
}
|
|
|
|
for i, tt := range tests {
|
|
if conn.PgConn().ParameterStatus("crdb_version") != "" && strings.Contains(tt.sql, "cidr") {
|
|
t.Log("Server does not support cidr type (https://github.com/cockroachdb/cockroach/issues/18846)")
|
|
continue
|
|
}
|
|
|
|
var actual net.IP
|
|
|
|
err := conn.QueryRow(context.Background(), tt.sql, tt.value).Scan(&actual)
|
|
if err != nil {
|
|
t.Errorf("%d. Unexpected failure: %v (sql -> %v, value -> %v)", i, err, tt.sql, tt.value)
|
|
continue
|
|
}
|
|
|
|
if !actual.Equal(tt.value) {
|
|
t.Errorf("%d. Expected %v, got %v (sql -> %v)", i, tt.value, actual, tt.sql)
|
|
}
|
|
|
|
ensureConnValid(t, conn)
|
|
}
|
|
|
|
failTests := []struct {
|
|
sql string
|
|
value *net.IPNet
|
|
}{
|
|
{"select $1::inet", mustParseCIDR(t, "192.168.1.0/24")},
|
|
{"select $1::cidr", mustParseCIDR(t, "192.168.1.0/24")},
|
|
}
|
|
for i, tt := range failTests {
|
|
var actual net.IP
|
|
|
|
err := conn.QueryRow(context.Background(), tt.sql, tt.value).Scan(&actual)
|
|
if err == nil {
|
|
t.Errorf("%d. Expected failure but got none", i)
|
|
continue
|
|
}
|
|
|
|
ensureConnValid(t, conn)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestInetCIDRArrayTranscodeIPNet(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
|
|
pgxtest.RunWithQueryExecModes(ctx, t, defaultConnTestRunner, nil, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
|
|
tests := []struct {
|
|
sql string
|
|
value []*net.IPNet
|
|
}{
|
|
{
|
|
"select $1::inet[]",
|
|
[]*net.IPNet{
|
|
mustParseCIDR(t, "0.0.0.0/32"),
|
|
mustParseCIDR(t, "127.0.0.1/32"),
|
|
mustParseCIDR(t, "12.34.56.0/32"),
|
|
mustParseCIDR(t, "192.168.1.0/24"),
|
|
mustParseCIDR(t, "255.0.0.0/8"),
|
|
mustParseCIDR(t, "255.255.255.255/32"),
|
|
mustParseCIDR(t, "::/128"),
|
|
mustParseCIDR(t, "::/0"),
|
|
mustParseCIDR(t, "::1/128"),
|
|
mustParseCIDR(t, "2607:f8b0:4009:80b::200e/128"),
|
|
},
|
|
},
|
|
{
|
|
"select $1::cidr[]",
|
|
[]*net.IPNet{
|
|
mustParseCIDR(t, "0.0.0.0/32"),
|
|
mustParseCIDR(t, "127.0.0.1/32"),
|
|
mustParseCIDR(t, "12.34.56.0/32"),
|
|
mustParseCIDR(t, "192.168.1.0/24"),
|
|
mustParseCIDR(t, "255.0.0.0/8"),
|
|
mustParseCIDR(t, "255.255.255.255/32"),
|
|
mustParseCIDR(t, "::/128"),
|
|
mustParseCIDR(t, "::/0"),
|
|
mustParseCIDR(t, "::1/128"),
|
|
mustParseCIDR(t, "2607:f8b0:4009:80b::200e/128"),
|
|
},
|
|
},
|
|
}
|
|
|
|
for i, tt := range tests {
|
|
if conn.PgConn().ParameterStatus("crdb_version") != "" && strings.Contains(tt.sql, "cidr") {
|
|
t.Log("Server does not support cidr type (https://github.com/cockroachdb/cockroach/issues/18846)")
|
|
continue
|
|
}
|
|
|
|
var actual []*net.IPNet
|
|
|
|
err := conn.QueryRow(context.Background(), tt.sql, tt.value).Scan(&actual)
|
|
if err != nil {
|
|
t.Errorf("%d. Unexpected failure: %v (sql -> %v, value -> %v)", i, err, tt.sql, tt.value)
|
|
continue
|
|
}
|
|
|
|
if !reflect.DeepEqual(actual, tt.value) {
|
|
t.Errorf("%d. Expected %v, got %v (sql -> %v)", i, tt.value, actual, tt.sql)
|
|
}
|
|
|
|
ensureConnValid(t, conn)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestInetCIDRArrayTranscodeIP(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
|
|
pgxtest.RunWithQueryExecModes(ctx, t, defaultConnTestRunner, nil, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
|
|
tests := []struct {
|
|
sql string
|
|
value []net.IP
|
|
}{
|
|
{
|
|
"select $1::inet[]",
|
|
[]net.IP{
|
|
net.ParseIP("0.0.0.0"),
|
|
net.ParseIP("127.0.0.1"),
|
|
net.ParseIP("12.34.56.0"),
|
|
net.ParseIP("255.255.255.255"),
|
|
net.ParseIP("2607:f8b0:4009:80b::200e"),
|
|
},
|
|
},
|
|
{
|
|
"select $1::cidr[]",
|
|
[]net.IP{
|
|
net.ParseIP("0.0.0.0"),
|
|
net.ParseIP("127.0.0.1"),
|
|
net.ParseIP("12.34.56.0"),
|
|
net.ParseIP("255.255.255.255"),
|
|
net.ParseIP("2607:f8b0:4009:80b::200e"),
|
|
},
|
|
},
|
|
}
|
|
|
|
for i, tt := range tests {
|
|
if conn.PgConn().ParameterStatus("crdb_version") != "" && strings.Contains(tt.sql, "cidr") {
|
|
t.Log("Server does not support cidr type (https://github.com/cockroachdb/cockroach/issues/18846)")
|
|
continue
|
|
}
|
|
|
|
var actual []net.IP
|
|
|
|
err := conn.QueryRow(context.Background(), tt.sql, tt.value).Scan(&actual)
|
|
if err != nil {
|
|
t.Errorf("%d. Unexpected failure: %v (sql -> %v, value -> %v)", i, err, tt.sql, tt.value)
|
|
continue
|
|
}
|
|
|
|
assert.Equal(t, len(tt.value), len(actual), "%d", i)
|
|
for j := range actual {
|
|
assert.True(t, actual[j].Equal(tt.value[j]), "%d", i)
|
|
}
|
|
|
|
ensureConnValid(t, conn)
|
|
}
|
|
|
|
failTests := []struct {
|
|
sql string
|
|
value []*net.IPNet
|
|
}{
|
|
{
|
|
"select $1::inet[]",
|
|
[]*net.IPNet{
|
|
mustParseCIDR(t, "12.34.56.0/32"),
|
|
mustParseCIDR(t, "192.168.1.0/24"),
|
|
},
|
|
},
|
|
{
|
|
"select $1::cidr[]",
|
|
[]*net.IPNet{
|
|
mustParseCIDR(t, "12.34.56.0/32"),
|
|
mustParseCIDR(t, "192.168.1.0/24"),
|
|
},
|
|
},
|
|
}
|
|
|
|
for i, tt := range failTests {
|
|
var actual []net.IP
|
|
|
|
err := conn.QueryRow(context.Background(), tt.sql, tt.value).Scan(&actual)
|
|
if err == nil {
|
|
t.Errorf("%d. Expected failure but got none", i)
|
|
continue
|
|
}
|
|
|
|
ensureConnValid(t, conn)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestInetCIDRTranscodeWithJustIP(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
|
|
pgxtest.RunWithQueryExecModes(ctx, t, defaultConnTestRunner, nil, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
|
|
tests := []struct {
|
|
sql string
|
|
value string
|
|
}{
|
|
{"select $1::inet", "0.0.0.0/32"},
|
|
{"select $1::inet", "127.0.0.1/32"},
|
|
{"select $1::inet", "12.34.56.0/32"},
|
|
{"select $1::inet", "255.255.255.255/32"},
|
|
{"select $1::inet", "::/128"},
|
|
{"select $1::inet", "2607:f8b0:4009:80b::200e/128"},
|
|
{"select $1::cidr", "0.0.0.0/32"},
|
|
{"select $1::cidr", "127.0.0.1/32"},
|
|
{"select $1::cidr", "12.34.56.0/32"},
|
|
{"select $1::cidr", "255.255.255.255/32"},
|
|
{"select $1::cidr", "::/128"},
|
|
{"select $1::cidr", "2607:f8b0:4009:80b::200e/128"},
|
|
}
|
|
|
|
for i, tt := range tests {
|
|
if conn.PgConn().ParameterStatus("crdb_version") != "" && strings.Contains(tt.sql, "cidr") {
|
|
t.Log("Server does not support cidr type (https://github.com/cockroachdb/cockroach/issues/18846)")
|
|
continue
|
|
}
|
|
|
|
expected := mustParseCIDR(t, tt.value)
|
|
var actual net.IPNet
|
|
|
|
err := conn.QueryRow(context.Background(), tt.sql, expected.IP).Scan(&actual)
|
|
if err != nil {
|
|
t.Errorf("%d. Unexpected failure: %v (sql -> %v, value -> %v)", i, err, tt.sql, tt.value)
|
|
continue
|
|
}
|
|
|
|
if actual.String() != expected.String() {
|
|
t.Errorf("%d. Expected %v, got %v (sql -> %v)", i, tt.value, actual, tt.sql)
|
|
}
|
|
|
|
ensureConnValid(t, conn)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestArrayDecoding(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
|
|
pgxtest.RunWithQueryExecModes(ctx, t, defaultConnTestRunner, nil, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
|
|
tests := []struct {
|
|
sql string
|
|
query any
|
|
scan any
|
|
assert func(testing.TB, any, any)
|
|
}{
|
|
{
|
|
"select $1::bool[]", []bool{true, false, true}, &[]bool{},
|
|
func(t testing.TB, query, scan any) {
|
|
if !reflect.DeepEqual(query, *(scan.(*[]bool))) {
|
|
t.Errorf("failed to encode bool[]")
|
|
}
|
|
},
|
|
},
|
|
{
|
|
"select $1::smallint[]", []int16{2, 4, 484, 32767}, &[]int16{},
|
|
func(t testing.TB, query, scan any) {
|
|
if !reflect.DeepEqual(query, *(scan.(*[]int16))) {
|
|
t.Errorf("failed to encode smallint[]")
|
|
}
|
|
},
|
|
},
|
|
{
|
|
"select $1::smallint[]", []uint16{2, 4, 484, 32767}, &[]uint16{},
|
|
func(t testing.TB, query, scan any) {
|
|
if !reflect.DeepEqual(query, *(scan.(*[]uint16))) {
|
|
t.Errorf("failed to encode smallint[]")
|
|
}
|
|
},
|
|
},
|
|
{
|
|
"select $1::int[]", []int32{2, 4, 484}, &[]int32{},
|
|
func(t testing.TB, query, scan any) {
|
|
if !reflect.DeepEqual(query, *(scan.(*[]int32))) {
|
|
t.Errorf("failed to encode int[]")
|
|
}
|
|
},
|
|
},
|
|
{
|
|
"select $1::int[]", []uint32{2, 4, 484, 2147483647}, &[]uint32{},
|
|
func(t testing.TB, query, scan any) {
|
|
if !reflect.DeepEqual(query, *(scan.(*[]uint32))) {
|
|
t.Errorf("failed to encode int[]")
|
|
}
|
|
},
|
|
},
|
|
{
|
|
"select $1::bigint[]", []int64{2, 4, 484, 9223372036854775807}, &[]int64{},
|
|
func(t testing.TB, query, scan any) {
|
|
if !reflect.DeepEqual(query, *(scan.(*[]int64))) {
|
|
t.Errorf("failed to encode bigint[]")
|
|
}
|
|
},
|
|
},
|
|
{
|
|
"select $1::bigint[]", []uint64{2, 4, 484, 9223372036854775807}, &[]uint64{},
|
|
func(t testing.TB, query, scan any) {
|
|
if !reflect.DeepEqual(query, *(scan.(*[]uint64))) {
|
|
t.Errorf("failed to encode bigint[]")
|
|
}
|
|
},
|
|
},
|
|
{
|
|
"select $1::text[]", []string{"it's", "over", "9000!"}, &[]string{},
|
|
func(t testing.TB, query, scan any) {
|
|
if !reflect.DeepEqual(query, *(scan.(*[]string))) {
|
|
t.Errorf("failed to encode text[]")
|
|
}
|
|
},
|
|
},
|
|
{
|
|
"select $1::timestamptz[]", []time.Time{time.Unix(323232, 0), time.Unix(3239949334, 00)}, &[]time.Time{},
|
|
func(t testing.TB, query, scan any) {
|
|
queryTimeSlice := query.([]time.Time)
|
|
scanTimeSlice := *(scan.(*[]time.Time))
|
|
require.Equal(t, len(queryTimeSlice), len(scanTimeSlice))
|
|
for i := range queryTimeSlice {
|
|
assert.Truef(t, queryTimeSlice[i].Equal(scanTimeSlice[i]), "%d", i)
|
|
}
|
|
},
|
|
},
|
|
{
|
|
"select $1::bytea[]", [][]byte{{0, 1, 2, 3}, {4, 5, 6, 7}}, &[][]byte{},
|
|
func(t testing.TB, query, scan any) {
|
|
queryBytesSliceSlice := query.([][]byte)
|
|
scanBytesSliceSlice := *(scan.(*[][]byte))
|
|
if len(queryBytesSliceSlice) != len(scanBytesSliceSlice) {
|
|
t.Errorf("failed to encode byte[][] to bytea[]: expected %d to equal %d", len(queryBytesSliceSlice), len(scanBytesSliceSlice))
|
|
}
|
|
for i := range queryBytesSliceSlice {
|
|
qb := queryBytesSliceSlice[i]
|
|
sb := scanBytesSliceSlice[i]
|
|
if !bytes.Equal(qb, sb) {
|
|
t.Errorf("failed to encode byte[][] to bytea[]: expected %v to equal %v", qb, sb)
|
|
}
|
|
}
|
|
},
|
|
},
|
|
}
|
|
|
|
for i, tt := range tests {
|
|
err := conn.QueryRow(context.Background(), tt.sql, tt.query).Scan(tt.scan)
|
|
if err != nil {
|
|
t.Errorf(`%d. error reading array: %v`, i, err)
|
|
continue
|
|
}
|
|
tt.assert(t, tt.query, tt.scan)
|
|
ensureConnValid(t, conn)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestEmptyArrayDecoding(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
|
|
pgxtest.RunWithQueryExecModes(ctx, t, defaultConnTestRunner, nil, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
|
|
var val []string
|
|
|
|
err := conn.QueryRow(context.Background(), "select array[]::text[]").Scan(&val)
|
|
if err != nil {
|
|
t.Errorf(`error reading array: %v`, err)
|
|
}
|
|
if len(val) != 0 {
|
|
t.Errorf("Expected 0 values, got %d", len(val))
|
|
}
|
|
|
|
var n, m int32
|
|
|
|
err = conn.QueryRow(context.Background(), "select 1::integer, array[]::text[], 42::integer").Scan(&n, &val, &m)
|
|
if err != nil {
|
|
t.Errorf(`error reading array: %v`, err)
|
|
}
|
|
if len(val) != 0 {
|
|
t.Errorf("Expected 0 values, got %d", len(val))
|
|
}
|
|
if n != 1 {
|
|
t.Errorf("Expected n to be 1, but it was %d", n)
|
|
}
|
|
if m != 42 {
|
|
t.Errorf("Expected n to be 42, but it was %d", n)
|
|
}
|
|
|
|
rows, err := conn.Query(context.Background(), "select 1::integer, array['test']::text[] union select 2::integer, array[]::text[] union select 3::integer, array['test']::text[]")
|
|
if err != nil {
|
|
t.Errorf(`error retrieving rows with array: %v`, err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
for rows.Next() {
|
|
err = rows.Scan(&n, &val)
|
|
if err != nil {
|
|
t.Errorf(`error reading array: %v`, err)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestPointerPointer(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
|
|
pgxtest.RunWithQueryExecModes(ctx, t, defaultConnTestRunner, nil, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
|
|
pgxtest.SkipCockroachDB(t, conn, "Server auto converts ints to bigint and test relies on exact types")
|
|
|
|
type allTypes struct {
|
|
s *string
|
|
i16 *int16
|
|
i32 *int32
|
|
i64 *int64
|
|
f32 *float32
|
|
f64 *float64
|
|
b *bool
|
|
t *time.Time
|
|
}
|
|
|
|
var actual, zero, expected allTypes
|
|
|
|
{
|
|
s := "foo"
|
|
expected.s = &s
|
|
i16 := int16(1)
|
|
expected.i16 = &i16
|
|
i32 := int32(1)
|
|
expected.i32 = &i32
|
|
i64 := int64(1)
|
|
expected.i64 = &i64
|
|
f32 := float32(1.23)
|
|
expected.f32 = &f32
|
|
f64 := float64(1.23)
|
|
expected.f64 = &f64
|
|
b := true
|
|
expected.b = &b
|
|
t := time.Unix(123, 5000)
|
|
expected.t = &t
|
|
}
|
|
|
|
tests := []struct {
|
|
sql string
|
|
queryArgs []any
|
|
scanArgs []any
|
|
expected allTypes
|
|
}{
|
|
{"select $1::text", []any{expected.s}, []any{&actual.s}, allTypes{s: expected.s}},
|
|
{"select $1::text", []any{zero.s}, []any{&actual.s}, allTypes{}},
|
|
{"select $1::int2", []any{expected.i16}, []any{&actual.i16}, allTypes{i16: expected.i16}},
|
|
{"select $1::int2", []any{zero.i16}, []any{&actual.i16}, allTypes{}},
|
|
{"select $1::int4", []any{expected.i32}, []any{&actual.i32}, allTypes{i32: expected.i32}},
|
|
{"select $1::int4", []any{zero.i32}, []any{&actual.i32}, allTypes{}},
|
|
{"select $1::int8", []any{expected.i64}, []any{&actual.i64}, allTypes{i64: expected.i64}},
|
|
{"select $1::int8", []any{zero.i64}, []any{&actual.i64}, allTypes{}},
|
|
{"select $1::float4", []any{expected.f32}, []any{&actual.f32}, allTypes{f32: expected.f32}},
|
|
{"select $1::float4", []any{zero.f32}, []any{&actual.f32}, allTypes{}},
|
|
{"select $1::float8", []any{expected.f64}, []any{&actual.f64}, allTypes{f64: expected.f64}},
|
|
{"select $1::float8", []any{zero.f64}, []any{&actual.f64}, allTypes{}},
|
|
{"select $1::bool", []any{expected.b}, []any{&actual.b}, allTypes{b: expected.b}},
|
|
{"select $1::bool", []any{zero.b}, []any{&actual.b}, allTypes{}},
|
|
{"select $1::timestamptz", []any{expected.t}, []any{&actual.t}, allTypes{t: expected.t}},
|
|
{"select $1::timestamptz", []any{zero.t}, []any{&actual.t}, allTypes{}},
|
|
}
|
|
|
|
for i, tt := range tests {
|
|
actual = zero
|
|
|
|
err := conn.QueryRow(context.Background(), tt.sql, tt.queryArgs...).Scan(tt.scanArgs...)
|
|
if err != nil {
|
|
t.Errorf("%d. Unexpected failure: %v (sql -> %v, queryArgs -> %v)", i, err, tt.sql, tt.queryArgs)
|
|
}
|
|
|
|
assert.Equal(t, tt.expected.s, actual.s)
|
|
assert.Equal(t, tt.expected.i16, actual.i16)
|
|
assert.Equal(t, tt.expected.i32, actual.i32)
|
|
assert.Equal(t, tt.expected.i64, actual.i64)
|
|
assert.Equal(t, tt.expected.f32, actual.f32)
|
|
assert.Equal(t, tt.expected.f64, actual.f64)
|
|
assert.Equal(t, tt.expected.b, actual.b)
|
|
if tt.expected.t != nil || actual.t != nil {
|
|
assert.True(t, tt.expected.t.Equal(*actual.t))
|
|
}
|
|
|
|
ensureConnValid(t, conn)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestPointerPointerNonZero(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
|
|
pgxtest.RunWithQueryExecModes(ctx, t, defaultConnTestRunner, nil, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
|
|
f := "foo"
|
|
dest := &f
|
|
|
|
err := conn.QueryRow(context.Background(), "select $1::text", nil).Scan(&dest)
|
|
if err != nil {
|
|
t.Errorf("Unexpected failure scanning: %v", err)
|
|
}
|
|
if dest != nil {
|
|
t.Errorf("Expected dest to be nil, got %#v", dest)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestEncodeTypeRename(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
|
|
pgxtest.RunWithQueryExecModes(ctx, t, defaultConnTestRunner, nil, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
|
|
type _int int
|
|
inInt := _int(1)
|
|
var outInt _int
|
|
|
|
type _int8 int8
|
|
inInt8 := _int8(2)
|
|
var outInt8 _int8
|
|
|
|
type _int16 int16
|
|
inInt16 := _int16(3)
|
|
var outInt16 _int16
|
|
|
|
type _int32 int32
|
|
inInt32 := _int32(4)
|
|
var outInt32 _int32
|
|
|
|
type _int64 int64
|
|
inInt64 := _int64(5)
|
|
var outInt64 _int64
|
|
|
|
type _uint uint
|
|
inUint := _uint(6)
|
|
var outUint _uint
|
|
|
|
type _uint8 uint8
|
|
inUint8 := _uint8(7)
|
|
var outUint8 _uint8
|
|
|
|
type _uint16 uint16
|
|
inUint16 := _uint16(8)
|
|
var outUint16 _uint16
|
|
|
|
type _uint32 uint32
|
|
inUint32 := _uint32(9)
|
|
var outUint32 _uint32
|
|
|
|
type _uint64 uint64
|
|
inUint64 := _uint64(10)
|
|
var outUint64 _uint64
|
|
|
|
type _string string
|
|
inString := _string("foo")
|
|
var outString _string
|
|
|
|
type _bool bool
|
|
inBool := _bool(true)
|
|
var outBool _bool
|
|
|
|
// pgx.QueryExecModeExec requires all types to be registered.
|
|
conn.TypeMap().RegisterDefaultPgType(inInt, "int8")
|
|
conn.TypeMap().RegisterDefaultPgType(inInt8, "int8")
|
|
conn.TypeMap().RegisterDefaultPgType(inInt16, "int8")
|
|
conn.TypeMap().RegisterDefaultPgType(inInt32, "int8")
|
|
conn.TypeMap().RegisterDefaultPgType(inInt64, "int8")
|
|
conn.TypeMap().RegisterDefaultPgType(inUint, "int8")
|
|
conn.TypeMap().RegisterDefaultPgType(inUint8, "int8")
|
|
conn.TypeMap().RegisterDefaultPgType(inUint16, "int8")
|
|
conn.TypeMap().RegisterDefaultPgType(inUint32, "int8")
|
|
conn.TypeMap().RegisterDefaultPgType(inUint64, "int8")
|
|
conn.TypeMap().RegisterDefaultPgType(inString, "text")
|
|
conn.TypeMap().RegisterDefaultPgType(inBool, "bool")
|
|
|
|
err := conn.QueryRow(context.Background(), "select $1::int, $2::int, $3::int2, $4::int4, $5::int8, $6::int, $7::int, $8::int, $9::int, $10::int, $11::text, $12::bool",
|
|
inInt, inInt8, inInt16, inInt32, inInt64, inUint, inUint8, inUint16, inUint32, inUint64, inString, inBool,
|
|
).Scan(&outInt, &outInt8, &outInt16, &outInt32, &outInt64, &outUint, &outUint8, &outUint16, &outUint32, &outUint64, &outString, &outBool)
|
|
if err != nil {
|
|
t.Fatalf("Failed with type rename: %v", err)
|
|
}
|
|
|
|
if inInt != outInt {
|
|
t.Errorf("int rename: expected %v, got %v", inInt, outInt)
|
|
}
|
|
|
|
if inInt8 != outInt8 {
|
|
t.Errorf("int8 rename: expected %v, got %v", inInt8, outInt8)
|
|
}
|
|
|
|
if inInt16 != outInt16 {
|
|
t.Errorf("int16 rename: expected %v, got %v", inInt16, outInt16)
|
|
}
|
|
|
|
if inInt32 != outInt32 {
|
|
t.Errorf("int32 rename: expected %v, got %v", inInt32, outInt32)
|
|
}
|
|
|
|
if inInt64 != outInt64 {
|
|
t.Errorf("int64 rename: expected %v, got %v", inInt64, outInt64)
|
|
}
|
|
|
|
if inUint != outUint {
|
|
t.Errorf("uint rename: expected %v, got %v", inUint, outUint)
|
|
}
|
|
|
|
if inUint8 != outUint8 {
|
|
t.Errorf("uint8 rename: expected %v, got %v", inUint8, outUint8)
|
|
}
|
|
|
|
if inUint16 != outUint16 {
|
|
t.Errorf("uint16 rename: expected %v, got %v", inUint16, outUint16)
|
|
}
|
|
|
|
if inUint32 != outUint32 {
|
|
t.Errorf("uint32 rename: expected %v, got %v", inUint32, outUint32)
|
|
}
|
|
|
|
if inUint64 != outUint64 {
|
|
t.Errorf("uint64 rename: expected %v, got %v", inUint64, outUint64)
|
|
}
|
|
|
|
if inString != outString {
|
|
t.Errorf("string rename: expected %v, got %v", inString, outString)
|
|
}
|
|
|
|
if inBool != outBool {
|
|
t.Errorf("bool rename: expected %v, got %v", inBool, outBool)
|
|
}
|
|
})
|
|
}
|
|
|
|
// func TestRowDecodeBinary(t *testing.T) {
|
|
// t.Parallel()
|
|
|
|
// conn := mustConnectString(t, os.Getenv("PGX_TEST_DATABASE"))
|
|
// defer closeConn(t, conn)
|
|
|
|
// tests := []struct {
|
|
// sql string
|
|
// expected []any
|
|
// }{
|
|
// {
|
|
// "select row(1, 'cat', '2015-01-01 08:12:42-00'::timestamptz)",
|
|
// []any{
|
|
// int32(1),
|
|
// "cat",
|
|
// time.Date(2015, 1, 1, 8, 12, 42, 0, time.UTC).Local(),
|
|
// },
|
|
// },
|
|
// {
|
|
// "select row(100.0::float, 1.09::float)",
|
|
// []any{
|
|
// float64(100),
|
|
// float64(1.09),
|
|
// },
|
|
// },
|
|
// }
|
|
|
|
// for i, tt := range tests {
|
|
// var actual []any
|
|
|
|
// err := conn.QueryRow(context.Background(), tt.sql).Scan(&actual)
|
|
// if err != nil {
|
|
// t.Errorf("%d. Unexpected failure: %v (sql -> %v)", i, err, tt.sql)
|
|
// continue
|
|
// }
|
|
|
|
// for j := range tt.expected {
|
|
// assert.EqualValuesf(t, tt.expected[j], actual[j], "%d. [%d]", i, j)
|
|
|
|
// }
|
|
|
|
// ensureConnValid(t, conn)
|
|
// }
|
|
// }
|
|
|
|
// https://github.com/jackc/pgx/issues/810
|
|
func TestRowsScanNilThenScanValue(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
|
|
pgxtest.RunWithQueryExecModes(ctx, t, defaultConnTestRunner, nil, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
|
|
sql := `select null as a, null as b
|
|
union
|
|
select 1, 2
|
|
order by a nulls first
|
|
`
|
|
rows, err := conn.Query(context.Background(), sql)
|
|
require.NoError(t, err)
|
|
|
|
require.True(t, rows.Next())
|
|
|
|
err = rows.Scan(nil, nil)
|
|
require.NoError(t, err)
|
|
|
|
require.True(t, rows.Next())
|
|
|
|
var a int
|
|
var b int
|
|
err = rows.Scan(&a, &b)
|
|
require.NoError(t, err)
|
|
|
|
require.EqualValues(t, 1, a)
|
|
require.EqualValues(t, 2, b)
|
|
|
|
rows.Close()
|
|
require.NoError(t, rows.Err())
|
|
})
|
|
}
|
|
|
|
func TestScanIntoByteSlice(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
conn := mustConnectString(t, os.Getenv("PGX_TEST_DATABASE"))
|
|
defer closeConn(t, conn)
|
|
// Success cases
|
|
for _, tt := range []struct {
|
|
name string
|
|
sql string
|
|
resultFormatCode int16
|
|
output []byte
|
|
}{
|
|
{"int - text", "select 42", pgx.TextFormatCode, []byte("42")},
|
|
{"int - binary", "select 42", pgx.BinaryFormatCode, []byte("42")},
|
|
{"text - text", "select 'hi'", pgx.TextFormatCode, []byte("hi")},
|
|
{"text - binary", "select 'hi'", pgx.BinaryFormatCode, []byte("hi")},
|
|
{"json - text", "select '{}'::json", pgx.TextFormatCode, []byte("{}")},
|
|
{"json - binary", "select '{}'::json", pgx.BinaryFormatCode, []byte("{}")},
|
|
{"jsonb - text", "select '{}'::jsonb", pgx.TextFormatCode, []byte("{}")},
|
|
{"jsonb - binary", "select '{}'::jsonb", pgx.BinaryFormatCode, []byte("{}")},
|
|
} {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
var buf []byte
|
|
err := conn.QueryRow(context.Background(), tt.sql, pgx.QueryResultFormats{tt.resultFormatCode}).Scan(&buf)
|
|
require.NoError(t, err)
|
|
require.Equal(t, tt.output, buf)
|
|
})
|
|
}
|
|
}
|