blob: 34207eded19a86e432591e6baf76a1fe656bead5 [file] [log] [blame]
/*
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"
"github.com/golang/protobuf/proto"
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{{30}, {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),
{"hello", nil, []*structf{nil, {40}}},
{"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{{1}, {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), {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{{"abc", false}, {"def", true}},
[]NullInt64{{4, false}, {67, true}},
[]NullInt64{{5, false}, {68, true}},
[]NullBool{{true, false}, {false, true}},
[]NullFloat64{{3.45, false}, {0.93, true}},
[][]byte{[]byte("foo"), nil},
[]NullTime{{t1, false}, {t2, true}},
[]NullDate{{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
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)
}
}
}
func TestEncodeStructValueDynamicStructs(t *testing.T) {
dynStructType := reflect.StructOf([]reflect.StructField{
{Name: "A", Type: reflect.TypeOf(0), Tag: `spanner:"a"`},
{Name: "B", Type: reflect.TypeOf(""), Tag: `spanner:"b"`},
})
dynNullableStructType := reflect.PtrTo(dynStructType)
dynStructArrType := reflect.SliceOf(dynStructType)
dynNullableStructArrType := reflect.SliceOf(dynNullableStructType)
dynStructValue := reflect.New(dynStructType)
dynStructValue.Elem().Field(0).SetInt(10)
dynStructValue.Elem().Field(1).SetString("abc")
dynStructArrValue := reflect.MakeSlice(dynNullableStructArrType, 2, 2)
dynStructArrValue.Index(0).Set(reflect.Zero(dynNullableStructType))
dynStructArrValue.Index(1).Set(dynStructValue)
structProtoType := structType(
mkField("a", intType()),
mkField("b", stringType()))
arrProtoType := listType(structProtoType)
for _, test := range []encodeTest{
{
"Dynanic non-NULL struct value.",
dynStructValue.Elem().Interface(),
listProto(intProto(10), stringProto("abc")),
structProtoType,
},
{
"Dynanic NULL struct value.",
reflect.Zero(dynNullableStructType).Interface(),
nullProto(),
structProtoType,
},
{
"Empty array of dynamic structs.",
reflect.MakeSlice(dynStructArrType, 0, 0).Interface(),
listProto([]*proto3.Value{}...),
arrProtoType,
},
{
"NULL array of non-NULL-able dynamic structs.",
reflect.Zero(dynStructArrType).Interface(),
nullProto(),
arrProtoType,
},
{
"NULL array of NULL-able(nil) dynamic structs.",
reflect.Zero(dynNullableStructArrType).Interface(),
nullProto(),
arrProtoType,
},
{
"Array containing NULL(nil) dynamic-typed struct elements.",
dynStructArrValue.Interface(),
listProto(
nullProto(),
listProto(intProto(10), stringProto("abc"))),
arrProtoType,
},
} {
encodeStructValue(test, t)
}
}
func TestEncodeStructValueEmptyStruct(t *testing.T) {
emptyListValue := listProto([]*proto3.Value{}...)
emptyStructType := structType([]*sppb.StructType_Field{}...)
emptyStruct := struct{}{}
nullEmptyStruct := (*struct{})(nil)
dynamicEmptyStructType := reflect.StructOf(make([]reflect.StructField, 0, 0))
dynamicStructArrType := reflect.SliceOf(reflect.PtrTo((dynamicEmptyStructType)))
dynamicEmptyStruct := reflect.New(dynamicEmptyStructType)
dynamicNullEmptyStruct := reflect.Zero(reflect.PtrTo(dynamicEmptyStructType))
dynamicStructArrValue := reflect.MakeSlice(dynamicStructArrType, 2, 2)
dynamicStructArrValue.Index(0).Set(dynamicNullEmptyStruct)
dynamicStructArrValue.Index(1).Set(dynamicEmptyStruct)
for _, test := range []encodeTest{
{
"Go empty struct.",
emptyStruct,
emptyListValue,
emptyStructType,
},
{
"Dynamic empty struct.",
dynamicEmptyStruct.Interface(),
emptyListValue,
emptyStructType,
},
{
"Go NULL empty struct.",
nullEmptyStruct,
nullProto(),
emptyStructType,
},
{
"Dynamic NULL empty struct.",
dynamicNullEmptyStruct.Interface(),
nullProto(),
emptyStructType,
},
{
"Non-empty array of dynamic NULL and non-NULL empty structs.",
dynamicStructArrValue.Interface(),
listProto(nullProto(), emptyListValue),
listType(emptyStructType),
},
{
"Non-empty array of nullable empty structs.",
[]*struct{}{nullEmptyStruct, &emptyStruct},
listProto(nullProto(), emptyListValue),
listType(emptyStructType),
},
{
"Empty array of empty struct.",
[]struct{}{},
emptyListValue,
listType(emptyStructType),
},
{
"Null array of empty structs.",
[]struct{}(nil),
nullProto(),
listType(emptyStructType),
},
} {
encodeStructValue(test, t)
}
}
func TestEncodeStructValueMixedStructTypes(t *testing.T) {
type staticStruct struct {
F int `spanner:"fStatic"`
}
s1 := staticStruct{10}
s2 := (*staticStruct)(nil)
var f float64
dynStructType := reflect.StructOf([]reflect.StructField{
{Name: "A", Type: reflect.TypeOf(f), Tag: `spanner:"fDynamic"`},
})
s3 := reflect.New(dynStructType)
s3.Elem().Field(0).SetFloat(3.14)
for _, test := range []encodeTest{
{
"'struct' with static and dynamic *struct, []*struct, []struct fields",
struct {
A []staticStruct
B []*staticStruct
C interface{}
}{
[]staticStruct{s1, s1},
[]*staticStruct{&s1, s2},
s3.Interface(),
},
listProto(
listProto(listProto(intProto(10)), listProto(intProto(10))),
listProto(listProto(intProto(10)), nullProto()),
listProto(floatProto(3.14))),
structType(
mkField("A", listType(structType(mkField("fStatic", intType())))),
mkField("B", listType(structType(mkField("fStatic", intType())))),
mkField("C", structType(mkField("fDynamic", floatType())))),
},
} {
encodeStructValue(test, t)
}
}
func TestBindParamsDynamic(t *testing.T) {
// Verify Statement.bindParams generates correct values and types.
st := Statement{
SQL: "SELECT id from t_foo WHERE col = @var",
Params: map[string]interface{}{"var": nil},
}
want := &sppb.ExecuteSqlRequest{
Params: &proto3.Struct{
Fields: map[string]*proto3.Value{"var": nil},
},
ParamTypes: map[string]*sppb.Type{"var": nil},
}
var (
t1, _ = time.Parse(time.RFC3339Nano, "2016-11-15T15:04:05.999999999Z")
// Boundaries
t2, _ = time.Parse(time.RFC3339Nano, "0001-01-01T00:00:00.000000000Z")
)
dynamicStructType := reflect.StructOf([]reflect.StructField{
{Name: "A", Type: reflect.TypeOf(t1), Tag: `spanner:"field"`},
{Name: "B", Type: reflect.TypeOf(3.14), Tag: `spanner:""`},
})
dynamicStructArrType := reflect.SliceOf(reflect.PtrTo(dynamicStructType))
dynamicEmptyStructType := reflect.StructOf(make([]reflect.StructField, 0, 0))
dynamicStructTypeProto := structType(
mkField("field", timeType()),
mkField("", floatType()))
s3 := reflect.New(dynamicStructType)
s3.Elem().Field(0).Set(reflect.ValueOf(t1))
s3.Elem().Field(1).SetFloat(1.4)
s4 := reflect.New(dynamicStructType)
s4.Elem().Field(0).Set(reflect.ValueOf(t2))
s4.Elem().Field(1).SetFloat(-13.3)
dynamicStructArrayVal := reflect.MakeSlice(dynamicStructArrType, 2, 2)
dynamicStructArrayVal.Index(0).Set(s3)
dynamicStructArrayVal.Index(1).Set(s4)
for _, test := range []struct {
val interface{}
wantField *proto3.Value
wantType *sppb.Type
}{
{
s3.Interface(),
listProto(timeProto(t1), floatProto(1.4)),
structType(
mkField("field", timeType()),
mkField("", floatType())),
},
{
reflect.Zero(reflect.PtrTo(dynamicEmptyStructType)).Interface(),
nullProto(),
structType([]*sppb.StructType_Field{}...),
},
{
dynamicStructArrayVal.Interface(),
listProto(
listProto(timeProto(t1), floatProto(1.4)),
listProto(timeProto(t2), floatProto(-13.3))),
listType(dynamicStructTypeProto),
},
{
[]*struct {
F1 time.Time `spanner:"field"`
F2 float64 `spanner:""`
}{
nil,
{t1, 1.4},
},
listProto(
nullProto(),
listProto(timeProto(t1), floatProto(1.4))),
listType(dynamicStructTypeProto),
},
} {
st.Params["var"] = test.val
want.Params.Fields["var"] = test.wantField
want.ParamTypes["var"] = test.wantType
gotParams, gotParamTypes, gotErr := st.convertParams()
if gotErr != nil {
t.Error(gotErr)
continue
}
gotParamField := gotParams.Fields["var"]
if !proto.Equal(gotParamField, test.wantField) {
// handle NaN
if test.wantType.Code == floatType().Code && proto.MarshalTextString(gotParamField) == proto.MarshalTextString(test.wantField) {
continue
}
t.Errorf("%#v: got %v, want %v\n", test.val, gotParamField, test.wantField)
}
gotParamType := gotParamTypes["var"]
if !proto.Equal(gotParamType, test.wantType) {
t.Errorf("%#v: got %v, want %v\n", test.val, gotParamType, test.wantField)
}
}
}