2
0

Add multidimensional array and slice support.

Adds array support - previously only slices were supported.
Adds new test cases for multidimensional arrays and slices.
All previous test cases are unmodified and passed (fully backwards compatible).
Removes hard-coded type conversions for arrays, instead now relies on the type support of the array element's type conversion support.
Less maintenance for arrays, new type conversions are automatically supported when array's element gains new type support.
Simplifies typed_array_gen.sh generator script by removing the hard-coded single-dimensional types for arrays.
Only typed_array.go.erb and typed_array_gen.sh have been changed + 1 new auxiliary function in array.go file + additional tests in test files for each array. Other changes are from generated code.
This commit is contained in:
Simo Haasanen
2020-08-07 13:10:32 +01:00
parent d831ba712a
commit 449a8a4f8e
46 changed files with 6193 additions and 3113 deletions
+138 -74
View File
@@ -4,6 +4,7 @@ package pgtype
import (
"database/sql/driver"
"reflect"
errors "golang.org/x/xerrors"
)
@@ -28,68 +29,94 @@ func (dst *EnumArray) Set(src interface{}) error {
}
}
switch value := src.(type) {
value := reflect.ValueOf(src)
if !value.IsValid() || value.IsZero() {
*dst = EnumArray{Status: Null}
return nil
}
case []string:
if value == nil {
*dst = EnumArray{Status: Null}
} else if len(value) == 0 {
*dst = EnumArray{Status: Present}
} else {
elements := make([]GenericText, len(value))
for i := range value {
if err := elements[i].Set(value[i]); err != nil {
return err
}
}
*dst = EnumArray{
Elements: elements,
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
Status: Present,
}
}
case []*string:
if value == nil {
*dst = EnumArray{Status: Null}
} else if len(value) == 0 {
*dst = EnumArray{Status: Present}
} else {
elements := make([]GenericText, len(value))
for i := range value {
if err := elements[i].Set(value[i]); err != nil {
return err
}
}
*dst = EnumArray{
Elements: elements,
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
Status: Present,
}
}
case []GenericText:
if value == nil {
*dst = EnumArray{Status: Null}
} else if len(value) == 0 {
*dst = EnumArray{Status: Present}
} else {
*dst = EnumArray{
Elements: value,
Dimensions: []ArrayDimension{{Length: int32(len(value)), LowerBound: 1}},
Status: Present,
}
}
default:
dimensions, elementsLength, ok := findDimensionsFromValue(reflect.ValueOf(src), nil, 0)
if !ok {
return errors.Errorf("cannot find dimensions of %v for EnumArray", src)
}
if elementsLength == 0 {
*dst = EnumArray{Status: Present}
return nil
}
if len(dimensions) == 0 {
if originalSrc, ok := underlyingSliceType(src); ok {
return dst.Set(originalSrc)
}
return errors.Errorf("cannot convert %v to EnumArray", value)
return errors.Errorf("cannot convert %v to EnumArray", src)
}
*dst = EnumArray{
Elements: make([]GenericText, elementsLength),
Dimensions: dimensions,
Status: Present,
}
elementCount, err := dst.setRecursive(reflect.ValueOf(src), 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([]GenericText, elementsLength)
elementCount, err = dst.setRecursive(reflect.ValueOf(src), 0, 0)
if err != nil {
return err
}
} else {
return err
}
}
if elementCount != len(dst.Elements) {
return errors.Errorf("cannot convert %v to EnumArray, expected %d dst.Elements, but got %d instead", src, len(dst.Elements), elementCount)
}
return nil
}
func (dst *EnumArray) 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
}
if int32(value.Len()) != dst.Dimensions[dimension].Length {
return 0, errors.Errorf("multidimensional arrays must have array expressions with matching dimensions")
}
for i := 0; i < value.Len(); 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, errors.Errorf("cannot convert all values to EnumArray")
}
if err := dst.Elements[index].Set(value.Interface()); err != nil {
return 0, errors.Errorf("%v in EnumArray", err)
}
index++
return index, nil
}
func (dst EnumArray) Get() interface{} {
switch dst.Status {
case Present:
@@ -104,32 +131,26 @@ func (dst EnumArray) Get() interface{} {
func (src *EnumArray) AssignTo(dst interface{}) error {
switch src.Status {
case Present:
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
default:
value := reflect.ValueOf(dst)
if value.Kind() == reflect.Ptr {
value = value.Elem()
}
if !value.CanSet() {
if nextDst, retry := GetAssignToDstType(dst); retry {
return src.AssignTo(nextDst)
}
return errors.Errorf("unable to assign to %T", dst)
}
elementCount, err := src.assignToRecursive(value, 0, 0)
if err != nil {
return err
}
if elementCount != len(src.Elements) {
return errors.Errorf("cannot assign %v, needed to assign %d elements, but only assigned %d", dst, len(src.Elements), elementCount)
}
return nil
case Null:
return NullAssignTo(dst)
}
@@ -137,6 +158,49 @@ func (src *EnumArray) AssignTo(dst interface{}) error {
return errors.Errorf("cannot decode %#v into %T", src, dst)
}
func (src *EnumArray) 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 {
if value.Type().Len() != length {
return 0, errors.Errorf("expected size %d array, but %s has size %d array", length, value.Type(), value.Type().Len())
}
value.Set(reflect.New(value.Type()).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, errors.Errorf("incorrect dimensions, expected %d, found %d", len(src.Dimensions), dimension)
}
if !value.CanAddr() || !value.Addr().CanInterface() {
return 0, errors.Errorf("cannot assign all values from EnumArray")
}
err := src.Elements[index].AssignTo(value.Addr().Interface())
if err != nil {
return 0, err
}
index++
return index, nil
}
func (dst *EnumArray) DecodeText(ci *ConnInfo, src []byte) error {
if src == nil {
*dst = EnumArray{Status: Null}