github.com/jackc/pgx/v5@v5.5.5/pgtype/array_codec_test.go (about) 1 package pgtype_test 2 3 import ( 4 "context" 5 "encoding/hex" 6 "reflect" 7 "strings" 8 "testing" 9 10 pgx "github.com/jackc/pgx/v5" 11 "github.com/jackc/pgx/v5/pgtype" 12 "github.com/jackc/pgx/v5/pgxtest" 13 "github.com/stretchr/testify/assert" 14 "github.com/stretchr/testify/require" 15 ) 16 17 func TestArrayCodec(t *testing.T) { 18 defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) { 19 for i, tt := range []struct { 20 expected any 21 }{ 22 {[]int16(nil)}, 23 {[]int16{}}, 24 {[]int16{1, 2, 3}}, 25 } { 26 var actual []int16 27 err := conn.QueryRow( 28 ctx, 29 "select $1::smallint[]", 30 tt.expected, 31 ).Scan(&actual) 32 assert.NoErrorf(t, err, "%d", i) 33 assert.Equalf(t, tt.expected, actual, "%d", i) 34 } 35 36 newInt16 := func(n int16) *int16 { return &n } 37 38 for i, tt := range []struct { 39 expected any 40 }{ 41 {[]*int16{newInt16(1), nil, newInt16(3), nil, newInt16(5)}}, 42 } { 43 var actual []*int16 44 err := conn.QueryRow( 45 ctx, 46 "select $1::smallint[]", 47 tt.expected, 48 ).Scan(&actual) 49 assert.NoErrorf(t, err, "%d", i) 50 assert.Equalf(t, tt.expected, actual, "%d", i) 51 } 52 }) 53 } 54 55 func TestArrayCodecFlatArrayString(t *testing.T) { 56 testCases := []struct { 57 input []string 58 }{ 59 {nil}, 60 {[]string{}}, 61 {[]string{"a"}}, 62 {[]string{"a", "b"}}, 63 // previously had a bug with whitespace handling 64 {[]string{"\v", "\t", "\n", "\r", "\f", " "}}, 65 {[]string{"a\vb", "a\tb", "a\nb", "a\rb", "a\fb", "a b"}}, 66 } 67 68 queryModes := []pgx.QueryExecMode{pgx.QueryExecModeSimpleProtocol, pgx.QueryExecModeDescribeExec} 69 70 defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) { 71 for i, testCase := range testCases { 72 for _, queryMode := range queryModes { 73 var out []string 74 err := conn.QueryRow(ctx, "select $1::text[]", queryMode, testCase.input).Scan(&out) 75 if err != nil { 76 t.Fatalf("i=%d input=%#v queryMode=%s: Scan failed: %s", 77 i, testCase.input, queryMode, err) 78 } 79 if !reflect.DeepEqual(out, testCase.input) { 80 t.Errorf("i=%d input=%#v queryMode=%s: not equal output=%#v", 81 i, testCase.input, queryMode, out) 82 } 83 } 84 } 85 }) 86 } 87 88 func TestArrayCodecArray(t *testing.T) { 89 ctr := defaultConnTestRunner 90 ctr.AfterConnect = func(ctx context.Context, t testing.TB, conn *pgx.Conn) { 91 pgxtest.SkipCockroachDB(t, conn, "Server does not support multi-dimensional arrays") 92 } 93 94 ctr.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) { 95 for i, tt := range []struct { 96 expected any 97 }{ 98 {pgtype.Array[int32]{ 99 Elements: []int32{1, 2, 3, 4}, 100 Dims: []pgtype.ArrayDimension{ 101 {Length: 2, LowerBound: 2}, 102 {Length: 2, LowerBound: 2}, 103 }, 104 Valid: true, 105 }}, 106 } { 107 var actual pgtype.Array[int32] 108 err := conn.QueryRow( 109 ctx, 110 "select $1::int[]", 111 tt.expected, 112 ).Scan(&actual) 113 assert.NoErrorf(t, err, "%d", i) 114 assert.Equalf(t, tt.expected, actual, "%d", i) 115 } 116 }) 117 } 118 119 func TestArrayCodecNamedSliceType(t *testing.T) { 120 defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) { 121 type _int16Slice []int16 122 123 for i, tt := range []struct { 124 expected any 125 }{ 126 {_int16Slice(nil)}, 127 {_int16Slice{}}, 128 {_int16Slice{1, 2, 3}}, 129 } { 130 var actual _int16Slice 131 err := conn.QueryRow( 132 ctx, 133 "select $1::smallint[]", 134 tt.expected, 135 ).Scan(&actual) 136 assert.NoErrorf(t, err, "%d", i) 137 assert.Equalf(t, tt.expected, actual, "%d", i) 138 } 139 }) 140 } 141 142 // https://github.com/jackc/pgx/issues/1488 143 func TestArrayCodecAnySliceArgument(t *testing.T) { 144 defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) { 145 type _int16Slice []int16 146 147 for i, tt := range []struct { 148 arg any 149 expected []int16 150 }{ 151 {[]any{1, 2, 3}, []int16{1, 2, 3}}, 152 } { 153 var actual []int16 154 err := conn.QueryRow( 155 ctx, 156 "select $1::smallint[]", 157 tt.arg, 158 ).Scan(&actual) 159 assert.NoErrorf(t, err, "%d", i) 160 assert.Equalf(t, tt.expected, actual, "%d", i) 161 } 162 }) 163 } 164 165 // https://github.com/jackc/pgx/issues/1442 166 func TestArrayCodecAnyArray(t *testing.T) { 167 defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) { 168 type _point3 [3]float32 169 170 for i, tt := range []struct { 171 expected any 172 }{ 173 {_point3{0, 0, 0}}, 174 {_point3{1, 2, 3}}, 175 } { 176 var actual _point3 177 err := conn.QueryRow( 178 ctx, 179 "select $1::float4[]", 180 tt.expected, 181 ).Scan(&actual) 182 assert.NoErrorf(t, err, "%d", i) 183 assert.Equalf(t, tt.expected, actual, "%d", i) 184 } 185 }) 186 } 187 188 // https://github.com/jackc/pgx/issues/1273#issuecomment-1218262703 189 func TestArrayCodecSliceArgConversion(t *testing.T) { 190 defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) { 191 arg := []string{ 192 "3ad95bfd-ecea-4032-83c3-0c823cafb372", 193 "951baf11-c0cc-4afc-a779-abff0611dbf1", 194 "8327f244-7e2f-45e7-a10b-fbdc9d6f3378", 195 } 196 197 var expected []pgtype.UUID 198 199 for _, s := range arg { 200 buf, err := hex.DecodeString(strings.ReplaceAll(s, "-", "")) 201 require.NoError(t, err) 202 var u pgtype.UUID 203 copy(u.Bytes[:], buf) 204 u.Valid = true 205 expected = append(expected, u) 206 } 207 208 var actual []pgtype.UUID 209 err := conn.QueryRow( 210 ctx, 211 "select $1::uuid[]", 212 arg, 213 ).Scan(&actual) 214 require.NoError(t, err) 215 require.Equal(t, expected, actual) 216 }) 217 } 218 219 func TestArrayCodecDecodeValue(t *testing.T) { 220 defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, _ testing.TB, conn *pgx.Conn) { 221 for _, tt := range []struct { 222 sql string 223 expected any 224 }{ 225 { 226 sql: `select '{}'::int4[]`, 227 expected: []any{}, 228 }, 229 { 230 sql: `select '{1,2}'::int8[]`, 231 expected: []any{int64(1), int64(2)}, 232 }, 233 { 234 sql: `select '{foo,bar}'::text[]`, 235 expected: []any{"foo", "bar"}, 236 }, 237 } { 238 t.Run(tt.sql, func(t *testing.T) { 239 rows, err := conn.Query(ctx, tt.sql) 240 require.NoError(t, err) 241 242 for rows.Next() { 243 values, err := rows.Values() 244 require.NoError(t, err) 245 require.Len(t, values, 1) 246 require.Equal(t, tt.expected, values[0]) 247 } 248 249 require.NoError(t, rows.Err()) 250 }) 251 } 252 }) 253 } 254 255 func TestArrayCodecScanMultipleDimensions(t *testing.T) { 256 skipCockroachDB(t, "Server does not support nested arrays (https://github.com/cockroachdb/cockroach/issues/36815)") 257 258 defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) { 259 260 rows, err := conn.Query(ctx, `select '{{1,2,3,4}, {5,6,7,8}, {9,10,11,12}}'::int4[]`) 261 require.NoError(t, err) 262 263 for rows.Next() { 264 var ss [][]int32 265 err := rows.Scan(&ss) 266 require.NoError(t, err) 267 require.Equal(t, [][]int32{{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}}, ss) 268 } 269 270 require.NoError(t, rows.Err()) 271 }) 272 } 273 274 func TestArrayCodecScanMultipleDimensionsEmpty(t *testing.T) { 275 skipCockroachDB(t, "Server does not support nested arrays (https://github.com/cockroachdb/cockroach/issues/36815)") 276 277 defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) { 278 rows, err := conn.Query(ctx, `select '{}'::int4[]`) 279 require.NoError(t, err) 280 281 for rows.Next() { 282 var ss [][]int32 283 err := rows.Scan(&ss) 284 require.NoError(t, err) 285 require.Equal(t, [][]int32{}, ss) 286 } 287 288 require.NoError(t, rows.Err()) 289 }) 290 } 291 292 func TestArrayCodecScanWrongMultipleDimensions(t *testing.T) { 293 skipCockroachDB(t, "Server does not support nested arrays (https://github.com/cockroachdb/cockroach/issues/36815)") 294 295 defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) { 296 rows, err := conn.Query(ctx, `select '{{1,2,3,4}, {5,6,7,8}, {9,10,11,12}}'::int4[]`) 297 require.NoError(t, err) 298 299 for rows.Next() { 300 var ss [][][]int32 301 err := rows.Scan(&ss) 302 require.Error(t, err, "can't scan into dest[0]: PostgreSQL array has 2 dimensions but slice has 3 dimensions") 303 } 304 }) 305 } 306 307 func TestArrayCodecEncodeMultipleDimensions(t *testing.T) { 308 skipCockroachDB(t, "Server does not support nested arrays (https://github.com/cockroachdb/cockroach/issues/36815)") 309 310 defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) { 311 rows, err := conn.Query(ctx, `select $1::int4[]`, [][]int32{{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}}) 312 require.NoError(t, err) 313 314 for rows.Next() { 315 var ss [][]int32 316 err := rows.Scan(&ss) 317 require.NoError(t, err) 318 require.Equal(t, [][]int32{{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}}, ss) 319 } 320 321 require.NoError(t, rows.Err()) 322 }) 323 } 324 325 func TestArrayCodecEncodeMultipleDimensionsRagged(t *testing.T) { 326 skipCockroachDB(t, "Server does not support nested arrays (https://github.com/cockroachdb/cockroach/issues/36815)") 327 328 defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) { 329 rows, err := conn.Query(ctx, `select $1::int4[]`, [][]int32{{1, 2, 3, 4}, {5}, {9, 10, 11, 12}}) 330 require.Error(t, err, "cannot convert [][]int32 to ArrayGetter because it is a ragged multi-dimensional") 331 defer rows.Close() 332 }) 333 } 334 335 // https://github.com/jackc/pgx/issues/1494 336 func TestArrayCodecDecodeTextArrayWithTextOfNULL(t *testing.T) { 337 defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) { 338 { 339 var actual []string 340 err := conn.QueryRow(ctx, `select '{"foo", "NULL", " NULL "}'::text[]`).Scan(&actual) 341 require.NoError(t, err) 342 require.Equal(t, []string{"foo", "NULL", " NULL "}, actual) 343 } 344 345 { 346 var actual []pgtype.Text 347 err := conn.QueryRow(ctx, `select '{"foo", "NULL", NULL, " NULL "}'::text[]`).Scan(&actual) 348 require.NoError(t, err) 349 require.Equal(t, []pgtype.Text{ 350 {String: "foo", Valid: true}, 351 {String: "NULL", Valid: true}, 352 {}, 353 {String: " NULL ", Valid: true}, 354 }, actual) 355 } 356 }) 357 } 358 359 func TestArrayCodecDecodeTextArrayPrefersBinaryFormat(t *testing.T) { 360 defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) { 361 sd, err := conn.Prepare(ctx, "", `select '{"foo", "NULL", " NULL "}'::text[]`) 362 require.NoError(t, err) 363 require.Equal(t, int16(1), conn.TypeMap().FormatCodeForOID(sd.Fields[0].DataTypeOID)) 364 }) 365 }