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  }