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_test 18 19import ( 20 "context" 21 "errors" 22 "fmt" 23 "sync" 24 "time" 25 26 "cloud.google.com/go/spanner" 27 "google.golang.org/api/iterator" 28 sppb "google.golang.org/genproto/googleapis/spanner/v1" 29 "google.golang.org/grpc/codes" 30 "google.golang.org/grpc/status" 31) 32 33func ExampleNewClient() { 34 ctx := context.Background() 35 const myDB = "projects/my-project/instances/my-instance/database/my-db" 36 client, err := spanner.NewClient(ctx, myDB) 37 if err != nil { 38 // TODO: Handle error. 39 } 40 _ = client // TODO: Use client. 41} 42 43const myDB = "projects/my-project/instances/my-instance/database/my-db" 44 45func ExampleNewClientWithConfig() { 46 ctx := context.Background() 47 const myDB = "projects/my-project/instances/my-instance/database/my-db" 48 client, err := spanner.NewClientWithConfig(ctx, myDB, spanner.ClientConfig{ 49 NumChannels: 10, 50 }) 51 if err != nil { 52 // TODO: Handle error. 53 } 54 _ = client // TODO: Use client. 55 client.Close() // Close client when done. 56} 57 58func ExampleClient_Single() { 59 ctx := context.Background() 60 client, err := spanner.NewClient(ctx, myDB) 61 if err != nil { 62 // TODO: Handle error. 63 } 64 iter := client.Single().Query(ctx, spanner.NewStatement("SELECT FirstName FROM Singers")) 65 _ = iter // TODO: iterate using Next or Do. 66} 67 68func ExampleClient_ReadOnlyTransaction() { 69 ctx := context.Background() 70 client, err := spanner.NewClient(ctx, myDB) 71 if err != nil { 72 // TODO: Handle error. 73 } 74 t := client.ReadOnlyTransaction() 75 defer t.Close() 76 // TODO: Read with t using Read, ReadRow, ReadUsingIndex, or Query. 77} 78 79func ExampleClient_ReadWriteTransaction() { 80 ctx := context.Background() 81 client, err := spanner.NewClient(ctx, myDB) 82 if err != nil { 83 // TODO: Handle error. 84 } 85 _, err = client.ReadWriteTransaction(ctx, func(ctx context.Context, txn *spanner.ReadWriteTransaction) error { 86 var balance int64 87 row, err := txn.ReadRow(ctx, "Accounts", spanner.Key{"alice"}, []string{"balance"}) 88 if err != nil { 89 // This function will be called again if this is an IsAborted error. 90 return err 91 } 92 if err := row.Column(0, &balance); err != nil { 93 return err 94 } 95 96 if balance <= 10 { 97 return errors.New("insufficient funds in account") 98 } 99 balance -= 10 100 m := spanner.Update("Accounts", []string{"user", "balance"}, []interface{}{"alice", balance}) 101 return txn.BufferWrite([]*spanner.Mutation{m}) 102 // The buffered mutation will be committed. If the commit fails with an 103 // IsAborted error, this function will be called again. 104 }) 105 if err != nil { 106 // TODO: Handle error. 107 } 108} 109 110func ExampleUpdate() { 111 ctx := context.Background() 112 client, err := spanner.NewClient(ctx, myDB) 113 if err != nil { 114 // TODO: Handle error. 115 } 116 _, err = client.ReadWriteTransaction(ctx, func(ctx context.Context, txn *spanner.ReadWriteTransaction) error { 117 row, err := txn.ReadRow(ctx, "Accounts", spanner.Key{"alice"}, []string{"balance"}) 118 if err != nil { 119 return err 120 } 121 var balance int64 122 if err := row.Column(0, &balance); err != nil { 123 return err 124 } 125 return txn.BufferWrite([]*spanner.Mutation{ 126 spanner.Update("Accounts", []string{"user", "balance"}, []interface{}{"alice", balance + 10}), 127 }) 128 }) 129 if err != nil { 130 // TODO: Handle error. 131 } 132} 133 134// This example is the same as the one for Update, except for the use of UpdateMap. 135func ExampleUpdateMap() { 136 ctx := context.Background() 137 client, err := spanner.NewClient(ctx, myDB) 138 if err != nil { 139 // TODO: Handle error. 140 } 141 _, err = client.ReadWriteTransaction(ctx, func(ctx context.Context, txn *spanner.ReadWriteTransaction) error { 142 row, err := txn.ReadRow(ctx, "Accounts", spanner.Key{"alice"}, []string{"balance"}) 143 if err != nil { 144 return err 145 } 146 var balance int64 147 if err := row.Column(0, &balance); err != nil { 148 return err 149 } 150 return txn.BufferWrite([]*spanner.Mutation{ 151 spanner.UpdateMap("Accounts", map[string]interface{}{ 152 "user": "alice", 153 "balance": balance + 10, 154 }), 155 }) 156 }) 157 if err != nil { 158 // TODO: Handle error. 159 } 160} 161 162// This example is the same as the one for Update, except for the use of 163// UpdateStruct. 164func ExampleUpdateStruct() { 165 ctx := context.Background() 166 client, err := spanner.NewClient(ctx, myDB) 167 if err != nil { 168 // TODO: Handle error. 169 } 170 type account struct { 171 User string `spanner:"user"` 172 Balance int64 `spanner:"balance"` 173 } 174 _, err = client.ReadWriteTransaction(ctx, func(ctx context.Context, txn *spanner.ReadWriteTransaction) error { 175 row, err := txn.ReadRow(ctx, "Accounts", spanner.Key{"alice"}, []string{"balance"}) 176 if err != nil { 177 return err 178 } 179 var balance int64 180 if err := row.Column(0, &balance); err != nil { 181 return err 182 } 183 m, err := spanner.UpdateStruct("Accounts", account{ 184 User: "alice", 185 Balance: balance + 10, 186 }) 187 if err != nil { 188 return err 189 } 190 return txn.BufferWrite([]*spanner.Mutation{m}) 191 }) 192 if err != nil { 193 // TODO: Handle error. 194 } 195} 196 197func ExampleClient_Apply() { 198 ctx := context.Background() 199 client, err := spanner.NewClient(ctx, myDB) 200 if err != nil { 201 // TODO: Handle error. 202 } 203 m := spanner.Update("Users", []string{"name", "email"}, []interface{}{"alice", "a@example.com"}) 204 _, err = client.Apply(ctx, []*spanner.Mutation{m}) 205 if err != nil { 206 // TODO: Handle error. 207 } 208} 209 210func ExampleInsert() { 211 m := spanner.Insert("Users", []string{"name", "email"}, []interface{}{"alice", "a@example.com"}) 212 _ = m // TODO: use with Client.Apply or in a ReadWriteTransaction. 213} 214 215func ExampleInsertMap() { 216 m := spanner.InsertMap("Users", map[string]interface{}{ 217 "name": "alice", 218 "email": "a@example.com", 219 }) 220 _ = m // TODO: use with Client.Apply or in a ReadWriteTransaction. 221} 222 223func ExampleInsertStruct() { 224 type User struct { 225 Name, Email string 226 } 227 u := User{Name: "alice", Email: "a@example.com"} 228 m, err := spanner.InsertStruct("Users", u) 229 if err != nil { 230 // TODO: Handle error. 231 } 232 _ = m // TODO: use with Client.Apply or in a ReadWriteTransaction. 233} 234 235func ExampleDelete() { 236 m := spanner.Delete("Users", spanner.Key{"alice"}) 237 _ = m // TODO: use with Client.Apply or in a ReadWriteTransaction. 238} 239 240func ExampleDelete_keyRange() { 241 m := spanner.Delete("Users", spanner.KeyRange{ 242 Start: spanner.Key{"alice"}, 243 End: spanner.Key{"bob"}, 244 Kind: spanner.ClosedClosed, 245 }) 246 _ = m // TODO: use with Client.Apply or in a ReadWriteTransaction. 247} 248 249func ExampleRowIterator_Next() { 250 ctx := context.Background() 251 client, err := spanner.NewClient(ctx, myDB) 252 if err != nil { 253 // TODO: Handle error. 254 } 255 iter := client.Single().Query(ctx, spanner.NewStatement("SELECT FirstName FROM Singers")) 256 defer iter.Stop() 257 for { 258 row, err := iter.Next() 259 if err == iterator.Done { 260 break 261 } 262 if err != nil { 263 // TODO: Handle error. 264 } 265 var firstName string 266 if err := row.Column(0, &firstName); err != nil { 267 // TODO: Handle error. 268 } 269 fmt.Println(firstName) 270 } 271} 272 273func ExampleRowIterator_Do() { 274 ctx := context.Background() 275 client, err := spanner.NewClient(ctx, myDB) 276 if err != nil { 277 // TODO: Handle error. 278 } 279 iter := client.Single().Query(ctx, spanner.NewStatement("SELECT FirstName FROM Singers")) 280 err = iter.Do(func(r *spanner.Row) error { 281 var firstName string 282 if err := r.Column(0, &firstName); err != nil { 283 return err 284 } 285 fmt.Println(firstName) 286 return nil 287 }) 288 if err != nil { 289 // TODO: Handle error. 290 } 291} 292 293func ExampleRow_Size() { 294 ctx := context.Background() 295 client, err := spanner.NewClient(ctx, myDB) 296 if err != nil { 297 // TODO: Handle error. 298 } 299 row, err := client.Single().ReadRow(ctx, "Accounts", spanner.Key{"alice"}, []string{"name", "balance"}) 300 if err != nil { 301 // TODO: Handle error. 302 } 303 fmt.Println(row.Size()) // 2 304} 305 306func ExampleRow_ColumnName() { 307 ctx := context.Background() 308 client, err := spanner.NewClient(ctx, myDB) 309 if err != nil { 310 // TODO: Handle error. 311 } 312 row, err := client.Single().ReadRow(ctx, "Accounts", spanner.Key{"alice"}, []string{"name", "balance"}) 313 if err != nil { 314 // TODO: Handle error. 315 } 316 fmt.Println(row.ColumnName(1)) // "balance" 317} 318 319func ExampleRow_ColumnIndex() { 320 ctx := context.Background() 321 client, err := spanner.NewClient(ctx, myDB) 322 if err != nil { 323 // TODO: Handle error. 324 } 325 row, err := client.Single().ReadRow(ctx, "Accounts", spanner.Key{"alice"}, []string{"name", "balance"}) 326 if err != nil { 327 // TODO: Handle error. 328 } 329 index, err := row.ColumnIndex("balance") 330 if err != nil { 331 // TODO: Handle error. 332 } 333 fmt.Println(index) 334} 335 336func ExampleRow_ColumnNames() { 337 ctx := context.Background() 338 client, err := spanner.NewClient(ctx, myDB) 339 if err != nil { 340 // TODO: Handle error. 341 } 342 row, err := client.Single().ReadRow(ctx, "Accounts", spanner.Key{"alice"}, []string{"name", "balance"}) 343 if err != nil { 344 // TODO: Handle error. 345 } 346 fmt.Println(row.ColumnNames()) 347} 348 349func ExampleRow_ColumnByName() { 350 ctx := context.Background() 351 client, err := spanner.NewClient(ctx, myDB) 352 if err != nil { 353 // TODO: Handle error. 354 } 355 row, err := client.Single().ReadRow(ctx, "Accounts", spanner.Key{"alice"}, []string{"name", "balance"}) 356 if err != nil { 357 // TODO: Handle error. 358 } 359 var balance int64 360 if err := row.ColumnByName("balance", &balance); err != nil { 361 // TODO: Handle error. 362 } 363 fmt.Println(balance) 364} 365 366func ExampleRow_Columns() { 367 ctx := context.Background() 368 client, err := spanner.NewClient(ctx, myDB) 369 if err != nil { 370 // TODO: Handle error. 371 } 372 row, err := client.Single().ReadRow(ctx, "Accounts", spanner.Key{"alice"}, []string{"name", "balance"}) 373 if err != nil { 374 // TODO: Handle error. 375 } 376 var name string 377 var balance int64 378 if err := row.Columns(&name, &balance); err != nil { 379 // TODO: Handle error. 380 } 381 fmt.Println(name, balance) 382} 383 384func ExampleRow_ToStruct() { 385 ctx := context.Background() 386 client, err := spanner.NewClient(ctx, myDB) 387 if err != nil { 388 // TODO: Handle error. 389 } 390 row, err := client.Single().ReadRow(ctx, "Accounts", spanner.Key{"alice"}, []string{"name", "balance"}) 391 if err != nil { 392 // TODO: Handle error. 393 } 394 395 type Account struct { 396 Name string 397 Balance int64 398 } 399 400 var acct Account 401 if err := row.ToStruct(&acct); err != nil { 402 // TODO: Handle error. 403 } 404 fmt.Println(acct) 405} 406 407func ExampleReadOnlyTransaction_Read() { 408 ctx := context.Background() 409 client, err := spanner.NewClient(ctx, myDB) 410 if err != nil { 411 // TODO: Handle error. 412 } 413 iter := client.Single().Read(ctx, "Users", 414 spanner.KeySets(spanner.Key{"alice"}, spanner.Key{"bob"}), 415 []string{"name", "email"}) 416 _ = iter // TODO: iterate using Next or Do. 417} 418 419func ExampleReadOnlyTransaction_ReadUsingIndex() { 420 ctx := context.Background() 421 client, err := spanner.NewClient(ctx, myDB) 422 if err != nil { 423 // TODO: Handle error. 424 } 425 iter := client.Single().ReadUsingIndex(ctx, "Users", 426 "UsersByEmail", 427 spanner.KeySets(spanner.Key{"a@example.com"}, spanner.Key{"b@example.com"}), 428 []string{"name", "email"}) 429 _ = iter // TODO: iterate using Next or Do. 430} 431 432func ExampleReadOnlyTransaction_ReadWithOptions() { 433 ctx := context.Background() 434 client, err := spanner.NewClient(ctx, myDB) 435 if err != nil { 436 // TODO: Handle error. 437 } 438 // Use an index, and limit to 100 rows at most. 439 iter := client.Single().ReadWithOptions(ctx, "Users", 440 spanner.KeySets(spanner.Key{"a@example.com"}, spanner.Key{"b@example.com"}), 441 []string{"name", "email"}, &spanner.ReadOptions{ 442 Index: "UsersByEmail", 443 Limit: 100, 444 }) 445 _ = iter // TODO: iterate using Next or Do. 446} 447 448func ExampleReadOnlyTransaction_ReadRow() { 449 ctx := context.Background() 450 client, err := spanner.NewClient(ctx, myDB) 451 if err != nil { 452 // TODO: Handle error. 453 } 454 row, err := client.Single().ReadRow(ctx, "Users", spanner.Key{"alice"}, 455 []string{"name", "email"}) 456 if err != nil { 457 // TODO: Handle error. 458 } 459 _ = row // TODO: use row 460} 461 462func ExampleReadOnlyTransaction_Query() { 463 ctx := context.Background() 464 client, err := spanner.NewClient(ctx, myDB) 465 if err != nil { 466 // TODO: Handle error. 467 } 468 iter := client.Single().Query(ctx, spanner.NewStatement("SELECT FirstName FROM Singers")) 469 _ = iter // TODO: iterate using Next or Do. 470} 471 472func ExampleNewStatement() { 473 stmt := spanner.NewStatement("SELECT FirstName, LastName FROM SINGERS WHERE LastName >= @start") 474 stmt.Params["start"] = "Dylan" 475 // TODO: Use stmt in Query. 476} 477 478func ExampleNewStatement_structLiteral() { 479 stmt := spanner.Statement{ 480 SQL: `SELECT FirstName, LastName FROM SINGERS WHERE LastName = ("Lea", "Martin")`, 481 } 482 _ = stmt // TODO: Use stmt in Query. 483} 484 485func ExampleStructParam() { 486 stmt := spanner.Statement{ 487 SQL: "SELECT * FROM SINGERS WHERE (FirstName, LastName) = @singerinfo", 488 Params: map[string]interface{}{ 489 "singerinfo": struct { 490 FirstName string 491 LastName string 492 }{"Bob", "Dylan"}, 493 }, 494 } 495 _ = stmt // TODO: Use stmt in Query. 496} 497 498func ExampleArrayOfStructParam() { 499 stmt := spanner.Statement{ 500 SQL: "SELECT * FROM SINGERS WHERE (FirstName, LastName) IN UNNEST(@singerinfo)", 501 Params: map[string]interface{}{ 502 "singerinfo": []struct { 503 FirstName string 504 LastName string 505 }{ 506 {"Ringo", "Starr"}, 507 {"John", "Lennon"}, 508 }, 509 }, 510 } 511 _ = stmt // TODO: Use stmt in Query. 512} 513 514func ExampleReadOnlyTransaction_Timestamp() { 515 ctx := context.Background() 516 client, err := spanner.NewClient(ctx, myDB) 517 if err != nil { 518 // TODO: Handle error. 519 } 520 txn := client.Single() 521 row, err := txn.ReadRow(ctx, "Users", spanner.Key{"alice"}, 522 []string{"name", "email"}) 523 if err != nil { 524 // TODO: Handle error. 525 } 526 readTimestamp, err := txn.Timestamp() 527 if err != nil { 528 // TODO: Handle error. 529 } 530 fmt.Println("read happened at", readTimestamp) 531 _ = row // TODO: use row 532} 533 534func ExampleReadOnlyTransaction_WithTimestampBound() { 535 ctx := context.Background() 536 client, err := spanner.NewClient(ctx, myDB) 537 if err != nil { 538 // TODO: Handle error. 539 } 540 txn := client.Single().WithTimestampBound(spanner.MaxStaleness(30 * time.Second)) 541 row, err := txn.ReadRow(ctx, "Users", spanner.Key{"alice"}, []string{"name", "email"}) 542 if err != nil { 543 // TODO: Handle error. 544 } 545 _ = row // TODO: use row 546 readTimestamp, err := txn.Timestamp() 547 if err != nil { 548 // TODO: Handle error. 549 } 550 fmt.Println("read happened at", readTimestamp) 551} 552 553func ExampleGenericColumnValue_Decode() { 554 // In real applications, rows can be retrieved by methods like client.Single().ReadRow(). 555 row, err := spanner.NewRow([]string{"intCol", "strCol"}, []interface{}{42, "my-text"}) 556 if err != nil { 557 // TODO: Handle error. 558 } 559 for i := 0; i < row.Size(); i++ { 560 var col spanner.GenericColumnValue 561 if err := row.Column(i, &col); err != nil { 562 // TODO: Handle error. 563 } 564 switch col.Type.Code { 565 case sppb.TypeCode_INT64: 566 var v int64 567 if err := col.Decode(&v); err != nil { 568 // TODO: Handle error. 569 } 570 fmt.Println("int", v) 571 case sppb.TypeCode_STRING: 572 var v string 573 if err := col.Decode(&v); err != nil { 574 // TODO: Handle error. 575 } 576 fmt.Println("string", v) 577 } 578 } 579 // Output: 580 // int 42 581 // string my-text 582} 583 584func ExampleClient_BatchReadOnlyTransaction() { 585 ctx := context.Background() 586 var ( 587 client *spanner.Client 588 txn *spanner.BatchReadOnlyTransaction 589 err error 590 ) 591 if client, err = spanner.NewClient(ctx, myDB); err != nil { 592 // TODO: Handle error. 593 } 594 defer client.Close() 595 if txn, err = client.BatchReadOnlyTransaction(ctx, spanner.StrongRead()); err != nil { 596 // TODO: Handle error. 597 } 598 defer txn.Close() 599 600 // Singer represents the elements in a row from the Singers table. 601 type Singer struct { 602 SingerID int64 603 FirstName string 604 LastName string 605 SingerInfo []byte 606 } 607 stmt := spanner.Statement{SQL: "SELECT * FROM Singers;"} 608 partitions, err := txn.PartitionQuery(ctx, stmt, spanner.PartitionOptions{}) 609 if err != nil { 610 // TODO: Handle error. 611 } 612 // Note: here we use multiple goroutines, but you should use separate 613 // processes/machines. 614 wg := sync.WaitGroup{} 615 for i, p := range partitions { 616 wg.Add(1) 617 go func(i int, p *spanner.Partition) { 618 defer wg.Done() 619 iter := txn.Execute(ctx, p) 620 defer iter.Stop() 621 for { 622 row, err := iter.Next() 623 if err == iterator.Done { 624 break 625 } else if err != nil { 626 // TODO: Handle error. 627 } 628 var s Singer 629 if err := row.ToStruct(&s); err != nil { 630 // TODO: Handle error. 631 } 632 _ = s // TODO: Process the row. 633 } 634 }(i, p) 635 } 636 wg.Wait() 637} 638 639func ExampleCommitTimestamp() { 640 ctx := context.Background() 641 client, err := spanner.NewClient(ctx, myDB) 642 if err != nil { 643 // TODO: Handle error. 644 } 645 646 type account struct { 647 User string 648 Creation spanner.NullTime // time.Time can also be used if column isNOT NULL 649 } 650 651 a := account{User: "Joe", Creation: spanner.NullTime{spanner.CommitTimestamp, true}} 652 m, err := spanner.InsertStruct("Accounts", a) 653 if err != nil { 654 // TODO: Handle error. 655 } 656 _, err = client.Apply(ctx, []*spanner.Mutation{m}, spanner.ApplyAtLeastOnce()) 657 if err != nil { 658 // TODO: Handle error. 659 } 660 661 if r, e := client.Single().ReadRow(ctx, "Accounts", spanner.Key{"Joe"}, []string{"User", "Creation"}); e != nil { 662 // TODO: Handle error. 663 } else { 664 var got account 665 if err := r.ToStruct(&got); err != nil { 666 // TODO: Handle error. 667 } 668 _ = got // TODO: Process row. 669 } 670} 671 672func ExampleStatement_regexpContains() { 673 // Search for accounts with valid emails using regexp as per: 674 // https://cloud.google.com/spanner/docs/functions-and-operators#regexp_contains 675 stmt := spanner.Statement{ 676 SQL: `SELECT * FROM users WHERE REGEXP_CONTAINS(email, @valid_email)`, 677 Params: map[string]interface{}{ 678 "valid_email": `\Q@\E`, 679 }, 680 } 681 _ = stmt // TODO: Use stmt in a query. 682} 683 684func ExampleKeySets() { 685 ctx := context.Background() 686 client, err := spanner.NewClient(ctx, myDB) 687 if err != nil { 688 // TODO: Handle error. 689 } 690 // Get some rows from the Accounts table using a secondary index. In this case we get all users who are in Georgia. 691 iter := client.Single().ReadUsingIndex(context.Background(), "Accounts", "idx_state", spanner.Key{"GA"}, []string{"state"}) 692 693 // Create a empty KeySet by calling the KeySets function with no parameters. 694 ks := spanner.KeySets() 695 696 // Loop the results of a previous query iterator. 697 for { 698 row, err := iter.Next() 699 if err == iterator.Done { 700 break 701 } else if err != nil { 702 // TODO: Handle error. 703 } 704 var id string 705 err = row.ColumnByName("User", &id) 706 if err != nil { 707 // TODO: Handle error. 708 } 709 ks = spanner.KeySets(spanner.KeySets(spanner.Key{id}, ks)) 710 } 711 712 _ = ks //TODO: Go use the KeySet in another query. 713 714} 715 716func ExampleNewReadWriteStmtBasedTransaction() { 717 ctx := context.Background() 718 client, err := spanner.NewClient(ctx, myDB) 719 if err != nil { 720 // TODO: Handle error. 721 } 722 defer client.Close() 723 724 f := func(tx *spanner.ReadWriteStmtBasedTransaction) error { 725 var balance int64 726 row, err := tx.ReadRow(ctx, "Accounts", spanner.Key{"alice"}, []string{"balance"}) 727 if err != nil { 728 return err 729 } 730 if err := row.Column(0, &balance); err != nil { 731 return err 732 } 733 734 if balance <= 10 { 735 return errors.New("insufficient funds in account") 736 } 737 balance -= 10 738 m := spanner.Update("Accounts", []string{"user", "balance"}, []interface{}{"alice", balance}) 739 return tx.BufferWrite([]*spanner.Mutation{m}) 740 } 741 742 for { 743 tx, err := spanner.NewReadWriteStmtBasedTransaction(ctx, client) 744 if err != nil { 745 // TODO: Handle error. 746 break 747 } 748 err = f(tx) 749 if err != nil && status.Code(err) != codes.Aborted { 750 tx.Rollback(ctx) 751 // TODO: Handle error. 752 break 753 } else if err == nil { 754 _, err = tx.Commit(ctx) 755 if err == nil { 756 break 757 } else if status.Code(err) != codes.Aborted { 758 // TODO: Handle error. 759 break 760 } 761 } 762 // Set a default sleep time if the server delay is absent. 763 delay := 10 * time.Millisecond 764 if serverDelay, hasServerDelay := spanner.ExtractRetryDelay(err); hasServerDelay { 765 delay = serverDelay 766 } 767 time.Sleep(delay) 768 } 769} 770