cri-o/vendor/cloud.google.com/go/spanner/row_test.go

1776 lines
44 KiB
Go
Raw Normal View History

/*
Copyright 2017 Google Inc. All Rights Reserved.
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 (
"encoding/base64"
"reflect"
"strconv"
"strings"
"testing"
"time"
"cloud.google.com/go/civil"
proto "github.com/golang/protobuf/proto"
proto3 "github.com/golang/protobuf/ptypes/struct"
sppb "google.golang.org/genproto/googleapis/spanner/v1"
)
var (
tm = time.Date(2016, 11, 15, 0, 0, 0, 0, time.UTC)
dt, _ = civil.ParseDate("2016-11-15")
// row contains a column for each unique Cloud Spanner type.
row = Row{
[]*sppb.StructType_Field{
// STRING / STRING ARRAY
{"STRING", stringType()},
{"NULL_STRING", stringType()},
{"STRING_ARRAY", listType(stringType())},
{"NULL_STRING_ARRAY", listType(stringType())},
// BYTES / BYTES ARRAY
{"BYTES", bytesType()},
{"NULL_BYTES", bytesType()},
{"BYTES_ARRAY", listType(bytesType())},
{"NULL_BYTES_ARRAY", listType(bytesType())},
// INT64 / INT64 ARRAY
{"INT64", intType()},
{"NULL_INT64", intType()},
{"INT64_ARRAY", listType(intType())},
{"NULL_INT64_ARRAY", listType(intType())},
// BOOL / BOOL ARRAY
{"BOOL", boolType()},
{"NULL_BOOL", boolType()},
{"BOOL_ARRAY", listType(boolType())},
{"NULL_BOOL_ARRAY", listType(boolType())},
// FLOAT64 / FLOAT64 ARRAY
{"FLOAT64", floatType()},
{"NULL_FLOAT64", floatType()},
{"FLOAT64_ARRAY", listType(floatType())},
{"NULL_FLOAT64_ARRAY", listType(floatType())},
// TIMESTAMP / TIMESTAMP ARRAY
{"TIMESTAMP", timeType()},
{"NULL_TIMESTAMP", timeType()},
{"TIMESTAMP_ARRAY", listType(timeType())},
{"NULL_TIMESTAMP_ARRAY", listType(timeType())},
// DATE / DATE ARRAY
{"DATE", dateType()},
{"NULL_DATE", dateType()},
{"DATE_ARRAY", listType(dateType())},
{"NULL_DATE_ARRAY", listType(dateType())},
// STRUCT ARRAY
{
"STRUCT_ARRAY",
listType(
structType(
mkField("Col1", intType()),
mkField("Col2", floatType()),
mkField("Col3", stringType()),
),
),
},
{
"NULL_STRUCT_ARRAY",
listType(
structType(
mkField("Col1", intType()),
mkField("Col2", floatType()),
mkField("Col3", stringType()),
),
),
},
},
[]*proto3.Value{
// STRING / STRING ARRAY
stringProto("value"),
nullProto(),
listProto(stringProto("value1"), nullProto(), stringProto("value3")),
nullProto(),
// BYTES / BYTES ARRAY
bytesProto([]byte("value")),
nullProto(),
listProto(bytesProto([]byte("value1")), nullProto(), bytesProto([]byte("value3"))),
nullProto(),
// INT64 / INT64 ARRAY
intProto(17),
nullProto(),
listProto(intProto(1), intProto(2), nullProto()),
nullProto(),
// BOOL / BOOL ARRAY
boolProto(true),
nullProto(),
listProto(nullProto(), boolProto(true), boolProto(false)),
nullProto(),
// FLOAT64 / FLOAT64 ARRAY
floatProto(1.7),
nullProto(),
listProto(nullProto(), nullProto(), floatProto(1.7)),
nullProto(),
// TIMESTAMP / TIMESTAMP ARRAY
timeProto(tm),
nullProto(),
listProto(nullProto(), timeProto(tm)),
nullProto(),
// DATE / DATE ARRAY
dateProto(dt),
nullProto(),
listProto(nullProto(), dateProto(dt)),
nullProto(),
// STRUCT ARRAY
listProto(
nullProto(),
listProto(intProto(3), floatProto(33.3), stringProto("three")),
nullProto(),
),
nullProto(),
},
}
)
// Test helpers for getting column values.
func TestColumnValues(t *testing.T) {
vals := []interface{}{}
wantVals := []interface{}{}
// Test getting column values.
for i, wants := range [][]interface{}{
// STRING / STRING ARRAY
{"value", NullString{"value", true}},
{NullString{}},
{[]NullString{{"value1", true}, {}, {"value3", true}}},
{[]NullString(nil)},
// BYTES / BYTES ARRAY
{[]byte("value")},
{[]byte(nil)},
{[][]byte{[]byte("value1"), nil, []byte("value3")}},
{[][]byte(nil)},
// INT64 / INT64 ARRAY
{int64(17), NullInt64{17, true}},
{NullInt64{}},
{[]NullInt64{{1, true}, {2, true}, {}}},
{[]NullInt64(nil)},
// BOOL / BOOL ARRAY
{true, NullBool{true, true}},
{NullBool{}},
{[]NullBool{{}, {true, true}, {false, true}}},
{[]NullBool(nil)},
// FLOAT64 / FLOAT64 ARRAY
{1.7, NullFloat64{1.7, true}},
{NullFloat64{}},
{[]NullFloat64{{}, {}, {1.7, true}}},
{[]NullFloat64(nil)},
// TIMESTAMP / TIMESTAMP ARRAY
{tm, NullTime{tm, true}},
{NullTime{}},
{[]NullTime{{}, {tm, true}}},
{[]NullTime(nil)},
// DATE / DATE ARRAY
{dt, NullDate{dt, true}},
{NullDate{}},
{[]NullDate{{}, {dt, true}}},
{[]NullDate(nil)},
// STRUCT ARRAY
{
[]*struct {
Col1 NullInt64
Col2 NullFloat64
Col3 string
}{
nil,
&struct {
Col1 NullInt64
Col2 NullFloat64
Col3 string
}{
NullInt64{3, true},
NullFloat64{33.3, true},
"three",
},
nil,
},
[]NullRow{
{},
{
Row: Row{
fields: []*sppb.StructType_Field{
mkField("Col1", intType()),
mkField("Col2", floatType()),
mkField("Col3", stringType()),
},
vals: []*proto3.Value{
intProto(3),
floatProto(33.3),
stringProto("three"),
},
},
Valid: true,
},
{},
},
},
{
[]*struct {
Col1 NullInt64
Col2 NullFloat64
Col3 string
}(nil),
[]NullRow(nil),
},
} {
for j, want := range wants {
// Prepare Value vector to test Row.Columns.
if j == 0 {
vals = append(vals, reflect.New(reflect.TypeOf(want)).Interface())
wantVals = append(wantVals, want)
}
// Column
gotp := reflect.New(reflect.TypeOf(want))
err := row.Column(i, gotp.Interface())
if err != nil {
t.Errorf("\t row.Column(%v, %T) returns error: %v, want nil", i, gotp.Interface(), err)
}
if got := reflect.Indirect(gotp).Interface(); !reflect.DeepEqual(got, want) {
t.Errorf("\t row.Column(%v, %T) retrives %v, want %v", i, gotp.Interface(), got, want)
}
// ColumnByName
gotp = reflect.New(reflect.TypeOf(want))
err = row.ColumnByName(row.fields[i].Name, gotp.Interface())
if err != nil {
t.Errorf("\t row.ColumnByName(%v, %T) returns error: %v, want nil", row.fields[i].Name, gotp.Interface(), err)
}
if got := reflect.Indirect(gotp).Interface(); !reflect.DeepEqual(got, want) {
t.Errorf("\t row.ColumnByName(%v, %T) retrives %v, want %v", row.fields[i].Name, gotp.Interface(), got, want)
}
}
}
// Test Row.Columns.
if err := row.Columns(vals...); err != nil {
t.Errorf("row.Columns() returns error: %v, want nil", err)
}
for i, want := range wantVals {
if got := reflect.Indirect(reflect.ValueOf(vals[i])).Interface(); !reflect.DeepEqual(got, want) {
t.Errorf("\t got %v(%T) for column[%v], want %v(%T)", got, got, row.fields[i].Name, want, want)
}
}
}
// Test decoding into nil destination.
func TestNilDst(t *testing.T) {
for i, test := range []struct {
r *Row
dst interface{}
wantErr error
structDst interface{}
wantToStructErr error
}{
{
&Row{
[]*sppb.StructType_Field{
{"Col0", stringType()},
},
[]*proto3.Value{stringProto("value")},
},
nil,
errDecodeColumn(0, errNilDst(nil)),
nil,
errToStructArgType(nil),
},
{
&Row{
[]*sppb.StructType_Field{
{"Col0", stringType()},
},
[]*proto3.Value{stringProto("value")},
},
(*string)(nil),
errDecodeColumn(0, errNilDst((*string)(nil))),
(*struct{ STRING string })(nil),
errNilDst((*struct{ STRING string })(nil)),
},
{
&Row{
[]*sppb.StructType_Field{
{
"Col0",
listType(
structType(
mkField("Col1", intType()),
mkField("Col2", floatType()),
),
),
},
},
[]*proto3.Value{listProto(
listProto(intProto(3), floatProto(33.3)),
)},
},
(*[]*struct {
Col1 int
Col2 float64
})(nil),
errDecodeColumn(0, errNilDst((*[]*struct {
Col1 int
Col2 float64
})(nil))),
(*struct {
StructArray []*struct {
Col1 int
Col2 float64
} `spanner:"STRUCT_ARRAY"`
})(nil),
errNilDst((*struct {
StructArray []*struct {
Col1 int
Col2 float64
} `spanner:"STRUCT_ARRAY"`
})(nil)),
},
} {
if gotErr := test.r.Column(0, test.dst); !reflect.DeepEqual(gotErr, test.wantErr) {
t.Errorf("%v: test.r.Column() returns error %v, want %v", i, gotErr, test.wantErr)
}
if gotErr := test.r.ColumnByName("Col0", test.dst); !reflect.DeepEqual(gotErr, test.wantErr) {
t.Errorf("%v: test.r.ColumnByName() returns error %v, want %v", i, gotErr, test.wantErr)
}
// Row.Columns(T) should return nil on T == nil, otherwise, it should return test.wantErr.
wantColumnsErr := test.wantErr
if test.dst == nil {
wantColumnsErr = nil
}
if gotErr := test.r.Columns(test.dst); !reflect.DeepEqual(gotErr, wantColumnsErr) {
t.Errorf("%v: test.r.Columns() returns error %v, want %v", i, gotErr, wantColumnsErr)
}
if gotErr := test.r.ToStruct(test.structDst); !reflect.DeepEqual(gotErr, test.wantToStructErr) {
t.Errorf("%v: test.r.ToStruct() returns error %v, want %v", i, gotErr, test.wantToStructErr)
}
}
}
// Test decoding NULL columns using Go types that don't support NULL.
func TestNullTypeErr(t *testing.T) {
var tm time.Time
ntoi := func(n string) int {
for i, f := range row.fields {
if f.Name == n {
return i
}
}
t.Errorf("cannot find column name %q in row", n)
return 0
}
for _, test := range []struct {
colName string
dst interface{}
}{
{
"NULL_STRING",
proto.String(""),
},
{
"NULL_INT64",
proto.Int64(0),
},
{
"NULL_BOOL",
proto.Bool(false),
},
{
"NULL_FLOAT64",
proto.Float64(0.0),
},
{
"NULL_TIMESTAMP",
&tm,
},
{
"NULL_DATE",
&dt,
},
} {
wantErr := errDecodeColumn(ntoi(test.colName), errDstNotForNull(test.dst))
if gotErr := row.ColumnByName(test.colName, test.dst); !reflect.DeepEqual(gotErr, wantErr) {
t.Errorf("row.ColumnByName(%v) returns error %v, want %v", test.colName, gotErr, wantErr)
}
}
}
// Test using wrong destination type in column decoders.
func TestColumnTypeErr(t *testing.T) {
// badDst cannot hold any of the column values.
badDst := &struct{}{}
for i, f := range row.fields { // For each of the columns, try to decode it into badDst.
tc := f.Type.Code
isArray := strings.Contains(f.Name, "ARRAY")
if isArray {
tc = f.Type.ArrayElementType.Code
}
wantErr := errDecodeColumn(i, errTypeMismatch(tc, isArray, badDst))
if gotErr := row.Column(i, badDst); !reflect.DeepEqual(gotErr, wantErr) {
t.Errorf("Column(%v): decoding into destination with wrong type %T returns error %v, want %v",
i, badDst, gotErr, wantErr)
}
if gotErr := row.ColumnByName(f.Name, badDst); !reflect.DeepEqual(gotErr, wantErr) {
t.Errorf("ColumnByName(%v): decoding into destination with wrong type %T returns error %v, want %v",
f.Name, badDst, gotErr, wantErr)
}
}
wantErr := errDecodeColumn(1, errTypeMismatch(sppb.TypeCode_STRING, false, badDst))
// badDst is used to receive column 1.
vals := []interface{}{nil, badDst} // Row.Column() is expected to fail at column 1.
// Skip decoding the rest columns by providing nils as the destinations.
for i := 2; i < len(row.fields); i++ {
vals = append(vals, nil)
}
if gotErr := row.Columns(vals...); !reflect.DeepEqual(gotErr, wantErr) {
t.Errorf("Columns(): decoding column 1 with wrong type %T returns error %v, want %v",
badDst, gotErr, wantErr)
}
}
// Test the handling of invalid column decoding requests which cannot be mapped to correct column(s).
func TestInvalidColumnRequest(t *testing.T) {
for _, test := range []struct {
desc string
f func() error
wantErr error
}{
{
"Request column index is out of range",
func() error {
return row.Column(10000, &struct{}{})
},
errColIdxOutOfRange(10000, &row),
},
{
"Cannot find the named column",
func() error {
return row.ColumnByName("string", &struct{}{})
},
errColNotFound("string"),
},
{
"Not enough arguments to call row.Columns()",
func() error {
return row.Columns(nil, nil)
},
errNumOfColValue(2, &row),
},
{
"Call ColumnByName on row with duplicated column names",
func() error {
var s string
r := &Row{
[]*sppb.StructType_Field{
{"Val", stringType()},
{"Val", stringType()},
},
[]*proto3.Value{stringProto("value1"), stringProto("value2")},
}
return r.ColumnByName("Val", &s)
},
errDupColName("Val"),
},
{
"Call ToStruct on row with duplicated column names",
func() error {
s := &struct {
Val string
}{}
r := &Row{
[]*sppb.StructType_Field{
{"Val", stringType()},
{"Val", stringType()},
},
[]*proto3.Value{stringProto("value1"), stringProto("value2")},
}
return r.ToStruct(s)
},
errDupSpannerField("Val", &sppb.StructType{
Fields: []*sppb.StructType_Field{
{"Val", stringType()},
{"Val", stringType()},
},
}),
},
{
"Call ToStruct on a row with unnamed field",
func() error {
s := &struct {
Val string
}{}
r := &Row{
[]*sppb.StructType_Field{
{"", stringType()},
},
[]*proto3.Value{stringProto("value1")},
}
return r.ToStruct(s)
},
errUnnamedField(&sppb.StructType{Fields: []*sppb.StructType_Field{{"", stringType()}}}, 0),
},
} {
if gotErr := test.f(); !reflect.DeepEqual(gotErr, test.wantErr) {
t.Errorf("%v: test.f() returns error %v, want %v", test.desc, gotErr, test.wantErr)
}
}
}
// Test decoding the row with row.ToStruct into an invalid destination.
func TestToStructInvalidDst(t *testing.T) {
for _, test := range []struct {
desc string
dst interface{}
wantErr error
}{
{
"Decode row as STRUCT into int32",
proto.Int(1),
errToStructArgType(proto.Int(1)),
},
{
"Decode row as STRUCT to nil Go struct",
(*struct{})(nil),
errNilDst((*struct{})(nil)),
},
{
"Decode row as STRUCT to Go struct with duplicated fields for the PK column",
&struct {
PK1 string `spanner:"STRING"`
PK2 string `spanner:"STRING"`
}{},
errNoOrDupGoField(&struct {
PK1 string `spanner:"STRING"`
PK2 string `spanner:"STRING"`
}{}, "STRING"),
},
{
"Decode row as STRUCT to Go struct with no field for the PK column",
&struct {
PK1 string `spanner:"_STRING"`
}{},
errNoOrDupGoField(&struct {
PK1 string `spanner:"_STRING"`
}{}, "STRING"),
},
{
"Decode row as STRUCT to Go struct with wrong type for the PK column",
&struct {
PK1 int64 `spanner:"STRING"`
}{},
errDecodeStructField(&sppb.StructType{Fields: row.fields}, "STRING",
errTypeMismatch(sppb.TypeCode_STRING, false, proto.Int64(0))),
},
} {
if gotErr := row.ToStruct(test.dst); !reflect.DeepEqual(gotErr, test.wantErr) {
t.Errorf("%v: decoding:\ngot %v\nwant %v", test.desc, gotErr, test.wantErr)
}
}
}
// Test decoding a broken row.
func TestBrokenRow(t *testing.T) {
for i, test := range []struct {
row *Row
dst interface{}
wantErr error
}{
{
// A row with no field.
&Row{
[]*sppb.StructType_Field{},
[]*proto3.Value{stringProto("value")},
},
&NullString{"value", true},
errFieldsMismatchVals(&Row{
[]*sppb.StructType_Field{},
[]*proto3.Value{stringProto("value")},
}),
},
{
// A row with nil field.
&Row{
[]*sppb.StructType_Field{nil},
[]*proto3.Value{stringProto("value")},
},
&NullString{"value", true},
errNilColType(0),
},
{
// Field is not nil, but its type is nil.
&Row{
[]*sppb.StructType_Field{
{
"Col0",
nil,
},
},
[]*proto3.Value{listProto(stringProto("value1"), stringProto("value2"))},
},
&[]NullString{},
errDecodeColumn(0, errNilSpannerType()),
},
{
// Field is not nil, field type is not nil, but it is an array and its array element type is nil.
&Row{
[]*sppb.StructType_Field{
{
"Col0",
&sppb.Type{
Code: sppb.TypeCode_ARRAY,
},
},
},
[]*proto3.Value{listProto(stringProto("value1"), stringProto("value2"))},
},
&[]NullString{},
errDecodeColumn(0, errNilArrElemType(&sppb.Type{Code: sppb.TypeCode_ARRAY})),
},
{
// Field specifies valid type, value is nil.
&Row{
[]*sppb.StructType_Field{
{
"Col0",
intType(),
},
},
[]*proto3.Value{nil},
},
&NullInt64{1, true},
errDecodeColumn(0, errNilSrc()),
},
{
// Field specifies INT64 type, value is having a nil Kind.
&Row{
[]*sppb.StructType_Field{
{
"Col0",
intType(),
},
},
[]*proto3.Value{{Kind: (*proto3.Value_StringValue)(nil)}},
},
&NullInt64{1, true},
errDecodeColumn(0, errSrcVal(&proto3.Value{Kind: (*proto3.Value_StringValue)(nil)}, "String")),
},
{
// Field specifies INT64 type, but value is for Number type.
&Row{
[]*sppb.StructType_Field{
{
"Col0",
intType(),
},
},
[]*proto3.Value{floatProto(1.0)},
},
&NullInt64{1, true},
errDecodeColumn(0, errSrcVal(floatProto(1.0), "String")),
},
{
// Field specifies INT64 type, but value is wrongly encoded.
&Row{
[]*sppb.StructType_Field{
{
"Col0",
intType(),
},
},
[]*proto3.Value{stringProto("&1")},
},
proto.Int64(0),
errDecodeColumn(0, errBadEncoding(stringProto("&1"), func() error {
_, err := strconv.ParseInt("&1", 10, 64)
return err
}())),
},
{
// Field specifies INT64 type, but value is wrongly encoded.
&Row{
[]*sppb.StructType_Field{
{
"Col0",
intType(),
},
},
[]*proto3.Value{stringProto("&1")},
},
&NullInt64{},
errDecodeColumn(0, errBadEncoding(stringProto("&1"), func() error {
_, err := strconv.ParseInt("&1", 10, 64)
return err
}())),
},
{
// Field specifies STRING type, but value is having a nil Kind.
&Row{
[]*sppb.StructType_Field{
{
"Col0",
stringType(),
},
},
[]*proto3.Value{{Kind: (*proto3.Value_StringValue)(nil)}},
},
&NullString{"value", true},
errDecodeColumn(0, errSrcVal(&proto3.Value{Kind: (*proto3.Value_StringValue)(nil)}, "String")),
},
{
// Field specifies STRING type, but value is for ARRAY type.
&Row{
[]*sppb.StructType_Field{
{
"Col0",
stringType(),
},
},
[]*proto3.Value{listProto(stringProto("value"))},
},
&NullString{"value", true},
errDecodeColumn(0, errSrcVal(listProto(stringProto("value")), "String")),
},
{
// Field specifies FLOAT64 type, value is having a nil Kind.
&Row{
[]*sppb.StructType_Field{
{
"Col0",
floatType(),
},
},
[]*proto3.Value{{Kind: (*proto3.Value_NumberValue)(nil)}},
},
&NullFloat64{1.0, true},
errDecodeColumn(0, errSrcVal(&proto3.Value{Kind: (*proto3.Value_NumberValue)(nil)}, "Number")),
},
{
// Field specifies FLOAT64 type, but value is for BOOL type.
&Row{
[]*sppb.StructType_Field{
{
"Col0",
floatType(),
},
},
[]*proto3.Value{boolProto(true)},
},
&NullFloat64{1.0, true},
errDecodeColumn(0, errSrcVal(boolProto(true), "Number")),
},
{
// Field specifies FLOAT64 type, but value is wrongly encoded.
&Row{
[]*sppb.StructType_Field{
{
"Col0",
floatType(),
},
},
[]*proto3.Value{stringProto("nan")},
},
&NullFloat64{},
errDecodeColumn(0, errUnexpectedNumStr("nan")),
},
{
// Field specifies FLOAT64 type, but value is wrongly encoded.
&Row{
[]*sppb.StructType_Field{
{
"Col0",
floatType(),
},
},
[]*proto3.Value{stringProto("nan")},
},
proto.Float64(0),
errDecodeColumn(0, errUnexpectedNumStr("nan")),
},
{
// Field specifies BYTES type, value is having a nil Kind.
&Row{
[]*sppb.StructType_Field{
{
"Col0",
bytesType(),
},
},
[]*proto3.Value{{Kind: (*proto3.Value_StringValue)(nil)}},
},
&[]byte{},
errDecodeColumn(0, errSrcVal(&proto3.Value{Kind: (*proto3.Value_StringValue)(nil)}, "String")),
},
{
// Field specifies BYTES type, but value is for BOOL type.
&Row{
[]*sppb.StructType_Field{
{
"Col0",
bytesType(),
},
},
[]*proto3.Value{boolProto(false)},
},
&[]byte{},
errDecodeColumn(0, errSrcVal(boolProto(false), "String")),
},
{
// Field specifies BYTES type, but value is wrongly encoded.
&Row{
[]*sppb.StructType_Field{
{
"Col0",
bytesType(),
},
},
[]*proto3.Value{stringProto("&&")},
},
&[]byte{},
errDecodeColumn(0, errBadEncoding(stringProto("&&"), func() error {
_, err := base64.StdEncoding.DecodeString("&&")
return err
}())),
},
{
// Field specifies BOOL type, value is having a nil Kind.
&Row{
[]*sppb.StructType_Field{
{
"Col0",
boolType(),
},
},
[]*proto3.Value{{Kind: (*proto3.Value_BoolValue)(nil)}},
},
&NullBool{false, true},
errDecodeColumn(0, errSrcVal(&proto3.Value{Kind: (*proto3.Value_BoolValue)(nil)}, "Bool")),
},
{
// Field specifies BOOL type, but value is for STRING type.
&Row{
[]*sppb.StructType_Field{
{
"Col0",
boolType(),
},
},
[]*proto3.Value{stringProto("false")},
},
&NullBool{false, true},
errDecodeColumn(0, errSrcVal(stringProto("false"), "Bool")),
},
{
// Field specifies TIMESTAMP type, value is having a nil Kind.
&Row{
[]*sppb.StructType_Field{
{
"Col0",
timeType(),
},
},
[]*proto3.Value{{Kind: (*proto3.Value_StringValue)(nil)}},
},
&NullTime{time.Now(), true},
errDecodeColumn(0, errSrcVal(&proto3.Value{Kind: (*proto3.Value_StringValue)(nil)}, "String")),
},
{
// Field specifies TIMESTAMP type, but value is for BOOL type.
&Row{
[]*sppb.StructType_Field{
{
"Col0",
timeType(),
},
},
[]*proto3.Value{boolProto(false)},
},
&NullTime{time.Now(), true},
errDecodeColumn(0, errSrcVal(boolProto(false), "String")),
},
{
// Field specifies TIMESTAMP type, but value is invalid timestamp.
&Row{
[]*sppb.StructType_Field{
{
"Col0",
timeType(),
},
},
[]*proto3.Value{stringProto("junk")},
},
&NullTime{time.Now(), true},
errDecodeColumn(0, errBadEncoding(stringProto("junk"), func() error {
_, err := time.Parse(time.RFC3339Nano, "junk")
return err
}())),
},
{
// Field specifies DATE type, value is having a nil Kind.
&Row{
[]*sppb.StructType_Field{
{
"Col0",
dateType(),
},
},
[]*proto3.Value{{Kind: (*proto3.Value_StringValue)(nil)}},
},
&NullDate{civil.Date{}, true},
errDecodeColumn(0, errSrcVal(&proto3.Value{Kind: (*proto3.Value_StringValue)(nil)}, "String")),
},
{
// Field specifies DATE type, but value is for BOOL type.
&Row{
[]*sppb.StructType_Field{
{
"Col0",
dateType(),
},
},
[]*proto3.Value{boolProto(false)},
},
&NullDate{civil.Date{}, true},
errDecodeColumn(0, errSrcVal(boolProto(false), "String")),
},
{
// Field specifies DATE type, but value is invalid timestamp.
&Row{
[]*sppb.StructType_Field{
{
"Col0",
dateType(),
},
},
[]*proto3.Value{stringProto("junk")},
},
&NullDate{civil.Date{}, true},
errDecodeColumn(0, errBadEncoding(stringProto("junk"), func() error {
_, err := civil.ParseDate("junk")
return err
}())),
},
{
// Field specifies ARRAY<INT64> type, value is having a nil Kind.
&Row{
[]*sppb.StructType_Field{
{
"Col0",
listType(intType()),
},
},
[]*proto3.Value{{Kind: (*proto3.Value_ListValue)(nil)}},
},
&[]NullInt64{},
errDecodeColumn(0, errSrcVal(&proto3.Value{Kind: (*proto3.Value_ListValue)(nil)}, "List")),
},
{
// Field specifies ARRAY<INT64> type, value is having a nil ListValue.
&Row{
[]*sppb.StructType_Field{
{
"Col0",
listType(intType()),
},
},
[]*proto3.Value{{Kind: &proto3.Value_ListValue{}}},
},
&[]NullInt64{},
errDecodeColumn(0, errNilListValue("INT64")),
},
{
// Field specifies ARRAY<INT64> type, but value is for BYTES type.
&Row{
[]*sppb.StructType_Field{
{
"Col0",
listType(intType()),
},
},
[]*proto3.Value{bytesProto([]byte("value"))},
},
&[]NullInt64{},
errDecodeColumn(0, errSrcVal(bytesProto([]byte("value")), "List")),
},
{
// Field specifies ARRAY<INT64> type, but value is for ARRAY<BOOL> type.
&Row{
[]*sppb.StructType_Field{
{
"Col0",
listType(intType()),
},
},
[]*proto3.Value{listProto(boolProto(true))},
},
&[]NullInt64{},
errDecodeColumn(0, errDecodeArrayElement(0, boolProto(true),
"INT64", errSrcVal(boolProto(true), "String"))),
},
{
// Field specifies ARRAY<STRING> type, value is having a nil Kind.
&Row{
[]*sppb.StructType_Field{
{
"Col0",
listType(stringType()),
},
},
[]*proto3.Value{{Kind: (*proto3.Value_ListValue)(nil)}},
},
&[]NullString{},
errDecodeColumn(0, errSrcVal(&proto3.Value{Kind: (*proto3.Value_ListValue)(nil)}, "List")),
},
{
// Field specifies ARRAY<STRING> type, value is having a nil ListValue.
&Row{
[]*sppb.StructType_Field{
{
"Col0",
listType(stringType()),
},
},
[]*proto3.Value{{Kind: &proto3.Value_ListValue{}}},
},
&[]NullString{},
errDecodeColumn(0, errNilListValue("STRING")),
},
{
// Field specifies ARRAY<STRING> type, but value is for BOOL type.
&Row{
[]*sppb.StructType_Field{
{
"Col0",
listType(stringType()),
},
},
[]*proto3.Value{boolProto(true)},
},
&[]NullString{},
errDecodeColumn(0, errSrcVal(boolProto(true), "List")),
},
{
// Field specifies ARRAY<STRING> type, but value is for ARRAY<BOOL> type.
&Row{
[]*sppb.StructType_Field{
{
"Col0",
listType(stringType()),
},
},
[]*proto3.Value{listProto(boolProto(true))},
},
&[]NullString{},
errDecodeColumn(0, errDecodeArrayElement(0, boolProto(true),
"STRING", errSrcVal(boolProto(true), "String"))),
},
{
// Field specifies ARRAY<FLOAT64> type, value is having a nil Kind.
&Row{
[]*sppb.StructType_Field{
{
"Col0",
listType(floatType()),
},
},
[]*proto3.Value{{Kind: (*proto3.Value_ListValue)(nil)}},
},
&[]NullFloat64{},
errDecodeColumn(0, errSrcVal(&proto3.Value{Kind: (*proto3.Value_ListValue)(nil)}, "List")),
},
{
// Field specifies ARRAY<FLOAT64> type, value is having a nil ListValue.
&Row{
[]*sppb.StructType_Field{
{
"Col0",
listType(floatType()),
},
},
[]*proto3.Value{{Kind: &proto3.Value_ListValue{}}},
},
&[]NullFloat64{},
errDecodeColumn(0, errNilListValue("FLOAT64")),
},
{
// Field specifies ARRAY<FLOAT64> type, but value is for STRING type.
&Row{
[]*sppb.StructType_Field{
{
"Col0",
listType(floatType()),
},
},
[]*proto3.Value{stringProto("value")},
},
&[]NullFloat64{},
errDecodeColumn(0, errSrcVal(stringProto("value"), "List")),
},
{
// Field specifies ARRAY<FLOAT64> type, but value is for ARRAY<BOOL> type.
&Row{
[]*sppb.StructType_Field{
{
"Col0",
listType(floatType()),
},
},
[]*proto3.Value{listProto(boolProto(true))},
},
&[]NullFloat64{},
errDecodeColumn(0, errDecodeArrayElement(0, boolProto(true),
"FLOAT64", errSrcVal(boolProto(true), "Number"))),
},
{
// Field specifies ARRAY<BYTES> type, value is having a nil Kind.
&Row{
[]*sppb.StructType_Field{
{
"Col0",
listType(bytesType()),
},
},
[]*proto3.Value{{Kind: (*proto3.Value_ListValue)(nil)}},
},
&[][]byte{},
errDecodeColumn(0, errSrcVal(&proto3.Value{Kind: (*proto3.Value_ListValue)(nil)}, "List")),
},
{
// Field specifies ARRAY<BYTES> type, value is having a nil ListValue.
&Row{
[]*sppb.StructType_Field{
{
"Col0",
listType(bytesType()),
},
},
[]*proto3.Value{{Kind: &proto3.Value_ListValue{}}},
},
&[][]byte{},
errDecodeColumn(0, errNilListValue("BYTES")),
},
{
// Field specifies ARRAY<BYTES> type, but value is for FLOAT64 type.
&Row{
[]*sppb.StructType_Field{
{
"Col0",
listType(bytesType()),
},
},
[]*proto3.Value{floatProto(1.0)},
},
&[][]byte{},
errDecodeColumn(0, errSrcVal(floatProto(1.0), "List")),
},
{
// Field specifies ARRAY<BYTES> type, but value is for ARRAY<FLOAT64> type.
&Row{
[]*sppb.StructType_Field{
{
"Col0",
listType(bytesType()),
},
},
[]*proto3.Value{listProto(floatProto(1.0))},
},
&[][]byte{},
errDecodeColumn(0, errDecodeArrayElement(0, floatProto(1.0),
"BYTES", errSrcVal(floatProto(1.0), "String"))),
},
{
// Field specifies ARRAY<BOOL> type, value is having a nil Kind.
&Row{
[]*sppb.StructType_Field{
{
"Col0",
listType(boolType()),
},
},
[]*proto3.Value{{Kind: (*proto3.Value_ListValue)(nil)}},
},
&[]NullBool{},
errDecodeColumn(0, errSrcVal(&proto3.Value{Kind: (*proto3.Value_ListValue)(nil)}, "List")),
},
{
// Field specifies ARRAY<BOOL> type, value is having a nil ListValue.
&Row{
[]*sppb.StructType_Field{
{
"Col0",
listType(boolType()),
},
},
[]*proto3.Value{{Kind: &proto3.Value_ListValue{}}},
},
&[]NullBool{},
errDecodeColumn(0, errNilListValue("BOOL")),
},
{
// Field specifies ARRAY<BOOL> type, but value is for FLOAT64 type.
&Row{
[]*sppb.StructType_Field{
{
"Col0",
listType(boolType()),
},
},
[]*proto3.Value{floatProto(1.0)},
},
&[]NullBool{},
errDecodeColumn(0, errSrcVal(floatProto(1.0), "List")),
},
{
// Field specifies ARRAY<BOOL> type, but value is for ARRAY<FLOAT64> type.
&Row{
[]*sppb.StructType_Field{
{
"Col0",
listType(boolType()),
},
},
[]*proto3.Value{listProto(floatProto(1.0))},
},
&[]NullBool{},
errDecodeColumn(0, errDecodeArrayElement(0, floatProto(1.0),
"BOOL", errSrcVal(floatProto(1.0), "Bool"))),
},
{
// Field specifies ARRAY<TIMESTAMP> type, value is having a nil Kind.
&Row{
[]*sppb.StructType_Field{
{
"Col0",
listType(timeType()),
},
},
[]*proto3.Value{{Kind: (*proto3.Value_ListValue)(nil)}},
},
&[]NullTime{},
errDecodeColumn(0, errSrcVal(&proto3.Value{Kind: (*proto3.Value_ListValue)(nil)}, "List")),
},
{
// Field specifies ARRAY<TIMESTAMP> type, value is having a nil ListValue.
&Row{
[]*sppb.StructType_Field{
{
"Col0",
listType(timeType()),
},
},
[]*proto3.Value{{Kind: &proto3.Value_ListValue{}}},
},
&[]NullTime{},
errDecodeColumn(0, errNilListValue("TIMESTAMP")),
},
{
// Field specifies ARRAY<TIMESTAMP> type, but value is for FLOAT64 type.
&Row{
[]*sppb.StructType_Field{
{
"Col0",
listType(timeType()),
},
},
[]*proto3.Value{floatProto(1.0)},
},
&[]NullTime{},
errDecodeColumn(0, errSrcVal(floatProto(1.0), "List")),
},
{
// Field specifies ARRAY<TIMESTAMP> type, but value is for ARRAY<FLOAT64> type.
&Row{
[]*sppb.StructType_Field{
{
"Col0",
listType(timeType()),
},
},
[]*proto3.Value{listProto(floatProto(1.0))},
},
&[]NullTime{},
errDecodeColumn(0, errDecodeArrayElement(0, floatProto(1.0),
"TIMESTAMP", errSrcVal(floatProto(1.0), "String"))),
},
{
// Field specifies ARRAY<DATE> type, value is having a nil Kind.
&Row{
[]*sppb.StructType_Field{
{
"Col0",
listType(dateType()),
},
},
[]*proto3.Value{{Kind: (*proto3.Value_ListValue)(nil)}},
},
&[]NullDate{},
errDecodeColumn(0, errSrcVal(&proto3.Value{Kind: (*proto3.Value_ListValue)(nil)}, "List")),
},
{
// Field specifies ARRAY<DATE> type, value is having a nil ListValue.
&Row{
[]*sppb.StructType_Field{
{
"Col0",
listType(dateType()),
},
},
[]*proto3.Value{{Kind: &proto3.Value_ListValue{}}},
},
&[]NullDate{},
errDecodeColumn(0, errNilListValue("DATE")),
},
{
// Field specifies ARRAY<DATE> type, but value is for FLOAT64 type.
&Row{
[]*sppb.StructType_Field{
{
"Col0",
listType(dateType()),
},
},
[]*proto3.Value{floatProto(1.0)},
},
&[]NullDate{},
errDecodeColumn(0, errSrcVal(floatProto(1.0), "List")),
},
{
// Field specifies ARRAY<DATE> type, but value is for ARRAY<FLOAT64> type.
&Row{
[]*sppb.StructType_Field{
{
"Col0",
listType(dateType()),
},
},
[]*proto3.Value{listProto(floatProto(1.0))},
},
&[]NullDate{},
errDecodeColumn(0, errDecodeArrayElement(0, floatProto(1.0),
"DATE", errSrcVal(floatProto(1.0), "String"))),
},
{
// Field specifies ARRAY<STRUCT> type, value is having a nil Kind.
&Row{
[]*sppb.StructType_Field{
{
"Col0",
listType(
structType(
mkField("Col1", intType()),
mkField("Col2", floatType()),
mkField("Col3", stringType()),
),
),
},
},
[]*proto3.Value{{Kind: (*proto3.Value_ListValue)(nil)}},
},
&[]*struct {
Col1 int64
Col2 float64
Col3 string
}{},
errDecodeColumn(0, errSrcVal(&proto3.Value{Kind: (*proto3.Value_ListValue)(nil)}, "List")),
},
{
// Field specifies ARRAY<STRUCT> type, value is having a nil ListValue.
&Row{
[]*sppb.StructType_Field{
{
"Col0",
listType(
structType(
mkField("Col1", intType()),
mkField("Col2", floatType()),
mkField("Col3", stringType()),
),
),
},
},
[]*proto3.Value{{Kind: &proto3.Value_ListValue{}}},
},
&[]*struct {
Col1 int64
Col2 float64
Col3 string
}{},
errDecodeColumn(0, errNilListValue("STRUCT")),
},
{
// Field specifies ARRAY<STRUCT> type, value is having a nil ListValue.
&Row{
[]*sppb.StructType_Field{
{
"Col0",
listType(
structType(
mkField("Col1", intType()),
mkField("Col2", floatType()),
mkField("Col3", stringType()),
),
),
},
},
[]*proto3.Value{{Kind: &proto3.Value_ListValue{}}},
},
&[]NullRow{},
errDecodeColumn(0, errNilListValue("STRUCT")),
},
{
// Field specifies ARRAY<STRUCT> type, value is for BYTES type.
&Row{
[]*sppb.StructType_Field{
{
"Col0",
listType(
structType(
mkField("Col1", intType()),
mkField("Col2", floatType()),
mkField("Col3", stringType()),
),
),
},
},
[]*proto3.Value{bytesProto([]byte("value"))},
},
&[]*struct {
Col1 int64
Col2 float64
Col3 string
}{},
errDecodeColumn(0, errSrcVal(bytesProto([]byte("value")), "List")),
},
{
// Field specifies ARRAY<STRUCT> type, value is for BYTES type.
&Row{
[]*sppb.StructType_Field{
{
"Col0",
listType(
structType(
mkField("Col1", intType()),
mkField("Col2", floatType()),
mkField("Col3", stringType()),
),
),
},
},
[]*proto3.Value{listProto(bytesProto([]byte("value")))},
},
&[]NullRow{},
errDecodeColumn(0, errNotStructElement(0, bytesProto([]byte("value")))),
},
{
// Field specifies ARRAY<STRUCT> type, value is for ARRAY<BYTES> type.
&Row{
[]*sppb.StructType_Field{
{
"Col0",
listType(
structType(
mkField("Col1", intType()),
mkField("Col2", floatType()),
mkField("Col3", stringType()),
),
),
},
},
[]*proto3.Value{listProto(bytesProto([]byte("value")))},
},
&[]*struct {
Col1 int64
Col2 float64
Col3 string
}{},
errDecodeColumn(0, errDecodeArrayElement(0, bytesProto([]byte("value")),
"STRUCT", errSrcVal(bytesProto([]byte("value")), "List"))),
},
{
// Field specifies ARRAY<STRUCT>, but is having nil StructType.
&Row{
[]*sppb.StructType_Field{
{
"Col0",
listType(
&sppb.Type{Code: sppb.TypeCode_STRUCT},
),
},
},
[]*proto3.Value{listProto(listProto(intProto(1), floatProto(2.0), stringProto("3")))},
},
&[]*struct {
Col1 int64
Col2 float64
Col3 string
}{},
errDecodeColumn(0, errDecodeArrayElement(0, listProto(intProto(1), floatProto(2.0), stringProto("3")),
"STRUCT", errNilSpannerStructType())),
},
{
// Field specifies ARRAY<STRUCT>, but the second struct value is for BOOL type instead of FLOAT64.
&Row{
[]*sppb.StructType_Field{
{
"Col0",
listType(
structType(
mkField("Col1", intType()),
mkField("Col2", floatType()),
mkField("Col3", stringType()),
),
),
},
},
[]*proto3.Value{listProto(listProto(intProto(1), boolProto(true), stringProto("3")))},
},
&[]*struct {
Col1 int64
Col2 float64
Col3 string
}{},
errDecodeColumn(
0,
errDecodeArrayElement(
0, listProto(intProto(1), boolProto(true), stringProto("3")), "STRUCT",
errDecodeStructField(
&sppb.StructType{
Fields: []*sppb.StructType_Field{
mkField("Col1", intType()),
mkField("Col2", floatType()),
mkField("Col3", stringType()),
},
},
"Col2",
errSrcVal(boolProto(true), "Number"),
),
),
),
},
} {
if gotErr := test.row.Column(0, test.dst); !reflect.DeepEqual(gotErr, test.wantErr) {
t.Errorf("%v: test.row.Column(0) got error %v, want %v", i, gotErr, test.wantErr)
}
if gotErr := test.row.ColumnByName("Col0", test.dst); !reflect.DeepEqual(gotErr, test.wantErr) {
t.Errorf("%v: test.row.ColumnByName(%q) got error %v, want %v", i, "Col0", gotErr, test.wantErr)
}
if gotErr := test.row.Columns(test.dst); !reflect.DeepEqual(gotErr, test.wantErr) {
t.Errorf("%v: test.row.Columns(%T) got error %v, want %v", i, test.dst, gotErr, test.wantErr)
}
}
}
// Test Row.ToStruct().
func TestToStruct(t *testing.T) {
s := []struct {
// STRING / STRING ARRAY
PrimaryKey string `spanner:"STRING"`
NullString NullString `spanner:"NULL_STRING"`
StringArray []NullString `spanner:"STRING_ARRAY"`
NullStringArray []NullString `spanner:"NULL_STRING_ARRAY"`
// BYTES / BYTES ARRAY
Bytes []byte `spanner:"BYTES"`
NullBytes []byte `spanner:"NULL_BYTES"`
BytesArray [][]byte `spanner:"BYTES_ARRAY"`
NullBytesArray [][]byte `spanner:"NULL_BYTES_ARRAY"`
// INT64 / INT64 ARRAY
Int64 int64 `spanner:"INT64"`
NullInt64 NullInt64 `spanner:"NULL_INT64"`
Int64Array []NullInt64 `spanner:"INT64_ARRAY"`
NullInt64Array []NullInt64 `spanner:"NULL_INT64_ARRAY"`
// BOOL / BOOL ARRAY
Bool bool `spanner:"BOOL"`
NullBool NullBool `spanner:"NULL_BOOL"`
BoolArray []NullBool `spanner:"BOOL_ARRAY"`
NullBoolArray []NullBool `spanner:"NULL_BOOL_ARRAY"`
// FLOAT64 / FLOAT64 ARRAY
Float64 float64 `spanner:"FLOAT64"`
NullFloat64 NullFloat64 `spanner:"NULL_FLOAT64"`
Float64Array []NullFloat64 `spanner:"FLOAT64_ARRAY"`
NullFloat64Array []NullFloat64 `spanner:"NULL_FLOAT64_ARRAY"`
// TIMESTAMP / TIMESTAMP ARRAY
Timestamp time.Time `spanner:"TIMESTAMP"`
NullTimestamp NullTime `spanner:"NULL_TIMESTAMP"`
TimestampArray []NullTime `spanner:"TIMESTAMP_ARRAY"`
NullTimestampArray []NullTime `spanner:"NULL_TIMESTAMP_ARRAY"`
// DATE / DATE ARRAY
Date civil.Date `spanner:"DATE"`
NullDate NullDate `spanner:"NULL_DATE"`
DateArray []NullDate `spanner:"DATE_ARRAY"`
NullDateArray []NullDate `spanner:"NULL_DATE_ARRAY"`
// STRUCT ARRAY
StructArray []*struct {
Col1 int64
Col2 float64
Col3 string
} `spanner:"STRUCT_ARRAY"`
NullStructArray []*struct {
Col1 int64
Col2 float64
Col3 string
} `spanner:"NULL_STRUCT_ARRAY"`
}{
{}, // got
{
// STRING / STRING ARRAY
"value",
NullString{},
[]NullString{{"value1", true}, {}, {"value3", true}},
[]NullString(nil),
// BYTES / BYTES ARRAY
[]byte("value"),
[]byte(nil),
[][]byte{[]byte("value1"), nil, []byte("value3")},
[][]byte(nil),
// INT64 / INT64 ARRAY
int64(17),
NullInt64{},
[]NullInt64{{int64(1), true}, {int64(2), true}, {}},
[]NullInt64(nil),
// BOOL / BOOL ARRAY
true,
NullBool{},
[]NullBool{{}, {true, true}, {false, true}},
[]NullBool(nil),
// FLOAT64 / FLOAT64 ARRAY
1.7,
NullFloat64{},
[]NullFloat64{{}, {}, {1.7, true}},
[]NullFloat64(nil),
// TIMESTAMP / TIMESTAMP ARRAY
tm,
NullTime{},
[]NullTime{{}, {tm, true}},
[]NullTime(nil),
// DATE / DATE ARRAY
dt,
NullDate{},
[]NullDate{{}, {dt, true}},
[]NullDate(nil),
// STRUCT ARRAY
[]*struct {
Col1 int64
Col2 float64
Col3 string
}{
nil,
&struct {
Col1 int64
Col2 float64
Col3 string
}{3, 33.3, "three"},
nil,
},
[]*struct {
Col1 int64
Col2 float64
Col3 string
}(nil),
}, // want
}
err := row.ToStruct(&s[0])
if err != nil {
t.Errorf("row.ToStruct() returns error: %v, want nil", err)
}
if !reflect.DeepEqual(s[0], s[1]) {
t.Errorf("row.ToStruct() fetches struct %v, want %v", s[0], s[1])
}
}
// Test helpers for getting column names.
func TestColumnNameAndIndex(t *testing.T) {
// Test Row.Size().
if rs := row.Size(); rs != len(row.fields) {
t.Errorf("row.Size() returns %v, want %v", rs, len(row.fields))
}
// Test Row.Size() on empty Row.
if rs := (&Row{}).Size(); rs != 0 {
t.Errorf("empty_row.Size() returns %v, want %v", rs, 0)
}
// Test Row.ColumnName()
for i, col := range row.fields {
if cn := row.ColumnName(i); cn != col.Name {
t.Errorf("row.ColumnName(%v) returns %q, want %q", i, cn, col.Name)
}
goti, err := row.ColumnIndex(col.Name)
if err != nil {
t.Errorf("ColumnIndex(%q) error %v", col.Name, err)
continue
}
if goti != i {
t.Errorf("ColumnIndex(%q) = %d, want %d", col.Name, goti, i)
}
}
// Test Row.ColumnName on empty Row.
if cn := (&Row{}).ColumnName(0); cn != "" {
t.Errorf("empty_row.ColumnName(%v) returns %q, want %q", 0, cn, "")
}
// Test Row.ColumnIndex on empty Row.
if _, err := (&Row{}).ColumnIndex(""); err == nil {
t.Error("empty_row.ColumnIndex returns nil, want error")
}
}
func TestNewRow(t *testing.T) {
for _, test := range []struct {
names []string
values []interface{}
want *Row
wantErr error
}{
{
want: &Row{fields: []*sppb.StructType_Field{}, vals: []*proto3.Value{}},
},
{
names: []string{},
values: []interface{}{},
want: &Row{fields: []*sppb.StructType_Field{}, vals: []*proto3.Value{}},
},
{
names: []string{"a", "b"},
values: []interface{}{},
want: nil,
wantErr: errNamesValuesMismatch([]string{"a", "b"}, []interface{}{}),
},
{
names: []string{"a", "b", "c"},
values: []interface{}{5, "abc", GenericColumnValue{listType(intType()), listProto(intProto(91), nullProto(), intProto(87))}},
want: &Row{
[]*sppb.StructType_Field{
{"a", intType()},
{"b", stringType()},
{"c", listType(intType())},
},
[]*proto3.Value{
intProto(5),
stringProto("abc"),
listProto(intProto(91), nullProto(), intProto(87)),
},
},
},
} {
got, err := NewRow(test.names, test.values)
if !reflect.DeepEqual(err, test.wantErr) {
t.Errorf("NewRow(%v,%v).err = %s, want %s", test.names, test.values, err, test.wantErr)
continue
}
if !reflect.DeepEqual(got, test.want) {
t.Errorf("NewRow(%v,%v) = %s, want %s", test.names, test.values, got, test.want)
continue
}
}
}