blob: d1f6bfbe1bd52281124dac6045eb43554fe2ad38 [file] [log] [blame]
// 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)
}
}
}