From c39924d0c67d4f5142fa5cdf56414a702c149f3c Mon Sep 17 00:00:00 2001 From: Jack Christensen Date: Fri, 31 Dec 2021 12:28:45 -0600 Subject: [PATCH] Improvements to ArrayCodec --- pgtype/array_codec_test.go | 116 ++++------ pgtype/array_getter_setter.go | 143 +++++++++++++ pgtype/array_getter_setter.go.erb | 117 ++++++++++ pgtype/int2_array_test.go | 342 ------------------------------ 4 files changed, 301 insertions(+), 417 deletions(-) create mode 100644 pgtype/array_getter_setter.go create mode 100644 pgtype/array_getter_setter.go.erb delete mode 100644 pgtype/int2_array_test.go diff --git a/pgtype/array_codec_test.go b/pgtype/array_codec_test.go index f213d0ec..c358586e 100644 --- a/pgtype/array_codec_test.go +++ b/pgtype/array_codec_test.go @@ -12,14 +12,13 @@ func TestArrayCodec(t *testing.T) { conn := testutil.MustConnectPgx(t) defer testutil.MustCloseContext(t, conn) - tests := []struct { - expected []int16 + for i, tt := range []struct { + expected interface{} }{ {[]int16(nil)}, {[]int16{}}, {[]int16{1, 2, 3}}, - } - for i, tt := range tests { + } { var actual []int16 err := conn.QueryRow( context.Background(), @@ -29,78 +28,45 @@ func TestArrayCodec(t *testing.T) { assert.NoErrorf(t, err, "%d", i) assert.Equalf(t, tt.expected, actual, "%d", i) } + + newInt16 := func(n int16) *int16 { return &n } + + for i, tt := range []struct { + expected interface{} + }{ + {[]*int16{newInt16(1), nil, newInt16(3), nil, newInt16(5)}}, + } { + var actual []*int16 + err := conn.QueryRow( + context.Background(), + "select $1::smallint[]", + tt.expected, + ).Scan(&actual) + assert.NoErrorf(t, err, "%d", i) + assert.Equalf(t, tt.expected, actual, "%d", i) + } } -// func TestArrayCodecValue(t *testing.T) { -// ArrayCodec := pgtype.NewArrayCodec("_text", pgtype.TextOID, func() pgtype.ValueTranscoder { return &pgtype.Text{} }) +func TestArrayCodecAnySlice(t *testing.T) { + conn := testutil.MustConnectPgx(t) + defer testutil.MustCloseContext(t, conn) -// err := ArrayCodec.Set(nil) -// require.NoError(t, err) + type _int16Slice []int16 -// gotValue := ArrayCodec.Get() -// require.Nil(t, gotValue) - -// slice := []string{"foo", "bar"} -// err = ArrayCodec.AssignTo(&slice) -// require.NoError(t, err) -// require.Nil(t, slice) - -// err = ArrayCodec.Set([]string{}) -// require.NoError(t, err) - -// gotValue = ArrayCodec.Get() -// require.Len(t, gotValue, 0) - -// err = ArrayCodec.AssignTo(&slice) -// require.NoError(t, err) -// require.EqualValues(t, []string{}, slice) - -// err = ArrayCodec.Set([]string{"baz", "quz"}) -// require.NoError(t, err) - -// gotValue = ArrayCodec.Get() -// require.Len(t, gotValue, 2) - -// err = ArrayCodec.AssignTo(&slice) -// require.NoError(t, err) -// require.EqualValues(t, []string{"baz", "quz"}, slice) -// } - -// func TestArrayCodecTranscode(t *testing.T) { -// conn := testutil.MustConnectPgx(t) -// defer testutil.MustCloseContext(t, conn) - -// conn.ConnInfo().RegisterDataType(pgtype.DataType{ -// Value: pgtype.NewArrayCodec("_text", pgtype.TextOID, func() pgtype.ValueTranscoder { return &pgtype.Text{} }), -// Name: "_text", -// OID: pgtype.TextArrayOID, -// }) - -// var dstStrings []string -// err := conn.QueryRow(context.Background(), "select $1::text[]", []string{"red", "green", "blue"}).Scan(&dstStrings) -// require.NoError(t, err) - -// require.EqualValues(t, []string{"red", "green", "blue"}, dstStrings) -// } - -// func TestArrayCodecEmptyArrayDoesNotBreakArrayCodec(t *testing.T) { -// conn := testutil.MustConnectPgx(t) -// defer testutil.MustCloseContext(t, conn) - -// conn.ConnInfo().RegisterDataType(pgtype.DataType{ -// Value: pgtype.NewArrayCodec("_text", pgtype.TextOID, func() pgtype.ValueTranscoder { return &pgtype.Text{} }), -// Name: "_text", -// OID: pgtype.TextArrayOID, -// }) - -// var dstStrings []string -// err := conn.QueryRow(context.Background(), "select '{}'::text[]").Scan(&dstStrings) -// require.NoError(t, err) - -// require.EqualValues(t, []string{}, dstStrings) - -// err = conn.QueryRow(context.Background(), "select $1::text[]", []string{"red", "green", "blue"}).Scan(&dstStrings) -// require.NoError(t, err) - -// require.EqualValues(t, []string{"red", "green", "blue"}, dstStrings) -// } + for i, tt := range []struct { + expected interface{} + }{ + {_int16Slice(nil)}, + {_int16Slice{}}, + {_int16Slice{1, 2, 3}}, + } { + var actual _int16Slice + err := conn.QueryRow( + context.Background(), + "select $1::smallint[]", + tt.expected, + ).Scan(&actual) + assert.NoErrorf(t, err, "%d", i) + assert.Equalf(t, tt.expected, actual, "%d", i) + } +} diff --git a/pgtype/array_getter_setter.go b/pgtype/array_getter_setter.go new file mode 100644 index 00000000..72a6f0e7 --- /dev/null +++ b/pgtype/array_getter_setter.go @@ -0,0 +1,143 @@ +package pgtype + +import ( + "fmt" + "reflect" +) + +type int16Array []int16 + +func (a int16Array) Dimensions() []ArrayDimension { + if a == nil { + return nil + } + + return []ArrayDimension{{Length: int32(len(a)), LowerBound: 1}} +} + +func (a int16Array) Index(i int) interface{} { + return a[i] +} + +func (a *int16Array) SetDimensions(dimensions []ArrayDimension) error { + if dimensions == nil { + a = nil + return nil + } + + elementCount := cardinality(dimensions) + *a = make(int16Array, elementCount) + return nil +} + +func (a int16Array) ScanIndex(i int) interface{} { + return &a[i] +} + +type uint16Array []uint16 + +func (a uint16Array) Dimensions() []ArrayDimension { + if a == nil { + return nil + } + + return []ArrayDimension{{Length: int32(len(a)), LowerBound: 1}} +} + +func (a uint16Array) Index(i int) interface{} { + return a[i] +} + +func (a *uint16Array) SetDimensions(dimensions []ArrayDimension) error { + if dimensions == nil { + a = nil + return nil + } + + elementCount := cardinality(dimensions) + *a = make(uint16Array, elementCount) + return nil +} + +func (a uint16Array) ScanIndex(i int) interface{} { + return &a[i] +} + +type anySliceArray struct { + slice reflect.Value +} + +func (a anySliceArray) Dimensions() []ArrayDimension { + if a.slice.IsNil() { + return nil + } + + return []ArrayDimension{{Length: int32(a.slice.Len()), LowerBound: 1}} +} + +func (a anySliceArray) Index(i int) interface{} { + return a.slice.Index(i).Interface() +} + +func (a *anySliceArray) SetDimensions(dimensions []ArrayDimension) error { + sliceType := a.slice.Type() + + if dimensions == nil { + a.slice.Set(reflect.Zero(sliceType)) + return nil + } + + elementCount := cardinality(dimensions) + slice := reflect.MakeSlice(sliceType, elementCount, elementCount) + a.slice.Set(slice) + return nil +} + +func (a anySliceArray) ScanIndex(i int) interface{} { + return a.slice.Index(i).Addr().Interface() +} + +func makeArrayGetter(a interface{}) (ArrayGetter, error) { + switch a := a.(type) { + case ArrayGetter: + return a, nil + + case []int16: + return (*int16Array)(&a), nil + + case []uint16: + return (*uint16Array)(&a), nil + + } + + reflectValue := reflect.ValueOf(a) + if reflectValue.Kind() == reflect.Slice { + return &anySliceArray{slice: reflectValue}, nil + } + + return nil, fmt.Errorf("cannot convert %T to ArrayGetter", a) +} + +func makeArraySetter(a interface{}) (ArraySetter, error) { + switch a := a.(type) { + case ArraySetter: + return a, nil + + case *[]int16: + return (*int16Array)(a), nil + + case *[]uint16: + return (*uint16Array)(a), nil + + } + + value := reflect.ValueOf(a) + if value.Kind() == reflect.Ptr { + elemValue := value.Elem() + if elemValue.Kind() == reflect.Slice { + return &anySliceArray{slice: elemValue}, nil + } + } + + return nil, fmt.Errorf("cannot convert %T to ArraySetter", a) +} diff --git a/pgtype/array_getter_setter.go.erb b/pgtype/array_getter_setter.go.erb new file mode 100644 index 00000000..01b7d4fa --- /dev/null +++ b/pgtype/array_getter_setter.go.erb @@ -0,0 +1,117 @@ +package pgtype + +import ( + "fmt" + "reflect" +) + +<% + types = [ + ["int16Array", "int16"], + ["uint16Array", "uint16"], + ] +%> + +<% types.each do |array_type, element_type| %> + type <%= array_type %> []<%= element_type %> + + func (a <%= array_type %>) Dimensions() []ArrayDimension { + if a == nil { + return nil + } + + return []ArrayDimension{{Length: int32(len(a)), LowerBound: 1}} + } + + func (a <%= array_type %>) Index(i int) interface{} { + return a[i] + } + + func (a *<%= array_type %>) SetDimensions(dimensions []ArrayDimension) error { + if dimensions == nil { + a = nil + return nil + } + + elementCount := cardinality(dimensions) + *a = make(<%= array_type %>, elementCount) + return nil + } + + func (a <%= array_type %>) ScanIndex(i int) interface{} { + return &a[i] + } +<% end %> + +type anySliceArray struct { + slice reflect.Value +} + +func (a anySliceArray) Dimensions() []ArrayDimension { + if a.slice.IsNil() { + return nil + } + + return []ArrayDimension{{Length: int32(a.slice.Len()), LowerBound: 1}} +} + +func (a anySliceArray) Index(i int) interface{} { + return a.slice.Index(i).Interface() +} + +func (a *anySliceArray) SetDimensions(dimensions []ArrayDimension) error { + sliceType := a.slice.Type() + + if dimensions == nil { + a.slice.Set(reflect.Zero(sliceType)) + return nil + } + + elementCount := cardinality(dimensions) + slice := reflect.MakeSlice(sliceType, elementCount, elementCount) + a.slice.Set(slice) + return nil +} + +func (a anySliceArray) ScanIndex(i int) interface{} { + return a.slice.Index(i).Addr().Interface() +} + +func makeArrayGetter(a interface{}) (ArrayGetter, error) { + switch a := a.(type) { + case ArrayGetter: + return a, nil + <% types.each do |array_type, element_type| %> + case []<%= element_type %>: + return (*<%= array_type %>)(&a), nil + <% end %> + } + + reflectValue := reflect.ValueOf(a) + if reflectValue.Kind() == reflect.Slice { + return &anySliceArray{slice: reflectValue}, nil + } + + return nil, fmt.Errorf("cannot convert %T to ArrayGetter", a) +} + +func makeArraySetter(a interface{}) (ArraySetter, error) { + switch a := a.(type) { + case ArraySetter: + return a, nil + <% types.each do |array_type, element_type| %> + case *[]<%= element_type %>: + return (*<%= array_type %>)(a), nil + <% end %> + } + + value := reflect.ValueOf(a) + if value.Kind() == reflect.Ptr { + elemValue := value.Elem() + if elemValue.Kind() == reflect.Slice { + return &anySliceArray{slice: elemValue}, nil + } + } + + return nil, fmt.Errorf("cannot convert %T to ArraySetter", a) +} diff --git a/pgtype/int2_array_test.go b/pgtype/int2_array_test.go deleted file mode 100644 index 110968fc..00000000 --- a/pgtype/int2_array_test.go +++ /dev/null @@ -1,342 +0,0 @@ -package pgtype_test - -import ( - "reflect" - "testing" - - "github.com/jackc/pgx/v5/pgtype" - "github.com/jackc/pgx/v5/pgtype/testutil" -) - -func TestInt2ArrayTranscode(t *testing.T) { - testutil.TestSuccessfulTranscode(t, "int2[]", []interface{}{ - &pgtype.Int2Array{ - Elements: nil, - Dimensions: nil, - Valid: true, - }, - &pgtype.Int2Array{ - Elements: []pgtype.Int2{ - {Int: 1, Valid: true}, - {}, - }, - Dimensions: []pgtype.ArrayDimension{{Length: 2, LowerBound: 1}}, - Valid: true, - }, - &pgtype.Int2Array{}, - &pgtype.Int2Array{ - Elements: []pgtype.Int2{ - {Int: 1, Valid: true}, - {Int: 2, Valid: true}, - {Int: 3, Valid: true}, - {Int: 4, Valid: true}, - {}, - {Int: 6, Valid: true}, - }, - Dimensions: []pgtype.ArrayDimension{{Length: 3, LowerBound: 1}, {Length: 2, LowerBound: 1}}, - Valid: true, - }, - &pgtype.Int2Array{ - Elements: []pgtype.Int2{ - {Int: 1, Valid: true}, - {Int: 2, Valid: true}, - {Int: 3, Valid: true}, - {Int: 4, Valid: true}, - }, - Dimensions: []pgtype.ArrayDimension{ - {Length: 2, LowerBound: 4}, - {Length: 2, LowerBound: 2}, - }, - Valid: true, - }, - }) -} - -func TestInt2ArraySet(t *testing.T) { - successfulTests := []struct { - source interface{} - result pgtype.Int2Array - }{ - { - source: []int64{1}, - result: pgtype.Int2Array{ - Elements: []pgtype.Int2{{Int: 1, Valid: true}}, - Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}}, - Valid: true}, - }, - { - source: []int32{1}, - result: pgtype.Int2Array{ - Elements: []pgtype.Int2{{Int: 1, Valid: true}}, - Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}}, - Valid: true}, - }, - { - source: []int16{1}, - result: pgtype.Int2Array{ - Elements: []pgtype.Int2{{Int: 1, Valid: true}}, - Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}}, - Valid: true}, - }, - { - source: []int{1}, - result: pgtype.Int2Array{ - Elements: []pgtype.Int2{{Int: 1, Valid: true}}, - Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}}, - Valid: true}, - }, - { - source: []uint64{1}, - result: pgtype.Int2Array{ - Elements: []pgtype.Int2{{Int: 1, Valid: true}}, - Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}}, - Valid: true}, - }, - { - source: []uint32{1}, - result: pgtype.Int2Array{ - Elements: []pgtype.Int2{{Int: 1, Valid: true}}, - Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}}, - Valid: true}, - }, - { - source: []uint16{1}, - result: pgtype.Int2Array{ - Elements: []pgtype.Int2{{Int: 1, Valid: true}}, - Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}}, - Valid: true}, - }, - { - source: (([]int16)(nil)), - result: pgtype.Int2Array{}, - }, - { - source: [][]int16{{1}, {2}}, - result: pgtype.Int2Array{ - Elements: []pgtype.Int2{{Int: 1, Valid: true}, {Int: 2, Valid: true}}, - Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}}, - Valid: true}, - }, - { - source: [][][][]int16{{{{1, 2, 3}}}, {{{4, 5, 6}}}}, - result: pgtype.Int2Array{ - Elements: []pgtype.Int2{ - {Int: 1, Valid: true}, - {Int: 2, Valid: true}, - {Int: 3, Valid: true}, - {Int: 4, Valid: true}, - {Int: 5, Valid: true}, - {Int: 6, Valid: true}}, - Dimensions: []pgtype.ArrayDimension{ - {LowerBound: 1, Length: 2}, - {LowerBound: 1, Length: 1}, - {LowerBound: 1, Length: 1}, - {LowerBound: 1, Length: 3}}, - Valid: true}, - }, - { - source: [2][1]int16{{1}, {2}}, - result: pgtype.Int2Array{ - Elements: []pgtype.Int2{{Int: 1, Valid: true}, {Int: 2, Valid: true}}, - Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}}, - Valid: true}, - }, - { - source: [2][1][1][3]int16{{{{1, 2, 3}}}, {{{4, 5, 6}}}}, - result: pgtype.Int2Array{ - Elements: []pgtype.Int2{ - {Int: 1, Valid: true}, - {Int: 2, Valid: true}, - {Int: 3, Valid: true}, - {Int: 4, Valid: true}, - {Int: 5, Valid: true}, - {Int: 6, Valid: true}}, - Dimensions: []pgtype.ArrayDimension{ - {LowerBound: 1, Length: 2}, - {LowerBound: 1, Length: 1}, - {LowerBound: 1, Length: 1}, - {LowerBound: 1, Length: 3}}, - Valid: true}, - }, - } - - for i, tt := range successfulTests { - var r pgtype.Int2Array - err := r.Set(tt.source) - if err != nil { - t.Errorf("%d: %v", i, err) - } - - if !reflect.DeepEqual(r, tt.result) { - t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, r) - } - } -} - -func TestInt2ArrayAssignTo(t *testing.T) { - var int16Slice []int16 - var uint16Slice []uint16 - var namedInt16Slice _int16Slice - var int16SliceDim2 [][]int16 - var int16SliceDim4 [][][][]int16 - var int16ArrayDim2 [2][1]int16 - var int16ArrayDim4 [2][1][1][3]int16 - - simpleTests := []struct { - src pgtype.Int2Array - dst interface{} - expected interface{} - }{ - { - src: pgtype.Int2Array{ - Elements: []pgtype.Int2{{Int: 1, Valid: true}}, - Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}}, - Valid: true, - }, - dst: &int16Slice, - expected: []int16{1}, - }, - { - src: pgtype.Int2Array{ - Elements: []pgtype.Int2{{Int: 1, Valid: true}}, - Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}}, - Valid: true, - }, - dst: &uint16Slice, - expected: []uint16{1}, - }, - { - src: pgtype.Int2Array{ - Elements: []pgtype.Int2{{Int: 1, Valid: true}}, - Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}}, - Valid: true, - }, - dst: &namedInt16Slice, - expected: _int16Slice{1}, - }, - { - src: pgtype.Int2Array{}, - dst: &int16Slice, - expected: (([]int16)(nil)), - }, - { - src: pgtype.Int2Array{Valid: true}, - dst: &int16Slice, - expected: []int16{}, - }, - { - src: pgtype.Int2Array{ - Elements: []pgtype.Int2{{Int: 1, Valid: true}, {Int: 2, Valid: true}}, - Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}}, - Valid: true}, - expected: [][]int16{{1}, {2}}, - dst: &int16SliceDim2, - }, - { - src: pgtype.Int2Array{ - Elements: []pgtype.Int2{ - {Int: 1, Valid: true}, - {Int: 2, Valid: true}, - {Int: 3, Valid: true}, - {Int: 4, Valid: true}, - {Int: 5, Valid: true}, - {Int: 6, Valid: true}}, - Dimensions: []pgtype.ArrayDimension{ - {LowerBound: 1, Length: 2}, - {LowerBound: 1, Length: 1}, - {LowerBound: 1, Length: 1}, - {LowerBound: 1, Length: 3}}, - Valid: true}, - expected: [][][][]int16{{{{1, 2, 3}}}, {{{4, 5, 6}}}}, - dst: &int16SliceDim4, - }, - { - src: pgtype.Int2Array{ - Elements: []pgtype.Int2{{Int: 1, Valid: true}, {Int: 2, Valid: true}}, - Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}}, - Valid: true}, - expected: [2][1]int16{{1}, {2}}, - dst: &int16ArrayDim2, - }, - { - src: pgtype.Int2Array{ - Elements: []pgtype.Int2{ - {Int: 1, Valid: true}, - {Int: 2, Valid: true}, - {Int: 3, Valid: true}, - {Int: 4, Valid: true}, - {Int: 5, Valid: true}, - {Int: 6, Valid: true}}, - Dimensions: []pgtype.ArrayDimension{ - {LowerBound: 1, Length: 2}, - {LowerBound: 1, Length: 1}, - {LowerBound: 1, Length: 1}, - {LowerBound: 1, Length: 3}}, - Valid: true}, - expected: [2][1][1][3]int16{{{{1, 2, 3}}}, {{{4, 5, 6}}}}, - dst: &int16ArrayDim4, - }, - } - - for i, tt := range simpleTests { - err := tt.src.AssignTo(tt.dst) - if err != nil { - t.Errorf("%d: %v", i, err) - } - - if dst := reflect.ValueOf(tt.dst).Elem().Interface(); !reflect.DeepEqual(dst, tt.expected) { - t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst) - } - } - - errorTests := []struct { - src pgtype.Int2Array - dst interface{} - }{ - { - src: pgtype.Int2Array{ - Elements: []pgtype.Int2{{}}, - Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}}, - Valid: true, - }, - dst: &int16Slice, - }, - { - src: pgtype.Int2Array{ - Elements: []pgtype.Int2{{Int: -1, Valid: true}}, - Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}}, - Valid: true, - }, - dst: &uint16Slice, - }, - { - src: pgtype.Int2Array{ - Elements: []pgtype.Int2{{Int: 1, Valid: true}, {Int: 2, Valid: true}}, - Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}, {LowerBound: 1, Length: 2}}, - Valid: true}, - dst: &int16ArrayDim2, - }, - { - src: pgtype.Int2Array{ - Elements: []pgtype.Int2{{Int: 1, Valid: true}, {Int: 2, Valid: true}}, - Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}, {LowerBound: 1, Length: 2}}, - Valid: true}, - dst: &int16Slice, - }, - { - src: pgtype.Int2Array{ - Elements: []pgtype.Int2{{Int: 1, Valid: true}, {Int: 2, Valid: true}}, - Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}}, - Valid: true}, - dst: &int16ArrayDim4, - }, - } - - for i, tt := range errorTests { - err := tt.src.AssignTo(tt.dst) - if err == nil { - t.Errorf("%d: expected error but none was returned (%v -> %v)", i, tt.src, tt.dst) - } - } - -}