pgx/pgtype/text_test.go

179 lines
5.8 KiB
Go

package pgtype_test
import (
"context"
"testing"
pgx "github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype"
"github.com/jackc/pgx/v5/pgxtest"
"github.com/stretchr/testify/require"
)
type someFmtStringer struct{}
func (someFmtStringer) String() string {
return "some fmt.Stringer"
}
func TestTextCodec(t *testing.T) {
for _, pgTypeName := range []string{"text", "varchar"} {
pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, pgTypeName, []pgxtest.ValueRoundTripTest{
{
pgtype.Text{String: "", Valid: true},
new(pgtype.Text),
isExpectedEq(pgtype.Text{String: "", Valid: true}),
},
{
pgtype.Text{String: "foo", Valid: true},
new(pgtype.Text),
isExpectedEq(pgtype.Text{String: "foo", Valid: true}),
},
{nil, new(pgtype.Text), isExpectedEq(pgtype.Text{})},
{"foo", new(string), isExpectedEq("foo")},
{someFmtStringer{}, new(string), isExpectedEq("some fmt.Stringer")},
})
}
}
// name is PostgreSQL's special 63-byte data type, used for identifiers like table names. The pg_class.relname column
// is a good example of where the name data type is used.
//
// TextCodec does not do length checking. Inputting a longer name into PostgreSQL will result in silent truncation to
// 63 bytes.
//
// Length checking would be possible with a Codec specialized for "name" but it would be perfect because a
// custom-compiled PostgreSQL could have set NAMEDATALEN to a different value rather than the default 63.
//
// So this is simply a smoke test of the name type.
func TestTextCodecName(t *testing.T) {
pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "name", []pgxtest.ValueRoundTripTest{
{
pgtype.Text{String: "", Valid: true},
new(pgtype.Text),
isExpectedEq(pgtype.Text{String: "", Valid: true}),
},
{
pgtype.Text{String: "foo", Valid: true},
new(pgtype.Text),
isExpectedEq(pgtype.Text{String: "foo", Valid: true}),
},
{nil, new(pgtype.Text), isExpectedEq(pgtype.Text{})},
{"foo", new(string), isExpectedEq("foo")},
})
}
// Test fixed length char types like char(3)
func TestTextCodecBPChar(t *testing.T) {
skipCockroachDB(t, "Server does not properly handle bpchar with multi-byte character")
pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "char(3)", []pgxtest.ValueRoundTripTest{
{
pgtype.Text{String: "a ", Valid: true},
new(pgtype.Text),
isExpectedEq(pgtype.Text{String: "a ", Valid: true}),
},
{nil, new(pgtype.Text), isExpectedEq(pgtype.Text{})},
{" ", new(string), isExpectedEq(" ")},
{"", new(string), isExpectedEq(" ")},
{" 嗨 ", new(string), isExpectedEq(" 嗨 ")},
})
}
// ACLItem is used for PostgreSQL's aclitem data type. A sample aclitem
// might look like this:
//
// postgres=arwdDxt/postgres
//
// Note, however, that because the user/role name part of an aclitem is
// an identifier, it follows all the usual formatting rules for SQL
// identifiers: if it contains spaces and other special characters,
// it should appear in double-quotes:
//
// postgres=arwdDxt/"role with spaces"
//
// It only supports the text format.
func TestTextCodecACLItem(t *testing.T) {
ctr := defaultConnTestRunner
ctr.AfterConnect = func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
pgxtest.SkipCockroachDB(t, conn, "Server does not support type aclitem")
}
pgxtest.RunValueRoundTripTests(context.Background(), t, ctr, nil, "aclitem", []pgxtest.ValueRoundTripTest{
{
pgtype.Text{String: "postgres=arwdDxt/postgres", Valid: true},
new(pgtype.Text),
isExpectedEq(pgtype.Text{String: "postgres=arwdDxt/postgres", Valid: true}),
},
{pgtype.Text{}, new(pgtype.Text), isExpectedEq(pgtype.Text{})},
{nil, new(pgtype.Text), isExpectedEq(pgtype.Text{})},
})
}
func TestTextCodecACLItemRoleWithSpecialCharacters(t *testing.T) {
ctr := defaultConnTestRunner
ctr.AfterConnect = func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
pgxtest.SkipCockroachDB(t, conn, "Server does not support type aclitem")
// 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)
if commandTag.RowsAffected() == 0 {
t.Skipf("Role with special characters does not exist.")
}
}
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}),
},
})
}
func TestTextMarshalJSON(t *testing.T) {
successfulTests := []struct {
source pgtype.Text
result string
}{
{source: pgtype.Text{String: ""}, result: "null"},
{source: pgtype.Text{String: "a", Valid: true}, result: "\"a\""},
}
for i, tt := range successfulTests {
r, err := tt.source.MarshalJSON()
if err != nil {
t.Errorf("%d: %v", i, err)
}
if string(r) != tt.result {
t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, string(r))
}
}
}
func TestTextUnmarshalJSON(t *testing.T) {
successfulTests := []struct {
source string
result pgtype.Text
}{
{source: "null", result: pgtype.Text{String: ""}},
{source: "\"a\"", result: pgtype.Text{String: "a", Valid: true}},
}
for i, tt := range successfulTests {
var r pgtype.Text
err := r.UnmarshalJSON([]byte(tt.source))
if err != nil {
t.Errorf("%d: %v", i, err)
}
if r != tt.result {
t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, r)
}
}
}