blob: f301720e5d813721a82a544e56d901b4274ab876 [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_test
import (
"context"
"errors"
"fmt"
"sync"
"time"
"cloud.google.com/go/spanner"
sppb "cloud.google.com/go/spanner/apiv1/spannerpb"
"google.golang.org/api/iterator"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func ExampleNewClient() {
ctx := context.Background()
const myDB = "projects/my-project/instances/my-instance/database/my-db"
client, err := spanner.NewClient(ctx, myDB)
if err != nil {
// TODO: Handle error.
}
_ = client // TODO: Use client.
}
const myDB = "projects/my-project/instances/my-instance/database/my-db"
func ExampleNewClientWithConfig() {
ctx := context.Background()
const myDB = "projects/my-project/instances/my-instance/database/my-db"
client, err := spanner.NewClientWithConfig(ctx, myDB, spanner.ClientConfig{
NumChannels: 10,
})
if err != nil {
// TODO: Handle error.
}
_ = client // TODO: Use client.
client.Close() // Close client when done.
}
func ExampleClient_Single() {
ctx := context.Background()
client, err := spanner.NewClient(ctx, myDB)
if err != nil {
// TODO: Handle error.
}
iter := client.Single().Query(ctx, spanner.NewStatement("SELECT FirstName FROM Singers"))
_ = iter // TODO: iterate using Next or Do.
}
func ExampleClient_ReadOnlyTransaction() {
ctx := context.Background()
client, err := spanner.NewClient(ctx, myDB)
if err != nil {
// TODO: Handle error.
}
t := client.ReadOnlyTransaction()
defer t.Close()
// TODO: Read with t using Read, ReadRow, ReadUsingIndex, or Query.
}
func ExampleClient_ReadWriteTransaction() {
ctx := context.Background()
client, err := spanner.NewClient(ctx, myDB)
if err != nil {
// TODO: Handle error.
}
_, err = client.ReadWriteTransaction(ctx, func(ctx context.Context, txn *spanner.ReadWriteTransaction) error {
var balance int64
row, err := txn.ReadRow(ctx, "Accounts", spanner.Key{"alice"}, []string{"balance"})
if err != nil {
// This function will be called again if this is an IsAborted error.
return err
}
if err := row.Column(0, &balance); err != nil {
return err
}
if balance <= 10 {
return errors.New("insufficient funds in account")
}
balance -= 10
m := spanner.Update("Accounts", []string{"user", "balance"}, []interface{}{"alice", balance})
// The buffered mutation will be committed. If the commit fails with an
// IsAborted error, this function will be called again.
return txn.BufferWrite([]*spanner.Mutation{m})
})
if err != nil {
// TODO: Handle error.
}
}
func ExampleUpdate() {
ctx := context.Background()
client, err := spanner.NewClient(ctx, myDB)
if err != nil {
// TODO: Handle error.
}
_, err = client.ReadWriteTransaction(ctx, func(ctx context.Context, txn *spanner.ReadWriteTransaction) error {
row, err := txn.ReadRow(ctx, "Accounts", spanner.Key{"alice"}, []string{"balance"})
if err != nil {
return err
}
var balance int64
if err := row.Column(0, &balance); err != nil {
return err
}
return txn.BufferWrite([]*spanner.Mutation{
spanner.Update("Accounts", []string{"user", "balance"}, []interface{}{"alice", balance + 10}),
})
})
if err != nil {
// TODO: Handle error.
}
}
// This example is the same as the one for Update, except for the use of UpdateMap.
func ExampleUpdateMap() {
ctx := context.Background()
client, err := spanner.NewClient(ctx, myDB)
if err != nil {
// TODO: Handle error.
}
_, err = client.ReadWriteTransaction(ctx, func(ctx context.Context, txn *spanner.ReadWriteTransaction) error {
row, err := txn.ReadRow(ctx, "Accounts", spanner.Key{"alice"}, []string{"balance"})
if err != nil {
return err
}
var balance int64
if err := row.Column(0, &balance); err != nil {
return err
}
return txn.BufferWrite([]*spanner.Mutation{
spanner.UpdateMap("Accounts", map[string]interface{}{
"user": "alice",
"balance": balance + 10,
}),
})
})
if err != nil {
// TODO: Handle error.
}
}
// This example is the same as the one for Update, except for the use of
// UpdateStruct.
func ExampleUpdateStruct() {
ctx := context.Background()
client, err := spanner.NewClient(ctx, myDB)
if err != nil {
// TODO: Handle error.
}
type account struct {
User string `spanner:"user"`
Balance int64 `spanner:"balance"`
}
_, err = client.ReadWriteTransaction(ctx, func(ctx context.Context, txn *spanner.ReadWriteTransaction) error {
row, err := txn.ReadRow(ctx, "Accounts", spanner.Key{"alice"}, []string{"balance"})
if err != nil {
return err
}
var balance int64
if err := row.Column(0, &balance); err != nil {
return err
}
m, err := spanner.UpdateStruct("Accounts", account{
User: "alice",
Balance: balance + 10,
})
if err != nil {
return err
}
return txn.BufferWrite([]*spanner.Mutation{m})
})
if err != nil {
// TODO: Handle error.
}
}
func ExampleClient_Apply() {
ctx := context.Background()
client, err := spanner.NewClient(ctx, myDB)
if err != nil {
// TODO: Handle error.
}
m := spanner.Update("Users", []string{"name", "email"}, []interface{}{"alice", "a@example.com"})
_, err = client.Apply(ctx, []*spanner.Mutation{m})
if err != nil {
// TODO: Handle error.
}
}
func ExampleInsert() {
m := spanner.Insert("Users", []string{"name", "email"}, []interface{}{"alice", "a@example.com"})
_ = m // TODO: use with Client.Apply or in a ReadWriteTransaction.
}
func ExampleInsertMap() {
m := spanner.InsertMap("Users", map[string]interface{}{
"name": "alice",
"email": "a@example.com",
})
_ = m // TODO: use with Client.Apply or in a ReadWriteTransaction.
}
func ExampleInsertStruct() {
type User struct {
Name, Email string
}
u := User{Name: "alice", Email: "a@example.com"}
m, err := spanner.InsertStruct("Users", u)
if err != nil {
// TODO: Handle error.
}
_ = m // TODO: use with Client.Apply or in a ReadWriteTransaction.
}
func ExampleDelete() {
m := spanner.Delete("Users", spanner.Key{"alice"})
_ = m // TODO: use with Client.Apply or in a ReadWriteTransaction.
}
func ExampleDelete_keyRange() {
m := spanner.Delete("Users", spanner.KeyRange{
Start: spanner.Key{"alice"},
End: spanner.Key{"bob"},
Kind: spanner.ClosedClosed,
})
_ = m // TODO: use with Client.Apply or in a ReadWriteTransaction.
}
func ExampleRowIterator_Next() {
ctx := context.Background()
client, err := spanner.NewClient(ctx, myDB)
if err != nil {
// TODO: Handle error.
}
iter := client.Single().Query(ctx, spanner.NewStatement("SELECT FirstName FROM Singers"))
defer iter.Stop()
for {
row, err := iter.Next()
if err == iterator.Done {
break
}
if err != nil {
// TODO: Handle error.
}
var firstName string
if err := row.Column(0, &firstName); err != nil {
// TODO: Handle error.
}
fmt.Println(firstName)
}
}
func ExampleRowIterator_Do() {
ctx := context.Background()
client, err := spanner.NewClient(ctx, myDB)
if err != nil {
// TODO: Handle error.
}
iter := client.Single().Query(ctx, spanner.NewStatement("SELECT FirstName FROM Singers"))
err = iter.Do(func(r *spanner.Row) error {
var firstName string
if err := r.Column(0, &firstName); err != nil {
return err
}
fmt.Println(firstName)
return nil
})
if err != nil {
// TODO: Handle error.
}
}
func ExampleRow_Size() {
ctx := context.Background()
client, err := spanner.NewClient(ctx, myDB)
if err != nil {
// TODO: Handle error.
}
row, err := client.Single().ReadRow(ctx, "Accounts", spanner.Key{"alice"}, []string{"name", "balance"})
if err != nil {
// TODO: Handle error.
}
fmt.Println(row.Size()) // 2
}
func ExampleRow_ColumnName() {
ctx := context.Background()
client, err := spanner.NewClient(ctx, myDB)
if err != nil {
// TODO: Handle error.
}
row, err := client.Single().ReadRow(ctx, "Accounts", spanner.Key{"alice"}, []string{"name", "balance"})
if err != nil {
// TODO: Handle error.
}
fmt.Println(row.ColumnName(1)) // "balance"
}
func ExampleRow_ColumnIndex() {
ctx := context.Background()
client, err := spanner.NewClient(ctx, myDB)
if err != nil {
// TODO: Handle error.
}
row, err := client.Single().ReadRow(ctx, "Accounts", spanner.Key{"alice"}, []string{"name", "balance"})
if err != nil {
// TODO: Handle error.
}
index, err := row.ColumnIndex("balance")
if err != nil {
// TODO: Handle error.
}
fmt.Println(index)
}
func ExampleRow_ColumnNames() {
ctx := context.Background()
client, err := spanner.NewClient(ctx, myDB)
if err != nil {
// TODO: Handle error.
}
row, err := client.Single().ReadRow(ctx, "Accounts", spanner.Key{"alice"}, []string{"name", "balance"})
if err != nil {
// TODO: Handle error.
}
fmt.Println(row.ColumnNames())
}
func ExampleRow_ColumnByName() {
ctx := context.Background()
client, err := spanner.NewClient(ctx, myDB)
if err != nil {
// TODO: Handle error.
}
row, err := client.Single().ReadRow(ctx, "Accounts", spanner.Key{"alice"}, []string{"name", "balance"})
if err != nil {
// TODO: Handle error.
}
var balance int64
if err := row.ColumnByName("balance", &balance); err != nil {
// TODO: Handle error.
}
fmt.Println(balance)
}
func ExampleRow_Columns() {
ctx := context.Background()
client, err := spanner.NewClient(ctx, myDB)
if err != nil {
// TODO: Handle error.
}
row, err := client.Single().ReadRow(ctx, "Accounts", spanner.Key{"alice"}, []string{"name", "balance"})
if err != nil {
// TODO: Handle error.
}
var name string
var balance int64
if err := row.Columns(&name, &balance); err != nil {
// TODO: Handle error.
}
fmt.Println(name, balance)
}
func ExampleRow_ToStruct() {
ctx := context.Background()
client, err := spanner.NewClient(ctx, myDB)
if err != nil {
// TODO: Handle error.
}
row, err := client.Single().ReadRow(ctx, "Accounts", spanner.Key{"alice"}, []string{"name", "balance"})
if err != nil {
// TODO: Handle error.
}
type Account struct {
Name string
Balance int64
}
var acct Account
if err := row.ToStruct(&acct); err != nil {
// TODO: Handle error.
}
fmt.Println(acct)
}
func ExampleRow_ToStructLenient() {
ctx := context.Background()
client, err := spanner.NewClient(ctx, myDB)
if err != nil {
// TODO: Handle error.
}
row, err := client.Single().ReadRow(ctx, "Accounts", spanner.Key{"alice"}, []string{"accountID", "name", "balance"})
if err != nil {
// TODO: Handle error.
}
type Account struct {
Name string
Balance int64
NickName string
}
var acct Account
if err := row.ToStructLenient(&acct); err != nil {
// TODO: Handle error.
}
fmt.Println(acct)
}
func ExampleReadOnlyTransaction_Read() {
ctx := context.Background()
client, err := spanner.NewClient(ctx, myDB)
if err != nil {
// TODO: Handle error.
}
iter := client.Single().Read(ctx, "Users",
spanner.KeySets(spanner.Key{"alice"}, spanner.Key{"bob"}),
[]string{"name", "email"})
_ = iter // TODO: iterate using Next or Do.
}
func ExampleReadOnlyTransaction_ReadUsingIndex() {
ctx := context.Background()
client, err := spanner.NewClient(ctx, myDB)
if err != nil {
// TODO: Handle error.
}
iter := client.Single().ReadUsingIndex(ctx, "Users",
"UsersByEmail",
spanner.KeySets(spanner.Key{"a@example.com"}, spanner.Key{"b@example.com"}),
[]string{"name", "email"})
_ = iter // TODO: iterate using Next or Do.
}
func ExampleReadOnlyTransaction_ReadWithOptions() {
ctx := context.Background()
client, err := spanner.NewClient(ctx, myDB)
if err != nil {
// TODO: Handle error.
}
// Use an index, and limit to 100 rows at most.
iter := client.Single().ReadWithOptions(ctx, "Users",
spanner.KeySets(spanner.Key{"a@example.com"}, spanner.Key{"b@example.com"}),
[]string{"name", "email"}, &spanner.ReadOptions{
Index: "UsersByEmail",
Limit: 100,
})
_ = iter // TODO: iterate using Next or Do.
}
func ExampleReadOnlyTransaction_ReadRow() {
ctx := context.Background()
client, err := spanner.NewClient(ctx, myDB)
if err != nil {
// TODO: Handle error.
}
row, err := client.Single().ReadRow(ctx, "Users", spanner.Key{"alice"},
[]string{"name", "email"})
if err != nil {
// TODO: Handle error.
}
_ = row // TODO: use row
}
func ExampleReadOnlyTransaction_Query() {
ctx := context.Background()
client, err := spanner.NewClient(ctx, myDB)
if err != nil {
// TODO: Handle error.
}
iter := client.Single().Query(ctx, spanner.NewStatement("SELECT FirstName FROM Singers"))
_ = iter // TODO: iterate using Next or Do.
}
func ExampleNewStatement() {
stmt := spanner.NewStatement("SELECT FirstName, LastName FROM SINGERS WHERE LastName >= @start")
stmt.Params["start"] = "Dylan"
// TODO: Use stmt in Query.
}
func ExampleNewStatement_structLiteral() {
stmt := spanner.Statement{
SQL: `SELECT FirstName, LastName FROM SINGERS WHERE LastName = ("Lea", "Martin")`,
}
_ = stmt // TODO: Use stmt in Query.
}
func ExampleStructParam() {
stmt := spanner.Statement{
SQL: "SELECT * FROM SINGERS WHERE (FirstName, LastName) = @singerinfo",
Params: map[string]interface{}{
"singerinfo": struct {
FirstName string
LastName string
}{"Bob", "Dylan"},
},
}
_ = stmt // TODO: Use stmt in Query.
}
func ExampleArrayOfStructParam() {
stmt := spanner.Statement{
SQL: "SELECT * FROM SINGERS WHERE (FirstName, LastName) IN UNNEST(@singerinfo)",
Params: map[string]interface{}{
"singerinfo": []struct {
FirstName string
LastName string
}{
{"Ringo", "Starr"},
{"John", "Lennon"},
},
},
}
_ = stmt // TODO: Use stmt in Query.
}
func ExampleReadOnlyTransaction_Timestamp() {
ctx := context.Background()
client, err := spanner.NewClient(ctx, myDB)
if err != nil {
// TODO: Handle error.
}
txn := client.Single()
row, err := txn.ReadRow(ctx, "Users", spanner.Key{"alice"},
[]string{"name", "email"})
if err != nil {
// TODO: Handle error.
}
readTimestamp, err := txn.Timestamp()
if err != nil {
// TODO: Handle error.
}
fmt.Println("read happened at", readTimestamp)
_ = row // TODO: use row
}
func ExampleReadOnlyTransaction_WithTimestampBound() {
ctx := context.Background()
client, err := spanner.NewClient(ctx, myDB)
if err != nil {
// TODO: Handle error.
}
txn := client.Single().WithTimestampBound(spanner.MaxStaleness(30 * time.Second))
row, err := txn.ReadRow(ctx, "Users", spanner.Key{"alice"}, []string{"name", "email"})
if err != nil {
// TODO: Handle error.
}
_ = row // TODO: use row
readTimestamp, err := txn.Timestamp()
if err != nil {
// TODO: Handle error.
}
fmt.Println("read happened at", readTimestamp)
}
func ExampleGenericColumnValue_Decode() {
// In real applications, rows can be retrieved by methods like client.Single().ReadRow().
row, err := spanner.NewRow([]string{"intCol", "strCol"}, []interface{}{42, "my-text"})
if err != nil {
// TODO: Handle error.
}
for i := 0; i < row.Size(); i++ {
var col spanner.GenericColumnValue
if err := row.Column(i, &col); err != nil {
// TODO: Handle error.
}
switch col.Type.Code {
case sppb.TypeCode_INT64:
var v int64
if err := col.Decode(&v); err != nil {
// TODO: Handle error.
}
fmt.Println("int", v)
case sppb.TypeCode_STRING:
var v string
if err := col.Decode(&v); err != nil {
// TODO: Handle error.
}
fmt.Println("string", v)
}
}
// Output:
// int 42
// string my-text
}
func ExampleClient_BatchReadOnlyTransaction() {
ctx := context.Background()
var (
client *spanner.Client
txn *spanner.BatchReadOnlyTransaction
err error
)
if client, err = spanner.NewClient(ctx, myDB); err != nil {
// TODO: Handle error.
}
defer client.Close()
if txn, err = client.BatchReadOnlyTransaction(ctx, spanner.StrongRead()); err != nil {
// TODO: Handle error.
}
defer txn.Close()
// Singer represents the elements in a row from the Singers table.
type Singer struct {
SingerID int64
FirstName string
LastName string
SingerInfo []byte
}
stmt := spanner.Statement{SQL: "SELECT * FROM Singers;"}
partitions, err := txn.PartitionQuery(ctx, stmt, spanner.PartitionOptions{})
if err != nil {
// TODO: Handle error.
}
// Note: here we use multiple goroutines, but you should use separate
// processes/machines.
wg := sync.WaitGroup{}
for i, p := range partitions {
wg.Add(1)
go func(i int, p *spanner.Partition) {
defer wg.Done()
iter := txn.Execute(ctx, p)
defer iter.Stop()
for {
row, err := iter.Next()
if err == iterator.Done {
break
} else if err != nil {
// TODO: Handle error.
}
var s Singer
if err := row.ToStruct(&s); err != nil {
// TODO: Handle error.
}
_ = s // TODO: Process the row.
}
}(i, p)
}
wg.Wait()
}
func ExampleCommitTimestamp() {
ctx := context.Background()
client, err := spanner.NewClient(ctx, myDB)
if err != nil {
// TODO: Handle error.
}
type account struct {
User string
Creation spanner.NullTime // time.Time can also be used if column isNOT NULL
}
a := account{User: "Joe", Creation: spanner.NullTime{spanner.CommitTimestamp, true}}
m, err := spanner.InsertStruct("Accounts", a)
if err != nil {
// TODO: Handle error.
}
_, err = client.Apply(ctx, []*spanner.Mutation{m}, spanner.ApplyAtLeastOnce())
if err != nil {
// TODO: Handle error.
}
if r, e := client.Single().ReadRow(ctx, "Accounts", spanner.Key{"Joe"}, []string{"User", "Creation"}); e != nil {
// TODO: Handle error.
} else {
var got account
if err := r.ToStruct(&got); err != nil {
// TODO: Handle error.
}
_ = got // TODO: Process row.
}
}
func ExampleStatement_regexpContains() {
// Search for accounts with valid emails using regexp as per:
// https://cloud.google.com/spanner/docs/functions-and-operators#regexp_contains
stmt := spanner.Statement{
SQL: `SELECT * FROM users WHERE REGEXP_CONTAINS(email, @valid_email)`,
Params: map[string]interface{}{
"valid_email": `\Q@\E`,
},
}
_ = stmt // TODO: Use stmt in a query.
}
func ExampleKeySets() {
ctx := context.Background()
client, err := spanner.NewClient(ctx, myDB)
if err != nil {
// TODO: Handle error.
}
// Get some rows from the Accounts table using a secondary index. In this case we get all users who are in Georgia.
iter := client.Single().ReadUsingIndex(context.Background(), "Accounts", "idx_state", spanner.Key{"GA"}, []string{"state"})
// Create a empty KeySet by calling the KeySets function with no parameters.
ks := spanner.KeySets()
// Loop the results of a previous query iterator.
for {
row, err := iter.Next()
if err == iterator.Done {
break
} else if err != nil {
// TODO: Handle error.
}
var id string
err = row.ColumnByName("User", &id)
if err != nil {
// TODO: Handle error.
}
ks = spanner.KeySets(spanner.KeySets(spanner.Key{id}, ks))
}
_ = ks //TODO: Go use the KeySet in another query.
}
func ExampleNewReadWriteStmtBasedTransaction() {
ctx := context.Background()
client, err := spanner.NewClient(ctx, myDB)
if err != nil {
// TODO: Handle error.
}
defer client.Close()
f := func(tx *spanner.ReadWriteStmtBasedTransaction) error {
var balance int64
row, err := tx.ReadRow(ctx, "Accounts", spanner.Key{"alice"}, []string{"balance"})
if err != nil {
return err
}
if err := row.Column(0, &balance); err != nil {
return err
}
if balance <= 10 {
return errors.New("insufficient funds in account")
}
balance -= 10
m := spanner.Update("Accounts", []string{"user", "balance"}, []interface{}{"alice", balance})
return tx.BufferWrite([]*spanner.Mutation{m})
}
for {
tx, err := spanner.NewReadWriteStmtBasedTransaction(ctx, client)
if err != nil {
// TODO: Handle error.
break
}
err = f(tx)
if err != nil && status.Code(err) != codes.Aborted {
tx.Rollback(ctx)
// TODO: Handle error.
break
} else if err == nil {
_, err = tx.Commit(ctx)
if err == nil {
break
} else if status.Code(err) != codes.Aborted {
// TODO: Handle error.
break
}
}
// Set a default sleep time if the server delay is absent.
delay := 10 * time.Millisecond
if serverDelay, hasServerDelay := spanner.ExtractRetryDelay(err); hasServerDelay {
delay = serverDelay
}
time.Sleep(delay)
}
}