github.com/jackc/pgx/v5@v5.5.5/pgtype/text_test.go (about) 1 package pgtype_test 2 3 import ( 4 "context" 5 "testing" 6 7 pgx "github.com/jackc/pgx/v5" 8 "github.com/jackc/pgx/v5/pgtype" 9 "github.com/jackc/pgx/v5/pgxtest" 10 "github.com/stretchr/testify/require" 11 ) 12 13 type someFmtStringer struct{} 14 15 func (someFmtStringer) String() string { 16 return "some fmt.Stringer" 17 } 18 19 func TestTextCodec(t *testing.T) { 20 for _, pgTypeName := range []string{"text", "varchar"} { 21 pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, pgTypeName, []pgxtest.ValueRoundTripTest{ 22 { 23 pgtype.Text{String: "", Valid: true}, 24 new(pgtype.Text), 25 isExpectedEq(pgtype.Text{String: "", Valid: true}), 26 }, 27 { 28 pgtype.Text{String: "foo", Valid: true}, 29 new(pgtype.Text), 30 isExpectedEq(pgtype.Text{String: "foo", Valid: true}), 31 }, 32 {nil, new(pgtype.Text), isExpectedEq(pgtype.Text{})}, 33 {"foo", new(string), isExpectedEq("foo")}, 34 {someFmtStringer{}, new(string), isExpectedEq("some fmt.Stringer")}, 35 }) 36 } 37 } 38 39 // name is PostgreSQL's special 63-byte data type, used for identifiers like table names. The pg_class.relname column 40 // is a good example of where the name data type is used. 41 // 42 // TextCodec does not do length checking. Inputting a longer name into PostgreSQL will result in silent truncation to 43 // 63 bytes. 44 // 45 // Length checking would be possible with a Codec specialized for "name" but it would be perfect because a 46 // custom-compiled PostgreSQL could have set NAMEDATALEN to a different value rather than the default 63. 47 // 48 // So this is simply a smoke test of the name type. 49 func TestTextCodecName(t *testing.T) { 50 pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "name", []pgxtest.ValueRoundTripTest{ 51 { 52 pgtype.Text{String: "", Valid: true}, 53 new(pgtype.Text), 54 isExpectedEq(pgtype.Text{String: "", Valid: true}), 55 }, 56 { 57 pgtype.Text{String: "foo", Valid: true}, 58 new(pgtype.Text), 59 isExpectedEq(pgtype.Text{String: "foo", Valid: true}), 60 }, 61 {nil, new(pgtype.Text), isExpectedEq(pgtype.Text{})}, 62 {"foo", new(string), isExpectedEq("foo")}, 63 }) 64 } 65 66 // Test fixed length char types like char(3) 67 func TestTextCodecBPChar(t *testing.T) { 68 skipCockroachDB(t, "Server does not properly handle bpchar with multi-byte character") 69 70 pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "char(3)", []pgxtest.ValueRoundTripTest{ 71 { 72 pgtype.Text{String: "a ", Valid: true}, 73 new(pgtype.Text), 74 isExpectedEq(pgtype.Text{String: "a ", Valid: true}), 75 }, 76 {nil, new(pgtype.Text), isExpectedEq(pgtype.Text{})}, 77 {" ", new(string), isExpectedEq(" ")}, 78 {"", new(string), isExpectedEq(" ")}, 79 {" 嗨 ", new(string), isExpectedEq(" 嗨 ")}, 80 }) 81 } 82 83 // ACLItem is used for PostgreSQL's aclitem data type. A sample aclitem 84 // might look like this: 85 // 86 // postgres=arwdDxt/postgres 87 // 88 // Note, however, that because the user/role name part of an aclitem is 89 // an identifier, it follows all the usual formatting rules for SQL 90 // identifiers: if it contains spaces and other special characters, 91 // it should appear in double-quotes: 92 // 93 // postgres=arwdDxt/"role with spaces" 94 // 95 // It only supports the text format. 96 func TestTextCodecACLItem(t *testing.T) { 97 ctr := defaultConnTestRunner 98 ctr.AfterConnect = func(ctx context.Context, t testing.TB, conn *pgx.Conn) { 99 pgxtest.SkipCockroachDB(t, conn, "Server does not support type aclitem") 100 } 101 102 pgxtest.RunValueRoundTripTests(context.Background(), t, ctr, nil, "aclitem", []pgxtest.ValueRoundTripTest{ 103 { 104 pgtype.Text{String: "postgres=arwdDxt/postgres", Valid: true}, 105 new(pgtype.Text), 106 isExpectedEq(pgtype.Text{String: "postgres=arwdDxt/postgres", Valid: true}), 107 }, 108 {pgtype.Text{}, new(pgtype.Text), isExpectedEq(pgtype.Text{})}, 109 {nil, new(pgtype.Text), isExpectedEq(pgtype.Text{})}, 110 }) 111 } 112 113 func TestTextCodecACLItemRoleWithSpecialCharacters(t *testing.T) { 114 ctr := defaultConnTestRunner 115 ctr.AfterConnect = func(ctx context.Context, t testing.TB, conn *pgx.Conn) { 116 pgxtest.SkipCockroachDB(t, conn, "Server does not support type aclitem") 117 118 // The tricky test user, below, has to actually exist so that it can be used in a test 119 // of aclitem formatting. It turns out aclitems cannot contain non-existing users/roles. 120 roleWithSpecialCharacters := ` tricky, ' } " \ test user ` 121 122 commandTag, err := conn.Exec(ctx, `select * from pg_roles where rolname = $1`, roleWithSpecialCharacters) 123 require.NoError(t, err) 124 125 if commandTag.RowsAffected() == 0 { 126 t.Skipf("Role with special characters does not exist.") 127 } 128 } 129 130 pgxtest.RunValueRoundTripTests(context.Background(), t, ctr, nil, "aclitem", []pgxtest.ValueRoundTripTest{ 131 { 132 pgtype.Text{String: `postgres=arwdDxt/" tricky, ' } "" \ test user "`, Valid: true}, 133 new(pgtype.Text), 134 isExpectedEq(pgtype.Text{String: `postgres=arwdDxt/" tricky, ' } "" \ test user "`, Valid: true}), 135 }, 136 }) 137 } 138 139 func TestTextMarshalJSON(t *testing.T) { 140 successfulTests := []struct { 141 source pgtype.Text 142 result string 143 }{ 144 {source: pgtype.Text{String: ""}, result: "null"}, 145 {source: pgtype.Text{String: "a", Valid: true}, result: "\"a\""}, 146 } 147 for i, tt := range successfulTests { 148 r, err := tt.source.MarshalJSON() 149 if err != nil { 150 t.Errorf("%d: %v", i, err) 151 } 152 153 if string(r) != tt.result { 154 t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, string(r)) 155 } 156 } 157 } 158 159 func TestTextUnmarshalJSON(t *testing.T) { 160 successfulTests := []struct { 161 source string 162 result pgtype.Text 163 }{ 164 {source: "null", result: pgtype.Text{String: ""}}, 165 {source: "\"a\"", result: pgtype.Text{String: "a", Valid: true}}, 166 } 167 for i, tt := range successfulTests { 168 var r pgtype.Text 169 err := r.UnmarshalJSON([]byte(tt.source)) 170 if err != nil { 171 t.Errorf("%d: %v", i, err) 172 } 173 174 if r != tt.result { 175 t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, r) 176 } 177 } 178 }