Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Spanner: Refactor SpannerImpl - Move DatabaseAdminClientImpl to separate file #4892

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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<Database, CreateDatabaseMetadata> createDatabase(
String instanceId, String databaseId, Iterable<String> statements) throws SpannerException {
// CreateDatabase() is not idempotent, so we're not retrying this request.
String instanceName = getInstanceName(instanceId);
String createStatement = "CREATE DATABASE `" + databaseId + "`";
OperationFuture<com.google.spanner.admin.database.v1.Database, CreateDatabaseMetadata>
rawOperationFuture = rpc.createDatabase(instanceName, createStatement, statements);
return new OperationFutureImpl<Database, CreateDatabaseMetadata>(
rawOperationFuture.getPollingFuture(),
rawOperationFuture.getInitialFuture(),
new ApiFunction<OperationSnapshot, Database>() {
@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<Exception, Database>() {
@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<Database> callable =
new Callable<Database>() {
@Override
public Database call() throws Exception {
return Database.fromProto(rpc.getDatabase(dbName), DatabaseAdminClientImpl.this);
}
};
return SpannerImpl.runWithRetries(callable);
}

@Override
public OperationFuture<Void, UpdateDatabaseDdlMetadata> updateDatabaseDdl(
final String instanceId,
final String databaseId,
final Iterable<String> statements,
@Nullable String operationId)
throws SpannerException {
final String dbName = getDatabaseName(instanceId, databaseId);
final String opId = operationId != null ? operationId : randomOperationId();
OperationFuture<Empty, UpdateDatabaseDdlMetadata> rawOperationFuture =
rpc.updateDatabaseDdl(dbName, statements, opId);
return new OperationFutureImpl<Void, UpdateDatabaseDdlMetadata>(
rawOperationFuture.getPollingFuture(),
rawOperationFuture.getInitialFuture(),
new ApiFunction<OperationSnapshot, Void>() {
@Override
public Void apply(OperationSnapshot snapshot) {
ProtoOperationTransformers.ResponseTransformer.create(Empty.class).apply(snapshot);
return null;
}
},
ProtoOperationTransformers.MetadataTransformer.create(UpdateDatabaseDdlMetadata.class),
new ApiFunction<Exception, Void>() {
@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<Void> callable =
new Callable<Void>() {
@Override
public Void call() throws Exception {
rpc.dropDatabase(dbName);
return null;
}
};
SpannerImpl.runWithRetries(callable);
}

@Override
public List<String> getDatabaseDdl(String instanceId, String databaseId) {
final String dbName = getDatabaseName(instanceId, databaseId);
Callable<List<String>> callable =
new Callable<List<String>>() {
@Override
public List<String> call() throws Exception {
return rpc.getDatabaseDdl(dbName);
}
};
return SpannerImpl.runWithRetries(callable);
}

@Override
public Page<Database> 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<Database, com.google.spanner.admin.database.v1.Database> pageFetcher =
new PageFetcher<Database, com.google.spanner.admin.database.v1.Database>() {
@Override
public Paginated<com.google.spanner.admin.database.v1.Database> 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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -391,7 +387,7 @@ private static <T extends Message> T unpack(Any response, Class<T> clazz)
}
}

private abstract static class PageFetcher<S, T> implements NextPageFetcher<S> {
abstract static class PageFetcher<S, T> implements NextPageFetcher<S> {
private String nextPageToken;

@Override
Expand All @@ -412,159 +408,13 @@ public Paginated<T> call() {
return new PageImpl<S>(this, nextPageToken, results);
}

abstract Paginated<T> 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<Database, CreateDatabaseMetadata> createDatabase(
String instanceId, String databaseId, Iterable<String> statements) throws SpannerException {
// CreateDatabase() is not idempotent, so we're not retrying this request.
String instanceName = getInstanceName(instanceId);
String createStatement = "CREATE DATABASE `" + databaseId + "`";
OperationFuture<com.google.spanner.admin.database.v1.Database, CreateDatabaseMetadata>
rawOperationFuture = rpc.createDatabase(instanceName, createStatement, statements);
return new OperationFutureImpl<Database, CreateDatabaseMetadata>(
rawOperationFuture.getPollingFuture(),
rawOperationFuture.getInitialFuture(),
new ApiFunction<OperationSnapshot, Database>() {
@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<Exception, Database>() {
@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<Database> callable =
new Callable<Database>() {
@Override
public Database call() throws Exception {
return Database.fromProto(rpc.getDatabase(dbName), DatabaseAdminClientImpl.this);
}
};
return runWithRetries(callable);
}

@Override
public OperationFuture<Void, UpdateDatabaseDdlMetadata> updateDatabaseDdl(
final String instanceId,
final String databaseId,
final Iterable<String> statements,
@Nullable String operationId)
throws SpannerException {
final String dbName = getDatabaseName(instanceId, databaseId);
final String opId = operationId != null ? operationId : randomOperationId();
OperationFuture<Empty, UpdateDatabaseDdlMetadata> rawOperationFuture =
rpc.updateDatabaseDdl(dbName, statements, opId);
return new OperationFutureImpl<Void, UpdateDatabaseDdlMetadata>(
rawOperationFuture.getPollingFuture(),
rawOperationFuture.getInitialFuture(),
new ApiFunction<OperationSnapshot, Void>() {
@Override
public Void apply(OperationSnapshot snapshot) {
ProtoOperationTransformers.ResponseTransformer.create(Empty.class).apply(snapshot);
return null;
}
},
ProtoOperationTransformers.MetadataTransformer.create(UpdateDatabaseDdlMetadata.class),
new ApiFunction<Exception, Void>() {
@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<Void> callable =
new Callable<Void>() {
@Override
public Void call() throws Exception {
rpc.dropDatabase(dbName);
return null;
}
};
runWithRetries(callable);
}

@Override
public List<String> getDatabaseDdl(String instanceId, String databaseId) {
final String dbName = getDatabaseName(instanceId, databaseId);
Callable<List<String>> callable =
new Callable<List<String>>() {
@Override
public List<String> call() throws Exception {
return rpc.getDatabaseDdl(dbName);
}
};
return runWithRetries(callable);
}

@Override
public Page<Database> 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<Database, com.google.spanner.admin.database.v1.Database> pageFetcher =
new PageFetcher<Database, com.google.spanner.admin.database.v1.Database>() {
@Override
public Paginated<com.google.spanner.admin.database.v1.Database> 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<T> 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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down