diff --git a/google-cloud-clients/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseAdminClientImpl.java b/google-cloud-clients/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseAdminClientImpl.java new file mode 100644 index 000000000000..b9f7ce682046 --- /dev/null +++ b/google-cloud-clients/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseAdminClientImpl.java @@ -0,0 +1,187 @@ +/* + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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 com.google.cloud.spanner; + +import com.google.api.core.ApiFunction; +import com.google.api.gax.grpc.ProtoOperationTransformers; +import com.google.api.gax.longrunning.OperationFuture; +import com.google.api.gax.longrunning.OperationFutureImpl; +import com.google.api.gax.longrunning.OperationSnapshot; +import com.google.api.gax.paging.Page; +import com.google.cloud.spanner.Options.ListOption; +import com.google.cloud.spanner.SpannerImpl.PageFetcher; +import com.google.cloud.spanner.spi.v1.SpannerRpc; +import com.google.cloud.spanner.spi.v1.SpannerRpc.Paginated; +import com.google.common.base.Preconditions; +import com.google.protobuf.Empty; +import com.google.spanner.admin.database.v1.CreateDatabaseMetadata; +import com.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.Callable; +import javax.annotation.Nullable; + +/** Default implementation of {@link DatabaseAdminClient}. */ +class DatabaseAdminClientImpl implements DatabaseAdminClient { + private final String projectId; + private final SpannerRpc rpc; + + DatabaseAdminClientImpl(String projectId, SpannerRpc rpc) { + this.projectId = projectId; + this.rpc = rpc; + } + + /** Generates a random operation id for long-running database operations. */ + private static String randomOperationId() { + UUID uuid = UUID.randomUUID(); + return ("r" + uuid.toString()).replace("-", "_"); + } + + @Override + public OperationFuture createDatabase( + String instanceId, String databaseId, Iterable statements) throws SpannerException { + // CreateDatabase() is not idempotent, so we're not retrying this request. + String instanceName = getInstanceName(instanceId); + String createStatement = "CREATE DATABASE `" + databaseId + "`"; + OperationFuture + rawOperationFuture = rpc.createDatabase(instanceName, createStatement, statements); + return new OperationFutureImpl( + rawOperationFuture.getPollingFuture(), + rawOperationFuture.getInitialFuture(), + new ApiFunction() { + @Override + public Database apply(OperationSnapshot snapshot) { + return Database.fromProto( + ProtoOperationTransformers.ResponseTransformer.create( + com.google.spanner.admin.database.v1.Database.class) + .apply(snapshot), + DatabaseAdminClientImpl.this); + } + }, + ProtoOperationTransformers.MetadataTransformer.create(CreateDatabaseMetadata.class), + new ApiFunction() { + @Override + public Database apply(Exception e) { + throw SpannerExceptionFactory.newSpannerException(e); + } + }); + } + + @Override + public Database getDatabase(String instanceId, String databaseId) throws SpannerException { + final String dbName = getDatabaseName(instanceId, databaseId); + Callable callable = + new Callable() { + @Override + public Database call() throws Exception { + return Database.fromProto(rpc.getDatabase(dbName), DatabaseAdminClientImpl.this); + } + }; + return SpannerImpl.runWithRetries(callable); + } + + @Override + public OperationFuture updateDatabaseDdl( + final String instanceId, + final String databaseId, + final Iterable statements, + @Nullable String operationId) + throws SpannerException { + final String dbName = getDatabaseName(instanceId, databaseId); + final String opId = operationId != null ? operationId : randomOperationId(); + OperationFuture rawOperationFuture = + rpc.updateDatabaseDdl(dbName, statements, opId); + return new OperationFutureImpl( + rawOperationFuture.getPollingFuture(), + rawOperationFuture.getInitialFuture(), + new ApiFunction() { + @Override + public Void apply(OperationSnapshot snapshot) { + ProtoOperationTransformers.ResponseTransformer.create(Empty.class).apply(snapshot); + return null; + } + }, + ProtoOperationTransformers.MetadataTransformer.create(UpdateDatabaseDdlMetadata.class), + new ApiFunction() { + @Override + public Void apply(Exception e) { + throw SpannerExceptionFactory.newSpannerException(e); + } + }); + } + + @Override + public void dropDatabase(String instanceId, String databaseId) throws SpannerException { + final String dbName = getDatabaseName(instanceId, databaseId); + Callable callable = + new Callable() { + @Override + public Void call() throws Exception { + rpc.dropDatabase(dbName); + return null; + } + }; + SpannerImpl.runWithRetries(callable); + } + + @Override + public List getDatabaseDdl(String instanceId, String databaseId) { + final String dbName = getDatabaseName(instanceId, databaseId); + Callable> callable = + new Callable>() { + @Override + public List call() throws Exception { + return rpc.getDatabaseDdl(dbName); + } + }; + return SpannerImpl.runWithRetries(callable); + } + + @Override + public Page listDatabases(String instanceId, ListOption... options) { + final String instanceName = getInstanceName(instanceId); + final Options listOptions = Options.fromListOptions(options); + Preconditions.checkArgument( + !listOptions.hasFilter(), "Filter option is not support by" + "listDatabases"); + final int pageSize = listOptions.hasPageSize() ? listOptions.pageSize() : 0; + PageFetcher pageFetcher = + new PageFetcher() { + @Override + public Paginated getNextPage( + String nextPageToken) { + return rpc.listDatabases(instanceName, pageSize, nextPageToken); + } + + @Override + public Database fromProto(com.google.spanner.admin.database.v1.Database proto) { + return Database.fromProto(proto, DatabaseAdminClientImpl.this); + } + }; + if (listOptions.hasPageToken()) { + pageFetcher.setNextPageToken(listOptions.pageToken()); + } + return pageFetcher.getNextPage(); + } + + private String getInstanceName(String instanceId) { + return new InstanceId(projectId, instanceId).getName(); + } + + private String getDatabaseName(String instanceId, String databaseId) { + return new DatabaseId(new InstanceId(projectId, instanceId), databaseId).getName(); + } +} diff --git a/google-cloud-clients/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerImpl.java b/google-cloud-clients/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerImpl.java index 4d34d8969b97..c45a10c8fdc2 100644 --- a/google-cloud-clients/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerImpl.java +++ b/google-cloud-clients/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerImpl.java @@ -52,12 +52,9 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.protobuf.Any; import com.google.protobuf.ByteString; -import com.google.protobuf.Empty; import com.google.protobuf.FieldMask; import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.Message; -import com.google.spanner.admin.database.v1.CreateDatabaseMetadata; -import com.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata; import com.google.spanner.admin.instance.v1.CreateInstanceMetadata; import com.google.spanner.admin.instance.v1.UpdateInstanceMetadata; import com.google.spanner.v1.BeginTransactionRequest; @@ -84,7 +81,6 @@ import java.util.List; import java.util.Map; import java.util.Random; -import java.util.UUID; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; @@ -391,7 +387,7 @@ private static T unpack(Any response, Class clazz) } } - private abstract static class PageFetcher implements NextPageFetcher { + abstract static class PageFetcher implements NextPageFetcher { private String nextPageToken; @Override @@ -412,159 +408,13 @@ public Paginated call() { return new PageImpl(this, nextPageToken, results); } - abstract Paginated getNextPage(@Nullable String nextPageToken); - - abstract S fromProto(T proto); - } - - private static String randomOperationId() { - UUID uuid = UUID.randomUUID(); - return ("r" + uuid.toString()).replace("-", "_"); - } - - static class DatabaseAdminClientImpl implements DatabaseAdminClient { - - private final String projectId; - private final SpannerRpc rpc; - - DatabaseAdminClientImpl(String projectId, SpannerRpc rpc) { - this.projectId = projectId; - this.rpc = rpc; + void setNextPageToken(String nextPageToken) { + this.nextPageToken = nextPageToken; } - @Override - public OperationFuture createDatabase( - String instanceId, String databaseId, Iterable statements) throws SpannerException { - // CreateDatabase() is not idempotent, so we're not retrying this request. - String instanceName = getInstanceName(instanceId); - String createStatement = "CREATE DATABASE `" + databaseId + "`"; - OperationFuture - rawOperationFuture = rpc.createDatabase(instanceName, createStatement, statements); - return new OperationFutureImpl( - rawOperationFuture.getPollingFuture(), - rawOperationFuture.getInitialFuture(), - new ApiFunction() { - @Override - public Database apply(OperationSnapshot snapshot) { - return Database.fromProto( - ProtoOperationTransformers.ResponseTransformer.create( - com.google.spanner.admin.database.v1.Database.class) - .apply(snapshot), - DatabaseAdminClientImpl.this); - } - }, - ProtoOperationTransformers.MetadataTransformer.create(CreateDatabaseMetadata.class), - new ApiFunction() { - @Override - public Database apply(Exception e) { - throw SpannerExceptionFactory.newSpannerException(e); - } - }); - } - - @Override - public Database getDatabase(String instanceId, String databaseId) throws SpannerException { - final String dbName = getDatabaseName(instanceId, databaseId); - Callable callable = - new Callable() { - @Override - public Database call() throws Exception { - return Database.fromProto(rpc.getDatabase(dbName), DatabaseAdminClientImpl.this); - } - }; - return runWithRetries(callable); - } - - @Override - public OperationFuture updateDatabaseDdl( - final String instanceId, - final String databaseId, - final Iterable statements, - @Nullable String operationId) - throws SpannerException { - final String dbName = getDatabaseName(instanceId, databaseId); - final String opId = operationId != null ? operationId : randomOperationId(); - OperationFuture rawOperationFuture = - rpc.updateDatabaseDdl(dbName, statements, opId); - return new OperationFutureImpl( - rawOperationFuture.getPollingFuture(), - rawOperationFuture.getInitialFuture(), - new ApiFunction() { - @Override - public Void apply(OperationSnapshot snapshot) { - ProtoOperationTransformers.ResponseTransformer.create(Empty.class).apply(snapshot); - return null; - } - }, - ProtoOperationTransformers.MetadataTransformer.create(UpdateDatabaseDdlMetadata.class), - new ApiFunction() { - @Override - public Void apply(Exception e) { - throw SpannerExceptionFactory.newSpannerException(e); - } - }); - } - - @Override - public void dropDatabase(String instanceId, String databaseId) throws SpannerException { - final String dbName = getDatabaseName(instanceId, databaseId); - Callable callable = - new Callable() { - @Override - public Void call() throws Exception { - rpc.dropDatabase(dbName); - return null; - } - }; - runWithRetries(callable); - } - - @Override - public List getDatabaseDdl(String instanceId, String databaseId) { - final String dbName = getDatabaseName(instanceId, databaseId); - Callable> callable = - new Callable>() { - @Override - public List call() throws Exception { - return rpc.getDatabaseDdl(dbName); - } - }; - return runWithRetries(callable); - } - - @Override - public Page listDatabases(String instanceId, ListOption... options) { - final String instanceName = getInstanceName(instanceId); - final Options listOptions = Options.fromListOptions(options); - Preconditions.checkArgument( - !listOptions.hasFilter(), "Filter option is not support by" + "listDatabases"); - final int pageSize = listOptions.hasPageSize() ? listOptions.pageSize() : 0; - PageFetcher pageFetcher = - new PageFetcher() { - @Override - public Paginated getNextPage( - String nextPageToken) { - return rpc.listDatabases(instanceName, pageSize, nextPageToken); - } - - @Override - public Database fromProto(com.google.spanner.admin.database.v1.Database proto) { - return Database.fromProto(proto, DatabaseAdminClientImpl.this); - } - }; - if (listOptions.hasPageToken()) { - pageFetcher.nextPageToken = listOptions.pageToken(); - } - return pageFetcher.getNextPage(); - } - - private String getInstanceName(String instanceId) { - return new InstanceId(projectId, instanceId).getName(); - } + abstract Paginated getNextPage(@Nullable String nextPageToken); - private String getDatabaseName(String instanceId, String databaseId) { - return new DatabaseId(new InstanceId(projectId, instanceId), databaseId).getName(); - } + abstract S fromProto(T proto); } static class InstanceAdminClientImpl implements InstanceAdminClient { diff --git a/google-cloud-clients/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseAdminClientImplTest.java b/google-cloud-clients/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseAdminClientImplTest.java index 8baa98d7ac32..3315faa8d03c 100644 --- a/google-cloud-clients/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseAdminClientImplTest.java +++ b/google-cloud-clients/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseAdminClientImplTest.java @@ -52,12 +52,12 @@ public class DatabaseAdminClientImplTest { "projects/my-project/instances/my-instance/databases/my-db2"; @Mock SpannerRpc rpc; - SpannerImpl.DatabaseAdminClientImpl client; + DatabaseAdminClientImpl client; @Before public void setUp() { initMocks(this); - client = new SpannerImpl.DatabaseAdminClientImpl(PROJECT_ID, rpc); + client = new DatabaseAdminClientImpl(PROJECT_ID, rpc); } private Database getDatabaseProto() {