|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571 |
- /*
- 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)
- }
- }
- }
|