diff --git a/pgtype/aclitem.go b/pgtype/aclitem.go deleted file mode 100644 index 0c1f23b5..00000000 --- a/pgtype/aclitem.go +++ /dev/null @@ -1,127 +0,0 @@ -package pgtype - -import ( - "database/sql/driver" - "fmt" -) - -// 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" -// -type ACLItem struct { - String string - Valid bool -} - -func (dst *ACLItem) Set(src interface{}) error { - if src == nil { - *dst = ACLItem{} - return nil - } - - if value, ok := src.(interface{ Get() interface{} }); ok { - value2 := value.Get() - if value2 != value { - return dst.Set(value2) - } - } - - switch value := src.(type) { - case string: - *dst = ACLItem{String: value, Valid: true} - case *string: - if value == nil { - *dst = ACLItem{} - } else { - *dst = ACLItem{String: *value, Valid: true} - } - default: - if originalSrc, ok := underlyingStringType(src); ok { - return dst.Set(originalSrc) - } - return fmt.Errorf("cannot convert %v to ACLItem", value) - } - - return nil -} - -func (dst ACLItem) Get() interface{} { - if !dst.Valid { - return nil - } - return dst.String -} - -func (src *ACLItem) AssignTo(dst interface{}) error { - if !src.Valid { - return NullAssignTo(dst) - } - - switch v := dst.(type) { - case *string: - *v = src.String - return nil - default: - if nextDst, retry := GetAssignToDstType(dst); retry { - return src.AssignTo(nextDst) - } - return fmt.Errorf("unable to assign to %T", dst) - } - - return fmt.Errorf("cannot decode %#v into %T", src, dst) -} - -func (dst *ACLItem) DecodeText(ci *ConnInfo, src []byte) error { - if src == nil { - *dst = ACLItem{} - return nil - } - - *dst = ACLItem{String: string(src), Valid: true} - return nil -} - -func (src ACLItem) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) { - if !src.Valid { - return nil, nil - } - - return append(buf, src.String...), nil -} - -// Scan implements the database/sql Scanner interface. -func (dst *ACLItem) Scan(src interface{}) error { - if src == nil { - *dst = ACLItem{} - return nil - } - - switch src := src.(type) { - case string: - return dst.DecodeText(nil, []byte(src)) - case []byte: - srcCopy := make([]byte, len(src)) - copy(srcCopy, src) - return dst.DecodeText(nil, srcCopy) - } - - return fmt.Errorf("cannot scan %T", src) -} - -// Value implements the database/sql/driver Valuer interface. -func (src ACLItem) Value() (driver.Value, error) { - if !src.Valid { - return nil, nil - } - - return src.String, nil -} diff --git a/pgtype/aclitem_array.go b/pgtype/aclitem_array.go deleted file mode 100644 index fc1128b7..00000000 --- a/pgtype/aclitem_array.go +++ /dev/null @@ -1,418 +0,0 @@ -// Code generated by erb. DO NOT EDIT. - -package pgtype - -import ( - "database/sql/driver" - "fmt" - "reflect" -) - -type ACLItemArray struct { - Elements []ACLItem - Dimensions []ArrayDimension - Valid bool -} - -func (dst *ACLItemArray) Set(src interface{}) error { - // untyped nil and typed nil interfaces are different - if src == nil { - *dst = ACLItemArray{} - return nil - } - - if value, ok := src.(interface{ Get() interface{} }); ok { - value2 := value.Get() - if value2 != value { - return dst.Set(value2) - } - } - - // Attempt to match to select common types: - switch value := src.(type) { - - case []string: - if value == nil { - *dst = ACLItemArray{} - } else if len(value) == 0 { - *dst = ACLItemArray{Valid: true} - } else { - elements := make([]ACLItem, len(value)) - for i := range value { - if err := elements[i].Set(value[i]); err != nil { - return err - } - } - *dst = ACLItemArray{ - Elements: elements, - Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}}, - Valid: true, - } - } - - case []*string: - if value == nil { - *dst = ACLItemArray{} - } else if len(value) == 0 { - *dst = ACLItemArray{Valid: true} - } else { - elements := make([]ACLItem, len(value)) - for i := range value { - if err := elements[i].Set(value[i]); err != nil { - return err - } - } - *dst = ACLItemArray{ - Elements: elements, - Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}}, - Valid: true, - } - } - - case []ACLItem: - if value == nil { - *dst = ACLItemArray{} - } else if len(value) == 0 { - *dst = ACLItemArray{Valid: true} - } else { - *dst = ACLItemArray{ - Elements: value, - Dimensions: []ArrayDimension{{Length: int32(len(value)), LowerBound: 1}}, - Valid: true, - } - } - default: - // Fallback to reflection if an optimised match was not found. - // The reflection is necessary for arrays and multidimensional slices, - // but it comes with a 20-50% performance penalty for large arrays/slices - reflectedValue := reflect.ValueOf(src) - if !reflectedValue.IsValid() || reflectedValue.IsZero() { - *dst = ACLItemArray{} - return nil - } - - dimensions, elementsLength, ok := findDimensionsFromValue(reflectedValue, nil, 0) - if !ok { - return fmt.Errorf("cannot find dimensions of %v for ACLItemArray", src) - } - if elementsLength == 0 { - *dst = ACLItemArray{Valid: true} - return nil - } - if len(dimensions) == 0 { - if originalSrc, ok := underlyingSliceType(src); ok { - return dst.Set(originalSrc) - } - return fmt.Errorf("cannot convert %v to ACLItemArray", src) - } - - *dst = ACLItemArray{ - Elements: make([]ACLItem, elementsLength), - Dimensions: dimensions, - Valid: true, - } - elementCount, err := dst.setRecursive(reflectedValue, 0, 0) - if err != nil { - // Maybe the target was one dimension too far, try again: - if len(dst.Dimensions) > 1 { - dst.Dimensions = dst.Dimensions[:len(dst.Dimensions)-1] - elementsLength = 0 - for _, dim := range dst.Dimensions { - if elementsLength == 0 { - elementsLength = int(dim.Length) - } else { - elementsLength *= int(dim.Length) - } - } - dst.Elements = make([]ACLItem, elementsLength) - elementCount, err = dst.setRecursive(reflectedValue, 0, 0) - if err != nil { - return err - } - } else { - return err - } - } - if elementCount != len(dst.Elements) { - return fmt.Errorf("cannot convert %v to ACLItemArray, expected %d dst.Elements, but got %d instead", src, len(dst.Elements), elementCount) - } - } - - return nil -} - -func (dst *ACLItemArray) setRecursive(value reflect.Value, index, dimension int) (int, error) { - switch value.Kind() { - case reflect.Array: - fallthrough - case reflect.Slice: - if len(dst.Dimensions) == dimension { - break - } - - valueLen := value.Len() - if int32(valueLen) != dst.Dimensions[dimension].Length { - return 0, fmt.Errorf("multidimensional arrays must have array expressions with matching dimensions") - } - for i := 0; i < valueLen; i++ { - var err error - index, err = dst.setRecursive(value.Index(i), index, dimension+1) - if err != nil { - return 0, err - } - } - - return index, nil - } - if !value.CanInterface() { - return 0, fmt.Errorf("cannot convert all values to ACLItemArray") - } - if err := dst.Elements[index].Set(value.Interface()); err != nil { - return 0, fmt.Errorf("%v in ACLItemArray", err) - } - index++ - - return index, nil -} - -func (dst ACLItemArray) Get() interface{} { - if !dst.Valid { - return nil - } - return dst -} - -func (src *ACLItemArray) AssignTo(dst interface{}) error { - if !src.Valid { - return NullAssignTo(dst) - } - - if len(src.Dimensions) <= 1 { - // Attempt to match to select common types: - switch v := dst.(type) { - - case *[]string: - *v = make([]string, len(src.Elements)) - for i := range src.Elements { - if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil { - return err - } - } - return nil - - case *[]*string: - *v = make([]*string, len(src.Elements)) - for i := range src.Elements { - if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil { - return err - } - } - return nil - - } - } - - // Try to convert to something AssignTo can use directly. - if nextDst, retry := GetAssignToDstType(dst); retry { - return src.AssignTo(nextDst) - } - - // Fallback to reflection if an optimised match was not found. - // The reflection is necessary for arrays and multidimensional slices, - // but it comes with a 20-50% performance penalty for large arrays/slices - value := reflect.ValueOf(dst) - if value.Kind() == reflect.Ptr { - value = value.Elem() - } - - switch value.Kind() { - case reflect.Array, reflect.Slice: - default: - return fmt.Errorf("cannot assign %T to %T", src, dst) - } - - if len(src.Elements) == 0 { - if value.Kind() == reflect.Slice { - value.Set(reflect.MakeSlice(value.Type(), 0, 0)) - return nil - } - } - - elementCount, err := src.assignToRecursive(value, 0, 0) - if err != nil { - return err - } - if elementCount != len(src.Elements) { - return fmt.Errorf("cannot assign %v, needed to assign %d elements, but only assigned %d", dst, len(src.Elements), elementCount) - } - - return nil -} - -func (src *ACLItemArray) assignToRecursive(value reflect.Value, index, dimension int) (int, error) { - switch kind := value.Kind(); kind { - case reflect.Array: - fallthrough - case reflect.Slice: - if len(src.Dimensions) == dimension { - break - } - - length := int(src.Dimensions[dimension].Length) - if reflect.Array == kind { - typ := value.Type() - if typ.Len() != length { - return 0, fmt.Errorf("expected size %d array, but %s has size %d array", length, typ, typ.Len()) - } - value.Set(reflect.New(typ).Elem()) - } else { - value.Set(reflect.MakeSlice(value.Type(), length, length)) - } - - var err error - for i := 0; i < length; i++ { - index, err = src.assignToRecursive(value.Index(i), index, dimension+1) - if err != nil { - return 0, err - } - } - - return index, nil - } - if len(src.Dimensions) != dimension { - return 0, fmt.Errorf("incorrect dimensions, expected %d, found %d", len(src.Dimensions), dimension) - } - if !value.CanAddr() { - return 0, fmt.Errorf("cannot assign all values from ACLItemArray") - } - addr := value.Addr() - if !addr.CanInterface() { - return 0, fmt.Errorf("cannot assign all values from ACLItemArray") - } - if err := src.Elements[index].AssignTo(addr.Interface()); err != nil { - return 0, err - } - index++ - return index, nil -} - -func (dst *ACLItemArray) DecodeText(ci *ConnInfo, src []byte) error { - if src == nil { - *dst = ACLItemArray{} - return nil - } - - uta, err := ParseUntypedTextArray(string(src)) - if err != nil { - return err - } - - var elements []ACLItem - - if len(uta.Elements) > 0 { - elements = make([]ACLItem, len(uta.Elements)) - - for i, s := range uta.Elements { - var elem ACLItem - var elemSrc []byte - if s != "NULL" || uta.Quoted[i] { - elemSrc = []byte(s) - } - err = elem.DecodeText(ci, elemSrc) - if err != nil { - return err - } - - elements[i] = elem - } - } - - *dst = ACLItemArray{Elements: elements, Dimensions: uta.Dimensions, Valid: true} - - return nil -} - -func (src ACLItemArray) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) { - if !src.Valid { - return nil, nil - } - - if len(src.Dimensions) == 0 { - return append(buf, '{', '}'), nil - } - - buf = EncodeTextArrayDimensions(buf, src.Dimensions) - - // dimElemCounts is the multiples of elements that each array lies on. For - // example, a single dimension array of length 4 would have a dimElemCounts of - // [4]. A multi-dimensional array of lengths [3,5,2] would have a - // dimElemCounts of [30,10,2]. This is used to simplify when to render a '{' - // or '}'. - dimElemCounts := make([]int, len(src.Dimensions)) - dimElemCounts[len(src.Dimensions)-1] = int(src.Dimensions[len(src.Dimensions)-1].Length) - for i := len(src.Dimensions) - 2; i > -1; i-- { - dimElemCounts[i] = int(src.Dimensions[i].Length) * dimElemCounts[i+1] - } - - inElemBuf := make([]byte, 0, 32) - for i, elem := range src.Elements { - if i > 0 { - buf = append(buf, ',') - } - - for _, dec := range dimElemCounts { - if i%dec == 0 { - buf = append(buf, '{') - } - } - - elemBuf, err := elem.EncodeText(ci, inElemBuf) - if err != nil { - return nil, err - } - if elemBuf == nil { - buf = append(buf, `NULL`...) - } else { - buf = append(buf, QuoteArrayElementIfNeeded(string(elemBuf))...) - } - - for _, dec := range dimElemCounts { - if (i+1)%dec == 0 { - buf = append(buf, '}') - } - } - } - - return buf, nil -} - -// Scan implements the database/sql Scanner interface. -func (dst *ACLItemArray) Scan(src interface{}) error { - if src == nil { - return dst.DecodeText(nil, nil) - } - - switch src := src.(type) { - case string: - return dst.DecodeText(nil, []byte(src)) - case []byte: - srcCopy := make([]byte, len(src)) - copy(srcCopy, src) - return dst.DecodeText(nil, srcCopy) - } - - return fmt.Errorf("cannot scan %T", src) -} - -// Value implements the database/sql/driver Valuer interface. -func (src ACLItemArray) Value() (driver.Value, error) { - buf, err := src.EncodeText(nil, nil) - if err != nil { - return nil, err - } - if buf == nil { - return nil, nil - } - - return string(buf), nil -} diff --git a/pgtype/aclitem_array_test.go b/pgtype/aclitem_array_test.go deleted file mode 100644 index bdae9b56..00000000 --- a/pgtype/aclitem_array_test.go +++ /dev/null @@ -1,329 +0,0 @@ -package pgtype_test - -import ( - "reflect" - "testing" - - "github.com/jackc/pgx/v5/pgtype" - "github.com/jackc/pgx/v5/pgtype/testutil" -) - -func TestACLItemArrayTranscode(t *testing.T) { - testutil.TestSuccessfulTranscode(t, "aclitem[]", []interface{}{ - &pgtype.ACLItemArray{ - Elements: nil, - Dimensions: nil, - Valid: true, - }, - &pgtype.ACLItemArray{ - Elements: []pgtype.ACLItem{ - {String: "=r/postgres", Valid: true}, - {}, - }, - Dimensions: []pgtype.ArrayDimension{{Length: 2, LowerBound: 1}}, - Valid: true, - }, - &pgtype.ACLItemArray{}, - &pgtype.ACLItemArray{ - Elements: []pgtype.ACLItem{ - {String: "=r/postgres", Valid: true}, - {String: "postgres=arwdDxt/postgres", Valid: true}, - //{String: `postgres=arwdDxt/" tricky, ' } "" \ test user "`, Valid: true}, - {String: `postgres=arwdDxt/postgres`, Valid: true}, // todo: remove after fixing above case - {String: "=r/postgres", Valid: true}, - {}, - {String: "=r/postgres", Valid: true}, - }, - Dimensions: []pgtype.ArrayDimension{{Length: 3, LowerBound: 1}, {Length: 2, LowerBound: 1}}, - Valid: true, - }, - &pgtype.ACLItemArray{ - Elements: []pgtype.ACLItem{ - {String: "=r/postgres", Valid: true}, - {String: "postgres=arwdDxt/postgres", Valid: true}, - {String: "=r/postgres", Valid: true}, - {String: "postgres=arwdDxt/postgres", Valid: true}, - }, - Dimensions: []pgtype.ArrayDimension{ - {Length: 2, LowerBound: 4}, - {Length: 2, LowerBound: 2}, - }, - Valid: true, - }, - }) -} - -func TestACLItemArraySet(t *testing.T) { - successfulTests := []struct { - source interface{} - result pgtype.ACLItemArray - }{ - { - source: []string{"=r/postgres"}, - result: pgtype.ACLItemArray{ - Elements: []pgtype.ACLItem{{String: "=r/postgres", Valid: true}}, - Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}}, - Valid: true}, - }, - { - source: (([]string)(nil)), - result: pgtype.ACLItemArray{}, - }, - { - source: [][]string{{"=r/postgres"}, {"postgres=arwdDxt/postgres"}}, - result: pgtype.ACLItemArray{ - Elements: []pgtype.ACLItem{ - {String: "=r/postgres", Valid: true}, - {String: "postgres=arwdDxt/postgres", Valid: true}}, - Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}}, - Valid: true}, - }, - { - source: [][][][]string{ - {{{ - "=r/postgres", - "postgres=arwdDxt/postgres", - "=r/postgres"}}}, - {{{ - "postgres=arwdDxt/postgres", - "=r/postgres", - "postgres=arwdDxt/postgres"}}}}, - result: pgtype.ACLItemArray{ - Elements: []pgtype.ACLItem{ - {String: "=r/postgres", Valid: true}, - {String: "postgres=arwdDxt/postgres", Valid: true}, - {String: "=r/postgres", Valid: true}, - {String: "postgres=arwdDxt/postgres", Valid: true}, - {String: "=r/postgres", Valid: true}, - {String: "postgres=arwdDxt/postgres", 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]string{{"=r/postgres"}, {"postgres=arwdDxt/postgres"}}, - result: pgtype.ACLItemArray{ - Elements: []pgtype.ACLItem{ - {String: "=r/postgres", Valid: true}, - {String: "postgres=arwdDxt/postgres", Valid: true}}, - Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}}, - Valid: true}, - }, - { - source: [2][1][1][3]string{ - {{{ - "=r/postgres", - "postgres=arwdDxt/postgres", - "=r/postgres"}}}, - {{{ - "postgres=arwdDxt/postgres", - "=r/postgres", - "postgres=arwdDxt/postgres"}}}}, - result: pgtype.ACLItemArray{ - Elements: []pgtype.ACLItem{ - {String: "=r/postgres", Valid: true}, - {String: "postgres=arwdDxt/postgres", Valid: true}, - {String: "=r/postgres", Valid: true}, - {String: "postgres=arwdDxt/postgres", Valid: true}, - {String: "=r/postgres", Valid: true}, - {String: "postgres=arwdDxt/postgres", 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.ACLItemArray - 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 TestACLItemArrayAssignTo(t *testing.T) { - var stringSlice []string - type _stringSlice []string - var namedStringSlice _stringSlice - var stringSliceDim2 [][]string - var stringSliceDim4 [][][][]string - var stringArrayDim2 [2][1]string - var stringArrayDim4 [2][1][1][3]string - - simpleTests := []struct { - src pgtype.ACLItemArray - dst interface{} - expected interface{} - }{ - { - src: pgtype.ACLItemArray{ - Elements: []pgtype.ACLItem{{String: "=r/postgres", Valid: true}}, - Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}}, - Valid: true, - }, - dst: &stringSlice, - expected: []string{"=r/postgres"}, - }, - { - src: pgtype.ACLItemArray{ - Elements: []pgtype.ACLItem{{String: "=r/postgres", Valid: true}}, - Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}}, - Valid: true, - }, - dst: &namedStringSlice, - expected: _stringSlice{"=r/postgres"}, - }, - { - src: pgtype.ACLItemArray{}, - dst: &stringSlice, - expected: (([]string)(nil)), - }, - { - src: pgtype.ACLItemArray{Valid: true}, - dst: &stringSlice, - expected: []string{}, - }, - { - src: pgtype.ACLItemArray{ - Elements: []pgtype.ACLItem{ - {String: "=r/postgres", Valid: true}, - {String: "postgres=arwdDxt/postgres", Valid: true}}, - Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}}, - Valid: true}, - dst: &stringSliceDim2, - expected: [][]string{{"=r/postgres"}, {"postgres=arwdDxt/postgres"}}, - }, - { - src: pgtype.ACLItemArray{ - Elements: []pgtype.ACLItem{ - {String: "=r/postgres", Valid: true}, - {String: "postgres=arwdDxt/postgres", Valid: true}, - {String: "=r/postgres", Valid: true}, - {String: "postgres=arwdDxt/postgres", Valid: true}, - {String: "=r/postgres", Valid: true}, - {String: "postgres=arwdDxt/postgres", Valid: true}}, - Dimensions: []pgtype.ArrayDimension{ - {LowerBound: 1, Length: 2}, - {LowerBound: 1, Length: 1}, - {LowerBound: 1, Length: 1}, - {LowerBound: 1, Length: 3}}, - Valid: true}, - dst: &stringSliceDim4, - expected: [][][][]string{ - {{{ - "=r/postgres", - "postgres=arwdDxt/postgres", - "=r/postgres"}}}, - {{{ - "postgres=arwdDxt/postgres", - "=r/postgres", - "postgres=arwdDxt/postgres"}}}}, - }, - { - src: pgtype.ACLItemArray{ - Elements: []pgtype.ACLItem{ - {String: "=r/postgres", Valid: true}, - {String: "postgres=arwdDxt/postgres", Valid: true}}, - Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}}, - Valid: true}, - dst: &stringArrayDim2, - expected: [2][1]string{{"=r/postgres"}, {"postgres=arwdDxt/postgres"}}, - }, - { - src: pgtype.ACLItemArray{ - Elements: []pgtype.ACLItem{ - {String: "=r/postgres", Valid: true}, - {String: "postgres=arwdDxt/postgres", Valid: true}, - {String: "=r/postgres", Valid: true}, - {String: "postgres=arwdDxt/postgres", Valid: true}, - {String: "=r/postgres", Valid: true}, - {String: "postgres=arwdDxt/postgres", Valid: true}}, - Dimensions: []pgtype.ArrayDimension{ - {LowerBound: 1, Length: 2}, - {LowerBound: 1, Length: 1}, - {LowerBound: 1, Length: 1}, - {LowerBound: 1, Length: 3}}, - Valid: true}, - dst: &stringArrayDim4, - expected: [2][1][1][3]string{ - {{{ - "=r/postgres", - "postgres=arwdDxt/postgres", - "=r/postgres"}}}, - {{{ - "postgres=arwdDxt/postgres", - "=r/postgres", - "postgres=arwdDxt/postgres"}}}}, - }, - } - - 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.ACLItemArray - dst interface{} - }{ - { - src: pgtype.ACLItemArray{ - Elements: []pgtype.ACLItem{{}}, - Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}}, - Valid: true, - }, - dst: &stringSlice, - }, - { - src: pgtype.ACLItemArray{ - Elements: []pgtype.ACLItem{ - {String: "=r/postgres", Valid: true}, - {String: "postgres=arwdDxt/postgres", Valid: true}}, - Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}, {LowerBound: 1, Length: 2}}, - Valid: true}, - dst: &stringArrayDim2, - }, - { - src: pgtype.ACLItemArray{ - Elements: []pgtype.ACLItem{ - {String: "=r/postgres", Valid: true}, - {String: "postgres=arwdDxt/postgres", Valid: true}}, - Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}, {LowerBound: 1, Length: 2}}, - Valid: true}, - dst: &stringSlice, - }, - { - src: pgtype.ACLItemArray{ - Elements: []pgtype.ACLItem{ - {String: "=r/postgres", Valid: true}, - {String: "postgres=arwdDxt/postgres", Valid: true}}, - Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}}, - Valid: true}, - dst: &stringArrayDim4, - }, - } - - 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) - } - } -} diff --git a/pgtype/aclitem_test.go b/pgtype/aclitem_test.go deleted file mode 100644 index 84388142..00000000 --- a/pgtype/aclitem_test.go +++ /dev/null @@ -1,97 +0,0 @@ -package pgtype_test - -import ( - "reflect" - "testing" - - "github.com/jackc/pgx/v5/pgtype" - "github.com/jackc/pgx/v5/pgtype/testutil" -) - -func TestACLItemTranscode(t *testing.T) { - testutil.TestSuccessfulTranscode(t, "aclitem", []interface{}{ - &pgtype.ACLItem{String: "postgres=arwdDxt/postgres", Valid: true}, - //&pgtype.ACLItem{String: `postgres=arwdDxt/" tricky, ' } "" \ test user "`, Valid: true}, - &pgtype.ACLItem{}, - }) -} - -func TestACLItemSet(t *testing.T) { - successfulTests := []struct { - source interface{} - result pgtype.ACLItem - }{ - {source: "postgres=arwdDxt/postgres", result: pgtype.ACLItem{String: "postgres=arwdDxt/postgres", Valid: true}}, - {source: (*string)(nil), result: pgtype.ACLItem{}}, - } - - for i, tt := range successfulTests { - var d pgtype.ACLItem - err := d.Set(tt.source) - if err != nil { - t.Errorf("%d: %v", i, err) - } - - if d != tt.result { - t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, d) - } - } -} - -func TestACLItemAssignTo(t *testing.T) { - var s string - var ps *string - - simpleTests := []struct { - src pgtype.ACLItem - dst interface{} - expected interface{} - }{ - {src: pgtype.ACLItem{String: "postgres=arwdDxt/postgres", Valid: true}, dst: &s, expected: "postgres=arwdDxt/postgres"}, - {src: pgtype.ACLItem{}, dst: &ps, expected: ((*string)(nil))}, - } - - 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(); dst != tt.expected { - t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst) - } - } - - pointerAllocTests := []struct { - src pgtype.ACLItem - dst interface{} - expected interface{} - }{ - {src: pgtype.ACLItem{String: "postgres=arwdDxt/postgres", Valid: true}, dst: &ps, expected: "postgres=arwdDxt/postgres"}, - } - - for i, tt := range pointerAllocTests { - err := tt.src.AssignTo(tt.dst) - if err != nil { - t.Errorf("%d: %v", i, err) - } - - if dst := reflect.ValueOf(tt.dst).Elem().Elem().Interface(); 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.ACLItem - dst interface{} - }{ - {src: pgtype.ACLItem{}, dst: &s}, - } - - 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) - } - } -} diff --git a/pgtype/pgtype.go b/pgtype/pgtype.go index 6ead989e..d4001392 100644 --- a/pgtype/pgtype.go +++ b/pgtype/pgtype.go @@ -253,7 +253,7 @@ func newConnInfo() *ConnInfo { func NewConnInfo() *ConnInfo { ci := newConnInfo() - ci.RegisterDataType(DataType{Value: &ACLItemArray{}, Name: "_aclitem", OID: ACLItemArrayOID}) + ci.RegisterDataType(DataType{Name: "_aclitem", OID: ACLItemArrayOID, Codec: &ArrayCodec{ElementCodec: &TextFormatOnlyCodec{TextCodec{}}, ElementOID: ACLItemOID}}) ci.RegisterDataType(DataType{Name: "_bool", OID: BoolArrayOID, Codec: &ArrayCodec{ElementCodec: BoolCodec{}, ElementOID: BoolOID}}) ci.RegisterDataType(DataType{Name: "_bpchar", OID: BPCharArrayOID, Codec: &ArrayCodec{ElementCodec: TextCodec{}, ElementOID: BPCharOID}}) ci.RegisterDataType(DataType{Value: &ByteaArray{}, Name: "_bytea", OID: ByteaArrayOID}) @@ -275,7 +275,7 @@ func NewConnInfo() *ConnInfo { ci.RegisterDataType(DataType{Value: &TimestamptzArray{}, Name: "_timestamptz", OID: TimestamptzArrayOID}) ci.RegisterDataType(DataType{Value: &UUIDArray{}, Name: "_uuid", OID: UUIDArrayOID}) ci.RegisterDataType(DataType{Name: "_varchar", OID: VarcharArrayOID, Codec: &ArrayCodec{ElementCodec: TextCodec{}, ElementOID: VarcharOID}}) - ci.RegisterDataType(DataType{Value: &ACLItem{}, Name: "aclitem", OID: ACLItemOID}) + ci.RegisterDataType(DataType{Name: "aclitem", OID: ACLItemOID, Codec: &TextFormatOnlyCodec{TextCodec{}}}) ci.RegisterDataType(DataType{Value: &Bit{}, Name: "bit", OID: BitOID}) ci.RegisterDataType(DataType{Name: "bool", OID: BoolOID, Codec: BoolCodec{}}) ci.RegisterDataType(DataType{Name: "box", OID: BoxOID, Codec: BoxCodec{}}) diff --git a/pgtype/pgtype_test.go b/pgtype/pgtype_test.go index 56064281..703e1843 100644 --- a/pgtype/pgtype_test.go +++ b/pgtype/pgtype_test.go @@ -308,11 +308,6 @@ func testPgxCodec(t testing.TB, pgTypeName string, tests []PgxTranscodeTestCase) conn := testutil.MustConnectPgx(t) defer testutil.MustCloseContext(t, conn) - _, err := conn.Prepare(context.Background(), "test", fmt.Sprintf("select $1::%s", pgTypeName)) - if err != nil { - t.Fatal(err) - } - formats := []struct { name string code int16 @@ -321,21 +316,30 @@ func testPgxCodec(t testing.TB, pgTypeName string, tests []PgxTranscodeTestCase) {name: "BinaryFormat", code: pgx.BinaryFormatCode}, } + for _, format := range formats { + testPgxCodecFormat(t, pgTypeName, tests, conn, format.name, format.code) + } +} + +func testPgxCodecFormat(t testing.TB, pgTypeName string, tests []PgxTranscodeTestCase, conn *pgx.Conn, formatName string, formatCode int16) { + _, err := conn.Prepare(context.Background(), "test", fmt.Sprintf("select $1::%s", pgTypeName)) + if err != nil { + t.Fatal(err) + } + for i, tt := range tests { - for _, format := range formats { - err := conn.QueryRow(context.Background(), "test", pgx.QueryResultFormats{format.code}, tt.src).Scan(tt.dst) - if err != nil { - t.Errorf("%s %d: %v", format.name, i, err) - } + err := conn.QueryRow(context.Background(), "test", pgx.QueryResultFormats{formatCode}, tt.src).Scan(tt.dst) + if err != nil { + t.Errorf("%s %d: %v", formatName, i, err) + } - dst := reflect.ValueOf(tt.dst) - if dst.Kind() == reflect.Ptr { - dst = dst.Elem() - } + dst := reflect.ValueOf(tt.dst) + if dst.Kind() == reflect.Ptr { + dst = dst.Elem() + } - if !tt.test(dst.Interface()) { - t.Errorf("%s %d: unexpected result for %v: %v", format.name, i, tt.src, dst.Interface()) - } + if !tt.test(dst.Interface()) { + t.Errorf("%s %d: unexpected result for %v: %v", formatName, i, tt.src, dst.Interface()) } } } diff --git a/pgtype/text_format_only_codec.go b/pgtype/text_format_only_codec.go new file mode 100644 index 00000000..d5e4cdb3 --- /dev/null +++ b/pgtype/text_format_only_codec.go @@ -0,0 +1,13 @@ +package pgtype + +type TextFormatOnlyCodec struct { + Codec +} + +func (c *TextFormatOnlyCodec) FormatSupported(format int16) bool { + return format == TextFormatCode && c.Codec.FormatSupported(format) +} + +func (TextFormatOnlyCodec) PreferredFormat() int16 { + return TextFormatCode +} diff --git a/pgtype/text_test.go b/pgtype/text_test.go index 148aa97b..27b01c15 100644 --- a/pgtype/text_test.go +++ b/pgtype/text_test.go @@ -1,9 +1,12 @@ package pgtype_test import ( + "context" "testing" "github.com/jackc/pgx/v5/pgtype" + "github.com/jackc/pgx/v5/pgtype/testutil" + "github.com/stretchr/testify/require" ) func TestTextCodec(t *testing.T) { @@ -68,6 +71,60 @@ func TestTextCodecBPChar(t *testing.T) { }) } +// 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) { + conn := testutil.MustConnectPgx(t) + defer testutil.MustCloseContext(t, conn) + + testPgxCodecFormat(t, "aclitem", []PgxTranscodeTestCase{ + { + 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{})}, + }, conn, "Text", pgtype.TextFormatCode) +} + +func TestTextCodecACLItemRoleWithSpecialCharacters(t *testing.T) { + conn := testutil.MustConnectPgx(t) + defer testutil.MustCloseContext(t, conn) + + ctx := context.Background() + + // 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.") + } + + testPgxCodecFormat(t, "aclitem", []PgxTranscodeTestCase{ + { + pgtype.Text{String: `postgres=arwdDxt/" tricky, ' } "" \ test user "`, Valid: true}, + new(pgtype.Text), + isExpectedEq(pgtype.Text{String: `postgres=arwdDxt/" tricky, ' } "" \ test user "`, Valid: true}), + }, + }, conn, "Text", pgtype.TextFormatCode) +} + func TestTextMarshalJSON(t *testing.T) { successfulTests := []struct { source pgtype.Text