Update dependencies

Among others, this updates minio-go, so that the new "eu-west-3" zone
for AWS is supported.
This commit is contained in:
Alexander Neumann
2018-01-23 19:40:42 +01:00
parent b63de7c798
commit 2b39f9f4b2
3435 changed files with 1317989 additions and 315639 deletions
@@ -1,10 +1,10 @@
// Copyright 2017, Google Inc. All rights reserved.
// 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
// 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,
@@ -100,8 +100,8 @@ type DatabaseAdminClient struct {
// The call options for this service.
CallOptions *DatabaseAdminCallOptions
// The metadata to be sent with each request.
Metadata metadata.MD
// The x-goog-* metadata to be sent with each request.
xGoogMetadata metadata.MD
}
// NewDatabaseAdminClient creates a new database admin client.
@@ -154,34 +154,12 @@ func (c *DatabaseAdminClient) Close() error {
func (c *DatabaseAdminClient) setGoogleClientInfo(keyval ...string) {
kv := append([]string{"gl-go", version.Go()}, keyval...)
kv = append(kv, "gapic", version.Repo, "gax", gax.Version, "grpc", grpc.Version)
c.Metadata = metadata.Pairs("x-goog-api-client", gax.XGoogHeader(kv...))
}
// DatabaseAdminInstancePath returns the path for the instance resource.
func DatabaseAdminInstancePath(project, instance string) string {
return "" +
"projects/" +
project +
"/instances/" +
instance +
""
}
// DatabaseAdminDatabasePath returns the path for the database resource.
func DatabaseAdminDatabasePath(project, instance, database string) string {
return "" +
"projects/" +
project +
"/instances/" +
instance +
"/databases/" +
database +
""
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 {
ctx = insertMetadata(ctx, c.Metadata)
ctx = insertMetadata(ctx, c.xGoogMetadata)
opts = append(c.CallOptions.ListDatabases[0:len(c.CallOptions.ListDatabases):len(c.CallOptions.ListDatabases)], opts...)
it := &DatabaseIterator{}
it.InternalFetch = func(pageSize int, pageToken string) ([]*databasepb.Database, string, error) {
@@ -223,7 +201,7 @@ func (c *DatabaseAdminClient) ListDatabases(ctx context.Context, req *databasepb
// [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) {
ctx = insertMetadata(ctx, c.Metadata)
ctx = insertMetadata(ctx, c.xGoogMetadata)
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 {
@@ -241,7 +219,7 @@ func (c *DatabaseAdminClient) CreateDatabase(ctx context.Context, req *databasep
// 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) {
ctx = insertMetadata(ctx, c.Metadata)
ctx = insertMetadata(ctx, c.xGoogMetadata)
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 {
@@ -263,7 +241,7 @@ func (c *DatabaseAdminClient) GetDatabase(ctx context.Context, req *databasepb.G
// [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) {
ctx = insertMetadata(ctx, c.Metadata)
ctx = insertMetadata(ctx, c.xGoogMetadata)
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 {
@@ -281,7 +259,7 @@ func (c *DatabaseAdminClient) UpdateDatabaseDdl(ctx context.Context, req *databa
// DropDatabase drops (aka deletes) a Cloud Spanner database.
func (c *DatabaseAdminClient) DropDatabase(ctx context.Context, req *databasepb.DropDatabaseRequest, opts ...gax.CallOption) error {
ctx = insertMetadata(ctx, c.Metadata)
ctx = insertMetadata(ctx, c.xGoogMetadata)
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
@@ -295,7 +273,7 @@ func (c *DatabaseAdminClient) DropDatabase(ctx context.Context, req *databasepb.
// 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) {
ctx = insertMetadata(ctx, c.Metadata)
ctx = insertMetadata(ctx, c.xGoogMetadata)
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 {
@@ -315,7 +293,7 @@ func (c *DatabaseAdminClient) GetDatabaseDdl(ctx context.Context, req *databasep
// 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) {
ctx = insertMetadata(ctx, c.Metadata)
ctx = insertMetadata(ctx, c.xGoogMetadata)
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 {
@@ -335,7 +313,7 @@ func (c *DatabaseAdminClient) SetIamPolicy(ctx context.Context, req *iampb.SetIa
// 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) {
ctx = insertMetadata(ctx, c.Metadata)
ctx = insertMetadata(ctx, c.xGoogMetadata)
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 {
@@ -356,7 +334,7 @@ func (c *DatabaseAdminClient) GetIamPolicy(ctx context.Context, req *iampb.GetIa
// 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) {
ctx = insertMetadata(ctx, c.Metadata)
ctx = insertMetadata(ctx, c.xGoogMetadata)
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 {
@@ -1,10 +1,10 @@
// Copyright 2017, Google Inc. All rights reserved.
// 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
// 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,
@@ -42,7 +42,7 @@ func ExampleDatabaseAdminClient_ListDatabases() {
}
req := &databasepb.ListDatabasesRequest{
// TODO: Fill request struct fields.
// TODO: Fill request struct fields.
}
it := c.ListDatabases(ctx, req)
for {
@@ -66,7 +66,7 @@ func ExampleDatabaseAdminClient_CreateDatabase() {
}
req := &databasepb.CreateDatabaseRequest{
// TODO: Fill request struct fields.
// TODO: Fill request struct fields.
}
op, err := c.CreateDatabase(ctx, req)
if err != nil {
@@ -89,7 +89,7 @@ func ExampleDatabaseAdminClient_GetDatabase() {
}
req := &databasepb.GetDatabaseRequest{
// TODO: Fill request struct fields.
// TODO: Fill request struct fields.
}
resp, err := c.GetDatabase(ctx, req)
if err != nil {
@@ -107,7 +107,7 @@ func ExampleDatabaseAdminClient_UpdateDatabaseDdl() {
}
req := &databasepb.UpdateDatabaseDdlRequest{
// TODO: Fill request struct fields.
// TODO: Fill request struct fields.
}
op, err := c.UpdateDatabaseDdl(ctx, req)
if err != nil {
@@ -126,7 +126,7 @@ func ExampleDatabaseAdminClient_DropDatabase() {
}
req := &databasepb.DropDatabaseRequest{
// TODO: Fill request struct fields.
// TODO: Fill request struct fields.
}
err = c.DropDatabase(ctx, req)
if err != nil {
@@ -142,7 +142,7 @@ func ExampleDatabaseAdminClient_GetDatabaseDdl() {
}
req := &databasepb.GetDatabaseDdlRequest{
// TODO: Fill request struct fields.
// TODO: Fill request struct fields.
}
resp, err := c.GetDatabaseDdl(ctx, req)
if err != nil {
@@ -160,7 +160,7 @@ func ExampleDatabaseAdminClient_SetIamPolicy() {
}
req := &iampb.SetIamPolicyRequest{
// TODO: Fill request struct fields.
// TODO: Fill request struct fields.
}
resp, err := c.SetIamPolicy(ctx, req)
if err != nil {
@@ -178,7 +178,7 @@ func ExampleDatabaseAdminClient_GetIamPolicy() {
}
req := &iampb.GetIamPolicyRequest{
// TODO: Fill request struct fields.
// TODO: Fill request struct fields.
}
resp, err := c.GetIamPolicy(ctx, req)
if err != nil {
@@ -196,7 +196,7 @@ func ExampleDatabaseAdminClient_TestIamPermissions() {
}
req := &iampb.TestIamPermissionsRequest{
// TODO: Fill request struct fields.
// TODO: Fill request struct fields.
}
resp, err := c.TestIamPermissions(ctx, req)
if err != nil {
+3 -3
View File
@@ -1,10 +1,10 @@
// Copyright 2017, Google Inc. All rights reserved.
// 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
// 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,
@@ -17,7 +17,7 @@
// 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 be subject to changes.
// NOTE: This package is in alpha. It is not stable, and is likely to change.
//
package database // import "cloud.google.com/go/spanner/admin/database/apiv1"
+20 -20
View File
@@ -1,10 +1,10 @@
// Copyright 2017, Google Inc. All rights reserved.
// 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
// 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,
@@ -214,7 +214,7 @@ func TestDatabaseAdminListDatabases(t *testing.T) {
mockDatabaseAdmin.resps = append(mockDatabaseAdmin.resps[:0], expectedResponse)
var formattedParent string = DatabaseAdminInstancePath("[PROJECT]", "[INSTANCE]")
var formattedParent string = fmt.Sprintf("projects/%s/instances/%s", "[PROJECT]", "[INSTANCE]")
var request = &databasepb.ListDatabasesRequest{
Parent: formattedParent,
}
@@ -253,7 +253,7 @@ func TestDatabaseAdminListDatabasesError(t *testing.T) {
errCode := codes.PermissionDenied
mockDatabaseAdmin.err = gstatus.Error(errCode, "test error")
var formattedParent string = DatabaseAdminInstancePath("[PROJECT]", "[INSTANCE]")
var formattedParent string = fmt.Sprintf("projects/%s/instances/%s", "[PROJECT]", "[INSTANCE]")
var request = &databasepb.ListDatabasesRequest{
Parent: formattedParent,
}
@@ -291,7 +291,7 @@ func TestDatabaseAdminCreateDatabase(t *testing.T) {
Result: &longrunningpb.Operation_Response{Response: any},
})
var formattedParent string = DatabaseAdminInstancePath("[PROJECT]", "[INSTANCE]")
var formattedParent string = fmt.Sprintf("projects/%s/instances/%s", "[PROJECT]", "[INSTANCE]")
var createStatement string = "createStatement552974828"
var request = &databasepb.CreateDatabaseRequest{
Parent: formattedParent,
@@ -336,7 +336,7 @@ func TestDatabaseAdminCreateDatabaseError(t *testing.T) {
},
})
var formattedParent string = DatabaseAdminInstancePath("[PROJECT]", "[INSTANCE]")
var formattedParent string = fmt.Sprintf("projects/%s/instances/%s", "[PROJECT]", "[INSTANCE]")
var createStatement string = "createStatement552974828"
var request = &databasepb.CreateDatabaseRequest{
Parent: formattedParent,
@@ -372,7 +372,7 @@ func TestDatabaseAdminGetDatabase(t *testing.T) {
mockDatabaseAdmin.resps = append(mockDatabaseAdmin.resps[:0], expectedResponse)
var formattedName string = DatabaseAdminDatabasePath("[PROJECT]", "[INSTANCE]", "[DATABASE]")
var formattedName string = fmt.Sprintf("projects/%s/instances/%s/databases/%s", "[PROJECT]", "[INSTANCE]", "[DATABASE]")
var request = &databasepb.GetDatabaseRequest{
Name: formattedName,
}
@@ -401,7 +401,7 @@ func TestDatabaseAdminGetDatabaseError(t *testing.T) {
errCode := codes.PermissionDenied
mockDatabaseAdmin.err = gstatus.Error(errCode, "test error")
var formattedName string = DatabaseAdminDatabasePath("[PROJECT]", "[INSTANCE]", "[DATABASE]")
var formattedName string = fmt.Sprintf("projects/%s/instances/%s/databases/%s", "[PROJECT]", "[INSTANCE]", "[DATABASE]")
var request = &databasepb.GetDatabaseRequest{
Name: formattedName,
}
@@ -436,7 +436,7 @@ func TestDatabaseAdminUpdateDatabaseDdl(t *testing.T) {
Result: &longrunningpb.Operation_Response{Response: any},
})
var formattedDatabase string = DatabaseAdminDatabasePath("[PROJECT]", "[INSTANCE]", "[DATABASE]")
var formattedDatabase string = fmt.Sprintf("projects/%s/instances/%s/databases/%s", "[PROJECT]", "[INSTANCE]", "[DATABASE]")
var statements []string = nil
var request = &databasepb.UpdateDatabaseDdlRequest{
Database: formattedDatabase,
@@ -478,7 +478,7 @@ func TestDatabaseAdminUpdateDatabaseDdlError(t *testing.T) {
},
})
var formattedDatabase string = DatabaseAdminDatabasePath("[PROJECT]", "[INSTANCE]", "[DATABASE]")
var formattedDatabase string = fmt.Sprintf("projects/%s/instances/%s/databases/%s", "[PROJECT]", "[INSTANCE]", "[DATABASE]")
var statements []string = nil
var request = &databasepb.UpdateDatabaseDdlRequest{
Database: formattedDatabase,
@@ -510,7 +510,7 @@ func TestDatabaseAdminDropDatabase(t *testing.T) {
mockDatabaseAdmin.resps = append(mockDatabaseAdmin.resps[:0], expectedResponse)
var formattedDatabase string = DatabaseAdminDatabasePath("[PROJECT]", "[INSTANCE]", "[DATABASE]")
var formattedDatabase string = fmt.Sprintf("projects/%s/instances/%s/databases/%s", "[PROJECT]", "[INSTANCE]", "[DATABASE]")
var request = &databasepb.DropDatabaseRequest{
Database: formattedDatabase,
}
@@ -536,7 +536,7 @@ func TestDatabaseAdminDropDatabaseError(t *testing.T) {
errCode := codes.PermissionDenied
mockDatabaseAdmin.err = gstatus.Error(errCode, "test error")
var formattedDatabase string = DatabaseAdminDatabasePath("[PROJECT]", "[INSTANCE]", "[DATABASE]")
var formattedDatabase string = fmt.Sprintf("projects/%s/instances/%s/databases/%s", "[PROJECT]", "[INSTANCE]", "[DATABASE]")
var request = &databasepb.DropDatabaseRequest{
Database: formattedDatabase,
}
@@ -562,7 +562,7 @@ func TestDatabaseAdminGetDatabaseDdl(t *testing.T) {
mockDatabaseAdmin.resps = append(mockDatabaseAdmin.resps[:0], expectedResponse)
var formattedDatabase string = DatabaseAdminDatabasePath("[PROJECT]", "[INSTANCE]", "[DATABASE]")
var formattedDatabase string = fmt.Sprintf("projects/%s/instances/%s/databases/%s", "[PROJECT]", "[INSTANCE]", "[DATABASE]")
var request = &databasepb.GetDatabaseDdlRequest{
Database: formattedDatabase,
}
@@ -591,7 +591,7 @@ func TestDatabaseAdminGetDatabaseDdlError(t *testing.T) {
errCode := codes.PermissionDenied
mockDatabaseAdmin.err = gstatus.Error(errCode, "test error")
var formattedDatabase string = DatabaseAdminDatabasePath("[PROJECT]", "[INSTANCE]", "[DATABASE]")
var formattedDatabase string = fmt.Sprintf("projects/%s/instances/%s/databases/%s", "[PROJECT]", "[INSTANCE]", "[DATABASE]")
var request = &databasepb.GetDatabaseDdlRequest{
Database: formattedDatabase,
}
@@ -623,7 +623,7 @@ func TestDatabaseAdminSetIamPolicy(t *testing.T) {
mockDatabaseAdmin.resps = append(mockDatabaseAdmin.resps[:0], expectedResponse)
var formattedResource string = DatabaseAdminDatabasePath("[PROJECT]", "[INSTANCE]", "[DATABASE]")
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,
@@ -654,7 +654,7 @@ func TestDatabaseAdminSetIamPolicyError(t *testing.T) {
errCode := codes.PermissionDenied
mockDatabaseAdmin.err = gstatus.Error(errCode, "test error")
var formattedResource string = DatabaseAdminDatabasePath("[PROJECT]", "[INSTANCE]", "[DATABASE]")
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,
@@ -688,7 +688,7 @@ func TestDatabaseAdminGetIamPolicy(t *testing.T) {
mockDatabaseAdmin.resps = append(mockDatabaseAdmin.resps[:0], expectedResponse)
var formattedResource string = DatabaseAdminDatabasePath("[PROJECT]", "[INSTANCE]", "[DATABASE]")
var formattedResource string = fmt.Sprintf("projects/%s/instances/%s/databases/%s", "[PROJECT]", "[INSTANCE]", "[DATABASE]")
var request = &iampb.GetIamPolicyRequest{
Resource: formattedResource,
}
@@ -717,7 +717,7 @@ func TestDatabaseAdminGetIamPolicyError(t *testing.T) {
errCode := codes.PermissionDenied
mockDatabaseAdmin.err = gstatus.Error(errCode, "test error")
var formattedResource string = DatabaseAdminDatabasePath("[PROJECT]", "[INSTANCE]", "[DATABASE]")
var formattedResource string = fmt.Sprintf("projects/%s/instances/%s/databases/%s", "[PROJECT]", "[INSTANCE]", "[DATABASE]")
var request = &iampb.GetIamPolicyRequest{
Resource: formattedResource,
}
@@ -744,7 +744,7 @@ func TestDatabaseAdminTestIamPermissions(t *testing.T) {
mockDatabaseAdmin.resps = append(mockDatabaseAdmin.resps[:0], expectedResponse)
var formattedResource string = DatabaseAdminDatabasePath("[PROJECT]", "[INSTANCE]", "[DATABASE]")
var formattedResource string = fmt.Sprintf("projects/%s/instances/%s/databases/%s", "[PROJECT]", "[INSTANCE]", "[DATABASE]")
var permissions []string = nil
var request = &iampb.TestIamPermissionsRequest{
Resource: formattedResource,
@@ -775,7 +775,7 @@ func TestDatabaseAdminTestIamPermissionsError(t *testing.T) {
errCode := codes.PermissionDenied
mockDatabaseAdmin.err = gstatus.Error(errCode, "test error")
var formattedResource string = DatabaseAdminDatabasePath("[PROJECT]", "[INSTANCE]", "[DATABASE]")
var formattedResource string = fmt.Sprintf("projects/%s/instances/%s/databases/%s", "[PROJECT]", "[INSTANCE]", "[DATABASE]")
var permissions []string = nil
var request = &iampb.TestIamPermissionsRequest{
Resource: formattedResource,
+45
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 +
""
}
+3 -3
View File
@@ -1,10 +1,10 @@
// Copyright 2017, Google Inc. All rights reserved.
// 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
// 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,
@@ -17,7 +17,7 @@
// 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 be subject to changes.
// NOTE: This package is in alpha. It is not stable, and is likely to change.
//
package instance // import "cloud.google.com/go/spanner/admin/instance/apiv1"
@@ -1,10 +1,10 @@
// Copyright 2017, Google Inc. All rights reserved.
// 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
// 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,
@@ -102,8 +102,8 @@ type InstanceAdminClient struct {
// The call options for this service.
CallOptions *InstanceAdminCallOptions
// The metadata to be sent with each request.
Metadata metadata.MD
// The x-goog-* metadata to be sent with each request.
xGoogMetadata metadata.MD
}
// NewInstanceAdminClient creates a new instance admin client.
@@ -172,40 +172,12 @@ func (c *InstanceAdminClient) Close() error {
func (c *InstanceAdminClient) setGoogleClientInfo(keyval ...string) {
kv := append([]string{"gl-go", version.Go()}, keyval...)
kv = append(kv, "gapic", version.Repo, "gax", gax.Version, "grpc", grpc.Version)
c.Metadata = metadata.Pairs("x-goog-api-client", gax.XGoogHeader(kv...))
}
// InstanceAdminProjectPath returns the path for the project resource.
func InstanceAdminProjectPath(project string) string {
return "" +
"projects/" +
project +
""
}
// InstanceAdminInstanceConfigPath returns the path for the instance config resource.
func InstanceAdminInstanceConfigPath(project, instanceConfig string) string {
return "" +
"projects/" +
project +
"/instanceConfigs/" +
instanceConfig +
""
}
// InstanceAdminInstancePath returns the path for the instance resource.
func InstanceAdminInstancePath(project, instance string) string {
return "" +
"projects/" +
project +
"/instances/" +
instance +
""
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 {
ctx = insertMetadata(ctx, c.Metadata)
ctx = insertMetadata(ctx, c.xGoogMetadata)
opts = append(c.CallOptions.ListInstanceConfigs[0:len(c.CallOptions.ListInstanceConfigs):len(c.CallOptions.ListInstanceConfigs)], opts...)
it := &InstanceConfigIterator{}
it.InternalFetch = func(pageSize int, pageToken string) ([]*instancepb.InstanceConfig, string, error) {
@@ -240,7 +212,7 @@ func (c *InstanceAdminClient) ListInstanceConfigs(ctx context.Context, req *inst
// GetInstanceConfig gets information about a particular instance configuration.
func (c *InstanceAdminClient) GetInstanceConfig(ctx context.Context, req *instancepb.GetInstanceConfigRequest, opts ...gax.CallOption) (*instancepb.InstanceConfig, error) {
ctx = insertMetadata(ctx, c.Metadata)
ctx = insertMetadata(ctx, c.xGoogMetadata)
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 {
@@ -256,7 +228,7 @@ func (c *InstanceAdminClient) GetInstanceConfig(ctx context.Context, req *instan
// ListInstances lists all instances in the given project.
func (c *InstanceAdminClient) ListInstances(ctx context.Context, req *instancepb.ListInstancesRequest, opts ...gax.CallOption) *InstanceIterator {
ctx = insertMetadata(ctx, c.Metadata)
ctx = insertMetadata(ctx, c.xGoogMetadata)
opts = append(c.CallOptions.ListInstances[0:len(c.CallOptions.ListInstances):len(c.CallOptions.ListInstances)], opts...)
it := &InstanceIterator{}
it.InternalFetch = func(pageSize int, pageToken string) ([]*instancepb.Instance, string, error) {
@@ -291,7 +263,7 @@ func (c *InstanceAdminClient) ListInstances(ctx context.Context, req *instancepb
// GetInstance gets information about a particular instance.
func (c *InstanceAdminClient) GetInstance(ctx context.Context, req *instancepb.GetInstanceRequest, opts ...gax.CallOption) (*instancepb.Instance, error) {
ctx = insertMetadata(ctx, c.Metadata)
ctx = insertMetadata(ctx, c.xGoogMetadata)
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 {
@@ -345,7 +317,7 @@ func (c *InstanceAdminClient) GetInstance(ctx context.Context, req *instancepb.G
// 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) {
ctx = insertMetadata(ctx, c.Metadata)
ctx = insertMetadata(ctx, c.xGoogMetadata)
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 {
@@ -406,7 +378,7 @@ func (c *InstanceAdminClient) CreateInstance(ctx context.Context, req *instancep
// 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) {
ctx = insertMetadata(ctx, c.Metadata)
ctx = insertMetadata(ctx, c.xGoogMetadata)
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 {
@@ -434,7 +406,7 @@ func (c *InstanceAdminClient) UpdateInstance(ctx context.Context, req *instancep
// 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 {
ctx = insertMetadata(ctx, c.Metadata)
ctx = insertMetadata(ctx, c.xGoogMetadata)
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
@@ -450,7 +422,7 @@ func (c *InstanceAdminClient) DeleteInstance(ctx context.Context, req *instancep
// 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) {
ctx = insertMetadata(ctx, c.Metadata)
ctx = insertMetadata(ctx, c.xGoogMetadata)
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 {
@@ -470,7 +442,7 @@ func (c *InstanceAdminClient) SetIamPolicy(ctx context.Context, req *iampb.SetIa
// 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) {
ctx = insertMetadata(ctx, c.Metadata)
ctx = insertMetadata(ctx, c.xGoogMetadata)
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 {
@@ -491,7 +463,7 @@ func (c *InstanceAdminClient) GetIamPolicy(ctx context.Context, req *iampb.GetIa
// 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) {
ctx = insertMetadata(ctx, c.Metadata)
ctx = insertMetadata(ctx, c.xGoogMetadata)
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 {
@@ -1,10 +1,10 @@
// Copyright 2017, Google Inc. All rights reserved.
// 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
// 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,
@@ -42,7 +42,7 @@ func ExampleInstanceAdminClient_ListInstanceConfigs() {
}
req := &instancepb.ListInstanceConfigsRequest{
// TODO: Fill request struct fields.
// TODO: Fill request struct fields.
}
it := c.ListInstanceConfigs(ctx, req)
for {
@@ -66,7 +66,7 @@ func ExampleInstanceAdminClient_GetInstanceConfig() {
}
req := &instancepb.GetInstanceConfigRequest{
// TODO: Fill request struct fields.
// TODO: Fill request struct fields.
}
resp, err := c.GetInstanceConfig(ctx, req)
if err != nil {
@@ -84,7 +84,7 @@ func ExampleInstanceAdminClient_ListInstances() {
}
req := &instancepb.ListInstancesRequest{
// TODO: Fill request struct fields.
// TODO: Fill request struct fields.
}
it := c.ListInstances(ctx, req)
for {
@@ -108,7 +108,7 @@ func ExampleInstanceAdminClient_GetInstance() {
}
req := &instancepb.GetInstanceRequest{
// TODO: Fill request struct fields.
// TODO: Fill request struct fields.
}
resp, err := c.GetInstance(ctx, req)
if err != nil {
@@ -126,7 +126,7 @@ func ExampleInstanceAdminClient_CreateInstance() {
}
req := &instancepb.CreateInstanceRequest{
// TODO: Fill request struct fields.
// TODO: Fill request struct fields.
}
op, err := c.CreateInstance(ctx, req)
if err != nil {
@@ -149,7 +149,7 @@ func ExampleInstanceAdminClient_UpdateInstance() {
}
req := &instancepb.UpdateInstanceRequest{
// TODO: Fill request struct fields.
// TODO: Fill request struct fields.
}
op, err := c.UpdateInstance(ctx, req)
if err != nil {
@@ -172,7 +172,7 @@ func ExampleInstanceAdminClient_DeleteInstance() {
}
req := &instancepb.DeleteInstanceRequest{
// TODO: Fill request struct fields.
// TODO: Fill request struct fields.
}
err = c.DeleteInstance(ctx, req)
if err != nil {
@@ -188,7 +188,7 @@ func ExampleInstanceAdminClient_SetIamPolicy() {
}
req := &iampb.SetIamPolicyRequest{
// TODO: Fill request struct fields.
// TODO: Fill request struct fields.
}
resp, err := c.SetIamPolicy(ctx, req)
if err != nil {
@@ -206,7 +206,7 @@ func ExampleInstanceAdminClient_GetIamPolicy() {
}
req := &iampb.GetIamPolicyRequest{
// TODO: Fill request struct fields.
// TODO: Fill request struct fields.
}
resp, err := c.GetIamPolicy(ctx, req)
if err != nil {
@@ -224,7 +224,7 @@ func ExampleInstanceAdminClient_TestIamPermissions() {
}
req := &iampb.TestIamPermissionsRequest{
// TODO: Fill request struct fields.
// TODO: Fill request struct fields.
}
resp, err := c.TestIamPermissions(ctx, req)
if err != nil {
+20 -20
View File
@@ -1,10 +1,10 @@
// Copyright 2017, Google Inc. All rights reserved.
// 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
// 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,
@@ -227,7 +227,7 @@ func TestInstanceAdminListInstanceConfigs(t *testing.T) {
mockInstanceAdmin.resps = append(mockInstanceAdmin.resps[:0], expectedResponse)
var formattedParent string = InstanceAdminProjectPath("[PROJECT]")
var formattedParent string = fmt.Sprintf("projects/%s", "[PROJECT]")
var request = &instancepb.ListInstanceConfigsRequest{
Parent: formattedParent,
}
@@ -266,7 +266,7 @@ func TestInstanceAdminListInstanceConfigsError(t *testing.T) {
errCode := codes.PermissionDenied
mockInstanceAdmin.err = gstatus.Error(errCode, "test error")
var formattedParent string = InstanceAdminProjectPath("[PROJECT]")
var formattedParent string = fmt.Sprintf("projects/%s", "[PROJECT]")
var request = &instancepb.ListInstanceConfigsRequest{
Parent: formattedParent,
}
@@ -298,7 +298,7 @@ func TestInstanceAdminGetInstanceConfig(t *testing.T) {
mockInstanceAdmin.resps = append(mockInstanceAdmin.resps[:0], expectedResponse)
var formattedName string = InstanceAdminInstanceConfigPath("[PROJECT]", "[INSTANCE_CONFIG]")
var formattedName string = fmt.Sprintf("projects/%s/instanceConfigs/%s", "[PROJECT]", "[INSTANCE_CONFIG]")
var request = &instancepb.GetInstanceConfigRequest{
Name: formattedName,
}
@@ -327,7 +327,7 @@ func TestInstanceAdminGetInstanceConfigError(t *testing.T) {
errCode := codes.PermissionDenied
mockInstanceAdmin.err = gstatus.Error(errCode, "test error")
var formattedName string = InstanceAdminInstanceConfigPath("[PROJECT]", "[INSTANCE_CONFIG]")
var formattedName string = fmt.Sprintf("projects/%s/instanceConfigs/%s", "[PROJECT]", "[INSTANCE_CONFIG]")
var request = &instancepb.GetInstanceConfigRequest{
Name: formattedName,
}
@@ -360,7 +360,7 @@ func TestInstanceAdminListInstances(t *testing.T) {
mockInstanceAdmin.resps = append(mockInstanceAdmin.resps[:0], expectedResponse)
var formattedParent string = InstanceAdminProjectPath("[PROJECT]")
var formattedParent string = fmt.Sprintf("projects/%s", "[PROJECT]")
var request = &instancepb.ListInstancesRequest{
Parent: formattedParent,
}
@@ -399,7 +399,7 @@ func TestInstanceAdminListInstancesError(t *testing.T) {
errCode := codes.PermissionDenied
mockInstanceAdmin.err = gstatus.Error(errCode, "test error")
var formattedParent string = InstanceAdminProjectPath("[PROJECT]")
var formattedParent string = fmt.Sprintf("projects/%s", "[PROJECT]")
var request = &instancepb.ListInstancesRequest{
Parent: formattedParent,
}
@@ -435,7 +435,7 @@ func TestInstanceAdminGetInstance(t *testing.T) {
mockInstanceAdmin.resps = append(mockInstanceAdmin.resps[:0], expectedResponse)
var formattedName string = InstanceAdminInstancePath("[PROJECT]", "[INSTANCE]")
var formattedName string = fmt.Sprintf("projects/%s/instances/%s", "[PROJECT]", "[INSTANCE]")
var request = &instancepb.GetInstanceRequest{
Name: formattedName,
}
@@ -464,7 +464,7 @@ func TestInstanceAdminGetInstanceError(t *testing.T) {
errCode := codes.PermissionDenied
mockInstanceAdmin.err = gstatus.Error(errCode, "test error")
var formattedName string = InstanceAdminInstancePath("[PROJECT]", "[INSTANCE]")
var formattedName string = fmt.Sprintf("projects/%s/instances/%s", "[PROJECT]", "[INSTANCE]")
var request = &instancepb.GetInstanceRequest{
Name: formattedName,
}
@@ -508,7 +508,7 @@ func TestInstanceAdminCreateInstance(t *testing.T) {
Result: &longrunningpb.Operation_Response{Response: any},
})
var formattedParent string = InstanceAdminProjectPath("[PROJECT]")
var formattedParent string = fmt.Sprintf("projects/%s", "[PROJECT]")
var instanceId string = "instanceId-2101995259"
var instance *instancepb.Instance = &instancepb.Instance{}
var request = &instancepb.CreateInstanceRequest{
@@ -555,7 +555,7 @@ func TestInstanceAdminCreateInstanceError(t *testing.T) {
},
})
var formattedParent string = InstanceAdminProjectPath("[PROJECT]")
var formattedParent string = fmt.Sprintf("projects/%s", "[PROJECT]")
var instanceId string = "instanceId-2101995259"
var instance *instancepb.Instance = &instancepb.Instance{}
var request = &instancepb.CreateInstanceRequest{
@@ -685,7 +685,7 @@ func TestInstanceAdminDeleteInstance(t *testing.T) {
mockInstanceAdmin.resps = append(mockInstanceAdmin.resps[:0], expectedResponse)
var formattedName string = InstanceAdminInstancePath("[PROJECT]", "[INSTANCE]")
var formattedName string = fmt.Sprintf("projects/%s/instances/%s", "[PROJECT]", "[INSTANCE]")
var request = &instancepb.DeleteInstanceRequest{
Name: formattedName,
}
@@ -711,7 +711,7 @@ func TestInstanceAdminDeleteInstanceError(t *testing.T) {
errCode := codes.PermissionDenied
mockInstanceAdmin.err = gstatus.Error(errCode, "test error")
var formattedName string = InstanceAdminInstancePath("[PROJECT]", "[INSTANCE]")
var formattedName string = fmt.Sprintf("projects/%s/instances/%s", "[PROJECT]", "[INSTANCE]")
var request = &instancepb.DeleteInstanceRequest{
Name: formattedName,
}
@@ -742,7 +742,7 @@ func TestInstanceAdminSetIamPolicy(t *testing.T) {
mockInstanceAdmin.resps = append(mockInstanceAdmin.resps[:0], expectedResponse)
var formattedResource string = InstanceAdminInstancePath("[PROJECT]", "[INSTANCE]")
var formattedResource string = fmt.Sprintf("projects/%s/instances/%s", "[PROJECT]", "[INSTANCE]")
var policy *iampb.Policy = &iampb.Policy{}
var request = &iampb.SetIamPolicyRequest{
Resource: formattedResource,
@@ -773,7 +773,7 @@ func TestInstanceAdminSetIamPolicyError(t *testing.T) {
errCode := codes.PermissionDenied
mockInstanceAdmin.err = gstatus.Error(errCode, "test error")
var formattedResource string = InstanceAdminInstancePath("[PROJECT]", "[INSTANCE]")
var formattedResource string = fmt.Sprintf("projects/%s/instances/%s", "[PROJECT]", "[INSTANCE]")
var policy *iampb.Policy = &iampb.Policy{}
var request = &iampb.SetIamPolicyRequest{
Resource: formattedResource,
@@ -807,7 +807,7 @@ func TestInstanceAdminGetIamPolicy(t *testing.T) {
mockInstanceAdmin.resps = append(mockInstanceAdmin.resps[:0], expectedResponse)
var formattedResource string = InstanceAdminInstancePath("[PROJECT]", "[INSTANCE]")
var formattedResource string = fmt.Sprintf("projects/%s/instances/%s", "[PROJECT]", "[INSTANCE]")
var request = &iampb.GetIamPolicyRequest{
Resource: formattedResource,
}
@@ -836,7 +836,7 @@ func TestInstanceAdminGetIamPolicyError(t *testing.T) {
errCode := codes.PermissionDenied
mockInstanceAdmin.err = gstatus.Error(errCode, "test error")
var formattedResource string = InstanceAdminInstancePath("[PROJECT]", "[INSTANCE]")
var formattedResource string = fmt.Sprintf("projects/%s/instances/%s", "[PROJECT]", "[INSTANCE]")
var request = &iampb.GetIamPolicyRequest{
Resource: formattedResource,
}
@@ -863,7 +863,7 @@ func TestInstanceAdminTestIamPermissions(t *testing.T) {
mockInstanceAdmin.resps = append(mockInstanceAdmin.resps[:0], expectedResponse)
var formattedResource string = InstanceAdminInstancePath("[PROJECT]", "[INSTANCE]")
var formattedResource string = fmt.Sprintf("projects/%s/instances/%s", "[PROJECT]", "[INSTANCE]")
var permissions []string = nil
var request = &iampb.TestIamPermissionsRequest{
Resource: formattedResource,
@@ -894,7 +894,7 @@ func TestInstanceAdminTestIamPermissionsError(t *testing.T) {
errCode := codes.PermissionDenied
mockInstanceAdmin.err = gstatus.Error(errCode, "test error")
var formattedResource string = InstanceAdminInstancePath("[PROJECT]", "[INSTANCE]")
var formattedResource string = fmt.Sprintf("projects/%s/instances/%s", "[PROJECT]", "[INSTANCE]")
var permissions []string = nil
var request = &iampb.TestIamPermissionsRequest{
Resource: formattedResource,
+55
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 +
""
}
+3 -3
View File
@@ -1,10 +1,10 @@
// Copyright 2017, Google Inc. All rights reserved.
// 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
// 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,
@@ -17,7 +17,7 @@
// Package spanner is an auto-generated package for the
// Cloud Spanner API.
//
// NOTE: This package is in alpha. It is not stable, and is likely to be subject to changes.
// NOTE: This package is in alpha. It is not stable, and is likely to change.
//
// Cloud Spanner is a managed, mission-critical, globally consistent and
// scalable relational database service.
+24 -24
View File
@@ -1,10 +1,10 @@
// Copyright 2017, Google Inc. All rights reserved.
// 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
// 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,
@@ -243,7 +243,7 @@ func TestSpannerCreateSession(t *testing.T) {
mockSpanner.resps = append(mockSpanner.resps[:0], expectedResponse)
var formattedDatabase string = DatabasePath("[PROJECT]", "[INSTANCE]", "[DATABASE]")
var formattedDatabase string = fmt.Sprintf("projects/%s/instances/%s/databases/%s", "[PROJECT]", "[INSTANCE]", "[DATABASE]")
var request = &spannerpb.CreateSessionRequest{
Database: formattedDatabase,
}
@@ -272,7 +272,7 @@ func TestSpannerCreateSessionError(t *testing.T) {
errCode := codes.PermissionDenied
mockSpanner.err = gstatus.Error(errCode, "test error")
var formattedDatabase string = DatabasePath("[PROJECT]", "[INSTANCE]", "[DATABASE]")
var formattedDatabase string = fmt.Sprintf("projects/%s/instances/%s/databases/%s", "[PROJECT]", "[INSTANCE]", "[DATABASE]")
var request = &spannerpb.CreateSessionRequest{
Database: formattedDatabase,
}
@@ -302,7 +302,7 @@ func TestSpannerGetSession(t *testing.T) {
mockSpanner.resps = append(mockSpanner.resps[:0], expectedResponse)
var formattedName string = SessionPath("[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]")
var formattedName string = fmt.Sprintf("projects/%s/instances/%s/databases/%s/sessions/%s", "[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]")
var request = &spannerpb.GetSessionRequest{
Name: formattedName,
}
@@ -331,7 +331,7 @@ func TestSpannerGetSessionError(t *testing.T) {
errCode := codes.PermissionDenied
mockSpanner.err = gstatus.Error(errCode, "test error")
var formattedName string = SessionPath("[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]")
var formattedName string = fmt.Sprintf("projects/%s/instances/%s/databases/%s/sessions/%s", "[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]")
var request = &spannerpb.GetSessionRequest{
Name: formattedName,
}
@@ -364,7 +364,7 @@ func TestSpannerListSessions(t *testing.T) {
mockSpanner.resps = append(mockSpanner.resps[:0], expectedResponse)
var formattedDatabase string = DatabasePath("[PROJECT]", "[INSTANCE]", "[DATABASE]")
var formattedDatabase string = fmt.Sprintf("projects/%s/instances/%s/databases/%s", "[PROJECT]", "[INSTANCE]", "[DATABASE]")
var request = &spannerpb.ListSessionsRequest{
Database: formattedDatabase,
}
@@ -403,7 +403,7 @@ func TestSpannerListSessionsError(t *testing.T) {
errCode := codes.PermissionDenied
mockSpanner.err = gstatus.Error(errCode, "test error")
var formattedDatabase string = DatabasePath("[PROJECT]", "[INSTANCE]", "[DATABASE]")
var formattedDatabase string = fmt.Sprintf("projects/%s/instances/%s/databases/%s", "[PROJECT]", "[INSTANCE]", "[DATABASE]")
var request = &spannerpb.ListSessionsRequest{
Database: formattedDatabase,
}
@@ -430,7 +430,7 @@ func TestSpannerDeleteSession(t *testing.T) {
mockSpanner.resps = append(mockSpanner.resps[:0], expectedResponse)
var formattedName string = SessionPath("[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]")
var formattedName string = fmt.Sprintf("projects/%s/instances/%s/databases/%s/sessions/%s", "[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]")
var request = &spannerpb.DeleteSessionRequest{
Name: formattedName,
}
@@ -456,7 +456,7 @@ func TestSpannerDeleteSessionError(t *testing.T) {
errCode := codes.PermissionDenied
mockSpanner.err = gstatus.Error(errCode, "test error")
var formattedName string = SessionPath("[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]")
var formattedName string = fmt.Sprintf("projects/%s/instances/%s/databases/%s/sessions/%s", "[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]")
var request = &spannerpb.DeleteSessionRequest{
Name: formattedName,
}
@@ -482,7 +482,7 @@ func TestSpannerExecuteSql(t *testing.T) {
mockSpanner.resps = append(mockSpanner.resps[:0], expectedResponse)
var formattedSession string = SessionPath("[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]")
var formattedSession string = fmt.Sprintf("projects/%s/instances/%s/databases/%s/sessions/%s", "[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]")
var sql string = "sql114126"
var request = &spannerpb.ExecuteSqlRequest{
Session: formattedSession,
@@ -513,7 +513,7 @@ func TestSpannerExecuteSqlError(t *testing.T) {
errCode := codes.PermissionDenied
mockSpanner.err = gstatus.Error(errCode, "test error")
var formattedSession string = SessionPath("[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]")
var formattedSession string = fmt.Sprintf("projects/%s/instances/%s/databases/%s/sessions/%s", "[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]")
var sql string = "sql114126"
var request = &spannerpb.ExecuteSqlRequest{
Session: formattedSession,
@@ -547,7 +547,7 @@ func TestSpannerExecuteStreamingSql(t *testing.T) {
mockSpanner.resps = append(mockSpanner.resps[:0], expectedResponse)
var formattedSession string = SessionPath("[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]")
var formattedSession string = fmt.Sprintf("projects/%s/instances/%s/databases/%s/sessions/%s", "[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]")
var sql string = "sql114126"
var request = &spannerpb.ExecuteSqlRequest{
Session: formattedSession,
@@ -582,7 +582,7 @@ func TestSpannerExecuteStreamingSqlError(t *testing.T) {
errCode := codes.PermissionDenied
mockSpanner.err = gstatus.Error(errCode, "test error")
var formattedSession string = SessionPath("[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]")
var formattedSession string = fmt.Sprintf("projects/%s/instances/%s/databases/%s/sessions/%s", "[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]")
var sql string = "sql114126"
var request = &spannerpb.ExecuteSqlRequest{
Session: formattedSession,
@@ -615,7 +615,7 @@ func TestSpannerRead(t *testing.T) {
mockSpanner.resps = append(mockSpanner.resps[:0], expectedResponse)
var formattedSession string = SessionPath("[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]")
var formattedSession string = fmt.Sprintf("projects/%s/instances/%s/databases/%s/sessions/%s", "[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]")
var table string = "table110115790"
var columns []string = nil
var keySet *spannerpb.KeySet = &spannerpb.KeySet{}
@@ -650,7 +650,7 @@ func TestSpannerReadError(t *testing.T) {
errCode := codes.PermissionDenied
mockSpanner.err = gstatus.Error(errCode, "test error")
var formattedSession string = SessionPath("[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]")
var formattedSession string = fmt.Sprintf("projects/%s/instances/%s/databases/%s/sessions/%s", "[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]")
var table string = "table110115790"
var columns []string = nil
var keySet *spannerpb.KeySet = &spannerpb.KeySet{}
@@ -688,7 +688,7 @@ func TestSpannerStreamingRead(t *testing.T) {
mockSpanner.resps = append(mockSpanner.resps[:0], expectedResponse)
var formattedSession string = SessionPath("[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]")
var formattedSession string = fmt.Sprintf("projects/%s/instances/%s/databases/%s/sessions/%s", "[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]")
var table string = "table110115790"
var columns []string = nil
var keySet *spannerpb.KeySet = &spannerpb.KeySet{}
@@ -727,7 +727,7 @@ func TestSpannerStreamingReadError(t *testing.T) {
errCode := codes.PermissionDenied
mockSpanner.err = gstatus.Error(errCode, "test error")
var formattedSession string = SessionPath("[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]")
var formattedSession string = fmt.Sprintf("projects/%s/instances/%s/databases/%s/sessions/%s", "[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]")
var table string = "table110115790"
var columns []string = nil
var keySet *spannerpb.KeySet = &spannerpb.KeySet{}
@@ -767,7 +767,7 @@ func TestSpannerBeginTransaction(t *testing.T) {
mockSpanner.resps = append(mockSpanner.resps[:0], expectedResponse)
var formattedSession string = SessionPath("[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]")
var formattedSession string = fmt.Sprintf("projects/%s/instances/%s/databases/%s/sessions/%s", "[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]")
var options *spannerpb.TransactionOptions = &spannerpb.TransactionOptions{}
var request = &spannerpb.BeginTransactionRequest{
Session: formattedSession,
@@ -798,7 +798,7 @@ func TestSpannerBeginTransactionError(t *testing.T) {
errCode := codes.PermissionDenied
mockSpanner.err = gstatus.Error(errCode, "test error")
var formattedSession string = SessionPath("[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]")
var formattedSession string = fmt.Sprintf("projects/%s/instances/%s/databases/%s/sessions/%s", "[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]")
var options *spannerpb.TransactionOptions = &spannerpb.TransactionOptions{}
var request = &spannerpb.BeginTransactionRequest{
Session: formattedSession,
@@ -827,7 +827,7 @@ func TestSpannerCommit(t *testing.T) {
mockSpanner.resps = append(mockSpanner.resps[:0], expectedResponse)
var formattedSession string = SessionPath("[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]")
var formattedSession string = fmt.Sprintf("projects/%s/instances/%s/databases/%s/sessions/%s", "[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]")
var mutations []*spannerpb.Mutation = nil
var request = &spannerpb.CommitRequest{
Session: formattedSession,
@@ -858,7 +858,7 @@ func TestSpannerCommitError(t *testing.T) {
errCode := codes.PermissionDenied
mockSpanner.err = gstatus.Error(errCode, "test error")
var formattedSession string = SessionPath("[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]")
var formattedSession string = fmt.Sprintf("projects/%s/instances/%s/databases/%s/sessions/%s", "[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]")
var mutations []*spannerpb.Mutation = nil
var request = &spannerpb.CommitRequest{
Session: formattedSession,
@@ -887,7 +887,7 @@ func TestSpannerRollback(t *testing.T) {
mockSpanner.resps = append(mockSpanner.resps[:0], expectedResponse)
var formattedSession string = SessionPath("[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]")
var formattedSession string = fmt.Sprintf("projects/%s/instances/%s/databases/%s/sessions/%s", "[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]")
var transactionId []byte = []byte("28")
var request = &spannerpb.RollbackRequest{
Session: formattedSession,
@@ -915,7 +915,7 @@ func TestSpannerRollbackError(t *testing.T) {
errCode := codes.PermissionDenied
mockSpanner.err = gstatus.Error(errCode, "test error")
var formattedSession string = SessionPath("[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]")
var formattedSession string = fmt.Sprintf("projects/%s/instances/%s/databases/%s/sessions/%s", "[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]")
var transactionId []byte = []byte("28")
var request = &spannerpb.RollbackRequest{
Session: formattedSession,
+49
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 +
""
}
+16 -42
View File
@@ -1,10 +1,10 @@
// Copyright 2017, Google Inc. All rights reserved.
// 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
// 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,
@@ -106,8 +106,8 @@ type Client struct {
// The call options for this service.
CallOptions *CallOptions
// The metadata to be sent with each request.
Metadata metadata.MD
// The x-goog-* metadata to be sent with each request.
xGoogMetadata metadata.MD
}
// NewClient creates a new spanner client.
@@ -148,33 +148,7 @@ func (c *Client) Close() error {
func (c *Client) SetGoogleClientInfo(keyval ...string) {
kv := append([]string{"gl-go", version.Go()}, keyval...)
kv = append(kv, "gapic", version.Repo, "gax", gax.Version, "grpc", grpc.Version)
c.Metadata = metadata.Pairs("x-goog-api-client", gax.XGoogHeader(kv...))
}
// DatabasePath returns the path for the database resource.
func DatabasePath(project, instance, database string) string {
return "" +
"projects/" +
project +
"/instances/" +
instance +
"/databases/" +
database +
""
}
// SessionPath returns the path for the session resource.
func SessionPath(project, instance, database, session string) string {
return "" +
"projects/" +
project +
"/instances/" +
instance +
"/databases/" +
database +
"/sessions/" +
session +
""
c.xGoogMetadata = metadata.Pairs("x-goog-api-client", gax.XGoogHeader(kv...))
}
// CreateSession creates a new session. A session can be used to perform
@@ -197,7 +171,7 @@ func SessionPath(project, instance, database, session string) string {
// 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) {
ctx = insertMetadata(ctx, c.Metadata)
ctx = insertMetadata(ctx, c.xGoogMetadata)
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 {
@@ -215,7 +189,7 @@ func (c *Client) CreateSession(ctx context.Context, req *spannerpb.CreateSession
// 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) {
ctx = insertMetadata(ctx, c.Metadata)
ctx = insertMetadata(ctx, c.xGoogMetadata)
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 {
@@ -231,7 +205,7 @@ func (c *Client) GetSession(ctx context.Context, req *spannerpb.GetSessionReques
// ListSessions lists all sessions in a given database.
func (c *Client) ListSessions(ctx context.Context, req *spannerpb.ListSessionsRequest, opts ...gax.CallOption) *SessionIterator {
ctx = insertMetadata(ctx, c.Metadata)
ctx = insertMetadata(ctx, c.xGoogMetadata)
opts = append(c.CallOptions.ListSessions[0:len(c.CallOptions.ListSessions):len(c.CallOptions.ListSessions)], opts...)
it := &SessionIterator{}
it.InternalFetch = func(pageSize int, pageToken string) ([]*spannerpb.Session, string, error) {
@@ -266,7 +240,7 @@ func (c *Client) ListSessions(ctx context.Context, req *spannerpb.ListSessionsRe
// DeleteSession ends a session, releasing server resources associated with it.
func (c *Client) DeleteSession(ctx context.Context, req *spannerpb.DeleteSessionRequest, opts ...gax.CallOption) error {
ctx = insertMetadata(ctx, c.Metadata)
ctx = insertMetadata(ctx, c.xGoogMetadata)
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
@@ -288,7 +262,7 @@ func (c *Client) DeleteSession(ctx context.Context, req *spannerpb.DeleteSession
// 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) {
ctx = insertMetadata(ctx, c.Metadata)
ctx = insertMetadata(ctx, c.xGoogMetadata)
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 {
@@ -308,7 +282,7 @@ func (c *Client) ExecuteSql(ctx context.Context, req *spannerpb.ExecuteSqlReques
// 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) {
ctx = insertMetadata(ctx, c.Metadata)
ctx = insertMetadata(ctx, c.xGoogMetadata)
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 {
@@ -336,7 +310,7 @@ func (c *Client) ExecuteStreamingSql(ctx context.Context, req *spannerpb.Execute
// 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) {
ctx = insertMetadata(ctx, c.Metadata)
ctx = insertMetadata(ctx, c.xGoogMetadata)
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 {
@@ -356,7 +330,7 @@ func (c *Client) Read(ctx context.Context, req *spannerpb.ReadRequest, opts ...g
// 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) {
ctx = insertMetadata(ctx, c.Metadata)
ctx = insertMetadata(ctx, c.xGoogMetadata)
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 {
@@ -375,7 +349,7 @@ func (c *Client) StreamingRead(ctx context.Context, req *spannerpb.ReadRequest,
// [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) {
ctx = insertMetadata(ctx, c.Metadata)
ctx = insertMetadata(ctx, c.xGoogMetadata)
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 {
@@ -398,7 +372,7 @@ func (c *Client) BeginTransaction(ctx context.Context, req *spannerpb.BeginTrans
// 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) {
ctx = insertMetadata(ctx, c.Metadata)
ctx = insertMetadata(ctx, c.xGoogMetadata)
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 {
@@ -421,7 +395,7 @@ func (c *Client) Commit(ctx context.Context, req *spannerpb.CommitRequest, opts
// 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 {
ctx = insertMetadata(ctx, c.Metadata)
ctx = insertMetadata(ctx, c.xGoogMetadata)
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
+13 -13
View File
@@ -1,10 +1,10 @@
// Copyright 2017, Google Inc. All rights reserved.
// 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
// 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,
@@ -43,7 +43,7 @@ func ExampleClient_CreateSession() {
}
req := &spannerpb.CreateSessionRequest{
// TODO: Fill request struct fields.
// TODO: Fill request struct fields.
}
resp, err := c.CreateSession(ctx, req)
if err != nil {
@@ -61,7 +61,7 @@ func ExampleClient_GetSession() {
}
req := &spannerpb.GetSessionRequest{
// TODO: Fill request struct fields.
// TODO: Fill request struct fields.
}
resp, err := c.GetSession(ctx, req)
if err != nil {
@@ -79,7 +79,7 @@ func ExampleClient_ListSessions() {
}
req := &spannerpb.ListSessionsRequest{
// TODO: Fill request struct fields.
// TODO: Fill request struct fields.
}
it := c.ListSessions(ctx, req)
for {
@@ -103,7 +103,7 @@ func ExampleClient_DeleteSession() {
}
req := &spannerpb.DeleteSessionRequest{
// TODO: Fill request struct fields.
// TODO: Fill request struct fields.
}
err = c.DeleteSession(ctx, req)
if err != nil {
@@ -119,7 +119,7 @@ func ExampleClient_ExecuteSql() {
}
req := &spannerpb.ExecuteSqlRequest{
// TODO: Fill request struct fields.
// TODO: Fill request struct fields.
}
resp, err := c.ExecuteSql(ctx, req)
if err != nil {
@@ -137,7 +137,7 @@ func ExampleClient_ExecuteStreamingSql() {
}
req := &spannerpb.ExecuteSqlRequest{
// TODO: Fill request struct fields.
// TODO: Fill request struct fields.
}
stream, err := c.ExecuteStreamingSql(ctx, req)
if err != nil {
@@ -164,7 +164,7 @@ func ExampleClient_Read() {
}
req := &spannerpb.ReadRequest{
// TODO: Fill request struct fields.
// TODO: Fill request struct fields.
}
resp, err := c.Read(ctx, req)
if err != nil {
@@ -182,7 +182,7 @@ func ExampleClient_StreamingRead() {
}
req := &spannerpb.ReadRequest{
// TODO: Fill request struct fields.
// TODO: Fill request struct fields.
}
stream, err := c.StreamingRead(ctx, req)
if err != nil {
@@ -209,7 +209,7 @@ func ExampleClient_BeginTransaction() {
}
req := &spannerpb.BeginTransactionRequest{
// TODO: Fill request struct fields.
// TODO: Fill request struct fields.
}
resp, err := c.BeginTransaction(ctx, req)
if err != nil {
@@ -227,7 +227,7 @@ func ExampleClient_Commit() {
}
req := &spannerpb.CommitRequest{
// TODO: Fill request struct fields.
// TODO: Fill request struct fields.
}
resp, err := c.Commit(ctx, req)
if err != nil {
@@ -245,7 +245,7 @@ func ExampleClient_Rollback() {
}
req := &spannerpb.RollbackRequest{
// TODO: Fill request struct fields.
// TODO: Fill request struct fields.
}
err = c.Rollback(ctx, req)
if err != nil {
+20
View File
@@ -0,0 +1,20 @@
// Copyright 2017 Google Inc. All Rights Reserved.
//
// 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
+28 -18
View File
@@ -41,9 +41,6 @@ const (
// xGoogHeaderKey is the name of the metadata header used to indicate client
// information.
xGoogHeaderKey = "x-goog-api-client"
// numChannels is the default value for NumChannels of client
numChannels = 4
)
const (
@@ -82,8 +79,8 @@ type Client struct {
// ClientConfig has configurations for the client.
type ClientConfig struct {
// NumChannels is the number of GRPC channels.
// If zero, numChannels is used.
// NumChannels is the number of gRPC channels.
// If zero, a reasonable default is used based on the execution environment.
NumChannels int
co []option.ClientOption
// SessionPoolConfig is the configuration for session pool.
@@ -114,7 +111,10 @@ func NewClient(ctx context.Context, database string, opts ...option.ClientOption
// 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) (*Client, error) {
func NewClientWithConfig(ctx context.Context, database string, config ClientConfig, opts ...option.ClientOption) (_ *Client, err error) {
ctx = traceStartSpan(ctx, "cloud.google.com/go/spanner.NewClient")
defer func() { traceEndSpan(ctx, err) }()
// Validate database path.
if err := validDatabaseName(database); err != nil {
return nil, err
@@ -125,7 +125,17 @@ func NewClientWithConfig(ctx context.Context, database string, config ClientConf
resourcePrefixHeader, database,
xGoogHeaderKey, xGoogHeaderVal),
}
allOpts := []option.ClientOption{option.WithEndpoint(prodAddr), option.WithScopes(Scope), option.WithGRPCDialOption(grpc.WithDefaultCallOptions(grpc.MaxCallSendMsgSize(100<<20), grpc.MaxCallRecvMsgSize(100<<20)))}
allOpts := []option.ClientOption{
option.WithEndpoint(prodAddr),
option.WithScopes(Scope),
option.WithGRPCDialOption(
grpc.WithDefaultCallOptions(
grpc.MaxCallSendMsgSize(100<<20),
grpc.MaxCallRecvMsgSize(100<<20),
),
),
}
allOpts = append(allOpts, openCensusOptions()...)
allOpts = append(allOpts, opts...)
// Prepare gRPC channels.
if config.NumChannels == 0 {
@@ -138,10 +148,6 @@ func NewClientWithConfig(ctx context.Context, database string, config ClientConf
if config.MaxBurst == 0 {
config.MaxBurst = 10
}
// Default MaxSessionAge
if config.maxSessionAge == 0 {
config.maxSessionAge = time.Minute * 30
}
for i := 0; i < config.NumChannels; i++ {
conn, err := gtransport.Dial(ctx, allOpts...)
if err != nil {
@@ -237,7 +243,9 @@ func checkNestedTxn(ctx context.Context) 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.
func (c *Client) ReadWriteTransaction(ctx context.Context, f func(context.Context, *ReadWriteTransaction) error) (time.Time, error) {
func (c *Client) ReadWriteTransaction(ctx context.Context, f func(context.Context, *ReadWriteTransaction) error) (commitTimestamp time.Time, err error) {
ctx = traceStartSpan(ctx, "cloud.google.com/go/spanner.ReadWriteTransaction")
defer func() { traceEndSpan(ctx, err) }()
if err := checkNestedTxn(ctx); err != nil {
return time.Time{}, err
}
@@ -245,7 +253,7 @@ func (c *Client) ReadWriteTransaction(ctx context.Context, f func(context.Contex
ts time.Time
sh *sessionHandle
)
err := runRetryable(ctx, func(ctx context.Context) error {
err = runRetryableNoWrap(ctx, func(ctx context.Context) error {
var (
err error
t *ReadWriteTransaction
@@ -267,15 +275,14 @@ func (c *Client) ReadWriteTransaction(ctx context.Context, f func(context.Contex
}
}
t.txReadOnly.txReadEnv = t
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)
if err != nil {
return err
}
return nil
return err
})
if sh != nil {
sh.recycle()
@@ -310,7 +317,7 @@ func ApplyAtLeastOnce() ApplyOption {
}
// Apply applies a list of mutations atomically to the database.
func (c *Client) Apply(ctx context.Context, ms []*Mutation, opts ...ApplyOption) (time.Time, error) {
func (c *Client) Apply(ctx context.Context, ms []*Mutation, opts ...ApplyOption) (commitTimestamp time.Time, err error) {
ao := &applyOption{}
for _, opt := range opts {
opt(ao)
@@ -321,6 +328,9 @@ func (c *Client) Apply(ctx context.Context, ms []*Mutation, opts ...ApplyOption)
return nil
})
}
ctx = traceStartSpan(ctx, "cloud.google.com/go/spanner.Apply")
defer func() { traceEndSpan(ctx, err) }()
t := &writeOnlyTransaction{c.idleSessions}
return t.applyAtLeastOnce(ctx, ms...)
}
+6
View File
@@ -302,6 +302,12 @@ mutations, which will all be executed at the end of the transaction:
return nil
})
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.
Authentication
See examples of authorization and authentication at
+9 -2
View File
@@ -19,6 +19,7 @@ package spanner
import (
"fmt"
"golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
@@ -74,10 +75,16 @@ func toSpannerErrorWithMetadata(err error, trailers metadata.MD) error {
}
return se
}
if grpc.Code(err) == codes.Unknown {
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}
}
return &Error{grpc.Code(err), grpc.ErrorDesc(err), trailers}
}
// ErrCode extracts the canonical error code from a Go error.
+44
View File
@@ -0,0 +1,44 @@
/*
Copyright 2017 Google Inc. All Rights Reserved.
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"
"testing"
"golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
)
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},
{grpc.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)
}
}
}
+16
View File
@@ -427,6 +427,22 @@ func ExampleReadOnlyTransaction_ReadUsingIndex() {
_ = 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)
+66
View File
@@ -0,0 +1,66 @@
// Copyright 2017 Google Inc. All Rights Reserved.
//
// 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 go1.8
package spanner
import (
"fmt"
ocgrpc "go.opencensus.io/plugin/grpc"
"go.opencensus.io/trace"
"golang.org/x/net/context"
"google.golang.org/api/option"
"google.golang.org/grpc"
)
func openCensusOptions() []option.ClientOption {
return []option.ClientOption{
option.WithGRPCDialOption(grpc.WithStatsHandler(ocgrpc.NewClientStatsHandler())),
}
}
func traceStartSpan(ctx context.Context, name string) context.Context {
return trace.StartSpan(ctx, name)
}
func traceEndSpan(ctx context.Context, err error) {
span := trace.FromContext(ctx)
if err != nil {
span.SetStatus(trace.Status{Message: err.Error()})
}
trace.EndSpan(ctx)
}
func tracePrintf(ctx context.Context, attrMap map[string]interface{}, format string, args ...interface{}) {
var attrs []trace.Attribute
for k, v := range attrMap {
var a trace.Attribute
switch v := v.(type) {
case string:
a = trace.StringAttribute{k, v}
case bool:
a = trace.BoolAttribute{k, v}
case int:
a = trace.Int64Attribute{k, int64(v)}
case int64:
a = trace.Int64Attribute{k, v}
default:
a = trace.StringAttribute{k, fmt.Sprintf("%#v", v)}
}
attrs = append(attrs, a)
}
trace.FromContext(ctx).Annotatef(attrs, format, args...)
}
+67 -87
View File
@@ -19,13 +19,13 @@ package testutil
import (
"errors"
"fmt"
"reflect"
"sync"
"testing"
"time"
"golang.org/x/net/context"
"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"
@@ -37,17 +37,14 @@ import (
// Action is a mocked RPC activity that MockCloudSpannerClient will take.
type Action struct {
method string
err error
}
// NewAction creates Action objects.
func NewAction(m string, e error) Action {
return Action{m, e}
Method string
Err error
}
// MockCloudSpannerClient is a mock implementation of sppb.SpannerClient.
type MockCloudSpannerClient struct {
sppb.SpannerClient
mu sync.Mutex
t *testing.T
// Live sessions on the client.
@@ -62,9 +59,6 @@ type MockCloudSpannerClient struct {
nice bool
// Client will stall on any requests.
freezed chan struct{}
// embed nil interface so updating proto with new methods don't fail the build
sppb.SpannerClient
}
// NewMockCloudSpannerClient creates new MockCloudSpannerClient instance.
@@ -91,8 +85,8 @@ func (m *MockCloudSpannerClient) MakeStrict() {
m.nice = false
}
// InjectError injects a global error that will be returned by all APIs regardless of
// the actions array.
// InjectError injects a global error that will be returned by all calls to method
// regardless of the actions array.
func (m *MockCloudSpannerClient) InjectError(method string, err error) {
m.mu.Lock()
defer m.mu.Unlock()
@@ -103,7 +97,7 @@ func (m *MockCloudSpannerClient) InjectError(method string, err error) {
func (m *MockCloudSpannerClient) SetActions(acts ...Action) {
m.mu.Lock()
defer m.mu.Unlock()
m.actions = []Action{}
m.actions = nil
for _, act := range acts {
m.actions = append(m.actions, act)
}
@@ -178,28 +172,15 @@ func (m *MockCloudSpannerClient) DeleteSession(c context.Context, r *sppb.Delete
return &empty.Empty{}, nil
}
// ExecuteSql is a placeholder for SpannerClient.ExecuteSql.
func (m *MockCloudSpannerClient) ExecuteSql(c context.Context, r *sppb.ExecuteSqlRequest, opts ...grpc.CallOption) (*sppb.ResultSet, error) {
m.ready()
return nil, errors.New("Unimplemented")
}
// ExecuteStreamingSql is a mock implementation of SpannerClient.ExecuteStreamingSql.
func (m *MockCloudSpannerClient) ExecuteStreamingSql(c context.Context, r *sppb.ExecuteSqlRequest, opts ...grpc.CallOption) (sppb.Spanner_ExecuteStreamingSqlClient, error) {
m.ready()
m.mu.Lock()
defer m.mu.Unlock()
if err := m.injErr["ExecuteStreamingSql"]; err != nil {
act, err := m.expectAction("ExecuteStreamingSql")
if err != nil {
return nil, err
}
if len(m.actions) == 0 {
m.t.Fatalf("unexpected ExecuteStreamingSql executed")
}
act := m.actions[0]
m.actions = m.actions[1:]
if act.method != "ExecuteStreamingSql" {
m.t.Fatalf("unexpected ExecuteStreamingSql call, want action: %v", act)
}
wantReq := &sppb.ExecuteSqlRequest{
Session: "mocksession",
Transaction: &sppb.TransactionSelector{
@@ -222,38 +203,24 @@ func (m *MockCloudSpannerClient) ExecuteStreamingSql(c context.Context, r *sppb.
},
ParamTypes: map[string]*sppb.Type{"var1": &sppb.Type{Code: sppb.TypeCode_STRING}},
}
if !reflect.DeepEqual(r, wantReq) {
if !proto.Equal(r, wantReq) {
return nil, fmt.Errorf("got query request: %v, want: %v", r, wantReq)
}
if act.err != nil {
return nil, act.err
if act.Err != nil {
return nil, act.Err
}
return nil, errors.New("query never succeeds on mock client")
}
// Read is a placeholder for SpannerClient.Read.
func (m *MockCloudSpannerClient) Read(c context.Context, r *sppb.ReadRequest, opts ...grpc.CallOption) (*sppb.ResultSet, error) {
m.ready()
m.t.Fatalf("Read is unimplemented")
return nil, errors.New("Unimplemented")
}
// StreamingRead is a placeholder for SpannerClient.StreamingRead.
func (m *MockCloudSpannerClient) StreamingRead(c context.Context, r *sppb.ReadRequest, opts ...grpc.CallOption) (sppb.Spanner_StreamingReadClient, error) {
m.ready()
m.mu.Lock()
defer m.mu.Unlock()
if err := m.injErr["StreamingRead"]; err != nil {
act, err := m.expectAction("StreamingRead", "StreamingReadIndex")
if err != nil {
return nil, err
}
if len(m.actions) == 0 {
m.t.Fatalf("unexpected StreamingRead executed")
}
act := m.actions[0]
m.actions = m.actions[1:]
if act.method != "StreamingRead" && act.method != "StreamingIndexRead" {
m.t.Fatalf("unexpected read call, want action: %v", act)
}
wantReq := &sppb.ReadRequest{
Session: "mocksession",
Transaction: &sppb.TransactionSelector{
@@ -284,14 +251,14 @@ func (m *MockCloudSpannerClient) StreamingRead(c context.Context, r *sppb.ReadRe
All: false,
},
}
if act.method == "StreamingIndexRead" {
if act.Method == "StreamingIndexRead" {
wantReq.Index = "idx1"
}
if !reflect.DeepEqual(r, wantReq) {
if !proto.Equal(r, wantReq) {
return nil, fmt.Errorf("got query request: %v, want: %v", r, wantReq)
}
if act.err != nil {
return nil, act.err
if act.Err != nil {
return nil, act.Err
}
return nil, errors.New("read never succeeds on mock client")
}
@@ -302,19 +269,12 @@ func (m *MockCloudSpannerClient) BeginTransaction(c context.Context, r *sppb.Beg
m.mu.Lock()
defer m.mu.Unlock()
if !m.nice {
if err := m.injErr["BeginTransaction"]; err != nil {
act, err := m.expectAction("BeginTransaction")
if err != nil {
return nil, err
}
if len(m.actions) == 0 {
m.t.Fatalf("unexpected Begin executed")
}
act := m.actions[0]
m.actions = m.actions[1:]
if act.method != "Begin" {
m.t.Fatalf("unexpected Begin call, want action: %v", act)
}
if act.err != nil {
return nil, act.err
if act.Err != nil {
return nil, act.Err
}
}
resp := &sppb.Transaction{Id: []byte("transaction-1")}
@@ -330,19 +290,12 @@ func (m *MockCloudSpannerClient) Commit(c context.Context, r *sppb.CommitRequest
m.mu.Lock()
defer m.mu.Unlock()
if !m.nice {
if err := m.injErr["Commit"]; err != nil {
act, err := m.expectAction("Commit")
if err != nil {
return nil, err
}
if len(m.actions) == 0 {
m.t.Fatalf("unexpected Commit executed")
}
act := m.actions[0]
m.actions = m.actions[1:]
if act.method != "Commit" {
m.t.Fatalf("unexpected Commit call, want action: %v", act)
}
if act.err != nil {
return nil, act.err
if act.Err != nil {
return nil, act.Err
}
}
return &sppb.CommitResponse{CommitTimestamp: &pbt.Timestamp{Seconds: 1, Nanos: 2}}, nil
@@ -354,37 +307,64 @@ func (m *MockCloudSpannerClient) Rollback(c context.Context, r *sppb.RollbackReq
m.mu.Lock()
defer m.mu.Unlock()
if !m.nice {
if err := m.injErr["Rollback"]; err != nil {
act, err := m.expectAction("Rollback")
if err != nil {
return nil, err
}
if len(m.actions) == 0 {
m.t.Fatalf("unexpected Rollback executed")
}
act := m.actions[0]
m.actions = m.actions[1:]
if act.method != "Rollback" {
m.t.Fatalf("unexpected Rollback call, want action: %v", act)
}
if act.err != nil {
return nil, act.err
if act.Err != nil {
return nil, act.Err
}
}
return nil, nil
}
func (m *MockCloudSpannerClient) expectAction(methods ...string) (Action, error) {
for _, me := range methods {
if err := m.injErr[me]; err != nil {
return Action{}, err
}
}
if len(m.actions) == 0 {
m.t.Fatalf("unexpected %v executed", methods)
}
act := m.actions[0]
m.actions = m.actions[1:]
for _, me := range methods {
if me == act.Method {
return act, nil
}
}
m.t.Fatalf("unexpected call of one of %v, want method %s", methods, act.Method)
return Action{}, nil
}
// 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)
}
// CheckActionsConsumed checks that all actions have been consumed.
func (m *MockCloudSpannerClient) CheckActionsConsumed() {
if len(m.actions) != 0 {
m.t.Fatalf("unconsumed mock client actions: %v", m.actions)
}
}
// ready checks conditions before executing requests
// TODO: also check injected errors, actions
// 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
<-m.freezed
<-freezed
}
+2 -3
View File
@@ -67,15 +67,14 @@ type MockCtlMsg struct {
// 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
next int
// embed nil interface so updating proto with new methods don't fail the build
sppb.SpannerServer
}
// Addr returns the listening address of mock server.
+4 -5
View File
@@ -17,7 +17,6 @@ limitations under the License.
package spanner
import (
"reflect"
"testing"
"time"
@@ -193,7 +192,7 @@ func TestKey(t *testing.T) {
if err != nil {
t.Errorf("%v.proto() returns error %v; want nil error", test.k, err)
}
if !reflect.DeepEqual(gotProto, test.wantProto) {
if !testEqual(gotProto, test.wantProto) {
t.Errorf("%v.proto() = \n%v\nwant:\n%v", test.k, gotProto, test.wantProto)
}
}
@@ -246,7 +245,7 @@ func TestKeyRange(t *testing.T) {
if err != nil {
t.Errorf("%v.proto() returns error %v; want nil error", test.kr, err)
}
if !reflect.DeepEqual(gotProto, test.wantProto) {
if !testEqual(gotProto, test.wantProto) {
t.Errorf("%v.proto() = \n%v\nwant:\n%v", test.kr, gotProto.String(), test.wantProto.String())
}
}
@@ -255,7 +254,7 @@ func TestKeyRange(t *testing.T) {
func TestPrefixRange(t *testing.T) {
got := Key{1}.AsPrefix()
want := KeyRange{Start: Key{1}, End: Key{1}, Kind: ClosedClosed}
if !reflect.DeepEqual(got, want) {
if !testEqual(got, want) {
t.Errorf("got %v, want %v", got, want)
}
}
@@ -366,7 +365,7 @@ func TestKeySets(t *testing.T) {
if err != nil {
t.Errorf("#%d: %v.proto() returns error %v; want nil error", i, test.ks, err)
}
if !reflect.DeepEqual(gotProto, test.wantProto) {
if !testEqual(gotProto, test.wantProto) {
t.Errorf("#%d: %v.proto() = \n%v\nwant:\n%v", i, test.ks, gotProto.String(), test.wantProto.String())
}
}
+13 -14
View File
@@ -17,7 +17,6 @@ limitations under the License.
package spanner
import (
"reflect"
"sort"
"strings"
"testing"
@@ -123,7 +122,7 @@ func TestMutationToProto(t *testing.T) {
},
},
} {
if got, err := test.m.proto(); err != nil || !reflect.DeepEqual(got, test.want) {
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)
}
}
@@ -173,7 +172,7 @@ func mutationEqual(t *testing.T, m1, m2 Mutation) bool {
ms2 := newMutationColumnSorter(&m2)
sort.Sort(ms1)
sort.Sort(ms2)
return reflect.DeepEqual(ms1, ms2)
return testEqual(ms1, ms2)
}
// Test helper functions which help to generate spanner.Mutation.
@@ -316,16 +315,16 @@ func TestMutationHelpers(t *testing.T) {
func TestBadStructs(t *testing.T) {
val := "i_am_not_a_struct"
wantErr := errNotStruct(val)
if _, gotErr := InsertStruct("t_test", val); !reflect.DeepEqual(gotErr, wantErr) {
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); !reflect.DeepEqual(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); !reflect.DeepEqual(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); !reflect.DeepEqual(gotErr, wantErr) {
if _, gotErr := ReplaceStruct("t_test", val); !testEqual(gotErr, wantErr) {
t.Errorf("ReplaceStruct(%q) returns error %v, want %v", val, gotErr, wantErr)
}
}
@@ -346,13 +345,13 @@ func TestStructToMutationParams(t *testing.T) {
{&S{F: 1}, []string{"F"}, []interface{}{1}, nil},
} {
gotCols, gotVals, gotErr := structToMutationParams(test.in)
if !reflect.DeepEqual(gotCols, test.wantCols) {
if !testEqual(gotCols, test.wantCols) {
t.Errorf("%#v: got cols %v, want %v", test.in, gotCols, test.wantCols)
}
if !reflect.DeepEqual(gotVals, test.wantVals) {
if !testEqual(gotVals, test.wantVals) {
t.Errorf("%#v: got vals %v, want %v", test.in, gotVals, test.wantVals)
}
if !reflect.DeepEqual(gotErr, test.wantErr) {
if !testEqual(gotErr, test.wantErr) {
t.Errorf("%#v: got err %v, want %v", test.in, gotErr, test.wantErr)
}
}
@@ -499,12 +498,12 @@ func TestEncodeMutation(t *testing.T) {
} {
gotProto, gotErr := test.mutation.proto()
if gotErr != nil {
if !reflect.DeepEqual(gotErr, test.wantErr) {
if !testEqual(gotErr, test.wantErr) {
t.Errorf("%s: %v.proto() returns error %v, want %v", test.name, test.mutation, gotErr, test.wantErr)
}
continue
}
if !reflect.DeepEqual(gotProto, test.wantProto) {
if !testEqual(gotProto, test.wantProto) {
t.Errorf("%s: %v.proto() = (%v, nil), want (%v, nil)", test.name, test.mutation, gotProto, test.wantProto)
}
}
@@ -559,12 +558,12 @@ func TestEncodeMutationArray(t *testing.T) {
} {
gotProto, gotErr := mutationsProto(test.ms)
if gotErr != nil {
if !reflect.DeepEqual(gotErr, test.wantErr) {
if !testEqual(gotErr, test.wantErr) {
t.Errorf("%v: mutationsProto(%v) returns error %v, want %v", test.name, test.ms, gotErr, test.wantErr)
}
continue
}
if !reflect.DeepEqual(gotProto, test.want) {
if !testEqual(gotProto, test.want) {
t.Errorf("%v: mutationsProto(%v) = (%v, nil), want (%v, nil)", test.name, test.ms, gotProto, test.want)
}
}
+20
View File
@@ -0,0 +1,20 @@
// Copyright 2017 Google Inc. All Rights Reserved.
//
// 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
+36
View File
@@ -0,0 +1,36 @@
// Copyright 2017 Google Inc. All Rights Reserved.
//
// 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 !go1.8
package spanner
import (
"golang.org/x/net/context"
"google.golang.org/api/option"
)
// OpenCensus only supports go 1.8 and higher.
func openCensusOptions() []option.ClientOption { return nil }
func traceStartSpan(ctx context.Context, _ string) context.Context {
return ctx
}
func traceEndSpan(context.Context, error) {
}
func tracePrintf(context.Context, map[string]interface{}, string, ...interface{}) {
}
+24 -5
View File
@@ -19,14 +19,14 @@ package spanner
import (
"bytes"
"io"
"log"
"sync/atomic"
"time"
log "github.com/golang/glog"
"cloud.google.com/go/internal/protostruct"
proto "github.com/golang/protobuf/proto"
proto3 "github.com/golang/protobuf/ptypes/struct"
"golang.org/x/net/context"
"google.golang.org/api/iterator"
sppb "google.golang.org/genproto/googleapis/spanner/v1"
"google.golang.org/grpc/codes"
@@ -47,6 +47,7 @@ func errEarlyReadEnd() error {
// 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 = traceStartSpan(ctx, "cloud.google.com/go/spanner.RowIterator")
return &RowIterator{
streamd: newResumableStreamDecoder(ctx, rpc),
rowd: &partialResultSetDecoder{},
@@ -58,6 +59,14 @@ func stream(ctx context.Context, rpc func(ct context.Context, resumeToken []byte
// 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{}
streamd *resumableStreamDecoder
rowd *partialResultSetDecoder
setTimestamp func(time.Time)
@@ -75,7 +84,12 @@ func (r *RowIterator) Next() (*Row, error) {
return nil, r.err
}
for len(r.rows) == 0 && r.streamd.next() {
r.rows, r.err = r.rowd.add(r.streamd.get())
prs := r.streamd.get()
if prs.Stats != nil {
r.QueryPlan = prs.Stats.QueryPlan
r.QueryStats = protostruct.DecodeToMap(prs.Stats.QueryStats)
}
r.rows, r.err = r.rowd.add(prs)
if r.err != nil {
return nil, r.err
}
@@ -125,6 +139,9 @@ func (r *RowIterator) Do(f func(r *Row) error) error {
// Stop terminates the iteration. It should be called after every iteration.
func (r *RowIterator) Stop() {
if r.streamd != nil {
defer traceEndSpan(r.streamd.ctx, r.err)
}
if r.cancel != nil {
r.cancel()
}
@@ -441,7 +458,7 @@ func (d *resumableStreamDecoder) next() bool {
return true
default:
log.Errorf("Unexpected resumableStreamDecoder.state: %v", d.state)
log.Printf("Unexpected resumableStreamDecoder.state: %v", d.state)
return false
}
}
@@ -486,7 +503,9 @@ func (d *resumableStreamDecoder) resetBackOff() {
// doBackoff does an exponential backoff sleep.
func (d *resumableStreamDecoder) doBackOff() {
ticker := time.NewTicker(d.backoff.delay(d.retryCount))
delay := d.backoff.delay(d.retryCount)
tracePrintf(d.ctx, nil, "Backing off stream read for %s", delay)
ticker := time.NewTicker(delay)
defer ticker.Stop()
d.retryCount++
select {
+48 -19
View File
@@ -20,7 +20,6 @@ import (
"errors"
"fmt"
"io"
"reflect"
"sync/atomic"
"testing"
"time"
@@ -588,10 +587,10 @@ nextTest:
}
rows = append(rows, rs...)
}
if !reflect.DeepEqual(p.ts, test.wantTs) {
if !testEqual(p.ts, test.wantTs) {
t.Errorf("got transaction(%v), want %v", p.ts, test.wantTs)
}
if !reflect.DeepEqual(rows, test.wantF) {
if !testEqual(rows, test.wantF) {
t.Errorf("test %d: rows=\n%v\n; want\n%v\n; p.row:\n%v\n", i, describeRows(rows), describeRows(test.wantF), p.row)
}
if got := p.done(); got != test.wantD {
@@ -840,13 +839,13 @@ nextTest:
if stateDone {
// Check if resumableStreamDecoder carried out expected
// state transitions.
if !reflect.DeepEqual(st, test.stateHistory) {
if !testEqual(st, test.stateHistory) {
t.Errorf("%v: observed state transitions: \n%v\n, want \n%v\n",
test.name, st, test.stateHistory)
}
// Check if resumableStreamDecoder returns expected array of
// PartialResultSets.
if !reflect.DeepEqual(rs, test.want) {
if !testEqual(rs, test.want) {
t.Errorf("%v: received PartialResultSets: \n%v\n, want \n%v\n", test.name, rs, test.want)
}
// Verify that resumableStreamDecoder's internal buffering is also correct.
@@ -858,15 +857,15 @@ nextTest:
}
q = append(q, item)
}
if !reflect.DeepEqual(q, test.queue) {
if !testEqual(q, test.queue) {
t.Errorf("%v: PartialResultSets still queued: \n%v\n, want \n%v\n", test.name, q, test.queue)
}
// Verify resume token.
if test.resumeToken != nil && !reflect.DeepEqual(r.resumeToken, test.resumeToken) {
if test.resumeToken != nil && !testEqual(r.resumeToken, test.resumeToken) {
t.Errorf("%v: Resume token is %v, want %v\n", test.name, r.resumeToken, test.resumeToken)
}
// Verify error message.
if !reflect.DeepEqual(lastErr, test.wantErr) {
if !testEqual(lastErr, test.wantErr) {
t.Errorf("%v: got error %v, want %v", test.name, lastErr, test.wantErr)
}
// Proceed to next test
@@ -1120,25 +1119,25 @@ func TestRsdBlockingStates(t *testing.T) {
case <-stateDone: // Note that at this point, receiver is still blocking on r.next().
// Check if resumableStreamDecoder carried out expected
// state transitions.
if !reflect.DeepEqual(st, test.stateHistory) {
if !testEqual(st, test.stateHistory) {
t.Errorf("%v: observed state transitions: \n%v\n, want \n%v\n",
test.name, st, test.stateHistory)
}
// Check if resumableStreamDecoder returns expected array of
// PartialResultSets.
if !reflect.DeepEqual(rs, test.want) {
if !testEqual(rs, test.want) {
t.Errorf("%v: received PartialResultSets: \n%v\n, want \n%v\n", test.name, rs, test.want)
}
// Verify that resumableStreamDecoder's internal buffering is also correct.
if !reflect.DeepEqual(q, test.queue) {
if !testEqual(q, test.queue) {
t.Errorf("%v: PartialResultSets still queued: \n%v\n, want \n%v\n", test.name, q, test.queue)
}
// Verify resume token.
if test.resumeToken != nil && !reflect.DeepEqual(r.resumeToken, test.resumeToken) {
if test.resumeToken != nil && !testEqual(r.resumeToken, test.resumeToken) {
t.Errorf("%v: Resume token is %v, want %v\n", test.name, r.resumeToken, test.resumeToken)
}
// Verify error message.
if !reflect.DeepEqual(lastErr, test.wantErr) {
if !testEqual(lastErr, test.wantErr) {
t.Errorf("%v: got error %v, want %v", test.name, lastErr, test.wantErr)
}
case <-time.After(1 * time.Second):
@@ -1340,7 +1339,7 @@ func TestResumeToken(t *testing.T) {
},
},
}
if !reflect.DeepEqual(rows, want) {
if !testEqual(rows, want) {
t.Errorf("received rows: \n%v\n; but want\n%v\n", rows, want)
}
// Inject resumable failure.
@@ -1365,7 +1364,7 @@ func TestResumeToken(t *testing.T) {
{Kind: &proto3.Value_StringValue{StringValue: valStr(2)}},
},
})
if !reflect.DeepEqual(rows, want) {
if !testEqual(rows, want) {
t.Errorf("received rows: \n%v\n, want\n%v\n", rows, want)
}
// Sending 3rd - (maxBuffers+1)th rows without resume tokens, client should buffer them.
@@ -1376,7 +1375,7 @@ func TestResumeToken(t *testing.T) {
t.Fatalf("failed to wait for row 3-%v: %v", maxBuffers+1, err)
}
// Received rows should be unchanged.
if !reflect.DeepEqual(rows, want) {
if !testEqual(rows, want) {
t.Errorf("receive rows: \n%v\n, want\n%v\n", rows, want)
}
// Send (maxBuffers+2)th row to trigger state change of resumableStreamDecoder:
@@ -1396,7 +1395,7 @@ func TestResumeToken(t *testing.T) {
},
})
}
if !reflect.DeepEqual(rows, want) {
if !testEqual(rows, want) {
t.Errorf("received rows: \n%v\n; want\n%v\n", rows, want)
}
// Inject resumable error, but since resumableStreamDecoder is already at queueingUnretryable
@@ -1411,7 +1410,7 @@ func TestResumeToken(t *testing.T) {
case <-time.After(10 * time.Second):
t.Fatalf("timeout in waiting for failed query to return.")
}
if wantErr := toSpannerError(grpc.Errorf(codes.Unavailable, "mock server wants some sleep")); !reflect.DeepEqual(gotErr, wantErr) {
if wantErr := toSpannerError(grpc.Errorf(codes.Unavailable, "mock server wants some sleep")); !testEqual(gotErr, wantErr) {
t.Fatalf("stream() returns error: %v, but want error: %v", gotErr, wantErr)
}
@@ -1455,7 +1454,7 @@ func TestResumeToken(t *testing.T) {
},
},
}
if !reflect.DeepEqual(rows, want) {
if !testEqual(rows, want) {
t.Errorf("received rows: \n%v\n; but want\n%v\n", rows, want)
}
}
@@ -1647,6 +1646,36 @@ func TestRowIteratorDo(t *testing.T) {
}
}
func TestRowIteratorDoWithError(t *testing.T) {
restore := setMaxBytesBetweenResumeTokens()
defer restore()
ms := testutil.NewMockCloudSpanner(t, trxTs)
ms.Serve()
defer ms.Stop()
cc := dialMock(t, ms)
defer cc.Close()
mc := sppb.NewSpannerClient(cc)
for i := 0; i < 3; i++ {
ms.AddMsg(nil, false)
}
ms.AddMsg(io.EOF, true)
iter := stream(context.Background(),
func(ct context.Context, resumeToken []byte) (streamingReceiver, error) {
return mc.ExecuteStreamingSql(ct, &sppb.ExecuteSqlRequest{
Sql: "SELECT t.key key, t.value value FROM t_mock t",
ResumeToken: resumeToken,
})
},
nil,
func(error) {})
injected := errors.New("Failed iterator")
err := iter.Do(func(r *Row) error { return injected })
if err != injected {
t.Errorf("got <%v>, want <%v>", err, injected)
}
}
func TestIteratorStopEarly(t *testing.T) {
ctx := context.Background()
restore := setMaxBytesBetweenResumeTokens()
+7 -1
View File
@@ -157,6 +157,11 @@ func extractRetryDelay(err error) (time.Duration, bool) {
// TODO: consider using https://github.com/googleapis/gax-go 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 {
@@ -178,6 +183,7 @@ func runRetryable(ctx context.Context, f func(context.Context) error) error {
if !ok {
b = defaultBackoff.delay(retryCount)
}
tracePrintf(ctx, nil, "Backing off for %s, then retrying", b)
select {
case <-ctx.Done():
return errContextCanceled(ctx, funcErr)
@@ -187,6 +193,6 @@ func runRetryable(ctx context.Context, f func(context.Context) error) error {
continue
}
// Error isn't retryable / no error, return immediately.
return toSpannerError(funcErr)
return funcErr
}
}
+4 -5
View File
@@ -19,7 +19,6 @@ package spanner
import (
"errors"
"fmt"
"reflect"
"testing"
"time"
@@ -60,7 +59,7 @@ func TestRetry(t *testing.T) {
err = runRetryable(context.Background(), func(ct context.Context) error {
return injErr
})
if wantErr := toSpannerError(injErr); !reflect.DeepEqual(err, wantErr) {
if wantErr := toSpannerError(injErr); !testEqual(err, wantErr) {
t.Errorf("runRetryable returns error %v, want %v", err, wantErr)
}
// Timeout
@@ -74,7 +73,7 @@ func TestRetry(t *testing.T) {
return retryErr
})
// Check error code and error message
if wantErrCode, wantErr := codes.DeadlineExceeded, errContextCanceled(ctx, retryErr); ErrCode(err) != wantErrCode || !reflect.DeepEqual(err, wantErr) {
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
@@ -89,7 +88,7 @@ func TestRetry(t *testing.T) {
return retryErr
})
// Check error code, error message, retry count
if wantErrCode, wantErr := codes.Canceled, errContextCanceled(ctx, retryErr); ErrCode(err) != wantErrCode || !reflect.DeepEqual(err, wantErr) || retries != 0 {
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)
}
}
@@ -102,7 +101,7 @@ func TestRetryInfo(t *testing.T) {
retryInfoKey: string(b),
}
gotDelay, ok := extractRetryDelay(errRetry(toSpannerErrorWithMetadata(grpc.Errorf(codes.Aborted, ""), metadata.New(trailers))))
if !ok || !reflect.DeepEqual(time.Second, gotDelay) {
if !ok || !testEqual(time.Second, gotDelay) {
t.Errorf("<ok, retryDelay> = <%t, %v>, want <true, %v>", ok, gotDelay, time.Second)
}
}
+47 -20
View File
@@ -242,7 +242,7 @@ func TestColumnValues(t *testing.T) {
if err != nil {
t.Errorf("\t row.Column(%v, %T) returns error: %v, want nil", i, gotp.Interface(), err)
}
if got := reflect.Indirect(gotp).Interface(); !reflect.DeepEqual(got, want) {
if got := reflect.Indirect(gotp).Interface(); !testEqual(got, want) {
t.Errorf("\t row.Column(%v, %T) retrives %v, want %v", i, gotp.Interface(), got, want)
}
// ColumnByName
@@ -251,7 +251,7 @@ func TestColumnValues(t *testing.T) {
if err != nil {
t.Errorf("\t row.ColumnByName(%v, %T) returns error: %v, want nil", row.fields[i].Name, gotp.Interface(), err)
}
if got := reflect.Indirect(gotp).Interface(); !reflect.DeepEqual(got, want) {
if got := reflect.Indirect(gotp).Interface(); !testEqual(got, want) {
t.Errorf("\t row.ColumnByName(%v, %T) retrives %v, want %v", row.fields[i].Name, gotp.Interface(), got, want)
}
}
@@ -261,7 +261,7 @@ func TestColumnValues(t *testing.T) {
t.Errorf("row.Columns() returns error: %v, want nil", err)
}
for i, want := range wantVals {
if got := reflect.Indirect(reflect.ValueOf(vals[i])).Interface(); !reflect.DeepEqual(got, want) {
if got := reflect.Indirect(reflect.ValueOf(vals[i])).Interface(); !testEqual(got, want) {
t.Errorf("\t got %v(%T) for column[%v], want %v(%T)", got, got, row.fields[i].Name, want, want)
}
}
@@ -339,10 +339,10 @@ func TestNilDst(t *testing.T) {
})(nil)),
},
} {
if gotErr := test.r.Column(0, test.dst); !reflect.DeepEqual(gotErr, test.wantErr) {
if gotErr := test.r.Column(0, test.dst); !testEqual(gotErr, test.wantErr) {
t.Errorf("%v: test.r.Column() returns error %v, want %v", i, gotErr, test.wantErr)
}
if gotErr := test.r.ColumnByName("Col0", test.dst); !reflect.DeepEqual(gotErr, test.wantErr) {
if gotErr := test.r.ColumnByName("Col0", test.dst); !testEqual(gotErr, test.wantErr) {
t.Errorf("%v: test.r.ColumnByName() returns error %v, want %v", i, gotErr, test.wantErr)
}
// Row.Columns(T) should return nil on T == nil, otherwise, it should return test.wantErr.
@@ -350,10 +350,10 @@ func TestNilDst(t *testing.T) {
if test.dst == nil {
wantColumnsErr = nil
}
if gotErr := test.r.Columns(test.dst); !reflect.DeepEqual(gotErr, wantColumnsErr) {
if gotErr := test.r.Columns(test.dst); !testEqual(gotErr, wantColumnsErr) {
t.Errorf("%v: test.r.Columns() returns error %v, want %v", i, gotErr, wantColumnsErr)
}
if gotErr := test.r.ToStruct(test.structDst); !reflect.DeepEqual(gotErr, test.wantToStructErr) {
if gotErr := test.r.ToStruct(test.structDst); !testEqual(gotErr, test.wantToStructErr) {
t.Errorf("%v: test.r.ToStruct() returns error %v, want %v", i, gotErr, test.wantToStructErr)
}
}
@@ -401,7 +401,7 @@ func TestNullTypeErr(t *testing.T) {
},
} {
wantErr := errDecodeColumn(ntoi(test.colName), errDstNotForNull(test.dst))
if gotErr := row.ColumnByName(test.colName, test.dst); !reflect.DeepEqual(gotErr, wantErr) {
if gotErr := row.ColumnByName(test.colName, test.dst); !testEqual(gotErr, wantErr) {
t.Errorf("row.ColumnByName(%v) returns error %v, want %v", test.colName, gotErr, wantErr)
}
}
@@ -418,11 +418,11 @@ func TestColumnTypeErr(t *testing.T) {
etc = f.Type.ArrayElementType.Code
}
wantErr := errDecodeColumn(i, errTypeMismatch(tc, etc, badDst))
if gotErr := row.Column(i, badDst); !reflect.DeepEqual(gotErr, wantErr) {
if gotErr := row.Column(i, badDst); !testEqual(gotErr, wantErr) {
t.Errorf("Column(%v): decoding into destination with wrong type %T returns error %v, want %v",
i, badDst, gotErr, wantErr)
}
if gotErr := row.ColumnByName(f.Name, badDst); !reflect.DeepEqual(gotErr, wantErr) {
if gotErr := row.ColumnByName(f.Name, badDst); !testEqual(gotErr, wantErr) {
t.Errorf("ColumnByName(%v): decoding into destination with wrong type %T returns error %v, want %v",
f.Name, badDst, gotErr, wantErr)
}
@@ -434,7 +434,7 @@ func TestColumnTypeErr(t *testing.T) {
for i := 2; i < len(row.fields); i++ {
vals = append(vals, nil)
}
if gotErr := row.Columns(vals...); !reflect.DeepEqual(gotErr, wantErr) {
if gotErr := row.Columns(vals...); !testEqual(gotErr, wantErr) {
t.Errorf("Columns(): decoding column 1 with wrong type %T returns error %v, want %v",
badDst, gotErr, wantErr)
}
@@ -522,7 +522,7 @@ func TestInvalidColumnRequest(t *testing.T) {
errUnnamedField(&sppb.StructType{Fields: []*sppb.StructType_Field{{"", stringType()}}}, 0),
},
} {
if gotErr := test.f(); !reflect.DeepEqual(gotErr, test.wantErr) {
if gotErr := test.f(); !testEqual(gotErr, test.wantErr) {
t.Errorf("%v: test.f() returns error %v, want %v", test.desc, gotErr, test.wantErr)
}
}
@@ -574,7 +574,7 @@ func TestToStructInvalidDst(t *testing.T) {
errTypeMismatch(sppb.TypeCode_STRING, sppb.TypeCode_TYPE_CODE_UNSPECIFIED, proto.Int64(0))),
},
} {
if gotErr := row.ToStruct(test.dst); !reflect.DeepEqual(gotErr, test.wantErr) {
if gotErr := row.ToStruct(test.dst); !testEqual(gotErr, test.wantErr) {
t.Errorf("%v: decoding:\ngot %v\nwant %v", test.desc, gotErr, test.wantErr)
}
}
@@ -1560,13 +1560,13 @@ func TestBrokenRow(t *testing.T) {
),
},
} {
if gotErr := test.row.Column(0, test.dst); !reflect.DeepEqual(gotErr, test.wantErr) {
if gotErr := test.row.Column(0, test.dst); !testEqual(gotErr, test.wantErr) {
t.Errorf("%v: test.row.Column(0) got error %v, want %v", i, gotErr, test.wantErr)
}
if gotErr := test.row.ColumnByName("Col0", test.dst); !reflect.DeepEqual(gotErr, test.wantErr) {
if gotErr := test.row.ColumnByName("Col0", test.dst); !testEqual(gotErr, test.wantErr) {
t.Errorf("%v: test.row.ColumnByName(%q) got error %v, want %v", i, "Col0", gotErr, test.wantErr)
}
if gotErr := test.row.Columns(test.dst); !reflect.DeepEqual(gotErr, test.wantErr) {
if gotErr := test.row.Columns(test.dst); !testEqual(gotErr, test.wantErr) {
t.Errorf("%v: test.row.Columns(%T) got error %v, want %v", i, test.dst, gotErr, test.wantErr)
}
}
@@ -1684,12 +1684,39 @@ func TestToStruct(t *testing.T) {
err := row.ToStruct(&s[0])
if err != nil {
t.Errorf("row.ToStruct() returns error: %v, want nil", err)
}
if !reflect.DeepEqual(s[0], s[1]) {
} else if !testEqual(s[0], s[1]) {
t.Errorf("row.ToStruct() fetches struct %v, want %v", s[0], s[1])
}
}
func TestToStructEmbedded(t *testing.T) {
type (
S1 struct{ F1 string }
S2 struct {
S1
F2 string
}
)
r := Row{
[]*sppb.StructType_Field{
{"F1", stringType()},
{"F2", stringType()},
},
[]*proto3.Value{
stringProto("v1"),
stringProto("v2"),
},
}
var got S2
if err := r.ToStruct(&got); err != nil {
t.Fatal(err)
}
want := S2{S1: S1{F1: "v1"}, F2: "v2"}
if !testEqual(got, want) {
t.Errorf("got %+v, want %+v", got, want)
}
}
// Test helpers for getting column names.
func TestColumnNameAndIndex(t *testing.T) {
// Test Row.Size().
@@ -1763,11 +1790,11 @@ func TestNewRow(t *testing.T) {
},
} {
got, err := NewRow(test.names, test.values)
if !reflect.DeepEqual(err, test.wantErr) {
if !testEqual(err, test.wantErr) {
t.Errorf("NewRow(%v,%v).err = %s, want %s", test.names, test.values, err, test.wantErr)
continue
}
if !reflect.DeepEqual(got, test.want) {
if !testEqual(got, test.want) {
t.Errorf("NewRow(%v,%v) = %s, want %s", test.names, test.values, got, test.want)
continue
}
+53 -104
View File
@@ -20,12 +20,12 @@ import (
"container/heap"
"container/list"
"fmt"
"log"
"math/rand"
"strings"
"sync"
"time"
log "github.com/golang/glog"
"golang.org/x/net/context"
sppb "google.golang.org/genproto/googleapis/spanner/v1"
@@ -171,60 +171,6 @@ func (s *session) ping() error {
})
}
// refreshIdle refreshes the session's session ID if it is in its home session pool's idle list
// and returns true if successful.
func (s *session) refreshIdle() bool {
s.mu.Lock()
validAndIdle := s.valid && s.idleList != nil
s.mu.Unlock()
if !validAndIdle {
// Optimization: return early if s is not valid or if s is not in idle list.
return false
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
var sid string
err := runRetryable(ctx, func(ctx context.Context) error {
session, e := s.client.CreateSession(contextWithOutgoingMetadata(ctx, s.pool.md), &sppb.CreateSessionRequest{Database: s.pool.db})
if e != nil {
return e
}
sid = session.Name
return nil
})
if err != nil {
return false
}
s.pool.mu.Lock()
s.mu.Lock()
var recycle bool
if s.valid && s.idleList != nil {
// session is in idle list, refresh its session id.
sid, s.id = s.id, sid
s.createTime = time.Now()
if s.tx != nil {
s.tx = nil
s.pool.idleWriteList.Remove(s.idleList)
// We need to put this session back into the pool.
recycle = true
}
}
s.mu.Unlock()
s.pool.mu.Unlock()
if recycle {
s.pool.recycle(s)
}
// If we fail to explicitly destroy the session, it will be eventually garbage collected by
// Cloud Spanner.
if err = runRetryable(ctx, func(ctx context.Context) error {
_, e := s.client.DeleteSession(contextWithOutgoingMetadata(ctx, s.pool.md), &sppb.DeleteSessionRequest{Name: sid})
return e
}); err != nil && log.V(2) {
log.Warningf("Failed to delete session %v. Error: %v", sid, err)
}
return true
}
// setHcIndex atomically sets the session's index in the healthcheck queue and returns the old index.
func (s *session) setHcIndex(i int) int {
s.mu.Lock()
@@ -320,8 +266,8 @@ func (s *session) destroy(isExpire bool) bool {
_, e := s.client.DeleteSession(ctx, &sppb.DeleteSessionRequest{Name: s.getID()})
return e
})
if err != nil && log.V(2) {
log.Warningf("Failed to delete session %v. Error: %v", s.getID(), err)
if err != nil {
log.Printf("Failed to delete session %v. Error: %v", s.getID(), err)
}
return true
}
@@ -352,9 +298,6 @@ type SessionPoolConfig struct {
// to be broken, it will still be evicted from session pool, therefore it is
// posssible that the number of opened sessions drops below MinOpened.
MinOpened uint64
// maxSessionAge is the maximum duration that a session can be reused, zero
// means session pool will never expire sessions.
maxSessionAge time.Duration
// MaxIdle is the maximum number of idle sessions, pool is allowed to keep. Defaults to 0.
MaxIdle uint64
// MaxBurst is the maximum number of concurrent session creation requests. Defaults to 10.
@@ -365,8 +308,6 @@ type SessionPoolConfig struct {
HealthCheckWorkers int
// HealthCheckInterval is how often the health checker pings a session. Defaults to 5 min.
HealthCheckInterval time.Duration
// healthCheckMaintainerEnabled enables the session pool maintainer.
healthCheckMaintainerEnabled bool
// healthCheckSampleInterval is how often the health checker samples live session (for use in maintaining session pool size). Defaults to 1 min.
healthCheckSampleInterval time.Duration
}
@@ -502,6 +443,7 @@ func (p *sessionPool) shouldPrepareWrite() bool {
}
func (p *sessionPool) createSession(ctx context.Context) (*session, error) {
tracePrintf(ctx, nil, "Creating a new session")
doneCreate := func(done bool) {
p.mu.Lock()
if !done {
@@ -555,6 +497,7 @@ func (p *sessionPool) isHealthy(s *session) bool {
// take returns a cached session if there are available ones; if there isn't any, it tries to allocate a new one.
// Session returned by take should be used for read operations.
func (p *sessionPool) take(ctx context.Context) (*sessionHandle, error) {
tracePrintf(ctx, nil, "Acquiring a read-only session")
ctx = contextWithOutgoingMetadata(ctx, p.md)
for {
var (
@@ -570,8 +513,12 @@ func (p *sessionPool) take(ctx context.Context) (*sessionHandle, error) {
if p.idleList.Len() > 0 {
// Idle sessions are available, get one from the top of the idle list.
s = p.idleList.Remove(p.idleList.Front()).(*session)
tracePrintf(ctx, map[string]interface{}{"sessionID": s.getID()},
"Acquired read-only session")
} else if p.idleWriteList.Len() > 0 {
s = p.idleWriteList.Remove(p.idleWriteList.Front()).(*session)
tracePrintf(ctx, map[string]interface{}{"sessionID": s.getID()},
"Acquired read-write session")
}
if s != nil {
s.setIdleList(nil)
@@ -588,8 +535,10 @@ func (p *sessionPool) take(ctx context.Context) (*sessionHandle, error) {
if (p.MaxOpened > 0 && p.numOpened >= p.MaxOpened) || (p.MaxBurst > 0 && p.createReqs >= p.MaxBurst) {
mayGetSession := p.mayGetSession
p.mu.Unlock()
tracePrintf(ctx, nil, "Waiting for read-only session to become available")
select {
case <-ctx.Done():
tracePrintf(ctx, nil, "Context done waiting for session")
return nil, errGetSessionTimeout()
case <-mayGetSession:
}
@@ -600,8 +549,11 @@ func (p *sessionPool) take(ctx context.Context) (*sessionHandle, error) {
p.createReqs++
p.mu.Unlock()
if s, err = p.createSession(ctx); err != nil {
tracePrintf(ctx, nil, "Error creating session: %v", err)
return nil, toSpannerError(err)
}
tracePrintf(ctx, map[string]interface{}{"sessionID": s.getID()},
"Created session")
return &sessionHandle{session: s}, nil
}
}
@@ -609,6 +561,7 @@ func (p *sessionPool) take(ctx context.Context) (*sessionHandle, error) {
// takeWriteSession returns a write prepared cached session if there are available ones; if there isn't any, it tries to allocate a new one.
// Session returned should be used for read write transactions.
func (p *sessionPool) takeWriteSession(ctx context.Context) (*sessionHandle, error) {
tracePrintf(ctx, nil, "Acquiring a read-write session")
ctx = contextWithOutgoingMetadata(ctx, p.md)
for {
var (
@@ -624,8 +577,10 @@ func (p *sessionPool) takeWriteSession(ctx context.Context) (*sessionHandle, err
if p.idleWriteList.Len() > 0 {
// Idle sessions are available, get one from the top of the idle list.
s = p.idleWriteList.Remove(p.idleWriteList.Front()).(*session)
tracePrintf(ctx, map[string]interface{}{"sessionID": s.getID()}, "Acquired read-write session")
} else if p.idleList.Len() > 0 {
s = p.idleList.Remove(p.idleList.Front()).(*session)
tracePrintf(ctx, map[string]interface{}{"sessionID": s.getID()}, "Acquired read-only session")
}
if s != nil {
s.setIdleList(nil)
@@ -636,34 +591,39 @@ func (p *sessionPool) takeWriteSession(ctx context.Context) (*sessionHandle, err
if !p.isHealthy(s) {
continue
}
if !s.isWritePrepared() {
if err = s.prepareForWrite(ctx); err != nil {
return nil, toSpannerError(err)
} else {
// Idle list is empty, block if session pool has reached max session creation concurrency or max number of open sessions.
if (p.MaxOpened > 0 && p.numOpened >= p.MaxOpened) || (p.MaxBurst > 0 && p.createReqs >= p.MaxBurst) {
mayGetSession := p.mayGetSession
p.mu.Unlock()
tracePrintf(ctx, nil, "Waiting for read-write session to become available")
select {
case <-ctx.Done():
tracePrintf(ctx, nil, "Context done waiting for session")
return nil, errGetSessionTimeout()
case <-mayGetSession:
}
continue
}
return &sessionHandle{session: s}, nil
}
// Idle list is empty, block if session pool has reached max session creation concurrency or max number of open sessions.
if (p.MaxOpened > 0 && p.numOpened >= p.MaxOpened) || (p.MaxBurst > 0 && p.createReqs >= p.MaxBurst) {
mayGetSession := p.mayGetSession
p.mu.Unlock()
select {
case <-ctx.Done():
return nil, errGetSessionTimeout()
case <-mayGetSession:
}
continue
}
// Take budget before the actual session creation.
p.numOpened++
p.createReqs++
p.mu.Unlock()
if s, err = p.createSession(ctx); err != nil {
return nil, toSpannerError(err)
// Take budget before the actual session creation.
p.numOpened++
p.createReqs++
p.mu.Unlock()
if s, err = p.createSession(ctx); err != nil {
tracePrintf(ctx, nil, "Error creating session: %v", err)
return nil, toSpannerError(err)
}
tracePrintf(ctx, map[string]interface{}{"sessionID": s.getID()},
"Created session")
}
if err = s.prepareForWrite(ctx); err != nil {
return nil, toSpannerError(err)
if !s.isWritePrepared() {
if err = s.prepareForWrite(ctx); err != nil {
s.recycle()
tracePrintf(ctx, map[string]interface{}{"sessionID": s.getID()},
"Error preparing session for write")
return nil, toSpannerError(err)
}
}
return &sessionHandle{session: s}, nil
}
@@ -677,10 +637,6 @@ func (p *sessionPool) recycle(s *session) bool {
// Reject the session if session is invalid or pool itself is invalid.
return false
}
if p.maxSessionAge != 0 && s.createTime.Add(p.maxSessionAge).Before(time.Now()) && p.numOpened > p.MinOpened {
// session expires and number of opened sessions exceeds MinOpened, let the session destroy itself.
return false
}
// Put session at the back of the list to round robin for load balancing across channels.
if s.isWritePrepared() {
s.setIdleList(p.idleWriteList.PushBack(s))
@@ -796,10 +752,8 @@ func newHealthChecker(interval time.Duration, workers int, sampleInterval time.D
ready: make(chan struct{}),
done: make(chan struct{}),
}
if hc.pool.healthCheckMaintainerEnabled {
hc.waitWorkers.Add(1)
go hc.maintainer()
}
hc.waitWorkers.Add(1)
go hc.maintainer()
for i := 1; i <= hc.workers; i++ {
hc.waitWorkers.Add(1)
go hc.worker(i)
@@ -881,12 +835,6 @@ func (hc *healthChecker) healthCheck(s *session) {
s.destroy(false)
return
}
if s.pool.maxSessionAge != 0 && s.createTime.Add(s.pool.maxSessionAge).Before(time.Now()) {
// Session reaches its maximum age, retire it. Failing that try to refresh it.
if s.destroy(true) || !s.refreshIdle() {
return
}
}
if err := s.ping(); shouldDropSession(err) {
// Ping failed, destroy the session.
s.destroy(false)
@@ -951,8 +899,8 @@ func (hc *healthChecker) worker(i int) {
err := ws.prepareForWrite(contextWithOutgoingMetadata(ctx, hc.pool.md))
cancel()
if err != nil {
// TODO(dixiao): handle error properly
log.Errorf("prepareForWrite failed: %v", err)
// Skip handling prepare error, session can be prepared in next cycle
log.Printf("Failed to prepare session, error: %v", toSpannerError(err))
}
hc.pool.recycle(ws)
hc.pool.mu.Lock()
@@ -1021,12 +969,13 @@ func (hc *healthChecker) maintainer() {
err error
)
if s, err = p.createSession(ctx); err != nil {
log.Warningf("Failed to create session, error: %v", toSpannerError(err))
log.Printf("Failed to create session, error: %v", toSpannerError(err))
continue
}
if shouldPrepareWrite {
if err = s.prepareForWrite(ctx); err != nil {
log.Warningf("Failed to prepare session, error: %v", toSpannerError(err))
p.recycle(s)
log.Printf("Failed to prepare session, error: %v", toSpannerError(err))
continue
}
}
+45 -89
View File
@@ -17,9 +17,9 @@ limitations under the License.
package spanner
import (
"bytes"
"container/heap"
"math/rand"
"reflect"
"sync"
"testing"
"time"
@@ -44,7 +44,6 @@ func setup(t *testing.T, spc SessionPoolConfig) (sp *sessionPool, sc *testutil.M
if spc.healthCheckSampleInterval == 0 {
spc.healthCheckSampleInterval = 10 * time.Millisecond
}
spc.healthCheckMaintainerEnabled = true
sp, err := newSessionPool("mockdb", spc, nil)
if err != nil {
t.Fatalf("cannot create session pool: %v", err)
@@ -75,7 +74,7 @@ func TestSessionCreation(t *testing.T) {
if len(gotDs) != len(shs) {
t.Errorf("session pool created %v sessions, want %v", len(gotDs), len(shs))
}
if wantDs := sc.DumpSessions(); !reflect.DeepEqual(gotDs, wantDs) {
if wantDs := sc.DumpSessions(); !testEqual(gotDs, wantDs) {
t.Errorf("session pool creates sessions %v, want %v", gotDs, wantDs)
}
// Verify that created sessions are recorded correctly in session pool.
@@ -134,7 +133,7 @@ func TestTakeFromIdleList(t *testing.T) {
if len(gotSessions) != 10 {
t.Errorf("got %v unique sessions, want 10", len(gotSessions))
}
if !reflect.DeepEqual(gotSessions, wantSessions) {
if !testEqual(gotSessions, wantSessions) {
t.Errorf("got sessions: %v, want %v", gotSessions, wantSessions)
}
}
@@ -144,10 +143,10 @@ func TestTakeWriteSessionFromIdleList(t *testing.T) {
t.Parallel()
sp, sc, cancel := setup(t, SessionPoolConfig{MaxIdle: 20}) // make sure maintainer keeps the idle sessions
defer cancel()
act := testutil.NewAction("Begin", nil)
acts := make([]testutil.Action, 20)
for i := 0; i < len(acts); i++ {
acts[i] = act
acts[i] = testutil.Action{"BeginTransaction", nil}
}
sc.SetActions(acts...)
// Take ten sessions from session pool and recycle them.
@@ -178,7 +177,7 @@ func TestTakeWriteSessionFromIdleList(t *testing.T) {
if len(gotSessions) != 10 {
t.Errorf("got %v unique sessions, want 10", len(gotSessions))
}
if !reflect.DeepEqual(gotSessions, wantSessions) {
if !testEqual(gotSessions, wantSessions) {
t.Errorf("got sessions: %v, want %v", gotSessions, wantSessions)
}
}
@@ -216,7 +215,7 @@ func TestTakeFromIdleListChecked(t *testing.T) {
}
// The two back-to-back session requests shouldn't trigger any session pings because sessionPool.Take
// reschedules the next healthcheck.
if got, want := sc.DumpPings(), ([]string{wantSid}); !reflect.DeepEqual(got, want) {
if got, want := sc.DumpPings(), ([]string{wantSid}); !testEqual(got, want) {
t.Errorf("%v - got ping session requests: %v, want %v", i, got, want)
}
sh.recycle()
@@ -273,7 +272,7 @@ func TestTakeFromIdleWriteListChecked(t *testing.T) {
}
// The two back-to-back session requests shouldn't trigger any session pings because sessionPool.Take
// reschedules the next healthcheck.
if got, want := sc.DumpPings(), ([]string{wantSid}); !reflect.DeepEqual(got, want) {
if got, want := sc.DumpPings(), ([]string{wantSid}); !testEqual(got, want) {
t.Errorf("%v - got ping session requests: %v, want %v", i, got, want)
}
sh.recycle()
@@ -312,7 +311,7 @@ func TestMaxOpenedSessions(t *testing.T) {
defer cancel()
// Session request will timeout due to the max open sessions constraint.
sh2, gotErr := sp.take(ctx)
if wantErr := errGetSessionTimeout(); !reflect.DeepEqual(gotErr, wantErr) {
if wantErr := errGetSessionTimeout(); !testEqual(gotErr, wantErr) {
t.Errorf("the second session retrival returns error %v, want %v", gotErr, wantErr)
}
go func() {
@@ -389,7 +388,7 @@ func TestMaxBurst(t *testing.T) {
defer cancel()
sh, gotErr := sp.take(ctx)
// Since MaxBurst == 1, the second session request should block.
if wantErr := errGetSessionTimeout(); !reflect.DeepEqual(gotErr, wantErr) {
if wantErr := errGetSessionTimeout(); !testEqual(gotErr, wantErr) {
t.Errorf("session retrival returns error %v, want %v", gotErr, wantErr)
}
// Let the first session request succeed.
@@ -410,10 +409,8 @@ func TestSessionRecycle(t *testing.T) {
if testing.Short() {
t.SkipNow()
}
sp, _, cancel := setup(t, SessionPoolConfig{maxSessionAge: 100 * time.Millisecond, MinOpened: 1, MaxIdle: 2})
sp, _, cancel := setup(t, SessionPoolConfig{MinOpened: 1, MaxIdle: 2})
// Set MaxIdle to ensure shs[0] is not destroyed from scale down.
// Healthcheck is explicitly turned off in this test because it might aggressively expire sessions in idle list.
sp.hc.close()
defer cancel()
// Test session is correctly recycled and reused.
@@ -427,31 +424,6 @@ func TestSessionRecycle(t *testing.T) {
if sp.numOpened != 1 {
t.Errorf("Expect session pool size %d, got %d", 1, sp.numOpened)
}
// Test recycling expired session.
var ss []*session
shs := make([]*sessionHandle, 2)
for i := 0; i < len(shs); i++ {
var err error
shs[i], err = sp.take(context.Background())
if err != nil {
t.Errorf("cannot get the session %v: %v", i, err)
}
ss = append(ss, shs[i].session)
}
// recycle the first session immediately.
shs[0].recycle()
// Let the second session expire.
<-time.After(time.Second)
// recycle the second session.
shs[1].recycle()
// Now the first session should be still valid, but the second session should have been destroyed.
if !ss[0].isValid() {
t.Errorf("the first session (%v) is invalid, want it to be valid", ss[0])
}
if ss[1].isValid() {
t.Errorf("the second session (%v) is valid, want it to be invalid", ss[1])
}
}
// TestSessionDestroy tests destroying sessions.
@@ -459,6 +431,7 @@ func TestSessionDestroy(t *testing.T) {
t.Parallel()
sp, _, cancel := setup(t, SessionPoolConfig{MinOpened: 1})
defer cancel()
<-time.After(10 * time.Millisecond) // maintainer will create one session, we wait for it create session to avoid flakiness in test
sh, err := sp.take(context.Background())
if err != nil {
t.Errorf("cannot get session from session pool: %v", err)
@@ -467,11 +440,11 @@ func TestSessionDestroy(t *testing.T) {
sh.recycle()
if d := s.destroy(true); d || !s.isValid() {
// Session should be remaining because of min open sessions constraint.
t.Errorf("session %v was destroyed in expiration mode, want it to stay alive", s)
t.Errorf("session %v invalid, want it to stay alive. (destroy in expiration mode, success: %v)", s, d)
}
if d := s.destroy(false); !d || s.isValid() {
// Session should be destroyed.
t.Errorf("failed to destroy session %s", s)
t.Errorf("failed to destroy session %v. (destroy in default mode, success: %v)", s, d)
}
}
@@ -501,7 +474,7 @@ func TestHcHeap(t *testing.T) {
for idx := 0; hh.Len() > 0; idx++ {
got := heap.Pop(&hh).(*session)
want[idx].hcIndex = -1
if !reflect.DeepEqual(got, want[idx]) {
if !testEqual(got, want[idx]) {
t.Errorf("%v: heap.Pop returns %v, want %v", idx, got, want[idx])
}
}
@@ -629,7 +602,7 @@ func TestSessionHealthCheck(t *testing.T) {
if testing.Short() {
t.SkipNow()
}
sp, sc, cancel := setup(t, SessionPoolConfig{maxSessionAge: 2 * time.Second})
sp, sc, cancel := setup(t, SessionPoolConfig{})
defer cancel()
// Test pinging sessions.
sh, err := sp.take(context.Background())
@@ -641,14 +614,6 @@ func TestSessionHealthCheck(t *testing.T) {
if len(pings) == 0 || pings[0] != sh.getID() {
t.Errorf("healthchecker didn't send any ping to session %v", sh.getID())
}
// Test expiring sessions.
s := sh.session
sh.recycle()
// Sleep enough long for session in idle list to expire.
<-time.After(2 * time.Second)
if s.isValid() {
t.Errorf("session(%v) is still alive, want it to expire", s)
}
// Test broken session detection.
sh, err = sp.take(context.Background())
if err != nil {
@@ -657,6 +622,7 @@ func TestSessionHealthCheck(t *testing.T) {
sc.InjectError("GetSession", grpc.Errorf(codes.NotFound, "Session not found:"))
// Wait for healthcheck workers to find the broken session and tear it down.
<-time.After(1 * time.Second)
s := sh.session
if sh.session.isValid() {
t.Errorf("session(%v) is still alive, want it to be dropped by healthcheck workers", s)
}
@@ -670,43 +636,20 @@ func TestSessionHealthCheck(t *testing.T) {
if sh.session.isValid() {
t.Errorf("session(%v) is still alive, want it to be garbage collected", s)
}
// Test session id refresh.
// Recreate the session pool with min open sessions constraint.
sp, err = newSessionPool("mockdb", SessionPoolConfig{
maxSessionAge: time.Second,
MinOpened: 1,
getRPCClient: func() (sppb.SpannerClient, error) {
return sc, nil
},
HealthCheckInterval: 50 * time.Millisecond,
}, nil)
sh, err = sp.take(context.Background())
if err != nil {
t.Errorf("cannot get session from session pool: %v", err)
}
oid := sh.getID()
s = sh.session
sh.recycle()
<-time.After(2 * time.Second)
nid := s.getID()
if nid == "" || nid == oid {
t.Errorf("healthcheck workers failed to refresh session: oid=%v, nid=%v", oid, nid)
}
if gotDs, wantDs := sc.DumpSessions(), (map[string]bool{nid: true}); !reflect.DeepEqual(gotDs, wantDs) {
t.Errorf("sessions in mockclient: %v, want %v", gotDs, wantDs)
}
}
// TestStressSessionPool does stress test on session pool by the following concurrent operations:
// 1) Test worker gets a session from the pool.
// 2) Test worker turns a session back into the pool.
// 3) Test worker destroys a session got from the pool.
// 4) Healthcheck retires an old session from the pool's idlelist by refreshing its session id.
// 5) Healthcheck destroys a broken session (because a worker has already destroyed it).
// 6) Test worker closes the session pool.
// 4) Healthcheck destroys a broken session (because a worker has already destroyed it).
// 5) Test worker closes the session pool.
//
// During the test, it is expected that all sessions that are taken from session pool remains valid and
// when all test workers and healthcheck workers exit, mockclient, session pool and healthchecker should be in consistent state.
// During the test, the session pool maintainer maintains the number of sessions,
// and it is expected that all sessions that are taken from session pool remains valid.
// When all test workers and healthcheck workers exit, mockclient, session pool
// and healthchecker should be in consistent state.
func TestStressSessionPool(t *testing.T) {
t.Parallel()
// Use concurrent workers to test different session pool built from different configurations.
@@ -715,11 +658,10 @@ func TestStressSessionPool(t *testing.T) {
}
for ti, cfg := range []SessionPoolConfig{
SessionPoolConfig{},
SessionPoolConfig{maxSessionAge: 20 * time.Millisecond},
SessionPoolConfig{MinOpened: 10, MaxOpened: 100},
SessionPoolConfig{MaxBurst: 50},
SessionPoolConfig{maxSessionAge: 20 * time.Millisecond, MinOpened: 10, MaxOpened: 200, MaxBurst: 5},
SessionPoolConfig{maxSessionAge: 20 * time.Millisecond, MinOpened: 10, MaxOpened: 200, MaxBurst: 5, WriteSessions: 0.2},
SessionPoolConfig{MinOpened: 10, MaxOpened: 200, MaxBurst: 5},
SessionPoolConfig{MinOpened: 10, MaxOpened: 200, MaxBurst: 5, WriteSessions: 0.2},
} {
var wg sync.WaitGroup
// Create a more aggressive session healthchecker to increase test concurrency.
@@ -758,7 +700,7 @@ func TestStressSessionPool(t *testing.T) {
if pool.isValid() {
t.Errorf("%v.%v: pool.take returns error when pool is still valid: %v", ti, idx, gotErr)
}
if wantErr := errInvalidSessionPool(); !reflect.DeepEqual(gotErr, wantErr) {
if wantErr := errInvalidSessionPool(); !testEqual(gotErr, wantErr) {
t.Errorf("%v.%v: got error when pool is closed: %v, want %v", ti, idx, gotErr, wantErr)
}
continue
@@ -771,9 +713,9 @@ func TestStressSessionPool(t *testing.T) {
if takeWrite && sh.getTransactionID() == nil {
t.Errorf("%v.%v: pool.takeWriteSession returns session %v without transaction", ti, idx, sh.session)
}
if int64(cfg.maxSessionAge) > 0 && rand.Intn(100) < idx {
if rand.Intn(100) < idx {
// Random sleep before destroying/recycling the session, to give healthcheck worker a chance to step in.
<-time.After(time.Duration(rand.Int63n(int64(cfg.maxSessionAge))))
<-time.After(time.Duration(rand.Int63n(int64(cfg.HealthCheckInterval))))
}
if rand.Intn(100) < idx {
// destroy the session.
@@ -823,10 +765,10 @@ func TestStressSessionPool(t *testing.T) {
sp.mu.Unlock()
// Verify that idleSessions == hcSessions == mockSessions.
if !reflect.DeepEqual(idleSessions, hcSessions) {
if !testEqual(idleSessions, hcSessions) {
t.Errorf("%v: sessions in idle list (%v) != sessions in healthcheck queue (%v)", ti, idleSessions, hcSessions)
}
if !reflect.DeepEqual(hcSessions, mockSessions) {
if !testEqual(hcSessions, mockSessions) {
t.Errorf("%v: sessions in healthcheck queue (%v) != sessions in mockclient (%v)", ti, hcSessions, mockSessions)
}
sp.close()
@@ -899,3 +841,17 @@ func TestMaintainer(t *testing.T) {
}
sp.mu.Unlock()
}
func (s1 *session) Equal(s2 *session) bool {
return s1.client == s2.client &&
s1.id == s2.id &&
s1.pool == s2.pool &&
s1.createTime == s2.createTime &&
s1.valid == s2.valid &&
s1.hcIndex == s2.hcIndex &&
s1.idleList == s2.idleList &&
s1.nextCheck.Equal(s2.nextCheck) &&
s1.checkingHealth == s2.checkingHealth &&
testEqual(s1.md, s2.md) &&
bytes.Equal(s1.tx, s2.tx)
}
+74 -18
View File
@@ -114,7 +114,7 @@ func initIntegrationTest() {
ctx := context.Background()
ts := testutil.TokenSource(ctx, AdminScope, Scope)
if ts == nil {
log.Print("Integration test skipped: cannot get service account credential from environment variable %v", "GCLOUD_TESTS_GOLANG_KEY")
log.Printf("Integration test skipped: cannot get service account credential from environment variable %v", "GCLOUD_TESTS_GOLANG_KEY")
return
}
var err error
@@ -164,7 +164,8 @@ func prepare(ctx context.Context, t *testing.T, statements []string) (client *Cl
}
return client, dbPath, func() {
if err := admin.DropDatabase(ctx, &adminpb.DropDatabaseRequest{dbPath}); err != nil {
t.Logf("failed to drop testing database: %v, might need a manual removal", dbPath)
t.Logf("failed to drop database %s (error %v), might need a manual removal",
dbPath, err)
}
client.Close()
}
@@ -173,7 +174,7 @@ func prepare(ctx context.Context, t *testing.T, statements []string) (client *Cl
// Test SingleUse transaction.
func TestSingleUse(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), 45*time.Second)
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
// Set up testing environment.
client, _, tearDown := prepare(ctx, t, singerDBStatements)
@@ -278,7 +279,7 @@ func TestSingleUse(t *testing.T) {
if err != nil {
t.Errorf("%d: SingleUse.Query returns error %v, want nil", i, err)
}
if !reflect.DeepEqual(got, test.want) {
if !testEqual(got, test.want) {
t.Errorf("%d: got unexpected result from SingleUse.Query: %v, want %v", i, got, test.want)
}
rts, err := su.Timestamp()
@@ -294,7 +295,7 @@ func TestSingleUse(t *testing.T) {
if err != nil {
t.Errorf("%d: SingleUse.Read returns error %v, want nil", i, err)
}
if !reflect.DeepEqual(got, test.want) {
if !testEqual(got, test.want) {
t.Errorf("%d: got unexpected result from SingleUse.Read: %v, want %v", i, got, test.want)
}
rts, err = su.Timestamp()
@@ -325,7 +326,7 @@ func TestSingleUse(t *testing.T) {
t.Errorf("%d: SingleUse.ReadRow(%v) doesn't return expected timestamp: %v", i, k, err)
}
}
if !reflect.DeepEqual(got, test.want) {
if !testEqual(got, test.want) {
t.Errorf("%d: got unexpected results from SingleUse.ReadRow: %v, want %v", i, got, test.want)
}
// SingleUse.ReadUsingIndex
@@ -348,7 +349,7 @@ func TestSingleUse(t *testing.T) {
}
found := false
for _, w := range test.want {
if reflect.DeepEqual(g, w) {
if testEqual(g, w) {
found = true
}
}
@@ -365,6 +366,19 @@ func TestSingleUse(t *testing.T) {
t.Errorf("%d: SingleUse.ReadUsingIndex doesn't return expected timestamp: %v", i, err)
}
}
// Reading with limit.
su := client.Single()
const limit = 1
gotRows, err := readAll(su.ReadWithOptions(ctx, "Singers", KeySets(Key{1}, Key{3}, Key{4}),
[]string{"SingerId", "FirstName", "LastName"}, &ReadOptions{Limit: limit}))
if err != nil {
t.Errorf("SingleUse.ReadWithOptions returns error %v, want nil", err)
}
if got, want := len(gotRows), limit; got != want {
t.Errorf("got %d, want %d", got, want)
}
}
// Test ReadOnlyTransaction. The testsuite is mostly like SingleUse, except it
@@ -455,7 +469,7 @@ func TestReadOnlyTransaction(t *testing.T) {
if err != nil {
t.Errorf("%d: ReadOnlyTransaction.Query returns error %v, want nil", i, err)
}
if !reflect.DeepEqual(got, test.want) {
if !testEqual(got, test.want) {
t.Errorf("%d: got unexpected result from ReadOnlyTransaction.Query: %v, want %v", i, got, test.want)
}
rts, err := ro.Timestamp()
@@ -471,7 +485,7 @@ func TestReadOnlyTransaction(t *testing.T) {
if err != nil {
t.Errorf("%d: ReadOnlyTransaction.Read returns error %v, want nil", i, err)
}
if !reflect.DeepEqual(got, test.want) {
if !testEqual(got, test.want) {
t.Errorf("%d: got unexpected result from ReadOnlyTransaction.Read: %v, want %v", i, got, test.want)
}
rts, err = ro.Timestamp()
@@ -507,7 +521,7 @@ func TestReadOnlyTransaction(t *testing.T) {
t.Errorf("%d: got two read timestamps: %v, %v, want ReadOnlyTransaction to return always the same read timestamp", i, roTs, rts)
}
}
if !reflect.DeepEqual(got, test.want) {
if !testEqual(got, test.want) {
t.Errorf("%d: got unexpected results from ReadOnlyTransaction.ReadRow: %v, want %v", i, got, test.want)
}
// SingleUse.ReadUsingIndex
@@ -529,7 +543,7 @@ func TestReadOnlyTransaction(t *testing.T) {
}
found := false
for _, w := range test.want {
if reflect.DeepEqual(g, w) {
if testEqual(g, w) {
found = true
}
}
@@ -587,7 +601,7 @@ func TestUpdateDuringRead(t *testing.T) {
func TestReadWriteTransaction(t *testing.T) {
t.Parallel()
// Give a longer deadline because of transaction backoffs.
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)
defer cancel()
client, _, tearDown := prepare(ctx, t, singerDBStatements)
defer tearDown()
@@ -832,7 +846,7 @@ func compareRows(iter *RowIterator, wantNums []int) (string, bool) {
for _, r := range rows {
got[r.Key] = r.StringValue
}
if !reflect.DeepEqual(got, want) {
if !testEqual(got, want) {
return fmt.Sprintf("got %v, want %v", got, want), false
}
return "", true
@@ -842,7 +856,7 @@ func TestEarlyTimestamp(t *testing.T) {
t.Parallel()
// Test that we can get the timestamp from a read-only transaction as
// soon as we have read at least one row.
ctx, cancel := context.WithTimeout(context.Background(), 45*time.Second)
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
// Set up testing environment.
client, _, tearDown := prepare(ctx, t, readDBStatements)
@@ -1092,7 +1106,7 @@ func TestBasicTypes(t *testing.T) {
}
// Check non-NaN cases.
if !reflect.DeepEqual(got, want) {
if !testEqual(got, want) {
t.Errorf("%d: col:%v val:%#v, got %#v, want %#v", i, test.col, test.val, got, want)
continue
}
@@ -1164,7 +1178,7 @@ func TestStructTypes(t *testing.T) {
},
},
}
if !reflect.DeepEqual(want, s) {
if !testEqual(want, s) {
return fmt.Errorf("unexpected decoding result: %v, want %v", s, want)
}
return nil
@@ -1235,12 +1249,54 @@ func TestQueryExpressions(t *testing.T) {
if isNaN(got) && isNaN(test.want) {
continue
}
if !reflect.DeepEqual(got, test.want) {
if !testEqual(got, test.want) {
t.Errorf("%q\n got %#v\nwant %#v", test.expr, got, test.want)
}
}
}
func TestQueryStats(t *testing.T) {
t.Parallel()
ctx := context.Background()
client, _, tearDown := prepare(ctx, t, singerDBStatements)
defer tearDown()
accounts := []*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 _, err := client.Apply(ctx, accounts, ApplyAtLeastOnce()); err != nil {
t.Fatal(err)
}
const sql = "SELECT Balance FROM Accounts"
qp, err := client.Single().AnalyzeQuery(ctx, Statement{sql, nil})
if err != nil {
t.Fatal(err)
}
if len(qp.PlanNodes) == 0 {
t.Error("got zero plan nodes, expected at least one")
}
iter := client.Single().QueryWithStats(ctx, Statement{sql, nil})
defer iter.Stop()
for {
_, err := iter.Next()
if err == iterator.Done {
break
}
if err != nil {
t.Fatal(err)
}
}
if iter.QueryPlan == nil {
t.Error("got nil QueryPlan, expected one")
}
if iter.QueryStats == nil {
t.Error("got nil QueryStats, expected some")
}
}
func isNaN(x interface{}) bool {
f, ok := x.(float64)
if !ok {
@@ -1260,7 +1316,7 @@ func TestInvalidDatabase(t *testing.T) {
ctx := context.Background()
ts := testutil.TokenSource(ctx, Scope)
if ts == nil {
t.Skip("Integration test skipped: cannot get service account credential from environment variable %v", "GCLOUD_TESTS_GOLANG_KEY")
t.Skip("Integration test skipped: cannot get service account credential from environment variable GCLOUD_TESTS_GOLANG_KEY")
}
db := fmt.Sprintf("projects/%v/instances/%v/databases/invalid", testProjectID, testInstanceID)
c, err := NewClient(ctx, db, option.WithTokenSource(ts))
+1 -2
View File
@@ -18,7 +18,6 @@ package spanner
import (
"math"
"reflect"
"testing"
"time"
@@ -158,7 +157,7 @@ func TestBindParams(t *testing.T) {
} {
st.Params["var"] = test.val
var got sppb.ExecuteSqlRequest
if err := st.bindParams(&got); !reflect.DeepEqual(err, test.wantErr) {
if err := st.bindParams(&got); !testEqual(err, test.wantErr) {
t.Errorf("value %#v:\ngot: %v\nwant: %v", test.val, err, test.wantErr)
}
}
+8 -9
View File
@@ -17,7 +17,6 @@ limitations under the License.
package spanner
import (
"reflect"
"testing"
"time"
@@ -31,7 +30,7 @@ import (
func TestStrong(t *testing.T) {
got := StrongRead()
want := TimestampBound{mode: strong}
if !reflect.DeepEqual(got, want) {
if !testEqual(got, want) {
t.Errorf("Strong() = %v; want %v", got, want)
}
}
@@ -40,7 +39,7 @@ func TestStrong(t *testing.T) {
func TestExactStaleness(t *testing.T) {
got := ExactStaleness(10 * time.Second)
want := TimestampBound{mode: exactStaleness, d: 10 * time.Second}
if !reflect.DeepEqual(got, want) {
if !testEqual(got, want) {
t.Errorf("ExactStaleness(10*time.Second) = %v; want %v", got, want)
}
}
@@ -49,7 +48,7 @@ func TestExactStaleness(t *testing.T) {
func TestMaxStaleness(t *testing.T) {
got := MaxStaleness(10 * time.Second)
want := TimestampBound{mode: maxStaleness, d: 10 * time.Second}
if !reflect.DeepEqual(got, want) {
if !testEqual(got, want) {
t.Errorf("MaxStaleness(10*time.Second) = %v; want %v", got, want)
}
}
@@ -59,7 +58,7 @@ func TestMinReadTimestamp(t *testing.T) {
ts := time.Now()
got := MinReadTimestamp(ts)
want := TimestampBound{mode: minReadTimestamp, t: ts}
if !reflect.DeepEqual(got, want) {
if !testEqual(got, want) {
t.Errorf("MinReadTimestamp(%v) = %v; want %v", ts, got, want)
}
}
@@ -69,7 +68,7 @@ func TestReadTimestamp(t *testing.T) {
ts := time.Now()
got := ReadTimestamp(ts)
want := TimestampBound{mode: readTimestamp, t: ts}
if !reflect.DeepEqual(got, want) {
if !testEqual(got, want) {
t.Errorf("ReadTimestamp(%v) = %v; want %v", ts, got, want)
}
}
@@ -125,7 +124,7 @@ func TestDurationProto(t *testing.T) {
}
for _, test := range tests {
got := durationProto(test.d)
if !reflect.DeepEqual(got, &test.want) {
if !testEqual(got, &test.want) {
t.Errorf("durationProto(%v) = %v; want %v", test.d, got, test.want)
}
}
@@ -143,7 +142,7 @@ func TestTimeProto(t *testing.T) {
}
for _, test := range tests {
got := timestampProto(test.t)
if !reflect.DeepEqual(got, &test.want) {
if !testEqual(got, &test.want) {
t.Errorf("timestampProto(%v) = %v; want %v", test.t, got, test.want)
}
}
@@ -201,7 +200,7 @@ func TestBuildTransactionOptionsReadOnly(t *testing.T) {
}
for _, test := range tests {
got := buildTransactionOptionsReadOnly(test.tb, test.ts)
if !reflect.DeepEqual(got, &test.want) {
if !testEqual(got, &test.want) {
t.Errorf("buildTransactionOptionsReadOnly(%v,%v) = %v; want %v", test.tb, test.ts, got, test.want)
}
}
+68 -10
View File
@@ -56,17 +56,30 @@ func errSessionClosed(sh *sessionHandle) error {
// 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 {
// ReadUsingIndex will use primary index if an empty index name is provided.
return t.ReadUsingIndex(ctx, table, "", keys, columns)
return t.ReadWithOptions(ctx, table, keys, columns, nil)
}
// ReadUsingIndex returns a RowIterator for reading multiple rows from the database
// using an index.
//
// Currently, this function 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.
func (t *txReadOnly) ReadUsingIndex(ctx context.Context, table, index string, keys KeySet, columns []string) *RowIterator {
// 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 = traceStartSpan(ctx, "cloud.google.com/go/spanner.Read")
defer func() { traceEndSpan(ctx, ri.err) }()
var (
sh *sessionHandle
ts *sppb.TransactionSelector
@@ -85,6 +98,14 @@ func (t *txReadOnly) ReadUsingIndex(ctx context.Context, table, index string, ke
// 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) {
@@ -97,6 +118,7 @@ func (t *txReadOnly) ReadUsingIndex(ctx context.Context, table, index string, ke
Columns: columns,
KeySet: kset,
ResumeToken: resumeToken,
Limit: int64(limit),
})
},
t.setTimestamp,
@@ -129,7 +151,42 @@ func (t *txReadOnly) ReadRow(ctx context.Context, table string, key Key, columns
// 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 query 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)
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 = traceStartSpan(ctx, "cloud.google.com/go/spanner.Query")
defer func() { traceEndSpan(ctx, ri.err) }()
var (
sh *sessionHandle
ts *sppb.TransactionSelector
@@ -148,6 +205,7 @@ func (t *txReadOnly) Query(ctx context.Context, statement Statement) *RowIterato
Session: sid,
Transaction: ts,
Sql: statement.SQL,
QueryMode: mode,
}
if err := statement.bindParams(req); err != nil {
return &RowIterator{err: err}
@@ -742,7 +800,7 @@ func (t *ReadWriteTransaction) runInTransaction(ctx context.Context, f func(cont
return ts, err
}
// err == nil, return commit timestamp.
return ts, err
return ts, nil
}
// writeOnlyTransaction provides the most efficient way of doing write-only transactions. It essentially does blind writes to Cloud Spanner.
+52 -39
View File
@@ -18,7 +18,6 @@ package spanner
import (
"errors"
"reflect"
"sync"
"testing"
"time"
@@ -60,12 +59,12 @@ func TestReadOnlyAcquire(t *testing.T) {
t.Parallel()
_, mc, client := mockClient(t)
defer client.Close()
acts := []testutil.Action{
testutil.NewAction("Begin", errUsr),
testutil.NewAction("Begin", nil),
testutil.NewAction("Begin", nil),
}
mc.SetActions(acts...)
mc.SetActions(
testutil.Action{"BeginTransaction", errUsr},
testutil.Action{"BeginTransaction", nil},
testutil.Action{"BeginTransaction", nil},
)
// Singleuse should only be used once.
txn := client.Single()
defer txn.Close()
@@ -74,13 +73,13 @@ func TestReadOnlyAcquire(t *testing.T) {
t.Errorf("Acquire for single use, got %v, want nil.", e)
}
_, _, e = txn.acquire(context.Background())
if wantErr := errTxClosed(); !reflect.DeepEqual(e, wantErr) {
if wantErr := errTxClosed(); !testEqual(e, wantErr) {
t.Errorf("Second acquire for single use, got %v, want %v.", e, wantErr)
}
// Multiuse can recover from acquire failure.
txn = client.ReadOnlyTransaction()
_, _, e = txn.acquire(context.Background())
if wantErr := toSpannerError(errUsr); !reflect.DeepEqual(e, wantErr) {
if wantErr := toSpannerError(errUsr); !testEqual(e, wantErr) {
t.Errorf("Acquire for multi use, got %v, want %v.", e, wantErr)
}
_, _, e = txn.acquire(context.Background())
@@ -90,7 +89,7 @@ func TestReadOnlyAcquire(t *testing.T) {
txn.Close()
// Multiuse can not be used after close.
_, _, e = txn.acquire(context.Background())
if wantErr := errTxClosed(); !reflect.DeepEqual(e, wantErr) {
if wantErr := errTxClosed(); !testEqual(e, wantErr) {
t.Errorf("Second acquire for multi use, got %v, want %v.", e, wantErr)
}
// Multiuse can be acquired concurrently.
@@ -118,10 +117,10 @@ func TestReadOnlyAcquire(t *testing.T) {
<-time.After(100 * time.Millisecond)
mc.Unfreeze()
wg.Wait()
if !reflect.DeepEqual(sh1, sh2) {
if !testEqual(sh1.session, sh2.session) {
t.Errorf("Expect acquire to get same session handle, got %v and %v.", sh1, sh2)
}
if !reflect.DeepEqual(ts1, ts2) {
if !testEqual(ts1, ts2) {
t.Errorf("Expect acquire to get same transaction selector, got %v and %v.", ts1, ts2)
}
}
@@ -132,11 +131,11 @@ func TestRetryOnAbort(t *testing.T) {
_, mc, client := mockClient(t)
defer client.Close()
// commit in writeOnlyTransaction
acts := []testutil.Action{
testutil.NewAction("Commit", errAbrt), // abort on first commit
testutil.NewAction("Commit", nil),
}
mc.SetActions(acts...)
mc.SetActions(
testutil.Action{"Commit", errAbrt}, // abort on first commit
testutil.Action{"Commit", nil},
)
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)}),
@@ -145,14 +144,14 @@ func TestRetryOnAbort(t *testing.T) {
t.Errorf("applyAtLeastOnce retry on abort, got %v, want nil.", e)
}
// begin and commit in ReadWriteTransaction
acts = []testutil.Action{
testutil.NewAction("Begin", nil), // let takeWriteSession succeed and get a session handle
testutil.NewAction("Commit", errAbrt), // let first commit fail and retry will begin new transaction
testutil.NewAction("Begin", errAbrt), // this time we can fail the begin attempt
testutil.NewAction("Begin", nil),
testutil.NewAction("Commit", nil),
}
mc.SetActions(acts...)
mc.SetActions(
testutil.Action{"BeginTransaction", nil}, // let takeWriteSession succeed and get a session handle
testutil.Action{"Commit", errAbrt}, // let first commit fail and retry will begin new transaction
testutil.Action{"BeginTransaction", errAbrt}, // this time we can fail the begin attempt
testutil.Action{"BeginTransaction", nil},
testutil.Action{"Commit", nil},
)
if _, e := client.Apply(context.Background(), ms); e != nil {
t.Errorf("ReadWriteTransaction retry on abort, got %v, want nil.", e)
}
@@ -176,22 +175,21 @@ func TestBadSession(t *testing.T) {
wantErr := spannerErrorf(codes.NotFound, "Session not found: %v", sid)
// ReadOnlyTransaction
acts := []testutil.Action{
testutil.NewAction("Begin", wantErr),
testutil.NewAction("Begin", wantErr),
testutil.NewAction("Begin", wantErr),
}
mc.SetActions(acts...)
mc.SetActions(
testutil.Action{"BeginTransaction", wantErr},
testutil.Action{"BeginTransaction", wantErr},
testutil.Action{"BeginTransaction", wantErr},
)
txn := client.ReadOnlyTransaction()
defer txn.Close()
if _, _, got := txn.acquire(ctx); !reflect.DeepEqual(wantErr, got) {
if _, _, got := txn.acquire(ctx); !testEqual(wantErr, got) {
t.Errorf("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")); !reflect.DeepEqual(wantErr, got.err) {
if got := txn.Query(ctx, NewStatement("SELECT 1")); !testEqual(wantErr, got.err) {
t.Errorf("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"}); !reflect.DeepEqual(wantErr, got.err) {
if got := txn.Read(ctx, "Users", KeySets(Key{"alice"}, Key{"bob"}), []string{"name", "email"}); !testEqual(wantErr, got.err) {
t.Errorf("Expect Read to fail, got %v, want %v.", got.err, wantErr)
}
// writeOnlyTransaction
@@ -199,11 +197,26 @@ func TestBadSession(t *testing.T) {
Insert("Accounts", []string{"AccountId", "Nickname", "Balance"}, []interface{}{int64(1), "Foo", int64(50)}),
Insert("Accounts", []string{"AccountId", "Nickname", "Balance"}, []interface{}{int64(2), "Bar", int64(1)}),
}
acts = []testutil.Action{
testutil.NewAction("Commit", wantErr),
}
mc.SetActions(acts...)
if _, got := client.Apply(context.Background(), ms, ApplyAtLeastOnce()); !reflect.DeepEqual(wantErr, got) {
mc.SetActions(testutil.Action{"Commit", wantErr})
if _, got := client.Apply(context.Background(), ms, ApplyAtLeastOnce()); !testEqual(wantErr, got) {
t.Errorf("Expect applyAtLeastOnce to fail, got %v, want %v.", got, wantErr)
}
}
func TestFunctionErrorReturned(t *testing.T) {
t.Parallel()
_, mc, client := mockClient(t)
defer client.Close()
mc.SetActions(
testutil.Action{"BeginTransaction", nil},
testutil.Action{"Rollback", nil},
)
want := errors.New("an error")
_, got := client.ReadWriteTransaction(context.Background(),
func(context.Context, *ReadWriteTransaction) error { return want })
if got != want {
t.Errorf("got <%v>, want <%v>", got, want)
}
mc.CheckActionsConsumed()
}
+27
View File
@@ -0,0 +1,27 @@
/*
Copyright 2017 Google Inc. All Rights Reserved.
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{}))
}
+4 -4
View File
@@ -137,10 +137,10 @@ func TestEncodeValue(t *testing.T) {
if err != nil {
t.Fatalf("#%d: got error during encoding: %v, want nil", i, err)
}
if !reflect.DeepEqual(got, test.want) {
if !testEqual(got, test.want) {
t.Errorf("#%d: got encode result: %v, want %v", i, got, test.want)
}
if !reflect.DeepEqual(gotType, test.wantType) {
if !testEqual(gotType, test.wantType) {
t.Errorf("#%d: got encode type: %v, want %v", i, gotType, test.wantType)
}
}
@@ -415,7 +415,7 @@ func TestDecodeValue(t *testing.T) {
continue
}
got := reflect.Indirect(gotp).Interface()
if !reflect.DeepEqual(got, test.want) {
if !testEqual(got, test.want) {
t.Errorf("%d: unexpected decoding result - got %v, want %v", i, got, test.want)
continue
}
@@ -513,7 +513,7 @@ func TestGenericColumnValue(t *testing.T) {
t.Errorf("NewGenericColumnValue failed: %v", err)
continue
}
if !reflect.DeepEqual(*v, test.in) {
if !testEqual(*v, test.in) {
t.Errorf("unexpected encode result - got %v, want %v", v, test.in)
}
}