blob: afbcb326ffd4134b2bfa7788ddf0e4f14dd0c8cf [file] [log] [blame]
/*
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 (
"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{
{
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{
{
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{
{
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{
{
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{
{opDelete, "t_test", Key{"bar"}, nil, nil},
{opInsertOrUpdate, "t_test", nil, []string{"key", "val"}, []interface{}{"foo", 1}},
},
[]*sppb.Mutation{
{
Operation: &sppb.Mutation_Delete_{
Delete: &sppb.Mutation_Delete{
Table: "t_test",
KeySet: &sppb.KeySet{
Keys: []*proto3.ListValue{listValueProto(stringProto("bar"))},
},
},
},
},
{
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{
{opDelete, "t_test", Key{"bar"}, nil, nil},
{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)
}
}
}