mirror of
https://github.com/Luzifer/nginx-sso.git
synced 2024-12-21 05:11:17 +00:00
327 lines
10 KiB
Go
327 lines
10 KiB
Go
|
/*
|
||
|
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 provides a client for reading and writing to Cloud Spanner
|
||
|
databases. See the packages under admin for clients that operate on databases
|
||
|
and instances.
|
||
|
|
||
|
See https://cloud.google.com/spanner/docs/getting-started/go/ for an introduction
|
||
|
to Cloud Spanner and additional help on using this API.
|
||
|
|
||
|
See https://godoc.org/cloud.google.com/go for authentication, timeouts,
|
||
|
connection pooling and similar aspects of this package.
|
||
|
|
||
|
|
||
|
Creating a Client
|
||
|
|
||
|
To start working with this package, create a client that refers to the database
|
||
|
of interest:
|
||
|
|
||
|
ctx := context.Background()
|
||
|
client, err := spanner.NewClient(ctx, "projects/P/instances/I/databases/D")
|
||
|
if err != nil {
|
||
|
// TODO: Handle error.
|
||
|
}
|
||
|
defer client.Close()
|
||
|
|
||
|
Remember to close the client after use to free up the sessions in the session
|
||
|
pool.
|
||
|
|
||
|
|
||
|
Simple Reads and Writes
|
||
|
|
||
|
Two Client methods, Apply and Single, work well for simple reads and writes. As
|
||
|
a quick introduction, here we write a new row to the database and read it back:
|
||
|
|
||
|
_, err := client.Apply(ctx, []*spanner.Mutation{
|
||
|
spanner.Insert("Users",
|
||
|
[]string{"name", "email"},
|
||
|
[]interface{}{"alice", "a@example.com"})})
|
||
|
if err != nil {
|
||
|
// TODO: Handle error.
|
||
|
}
|
||
|
row, err := client.Single().ReadRow(ctx, "Users",
|
||
|
spanner.Key{"alice"}, []string{"email"})
|
||
|
if err != nil {
|
||
|
// TODO: Handle error.
|
||
|
}
|
||
|
|
||
|
All the methods used above are discussed in more detail below.
|
||
|
|
||
|
|
||
|
Keys
|
||
|
|
||
|
Every Cloud Spanner row has a unique key, composed of one or more columns.
|
||
|
Construct keys with a literal of type Key:
|
||
|
|
||
|
key1 := spanner.Key{"alice"}
|
||
|
|
||
|
|
||
|
KeyRanges
|
||
|
|
||
|
The keys of a Cloud Spanner table are ordered. You can specify ranges of keys
|
||
|
using the KeyRange type:
|
||
|
|
||
|
kr1 := spanner.KeyRange{Start: key1, End: key2}
|
||
|
|
||
|
By default, a KeyRange includes its start key but not its end key. Use
|
||
|
the Kind field to specify other boundary conditions:
|
||
|
|
||
|
// include both keys
|
||
|
kr2 := spanner.KeyRange{Start: key1, End: key2, Kind: spanner.ClosedClosed}
|
||
|
|
||
|
|
||
|
KeySets
|
||
|
|
||
|
A KeySet represents a set of keys. A single Key or KeyRange can act as a KeySet. Use
|
||
|
the KeySets function to build the union of several KeySets:
|
||
|
|
||
|
ks1 := spanner.KeySets(key1, key2, kr1, kr2)
|
||
|
|
||
|
AllKeys returns a KeySet that refers to all the keys in a table:
|
||
|
|
||
|
ks2 := spanner.AllKeys()
|
||
|
|
||
|
|
||
|
Transactions
|
||
|
|
||
|
All Cloud Spanner reads and writes occur inside transactions. There are two
|
||
|
types of transactions, read-only and read-write. Read-only transactions cannot
|
||
|
change the database, do not acquire locks, and may access either the current
|
||
|
database state or states in the past. Read-write transactions can read the
|
||
|
database before writing to it, and always apply to the most recent database
|
||
|
state.
|
||
|
|
||
|
|
||
|
Single Reads
|
||
|
|
||
|
The simplest and fastest transaction is a ReadOnlyTransaction that supports a
|
||
|
single read operation. Use Client.Single to create such a transaction. You can
|
||
|
chain the call to Single with a call to a Read method.
|
||
|
|
||
|
When you only want one row whose key you know, use ReadRow. Provide the table
|
||
|
name, key, and the columns you want to read:
|
||
|
|
||
|
row, err := client.Single().ReadRow(ctx, "Accounts", spanner.Key{"alice"}, []string{"balance"})
|
||
|
|
||
|
Read multiple rows with the Read method. It takes a table name, KeySet, and list
|
||
|
of columns:
|
||
|
|
||
|
iter := client.Single().Read(ctx, "Accounts", keyset1, columns)
|
||
|
|
||
|
Read returns a RowIterator. You can call the Do method on the iterator and pass
|
||
|
a callback:
|
||
|
|
||
|
err := iter.Do(func(row *Row) error {
|
||
|
// TODO: use row
|
||
|
return nil
|
||
|
})
|
||
|
|
||
|
RowIterator also follows the standard pattern for the Google
|
||
|
Cloud Client Libraries:
|
||
|
|
||
|
defer iter.Stop()
|
||
|
for {
|
||
|
row, err := iter.Next()
|
||
|
if err == iterator.Done {
|
||
|
break
|
||
|
}
|
||
|
if err != nil {
|
||
|
// TODO: Handle error.
|
||
|
}
|
||
|
// TODO: use row
|
||
|
}
|
||
|
|
||
|
Always call Stop when you finish using an iterator this way, whether or not you
|
||
|
iterate to the end. (Failing to call Stop could lead you to exhaust the
|
||
|
database's session quota.)
|
||
|
|
||
|
To read rows with an index, use ReadUsingIndex.
|
||
|
|
||
|
Statements
|
||
|
|
||
|
The most general form of reading uses SQL statements. Construct a Statement
|
||
|
with NewStatement, setting any parameters using the Statement's Params map:
|
||
|
|
||
|
stmt := spanner.NewStatement("SELECT First, Last FROM SINGERS WHERE Last >= @start")
|
||
|
stmt.Params["start"] = "Dylan"
|
||
|
|
||
|
You can also construct a Statement directly with a struct literal, providing
|
||
|
your own map of parameters.
|
||
|
|
||
|
Use the Query method to run the statement and obtain an iterator:
|
||
|
|
||
|
iter := client.Single().Query(ctx, stmt)
|
||
|
|
||
|
|
||
|
Rows
|
||
|
|
||
|
Once you have a Row, via an iterator or a call to ReadRow, you can extract
|
||
|
column values in several ways. Pass in a pointer to a Go variable of the
|
||
|
appropriate type when you extract a value.
|
||
|
|
||
|
You can extract by column position or name:
|
||
|
|
||
|
err := row.Column(0, &name)
|
||
|
err = row.ColumnByName("balance", &balance)
|
||
|
|
||
|
You can extract all the columns at once:
|
||
|
|
||
|
err = row.Columns(&name, &balance)
|
||
|
|
||
|
Or you can define a Go struct that corresponds to your columns, and extract
|
||
|
into that:
|
||
|
|
||
|
var s struct { Name string; Balance int64 }
|
||
|
err = row.ToStruct(&s)
|
||
|
|
||
|
|
||
|
For Cloud Spanner columns that may contain NULL, use one of the NullXXX types,
|
||
|
like NullString:
|
||
|
|
||
|
var ns spanner.NullString
|
||
|
if err := row.Column(0, &ns); err != nil {
|
||
|
// TODO: Handle error.
|
||
|
}
|
||
|
if ns.Valid {
|
||
|
fmt.Println(ns.StringVal)
|
||
|
} else {
|
||
|
fmt.Println("column is NULL")
|
||
|
}
|
||
|
|
||
|
|
||
|
Multiple Reads
|
||
|
|
||
|
To perform more than one read in a transaction, use ReadOnlyTransaction:
|
||
|
|
||
|
txn := client.ReadOnlyTransaction()
|
||
|
defer txn.Close()
|
||
|
iter := txn.Query(ctx, stmt1)
|
||
|
// ...
|
||
|
iter = txn.Query(ctx, stmt2)
|
||
|
// ...
|
||
|
|
||
|
You must call Close when you are done with the transaction.
|
||
|
|
||
|
|
||
|
Timestamps and Timestamp Bounds
|
||
|
|
||
|
Cloud Spanner read-only transactions conceptually perform all their reads at a
|
||
|
single moment in time, called the transaction's read timestamp. Once a read has
|
||
|
started, you can call ReadOnlyTransaction's Timestamp method to obtain the read
|
||
|
timestamp.
|
||
|
|
||
|
By default, a transaction will pick the most recent time (a time where all
|
||
|
previously committed transactions are visible) for its reads. This provides the
|
||
|
freshest data, but may involve some delay. You can often get a quicker response
|
||
|
if you are willing to tolerate "stale" data. You can control the read timestamp
|
||
|
selected by a transaction by calling the WithTimestampBound method on the
|
||
|
transaction before using it. For example, to perform a query on data that is at
|
||
|
most one minute stale, use
|
||
|
|
||
|
client.Single().
|
||
|
WithTimestampBound(spanner.MaxStaleness(1*time.Minute)).
|
||
|
Query(ctx, stmt)
|
||
|
|
||
|
See the documentation of TimestampBound for more details.
|
||
|
|
||
|
|
||
|
Mutations
|
||
|
|
||
|
To write values to a Cloud Spanner database, construct a Mutation. The spanner
|
||
|
package has functions for inserting, updating and deleting rows. Except for the
|
||
|
Delete methods, which take a Key or KeyRange, each mutation-building function
|
||
|
comes in three varieties.
|
||
|
|
||
|
One takes lists of columns and values along with the table name:
|
||
|
|
||
|
m1 := spanner.Insert("Users",
|
||
|
[]string{"name", "email"},
|
||
|
[]interface{}{"alice", "a@example.com"})
|
||
|
|
||
|
One takes a map from column names to values:
|
||
|
|
||
|
m2 := spanner.InsertMap("Users", map[string]interface{}{
|
||
|
"name": "alice",
|
||
|
"email": "a@example.com",
|
||
|
})
|
||
|
|
||
|
And the third accepts a struct value, and determines the columns from the
|
||
|
struct field names:
|
||
|
|
||
|
type User struct { Name, Email string }
|
||
|
u := User{Name: "alice", Email: "a@example.com"}
|
||
|
m3, err := spanner.InsertStruct("Users", u)
|
||
|
|
||
|
|
||
|
Writes
|
||
|
|
||
|
To apply a list of mutations to the database, use Apply:
|
||
|
|
||
|
_, err := client.Apply(ctx, []*spanner.Mutation{m1, m2, m3})
|
||
|
|
||
|
If you need to read before writing in a single transaction, use a
|
||
|
ReadWriteTransaction. ReadWriteTransactions may abort and need to be retried.
|
||
|
You pass in a function to ReadWriteTransaction, and the client will handle the
|
||
|
retries automatically. Use the transaction's BufferWrite method to buffer
|
||
|
mutations, which will all be executed at the end of the transaction:
|
||
|
|
||
|
_, 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})
|
||
|
txn.BufferWrite([]*spanner.Mutation{m})
|
||
|
|
||
|
// The buffered mutation will be committed. If the commit
|
||
|
// fails with an IsAborted error, this function will be called
|
||
|
// again.
|
||
|
return nil
|
||
|
})
|
||
|
|
||
|
|
||
|
DML and Partitioned DML
|
||
|
|
||
|
Spanner supports DML statements like INSERT, UPDATE and DELETE. Use
|
||
|
ReadWriteTransaction.Update to run DML statements. It returns the number of rows
|
||
|
affected. (You can call use ReadWriteTransaction.Query with a DML statement. The first
|
||
|
call to Next on the resulting RowIterator will return iterator.Done, and the RowCount
|
||
|
field of the iterator will hold the number of affected rows.)
|
||
|
|
||
|
For large databases, it may be more efficient to partition the DML statement. Use
|
||
|
client.PartitionedUpdate to run a DML statement in this way. Not all DML statements
|
||
|
can be partitioned.
|
||
|
|
||
|
Tracing
|
||
|
|
||
|
This client has been instrumented to use OpenCensus tracing (http://opencensus.io).
|
||
|
To enable tracing, see "Enabling Tracing for a Program" at
|
||
|
https://godoc.org/go.opencensus.io/trace. OpenCensus tracing requires Go 1.8 or higher.
|
||
|
*/
|
||
|
package spanner // import "cloud.google.com/go/spanner"
|