spanner: Support encoding custom types
Support encoding custom types that point back to supported basic
types.
Fixes #853.
Change-Id: I669a717dae021400ce3b5b2c3a441bdedd82cfc2
Reviewed-on: https://code-review.googlesource.com/c/gocloud/+/49873
Reviewed-by: kokoro <noreply+kokoro@google.com>
Reviewed-by: Tyler Bui-Palsulich <tbp@google.com>
diff --git a/spanner/value.go b/spanner/value.go
index 74a0d70..25f2f58 100644
--- a/spanner/value.go
+++ b/spanner/value.go
@@ -1899,6 +1899,16 @@
case []GenericColumnValue:
return nil, nil, errEncoderUnsupportedType(v)
default:
+ // Check if the value is a variant of a base type.
+ decodableType := getDecodableSpannerType(v)
+ if decodableType != spannerTypeUnknown && decodableType != spannerTypeInvalid {
+ converted, err := convertCustomTypeValue(decodableType, v)
+ if err != nil {
+ return nil, nil, err
+ }
+ return encodeValue(converted)
+ }
+
if !isStructOrArrayOfStructValue(v) {
return nil, nil, errEncoderUnsupportedType(v)
}
@@ -1918,6 +1928,130 @@
return pb, pt, nil
}
+func convertCustomTypeValue(sourceType decodableSpannerType, v interface{}) (interface{}, error) {
+ // destination will be initialized to a base type. The input value will be
+ // converted to this type and copied to destination.
+ var destination reflect.Value
+ switch sourceType {
+ case spannerTypeInvalid:
+ return nil, fmt.Errorf("cannot encode a value to type spannerTypeInvalid")
+ case spannerTypeNonNullString:
+ destination = reflect.Indirect(reflect.New(reflect.TypeOf("")))
+ case spannerTypeNullString:
+ destination = reflect.Indirect(reflect.New(reflect.TypeOf(NullString{})))
+ case spannerTypeByteArray:
+ // Return a nil array directly if the input value is nil instead of
+ // creating an empty slice and returning that.
+ if reflect.ValueOf(v).IsNil() {
+ return []byte(nil), nil
+ }
+ destination = reflect.MakeSlice(reflect.TypeOf([]byte{}), reflect.ValueOf(v).Len(), reflect.ValueOf(v).Cap())
+ case spannerTypeNonNullInt64:
+ destination = reflect.Indirect(reflect.New(reflect.TypeOf(int64(0))))
+ case spannerTypeNullInt64:
+ destination = reflect.Indirect(reflect.New(reflect.TypeOf(NullInt64{})))
+ case spannerTypeNonNullBool:
+ destination = reflect.Indirect(reflect.New(reflect.TypeOf(false)))
+ case spannerTypeNullBool:
+ destination = reflect.Indirect(reflect.New(reflect.TypeOf(NullBool{})))
+ case spannerTypeNonNullFloat64:
+ destination = reflect.Indirect(reflect.New(reflect.TypeOf(float64(0.0))))
+ case spannerTypeNullFloat64:
+ destination = reflect.Indirect(reflect.New(reflect.TypeOf(NullFloat64{})))
+ case spannerTypeNonNullTime:
+ destination = reflect.Indirect(reflect.New(reflect.TypeOf(time.Time{})))
+ case spannerTypeNullTime:
+ destination = reflect.Indirect(reflect.New(reflect.TypeOf(NullTime{})))
+ case spannerTypeNonNullDate:
+ destination = reflect.Indirect(reflect.New(reflect.TypeOf(civil.Date{})))
+ case spannerTypeNullDate:
+ destination = reflect.Indirect(reflect.New(reflect.TypeOf(NullDate{})))
+ case spannerTypeArrayOfNonNullString:
+ if reflect.ValueOf(v).IsNil() {
+ return []string(nil), nil
+ }
+ destination = reflect.MakeSlice(reflect.TypeOf([]string{}), reflect.ValueOf(v).Len(), reflect.ValueOf(v).Cap())
+ case spannerTypeArrayOfNullString:
+ if reflect.ValueOf(v).IsNil() {
+ return []NullString(nil), nil
+ }
+ destination = reflect.MakeSlice(reflect.TypeOf([]NullString{}), reflect.ValueOf(v).Len(), reflect.ValueOf(v).Cap())
+ case spannerTypeArrayOfByteArray:
+ if reflect.ValueOf(v).IsNil() {
+ return [][]byte(nil), nil
+ }
+ destination = reflect.MakeSlice(reflect.TypeOf([][]byte{}), reflect.ValueOf(v).Len(), reflect.ValueOf(v).Cap())
+ case spannerTypeArrayOfNonNullInt64:
+ if reflect.ValueOf(v).IsNil() {
+ return []int64(nil), nil
+ }
+ destination = reflect.MakeSlice(reflect.TypeOf([]int64{}), reflect.ValueOf(v).Len(), reflect.ValueOf(v).Cap())
+ case spannerTypeArrayOfNullInt64:
+ if reflect.ValueOf(v).IsNil() {
+ return []NullInt64(nil), nil
+ }
+ destination = reflect.MakeSlice(reflect.TypeOf([]NullInt64{}), reflect.ValueOf(v).Len(), reflect.ValueOf(v).Cap())
+ case spannerTypeArrayOfNonNullBool:
+ if reflect.ValueOf(v).IsNil() {
+ return []bool(nil), nil
+ }
+ destination = reflect.MakeSlice(reflect.TypeOf([]bool{}), reflect.ValueOf(v).Len(), reflect.ValueOf(v).Cap())
+ case spannerTypeArrayOfNullBool:
+ if reflect.ValueOf(v).IsNil() {
+ return []NullBool(nil), nil
+ }
+ destination = reflect.MakeSlice(reflect.TypeOf([]NullBool{}), reflect.ValueOf(v).Len(), reflect.ValueOf(v).Cap())
+ case spannerTypeArrayOfNonNullFloat64:
+ if reflect.ValueOf(v).IsNil() {
+ return []float64(nil), nil
+ }
+ destination = reflect.MakeSlice(reflect.TypeOf([]float64{}), reflect.ValueOf(v).Len(), reflect.ValueOf(v).Cap())
+ case spannerTypeArrayOfNullFloat64:
+ if reflect.ValueOf(v).IsNil() {
+ return []NullFloat64(nil), nil
+ }
+ destination = reflect.MakeSlice(reflect.TypeOf([]NullFloat64{}), reflect.ValueOf(v).Len(), reflect.ValueOf(v).Cap())
+ case spannerTypeArrayOfNonNullTime:
+ if reflect.ValueOf(v).IsNil() {
+ return []time.Time(nil), nil
+ }
+ destination = reflect.MakeSlice(reflect.TypeOf([]time.Time{}), reflect.ValueOf(v).Len(), reflect.ValueOf(v).Cap())
+ case spannerTypeArrayOfNullTime:
+ if reflect.ValueOf(v).IsNil() {
+ return []NullTime(nil), nil
+ }
+ destination = reflect.MakeSlice(reflect.TypeOf([]NullTime{}), reflect.ValueOf(v).Len(), reflect.ValueOf(v).Cap())
+ case spannerTypeArrayOfNonNullDate:
+ if reflect.ValueOf(v).IsNil() {
+ return []civil.Date(nil), nil
+ }
+ destination = reflect.MakeSlice(reflect.TypeOf([]civil.Date{}), reflect.ValueOf(v).Len(), reflect.ValueOf(v).Cap())
+ case spannerTypeArrayOfNullDate:
+ if reflect.ValueOf(v).IsNil() {
+ return []NullDate(nil), nil
+ }
+ destination = reflect.MakeSlice(reflect.TypeOf([]NullDate{}), reflect.ValueOf(v).Len(), reflect.ValueOf(v).Cap())
+ default:
+ // This should not be possible.
+ return nil, fmt.Errorf("unknown decodable type found: %v", sourceType)
+ }
+ // destination has been initialized. Convert and copy the input value to
+ // destination. That must be done per element if the input type is a slice
+ // or an array.
+ if destination.Kind() == reflect.Slice || destination.Kind() == reflect.Array {
+ sourceSlice := reflect.ValueOf(v)
+ for i := 0; i < destination.Len(); i++ {
+ source := reflect.Indirect(sourceSlice.Index(i))
+ destination.Index(i).Set(source.Convert(destination.Type().Elem()))
+ }
+ } else {
+ source := reflect.Indirect(reflect.ValueOf(v))
+ destination.Set(source.Convert(destination.Type()))
+ }
+ // Return the converted value.
+ return destination.Interface(), nil
+}
+
// Encodes a Go struct value/ptr in v to the spanner Value and Type protos. v
// itself must be non-nil.
func encodeStruct(v interface{}) (*proto3.Value, *sppb.Type, error) {
diff --git a/spanner/value_test.go b/spanner/value_test.go
index 24016bd..4bd942f 100644
--- a/spanner/value_test.go
+++ b/spanner/value_test.go
@@ -59,6 +59,21 @@
// Test encoding Values.
func TestEncodeValue(t *testing.T) {
+ type CustomString string
+ type CustomBytes []byte
+ type CustomInt64 int64
+ type CustomBool bool
+ type CustomFloat64 float64
+ type CustomTime time.Time
+ type CustomDate civil.Date
+
+ type CustomNullString NullString
+ type CustomNullInt64 NullInt64
+ type CustomNullBool NullBool
+ type CustomNullFloat64 NullFloat64
+ type CustomNullTime NullTime
+ type CustomNullDate NullDate
+
var (
tString = stringType()
tInt = intType()
@@ -72,60 +87,61 @@
in interface{}
want *proto3.Value
wantType *sppb.Type
+ name string
}{
- // STRING / STRING ARRAY
- {"abc", stringProto("abc"), tString},
- {NullString{"abc", true}, stringProto("abc"), tString},
- {NullString{"abc", false}, nullProto(), tString},
- {[]string(nil), nullProto(), listType(tString)},
- {[]string{"abc", "bcd"}, listProto(stringProto("abc"), stringProto("bcd")), listType(tString)},
- {[]NullString{{"abcd", true}, {"xyz", false}}, listProto(stringProto("abcd"), nullProto()), listType(tString)},
+ // STRING / STRING ARRAY:
+ {"abc", stringProto("abc"), tString, "string"},
+ {NullString{"abc", true}, stringProto("abc"), tString, "NullString with value"},
+ {NullString{"abc", false}, nullProto(), tString, "NullString with null"},
+ {[]string(nil), nullProto(), listType(tString), "null []string"},
+ {[]string{"abc", "bcd"}, listProto(stringProto("abc"), stringProto("bcd")), listType(tString), "[]string"},
+ {[]NullString{{"abcd", true}, {"xyz", false}}, listProto(stringProto("abcd"), nullProto()), listType(tString), "[]NullString"},
// BYTES / BYTES ARRAY
- {[]byte("foo"), bytesProto([]byte("foo")), tBytes},
- {[]byte(nil), nullProto(), tBytes},
- {[][]byte{nil, []byte("ab")}, listProto(nullProto(), bytesProto([]byte("ab"))), listType(tBytes)},
- {[][]byte(nil), nullProto(), listType(tBytes)},
+ {[]byte("foo"), bytesProto([]byte("foo")), tBytes, "[]byte with value"},
+ {[]byte(nil), nullProto(), tBytes, "null []byte"},
+ {[][]byte{nil, []byte("ab")}, listProto(nullProto(), bytesProto([]byte("ab"))), listType(tBytes), "[][]byte"},
+ {[][]byte(nil), nullProto(), listType(tBytes), "null [][]byte"},
// INT64 / INT64 ARRAY
- {7, intProto(7), tInt},
- {[]int(nil), nullProto(), listType(tInt)},
- {[]int{31, 127}, listProto(intProto(31), intProto(127)), listType(tInt)},
- {int64(81), intProto(81), tInt},
- {[]int64(nil), nullProto(), listType(tInt)},
- {[]int64{33, 129}, listProto(intProto(33), intProto(129)), listType(tInt)},
- {NullInt64{11, true}, intProto(11), tInt},
- {NullInt64{11, false}, nullProto(), tInt},
- {[]NullInt64{{35, true}, {131, false}}, listProto(intProto(35), nullProto()), listType(tInt)},
+ {7, intProto(7), tInt, "int"},
+ {[]int(nil), nullProto(), listType(tInt), "null []int"},
+ {[]int{31, 127}, listProto(intProto(31), intProto(127)), listType(tInt), "[]int"},
+ {int64(81), intProto(81), tInt, "int64"},
+ {[]int64(nil), nullProto(), listType(tInt), "null []int64"},
+ {[]int64{33, 129}, listProto(intProto(33), intProto(129)), listType(tInt), "[]int64"},
+ {NullInt64{11, true}, intProto(11), tInt, "NullInt64 with value"},
+ {NullInt64{11, false}, nullProto(), tInt, "NullInt64 with null"},
+ {[]NullInt64{{35, true}, {131, false}}, listProto(intProto(35), nullProto()), listType(tInt), "[]NullInt64"},
// BOOL / BOOL ARRAY
- {true, boolProto(true), tBool},
- {NullBool{true, true}, boolProto(true), tBool},
- {NullBool{true, false}, nullProto(), tBool},
- {[]bool{true, false}, listProto(boolProto(true), boolProto(false)), listType(tBool)},
- {[]NullBool{{true, true}, {true, false}}, listProto(boolProto(true), nullProto()), listType(tBool)},
+ {true, boolProto(true), tBool, "bool"},
+ {NullBool{true, true}, boolProto(true), tBool, "NullBool with value"},
+ {NullBool{true, false}, nullProto(), tBool, "NullBool with null"},
+ {[]bool{true, false}, listProto(boolProto(true), boolProto(false)), listType(tBool), "[]bool"},
+ {[]NullBool{{true, true}, {true, false}}, listProto(boolProto(true), nullProto()), listType(tBool), "[]NullBool"},
// FLOAT64 / FLOAT64 ARRAY
- {3.14, floatProto(3.14), tFloat},
- {NullFloat64{3.1415, true}, floatProto(3.1415), tFloat},
- {NullFloat64{math.Inf(1), true}, floatProto(math.Inf(1)), tFloat},
- {NullFloat64{3.14159, false}, nullProto(), tFloat},
- {[]float64(nil), nullProto(), listType(tFloat)},
- {[]float64{3.141, 0.618, math.Inf(-1)}, listProto(floatProto(3.141), floatProto(0.618), floatProto(math.Inf(-1))), listType(tFloat)},
- {[]NullFloat64{{3.141, true}, {0.618, false}}, listProto(floatProto(3.141), nullProto()), listType(tFloat)},
+ {3.14, floatProto(3.14), tFloat, "float"},
+ {NullFloat64{3.1415, true}, floatProto(3.1415), tFloat, "NullFloat64 with value"},
+ {NullFloat64{math.Inf(1), true}, floatProto(math.Inf(1)), tFloat, "NullFloat64 with infinity"},
+ {NullFloat64{3.14159, false}, nullProto(), tFloat, "NullFloat64 with null"},
+ {[]float64(nil), nullProto(), listType(tFloat), "null []float64"},
+ {[]float64{3.141, 0.618, math.Inf(-1)}, listProto(floatProto(3.141), floatProto(0.618), floatProto(math.Inf(-1))), listType(tFloat), "[]float64"},
+ {[]NullFloat64{{3.141, true}, {0.618, false}}, listProto(floatProto(3.141), nullProto()), listType(tFloat), "[]NullFloat64"},
// TIMESTAMP / TIMESTAMP ARRAY
- {t1, timeProto(t1), tTime},
- {NullTime{t1, true}, timeProto(t1), tTime},
- {NullTime{t1, false}, nullProto(), tTime},
- {[]time.Time(nil), nullProto(), listType(tTime)},
- {[]time.Time{t1, t2, t3, t4}, listProto(timeProto(t1), timeProto(t2), timeProto(t3), timeProto(t4)), listType(tTime)},
- {[]NullTime{{t1, true}, {t1, false}}, listProto(timeProto(t1), nullProto()), listType(tTime)},
+ {t1, timeProto(t1), tTime, "time"},
+ {NullTime{t1, true}, timeProto(t1), tTime, "NullTime with value"},
+ {NullTime{t1, false}, nullProto(), tTime, "NullTime with null"},
+ {[]time.Time(nil), nullProto(), listType(tTime), "null []time"},
+ {[]time.Time{t1, t2, t3, t4}, listProto(timeProto(t1), timeProto(t2), timeProto(t3), timeProto(t4)), listType(tTime), "[]time"},
+ {[]NullTime{{t1, true}, {t1, false}}, listProto(timeProto(t1), nullProto()), listType(tTime), "[]NullTime"},
// DATE / DATE ARRAY
- {d1, dateProto(d1), tDate},
- {NullDate{d1, true}, dateProto(d1), tDate},
- {NullDate{civil.Date{}, false}, nullProto(), tDate},
- {[]civil.Date(nil), nullProto(), listType(tDate)},
- {[]civil.Date{d1, d2}, listProto(dateProto(d1), dateProto(d2)), listType(tDate)},
- {[]NullDate{{d1, true}, {civil.Date{}, false}}, listProto(dateProto(d1), nullProto()), listType(tDate)},
+ {d1, dateProto(d1), tDate, "date"},
+ {NullDate{d1, true}, dateProto(d1), tDate, "NullDate with value"},
+ {NullDate{civil.Date{}, false}, nullProto(), tDate, "NullDate with null"},
+ {[]civil.Date(nil), nullProto(), listType(tDate), "null []date"},
+ {[]civil.Date{d1, d2}, listProto(dateProto(d1), dateProto(d2)), listType(tDate), "[]date"},
+ {[]NullDate{{d1, true}, {civil.Date{}, false}}, listProto(dateProto(d1), nullProto()), listType(tDate), "[]NullDate"},
// GenericColumnValue
- {GenericColumnValue{tString, stringProto("abc")}, stringProto("abc"), tString},
- {GenericColumnValue{tString, nullProto()}, nullProto(), tString},
+ {GenericColumnValue{tString, stringProto("abc")}, stringProto("abc"), tString, "GenericColumnValue with value"},
+ {GenericColumnValue{tString, nullProto()}, nullProto(), tString, "GenericColumnValue with null"},
// not actually valid (stringProto inside int list), but demonstrates pass-through.
{
GenericColumnValue{
@@ -134,19 +150,72 @@
},
listProto(intProto(5), nullProto(), stringProto("bcd")),
listType(tInt),
+ "pass-through",
},
// placeholder
- {CommitTimestamp, stringProto(commitTimestampPlaceholderString), tTime},
+ {CommitTimestamp, stringProto(commitTimestampPlaceholderString), tTime, "CommitTimestampPlaceholder"},
+ // CUSTOM STRING / CUSTOM STRING ARRAY
+ {CustomString("abc"), stringProto("abc"), tString, "CustomString"},
+ {CustomNullString{"abc", true}, stringProto("abc"), tString, "CustomNullString with value"},
+ {CustomNullString{"abc", false}, nullProto(), tString, "CustomNullString with null"},
+ {[]CustomString(nil), nullProto(), listType(tString), "null []CustomString"},
+ {[]CustomString{"abc", "bcd"}, listProto(stringProto("abc"), stringProto("bcd")), listType(tString), "[]CustomString"},
+ {[]CustomNullString(nil), nullProto(), listType(tString), "null []NullCustomString"},
+ {[]CustomNullString{{"abcd", true}, {"xyz", false}}, listProto(stringProto("abcd"), nullProto()), listType(tString), "[]NullCustomString"},
+ // CUSTOM BYTES / CUSTOM BYTES ARRAY
+ {CustomBytes("foo"), bytesProto([]byte("foo")), tBytes, "CustomBytes with value"},
+ {CustomBytes(nil), nullProto(), tBytes, "null CustomBytes"},
+ {[]CustomBytes{nil, CustomBytes("ab")}, listProto(nullProto(), bytesProto([]byte("ab"))), listType(tBytes), "[]CustomBytes"},
+ {[]CustomBytes(nil), nullProto(), listType(tBytes), "null []CustomBytes"},
+ // CUSTOM INT64 / CUSTOM INT64 ARRAY
+ {CustomInt64(81), intProto(81), tInt, "CustomInt64"},
+ {[]CustomInt64(nil), nullProto(), listType(tInt), "null []CustomInt64"},
+ {[]CustomInt64{33, 129}, listProto(intProto(33), intProto(129)), listType(tInt), "[]CustomInt64"},
+ {CustomNullInt64{11, true}, intProto(11), tInt, "CustomNullInt64 with value"},
+ {CustomNullInt64{11, false}, nullProto(), tInt, "CustomNullInt64 with null"},
+ {[]CustomNullInt64(nil), nullProto(), listType(tInt), "null []CustomNullInt64"},
+ {[]CustomNullInt64{{35, true}, {131, false}}, listProto(intProto(35), nullProto()), listType(tInt), "[]CustomNullInt64"},
+ // CUSTOM BOOL / CUSTOM BOOL ARRAY
+ {CustomBool(true), boolProto(true), tBool, "CustomBool"},
+ {CustomNullBool{true, true}, boolProto(true), tBool, "CustomNullBool with value"},
+ {CustomNullBool{true, false}, nullProto(), tBool, "CustomNullBool with null"},
+ {[]CustomBool{true, false}, listProto(boolProto(true), boolProto(false)), listType(tBool), "[]CustomBool"},
+ {[]CustomNullBool{{true, true}, {true, false}}, listProto(boolProto(true), nullProto()), listType(tBool), "[]CustomNullBool"},
+ // FLOAT64 / FLOAT64 ARRAY
+ {CustomFloat64(3.14), floatProto(3.14), tFloat, "CustomFloat64"},
+ {CustomNullFloat64{3.1415, true}, floatProto(3.1415), tFloat, "CustomNullFloat64 with value"},
+ {CustomNullFloat64{math.Inf(1), true}, floatProto(math.Inf(1)), tFloat, "CustomNullFloat64 with infinity"},
+ {CustomNullFloat64{3.14159, false}, nullProto(), tFloat, "CustomNullFloat64 with null"},
+ {[]CustomFloat64(nil), nullProto(), listType(tFloat), "null []CustomFloat64"},
+ {[]CustomFloat64{3.141, 0.618, CustomFloat64(math.Inf(-1))}, listProto(floatProto(3.141), floatProto(0.618), floatProto(math.Inf(-1))), listType(tFloat), "[]CustomFloat64"},
+ {[]CustomNullFloat64(nil), nullProto(), listType(tFloat), "null []CustomNullFloat64"},
+ {[]CustomNullFloat64{{3.141, true}, {0.618, false}}, listProto(floatProto(3.141), nullProto()), listType(tFloat), "[]CustomNullFloat64"},
+ // CUSTOM TIMESTAMP / CUSTOM TIMESTAMP ARRAY
+ {CustomTime(t1), timeProto(t1), tTime, "CustomTime"},
+ {CustomNullTime{t1, true}, timeProto(t1), tTime, "CustomNullTime with value"},
+ {CustomNullTime{t1, false}, nullProto(), tTime, "CustomNullTime with null"},
+ {[]CustomTime(nil), nullProto(), listType(tTime), "null []CustomTime"},
+ {[]CustomTime{CustomTime(t1), CustomTime(t2), CustomTime(t3), CustomTime(t4)}, listProto(timeProto(t1), timeProto(t2), timeProto(t3), timeProto(t4)), listType(tTime), "[]CustomTime"},
+ {[]CustomNullTime(nil), nullProto(), listType(tTime), "null []CustomNullTime"},
+ {[]CustomNullTime{{t1, true}, {t1, false}}, listProto(timeProto(t1), nullProto()), listType(tTime), "[]CustomNullTime"},
+ // CUSTOM DATE / CUSTOM DATE ARRAY
+ {CustomDate(d1), dateProto(d1), tDate, "CustomDate"},
+ {CustomNullDate{d1, true}, dateProto(d1), tDate, "CustomNullDate with value"},
+ {CustomNullDate{civil.Date{}, false}, nullProto(), tDate, "CustomNullDate with null"},
+ {[]CustomDate(nil), nullProto(), listType(tDate), "null []CustomDate"},
+ {[]CustomDate{CustomDate(d1), CustomDate(d2)}, listProto(dateProto(d1), dateProto(d2)), listType(tDate), "[]CustomDate"},
+ {[]CustomNullDate(nil), nullProto(), listType(tDate), "null []CustomNullDate"},
+ {[]CustomNullDate{{d1, true}, {civil.Date{}, false}}, listProto(dateProto(d1), nullProto()), listType(tDate), "[]NullDate"},
} {
got, gotType, err := encodeValue(test.in)
if err != nil {
- t.Fatalf("#%d: got error during encoding: %v, want nil", i, err)
+ t.Fatalf("#%d (%s): got error during encoding: %v, want nil", i, test.name, err)
}
if !testEqual(got, test.want) {
- t.Errorf("#%d: got encode result: %v, want %v", i, got, test.want)
+ t.Errorf("#%d (%s): got encode result: %v, want %v", i, test.name, got, test.want)
}
if !testEqual(gotType, test.wantType) {
- t.Errorf("#%d: got encode type: %v, want %v", i, gotType, test.wantType)
+ t.Errorf("#%d (%s): got encode type: %v, want %v", i, test.name, gotType, test.wantType)
}
}
}
@@ -519,6 +588,21 @@
}
func TestEncodeStructValueBasicFields(t *testing.T) {
+ type CustomString string
+ type CustomBytes []byte
+ type CustomInt64 int64
+ type CustomBool bool
+ type CustomFloat64 float64
+ type CustomTime time.Time
+ type CustomDate civil.Date
+
+ type CustomNullString NullString
+ type CustomNullInt64 NullInt64
+ type CustomNullBool NullBool
+ type CustomNullFloat64 NullFloat64
+ type CustomNullTime NullTime
+ type CustomNullDate NullDate
+
StructTypeProto := structType(
mkField("Stringf", stringType()),
mkField("Intf", intType()),
@@ -551,6 +635,27 @@
StructTypeProto,
},
{
+ "Basic custom types.",
+ struct {
+ Stringf CustomString
+ Intf CustomInt64
+ Boolf CustomBool
+ Floatf CustomFloat64
+ Bytef CustomBytes
+ Timef CustomTime
+ Datef CustomDate
+ }{"abc", 300, false, 3.45, []byte("foo"), CustomTime(t1), CustomDate(d1)},
+ listProto(
+ stringProto("abc"),
+ intProto(300),
+ boolProto(false),
+ floatProto(3.45),
+ bytesProto([]byte("foo")),
+ timeProto(t1),
+ dateProto(d1)),
+ StructTypeProto,
+ },
+ {
"Basic types null values.",
struct {
Stringf NullString
@@ -579,12 +684,56 @@
nullProto()),
StructTypeProto,
},
+ {
+ "Basic custom types null values.",
+ struct {
+ Stringf CustomNullString
+ Intf CustomNullInt64
+ Boolf CustomNullBool
+ Floatf CustomNullFloat64
+ Bytef CustomBytes
+ Timef CustomNullTime
+ Datef CustomNullDate
+ }{
+ CustomNullString{"abc", false},
+ CustomNullInt64{4, false},
+ CustomNullBool{false, false},
+ CustomNullFloat64{5.6, false},
+ nil,
+ CustomNullTime{t1, false},
+ CustomNullDate{d1, false},
+ },
+ listProto(
+ nullProto(),
+ nullProto(),
+ nullProto(),
+ nullProto(),
+ nullProto(),
+ nullProto(),
+ nullProto()),
+ StructTypeProto,
+ },
} {
encodeStructValue(test, t)
}
}
func TestEncodeStructValueArrayFields(t *testing.T) {
+ type CustomString string
+ type CustomBytes []byte
+ type CustomInt64 int64
+ type CustomBool bool
+ type CustomFloat64 float64
+ type CustomTime time.Time
+ type CustomDate civil.Date
+
+ type CustomNullString NullString
+ type CustomNullInt64 NullInt64
+ type CustomNullBool NullBool
+ type CustomNullFloat64 NullFloat64
+ type CustomNullTime NullTime
+ type CustomNullDate NullDate
+
StructTypeProto := structType(
mkField("Stringf", listType(stringType())),
mkField("Intf", listType(intType())),
@@ -629,6 +778,38 @@
StructTypeProto,
},
{
+ "Arrays of basic custom types with non-nullable elements",
+ struct {
+ Stringf []CustomString
+ Intf []CustomInt64
+ Int64f []CustomInt64
+ Boolf []CustomBool
+ Floatf []CustomFloat64
+ Bytef []CustomBytes
+ Timef []CustomTime
+ Datef []CustomDate
+ }{
+ []CustomString{"abc", "def"},
+ []CustomInt64{4, 67},
+ []CustomInt64{5, 68},
+ []CustomBool{false, true},
+ []CustomFloat64{3.45, 0.93},
+ []CustomBytes{[]byte("foo"), nil},
+ []CustomTime{CustomTime(t1), CustomTime(t2)},
+ []CustomDate{CustomDate(d1), CustomDate(d2)},
+ },
+ listProto(
+ listProto(stringProto("abc"), stringProto("def")),
+ listProto(intProto(4), intProto(67)),
+ listProto(intProto(5), intProto(68)),
+ listProto(boolProto(false), boolProto(true)),
+ listProto(floatProto(3.45), floatProto(0.93)),
+ listProto(bytesProto([]byte("foo")), nullProto()),
+ listProto(timeProto(t1), timeProto(t2)),
+ listProto(dateProto(d1), dateProto(d2))),
+ StructTypeProto,
+ },
+ {
"Arrays of basic types with nullable elements.",
struct {
Stringf []NullString
@@ -661,6 +842,38 @@
StructTypeProto,
},
{
+ "Arrays of basic custom types with nullable elements.",
+ struct {
+ Stringf []CustomNullString
+ Intf []CustomNullInt64
+ Int64f []CustomNullInt64
+ Boolf []CustomNullBool
+ Floatf []CustomNullFloat64
+ Bytef []CustomBytes
+ Timef []CustomNullTime
+ Datef []CustomNullDate
+ }{
+ []CustomNullString{{"abc", false}, {"def", true}},
+ []CustomNullInt64{{4, false}, {67, true}},
+ []CustomNullInt64{{5, false}, {68, true}},
+ []CustomNullBool{{true, false}, {false, true}},
+ []CustomNullFloat64{{3.45, false}, {0.93, true}},
+ []CustomBytes{[]byte("foo"), nil},
+ []CustomNullTime{{t1, false}, {t2, true}},
+ []CustomNullDate{{d1, false}, {d2, true}},
+ },
+ listProto(
+ listProto(nullProto(), stringProto("def")),
+ listProto(nullProto(), intProto(67)),
+ listProto(nullProto(), intProto(68)),
+ listProto(nullProto(), boolProto(false)),
+ listProto(nullProto(), floatProto(0.93)),
+ listProto(bytesProto([]byte("foo")), nullProto()),
+ listProto(nullProto(), timeProto(t2)),
+ listProto(nullProto(), dateProto(d2))),
+ StructTypeProto,
+ },
+ {
"Null arrays of basic types.",
struct {
Stringf []NullString
@@ -692,6 +905,38 @@
nullProto()),
StructTypeProto,
},
+ {
+ "Null arrays of basic custom types.",
+ struct {
+ Stringf []CustomNullString
+ Intf []CustomNullInt64
+ Int64f []CustomNullInt64
+ Boolf []CustomNullBool
+ Floatf []CustomNullFloat64
+ Bytef []CustomBytes
+ Timef []CustomNullTime
+ Datef []CustomNullDate
+ }{
+ nil,
+ nil,
+ nil,
+ nil,
+ nil,
+ nil,
+ nil,
+ nil,
+ },
+ listProto(
+ nullProto(),
+ nullProto(),
+ nullProto(),
+ nullProto(),
+ nullProto(),
+ nullProto(),
+ nullProto(),
+ nullProto()),
+ StructTypeProto,
+ },
} {
encodeStructValue(test, t)
}