From 25b2b69aef1480130cc2b5335aed9f248276de6d Mon Sep 17 00:00:00 2001 From: Olav Loite Date: Thu, 26 Dec 2019 20:56:25 +0100 Subject: [PATCH] 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 Reviewed-by: Tyler Bui-Palsulich --- spanner/value.go | 134 ++++++++++++++++ spanner/value_test.go | 345 ++++++++++++++++++++++++++++++++++++------ 2 files changed, 429 insertions(+), 50 deletions(-) diff --git a/spanner/value.go b/spanner/value.go index 74a0d70f6809..25f2f5802e80 100644 --- a/spanner/value.go +++ b/spanner/value.go @@ -1899,6 +1899,16 @@ func encodeValue(v interface{}) (*proto3.Value, *sppb.Type, error) { 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 @@ func encodeValue(v interface{}) (*proto3.Value, *sppb.Type, error) { 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 24016bd75574..4bd942f655de 100644 --- a/spanner/value_test.go +++ b/spanner/value_test.go @@ -59,6 +59,21 @@ func mustParseDate(s string) civil.Date { // 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 @@ func TestEncodeValue(t *testing.T) { 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 @@ func TestEncodeValue(t *testing.T) { }, 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 TestEncodeStructValueFieldNames(t *testing.T) { } 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()), @@ -550,6 +634,27 @@ func TestEncodeStructValueBasicFields(t *testing.T) { dateProto(d1)), 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 { @@ -579,12 +684,56 @@ func TestEncodeStructValueBasicFields(t *testing.T) { 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())), @@ -628,6 +777,38 @@ func TestEncodeStructValueArrayFields(t *testing.T) { listProto(dateProto(d1), dateProto(d2))), 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 { @@ -660,6 +841,38 @@ func TestEncodeStructValueArrayFields(t *testing.T) { listProto(nullProto(), dateProto(d2))), 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 { @@ -692,6 +905,38 @@ func TestEncodeStructValueArrayFields(t *testing.T) { 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) }