1
0
mirror of https://github.com/kubernetes-sigs/descheduler.git synced 2026-01-26 13:29:11 +01:00

bump(*): kubernetes release-1.16.0 dependencies

This commit is contained in:
Mike Dame
2019-10-12 11:11:43 -04:00
parent 5af668e89a
commit 1652ba7976
28121 changed files with 3491095 additions and 2280257 deletions

View File

@@ -0,0 +1,531 @@
// Copyright 2019 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
//
// https://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.
// Code generated by gapic-generator. DO NOT EDIT.
package database
import (
"context"
"fmt"
"math"
"time"
"cloud.google.com/go/longrunning"
lroauto "cloud.google.com/go/longrunning/autogen"
"github.com/golang/protobuf/proto"
gax "github.com/googleapis/gax-go/v2"
"google.golang.org/api/iterator"
"google.golang.org/api/option"
"google.golang.org/api/transport"
iampb "google.golang.org/genproto/googleapis/iam/v1"
longrunningpb "google.golang.org/genproto/googleapis/longrunning"
databasepb "google.golang.org/genproto/googleapis/spanner/admin/database/v1"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
)
// DatabaseAdminCallOptions contains the retry settings for each method of DatabaseAdminClient.
type DatabaseAdminCallOptions struct {
ListDatabases []gax.CallOption
CreateDatabase []gax.CallOption
GetDatabase []gax.CallOption
UpdateDatabaseDdl []gax.CallOption
DropDatabase []gax.CallOption
GetDatabaseDdl []gax.CallOption
SetIamPolicy []gax.CallOption
GetIamPolicy []gax.CallOption
TestIamPermissions []gax.CallOption
}
func defaultDatabaseAdminClientOptions() []option.ClientOption {
return []option.ClientOption{
option.WithEndpoint("spanner.googleapis.com:443"),
option.WithScopes(DefaultAuthScopes()...),
}
}
func defaultDatabaseAdminCallOptions() *DatabaseAdminCallOptions {
retry := map[[2]string][]gax.CallOption{
{"default", "idempotent"}: {
gax.WithRetry(func() gax.Retryer {
return gax.OnCodes([]codes.Code{
codes.DeadlineExceeded,
codes.Unavailable,
}, gax.Backoff{
Initial: 1000 * time.Millisecond,
Max: 32000 * time.Millisecond,
Multiplier: 1.3,
})
}),
},
}
return &DatabaseAdminCallOptions{
ListDatabases: retry[[2]string{"default", "idempotent"}],
CreateDatabase: retry[[2]string{"default", "non_idempotent"}],
GetDatabase: retry[[2]string{"default", "idempotent"}],
UpdateDatabaseDdl: retry[[2]string{"default", "idempotent"}],
DropDatabase: retry[[2]string{"default", "idempotent"}],
GetDatabaseDdl: retry[[2]string{"default", "idempotent"}],
SetIamPolicy: retry[[2]string{"default", "non_idempotent"}],
GetIamPolicy: retry[[2]string{"default", "idempotent"}],
TestIamPermissions: retry[[2]string{"default", "non_idempotent"}],
}
}
// DatabaseAdminClient is a client for interacting with Cloud Spanner Database Admin API.
//
// Methods, except Close, may be called concurrently. However, fields must not be modified concurrently with method calls.
type DatabaseAdminClient struct {
// The connection to the service.
conn *grpc.ClientConn
// The gRPC API client.
databaseAdminClient databasepb.DatabaseAdminClient
// LROClient is used internally to handle longrunning operations.
// It is exposed so that its CallOptions can be modified if required.
// Users should not Close this client.
LROClient *lroauto.OperationsClient
// The call options for this service.
CallOptions *DatabaseAdminCallOptions
// The x-goog-* metadata to be sent with each request.
xGoogMetadata metadata.MD
}
// NewDatabaseAdminClient creates a new database admin client.
//
// Cloud Spanner Database Admin API
//
// The Cloud Spanner Database Admin API can be used to create, drop, and
// list databases. It also enables updating the schema of pre-existing
// databases.
func NewDatabaseAdminClient(ctx context.Context, opts ...option.ClientOption) (*DatabaseAdminClient, error) {
conn, err := transport.DialGRPC(ctx, append(defaultDatabaseAdminClientOptions(), opts...)...)
if err != nil {
return nil, err
}
c := &DatabaseAdminClient{
conn: conn,
CallOptions: defaultDatabaseAdminCallOptions(),
databaseAdminClient: databasepb.NewDatabaseAdminClient(conn),
}
c.setGoogleClientInfo()
c.LROClient, err = lroauto.NewOperationsClient(ctx, option.WithGRPCConn(conn))
if err != nil {
// This error "should not happen", since we are just reusing old connection
// and never actually need to dial.
// If this does happen, we could leak conn. However, we cannot close conn:
// If the user invoked the function with option.WithGRPCConn,
// we would close a connection that's still in use.
// TODO(pongad): investigate error conditions.
return nil, err
}
return c, nil
}
// Connection returns the client's connection to the API service.
func (c *DatabaseAdminClient) Connection() *grpc.ClientConn {
return c.conn
}
// Close closes the connection to the API service. The user should invoke this when
// the client is no longer required.
func (c *DatabaseAdminClient) Close() error {
return c.conn.Close()
}
// setGoogleClientInfo sets the name and version of the application in
// the `x-goog-api-client` header passed on each request. Intended for
// use by Google-written clients.
func (c *DatabaseAdminClient) setGoogleClientInfo(keyval ...string) {
kv := append([]string{"gl-go", versionGo()}, keyval...)
kv = append(kv, "gapic", versionClient, "gax", gax.Version, "grpc", grpc.Version)
c.xGoogMetadata = metadata.Pairs("x-goog-api-client", gax.XGoogHeader(kv...))
}
// ListDatabases lists Cloud Spanner databases.
func (c *DatabaseAdminClient) ListDatabases(ctx context.Context, req *databasepb.ListDatabasesRequest, opts ...gax.CallOption) *DatabaseIterator {
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "parent", req.GetParent()))
ctx = insertMetadata(ctx, c.xGoogMetadata, md)
opts = append(c.CallOptions.ListDatabases[0:len(c.CallOptions.ListDatabases):len(c.CallOptions.ListDatabases)], opts...)
it := &DatabaseIterator{}
req = proto.Clone(req).(*databasepb.ListDatabasesRequest)
it.InternalFetch = func(pageSize int, pageToken string) ([]*databasepb.Database, string, error) {
var resp *databasepb.ListDatabasesResponse
req.PageToken = pageToken
if pageSize > math.MaxInt32 {
req.PageSize = math.MaxInt32
} else {
req.PageSize = int32(pageSize)
}
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
resp, err = c.databaseAdminClient.ListDatabases(ctx, req, settings.GRPC...)
return err
}, opts...)
if err != nil {
return nil, "", err
}
return resp.Databases, resp.NextPageToken, nil
}
fetch := func(pageSize int, pageToken string) (string, error) {
items, nextPageToken, err := it.InternalFetch(pageSize, pageToken)
if err != nil {
return "", err
}
it.items = append(it.items, items...)
return nextPageToken, nil
}
it.pageInfo, it.nextFunc = iterator.NewPageInfo(fetch, it.bufLen, it.takeBuf)
it.pageInfo.MaxSize = int(req.PageSize)
return it
}
// CreateDatabase creates a new Cloud Spanner database and starts to prepare it for serving.
// The returned [long-running operation][google.longrunning.Operation] will
// have a name of the format <database_name>/operations/<operation_id> and
// can be used to track preparation of the database. The
// [metadata][google.longrunning.Operation.metadata] field type is
// [CreateDatabaseMetadata][google.spanner.admin.database.v1.CreateDatabaseMetadata].
// The [response][google.longrunning.Operation.response] field type is
// [Database][google.spanner.admin.database.v1.Database], if successful.
func (c *DatabaseAdminClient) CreateDatabase(ctx context.Context, req *databasepb.CreateDatabaseRequest, opts ...gax.CallOption) (*CreateDatabaseOperation, error) {
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "parent", req.GetParent()))
ctx = insertMetadata(ctx, c.xGoogMetadata, md)
opts = append(c.CallOptions.CreateDatabase[0:len(c.CallOptions.CreateDatabase):len(c.CallOptions.CreateDatabase)], opts...)
var resp *longrunningpb.Operation
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
resp, err = c.databaseAdminClient.CreateDatabase(ctx, req, settings.GRPC...)
return err
}, opts...)
if err != nil {
return nil, err
}
return &CreateDatabaseOperation{
lro: longrunning.InternalNewOperation(c.LROClient, resp),
}, nil
}
// GetDatabase gets the state of a Cloud Spanner database.
func (c *DatabaseAdminClient) GetDatabase(ctx context.Context, req *databasepb.GetDatabaseRequest, opts ...gax.CallOption) (*databasepb.Database, error) {
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "name", req.GetName()))
ctx = insertMetadata(ctx, c.xGoogMetadata, md)
opts = append(c.CallOptions.GetDatabase[0:len(c.CallOptions.GetDatabase):len(c.CallOptions.GetDatabase)], opts...)
var resp *databasepb.Database
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
resp, err = c.databaseAdminClient.GetDatabase(ctx, req, settings.GRPC...)
return err
}, opts...)
if err != nil {
return nil, err
}
return resp, nil
}
// UpdateDatabaseDdl updates the schema of a Cloud Spanner database by
// creating/altering/dropping tables, columns, indexes, etc. The returned
// [long-running operation][google.longrunning.Operation] will have a name of
// the format <database_name>/operations/<operation_id> and can be used to
// track execution of the schema change(s). The
// [metadata][google.longrunning.Operation.metadata] field type is
// [UpdateDatabaseDdlMetadata][google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata].
// The operation has no response.
func (c *DatabaseAdminClient) UpdateDatabaseDdl(ctx context.Context, req *databasepb.UpdateDatabaseDdlRequest, opts ...gax.CallOption) (*UpdateDatabaseDdlOperation, error) {
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "database", req.GetDatabase()))
ctx = insertMetadata(ctx, c.xGoogMetadata, md)
opts = append(c.CallOptions.UpdateDatabaseDdl[0:len(c.CallOptions.UpdateDatabaseDdl):len(c.CallOptions.UpdateDatabaseDdl)], opts...)
var resp *longrunningpb.Operation
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
resp, err = c.databaseAdminClient.UpdateDatabaseDdl(ctx, req, settings.GRPC...)
return err
}, opts...)
if err != nil {
return nil, err
}
return &UpdateDatabaseDdlOperation{
lro: longrunning.InternalNewOperation(c.LROClient, resp),
}, nil
}
// DropDatabase drops (aka deletes) a Cloud Spanner database.
func (c *DatabaseAdminClient) DropDatabase(ctx context.Context, req *databasepb.DropDatabaseRequest, opts ...gax.CallOption) error {
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "database", req.GetDatabase()))
ctx = insertMetadata(ctx, c.xGoogMetadata, md)
opts = append(c.CallOptions.DropDatabase[0:len(c.CallOptions.DropDatabase):len(c.CallOptions.DropDatabase)], opts...)
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
_, err = c.databaseAdminClient.DropDatabase(ctx, req, settings.GRPC...)
return err
}, opts...)
return err
}
// GetDatabaseDdl returns the schema of a Cloud Spanner database as a list of formatted
// DDL statements. This method does not show pending schema updates, those may
// be queried using the [Operations][google.longrunning.Operations] API.
func (c *DatabaseAdminClient) GetDatabaseDdl(ctx context.Context, req *databasepb.GetDatabaseDdlRequest, opts ...gax.CallOption) (*databasepb.GetDatabaseDdlResponse, error) {
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "database", req.GetDatabase()))
ctx = insertMetadata(ctx, c.xGoogMetadata, md)
opts = append(c.CallOptions.GetDatabaseDdl[0:len(c.CallOptions.GetDatabaseDdl):len(c.CallOptions.GetDatabaseDdl)], opts...)
var resp *databasepb.GetDatabaseDdlResponse
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
resp, err = c.databaseAdminClient.GetDatabaseDdl(ctx, req, settings.GRPC...)
return err
}, opts...)
if err != nil {
return nil, err
}
return resp, nil
}
// SetIamPolicy sets the access control policy on a database resource. Replaces any
// existing policy.
//
// Authorization requires spanner.databases.setIamPolicy permission on
// [resource][google.iam.v1.SetIamPolicyRequest.resource].
func (c *DatabaseAdminClient) SetIamPolicy(ctx context.Context, req *iampb.SetIamPolicyRequest, opts ...gax.CallOption) (*iampb.Policy, error) {
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "resource", req.GetResource()))
ctx = insertMetadata(ctx, c.xGoogMetadata, md)
opts = append(c.CallOptions.SetIamPolicy[0:len(c.CallOptions.SetIamPolicy):len(c.CallOptions.SetIamPolicy)], opts...)
var resp *iampb.Policy
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
resp, err = c.databaseAdminClient.SetIamPolicy(ctx, req, settings.GRPC...)
return err
}, opts...)
if err != nil {
return nil, err
}
return resp, nil
}
// GetIamPolicy gets the access control policy for a database resource. Returns an empty
// policy if a database exists but does not have a policy set.
//
// Authorization requires spanner.databases.getIamPolicy permission on
// [resource][google.iam.v1.GetIamPolicyRequest.resource].
func (c *DatabaseAdminClient) GetIamPolicy(ctx context.Context, req *iampb.GetIamPolicyRequest, opts ...gax.CallOption) (*iampb.Policy, error) {
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "resource", req.GetResource()))
ctx = insertMetadata(ctx, c.xGoogMetadata, md)
opts = append(c.CallOptions.GetIamPolicy[0:len(c.CallOptions.GetIamPolicy):len(c.CallOptions.GetIamPolicy)], opts...)
var resp *iampb.Policy
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
resp, err = c.databaseAdminClient.GetIamPolicy(ctx, req, settings.GRPC...)
return err
}, opts...)
if err != nil {
return nil, err
}
return resp, nil
}
// TestIamPermissions returns permissions that the caller has on the specified database resource.
//
// Attempting this RPC on a non-existent Cloud Spanner database will result in
// a NOT_FOUND error if the user has spanner.databases.list permission on
// the containing Cloud Spanner instance. Otherwise returns an empty set of
// permissions.
func (c *DatabaseAdminClient) TestIamPermissions(ctx context.Context, req *iampb.TestIamPermissionsRequest, opts ...gax.CallOption) (*iampb.TestIamPermissionsResponse, error) {
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "resource", req.GetResource()))
ctx = insertMetadata(ctx, c.xGoogMetadata, md)
opts = append(c.CallOptions.TestIamPermissions[0:len(c.CallOptions.TestIamPermissions):len(c.CallOptions.TestIamPermissions)], opts...)
var resp *iampb.TestIamPermissionsResponse
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
resp, err = c.databaseAdminClient.TestIamPermissions(ctx, req, settings.GRPC...)
return err
}, opts...)
if err != nil {
return nil, err
}
return resp, nil
}
// DatabaseIterator manages a stream of *databasepb.Database.
type DatabaseIterator struct {
items []*databasepb.Database
pageInfo *iterator.PageInfo
nextFunc func() error
// InternalFetch is for use by the Google Cloud Libraries only.
// It is not part of the stable interface of this package.
//
// InternalFetch returns results from a single call to the underlying RPC.
// The number of results is no greater than pageSize.
// If there are no more results, nextPageToken is empty and err is nil.
InternalFetch func(pageSize int, pageToken string) (results []*databasepb.Database, nextPageToken string, err error)
}
// PageInfo supports pagination. See the google.golang.org/api/iterator package for details.
func (it *DatabaseIterator) PageInfo() *iterator.PageInfo {
return it.pageInfo
}
// Next returns the next result. Its second return value is iterator.Done if there are no more
// results. Once Next returns Done, all subsequent calls will return Done.
func (it *DatabaseIterator) Next() (*databasepb.Database, error) {
var item *databasepb.Database
if err := it.nextFunc(); err != nil {
return item, err
}
item = it.items[0]
it.items = it.items[1:]
return item, nil
}
func (it *DatabaseIterator) bufLen() int {
return len(it.items)
}
func (it *DatabaseIterator) takeBuf() interface{} {
b := it.items
it.items = nil
return b
}
// CreateDatabaseOperation manages a long-running operation from CreateDatabase.
type CreateDatabaseOperation struct {
lro *longrunning.Operation
}
// CreateDatabaseOperation returns a new CreateDatabaseOperation from a given name.
// The name must be that of a previously created CreateDatabaseOperation, possibly from a different process.
func (c *DatabaseAdminClient) CreateDatabaseOperation(name string) *CreateDatabaseOperation {
return &CreateDatabaseOperation{
lro: longrunning.InternalNewOperation(c.LROClient, &longrunningpb.Operation{Name: name}),
}
}
// Wait blocks until the long-running operation is completed, returning the response and any errors encountered.
//
// See documentation of Poll for error-handling information.
func (op *CreateDatabaseOperation) Wait(ctx context.Context, opts ...gax.CallOption) (*databasepb.Database, error) {
var resp databasepb.Database
if err := op.lro.WaitWithInterval(ctx, &resp, 45000*time.Millisecond, opts...); err != nil {
return nil, err
}
return &resp, nil
}
// Poll fetches the latest state of the long-running operation.
//
// Poll also fetches the latest metadata, which can be retrieved by Metadata.
//
// If Poll fails, the error is returned and op is unmodified. If Poll succeeds and
// the operation has completed with failure, the error is returned and op.Done will return true.
// If Poll succeeds and the operation has completed successfully,
// op.Done will return true, and the response of the operation is returned.
// If Poll succeeds and the operation has not completed, the returned response and error are both nil.
func (op *CreateDatabaseOperation) Poll(ctx context.Context, opts ...gax.CallOption) (*databasepb.Database, error) {
var resp databasepb.Database
if err := op.lro.Poll(ctx, &resp, opts...); err != nil {
return nil, err
}
if !op.Done() {
return nil, nil
}
return &resp, nil
}
// Metadata returns metadata associated with the long-running operation.
// Metadata itself does not contact the server, but Poll does.
// To get the latest metadata, call this method after a successful call to Poll.
// If the metadata is not available, the returned metadata and error are both nil.
func (op *CreateDatabaseOperation) Metadata() (*databasepb.CreateDatabaseMetadata, error) {
var meta databasepb.CreateDatabaseMetadata
if err := op.lro.Metadata(&meta); err == longrunning.ErrNoMetadata {
return nil, nil
} else if err != nil {
return nil, err
}
return &meta, nil
}
// Done reports whether the long-running operation has completed.
func (op *CreateDatabaseOperation) Done() bool {
return op.lro.Done()
}
// Name returns the name of the long-running operation.
// The name is assigned by the server and is unique within the service from which the operation is created.
func (op *CreateDatabaseOperation) Name() string {
return op.lro.Name()
}
// UpdateDatabaseDdlOperation manages a long-running operation from UpdateDatabaseDdl.
type UpdateDatabaseDdlOperation struct {
lro *longrunning.Operation
}
// UpdateDatabaseDdlOperation returns a new UpdateDatabaseDdlOperation from a given name.
// The name must be that of a previously created UpdateDatabaseDdlOperation, possibly from a different process.
func (c *DatabaseAdminClient) UpdateDatabaseDdlOperation(name string) *UpdateDatabaseDdlOperation {
return &UpdateDatabaseDdlOperation{
lro: longrunning.InternalNewOperation(c.LROClient, &longrunningpb.Operation{Name: name}),
}
}
// Wait blocks until the long-running operation is completed, returning any error encountered.
//
// See documentation of Poll for error-handling information.
func (op *UpdateDatabaseDdlOperation) Wait(ctx context.Context, opts ...gax.CallOption) error {
return op.lro.WaitWithInterval(ctx, nil, 45000*time.Millisecond, opts...)
}
// Poll fetches the latest state of the long-running operation.
//
// Poll also fetches the latest metadata, which can be retrieved by Metadata.
//
// If Poll fails, the error is returned and op is unmodified. If Poll succeeds and
// the operation has completed with failure, the error is returned and op.Done will return true.
// If Poll succeeds and the operation has completed successfully, op.Done will return true.
func (op *UpdateDatabaseDdlOperation) Poll(ctx context.Context, opts ...gax.CallOption) error {
return op.lro.Poll(ctx, nil, opts...)
}
// Metadata returns metadata associated with the long-running operation.
// Metadata itself does not contact the server, but Poll does.
// To get the latest metadata, call this method after a successful call to Poll.
// If the metadata is not available, the returned metadata and error are both nil.
func (op *UpdateDatabaseDdlOperation) Metadata() (*databasepb.UpdateDatabaseDdlMetadata, error) {
var meta databasepb.UpdateDatabaseDdlMetadata
if err := op.lro.Metadata(&meta); err == longrunning.ErrNoMetadata {
return nil, nil
} else if err != nil {
return nil, err
}
return &meta, nil
}
// Done reports whether the long-running operation has completed.
func (op *UpdateDatabaseDdlOperation) Done() bool {
return op.lro.Done()
}
// Name returns the name of the long-running operation.
// The name is assigned by the server and is unique within the service from which the operation is created.
func (op *UpdateDatabaseDdlOperation) Name() string {
return op.lro.Name()
}

View File

@@ -0,0 +1,208 @@
// Copyright 2019 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
//
// https://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.
// Code generated by gapic-generator. DO NOT EDIT.
package database_test
import (
"context"
database "cloud.google.com/go/spanner/admin/database/apiv1"
"google.golang.org/api/iterator"
iampb "google.golang.org/genproto/googleapis/iam/v1"
databasepb "google.golang.org/genproto/googleapis/spanner/admin/database/v1"
)
func ExampleNewDatabaseAdminClient() {
ctx := context.Background()
c, err := database.NewDatabaseAdminClient(ctx)
if err != nil {
// TODO: Handle error.
}
// TODO: Use client.
_ = c
}
func ExampleDatabaseAdminClient_ListDatabases() {
ctx := context.Background()
c, err := database.NewDatabaseAdminClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &databasepb.ListDatabasesRequest{
// TODO: Fill request struct fields.
}
it := c.ListDatabases(ctx, req)
for {
resp, err := it.Next()
if err == iterator.Done {
break
}
if err != nil {
// TODO: Handle error.
}
// TODO: Use resp.
_ = resp
}
}
func ExampleDatabaseAdminClient_CreateDatabase() {
ctx := context.Background()
c, err := database.NewDatabaseAdminClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &databasepb.CreateDatabaseRequest{
// TODO: Fill request struct fields.
}
op, err := c.CreateDatabase(ctx, req)
if err != nil {
// TODO: Handle error.
}
resp, err := op.Wait(ctx)
if err != nil {
// TODO: Handle error.
}
// TODO: Use resp.
_ = resp
}
func ExampleDatabaseAdminClient_GetDatabase() {
ctx := context.Background()
c, err := database.NewDatabaseAdminClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &databasepb.GetDatabaseRequest{
// TODO: Fill request struct fields.
}
resp, err := c.GetDatabase(ctx, req)
if err != nil {
// TODO: Handle error.
}
// TODO: Use resp.
_ = resp
}
func ExampleDatabaseAdminClient_UpdateDatabaseDdl() {
ctx := context.Background()
c, err := database.NewDatabaseAdminClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &databasepb.UpdateDatabaseDdlRequest{
// TODO: Fill request struct fields.
}
op, err := c.UpdateDatabaseDdl(ctx, req)
if err != nil {
// TODO: Handle error.
}
err = op.Wait(ctx)
// TODO: Handle error.
}
func ExampleDatabaseAdminClient_DropDatabase() {
ctx := context.Background()
c, err := database.NewDatabaseAdminClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &databasepb.DropDatabaseRequest{
// TODO: Fill request struct fields.
}
err = c.DropDatabase(ctx, req)
if err != nil {
// TODO: Handle error.
}
}
func ExampleDatabaseAdminClient_GetDatabaseDdl() {
ctx := context.Background()
c, err := database.NewDatabaseAdminClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &databasepb.GetDatabaseDdlRequest{
// TODO: Fill request struct fields.
}
resp, err := c.GetDatabaseDdl(ctx, req)
if err != nil {
// TODO: Handle error.
}
// TODO: Use resp.
_ = resp
}
func ExampleDatabaseAdminClient_SetIamPolicy() {
ctx := context.Background()
c, err := database.NewDatabaseAdminClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &iampb.SetIamPolicyRequest{
// TODO: Fill request struct fields.
}
resp, err := c.SetIamPolicy(ctx, req)
if err != nil {
// TODO: Handle error.
}
// TODO: Use resp.
_ = resp
}
func ExampleDatabaseAdminClient_GetIamPolicy() {
ctx := context.Background()
c, err := database.NewDatabaseAdminClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &iampb.GetIamPolicyRequest{
// TODO: Fill request struct fields.
}
resp, err := c.GetIamPolicy(ctx, req)
if err != nil {
// TODO: Handle error.
}
// TODO: Use resp.
_ = resp
}
func ExampleDatabaseAdminClient_TestIamPermissions() {
ctx := context.Background()
c, err := database.NewDatabaseAdminClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &iampb.TestIamPermissionsRequest{
// TODO: Fill request struct fields.
}
resp, err := c.TestIamPermissions(ctx, req)
if err != nil {
// TODO: Handle error.
}
// TODO: Use resp.
_ = resp
}

View File

@@ -0,0 +1,100 @@
// Copyright 2019 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
//
// https://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.
// Code generated by gapic-generator. DO NOT EDIT.
// Package database is an auto-generated package for the
// Cloud Spanner Database Admin API.
//
// NOTE: This package is in alpha. It is not stable, and is likely to change.
//
//
// Use of Context
//
// The ctx passed to NewClient is used for authentication requests and
// for creating the underlying connection, but is not used for subsequent calls.
// Individual methods on the client use the ctx given to them.
//
// To close the open connection, use the Close() method.
//
// For information about setting deadlines, reusing contexts, and more
// please visit godoc.org/cloud.google.com/go.
package database // import "cloud.google.com/go/spanner/admin/database/apiv1"
import (
"context"
"runtime"
"strings"
"unicode"
"google.golang.org/grpc/metadata"
)
func insertMetadata(ctx context.Context, mds ...metadata.MD) context.Context {
out, _ := metadata.FromOutgoingContext(ctx)
out = out.Copy()
for _, md := range mds {
for k, v := range md {
out[k] = append(out[k], v...)
}
}
return metadata.NewOutgoingContext(ctx, out)
}
// DefaultAuthScopes reports the default set of authentication scopes to use with this package.
func DefaultAuthScopes() []string {
return []string{
"https://www.googleapis.com/auth/cloud-platform",
"https://www.googleapis.com/auth/spanner.admin",
}
}
// versionGo returns the Go runtime version. The returned string
// has no whitespace, suitable for reporting in header.
func versionGo() string {
const develPrefix = "devel +"
s := runtime.Version()
if strings.HasPrefix(s, develPrefix) {
s = s[len(develPrefix):]
if p := strings.IndexFunc(s, unicode.IsSpace); p >= 0 {
s = s[:p]
}
return s
}
notSemverRune := func(r rune) bool {
return strings.IndexRune("0123456789.", r) < 0
}
if strings.HasPrefix(s, "go1") {
s = s[2:]
var prerelease string
if p := strings.IndexFunc(s, notSemverRune); p >= 0 {
s, prerelease = s[:p], s[p:]
}
if strings.HasSuffix(s, ".") {
s += "0"
} else if strings.Count(s, ".") < 2 {
s += ".0"
}
if prerelease != "" {
s += "-" + prerelease
}
return s
}
return "UNKNOWN"
}
const versionClient = "20190404"

View File

@@ -0,0 +1,798 @@
// Copyright 2019 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
//
// https://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.
// Code generated by gapic-generator. DO NOT EDIT.
package database
import (
emptypb "github.com/golang/protobuf/ptypes/empty"
iampb "google.golang.org/genproto/googleapis/iam/v1"
longrunningpb "google.golang.org/genproto/googleapis/longrunning"
databasepb "google.golang.org/genproto/googleapis/spanner/admin/database/v1"
)
import (
"context"
"flag"
"fmt"
"io"
"log"
"net"
"os"
"strings"
"testing"
"github.com/golang/protobuf/proto"
"github.com/golang/protobuf/ptypes"
"google.golang.org/api/option"
status "google.golang.org/genproto/googleapis/rpc/status"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
gstatus "google.golang.org/grpc/status"
)
var _ = io.EOF
var _ = ptypes.MarshalAny
var _ status.Status
type mockDatabaseAdminServer struct {
// Embed for forward compatibility.
// Tests will keep working if more methods are added
// in the future.
databasepb.DatabaseAdminServer
reqs []proto.Message
// If set, all calls return this error.
err error
// responses to return if err == nil
resps []proto.Message
}
func (s *mockDatabaseAdminServer) ListDatabases(ctx context.Context, req *databasepb.ListDatabasesRequest) (*databasepb.ListDatabasesResponse, error) {
md, _ := metadata.FromIncomingContext(ctx)
if xg := md["x-goog-api-client"]; len(xg) == 0 || !strings.Contains(xg[0], "gl-go/") {
return nil, fmt.Errorf("x-goog-api-client = %v, expected gl-go key", xg)
}
s.reqs = append(s.reqs, req)
if s.err != nil {
return nil, s.err
}
return s.resps[0].(*databasepb.ListDatabasesResponse), nil
}
func (s *mockDatabaseAdminServer) CreateDatabase(ctx context.Context, req *databasepb.CreateDatabaseRequest) (*longrunningpb.Operation, error) {
md, _ := metadata.FromIncomingContext(ctx)
if xg := md["x-goog-api-client"]; len(xg) == 0 || !strings.Contains(xg[0], "gl-go/") {
return nil, fmt.Errorf("x-goog-api-client = %v, expected gl-go key", xg)
}
s.reqs = append(s.reqs, req)
if s.err != nil {
return nil, s.err
}
return s.resps[0].(*longrunningpb.Operation), nil
}
func (s *mockDatabaseAdminServer) GetDatabase(ctx context.Context, req *databasepb.GetDatabaseRequest) (*databasepb.Database, error) {
md, _ := metadata.FromIncomingContext(ctx)
if xg := md["x-goog-api-client"]; len(xg) == 0 || !strings.Contains(xg[0], "gl-go/") {
return nil, fmt.Errorf("x-goog-api-client = %v, expected gl-go key", xg)
}
s.reqs = append(s.reqs, req)
if s.err != nil {
return nil, s.err
}
return s.resps[0].(*databasepb.Database), nil
}
func (s *mockDatabaseAdminServer) UpdateDatabaseDdl(ctx context.Context, req *databasepb.UpdateDatabaseDdlRequest) (*longrunningpb.Operation, error) {
md, _ := metadata.FromIncomingContext(ctx)
if xg := md["x-goog-api-client"]; len(xg) == 0 || !strings.Contains(xg[0], "gl-go/") {
return nil, fmt.Errorf("x-goog-api-client = %v, expected gl-go key", xg)
}
s.reqs = append(s.reqs, req)
if s.err != nil {
return nil, s.err
}
return s.resps[0].(*longrunningpb.Operation), nil
}
func (s *mockDatabaseAdminServer) DropDatabase(ctx context.Context, req *databasepb.DropDatabaseRequest) (*emptypb.Empty, error) {
md, _ := metadata.FromIncomingContext(ctx)
if xg := md["x-goog-api-client"]; len(xg) == 0 || !strings.Contains(xg[0], "gl-go/") {
return nil, fmt.Errorf("x-goog-api-client = %v, expected gl-go key", xg)
}
s.reqs = append(s.reqs, req)
if s.err != nil {
return nil, s.err
}
return s.resps[0].(*emptypb.Empty), nil
}
func (s *mockDatabaseAdminServer) GetDatabaseDdl(ctx context.Context, req *databasepb.GetDatabaseDdlRequest) (*databasepb.GetDatabaseDdlResponse, error) {
md, _ := metadata.FromIncomingContext(ctx)
if xg := md["x-goog-api-client"]; len(xg) == 0 || !strings.Contains(xg[0], "gl-go/") {
return nil, fmt.Errorf("x-goog-api-client = %v, expected gl-go key", xg)
}
s.reqs = append(s.reqs, req)
if s.err != nil {
return nil, s.err
}
return s.resps[0].(*databasepb.GetDatabaseDdlResponse), nil
}
func (s *mockDatabaseAdminServer) SetIamPolicy(ctx context.Context, req *iampb.SetIamPolicyRequest) (*iampb.Policy, error) {
md, _ := metadata.FromIncomingContext(ctx)
if xg := md["x-goog-api-client"]; len(xg) == 0 || !strings.Contains(xg[0], "gl-go/") {
return nil, fmt.Errorf("x-goog-api-client = %v, expected gl-go key", xg)
}
s.reqs = append(s.reqs, req)
if s.err != nil {
return nil, s.err
}
return s.resps[0].(*iampb.Policy), nil
}
func (s *mockDatabaseAdminServer) GetIamPolicy(ctx context.Context, req *iampb.GetIamPolicyRequest) (*iampb.Policy, error) {
md, _ := metadata.FromIncomingContext(ctx)
if xg := md["x-goog-api-client"]; len(xg) == 0 || !strings.Contains(xg[0], "gl-go/") {
return nil, fmt.Errorf("x-goog-api-client = %v, expected gl-go key", xg)
}
s.reqs = append(s.reqs, req)
if s.err != nil {
return nil, s.err
}
return s.resps[0].(*iampb.Policy), nil
}
func (s *mockDatabaseAdminServer) TestIamPermissions(ctx context.Context, req *iampb.TestIamPermissionsRequest) (*iampb.TestIamPermissionsResponse, error) {
md, _ := metadata.FromIncomingContext(ctx)
if xg := md["x-goog-api-client"]; len(xg) == 0 || !strings.Contains(xg[0], "gl-go/") {
return nil, fmt.Errorf("x-goog-api-client = %v, expected gl-go key", xg)
}
s.reqs = append(s.reqs, req)
if s.err != nil {
return nil, s.err
}
return s.resps[0].(*iampb.TestIamPermissionsResponse), nil
}
// clientOpt is the option tests should use to connect to the test server.
// It is initialized by TestMain.
var clientOpt option.ClientOption
var (
mockDatabaseAdmin mockDatabaseAdminServer
)
func TestMain(m *testing.M) {
flag.Parse()
serv := grpc.NewServer()
databasepb.RegisterDatabaseAdminServer(serv, &mockDatabaseAdmin)
lis, err := net.Listen("tcp", "localhost:0")
if err != nil {
log.Fatal(err)
}
go serv.Serve(lis)
conn, err := grpc.Dial(lis.Addr().String(), grpc.WithInsecure())
if err != nil {
log.Fatal(err)
}
clientOpt = option.WithGRPCConn(conn)
os.Exit(m.Run())
}
func TestDatabaseAdminListDatabases(t *testing.T) {
var nextPageToken string = ""
var databasesElement *databasepb.Database = &databasepb.Database{}
var databases = []*databasepb.Database{databasesElement}
var expectedResponse = &databasepb.ListDatabasesResponse{
NextPageToken: nextPageToken,
Databases: databases,
}
mockDatabaseAdmin.err = nil
mockDatabaseAdmin.reqs = nil
mockDatabaseAdmin.resps = append(mockDatabaseAdmin.resps[:0], expectedResponse)
var formattedParent string = fmt.Sprintf("projects/%s/instances/%s", "[PROJECT]", "[INSTANCE]")
var request = &databasepb.ListDatabasesRequest{
Parent: formattedParent,
}
c, err := NewDatabaseAdminClient(context.Background(), clientOpt)
if err != nil {
t.Fatal(err)
}
resp, err := c.ListDatabases(context.Background(), request).Next()
if err != nil {
t.Fatal(err)
}
if want, got := request, mockDatabaseAdmin.reqs[0]; !proto.Equal(want, got) {
t.Errorf("wrong request %q, want %q", got, want)
}
want := (interface{})(expectedResponse.Databases[0])
got := (interface{})(resp)
var ok bool
switch want := (want).(type) {
case proto.Message:
ok = proto.Equal(want, got.(proto.Message))
default:
ok = want == got
}
if !ok {
t.Errorf("wrong response %q, want %q)", got, want)
}
}
func TestDatabaseAdminListDatabasesError(t *testing.T) {
errCode := codes.PermissionDenied
mockDatabaseAdmin.err = gstatus.Error(errCode, "test error")
var formattedParent string = fmt.Sprintf("projects/%s/instances/%s", "[PROJECT]", "[INSTANCE]")
var request = &databasepb.ListDatabasesRequest{
Parent: formattedParent,
}
c, err := NewDatabaseAdminClient(context.Background(), clientOpt)
if err != nil {
t.Fatal(err)
}
resp, err := c.ListDatabases(context.Background(), request).Next()
if st, ok := gstatus.FromError(err); !ok {
t.Errorf("got error %v, expected grpc error", err)
} else if c := st.Code(); c != errCode {
t.Errorf("got error code %q, want %q", c, errCode)
}
_ = resp
}
func TestDatabaseAdminCreateDatabase(t *testing.T) {
var name string = "name3373707"
var expectedResponse = &databasepb.Database{
Name: name,
}
mockDatabaseAdmin.err = nil
mockDatabaseAdmin.reqs = nil
any, err := ptypes.MarshalAny(expectedResponse)
if err != nil {
t.Fatal(err)
}
mockDatabaseAdmin.resps = append(mockDatabaseAdmin.resps[:0], &longrunningpb.Operation{
Name: "longrunning-test",
Done: true,
Result: &longrunningpb.Operation_Response{Response: any},
})
var formattedParent string = fmt.Sprintf("projects/%s/instances/%s", "[PROJECT]", "[INSTANCE]")
var createStatement string = "createStatement552974828"
var request = &databasepb.CreateDatabaseRequest{
Parent: formattedParent,
CreateStatement: createStatement,
}
c, err := NewDatabaseAdminClient(context.Background(), clientOpt)
if err != nil {
t.Fatal(err)
}
respLRO, err := c.CreateDatabase(context.Background(), request)
if err != nil {
t.Fatal(err)
}
resp, err := respLRO.Wait(context.Background())
if err != nil {
t.Fatal(err)
}
if want, got := request, mockDatabaseAdmin.reqs[0]; !proto.Equal(want, got) {
t.Errorf("wrong request %q, want %q", got, want)
}
if want, got := expectedResponse, resp; !proto.Equal(want, got) {
t.Errorf("wrong response %q, want %q)", got, want)
}
}
func TestDatabaseAdminCreateDatabaseError(t *testing.T) {
errCode := codes.PermissionDenied
mockDatabaseAdmin.err = nil
mockDatabaseAdmin.resps = append(mockDatabaseAdmin.resps[:0], &longrunningpb.Operation{
Name: "longrunning-test",
Done: true,
Result: &longrunningpb.Operation_Error{
Error: &status.Status{
Code: int32(errCode),
Message: "test error",
},
},
})
var formattedParent string = fmt.Sprintf("projects/%s/instances/%s", "[PROJECT]", "[INSTANCE]")
var createStatement string = "createStatement552974828"
var request = &databasepb.CreateDatabaseRequest{
Parent: formattedParent,
CreateStatement: createStatement,
}
c, err := NewDatabaseAdminClient(context.Background(), clientOpt)
if err != nil {
t.Fatal(err)
}
respLRO, err := c.CreateDatabase(context.Background(), request)
if err != nil {
t.Fatal(err)
}
resp, err := respLRO.Wait(context.Background())
if st, ok := gstatus.FromError(err); !ok {
t.Errorf("got error %v, expected grpc error", err)
} else if c := st.Code(); c != errCode {
t.Errorf("got error code %q, want %q", c, errCode)
}
_ = resp
}
func TestDatabaseAdminGetDatabase(t *testing.T) {
var name2 string = "name2-1052831874"
var expectedResponse = &databasepb.Database{
Name: name2,
}
mockDatabaseAdmin.err = nil
mockDatabaseAdmin.reqs = nil
mockDatabaseAdmin.resps = append(mockDatabaseAdmin.resps[:0], expectedResponse)
var formattedName string = fmt.Sprintf("projects/%s/instances/%s/databases/%s", "[PROJECT]", "[INSTANCE]", "[DATABASE]")
var request = &databasepb.GetDatabaseRequest{
Name: formattedName,
}
c, err := NewDatabaseAdminClient(context.Background(), clientOpt)
if err != nil {
t.Fatal(err)
}
resp, err := c.GetDatabase(context.Background(), request)
if err != nil {
t.Fatal(err)
}
if want, got := request, mockDatabaseAdmin.reqs[0]; !proto.Equal(want, got) {
t.Errorf("wrong request %q, want %q", got, want)
}
if want, got := expectedResponse, resp; !proto.Equal(want, got) {
t.Errorf("wrong response %q, want %q)", got, want)
}
}
func TestDatabaseAdminGetDatabaseError(t *testing.T) {
errCode := codes.PermissionDenied
mockDatabaseAdmin.err = gstatus.Error(errCode, "test error")
var formattedName string = fmt.Sprintf("projects/%s/instances/%s/databases/%s", "[PROJECT]", "[INSTANCE]", "[DATABASE]")
var request = &databasepb.GetDatabaseRequest{
Name: formattedName,
}
c, err := NewDatabaseAdminClient(context.Background(), clientOpt)
if err != nil {
t.Fatal(err)
}
resp, err := c.GetDatabase(context.Background(), request)
if st, ok := gstatus.FromError(err); !ok {
t.Errorf("got error %v, expected grpc error", err)
} else if c := st.Code(); c != errCode {
t.Errorf("got error code %q, want %q", c, errCode)
}
_ = resp
}
func TestDatabaseAdminUpdateDatabaseDdl(t *testing.T) {
var expectedResponse *emptypb.Empty = &emptypb.Empty{}
mockDatabaseAdmin.err = nil
mockDatabaseAdmin.reqs = nil
any, err := ptypes.MarshalAny(expectedResponse)
if err != nil {
t.Fatal(err)
}
mockDatabaseAdmin.resps = append(mockDatabaseAdmin.resps[:0], &longrunningpb.Operation{
Name: "longrunning-test",
Done: true,
Result: &longrunningpb.Operation_Response{Response: any},
})
var formattedDatabase string = fmt.Sprintf("projects/%s/instances/%s/databases/%s", "[PROJECT]", "[INSTANCE]", "[DATABASE]")
var statements []string = nil
var request = &databasepb.UpdateDatabaseDdlRequest{
Database: formattedDatabase,
Statements: statements,
}
c, err := NewDatabaseAdminClient(context.Background(), clientOpt)
if err != nil {
t.Fatal(err)
}
respLRO, err := c.UpdateDatabaseDdl(context.Background(), request)
if err != nil {
t.Fatal(err)
}
err = respLRO.Wait(context.Background())
if err != nil {
t.Fatal(err)
}
if want, got := request, mockDatabaseAdmin.reqs[0]; !proto.Equal(want, got) {
t.Errorf("wrong request %q, want %q", got, want)
}
}
func TestDatabaseAdminUpdateDatabaseDdlError(t *testing.T) {
errCode := codes.PermissionDenied
mockDatabaseAdmin.err = nil
mockDatabaseAdmin.resps = append(mockDatabaseAdmin.resps[:0], &longrunningpb.Operation{
Name: "longrunning-test",
Done: true,
Result: &longrunningpb.Operation_Error{
Error: &status.Status{
Code: int32(errCode),
Message: "test error",
},
},
})
var formattedDatabase string = fmt.Sprintf("projects/%s/instances/%s/databases/%s", "[PROJECT]", "[INSTANCE]", "[DATABASE]")
var statements []string = nil
var request = &databasepb.UpdateDatabaseDdlRequest{
Database: formattedDatabase,
Statements: statements,
}
c, err := NewDatabaseAdminClient(context.Background(), clientOpt)
if err != nil {
t.Fatal(err)
}
respLRO, err := c.UpdateDatabaseDdl(context.Background(), request)
if err != nil {
t.Fatal(err)
}
err = respLRO.Wait(context.Background())
if st, ok := gstatus.FromError(err); !ok {
t.Errorf("got error %v, expected grpc error", err)
} else if c := st.Code(); c != errCode {
t.Errorf("got error code %q, want %q", c, errCode)
}
}
func TestDatabaseAdminDropDatabase(t *testing.T) {
var expectedResponse *emptypb.Empty = &emptypb.Empty{}
mockDatabaseAdmin.err = nil
mockDatabaseAdmin.reqs = nil
mockDatabaseAdmin.resps = append(mockDatabaseAdmin.resps[:0], expectedResponse)
var formattedDatabase string = fmt.Sprintf("projects/%s/instances/%s/databases/%s", "[PROJECT]", "[INSTANCE]", "[DATABASE]")
var request = &databasepb.DropDatabaseRequest{
Database: formattedDatabase,
}
c, err := NewDatabaseAdminClient(context.Background(), clientOpt)
if err != nil {
t.Fatal(err)
}
err = c.DropDatabase(context.Background(), request)
if err != nil {
t.Fatal(err)
}
if want, got := request, mockDatabaseAdmin.reqs[0]; !proto.Equal(want, got) {
t.Errorf("wrong request %q, want %q", got, want)
}
}
func TestDatabaseAdminDropDatabaseError(t *testing.T) {
errCode := codes.PermissionDenied
mockDatabaseAdmin.err = gstatus.Error(errCode, "test error")
var formattedDatabase string = fmt.Sprintf("projects/%s/instances/%s/databases/%s", "[PROJECT]", "[INSTANCE]", "[DATABASE]")
var request = &databasepb.DropDatabaseRequest{
Database: formattedDatabase,
}
c, err := NewDatabaseAdminClient(context.Background(), clientOpt)
if err != nil {
t.Fatal(err)
}
err = c.DropDatabase(context.Background(), request)
if st, ok := gstatus.FromError(err); !ok {
t.Errorf("got error %v, expected grpc error", err)
} else if c := st.Code(); c != errCode {
t.Errorf("got error code %q, want %q", c, errCode)
}
}
func TestDatabaseAdminGetDatabaseDdl(t *testing.T) {
var expectedResponse *databasepb.GetDatabaseDdlResponse = &databasepb.GetDatabaseDdlResponse{}
mockDatabaseAdmin.err = nil
mockDatabaseAdmin.reqs = nil
mockDatabaseAdmin.resps = append(mockDatabaseAdmin.resps[:0], expectedResponse)
var formattedDatabase string = fmt.Sprintf("projects/%s/instances/%s/databases/%s", "[PROJECT]", "[INSTANCE]", "[DATABASE]")
var request = &databasepb.GetDatabaseDdlRequest{
Database: formattedDatabase,
}
c, err := NewDatabaseAdminClient(context.Background(), clientOpt)
if err != nil {
t.Fatal(err)
}
resp, err := c.GetDatabaseDdl(context.Background(), request)
if err != nil {
t.Fatal(err)
}
if want, got := request, mockDatabaseAdmin.reqs[0]; !proto.Equal(want, got) {
t.Errorf("wrong request %q, want %q", got, want)
}
if want, got := expectedResponse, resp; !proto.Equal(want, got) {
t.Errorf("wrong response %q, want %q)", got, want)
}
}
func TestDatabaseAdminGetDatabaseDdlError(t *testing.T) {
errCode := codes.PermissionDenied
mockDatabaseAdmin.err = gstatus.Error(errCode, "test error")
var formattedDatabase string = fmt.Sprintf("projects/%s/instances/%s/databases/%s", "[PROJECT]", "[INSTANCE]", "[DATABASE]")
var request = &databasepb.GetDatabaseDdlRequest{
Database: formattedDatabase,
}
c, err := NewDatabaseAdminClient(context.Background(), clientOpt)
if err != nil {
t.Fatal(err)
}
resp, err := c.GetDatabaseDdl(context.Background(), request)
if st, ok := gstatus.FromError(err); !ok {
t.Errorf("got error %v, expected grpc error", err)
} else if c := st.Code(); c != errCode {
t.Errorf("got error code %q, want %q", c, errCode)
}
_ = resp
}
func TestDatabaseAdminSetIamPolicy(t *testing.T) {
var version int32 = 351608024
var etag []byte = []byte("21")
var expectedResponse = &iampb.Policy{
Version: version,
Etag: etag,
}
mockDatabaseAdmin.err = nil
mockDatabaseAdmin.reqs = nil
mockDatabaseAdmin.resps = append(mockDatabaseAdmin.resps[:0], expectedResponse)
var formattedResource string = fmt.Sprintf("projects/%s/instances/%s/databases/%s", "[PROJECT]", "[INSTANCE]", "[DATABASE]")
var policy *iampb.Policy = &iampb.Policy{}
var request = &iampb.SetIamPolicyRequest{
Resource: formattedResource,
Policy: policy,
}
c, err := NewDatabaseAdminClient(context.Background(), clientOpt)
if err != nil {
t.Fatal(err)
}
resp, err := c.SetIamPolicy(context.Background(), request)
if err != nil {
t.Fatal(err)
}
if want, got := request, mockDatabaseAdmin.reqs[0]; !proto.Equal(want, got) {
t.Errorf("wrong request %q, want %q", got, want)
}
if want, got := expectedResponse, resp; !proto.Equal(want, got) {
t.Errorf("wrong response %q, want %q)", got, want)
}
}
func TestDatabaseAdminSetIamPolicyError(t *testing.T) {
errCode := codes.PermissionDenied
mockDatabaseAdmin.err = gstatus.Error(errCode, "test error")
var formattedResource string = fmt.Sprintf("projects/%s/instances/%s/databases/%s", "[PROJECT]", "[INSTANCE]", "[DATABASE]")
var policy *iampb.Policy = &iampb.Policy{}
var request = &iampb.SetIamPolicyRequest{
Resource: formattedResource,
Policy: policy,
}
c, err := NewDatabaseAdminClient(context.Background(), clientOpt)
if err != nil {
t.Fatal(err)
}
resp, err := c.SetIamPolicy(context.Background(), request)
if st, ok := gstatus.FromError(err); !ok {
t.Errorf("got error %v, expected grpc error", err)
} else if c := st.Code(); c != errCode {
t.Errorf("got error code %q, want %q", c, errCode)
}
_ = resp
}
func TestDatabaseAdminGetIamPolicy(t *testing.T) {
var version int32 = 351608024
var etag []byte = []byte("21")
var expectedResponse = &iampb.Policy{
Version: version,
Etag: etag,
}
mockDatabaseAdmin.err = nil
mockDatabaseAdmin.reqs = nil
mockDatabaseAdmin.resps = append(mockDatabaseAdmin.resps[:0], expectedResponse)
var formattedResource string = fmt.Sprintf("projects/%s/instances/%s/databases/%s", "[PROJECT]", "[INSTANCE]", "[DATABASE]")
var request = &iampb.GetIamPolicyRequest{
Resource: formattedResource,
}
c, err := NewDatabaseAdminClient(context.Background(), clientOpt)
if err != nil {
t.Fatal(err)
}
resp, err := c.GetIamPolicy(context.Background(), request)
if err != nil {
t.Fatal(err)
}
if want, got := request, mockDatabaseAdmin.reqs[0]; !proto.Equal(want, got) {
t.Errorf("wrong request %q, want %q", got, want)
}
if want, got := expectedResponse, resp; !proto.Equal(want, got) {
t.Errorf("wrong response %q, want %q)", got, want)
}
}
func TestDatabaseAdminGetIamPolicyError(t *testing.T) {
errCode := codes.PermissionDenied
mockDatabaseAdmin.err = gstatus.Error(errCode, "test error")
var formattedResource string = fmt.Sprintf("projects/%s/instances/%s/databases/%s", "[PROJECT]", "[INSTANCE]", "[DATABASE]")
var request = &iampb.GetIamPolicyRequest{
Resource: formattedResource,
}
c, err := NewDatabaseAdminClient(context.Background(), clientOpt)
if err != nil {
t.Fatal(err)
}
resp, err := c.GetIamPolicy(context.Background(), request)
if st, ok := gstatus.FromError(err); !ok {
t.Errorf("got error %v, expected grpc error", err)
} else if c := st.Code(); c != errCode {
t.Errorf("got error code %q, want %q", c, errCode)
}
_ = resp
}
func TestDatabaseAdminTestIamPermissions(t *testing.T) {
var expectedResponse *iampb.TestIamPermissionsResponse = &iampb.TestIamPermissionsResponse{}
mockDatabaseAdmin.err = nil
mockDatabaseAdmin.reqs = nil
mockDatabaseAdmin.resps = append(mockDatabaseAdmin.resps[:0], expectedResponse)
var formattedResource string = fmt.Sprintf("projects/%s/instances/%s/databases/%s", "[PROJECT]", "[INSTANCE]", "[DATABASE]")
var permissions []string = nil
var request = &iampb.TestIamPermissionsRequest{
Resource: formattedResource,
Permissions: permissions,
}
c, err := NewDatabaseAdminClient(context.Background(), clientOpt)
if err != nil {
t.Fatal(err)
}
resp, err := c.TestIamPermissions(context.Background(), request)
if err != nil {
t.Fatal(err)
}
if want, got := request, mockDatabaseAdmin.reqs[0]; !proto.Equal(want, got) {
t.Errorf("wrong request %q, want %q", got, want)
}
if want, got := expectedResponse, resp; !proto.Equal(want, got) {
t.Errorf("wrong response %q, want %q)", got, want)
}
}
func TestDatabaseAdminTestIamPermissionsError(t *testing.T) {
errCode := codes.PermissionDenied
mockDatabaseAdmin.err = gstatus.Error(errCode, "test error")
var formattedResource string = fmt.Sprintf("projects/%s/instances/%s/databases/%s", "[PROJECT]", "[INSTANCE]", "[DATABASE]")
var permissions []string = nil
var request = &iampb.TestIamPermissionsRequest{
Resource: formattedResource,
Permissions: permissions,
}
c, err := NewDatabaseAdminClient(context.Background(), clientOpt)
if err != nil {
t.Fatal(err)
}
resp, err := c.TestIamPermissions(context.Background(), request)
if st, ok := gstatus.FromError(err); !ok {
t.Errorf("got error %v, expected grpc error", err)
} else if c := st.Code(); c != errCode {
t.Errorf("got error code %q, want %q", c, errCode)
}
_ = resp
}

View File

@@ -0,0 +1,45 @@
// Copyright 2018 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
//
// https://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 database
// DatabaseAdminInstancePath returns the path for the instance resource.
//
// Deprecated: Use
// fmt.Sprintf("projects/%s/instances/%s", project, instance)
// instead.
func DatabaseAdminInstancePath(project, instance string) string {
return "" +
"projects/" +
project +
"/instances/" +
instance +
""
}
// DatabaseAdminDatabasePath returns the path for the database resource.
//
// Deprecated: Use
// fmt.Sprintf("projects/%s/instances/%s/databases/%s", project, instance, database)
// instead.
func DatabaseAdminDatabasePath(project, instance, database string) string {
return "" +
"projects/" +
project +
"/instances/" +
instance +
"/databases/" +
database +
""
}

View File

@@ -0,0 +1,100 @@
// Copyright 2019 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
//
// https://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.
// Code generated by gapic-generator. DO NOT EDIT.
// Package instance is an auto-generated package for the
// Cloud Spanner Instance Admin API.
//
// NOTE: This package is in alpha. It is not stable, and is likely to change.
//
//
// Use of Context
//
// The ctx passed to NewClient is used for authentication requests and
// for creating the underlying connection, but is not used for subsequent calls.
// Individual methods on the client use the ctx given to them.
//
// To close the open connection, use the Close() method.
//
// For information about setting deadlines, reusing contexts, and more
// please visit godoc.org/cloud.google.com/go.
package instance // import "cloud.google.com/go/spanner/admin/instance/apiv1"
import (
"context"
"runtime"
"strings"
"unicode"
"google.golang.org/grpc/metadata"
)
func insertMetadata(ctx context.Context, mds ...metadata.MD) context.Context {
out, _ := metadata.FromOutgoingContext(ctx)
out = out.Copy()
for _, md := range mds {
for k, v := range md {
out[k] = append(out[k], v...)
}
}
return metadata.NewOutgoingContext(ctx, out)
}
// DefaultAuthScopes reports the default set of authentication scopes to use with this package.
func DefaultAuthScopes() []string {
return []string{
"https://www.googleapis.com/auth/cloud-platform",
"https://www.googleapis.com/auth/spanner.admin",
}
}
// versionGo returns the Go runtime version. The returned string
// has no whitespace, suitable for reporting in header.
func versionGo() string {
const develPrefix = "devel +"
s := runtime.Version()
if strings.HasPrefix(s, develPrefix) {
s = s[len(develPrefix):]
if p := strings.IndexFunc(s, unicode.IsSpace); p >= 0 {
s = s[:p]
}
return s
}
notSemverRune := func(r rune) bool {
return strings.IndexRune("0123456789.", r) < 0
}
if strings.HasPrefix(s, "go1") {
s = s[2:]
var prerelease string
if p := strings.IndexFunc(s, notSemverRune); p >= 0 {
s, prerelease = s[:p], s[p:]
}
if strings.HasSuffix(s, ".") {
s += "0"
} else if strings.Count(s, ".") < 2 {
s += ".0"
}
if prerelease != "" {
s += "-" + prerelease
}
return s
}
return "UNKNOWN"
}
const versionClient = "20190404"

View File

@@ -0,0 +1,717 @@
// Copyright 2019 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
//
// https://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.
// Code generated by gapic-generator. DO NOT EDIT.
package instance
import (
"context"
"fmt"
"math"
"time"
"cloud.google.com/go/longrunning"
lroauto "cloud.google.com/go/longrunning/autogen"
"github.com/golang/protobuf/proto"
gax "github.com/googleapis/gax-go/v2"
"google.golang.org/api/iterator"
"google.golang.org/api/option"
"google.golang.org/api/transport"
iampb "google.golang.org/genproto/googleapis/iam/v1"
longrunningpb "google.golang.org/genproto/googleapis/longrunning"
instancepb "google.golang.org/genproto/googleapis/spanner/admin/instance/v1"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
)
// InstanceAdminCallOptions contains the retry settings for each method of InstanceAdminClient.
type InstanceAdminCallOptions struct {
ListInstanceConfigs []gax.CallOption
GetInstanceConfig []gax.CallOption
ListInstances []gax.CallOption
GetInstance []gax.CallOption
CreateInstance []gax.CallOption
UpdateInstance []gax.CallOption
DeleteInstance []gax.CallOption
SetIamPolicy []gax.CallOption
GetIamPolicy []gax.CallOption
TestIamPermissions []gax.CallOption
}
func defaultInstanceAdminClientOptions() []option.ClientOption {
return []option.ClientOption{
option.WithEndpoint("spanner.googleapis.com:443"),
option.WithScopes(DefaultAuthScopes()...),
}
}
func defaultInstanceAdminCallOptions() *InstanceAdminCallOptions {
retry := map[[2]string][]gax.CallOption{
{"default", "idempotent"}: {
gax.WithRetry(func() gax.Retryer {
return gax.OnCodes([]codes.Code{
codes.DeadlineExceeded,
codes.Unavailable,
}, gax.Backoff{
Initial: 1000 * time.Millisecond,
Max: 32000 * time.Millisecond,
Multiplier: 1.3,
})
}),
},
}
return &InstanceAdminCallOptions{
ListInstanceConfigs: retry[[2]string{"default", "idempotent"}],
GetInstanceConfig: retry[[2]string{"default", "idempotent"}],
ListInstances: retry[[2]string{"default", "idempotent"}],
GetInstance: retry[[2]string{"default", "idempotent"}],
CreateInstance: retry[[2]string{"default", "non_idempotent"}],
UpdateInstance: retry[[2]string{"default", "non_idempotent"}],
DeleteInstance: retry[[2]string{"default", "idempotent"}],
SetIamPolicy: retry[[2]string{"default", "non_idempotent"}],
GetIamPolicy: retry[[2]string{"default", "idempotent"}],
TestIamPermissions: retry[[2]string{"default", "non_idempotent"}],
}
}
// InstanceAdminClient is a client for interacting with Cloud Spanner Instance Admin API.
//
// Methods, except Close, may be called concurrently. However, fields must not be modified concurrently with method calls.
type InstanceAdminClient struct {
// The connection to the service.
conn *grpc.ClientConn
// The gRPC API client.
instanceAdminClient instancepb.InstanceAdminClient
// LROClient is used internally to handle longrunning operations.
// It is exposed so that its CallOptions can be modified if required.
// Users should not Close this client.
LROClient *lroauto.OperationsClient
// The call options for this service.
CallOptions *InstanceAdminCallOptions
// The x-goog-* metadata to be sent with each request.
xGoogMetadata metadata.MD
}
// NewInstanceAdminClient creates a new instance admin client.
//
// Cloud Spanner Instance Admin API
//
// The Cloud Spanner Instance Admin API can be used to create, delete,
// modify and list instances. Instances are dedicated Cloud Spanner serving
// and storage resources to be used by Cloud Spanner databases.
//
// Each instance has a "configuration", which dictates where the
// serving resources for the Cloud Spanner instance are located (e.g.,
// US-central, Europe). Configurations are created by Google based on
// resource availability.
//
// Cloud Spanner billing is based on the instances that exist and their
// sizes. After an instance exists, there are no additional
// per-database or per-operation charges for use of the instance
// (though there may be additional network bandwidth charges).
// Instances offer isolation: problems with databases in one instance
// will not affect other instances. However, within an instance
// databases can affect each other. For example, if one database in an
// instance receives a lot of requests and consumes most of the
// instance resources, fewer resources are available for other
// databases in that instance, and their performance may suffer.
func NewInstanceAdminClient(ctx context.Context, opts ...option.ClientOption) (*InstanceAdminClient, error) {
conn, err := transport.DialGRPC(ctx, append(defaultInstanceAdminClientOptions(), opts...)...)
if err != nil {
return nil, err
}
c := &InstanceAdminClient{
conn: conn,
CallOptions: defaultInstanceAdminCallOptions(),
instanceAdminClient: instancepb.NewInstanceAdminClient(conn),
}
c.setGoogleClientInfo()
c.LROClient, err = lroauto.NewOperationsClient(ctx, option.WithGRPCConn(conn))
if err != nil {
// This error "should not happen", since we are just reusing old connection
// and never actually need to dial.
// If this does happen, we could leak conn. However, we cannot close conn:
// If the user invoked the function with option.WithGRPCConn,
// we would close a connection that's still in use.
// TODO(pongad): investigate error conditions.
return nil, err
}
return c, nil
}
// Connection returns the client's connection to the API service.
func (c *InstanceAdminClient) Connection() *grpc.ClientConn {
return c.conn
}
// Close closes the connection to the API service. The user should invoke this when
// the client is no longer required.
func (c *InstanceAdminClient) Close() error {
return c.conn.Close()
}
// setGoogleClientInfo sets the name and version of the application in
// the `x-goog-api-client` header passed on each request. Intended for
// use by Google-written clients.
func (c *InstanceAdminClient) setGoogleClientInfo(keyval ...string) {
kv := append([]string{"gl-go", versionGo()}, keyval...)
kv = append(kv, "gapic", versionClient, "gax", gax.Version, "grpc", grpc.Version)
c.xGoogMetadata = metadata.Pairs("x-goog-api-client", gax.XGoogHeader(kv...))
}
// ListInstanceConfigs lists the supported instance configurations for a given project.
func (c *InstanceAdminClient) ListInstanceConfigs(ctx context.Context, req *instancepb.ListInstanceConfigsRequest, opts ...gax.CallOption) *InstanceConfigIterator {
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "parent", req.GetParent()))
ctx = insertMetadata(ctx, c.xGoogMetadata, md)
opts = append(c.CallOptions.ListInstanceConfigs[0:len(c.CallOptions.ListInstanceConfigs):len(c.CallOptions.ListInstanceConfigs)], opts...)
it := &InstanceConfigIterator{}
req = proto.Clone(req).(*instancepb.ListInstanceConfigsRequest)
it.InternalFetch = func(pageSize int, pageToken string) ([]*instancepb.InstanceConfig, string, error) {
var resp *instancepb.ListInstanceConfigsResponse
req.PageToken = pageToken
if pageSize > math.MaxInt32 {
req.PageSize = math.MaxInt32
} else {
req.PageSize = int32(pageSize)
}
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
resp, err = c.instanceAdminClient.ListInstanceConfigs(ctx, req, settings.GRPC...)
return err
}, opts...)
if err != nil {
return nil, "", err
}
return resp.InstanceConfigs, resp.NextPageToken, nil
}
fetch := func(pageSize int, pageToken string) (string, error) {
items, nextPageToken, err := it.InternalFetch(pageSize, pageToken)
if err != nil {
return "", err
}
it.items = append(it.items, items...)
return nextPageToken, nil
}
it.pageInfo, it.nextFunc = iterator.NewPageInfo(fetch, it.bufLen, it.takeBuf)
it.pageInfo.MaxSize = int(req.PageSize)
return it
}
// GetInstanceConfig gets information about a particular instance configuration.
func (c *InstanceAdminClient) GetInstanceConfig(ctx context.Context, req *instancepb.GetInstanceConfigRequest, opts ...gax.CallOption) (*instancepb.InstanceConfig, error) {
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "name", req.GetName()))
ctx = insertMetadata(ctx, c.xGoogMetadata, md)
opts = append(c.CallOptions.GetInstanceConfig[0:len(c.CallOptions.GetInstanceConfig):len(c.CallOptions.GetInstanceConfig)], opts...)
var resp *instancepb.InstanceConfig
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
resp, err = c.instanceAdminClient.GetInstanceConfig(ctx, req, settings.GRPC...)
return err
}, opts...)
if err != nil {
return nil, err
}
return resp, nil
}
// ListInstances lists all instances in the given project.
func (c *InstanceAdminClient) ListInstances(ctx context.Context, req *instancepb.ListInstancesRequest, opts ...gax.CallOption) *InstanceIterator {
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "parent", req.GetParent()))
ctx = insertMetadata(ctx, c.xGoogMetadata, md)
opts = append(c.CallOptions.ListInstances[0:len(c.CallOptions.ListInstances):len(c.CallOptions.ListInstances)], opts...)
it := &InstanceIterator{}
req = proto.Clone(req).(*instancepb.ListInstancesRequest)
it.InternalFetch = func(pageSize int, pageToken string) ([]*instancepb.Instance, string, error) {
var resp *instancepb.ListInstancesResponse
req.PageToken = pageToken
if pageSize > math.MaxInt32 {
req.PageSize = math.MaxInt32
} else {
req.PageSize = int32(pageSize)
}
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
resp, err = c.instanceAdminClient.ListInstances(ctx, req, settings.GRPC...)
return err
}, opts...)
if err != nil {
return nil, "", err
}
return resp.Instances, resp.NextPageToken, nil
}
fetch := func(pageSize int, pageToken string) (string, error) {
items, nextPageToken, err := it.InternalFetch(pageSize, pageToken)
if err != nil {
return "", err
}
it.items = append(it.items, items...)
return nextPageToken, nil
}
it.pageInfo, it.nextFunc = iterator.NewPageInfo(fetch, it.bufLen, it.takeBuf)
it.pageInfo.MaxSize = int(req.PageSize)
return it
}
// GetInstance gets information about a particular instance.
func (c *InstanceAdminClient) GetInstance(ctx context.Context, req *instancepb.GetInstanceRequest, opts ...gax.CallOption) (*instancepb.Instance, error) {
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "name", req.GetName()))
ctx = insertMetadata(ctx, c.xGoogMetadata, md)
opts = append(c.CallOptions.GetInstance[0:len(c.CallOptions.GetInstance):len(c.CallOptions.GetInstance)], opts...)
var resp *instancepb.Instance
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
resp, err = c.instanceAdminClient.GetInstance(ctx, req, settings.GRPC...)
return err
}, opts...)
if err != nil {
return nil, err
}
return resp, nil
}
// CreateInstance creates an instance and begins preparing it to begin serving. The
// returned [long-running operation][google.longrunning.Operation]
// can be used to track the progress of preparing the new
// instance. The instance name is assigned by the caller. If the
// named instance already exists, CreateInstance returns
// ALREADY_EXISTS.
//
// Immediately upon completion of this request:
//
// The instance is readable via the API, with all requested attributes
// but no allocated resources. Its state is CREATING.
//
// Until completion of the returned operation:
//
// Cancelling the operation renders the instance immediately unreadable
// via the API.
//
// The instance can be deleted.
//
// All other attempts to modify the instance are rejected.
//
// Upon completion of the returned operation:
//
// Billing for all successfully-allocated resources begins (some types
// may have lower than the requested levels).
//
// Databases can be created in the instance.
//
// The instance's allocated resource levels are readable via the API.
//
// The instance's state becomes READY.
//
// The returned [long-running operation][google.longrunning.Operation] will
// have a name of the format <instance_name>/operations/<operation_id> and
// can be used to track creation of the instance. The
// [metadata][google.longrunning.Operation.metadata] field type is
// [CreateInstanceMetadata][google.spanner.admin.instance.v1.CreateInstanceMetadata].
// The [response][google.longrunning.Operation.response] field type is
// [Instance][google.spanner.admin.instance.v1.Instance], if successful.
func (c *InstanceAdminClient) CreateInstance(ctx context.Context, req *instancepb.CreateInstanceRequest, opts ...gax.CallOption) (*CreateInstanceOperation, error) {
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "parent", req.GetParent()))
ctx = insertMetadata(ctx, c.xGoogMetadata, md)
opts = append(c.CallOptions.CreateInstance[0:len(c.CallOptions.CreateInstance):len(c.CallOptions.CreateInstance)], opts...)
var resp *longrunningpb.Operation
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
resp, err = c.instanceAdminClient.CreateInstance(ctx, req, settings.GRPC...)
return err
}, opts...)
if err != nil {
return nil, err
}
return &CreateInstanceOperation{
lro: longrunning.InternalNewOperation(c.LROClient, resp),
}, nil
}
// UpdateInstance updates an instance, and begins allocating or releasing resources
// as requested. The returned [long-running
// operation][google.longrunning.Operation] can be used to track the
// progress of updating the instance. If the named instance does not
// exist, returns NOT_FOUND.
//
// Immediately upon completion of this request:
//
// For resource types for which a decrease in the instance's allocation
// has been requested, billing is based on the newly-requested level.
//
// Until completion of the returned operation:
//
// Cancelling the operation sets its metadata's
// [cancel_time][google.spanner.admin.instance.v1.UpdateInstanceMetadata.cancel_time],
// and begins restoring resources to their pre-request values. The
// operation is guaranteed to succeed at undoing all resource changes,
// after which point it terminates with a CANCELLED status.
//
// All other attempts to modify the instance are rejected.
//
// Reading the instance via the API continues to give the pre-request
// resource levels.
//
// Upon completion of the returned operation:
//
// Billing begins for all successfully-allocated resources (some types
// may have lower than the requested levels).
//
// All newly-reserved resources are available for serving the instance's
// tables.
//
// The instance's new resource levels are readable via the API.
//
// The returned [long-running operation][google.longrunning.Operation] will
// have a name of the format <instance_name>/operations/<operation_id> and
// can be used to track the instance modification. The
// [metadata][google.longrunning.Operation.metadata] field type is
// [UpdateInstanceMetadata][google.spanner.admin.instance.v1.UpdateInstanceMetadata].
// The [response][google.longrunning.Operation.response] field type is
// [Instance][google.spanner.admin.instance.v1.Instance], if successful.
//
// Authorization requires spanner.instances.update permission on
// resource [name][google.spanner.admin.instance.v1.Instance.name].
func (c *InstanceAdminClient) UpdateInstance(ctx context.Context, req *instancepb.UpdateInstanceRequest, opts ...gax.CallOption) (*UpdateInstanceOperation, error) {
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "instance.name", req.GetInstance().GetName()))
ctx = insertMetadata(ctx, c.xGoogMetadata, md)
opts = append(c.CallOptions.UpdateInstance[0:len(c.CallOptions.UpdateInstance):len(c.CallOptions.UpdateInstance)], opts...)
var resp *longrunningpb.Operation
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
resp, err = c.instanceAdminClient.UpdateInstance(ctx, req, settings.GRPC...)
return err
}, opts...)
if err != nil {
return nil, err
}
return &UpdateInstanceOperation{
lro: longrunning.InternalNewOperation(c.LROClient, resp),
}, nil
}
// DeleteInstance deletes an instance.
//
// Immediately upon completion of the request:
//
// Billing ceases for all of the instance's reserved resources.
//
// Soon afterward:
//
// The instance and all of its databases immediately and
// irrevocably disappear from the API. All data in the databases
// is permanently deleted.
func (c *InstanceAdminClient) DeleteInstance(ctx context.Context, req *instancepb.DeleteInstanceRequest, opts ...gax.CallOption) error {
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "name", req.GetName()))
ctx = insertMetadata(ctx, c.xGoogMetadata, md)
opts = append(c.CallOptions.DeleteInstance[0:len(c.CallOptions.DeleteInstance):len(c.CallOptions.DeleteInstance)], opts...)
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
_, err = c.instanceAdminClient.DeleteInstance(ctx, req, settings.GRPC...)
return err
}, opts...)
return err
}
// SetIamPolicy sets the access control policy on an instance resource. Replaces any
// existing policy.
//
// Authorization requires spanner.instances.setIamPolicy on
// [resource][google.iam.v1.SetIamPolicyRequest.resource].
func (c *InstanceAdminClient) SetIamPolicy(ctx context.Context, req *iampb.SetIamPolicyRequest, opts ...gax.CallOption) (*iampb.Policy, error) {
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "resource", req.GetResource()))
ctx = insertMetadata(ctx, c.xGoogMetadata, md)
opts = append(c.CallOptions.SetIamPolicy[0:len(c.CallOptions.SetIamPolicy):len(c.CallOptions.SetIamPolicy)], opts...)
var resp *iampb.Policy
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
resp, err = c.instanceAdminClient.SetIamPolicy(ctx, req, settings.GRPC...)
return err
}, opts...)
if err != nil {
return nil, err
}
return resp, nil
}
// GetIamPolicy gets the access control policy for an instance resource. Returns an empty
// policy if an instance exists but does not have a policy set.
//
// Authorization requires spanner.instances.getIamPolicy on
// [resource][google.iam.v1.GetIamPolicyRequest.resource].
func (c *InstanceAdminClient) GetIamPolicy(ctx context.Context, req *iampb.GetIamPolicyRequest, opts ...gax.CallOption) (*iampb.Policy, error) {
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "resource", req.GetResource()))
ctx = insertMetadata(ctx, c.xGoogMetadata, md)
opts = append(c.CallOptions.GetIamPolicy[0:len(c.CallOptions.GetIamPolicy):len(c.CallOptions.GetIamPolicy)], opts...)
var resp *iampb.Policy
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
resp, err = c.instanceAdminClient.GetIamPolicy(ctx, req, settings.GRPC...)
return err
}, opts...)
if err != nil {
return nil, err
}
return resp, nil
}
// TestIamPermissions returns permissions that the caller has on the specified instance resource.
//
// Attempting this RPC on a non-existent Cloud Spanner instance resource will
// result in a NOT_FOUND error if the user has spanner.instances.list
// permission on the containing Google Cloud Project. Otherwise returns an
// empty set of permissions.
func (c *InstanceAdminClient) TestIamPermissions(ctx context.Context, req *iampb.TestIamPermissionsRequest, opts ...gax.CallOption) (*iampb.TestIamPermissionsResponse, error) {
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "resource", req.GetResource()))
ctx = insertMetadata(ctx, c.xGoogMetadata, md)
opts = append(c.CallOptions.TestIamPermissions[0:len(c.CallOptions.TestIamPermissions):len(c.CallOptions.TestIamPermissions)], opts...)
var resp *iampb.TestIamPermissionsResponse
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
resp, err = c.instanceAdminClient.TestIamPermissions(ctx, req, settings.GRPC...)
return err
}, opts...)
if err != nil {
return nil, err
}
return resp, nil
}
// InstanceConfigIterator manages a stream of *instancepb.InstanceConfig.
type InstanceConfigIterator struct {
items []*instancepb.InstanceConfig
pageInfo *iterator.PageInfo
nextFunc func() error
// InternalFetch is for use by the Google Cloud Libraries only.
// It is not part of the stable interface of this package.
//
// InternalFetch returns results from a single call to the underlying RPC.
// The number of results is no greater than pageSize.
// If there are no more results, nextPageToken is empty and err is nil.
InternalFetch func(pageSize int, pageToken string) (results []*instancepb.InstanceConfig, nextPageToken string, err error)
}
// PageInfo supports pagination. See the google.golang.org/api/iterator package for details.
func (it *InstanceConfigIterator) PageInfo() *iterator.PageInfo {
return it.pageInfo
}
// Next returns the next result. Its second return value is iterator.Done if there are no more
// results. Once Next returns Done, all subsequent calls will return Done.
func (it *InstanceConfigIterator) Next() (*instancepb.InstanceConfig, error) {
var item *instancepb.InstanceConfig
if err := it.nextFunc(); err != nil {
return item, err
}
item = it.items[0]
it.items = it.items[1:]
return item, nil
}
func (it *InstanceConfigIterator) bufLen() int {
return len(it.items)
}
func (it *InstanceConfigIterator) takeBuf() interface{} {
b := it.items
it.items = nil
return b
}
// InstanceIterator manages a stream of *instancepb.Instance.
type InstanceIterator struct {
items []*instancepb.Instance
pageInfo *iterator.PageInfo
nextFunc func() error
// InternalFetch is for use by the Google Cloud Libraries only.
// It is not part of the stable interface of this package.
//
// InternalFetch returns results from a single call to the underlying RPC.
// The number of results is no greater than pageSize.
// If there are no more results, nextPageToken is empty and err is nil.
InternalFetch func(pageSize int, pageToken string) (results []*instancepb.Instance, nextPageToken string, err error)
}
// PageInfo supports pagination. See the google.golang.org/api/iterator package for details.
func (it *InstanceIterator) PageInfo() *iterator.PageInfo {
return it.pageInfo
}
// Next returns the next result. Its second return value is iterator.Done if there are no more
// results. Once Next returns Done, all subsequent calls will return Done.
func (it *InstanceIterator) Next() (*instancepb.Instance, error) {
var item *instancepb.Instance
if err := it.nextFunc(); err != nil {
return item, err
}
item = it.items[0]
it.items = it.items[1:]
return item, nil
}
func (it *InstanceIterator) bufLen() int {
return len(it.items)
}
func (it *InstanceIterator) takeBuf() interface{} {
b := it.items
it.items = nil
return b
}
// CreateInstanceOperation manages a long-running operation from CreateInstance.
type CreateInstanceOperation struct {
lro *longrunning.Operation
}
// CreateInstanceOperation returns a new CreateInstanceOperation from a given name.
// The name must be that of a previously created CreateInstanceOperation, possibly from a different process.
func (c *InstanceAdminClient) CreateInstanceOperation(name string) *CreateInstanceOperation {
return &CreateInstanceOperation{
lro: longrunning.InternalNewOperation(c.LROClient, &longrunningpb.Operation{Name: name}),
}
}
// Wait blocks until the long-running operation is completed, returning the response and any errors encountered.
//
// See documentation of Poll for error-handling information.
func (op *CreateInstanceOperation) Wait(ctx context.Context, opts ...gax.CallOption) (*instancepb.Instance, error) {
var resp instancepb.Instance
if err := op.lro.WaitWithInterval(ctx, &resp, 45000*time.Millisecond, opts...); err != nil {
return nil, err
}
return &resp, nil
}
// Poll fetches the latest state of the long-running operation.
//
// Poll also fetches the latest metadata, which can be retrieved by Metadata.
//
// If Poll fails, the error is returned and op is unmodified. If Poll succeeds and
// the operation has completed with failure, the error is returned and op.Done will return true.
// If Poll succeeds and the operation has completed successfully,
// op.Done will return true, and the response of the operation is returned.
// If Poll succeeds and the operation has not completed, the returned response and error are both nil.
func (op *CreateInstanceOperation) Poll(ctx context.Context, opts ...gax.CallOption) (*instancepb.Instance, error) {
var resp instancepb.Instance
if err := op.lro.Poll(ctx, &resp, opts...); err != nil {
return nil, err
}
if !op.Done() {
return nil, nil
}
return &resp, nil
}
// Metadata returns metadata associated with the long-running operation.
// Metadata itself does not contact the server, but Poll does.
// To get the latest metadata, call this method after a successful call to Poll.
// If the metadata is not available, the returned metadata and error are both nil.
func (op *CreateInstanceOperation) Metadata() (*instancepb.CreateInstanceMetadata, error) {
var meta instancepb.CreateInstanceMetadata
if err := op.lro.Metadata(&meta); err == longrunning.ErrNoMetadata {
return nil, nil
} else if err != nil {
return nil, err
}
return &meta, nil
}
// Done reports whether the long-running operation has completed.
func (op *CreateInstanceOperation) Done() bool {
return op.lro.Done()
}
// Name returns the name of the long-running operation.
// The name is assigned by the server and is unique within the service from which the operation is created.
func (op *CreateInstanceOperation) Name() string {
return op.lro.Name()
}
// UpdateInstanceOperation manages a long-running operation from UpdateInstance.
type UpdateInstanceOperation struct {
lro *longrunning.Operation
}
// UpdateInstanceOperation returns a new UpdateInstanceOperation from a given name.
// The name must be that of a previously created UpdateInstanceOperation, possibly from a different process.
func (c *InstanceAdminClient) UpdateInstanceOperation(name string) *UpdateInstanceOperation {
return &UpdateInstanceOperation{
lro: longrunning.InternalNewOperation(c.LROClient, &longrunningpb.Operation{Name: name}),
}
}
// Wait blocks until the long-running operation is completed, returning the response and any errors encountered.
//
// See documentation of Poll for error-handling information.
func (op *UpdateInstanceOperation) Wait(ctx context.Context, opts ...gax.CallOption) (*instancepb.Instance, error) {
var resp instancepb.Instance
if err := op.lro.WaitWithInterval(ctx, &resp, 45000*time.Millisecond, opts...); err != nil {
return nil, err
}
return &resp, nil
}
// Poll fetches the latest state of the long-running operation.
//
// Poll also fetches the latest metadata, which can be retrieved by Metadata.
//
// If Poll fails, the error is returned and op is unmodified. If Poll succeeds and
// the operation has completed with failure, the error is returned and op.Done will return true.
// If Poll succeeds and the operation has completed successfully,
// op.Done will return true, and the response of the operation is returned.
// If Poll succeeds and the operation has not completed, the returned response and error are both nil.
func (op *UpdateInstanceOperation) Poll(ctx context.Context, opts ...gax.CallOption) (*instancepb.Instance, error) {
var resp instancepb.Instance
if err := op.lro.Poll(ctx, &resp, opts...); err != nil {
return nil, err
}
if !op.Done() {
return nil, nil
}
return &resp, nil
}
// Metadata returns metadata associated with the long-running operation.
// Metadata itself does not contact the server, but Poll does.
// To get the latest metadata, call this method after a successful call to Poll.
// If the metadata is not available, the returned metadata and error are both nil.
func (op *UpdateInstanceOperation) Metadata() (*instancepb.UpdateInstanceMetadata, error) {
var meta instancepb.UpdateInstanceMetadata
if err := op.lro.Metadata(&meta); err == longrunning.ErrNoMetadata {
return nil, nil
} else if err != nil {
return nil, err
}
return &meta, nil
}
// Done reports whether the long-running operation has completed.
func (op *UpdateInstanceOperation) Done() bool {
return op.lro.Done()
}
// Name returns the name of the long-running operation.
// The name is assigned by the server and is unique within the service from which the operation is created.
func (op *UpdateInstanceOperation) Name() string {
return op.lro.Name()
}

View File

@@ -0,0 +1,236 @@
// Copyright 2019 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
//
// https://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.
// Code generated by gapic-generator. DO NOT EDIT.
package instance_test
import (
"context"
instance "cloud.google.com/go/spanner/admin/instance/apiv1"
"google.golang.org/api/iterator"
iampb "google.golang.org/genproto/googleapis/iam/v1"
instancepb "google.golang.org/genproto/googleapis/spanner/admin/instance/v1"
)
func ExampleNewInstanceAdminClient() {
ctx := context.Background()
c, err := instance.NewInstanceAdminClient(ctx)
if err != nil {
// TODO: Handle error.
}
// TODO: Use client.
_ = c
}
func ExampleInstanceAdminClient_ListInstanceConfigs() {
ctx := context.Background()
c, err := instance.NewInstanceAdminClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &instancepb.ListInstanceConfigsRequest{
// TODO: Fill request struct fields.
}
it := c.ListInstanceConfigs(ctx, req)
for {
resp, err := it.Next()
if err == iterator.Done {
break
}
if err != nil {
// TODO: Handle error.
}
// TODO: Use resp.
_ = resp
}
}
func ExampleInstanceAdminClient_GetInstanceConfig() {
ctx := context.Background()
c, err := instance.NewInstanceAdminClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &instancepb.GetInstanceConfigRequest{
// TODO: Fill request struct fields.
}
resp, err := c.GetInstanceConfig(ctx, req)
if err != nil {
// TODO: Handle error.
}
// TODO: Use resp.
_ = resp
}
func ExampleInstanceAdminClient_ListInstances() {
ctx := context.Background()
c, err := instance.NewInstanceAdminClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &instancepb.ListInstancesRequest{
// TODO: Fill request struct fields.
}
it := c.ListInstances(ctx, req)
for {
resp, err := it.Next()
if err == iterator.Done {
break
}
if err != nil {
// TODO: Handle error.
}
// TODO: Use resp.
_ = resp
}
}
func ExampleInstanceAdminClient_GetInstance() {
ctx := context.Background()
c, err := instance.NewInstanceAdminClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &instancepb.GetInstanceRequest{
// TODO: Fill request struct fields.
}
resp, err := c.GetInstance(ctx, req)
if err != nil {
// TODO: Handle error.
}
// TODO: Use resp.
_ = resp
}
func ExampleInstanceAdminClient_CreateInstance() {
ctx := context.Background()
c, err := instance.NewInstanceAdminClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &instancepb.CreateInstanceRequest{
// TODO: Fill request struct fields.
}
op, err := c.CreateInstance(ctx, req)
if err != nil {
// TODO: Handle error.
}
resp, err := op.Wait(ctx)
if err != nil {
// TODO: Handle error.
}
// TODO: Use resp.
_ = resp
}
func ExampleInstanceAdminClient_UpdateInstance() {
ctx := context.Background()
c, err := instance.NewInstanceAdminClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &instancepb.UpdateInstanceRequest{
// TODO: Fill request struct fields.
}
op, err := c.UpdateInstance(ctx, req)
if err != nil {
// TODO: Handle error.
}
resp, err := op.Wait(ctx)
if err != nil {
// TODO: Handle error.
}
// TODO: Use resp.
_ = resp
}
func ExampleInstanceAdminClient_DeleteInstance() {
ctx := context.Background()
c, err := instance.NewInstanceAdminClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &instancepb.DeleteInstanceRequest{
// TODO: Fill request struct fields.
}
err = c.DeleteInstance(ctx, req)
if err != nil {
// TODO: Handle error.
}
}
func ExampleInstanceAdminClient_SetIamPolicy() {
ctx := context.Background()
c, err := instance.NewInstanceAdminClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &iampb.SetIamPolicyRequest{
// TODO: Fill request struct fields.
}
resp, err := c.SetIamPolicy(ctx, req)
if err != nil {
// TODO: Handle error.
}
// TODO: Use resp.
_ = resp
}
func ExampleInstanceAdminClient_GetIamPolicy() {
ctx := context.Background()
c, err := instance.NewInstanceAdminClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &iampb.GetIamPolicyRequest{
// TODO: Fill request struct fields.
}
resp, err := c.GetIamPolicy(ctx, req)
if err != nil {
// TODO: Handle error.
}
// TODO: Use resp.
_ = resp
}
func ExampleInstanceAdminClient_TestIamPermissions() {
ctx := context.Background()
c, err := instance.NewInstanceAdminClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &iampb.TestIamPermissionsRequest{
// TODO: Fill request struct fields.
}
resp, err := c.TestIamPermissions(ctx, req)
if err != nil {
// TODO: Handle error.
}
// TODO: Use resp.
_ = resp
}

View File

@@ -0,0 +1,917 @@
// Copyright 2019 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
//
// https://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.
// Code generated by gapic-generator. DO NOT EDIT.
package instance
import (
emptypb "github.com/golang/protobuf/ptypes/empty"
iampb "google.golang.org/genproto/googleapis/iam/v1"
longrunningpb "google.golang.org/genproto/googleapis/longrunning"
instancepb "google.golang.org/genproto/googleapis/spanner/admin/instance/v1"
field_maskpb "google.golang.org/genproto/protobuf/field_mask"
)
import (
"context"
"flag"
"fmt"
"io"
"log"
"net"
"os"
"strings"
"testing"
"github.com/golang/protobuf/proto"
"github.com/golang/protobuf/ptypes"
"google.golang.org/api/option"
status "google.golang.org/genproto/googleapis/rpc/status"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
gstatus "google.golang.org/grpc/status"
)
var _ = io.EOF
var _ = ptypes.MarshalAny
var _ status.Status
type mockInstanceAdminServer struct {
// Embed for forward compatibility.
// Tests will keep working if more methods are added
// in the future.
instancepb.InstanceAdminServer
reqs []proto.Message
// If set, all calls return this error.
err error
// responses to return if err == nil
resps []proto.Message
}
func (s *mockInstanceAdminServer) ListInstanceConfigs(ctx context.Context, req *instancepb.ListInstanceConfigsRequest) (*instancepb.ListInstanceConfigsResponse, error) {
md, _ := metadata.FromIncomingContext(ctx)
if xg := md["x-goog-api-client"]; len(xg) == 0 || !strings.Contains(xg[0], "gl-go/") {
return nil, fmt.Errorf("x-goog-api-client = %v, expected gl-go key", xg)
}
s.reqs = append(s.reqs, req)
if s.err != nil {
return nil, s.err
}
return s.resps[0].(*instancepb.ListInstanceConfigsResponse), nil
}
func (s *mockInstanceAdminServer) GetInstanceConfig(ctx context.Context, req *instancepb.GetInstanceConfigRequest) (*instancepb.InstanceConfig, error) {
md, _ := metadata.FromIncomingContext(ctx)
if xg := md["x-goog-api-client"]; len(xg) == 0 || !strings.Contains(xg[0], "gl-go/") {
return nil, fmt.Errorf("x-goog-api-client = %v, expected gl-go key", xg)
}
s.reqs = append(s.reqs, req)
if s.err != nil {
return nil, s.err
}
return s.resps[0].(*instancepb.InstanceConfig), nil
}
func (s *mockInstanceAdminServer) ListInstances(ctx context.Context, req *instancepb.ListInstancesRequest) (*instancepb.ListInstancesResponse, error) {
md, _ := metadata.FromIncomingContext(ctx)
if xg := md["x-goog-api-client"]; len(xg) == 0 || !strings.Contains(xg[0], "gl-go/") {
return nil, fmt.Errorf("x-goog-api-client = %v, expected gl-go key", xg)
}
s.reqs = append(s.reqs, req)
if s.err != nil {
return nil, s.err
}
return s.resps[0].(*instancepb.ListInstancesResponse), nil
}
func (s *mockInstanceAdminServer) GetInstance(ctx context.Context, req *instancepb.GetInstanceRequest) (*instancepb.Instance, error) {
md, _ := metadata.FromIncomingContext(ctx)
if xg := md["x-goog-api-client"]; len(xg) == 0 || !strings.Contains(xg[0], "gl-go/") {
return nil, fmt.Errorf("x-goog-api-client = %v, expected gl-go key", xg)
}
s.reqs = append(s.reqs, req)
if s.err != nil {
return nil, s.err
}
return s.resps[0].(*instancepb.Instance), nil
}
func (s *mockInstanceAdminServer) CreateInstance(ctx context.Context, req *instancepb.CreateInstanceRequest) (*longrunningpb.Operation, error) {
md, _ := metadata.FromIncomingContext(ctx)
if xg := md["x-goog-api-client"]; len(xg) == 0 || !strings.Contains(xg[0], "gl-go/") {
return nil, fmt.Errorf("x-goog-api-client = %v, expected gl-go key", xg)
}
s.reqs = append(s.reqs, req)
if s.err != nil {
return nil, s.err
}
return s.resps[0].(*longrunningpb.Operation), nil
}
func (s *mockInstanceAdminServer) UpdateInstance(ctx context.Context, req *instancepb.UpdateInstanceRequest) (*longrunningpb.Operation, error) {
md, _ := metadata.FromIncomingContext(ctx)
if xg := md["x-goog-api-client"]; len(xg) == 0 || !strings.Contains(xg[0], "gl-go/") {
return nil, fmt.Errorf("x-goog-api-client = %v, expected gl-go key", xg)
}
s.reqs = append(s.reqs, req)
if s.err != nil {
return nil, s.err
}
return s.resps[0].(*longrunningpb.Operation), nil
}
func (s *mockInstanceAdminServer) DeleteInstance(ctx context.Context, req *instancepb.DeleteInstanceRequest) (*emptypb.Empty, error) {
md, _ := metadata.FromIncomingContext(ctx)
if xg := md["x-goog-api-client"]; len(xg) == 0 || !strings.Contains(xg[0], "gl-go/") {
return nil, fmt.Errorf("x-goog-api-client = %v, expected gl-go key", xg)
}
s.reqs = append(s.reqs, req)
if s.err != nil {
return nil, s.err
}
return s.resps[0].(*emptypb.Empty), nil
}
func (s *mockInstanceAdminServer) SetIamPolicy(ctx context.Context, req *iampb.SetIamPolicyRequest) (*iampb.Policy, error) {
md, _ := metadata.FromIncomingContext(ctx)
if xg := md["x-goog-api-client"]; len(xg) == 0 || !strings.Contains(xg[0], "gl-go/") {
return nil, fmt.Errorf("x-goog-api-client = %v, expected gl-go key", xg)
}
s.reqs = append(s.reqs, req)
if s.err != nil {
return nil, s.err
}
return s.resps[0].(*iampb.Policy), nil
}
func (s *mockInstanceAdminServer) GetIamPolicy(ctx context.Context, req *iampb.GetIamPolicyRequest) (*iampb.Policy, error) {
md, _ := metadata.FromIncomingContext(ctx)
if xg := md["x-goog-api-client"]; len(xg) == 0 || !strings.Contains(xg[0], "gl-go/") {
return nil, fmt.Errorf("x-goog-api-client = %v, expected gl-go key", xg)
}
s.reqs = append(s.reqs, req)
if s.err != nil {
return nil, s.err
}
return s.resps[0].(*iampb.Policy), nil
}
func (s *mockInstanceAdminServer) TestIamPermissions(ctx context.Context, req *iampb.TestIamPermissionsRequest) (*iampb.TestIamPermissionsResponse, error) {
md, _ := metadata.FromIncomingContext(ctx)
if xg := md["x-goog-api-client"]; len(xg) == 0 || !strings.Contains(xg[0], "gl-go/") {
return nil, fmt.Errorf("x-goog-api-client = %v, expected gl-go key", xg)
}
s.reqs = append(s.reqs, req)
if s.err != nil {
return nil, s.err
}
return s.resps[0].(*iampb.TestIamPermissionsResponse), nil
}
// clientOpt is the option tests should use to connect to the test server.
// It is initialized by TestMain.
var clientOpt option.ClientOption
var (
mockInstanceAdmin mockInstanceAdminServer
)
func TestMain(m *testing.M) {
flag.Parse()
serv := grpc.NewServer()
instancepb.RegisterInstanceAdminServer(serv, &mockInstanceAdmin)
lis, err := net.Listen("tcp", "localhost:0")
if err != nil {
log.Fatal(err)
}
go serv.Serve(lis)
conn, err := grpc.Dial(lis.Addr().String(), grpc.WithInsecure())
if err != nil {
log.Fatal(err)
}
clientOpt = option.WithGRPCConn(conn)
os.Exit(m.Run())
}
func TestInstanceAdminListInstanceConfigs(t *testing.T) {
var nextPageToken string = ""
var instanceConfigsElement *instancepb.InstanceConfig = &instancepb.InstanceConfig{}
var instanceConfigs = []*instancepb.InstanceConfig{instanceConfigsElement}
var expectedResponse = &instancepb.ListInstanceConfigsResponse{
NextPageToken: nextPageToken,
InstanceConfigs: instanceConfigs,
}
mockInstanceAdmin.err = nil
mockInstanceAdmin.reqs = nil
mockInstanceAdmin.resps = append(mockInstanceAdmin.resps[:0], expectedResponse)
var formattedParent string = fmt.Sprintf("projects/%s", "[PROJECT]")
var request = &instancepb.ListInstanceConfigsRequest{
Parent: formattedParent,
}
c, err := NewInstanceAdminClient(context.Background(), clientOpt)
if err != nil {
t.Fatal(err)
}
resp, err := c.ListInstanceConfigs(context.Background(), request).Next()
if err != nil {
t.Fatal(err)
}
if want, got := request, mockInstanceAdmin.reqs[0]; !proto.Equal(want, got) {
t.Errorf("wrong request %q, want %q", got, want)
}
want := (interface{})(expectedResponse.InstanceConfigs[0])
got := (interface{})(resp)
var ok bool
switch want := (want).(type) {
case proto.Message:
ok = proto.Equal(want, got.(proto.Message))
default:
ok = want == got
}
if !ok {
t.Errorf("wrong response %q, want %q)", got, want)
}
}
func TestInstanceAdminListInstanceConfigsError(t *testing.T) {
errCode := codes.PermissionDenied
mockInstanceAdmin.err = gstatus.Error(errCode, "test error")
var formattedParent string = fmt.Sprintf("projects/%s", "[PROJECT]")
var request = &instancepb.ListInstanceConfigsRequest{
Parent: formattedParent,
}
c, err := NewInstanceAdminClient(context.Background(), clientOpt)
if err != nil {
t.Fatal(err)
}
resp, err := c.ListInstanceConfigs(context.Background(), request).Next()
if st, ok := gstatus.FromError(err); !ok {
t.Errorf("got error %v, expected grpc error", err)
} else if c := st.Code(); c != errCode {
t.Errorf("got error code %q, want %q", c, errCode)
}
_ = resp
}
func TestInstanceAdminGetInstanceConfig(t *testing.T) {
var name2 string = "name2-1052831874"
var displayName string = "displayName1615086568"
var expectedResponse = &instancepb.InstanceConfig{
Name: name2,
DisplayName: displayName,
}
mockInstanceAdmin.err = nil
mockInstanceAdmin.reqs = nil
mockInstanceAdmin.resps = append(mockInstanceAdmin.resps[:0], expectedResponse)
var formattedName string = fmt.Sprintf("projects/%s/instanceConfigs/%s", "[PROJECT]", "[INSTANCE_CONFIG]")
var request = &instancepb.GetInstanceConfigRequest{
Name: formattedName,
}
c, err := NewInstanceAdminClient(context.Background(), clientOpt)
if err != nil {
t.Fatal(err)
}
resp, err := c.GetInstanceConfig(context.Background(), request)
if err != nil {
t.Fatal(err)
}
if want, got := request, mockInstanceAdmin.reqs[0]; !proto.Equal(want, got) {
t.Errorf("wrong request %q, want %q", got, want)
}
if want, got := expectedResponse, resp; !proto.Equal(want, got) {
t.Errorf("wrong response %q, want %q)", got, want)
}
}
func TestInstanceAdminGetInstanceConfigError(t *testing.T) {
errCode := codes.PermissionDenied
mockInstanceAdmin.err = gstatus.Error(errCode, "test error")
var formattedName string = fmt.Sprintf("projects/%s/instanceConfigs/%s", "[PROJECT]", "[INSTANCE_CONFIG]")
var request = &instancepb.GetInstanceConfigRequest{
Name: formattedName,
}
c, err := NewInstanceAdminClient(context.Background(), clientOpt)
if err != nil {
t.Fatal(err)
}
resp, err := c.GetInstanceConfig(context.Background(), request)
if st, ok := gstatus.FromError(err); !ok {
t.Errorf("got error %v, expected grpc error", err)
} else if c := st.Code(); c != errCode {
t.Errorf("got error code %q, want %q", c, errCode)
}
_ = resp
}
func TestInstanceAdminListInstances(t *testing.T) {
var nextPageToken string = ""
var instancesElement *instancepb.Instance = &instancepb.Instance{}
var instances = []*instancepb.Instance{instancesElement}
var expectedResponse = &instancepb.ListInstancesResponse{
NextPageToken: nextPageToken,
Instances: instances,
}
mockInstanceAdmin.err = nil
mockInstanceAdmin.reqs = nil
mockInstanceAdmin.resps = append(mockInstanceAdmin.resps[:0], expectedResponse)
var formattedParent string = fmt.Sprintf("projects/%s", "[PROJECT]")
var request = &instancepb.ListInstancesRequest{
Parent: formattedParent,
}
c, err := NewInstanceAdminClient(context.Background(), clientOpt)
if err != nil {
t.Fatal(err)
}
resp, err := c.ListInstances(context.Background(), request).Next()
if err != nil {
t.Fatal(err)
}
if want, got := request, mockInstanceAdmin.reqs[0]; !proto.Equal(want, got) {
t.Errorf("wrong request %q, want %q", got, want)
}
want := (interface{})(expectedResponse.Instances[0])
got := (interface{})(resp)
var ok bool
switch want := (want).(type) {
case proto.Message:
ok = proto.Equal(want, got.(proto.Message))
default:
ok = want == got
}
if !ok {
t.Errorf("wrong response %q, want %q)", got, want)
}
}
func TestInstanceAdminListInstancesError(t *testing.T) {
errCode := codes.PermissionDenied
mockInstanceAdmin.err = gstatus.Error(errCode, "test error")
var formattedParent string = fmt.Sprintf("projects/%s", "[PROJECT]")
var request = &instancepb.ListInstancesRequest{
Parent: formattedParent,
}
c, err := NewInstanceAdminClient(context.Background(), clientOpt)
if err != nil {
t.Fatal(err)
}
resp, err := c.ListInstances(context.Background(), request).Next()
if st, ok := gstatus.FromError(err); !ok {
t.Errorf("got error %v, expected grpc error", err)
} else if c := st.Code(); c != errCode {
t.Errorf("got error code %q, want %q", c, errCode)
}
_ = resp
}
func TestInstanceAdminGetInstance(t *testing.T) {
var name2 string = "name2-1052831874"
var config string = "config-1354792126"
var displayName string = "displayName1615086568"
var nodeCount int32 = 1539922066
var expectedResponse = &instancepb.Instance{
Name: name2,
Config: config,
DisplayName: displayName,
NodeCount: nodeCount,
}
mockInstanceAdmin.err = nil
mockInstanceAdmin.reqs = nil
mockInstanceAdmin.resps = append(mockInstanceAdmin.resps[:0], expectedResponse)
var formattedName string = fmt.Sprintf("projects/%s/instances/%s", "[PROJECT]", "[INSTANCE]")
var request = &instancepb.GetInstanceRequest{
Name: formattedName,
}
c, err := NewInstanceAdminClient(context.Background(), clientOpt)
if err != nil {
t.Fatal(err)
}
resp, err := c.GetInstance(context.Background(), request)
if err != nil {
t.Fatal(err)
}
if want, got := request, mockInstanceAdmin.reqs[0]; !proto.Equal(want, got) {
t.Errorf("wrong request %q, want %q", got, want)
}
if want, got := expectedResponse, resp; !proto.Equal(want, got) {
t.Errorf("wrong response %q, want %q)", got, want)
}
}
func TestInstanceAdminGetInstanceError(t *testing.T) {
errCode := codes.PermissionDenied
mockInstanceAdmin.err = gstatus.Error(errCode, "test error")
var formattedName string = fmt.Sprintf("projects/%s/instances/%s", "[PROJECT]", "[INSTANCE]")
var request = &instancepb.GetInstanceRequest{
Name: formattedName,
}
c, err := NewInstanceAdminClient(context.Background(), clientOpt)
if err != nil {
t.Fatal(err)
}
resp, err := c.GetInstance(context.Background(), request)
if st, ok := gstatus.FromError(err); !ok {
t.Errorf("got error %v, expected grpc error", err)
} else if c := st.Code(); c != errCode {
t.Errorf("got error code %q, want %q", c, errCode)
}
_ = resp
}
func TestInstanceAdminCreateInstance(t *testing.T) {
var name string = "name3373707"
var config string = "config-1354792126"
var displayName string = "displayName1615086568"
var nodeCount int32 = 1539922066
var expectedResponse = &instancepb.Instance{
Name: name,
Config: config,
DisplayName: displayName,
NodeCount: nodeCount,
}
mockInstanceAdmin.err = nil
mockInstanceAdmin.reqs = nil
any, err := ptypes.MarshalAny(expectedResponse)
if err != nil {
t.Fatal(err)
}
mockInstanceAdmin.resps = append(mockInstanceAdmin.resps[:0], &longrunningpb.Operation{
Name: "longrunning-test",
Done: true,
Result: &longrunningpb.Operation_Response{Response: any},
})
var formattedParent string = fmt.Sprintf("projects/%s", "[PROJECT]")
var instanceId string = "instanceId-2101995259"
var instance *instancepb.Instance = &instancepb.Instance{}
var request = &instancepb.CreateInstanceRequest{
Parent: formattedParent,
InstanceId: instanceId,
Instance: instance,
}
c, err := NewInstanceAdminClient(context.Background(), clientOpt)
if err != nil {
t.Fatal(err)
}
respLRO, err := c.CreateInstance(context.Background(), request)
if err != nil {
t.Fatal(err)
}
resp, err := respLRO.Wait(context.Background())
if err != nil {
t.Fatal(err)
}
if want, got := request, mockInstanceAdmin.reqs[0]; !proto.Equal(want, got) {
t.Errorf("wrong request %q, want %q", got, want)
}
if want, got := expectedResponse, resp; !proto.Equal(want, got) {
t.Errorf("wrong response %q, want %q)", got, want)
}
}
func TestInstanceAdminCreateInstanceError(t *testing.T) {
errCode := codes.PermissionDenied
mockInstanceAdmin.err = nil
mockInstanceAdmin.resps = append(mockInstanceAdmin.resps[:0], &longrunningpb.Operation{
Name: "longrunning-test",
Done: true,
Result: &longrunningpb.Operation_Error{
Error: &status.Status{
Code: int32(errCode),
Message: "test error",
},
},
})
var formattedParent string = fmt.Sprintf("projects/%s", "[PROJECT]")
var instanceId string = "instanceId-2101995259"
var instance *instancepb.Instance = &instancepb.Instance{}
var request = &instancepb.CreateInstanceRequest{
Parent: formattedParent,
InstanceId: instanceId,
Instance: instance,
}
c, err := NewInstanceAdminClient(context.Background(), clientOpt)
if err != nil {
t.Fatal(err)
}
respLRO, err := c.CreateInstance(context.Background(), request)
if err != nil {
t.Fatal(err)
}
resp, err := respLRO.Wait(context.Background())
if st, ok := gstatus.FromError(err); !ok {
t.Errorf("got error %v, expected grpc error", err)
} else if c := st.Code(); c != errCode {
t.Errorf("got error code %q, want %q", c, errCode)
}
_ = resp
}
func TestInstanceAdminUpdateInstance(t *testing.T) {
var name string = "name3373707"
var config string = "config-1354792126"
var displayName string = "displayName1615086568"
var nodeCount int32 = 1539922066
var expectedResponse = &instancepb.Instance{
Name: name,
Config: config,
DisplayName: displayName,
NodeCount: nodeCount,
}
mockInstanceAdmin.err = nil
mockInstanceAdmin.reqs = nil
any, err := ptypes.MarshalAny(expectedResponse)
if err != nil {
t.Fatal(err)
}
mockInstanceAdmin.resps = append(mockInstanceAdmin.resps[:0], &longrunningpb.Operation{
Name: "longrunning-test",
Done: true,
Result: &longrunningpb.Operation_Response{Response: any},
})
var instance *instancepb.Instance = &instancepb.Instance{}
var fieldMask *field_maskpb.FieldMask = &field_maskpb.FieldMask{}
var request = &instancepb.UpdateInstanceRequest{
Instance: instance,
FieldMask: fieldMask,
}
c, err := NewInstanceAdminClient(context.Background(), clientOpt)
if err != nil {
t.Fatal(err)
}
respLRO, err := c.UpdateInstance(context.Background(), request)
if err != nil {
t.Fatal(err)
}
resp, err := respLRO.Wait(context.Background())
if err != nil {
t.Fatal(err)
}
if want, got := request, mockInstanceAdmin.reqs[0]; !proto.Equal(want, got) {
t.Errorf("wrong request %q, want %q", got, want)
}
if want, got := expectedResponse, resp; !proto.Equal(want, got) {
t.Errorf("wrong response %q, want %q)", got, want)
}
}
func TestInstanceAdminUpdateInstanceError(t *testing.T) {
errCode := codes.PermissionDenied
mockInstanceAdmin.err = nil
mockInstanceAdmin.resps = append(mockInstanceAdmin.resps[:0], &longrunningpb.Operation{
Name: "longrunning-test",
Done: true,
Result: &longrunningpb.Operation_Error{
Error: &status.Status{
Code: int32(errCode),
Message: "test error",
},
},
})
var instance *instancepb.Instance = &instancepb.Instance{}
var fieldMask *field_maskpb.FieldMask = &field_maskpb.FieldMask{}
var request = &instancepb.UpdateInstanceRequest{
Instance: instance,
FieldMask: fieldMask,
}
c, err := NewInstanceAdminClient(context.Background(), clientOpt)
if err != nil {
t.Fatal(err)
}
respLRO, err := c.UpdateInstance(context.Background(), request)
if err != nil {
t.Fatal(err)
}
resp, err := respLRO.Wait(context.Background())
if st, ok := gstatus.FromError(err); !ok {
t.Errorf("got error %v, expected grpc error", err)
} else if c := st.Code(); c != errCode {
t.Errorf("got error code %q, want %q", c, errCode)
}
_ = resp
}
func TestInstanceAdminDeleteInstance(t *testing.T) {
var expectedResponse *emptypb.Empty = &emptypb.Empty{}
mockInstanceAdmin.err = nil
mockInstanceAdmin.reqs = nil
mockInstanceAdmin.resps = append(mockInstanceAdmin.resps[:0], expectedResponse)
var formattedName string = fmt.Sprintf("projects/%s/instances/%s", "[PROJECT]", "[INSTANCE]")
var request = &instancepb.DeleteInstanceRequest{
Name: formattedName,
}
c, err := NewInstanceAdminClient(context.Background(), clientOpt)
if err != nil {
t.Fatal(err)
}
err = c.DeleteInstance(context.Background(), request)
if err != nil {
t.Fatal(err)
}
if want, got := request, mockInstanceAdmin.reqs[0]; !proto.Equal(want, got) {
t.Errorf("wrong request %q, want %q", got, want)
}
}
func TestInstanceAdminDeleteInstanceError(t *testing.T) {
errCode := codes.PermissionDenied
mockInstanceAdmin.err = gstatus.Error(errCode, "test error")
var formattedName string = fmt.Sprintf("projects/%s/instances/%s", "[PROJECT]", "[INSTANCE]")
var request = &instancepb.DeleteInstanceRequest{
Name: formattedName,
}
c, err := NewInstanceAdminClient(context.Background(), clientOpt)
if err != nil {
t.Fatal(err)
}
err = c.DeleteInstance(context.Background(), request)
if st, ok := gstatus.FromError(err); !ok {
t.Errorf("got error %v, expected grpc error", err)
} else if c := st.Code(); c != errCode {
t.Errorf("got error code %q, want %q", c, errCode)
}
}
func TestInstanceAdminSetIamPolicy(t *testing.T) {
var version int32 = 351608024
var etag []byte = []byte("21")
var expectedResponse = &iampb.Policy{
Version: version,
Etag: etag,
}
mockInstanceAdmin.err = nil
mockInstanceAdmin.reqs = nil
mockInstanceAdmin.resps = append(mockInstanceAdmin.resps[:0], expectedResponse)
var formattedResource string = fmt.Sprintf("projects/%s/instances/%s", "[PROJECT]", "[INSTANCE]")
var policy *iampb.Policy = &iampb.Policy{}
var request = &iampb.SetIamPolicyRequest{
Resource: formattedResource,
Policy: policy,
}
c, err := NewInstanceAdminClient(context.Background(), clientOpt)
if err != nil {
t.Fatal(err)
}
resp, err := c.SetIamPolicy(context.Background(), request)
if err != nil {
t.Fatal(err)
}
if want, got := request, mockInstanceAdmin.reqs[0]; !proto.Equal(want, got) {
t.Errorf("wrong request %q, want %q", got, want)
}
if want, got := expectedResponse, resp; !proto.Equal(want, got) {
t.Errorf("wrong response %q, want %q)", got, want)
}
}
func TestInstanceAdminSetIamPolicyError(t *testing.T) {
errCode := codes.PermissionDenied
mockInstanceAdmin.err = gstatus.Error(errCode, "test error")
var formattedResource string = fmt.Sprintf("projects/%s/instances/%s", "[PROJECT]", "[INSTANCE]")
var policy *iampb.Policy = &iampb.Policy{}
var request = &iampb.SetIamPolicyRequest{
Resource: formattedResource,
Policy: policy,
}
c, err := NewInstanceAdminClient(context.Background(), clientOpt)
if err != nil {
t.Fatal(err)
}
resp, err := c.SetIamPolicy(context.Background(), request)
if st, ok := gstatus.FromError(err); !ok {
t.Errorf("got error %v, expected grpc error", err)
} else if c := st.Code(); c != errCode {
t.Errorf("got error code %q, want %q", c, errCode)
}
_ = resp
}
func TestInstanceAdminGetIamPolicy(t *testing.T) {
var version int32 = 351608024
var etag []byte = []byte("21")
var expectedResponse = &iampb.Policy{
Version: version,
Etag: etag,
}
mockInstanceAdmin.err = nil
mockInstanceAdmin.reqs = nil
mockInstanceAdmin.resps = append(mockInstanceAdmin.resps[:0], expectedResponse)
var formattedResource string = fmt.Sprintf("projects/%s/instances/%s", "[PROJECT]", "[INSTANCE]")
var request = &iampb.GetIamPolicyRequest{
Resource: formattedResource,
}
c, err := NewInstanceAdminClient(context.Background(), clientOpt)
if err != nil {
t.Fatal(err)
}
resp, err := c.GetIamPolicy(context.Background(), request)
if err != nil {
t.Fatal(err)
}
if want, got := request, mockInstanceAdmin.reqs[0]; !proto.Equal(want, got) {
t.Errorf("wrong request %q, want %q", got, want)
}
if want, got := expectedResponse, resp; !proto.Equal(want, got) {
t.Errorf("wrong response %q, want %q)", got, want)
}
}
func TestInstanceAdminGetIamPolicyError(t *testing.T) {
errCode := codes.PermissionDenied
mockInstanceAdmin.err = gstatus.Error(errCode, "test error")
var formattedResource string = fmt.Sprintf("projects/%s/instances/%s", "[PROJECT]", "[INSTANCE]")
var request = &iampb.GetIamPolicyRequest{
Resource: formattedResource,
}
c, err := NewInstanceAdminClient(context.Background(), clientOpt)
if err != nil {
t.Fatal(err)
}
resp, err := c.GetIamPolicy(context.Background(), request)
if st, ok := gstatus.FromError(err); !ok {
t.Errorf("got error %v, expected grpc error", err)
} else if c := st.Code(); c != errCode {
t.Errorf("got error code %q, want %q", c, errCode)
}
_ = resp
}
func TestInstanceAdminTestIamPermissions(t *testing.T) {
var expectedResponse *iampb.TestIamPermissionsResponse = &iampb.TestIamPermissionsResponse{}
mockInstanceAdmin.err = nil
mockInstanceAdmin.reqs = nil
mockInstanceAdmin.resps = append(mockInstanceAdmin.resps[:0], expectedResponse)
var formattedResource string = fmt.Sprintf("projects/%s/instances/%s", "[PROJECT]", "[INSTANCE]")
var permissions []string = nil
var request = &iampb.TestIamPermissionsRequest{
Resource: formattedResource,
Permissions: permissions,
}
c, err := NewInstanceAdminClient(context.Background(), clientOpt)
if err != nil {
t.Fatal(err)
}
resp, err := c.TestIamPermissions(context.Background(), request)
if err != nil {
t.Fatal(err)
}
if want, got := request, mockInstanceAdmin.reqs[0]; !proto.Equal(want, got) {
t.Errorf("wrong request %q, want %q", got, want)
}
if want, got := expectedResponse, resp; !proto.Equal(want, got) {
t.Errorf("wrong response %q, want %q)", got, want)
}
}
func TestInstanceAdminTestIamPermissionsError(t *testing.T) {
errCode := codes.PermissionDenied
mockInstanceAdmin.err = gstatus.Error(errCode, "test error")
var formattedResource string = fmt.Sprintf("projects/%s/instances/%s", "[PROJECT]", "[INSTANCE]")
var permissions []string = nil
var request = &iampb.TestIamPermissionsRequest{
Resource: formattedResource,
Permissions: permissions,
}
c, err := NewInstanceAdminClient(context.Background(), clientOpt)
if err != nil {
t.Fatal(err)
}
resp, err := c.TestIamPermissions(context.Background(), request)
if st, ok := gstatus.FromError(err); !ok {
t.Errorf("got error %v, expected grpc error", err)
} else if c := st.Code(); c != errCode {
t.Errorf("got error code %q, want %q", c, errCode)
}
_ = resp
}

View File

@@ -0,0 +1,55 @@
// Copyright 2018 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
//
// https://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 instance
// InstanceAdminProjectPath returns the path for the project resource.
//
// Deprecated: Use
// fmt.Sprintf("projects/%s", project)
// instead.
func InstanceAdminProjectPath(project string) string {
return "" +
"projects/" +
project +
""
}
// InstanceAdminInstanceConfigPath returns the path for the instance config resource.
//
// Deprecated: Use
// fmt.Sprintf("projects/%s/instanceConfigs/%s", project, instanceConfig)
// instead.
func InstanceAdminInstanceConfigPath(project, instanceConfig string) string {
return "" +
"projects/" +
project +
"/instanceConfigs/" +
instanceConfig +
""
}
// InstanceAdminInstancePath returns the path for the instance resource.
//
// Deprecated: Use
// fmt.Sprintf("projects/%s/instances/%s", project, instance)
// instead.
func InstanceAdminInstancePath(project, instance string) string {
return "" +
"projects/" +
project +
"/instances/" +
instance +
""
}

103
vendor/cloud.google.com/go/spanner/apiv1/doc.go generated vendored Normal file
View File

@@ -0,0 +1,103 @@
// Copyright 2019 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
//
// https://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.
// Code generated by gapic-generator. DO NOT EDIT.
// Package spanner is an auto-generated package for the
// Cloud Spanner API.
//
// Cloud Spanner is a managed, mission-critical, globally consistent and
// scalable relational database service.
//
// Use of Context
//
// The ctx passed to NewClient is used for authentication requests and
// for creating the underlying connection, but is not used for subsequent calls.
// Individual methods on the client use the ctx given to them.
//
// To close the open connection, use the Close() method.
//
// For information about setting deadlines, reusing contexts, and more
// please visit godoc.org/cloud.google.com/go.
//
// Use the client at cloud.google.com/go/spanner in preference to this.
package spanner // import "cloud.google.com/go/spanner/apiv1"
import (
"context"
"runtime"
"strings"
"unicode"
"google.golang.org/grpc/metadata"
)
func insertMetadata(ctx context.Context, mds ...metadata.MD) context.Context {
out, _ := metadata.FromOutgoingContext(ctx)
out = out.Copy()
for _, md := range mds {
for k, v := range md {
out[k] = append(out[k], v...)
}
}
return metadata.NewOutgoingContext(ctx, out)
}
// DefaultAuthScopes reports the default set of authentication scopes to use with this package.
func DefaultAuthScopes() []string {
return []string{
"https://www.googleapis.com/auth/cloud-platform",
"https://www.googleapis.com/auth/spanner.data",
}
}
// versionGo returns the Go runtime version. The returned string
// has no whitespace, suitable for reporting in header.
func versionGo() string {
const develPrefix = "devel +"
s := runtime.Version()
if strings.HasPrefix(s, develPrefix) {
s = s[len(develPrefix):]
if p := strings.IndexFunc(s, unicode.IsSpace); p >= 0 {
s = s[:p]
}
return s
}
notSemverRune := func(r rune) bool {
return strings.IndexRune("0123456789.", r) < 0
}
if strings.HasPrefix(s, "go1") {
s = s[2:]
var prerelease string
if p := strings.IndexFunc(s, notSemverRune); p >= 0 {
s, prerelease = s[:p], s[p:]
}
if strings.HasSuffix(s, ".") {
s += "0"
} else if strings.Count(s, ".") < 2 {
s += ".0"
}
if prerelease != "" {
s += "-" + prerelease
}
return s
}
return "UNKNOWN"
}
const versionClient = "20190404"

1165
vendor/cloud.google.com/go/spanner/apiv1/mock_test.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

49
vendor/cloud.google.com/go/spanner/apiv1/path_funcs.go generated vendored Normal file
View File

@@ -0,0 +1,49 @@
// Copyright 2018 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
//
// https://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
// DatabasePath returns the path for the database resource.
//
// Deprecated: Use
// fmt.Sprintf("projects/%s/instances/%s/databases/%s", project, instance, database)
// instead.
func DatabasePath(project, instance, database string) string {
return "" +
"projects/" +
project +
"/instances/" +
instance +
"/databases/" +
database +
""
}
// SessionPath returns the path for the session resource.
//
// Deprecated: Use
// fmt.Sprintf("projects/%s/instances/%s/databases/%s/sessions/%s", project, instance, database, session)
// instead.
func SessionPath(project, instance, database, session string) string {
return "" +
"projects/" +
project +
"/instances/" +
instance +
"/databases/" +
database +
"/sessions/" +
session +
""
}

View File

@@ -0,0 +1,570 @@
// Copyright 2019 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
//
// https://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.
// Code generated by gapic-generator. DO NOT EDIT.
package spanner
import (
"context"
"fmt"
"math"
"time"
"github.com/golang/protobuf/proto"
gax "github.com/googleapis/gax-go/v2"
"google.golang.org/api/iterator"
"google.golang.org/api/option"
"google.golang.org/api/transport"
spannerpb "google.golang.org/genproto/googleapis/spanner/v1"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
)
// CallOptions contains the retry settings for each method of Client.
type CallOptions struct {
CreateSession []gax.CallOption
GetSession []gax.CallOption
ListSessions []gax.CallOption
DeleteSession []gax.CallOption
ExecuteSql []gax.CallOption
ExecuteStreamingSql []gax.CallOption
ExecuteBatchDml []gax.CallOption
Read []gax.CallOption
StreamingRead []gax.CallOption
BeginTransaction []gax.CallOption
Commit []gax.CallOption
Rollback []gax.CallOption
PartitionQuery []gax.CallOption
PartitionRead []gax.CallOption
}
func defaultClientOptions() []option.ClientOption {
return []option.ClientOption{
option.WithEndpoint("spanner.googleapis.com:443"),
option.WithScopes(DefaultAuthScopes()...),
}
}
func defaultCallOptions() *CallOptions {
retry := map[[2]string][]gax.CallOption{
{"default", "idempotent"}: {
gax.WithRetry(func() gax.Retryer {
return gax.OnCodes([]codes.Code{
codes.DeadlineExceeded,
codes.Unavailable,
}, gax.Backoff{
Initial: 1000 * time.Millisecond,
Max: 32000 * time.Millisecond,
Multiplier: 1.3,
})
}),
},
{"long_running", "long_running"}: {
gax.WithRetry(func() gax.Retryer {
return gax.OnCodes([]codes.Code{
codes.Unavailable,
}, gax.Backoff{
Initial: 1000 * time.Millisecond,
Max: 32000 * time.Millisecond,
Multiplier: 1.3,
})
}),
},
}
return &CallOptions{
CreateSession: retry[[2]string{"default", "idempotent"}],
GetSession: retry[[2]string{"default", "idempotent"}],
ListSessions: retry[[2]string{"default", "idempotent"}],
DeleteSession: retry[[2]string{"default", "idempotent"}],
ExecuteSql: retry[[2]string{"default", "idempotent"}],
ExecuteStreamingSql: retry[[2]string{"streaming", "non_idempotent"}],
ExecuteBatchDml: retry[[2]string{"default", "idempotent"}],
Read: retry[[2]string{"default", "idempotent"}],
StreamingRead: retry[[2]string{"streaming", "non_idempotent"}],
BeginTransaction: retry[[2]string{"default", "idempotent"}],
Commit: retry[[2]string{"long_running", "long_running"}],
Rollback: retry[[2]string{"default", "idempotent"}],
PartitionQuery: retry[[2]string{"default", "idempotent"}],
PartitionRead: retry[[2]string{"default", "idempotent"}],
}
}
// Client is a client for interacting with Cloud Spanner API.
//
// Methods, except Close, may be called concurrently. However, fields must not be modified concurrently with method calls.
type Client struct {
// The connection to the service.
conn *grpc.ClientConn
// The gRPC API client.
client spannerpb.SpannerClient
// The call options for this service.
CallOptions *CallOptions
// The x-goog-* metadata to be sent with each request.
xGoogMetadata metadata.MD
}
// NewClient creates a new spanner client.
//
// Cloud Spanner API
//
// The Cloud Spanner API can be used to manage sessions and execute
// transactions on data stored in Cloud Spanner databases.
func NewClient(ctx context.Context, opts ...option.ClientOption) (*Client, error) {
conn, err := transport.DialGRPC(ctx, append(defaultClientOptions(), opts...)...)
if err != nil {
return nil, err
}
c := &Client{
conn: conn,
CallOptions: defaultCallOptions(),
client: spannerpb.NewSpannerClient(conn),
}
c.SetGoogleClientInfo()
return c, nil
}
// Connection returns the client's connection to the API service.
func (c *Client) Connection() *grpc.ClientConn {
return c.conn
}
// Close closes the connection to the API service. The user should invoke this when
// the client is no longer required.
func (c *Client) Close() error {
return c.conn.Close()
}
// SetGoogleClientInfo sets the name and version of the application in
// the `x-goog-api-client` header passed on each request. Intended for
// use by Google-written clients.
func (c *Client) SetGoogleClientInfo(keyval ...string) {
kv := append([]string{"gl-go", versionGo()}, keyval...)
kv = append(kv, "gapic", versionClient, "gax", gax.Version, "grpc", grpc.Version)
c.xGoogMetadata = metadata.Pairs("x-goog-api-client", gax.XGoogHeader(kv...))
}
// CreateSession creates a new session. A session can be used to perform
// transactions that read and/or modify data in a Cloud Spanner database.
// Sessions are meant to be reused for many consecutive
// transactions.
//
// Sessions can only execute one transaction at a time. To execute
// multiple concurrent read-write/write-only transactions, create
// multiple sessions. Note that standalone reads and queries use a
// transaction internally, and count toward the one transaction
// limit.
//
// Cloud Spanner limits the number of sessions that can exist at any given
// time; thus, it is a good idea to delete idle and/or unneeded sessions.
// Aside from explicit deletes, Cloud Spanner can delete sessions for which no
// operations are sent for more than an hour. If a session is deleted,
// requests to it return NOT_FOUND.
//
// Idle sessions can be kept alive by sending a trivial SQL query
// periodically, e.g., "SELECT 1".
func (c *Client) CreateSession(ctx context.Context, req *spannerpb.CreateSessionRequest, opts ...gax.CallOption) (*spannerpb.Session, error) {
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "database", req.GetDatabase()))
ctx = insertMetadata(ctx, c.xGoogMetadata, md)
opts = append(c.CallOptions.CreateSession[0:len(c.CallOptions.CreateSession):len(c.CallOptions.CreateSession)], opts...)
var resp *spannerpb.Session
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
resp, err = c.client.CreateSession(ctx, req, settings.GRPC...)
return err
}, opts...)
if err != nil {
return nil, err
}
return resp, nil
}
// GetSession gets a session. Returns NOT_FOUND if the session does not exist.
// This is mainly useful for determining whether a session is still
// alive.
func (c *Client) GetSession(ctx context.Context, req *spannerpb.GetSessionRequest, opts ...gax.CallOption) (*spannerpb.Session, error) {
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "name", req.GetName()))
ctx = insertMetadata(ctx, c.xGoogMetadata, md)
opts = append(c.CallOptions.GetSession[0:len(c.CallOptions.GetSession):len(c.CallOptions.GetSession)], opts...)
var resp *spannerpb.Session
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
resp, err = c.client.GetSession(ctx, req, settings.GRPC...)
return err
}, opts...)
if err != nil {
return nil, err
}
return resp, nil
}
// ListSessions lists all sessions in a given database.
func (c *Client) ListSessions(ctx context.Context, req *spannerpb.ListSessionsRequest, opts ...gax.CallOption) *SessionIterator {
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "database", req.GetDatabase()))
ctx = insertMetadata(ctx, c.xGoogMetadata, md)
opts = append(c.CallOptions.ListSessions[0:len(c.CallOptions.ListSessions):len(c.CallOptions.ListSessions)], opts...)
it := &SessionIterator{}
req = proto.Clone(req).(*spannerpb.ListSessionsRequest)
it.InternalFetch = func(pageSize int, pageToken string) ([]*spannerpb.Session, string, error) {
var resp *spannerpb.ListSessionsResponse
req.PageToken = pageToken
if pageSize > math.MaxInt32 {
req.PageSize = math.MaxInt32
} else {
req.PageSize = int32(pageSize)
}
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
resp, err = c.client.ListSessions(ctx, req, settings.GRPC...)
return err
}, opts...)
if err != nil {
return nil, "", err
}
return resp.Sessions, resp.NextPageToken, nil
}
fetch := func(pageSize int, pageToken string) (string, error) {
items, nextPageToken, err := it.InternalFetch(pageSize, pageToken)
if err != nil {
return "", err
}
it.items = append(it.items, items...)
return nextPageToken, nil
}
it.pageInfo, it.nextFunc = iterator.NewPageInfo(fetch, it.bufLen, it.takeBuf)
it.pageInfo.MaxSize = int(req.PageSize)
return it
}
// DeleteSession ends a session, releasing server resources associated with it. This will
// asynchronously trigger cancellation of any operations that are running with
// this session.
func (c *Client) DeleteSession(ctx context.Context, req *spannerpb.DeleteSessionRequest, opts ...gax.CallOption) error {
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "name", req.GetName()))
ctx = insertMetadata(ctx, c.xGoogMetadata, md)
opts = append(c.CallOptions.DeleteSession[0:len(c.CallOptions.DeleteSession):len(c.CallOptions.DeleteSession)], opts...)
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
_, err = c.client.DeleteSession(ctx, req, settings.GRPC...)
return err
}, opts...)
return err
}
// ExecuteSql executes an SQL statement, returning all results in a single reply. This
// method cannot be used to return a result set larger than 10 MiB;
// if the query yields more data than that, the query fails with
// a FAILED_PRECONDITION error.
//
// Operations inside read-write transactions might return ABORTED. If
// this occurs, the application should restart the transaction from
// the beginning. See [Transaction][google.spanner.v1.Transaction] for more
// details.
//
// Larger result sets can be fetched in streaming fashion by calling
// [ExecuteStreamingSql][google.spanner.v1.Spanner.ExecuteStreamingSql]
// instead.
func (c *Client) ExecuteSql(ctx context.Context, req *spannerpb.ExecuteSqlRequest, opts ...gax.CallOption) (*spannerpb.ResultSet, error) {
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "session", req.GetSession()))
ctx = insertMetadata(ctx, c.xGoogMetadata, md)
opts = append(c.CallOptions.ExecuteSql[0:len(c.CallOptions.ExecuteSql):len(c.CallOptions.ExecuteSql)], opts...)
var resp *spannerpb.ResultSet
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
resp, err = c.client.ExecuteSql(ctx, req, settings.GRPC...)
return err
}, opts...)
if err != nil {
return nil, err
}
return resp, nil
}
// ExecuteStreamingSql like [ExecuteSql][google.spanner.v1.Spanner.ExecuteSql], except returns the
// result set as a stream. Unlike
// [ExecuteSql][google.spanner.v1.Spanner.ExecuteSql], there is no limit on
// the size of the returned result set. However, no individual row in the
// result set can exceed 100 MiB, and no column value can exceed 10 MiB.
func (c *Client) ExecuteStreamingSql(ctx context.Context, req *spannerpb.ExecuteSqlRequest, opts ...gax.CallOption) (spannerpb.Spanner_ExecuteStreamingSqlClient, error) {
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "session", req.GetSession()))
ctx = insertMetadata(ctx, c.xGoogMetadata, md)
opts = append(c.CallOptions.ExecuteStreamingSql[0:len(c.CallOptions.ExecuteStreamingSql):len(c.CallOptions.ExecuteStreamingSql)], opts...)
var resp spannerpb.Spanner_ExecuteStreamingSqlClient
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
resp, err = c.client.ExecuteStreamingSql(ctx, req, settings.GRPC...)
return err
}, opts...)
if err != nil {
return nil, err
}
return resp, nil
}
// ExecuteBatchDml executes a batch of SQL DML statements. This method allows many statements
// to be run with lower latency than submitting them sequentially with
// [ExecuteSql][google.spanner.v1.Spanner.ExecuteSql].
//
// Statements are executed in order, sequentially.
// [ExecuteBatchDmlResponse][Spanner.ExecuteBatchDmlResponse] will contain a
// [ResultSet][google.spanner.v1.ResultSet] for each DML statement that has successfully executed. If a
// statement fails, its error status will be returned as part of the
// [ExecuteBatchDmlResponse][Spanner.ExecuteBatchDmlResponse]. Execution will
// stop at the first failed statement; the remaining statements will not run.
//
// ExecuteBatchDml is expected to return an OK status with a response even if
// there was an error while processing one of the DML statements. Clients must
// inspect response.status to determine if there were any errors while
// processing the request.
//
// See more details in
// [ExecuteBatchDmlRequest][Spanner.ExecuteBatchDmlRequest] and
// [ExecuteBatchDmlResponse][Spanner.ExecuteBatchDmlResponse].
func (c *Client) ExecuteBatchDml(ctx context.Context, req *spannerpb.ExecuteBatchDmlRequest, opts ...gax.CallOption) (*spannerpb.ExecuteBatchDmlResponse, error) {
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "session", req.GetSession()))
ctx = insertMetadata(ctx, c.xGoogMetadata, md)
opts = append(c.CallOptions.ExecuteBatchDml[0:len(c.CallOptions.ExecuteBatchDml):len(c.CallOptions.ExecuteBatchDml)], opts...)
var resp *spannerpb.ExecuteBatchDmlResponse
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
resp, err = c.client.ExecuteBatchDml(ctx, req, settings.GRPC...)
return err
}, opts...)
if err != nil {
return nil, err
}
return resp, nil
}
// Read reads rows from the database using key lookups and scans, as a
// simple key/value style alternative to
// [ExecuteSql][google.spanner.v1.Spanner.ExecuteSql]. This method cannot be
// used to return a result set larger than 10 MiB; if the read matches more
// data than that, the read fails with a FAILED_PRECONDITION
// error.
//
// Reads inside read-write transactions might return ABORTED. If
// this occurs, the application should restart the transaction from
// the beginning. See [Transaction][google.spanner.v1.Transaction] for more
// details.
//
// Larger result sets can be yielded in streaming fashion by calling
// [StreamingRead][google.spanner.v1.Spanner.StreamingRead] instead.
func (c *Client) Read(ctx context.Context, req *spannerpb.ReadRequest, opts ...gax.CallOption) (*spannerpb.ResultSet, error) {
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "session", req.GetSession()))
ctx = insertMetadata(ctx, c.xGoogMetadata, md)
opts = append(c.CallOptions.Read[0:len(c.CallOptions.Read):len(c.CallOptions.Read)], opts...)
var resp *spannerpb.ResultSet
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
resp, err = c.client.Read(ctx, req, settings.GRPC...)
return err
}, opts...)
if err != nil {
return nil, err
}
return resp, nil
}
// StreamingRead like [Read][google.spanner.v1.Spanner.Read], except returns the result set
// as a stream. Unlike [Read][google.spanner.v1.Spanner.Read], there is no
// limit on the size of the returned result set. However, no individual row in
// the result set can exceed 100 MiB, and no column value can exceed
// 10 MiB.
func (c *Client) StreamingRead(ctx context.Context, req *spannerpb.ReadRequest, opts ...gax.CallOption) (spannerpb.Spanner_StreamingReadClient, error) {
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "session", req.GetSession()))
ctx = insertMetadata(ctx, c.xGoogMetadata, md)
opts = append(c.CallOptions.StreamingRead[0:len(c.CallOptions.StreamingRead):len(c.CallOptions.StreamingRead)], opts...)
var resp spannerpb.Spanner_StreamingReadClient
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
resp, err = c.client.StreamingRead(ctx, req, settings.GRPC...)
return err
}, opts...)
if err != nil {
return nil, err
}
return resp, nil
}
// BeginTransaction begins a new transaction. This step can often be skipped:
// [Read][google.spanner.v1.Spanner.Read],
// [ExecuteSql][google.spanner.v1.Spanner.ExecuteSql] and
// [Commit][google.spanner.v1.Spanner.Commit] can begin a new transaction as a
// side-effect.
func (c *Client) BeginTransaction(ctx context.Context, req *spannerpb.BeginTransactionRequest, opts ...gax.CallOption) (*spannerpb.Transaction, error) {
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "session", req.GetSession()))
ctx = insertMetadata(ctx, c.xGoogMetadata, md)
opts = append(c.CallOptions.BeginTransaction[0:len(c.CallOptions.BeginTransaction):len(c.CallOptions.BeginTransaction)], opts...)
var resp *spannerpb.Transaction
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
resp, err = c.client.BeginTransaction(ctx, req, settings.GRPC...)
return err
}, opts...)
if err != nil {
return nil, err
}
return resp, nil
}
// Commit commits a transaction. The request includes the mutations to be
// applied to rows in the database.
//
// Commit might return an ABORTED error. This can occur at any time;
// commonly, the cause is conflicts with concurrent
// transactions. However, it can also happen for a variety of other
// reasons. If Commit returns ABORTED, the caller should re-attempt
// the transaction from the beginning, re-using the same session.
func (c *Client) Commit(ctx context.Context, req *spannerpb.CommitRequest, opts ...gax.CallOption) (*spannerpb.CommitResponse, error) {
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "session", req.GetSession()))
ctx = insertMetadata(ctx, c.xGoogMetadata, md)
opts = append(c.CallOptions.Commit[0:len(c.CallOptions.Commit):len(c.CallOptions.Commit)], opts...)
var resp *spannerpb.CommitResponse
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
resp, err = c.client.Commit(ctx, req, settings.GRPC...)
return err
}, opts...)
if err != nil {
return nil, err
}
return resp, nil
}
// Rollback rolls back a transaction, releasing any locks it holds. It is a good
// idea to call this for any transaction that includes one or more
// [Read][google.spanner.v1.Spanner.Read] or
// [ExecuteSql][google.spanner.v1.Spanner.ExecuteSql] requests and ultimately
// decides not to commit.
//
// Rollback returns OK if it successfully aborts the transaction, the
// transaction was already aborted, or the transaction is not
// found. Rollback never returns ABORTED.
func (c *Client) Rollback(ctx context.Context, req *spannerpb.RollbackRequest, opts ...gax.CallOption) error {
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "session", req.GetSession()))
ctx = insertMetadata(ctx, c.xGoogMetadata, md)
opts = append(c.CallOptions.Rollback[0:len(c.CallOptions.Rollback):len(c.CallOptions.Rollback)], opts...)
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
_, err = c.client.Rollback(ctx, req, settings.GRPC...)
return err
}, opts...)
return err
}
// PartitionQuery creates a set of partition tokens that can be used to execute a query
// operation in parallel. Each of the returned partition tokens can be used
// by [ExecuteStreamingSql][google.spanner.v1.Spanner.ExecuteStreamingSql] to
// specify a subset of the query result to read. The same session and
// read-only transaction must be used by the PartitionQueryRequest used to
// create the partition tokens and the ExecuteSqlRequests that use the
// partition tokens.
//
// Partition tokens become invalid when the session used to create them
// is deleted, is idle for too long, begins a new transaction, or becomes too
// old. When any of these happen, it is not possible to resume the query, and
// the whole operation must be restarted from the beginning.
func (c *Client) PartitionQuery(ctx context.Context, req *spannerpb.PartitionQueryRequest, opts ...gax.CallOption) (*spannerpb.PartitionResponse, error) {
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "session", req.GetSession()))
ctx = insertMetadata(ctx, c.xGoogMetadata, md)
opts = append(c.CallOptions.PartitionQuery[0:len(c.CallOptions.PartitionQuery):len(c.CallOptions.PartitionQuery)], opts...)
var resp *spannerpb.PartitionResponse
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
resp, err = c.client.PartitionQuery(ctx, req, settings.GRPC...)
return err
}, opts...)
if err != nil {
return nil, err
}
return resp, nil
}
// PartitionRead creates a set of partition tokens that can be used to execute a read
// operation in parallel. Each of the returned partition tokens can be used
// by [StreamingRead][google.spanner.v1.Spanner.StreamingRead] to specify a
// subset of the read result to read. The same session and read-only
// transaction must be used by the PartitionReadRequest used to create the
// partition tokens and the ReadRequests that use the partition tokens. There
// are no ordering guarantees on rows returned among the returned partition
// tokens, or even within each individual StreamingRead call issued with a
// partition_token.
//
// Partition tokens become invalid when the session used to create them
// is deleted, is idle for too long, begins a new transaction, or becomes too
// old. When any of these happen, it is not possible to resume the read, and
// the whole operation must be restarted from the beginning.
func (c *Client) PartitionRead(ctx context.Context, req *spannerpb.PartitionReadRequest, opts ...gax.CallOption) (*spannerpb.PartitionResponse, error) {
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "session", req.GetSession()))
ctx = insertMetadata(ctx, c.xGoogMetadata, md)
opts = append(c.CallOptions.PartitionRead[0:len(c.CallOptions.PartitionRead):len(c.CallOptions.PartitionRead)], opts...)
var resp *spannerpb.PartitionResponse
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
resp, err = c.client.PartitionRead(ctx, req, settings.GRPC...)
return err
}, opts...)
if err != nil {
return nil, err
}
return resp, nil
}
// SessionIterator manages a stream of *spannerpb.Session.
type SessionIterator struct {
items []*spannerpb.Session
pageInfo *iterator.PageInfo
nextFunc func() error
// InternalFetch is for use by the Google Cloud Libraries only.
// It is not part of the stable interface of this package.
//
// InternalFetch returns results from a single call to the underlying RPC.
// The number of results is no greater than pageSize.
// If there are no more results, nextPageToken is empty and err is nil.
InternalFetch func(pageSize int, pageToken string) (results []*spannerpb.Session, nextPageToken string, err error)
}
// PageInfo supports pagination. See the google.golang.org/api/iterator package for details.
func (it *SessionIterator) PageInfo() *iterator.PageInfo {
return it.pageInfo
}
// Next returns the next result. Its second return value is iterator.Done if there are no more
// results. Once Next returns Done, all subsequent calls will return Done.
func (it *SessionIterator) Next() (*spannerpb.Session, error) {
var item *spannerpb.Session
if err := it.nextFunc(); err != nil {
return item, err
}
item = it.items[0]
it.items = it.items[1:]
return item, nil
}
func (it *SessionIterator) bufLen() int {
return len(it.items)
}
func (it *SessionIterator) takeBuf() interface{} {
b := it.items
it.items = nil
return b
}

View File

@@ -0,0 +1,308 @@
// Copyright 2019 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
//
// https://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.
// Code generated by gapic-generator. DO NOT EDIT.
package spanner_test
import (
"context"
"io"
spanner "cloud.google.com/go/spanner/apiv1"
"google.golang.org/api/iterator"
spannerpb "google.golang.org/genproto/googleapis/spanner/v1"
)
func ExampleNewClient() {
ctx := context.Background()
c, err := spanner.NewClient(ctx)
if err != nil {
// TODO: Handle error.
}
// TODO: Use client.
_ = c
}
func ExampleClient_CreateSession() {
ctx := context.Background()
c, err := spanner.NewClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &spannerpb.CreateSessionRequest{
// TODO: Fill request struct fields.
}
resp, err := c.CreateSession(ctx, req)
if err != nil {
// TODO: Handle error.
}
// TODO: Use resp.
_ = resp
}
func ExampleClient_GetSession() {
ctx := context.Background()
c, err := spanner.NewClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &spannerpb.GetSessionRequest{
// TODO: Fill request struct fields.
}
resp, err := c.GetSession(ctx, req)
if err != nil {
// TODO: Handle error.
}
// TODO: Use resp.
_ = resp
}
func ExampleClient_ListSessions() {
ctx := context.Background()
c, err := spanner.NewClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &spannerpb.ListSessionsRequest{
// TODO: Fill request struct fields.
}
it := c.ListSessions(ctx, req)
for {
resp, err := it.Next()
if err == iterator.Done {
break
}
if err != nil {
// TODO: Handle error.
}
// TODO: Use resp.
_ = resp
}
}
func ExampleClient_DeleteSession() {
ctx := context.Background()
c, err := spanner.NewClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &spannerpb.DeleteSessionRequest{
// TODO: Fill request struct fields.
}
err = c.DeleteSession(ctx, req)
if err != nil {
// TODO: Handle error.
}
}
func ExampleClient_ExecuteSql() {
ctx := context.Background()
c, err := spanner.NewClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &spannerpb.ExecuteSqlRequest{
// TODO: Fill request struct fields.
}
resp, err := c.ExecuteSql(ctx, req)
if err != nil {
// TODO: Handle error.
}
// TODO: Use resp.
_ = resp
}
func ExampleClient_ExecuteStreamingSql() {
ctx := context.Background()
c, err := spanner.NewClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &spannerpb.ExecuteSqlRequest{
// TODO: Fill request struct fields.
}
stream, err := c.ExecuteStreamingSql(ctx, req)
if err != nil {
// TODO: Handle error.
}
for {
resp, err := stream.Recv()
if err == io.EOF {
break
}
if err != nil {
// TODO: handle error.
}
// TODO: Use resp.
_ = resp
}
}
func ExampleClient_ExecuteBatchDml() {
ctx := context.Background()
c, err := spanner.NewClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &spannerpb.ExecuteBatchDmlRequest{
// TODO: Fill request struct fields.
}
resp, err := c.ExecuteBatchDml(ctx, req)
if err != nil {
// TODO: Handle error.
}
// TODO: Use resp.
_ = resp
}
func ExampleClient_Read() {
ctx := context.Background()
c, err := spanner.NewClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &spannerpb.ReadRequest{
// TODO: Fill request struct fields.
}
resp, err := c.Read(ctx, req)
if err != nil {
// TODO: Handle error.
}
// TODO: Use resp.
_ = resp
}
func ExampleClient_StreamingRead() {
ctx := context.Background()
c, err := spanner.NewClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &spannerpb.ReadRequest{
// TODO: Fill request struct fields.
}
stream, err := c.StreamingRead(ctx, req)
if err != nil {
// TODO: Handle error.
}
for {
resp, err := stream.Recv()
if err == io.EOF {
break
}
if err != nil {
// TODO: handle error.
}
// TODO: Use resp.
_ = resp
}
}
func ExampleClient_BeginTransaction() {
ctx := context.Background()
c, err := spanner.NewClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &spannerpb.BeginTransactionRequest{
// TODO: Fill request struct fields.
}
resp, err := c.BeginTransaction(ctx, req)
if err != nil {
// TODO: Handle error.
}
// TODO: Use resp.
_ = resp
}
func ExampleClient_Commit() {
ctx := context.Background()
c, err := spanner.NewClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &spannerpb.CommitRequest{
// TODO: Fill request struct fields.
}
resp, err := c.Commit(ctx, req)
if err != nil {
// TODO: Handle error.
}
// TODO: Use resp.
_ = resp
}
func ExampleClient_Rollback() {
ctx := context.Background()
c, err := spanner.NewClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &spannerpb.RollbackRequest{
// TODO: Fill request struct fields.
}
err = c.Rollback(ctx, req)
if err != nil {
// TODO: Handle error.
}
}
func ExampleClient_PartitionQuery() {
ctx := context.Background()
c, err := spanner.NewClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &spannerpb.PartitionQueryRequest{
// TODO: Fill request struct fields.
}
resp, err := c.PartitionQuery(ctx, req)
if err != nil {
// TODO: Handle error.
}
// TODO: Use resp.
_ = resp
}
func ExampleClient_PartitionRead() {
ctx := context.Background()
c, err := spanner.NewClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &spannerpb.PartitionReadRequest{
// TODO: Fill request struct fields.
}
resp, err := c.PartitionRead(ctx, req)
if err != nil {
// TODO: Handle error.
}
// TODO: Use resp.
_ = resp
}

20
vendor/cloud.google.com/go/spanner/appengine.go generated vendored Normal file
View File

@@ -0,0 +1,20 @@
// 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.
// +build appengine
package spanner
// numChannels is the default value for NumChannels of client
const numChannels = 1

347
vendor/cloud.google.com/go/spanner/batch.go generated vendored Normal file
View File

@@ -0,0 +1,347 @@
/*
Copyright 2018 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 (
"bytes"
"context"
"encoding/gob"
"log"
"time"
"github.com/golang/protobuf/proto"
sppb "google.golang.org/genproto/googleapis/spanner/v1"
)
// BatchReadOnlyTransaction is a ReadOnlyTransaction that allows for exporting
// arbitrarily large amounts of data from Cloud Spanner databases.
// BatchReadOnlyTransaction partitions a read/query request. Read/query request
// can then be executed independently over each partition while observing the
// same snapshot of the database. BatchReadOnlyTransaction can also be shared
// across multiple clients by passing around the BatchReadOnlyTransactionID and
// then recreating the transaction using Client.BatchReadOnlyTransactionFromID.
//
// Note: if a client is used only to run partitions, you can
// create it using a ClientConfig with both MinOpened and MaxIdle set to
// zero to avoid creating unnecessary sessions. You can also avoid excess
// gRPC channels by setting ClientConfig.NumChannels to the number of
// concurrently active BatchReadOnlyTransactions you expect to have.
type BatchReadOnlyTransaction struct {
ReadOnlyTransaction
ID BatchReadOnlyTransactionID
}
// BatchReadOnlyTransactionID is a unique identifier for a
// BatchReadOnlyTransaction. It can be used to re-create a
// BatchReadOnlyTransaction on a different machine or process by calling
// Client.BatchReadOnlyTransactionFromID.
type BatchReadOnlyTransactionID struct {
// unique ID for the transaction.
tid transactionID
// sid is the id of the Cloud Spanner session used for this transaction.
sid string
// rts is the read timestamp of this transaction.
rts time.Time
}
// Partition defines a segment of data to be read in a batch read or query. A
// partition can be serialized and processed across several different machines
// or processes.
type Partition struct {
pt []byte
qreq *sppb.ExecuteSqlRequest
rreq *sppb.ReadRequest
}
// PartitionOptions specifies options for a PartitionQueryRequest and
// PartitionReadRequest. See
// https://godoc.org/google.golang.org/genproto/googleapis/spanner/v1#PartitionOptions
// for more details.
type PartitionOptions struct {
// The desired data size for each partition generated.
PartitionBytes int64
// The desired maximum number of partitions to return.
MaxPartitions int64
}
// toProto converts a spanner.PartitionOptions into a sppb.PartitionOptions
func (opt PartitionOptions) toProto() *sppb.PartitionOptions {
return &sppb.PartitionOptions{
PartitionSizeBytes: opt.PartitionBytes,
MaxPartitions: opt.MaxPartitions,
}
}
// PartitionRead returns a list of Partitions that can be used to read rows from
// the database. These partitions can be executed across multiple processes,
// even across different machines. The partition size and count hints can be
// configured using PartitionOptions.
func (t *BatchReadOnlyTransaction) PartitionRead(ctx context.Context, table string, keys KeySet, columns []string, opt PartitionOptions) ([]*Partition, error) {
return t.PartitionReadUsingIndex(ctx, table, "", keys, columns, opt)
}
// PartitionReadUsingIndex returns a list of Partitions that can be used to read
// rows from the database using an index.
func (t *BatchReadOnlyTransaction) PartitionReadUsingIndex(ctx context.Context, table, index string, keys KeySet, columns []string, opt PartitionOptions) ([]*Partition, error) {
sh, ts, err := t.acquire(ctx)
if err != nil {
return nil, err
}
sid, client := sh.getID(), sh.getClient()
var (
kset *sppb.KeySet
resp *sppb.PartitionResponse
partitions []*Partition
)
kset, err = keys.keySetProto()
// request Partitions
if err != nil {
return nil, err
}
resp, err = client.PartitionRead(ctx, &sppb.PartitionReadRequest{
Session: sid,
Transaction: ts,
Table: table,
Index: index,
Columns: columns,
KeySet: kset,
PartitionOptions: opt.toProto(),
})
// prepare ReadRequest
req := &sppb.ReadRequest{
Session: sid,
Transaction: ts,
Table: table,
Index: index,
Columns: columns,
KeySet: kset,
}
// generate Partitions
for _, p := range resp.GetPartitions() {
partitions = append(partitions, &Partition{
pt: p.PartitionToken,
rreq: req,
})
}
return partitions, err
}
// PartitionQuery returns a list of Partitions that can be used to execute a query against the database.
func (t *BatchReadOnlyTransaction) PartitionQuery(ctx context.Context, statement Statement, opt PartitionOptions) ([]*Partition, error) {
sh, ts, err := t.acquire(ctx)
if err != nil {
return nil, err
}
sid, client := sh.getID(), sh.getClient()
params, paramTypes, err := statement.convertParams()
if err != nil {
return nil, err
}
// request Partitions
req := &sppb.PartitionQueryRequest{
Session: sid,
Transaction: ts,
Sql: statement.SQL,
PartitionOptions: opt.toProto(),
Params: params,
ParamTypes: paramTypes,
}
resp, err := client.PartitionQuery(ctx, req)
// prepare ExecuteSqlRequest
r := &sppb.ExecuteSqlRequest{
Session: sid,
Transaction: ts,
Sql: statement.SQL,
Params: params,
ParamTypes: paramTypes,
}
// generate Partitions
var partitions []*Partition
for _, p := range resp.GetPartitions() {
partitions = append(partitions, &Partition{
pt: p.PartitionToken,
qreq: r,
})
}
return partitions, err
}
// release implements txReadEnv.release, noop.
func (t *BatchReadOnlyTransaction) release(err error) {
}
// setTimestamp implements txReadEnv.setTimestamp, noop.
// read timestamp is ready on txn initialization, avoid contending writing to it with future partitions.
func (t *BatchReadOnlyTransaction) setTimestamp(ts time.Time) {
}
// Close marks the txn as closed.
func (t *BatchReadOnlyTransaction) Close() {
t.mu.Lock()
defer t.mu.Unlock()
t.state = txClosed
}
// Cleanup cleans up all the resources used by this transaction and makes
// it unusable. Once this method is invoked, the transaction is no longer
// usable anywhere, including other clients/processes with which this
// transaction was shared.
//
// Calling Cleanup is optional, but recommended. If Cleanup is not called, the
// transaction's resources will be freed when the session expires on the backend and
// is deleted. For more information about recycled sessions, see
// https://cloud.google.com/spanner/docs/sessions.
func (t *BatchReadOnlyTransaction) Cleanup(ctx context.Context) {
t.Close()
t.mu.Lock()
defer t.mu.Unlock()
sh := t.sh
if sh == nil {
return
}
t.sh = nil
sid, client := sh.getID(), sh.getClient()
err := runRetryable(ctx, func(ctx context.Context) error {
_, e := client.DeleteSession(ctx, &sppb.DeleteSessionRequest{Name: sid})
return e
})
if err != nil {
log.Printf("Failed to delete session %v. Error: %v", sid, err)
}
}
// Execute runs a single Partition obtained from PartitionRead or PartitionQuery.
func (t *BatchReadOnlyTransaction) Execute(ctx context.Context, p *Partition) *RowIterator {
var (
sh *sessionHandle
err error
rpc func(ct context.Context, resumeToken []byte) (streamingReceiver, error)
)
if sh, _, err = t.acquire(ctx); err != nil {
return &RowIterator{err: err}
}
client := sh.getClient()
if client == nil {
// Might happen if transaction is closed in the middle of a API call.
return &RowIterator{err: errSessionClosed(sh)}
}
// read or query partition
if p.rreq != nil {
p.rreq.PartitionToken = p.pt
rpc = func(ctx context.Context, resumeToken []byte) (streamingReceiver, error) {
p.rreq.ResumeToken = resumeToken
return client.StreamingRead(ctx, p.rreq)
}
} else {
p.qreq.PartitionToken = p.pt
rpc = func(ctx context.Context, resumeToken []byte) (streamingReceiver, error) {
p.qreq.ResumeToken = resumeToken
return client.ExecuteStreamingSql(ctx, p.qreq)
}
}
return stream(
contextWithOutgoingMetadata(ctx, sh.getMetadata()),
rpc,
t.setTimestamp,
t.release)
}
// MarshalBinary implements BinaryMarshaler.
func (tid BatchReadOnlyTransactionID) MarshalBinary() (data []byte, err error) {
var buf bytes.Buffer
enc := gob.NewEncoder(&buf)
if err := enc.Encode(tid.tid); err != nil {
return nil, err
}
if err := enc.Encode(tid.sid); err != nil {
return nil, err
}
if err := enc.Encode(tid.rts); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
// UnmarshalBinary implements BinaryUnmarshaler.
func (tid *BatchReadOnlyTransactionID) UnmarshalBinary(data []byte) error {
dec := gob.NewDecoder(bytes.NewReader(data))
if err := dec.Decode(&tid.tid); err != nil {
return err
}
if err := dec.Decode(&tid.sid); err != nil {
return err
}
return dec.Decode(&tid.rts)
}
// MarshalBinary implements BinaryMarshaler.
func (p Partition) MarshalBinary() (data []byte, err error) {
var buf bytes.Buffer
enc := gob.NewEncoder(&buf)
if err := enc.Encode(p.pt); err != nil {
return nil, err
}
var isReadPartition bool
var req proto.Message
if p.rreq != nil {
isReadPartition = true
req = p.rreq
} else {
isReadPartition = false
req = p.qreq
}
if err := enc.Encode(isReadPartition); err != nil {
return nil, err
}
if data, err = proto.Marshal(req); err != nil {
return nil, err
}
if err := enc.Encode(data); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
// UnmarshalBinary implements BinaryUnmarshaler.
func (p *Partition) UnmarshalBinary(data []byte) error {
var (
isReadPartition bool
d []byte
err error
)
dec := gob.NewDecoder(bytes.NewReader(data))
if err := dec.Decode(&p.pt); err != nil {
return err
}
if err := dec.Decode(&isReadPartition); err != nil {
return err
}
if err := dec.Decode(&d); err != nil {
return err
}
if isReadPartition {
p.rreq = &sppb.ReadRequest{}
err = proto.Unmarshal(d, p.rreq)
} else {
p.qreq = &sppb.ExecuteSqlRequest{}
err = proto.Unmarshal(d, p.qreq)
}
return err
}

73
vendor/cloud.google.com/go/spanner/batch_test.go generated vendored Normal file
View File

@@ -0,0 +1,73 @@
/*
Copyright 2018 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 (
"testing"
"time"
sppb "google.golang.org/genproto/googleapis/spanner/v1"
)
func TestPartitionRoundTrip(t *testing.T) {
t.Parallel()
for i, want := range []Partition{
{rreq: &sppb.ReadRequest{Table: "t"}},
{qreq: &sppb.ExecuteSqlRequest{Sql: "sql"}},
} {
got := serdesPartition(t, i, &want)
if !testEqual(got, want) {
t.Errorf("got: %#v\nwant:%#v", got, want)
}
}
}
func TestBROTIDRoundTrip(t *testing.T) {
t.Parallel()
tm := time.Now()
want := BatchReadOnlyTransactionID{
tid: []byte("tid"),
sid: "sid",
rts: tm,
}
data, err := want.MarshalBinary()
if err != nil {
t.Fatal(err)
}
var got BatchReadOnlyTransactionID
if err := got.UnmarshalBinary(data); err != nil {
t.Fatal(err)
}
if !testEqual(got, want) {
t.Errorf("got: %#v\nwant:%#v", got, want)
}
}
// serdesPartition is a helper that serialize a Partition then deserialize it
func serdesPartition(t *testing.T, i int, p1 *Partition) (p2 Partition) {
var (
data []byte
err error
)
if data, err = p1.MarshalBinary(); err != nil {
t.Fatalf("#%d: encoding failed %v", i, err)
}
if err = p2.UnmarshalBinary(data); err != nil {
t.Fatalf("#%d: decoding failed %v", i, err)
}
return p2
}

60
vendor/cloud.google.com/go/spanner/big_pdml_test.go generated vendored Normal file
View File

@@ -0,0 +1,60 @@
// Copyright 2018 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.
// +build bigtest
// An integration test for PDML using a relatively large database.
package spanner
import (
"context"
"fmt"
"testing"
)
func TestIntegration_BigPDML(t *testing.T) {
const nRows int = 1e4
ctx := context.Background()
client, _, cleanup := prepareIntegrationTest(ctx, t, singerDBStatements)
defer cleanup()
columns := []string{"SingerId", "FirstName", "LastName"}
// Populate the Singers table with random data.
const rowsPerApply = 1000
for i := 0; i < nRows; i += rowsPerApply {
var muts []*Mutation
for j := 0; j < rowsPerApply; j++ {
id := i + j
row := []interface{}{id, fmt.Sprintf("FirstName%d", id), fmt.Sprintf("LastName%d", id)}
muts = append(muts, Insert("Singers", columns, row))
}
if _, err := client.Apply(ctx, muts); err != nil {
t.Fatal(err)
}
}
// Run a PDML statement.
count, err := client.PartitionedUpdate(ctx, Statement{
SQL: `UPDATE Singers SET Singers.FirstName = "changed" WHERE Singers.SingerId != -1`,
})
if err != nil {
t.Fatal(err)
}
if want := int64(nRows); count != want {
t.Errorf("got %d, want %d", count, want)
}
}

446
vendor/cloud.google.com/go/spanner/client.go generated vendored Normal file
View File

@@ -0,0 +1,446 @@
/*
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 (
"context"
"fmt"
"regexp"
"sync/atomic"
"time"
"cloud.google.com/go/internal/trace"
"cloud.google.com/go/internal/version"
"google.golang.org/api/option"
gtransport "google.golang.org/api/transport/grpc"
sppb "google.golang.org/genproto/googleapis/spanner/v1"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
)
const (
endpoint = "spanner.googleapis.com:443"
// resourcePrefixHeader is the name of the metadata header used to indicate
// the resource being operated on.
resourcePrefixHeader = "google-cloud-resource-prefix"
// xGoogHeaderKey is the name of the metadata header used to indicate client
// information.
xGoogHeaderKey = "x-goog-api-client"
)
const (
// Scope is the scope for Cloud Spanner Data API.
Scope = "https://www.googleapis.com/auth/spanner.data"
// AdminScope is the scope for Cloud Spanner Admin APIs.
AdminScope = "https://www.googleapis.com/auth/spanner.admin"
)
var (
validDBPattern = regexp.MustCompile("^projects/[^/]+/instances/[^/]+/databases/[^/]+$")
xGoogHeaderVal = fmt.Sprintf("gl-go/%s gccl/%s grpc/%s", version.Go(), version.Repo, grpc.Version)
)
func validDatabaseName(db string) error {
if matched := validDBPattern.MatchString(db); !matched {
return fmt.Errorf("database name %q should conform to pattern %q",
db, validDBPattern.String())
}
return nil
}
// Client is a client for reading and writing data to a Cloud Spanner database. A
// client is safe to use concurrently, except for its Close method.
type Client struct {
// rr must be accessed through atomic operations.
rr uint32
// TODO(deklerk): we should not keep multiple ClientConns / SpannerClients. Instead, we should
// have a single ClientConn that has many connections: https://github.com/googleapis/google-api-go-client/blob/003c13302b3ea5ae44344459ba080364bd46155f/internal/pool.go
conns []*grpc.ClientConn
clients []sppb.SpannerClient
database string
// Metadata to be sent with each request.
md metadata.MD
idleSessions *sessionPool
// sessionLabels for the sessions created by this client.
sessionLabels map[string]string
}
// ClientConfig has configurations for the client.
type ClientConfig struct {
// NumChannels is the number of gRPC channels.
// If zero, a reasonable default is used based on the execution environment.
NumChannels int
// SessionPoolConfig is the configuration for session pool.
SessionPoolConfig
// SessionLabels for the sessions created by this client.
// See https://cloud.google.com/spanner/docs/reference/rpc/google.spanner.v1#session for more info.
SessionLabels map[string]string
}
// errDial returns error for dialing to Cloud Spanner.
func errDial(ci int, err error) error {
e := toSpannerError(err).(*Error)
e.decorate(fmt.Sprintf("dialing fails for channel[%v]", ci))
return e
}
func contextWithOutgoingMetadata(ctx context.Context, md metadata.MD) context.Context {
existing, ok := metadata.FromOutgoingContext(ctx)
if ok {
md = metadata.Join(existing, md)
}
return metadata.NewOutgoingContext(ctx, md)
}
// NewClient creates a client to a database. A valid database name has the
// form projects/PROJECT_ID/instances/INSTANCE_ID/databases/DATABASE_ID. It uses a default
// configuration.
func NewClient(ctx context.Context, database string, opts ...option.ClientOption) (*Client, error) {
return NewClientWithConfig(ctx, database, ClientConfig{}, opts...)
}
// NewClientWithConfig creates a client to a database. A valid database name has the
// form projects/PROJECT_ID/instances/INSTANCE_ID/databases/DATABASE_ID.
func NewClientWithConfig(ctx context.Context, database string, config ClientConfig, opts ...option.ClientOption) (c *Client, err error) {
ctx = trace.StartSpan(ctx, "cloud.google.com/go/spanner.NewClient")
defer func() { trace.EndSpan(ctx, err) }()
// Validate database path.
if err := validDatabaseName(database); err != nil {
return nil, err
}
c = &Client{
database: database,
md: metadata.Pairs(
resourcePrefixHeader, database,
xGoogHeaderKey, xGoogHeaderVal),
}
// Make a copy of labels.
c.sessionLabels = make(map[string]string)
for k, v := range config.SessionLabels {
c.sessionLabels[k] = v
}
// gRPC options
allOpts := []option.ClientOption{
option.WithEndpoint(endpoint),
option.WithScopes(Scope),
option.WithGRPCDialOption(
grpc.WithDefaultCallOptions(
grpc.MaxCallSendMsgSize(100<<20),
grpc.MaxCallRecvMsgSize(100<<20),
),
),
}
allOpts = append(allOpts, opts...)
// Prepare gRPC channels.
if config.NumChannels == 0 {
config.NumChannels = numChannels
}
// Default configs for session pool.
if config.MaxOpened == 0 {
config.MaxOpened = uint64(config.NumChannels * 100)
}
if config.MaxBurst == 0 {
config.MaxBurst = 10
}
// TODO(deklerk) This should be replaced with a balancer with config.NumChannels
// connections, instead of config.NumChannels clientconns.
for i := 0; i < config.NumChannels; i++ {
conn, err := gtransport.Dial(ctx, allOpts...)
if err != nil {
return nil, errDial(i, err)
}
c.conns = append(c.conns, conn)
c.clients = append(c.clients, sppb.NewSpannerClient(conn))
}
// Prepare session pool.
config.SessionPoolConfig.getRPCClient = func() (sppb.SpannerClient, error) {
// TODO: support more loadbalancing options.
return c.rrNext(), nil
}
config.SessionPoolConfig.sessionLabels = c.sessionLabels
sp, err := newSessionPool(database, config.SessionPoolConfig, c.md)
if err != nil {
c.Close()
return nil, err
}
c.idleSessions = sp
return c, nil
}
// rrNext returns the next available Cloud Spanner RPC client in a round-robin manner.
func (c *Client) rrNext() sppb.SpannerClient {
return c.clients[atomic.AddUint32(&c.rr, 1)%uint32(len(c.clients))]
}
// Close closes the client.
func (c *Client) Close() {
if c.idleSessions != nil {
c.idleSessions.close()
}
for _, conn := range c.conns {
conn.Close()
}
}
// Single provides a read-only snapshot transaction optimized for the case
// where only a single read or query is needed. This is more efficient than
// using ReadOnlyTransaction() for a single read or query.
//
// Single will use a strong TimestampBound by default. Use
// ReadOnlyTransaction.WithTimestampBound to specify a different
// TimestampBound. A non-strong bound can be used to reduce latency, or
// "time-travel" to prior versions of the database, see the documentation of
// TimestampBound for details.
func (c *Client) Single() *ReadOnlyTransaction {
t := &ReadOnlyTransaction{singleUse: true, sp: c.idleSessions}
t.txReadOnly.txReadEnv = t
return t
}
// ReadOnlyTransaction returns a ReadOnlyTransaction that can be used for
// multiple reads from the database. You must call Close() when the
// ReadOnlyTransaction is no longer needed to release resources on the server.
//
// ReadOnlyTransaction will use a strong TimestampBound by default. Use
// ReadOnlyTransaction.WithTimestampBound to specify a different
// TimestampBound. A non-strong bound can be used to reduce latency, or
// "time-travel" to prior versions of the database, see the documentation of
// TimestampBound for details.
func (c *Client) ReadOnlyTransaction() *ReadOnlyTransaction {
t := &ReadOnlyTransaction{
singleUse: false,
sp: c.idleSessions,
txReadyOrClosed: make(chan struct{}),
}
t.txReadOnly.txReadEnv = t
return t
}
// BatchReadOnlyTransaction returns a BatchReadOnlyTransaction that can be used
// for partitioned reads or queries from a snapshot of the database. This is
// useful in batch processing pipelines where one wants to divide the work of
// reading from the database across multiple machines.
//
// Note: This transaction does not use the underlying session pool but creates a
// new session each time, and the session is reused across clients.
//
// You should call Close() after the txn is no longer needed on local
// client, and call Cleanup() when the txn is finished for all clients, to free
// the session.
func (c *Client) BatchReadOnlyTransaction(ctx context.Context, tb TimestampBound) (*BatchReadOnlyTransaction, error) {
var (
tx transactionID
rts time.Time
s *session
sh *sessionHandle
err error
)
defer func() {
if err != nil && sh != nil {
s.delete(ctx)
}
}()
// create session
sc := c.rrNext()
s, err = createSession(ctx, sc, c.database, c.sessionLabels, c.md)
if err != nil {
return nil, err
}
sh = &sessionHandle{session: s}
// begin transaction
err = runRetryable(contextWithOutgoingMetadata(ctx, sh.getMetadata()), func(ctx context.Context) error {
res, e := sh.getClient().BeginTransaction(ctx, &sppb.BeginTransactionRequest{
Session: sh.getID(),
Options: &sppb.TransactionOptions{
Mode: &sppb.TransactionOptions_ReadOnly_{
ReadOnly: buildTransactionOptionsReadOnly(tb, true),
},
},
})
if e != nil {
return e
}
tx = res.Id
if res.ReadTimestamp != nil {
rts = time.Unix(res.ReadTimestamp.Seconds, int64(res.ReadTimestamp.Nanos))
}
return nil
})
if err != nil {
return nil, err
}
t := &BatchReadOnlyTransaction{
ReadOnlyTransaction: ReadOnlyTransaction{
tx: tx,
txReadyOrClosed: make(chan struct{}),
state: txActive,
sh: sh,
rts: rts,
},
ID: BatchReadOnlyTransactionID{
tid: tx,
sid: sh.getID(),
rts: rts,
},
}
t.txReadOnly.txReadEnv = t
return t, nil
}
// BatchReadOnlyTransactionFromID reconstruct a BatchReadOnlyTransaction from BatchReadOnlyTransactionID
func (c *Client) BatchReadOnlyTransactionFromID(tid BatchReadOnlyTransactionID) *BatchReadOnlyTransaction {
sc := c.rrNext()
s := &session{valid: true, client: sc, id: tid.sid, createTime: time.Now(), md: c.md}
sh := &sessionHandle{session: s}
t := &BatchReadOnlyTransaction{
ReadOnlyTransaction: ReadOnlyTransaction{
tx: tid.tid,
txReadyOrClosed: make(chan struct{}),
state: txActive,
sh: sh,
rts: tid.rts,
},
ID: tid,
}
t.txReadOnly.txReadEnv = t
return t
}
type transactionInProgressKey struct{}
func checkNestedTxn(ctx context.Context) error {
if ctx.Value(transactionInProgressKey{}) != nil {
return spannerErrorf(codes.FailedPrecondition, "Cloud Spanner does not support nested transactions")
}
return nil
}
// ReadWriteTransaction executes a read-write transaction, with retries as
// necessary.
//
// The function f will be called one or more times. It must not maintain
// any state between calls.
//
// If the transaction cannot be committed or if f returns an ABORTED error,
// ReadWriteTransaction will call f again. It will continue to call f until the
// transaction can be committed or the Context times out or is cancelled. If f
// returns an error other than ABORTED, ReadWriteTransaction will abort the
// transaction and return the error.
//
// To limit the number of retries, set a deadline on the Context rather than
// using a fixed limit on the number of attempts. ReadWriteTransaction will
// retry as needed until that deadline is met.
//
// See https://godoc.org/cloud.google.com/go/spanner#ReadWriteTransaction for
// more details.
func (c *Client) ReadWriteTransaction(ctx context.Context, f func(context.Context, *ReadWriteTransaction) error) (commitTimestamp time.Time, err error) {
ctx = trace.StartSpan(ctx, "cloud.google.com/go/spanner.ReadWriteTransaction")
defer func() { trace.EndSpan(ctx, err) }()
if err := checkNestedTxn(ctx); err != nil {
return time.Time{}, err
}
var (
ts time.Time
sh *sessionHandle
)
err = runRetryableNoWrap(ctx, func(ctx context.Context) error {
var (
err error
t *ReadWriteTransaction
)
if sh == nil || sh.getID() == "" || sh.getClient() == nil {
// Session handle hasn't been allocated or has been destroyed.
sh, err = c.idleSessions.takeWriteSession(ctx)
if err != nil {
// If session retrieval fails, just fail the transaction.
return err
}
t = &ReadWriteTransaction{
sh: sh,
tx: sh.getTransactionID(),
}
} else {
t = &ReadWriteTransaction{
sh: sh,
}
}
t.txReadOnly.txReadEnv = t
trace.TracePrintf(ctx, map[string]interface{}{"transactionID": string(sh.getTransactionID())},
"Starting transaction attempt")
if err = t.begin(ctx); err != nil {
// Mask error from begin operation as retryable error.
return errRetry(err)
}
ts, err = t.runInTransaction(ctx, f)
return err
})
if sh != nil {
sh.recycle()
}
return ts, err
}
// applyOption controls the behavior of Client.Apply.
type applyOption struct {
// If atLeastOnce == true, Client.Apply will execute the mutations on Cloud Spanner at least once.
atLeastOnce bool
}
// An ApplyOption is an optional argument to Apply.
type ApplyOption func(*applyOption)
// ApplyAtLeastOnce returns an ApplyOption that removes replay protection.
//
// With this option, Apply may attempt to apply mutations more than once; if
// the mutations are not idempotent, this may lead to a failure being reported
// when the mutation was applied more than once. For example, an insert may
// fail with ALREADY_EXISTS even though the row did not exist before Apply was
// called. For this reason, most users of the library will prefer not to use
// this option. However, ApplyAtLeastOnce requires only a single RPC, whereas
// Apply's default replay protection may require an additional RPC. So this
// option may be appropriate for latency sensitive and/or high throughput blind
// writing.
func ApplyAtLeastOnce() ApplyOption {
return func(ao *applyOption) {
ao.atLeastOnce = true
}
}
// Apply applies a list of mutations atomically to the database.
func (c *Client) Apply(ctx context.Context, ms []*Mutation, opts ...ApplyOption) (commitTimestamp time.Time, err error) {
ao := &applyOption{}
for _, opt := range opts {
opt(ao)
}
if !ao.atLeastOnce {
return c.ReadWriteTransaction(ctx, func(ctx context.Context, t *ReadWriteTransaction) error {
return t.BufferWrite(ms)
})
}
ctx = trace.StartSpan(ctx, "cloud.google.com/go/spanner.Apply")
defer func() { trace.EndSpan(ctx, err) }()
t := &writeOnlyTransaction{c.idleSessions}
return t.applyAtLeastOnce(ctx, ms...)
}

50
vendor/cloud.google.com/go/spanner/client_test.go generated vendored Normal file
View File

@@ -0,0 +1,50 @@
/*
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 (
"strings"
"testing"
)
// Test validDatabaseName()
func TestValidDatabaseName(t *testing.T) {
validDbURI := "projects/spanner-cloud-test/instances/foo/databases/foodb"
invalidDbUris := []string{
// Completely wrong DB URI.
"foobarDB",
// Project ID contains "/".
"projects/spanner-cloud/test/instances/foo/databases/foodb",
// No instance ID.
"projects/spanner-cloud-test/instances//databases/foodb",
}
if err := validDatabaseName(validDbURI); err != nil {
t.Errorf("validateDatabaseName(%q) = %v, want nil", validDbURI, err)
}
for _, d := range invalidDbUris {
if err, wantErr := validDatabaseName(d), "should conform to pattern"; !strings.Contains(err.Error(), wantErr) {
t.Errorf("validateDatabaseName(%q) = %q, want error pattern %q", validDbURI, err, wantErr)
}
}
}
func TestReadOnlyTransactionClose(t *testing.T) {
// Closing a ReadOnlyTransaction shouldn't panic.
c := &Client{}
tx := c.ReadOnlyTransaction()
tx.Close()
}

28
vendor/cloud.google.com/go/spanner/cmp.go generated vendored Normal file
View File

@@ -0,0 +1,28 @@
/*
Copyright 2018 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 (
"cloud.google.com/go/internal/testutil"
"github.com/google/go-cmp/cmp"
)
func testEqual(a, b interface{}) bool {
return testutil.Equal(a, b,
cmp.AllowUnexported(TimestampBound{}, Error{}, Mutation{}, Row{},
Partition{}, BatchReadOnlyTransactionID{}))
}

326
vendor/cloud.google.com/go/spanner/doc.go generated vendored Normal file
View File

@@ -0,0 +1,326 @@
/*
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"

123
vendor/cloud.google.com/go/spanner/errors.go generated vendored Normal file
View File

@@ -0,0 +1,123 @@
/*
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 (
"context"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
)
// Error is the structured error returned by Cloud Spanner client.
type Error struct {
// Code is the canonical error code for describing the nature of a
// particular error.
Code codes.Code
// Desc explains more details of the error.
Desc string
// trailers are the trailers returned in the response, if any.
trailers metadata.MD
}
// Error implements error.Error.
func (e *Error) Error() string {
if e == nil {
return fmt.Sprintf("spanner: OK")
}
return fmt.Sprintf("spanner: code = %q, desc = %q", e.Code, e.Desc)
}
// GRPCStatus returns the corresponding gRPC Status of this Spanner error.
// This allows the error to be converted to a gRPC status using
// `status.Convert(error)`.
func (e *Error) GRPCStatus() *status.Status {
return status.New(e.Code, e.Desc)
}
// decorate decorates an existing spanner.Error with more information.
func (e *Error) decorate(info string) {
e.Desc = fmt.Sprintf("%v, %v", info, e.Desc)
}
// spannerErrorf generates a *spanner.Error with the given error code and
// description.
func spannerErrorf(ec codes.Code, format string, args ...interface{}) error {
return &Error{
Code: ec,
Desc: fmt.Sprintf(format, args...),
}
}
// toSpannerError converts general Go error to *spanner.Error.
func toSpannerError(err error) error {
return toSpannerErrorWithMetadata(err, nil)
}
// toSpannerErrorWithMetadata converts general Go error and grpc trailers to *spanner.Error.
// Note: modifies original error if trailers aren't nil
func toSpannerErrorWithMetadata(err error, trailers metadata.MD) error {
if err == nil {
return nil
}
if se, ok := err.(*Error); ok {
if trailers != nil {
se.trailers = metadata.Join(se.trailers, trailers)
}
return se
}
switch {
case err == context.DeadlineExceeded:
return &Error{codes.DeadlineExceeded, err.Error(), trailers}
case err == context.Canceled:
return &Error{codes.Canceled, err.Error(), trailers}
case grpc.Code(err) == codes.Unknown:
return &Error{codes.Unknown, err.Error(), trailers}
default:
return &Error{grpc.Code(err), grpc.ErrorDesc(err), trailers}
}
}
// ErrCode extracts the canonical error code from a Go error.
func ErrCode(err error) codes.Code {
se, ok := toSpannerError(err).(*Error)
if !ok {
return codes.Unknown
}
return se.Code
}
// ErrDesc extracts the Cloud Spanner error description from a Go error.
func ErrDesc(err error) string {
se, ok := toSpannerError(err).(*Error)
if !ok {
return err.Error()
}
return se.Desc
}
// errTrailers extracts the grpc trailers if present from a Go error.
func errTrailers(err error) metadata.MD {
se, ok := err.(*Error)
if !ok {
return nil
}
return se.trailers
}

48
vendor/cloud.google.com/go/spanner/errors_test.go generated vendored Normal file
View File

@@ -0,0 +1,48 @@
/*
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 (
"context"
"errors"
"testing"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func TestToSpannerError(t *testing.T) {
for _, test := range []struct {
err error
wantCode codes.Code
}{
{errors.New("wha?"), codes.Unknown},
{context.Canceled, codes.Canceled},
{context.DeadlineExceeded, codes.DeadlineExceeded},
{status.Errorf(codes.ResourceExhausted, "so tired"), codes.ResourceExhausted},
{spannerErrorf(codes.InvalidArgument, "bad"), codes.InvalidArgument},
} {
err := toSpannerError(test.err)
if got, want := err.(*Error).Code, test.wantCode; got != want {
t.Errorf("%v: got %s, want %s", test.err, got, want)
}
converted := status.Convert(err)
if converted.Code() != test.wantCode {
t.Errorf("%v: got status %v, want status %v", test.err, converted.Code(), test.wantCode)
}
}
}

680
vendor/cloud.google.com/go/spanner/examples_test.go generated vendored Normal file
View File

@@ -0,0 +1,680 @@
/*
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"
"google.golang.org/api/iterator"
sppb "google.golang.org/genproto/googleapis/spanner/v1"
)
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})
return 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.
})
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()) // size is 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)) // prints "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 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 is NOT 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.
}

2498
vendor/cloud.google.com/go/spanner/integration_test.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,58 @@
/*
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 backoff
import (
"math/rand"
"time"
)
const (
// minBackoff is the minimum backoff used by default.
minBackoff = 20 * time.Millisecond
// maxBackoff is the maximum backoff used by default.
maxBackoff = 32 * time.Second
// jitter is the jitter factor.
jitter = 0.4
// rate is the rate of exponential increase in the backoff.
rate = 1.3
)
var DefaultBackoff = ExponentialBackoff{minBackoff, maxBackoff}
type ExponentialBackoff struct {
Min, Max time.Duration
}
// delay calculates the delay that should happen at n-th
// exponential backoff in a series.
func (b ExponentialBackoff) Delay(retries int) time.Duration {
min, max := float64(b.Min), float64(b.Max)
delay := min
for delay < max && retries > 0 {
delay *= rate
retries--
}
if delay > max {
delay = max
}
delay -= delay * jitter * rand.Float64()
if delay < min {
delay = min
}
return time.Duration(delay)
}

View File

@@ -0,0 +1,61 @@
/*
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 backoff
import (
"math"
"testing"
"time"
)
// Test if exponential backoff helper can produce correct series of
// retry delays.
func TestBackoff(t *testing.T) {
b := ExponentialBackoff{minBackoff, maxBackoff}
tests := []struct {
retries int
min time.Duration
max time.Duration
}{
{
retries: 0,
min: minBackoff,
max: minBackoff,
},
{
retries: 1,
min: minBackoff,
max: time.Duration(rate * float64(minBackoff)),
},
{
retries: 3,
min: time.Duration(math.Pow(rate, 3) * (1 - jitter) * float64(minBackoff)),
max: time.Duration(math.Pow(rate, 3) * float64(minBackoff)),
},
{
retries: 1000,
min: time.Duration((1 - jitter) * float64(maxBackoff)),
max: maxBackoff,
},
}
for _, test := range tests {
got := b.Delay(test.retries)
if float64(got) < float64(test.min) || float64(got) > float64(test.max) {
t.Errorf("delay(%v) = %v, want in range [%v, %v]", test.retries, got, test.min, test.max)
}
}
}

View File

@@ -0,0 +1,65 @@
/*
Copyright 2018 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 testutil
import (
"context"
sppb "google.golang.org/genproto/googleapis/spanner/v1"
"google.golang.org/grpc"
)
// FuncMock overloads some of MockCloudSpannerClient's methods with pluggable
// functions.
//
// Note: if you overload a method, you're in charge of making sure
// MockCloudSpannerClient.ReceivedRequests receives the request appropriately.
type FuncMock struct {
CommitFn func(ctx context.Context, r *sppb.CommitRequest, opts ...grpc.CallOption) (*sppb.CommitResponse, error)
BeginTransactionFn func(ctx context.Context, r *sppb.BeginTransactionRequest, opts ...grpc.CallOption) (*sppb.Transaction, error)
GetSessionFn func(ctx context.Context, r *sppb.GetSessionRequest, opts ...grpc.CallOption) (*sppb.Session, error)
CreateSessionFn func(ctx context.Context, r *sppb.CreateSessionRequest, opts ...grpc.CallOption) (*sppb.Session, error)
*MockCloudSpannerClient
}
func (s FuncMock) Commit(ctx context.Context, r *sppb.CommitRequest, opts ...grpc.CallOption) (*sppb.CommitResponse, error) {
if s.CommitFn == nil {
return s.MockCloudSpannerClient.Commit(ctx, r, opts...)
}
return s.CommitFn(ctx, r, opts...)
}
func (s FuncMock) BeginTransaction(ctx context.Context, r *sppb.BeginTransactionRequest, opts ...grpc.CallOption) (*sppb.Transaction, error) {
if s.BeginTransactionFn == nil {
return s.MockCloudSpannerClient.BeginTransaction(ctx, r, opts...)
}
return s.BeginTransactionFn(ctx, r, opts...)
}
func (s *FuncMock) GetSession(ctx context.Context, r *sppb.GetSessionRequest, opts ...grpc.CallOption) (*sppb.Session, error) {
if s.GetSessionFn == nil {
return s.MockCloudSpannerClient.GetSession(ctx, r, opts...)
}
return s.GetSessionFn(ctx, r, opts...)
}
func (s *FuncMock) CreateSession(c context.Context, r *sppb.CreateSessionRequest, opts ...grpc.CallOption) (*sppb.Session, error) {
if s.CreateSessionFn == nil {
return s.MockCloudSpannerClient.CreateSession(c, r, opts...)
}
return s.CreateSessionFn(c, r, opts...)
}

View File

@@ -0,0 +1,308 @@
/*
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 testutil
import (
"context"
"errors"
"fmt"
"sync"
"testing"
"time"
"github.com/golang/protobuf/proto"
"github.com/golang/protobuf/ptypes/empty"
proto3 "github.com/golang/protobuf/ptypes/struct"
pbt "github.com/golang/protobuf/ptypes/timestamp"
pbs "google.golang.org/genproto/googleapis/rpc/status"
sppb "google.golang.org/genproto/googleapis/spanner/v1"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
// MockCloudSpannerClient is a mock implementation of sppb.SpannerClient.
type MockCloudSpannerClient struct {
sppb.SpannerClient
mu sync.Mutex
t *testing.T
// Live sessions on the client.
sessions map[string]bool
// Session ping history.
pings []string
// Client will stall on any requests.
freezed chan struct{}
// Expected set of actions that have been executed by the client. These
// interfaces should be type reflected against with *Request types in sppb,
// such as sppb.GetSessionRequest. Buffered to a large degree.
ReceivedRequests chan interface{}
}
// NewMockCloudSpannerClient creates new MockCloudSpannerClient instance.
func NewMockCloudSpannerClient(t *testing.T) *MockCloudSpannerClient {
mc := &MockCloudSpannerClient{
t: t,
sessions: map[string]bool{},
ReceivedRequests: make(chan interface{}, 100000),
}
// Produce a closed channel, so the default action of ready is to not block.
mc.Freeze()
mc.Unfreeze()
return mc
}
// DumpPings dumps the ping history.
func (m *MockCloudSpannerClient) DumpPings() []string {
m.mu.Lock()
defer m.mu.Unlock()
return append([]string(nil), m.pings...)
}
// DumpSessions dumps the internal session table.
func (m *MockCloudSpannerClient) DumpSessions() map[string]bool {
m.mu.Lock()
defer m.mu.Unlock()
st := map[string]bool{}
for s, v := range m.sessions {
st[s] = v
}
return st
}
// CreateSession is a placeholder for SpannerClient.CreateSession.
func (m *MockCloudSpannerClient) CreateSession(ctx context.Context, r *sppb.CreateSessionRequest, opts ...grpc.CallOption) (*sppb.Session, error) {
m.ready()
m.ReceivedRequests <- r
m.mu.Lock()
defer m.mu.Unlock()
s := &sppb.Session{}
if r.Database != "mockdb" {
// Reject other databases
return s, status.Errorf(codes.NotFound, fmt.Sprintf("database not found: %v", r.Database))
}
// Generate & record session name.
s.Name = fmt.Sprintf("mockdb-%v", time.Now().UnixNano())
m.sessions[s.Name] = true
return s, nil
}
// GetSession is a placeholder for SpannerClient.GetSession.
func (m *MockCloudSpannerClient) GetSession(ctx context.Context, r *sppb.GetSessionRequest, opts ...grpc.CallOption) (*sppb.Session, error) {
m.ready()
m.ReceivedRequests <- r
m.mu.Lock()
defer m.mu.Unlock()
m.pings = append(m.pings, r.Name)
if _, ok := m.sessions[r.Name]; !ok {
return nil, status.Errorf(codes.NotFound, fmt.Sprintf("Session not found: %v", r.Name))
}
return &sppb.Session{Name: r.Name}, nil
}
// DeleteSession is a placeholder for SpannerClient.DeleteSession.
func (m *MockCloudSpannerClient) DeleteSession(ctx context.Context, r *sppb.DeleteSessionRequest, opts ...grpc.CallOption) (*empty.Empty, error) {
m.ready()
m.ReceivedRequests <- r
m.mu.Lock()
defer m.mu.Unlock()
if _, ok := m.sessions[r.Name]; !ok {
// Session not found.
return &empty.Empty{}, status.Errorf(codes.NotFound, fmt.Sprintf("Session not found: %v", r.Name))
}
// Delete session from in-memory table.
delete(m.sessions, r.Name)
return &empty.Empty{}, nil
}
// ExecuteSql is a placeholder for SpannerClient.ExecuteSql.
func (m *MockCloudSpannerClient) ExecuteSql(ctx context.Context, r *sppb.ExecuteSqlRequest, opts ...grpc.CallOption) (*sppb.ResultSet, error) {
m.ready()
m.ReceivedRequests <- r
m.mu.Lock()
defer m.mu.Unlock()
return &sppb.ResultSet{Stats: &sppb.ResultSetStats{RowCount: &sppb.ResultSetStats_RowCountExact{7}}}, nil
}
// ExecuteBatchDml is a placeholder for SpannerClient.ExecuteBatchDml.
func (m *MockCloudSpannerClient) ExecuteBatchDml(ctx context.Context, r *sppb.ExecuteBatchDmlRequest, opts ...grpc.CallOption) (*sppb.ExecuteBatchDmlResponse, error) {
m.ready()
m.ReceivedRequests <- r
m.mu.Lock()
defer m.mu.Unlock()
return &sppb.ExecuteBatchDmlResponse{Status: &pbs.Status{Code: 0}, ResultSets: []*sppb.ResultSet{}}, nil
}
// ExecuteStreamingSql is a mock implementation of SpannerClient.ExecuteStreamingSql.
func (m *MockCloudSpannerClient) ExecuteStreamingSql(ctx context.Context, r *sppb.ExecuteSqlRequest, opts ...grpc.CallOption) (sppb.Spanner_ExecuteStreamingSqlClient, error) {
m.ready()
m.ReceivedRequests <- r
m.mu.Lock()
defer m.mu.Unlock()
wantReq := &sppb.ExecuteSqlRequest{
Session: "mocksession",
Transaction: &sppb.TransactionSelector{
Selector: &sppb.TransactionSelector_SingleUse{
SingleUse: &sppb.TransactionOptions{
Mode: &sppb.TransactionOptions_ReadOnly_{
ReadOnly: &sppb.TransactionOptions_ReadOnly{
TimestampBound: &sppb.TransactionOptions_ReadOnly_Strong{
Strong: true,
},
ReturnReadTimestamp: false,
},
},
},
},
},
Sql: "mockquery",
Params: &proto3.Struct{
Fields: map[string]*proto3.Value{"var1": {Kind: &proto3.Value_StringValue{StringValue: "abc"}}},
},
ParamTypes: map[string]*sppb.Type{"var1": {Code: sppb.TypeCode_STRING}},
}
if !proto.Equal(r, wantReq) {
return nil, fmt.Errorf("got query request: %v, want: %v", r, wantReq)
}
return nil, errors.New("query never succeeds on mock client")
}
// StreamingRead is a placeholder for SpannerClient.StreamingRead.
func (m *MockCloudSpannerClient) StreamingRead(ctx context.Context, r *sppb.ReadRequest, opts ...grpc.CallOption) (sppb.Spanner_StreamingReadClient, error) {
m.ready()
m.ReceivedRequests <- r
m.mu.Lock()
defer m.mu.Unlock()
wantReq := &sppb.ReadRequest{
Session: "mocksession",
Transaction: &sppb.TransactionSelector{
Selector: &sppb.TransactionSelector_SingleUse{
SingleUse: &sppb.TransactionOptions{
Mode: &sppb.TransactionOptions_ReadOnly_{
ReadOnly: &sppb.TransactionOptions_ReadOnly{
TimestampBound: &sppb.TransactionOptions_ReadOnly_Strong{
Strong: true,
},
ReturnReadTimestamp: false,
},
},
},
},
},
Table: "t_mock",
Columns: []string{"col1", "col2"},
KeySet: &sppb.KeySet{
Keys: []*proto3.ListValue{
{
Values: []*proto3.Value{
{Kind: &proto3.Value_StringValue{StringValue: "foo"}},
},
},
},
Ranges: []*sppb.KeyRange{},
All: false,
},
}
if !proto.Equal(r, wantReq) {
return nil, fmt.Errorf("got query request: %v, want: %v", r, wantReq)
}
return nil, errors.New("read never succeeds on mock client")
}
// BeginTransaction is a placeholder for SpannerClient.BeginTransaction.
func (m *MockCloudSpannerClient) BeginTransaction(ctx context.Context, r *sppb.BeginTransactionRequest, opts ...grpc.CallOption) (*sppb.Transaction, error) {
m.ready()
m.ReceivedRequests <- r
m.mu.Lock()
defer m.mu.Unlock()
resp := &sppb.Transaction{Id: []byte("transaction-1")}
if _, ok := r.Options.Mode.(*sppb.TransactionOptions_ReadOnly_); ok {
resp.ReadTimestamp = &pbt.Timestamp{Seconds: 3, Nanos: 4}
}
return resp, nil
}
// Commit is a placeholder for SpannerClient.Commit.
func (m *MockCloudSpannerClient) Commit(ctx context.Context, r *sppb.CommitRequest, opts ...grpc.CallOption) (*sppb.CommitResponse, error) {
m.ready()
m.ReceivedRequests <- r
m.mu.Lock()
defer m.mu.Unlock()
return &sppb.CommitResponse{CommitTimestamp: &pbt.Timestamp{Seconds: 1, Nanos: 2}}, nil
}
// Rollback is a placeholder for SpannerClient.Rollback.
func (m *MockCloudSpannerClient) Rollback(ctx context.Context, r *sppb.RollbackRequest, opts ...grpc.CallOption) (*empty.Empty, error) {
m.ready()
m.ReceivedRequests <- r
m.mu.Lock()
defer m.mu.Unlock()
return nil, nil
}
// PartitionQuery is a placeholder for SpannerServer.PartitionQuery.
func (m *MockCloudSpannerClient) PartitionQuery(ctx context.Context, r *sppb.PartitionQueryRequest, opts ...grpc.CallOption) (*sppb.PartitionResponse, error) {
m.ready()
m.ReceivedRequests <- r
return nil, errors.New("Unimplemented")
}
// PartitionRead is a placeholder for SpannerServer.PartitionRead.
func (m *MockCloudSpannerClient) PartitionRead(ctx context.Context, r *sppb.PartitionReadRequest, opts ...grpc.CallOption) (*sppb.PartitionResponse, error) {
m.ready()
m.ReceivedRequests <- r
return nil, errors.New("Unimplemented")
}
// Freeze stalls all requests.
func (m *MockCloudSpannerClient) Freeze() {
m.mu.Lock()
defer m.mu.Unlock()
m.freezed = make(chan struct{})
}
// Unfreeze restores processing requests.
func (m *MockCloudSpannerClient) Unfreeze() {
m.mu.Lock()
defer m.mu.Unlock()
close(m.freezed)
}
// ready checks conditions before executing requests
// TODO: add checks for injected errors, actions
func (m *MockCloudSpannerClient) ready() {
m.mu.Lock()
freezed := m.freezed
m.mu.Unlock()
// check if client should be freezed
<-freezed
}

View File

@@ -0,0 +1,265 @@
/*
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 testutil
import (
"context"
"encoding/binary"
"fmt"
"io"
"net"
"sync"
"testing"
"time"
"github.com/golang/protobuf/ptypes/empty"
proto3 "github.com/golang/protobuf/ptypes/struct"
pbt "github.com/golang/protobuf/ptypes/timestamp"
sppb "google.golang.org/genproto/googleapis/spanner/v1"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
var (
// KvMeta is the Metadata for mocked KV table.
KvMeta = sppb.ResultSetMetadata{
RowType: &sppb.StructType{
Fields: []*sppb.StructType_Field{
{
Name: "Key",
Type: &sppb.Type{Code: sppb.TypeCode_STRING},
},
{
Name: "Value",
Type: &sppb.Type{Code: sppb.TypeCode_STRING},
},
},
},
}
)
// MockCtlMsg encapsulates PartialResultSet/error that might be sent to
// client
type MockCtlMsg struct {
// If ResumeToken == true, mock server will generate a row with
// resume token.
ResumeToken bool
// If Err != nil, mock server will return error in RPC response.
Err error
}
// MockCloudSpanner is a mock implementation of SpannerServer interface.
// TODO: make MockCloudSpanner a full-fleged Cloud Spanner implementation.
type MockCloudSpanner struct {
sppb.SpannerServer
s *grpc.Server
t *testing.T
addr string
msgs chan MockCtlMsg
readTs time.Time
mu sync.Mutex
next int
nextSession int
sessions map[string]*sppb.Session
}
// Addr returns the listening address of mock server.
func (m *MockCloudSpanner) Addr() string {
return m.addr
}
// AddMsg generates a new mocked row which can be received by client.
func (m *MockCloudSpanner) AddMsg(err error, resumeToken bool) {
msg := MockCtlMsg{
ResumeToken: resumeToken,
Err: err,
}
if err == io.EOF {
close(m.msgs)
} else {
m.msgs <- msg
}
}
// Done signals an end to a mocked stream.
func (m *MockCloudSpanner) Done() {
close(m.msgs)
}
// CreateSession is a placeholder for SpannerServer.CreateSession.
func (m *MockCloudSpanner) CreateSession(c context.Context, r *sppb.CreateSessionRequest) (*sppb.Session, error) {
m.mu.Lock()
defer m.mu.Unlock()
name := fmt.Sprintf("session-%d", m.nextSession)
m.nextSession++
s := &sppb.Session{Name: name}
m.sessions[name] = s
return s, nil
}
// GetSession is a placeholder for SpannerServer.GetSession.
func (m *MockCloudSpanner) GetSession(c context.Context, r *sppb.GetSessionRequest) (*sppb.Session, error) {
m.mu.Lock()
defer m.mu.Unlock()
if s, ok := m.sessions[r.Name]; ok {
return s, nil
}
return nil, status.Errorf(codes.NotFound, "not found")
}
// DeleteSession is a placeholder for SpannerServer.DeleteSession.
func (m *MockCloudSpanner) DeleteSession(c context.Context, r *sppb.DeleteSessionRequest) (*empty.Empty, error) {
m.mu.Lock()
defer m.mu.Unlock()
delete(m.sessions, r.Name)
return &empty.Empty{}, nil
}
// EncodeResumeToken return mock resume token encoding for an uint64 integer.
func EncodeResumeToken(t uint64) []byte {
rt := make([]byte, 16)
binary.PutUvarint(rt, t)
return rt
}
// DecodeResumeToken decodes a mock resume token into an uint64 integer.
func DecodeResumeToken(t []byte) (uint64, error) {
s, n := binary.Uvarint(t)
if n <= 0 {
return 0, fmt.Errorf("invalid resume token: %v", t)
}
return s, nil
}
// ExecuteStreamingSql is a mock implementation of SpannerServer.ExecuteStreamingSql.
func (m *MockCloudSpanner) ExecuteStreamingSql(r *sppb.ExecuteSqlRequest, s sppb.Spanner_ExecuteStreamingSqlServer) error {
switch r.Sql {
case "SELECT * from t_unavailable":
return status.Errorf(codes.Unavailable, "mock table unavailable")
case "UPDATE t SET x = 2 WHERE x = 1":
err := s.Send(&sppb.PartialResultSet{
Stats: &sppb.ResultSetStats{RowCount: &sppb.ResultSetStats_RowCountLowerBound{3}},
})
if err != nil {
panic(err)
}
return nil
case "SELECT t.key key, t.value value FROM t_mock t":
if r.ResumeToken != nil {
s, err := DecodeResumeToken(r.ResumeToken)
if err != nil {
return err
}
m.mu.Lock()
m.next = int(s) + 1
m.mu.Unlock()
}
for {
msg, more := <-m.msgs
if !more {
break
}
if msg.Err == nil {
var rt []byte
if msg.ResumeToken {
m.mu.Lock()
rt = EncodeResumeToken(uint64(m.next))
m.mu.Unlock()
}
meta := KvMeta
meta.Transaction = &sppb.Transaction{
ReadTimestamp: &pbt.Timestamp{
Seconds: m.readTs.Unix(),
Nanos: int32(m.readTs.Nanosecond()),
},
}
m.mu.Lock()
next := m.next
m.next++
m.mu.Unlock()
err := s.Send(&sppb.PartialResultSet{
Metadata: &meta,
Values: []*proto3.Value{
{Kind: &proto3.Value_StringValue{StringValue: fmt.Sprintf("foo-%02d", next)}},
{Kind: &proto3.Value_StringValue{StringValue: fmt.Sprintf("bar-%02d", next)}},
},
ResumeToken: rt,
})
if err != nil {
return err
}
continue
}
return msg.Err
}
return nil
default:
return fmt.Errorf("unsupported SQL: %v", r.Sql)
}
}
// StreamingRead is a placeholder for SpannerServer.StreamingRead.
func (m *MockCloudSpanner) StreamingRead(r *sppb.ReadRequest, s sppb.Spanner_StreamingReadServer) error {
return s.Send(&sppb.PartialResultSet{})
}
// Serve runs a MockCloudSpanner listening on a random localhost address.
func (m *MockCloudSpanner) Serve() {
m.s = grpc.NewServer()
if m.addr == "" {
m.addr = "localhost:0"
}
lis, err := net.Listen("tcp", m.addr)
if err != nil {
m.t.Fatalf("Failed to listen: %v", err)
}
_, port, err := net.SplitHostPort(lis.Addr().String())
if err != nil {
m.t.Fatalf("Failed to parse listener address: %v", err)
}
sppb.RegisterSpannerServer(m.s, m)
m.addr = "localhost:" + port
go m.s.Serve(lis)
}
// BeginTransaction is a placeholder for SpannerServer.BeginTransaction.
func (m *MockCloudSpanner) BeginTransaction(_ context.Context, r *sppb.BeginTransactionRequest) (*sppb.Transaction, error) {
m.mu.Lock()
defer m.mu.Unlock()
return &sppb.Transaction{}, nil
}
// Stop terminates MockCloudSpanner and closes the serving port.
func (m *MockCloudSpanner) Stop() {
m.s.Stop()
}
// NewMockCloudSpanner creates a new MockCloudSpanner instance.
func NewMockCloudSpanner(t *testing.T, ts time.Time) *MockCloudSpanner {
mcs := &MockCloudSpanner{
t: t,
msgs: make(chan MockCtlMsg, 1000),
readTs: ts,
sessions: map[string]*sppb.Session{},
}
return mcs
}

397
vendor/cloud.google.com/go/spanner/key.go generated vendored Normal file
View File

@@ -0,0 +1,397 @@
/*
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 (
"bytes"
"fmt"
"time"
"cloud.google.com/go/civil"
proto3 "github.com/golang/protobuf/ptypes/struct"
sppb "google.golang.org/genproto/googleapis/spanner/v1"
"google.golang.org/grpc/codes"
)
// A Key can be either a Cloud Spanner row's primary key or a secondary index key.
// It is essentially an interface{} array, which represents a set of Cloud Spanner
// columns. A Key can be used as:
//
// - A primary key which uniquely identifies a Cloud Spanner row.
// - A secondary index key which maps to a set of Cloud Spanner rows indexed under it.
// - An endpoint of primary key/secondary index ranges; see the KeyRange type.
//
// Rows that are identified by the Key type are outputs of read operation or targets of
// delete operation in a mutation. Note that for Insert/Update/InsertOrUpdate/Update
// mutation types, although they don't require a primary key explicitly, the column list
// provided must contain enough columns that can comprise a primary key.
//
// Keys are easy to construct. For example, suppose you have a table with a
// primary key of username and product ID. To make a key for this table:
//
// key := spanner.Key{"john", 16}
//
// See the description of Row and Mutation types for how Go types are
// mapped to Cloud Spanner types. For convenience, Key type supports a wide range
// of Go types:
// - int, int8, int16, int32, int64, and NullInt64 are mapped to Cloud Spanner's INT64 type.
// - uint8, uint16 and uint32 are also mapped to Cloud Spanner's INT64 type.
// - float32, float64, NullFloat64 are mapped to Cloud Spanner's FLOAT64 type.
// - bool and NullBool are mapped to Cloud Spanner's BOOL type.
// - []byte is mapped to Cloud Spanner's BYTES type.
// - string and NullString are mapped to Cloud Spanner's STRING type.
// - time.Time and NullTime are mapped to Cloud Spanner's TIMESTAMP type.
// - civil.Date and NullDate are mapped to Cloud Spanner's DATE type.
type Key []interface{}
// errInvdKeyPartType returns error for unsupported key part type.
func errInvdKeyPartType(part interface{}) error {
return spannerErrorf(codes.InvalidArgument, "key part has unsupported type %T", part)
}
// keyPartValue converts a part of the Key (which is a valid Cloud Spanner type)
// into a proto3.Value. Used for encoding Key type into protobuf.
func keyPartValue(part interface{}) (pb *proto3.Value, err error) {
switch v := part.(type) {
case int:
pb, _, err = encodeValue(int64(v))
case int8:
pb, _, err = encodeValue(int64(v))
case int16:
pb, _, err = encodeValue(int64(v))
case int32:
pb, _, err = encodeValue(int64(v))
case uint8:
pb, _, err = encodeValue(int64(v))
case uint16:
pb, _, err = encodeValue(int64(v))
case uint32:
pb, _, err = encodeValue(int64(v))
case float32:
pb, _, err = encodeValue(float64(v))
case int64, float64, NullInt64, NullFloat64, bool, NullBool, []byte, string, NullString, time.Time, civil.Date, NullTime, NullDate:
pb, _, err = encodeValue(v)
default:
return nil, errInvdKeyPartType(v)
}
return pb, err
}
// proto converts a spanner.Key into a proto3.ListValue.
func (key Key) proto() (*proto3.ListValue, error) {
lv := &proto3.ListValue{}
lv.Values = make([]*proto3.Value, 0, len(key))
for _, part := range key {
v, err := keyPartValue(part)
if err != nil {
return nil, err
}
lv.Values = append(lv.Values, v)
}
return lv, nil
}
// keySetProto lets a single Key act as a KeySet.
func (key Key) keySetProto() (*sppb.KeySet, error) {
kp, err := key.proto()
if err != nil {
return nil, err
}
return &sppb.KeySet{Keys: []*proto3.ListValue{kp}}, nil
}
// String implements fmt.Stringer for Key. For string, []byte and NullString, it
// prints the uninterpreted bytes of their contents, leaving caller with the
// opportunity to escape the output.
func (key Key) String() string {
b := &bytes.Buffer{}
fmt.Fprint(b, "(")
for i, part := range []interface{}(key) {
if i != 0 {
fmt.Fprint(b, ",")
}
switch v := part.(type) {
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, float32, float64, bool:
// Use %v to print numeric types and bool.
fmt.Fprintf(b, "%v", v)
case string:
fmt.Fprintf(b, "%q", v)
case []byte:
if v != nil {
fmt.Fprintf(b, "%q", v)
} else {
fmt.Fprint(b, "<null>")
}
case NullInt64, NullFloat64, NullBool, NullString, NullTime, NullDate:
// The above types implement fmt.Stringer.
fmt.Fprintf(b, "%s", v)
case civil.Date:
fmt.Fprintf(b, "%q", v)
case time.Time:
fmt.Fprintf(b, "%q", v.Format(time.RFC3339Nano))
default:
fmt.Fprintf(b, "%v", v)
}
}
fmt.Fprint(b, ")")
return b.String()
}
// AsPrefix returns a KeyRange for all keys where k is the prefix.
func (key Key) AsPrefix() KeyRange {
return KeyRange{
Start: key,
End: key,
Kind: ClosedClosed,
}
}
// KeyRangeKind describes the kind of interval represented by a KeyRange:
// whether it is open or closed on the left and right.
type KeyRangeKind int
const (
// ClosedOpen is closed on the left and open on the right: the Start
// key is included, the End key is excluded.
ClosedOpen KeyRangeKind = iota
// ClosedClosed is closed on the left and the right: both keys are included.
ClosedClosed
// OpenClosed is open on the left and closed on the right: the Start
// key is excluded, the End key is included.
OpenClosed
// OpenOpen is open on the left and the right: neither key is included.
OpenOpen
)
// A KeyRange represents a range of rows in a table or index.
//
// A range has a Start key and an End key. IncludeStart and IncludeEnd
// indicate whether the Start and End keys are included in the range.
//
// For example, consider the following table definition:
//
// CREATE TABLE UserEvents (
// UserName STRING(MAX),
// EventDate STRING(10),
// ) PRIMARY KEY(UserName, EventDate);
//
// The following keys name rows in this table:
//
// spanner.Key{"Bob", "2014-09-23"}
// spanner.Key{"Alfred", "2015-06-12"}
//
// Since the UserEvents table's PRIMARY KEY clause names two columns, each
// UserEvents key has two elements; the first is the UserName, and the second
// is the EventDate.
//
// Key ranges with multiple components are interpreted lexicographically by
// component using the table or index key's declared sort order. For example,
// the following range returns all events for user "Bob" that occurred in the
// year 2015:
//
// spanner.KeyRange{
// Start: spanner.Key{"Bob", "2015-01-01"},
// End: spanner.Key{"Bob", "2015-12-31"},
// Kind: ClosedClosed,
// }
//
// Start and end keys can omit trailing key components. This affects the
// inclusion and exclusion of rows that exactly match the provided key
// components: if IncludeStart is true, then rows that exactly match the
// provided components of the Start key are included; if IncludeStart is false
// then rows that exactly match are not included. IncludeEnd and End key
// behave in the same fashion.
//
// For example, the following range includes all events for "Bob" that occurred
// during and after the year 2000:
//
// spanner.KeyRange{
// Start: spanner.Key{"Bob", "2000-01-01"},
// End: spanner.Key{"Bob"},
// Kind: ClosedClosed,
// }
//
// The next example retrieves all events for "Bob":
//
// spanner.Key{"Bob"}.AsPrefix()
//
// To retrieve events before the year 2000:
//
// spanner.KeyRange{
// Start: spanner.Key{"Bob"},
// End: spanner.Key{"Bob", "2000-01-01"},
// Kind: ClosedOpen,
// }
//
// Although we specified a Kind for this KeyRange, we didn't need to, because
// the default is ClosedOpen. In later examples we'll omit Kind if it is
// ClosedOpen.
//
// The following range includes all rows in a table or under a
// index:
//
// spanner.AllKeys()
//
// This range returns all users whose UserName begins with any
// character from A to C:
//
// spanner.KeyRange{
// Start: spanner.Key{"A"},
// End: spanner.Key{"D"},
// }
//
// This range returns all users whose UserName begins with B:
//
// spanner.KeyRange{
// Start: spanner.Key{"B"},
// End: spanner.Key{"C"},
// }
//
// Key ranges honor column sort order. For example, suppose a table is defined
// as follows:
//
// CREATE TABLE DescendingSortedTable {
// Key INT64,
// ...
// ) PRIMARY KEY(Key DESC);
//
// The following range retrieves all rows with key values between 1 and 100
// inclusive:
//
// spanner.KeyRange{
// Start: spanner.Key{100},
// End: spanner.Key{1},
// Kind: ClosedClosed,
// }
//
// Note that 100 is passed as the start, and 1 is passed as the end, because
// Key is a descending column in the schema.
type KeyRange struct {
// Start specifies the left boundary of the key range; End specifies
// the right boundary of the key range.
Start, End Key
// Kind describes whether the boundaries of the key range include
// their keys.
Kind KeyRangeKind
}
// String implements fmt.Stringer for KeyRange type.
func (r KeyRange) String() string {
var left, right string
switch r.Kind {
case ClosedClosed:
left, right = "[", "]"
case ClosedOpen:
left, right = "[", ")"
case OpenClosed:
left, right = "(", "]"
case OpenOpen:
left, right = "(", ")"
default:
left, right = "?", "?"
}
return fmt.Sprintf("%s%s,%s%s", left, r.Start, r.End, right)
}
// proto converts KeyRange into sppb.KeyRange.
func (r KeyRange) proto() (*sppb.KeyRange, error) {
var err error
var start, end *proto3.ListValue
pb := &sppb.KeyRange{}
if start, err = r.Start.proto(); err != nil {
return nil, err
}
if end, err = r.End.proto(); err != nil {
return nil, err
}
if r.Kind == ClosedClosed || r.Kind == ClosedOpen {
pb.StartKeyType = &sppb.KeyRange_StartClosed{StartClosed: start}
} else {
pb.StartKeyType = &sppb.KeyRange_StartOpen{StartOpen: start}
}
if r.Kind == ClosedClosed || r.Kind == OpenClosed {
pb.EndKeyType = &sppb.KeyRange_EndClosed{EndClosed: end}
} else {
pb.EndKeyType = &sppb.KeyRange_EndOpen{EndOpen: end}
}
return pb, nil
}
// keySetProto lets a KeyRange act as a KeySet.
func (r KeyRange) keySetProto() (*sppb.KeySet, error) {
rp, err := r.proto()
if err != nil {
return nil, err
}
return &sppb.KeySet{Ranges: []*sppb.KeyRange{rp}}, nil
}
// A KeySet defines a collection of Cloud Spanner keys and/or key ranges. All the
// keys are expected to be in the same table or index. The keys need not be sorted in
// any particular way.
//
// An individual Key can act as a KeySet, as can a KeyRange. Use the KeySets function
// to create a KeySet consisting of multiple Keys and KeyRanges. To obtain an empty
// KeySet, call KeySets with no arguments.
//
// If the same key is specified multiple times in the set (for example if two
// ranges, two keys, or a key and a range overlap), the Cloud Spanner backend behaves
// as if the key were only specified once.
type KeySet interface {
keySetProto() (*sppb.KeySet, error)
}
// AllKeys returns a KeySet that represents all Keys of a table or a index.
func AllKeys() KeySet {
return all{}
}
type all struct{}
func (all) keySetProto() (*sppb.KeySet, error) {
return &sppb.KeySet{All: true}, nil
}
// KeySets returns the union of the KeySets. If any of the KeySets is AllKeys, then
// the resulting KeySet will be equivalent to AllKeys.
func KeySets(keySets ...KeySet) KeySet {
u := make(union, len(keySets))
copy(u, keySets)
return u
}
type union []KeySet
func (u union) keySetProto() (*sppb.KeySet, error) {
upb := &sppb.KeySet{}
for _, ks := range u {
pb, err := ks.keySetProto()
if err != nil {
return nil, err
}
if pb.All {
return pb, nil
}
upb.Keys = append(upb.Keys, pb.Keys...)
upb.Ranges = append(upb.Ranges, pb.Ranges...)
}
return upb, nil
}

372
vendor/cloud.google.com/go/spanner/key_test.go generated vendored Normal file
View File

@@ -0,0 +1,372 @@
/*
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 (
"testing"
"time"
"cloud.google.com/go/civil"
proto3 "github.com/golang/protobuf/ptypes/struct"
sppb "google.golang.org/genproto/googleapis/spanner/v1"
)
// Test Key.String() and Key.proto().
func TestKey(t *testing.T) {
tm, _ := time.Parse(time.RFC3339Nano, "2016-11-15T15:04:05.999999999Z")
dt, _ := civil.ParseDate("2016-11-15")
for _, test := range []struct {
k Key
wantProto *proto3.ListValue
wantStr string
}{
{
k: Key{int(1)},
wantProto: listValueProto(stringProto("1")),
wantStr: "(1)",
},
{
k: Key{int8(1)},
wantProto: listValueProto(stringProto("1")),
wantStr: "(1)",
},
{
k: Key{int16(1)},
wantProto: listValueProto(stringProto("1")),
wantStr: "(1)",
},
{
k: Key{int32(1)},
wantProto: listValueProto(stringProto("1")),
wantStr: "(1)",
},
{
k: Key{int64(1)},
wantProto: listValueProto(stringProto("1")),
wantStr: "(1)",
},
{
k: Key{uint8(1)},
wantProto: listValueProto(stringProto("1")),
wantStr: "(1)",
},
{
k: Key{uint16(1)},
wantProto: listValueProto(stringProto("1")),
wantStr: "(1)",
},
{
k: Key{uint32(1)},
wantProto: listValueProto(stringProto("1")),
wantStr: "(1)",
},
{
k: Key{true},
wantProto: listValueProto(boolProto(true)),
wantStr: "(true)",
},
{
k: Key{float32(1.5)},
wantProto: listValueProto(floatProto(1.5)),
wantStr: "(1.5)",
},
{
k: Key{float64(1.5)},
wantProto: listValueProto(floatProto(1.5)),
wantStr: "(1.5)",
},
{
k: Key{"value"},
wantProto: listValueProto(stringProto("value")),
wantStr: `("value")`,
},
{
k: Key{[]byte(nil)},
wantProto: listValueProto(nullProto()),
wantStr: "(<null>)",
},
{
k: Key{[]byte{}},
wantProto: listValueProto(stringProto("")),
wantStr: `("")`,
},
{
k: Key{tm},
wantProto: listValueProto(stringProto("2016-11-15T15:04:05.999999999Z")),
wantStr: `("2016-11-15T15:04:05.999999999Z")`,
},
{k: Key{dt},
wantProto: listValueProto(stringProto("2016-11-15")),
wantStr: `("2016-11-15")`,
},
{
k: Key{[]byte("value")},
wantProto: listValueProto(bytesProto([]byte("value"))),
wantStr: `("value")`,
},
{
k: Key{NullInt64{1, true}},
wantProto: listValueProto(stringProto("1")),
wantStr: "(1)",
},
{
k: Key{NullInt64{2, false}},
wantProto: listValueProto(nullProto()),
wantStr: "(<null>)",
},
{
k: Key{NullFloat64{1.5, true}},
wantProto: listValueProto(floatProto(1.5)),
wantStr: "(1.5)",
},
{
k: Key{NullFloat64{2.0, false}},
wantProto: listValueProto(nullProto()),
wantStr: "(<null>)",
},
{
k: Key{NullBool{true, true}},
wantProto: listValueProto(boolProto(true)),
wantStr: "(true)",
},
{
k: Key{NullBool{true, false}},
wantProto: listValueProto(nullProto()),
wantStr: "(<null>)",
},
{
k: Key{NullString{"value", true}},
wantProto: listValueProto(stringProto("value")),
wantStr: `("value")`,
},
{
k: Key{NullString{"value", false}},
wantProto: listValueProto(nullProto()),
wantStr: "(<null>)",
},
{
k: Key{NullTime{tm, true}},
wantProto: listValueProto(timeProto(tm)),
wantStr: `("2016-11-15T15:04:05.999999999Z")`,
},
{
k: Key{NullTime{time.Now(), false}},
wantProto: listValueProto(nullProto()),
wantStr: "(<null>)",
},
{
k: Key{NullDate{dt, true}},
wantProto: listValueProto(dateProto(dt)),
wantStr: `("2016-11-15")`,
},
{
k: Key{NullDate{civil.Date{}, false}},
wantProto: listValueProto(nullProto()),
wantStr: "(<null>)",
},
{
k: Key{int(1), NullString{"value", false}, "value", 1.5, true},
wantProto: listValueProto(stringProto("1"), nullProto(), stringProto("value"), floatProto(1.5), boolProto(true)),
wantStr: `(1,<null>,"value",1.5,true)`,
},
} {
if got := test.k.String(); got != test.wantStr {
t.Errorf("%v.String() = %v, want %v", test.k, got, test.wantStr)
}
gotProto, err := test.k.proto()
if err != nil {
t.Errorf("%v.proto() returns error %v; want nil error", test.k, err)
}
if !testEqual(gotProto, test.wantProto) {
t.Errorf("%v.proto() = \n%v\nwant:\n%v", test.k, gotProto, test.wantProto)
}
}
}
// Test KeyRange.String() and KeyRange.proto().
func TestKeyRange(t *testing.T) {
for _, test := range []struct {
kr KeyRange
wantProto *sppb.KeyRange
wantStr string
}{
{
kr: KeyRange{Key{"A"}, Key{"D"}, OpenOpen},
wantProto: &sppb.KeyRange{
StartKeyType: &sppb.KeyRange_StartOpen{StartOpen: listValueProto(stringProto("A"))},
EndKeyType: &sppb.KeyRange_EndOpen{EndOpen: listValueProto(stringProto("D"))},
},
wantStr: `(("A"),("D"))`,
},
{
kr: KeyRange{Key{1}, Key{10}, OpenClosed},
wantProto: &sppb.KeyRange{
StartKeyType: &sppb.KeyRange_StartOpen{StartOpen: listValueProto(stringProto("1"))},
EndKeyType: &sppb.KeyRange_EndClosed{EndClosed: listValueProto(stringProto("10"))},
},
wantStr: "((1),(10)]",
},
{
kr: KeyRange{Key{1.5, 2.1, 0.2}, Key{1.9, 0.7}, ClosedOpen},
wantProto: &sppb.KeyRange{
StartKeyType: &sppb.KeyRange_StartClosed{StartClosed: listValueProto(floatProto(1.5), floatProto(2.1), floatProto(0.2))},
EndKeyType: &sppb.KeyRange_EndOpen{EndOpen: listValueProto(floatProto(1.9), floatProto(0.7))},
},
wantStr: "[(1.5,2.1,0.2),(1.9,0.7))",
},
{
kr: KeyRange{Key{NullInt64{1, true}}, Key{10}, ClosedClosed},
wantProto: &sppb.KeyRange{
StartKeyType: &sppb.KeyRange_StartClosed{StartClosed: listValueProto(stringProto("1"))},
EndKeyType: &sppb.KeyRange_EndClosed{EndClosed: listValueProto(stringProto("10"))},
},
wantStr: "[(1),(10)]",
},
} {
if got := test.kr.String(); got != test.wantStr {
t.Errorf("%v.String() = %v, want %v", test.kr, got, test.wantStr)
}
gotProto, err := test.kr.proto()
if err != nil {
t.Errorf("%v.proto() returns error %v; want nil error", test.kr, err)
}
if !testEqual(gotProto, test.wantProto) {
t.Errorf("%v.proto() = \n%v\nwant:\n%v", test.kr, gotProto.String(), test.wantProto.String())
}
}
}
func TestPrefixRange(t *testing.T) {
got := Key{1}.AsPrefix()
want := KeyRange{Start: Key{1}, End: Key{1}, Kind: ClosedClosed}
if !testEqual(got, want) {
t.Errorf("got %v, want %v", got, want)
}
}
func TestKeySets(t *testing.T) {
int1 := intProto(1)
int2 := intProto(2)
int3 := intProto(3)
int4 := intProto(4)
for i, test := range []struct {
ks KeySet
wantProto *sppb.KeySet
}{
{
KeySets(),
&sppb.KeySet{},
},
{
Key{4},
&sppb.KeySet{
Keys: []*proto3.ListValue{listValueProto(int4)},
},
},
{
AllKeys(),
&sppb.KeySet{All: true},
},
{
KeySets(Key{1, 2}, Key{3, 4}),
&sppb.KeySet{
Keys: []*proto3.ListValue{
listValueProto(int1, int2),
listValueProto(int3, int4),
},
},
},
{
KeyRange{Key{1}, Key{2}, ClosedOpen},
&sppb.KeySet{Ranges: []*sppb.KeyRange{
{
StartKeyType: &sppb.KeyRange_StartClosed{StartClosed: listValueProto(int1)},
EndKeyType: &sppb.KeyRange_EndOpen{EndOpen: listValueProto(int2)},
},
}},
},
{
Key{2}.AsPrefix(),
&sppb.KeySet{Ranges: []*sppb.KeyRange{
{
StartKeyType: &sppb.KeyRange_StartClosed{StartClosed: listValueProto(int2)},
EndKeyType: &sppb.KeyRange_EndClosed{EndClosed: listValueProto(int2)},
},
}},
},
{
KeySets(
KeyRange{Key{1}, Key{2}, ClosedClosed},
KeyRange{Key{3}, Key{4}, OpenClosed},
),
&sppb.KeySet{
Ranges: []*sppb.KeyRange{
{
StartKeyType: &sppb.KeyRange_StartClosed{StartClosed: listValueProto(int1)},
EndKeyType: &sppb.KeyRange_EndClosed{EndClosed: listValueProto(int2)},
},
{
StartKeyType: &sppb.KeyRange_StartOpen{StartOpen: listValueProto(int3)},
EndKeyType: &sppb.KeyRange_EndClosed{EndClosed: listValueProto(int4)},
},
},
},
},
{
KeySets(
Key{1},
KeyRange{Key{2}, Key{3}, ClosedClosed},
KeyRange{Key{4}, Key{5}, OpenClosed},
KeySets(),
Key{6}),
&sppb.KeySet{
Keys: []*proto3.ListValue{
listValueProto(int1),
listValueProto(intProto(6)),
},
Ranges: []*sppb.KeyRange{
{
StartKeyType: &sppb.KeyRange_StartClosed{StartClosed: listValueProto(int2)},
EndKeyType: &sppb.KeyRange_EndClosed{EndClosed: listValueProto(int3)},
},
{
StartKeyType: &sppb.KeyRange_StartOpen{StartOpen: listValueProto(int4)},
EndKeyType: &sppb.KeyRange_EndClosed{EndClosed: listValueProto(intProto(5))},
},
},
},
},
{
KeySets(
Key{1},
KeyRange{Key{2}, Key{3}, ClosedClosed},
AllKeys(),
KeyRange{Key{4}, Key{5}, OpenClosed},
Key{6}),
&sppb.KeySet{All: true},
},
} {
gotProto, err := test.ks.keySetProto()
if err != nil {
t.Errorf("#%d: %v.proto() returns error %v; want nil error", i, test.ks, err)
}
if !testEqual(gotProto, test.wantProto) {
t.Errorf("#%d: %v.proto() = \n%v\nwant:\n%v", i, test.ks, gotProto.String(), test.wantProto.String())
}
}
}

430
vendor/cloud.google.com/go/spanner/mutation.go generated vendored Normal file
View File

@@ -0,0 +1,430 @@
/*
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 (
"reflect"
proto3 "github.com/golang/protobuf/ptypes/struct"
sppb "google.golang.org/genproto/googleapis/spanner/v1"
"google.golang.org/grpc/codes"
)
// op is the mutation operation.
type op int
const (
// opDelete removes a row from a table. Succeeds whether or not the
// key was present.
opDelete op = iota
// opInsert inserts a row into a table. If the row already exists, the
// write or transaction fails.
opInsert
// opInsertOrUpdate inserts a row into a table. If the row already
// exists, it updates it instead. Any column values not explicitly
// written are preserved.
opInsertOrUpdate
// opReplace inserts a row into a table, deleting any existing row.
// Unlike InsertOrUpdate, this means any values not explicitly written
// become NULL.
opReplace
// opUpdate updates a row in a table. If the row does not already
// exist, the write or transaction fails.
opUpdate
)
// A Mutation describes a modification to one or more Cloud Spanner rows. The
// mutation represents an insert, update, delete, etc on a table.
//
// Many mutations can be applied in a single atomic commit. For purposes of
// constraint checking (such as foreign key constraints), the operations can be
// viewed as applying in the same order as the mutations are provided (so that, e.g.,
// a row and its logical "child" can be inserted in the same commit).
//
// The Apply function applies series of mutations. For example,
//
// m := spanner.Insert("User",
// []string{"user_id", "profile"},
// []interface{}{UserID, profile})
// _, err := client.Apply(ctx, []*spanner.Mutation{m})
//
// inserts a new row into the User table. The primary key
// for the new row is UserID (presuming that "user_id" has been declared as the
// primary key of the "User" table).
//
// To apply a series of mutations as part of an atomic read-modify-write operation,
// use ReadWriteTransaction.
//
// Updating a row
//
// Changing the values of columns in an existing row is very similar to
// inserting a new row:
//
// m := spanner.Update("User",
// []string{"user_id", "profile"},
// []interface{}{UserID, profile})
// _, err := client.Apply(ctx, []*spanner.Mutation{m})
//
// Deleting a row
//
// To delete a row, use spanner.Delete:
//
// m := spanner.Delete("User", spanner.Key{UserId})
// _, err := client.Apply(ctx, []*spanner.Mutation{m})
//
// spanner.Delete accepts a KeySet, so you can also pass in a KeyRange, or use the
// spanner.KeySets function to build any combination of Keys and KeyRanges.
//
// Note that deleting a row in a table may also delete rows from other tables
// if cascading deletes are specified in those tables' schemas. Delete does
// nothing if the named row does not exist (does not yield an error).
//
// Deleting a field
//
// To delete/clear a field within a row, use spanner.Update with the value nil:
//
// m := spanner.Update("User",
// []string{"user_id", "profile"},
// []interface{}{UserID, nil})
// _, err := client.Apply(ctx, []*spanner.Mutation{m})
//
// The valid Go types and their corresponding Cloud Spanner types that can be
// used in the Insert/Update/InsertOrUpdate functions are:
//
// string, NullString - STRING
// []string, []NullString - STRING ARRAY
// []byte - BYTES
// [][]byte - BYTES ARRAY
// int, int64, NullInt64 - INT64
// []int, []int64, []NullInt64 - INT64 ARRAY
// bool, NullBool - BOOL
// []bool, []NullBool - BOOL ARRAY
// float64, NullFloat64 - FLOAT64
// []float64, []NullFloat64 - FLOAT64 ARRAY
// time.Time, NullTime - TIMESTAMP
// []time.Time, []NullTime - TIMESTAMP ARRAY
// Date, NullDate - DATE
// []Date, []NullDate - DATE ARRAY
//
// To compare two Mutations for testing purposes, use reflect.DeepEqual.
type Mutation struct {
// op is the operation type of the mutation.
// See documentation for spanner.op for more details.
op op
// Table is the name of the target table to be modified.
table string
// keySet is a set of primary keys that names the rows
// in a delete operation.
keySet KeySet
// columns names the set of columns that are going to be
// modified by Insert, InsertOrUpdate, Replace or Update
// operations.
columns []string
// values specifies the new values for the target columns
// named by Columns.
values []interface{}
}
// mapToMutationParams converts Go map into mutation parameters.
func mapToMutationParams(in map[string]interface{}) ([]string, []interface{}) {
cols := []string{}
vals := []interface{}{}
for k, v := range in {
cols = append(cols, k)
vals = append(vals, v)
}
return cols, vals
}
// errNotStruct returns error for not getting a go struct type.
func errNotStruct(in interface{}) error {
return spannerErrorf(codes.InvalidArgument, "%T is not a go struct type", in)
}
// structToMutationParams converts Go struct into mutation parameters.
// If the input is not a valid Go struct type, structToMutationParams
// returns error.
func structToMutationParams(in interface{}) ([]string, []interface{}, error) {
if in == nil {
return nil, nil, errNotStruct(in)
}
v := reflect.ValueOf(in)
t := v.Type()
if t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct {
// t is a pointer to a struct.
if v.IsNil() {
// Return empty results.
return nil, nil, nil
}
// Get the struct value that in points to.
v = v.Elem()
t = t.Elem()
}
if t.Kind() != reflect.Struct {
return nil, nil, errNotStruct(in)
}
fields, err := fieldCache.Fields(t)
if err != nil {
return nil, nil, toSpannerError(err)
}
var cols []string
var vals []interface{}
for _, f := range fields {
cols = append(cols, f.Name)
vals = append(vals, v.FieldByIndex(f.Index).Interface())
}
return cols, vals, nil
}
// Insert returns a Mutation to insert a row into a table. If the row already
// exists, the write or transaction fails.
func Insert(table string, cols []string, vals []interface{}) *Mutation {
return &Mutation{
op: opInsert,
table: table,
columns: cols,
values: vals,
}
}
// InsertMap returns a Mutation to insert a row into a table, specified by
// a map of column name to value. If the row already exists, the write or
// transaction fails.
func InsertMap(table string, in map[string]interface{}) *Mutation {
cols, vals := mapToMutationParams(in)
return Insert(table, cols, vals)
}
// InsertStruct returns a Mutation to insert a row into a table, specified by
// a Go struct. If the row already exists, the write or transaction fails.
//
// The in argument must be a struct or a pointer to a struct. Its exported
// fields specify the column names and values. Use a field tag like "spanner:name"
// to provide an alternative column name, or use "spanner:-" to ignore the field.
func InsertStruct(table string, in interface{}) (*Mutation, error) {
cols, vals, err := structToMutationParams(in)
if err != nil {
return nil, err
}
return Insert(table, cols, vals), nil
}
// Update returns a Mutation to update a row in a table. If the row does not
// already exist, the write or transaction fails.
func Update(table string, cols []string, vals []interface{}) *Mutation {
return &Mutation{
op: opUpdate,
table: table,
columns: cols,
values: vals,
}
}
// UpdateMap returns a Mutation to update a row in a table, specified by
// a map of column to value. If the row does not already exist, the write or
// transaction fails.
func UpdateMap(table string, in map[string]interface{}) *Mutation {
cols, vals := mapToMutationParams(in)
return Update(table, cols, vals)
}
// UpdateStruct returns a Mutation to update a row in a table, specified by a Go
// struct. If the row does not already exist, the write or transaction fails.
func UpdateStruct(table string, in interface{}) (*Mutation, error) {
cols, vals, err := structToMutationParams(in)
if err != nil {
return nil, err
}
return Update(table, cols, vals), nil
}
// InsertOrUpdate returns a Mutation to insert a row into a table. If the row
// already exists, it updates it instead. Any column values not explicitly
// written are preserved.
//
// For a similar example, See Update.
func InsertOrUpdate(table string, cols []string, vals []interface{}) *Mutation {
return &Mutation{
op: opInsertOrUpdate,
table: table,
columns: cols,
values: vals,
}
}
// InsertOrUpdateMap returns a Mutation to insert a row into a table,
// specified by a map of column to value. If the row already exists, it
// updates it instead. Any column values not explicitly written are preserved.
//
// For a similar example, See UpdateMap.
func InsertOrUpdateMap(table string, in map[string]interface{}) *Mutation {
cols, vals := mapToMutationParams(in)
return InsertOrUpdate(table, cols, vals)
}
// InsertOrUpdateStruct returns a Mutation to insert a row into a table,
// specified by a Go struct. If the row already exists, it updates it instead.
// Any column values not explicitly written are preserved.
//
// The in argument must be a struct or a pointer to a struct. Its exported
// fields specify the column names and values. Use a field tag like "spanner:name"
// to provide an alternative column name, or use "spanner:-" to ignore the field.
//
// For a similar example, See UpdateStruct.
func InsertOrUpdateStruct(table string, in interface{}) (*Mutation, error) {
cols, vals, err := structToMutationParams(in)
if err != nil {
return nil, err
}
return InsertOrUpdate(table, cols, vals), nil
}
// Replace returns a Mutation to insert a row into a table, deleting any
// existing row. Unlike InsertOrUpdate, this means any values not explicitly
// written become NULL.
//
// For a similar example, See Update.
func Replace(table string, cols []string, vals []interface{}) *Mutation {
return &Mutation{
op: opReplace,
table: table,
columns: cols,
values: vals,
}
}
// ReplaceMap returns a Mutation to insert a row into a table, deleting any
// existing row. Unlike InsertOrUpdateMap, this means any values not explicitly
// written become NULL. The row is specified by a map of column to value.
//
// For a similar example, See UpdateMap.
func ReplaceMap(table string, in map[string]interface{}) *Mutation {
cols, vals := mapToMutationParams(in)
return Replace(table, cols, vals)
}
// ReplaceStruct returns a Mutation to insert a row into a table, deleting any
// existing row. Unlike InsertOrUpdateMap, this means any values not explicitly
// written become NULL. The row is specified by a Go struct.
//
// The in argument must be a struct or a pointer to a struct. Its exported
// fields specify the column names and values. Use a field tag like "spanner:name"
// to provide an alternative column name, or use "spanner:-" to ignore the field.
//
// For a similar example, See UpdateStruct.
func ReplaceStruct(table string, in interface{}) (*Mutation, error) {
cols, vals, err := structToMutationParams(in)
if err != nil {
return nil, err
}
return Replace(table, cols, vals), nil
}
// Delete removes the rows described by the KeySet from the table. It succeeds
// whether or not the keys were present.
func Delete(table string, ks KeySet) *Mutation {
return &Mutation{
op: opDelete,
table: table,
keySet: ks,
}
}
// prepareWrite generates sppb.Mutation_Write from table name, column names
// and new column values.
func prepareWrite(table string, columns []string, vals []interface{}) (*sppb.Mutation_Write, error) {
v, err := encodeValueArray(vals)
if err != nil {
return nil, err
}
return &sppb.Mutation_Write{
Table: table,
Columns: columns,
Values: []*proto3.ListValue{v},
}, nil
}
// errInvdMutationOp returns error for unrecognized mutation operation.
func errInvdMutationOp(m Mutation) error {
return spannerErrorf(codes.InvalidArgument, "Unknown op type: %d", m.op)
}
// proto converts spanner.Mutation to sppb.Mutation, in preparation to send
// RPCs.
func (m Mutation) proto() (*sppb.Mutation, error) {
var pb *sppb.Mutation
switch m.op {
case opDelete:
var kp *sppb.KeySet
if m.keySet != nil {
var err error
kp, err = m.keySet.keySetProto()
if err != nil {
return nil, err
}
}
pb = &sppb.Mutation{
Operation: &sppb.Mutation_Delete_{
Delete: &sppb.Mutation_Delete{
Table: m.table,
KeySet: kp,
},
},
}
case opInsert:
w, err := prepareWrite(m.table, m.columns, m.values)
if err != nil {
return nil, err
}
pb = &sppb.Mutation{Operation: &sppb.Mutation_Insert{Insert: w}}
case opInsertOrUpdate:
w, err := prepareWrite(m.table, m.columns, m.values)
if err != nil {
return nil, err
}
pb = &sppb.Mutation{Operation: &sppb.Mutation_InsertOrUpdate{InsertOrUpdate: w}}
case opReplace:
w, err := prepareWrite(m.table, m.columns, m.values)
if err != nil {
return nil, err
}
pb = &sppb.Mutation{Operation: &sppb.Mutation_Replace{Replace: w}}
case opUpdate:
w, err := prepareWrite(m.table, m.columns, m.values)
if err != nil {
return nil, err
}
pb = &sppb.Mutation{Operation: &sppb.Mutation_Update{Update: w}}
default:
return nil, errInvdMutationOp(m)
}
return pb, nil
}
// mutationsProto turns a spanner.Mutation array into a sppb.Mutation array,
// it is convenient for sending batch mutations to Cloud Spanner.
func mutationsProto(ms []*Mutation) ([]*sppb.Mutation, error) {
l := make([]*sppb.Mutation, 0, len(ms))
for _, m := range ms {
pb, err := m.proto()
if err != nil {
return nil, err
}
l = append(l, pb)
}
return l, nil
}

570
vendor/cloud.google.com/go/spanner/mutation_test.go generated vendored Normal file
View File

@@ -0,0 +1,570 @@
/*
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)
}
}
}

20
vendor/cloud.google.com/go/spanner/not_appengine.go generated vendored Normal file
View File

@@ -0,0 +1,20 @@
// 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.
// +build !appengine
package spanner
// numChannels is the default value for NumChannels of client
const numChannels = 4

52
vendor/cloud.google.com/go/spanner/oc_test.go generated vendored Normal file
View File

@@ -0,0 +1,52 @@
// Copyright 2018 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 (
"context"
"testing"
"time"
"cloud.google.com/go/internal/testutil"
stestutil "cloud.google.com/go/spanner/internal/testutil"
"google.golang.org/api/option"
"google.golang.org/grpc"
)
// Check that stats are being exported.
func TestOCStats(t *testing.T) {
te := testutil.NewTestExporter()
defer te.Unregister()
ms := stestutil.NewMockCloudSpanner(t, trxTs)
ms.Serve()
ctx := context.Background()
c, err := NewClient(ctx, "projects/P/instances/I/databases/D",
option.WithEndpoint(ms.Addr()),
option.WithGRPCDialOption(grpc.WithInsecure()),
option.WithoutAuthentication())
if err != nil {
t.Fatal(err)
}
defer c.Close()
c.Single().ReadRow(ctx, "Users", Key{"alice"}, []string{"email"})
// Wait until we see data from the view.
select {
case <-te.Stats:
case <-time.After(1 * time.Second):
t.Fatal("no stats were exported before timeout")
}
}

102
vendor/cloud.google.com/go/spanner/pdml.go generated vendored Normal file
View File

@@ -0,0 +1,102 @@
// Copyright 2018 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 (
"context"
"time"
"cloud.google.com/go/internal/trace"
"google.golang.org/api/iterator"
sppb "google.golang.org/genproto/googleapis/spanner/v1"
"google.golang.org/grpc/codes"
)
// PartitionedUpdate executes a DML statement in parallel across the database, using
// separate, internal transactions that commit independently. The DML statement must
// be fully partitionable: it must be expressible as the union of many statements
// each of which accesses only a single row of the table. The statement should also be
// idempotent, because it may be applied more than once.
//
// PartitionedUpdate returns an estimated count of the number of rows affected. The actual
// number of affected rows may be greater than the estimate.
func (c *Client) PartitionedUpdate(ctx context.Context, statement Statement) (count int64, err error) {
ctx = trace.StartSpan(ctx, "cloud.google.com/go/spanner.PartitionedUpdate")
defer func() { trace.EndSpan(ctx, err) }()
if err := checkNestedTxn(ctx); err != nil {
return 0, err
}
var (
tx transactionID
s *session
sh *sessionHandle
)
// create session
sc := c.rrNext()
s, err = createSession(ctx, sc, c.database, c.sessionLabels, c.md)
if err != nil {
return 0, toSpannerError(err)
}
defer s.delete(ctx)
sh = &sessionHandle{session: s}
// begin transaction
err = runRetryable(contextWithOutgoingMetadata(ctx, sh.getMetadata()), func(ctx context.Context) error {
res, e := sc.BeginTransaction(ctx, &sppb.BeginTransactionRequest{
Session: sh.getID(),
Options: &sppb.TransactionOptions{
Mode: &sppb.TransactionOptions_PartitionedDml_{PartitionedDml: &sppb.TransactionOptions_PartitionedDml{}},
},
})
if e != nil {
return e
}
tx = res.Id
return nil
})
if err != nil {
return 0, toSpannerError(err)
}
req := &sppb.ExecuteSqlRequest{
Session: sh.getID(),
Transaction: &sppb.TransactionSelector{
Selector: &sppb.TransactionSelector_Id{Id: tx},
},
Sql: statement.SQL,
}
rpc := func(ctx context.Context, resumeToken []byte) (streamingReceiver, error) {
req.ResumeToken = resumeToken
return sc.ExecuteStreamingSql(ctx, req)
}
iter := stream(contextWithOutgoingMetadata(ctx, sh.getMetadata()),
rpc, func(time.Time) {}, func(error) {})
// TODO(jba): factor out the following code from here and ReadWriteTransaction.Update.
defer iter.Stop()
for {
_, err := iter.Next()
if err == iterator.Done {
break
}
if err != nil {
return 0, toSpannerError(err)
}
time.Sleep(time.Second)
}
if !iter.sawStats {
return 0, spannerErrorf(codes.InvalidArgument, "query passed to Update: %q", statement.SQL)
}
return iter.RowCount, nil
}

61
vendor/cloud.google.com/go/spanner/pdml_test.go generated vendored Normal file
View File

@@ -0,0 +1,61 @@
// Copyright 2018 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 (
"context"
"io"
"testing"
"cloud.google.com/go/spanner/internal/testutil"
sppb "google.golang.org/genproto/googleapis/spanner/v1"
"google.golang.org/grpc/codes"
)
func TestMockPartitionedUpdate(t *testing.T) {
t.Parallel()
ctx := context.Background()
ms := testutil.NewMockCloudSpanner(t, trxTs)
ms.Serve()
mc := sppb.NewSpannerClient(dialMock(t, ms))
client := &Client{database: "mockdb"}
client.clients = append(client.clients, mc)
stmt := NewStatement("UPDATE t SET x = 2 WHERE x = 1")
rowCount, err := client.PartitionedUpdate(ctx, stmt)
if err != nil {
t.Fatal(err)
}
want := int64(3)
if rowCount != want {
t.Errorf("got %d, want %d", rowCount, want)
}
}
func TestMockPartitionedUpdateWithQuery(t *testing.T) {
t.Parallel()
ctx := context.Background()
ms := testutil.NewMockCloudSpanner(t, trxTs)
ms.AddMsg(io.EOF, true)
ms.Serve()
mc := sppb.NewSpannerClient(dialMock(t, ms))
client := &Client{database: "mockdb"}
client.clients = append(client.clients, mc)
stmt := NewStatement("SELECT t.key key, t.value value FROM t_mock t")
_, err := client.PartitionedUpdate(ctx, stmt)
wantCode := codes.InvalidArgument
if serr, ok := err.(*Error); !ok || serr.Code != wantCode {
t.Errorf("got error %v, want code %s", err, wantCode)
}
}

113
vendor/cloud.google.com/go/spanner/protoutils.go generated vendored Normal file
View File

@@ -0,0 +1,113 @@
/*
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 (
"encoding/base64"
"strconv"
"time"
"cloud.google.com/go/civil"
proto3 "github.com/golang/protobuf/ptypes/struct"
sppb "google.golang.org/genproto/googleapis/spanner/v1"
)
// Helpers to generate protobuf values and Cloud Spanner types.
func stringProto(s string) *proto3.Value {
return &proto3.Value{Kind: stringKind(s)}
}
func stringKind(s string) *proto3.Value_StringValue {
return &proto3.Value_StringValue{StringValue: s}
}
func stringType() *sppb.Type {
return &sppb.Type{Code: sppb.TypeCode_STRING}
}
func boolProto(b bool) *proto3.Value {
return &proto3.Value{Kind: &proto3.Value_BoolValue{BoolValue: b}}
}
func boolType() *sppb.Type {
return &sppb.Type{Code: sppb.TypeCode_BOOL}
}
func intProto(n int64) *proto3.Value {
return &proto3.Value{Kind: &proto3.Value_StringValue{StringValue: strconv.FormatInt(n, 10)}}
}
func intType() *sppb.Type {
return &sppb.Type{Code: sppb.TypeCode_INT64}
}
func floatProto(n float64) *proto3.Value {
return &proto3.Value{Kind: &proto3.Value_NumberValue{NumberValue: n}}
}
func floatType() *sppb.Type {
return &sppb.Type{Code: sppb.TypeCode_FLOAT64}
}
func bytesProto(b []byte) *proto3.Value {
return &proto3.Value{Kind: &proto3.Value_StringValue{StringValue: base64.StdEncoding.EncodeToString(b)}}
}
func bytesType() *sppb.Type {
return &sppb.Type{Code: sppb.TypeCode_BYTES}
}
func timeProto(t time.Time) *proto3.Value {
return stringProto(t.UTC().Format(time.RFC3339Nano))
}
func timeType() *sppb.Type {
return &sppb.Type{Code: sppb.TypeCode_TIMESTAMP}
}
func dateProto(d civil.Date) *proto3.Value {
return stringProto(d.String())
}
func dateType() *sppb.Type {
return &sppb.Type{Code: sppb.TypeCode_DATE}
}
func listProto(p ...*proto3.Value) *proto3.Value {
return &proto3.Value{Kind: &proto3.Value_ListValue{ListValue: &proto3.ListValue{Values: p}}}
}
func listValueProto(p ...*proto3.Value) *proto3.ListValue {
return &proto3.ListValue{Values: p}
}
func listType(t *sppb.Type) *sppb.Type {
return &sppb.Type{Code: sppb.TypeCode_ARRAY, ArrayElementType: t}
}
func mkField(n string, t *sppb.Type) *sppb.StructType_Field {
return &sppb.StructType_Field{Name: n, Type: t}
}
func structType(fields ...*sppb.StructType_Field) *sppb.Type {
return &sppb.Type{Code: sppb.TypeCode_STRUCT, StructType: &sppb.StructType{Fields: fields}}
}
func nullProto() *proto3.Value {
return &proto3.Value{Kind: &proto3.Value_NullValue{NullValue: proto3.NullValue_NULL_VALUE}}
}

733
vendor/cloud.google.com/go/spanner/read.go generated vendored Normal file
View File

@@ -0,0 +1,733 @@
/*
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 (
"bytes"
"context"
"io"
"log"
"sync/atomic"
"time"
"cloud.google.com/go/internal/protostruct"
"cloud.google.com/go/internal/trace"
"cloud.google.com/go/spanner/internal/backoff"
proto "github.com/golang/protobuf/proto"
proto3 "github.com/golang/protobuf/ptypes/struct"
"google.golang.org/api/iterator"
sppb "google.golang.org/genproto/googleapis/spanner/v1"
"google.golang.org/grpc/codes"
)
// streamingReceiver is the interface for receiving data from a client side
// stream.
type streamingReceiver interface {
Recv() (*sppb.PartialResultSet, error)
}
// errEarlyReadEnd returns error for read finishes when gRPC stream is still active.
func errEarlyReadEnd() error {
return spannerErrorf(codes.FailedPrecondition, "read completed with active stream")
}
// stream is the internal fault tolerant method for streaming data from
// Cloud Spanner.
func stream(ctx context.Context, rpc func(ct context.Context, resumeToken []byte) (streamingReceiver, error), setTimestamp func(time.Time), release func(error)) *RowIterator {
ctx, cancel := context.WithCancel(ctx)
ctx = trace.StartSpan(ctx, "cloud.google.com/go/spanner.RowIterator")
return &RowIterator{
streamd: newResumableStreamDecoder(ctx, rpc),
rowd: &partialResultSetDecoder{},
setTimestamp: setTimestamp,
release: release,
cancel: cancel,
}
}
// RowIterator is an iterator over Rows.
type RowIterator struct {
// The plan for the query. Available after RowIterator.Next returns iterator.Done
// if QueryWithStats was called.
QueryPlan *sppb.QueryPlan
// Execution statistics for the query. Available after RowIterator.Next returns iterator.Done
// if QueryWithStats was called.
QueryStats map[string]interface{}
// For a DML statement, the number of rows affected. For PDML, this is a lower bound.
// Available for DML statements after RowIterator.Next returns iterator.Done.
RowCount int64
streamd *resumableStreamDecoder
rowd *partialResultSetDecoder
setTimestamp func(time.Time)
release func(error)
cancel func()
err error
rows []*Row
sawStats bool
}
// Next returns the next result. Its second return value is iterator.Done if
// there are no more results. Once Next returns Done, all subsequent calls
// will return Done.
func (r *RowIterator) Next() (*Row, error) {
if r.err != nil {
return nil, r.err
}
for len(r.rows) == 0 && r.streamd.next() {
prs := r.streamd.get()
if prs.Stats != nil {
r.sawStats = true
r.QueryPlan = prs.Stats.QueryPlan
r.QueryStats = protostruct.DecodeToMap(prs.Stats.QueryStats)
if prs.Stats.RowCount != nil {
rc, err := extractRowCount(prs.Stats)
if err != nil {
return nil, err
}
r.RowCount = rc
}
}
r.rows, r.err = r.rowd.add(prs)
if r.err != nil {
return nil, r.err
}
if !r.rowd.ts.IsZero() && r.setTimestamp != nil {
r.setTimestamp(r.rowd.ts)
r.setTimestamp = nil
}
}
if len(r.rows) > 0 {
row := r.rows[0]
r.rows = r.rows[1:]
return row, nil
}
if err := r.streamd.lastErr(); err != nil {
r.err = toSpannerError(err)
} else if !r.rowd.done() {
r.err = errEarlyReadEnd()
} else {
r.err = iterator.Done
}
return nil, r.err
}
func extractRowCount(stats *sppb.ResultSetStats) (int64, error) {
if stats.RowCount == nil {
return 0, spannerErrorf(codes.Internal, "missing RowCount")
}
switch rc := stats.RowCount.(type) {
case *sppb.ResultSetStats_RowCountExact:
return rc.RowCountExact, nil
case *sppb.ResultSetStats_RowCountLowerBound:
return rc.RowCountLowerBound, nil
default:
return 0, spannerErrorf(codes.Internal, "unknown RowCount type %T", stats.RowCount)
}
}
// Do calls the provided function once in sequence for each row in the iteration. If the
// function returns a non-nil error, Do immediately returns that error.
//
// If there are no rows in the iterator, Do will return nil without calling the
// provided function.
//
// Do always calls Stop on the iterator.
func (r *RowIterator) Do(f func(r *Row) error) error {
defer r.Stop()
for {
row, err := r.Next()
switch err {
case iterator.Done:
return nil
case nil:
if err = f(row); err != nil {
return err
}
default:
return err
}
}
}
// Stop terminates the iteration. It should be called after you finish using the iterator.
func (r *RowIterator) Stop() {
if r.streamd != nil {
defer trace.EndSpan(r.streamd.ctx, r.err)
}
if r.cancel != nil {
r.cancel()
}
if r.release != nil {
r.release(r.err)
if r.err == nil {
r.err = spannerErrorf(codes.FailedPrecondition, "Next called after Stop")
}
r.release = nil
}
}
// partialResultQueue implements a simple FIFO queue. The zero value is a
// valid queue.
type partialResultQueue struct {
q []*sppb.PartialResultSet
first int
last int
n int // number of elements in queue
}
// empty returns if the partialResultQueue is empty.
func (q *partialResultQueue) empty() bool {
return q.n == 0
}
// errEmptyQueue returns error for dequeuing an empty queue.
func errEmptyQueue() error {
return spannerErrorf(codes.OutOfRange, "empty partialResultQueue")
}
// peekLast returns the last item in partialResultQueue; if the queue
// is empty, it returns error.
func (q *partialResultQueue) peekLast() (*sppb.PartialResultSet, error) {
if q.empty() {
return nil, errEmptyQueue()
}
return q.q[(q.last+cap(q.q)-1)%cap(q.q)], nil
}
// push adds an item to the tail of partialResultQueue.
func (q *partialResultQueue) push(r *sppb.PartialResultSet) {
if q.q == nil {
q.q = make([]*sppb.PartialResultSet, 8 /* arbitrary */)
}
if q.n == cap(q.q) {
buf := make([]*sppb.PartialResultSet, cap(q.q)*2)
for i := 0; i < q.n; i++ {
buf[i] = q.q[(q.first+i)%cap(q.q)]
}
q.q = buf
q.first = 0
q.last = q.n
}
q.q[q.last] = r
q.last = (q.last + 1) % cap(q.q)
q.n++
}
// pop removes an item from the head of partialResultQueue and returns
// it.
func (q *partialResultQueue) pop() *sppb.PartialResultSet {
if q.n == 0 {
return nil
}
r := q.q[q.first]
q.q[q.first] = nil
q.first = (q.first + 1) % cap(q.q)
q.n--
return r
}
// clear empties partialResultQueue.
func (q *partialResultQueue) clear() {
*q = partialResultQueue{}
}
// dump retrieves all items from partialResultQueue and return them in a slice.
// It is used only in tests.
func (q *partialResultQueue) dump() []*sppb.PartialResultSet {
var dq []*sppb.PartialResultSet
for i := q.first; len(dq) < q.n; i = (i + 1) % cap(q.q) {
dq = append(dq, q.q[i])
}
return dq
}
// resumableStreamDecoderState encodes resumableStreamDecoder's status.
// See also the comments for resumableStreamDecoder.Next.
type resumableStreamDecoderState int
const (
unConnected resumableStreamDecoderState = iota // 0
queueingRetryable // 1
queueingUnretryable // 2
aborted // 3
finished // 4
)
// resumableStreamDecoder provides a resumable interface for receiving
// sppb.PartialResultSet(s) from a given query wrapped by
// resumableStreamDecoder.rpc().
type resumableStreamDecoder struct {
// state is the current status of resumableStreamDecoder, see also
// the comments for resumableStreamDecoder.Next.
state resumableStreamDecoderState
// stateWitness when non-nil is called to observe state change,
// used for testing.
stateWitness func(resumableStreamDecoderState)
// ctx is the caller's context, used for cancel/timeout Next().
ctx context.Context
// rpc is a factory of streamingReceiver, which might resume
// a previous stream from the point encoded in restartToken.
// rpc is always a wrapper of a Cloud Spanner query which is
// resumable.
rpc func(ctx context.Context, restartToken []byte) (streamingReceiver, error)
// stream is the current RPC streaming receiver.
stream streamingReceiver
// q buffers received yet undecoded partial results.
q partialResultQueue
// bytesBetweenResumeTokens is the proxy of the byte size of PartialResultSets being queued
// between two resume tokens. Once bytesBetweenResumeTokens is greater than
// maxBytesBetweenResumeTokens, resumableStreamDecoder goes into queueingUnretryable state.
bytesBetweenResumeTokens int32
// maxBytesBetweenResumeTokens is the max number of bytes that can be buffered
// between two resume tokens. It is always copied from the global maxBytesBetweenResumeTokens
// atomically.
maxBytesBetweenResumeTokens int32
// np is the next sppb.PartialResultSet ready to be returned
// to caller of resumableStreamDecoder.Get().
np *sppb.PartialResultSet
// resumeToken stores the resume token that resumableStreamDecoder has
// last revealed to caller.
resumeToken []byte
// retryCount is the number of retries that have been carried out so far
retryCount int
// err is the last error resumableStreamDecoder has encountered so far.
err error
// backoff to compute delays between retries.
backoff backoff.ExponentialBackoff
}
// newResumableStreamDecoder creates a new resumeableStreamDecoder instance.
// Parameter rpc should be a function that creates a new stream
// beginning at the restartToken if non-nil.
func newResumableStreamDecoder(ctx context.Context, rpc func(ct context.Context, restartToken []byte) (streamingReceiver, error)) *resumableStreamDecoder {
return &resumableStreamDecoder{
ctx: ctx,
rpc: rpc,
maxBytesBetweenResumeTokens: atomic.LoadInt32(&maxBytesBetweenResumeTokens),
backoff: backoff.DefaultBackoff,
}
}
// changeState fulfills state transition for resumableStateDecoder.
func (d *resumableStreamDecoder) changeState(target resumableStreamDecoderState) {
if d.state == queueingRetryable && d.state != target {
// Reset bytesBetweenResumeTokens because it is only meaningful/changed under
// queueingRetryable state.
d.bytesBetweenResumeTokens = 0
}
d.state = target
if d.stateWitness != nil {
d.stateWitness(target)
}
}
// isNewResumeToken returns if the observed resume token is different from
// the one returned from server last time.
func (d *resumableStreamDecoder) isNewResumeToken(rt []byte) bool {
if rt == nil {
return false
}
if bytes.Equal(rt, d.resumeToken) {
return false
}
return true
}
// Next advances to the next available partial result set. If error or no
// more, returns false, call Err to determine if an error was encountered.
// The following diagram illustrates the state machine of resumableStreamDecoder
// that Next() implements. Note that state transition can be only triggered by
// RPC activities.
/*
rpc() fails retryable
+---------+
| | rpc() fails unretryable/ctx timeouts or cancelled
| | +------------------------------------------------+
| | | |
| v | v
| +---+---+---+ +--------+ +------+--+
+-----+unConnected| |finished| | aborted |<----+
| | ++-----+-+ +------+--+ |
+---+----+--+ ^ ^ ^ |
| ^ | | | |
| | | | recv() fails |
| | | | | |
| |recv() fails retryable | | | |
| |with valid ctx | | | |
| | | | | |
rpc() succeeds | +-----------------------+ | | |
| | | recv EOF recv EOF | |
| | | | | |
v | | Queue size exceeds | | |
+---+----+---+----+threshold +-------+-----------+ | |
+---------->+ +--------------->+ +-+ |
| |queueingRetryable| |queueingUnretryable| |
| | +<---------------+ | |
| +---+----------+--+ pop() returns +--+----+-----------+ |
| | | resume token | ^ |
| | | | | |
| | | | | |
+---------------+ | | | |
recv() succeeds | +----+ |
| recv() succeeds |
| |
| |
| |
| |
| |
+--------------------------------------------------+
recv() fails unretryable
*/
var (
// maxBytesBetweenResumeTokens is the maximum amount of bytes that resumableStreamDecoder
// in queueingRetryable state can use to queue PartialResultSets before getting
// into queueingUnretryable state.
maxBytesBetweenResumeTokens = int32(128 * 1024 * 1024)
)
func (d *resumableStreamDecoder) next() bool {
for {
select {
case <-d.ctx.Done():
// Do context check here so that even gRPC failed to do
// so, resumableStreamDecoder can still break the loop
// as expected.
d.err = errContextCanceled(d.ctx, d.err)
d.changeState(aborted)
default:
}
switch d.state {
case unConnected:
// If no gRPC stream is available, try to initiate one.
if d.stream, d.err = d.rpc(d.ctx, d.resumeToken); d.err != nil {
if isRetryable(d.err) {
d.doBackOff()
// Be explicit about state transition, although the
// state doesn't actually change. State transition
// will be triggered only by RPC activity, regardless of
// whether there is an actual state change or not.
d.changeState(unConnected)
continue
}
d.changeState(aborted)
continue
}
d.resetBackOff()
d.changeState(queueingRetryable)
continue
case queueingRetryable:
fallthrough
case queueingUnretryable:
// Receiving queue is not empty.
last, err := d.q.peekLast()
if err != nil {
// Only the case that receiving queue is empty could cause peekLast to
// return error and in such case, we should try to receive from stream.
d.tryRecv()
continue
}
if d.isNewResumeToken(last.ResumeToken) {
// Got new resume token, return buffered sppb.PartialResultSets to caller.
d.np = d.q.pop()
if d.q.empty() {
d.bytesBetweenResumeTokens = 0
// The new resume token was just popped out from queue, record it.
d.resumeToken = d.np.ResumeToken
d.changeState(queueingRetryable)
}
return true
}
if d.bytesBetweenResumeTokens >= d.maxBytesBetweenResumeTokens && d.state == queueingRetryable {
d.changeState(queueingUnretryable)
continue
}
if d.state == queueingUnretryable {
// When there is no resume token observed,
// only yield sppb.PartialResultSets to caller under
// queueingUnretryable state.
d.np = d.q.pop()
return true
}
// Needs to receive more from gRPC stream till a new resume token
// is observed.
d.tryRecv()
continue
case aborted:
// Discard all pending items because none of them
// should be yield to caller.
d.q.clear()
return false
case finished:
// If query has finished, check if there are still buffered messages.
if d.q.empty() {
// No buffered PartialResultSet.
return false
}
// Although query has finished, there are still buffered PartialResultSets.
d.np = d.q.pop()
return true
default:
log.Printf("Unexpected resumableStreamDecoder.state: %v", d.state)
return false
}
}
}
// tryRecv attempts to receive a PartialResultSet from gRPC stream.
func (d *resumableStreamDecoder) tryRecv() {
var res *sppb.PartialResultSet
if res, d.err = d.stream.Recv(); d.err != nil {
if d.err == io.EOF {
d.err = nil
d.changeState(finished)
return
}
if isRetryable(d.err) && d.state == queueingRetryable {
d.err = nil
// Discard all queue items (none have resume tokens).
d.q.clear()
d.stream = nil
d.changeState(unConnected)
d.doBackOff()
return
}
d.changeState(aborted)
return
}
d.q.push(res)
if d.state == queueingRetryable && !d.isNewResumeToken(res.ResumeToken) {
// adjusting d.bytesBetweenResumeTokens
d.bytesBetweenResumeTokens += int32(proto.Size(res))
}
d.resetBackOff()
d.changeState(d.state)
}
// resetBackOff clears the internal retry counter of
// resumableStreamDecoder so that the next exponential
// backoff will start at a fresh state.
func (d *resumableStreamDecoder) resetBackOff() {
d.retryCount = 0
}
// doBackoff does an exponential backoff sleep.
func (d *resumableStreamDecoder) doBackOff() {
delay := d.backoff.Delay(d.retryCount)
trace.TracePrintf(d.ctx, nil, "Backing off stream read for %s", delay)
ticker := time.NewTicker(delay)
defer ticker.Stop()
d.retryCount++
select {
case <-d.ctx.Done():
case <-ticker.C:
}
}
// get returns the most recent PartialResultSet generated by a call to next.
func (d *resumableStreamDecoder) get() *sppb.PartialResultSet {
return d.np
}
// lastErr returns the last non-EOF error encountered.
func (d *resumableStreamDecoder) lastErr() error {
return d.err
}
// partialResultSetDecoder assembles PartialResultSet(s) into Cloud Spanner
// Rows.
type partialResultSetDecoder struct {
row Row
tx *sppb.Transaction
chunked bool // if true, next value should be merged with last values entry.
ts time.Time // read timestamp
}
// yield checks we have a complete row, and if so returns it. A row is not
// complete if it doesn't have enough columns, or if this is a chunked response
// and there are no further values to process.
func (p *partialResultSetDecoder) yield(chunked, last bool) *Row {
if len(p.row.vals) == len(p.row.fields) && (!chunked || !last) {
// When partialResultSetDecoder gets enough number of
// Column values, There are two cases that a new Row
// should be yield:
// 1. The incoming PartialResultSet is not chunked;
// 2. The incoming PartialResultSet is chunked, but the
// proto3.Value being merged is not the last one in
// the PartialResultSet.
//
// Use a fresh Row to simplify clients that want to use yielded results
// after the next row is retrieved. Note that fields is never changed
// so it doesn't need to be copied.
fresh := Row{
fields: p.row.fields,
vals: make([]*proto3.Value, len(p.row.vals)),
}
copy(fresh.vals, p.row.vals)
p.row.vals = p.row.vals[:0] // empty and reuse slice
return &fresh
}
return nil
}
// yieldTx returns transaction information via caller supplied callback.
func errChunkedEmptyRow() error {
return spannerErrorf(codes.FailedPrecondition, "got invalid chunked PartialResultSet with empty Row")
}
// add tries to merge a new PartialResultSet into buffered Row. It returns
// any rows that have been completed as a result.
func (p *partialResultSetDecoder) add(r *sppb.PartialResultSet) ([]*Row, error) {
var rows []*Row
if r.Metadata != nil {
// Metadata should only be returned in the first result.
if p.row.fields == nil {
p.row.fields = r.Metadata.RowType.Fields
}
if p.tx == nil && r.Metadata.Transaction != nil {
p.tx = r.Metadata.Transaction
if p.tx.ReadTimestamp != nil {
p.ts = time.Unix(p.tx.ReadTimestamp.Seconds, int64(p.tx.ReadTimestamp.Nanos))
}
}
}
if len(r.Values) == 0 {
return nil, nil
}
if p.chunked {
p.chunked = false
// Try to merge first value in r.Values into
// uncompleted row.
last := len(p.row.vals) - 1
if last < 0 { // sanity check
return nil, errChunkedEmptyRow()
}
var err error
// If p is chunked, then we should always try to merge p.last with r.first.
if p.row.vals[last], err = p.merge(p.row.vals[last], r.Values[0]); err != nil {
return nil, err
}
r.Values = r.Values[1:]
// Merge is done, try to yield a complete Row.
if row := p.yield(r.ChunkedValue, len(r.Values) == 0); row != nil {
rows = append(rows, row)
}
}
for i, v := range r.Values {
// The rest values in r can be appened into p directly.
p.row.vals = append(p.row.vals, v)
// Again, check to see if a complete Row can be yielded because of
// the newly added value.
if row := p.yield(r.ChunkedValue, i == len(r.Values)-1); row != nil {
rows = append(rows, row)
}
}
if r.ChunkedValue {
// After dealing with all values in r, if r is chunked then p must
// be also chunked.
p.chunked = true
}
return rows, nil
}
// isMergeable returns if a protobuf Value can be potentially merged with
// other protobuf Values.
func (p *partialResultSetDecoder) isMergeable(a *proto3.Value) bool {
switch a.Kind.(type) {
case *proto3.Value_StringValue:
return true
case *proto3.Value_ListValue:
return true
default:
return false
}
}
// errIncompatibleMergeTypes returns error for incompatible protobuf types
// that cannot be merged by partialResultSetDecoder.
func errIncompatibleMergeTypes(a, b *proto3.Value) error {
return spannerErrorf(codes.FailedPrecondition, "incompatible type in chunked PartialResultSet. expected (%T), got (%T)", a.Kind, b.Kind)
}
// errUnsupportedMergeType returns error for protobuf type that cannot be
// merged to other protobufs.
func errUnsupportedMergeType(a *proto3.Value) error {
return spannerErrorf(codes.FailedPrecondition, "unsupported type merge (%T)", a.Kind)
}
// merge tries to combine two protobuf Values if possible.
func (p *partialResultSetDecoder) merge(a, b *proto3.Value) (*proto3.Value, error) {
var err error
typeErr := errIncompatibleMergeTypes(a, b)
switch t := a.Kind.(type) {
case *proto3.Value_StringValue:
s, ok := b.Kind.(*proto3.Value_StringValue)
if !ok {
return nil, typeErr
}
return &proto3.Value{
Kind: &proto3.Value_StringValue{StringValue: t.StringValue + s.StringValue},
}, nil
case *proto3.Value_ListValue:
l, ok := b.Kind.(*proto3.Value_ListValue)
if !ok {
return nil, typeErr
}
if l.ListValue == nil || len(l.ListValue.Values) <= 0 {
// b is an empty list, just return a.
return a, nil
}
if t.ListValue == nil || len(t.ListValue.Values) <= 0 {
// a is an empty list, just return b.
return b, nil
}
if la := len(t.ListValue.Values) - 1; p.isMergeable(t.ListValue.Values[la]) {
// When the last item in a is of type String,
// List or Struct(encoded into List by Cloud Spanner),
// try to Merge last item in a and first item in b.
t.ListValue.Values[la], err = p.merge(t.ListValue.Values[la], l.ListValue.Values[0])
if err != nil {
return nil, err
}
l.ListValue.Values = l.ListValue.Values[1:]
}
return &proto3.Value{
Kind: &proto3.Value_ListValue{
ListValue: &proto3.ListValue{
Values: append(t.ListValue.Values, l.ListValue.Values...),
},
},
}, nil
default:
return nil, errUnsupportedMergeType(a)
}
}
// Done returns if partialResultSetDecoder has already done with all buffered
// values.
func (p *partialResultSetDecoder) done() bool {
// There is no explicit end of stream marker, but ending part way
// through a row is obviously bad, or ending with the last column still
// awaiting completion.
return len(p.row.vals) == 0 && !p.chunked
}

1736
vendor/cloud.google.com/go/spanner/read_test.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

201
vendor/cloud.google.com/go/spanner/retry.go generated vendored Normal file
View File

@@ -0,0 +1,201 @@
/*
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 (
"context"
"fmt"
"strings"
"time"
"cloud.google.com/go/internal/trace"
"cloud.google.com/go/spanner/internal/backoff"
"github.com/golang/protobuf/proto"
"github.com/golang/protobuf/ptypes"
edpb "google.golang.org/genproto/googleapis/rpc/errdetails"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
)
const (
retryInfoKey = "google.rpc.retryinfo-bin"
)
// errRetry returns an unavailable error under error namespace EsOther. It is a
// generic retryable error that is used to mask and recover unretryable errors
// in a retry loop.
func errRetry(err error) error {
if se, ok := err.(*Error); ok {
return &Error{codes.Unavailable, fmt.Sprintf("generic Cloud Spanner retryable error: { %v }", se.Error()), se.trailers}
}
return spannerErrorf(codes.Unavailable, "generic Cloud Spanner retryable error: { %v }", err.Error())
}
// isErrorClosing reports whether the error is generated by gRPC layer talking to a closed server.
func isErrorClosing(err error) bool {
if err == nil {
return false
}
if ErrCode(err) == codes.Internal && strings.Contains(ErrDesc(err), "transport is closing") {
// Handle the case when connection is closed unexpectedly.
// TODO: once gRPC is able to categorize
// this as retryable error, we should stop parsing the
// error message here.
return true
}
return false
}
// isErrorRST reports whether the error is generated by gRPC client receiving a RST frame from server.
func isErrorRST(err error) bool {
if err == nil {
return false
}
if ErrCode(err) == codes.Internal && strings.Contains(ErrDesc(err), "stream terminated by RST_STREAM") {
// TODO: once gRPC is able to categorize this error as "go away" or "retryable",
// we should stop parsing the error message.
return true
}
return false
}
// isErrorUnexpectedEOF returns true if error is generated by gRPC layer receiving io.EOF unexpectedly.
func isErrorUnexpectedEOF(err error) bool {
if err == nil {
return false
}
// Unexpected EOF is a transport layer issue that could be recovered by
// retries. The most likely scenario is a flaky RecvMsg() call due to
// network issues.
// For grpc version >= 1.14.0, the error code is Internal.
// (https://github.com/grpc/grpc-go/releases/tag/v1.14.0)
if ErrCode(err) == codes.Internal && strings.Contains(ErrDesc(err), "unexpected EOF") {
return true
}
// For grpc version < 1.14.0, the error code in Unknown.
if ErrCode(err) == codes.Unknown && strings.Contains(ErrDesc(err), "unexpected EOF") {
return true
}
return false
}
// isErrorUnavailable returns true if the error is about server being unavailable.
func isErrorUnavailable(err error) bool {
if err == nil {
return false
}
if ErrCode(err) == codes.Unavailable {
return true
}
return false
}
// isRetryable returns true if the Cloud Spanner error being checked is a retryable error.
func isRetryable(err error) bool {
if isErrorClosing(err) {
return true
}
if isErrorUnexpectedEOF(err) {
return true
}
if isErrorRST(err) {
return true
}
if isErrorUnavailable(err) {
return true
}
return false
}
// errContextCanceled returns *spanner.Error for canceled context.
func errContextCanceled(ctx context.Context, lastErr error) error {
if ctx.Err() == context.DeadlineExceeded {
return spannerErrorf(codes.DeadlineExceeded, "%v, lastErr is <%v>", ctx.Err(), lastErr)
}
return spannerErrorf(codes.Canceled, "%v, lastErr is <%v>", ctx.Err(), lastErr)
}
// extractRetryDelay extracts retry backoff if present.
func extractRetryDelay(err error) (time.Duration, bool) {
trailers := errTrailers(err)
if trailers == nil {
return 0, false
}
elem, ok := trailers[retryInfoKey]
if !ok || len(elem) <= 0 {
return 0, false
}
_, b, err := metadata.DecodeKeyValue(retryInfoKey, elem[0])
if err != nil {
return 0, false
}
var retryInfo edpb.RetryInfo
if proto.Unmarshal([]byte(b), &retryInfo) != nil {
return 0, false
}
delay, err := ptypes.Duration(retryInfo.RetryDelay)
if err != nil {
return 0, false
}
return delay, true
}
// runRetryable keeps attempting to run f until one of the following happens:
// 1) f returns nil error or an unretryable error;
// 2) context is cancelled or timeout.
// TODO: consider using https://github.com/googleapis/gax-go/v2 once it
// becomes available internally.
func runRetryable(ctx context.Context, f func(context.Context) error) error {
return toSpannerError(runRetryableNoWrap(ctx, f))
}
// Like runRetryable, but doesn't wrap the returned error in a spanner.Error.
func runRetryableNoWrap(ctx context.Context, f func(context.Context) error) error {
var funcErr error
retryCount := 0
for {
select {
case <-ctx.Done():
// Do context check here so that even f() failed to do
// so (for example, gRPC implementation bug), the loop
// can still have a chance to exit as expected.
return errContextCanceled(ctx, funcErr)
default:
}
funcErr = f(ctx)
if funcErr == nil {
return nil
}
if isRetryable(funcErr) {
// Error is retryable, do exponential backoff and continue.
b, ok := extractRetryDelay(funcErr)
if !ok {
b = backoff.DefaultBackoff.Delay(retryCount)
}
trace.TracePrintf(ctx, nil, "Backing off for %s, then retrying", b)
select {
case <-ctx.Done():
return errContextCanceled(ctx, funcErr)
case <-time.After(b):
}
retryCount++
continue
}
// Error isn't retryable / no error, return immediately.
return funcErr
}
}

108
vendor/cloud.google.com/go/spanner/retry_test.go generated vendored Normal file
View File

@@ -0,0 +1,108 @@
/*
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 (
"context"
"errors"
"fmt"
"testing"
"time"
"github.com/golang/protobuf/proto"
"github.com/golang/protobuf/ptypes"
edpb "google.golang.org/genproto/googleapis/rpc/errdetails"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
)
// Test if runRetryable loop deals with various errors correctly.
func TestRetry(t *testing.T) {
if testing.Short() {
t.SkipNow()
}
responses := []error{
status.Errorf(codes.Internal, "transport is closing"),
status.Errorf(codes.Unknown, "unexpected EOF"),
status.Errorf(codes.Internal, "unexpected EOF"),
status.Errorf(codes.Internal, "stream terminated by RST_STREAM with error code: 2"),
status.Errorf(codes.Unavailable, "service is currently unavailable"),
errRetry(fmt.Errorf("just retry it")),
}
err := runRetryable(context.Background(), func(ct context.Context) error {
var r error
if len(responses) > 0 {
r = responses[0]
responses = responses[1:]
}
return r
})
if err != nil {
t.Errorf("runRetryable should be able to survive all retryable errors, but it returns %v", err)
}
// Unretryable errors
injErr := errors.New("this is unretryable")
err = runRetryable(context.Background(), func(ct context.Context) error {
return injErr
})
if wantErr := toSpannerError(injErr); !testEqual(err, wantErr) {
t.Errorf("runRetryable returns error %v, want %v", err, wantErr)
}
// Timeout
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
retryErr := errRetry(fmt.Errorf("still retrying"))
err = runRetryable(ctx, func(ct context.Context) error {
// Expect to trigger timeout in retryable runner after 10 executions.
<-time.After(100 * time.Millisecond)
// Let retryable runner to retry so that timeout will eventually happen.
return retryErr
})
// Check error code and error message
if wantErrCode, wantErr := codes.DeadlineExceeded, errContextCanceled(ctx, retryErr); ErrCode(err) != wantErrCode || !testEqual(err, wantErr) {
t.Errorf("<err code, err>=\n<%v, %v>, want:\n<%v, %v>", ErrCode(err), err, wantErrCode, wantErr)
}
// Cancellation
ctx, cancel = context.WithCancel(context.Background())
retries := 3
retryErr = errRetry(fmt.Errorf("retry before cancel"))
err = runRetryable(ctx, func(ct context.Context) error {
retries--
if retries == 0 {
cancel()
}
return retryErr
})
// Check error code, error message, retry count
if wantErrCode, wantErr := codes.Canceled, errContextCanceled(ctx, retryErr); ErrCode(err) != wantErrCode || !testEqual(err, wantErr) || retries != 0 {
t.Errorf("<err code, err, retries>=\n<%v, %v, %v>, want:\n<%v, %v, %v>", ErrCode(err), err, retries, wantErrCode, wantErr, 0)
}
}
func TestRetryInfo(t *testing.T) {
b, _ := proto.Marshal(&edpb.RetryInfo{
RetryDelay: ptypes.DurationProto(time.Second),
})
trailers := map[string]string{
retryInfoKey: string(b),
}
gotDelay, ok := extractRetryDelay(errRetry(toSpannerErrorWithMetadata(status.Errorf(codes.Aborted, ""), metadata.New(trailers))))
if !ok || !testEqual(time.Second, gotDelay) {
t.Errorf("<ok, retryDelay> = <%t, %v>, want <true, %v>", ok, gotDelay, time.Second)
}
}

308
vendor/cloud.google.com/go/spanner/row.go generated vendored Normal file
View File

@@ -0,0 +1,308 @@
/*
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 (
"fmt"
"reflect"
proto3 "github.com/golang/protobuf/ptypes/struct"
sppb "google.golang.org/genproto/googleapis/spanner/v1"
"google.golang.org/grpc/codes"
)
// A Row is a view of a row of data returned by a Cloud Spanner read.
// It consists of a number of columns; the number depends on the columns
// used to construct the read.
//
// The column values can be accessed by index. For instance, if the read specified
// []string{"photo_id", "caption"}, then each row will contain two
// columns: "photo_id" with index 0, and "caption" with index 1.
//
// Column values are decoded by using one of the Column, ColumnByName, or
// Columns methods. The valid values passed to these methods depend on the
// column type. For example:
//
// var photoID int64
// err := row.Column(0, &photoID) // Decode column 0 as an integer.
//
// var caption string
// err := row.Column(1, &caption) // Decode column 1 as a string.
//
// // Decode all the columns.
// err := row.Columns(&photoID, &caption)
//
// Supported types and their corresponding Cloud Spanner column type(s) are:
//
// *string(not NULL), *NullString - STRING
// *[]string, *[]NullString - STRING ARRAY
// *[]byte - BYTES
// *[][]byte - BYTES ARRAY
// *int64(not NULL), *NullInt64 - INT64
// *[]int64, *[]NullInt64 - INT64 ARRAY
// *bool(not NULL), *NullBool - BOOL
// *[]bool, *[]NullBool - BOOL ARRAY
// *float64(not NULL), *NullFloat64 - FLOAT64
// *[]float64, *[]NullFloat64 - FLOAT64 ARRAY
// *time.Time(not NULL), *NullTime - TIMESTAMP
// *[]time.Time, *[]NullTime - TIMESTAMP ARRAY
// *Date(not NULL), *NullDate - DATE
// *[]civil.Date, *[]NullDate - DATE ARRAY
// *[]*some_go_struct, *[]NullRow - STRUCT ARRAY
// *GenericColumnValue - any Cloud Spanner type
//
// For TIMESTAMP columns, the returned time.Time object will be in UTC.
//
// To fetch an array of BYTES, pass a *[][]byte. To fetch an array of (sub)rows, pass
// a *[]spanner.NullRow or a *[]*some_go_struct where some_go_struct holds all
// information of the subrow, see spanner.Row.ToStruct for the mapping between a
// Cloud Spanner row and a Go struct. To fetch an array of other types, pass a
// *[]spanner.NullXXX type of the appropriate type. Use GenericColumnValue when you
// don't know in advance what column type to expect.
//
// Row decodes the row contents lazily; as a result, each call to a getter has
// a chance of returning an error.
//
// A column value may be NULL if the corresponding value is not present in
// Cloud Spanner. The spanner.NullXXX types (spanner.NullInt64 et al.) allow fetching
// values that may be null. A NULL BYTES can be fetched into a *[]byte as nil.
// It is an error to fetch a NULL value into any other type.
type Row struct {
fields []*sppb.StructType_Field
vals []*proto3.Value // keep decoded for now
}
// errNamesValuesMismatch returns error for when columnNames count is not equal
// to columnValues count.
func errNamesValuesMismatch(columnNames []string, columnValues []interface{}) error {
return spannerErrorf(codes.FailedPrecondition,
"different number of names(%v) and values(%v)", len(columnNames), len(columnValues))
}
// NewRow returns a Row containing the supplied data. This can be useful for
// mocking Cloud Spanner Read and Query responses for unit testing.
func NewRow(columnNames []string, columnValues []interface{}) (*Row, error) {
if len(columnValues) != len(columnNames) {
return nil, errNamesValuesMismatch(columnNames, columnValues)
}
r := Row{
fields: make([]*sppb.StructType_Field, len(columnValues)),
vals: make([]*proto3.Value, len(columnValues)),
}
for i := range columnValues {
val, typ, err := encodeValue(columnValues[i])
if err != nil {
return nil, err
}
r.fields[i] = &sppb.StructType_Field{
Name: columnNames[i],
Type: typ,
}
r.vals[i] = val
}
return &r, nil
}
// Size is the number of columns in the row.
func (r *Row) Size() int {
return len(r.fields)
}
// ColumnName returns the name of column i, or empty string for invalid column.
func (r *Row) ColumnName(i int) string {
if i < 0 || i >= len(r.fields) {
return ""
}
return r.fields[i].Name
}
// ColumnIndex returns the index of the column with the given name. The
// comparison is case-sensitive.
func (r *Row) ColumnIndex(name string) (int, error) {
found := false
var index int
if len(r.vals) != len(r.fields) {
return 0, errFieldsMismatchVals(r)
}
for i, f := range r.fields {
if f == nil {
return 0, errNilColType(i)
}
if name == f.Name {
if found {
return 0, errDupColName(name)
}
found = true
index = i
}
}
if !found {
return 0, errColNotFound(name)
}
return index, nil
}
// ColumnNames returns all column names of the row.
func (r *Row) ColumnNames() []string {
var n []string
for _, c := range r.fields {
n = append(n, c.Name)
}
return n
}
// errColIdxOutOfRange returns error for requested column index is out of the
// range of the target Row's columns.
func errColIdxOutOfRange(i int, r *Row) error {
return spannerErrorf(codes.OutOfRange, "column index %d out of range [0,%d)", i, len(r.vals))
}
// errDecodeColumn returns error for not being able to decode a indexed column.
func errDecodeColumn(i int, err error) error {
if err == nil {
return nil
}
se, ok := toSpannerError(err).(*Error)
if !ok {
return spannerErrorf(codes.InvalidArgument, "failed to decode column %v, error = <%v>", i, err)
}
se.decorate(fmt.Sprintf("failed to decode column %v", i))
return se
}
// errFieldsMismatchVals returns error for field count isn't equal to value count in a Row.
func errFieldsMismatchVals(r *Row) error {
return spannerErrorf(codes.FailedPrecondition, "row has different number of fields(%v) and values(%v)",
len(r.fields), len(r.vals))
}
// errNilColType returns error for column type for column i being nil in the row.
func errNilColType(i int) error {
return spannerErrorf(codes.FailedPrecondition, "column(%v)'s type is nil", i)
}
// Column fetches the value from the ith column, decoding it into ptr.
// See the Row documentation for the list of acceptable argument types.
// see Client.ReadWriteTransaction for an example.
func (r *Row) Column(i int, ptr interface{}) error {
if len(r.vals) != len(r.fields) {
return errFieldsMismatchVals(r)
}
if i < 0 || i >= len(r.fields) {
return errColIdxOutOfRange(i, r)
}
if r.fields[i] == nil {
return errNilColType(i)
}
if err := decodeValue(r.vals[i], r.fields[i].Type, ptr); err != nil {
return errDecodeColumn(i, err)
}
return nil
}
// errDupColName returns error for duplicated column name in the same row.
func errDupColName(n string) error {
return spannerErrorf(codes.FailedPrecondition, "ambiguous column name %q", n)
}
// errColNotFound returns error for not being able to find a named column.
func errColNotFound(n string) error {
return spannerErrorf(codes.NotFound, "column %q not found", n)
}
// ColumnByName fetches the value from the named column, decoding it into ptr.
// See the Row documentation for the list of acceptable argument types.
func (r *Row) ColumnByName(name string, ptr interface{}) error {
index, err := r.ColumnIndex(name)
if err != nil {
return err
}
return r.Column(index, ptr)
}
// errNumOfColValue returns error for providing wrong number of values to Columns.
func errNumOfColValue(n int, r *Row) error {
return spannerErrorf(codes.InvalidArgument,
"Columns(): number of arguments (%d) does not match row size (%d)", n, len(r.vals))
}
// Columns fetches all the columns in the row at once.
//
// The value of the kth column will be decoded into the kth argument to Columns. See
// Row for the list of acceptable argument types. The number of arguments must be
// equal to the number of columns. Pass nil to specify that a column should be
// ignored.
func (r *Row) Columns(ptrs ...interface{}) error {
if len(ptrs) != len(r.vals) {
return errNumOfColValue(len(ptrs), r)
}
if len(r.vals) != len(r.fields) {
return errFieldsMismatchVals(r)
}
for i, p := range ptrs {
if p == nil {
continue
}
if err := r.Column(i, p); err != nil {
return err
}
}
return nil
}
// errToStructArgType returns error for p not having the correct data type(pointer to Go struct) to
// be the argument of Row.ToStruct.
func errToStructArgType(p interface{}) error {
return spannerErrorf(codes.InvalidArgument, "ToStruct(): type %T is not a valid pointer to Go struct", p)
}
// ToStruct fetches the columns in a row into the fields of a struct.
// The rules for mapping a row's columns into a struct's exported fields
// are:
//
// 1. If a field has a `spanner: "column_name"` tag, then decode column
// 'column_name' into the field. A special case is the `spanner: "-"`
// tag, which instructs ToStruct to ignore the field during decoding.
//
// 2. Otherwise, if the name of a field matches the name of a column (ignoring case),
// decode the column into the field.
//
// The fields of the destination struct can be of any type that is acceptable
// to spanner.Row.Column.
//
// Slice and pointer fields will be set to nil if the source column is NULL, and a
// non-nil value if the column is not NULL. To decode NULL values of other types, use
// one of the spanner.NullXXX types as the type of the destination field.
//
// If ToStruct returns an error, the contents of p are undefined. Some fields may
// have been successfully populated, while others were not; you should not use any of
// the fields.
func (r *Row) ToStruct(p interface{}) error {
// Check if p is a pointer to a struct
if t := reflect.TypeOf(p); t == nil || t.Kind() != reflect.Ptr || t.Elem().Kind() != reflect.Struct {
return errToStructArgType(p)
}
if len(r.vals) != len(r.fields) {
return errFieldsMismatchVals(r)
}
// Call decodeStruct directly to decode the row as a typed proto.ListValue.
return decodeStruct(
&sppb.StructType{Fields: r.fields},
&proto3.ListValue{Values: r.vals},
p,
)
}

1636
vendor/cloud.google.com/go/spanner/row_test.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

1107
vendor/cloud.google.com/go/spanner/session.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

1084
vendor/cloud.google.com/go/spanner/session_test.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

92
vendor/cloud.google.com/go/spanner/statement.go generated vendored Normal file
View File

@@ -0,0 +1,92 @@
/*
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 (
"errors"
"fmt"
proto3 "github.com/golang/protobuf/ptypes/struct"
structpb "github.com/golang/protobuf/ptypes/struct"
sppb "google.golang.org/genproto/googleapis/spanner/v1"
"google.golang.org/grpc/codes"
)
// A Statement is a SQL query with named parameters.
//
// A parameter placeholder consists of '@' followed by the parameter name.
// Parameter names consist of any combination of letters, numbers, and
// underscores. Names may be entirely numeric (e.g., "WHERE m.id = @5").
// Parameters may appear anywhere that a literal value is expected. The same
// parameter name may be used more than once. It is an error to execute a
// statement with unbound parameters. On the other hand, it is allowable to
// bind parameter names that are not used.
//
// See the documentation of the Row type for how Go types are mapped to Cloud
// Spanner types.
type Statement struct {
SQL string
Params map[string]interface{}
}
// NewStatement returns a Statement with the given SQL and an empty Params map.
func NewStatement(sql string) Statement {
return Statement{SQL: sql, Params: map[string]interface{}{}}
}
var (
errNilParam = errors.New("use T(nil), not nil")
errNoType = errors.New("no type information")
)
// convertParams converts a statement's parameters into proto Param and
// ParamTypes.
func (s *Statement) convertParams() (*structpb.Struct, map[string]*sppb.Type, error) {
params := &proto3.Struct{
Fields: map[string]*proto3.Value{},
}
paramTypes := map[string]*sppb.Type{}
for k, v := range s.Params {
if v == nil {
return nil, nil, errBindParam(k, v, errNilParam)
}
val, t, err := encodeValue(v)
if err != nil {
return nil, nil, errBindParam(k, v, err)
}
if t == nil { // should not happen, because of nil check above
return nil, nil, errBindParam(k, v, errNoType)
}
params.Fields[k] = val
paramTypes[k] = t
}
return params, paramTypes, nil
}
// errBindParam returns error for not being able to bind parameter to query request.
func errBindParam(k string, v interface{}, err error) error {
if err == nil {
return nil
}
se, ok := toSpannerError(err).(*Error)
if !ok {
return spannerErrorf(codes.InvalidArgument, "failed to bind query parameter(name: %q, value: %v), error = <%v>", k, v, err)
}
se.decorate(fmt.Sprintf("failed to bind query parameter(name: %q, value: %v)", k, v))
return se
}

196
vendor/cloud.google.com/go/spanner/statement_test.go generated vendored Normal file
View File

@@ -0,0 +1,196 @@
/*
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 (
"math"
"testing"
"time"
"cloud.google.com/go/civil"
"github.com/golang/protobuf/proto"
proto3 "github.com/golang/protobuf/ptypes/struct"
sppb "google.golang.org/genproto/googleapis/spanner/v1"
)
func TestConvertParams(t *testing.T) {
st := Statement{
SQL: "SELECT id from t_foo WHERE col = @var",
Params: map[string]interface{}{"var": nil},
}
var (
t1, _ = time.Parse(time.RFC3339Nano, "2016-11-15T15:04:05.999999999Z")
// Boundaries
t2, _ = time.Parse(time.RFC3339Nano, "0001-01-01T00:00:00.000000000Z")
t3, _ = time.Parse(time.RFC3339Nano, "9999-12-31T23:59:59.999999999Z")
d1, _ = civil.ParseDate("2016-11-15")
// Boundaries
d2, _ = civil.ParseDate("0001-01-01")
d3, _ = civil.ParseDate("9999-12-31")
)
type staticStruct struct {
Field int `spanner:"field"`
}
var (
s1 = staticStruct{10}
s2 = staticStruct{20}
)
for _, test := range []struct {
val interface{}
wantField *proto3.Value
wantType *sppb.Type
}{
// bool
{true, boolProto(true), boolType()},
{NullBool{true, true}, boolProto(true), boolType()},
{NullBool{true, false}, nullProto(), boolType()},
{[]bool(nil), nullProto(), listType(boolType())},
{[]bool{}, listProto(), listType(boolType())},
{[]bool{true, false}, listProto(boolProto(true), boolProto(false)), listType(boolType())},
{[]NullBool(nil), nullProto(), listType(boolType())},
{[]NullBool{}, listProto(), listType(boolType())},
{[]NullBool{{true, true}, {}}, listProto(boolProto(true), nullProto()), listType(boolType())},
// int
{int(1), intProto(1), intType()},
{[]int(nil), nullProto(), listType(intType())},
{[]int{}, listProto(), listType(intType())},
{[]int{1, 2}, listProto(intProto(1), intProto(2)), listType(intType())},
// int64
{int64(1), intProto(1), intType()},
{NullInt64{5, true}, intProto(5), intType()},
{NullInt64{5, false}, nullProto(), intType()},
{[]int64(nil), nullProto(), listType(intType())},
{[]int64{}, listProto(), listType(intType())},
{[]int64{1, 2}, listProto(intProto(1), intProto(2)), listType(intType())},
{[]NullInt64(nil), nullProto(), listType(intType())},
{[]NullInt64{}, listProto(), listType(intType())},
{[]NullInt64{{1, true}, {}}, listProto(intProto(1), nullProto()), listType(intType())},
// float64
{0.0, floatProto(0.0), floatType()},
{math.Inf(1), floatProto(math.Inf(1)), floatType()},
{math.Inf(-1), floatProto(math.Inf(-1)), floatType()},
{math.NaN(), floatProto(math.NaN()), floatType()},
{NullFloat64{2.71, true}, floatProto(2.71), floatType()},
{NullFloat64{1.41, false}, nullProto(), floatType()},
{[]float64(nil), nullProto(), listType(floatType())},
{[]float64{}, listProto(), listType(floatType())},
{[]float64{2.72, math.Inf(1)}, listProto(floatProto(2.72), floatProto(math.Inf(1))), listType(floatType())},
{[]NullFloat64(nil), nullProto(), listType(floatType())},
{[]NullFloat64{}, listProto(), listType(floatType())},
{[]NullFloat64{{2.72, true}, {}}, listProto(floatProto(2.72), nullProto()), listType(floatType())},
// string
{"", stringProto(""), stringType()},
{"foo", stringProto("foo"), stringType()},
{NullString{"bar", true}, stringProto("bar"), stringType()},
{NullString{"bar", false}, nullProto(), stringType()},
{[]string(nil), nullProto(), listType(stringType())},
{[]string{}, listProto(), listType(stringType())},
{[]string{"foo", "bar"}, listProto(stringProto("foo"), stringProto("bar")), listType(stringType())},
{[]NullString(nil), nullProto(), listType(stringType())},
{[]NullString{}, listProto(), listType(stringType())},
{[]NullString{{"foo", true}, {}}, listProto(stringProto("foo"), nullProto()), listType(stringType())},
// bytes
{[]byte{}, bytesProto([]byte{}), bytesType()},
{[]byte{1, 2, 3}, bytesProto([]byte{1, 2, 3}), bytesType()},
{[]byte(nil), nullProto(), bytesType()},
{[][]byte(nil), nullProto(), listType(bytesType())},
{[][]byte{}, listProto(), listType(bytesType())},
{[][]byte{{1}, []byte(nil)}, listProto(bytesProto([]byte{1}), nullProto()), listType(bytesType())},
// date
{d1, dateProto(d1), dateType()},
{NullDate{civil.Date{}, false}, nullProto(), dateType()},
{[]civil.Date(nil), nullProto(), listType(dateType())},
{[]civil.Date{}, listProto(), listType(dateType())},
{[]civil.Date{d1, d2, d3}, listProto(dateProto(d1), dateProto(d2), dateProto(d3)), listType(dateType())},
{[]NullDate{{d2, true}, {}}, listProto(dateProto(d2), nullProto()), listType(dateType())},
// timestamp
{t1, timeProto(t1), timeType()},
{NullTime{}, nullProto(), timeType()},
{[]time.Time(nil), nullProto(), listType(timeType())},
{[]time.Time{}, listProto(), listType(timeType())},
{[]time.Time{t1, t2, t3}, listProto(timeProto(t1), timeProto(t2), timeProto(t3)), listType(timeType())},
{[]NullTime{{t2, true}, {}}, listProto(timeProto(t2), nullProto()), listType(timeType())},
// Struct
{
s1,
listProto(intProto(10)),
structType(mkField("field", intType())),
},
{
(*struct {
F1 civil.Date `spanner:""`
F2 bool
})(nil),
nullProto(),
structType(
mkField("", dateType()),
mkField("F2", boolType())),
},
// Array-of-struct
{
[]staticStruct{s1, s2},
listProto(listProto(intProto(10)), listProto(intProto(20))),
listType(structType(mkField("field", intType()))),
},
} {
st.Params["var"] = test.val
gotParams, gotParamTypes, gotErr := st.convertParams()
if gotErr != nil {
t.Error(gotErr)
continue
}
gotParamField := gotParams.Fields["var"]
if !proto.Equal(gotParamField, test.wantField) {
// handle NaN
if test.wantType.Code == floatType().Code && proto.MarshalTextString(gotParamField) == proto.MarshalTextString(test.wantField) {
continue
}
t.Errorf("%#v: got %v, want %v\n", test.val, gotParamField, test.wantField)
}
gotParamType := gotParamTypes["var"]
if !proto.Equal(gotParamType, test.wantType) {
t.Errorf("%#v: got %v, want %v\n", test.val, gotParamType, test.wantField)
}
}
// Verify type error reporting.
for _, test := range []struct {
val interface{}
wantErr error
}{
{
nil,
errBindParam("var", nil, errNilParam),
},
} {
st.Params["var"] = test.val
_, _, gotErr := st.convertParams()
if !testEqual(gotErr, test.wantErr) {
t.Errorf("value %#v:\ngot: %v\nwant: %v", test.val, gotErr, test.wantErr)
}
}
}
func TestNewStatement(t *testing.T) {
s := NewStatement("query")
if got, want := s.SQL, "query"; got != want {
t.Errorf("got %q, want %q", got, want)
}
}

44
vendor/cloud.google.com/go/spanner/stats.go generated vendored Normal file
View File

@@ -0,0 +1,44 @@
// 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 (
"context"
"go.opencensus.io/stats"
"go.opencensus.io/stats/view"
)
const statsPrefix = "cloud.google.com/go/spanner/"
func recordStat(ctx context.Context, m *stats.Int64Measure, n int64) {
stats.Record(ctx, m.M(n))
}
var (
// OpenSessionCount is a measure of the number of sessions currently opened.
// It is EXPERIMENTAL and subject to change or removal without notice.
OpenSessionCount = stats.Int64(statsPrefix+"open_session_count", "Number of sessions currently opened",
stats.UnitDimensionless)
// OpenSessionCountView is a view of the last value of OpenSessionCount.
// It is EXPERIMENTAL and subject to change or removal without notice.
OpenSessionCountView = &view.View{
Name: OpenSessionCount.Name(),
Description: OpenSessionCount.Description(),
Measure: OpenSessionCount,
Aggregation: view.LastValue(),
}
)

240
vendor/cloud.google.com/go/spanner/timestampbound.go generated vendored Normal file
View File

@@ -0,0 +1,240 @@
/*
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 (
"fmt"
"time"
pbd "github.com/golang/protobuf/ptypes/duration"
pbt "github.com/golang/protobuf/ptypes/timestamp"
sppb "google.golang.org/genproto/googleapis/spanner/v1"
)
// timestampBoundType specifies the timestamp bound mode.
type timestampBoundType int
const (
strong timestampBoundType = iota // strong reads
exactStaleness // read with exact staleness
maxStaleness // read with max staleness
minReadTimestamp // read with min freshness
readTimestamp // read data at exact timestamp
)
// TimestampBound defines how Cloud Spanner will choose a timestamp for a single
// read/query or read-only transaction.
//
// There are three types of timestamp bound: strong, bounded staleness and exact
// staleness. Strong is the default.
//
// If the Cloud Spanner database to be read is geographically distributed, stale
// read-only transactions can execute more quickly than strong or read-write
// transactions, because they are able to execute far from the leader replica.
//
// Each type of timestamp bound is discussed in detail below. A TimestampBound
// can be specified when creating transactions, see the documentation of
// spanner.Client for an example.
//
// Strong reads
//
// Strong reads are guaranteed to see the effects of all transactions that have
// committed before the start of the read. Furthermore, all rows yielded by a
// single read are consistent with each other: if any part of the read
// observes a transaction, all parts of the read see the transaction.
//
// Strong reads are not repeatable: two consecutive strong read-only
// transactions might return inconsistent results if there are concurrent
// writes. If consistency across reads is required, the reads should be
// executed within a transaction or at an exact read timestamp.
//
// Use StrongRead to create a bound of this type.
//
// Exact staleness
//
// An exact staleness timestamp bound executes reads at a user-specified timestamp.
// Reads at a timestamp are guaranteed to see a consistent prefix of the global
// transaction history: they observe modifications done by all transactions with a
// commit timestamp less than or equal to the read timestamp, and observe none of the
// modifications done by transactions with a larger commit timestamp. They will block
// until all conflicting transactions that may be assigned commit timestamps less
// than or equal to the read timestamp have finished.
//
// The timestamp can either be expressed as an absolute Cloud Spanner commit
// timestamp or a staleness relative to the current time.
//
// These modes do not require a "negotiation phase" to pick a timestamp. As a
// result, they execute slightly faster than the equivalent boundedly stale
// concurrency modes. On the other hand, boundedly stale reads usually return
// fresher results.
//
// Use ReadTimestamp and ExactStaleness to create a bound of this type.
//
// Bounded staleness
//
// Bounded staleness modes allow Cloud Spanner to pick the read timestamp, subject to
// a user-provided staleness bound. Cloud Spanner chooses the newest timestamp within
// the staleness bound that allows execution of the reads at the closest
// available replica without blocking.
//
// All rows yielded are consistent with each other: if any part of the read
// observes a transaction, all parts of the read see the transaction. Boundedly
// stale reads are not repeatable: two stale reads, even if they use the same
// staleness bound, can execute at different timestamps and thus return
// inconsistent results.
//
// Boundedly stale reads execute in two phases. The first phase negotiates a
// timestamp among all replicas needed to serve the read. In the second phase,
// reads are executed at the negotiated timestamp.
//
// As a result of this two-phase execution, bounded staleness reads are usually
// a little slower than comparable exact staleness reads. However, they are
// typically able to return fresher results, and are more likely to execute at
// the closest replica.
//
// Because the timestamp negotiation requires up-front knowledge of which rows
// will be read, it can only be used with single-use reads and single-use
// read-only transactions.
//
// Use MinReadTimestamp and MaxStaleness to create a bound of this type.
//
// Old read timestamps and garbage collection
//
// Cloud Spanner continuously garbage collects deleted and overwritten data in the
// background to reclaim storage space. This process is known as "version
// GC". By default, version GC reclaims versions after they are four hours
// old. Because of this, Cloud Spanner cannot perform reads at read timestamps more
// than four hours in the past. This restriction also applies to in-progress
// reads and/or SQL queries whose timestamps become too old while
// executing. Reads and SQL queries with too-old read timestamps fail with the
// error ErrorCode.FAILED_PRECONDITION.
type TimestampBound struct {
mode timestampBoundType
d time.Duration
t time.Time
}
// StrongRead returns a TimestampBound that will perform reads and queries at a
// timestamp where all previously committed transactions are visible.
func StrongRead() TimestampBound {
return TimestampBound{mode: strong}
}
// ExactStaleness returns a TimestampBound that will perform reads and queries
// at an exact staleness.
func ExactStaleness(d time.Duration) TimestampBound {
return TimestampBound{
mode: exactStaleness,
d: d,
}
}
// MaxStaleness returns a TimestampBound that will perform reads and queries at
// a time chosen to be at most "d" stale.
func MaxStaleness(d time.Duration) TimestampBound {
return TimestampBound{
mode: maxStaleness,
d: d,
}
}
// MinReadTimestamp returns a TimestampBound that bound that will perform reads
// and queries at a time chosen to be at least "t".
func MinReadTimestamp(t time.Time) TimestampBound {
return TimestampBound{
mode: minReadTimestamp,
t: t,
}
}
// ReadTimestamp returns a TimestampBound that will peform reads and queries at
// the given time.
func ReadTimestamp(t time.Time) TimestampBound {
return TimestampBound{
mode: readTimestamp,
t: t,
}
}
func (tb TimestampBound) String() string {
switch tb.mode {
case strong:
return fmt.Sprintf("(strong)")
case exactStaleness:
return fmt.Sprintf("(exactStaleness: %s)", tb.d)
case maxStaleness:
return fmt.Sprintf("(maxStaleness: %s)", tb.d)
case minReadTimestamp:
return fmt.Sprintf("(minReadTimestamp: %s)", tb.t)
case readTimestamp:
return fmt.Sprintf("(readTimestamp: %s)", tb.t)
default:
return fmt.Sprintf("{mode=%v, d=%v, t=%v}", tb.mode, tb.d, tb.t)
}
}
// durationProto takes a time.Duration and converts it into pdb.Duration for
// calling gRPC APIs.
func durationProto(d time.Duration) *pbd.Duration {
n := d.Nanoseconds()
return &pbd.Duration{
Seconds: n / int64(time.Second),
Nanos: int32(n % int64(time.Second)),
}
}
// timestampProto takes a time.Time and converts it into pbt.Timestamp for calling
// gRPC APIs.
func timestampProto(t time.Time) *pbt.Timestamp {
return &pbt.Timestamp{
Seconds: t.Unix(),
Nanos: int32(t.Nanosecond()),
}
}
// buildTransactionOptionsReadOnly converts a spanner.TimestampBound into a sppb.TransactionOptions_ReadOnly
// transaction option, which is then used in transactional reads.
func buildTransactionOptionsReadOnly(tb TimestampBound, returnReadTimestamp bool) *sppb.TransactionOptions_ReadOnly {
pb := &sppb.TransactionOptions_ReadOnly{
ReturnReadTimestamp: returnReadTimestamp,
}
switch tb.mode {
case strong:
pb.TimestampBound = &sppb.TransactionOptions_ReadOnly_Strong{
Strong: true,
}
case exactStaleness:
pb.TimestampBound = &sppb.TransactionOptions_ReadOnly_ExactStaleness{
ExactStaleness: durationProto(tb.d),
}
case maxStaleness:
pb.TimestampBound = &sppb.TransactionOptions_ReadOnly_MaxStaleness{
MaxStaleness: durationProto(tb.d),
}
case minReadTimestamp:
pb.TimestampBound = &sppb.TransactionOptions_ReadOnly_MinReadTimestamp{
MinReadTimestamp: timestampProto(tb.t),
}
case readTimestamp:
pb.TimestampBound = &sppb.TransactionOptions_ReadOnly_ReadTimestamp{
ReadTimestamp: timestampProto(tb.t),
}
default:
panic(fmt.Sprintf("buildTransactionOptionsReadOnly(%v,%v)", tb, returnReadTimestamp))
}
return pb
}

View File

@@ -0,0 +1,206 @@
/*
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 (
"testing"
"time"
pbd "github.com/golang/protobuf/ptypes/duration"
pbt "github.com/golang/protobuf/ptypes/timestamp"
sppb "google.golang.org/genproto/googleapis/spanner/v1"
)
// Test generating TimestampBound for strong reads.
func TestStrong(t *testing.T) {
got := StrongRead()
want := TimestampBound{mode: strong}
if !testEqual(got, want) {
t.Errorf("Strong() = %v; want %v", got, want)
}
}
// Test generating TimestampBound for reads with exact staleness.
func TestExactStaleness(t *testing.T) {
got := ExactStaleness(10 * time.Second)
want := TimestampBound{mode: exactStaleness, d: 10 * time.Second}
if !testEqual(got, want) {
t.Errorf("ExactStaleness(10*time.Second) = %v; want %v", got, want)
}
}
// Test generating TimestampBound for reads with max staleness.
func TestMaxStaleness(t *testing.T) {
got := MaxStaleness(10 * time.Second)
want := TimestampBound{mode: maxStaleness, d: 10 * time.Second}
if !testEqual(got, want) {
t.Errorf("MaxStaleness(10*time.Second) = %v; want %v", got, want)
}
}
// Test generating TimestampBound for reads with minimum freshness requirement.
func TestMinReadTimestamp(t *testing.T) {
ts := time.Now()
got := MinReadTimestamp(ts)
want := TimestampBound{mode: minReadTimestamp, t: ts}
if !testEqual(got, want) {
t.Errorf("MinReadTimestamp(%v) = %v; want %v", ts, got, want)
}
}
// Test generating TimestampBound for reads requesting data at a exact timestamp.
func TestReadTimestamp(t *testing.T) {
ts := time.Now()
got := ReadTimestamp(ts)
want := TimestampBound{mode: readTimestamp, t: ts}
if !testEqual(got, want) {
t.Errorf("ReadTimestamp(%v) = %v; want %v", ts, got, want)
}
}
// Test TimestampBound.String.
func TestTimestampBoundString(t *testing.T) {
ts := time.Unix(1136239445, 0).UTC()
var tests = []struct {
tb TimestampBound
want string
}{
{
tb: TimestampBound{mode: strong},
want: "(strong)",
},
{
tb: TimestampBound{mode: exactStaleness, d: 10 * time.Second},
want: "(exactStaleness: 10s)",
},
{
tb: TimestampBound{mode: maxStaleness, d: 10 * time.Second},
want: "(maxStaleness: 10s)",
},
{
tb: TimestampBound{mode: minReadTimestamp, t: ts},
want: "(minReadTimestamp: 2006-01-02 22:04:05 +0000 UTC)",
},
{
tb: TimestampBound{mode: readTimestamp, t: ts},
want: "(readTimestamp: 2006-01-02 22:04:05 +0000 UTC)",
},
}
for _, test := range tests {
got := test.tb.String()
if got != test.want {
t.Errorf("%#v.String():\ngot %q\nwant %q", test.tb, got, test.want)
}
}
}
// Test time.Duration to pdb.Duration conversion.
func TestDurationProto(t *testing.T) {
var tests = []struct {
d time.Duration
want pbd.Duration
}{
{time.Duration(0), pbd.Duration{Seconds: 0, Nanos: 0}},
{time.Second, pbd.Duration{Seconds: 1, Nanos: 0}},
{time.Millisecond, pbd.Duration{Seconds: 0, Nanos: 1e6}},
{15 * time.Nanosecond, pbd.Duration{Seconds: 0, Nanos: 15}},
{42 * time.Hour, pbd.Duration{Seconds: 151200}},
{-(1*time.Hour + 4*time.Millisecond), pbd.Duration{Seconds: -3600, Nanos: -4e6}},
}
for _, test := range tests {
got := durationProto(test.d)
if !testEqual(got, &test.want) {
t.Errorf("durationProto(%v) = %v; want %v", test.d, got, test.want)
}
}
}
// Test time.Time to pbt.Timestamp conversion.
func TestTimeProto(t *testing.T) {
var tests = []struct {
t time.Time
want pbt.Timestamp
}{
{time.Unix(0, 0), pbt.Timestamp{}},
{time.Unix(1136239445, 12345), pbt.Timestamp{Seconds: 1136239445, Nanos: 12345}},
{time.Unix(-1000, 12345), pbt.Timestamp{Seconds: -1000, Nanos: 12345}},
}
for _, test := range tests {
got := timestampProto(test.t)
if !testEqual(got, &test.want) {
t.Errorf("timestampProto(%v) = %v; want %v", test.t, got, test.want)
}
}
}
// Test readonly transaction option builder.
func TestBuildTransactionOptionsReadOnly(t *testing.T) {
ts := time.Unix(1136239445, 12345)
var tests = []struct {
tb TimestampBound
ts bool
want sppb.TransactionOptions_ReadOnly
}{
{
StrongRead(), false,
sppb.TransactionOptions_ReadOnly{
TimestampBound: &sppb.TransactionOptions_ReadOnly_Strong{
Strong: true},
ReturnReadTimestamp: false,
},
},
{
ExactStaleness(10 * time.Second), true,
sppb.TransactionOptions_ReadOnly{
TimestampBound: &sppb.TransactionOptions_ReadOnly_ExactStaleness{
ExactStaleness: &pbd.Duration{Seconds: 10}},
ReturnReadTimestamp: true,
},
},
{
MaxStaleness(10 * time.Second), true,
sppb.TransactionOptions_ReadOnly{
TimestampBound: &sppb.TransactionOptions_ReadOnly_MaxStaleness{
MaxStaleness: &pbd.Duration{Seconds: 10}},
ReturnReadTimestamp: true,
},
},
{
MinReadTimestamp(ts), true,
sppb.TransactionOptions_ReadOnly{
TimestampBound: &sppb.TransactionOptions_ReadOnly_MinReadTimestamp{
MinReadTimestamp: &pbt.Timestamp{Seconds: 1136239445, Nanos: 12345}},
ReturnReadTimestamp: true,
},
},
{
ReadTimestamp(ts), true,
sppb.TransactionOptions_ReadOnly{
TimestampBound: &sppb.TransactionOptions_ReadOnly_ReadTimestamp{
ReadTimestamp: &pbt.Timestamp{Seconds: 1136239445, Nanos: 12345}},
ReturnReadTimestamp: true,
},
},
}
for _, test := range tests {
got := buildTransactionOptionsReadOnly(test.tb, test.ts)
if !testEqual(got, &test.want) {
t.Errorf("buildTransactionOptionsReadOnly(%v,%v) = %v; want %v", test.tb, test.ts, got, test.want)
}
}
}

965
vendor/cloud.google.com/go/spanner/transaction.go generated vendored Normal file
View File

@@ -0,0 +1,965 @@
/*
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 (
"context"
"sync"
"sync/atomic"
"time"
"cloud.google.com/go/internal/trace"
"google.golang.org/api/iterator"
sppb "google.golang.org/genproto/googleapis/spanner/v1"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
)
// transactionID stores a transaction ID which uniquely identifies a transaction in Cloud Spanner.
type transactionID []byte
// txReadEnv manages a read-transaction environment consisting of a session handle and a transaction selector.
type txReadEnv interface {
// acquire returns a read-transaction environment that can be used to perform a transactional read.
acquire(ctx context.Context) (*sessionHandle, *sppb.TransactionSelector, error)
// sets the transaction's read timestamp
setTimestamp(time.Time)
// release should be called at the end of every transactional read to deal with session recycling.
release(error)
}
// txReadOnly contains methods for doing transactional reads.
type txReadOnly struct {
// read-transaction environment for performing transactional read operations.
txReadEnv
sequenceNumber int64 // Atomic. Only needed for DML statements, but used for all.
}
// errSessionClosed returns error for using a recycled/destroyed session
func errSessionClosed(sh *sessionHandle) error {
return spannerErrorf(codes.FailedPrecondition,
"session is already recycled / destroyed: session_id = %q, rpc_client = %v", sh.getID(), sh.getClient())
}
// Read returns a RowIterator for reading multiple rows from the database.
func (t *txReadOnly) Read(ctx context.Context, table string, keys KeySet, columns []string) *RowIterator {
return t.ReadWithOptions(ctx, table, keys, columns, nil)
}
// ReadUsingIndex calls ReadWithOptions with ReadOptions{Index: index}.
func (t *txReadOnly) ReadUsingIndex(ctx context.Context, table, index string, keys KeySet, columns []string) (ri *RowIterator) {
return t.ReadWithOptions(ctx, table, keys, columns, &ReadOptions{Index: index})
}
// ReadOptions provides options for reading rows from a database.
type ReadOptions struct {
// The index to use for reading. If non-empty, you can only read columns that are
// part of the index key, part of the primary key, or stored in the index due to
// a STORING clause in the index definition.
Index string
// The maximum number of rows to read. A limit value less than 1 means no limit.
Limit int
}
// ReadWithOptions returns a RowIterator for reading multiple rows from the database.
// Pass a ReadOptions to modify the read operation.
func (t *txReadOnly) ReadWithOptions(ctx context.Context, table string, keys KeySet, columns []string, opts *ReadOptions) (ri *RowIterator) {
ctx = trace.StartSpan(ctx, "cloud.google.com/go/spanner.Read")
defer func() { trace.EndSpan(ctx, ri.err) }()
var (
sh *sessionHandle
ts *sppb.TransactionSelector
err error
)
kset, err := keys.keySetProto()
if err != nil {
return &RowIterator{err: err}
}
if sh, ts, err = t.acquire(ctx); err != nil {
return &RowIterator{err: err}
}
// Cloud Spanner will return "Session not found" on bad sessions.
sid, client := sh.getID(), sh.getClient()
if sid == "" || client == nil {
// Might happen if transaction is closed in the middle of a API call.
return &RowIterator{err: errSessionClosed(sh)}
}
index := ""
limit := 0
if opts != nil {
index = opts.Index
if opts.Limit > 0 {
limit = opts.Limit
}
}
return stream(
contextWithOutgoingMetadata(ctx, sh.getMetadata()),
func(ctx context.Context, resumeToken []byte) (streamingReceiver, error) {
return client.StreamingRead(ctx,
&sppb.ReadRequest{
Session: sid,
Transaction: ts,
Table: table,
Index: index,
Columns: columns,
KeySet: kset,
ResumeToken: resumeToken,
Limit: int64(limit),
})
},
t.setTimestamp,
t.release,
)
}
// errRowNotFound returns error for not being able to read the row identified by key.
func errRowNotFound(table string, key Key) error {
return spannerErrorf(codes.NotFound, "row not found(Table: %v, PrimaryKey: %v)", table, key)
}
// ReadRow reads a single row from the database.
//
// If no row is present with the given key, then ReadRow returns an error where
// spanner.ErrCode(err) is codes.NotFound.
func (t *txReadOnly) ReadRow(ctx context.Context, table string, key Key, columns []string) (*Row, error) {
iter := t.Read(ctx, table, key, columns)
defer iter.Stop()
row, err := iter.Next()
switch err {
case iterator.Done:
return nil, errRowNotFound(table, key)
case nil:
return row, nil
default:
return nil, err
}
}
// Query executes a query against the database. It returns a RowIterator
// for retrieving the resulting rows.
//
// Query returns only row data, without a query plan or execution statistics.
// Use QueryWithStats to get rows along with the plan and statistics.
// Use AnalyzeQuery to get just the plan.
func (t *txReadOnly) Query(ctx context.Context, statement Statement) *RowIterator {
return t.query(ctx, statement, sppb.ExecuteSqlRequest_NORMAL)
}
// Query executes a SQL statement against the database. It returns a RowIterator
// for retrieving the resulting rows. The RowIterator will also be populated
// with a query plan and execution statistics.
func (t *txReadOnly) QueryWithStats(ctx context.Context, statement Statement) *RowIterator {
return t.query(ctx, statement, sppb.ExecuteSqlRequest_PROFILE)
}
// AnalyzeQuery returns the query plan for statement.
func (t *txReadOnly) AnalyzeQuery(ctx context.Context, statement Statement) (*sppb.QueryPlan, error) {
iter := t.query(ctx, statement, sppb.ExecuteSqlRequest_PLAN)
defer iter.Stop()
for {
_, err := iter.Next()
if err == iterator.Done {
break
}
if err != nil {
return nil, err
}
}
if iter.QueryPlan == nil {
return nil, spannerErrorf(codes.Internal, "query plan unavailable")
}
return iter.QueryPlan, nil
}
func (t *txReadOnly) query(ctx context.Context, statement Statement, mode sppb.ExecuteSqlRequest_QueryMode) (ri *RowIterator) {
ctx = trace.StartSpan(ctx, "cloud.google.com/go/spanner.Query")
defer func() { trace.EndSpan(ctx, ri.err) }()
req, sh, err := t.prepareExecuteSQL(ctx, statement, mode)
if err != nil {
return &RowIterator{err: err}
}
client := sh.getClient()
return stream(
contextWithOutgoingMetadata(ctx, sh.getMetadata()),
func(ctx context.Context, resumeToken []byte) (streamingReceiver, error) {
req.ResumeToken = resumeToken
return client.ExecuteStreamingSql(ctx, req)
},
t.setTimestamp,
t.release)
}
func (t *txReadOnly) prepareExecuteSQL(ctx context.Context, stmt Statement, mode sppb.ExecuteSqlRequest_QueryMode) (*sppb.ExecuteSqlRequest, *sessionHandle, error) {
sh, ts, err := t.acquire(ctx)
if err != nil {
return nil, nil, err
}
// Cloud Spanner will return "Session not found" on bad sessions.
sid := sh.getID()
if sid == "" {
// Might happen if transaction is closed in the middle of a API call.
return nil, nil, errSessionClosed(sh)
}
params, paramTypes, err := stmt.convertParams()
if err != nil {
return nil, nil, err
}
req := &sppb.ExecuteSqlRequest{
Session: sid,
Transaction: ts,
Sql: stmt.SQL,
QueryMode: mode,
Seqno: atomic.AddInt64(&t.sequenceNumber, 1),
Params: params,
ParamTypes: paramTypes,
}
return req, sh, nil
}
// txState is the status of a transaction.
type txState int
const (
// transaction is new, waiting to be initialized.
txNew txState = iota
// transaction is being initialized.
txInit
// transaction is active and can perform read/write.
txActive
// transaction is closed, cannot be used anymore.
txClosed
)
// errRtsUnavailable returns error for read transaction's read timestamp being unavailable.
func errRtsUnavailable() error {
return spannerErrorf(codes.Internal, "read timestamp is unavailable")
}
// errTxClosed returns error for using a closed transaction.
func errTxClosed() error {
return spannerErrorf(codes.InvalidArgument, "cannot use a closed transaction")
}
// errUnexpectedTxState returns error for transaction enters an unexpected state.
func errUnexpectedTxState(ts txState) error {
return spannerErrorf(codes.FailedPrecondition, "unexpected transaction state: %v", ts)
}
// ReadOnlyTransaction provides a snapshot transaction with guaranteed
// consistency across reads, but does not allow writes. Read-only
// transactions can be configured to read at timestamps in the past.
//
// Read-only transactions do not take locks. Instead, they work by choosing a
// Cloud Spanner timestamp, then executing all reads at that timestamp. Since they do
// not acquire locks, they do not block concurrent read-write transactions.
//
// Unlike locking read-write transactions, read-only transactions never
// abort. They can fail if the chosen read timestamp is garbage collected;
// however, the default garbage collection policy is generous enough that most
// applications do not need to worry about this in practice. See the
// documentation of TimestampBound for more details.
//
// A ReadOnlyTransaction consumes resources on the server until Close is
// called.
type ReadOnlyTransaction struct {
// txReadOnly contains methods for performing transactional reads.
txReadOnly
// singleUse indicates that the transaction can be used for only one read.
singleUse bool
// sp is the session pool for allocating a session to execute the read-only transaction. It is set only once during initialization of the ReadOnlyTransaction.
sp *sessionPool
// mu protects concurrent access to the internal states of ReadOnlyTransaction.
mu sync.Mutex
// tx is the transaction ID in Cloud Spanner that uniquely identifies the ReadOnlyTransaction.
tx transactionID
// txReadyOrClosed is for broadcasting that transaction ID has been returned by Cloud Spanner or that transaction is closed.
txReadyOrClosed chan struct{}
// state is the current transaction status of the ReadOnly transaction.
state txState
// sh is the sessionHandle allocated from sp.
sh *sessionHandle
// rts is the read timestamp returned by transactional reads.
rts time.Time
// tb is the read staleness bound specification for transactional reads.
tb TimestampBound
}
// errTxInitTimeout returns error for timeout in waiting for initialization of the transaction.
func errTxInitTimeout() error {
return spannerErrorf(codes.Canceled, "timeout/context canceled in waiting for transaction's initialization")
}
// getTimestampBound returns the read staleness bound specified for the ReadOnlyTransaction.
func (t *ReadOnlyTransaction) getTimestampBound() TimestampBound {
t.mu.Lock()
defer t.mu.Unlock()
return t.tb
}
// begin starts a snapshot read-only Transaction on Cloud Spanner.
func (t *ReadOnlyTransaction) begin(ctx context.Context) error {
var (
locked bool
tx transactionID
rts time.Time
sh *sessionHandle
err error
)
defer func() {
if !locked {
t.mu.Lock()
// Not necessary, just to make it clear that t.mu is being held when locked == true.
locked = true
}
if t.state != txClosed {
// Signal other initialization routines.
close(t.txReadyOrClosed)
t.txReadyOrClosed = make(chan struct{})
}
t.mu.Unlock()
if err != nil && sh != nil {
// Got a valid session handle, but failed to initialize transaction on Cloud Spanner.
if shouldDropSession(err) {
sh.destroy()
}
// If sh.destroy was already executed, this becomes a noop.
sh.recycle()
}
}()
sh, err = t.sp.take(ctx)
if err != nil {
return err
}
err = runRetryable(contextWithOutgoingMetadata(ctx, sh.getMetadata()), func(ctx context.Context) error {
res, e := sh.getClient().BeginTransaction(ctx, &sppb.BeginTransactionRequest{
Session: sh.getID(),
Options: &sppb.TransactionOptions{
Mode: &sppb.TransactionOptions_ReadOnly_{
ReadOnly: buildTransactionOptionsReadOnly(t.getTimestampBound(), true),
},
},
})
if e != nil {
return e
}
tx = res.Id
if res.ReadTimestamp != nil {
rts = time.Unix(res.ReadTimestamp.Seconds, int64(res.ReadTimestamp.Nanos))
}
return nil
})
t.mu.Lock()
locked = true // defer function will be executed with t.mu being held.
if t.state == txClosed { // During the execution of t.begin(), t.Close() was invoked.
return errSessionClosed(sh)
}
// If begin() fails, this allows other queries to take over the initialization.
t.tx = nil
if err == nil {
t.tx = tx
t.rts = rts
t.sh = sh
// State transite to txActive.
t.state = txActive
}
return err
}
// acquire implements txReadEnv.acquire.
func (t *ReadOnlyTransaction) acquire(ctx context.Context) (*sessionHandle, *sppb.TransactionSelector, error) {
if err := checkNestedTxn(ctx); err != nil {
return nil, nil, err
}
if t.singleUse {
return t.acquireSingleUse(ctx)
}
return t.acquireMultiUse(ctx)
}
func (t *ReadOnlyTransaction) acquireSingleUse(ctx context.Context) (*sessionHandle, *sppb.TransactionSelector, error) {
t.mu.Lock()
defer t.mu.Unlock()
switch t.state {
case txClosed:
// A closed single-use transaction can never be reused.
return nil, nil, errTxClosed()
case txNew:
t.state = txClosed
ts := &sppb.TransactionSelector{
Selector: &sppb.TransactionSelector_SingleUse{
SingleUse: &sppb.TransactionOptions{
Mode: &sppb.TransactionOptions_ReadOnly_{
ReadOnly: buildTransactionOptionsReadOnly(t.tb, true),
},
},
},
}
sh, err := t.sp.take(ctx)
if err != nil {
return nil, nil, err
}
// Install session handle into t, which can be used for readonly operations later.
t.sh = sh
return sh, ts, nil
}
us := t.state
// SingleUse transaction should only be in either txNew state or txClosed state.
return nil, nil, errUnexpectedTxState(us)
}
func (t *ReadOnlyTransaction) acquireMultiUse(ctx context.Context) (*sessionHandle, *sppb.TransactionSelector, error) {
for {
t.mu.Lock()
switch t.state {
case txClosed:
t.mu.Unlock()
return nil, nil, errTxClosed()
case txNew:
// State transit to txInit so that no further TimestampBound change is accepted.
t.state = txInit
t.mu.Unlock()
continue
case txInit:
if t.tx != nil {
// Wait for a transaction ID to become ready.
txReadyOrClosed := t.txReadyOrClosed
t.mu.Unlock()
select {
case <-txReadyOrClosed:
// Need to check transaction state again.
continue
case <-ctx.Done():
// The waiting for initialization is timeout, return error directly.
return nil, nil, errTxInitTimeout()
}
}
// Take the ownership of initializing the transaction.
t.tx = transactionID{}
t.mu.Unlock()
// Begin a read-only transaction.
// TODO: consider adding a transaction option which allow queries to initiate transactions by themselves. Note that this option might not be
// always good because the ID of the new transaction won't be ready till the query returns some data or completes.
if err := t.begin(ctx); err != nil {
return nil, nil, err
}
// If t.begin() succeeded, t.state should have been changed to txActive, so we can just continue here.
continue
case txActive:
sh := t.sh
ts := &sppb.TransactionSelector{
Selector: &sppb.TransactionSelector_Id{
Id: t.tx,
},
}
t.mu.Unlock()
return sh, ts, nil
}
state := t.state
t.mu.Unlock()
return nil, nil, errUnexpectedTxState(state)
}
}
func (t *ReadOnlyTransaction) setTimestamp(ts time.Time) {
t.mu.Lock()
defer t.mu.Unlock()
if t.rts.IsZero() {
t.rts = ts
}
}
// release implements txReadEnv.release.
func (t *ReadOnlyTransaction) release(err error) {
t.mu.Lock()
sh := t.sh
t.mu.Unlock()
if sh != nil { // sh could be nil if t.acquire() fails.
if shouldDropSession(err) {
sh.destroy()
}
if t.singleUse {
// If session handle is already destroyed, this becomes a noop.
sh.recycle()
}
}
}
// Close closes a ReadOnlyTransaction, the transaction cannot perform any reads after being closed.
func (t *ReadOnlyTransaction) Close() {
if t.singleUse {
return
}
t.mu.Lock()
if t.state != txClosed {
t.state = txClosed
close(t.txReadyOrClosed)
}
sh := t.sh
t.mu.Unlock()
if sh == nil {
return
}
// If session handle is already destroyed, this becomes a noop.
// If there are still active queries and if the recycled session is reused before they complete, Cloud Spanner will cancel them
// on behalf of the new transaction on the session.
if sh != nil {
sh.recycle()
}
}
// Timestamp returns the timestamp chosen to perform reads and
// queries in this transaction. The value can only be read after some
// read or query has either returned some data or completed without
// returning any data.
func (t *ReadOnlyTransaction) Timestamp() (time.Time, error) {
t.mu.Lock()
defer t.mu.Unlock()
if t.rts.IsZero() {
return t.rts, errRtsUnavailable()
}
return t.rts, nil
}
// WithTimestampBound specifies the TimestampBound to use for read or query.
// This can only be used before the first read or query is invoked. Note:
// bounded staleness is not available with general ReadOnlyTransactions; use a
// single-use ReadOnlyTransaction instead.
//
// The returned value is the ReadOnlyTransaction so calls can be chained.
func (t *ReadOnlyTransaction) WithTimestampBound(tb TimestampBound) *ReadOnlyTransaction {
t.mu.Lock()
defer t.mu.Unlock()
if t.state == txNew {
// Only allow to set TimestampBound before the first query.
t.tb = tb
}
return t
}
// ReadWriteTransaction provides a locking read-write transaction.
//
// This type of transaction is the only way to write data into Cloud Spanner;
// (*Client).Apply, (*Client).ApplyAtLeastOnce, (*Client).PartitionedUpdate use
// transactions internally. These transactions rely on pessimistic locking and,
// if necessary, two-phase commit. Locking read-write transactions may abort,
// requiring the application to retry. However, the interface exposed by
// (*Client).ReadWriteTransaction eliminates the need for applications to write
// retry loops explicitly.
//
// Locking transactions may be used to atomically read-modify-write data
// anywhere in a database. This type of transaction is externally consistent.
//
// Clients should attempt to minimize the amount of time a transaction is
// active. Faster transactions commit with higher probability and cause less
// contention. Cloud Spanner attempts to keep read locks active as long as the
// transaction continues to do reads. Long periods of inactivity at the client
// may cause Cloud Spanner to release a transaction's locks and abort it.
//
// Reads performed within a transaction acquire locks on the data being
// read. Writes can only be done at commit time, after all reads have been
// completed. Conceptually, a read-write transaction consists of zero or more
// reads or SQL queries followed by a commit.
//
// See (*Client).ReadWriteTransaction for an example.
//
// Semantics
//
// Cloud Spanner can commit the transaction if all read locks it acquired are still
// valid at commit time, and it is able to acquire write locks for all
// writes. Cloud Spanner can abort the transaction for any reason. If a commit
// attempt returns ABORTED, Cloud Spanner guarantees that the transaction has not
// modified any user data in Cloud Spanner.
//
// Unless the transaction commits, Cloud Spanner makes no guarantees about how long
// the transaction's locks were held for. It is an error to use Cloud Spanner locks
// for any sort of mutual exclusion other than between Cloud Spanner transactions
// themselves.
//
// Aborted transactions
//
// Application code does not need to retry explicitly; RunInTransaction will
// automatically retry a transaction if an attempt results in an abort. The
// lock priority of a transaction increases after each prior aborted
// transaction, meaning that the next attempt has a slightly better chance of
// success than before.
//
// Under some circumstances (e.g., many transactions attempting to modify the
// same row(s)), a transaction can abort many times in a short period before
// successfully committing. Thus, it is not a good idea to cap the number of
// retries a transaction can attempt; instead, it is better to limit the total
// amount of wall time spent retrying.
//
// Idle transactions
//
// A transaction is considered idle if it has no outstanding reads or SQL
// queries and has not started a read or SQL query within the last 10
// seconds. Idle transactions can be aborted by Cloud Spanner so that they don't hold
// on to locks indefinitely. In that case, the commit will fail with error
// ABORTED.
//
// If this behavior is undesirable, periodically executing a simple SQL query
// in the transaction (e.g., SELECT 1) prevents the transaction from becoming
// idle.
type ReadWriteTransaction struct {
// txReadOnly contains methods for performing transactional reads.
txReadOnly
// sh is the sessionHandle allocated from sp. It is set only once during the initialization of ReadWriteTransaction.
sh *sessionHandle
// tx is the transaction ID in Cloud Spanner that uniquely identifies the ReadWriteTransaction.
// It is set only once in ReadWriteTransaction.begin() during the initialization of ReadWriteTransaction.
tx transactionID
// mu protects concurrent access to the internal states of ReadWriteTransaction.
mu sync.Mutex
// state is the current transaction status of the read-write transaction.
state txState
// wb is the set of buffered mutations waiting to be committed.
wb []*Mutation
}
// BufferWrite adds a list of mutations to the set of updates that will be
// applied when the transaction is committed. It does not actually apply the
// write until the transaction is committed, so the operation does not
// block. The effects of the write won't be visible to any reads (including
// reads done in the same transaction) until the transaction commits.
//
// See the example for Client.ReadWriteTransaction.
func (t *ReadWriteTransaction) BufferWrite(ms []*Mutation) error {
t.mu.Lock()
defer t.mu.Unlock()
if t.state == txClosed {
return errTxClosed()
}
if t.state != txActive {
return errUnexpectedTxState(t.state)
}
t.wb = append(t.wb, ms...)
return nil
}
// Update executes a DML statement against the database. It returns the number of
// affected rows.
// Update returns an error if the statement is a query. However, the
// query is executed, and any data read will be validated upon commit.
func (t *ReadWriteTransaction) Update(ctx context.Context, stmt Statement) (rowCount int64, err error) {
ctx = trace.StartSpan(ctx, "cloud.google.com/go/spanner.Update")
defer func() { trace.EndSpan(ctx, err) }()
req, sh, err := t.prepareExecuteSQL(ctx, stmt, sppb.ExecuteSqlRequest_NORMAL)
if err != nil {
return 0, err
}
resultSet, err := sh.getClient().ExecuteSql(ctx, req)
if err != nil {
return 0, err
}
if resultSet.Stats == nil {
return 0, spannerErrorf(codes.InvalidArgument, "query passed to Update: %q", stmt.SQL)
}
return extractRowCount(resultSet.Stats)
}
// BatchUpdate groups one or more DML statements and sends them to Spanner in a
// single RPC. This is an efficient way to execute multiple DML statements.
//
// A slice of counts is returned, where each count represents the number of
// affected rows for the given query at the same index. If an error occurs,
// counts will be returned up to the query that encountered the error.
func (t *ReadWriteTransaction) BatchUpdate(ctx context.Context, stmts []Statement) (_ []int64, err error) {
ctx = trace.StartSpan(ctx, "cloud.google.com/go/spanner.BatchUpdate")
defer func() { trace.EndSpan(ctx, err) }()
sh, ts, err := t.acquire(ctx)
if err != nil {
return nil, err
}
// Cloud Spanner will return "Session not found" on bad sessions.
sid := sh.getID()
if sid == "" {
// Might happen if transaction is closed in the middle of a API call.
return nil, errSessionClosed(sh)
}
var sppbStmts []*sppb.ExecuteBatchDmlRequest_Statement
for _, st := range stmts {
params, paramTypes, err := st.convertParams()
if err != nil {
return nil, err
}
sppbStmts = append(sppbStmts, &sppb.ExecuteBatchDmlRequest_Statement{
Sql: st.SQL,
Params: params,
ParamTypes: paramTypes,
})
}
resp, err := sh.getClient().ExecuteBatchDml(ctx, &sppb.ExecuteBatchDmlRequest{
Session: sh.getID(),
Transaction: ts,
Statements: sppbStmts,
Seqno: atomic.AddInt64(&t.sequenceNumber, 1),
})
if err != nil {
return nil, err
}
var counts []int64
for _, rs := range resp.ResultSets {
count, err := extractRowCount(rs.Stats)
if err != nil {
return nil, err
}
counts = append(counts, count)
}
if resp.Status.Code != 0 {
return counts, spannerErrorf(codes.Code(uint32(resp.Status.Code)), resp.Status.Message)
}
return counts, nil
}
// acquire implements txReadEnv.acquire.
func (t *ReadWriteTransaction) acquire(ctx context.Context) (*sessionHandle, *sppb.TransactionSelector, error) {
ts := &sppb.TransactionSelector{
Selector: &sppb.TransactionSelector_Id{
Id: t.tx,
},
}
t.mu.Lock()
defer t.mu.Unlock()
switch t.state {
case txClosed:
return nil, nil, errTxClosed()
case txActive:
return t.sh, ts, nil
}
return nil, nil, errUnexpectedTxState(t.state)
}
// release implements txReadEnv.release.
func (t *ReadWriteTransaction) release(err error) {
t.mu.Lock()
sh := t.sh
t.mu.Unlock()
if sh != nil && shouldDropSession(err) {
sh.destroy()
}
}
func beginTransaction(ctx context.Context, sid string, client sppb.SpannerClient) (transactionID, error) {
var tx transactionID
err := runRetryable(ctx, func(ctx context.Context) error {
res, e := client.BeginTransaction(ctx, &sppb.BeginTransactionRequest{
Session: sid,
Options: &sppb.TransactionOptions{
Mode: &sppb.TransactionOptions_ReadWrite_{
ReadWrite: &sppb.TransactionOptions_ReadWrite{},
},
},
})
if e != nil {
return e
}
tx = res.Id
return nil
})
if err != nil {
return nil, err
}
return tx, nil
}
// begin starts a read-write transacton on Cloud Spanner, it is always called before any of the public APIs.
func (t *ReadWriteTransaction) begin(ctx context.Context) error {
if t.tx != nil {
t.state = txActive
return nil
}
tx, err := beginTransaction(contextWithOutgoingMetadata(ctx, t.sh.getMetadata()), t.sh.getID(), t.sh.getClient())
if err == nil {
t.tx = tx
t.state = txActive
return nil
}
if shouldDropSession(err) {
t.sh.destroy()
}
return err
}
// commit tries to commit a readwrite transaction to Cloud Spanner. It also returns the commit timestamp for the transactions.
func (t *ReadWriteTransaction) commit(ctx context.Context) (time.Time, error) {
var ts time.Time
t.mu.Lock()
t.state = txClosed // No further operations after commit.
mPb, err := mutationsProto(t.wb)
t.mu.Unlock()
if err != nil {
return ts, err
}
// In case that sessionHandle was destroyed but transaction body fails to report it.
sid, client := t.sh.getID(), t.sh.getClient()
if sid == "" || client == nil {
return ts, errSessionClosed(t.sh)
}
err = runRetryable(contextWithOutgoingMetadata(ctx, t.sh.getMetadata()), func(ctx context.Context) error {
var trailer metadata.MD
res, e := client.Commit(ctx, &sppb.CommitRequest{
Session: sid,
Transaction: &sppb.CommitRequest_TransactionId{
TransactionId: t.tx,
},
Mutations: mPb,
}, grpc.Trailer(&trailer))
if e != nil {
return toSpannerErrorWithMetadata(e, trailer)
}
if tstamp := res.GetCommitTimestamp(); tstamp != nil {
ts = time.Unix(tstamp.Seconds, int64(tstamp.Nanos))
}
return nil
})
if shouldDropSession(err) {
t.sh.destroy()
}
return ts, err
}
// rollback is called when a commit is aborted or the transaction body runs into error.
func (t *ReadWriteTransaction) rollback(ctx context.Context) {
t.mu.Lock()
// Forbid further operations on rollbacked transaction.
t.state = txClosed
t.mu.Unlock()
// In case that sessionHandle was destroyed but transaction body fails to report it.
sid, client := t.sh.getID(), t.sh.getClient()
if sid == "" || client == nil {
return
}
err := runRetryable(contextWithOutgoingMetadata(ctx, t.sh.getMetadata()), func(ctx context.Context) error {
_, e := client.Rollback(ctx, &sppb.RollbackRequest{
Session: sid,
TransactionId: t.tx,
})
return e
})
if shouldDropSession(err) {
t.sh.destroy()
}
}
// runInTransaction executes f under a read-write transaction context.
func (t *ReadWriteTransaction) runInTransaction(ctx context.Context, f func(context.Context, *ReadWriteTransaction) error) (time.Time, error) {
var (
ts time.Time
err error
)
if err = f(context.WithValue(ctx, transactionInProgressKey{}, 1), t); err == nil {
// Try to commit if transaction body returns no error.
ts, err = t.commit(ctx)
}
if err != nil {
if isAbortErr(err) {
// Retry the transaction using the same session on ABORT error.
// Cloud Spanner will create the new transaction with the previous one's wound-wait priority.
err = errRetry(err)
return ts, err
}
// Not going to commit, according to API spec, should rollback the transaction.
t.rollback(ctx)
return ts, err
}
// err == nil, return commit timestamp.
return ts, nil
}
// writeOnlyTransaction provides the most efficient way of doing write-only transactions. It essentially does blind writes to Cloud Spanner.
type writeOnlyTransaction struct {
// sp is the session pool which writeOnlyTransaction uses to get Cloud Spanner sessions for blind writes.
sp *sessionPool
}
// applyAtLeastOnce commits a list of mutations to Cloud Spanner at least once, unless one of the following happens:
// 1) Context times out.
// 2) An unretryable error (e.g. database not found) occurs.
// 3) There is a malformed Mutation object.
func (t *writeOnlyTransaction) applyAtLeastOnce(ctx context.Context, ms ...*Mutation) (time.Time, error) {
var (
ts time.Time
sh *sessionHandle
)
mPb, err := mutationsProto(ms)
if err != nil {
// Malformed mutation found, just return the error.
return ts, err
}
err = runRetryable(ctx, func(ct context.Context) error {
var e error
var trailers metadata.MD
if sh == nil || sh.getID() == "" || sh.getClient() == nil {
// No usable session for doing the commit, take one from pool.
sh, e = t.sp.take(ctx)
if e != nil {
// sessionPool.Take already retries for session creations/retrivals.
return e
}
}
res, e := sh.getClient().Commit(contextWithOutgoingMetadata(ctx, sh.getMetadata()), &sppb.CommitRequest{
Session: sh.getID(),
Transaction: &sppb.CommitRequest_SingleUseTransaction{
SingleUseTransaction: &sppb.TransactionOptions{
Mode: &sppb.TransactionOptions_ReadWrite_{
ReadWrite: &sppb.TransactionOptions_ReadWrite{},
},
},
},
Mutations: mPb,
}, grpc.Trailer(&trailers))
if e != nil {
if isAbortErr(e) {
// Mask ABORT error as retryable, because aborted transactions are allowed to be retried.
return errRetry(toSpannerErrorWithMetadata(e, trailers))
}
if shouldDropSession(e) {
// Discard the bad session.
sh.destroy()
}
return e
}
if tstamp := res.GetCommitTimestamp(); tstamp != nil {
ts = time.Unix(tstamp.Seconds, int64(tstamp.Nanos))
}
return nil
})
if sh != nil {
sh.recycle()
}
return ts, err
}
// isAbortedErr returns true if the error indicates that an gRPC call is aborted on the server side.
func isAbortErr(err error) bool {
if err == nil {
return false
}
if ErrCode(err) == codes.Aborted {
return true
}
return false
}

409
vendor/cloud.google.com/go/spanner/transaction_test.go generated vendored Normal file
View File

@@ -0,0 +1,409 @@
/*
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 (
"context"
"errors"
"fmt"
"reflect"
"sync"
"testing"
"time"
"cloud.google.com/go/spanner/internal/testutil"
sppb "google.golang.org/genproto/googleapis/spanner/v1"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
)
// Single can only be used once.
func TestSingle(t *testing.T) {
t.Parallel()
ctx := context.Background()
client, _, mock, cleanup := serverClientMock(t, SessionPoolConfig{})
defer cleanup()
txn := client.Single()
defer txn.Close()
_, _, e := txn.acquire(ctx)
if e != nil {
t.Fatalf("Acquire for single use, got %v, want nil.", e)
}
_, _, e = txn.acquire(ctx)
if wantErr := errTxClosed(); !testEqual(e, wantErr) {
t.Fatalf("Second acquire for single use, got %v, want %v.", e, wantErr)
}
// Only one CreateSessionRequest is sent.
if _, err := shouldHaveReceived(mock, []interface{}{&sppb.CreateSessionRequest{}}); err != nil {
t.Fatal(err)
}
}
// Re-using ReadOnlyTransaction: can recover from acquire failure.
func TestReadOnlyTransaction_RecoverFromFailure(t *testing.T) {
t.Parallel()
ctx := context.Background()
client, _, mock, cleanup := serverClientMock(t, SessionPoolConfig{})
defer cleanup()
txn := client.ReadOnlyTransaction()
defer txn.Close()
// First request will fail, which should trigger a retry.
errUsr := errors.New("error")
firstCall := true
mock.BeginTransactionFn = func(c context.Context, r *sppb.BeginTransactionRequest, opts ...grpc.CallOption) (*sppb.Transaction, error) {
if firstCall {
mock.MockCloudSpannerClient.ReceivedRequests <- r
firstCall = false
return nil, errUsr
}
return mock.MockCloudSpannerClient.BeginTransaction(c, r, opts...)
}
_, _, e := txn.acquire(ctx)
if wantErr := toSpannerError(errUsr); !testEqual(e, wantErr) {
t.Fatalf("Acquire for multi use, got %v, want %v.", e, wantErr)
}
_, _, e = txn.acquire(ctx)
if e != nil {
t.Fatalf("Acquire for multi use, got %v, want nil.", e)
}
}
// ReadOnlyTransaction: can not be used after close.
func TestReadOnlyTransaction_UseAfterClose(t *testing.T) {
t.Parallel()
ctx := context.Background()
client, _, _, cleanup := serverClientMock(t, SessionPoolConfig{})
defer cleanup()
txn := client.ReadOnlyTransaction()
txn.Close()
_, _, e := txn.acquire(ctx)
if wantErr := errTxClosed(); !testEqual(e, wantErr) {
t.Fatalf("Second acquire for multi use, got %v, want %v.", e, wantErr)
}
}
// ReadOnlyTransaction: can be acquired concurrently.
func TestReadOnlyTransaction_Concurrent(t *testing.T) {
t.Parallel()
ctx := context.Background()
client, _, mock, cleanup := serverClientMock(t, SessionPoolConfig{})
defer cleanup()
txn := client.ReadOnlyTransaction()
defer txn.Close()
mock.Freeze()
var (
sh1 *sessionHandle
sh2 *sessionHandle
ts1 *sppb.TransactionSelector
ts2 *sppb.TransactionSelector
wg = sync.WaitGroup{}
)
acquire := func(sh **sessionHandle, ts **sppb.TransactionSelector) {
defer wg.Done()
var e error
*sh, *ts, e = txn.acquire(ctx)
if e != nil {
t.Errorf("Concurrent acquire for multiuse, got %v, expect nil.", e)
}
}
wg.Add(2)
go acquire(&sh1, &ts1)
go acquire(&sh2, &ts2)
// TODO(deklerk): Get rid of this.
<-time.After(100 * time.Millisecond)
mock.Unfreeze()
wg.Wait()
if sh1.session.id != sh2.session.id {
t.Fatalf("Expected acquire to get same session handle, got %v and %v.", sh1, sh2)
}
if !testEqual(ts1, ts2) {
t.Fatalf("Expected acquire to get same transaction selector, got %v and %v.", ts1, ts2)
}
}
func TestApply_Single(t *testing.T) {
t.Parallel()
ctx := context.Background()
client, _, mock, cleanup := serverClientMock(t, SessionPoolConfig{})
defer cleanup()
ms := []*Mutation{
Insert("Accounts", []string{"AccountId", "Nickname", "Balance"}, []interface{}{int64(1), "Foo", int64(50)}),
Insert("Accounts", []string{"AccountId", "Nickname", "Balance"}, []interface{}{int64(2), "Bar", int64(1)}),
}
if _, e := client.Apply(ctx, ms, ApplyAtLeastOnce()); e != nil {
t.Fatalf("applyAtLeastOnce retry on abort, got %v, want nil.", e)
}
if _, err := shouldHaveReceived(mock, []interface{}{
&sppb.CreateSessionRequest{},
&sppb.CommitRequest{},
}); err != nil {
t.Fatal(err)
}
}
// Transaction retries on abort.
func TestApply_RetryOnAbort(t *testing.T) {
ctx := context.Background()
t.Parallel()
client, _, mock, cleanup := serverClientMock(t, SessionPoolConfig{})
defer cleanup()
// First commit will fail, and the retry will begin a new transaction.
errAbrt := spannerErrorf(codes.Aborted, "")
firstCommitCall := true
mock.CommitFn = func(c context.Context, r *sppb.CommitRequest, opts ...grpc.CallOption) (*sppb.CommitResponse, error) {
if firstCommitCall {
mock.MockCloudSpannerClient.ReceivedRequests <- r
firstCommitCall = false
return nil, errAbrt
}
return mock.MockCloudSpannerClient.Commit(c, r, opts...)
}
ms := []*Mutation{
Insert("Accounts", []string{"AccountId"}, []interface{}{int64(1)}),
}
if _, e := client.Apply(ctx, ms); e != nil {
t.Fatalf("ReadWriteTransaction retry on abort, got %v, want nil.", e)
}
if _, err := shouldHaveReceived(mock, []interface{}{
&sppb.CreateSessionRequest{},
&sppb.BeginTransactionRequest{},
&sppb.CommitRequest{}, // First commit fails.
&sppb.BeginTransactionRequest{},
&sppb.CommitRequest{}, // Second commit succeeds.
}); err != nil {
t.Fatal(err)
}
}
// Tests that NotFound errors cause failures, and aren't retried.
func TestTransaction_NotFound(t *testing.T) {
t.Parallel()
ctx := context.Background()
client, _, mock, cleanup := serverClientMock(t, SessionPoolConfig{})
defer cleanup()
wantErr := spannerErrorf(codes.NotFound, "Session not found")
mock.BeginTransactionFn = func(c context.Context, r *sppb.BeginTransactionRequest, opts ...grpc.CallOption) (*sppb.Transaction, error) {
mock.MockCloudSpannerClient.ReceivedRequests <- r
return nil, wantErr
}
mock.CommitFn = func(c context.Context, r *sppb.CommitRequest, opts ...grpc.CallOption) (*sppb.CommitResponse, error) {
mock.MockCloudSpannerClient.ReceivedRequests <- r
return nil, wantErr
}
txn := client.ReadOnlyTransaction()
defer txn.Close()
if _, _, got := txn.acquire(ctx); !testEqual(wantErr, got) {
t.Fatalf("Expect acquire to fail, got %v, want %v.", got, wantErr)
}
// The failure should recycle the session, we expect it to be used in following requests.
if got := txn.Query(ctx, NewStatement("SELECT 1")); !testEqual(wantErr, got.err) {
t.Fatalf("Expect Query to fail, got %v, want %v.", got.err, wantErr)
}
if got := txn.Read(ctx, "Users", KeySets(Key{"alice"}, Key{"bob"}), []string{"name", "email"}); !testEqual(wantErr, got.err) {
t.Fatalf("Expect Read to fail, got %v, want %v.", got.err, wantErr)
}
ms := []*Mutation{
Insert("Accounts", []string{"AccountId", "Nickname", "Balance"}, []interface{}{int64(1), "Foo", int64(50)}),
Insert("Accounts", []string{"AccountId", "Nickname", "Balance"}, []interface{}{int64(2), "Bar", int64(1)}),
}
if _, got := client.Apply(ctx, ms, ApplyAtLeastOnce()); !testEqual(wantErr, got) {
t.Fatalf("Expect Apply to fail, got %v, want %v.", got, wantErr)
}
}
// When an error is returned from the closure sent into ReadWriteTransaction, it
// kicks off a rollback.
func TestReadWriteTransaction_ErrorReturned(t *testing.T) {
t.Parallel()
ctx := context.Background()
client, _, mock, cleanup := serverClientMock(t, SessionPoolConfig{})
defer cleanup()
want := errors.New("an error")
_, got := client.ReadWriteTransaction(ctx, func(context.Context, *ReadWriteTransaction) error {
return want
})
if got != want {
t.Fatalf("got %+v, want %+v", got, want)
}
requests := drainRequests(mock)
if err := compareRequests([]interface{}{
&sppb.CreateSessionRequest{},
&sppb.BeginTransactionRequest{},
&sppb.RollbackRequest{}}, requests); err != nil {
// TODO: remove this once the session pool maintainer has been changed
// so that is doesn't delete sessions already during the first
// maintenance window.
// If we failed to get 3, it might have because - due to timing - we got
// a fourth request. If this request is DeleteSession, that's OK and
// expected.
if err := compareRequests([]interface{}{
&sppb.CreateSessionRequest{},
&sppb.BeginTransactionRequest{},
&sppb.RollbackRequest{},
&sppb.DeleteSessionRequest{}}, requests); err != nil {
t.Fatal(err)
}
}
}
func TestBatchDML_WithMultipleDML(t *testing.T) {
t.Parallel()
ctx := context.Background()
client, _, mock, cleanup := serverClientMock(t, SessionPoolConfig{})
defer cleanup()
_, err := client.ReadWriteTransaction(ctx, func(ctx context.Context, tx *ReadWriteTransaction) (err error) {
if _, err = tx.Update(ctx, Statement{SQL: "SELECT * FROM whatever"}); err != nil {
return err
}
if _, err = tx.BatchUpdate(ctx, []Statement{{SQL: "SELECT * FROM whatever"}, {SQL: "SELECT * FROM whatever"}}); err != nil {
return err
}
if _, err = tx.Update(ctx, Statement{SQL: "SELECT * FROM whatever"}); err != nil {
return err
}
_, err = tx.BatchUpdate(ctx, []Statement{{SQL: "SELECT * FROM whatever"}})
return err
})
if err != nil {
t.Fatal(err)
}
gotReqs, err := shouldHaveReceived(mock, []interface{}{
&sppb.CreateSessionRequest{},
&sppb.BeginTransactionRequest{},
&sppb.ExecuteSqlRequest{},
&sppb.ExecuteBatchDmlRequest{},
&sppb.ExecuteSqlRequest{},
&sppb.ExecuteBatchDmlRequest{},
&sppb.CommitRequest{},
})
if err != nil {
t.Fatal(err)
}
if got, want := gotReqs[2].(*sppb.ExecuteSqlRequest).Seqno, int64(1); got != want {
t.Errorf("got %d, want %d", got, want)
}
if got, want := gotReqs[3].(*sppb.ExecuteBatchDmlRequest).Seqno, int64(2); got != want {
t.Errorf("got %d, want %d", got, want)
}
if got, want := gotReqs[4].(*sppb.ExecuteSqlRequest).Seqno, int64(3); got != want {
t.Errorf("got %d, want %d", got, want)
}
if got, want := gotReqs[5].(*sppb.ExecuteBatchDmlRequest).Seqno, int64(4); got != want {
t.Errorf("got %d, want %d", got, want)
}
}
// shouldHaveReceived asserts that exactly expectedRequests were present in
// the server's ReceivedRequests channel. It only looks at type, not contents.
//
// Note: this in-place modifies serverClientMock by popping items off the
// ReceivedRequests channel.
func shouldHaveReceived(mock *testutil.FuncMock, want []interface{}) ([]interface{}, error) {
got := drainRequests(mock)
return got, compareRequests(want, got)
}
// Compares expected requests (want) with actual requests (got).
func compareRequests(want []interface{}, got []interface{}) error {
if len(got) != len(want) {
var gotMsg string
for _, r := range got {
gotMsg += fmt.Sprintf("%v: %+v]\n", reflect.TypeOf(r), r)
}
var wantMsg string
for _, r := range want {
wantMsg += fmt.Sprintf("%v: %+v]\n", reflect.TypeOf(r), r)
}
return fmt.Errorf("got %d requests, want %d requests:\ngot:\n%s\nwant:\n%s", len(got), len(want), gotMsg, wantMsg)
}
for i, want := range want {
if reflect.TypeOf(got[i]) != reflect.TypeOf(want) {
return fmt.Errorf("request %d: got %+v, want %+v", i, reflect.TypeOf(got[i]), reflect.TypeOf(want))
}
}
return nil
}
func drainRequests(mock *testutil.FuncMock) []interface{} {
var reqs []interface{}
loop:
for {
select {
case req := <-mock.ReceivedRequests:
reqs = append(reqs, req)
default:
break loop
}
}
return reqs
}
// serverClientMock sets up a client configured to a NewMockCloudSpannerClient
// that is wrapped with a function-injectable wrapper.
//
// Note: be sure to call cleanup!
func serverClientMock(t *testing.T, spc SessionPoolConfig) (_ *Client, _ *sessionPool, _ *testutil.FuncMock, cleanup func()) {
rawServerStub := testutil.NewMockCloudSpannerClient(t)
serverClientMock := testutil.FuncMock{MockCloudSpannerClient: rawServerStub}
spc.getRPCClient = func() (sppb.SpannerClient, error) {
return &serverClientMock, nil
}
db := "mockdb"
sp, err := newSessionPool(db, spc, nil)
if err != nil {
t.Fatalf("cannot create session pool: %v", err)
}
client := Client{
database: db,
idleSessions: sp,
}
cleanup = func() {
client.Close()
sp.hc.close()
sp.close()
}
return &client, sp, &serverClientMock, cleanup
}

1608
vendor/cloud.google.com/go/spanner/value.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,211 @@
// 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 (
"reflect"
"strconv"
"testing"
"cloud.google.com/go/civil"
proto3 "github.com/golang/protobuf/ptypes/struct"
sppb "google.golang.org/genproto/googleapis/spanner/v1"
)
func BenchmarkEncodeIntArray(b *testing.B) {
for _, s := range []struct {
name string
f func(a []int) (*proto3.Value, *sppb.Type, error)
}{
{"Orig", encodeIntArrayOrig},
{"Func", encodeIntArrayFunc},
{"Reflect", encodeIntArrayReflect},
} {
b.Run(s.name, func(b *testing.B) {
for _, size := range []int{1, 10, 100, 1000} {
a := make([]int, size)
b.Run(strconv.Itoa(size), func(b *testing.B) {
for i := 0; i < b.N; i++ {
s.f(a)
}
})
}
})
}
}
func encodeIntArrayOrig(a []int) (*proto3.Value, *sppb.Type, error) {
vs := make([]*proto3.Value, len(a))
var err error
for i := range a {
vs[i], _, err = encodeValue(a[i])
if err != nil {
return nil, nil, err
}
}
return listProto(vs...), listType(intType()), nil
}
func encodeIntArrayFunc(a []int) (*proto3.Value, *sppb.Type, error) {
v, err := encodeArray(len(a), func(i int) interface{} { return a[i] })
if err != nil {
return nil, nil, err
}
return v, listType(intType()), nil
}
func encodeIntArrayReflect(a []int) (*proto3.Value, *sppb.Type, error) {
v, err := encodeArrayReflect(a)
if err != nil {
return nil, nil, err
}
return v, listType(intType()), nil
}
func encodeArrayReflect(a interface{}) (*proto3.Value, error) {
va := reflect.ValueOf(a)
len := va.Len()
vs := make([]*proto3.Value, len)
var err error
for i := 0; i < len; i++ {
vs[i], _, err = encodeValue(va.Index(i).Interface())
if err != nil {
return nil, err
}
}
return listProto(vs...), nil
}
func BenchmarkDecodeGeneric(b *testing.B) {
v := stringProto("test")
t := stringType()
var g GenericColumnValue
b.ResetTimer()
for i := 0; i < b.N; i++ {
decodeValue(v, t, &g)
}
}
func BenchmarkDecodeArray(b *testing.B) {
for _, size := range []int{1, 10, 100, 1000} {
vals := make([]*proto3.Value, size)
for i := 0; i < size; i++ {
vals[i] = dateProto(d1)
}
lv := &proto3.ListValue{Values: vals}
b.Run(strconv.Itoa(size), func(b *testing.B) {
for _, s := range []struct {
name string
decode func(*proto3.ListValue)
}{
{"DateDirect", decodeArrayDateDirect},
{"DateFunc", decodeArrayDateFunc},
{"DateReflect", decodeArrayDateReflect},
{"StringDecodeStringArray", decodeStringArrayWrap},
{"StringDirect", decodeArrayStringDirect},
{"StringFunc", decodeArrayStringFunc},
{"StringReflect", decodeArrayStringReflect},
} {
b.Run(s.name, func(b *testing.B) {
for i := 0; i < b.N; i++ {
s.decode(lv)
}
})
}
})
}
}
func decodeArrayDateDirect(pb *proto3.ListValue) {
a := make([]civil.Date, len(pb.Values))
t := dateType()
for i, v := range pb.Values {
if err := decodeValue(v, t, &a[i]); err != nil {
panic(err)
}
}
}
func decodeArrayDateFunc(pb *proto3.ListValue) {
a := make([]civil.Date, len(pb.Values))
if err := decodeArrayFunc(pb, "DATE", dateType(), func(i int) interface{} { return &a[i] }); err != nil {
panic(err)
}
}
func decodeArrayDateReflect(pb *proto3.ListValue) {
var a []civil.Date
if err := decodeArrayReflect(pb, "DATE", dateType(), &a); err != nil {
panic(err)
}
}
func decodeStringArrayWrap(pb *proto3.ListValue) {
if _, err := decodeStringArray(pb); err != nil {
panic(err)
}
}
func decodeArrayStringDirect(pb *proto3.ListValue) {
a := make([]string, len(pb.Values))
t := stringType()
for i, v := range pb.Values {
if err := decodeValue(v, t, &a[i]); err != nil {
panic(err)
}
}
}
func decodeArrayStringFunc(pb *proto3.ListValue) {
a := make([]string, len(pb.Values))
if err := decodeArrayFunc(pb, "STRING", stringType(), func(i int) interface{} { return &a[i] }); err != nil {
panic(err)
}
}
func decodeArrayStringReflect(pb *proto3.ListValue) {
var a []string
if err := decodeArrayReflect(pb, "STRING", stringType(), &a); err != nil {
panic(err)
}
}
func decodeArrayFunc(pb *proto3.ListValue, name string, typ *sppb.Type, elptr func(int) interface{}) error {
if pb == nil {
return errNilListValue(name)
}
for i, v := range pb.Values {
if err := decodeValue(v, typ, elptr(i)); err != nil {
return errDecodeArrayElement(i, v, name, err)
}
}
return nil
}
func decodeArrayReflect(pb *proto3.ListValue, name string, typ *sppb.Type, aptr interface{}) error {
if pb == nil {
return errNilListValue(name)
}
av := reflect.ValueOf(aptr).Elem()
av.Set(reflect.MakeSlice(av.Type(), len(pb.Values), len(pb.Values)))
for i, v := range pb.Values {
if err := decodeValue(v, typ, av.Index(i).Addr().Interface()); err != nil {
av.Set(reflect.Zero(av.Type())) // reset slice to nil
return errDecodeArrayElement(i, v, name, err)
}
}
return nil
}

1395
vendor/cloud.google.com/go/spanner/value_test.go generated vendored Normal file

File diff suppressed because it is too large Load Diff