mirror of
https://github.com/dutchcoders/transfer.sh.git
synced 2024-11-27 14:40:18 +01:00
1395 lines
37 KiB
Go
1395 lines
37 KiB
Go
/*
|
|
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)
|
|
}
|
|
}
|
|
}
|