|
- /*
- Copyright 2017 Google LLC
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- */
-
- package spanner
-
- import (
- "math"
- "reflect"
- "testing"
- "time"
-
- "cloud.google.com/go/civil"
- proto3 "github.com/golang/protobuf/ptypes/struct"
- sppb "google.golang.org/genproto/googleapis/spanner/v1"
- )
-
- var (
- t1 = mustParseTime("2016-11-15T15:04:05.999999999Z")
- // Boundaries
- t2 = mustParseTime("0000-01-01T00:00:00.000000000Z")
- t3 = mustParseTime("9999-12-31T23:59:59.999999999Z")
- // Local timezone
- t4 = time.Now()
- d1 = mustParseDate("2016-11-15")
- d2 = mustParseDate("1678-01-01")
- )
-
- func mustParseTime(s string) time.Time {
- t, err := time.Parse(time.RFC3339Nano, s)
- if err != nil {
- panic(err)
- }
- return t
- }
-
- func mustParseDate(s string) civil.Date {
- d, err := civil.ParseDate(s)
- if err != nil {
- panic(err)
- }
- return d
- }
-
- // Test encoding Values.
- func TestEncodeValue(t *testing.T) {
- var (
- tString = stringType()
- tInt = intType()
- tBool = boolType()
- tFloat = floatType()
- tBytes = bytesType()
- tTime = timeType()
- tDate = dateType()
- )
- for i, test := range []struct {
- in interface{}
- want *proto3.Value
- wantType *sppb.Type
- }{
- // 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)},
- // 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)},
- // 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)},
- // 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)},
- // 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)},
- // 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)},
- // 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)},
- // GenericColumnValue
- {GenericColumnValue{tString, stringProto("abc")}, stringProto("abc"), tString},
- {GenericColumnValue{tString, nullProto()}, nullProto(), tString},
- // not actually valid (stringProto inside int list), but demonstrates pass-through.
- {
- GenericColumnValue{
- Type: listType(tInt),
- Value: listProto(intProto(5), nullProto(), stringProto("bcd")),
- },
- listProto(intProto(5), nullProto(), stringProto("bcd")),
- listType(tInt),
- },
- // placeholder
- {CommitTimestamp, stringProto(commitTimestampPlaceholderString), tTime},
- } {
- got, gotType, err := encodeValue(test.in)
- if err != nil {
- t.Fatalf("#%d: got error during encoding: %v, want nil", i, err)
- }
- if !testEqual(got, test.want) {
- t.Errorf("#%d: got encode result: %v, want %v", i, got, test.want)
- }
- if !testEqual(gotType, test.wantType) {
- t.Errorf("#%d: got encode type: %v, want %v", i, gotType, test.wantType)
- }
- }
- }
-
- type encodeTest struct {
- desc string
- in interface{}
- want *proto3.Value
- wantType *sppb.Type
- }
-
- func checkStructEncoding(desc string, got *proto3.Value, gotType *sppb.Type,
- want *proto3.Value, wantType *sppb.Type, t *testing.T) {
- if !testEqual(got, want) {
- t.Errorf("Test %s: got encode result: %v, want %v", desc, got, want)
- }
- if !testEqual(gotType, wantType) {
- t.Errorf("Test %s: got encode type: %v, want %v", desc, gotType, wantType)
- }
- }
-
- // Testcase code
- func encodeStructValue(test encodeTest, t *testing.T) {
- got, gotType, err := encodeValue(test.in)
- if err != nil {
- t.Fatalf("Test %s: got error during encoding: %v, want nil", test.desc, err)
- }
- checkStructEncoding(test.desc, got, gotType, test.want, test.wantType, t)
- }
-
- func TestEncodeStructValuePointers(t *testing.T) {
- type structf struct {
- F int `spanner:"ff2"`
- }
- nestedStructProto := structType(mkField("ff2", intType()))
-
- type testType struct {
- Stringf string
- Structf *structf
- ArrStructf []*structf
- }
- testTypeProto := structType(
- mkField("Stringf", stringType()),
- mkField("Structf", nestedStructProto),
- mkField("ArrStructf", listType(nestedStructProto)))
-
- for _, test := range []encodeTest{
- {
- "Pointer to Go struct with pointers-to-(array)-struct fields.",
- &testType{"hello", &structf{50}, []*structf{&structf{30}, &structf{40}}},
- listProto(
- stringProto("hello"),
- listProto(intProto(50)),
- listProto(
- listProto(intProto(30)),
- listProto(intProto(40)))),
- testTypeProto,
- },
- {
- "Nil pointer to Go struct representing a NULL struct value.",
- (*testType)(nil),
- nullProto(),
- testTypeProto,
- },
- {
- "Slice of pointers to Go structs with NULL and non-NULL elements.",
- []*testType{
- (*testType)(nil),
- &testType{"hello", nil, []*structf{nil, &structf{40}}},
- &testType{"world", &structf{70}, nil},
- },
- listProto(
- nullProto(),
- listProto(
- stringProto("hello"),
- nullProto(),
- listProto(nullProto(), listProto(intProto(40)))),
- listProto(
- stringProto("world"),
- listProto(intProto(70)),
- nullProto())),
- listType(testTypeProto),
- },
- {
- "Nil slice of pointers to structs representing a NULL array of structs.",
- []*testType(nil),
- nullProto(),
- listType(testTypeProto),
- },
- {
- "Empty slice of pointers to structs representing an empty array of structs.",
- []*testType{},
- listProto(),
- listType(testTypeProto),
- },
- } {
- encodeStructValue(test, t)
- }
- }
-
- func TestEncodeStructValueErrors(t *testing.T) {
- type Embedded struct {
- A int
- }
- type embedded struct {
- B bool
- }
- x := 0
-
- for _, test := range []struct {
- desc string
- in interface{}
- wantErr error
- }{
- {
- "Unsupported embedded fields.",
- struct{ Embedded }{Embedded{10}},
- errUnsupportedEmbeddedStructFields("Embedded"),
- },
- {
- "Unsupported pointer to embedded fields.",
- struct{ *Embedded }{&Embedded{10}},
- errUnsupportedEmbeddedStructFields("Embedded"),
- },
- {
- "Unsupported embedded + unexported fields.",
- struct {
- int
- *bool
- embedded
- }{10, nil, embedded{false}},
- errUnsupportedEmbeddedStructFields("int"),
- },
- {
- "Unsupported type.",
- (**struct{})(nil),
- errEncoderUnsupportedType((**struct{})(nil)),
- },
- {
- "Unsupported type.",
- 3,
- errEncoderUnsupportedType(3),
- },
- {
- "Unsupported type.",
- &x,
- errEncoderUnsupportedType(&x),
- },
- } {
- _, _, got := encodeStruct(test.in)
- if got == nil || !testEqual(test.wantErr, got) {
- t.Errorf("Test: %s, expected error %v during decoding, got %v", test.desc, test.wantErr, got)
- }
- }
- }
-
- func TestEncodeStructValueArrayStructFields(t *testing.T) {
- type structf struct {
- Intff int
- }
-
- structfType := structType(mkField("Intff", intType()))
- for _, test := range []encodeTest{
- {
- "Unnamed array-of-struct-typed field.",
- struct {
- Intf int
- ArrStructf []structf `spanner:""`
- }{10, []structf{structf{1}, structf{2}}},
- listProto(
- intProto(10),
- listProto(
- listProto(intProto(1)),
- listProto(intProto(2)))),
- structType(
- mkField("Intf", intType()),
- mkField("", listType(structfType))),
- },
- {
- "Null array-of-struct-typed field.",
- struct {
- Intf int
- ArrStructf []structf
- }{10, []structf(nil)},
- listProto(intProto(10), nullProto()),
- structType(
- mkField("Intf", intType()),
- mkField("ArrStructf", listType(structfType))),
- },
- {
- "Array-of-struct-typed field representing empty array.",
- struct {
- Intf int
- ArrStructf []structf
- }{10, []structf{}},
- listProto(intProto(10), listProto([]*proto3.Value{}...)),
- structType(
- mkField("Intf", intType()),
- mkField("ArrStructf", listType(structfType))),
- },
- {
- "Array-of-struct-typed field with nullable struct elements.",
- struct {
- Intf int
- ArrStructf []*structf
- }{
- 10,
- []*structf{(*structf)(nil), &structf{1}},
- },
- listProto(
- intProto(10),
- listProto(
- nullProto(),
- listProto(intProto(1)))),
- structType(
- mkField("Intf", intType()),
- mkField("ArrStructf", listType(structfType))),
- },
- } {
- encodeStructValue(test, t)
- }
- }
-
- func TestEncodeStructValueStructFields(t *testing.T) {
- type structf struct {
- Intff int
- }
- structfType := structType(mkField("Intff", intType()))
- for _, test := range []encodeTest{
- {
- "Named struct-type field.",
- struct {
- Intf int
- Structf structf
- }{10, structf{10}},
- listProto(intProto(10), listProto(intProto(10))),
- structType(
- mkField("Intf", intType()),
- mkField("Structf", structfType)),
- },
- {
- "Unnamed struct-type field.",
- struct {
- Intf int
- Structf structf `spanner:""`
- }{10, structf{10}},
- listProto(intProto(10), listProto(intProto(10))),
- structType(
- mkField("Intf", intType()),
- mkField("", structfType)),
- },
- {
- "Duplicate struct-typed field.",
- struct {
- Structf1 structf `spanner:""`
- Structf2 structf `spanner:""`
- }{structf{10}, structf{20}},
- listProto(listProto(intProto(10)), listProto(intProto(20))),
- structType(
- mkField("", structfType),
- mkField("", structfType)),
- },
- {
- "Null struct-typed field.",
- struct {
- Intf int
- Structf *structf
- }{10, nil},
- listProto(intProto(10), nullProto()),
- structType(
- mkField("Intf", intType()),
- mkField("Structf", structfType)),
- },
- {
- "Empty struct-typed field.",
- struct {
- Intf int
- Structf struct{}
- }{10, struct{}{}},
- listProto(intProto(10), listProto([]*proto3.Value{}...)),
- structType(
- mkField("Intf", intType()),
- mkField("Structf", structType([]*sppb.StructType_Field{}...))),
- },
- } {
- encodeStructValue(test, t)
- }
- }
-
- func TestEncodeStructValueFieldNames(t *testing.T) {
- type embedded struct {
- B bool
- }
-
- for _, test := range []encodeTest{
- {
- "Duplicate fields.",
- struct {
- Field1 int `spanner:"field"`
- DupField1 int `spanner:"field"`
- }{10, 20},
- listProto(intProto(10), intProto(20)),
- structType(
- mkField("field", intType()),
- mkField("field", intType())),
- },
- {
- "Duplicate Fields (different types).",
- struct {
- IntField int `spanner:"field"`
- StringField string `spanner:"field"`
- }{10, "abc"},
- listProto(intProto(10), stringProto("abc")),
- structType(
- mkField("field", intType()),
- mkField("field", stringType())),
- },
- {
- "Duplicate unnamed fields.",
- struct {
- Dup int `spanner:""`
- Dup1 int `spanner:""`
- }{10, 20},
- listProto(intProto(10), intProto(20)),
- structType(
- mkField("", intType()),
- mkField("", intType())),
- },
- {
- "Named and unnamed fields.",
- struct {
- Field string
- Field1 int `spanner:""`
- Field2 string `spanner:"field"`
- }{"abc", 10, "def"},
- listProto(stringProto("abc"), intProto(10), stringProto("def")),
- structType(
- mkField("Field", stringType()),
- mkField("", intType()),
- mkField("field", stringType())),
- },
- {
- "Ignored unexported fields.",
- struct {
- Field int
- field bool
- Field1 string `spanner:"field"`
- }{10, false, "abc"},
- listProto(intProto(10), stringProto("abc")),
- structType(
- mkField("Field", intType()),
- mkField("field", stringType())),
- },
- {
- "Ignored unexported struct/slice fields.",
- struct {
- a []*embedded
- b []embedded
- c embedded
- d *embedded
- Field1 string `spanner:"field"`
- }{nil, nil, embedded{}, nil, "def"},
- listProto(stringProto("def")),
- structType(
- mkField("field", stringType())),
- },
- } {
- encodeStructValue(test, t)
- }
- }
-
- func TestEncodeStructValueBasicFields(t *testing.T) {
- StructTypeProto := structType(
- mkField("Stringf", stringType()),
- mkField("Intf", intType()),
- mkField("Boolf", boolType()),
- mkField("Floatf", floatType()),
- mkField("Bytef", bytesType()),
- mkField("Timef", timeType()),
- mkField("Datef", dateType()))
-
- for _, test := range []encodeTest{
- {
- "Basic types.",
- struct {
- Stringf string
- Intf int
- Boolf bool
- Floatf float64
- Bytef []byte
- Timef time.Time
- Datef civil.Date
- }{"abc", 300, false, 3.45, []byte("foo"), t1, 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
- Intf NullInt64
- Boolf NullBool
- Floatf NullFloat64
- Bytef []byte
- Timef NullTime
- Datef NullDate
- }{
- NullString{"abc", false},
- NullInt64{4, false},
- NullBool{false, false},
- NullFloat64{5.6, false},
- nil,
- NullTime{t1, false},
- NullDate{d1, false},
- },
- listProto(
- nullProto(),
- nullProto(),
- nullProto(),
- nullProto(),
- nullProto(),
- nullProto(),
- nullProto()),
- StructTypeProto,
- },
- } {
- encodeStructValue(test, t)
- }
- }
-
- func TestEncodeStructValueArrayFields(t *testing.T) {
- StructTypeProto := structType(
- mkField("Stringf", listType(stringType())),
- mkField("Intf", listType(intType())),
- mkField("Int64f", listType(intType())),
- mkField("Boolf", listType(boolType())),
- mkField("Floatf", listType(floatType())),
- mkField("Bytef", listType(bytesType())),
- mkField("Timef", listType(timeType())),
- mkField("Datef", listType(dateType())))
-
- for _, test := range []encodeTest{
- {
- "Arrays of basic types with non-nullable elements",
- struct {
- Stringf []string
- Intf []int
- Int64f []int64
- Boolf []bool
- Floatf []float64
- Bytef [][]byte
- Timef []time.Time
- Datef []civil.Date
- }{
- []string{"abc", "def"},
- []int{4, 67},
- []int64{5, 68},
- []bool{false, true},
- []float64{3.45, 0.93},
- [][]byte{[]byte("foo"), nil},
- []time.Time{t1, t2},
- []civil.Date{d1, 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
- Intf []NullInt64
- Int64f []NullInt64
- Boolf []NullBool
- Floatf []NullFloat64
- Bytef [][]byte
- Timef []NullTime
- Datef []NullDate
- }{
- []NullString{NullString{"abc", false}, NullString{"def", true}},
- []NullInt64{NullInt64{4, false}, NullInt64{67, true}},
- []NullInt64{NullInt64{5, false}, NullInt64{68, true}},
- []NullBool{NullBool{true, false}, NullBool{false, true}},
- []NullFloat64{NullFloat64{3.45, false}, NullFloat64{0.93, true}},
- [][]byte{[]byte("foo"), nil},
- []NullTime{NullTime{t1, false}, NullTime{t2, true}},
- []NullDate{NullDate{d1, false}, NullDate{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
- Intf []NullInt64
- Int64f []NullInt64
- Boolf []NullBool
- Floatf []NullFloat64
- Bytef [][]byte
- Timef []NullTime
- Datef []NullDate
- }{
- nil,
- nil,
- nil,
- nil,
- nil,
- nil,
- nil,
- nil,
- },
- listProto(
- nullProto(),
- nullProto(),
- nullProto(),
- nullProto(),
- nullProto(),
- nullProto(),
- nullProto(),
- nullProto()),
- StructTypeProto,
- },
- } {
- encodeStructValue(test, t)
- }
- }
-
- // Test decoding Values.
- func TestDecodeValue(t *testing.T) {
- for i, test := range []struct {
- in *proto3.Value
- t *sppb.Type
- want interface{}
- fail bool
- }{
- // STRING
- {stringProto("abc"), stringType(), "abc", false},
- {nullProto(), stringType(), "abc", true},
- {stringProto("abc"), stringType(), NullString{"abc", true}, false},
- {nullProto(), stringType(), NullString{}, false},
- // STRING ARRAY with []NullString
- {
- listProto(stringProto("abc"), nullProto(), stringProto("bcd")),
- listType(stringType()),
- []NullString{{"abc", true}, {}, {"bcd", true}},
- false,
- },
- {nullProto(), listType(stringType()), []NullString(nil), false},
- // STRING ARRAY with []string
- {
- listProto(stringProto("abc"), stringProto("bcd")),
- listType(stringType()),
- []string{"abc", "bcd"},
- false,
- },
- // BYTES
- {bytesProto([]byte("ab")), bytesType(), []byte("ab"), false},
- {nullProto(), bytesType(), []byte(nil), false},
- // BYTES ARRAY
- {listProto(bytesProto([]byte("ab")), nullProto()), listType(bytesType()), [][]byte{[]byte("ab"), nil}, false},
- {nullProto(), listType(bytesType()), [][]byte(nil), false},
- //INT64
- {intProto(15), intType(), int64(15), false},
- {nullProto(), intType(), int64(0), true},
- {intProto(15), intType(), NullInt64{15, true}, false},
- {nullProto(), intType(), NullInt64{}, false},
- // INT64 ARRAY with []NullInt64
- {listProto(intProto(91), nullProto(), intProto(87)), listType(intType()), []NullInt64{{91, true}, {}, {87, true}}, false},
- {nullProto(), listType(intType()), []NullInt64(nil), false},
- // INT64 ARRAY with []int64
- {listProto(intProto(91), intProto(87)), listType(intType()), []int64{91, 87}, false},
- // BOOL
- {boolProto(true), boolType(), true, false},
- {nullProto(), boolType(), true, true},
- {boolProto(true), boolType(), NullBool{true, true}, false},
- {nullProto(), boolType(), NullBool{}, false},
- // BOOL ARRAY with []NullBool
- {listProto(boolProto(true), boolProto(false), nullProto()), listType(boolType()), []NullBool{{true, true}, {false, true}, {}}, false},
- {nullProto(), listType(boolType()), []NullBool(nil), false},
- // BOOL ARRAY with []bool
- {listProto(boolProto(true), boolProto(false)), listType(boolType()), []bool{true, false}, false},
- // FLOAT64
- {floatProto(3.14), floatType(), 3.14, false},
- {nullProto(), floatType(), 0.00, true},
- {floatProto(3.14), floatType(), NullFloat64{3.14, true}, false},
- {nullProto(), floatType(), NullFloat64{}, false},
- // FLOAT64 ARRAY with []NullFloat64
- {
- listProto(floatProto(math.Inf(1)), floatProto(math.Inf(-1)), nullProto(), floatProto(3.1)),
- listType(floatType()),
- []NullFloat64{{math.Inf(1), true}, {math.Inf(-1), true}, {}, {3.1, true}},
- false,
- },
- {nullProto(), listType(floatType()), []NullFloat64(nil), false},
- // FLOAT64 ARRAY with []float64
- {
- listProto(floatProto(math.Inf(1)), floatProto(math.Inf(-1)), floatProto(3.1)),
- listType(floatType()),
- []float64{math.Inf(1), math.Inf(-1), 3.1},
- false,
- },
- // TIMESTAMP
- {timeProto(t1), timeType(), t1, false},
- {timeProto(t1), timeType(), NullTime{t1, true}, false},
- {nullProto(), timeType(), NullTime{}, false},
- // TIMESTAMP ARRAY with []NullTime
- {listProto(timeProto(t1), timeProto(t2), timeProto(t3), nullProto()), listType(timeType()), []NullTime{{t1, true}, {t2, true}, {t3, true}, {}}, false},
- {nullProto(), listType(timeType()), []NullTime(nil), false},
- // TIMESTAMP ARRAY with []time.Time
- {listProto(timeProto(t1), timeProto(t2), timeProto(t3)), listType(timeType()), []time.Time{t1, t2, t3}, false},
- // DATE
- {dateProto(d1), dateType(), d1, false},
- {dateProto(d1), dateType(), NullDate{d1, true}, false},
- {nullProto(), dateType(), NullDate{}, false},
- // DATE ARRAY with []NullDate
- {listProto(dateProto(d1), dateProto(d2), nullProto()), listType(dateType()), []NullDate{{d1, true}, {d2, true}, {}}, false},
- {nullProto(), listType(dateType()), []NullDate(nil), false},
- // DATE ARRAY with []civil.Date
- {listProto(dateProto(d1), dateProto(d2)), listType(dateType()), []civil.Date{d1, d2}, false},
- // STRUCT ARRAY
- // STRUCT schema is equal to the following Go struct:
- // type s struct {
- // Col1 NullInt64
- // Col2 []struct {
- // SubCol1 float64
- // SubCol2 string
- // }
- // }
- {
- in: listProto(
- listProto(
- intProto(3),
- listProto(
- listProto(floatProto(3.14), stringProto("this")),
- listProto(floatProto(0.57), stringProto("siht")),
- ),
- ),
- listProto(
- nullProto(),
- nullProto(),
- ),
- nullProto(),
- ),
- t: listType(
- structType(
- mkField("Col1", intType()),
- mkField(
- "Col2",
- listType(
- structType(
- mkField("SubCol1", floatType()),
- mkField("SubCol2", stringType()),
- ),
- ),
- ),
- ),
- ),
- want: []NullRow{
- {
- Row: Row{
- fields: []*sppb.StructType_Field{
- mkField("Col1", intType()),
- mkField(
- "Col2",
- listType(
- structType(
- mkField("SubCol1", floatType()),
- mkField("SubCol2", stringType()),
- ),
- ),
- ),
- },
- vals: []*proto3.Value{
- intProto(3),
- listProto(
- listProto(floatProto(3.14), stringProto("this")),
- listProto(floatProto(0.57), stringProto("siht")),
- ),
- },
- },
- Valid: true,
- },
- {
- Row: Row{
- fields: []*sppb.StructType_Field{
- mkField("Col1", intType()),
- mkField(
- "Col2",
- listType(
- structType(
- mkField("SubCol1", floatType()),
- mkField("SubCol2", stringType()),
- ),
- ),
- ),
- },
- vals: []*proto3.Value{
- nullProto(),
- nullProto(),
- },
- },
- Valid: true,
- },
- {},
- },
- fail: false,
- },
- {
- in: listProto(
- listProto(
- intProto(3),
- listProto(
- listProto(floatProto(3.14), stringProto("this")),
- listProto(floatProto(0.57), stringProto("siht")),
- ),
- ),
- listProto(
- nullProto(),
- nullProto(),
- ),
- nullProto(),
- ),
- t: listType(
- structType(
- mkField("Col1", intType()),
- mkField(
- "Col2",
- listType(
- structType(
- mkField("SubCol1", floatType()),
- mkField("SubCol2", stringType()),
- ),
- ),
- ),
- ),
- ),
- want: []*struct {
- Col1 NullInt64
- StructCol []*struct {
- SubCol1 NullFloat64
- SubCol2 string
- } `spanner:"Col2"`
- }{
- {
- Col1: NullInt64{3, true},
- StructCol: []*struct {
- SubCol1 NullFloat64
- SubCol2 string
- }{
- {
- SubCol1: NullFloat64{3.14, true},
- SubCol2: "this",
- },
- {
- SubCol1: NullFloat64{0.57, true},
- SubCol2: "siht",
- },
- },
- },
- {
- Col1: NullInt64{},
- StructCol: []*struct {
- SubCol1 NullFloat64
- SubCol2 string
- }(nil),
- },
- nil,
- },
- fail: false,
- },
- // GenericColumnValue
- {stringProto("abc"), stringType(), GenericColumnValue{stringType(), stringProto("abc")}, false},
- {nullProto(), stringType(), GenericColumnValue{stringType(), nullProto()}, false},
- // not actually valid (stringProto inside int list), but demonstrates pass-through.
- {
- in: listProto(intProto(5), nullProto(), stringProto("bcd")),
- t: listType(intType()),
- want: GenericColumnValue{
- Type: listType(intType()),
- Value: listProto(intProto(5), nullProto(), stringProto("bcd")),
- },
- fail: false,
- },
- } {
- gotp := reflect.New(reflect.TypeOf(test.want))
- if err := decodeValue(test.in, test.t, gotp.Interface()); err != nil {
- if !test.fail {
- t.Errorf("%d: cannot decode %v(%v): %v", i, test.in, test.t, err)
- }
- continue
- }
- if test.fail {
- t.Errorf("%d: decoding %v(%v) succeeds unexpectedly, want error", i, test.in, test.t)
- continue
- }
- got := reflect.Indirect(gotp).Interface()
- if !testEqual(got, test.want) {
- t.Errorf("%d: unexpected decoding result - got %v, want %v", i, got, test.want)
- continue
- }
- }
- }
-
- // Test error cases for decodeValue.
- func TestDecodeValueErrors(t *testing.T) {
- var s string
- for i, test := range []struct {
- in *proto3.Value
- t *sppb.Type
- v interface{}
- }{
- {nullProto(), stringType(), nil},
- {nullProto(), stringType(), 1},
- {timeProto(t1), timeType(), &s},
- } {
- err := decodeValue(test.in, test.t, test.v)
- if err == nil {
- t.Errorf("#%d: want error, got nil", i)
- }
- }
- }
-
- // Test NaN encoding/decoding.
- func TestNaN(t *testing.T) {
- // Decode NaN value.
- f := 0.0
- nf := NullFloat64{}
- // To float64
- if err := decodeValue(floatProto(math.NaN()), floatType(), &f); err != nil {
- t.Errorf("decodeValue returns %q for %v, want nil", err, floatProto(math.NaN()))
- }
- if !math.IsNaN(f) {
- t.Errorf("f = %v, want %v", f, math.NaN())
- }
- // To NullFloat64
- if err := decodeValue(floatProto(math.NaN()), floatType(), &nf); err != nil {
- t.Errorf("decodeValue returns %q for %v, want nil", err, floatProto(math.NaN()))
- }
- if !math.IsNaN(nf.Float64) || !nf.Valid {
- t.Errorf("f = %v, want %v", f, NullFloat64{math.NaN(), true})
- }
- // Encode NaN value
- // From float64
- v, _, err := encodeValue(math.NaN())
- if err != nil {
- t.Errorf("encodeValue returns %q for NaN, want nil", err)
- }
- x, ok := v.GetKind().(*proto3.Value_NumberValue)
- if !ok {
- t.Errorf("incorrect type for v.GetKind(): %T, want *proto3.Value_NumberValue", v.GetKind())
- }
- if !math.IsNaN(x.NumberValue) {
- t.Errorf("x.NumberValue = %v, want %v", x.NumberValue, math.NaN())
- }
- // From NullFloat64
- v, _, err = encodeValue(NullFloat64{math.NaN(), true})
- if err != nil {
- t.Errorf("encodeValue returns %q for NaN, want nil", err)
- }
- x, ok = v.GetKind().(*proto3.Value_NumberValue)
- if !ok {
- t.Errorf("incorrect type for v.GetKind(): %T, want *proto3.Value_NumberValue", v.GetKind())
- }
- if !math.IsNaN(x.NumberValue) {
- t.Errorf("x.NumberValue = %v, want %v", x.NumberValue, math.NaN())
- }
- }
-
- func TestGenericColumnValue(t *testing.T) {
- for _, test := range []struct {
- in GenericColumnValue
- want interface{}
- fail bool
- }{
- {GenericColumnValue{stringType(), stringProto("abc")}, "abc", false},
- {GenericColumnValue{stringType(), stringProto("abc")}, 5, true},
- {GenericColumnValue{listType(intType()), listProto(intProto(91), nullProto(), intProto(87))}, []NullInt64{{91, true}, {}, {87, true}}, false},
- {GenericColumnValue{intType(), intProto(42)}, GenericColumnValue{intType(), intProto(42)}, false}, // trippy! :-)
- } {
- gotp := reflect.New(reflect.TypeOf(test.want))
- if err := test.in.Decode(gotp.Interface()); err != nil {
- if !test.fail {
- t.Errorf("cannot decode %v to %v: %v", test.in, test.want, err)
- }
- continue
- }
- if test.fail {
- t.Errorf("decoding %v to %v succeeds unexpectedly", test.in, test.want)
- }
-
- // Test we can go backwards as well.
- v, err := newGenericColumnValue(test.want)
- if err != nil {
- t.Errorf("NewGenericColumnValue failed: %v", err)
- continue
- }
- if !testEqual(*v, test.in) {
- t.Errorf("unexpected encode result - got %v, want %v", v, test.in)
- }
- }
- }
-
- func TestDecodeStruct(t *testing.T) {
- stype := &sppb.StructType{Fields: []*sppb.StructType_Field{
- {Name: "Id", Type: stringType()},
- {Name: "Time", Type: timeType()},
- }}
- lv := listValueProto(stringProto("id"), timeProto(t1))
-
- type (
- S1 struct {
- Id string
- Time time.Time
- }
- S2 struct {
- Id string
- Time string
- }
- )
- var (
- s1 S1
- s2 S2
- )
-
- for i, test := range []struct {
- ptr interface{}
- want interface{}
- fail bool
- }{
- {
- ptr: &s1,
- want: &S1{Id: "id", Time: t1},
- },
- {
- ptr: &s2,
- fail: true,
- },
- } {
- err := decodeStruct(stype, lv, test.ptr)
- if (err != nil) != test.fail {
- t.Errorf("#%d: got error %v, wanted fail: %v", i, err, test.fail)
- }
- if err == nil && !testEqual(test.ptr, test.want) {
- t.Errorf("#%d: got %+v, want %+v", i, test.ptr, test.want)
- }
- }
- }
|