Fix randomize for all array types, remove generic
* GenericArray can't work with generated code. * Multi-dimensional arrays can't work because PSQL does not have a method to discover array depth.
This commit is contained in:
parent
e62dfe369f
commit
9bcaf51493
6 changed files with 89 additions and 534 deletions
|
@ -367,12 +367,12 @@ func getArrayType(c bdb.Column) string {
|
|||
return "types.BytesArray"
|
||||
case "bit", "interval", "uuint", "bit varying", "character", "money", "character varying", "cidr", "inet", "macaddr", "text", "uuid", "xml":
|
||||
return "types.StringArray"
|
||||
case "bool":
|
||||
case "boolean":
|
||||
return "types.BoolArray"
|
||||
case "decimal", "numeric", "double precision", "real":
|
||||
return "types.Float64Array"
|
||||
default:
|
||||
return "types.GenericArray"
|
||||
return "types.StringArray"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -47,7 +47,6 @@ var (
|
|||
typeBoolArray = reflect.TypeOf(types.BoolArray{})
|
||||
typeFloat64Array = reflect.TypeOf(types.Float64Array{})
|
||||
typeStringArray = reflect.TypeOf(types.StringArray{})
|
||||
typeGenericArray = reflect.TypeOf(types.GenericArray{})
|
||||
typeHstore = reflect.TypeOf(types.Hstore{})
|
||||
rgxValidTime = regexp.MustCompile(`[2-9]+`)
|
||||
|
||||
|
@ -318,11 +317,7 @@ func randomizeField(s *Seed, field reflect.Value, fieldType string, canBeNull bo
|
|||
|
||||
// If it's a Postgres array, treat it like one
|
||||
if strings.HasPrefix(fieldType, "ARRAY") {
|
||||
if isNull {
|
||||
value = getArrayNullValue(typ)
|
||||
} else {
|
||||
value = getArrayRandValue(s, typ)
|
||||
}
|
||||
value = getArrayRandValue(s, typ, fieldType)
|
||||
// Retrieve the value to be returned
|
||||
} else if kind == reflect.Struct {
|
||||
if isNull {
|
||||
|
@ -347,27 +342,8 @@ func randomizeField(s *Seed, field reflect.Value, fieldType string, canBeNull bo
|
|||
return nil
|
||||
}
|
||||
|
||||
func getArrayNullValue(typ reflect.Type) interface{} {
|
||||
fmt.Println(typ)
|
||||
switch typ {
|
||||
case typeInt64Array:
|
||||
return types.Int64Array{}
|
||||
case typeFloat64Array:
|
||||
return types.Float64Array{}
|
||||
case typeBoolArray:
|
||||
return types.BoolArray{}
|
||||
case typeStringArray:
|
||||
return types.StringArray{}
|
||||
case typeBytesArray:
|
||||
return types.BytesArray{}
|
||||
case typeGenericArray:
|
||||
return types.GenericArray{}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getArrayRandValue(s *Seed, typ reflect.Type) interface{} {
|
||||
func getArrayRandValue(s *Seed, typ reflect.Type, fieldType string) interface{} {
|
||||
fieldType = strings.TrimLeft(fieldType, "ARRAY")
|
||||
switch typ {
|
||||
case typeInt64Array:
|
||||
return types.Int64Array{int64(s.nextInt()), int64(s.nextInt())}
|
||||
|
@ -376,11 +352,54 @@ func getArrayRandValue(s *Seed, typ reflect.Type) interface{} {
|
|||
case typeBoolArray:
|
||||
return types.BoolArray{s.nextInt()%2 == 0, s.nextInt()%2 == 0, s.nextInt()%2 == 0}
|
||||
case typeStringArray:
|
||||
if fieldType == "interval" {
|
||||
value := strconv.Itoa((s.nextInt()%26)+2) + " days"
|
||||
return types.StringArray{value, value}
|
||||
}
|
||||
if fieldType == "uuid" {
|
||||
value := uuid.NewV4().String()
|
||||
return types.StringArray{value, value}
|
||||
}
|
||||
if fieldType == "box" || fieldType == "line" || fieldType == "lseg" ||
|
||||
fieldType == "path" || fieldType == "polygon" {
|
||||
value := randBox()
|
||||
return types.StringArray{value, value}
|
||||
}
|
||||
if fieldType == "cidr" || fieldType == "inet" {
|
||||
value := randNetAddr()
|
||||
return types.StringArray{value, value}
|
||||
}
|
||||
if fieldType == "macaddr" {
|
||||
value := randMacAddr()
|
||||
return types.StringArray{value, value}
|
||||
}
|
||||
if fieldType == "circle" {
|
||||
value := randCircle()
|
||||
return types.StringArray{value, value}
|
||||
}
|
||||
if fieldType == "pg_lsn" {
|
||||
value := randLsn()
|
||||
return types.StringArray{value, value}
|
||||
}
|
||||
if fieldType == "point" {
|
||||
value := randPoint()
|
||||
return types.StringArray{value, value}
|
||||
}
|
||||
if fieldType == "txid_snapshot" {
|
||||
value := randTxID()
|
||||
return types.StringArray{value, value}
|
||||
}
|
||||
if fieldType == "money" {
|
||||
value := randMoney(s)
|
||||
return types.StringArray{value, value}
|
||||
}
|
||||
if fieldType == "json" || fieldType == "jsonb" {
|
||||
value := []byte(fmt.Sprintf(`"%s"`, randStr(s, 1)))
|
||||
return types.StringArray{string(value)}
|
||||
}
|
||||
return types.StringArray{randStr(s, 4), randStr(s, 4), randStr(s, 4)}
|
||||
case typeBytesArray:
|
||||
return types.BytesArray{randByteSlice(s, 4), randByteSlice(s, 4), randByteSlice(s, 4)}
|
||||
case typeGenericArray:
|
||||
return types.GenericArray{A: []types.JSON{randJSON(s, 4), randJSON(s, 4), randJSON(s, 4)}}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -574,17 +593,6 @@ func randByteSlice(s *Seed, ln int) []byte {
|
|||
return str
|
||||
}
|
||||
|
||||
func randJSON(s *Seed, ln int) types.JSON {
|
||||
str := make(types.JSON, ln)
|
||||
str[0] = '"'
|
||||
for i := 1; i < ln-1; i++ {
|
||||
str[i] = byte(s.nextInt() % 256)
|
||||
}
|
||||
str[ln-1] = '"'
|
||||
|
||||
return str
|
||||
}
|
||||
|
||||
func randPoint() string {
|
||||
a := rand.Intn(100)
|
||||
b := a + 1
|
||||
|
|
|
@ -184,9 +184,10 @@ func Array(a interface{}) interface {
|
|||
return (*Int64Array)(a)
|
||||
case *[]string:
|
||||
return (*StringArray)(a)
|
||||
}
|
||||
|
||||
return GenericArray{a}
|
||||
default:
|
||||
panic(fmt.Sprintf("boil: invalid type received %T", a))
|
||||
}
|
||||
}
|
||||
|
||||
// ArrayDelimiter may be optionally implemented by driver.Valuer or sql.Scanner
|
||||
|
@ -208,7 +209,7 @@ func (a *BoolArray) Scan(src interface{}) error {
|
|||
return a.scanBytes([]byte(src))
|
||||
}
|
||||
|
||||
return fmt.Errorf("pq: cannot convert %T to BoolArray", src)
|
||||
return fmt.Errorf("boil: cannot convert %T to BoolArray", src)
|
||||
}
|
||||
|
||||
func (a *BoolArray) scanBytes(src []byte) error {
|
||||
|
@ -222,7 +223,7 @@ func (a *BoolArray) scanBytes(src []byte) error {
|
|||
b := make(BoolArray, len(elems))
|
||||
for i, v := range elems {
|
||||
if len(v) != 1 {
|
||||
return fmt.Errorf("pq: could not parse boolean array index %d: invalid boolean %q", i, v)
|
||||
return fmt.Errorf("boil: could not parse boolean array index %d: invalid boolean %q", i, v)
|
||||
}
|
||||
switch v[0] {
|
||||
case 't':
|
||||
|
@ -230,7 +231,7 @@ func (a *BoolArray) scanBytes(src []byte) error {
|
|||
case 'f':
|
||||
b[i] = false
|
||||
default:
|
||||
return fmt.Errorf("pq: could not parse boolean array index %d: invalid boolean %q", i, v)
|
||||
return fmt.Errorf("boil: could not parse boolean array index %d: invalid boolean %q", i, v)
|
||||
}
|
||||
}
|
||||
*a = b
|
||||
|
@ -279,7 +280,7 @@ func (a *BytesArray) Scan(src interface{}) error {
|
|||
return a.scanBytes([]byte(src))
|
||||
}
|
||||
|
||||
return fmt.Errorf("pq: cannot convert %T to BytesArray", src)
|
||||
return fmt.Errorf("boil: cannot convert %T to BytesArray", src)
|
||||
}
|
||||
|
||||
func (a *BytesArray) scanBytes(src []byte) error {
|
||||
|
@ -348,7 +349,7 @@ func (a *Float64Array) Scan(src interface{}) error {
|
|||
return a.scanBytes([]byte(src))
|
||||
}
|
||||
|
||||
return fmt.Errorf("pq: cannot convert %T to Float64Array", src)
|
||||
return fmt.Errorf("boil: cannot convert %T to Float64Array", src)
|
||||
}
|
||||
|
||||
func (a *Float64Array) scanBytes(src []byte) error {
|
||||
|
@ -362,7 +363,7 @@ func (a *Float64Array) scanBytes(src []byte) error {
|
|||
b := make(Float64Array, len(elems))
|
||||
for i, v := range elems {
|
||||
if b[i], err = strconv.ParseFloat(string(v), 64); err != nil {
|
||||
return fmt.Errorf("pq: parsing array element index %d: %v", i, err)
|
||||
return fmt.Errorf("boil: parsing array element index %d: %v", i, err)
|
||||
}
|
||||
}
|
||||
*a = b
|
||||
|
@ -394,151 +395,6 @@ func (a Float64Array) Value() (driver.Value, error) {
|
|||
return "{}", nil
|
||||
}
|
||||
|
||||
// GenericArray implements the driver.Valuer and sql.Scanner interfaces for
|
||||
// an array or slice of any dimension.
|
||||
type GenericArray struct{ A interface{} }
|
||||
|
||||
func (GenericArray) evaluateDestination(rt reflect.Type) (reflect.Type, func([]byte, reflect.Value) error, string) {
|
||||
var assign func([]byte, reflect.Value) error
|
||||
var del = ","
|
||||
|
||||
// TODO calculate the assign function for other types
|
||||
// TODO repeat this section on the element type of arrays or slices (multidimensional)
|
||||
{
|
||||
if reflect.PtrTo(rt).Implements(typeSQLScanner) {
|
||||
// dest is always addressable because it is an element of a slice.
|
||||
assign = func(src []byte, dest reflect.Value) (err error) {
|
||||
ss := dest.Addr().Interface().(sql.Scanner)
|
||||
if src == nil {
|
||||
err = ss.Scan(nil)
|
||||
} else {
|
||||
err = ss.Scan(src)
|
||||
}
|
||||
return
|
||||
}
|
||||
goto FoundType
|
||||
}
|
||||
|
||||
assign = func([]byte, reflect.Value) error {
|
||||
return fmt.Errorf("pq: scanning to %s is not implemented; only sql.Scanner", rt)
|
||||
}
|
||||
}
|
||||
|
||||
FoundType:
|
||||
|
||||
if ad, ok := reflect.Zero(rt).Interface().(ArrayDelimiter); ok {
|
||||
del = ad.ArrayDelimiter()
|
||||
}
|
||||
|
||||
return rt, assign, del
|
||||
}
|
||||
|
||||
// Scan implements the sql.Scanner interface.
|
||||
func (a GenericArray) Scan(src interface{}) error {
|
||||
dpv := reflect.ValueOf(a.A)
|
||||
switch {
|
||||
case dpv.Kind() != reflect.Ptr:
|
||||
return fmt.Errorf("pq: destination %T is not a pointer to array or slice", a.A)
|
||||
case dpv.IsNil():
|
||||
return fmt.Errorf("pq: destination %T is nil", a.A)
|
||||
}
|
||||
|
||||
dv := dpv.Elem()
|
||||
switch dv.Kind() {
|
||||
case reflect.Slice:
|
||||
case reflect.Array:
|
||||
default:
|
||||
return fmt.Errorf("pq: destination %T is not a pointer to array or slice", a.A)
|
||||
}
|
||||
|
||||
switch src := src.(type) {
|
||||
case []byte:
|
||||
return a.scanBytes(src, dv)
|
||||
case string:
|
||||
return a.scanBytes([]byte(src), dv)
|
||||
}
|
||||
|
||||
return fmt.Errorf("pq: cannot convert %T to %s", src, dv.Type())
|
||||
}
|
||||
|
||||
func (a GenericArray) scanBytes(src []byte, dv reflect.Value) error {
|
||||
dtype, assign, del := a.evaluateDestination(dv.Type().Elem())
|
||||
dims, elems, err := parseArray(src, []byte(del))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO allow multidimensional
|
||||
|
||||
if len(dims) > 1 {
|
||||
return fmt.Errorf("pq: scanning from multidimensional ARRAY%s is not implemented",
|
||||
strings.Replace(fmt.Sprint(dims), " ", "][", -1))
|
||||
}
|
||||
|
||||
// Treat a zero-dimensional array like an array with a single dimension of zero.
|
||||
if len(dims) == 0 {
|
||||
dims = append(dims, 0)
|
||||
}
|
||||
|
||||
for i, rt := 0, dv.Type(); i < len(dims); i, rt = i+1, rt.Elem() {
|
||||
switch rt.Kind() {
|
||||
case reflect.Slice:
|
||||
case reflect.Array:
|
||||
if rt.Len() != dims[i] {
|
||||
return fmt.Errorf("pq: cannot convert ARRAY%s to %s",
|
||||
strings.Replace(fmt.Sprint(dims), " ", "][", -1), dv.Type())
|
||||
}
|
||||
default:
|
||||
// TODO handle multidimensional
|
||||
}
|
||||
}
|
||||
|
||||
values := reflect.MakeSlice(reflect.SliceOf(dtype), len(elems), len(elems))
|
||||
for i, e := range elems {
|
||||
if err := assign(e, values.Index(i)); err != nil {
|
||||
return fmt.Errorf("pq: parsing array element index %d: %v", i, err)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO handle multidimensional
|
||||
|
||||
switch dv.Kind() {
|
||||
case reflect.Slice:
|
||||
dv.Set(values.Slice(0, dims[0]))
|
||||
case reflect.Array:
|
||||
for i := 0; i < dims[0]; i++ {
|
||||
dv.Index(i).Set(values.Index(i))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Value implements the driver.Valuer interface.
|
||||
func (a GenericArray) Value() (driver.Value, error) {
|
||||
if a.A == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
rv := reflect.ValueOf(a.A)
|
||||
|
||||
if k := rv.Kind(); k != reflect.Array && k != reflect.Slice {
|
||||
return nil, fmt.Errorf("pq: Unable to convert %T to array", a.A)
|
||||
}
|
||||
|
||||
if n := rv.Len(); n > 0 {
|
||||
// There will be at least two curly brackets, N bytes of values,
|
||||
// and N-1 bytes of delimiters.
|
||||
b := make([]byte, 0, 1+2*n)
|
||||
|
||||
b, _, err := appendArray(b, rv, n)
|
||||
return string(b), err
|
||||
}
|
||||
|
||||
return "{}", nil
|
||||
}
|
||||
|
||||
// Int64Array represents a one-dimensional array of the PostgreSQL integer types.
|
||||
type Int64Array []int64
|
||||
|
||||
// Scan implements the sql.Scanner interface.
|
||||
|
@ -550,7 +406,7 @@ func (a *Int64Array) Scan(src interface{}) error {
|
|||
return a.scanBytes([]byte(src))
|
||||
}
|
||||
|
||||
return fmt.Errorf("pq: cannot convert %T to Int64Array", src)
|
||||
return fmt.Errorf("boil: cannot convert %T to Int64Array", src)
|
||||
}
|
||||
|
||||
func (a *Int64Array) scanBytes(src []byte) error {
|
||||
|
@ -564,7 +420,7 @@ func (a *Int64Array) scanBytes(src []byte) error {
|
|||
b := make(Int64Array, len(elems))
|
||||
for i, v := range elems {
|
||||
if b[i], err = strconv.ParseInt(string(v), 10, 64); err != nil {
|
||||
return fmt.Errorf("pq: parsing array element index %d: %v", i, err)
|
||||
return fmt.Errorf("boil: parsing array element index %d: %v", i, err)
|
||||
}
|
||||
}
|
||||
*a = b
|
||||
|
@ -608,7 +464,7 @@ func (a *StringArray) Scan(src interface{}) error {
|
|||
return a.scanBytes([]byte(src))
|
||||
}
|
||||
|
||||
return fmt.Errorf("pq: cannot convert %T to StringArray", src)
|
||||
return fmt.Errorf("boil: cannot convert %T to StringArray", src)
|
||||
}
|
||||
|
||||
func (a *StringArray) scanBytes(src []byte) error {
|
||||
|
@ -622,7 +478,7 @@ func (a *StringArray) scanBytes(src []byte) error {
|
|||
b := make(StringArray, len(elems))
|
||||
for i, v := range elems {
|
||||
if b[i] = string(v); v == nil {
|
||||
return fmt.Errorf("pq: parsing array element index %d: cannot convert nil to string", i)
|
||||
return fmt.Errorf("boil: parsing array element index %d: cannot convert nil to string", i)
|
||||
}
|
||||
}
|
||||
*a = b
|
||||
|
@ -753,7 +609,7 @@ func parseArray(src, del []byte) (dims []int, elems [][]byte, err error) {
|
|||
var depth, i int
|
||||
|
||||
if len(src) < 1 || src[0] != '{' {
|
||||
return nil, nil, fmt.Errorf("pq: unable to parse array; expected %q at offset %d", '{', 0)
|
||||
return nil, nil, fmt.Errorf("boil: unable to parse array; expected %q at offset %d", '{', 0)
|
||||
}
|
||||
|
||||
Open:
|
||||
|
@ -803,7 +659,7 @@ Element:
|
|||
if bytes.HasPrefix(src[i:], del) || src[i] == '}' {
|
||||
elem := src[start:i]
|
||||
if len(elem) == 0 {
|
||||
return nil, nil, fmt.Errorf("pq: unable to parse array; unexpected %q at offset %d", src[i], i)
|
||||
return nil, nil, fmt.Errorf("boil: unable to parse array; unexpected %q at offset %d", src[i], i)
|
||||
}
|
||||
if bytes.Equal(elem, []byte("NULL")) {
|
||||
elem = nil
|
||||
|
@ -825,7 +681,7 @@ Element:
|
|||
depth--
|
||||
i++
|
||||
} else {
|
||||
return nil, nil, fmt.Errorf("pq: unable to parse array; unexpected %q at offset %d", src[i], i)
|
||||
return nil, nil, fmt.Errorf("boil: unable to parse array; unexpected %q at offset %d", src[i], i)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -835,16 +691,16 @@ Close:
|
|||
depth--
|
||||
i++
|
||||
} else {
|
||||
return nil, nil, fmt.Errorf("pq: unable to parse array; unexpected %q at offset %d", src[i], i)
|
||||
return nil, nil, fmt.Errorf("boil: unable to parse array; unexpected %q at offset %d", src[i], i)
|
||||
}
|
||||
}
|
||||
if depth > 0 {
|
||||
err = fmt.Errorf("pq: unable to parse array; expected %q at offset %d", '}', i)
|
||||
err = fmt.Errorf("boil: unable to parse array; expected %q at offset %d", '}', i)
|
||||
}
|
||||
if err == nil {
|
||||
for _, d := range dims {
|
||||
if (len(elems) % d) != 0 {
|
||||
err = fmt.Errorf("pq: multidimensional arrays must have elements with matching dimensions")
|
||||
err = fmt.Errorf("boil: multidimensional arrays must have elements with matching dimensions")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -857,7 +713,7 @@ func scanLinearArray(src, del []byte, typ string) (elems [][]byte, err error) {
|
|||
return nil, err
|
||||
}
|
||||
if len(dims) > 1 {
|
||||
return nil, fmt.Errorf("pq: cannot convert ARRAY%s to %s", strings.Replace(fmt.Sprint(dims), " ", "][", -1), typ)
|
||||
return nil, fmt.Errorf("boil: cannot convert ARRAY%s to %s", strings.Replace(fmt.Sprint(dims), " ", "][", -1), typ)
|
||||
}
|
||||
return elems, err
|
||||
}
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"math/rand"
|
||||
|
@ -125,19 +124,6 @@ func TestArrayScanner(t *testing.T) {
|
|||
if _, ok := s.(*StringArray); !ok {
|
||||
t.Errorf("Expected *StringArray, got %T", s)
|
||||
}
|
||||
|
||||
for _, tt := range []interface{}{
|
||||
&[]sql.Scanner{},
|
||||
&[][]bool{},
|
||||
&[][]float64{},
|
||||
&[][]int64{},
|
||||
&[][]string{},
|
||||
} {
|
||||
s = Array(tt)
|
||||
if _, ok := s.(GenericArray); !ok {
|
||||
t.Errorf("Expected GenericArray for %T, got %T", tt, s)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestArrayValuer(t *testing.T) {
|
||||
|
@ -162,20 +148,6 @@ func TestArrayValuer(t *testing.T) {
|
|||
if _, ok := v.(*StringArray); !ok {
|
||||
t.Errorf("Expected *StringArray, got %T", v)
|
||||
}
|
||||
|
||||
for _, tt := range []interface{}{
|
||||
nil,
|
||||
[]driver.Value{},
|
||||
[][]bool{},
|
||||
[][]float64{},
|
||||
[][]int64{},
|
||||
[][]string{},
|
||||
} {
|
||||
v = Array(tt)
|
||||
if _, ok := v.(GenericArray); !ok {
|
||||
t.Errorf("Expected GenericArray for %T, got %T", tt, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBoolArrayScanUnsupported(t *testing.T) {
|
||||
|
@ -826,300 +798,3 @@ func BenchmarkStringArrayValue(b *testing.B) {
|
|||
a.Value()
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenericArrayScanUnsupported(t *testing.T) {
|
||||
var s string
|
||||
var ss []string
|
||||
|
||||
for _, tt := range []struct {
|
||||
src, dest interface{}
|
||||
err string
|
||||
}{
|
||||
{nil, nil, "destination <nil> is not a pointer to array or slice"},
|
||||
{nil, true, "destination bool is not a pointer to array or slice"},
|
||||
{nil, &s, "destination *string is not a pointer to array or slice"},
|
||||
{nil, ss, "destination []string is not a pointer to array or slice"},
|
||||
{true, &ss, "bool to []string"},
|
||||
{`{{x}}`, &ss, "multidimensional ARRAY[1][1] is not implemented"},
|
||||
{`{{x},{x}}`, &ss, "multidimensional ARRAY[2][1] is not implemented"},
|
||||
{`{x}`, &ss, "scanning to string is not implemented"},
|
||||
} {
|
||||
err := GenericArray{tt.dest}.Scan(tt.src)
|
||||
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error for [%#v %#v]", tt.src, tt.dest)
|
||||
}
|
||||
if !strings.Contains(err.Error(), tt.err) {
|
||||
t.Errorf("Expected error to contain %q for [%#v %#v], got %q", tt.err, tt.src, tt.dest, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenericArrayScanScannerArrayBytes(t *testing.T) {
|
||||
src, expected, nsa := []byte(`{NULL,abc,"\""}`),
|
||||
[3]sql.NullString{{}, {String: `abc`, Valid: true}, {String: `"`, Valid: true}},
|
||||
[3]sql.NullString{{String: ``, Valid: true}, {}, {}}
|
||||
|
||||
if err := (GenericArray{&nsa}).Scan(src); err != nil {
|
||||
t.Fatalf("Expected no error, got %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(nsa, expected) {
|
||||
t.Errorf("Expected %v, got %v", expected, nsa)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenericArrayScanScannerArrayString(t *testing.T) {
|
||||
src, expected, nsa := `{NULL,"\"",xyz}`,
|
||||
[3]sql.NullString{{}, {String: `"`, Valid: true}, {String: `xyz`, Valid: true}},
|
||||
[3]sql.NullString{{String: ``, Valid: true}, {}, {}}
|
||||
|
||||
if err := (GenericArray{&nsa}).Scan(src); err != nil {
|
||||
t.Fatalf("Expected no error, got %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(nsa, expected) {
|
||||
t.Errorf("Expected %v, got %v", expected, nsa)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenericArrayScanScannerSliceBytes(t *testing.T) {
|
||||
src, expected, nss := []byte(`{NULL,abc,"\""}`),
|
||||
[]sql.NullString{{}, {String: `abc`, Valid: true}, {String: `"`, Valid: true}},
|
||||
[]sql.NullString{{String: ``, Valid: true}, {}, {}, {}, {}}
|
||||
|
||||
if err := (GenericArray{&nss}).Scan(src); err != nil {
|
||||
t.Fatalf("Expected no error, got %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(nss, expected) {
|
||||
t.Errorf("Expected %v, got %v", expected, nss)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkGenericArrayScanScannerSliceBytes(b *testing.B) {
|
||||
var a GenericArray
|
||||
var x interface{} = []byte(`{a,b,c,d,e,f,g,h,i,j}`)
|
||||
var y interface{} = []byte(`{"\a","\b","\c","\d","\e","\f","\g","\h","\i","\j"}`)
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
a = GenericArray{new([]sql.NullString)}
|
||||
a.Scan(x)
|
||||
a = GenericArray{new([]sql.NullString)}
|
||||
a.Scan(y)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenericArrayScanScannerSliceString(t *testing.T) {
|
||||
src, expected, nss := `{NULL,"\"",xyz}`,
|
||||
[]sql.NullString{{}, {String: `"`, Valid: true}, {String: `xyz`, Valid: true}},
|
||||
[]sql.NullString{{String: ``, Valid: true}, {}, {}}
|
||||
|
||||
if err := (GenericArray{&nss}).Scan(src); err != nil {
|
||||
t.Fatalf("Expected no error, got %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(nss, expected) {
|
||||
t.Errorf("Expected %v, got %v", expected, nss)
|
||||
}
|
||||
}
|
||||
|
||||
type TildeNullInt64 struct{ sql.NullInt64 }
|
||||
|
||||
func (TildeNullInt64) ArrayDelimiter() string { return "~" }
|
||||
|
||||
func TestGenericArrayScanDelimiter(t *testing.T) {
|
||||
src, expected, tnis := `{12~NULL~76}`,
|
||||
[]TildeNullInt64{{sql.NullInt64{Int64: 12, Valid: true}}, {}, {sql.NullInt64{Int64: 76, Valid: true}}},
|
||||
[]TildeNullInt64{{sql.NullInt64{Int64: 0, Valid: true}}, {}}
|
||||
|
||||
if err := (GenericArray{&tnis}).Scan(src); err != nil {
|
||||
t.Fatalf("Expected no error for %#v, got %v", src, err)
|
||||
}
|
||||
if !reflect.DeepEqual(tnis, expected) {
|
||||
t.Errorf("Expected %v for %#v, got %v", expected, src, tnis)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenericArrayScanErrors(t *testing.T) {
|
||||
var sa [1]string
|
||||
var nis []sql.NullInt64
|
||||
var pss *[]string
|
||||
|
||||
for _, tt := range []struct {
|
||||
src, dest interface{}
|
||||
err string
|
||||
}{
|
||||
{nil, pss, "destination *[]string is nil"},
|
||||
{`{`, &sa, "unable to parse"},
|
||||
{`{}`, &sa, "cannot convert ARRAY[0] to [1]string"},
|
||||
{`{x,x}`, &sa, "cannot convert ARRAY[2] to [1]string"},
|
||||
{`{x}`, &nis, `parsing array element index 0: converting`},
|
||||
} {
|
||||
err := GenericArray{tt.dest}.Scan(tt.src)
|
||||
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error for [%#v %#v]", tt.src, tt.dest)
|
||||
}
|
||||
if !strings.Contains(err.Error(), tt.err) {
|
||||
t.Errorf("Expected error to contain %q for [%#v %#v], got %q", tt.err, tt.src, tt.dest, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenericArrayValueUnsupported(t *testing.T) {
|
||||
_, err := GenericArray{true}.Value()
|
||||
|
||||
if err == nil {
|
||||
t.Fatal("Expected error for bool")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "bool to array") {
|
||||
t.Errorf("Expected type to be mentioned, got %q", err)
|
||||
}
|
||||
}
|
||||
|
||||
type ByteArrayValuer [1]byte
|
||||
type ByteSliceValuer []byte
|
||||
type FuncArrayValuer struct {
|
||||
delimiter func() string
|
||||
value func() (driver.Value, error)
|
||||
}
|
||||
|
||||
func (a ByteArrayValuer) Value() (driver.Value, error) { return a[:], nil }
|
||||
func (b ByteSliceValuer) Value() (driver.Value, error) { return []byte(b), nil }
|
||||
func (f FuncArrayValuer) ArrayDelimiter() string { return f.delimiter() }
|
||||
func (f FuncArrayValuer) Value() (driver.Value, error) { return f.value() }
|
||||
|
||||
func TestGenericArrayValue(t *testing.T) {
|
||||
result, err := GenericArray{nil}.Value()
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error for nil, got %v", err)
|
||||
}
|
||||
if result != nil {
|
||||
t.Errorf("Expected nil, got %q", result)
|
||||
}
|
||||
|
||||
Tilde := func(v driver.Value) FuncArrayValuer {
|
||||
return FuncArrayValuer{
|
||||
func() string { return "~" },
|
||||
func() (driver.Value, error) { return v, nil }}
|
||||
}
|
||||
|
||||
for _, tt := range []struct {
|
||||
result string
|
||||
input interface{}
|
||||
}{
|
||||
{`{}`, []bool{}},
|
||||
{`{true}`, []bool{true}},
|
||||
{`{true,false}`, []bool{true, false}},
|
||||
{`{true,false}`, [2]bool{true, false}},
|
||||
|
||||
{`{}`, [][]int{{}}},
|
||||
{`{}`, [][]int{{}, {}}},
|
||||
{`{{1}}`, [][]int{{1}}},
|
||||
{`{{1},{2}}`, [][]int{{1}, {2}}},
|
||||
{`{{1,2},{3,4}}`, [][]int{{1, 2}, {3, 4}}},
|
||||
{`{{1,2},{3,4}}`, [2][2]int{{1, 2}, {3, 4}}},
|
||||
|
||||
{`{"a","\\b","c\"","d,e"}`, []string{`a`, `\b`, `c"`, `d,e`}},
|
||||
{`{"a","\\b","c\"","d,e"}`, [][]byte{{'a'}, {'\\', 'b'}, {'c', '"'}, {'d', ',', 'e'}}},
|
||||
|
||||
{`{NULL}`, []*int{nil}},
|
||||
{`{0,NULL}`, []*int{new(int), nil}},
|
||||
|
||||
{`{NULL}`, []sql.NullString{{}}},
|
||||
{`{"\"",NULL}`, []sql.NullString{{String: `"`, Valid: true}, {}}},
|
||||
|
||||
{`{"a","b"}`, []ByteArrayValuer{{'a'}, {'b'}}},
|
||||
{`{{"a","b"},{"c","d"}}`, [][]ByteArrayValuer{{{'a'}, {'b'}}, {{'c'}, {'d'}}}},
|
||||
|
||||
{`{"e","f"}`, []ByteSliceValuer{{'e'}, {'f'}}},
|
||||
{`{{"e","f"},{"g","h"}}`, [][]ByteSliceValuer{{{'e'}, {'f'}}, {{'g'}, {'h'}}}},
|
||||
|
||||
{`{1~2}`, []FuncArrayValuer{Tilde(int64(1)), Tilde(int64(2))}},
|
||||
{`{{1~2}~{3~4}}`, [][]FuncArrayValuer{{Tilde(int64(1)), Tilde(int64(2))}, {Tilde(int64(3)), Tilde(int64(4))}}},
|
||||
} {
|
||||
result, err := GenericArray{tt.input}.Value()
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error for %q, got %v", tt.input, err)
|
||||
}
|
||||
if !reflect.DeepEqual(result, tt.result) {
|
||||
t.Errorf("Expected %q for %q, got %q", tt.result, tt.input, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenericArrayValueErrors(t *testing.T) {
|
||||
var v []interface{}
|
||||
|
||||
v = []interface{}{func() {}}
|
||||
if _, err := (GenericArray{v}).Value(); err == nil {
|
||||
t.Errorf("Expected error for %q, got nil", v)
|
||||
}
|
||||
|
||||
v = []interface{}{nil, func() {}}
|
||||
if _, err := (GenericArray{v}).Value(); err == nil {
|
||||
t.Errorf("Expected error for %q, got nil", v)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkGenericArrayValueBools(b *testing.B) {
|
||||
rand.Seed(1)
|
||||
x := make([]bool, 10)
|
||||
for i := 0; i < len(x); i++ {
|
||||
x[i] = rand.Intn(2) == 0
|
||||
}
|
||||
a := GenericArray{x}
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
a.Value()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkGenericArrayValueFloat64s(b *testing.B) {
|
||||
rand.Seed(1)
|
||||
x := make([]float64, 10)
|
||||
for i := 0; i < len(x); i++ {
|
||||
x[i] = rand.NormFloat64()
|
||||
}
|
||||
a := GenericArray{x}
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
a.Value()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkGenericArrayValueInt64s(b *testing.B) {
|
||||
rand.Seed(1)
|
||||
x := make([]int64, 10)
|
||||
for i := 0; i < len(x); i++ {
|
||||
x[i] = rand.Int63()
|
||||
}
|
||||
a := GenericArray{x}
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
a.Value()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkGenericArrayValueByteSlices(b *testing.B) {
|
||||
x := make([][]byte, 10)
|
||||
for i := 0; i < len(x); i++ {
|
||||
x[i] = bytes.Repeat([]byte(`abc"def\ghi`), 5)
|
||||
}
|
||||
a := GenericArray{x}
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
a.Value()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkGenericArrayValueStrings(b *testing.B) {
|
||||
x := make([]string, 10)
|
||||
for i := 0; i < len(x); i++ {
|
||||
x[i] = strings.Repeat(`abc"def\ghi`, 5)
|
||||
}
|
||||
a := GenericArray{x}
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
a.Value()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -305,9 +305,6 @@ var importsBasedOnType = map[string]imports{
|
|||
"types.BytesArray": {
|
||||
thirdParty: importList{`"github.com/vattle/sqlboiler/boil/types"`},
|
||||
},
|
||||
"types.GenericArray": {
|
||||
thirdParty: importList{`"github.com/vattle/sqlboiler/boil/types"`},
|
||||
},
|
||||
"types.Int64Array": {
|
||||
thirdParty: importList{`"github.com/vattle/sqlboiler/boil/types"`},
|
||||
},
|
||||
|
|
19
testdata/test_schema.sql
vendored
19
testdata/test_schema.sql
vendored
|
@ -194,3 +194,22 @@ create table enemies (
|
|||
enemies character varying,
|
||||
primary key (enemies)
|
||||
);
|
||||
|
||||
create table fun_arrays (
|
||||
id serial,
|
||||
fun_one integer[] null,
|
||||
fun_two integer[] not null,
|
||||
fun_three boolean[] null,
|
||||
fun_four boolean[] not null,
|
||||
fun_five varchar[] null,
|
||||
fun_six varchar[] not null,
|
||||
fun_seven decimal[] null,
|
||||
fun_eight decimal[] not null,
|
||||
fun_nine bytea[] null,
|
||||
fun_ten bytea[] not null,
|
||||
fun_eleven jsonb[] null,
|
||||
fun_twelve jsonb[] not null,
|
||||
fun_thirteen json[] null,
|
||||
fun_fourteen json[] not null,
|
||||
primary key (id)
|
||||
)
|
||||
|
|
Loading…
Add table
Reference in a new issue