572 lines
16 KiB
Go
572 lines
16 KiB
Go
/*
|
|
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 (
|
|
"sort"
|
|
"strings"
|
|
"testing"
|
|
|
|
proto3 "github.com/golang/protobuf/ptypes/struct"
|
|
|
|
sppb "google.golang.org/genproto/googleapis/spanner/v1"
|
|
)
|
|
|
|
// keysetProto returns protobuf encoding of valid spanner.KeySet.
|
|
func keysetProto(t *testing.T, ks KeySet) *sppb.KeySet {
|
|
k, err := ks.keySetProto()
|
|
if err != nil {
|
|
t.Fatalf("cannot convert keyset %v to protobuf: %v", ks, err)
|
|
}
|
|
return k
|
|
}
|
|
|
|
// Test encoding from spanner.Mutation to protobuf.
|
|
func TestMutationToProto(t *testing.T) {
|
|
for i, test := range []struct {
|
|
m *Mutation
|
|
want *sppb.Mutation
|
|
}{
|
|
// Delete Mutation
|
|
{
|
|
&Mutation{opDelete, "t_foo", Key{"foo"}, nil, nil},
|
|
&sppb.Mutation{
|
|
Operation: &sppb.Mutation_Delete_{
|
|
Delete: &sppb.Mutation_Delete{
|
|
Table: "t_foo",
|
|
KeySet: keysetProto(t, Key{"foo"}),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
// Insert Mutation
|
|
{
|
|
&Mutation{opInsert, "t_foo", KeySets(), []string{"col1", "col2"}, []interface{}{int64(1), int64(2)}},
|
|
&sppb.Mutation{
|
|
Operation: &sppb.Mutation_Insert{
|
|
Insert: &sppb.Mutation_Write{
|
|
Table: "t_foo",
|
|
Columns: []string{"col1", "col2"},
|
|
Values: []*proto3.ListValue{
|
|
&proto3.ListValue{
|
|
Values: []*proto3.Value{intProto(1), intProto(2)},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
// InsertOrUpdate Mutation
|
|
{
|
|
&Mutation{opInsertOrUpdate, "t_foo", KeySets(), []string{"col1", "col2"}, []interface{}{1.0, 2.0}},
|
|
&sppb.Mutation{
|
|
Operation: &sppb.Mutation_InsertOrUpdate{
|
|
InsertOrUpdate: &sppb.Mutation_Write{
|
|
Table: "t_foo",
|
|
Columns: []string{"col1", "col2"},
|
|
Values: []*proto3.ListValue{
|
|
&proto3.ListValue{
|
|
Values: []*proto3.Value{floatProto(1.0), floatProto(2.0)},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
// Replace Mutation
|
|
{
|
|
&Mutation{opReplace, "t_foo", KeySets(), []string{"col1", "col2"}, []interface{}{"one", 2.0}},
|
|
&sppb.Mutation{
|
|
Operation: &sppb.Mutation_Replace{
|
|
Replace: &sppb.Mutation_Write{
|
|
Table: "t_foo",
|
|
Columns: []string{"col1", "col2"},
|
|
Values: []*proto3.ListValue{
|
|
&proto3.ListValue{
|
|
Values: []*proto3.Value{stringProto("one"), floatProto(2.0)},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
// Update Mutation
|
|
{
|
|
&Mutation{opUpdate, "t_foo", KeySets(), []string{"col1", "col2"}, []interface{}{"one", []byte(nil)}},
|
|
&sppb.Mutation{
|
|
Operation: &sppb.Mutation_Update{
|
|
Update: &sppb.Mutation_Write{
|
|
Table: "t_foo",
|
|
Columns: []string{"col1", "col2"},
|
|
Values: []*proto3.ListValue{
|
|
&proto3.ListValue{
|
|
Values: []*proto3.Value{stringProto("one"), nullProto()},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
} {
|
|
if got, err := test.m.proto(); err != nil || !testEqual(got, test.want) {
|
|
t.Errorf("%d: (%#v).proto() = (%v, %v), want (%v, nil)", i, test.m, got, err, test.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// mutationColumnSorter implements sort.Interface for sorting column-value pairs in a Mutation by column names.
|
|
type mutationColumnSorter struct {
|
|
Mutation
|
|
}
|
|
|
|
// newMutationColumnSorter creates new instance of mutationColumnSorter by duplicating the input Mutation so that
|
|
// sorting won't change the input Mutation.
|
|
func newMutationColumnSorter(m *Mutation) *mutationColumnSorter {
|
|
return &mutationColumnSorter{
|
|
Mutation{
|
|
m.op,
|
|
m.table,
|
|
m.keySet,
|
|
append([]string(nil), m.columns...),
|
|
append([]interface{}(nil), m.values...),
|
|
},
|
|
}
|
|
}
|
|
|
|
// Len implements sort.Interface.Len.
|
|
func (ms *mutationColumnSorter) Len() int {
|
|
return len(ms.columns)
|
|
}
|
|
|
|
// Swap implements sort.Interface.Swap.
|
|
func (ms *mutationColumnSorter) Swap(i, j int) {
|
|
ms.columns[i], ms.columns[j] = ms.columns[j], ms.columns[i]
|
|
ms.values[i], ms.values[j] = ms.values[j], ms.values[i]
|
|
}
|
|
|
|
// Less implements sort.Interface.Less.
|
|
func (ms *mutationColumnSorter) Less(i, j int) bool {
|
|
return strings.Compare(ms.columns[i], ms.columns[j]) < 0
|
|
}
|
|
|
|
// mutationEqual returns true if two mutations in question are equal
|
|
// to each other.
|
|
func mutationEqual(t *testing.T, m1, m2 Mutation) bool {
|
|
// Two mutations are considered to be equal even if their column values have different
|
|
// orders.
|
|
ms1 := newMutationColumnSorter(&m1)
|
|
ms2 := newMutationColumnSorter(&m2)
|
|
sort.Sort(ms1)
|
|
sort.Sort(ms2)
|
|
return testEqual(ms1, ms2)
|
|
}
|
|
|
|
// Test helper functions which help to generate spanner.Mutation.
|
|
func TestMutationHelpers(t *testing.T) {
|
|
for _, test := range []struct {
|
|
m string
|
|
got *Mutation
|
|
want *Mutation
|
|
}{
|
|
{
|
|
"Insert",
|
|
Insert("t_foo", []string{"col1", "col2"}, []interface{}{int64(1), int64(2)}),
|
|
&Mutation{opInsert, "t_foo", nil, []string{"col1", "col2"}, []interface{}{int64(1), int64(2)}},
|
|
},
|
|
{
|
|
"InsertMap",
|
|
InsertMap("t_foo", map[string]interface{}{"col1": int64(1), "col2": int64(2)}),
|
|
&Mutation{opInsert, "t_foo", nil, []string{"col1", "col2"}, []interface{}{int64(1), int64(2)}},
|
|
},
|
|
{
|
|
"InsertStruct",
|
|
func() *Mutation {
|
|
m, err := InsertStruct(
|
|
"t_foo",
|
|
struct {
|
|
notCol bool
|
|
Col1 int64 `spanner:"col1"`
|
|
Col2 int64 `spanner:"col2"`
|
|
}{false, int64(1), int64(2)},
|
|
)
|
|
if err != nil {
|
|
t.Errorf("cannot convert struct into mutation: %v", err)
|
|
}
|
|
return m
|
|
}(),
|
|
&Mutation{opInsert, "t_foo", nil, []string{"col1", "col2"}, []interface{}{int64(1), int64(2)}},
|
|
},
|
|
{
|
|
"Update",
|
|
Update("t_foo", []string{"col1", "col2"}, []interface{}{"one", []byte(nil)}),
|
|
&Mutation{opUpdate, "t_foo", nil, []string{"col1", "col2"}, []interface{}{"one", []byte(nil)}},
|
|
},
|
|
{
|
|
"UpdateMap",
|
|
UpdateMap("t_foo", map[string]interface{}{"col1": "one", "col2": []byte(nil)}),
|
|
&Mutation{opUpdate, "t_foo", nil, []string{"col1", "col2"}, []interface{}{"one", []byte(nil)}},
|
|
},
|
|
{
|
|
"UpdateStruct",
|
|
func() *Mutation {
|
|
m, err := UpdateStruct(
|
|
"t_foo",
|
|
struct {
|
|
Col1 string `spanner:"col1"`
|
|
notCol int
|
|
Col2 []byte `spanner:"col2"`
|
|
}{"one", 1, nil},
|
|
)
|
|
if err != nil {
|
|
t.Errorf("cannot convert struct into mutation: %v", err)
|
|
}
|
|
return m
|
|
}(),
|
|
&Mutation{opUpdate, "t_foo", nil, []string{"col1", "col2"}, []interface{}{"one", []byte(nil)}},
|
|
},
|
|
{
|
|
"InsertOrUpdate",
|
|
InsertOrUpdate("t_foo", []string{"col1", "col2"}, []interface{}{1.0, 2.0}),
|
|
&Mutation{opInsertOrUpdate, "t_foo", nil, []string{"col1", "col2"}, []interface{}{1.0, 2.0}},
|
|
},
|
|
{
|
|
"InsertOrUpdateMap",
|
|
InsertOrUpdateMap("t_foo", map[string]interface{}{"col1": 1.0, "col2": 2.0}),
|
|
&Mutation{opInsertOrUpdate, "t_foo", nil, []string{"col1", "col2"}, []interface{}{1.0, 2.0}},
|
|
},
|
|
{
|
|
"InsertOrUpdateStruct",
|
|
func() *Mutation {
|
|
m, err := InsertOrUpdateStruct(
|
|
"t_foo",
|
|
struct {
|
|
Col1 float64 `spanner:"col1"`
|
|
Col2 float64 `spanner:"col2"`
|
|
notCol float64
|
|
}{1.0, 2.0, 3.0},
|
|
)
|
|
if err != nil {
|
|
t.Errorf("cannot convert struct into mutation: %v", err)
|
|
}
|
|
return m
|
|
}(),
|
|
&Mutation{opInsertOrUpdate, "t_foo", nil, []string{"col1", "col2"}, []interface{}{1.0, 2.0}},
|
|
},
|
|
{
|
|
"Replace",
|
|
Replace("t_foo", []string{"col1", "col2"}, []interface{}{"one", 2.0}),
|
|
&Mutation{opReplace, "t_foo", nil, []string{"col1", "col2"}, []interface{}{"one", 2.0}},
|
|
},
|
|
{
|
|
"ReplaceMap",
|
|
ReplaceMap("t_foo", map[string]interface{}{"col1": "one", "col2": 2.0}),
|
|
&Mutation{opReplace, "t_foo", nil, []string{"col1", "col2"}, []interface{}{"one", 2.0}},
|
|
},
|
|
{
|
|
"ReplaceStruct",
|
|
func() *Mutation {
|
|
m, err := ReplaceStruct(
|
|
"t_foo",
|
|
struct {
|
|
Col1 string `spanner:"col1"`
|
|
Col2 float64 `spanner:"col2"`
|
|
notCol string
|
|
}{"one", 2.0, "foo"},
|
|
)
|
|
if err != nil {
|
|
t.Errorf("cannot convert struct into mutation: %v", err)
|
|
}
|
|
return m
|
|
}(),
|
|
&Mutation{opReplace, "t_foo", nil, []string{"col1", "col2"}, []interface{}{"one", 2.0}},
|
|
},
|
|
{
|
|
"Delete",
|
|
Delete("t_foo", Key{"foo"}),
|
|
&Mutation{opDelete, "t_foo", Key{"foo"}, nil, nil},
|
|
},
|
|
{
|
|
"DeleteRange",
|
|
Delete("t_foo", KeyRange{Key{"bar"}, Key{"foo"}, ClosedClosed}),
|
|
&Mutation{opDelete, "t_foo", KeyRange{Key{"bar"}, Key{"foo"}, ClosedClosed}, nil, nil},
|
|
},
|
|
} {
|
|
if !mutationEqual(t, *test.got, *test.want) {
|
|
t.Errorf("%v: got Mutation %v, want %v", test.m, test.got, test.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test encoding non-struct types by using *Struct helpers.
|
|
func TestBadStructs(t *testing.T) {
|
|
val := "i_am_not_a_struct"
|
|
wantErr := errNotStruct(val)
|
|
if _, gotErr := InsertStruct("t_test", val); !testEqual(gotErr, wantErr) {
|
|
t.Errorf("InsertStruct(%q) returns error %v, want %v", val, gotErr, wantErr)
|
|
}
|
|
if _, gotErr := InsertOrUpdateStruct("t_test", val); !testEqual(gotErr, wantErr) {
|
|
t.Errorf("InsertOrUpdateStruct(%q) returns error %v, want %v", val, gotErr, wantErr)
|
|
}
|
|
if _, gotErr := UpdateStruct("t_test", val); !testEqual(gotErr, wantErr) {
|
|
t.Errorf("UpdateStruct(%q) returns error %v, want %v", val, gotErr, wantErr)
|
|
}
|
|
if _, gotErr := ReplaceStruct("t_test", val); !testEqual(gotErr, wantErr) {
|
|
t.Errorf("ReplaceStruct(%q) returns error %v, want %v", val, gotErr, wantErr)
|
|
}
|
|
}
|
|
|
|
func TestStructToMutationParams(t *testing.T) {
|
|
// Tests cases not covered elsewhere.
|
|
type S struct{ F interface{} }
|
|
|
|
for _, test := range []struct {
|
|
in interface{}
|
|
wantCols []string
|
|
wantVals []interface{}
|
|
wantErr error
|
|
}{
|
|
{nil, nil, nil, errNotStruct(nil)},
|
|
{3, nil, nil, errNotStruct(3)},
|
|
{(*S)(nil), nil, nil, nil},
|
|
{&S{F: 1}, []string{"F"}, []interface{}{1}, nil},
|
|
{&S{F: CommitTimestamp}, []string{"F"}, []interface{}{CommitTimestamp}, nil},
|
|
} {
|
|
gotCols, gotVals, gotErr := structToMutationParams(test.in)
|
|
if !testEqual(gotCols, test.wantCols) {
|
|
t.Errorf("%#v: got cols %v, want %v", test.in, gotCols, test.wantCols)
|
|
}
|
|
if !testEqual(gotVals, test.wantVals) {
|
|
t.Errorf("%#v: got vals %v, want %v", test.in, gotVals, test.wantVals)
|
|
}
|
|
if !testEqual(gotErr, test.wantErr) {
|
|
t.Errorf("%#v: got err %v, want %v", test.in, gotErr, test.wantErr)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test encoding Mutation into proto.
|
|
func TestEncodeMutation(t *testing.T) {
|
|
for _, test := range []struct {
|
|
name string
|
|
mutation Mutation
|
|
wantProto *sppb.Mutation
|
|
wantErr error
|
|
}{
|
|
{
|
|
"OpDelete",
|
|
Mutation{opDelete, "t_test", Key{1}, nil, nil},
|
|
&sppb.Mutation{
|
|
Operation: &sppb.Mutation_Delete_{
|
|
Delete: &sppb.Mutation_Delete{
|
|
Table: "t_test",
|
|
KeySet: &sppb.KeySet{
|
|
Keys: []*proto3.ListValue{listValueProto(intProto(1))},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
nil,
|
|
},
|
|
{
|
|
"OpDelete - Key error",
|
|
Mutation{opDelete, "t_test", Key{struct{}{}}, nil, nil},
|
|
&sppb.Mutation{
|
|
Operation: &sppb.Mutation_Delete_{
|
|
Delete: &sppb.Mutation_Delete{
|
|
Table: "t_test",
|
|
KeySet: &sppb.KeySet{},
|
|
},
|
|
},
|
|
},
|
|
errInvdKeyPartType(struct{}{}),
|
|
},
|
|
{
|
|
"OpInsert",
|
|
Mutation{opInsert, "t_test", nil, []string{"key", "val"}, []interface{}{"foo", 1}},
|
|
&sppb.Mutation{
|
|
Operation: &sppb.Mutation_Insert{
|
|
Insert: &sppb.Mutation_Write{
|
|
Table: "t_test",
|
|
Columns: []string{"key", "val"},
|
|
Values: []*proto3.ListValue{listValueProto(stringProto("foo"), intProto(1))},
|
|
},
|
|
},
|
|
},
|
|
nil,
|
|
},
|
|
{
|
|
"OpInsert - Value Type Error",
|
|
Mutation{opInsert, "t_test", nil, []string{"key", "val"}, []interface{}{struct{}{}, 1}},
|
|
&sppb.Mutation{
|
|
Operation: &sppb.Mutation_Insert{
|
|
Insert: &sppb.Mutation_Write{},
|
|
},
|
|
},
|
|
errEncoderUnsupportedType(struct{}{}),
|
|
},
|
|
{
|
|
"OpInsertOrUpdate",
|
|
Mutation{opInsertOrUpdate, "t_test", nil, []string{"key", "val"}, []interface{}{"foo", 1}},
|
|
&sppb.Mutation{
|
|
Operation: &sppb.Mutation_InsertOrUpdate{
|
|
InsertOrUpdate: &sppb.Mutation_Write{
|
|
Table: "t_test",
|
|
Columns: []string{"key", "val"},
|
|
Values: []*proto3.ListValue{listValueProto(stringProto("foo"), intProto(1))},
|
|
},
|
|
},
|
|
},
|
|
nil,
|
|
},
|
|
{
|
|
"OpInsertOrUpdate - Value Type Error",
|
|
Mutation{opInsertOrUpdate, "t_test", nil, []string{"key", "val"}, []interface{}{struct{}{}, 1}},
|
|
&sppb.Mutation{
|
|
Operation: &sppb.Mutation_InsertOrUpdate{
|
|
InsertOrUpdate: &sppb.Mutation_Write{},
|
|
},
|
|
},
|
|
errEncoderUnsupportedType(struct{}{}),
|
|
},
|
|
{
|
|
"OpReplace",
|
|
Mutation{opReplace, "t_test", nil, []string{"key", "val"}, []interface{}{"foo", 1}},
|
|
&sppb.Mutation{
|
|
Operation: &sppb.Mutation_Replace{
|
|
Replace: &sppb.Mutation_Write{
|
|
Table: "t_test",
|
|
Columns: []string{"key", "val"},
|
|
Values: []*proto3.ListValue{listValueProto(stringProto("foo"), intProto(1))},
|
|
},
|
|
},
|
|
},
|
|
nil,
|
|
},
|
|
{
|
|
"OpReplace - Value Type Error",
|
|
Mutation{opReplace, "t_test", nil, []string{"key", "val"}, []interface{}{struct{}{}, 1}},
|
|
&sppb.Mutation{
|
|
Operation: &sppb.Mutation_Replace{
|
|
Replace: &sppb.Mutation_Write{},
|
|
},
|
|
},
|
|
errEncoderUnsupportedType(struct{}{}),
|
|
},
|
|
{
|
|
"OpUpdate",
|
|
Mutation{opUpdate, "t_test", nil, []string{"key", "val"}, []interface{}{"foo", 1}},
|
|
&sppb.Mutation{
|
|
Operation: &sppb.Mutation_Update{
|
|
Update: &sppb.Mutation_Write{
|
|
Table: "t_test",
|
|
Columns: []string{"key", "val"},
|
|
Values: []*proto3.ListValue{listValueProto(stringProto("foo"), intProto(1))},
|
|
},
|
|
},
|
|
},
|
|
nil,
|
|
},
|
|
{
|
|
"OpUpdate - Value Type Error",
|
|
Mutation{opUpdate, "t_test", nil, []string{"key", "val"}, []interface{}{struct{}{}, 1}},
|
|
&sppb.Mutation{
|
|
Operation: &sppb.Mutation_Update{
|
|
Update: &sppb.Mutation_Write{},
|
|
},
|
|
},
|
|
errEncoderUnsupportedType(struct{}{}),
|
|
},
|
|
{
|
|
"OpKnown - Unknown Mutation Operation Code",
|
|
Mutation{op(100), "t_test", nil, nil, nil},
|
|
&sppb.Mutation{},
|
|
errInvdMutationOp(Mutation{op(100), "t_test", nil, nil, nil}),
|
|
},
|
|
} {
|
|
gotProto, gotErr := test.mutation.proto()
|
|
if gotErr != nil {
|
|
if !testEqual(gotErr, test.wantErr) {
|
|
t.Errorf("%s: %v.proto() returns error %v, want %v", test.name, test.mutation, gotErr, test.wantErr)
|
|
}
|
|
continue
|
|
}
|
|
if !testEqual(gotProto, test.wantProto) {
|
|
t.Errorf("%s: %v.proto() = (%v, nil), want (%v, nil)", test.name, test.mutation, gotProto, test.wantProto)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test Encoding an array of mutations.
|
|
func TestEncodeMutationArray(t *testing.T) {
|
|
for _, test := range []struct {
|
|
name string
|
|
ms []*Mutation
|
|
want []*sppb.Mutation
|
|
wantErr error
|
|
}{
|
|
{
|
|
"Multiple Mutations",
|
|
[]*Mutation{
|
|
&Mutation{opDelete, "t_test", Key{"bar"}, nil, nil},
|
|
&Mutation{opInsertOrUpdate, "t_test", nil, []string{"key", "val"}, []interface{}{"foo", 1}},
|
|
},
|
|
[]*sppb.Mutation{
|
|
&sppb.Mutation{
|
|
Operation: &sppb.Mutation_Delete_{
|
|
Delete: &sppb.Mutation_Delete{
|
|
Table: "t_test",
|
|
KeySet: &sppb.KeySet{
|
|
Keys: []*proto3.ListValue{listValueProto(stringProto("bar"))},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
&sppb.Mutation{
|
|
Operation: &sppb.Mutation_InsertOrUpdate{
|
|
InsertOrUpdate: &sppb.Mutation_Write{
|
|
Table: "t_test",
|
|
Columns: []string{"key", "val"},
|
|
Values: []*proto3.ListValue{listValueProto(stringProto("foo"), intProto(1))},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
nil,
|
|
},
|
|
{
|
|
"Multiple Mutations - Bad Mutation",
|
|
[]*Mutation{
|
|
&Mutation{opDelete, "t_test", Key{"bar"}, nil, nil},
|
|
&Mutation{opInsertOrUpdate, "t_test", nil, []string{"key", "val"}, []interface{}{"foo", struct{}{}}},
|
|
},
|
|
[]*sppb.Mutation{},
|
|
errEncoderUnsupportedType(struct{}{}),
|
|
},
|
|
} {
|
|
gotProto, gotErr := mutationsProto(test.ms)
|
|
if gotErr != nil {
|
|
if !testEqual(gotErr, test.wantErr) {
|
|
t.Errorf("%v: mutationsProto(%v) returns error %v, want %v", test.name, test.ms, gotErr, test.wantErr)
|
|
}
|
|
continue
|
|
}
|
|
if !testEqual(gotProto, test.want) {
|
|
t.Errorf("%v: mutationsProto(%v) = (%v, nil), want (%v, nil)", test.name, test.ms, gotProto, test.want)
|
|
}
|
|
}
|
|
}
|