|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466 |
- // Copyright 2018 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.
-
- // +build go1.7
-
- package spanner
-
- import (
- "reflect"
- "testing"
- "time"
-
- "cloud.google.com/go/civil"
- proto "github.com/golang/protobuf/proto"
- proto3 "github.com/golang/protobuf/ptypes/struct"
- "golang.org/x/net/context"
- sppb "google.golang.org/genproto/googleapis/spanner/v1"
- )
-
- 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 i, 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
- got := &sppb.ExecuteSqlRequest{}
- if err := st.bindParams(got); err != nil || !proto.Equal(got, want) {
- // handle NaN
- if test.wantType.Code == floatType().Code && proto.MarshalTextString(got) == proto.MarshalTextString(want) {
- continue
- }
- t.Errorf("#%d: bind result: \n(%v, %v)\nwant\n(%v, %v)\n", i, got, err, want, nil)
- }
- }
- }
-
- func TestStructParametersBind(t *testing.T) {
- t.Parallel()
- ctx := context.Background()
- client, _, tearDown := prepare(ctx, t, nil)
- defer tearDown()
-
- type tRow []interface{}
- type tRows []struct{ trow tRow }
-
- type allFields struct {
- Stringf string
- Intf int
- Boolf bool
- Floatf float64
- Bytef []byte
- Timef time.Time
- Datef civil.Date
- }
- allColumns := []string{
- "Stringf",
- "Intf",
- "Boolf",
- "Floatf",
- "Bytef",
- "Timef",
- "Datef",
- }
- s1 := allFields{"abc", 300, false, 3.45, []byte("foo"), t1, d1}
- s2 := allFields{"def", -300, false, -3.45, []byte("bar"), t2, d2}
-
- dynamicStructType := reflect.StructOf([]reflect.StructField{
- {Name: "A", Type: reflect.TypeOf(t1), Tag: `spanner:"ff1"`},
- })
- s3 := reflect.New(dynamicStructType)
- s3.Elem().Field(0).Set(reflect.ValueOf(t1))
-
- for i, test := range []struct {
- param interface{}
- sql string
- cols []string
- trows tRows
- }{
- // Struct value.
- {
- s1,
- "SELECT" +
- " @p.Stringf," +
- " @p.Intf," +
- " @p.Boolf," +
- " @p.Floatf," +
- " @p.Bytef," +
- " @p.Timef," +
- " @p.Datef",
- allColumns,
- tRows{
- {tRow{"abc", 300, false, 3.45, []byte("foo"), t1, d1}},
- },
- },
- // Array of struct value.
- {
- []allFields{s1, s2},
- "SELECT * FROM UNNEST(@p)",
- allColumns,
- tRows{
- {tRow{"abc", 300, false, 3.45, []byte("foo"), t1, d1}},
- {tRow{"def", -300, false, -3.45, []byte("bar"), t2, d2}},
- },
- },
- // Null struct.
- {
- (*allFields)(nil),
- "SELECT @p IS NULL",
- []string{""},
- tRows{
- {tRow{true}},
- },
- },
- // Null Array of struct.
- {
- []allFields(nil),
- "SELECT @p IS NULL",
- []string{""},
- tRows{
- {tRow{true}},
- },
- },
- // Empty struct.
- {
- struct{}{},
- "SELECT @p IS NULL ",
- []string{""},
- tRows{
- {tRow{false}},
- },
- },
- // Empty array of struct.
- {
- []allFields{},
- "SELECT * FROM UNNEST(@p) ",
- allColumns,
- tRows{},
- },
- // Struct with duplicate fields.
- {
- struct {
- A int `spanner:"field"`
- B int `spanner:"field"`
- }{10, 20},
- "SELECT * FROM UNNEST([@p]) ",
- []string{"field", "field"},
- tRows{
- {tRow{10, 20}},
- },
- },
- // Struct with unnamed fields.
- {
- struct {
- A string `spanner:""`
- }{"hello"},
- "SELECT * FROM UNNEST([@p]) ",
- []string{""},
- tRows{
- {tRow{"hello"}},
- },
- },
- // Mixed struct.
- {
- struct {
- DynamicStructField interface{} `spanner:"f1"`
- ArrayStructField []*allFields `spanner:"f2"`
- }{
- DynamicStructField: s3.Interface(),
- ArrayStructField: []*allFields{nil},
- },
- "SELECT @p.f1.ff1, ARRAY_LENGTH(@p.f2), @p.f2[OFFSET(0)] IS NULL ",
- []string{"ff1", "", ""},
- tRows{
- {tRow{t1, 1, true}},
- },
- },
- } {
- iter := client.Single().Query(ctx, Statement{
- SQL: test.sql,
- Params: map[string]interface{}{"p": test.param},
- })
- var gotRows []*Row
- err := iter.Do(func(r *Row) error {
- gotRows = append(gotRows, r)
- return nil
- })
- if err != nil {
- t.Errorf("Failed to execute test case %d, error: %v", i, err)
- }
-
- var wantRows []*Row
- for j, row := range test.trows {
- r, err := NewRow(test.cols, row.trow)
- if err != nil {
- t.Errorf("Invalid row %d in test case %d", j, i)
- }
- wantRows = append(wantRows, r)
- }
- if !testEqual(gotRows, wantRows) {
- t.Errorf("%d: Want result %v, got result %v", i, wantRows, gotRows)
- }
- }
- }
|