1/* 2Copyright 2017 Google LLC 3 4Licensed under the Apache License, Version 2.0 (the "License"); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an "AS IS" BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15*/ 16 17package spanner 18 19import ( 20 "sort" 21 "strings" 22 "testing" 23 24 proto3 "github.com/golang/protobuf/ptypes/struct" 25 sppb "google.golang.org/genproto/googleapis/spanner/v1" 26) 27 28// keysetProto returns protobuf encoding of valid spanner.KeySet. 29func keysetProto(t *testing.T, ks KeySet) *sppb.KeySet { 30 k, err := ks.keySetProto() 31 if err != nil { 32 t.Fatalf("cannot convert keyset %v to protobuf: %v", ks, err) 33 } 34 return k 35} 36 37// Test encoding from spanner.Mutation to protobuf. 38func TestMutationToProto(t *testing.T) { 39 for i, test := range []struct { 40 m *Mutation 41 want *sppb.Mutation 42 }{ 43 // Delete Mutation 44 { 45 &Mutation{opDelete, "t_foo", Key{"foo"}, nil, nil}, 46 &sppb.Mutation{ 47 Operation: &sppb.Mutation_Delete_{ 48 Delete: &sppb.Mutation_Delete{ 49 Table: "t_foo", 50 KeySet: keysetProto(t, Key{"foo"}), 51 }, 52 }, 53 }, 54 }, 55 // Insert Mutation 56 { 57 &Mutation{opInsert, "t_foo", KeySets(), []string{"col1", "col2"}, []interface{}{int64(1), int64(2)}}, 58 &sppb.Mutation{ 59 Operation: &sppb.Mutation_Insert{ 60 Insert: &sppb.Mutation_Write{ 61 Table: "t_foo", 62 Columns: []string{"col1", "col2"}, 63 Values: []*proto3.ListValue{ 64 { 65 Values: []*proto3.Value{intProto(1), intProto(2)}, 66 }, 67 }, 68 }, 69 }, 70 }, 71 }, 72 // InsertOrUpdate Mutation 73 { 74 &Mutation{opInsertOrUpdate, "t_foo", KeySets(), []string{"col1", "col2"}, []interface{}{1.0, 2.0}}, 75 &sppb.Mutation{ 76 Operation: &sppb.Mutation_InsertOrUpdate{ 77 InsertOrUpdate: &sppb.Mutation_Write{ 78 Table: "t_foo", 79 Columns: []string{"col1", "col2"}, 80 Values: []*proto3.ListValue{ 81 { 82 Values: []*proto3.Value{floatProto(1.0), floatProto(2.0)}, 83 }, 84 }, 85 }, 86 }, 87 }, 88 }, 89 // Replace Mutation 90 { 91 &Mutation{opReplace, "t_foo", KeySets(), []string{"col1", "col2"}, []interface{}{"one", 2.0}}, 92 &sppb.Mutation{ 93 Operation: &sppb.Mutation_Replace{ 94 Replace: &sppb.Mutation_Write{ 95 Table: "t_foo", 96 Columns: []string{"col1", "col2"}, 97 Values: []*proto3.ListValue{ 98 { 99 Values: []*proto3.Value{stringProto("one"), floatProto(2.0)}, 100 }, 101 }, 102 }, 103 }, 104 }, 105 }, 106 // Update Mutation 107 { 108 &Mutation{opUpdate, "t_foo", KeySets(), []string{"col1", "col2"}, []interface{}{"one", []byte(nil)}}, 109 &sppb.Mutation{ 110 Operation: &sppb.Mutation_Update{ 111 Update: &sppb.Mutation_Write{ 112 Table: "t_foo", 113 Columns: []string{"col1", "col2"}, 114 Values: []*proto3.ListValue{ 115 { 116 Values: []*proto3.Value{stringProto("one"), nullProto()}, 117 }, 118 }, 119 }, 120 }, 121 }, 122 }, 123 } { 124 if got, err := test.m.proto(); err != nil || !testEqual(got, test.want) { 125 t.Errorf("%d: (%#v).proto() = (%v, %v), want (%v, nil)", i, test.m, got, err, test.want) 126 } 127 } 128} 129 130// mutationColumnSorter implements sort.Interface for sorting column-value pairs in a Mutation by column names. 131type mutationColumnSorter struct { 132 Mutation 133} 134 135// newMutationColumnSorter creates new instance of mutationColumnSorter by duplicating the input Mutation so that 136// sorting won't change the input Mutation. 137func newMutationColumnSorter(m *Mutation) *mutationColumnSorter { 138 return &mutationColumnSorter{ 139 Mutation{ 140 m.op, 141 m.table, 142 m.keySet, 143 append([]string(nil), m.columns...), 144 append([]interface{}(nil), m.values...), 145 }, 146 } 147} 148 149// Len implements sort.Interface.Len. 150func (ms *mutationColumnSorter) Len() int { 151 return len(ms.columns) 152} 153 154// Swap implements sort.Interface.Swap. 155func (ms *mutationColumnSorter) Swap(i, j int) { 156 ms.columns[i], ms.columns[j] = ms.columns[j], ms.columns[i] 157 ms.values[i], ms.values[j] = ms.values[j], ms.values[i] 158} 159 160// Less implements sort.Interface.Less. 161func (ms *mutationColumnSorter) Less(i, j int) bool { 162 return strings.Compare(ms.columns[i], ms.columns[j]) < 0 163} 164 165// mutationEqual returns true if two mutations in question are equal 166// to each other. 167func mutationEqual(t *testing.T, m1, m2 Mutation) bool { 168 // Two mutations are considered to be equal even if their column values have different 169 // orders. 170 ms1 := newMutationColumnSorter(&m1) 171 ms2 := newMutationColumnSorter(&m2) 172 sort.Sort(ms1) 173 sort.Sort(ms2) 174 return testEqual(ms1, ms2) 175} 176 177// Test helper functions which help to generate spanner.Mutation. 178func TestMutationHelpers(t *testing.T) { 179 for _, test := range []struct { 180 m string 181 got *Mutation 182 want *Mutation 183 }{ 184 { 185 "Insert", 186 Insert("t_foo", []string{"col1", "col2"}, []interface{}{int64(1), int64(2)}), 187 &Mutation{opInsert, "t_foo", nil, []string{"col1", "col2"}, []interface{}{int64(1), int64(2)}}, 188 }, 189 { 190 "InsertMap", 191 InsertMap("t_foo", map[string]interface{}{"col1": int64(1), "col2": int64(2)}), 192 &Mutation{opInsert, "t_foo", nil, []string{"col1", "col2"}, []interface{}{int64(1), int64(2)}}, 193 }, 194 { 195 "InsertStruct", 196 func() *Mutation { 197 m, err := InsertStruct( 198 "t_foo", 199 struct { 200 notCol bool 201 Col1 int64 `spanner:"col1"` 202 Col2 int64 `spanner:"col2"` 203 }{false, int64(1), int64(2)}, 204 ) 205 if err != nil { 206 t.Errorf("cannot convert struct into mutation: %v", err) 207 } 208 return m 209 }(), 210 &Mutation{opInsert, "t_foo", nil, []string{"col1", "col2"}, []interface{}{int64(1), int64(2)}}, 211 }, 212 { 213 "Update", 214 Update("t_foo", []string{"col1", "col2"}, []interface{}{"one", []byte(nil)}), 215 &Mutation{opUpdate, "t_foo", nil, []string{"col1", "col2"}, []interface{}{"one", []byte(nil)}}, 216 }, 217 { 218 "UpdateMap", 219 UpdateMap("t_foo", map[string]interface{}{"col1": "one", "col2": []byte(nil)}), 220 &Mutation{opUpdate, "t_foo", nil, []string{"col1", "col2"}, []interface{}{"one", []byte(nil)}}, 221 }, 222 { 223 "UpdateStruct", 224 func() *Mutation { 225 m, err := UpdateStruct( 226 "t_foo", 227 struct { 228 Col1 string `spanner:"col1"` 229 notCol int 230 Col2 []byte `spanner:"col2"` 231 }{"one", 1, nil}, 232 ) 233 if err != nil { 234 t.Errorf("cannot convert struct into mutation: %v", err) 235 } 236 return m 237 }(), 238 &Mutation{opUpdate, "t_foo", nil, []string{"col1", "col2"}, []interface{}{"one", []byte(nil)}}, 239 }, 240 { 241 "InsertOrUpdate", 242 InsertOrUpdate("t_foo", []string{"col1", "col2"}, []interface{}{1.0, 2.0}), 243 &Mutation{opInsertOrUpdate, "t_foo", nil, []string{"col1", "col2"}, []interface{}{1.0, 2.0}}, 244 }, 245 { 246 "InsertOrUpdateMap", 247 InsertOrUpdateMap("t_foo", map[string]interface{}{"col1": 1.0, "col2": 2.0}), 248 &Mutation{opInsertOrUpdate, "t_foo", nil, []string{"col1", "col2"}, []interface{}{1.0, 2.0}}, 249 }, 250 { 251 "InsertOrUpdateStruct", 252 func() *Mutation { 253 m, err := InsertOrUpdateStruct( 254 "t_foo", 255 struct { 256 Col1 float64 `spanner:"col1"` 257 Col2 float64 `spanner:"col2"` 258 notCol float64 259 }{1.0, 2.0, 3.0}, 260 ) 261 if err != nil { 262 t.Errorf("cannot convert struct into mutation: %v", err) 263 } 264 return m 265 }(), 266 &Mutation{opInsertOrUpdate, "t_foo", nil, []string{"col1", "col2"}, []interface{}{1.0, 2.0}}, 267 }, 268 { 269 "Replace", 270 Replace("t_foo", []string{"col1", "col2"}, []interface{}{"one", 2.0}), 271 &Mutation{opReplace, "t_foo", nil, []string{"col1", "col2"}, []interface{}{"one", 2.0}}, 272 }, 273 { 274 "ReplaceMap", 275 ReplaceMap("t_foo", map[string]interface{}{"col1": "one", "col2": 2.0}), 276 &Mutation{opReplace, "t_foo", nil, []string{"col1", "col2"}, []interface{}{"one", 2.0}}, 277 }, 278 { 279 "ReplaceStruct", 280 func() *Mutation { 281 m, err := ReplaceStruct( 282 "t_foo", 283 struct { 284 Col1 string `spanner:"col1"` 285 Col2 float64 `spanner:"col2"` 286 notCol string 287 }{"one", 2.0, "foo"}, 288 ) 289 if err != nil { 290 t.Errorf("cannot convert struct into mutation: %v", err) 291 } 292 return m 293 }(), 294 &Mutation{opReplace, "t_foo", nil, []string{"col1", "col2"}, []interface{}{"one", 2.0}}, 295 }, 296 { 297 "Delete", 298 Delete("t_foo", Key{"foo"}), 299 &Mutation{opDelete, "t_foo", Key{"foo"}, nil, nil}, 300 }, 301 { 302 "DeleteRange", 303 Delete("t_foo", KeyRange{Key{"bar"}, Key{"foo"}, ClosedClosed}), 304 &Mutation{opDelete, "t_foo", KeyRange{Key{"bar"}, Key{"foo"}, ClosedClosed}, nil, nil}, 305 }, 306 } { 307 if !mutationEqual(t, *test.got, *test.want) { 308 t.Errorf("%v: got Mutation %v, want %v", test.m, test.got, test.want) 309 } 310 } 311} 312 313// Test encoding non-struct types by using *Struct helpers. 314func TestBadStructs(t *testing.T) { 315 val := "i_am_not_a_struct" 316 wantErr := errNotStruct(val) 317 if _, gotErr := InsertStruct("t_test", val); !testEqual(gotErr, wantErr) { 318 t.Errorf("InsertStruct(%q) returns error %v, want %v", val, gotErr, wantErr) 319 } 320 if _, gotErr := InsertOrUpdateStruct("t_test", val); !testEqual(gotErr, wantErr) { 321 t.Errorf("InsertOrUpdateStruct(%q) returns error %v, want %v", val, gotErr, wantErr) 322 } 323 if _, gotErr := UpdateStruct("t_test", val); !testEqual(gotErr, wantErr) { 324 t.Errorf("UpdateStruct(%q) returns error %v, want %v", val, gotErr, wantErr) 325 } 326 if _, gotErr := ReplaceStruct("t_test", val); !testEqual(gotErr, wantErr) { 327 t.Errorf("ReplaceStruct(%q) returns error %v, want %v", val, gotErr, wantErr) 328 } 329} 330 331func TestStructToMutationParams(t *testing.T) { 332 // Tests cases not covered elsewhere. 333 type S struct{ F interface{} } 334 335 for _, test := range []struct { 336 in interface{} 337 wantCols []string 338 wantVals []interface{} 339 wantErr error 340 }{ 341 {nil, nil, nil, errNotStruct(nil)}, 342 {3, nil, nil, errNotStruct(3)}, 343 {(*S)(nil), nil, nil, nil}, 344 {&S{F: 1}, []string{"F"}, []interface{}{1}, nil}, 345 {&S{F: CommitTimestamp}, []string{"F"}, []interface{}{CommitTimestamp}, nil}, 346 } { 347 gotCols, gotVals, gotErr := structToMutationParams(test.in) 348 if !testEqual(gotCols, test.wantCols) { 349 t.Errorf("%#v: got cols %v, want %v", test.in, gotCols, test.wantCols) 350 } 351 if !testEqual(gotVals, test.wantVals) { 352 t.Errorf("%#v: got vals %v, want %v", test.in, gotVals, test.wantVals) 353 } 354 if !testEqual(gotErr, test.wantErr) { 355 t.Errorf("%#v: got err %v, want %v", test.in, gotErr, test.wantErr) 356 } 357 } 358} 359 360// Test encoding Mutation into proto. 361func TestEncodeMutation(t *testing.T) { 362 for _, test := range []struct { 363 name string 364 mutation Mutation 365 wantProto *sppb.Mutation 366 wantErr error 367 }{ 368 { 369 "OpDelete", 370 Mutation{opDelete, "t_test", Key{1}, nil, nil}, 371 &sppb.Mutation{ 372 Operation: &sppb.Mutation_Delete_{ 373 Delete: &sppb.Mutation_Delete{ 374 Table: "t_test", 375 KeySet: &sppb.KeySet{ 376 Keys: []*proto3.ListValue{listValueProto(intProto(1))}, 377 }, 378 }, 379 }, 380 }, 381 nil, 382 }, 383 { 384 "OpDelete - Key error", 385 Mutation{opDelete, "t_test", Key{struct{}{}}, nil, nil}, 386 &sppb.Mutation{ 387 Operation: &sppb.Mutation_Delete_{ 388 Delete: &sppb.Mutation_Delete{ 389 Table: "t_test", 390 KeySet: &sppb.KeySet{}, 391 }, 392 }, 393 }, 394 errInvdKeyPartType(struct{}{}), 395 }, 396 { 397 "OpInsert", 398 Mutation{opInsert, "t_test", nil, []string{"key", "val"}, []interface{}{"foo", 1}}, 399 &sppb.Mutation{ 400 Operation: &sppb.Mutation_Insert{ 401 Insert: &sppb.Mutation_Write{ 402 Table: "t_test", 403 Columns: []string{"key", "val"}, 404 Values: []*proto3.ListValue{listValueProto(stringProto("foo"), intProto(1))}, 405 }, 406 }, 407 }, 408 nil, 409 }, 410 { 411 "OpInsert - Value Type Error", 412 Mutation{opInsert, "t_test", nil, []string{"key", "val"}, []interface{}{struct{}{}, 1}}, 413 &sppb.Mutation{ 414 Operation: &sppb.Mutation_Insert{ 415 Insert: &sppb.Mutation_Write{}, 416 }, 417 }, 418 errEncoderUnsupportedType(struct{}{}), 419 }, 420 { 421 "OpInsertOrUpdate", 422 Mutation{opInsertOrUpdate, "t_test", nil, []string{"key", "val"}, []interface{}{"foo", 1}}, 423 &sppb.Mutation{ 424 Operation: &sppb.Mutation_InsertOrUpdate{ 425 InsertOrUpdate: &sppb.Mutation_Write{ 426 Table: "t_test", 427 Columns: []string{"key", "val"}, 428 Values: []*proto3.ListValue{listValueProto(stringProto("foo"), intProto(1))}, 429 }, 430 }, 431 }, 432 nil, 433 }, 434 { 435 "OpInsertOrUpdate - Value Type Error", 436 Mutation{opInsertOrUpdate, "t_test", nil, []string{"key", "val"}, []interface{}{struct{}{}, 1}}, 437 &sppb.Mutation{ 438 Operation: &sppb.Mutation_InsertOrUpdate{ 439 InsertOrUpdate: &sppb.Mutation_Write{}, 440 }, 441 }, 442 errEncoderUnsupportedType(struct{}{}), 443 }, 444 { 445 "OpReplace", 446 Mutation{opReplace, "t_test", nil, []string{"key", "val"}, []interface{}{"foo", 1}}, 447 &sppb.Mutation{ 448 Operation: &sppb.Mutation_Replace{ 449 Replace: &sppb.Mutation_Write{ 450 Table: "t_test", 451 Columns: []string{"key", "val"}, 452 Values: []*proto3.ListValue{listValueProto(stringProto("foo"), intProto(1))}, 453 }, 454 }, 455 }, 456 nil, 457 }, 458 { 459 "OpReplace - Value Type Error", 460 Mutation{opReplace, "t_test", nil, []string{"key", "val"}, []interface{}{struct{}{}, 1}}, 461 &sppb.Mutation{ 462 Operation: &sppb.Mutation_Replace{ 463 Replace: &sppb.Mutation_Write{}, 464 }, 465 }, 466 errEncoderUnsupportedType(struct{}{}), 467 }, 468 { 469 "OpUpdate", 470 Mutation{opUpdate, "t_test", nil, []string{"key", "val"}, []interface{}{"foo", 1}}, 471 &sppb.Mutation{ 472 Operation: &sppb.Mutation_Update{ 473 Update: &sppb.Mutation_Write{ 474 Table: "t_test", 475 Columns: []string{"key", "val"}, 476 Values: []*proto3.ListValue{listValueProto(stringProto("foo"), intProto(1))}, 477 }, 478 }, 479 }, 480 nil, 481 }, 482 { 483 "OpUpdate - Value Type Error", 484 Mutation{opUpdate, "t_test", nil, []string{"key", "val"}, []interface{}{struct{}{}, 1}}, 485 &sppb.Mutation{ 486 Operation: &sppb.Mutation_Update{ 487 Update: &sppb.Mutation_Write{}, 488 }, 489 }, 490 errEncoderUnsupportedType(struct{}{}), 491 }, 492 { 493 "OpKnown - Unknown Mutation Operation Code", 494 Mutation{op(100), "t_test", nil, nil, nil}, 495 &sppb.Mutation{}, 496 errInvdMutationOp(Mutation{op(100), "t_test", nil, nil, nil}), 497 }, 498 } { 499 gotProto, gotErr := test.mutation.proto() 500 if gotErr != nil { 501 if !testEqual(gotErr, test.wantErr) { 502 t.Errorf("%s: %v.proto() returns error %v, want %v", test.name, test.mutation, gotErr, test.wantErr) 503 } 504 continue 505 } 506 if !testEqual(gotProto, test.wantProto) { 507 t.Errorf("%s: %v.proto() = (%v, nil), want (%v, nil)", test.name, test.mutation, gotProto, test.wantProto) 508 } 509 } 510} 511 512// Test Encoding an array of mutations. 513func TestEncodeMutationArray(t *testing.T) { 514 for _, test := range []struct { 515 name string 516 ms []*Mutation 517 want []*sppb.Mutation 518 wantErr error 519 }{ 520 { 521 "Multiple Mutations", 522 []*Mutation{ 523 {opDelete, "t_test", Key{"bar"}, nil, nil}, 524 {opInsertOrUpdate, "t_test", nil, []string{"key", "val"}, []interface{}{"foo", 1}}, 525 }, 526 []*sppb.Mutation{ 527 { 528 Operation: &sppb.Mutation_Delete_{ 529 Delete: &sppb.Mutation_Delete{ 530 Table: "t_test", 531 KeySet: &sppb.KeySet{ 532 Keys: []*proto3.ListValue{listValueProto(stringProto("bar"))}, 533 }, 534 }, 535 }, 536 }, 537 { 538 Operation: &sppb.Mutation_InsertOrUpdate{ 539 InsertOrUpdate: &sppb.Mutation_Write{ 540 Table: "t_test", 541 Columns: []string{"key", "val"}, 542 Values: []*proto3.ListValue{listValueProto(stringProto("foo"), intProto(1))}, 543 }, 544 }, 545 }, 546 }, 547 nil, 548 }, 549 { 550 "Multiple Mutations - Bad Mutation", 551 []*Mutation{ 552 {opDelete, "t_test", Key{"bar"}, nil, nil}, 553 {opInsertOrUpdate, "t_test", nil, []string{"key", "val"}, []interface{}{"foo", struct{}{}}}, 554 }, 555 []*sppb.Mutation{}, 556 errEncoderUnsupportedType(struct{}{}), 557 }, 558 } { 559 gotProto, gotErr := mutationsProto(test.ms) 560 if gotErr != nil { 561 if !testEqual(gotErr, test.wantErr) { 562 t.Errorf("%v: mutationsProto(%v) returns error %v, want %v", test.name, test.ms, gotErr, test.wantErr) 563 } 564 continue 565 } 566 if !testEqual(gotProto, test.want) { 567 t.Errorf("%v: mutationsProto(%v) = (%v, nil), want (%v, nil)", test.name, test.ms, gotProto, test.want) 568 } 569 } 570} 571