From d659f4dbb64be86ae1f76b6d8a240ec17ee98720 Mon Sep 17 00:00:00 2001 From: aayushimalik Date: Mon, 6 Feb 2023 11:32:37 +0000 Subject: [PATCH 01/10] feature: Cloud Spanner Drop Database Protection --- .../com/google/cloud/spanner/Database.java | 2 + .../cloud/spanner/DatabaseAdminClient.java | 41 ++++++++++ .../spanner/DatabaseAdminClientImpl.java | 34 ++++++++ .../google/cloud/spanner/DatabaseInfo.java | 82 ++++++++++++++++++- .../cloud/spanner/spi/v1/GapicSpannerRpc.java | 13 +++ .../cloud/spanner/spi/v1/SpannerRpc.java | 3 + .../spanner/DatabaseAdminClientImplTest.java | 25 ++++++ 7 files changed, 196 insertions(+), 4 deletions(-) diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Database.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Database.java index 94c30c9c702..c4e54de9356 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Database.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Database.java @@ -203,6 +203,8 @@ static Database fromProto( .setEncryptionConfig(CustomerManagedEncryption.fromProtoOrNull(proto.getEncryptionConfig())) .setDefaultLeader(proto.getDefaultLeader()) .setDialect(Dialect.fromProto(proto.getDatabaseDialect())) + .setEnableDropProtection(proto.getEnableDropProtection()) + .setReconciling(proto.getReconciling()) .setProto(proto) .build(); } diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseAdminClient.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseAdminClient.java index 1363118e3aa..e2c3df1c668 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseAdminClient.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseAdminClient.java @@ -28,6 +28,7 @@ import com.google.spanner.admin.database.v1.CreateDatabaseRequest; import com.google.spanner.admin.database.v1.RestoreDatabaseMetadata; import com.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata; +import com.google.spanner.admin.database.v1.UpdateDatabaseMetadata; import java.util.List; import javax.annotation.Nullable; @@ -358,6 +359,46 @@ OperationFuture restoreDatabase(Restore resto */ Database getDatabase(String instanceId, String databaseId) throws SpannerException; + /** + * Updates a Cloud Spanner database. The returned {@code Operation} can be used to track the + * progress of the update. Throws SpannerException if the Cloud Spanner database does not exist. + * + * Until completion of the returned operation: + * + * + * + * Upon completion of the returned operation: + * + * + * + *

Example of updating a database. + * + *

{@code
+   *  String databaseId = my_database_id;
+   *  boolean newEnableDropProtection = my_enalbe_drop_protection;
+   *  DatabaseInfo to_update =
+   *     DatabaseInfo.newBuilder(DatabaseId.of(databaseId))
+   *         .setEnableDropProtection(newEnableDropProtection);
+   *  OperationFuture op =
+   *     dbAdminClient.updateDatabase(to_update, DatabaseInfo.DatabaseField.ENABLE_DROP_PROTECTION);
+   *  Database db = op.waitFor().getResult();
+   * }
+ * + * @param database The database to update to. The current field values of the database will be + * updated to the values specified in this parameter. + * @param fieldsToUpdate The fields that should be updated. Only these fields will have their + * values updated to the values specified in {@param database}, even if there are other fields + * specified in {@param database}. + */ + OperationFuture updateDatabase(Database database, + DatabaseInfo.DatabaseField... fieldsToUpdate) throws SpannerException; + /** * Gets the current state of a Cloud Spanner database backup. * diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseAdminClientImpl.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseAdminClientImpl.java index 73ece214c3a..88f9dc01d0a 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseAdminClientImpl.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseAdminClientImpl.java @@ -23,6 +23,7 @@ import com.google.cloud.Policy; import com.google.cloud.Policy.DefaultMarshaller; import com.google.cloud.Timestamp; +import com.google.cloud.spanner.DatabaseInfo.DatabaseField; import com.google.cloud.spanner.Options.ListOption; import com.google.cloud.spanner.SpannerImpl.PageFetcher; import com.google.cloud.spanner.spi.v1.SpannerRpc; @@ -31,9 +32,11 @@ import com.google.common.base.Preconditions; import com.google.iam.v1.GetPolicyOptions; import com.google.longrunning.Operation; +import com.google.protobuf.Descriptors.FieldDescriptor; import com.google.protobuf.Empty; import com.google.protobuf.FieldMask; import com.google.spanner.admin.database.v1.*; +import com.google.spanner.admin.instance.v1.UpdateInstanceMetadata; import java.util.List; import java.util.UUID; import javax.annotation.Nullable; @@ -415,6 +418,37 @@ public Database getDatabase(String instanceId, String databaseId) throws Spanner return Database.fromProto(rpc.getDatabase(dbName), DatabaseAdminClientImpl.this); } + @Override + public OperationFuture updateDatabase(Database database, + DatabaseField... fieldsToUpdate) throws SpannerException { + FieldMask fieldMask = + fieldsToUpdate.length == 0 + ? DatabaseInfo.DatabaseField.toFieldMask( + DatabaseInfo.DatabaseField.defaultFieldsToUpdate()) + : DatabaseInfo.DatabaseField.toFieldMask(fieldsToUpdate); + com.google.spanner.admin.database.v1.Database.Builder database_builder = + com.google.spanner.admin.database.v1.Database.newBuilder() + .setName(database.getId().getName()); + if (fieldMask.getPathsList().contains(DatabaseField.ENABLE_DROP_PROTECTION.getSelector())) { + database_builder.setEnableDropProtection(database.getEnableDropProtection()); + } + OperationFuture + rawOperationFuture = rpc.updateDatabase(database_builder.build(), fieldMask); + return new OperationFutureImpl<>( + rawOperationFuture.getPollingFuture(), + rawOperationFuture.getInitialFuture(), + snapshot -> + Database.fromProto( + ProtoOperationTransformers.ResponseTransformer.create( + com.google.spanner.admin.database.v1.Database.class) + .apply(snapshot), + DatabaseAdminClientImpl.this), + ProtoOperationTransformers.MetadataTransformer.create(UpdateDatabaseMetadata.class), + e -> { + throw SpannerExceptionFactory.newSpannerException(e); + }); + } + @Override public OperationFuture updateDatabaseDdl( final String instanceId, diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseInfo.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseInfo.java index 565517e3419..81d39b7c251 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseInfo.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseInfo.java @@ -16,15 +16,47 @@ package com.google.cloud.spanner; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.cloud.FieldSelector; import com.google.cloud.Timestamp; +import com.google.cloud.spanner.InstanceInfo.BuilderImpl; import com.google.cloud.spanner.encryption.CustomerManagedEncryption; import com.google.common.base.Preconditions; +import com.google.protobuf.FieldMask; import java.util.Objects; import javax.annotation.Nullable; /** Represents a Cloud Spanner database. */ public class DatabaseInfo { + /** Represent an updatable field in a Cloud Spanner database. */ + public enum DatabaseField implements FieldSelector { + ENABLE_DROP_PROTECTION("enable_drop_protection"); + + static DatabaseInfo.DatabaseField[] defaultFieldsToUpdate() { + return new DatabaseInfo.DatabaseField[] {ENABLE_DROP_PROTECTION}; + } + + private final String selector; + + DatabaseField(String selector) { + this.selector = selector; + } + + @Override + public String getSelector() { + return selector; + } + + static FieldMask toFieldMask(DatabaseInfo.DatabaseField... fields) { + FieldMask.Builder builder = FieldMask.newBuilder(); + for (DatabaseInfo.DatabaseField field : fields) { + builder.addPaths(field.getSelector()); + } + return builder.build(); + } + } public abstract static class Builder { abstract Builder setState(State state); @@ -58,6 +90,14 @@ public Builder setDialect(Dialect dialect) { throw new UnsupportedOperationException("Unimplemented"); } + public Builder setEnableDropProtection(boolean enableDropProtection) { + throw new UnsupportedOperationException("Unimplemented"); + } + + public Builder setReconciling(boolean reconciling) { + throw new UnsupportedOperationException("Unimplemented"); + } + abstract Builder setProto(com.google.spanner.admin.database.v1.Database proto); /** Builds the database from this builder. */ @@ -74,6 +114,8 @@ abstract static class BuilderImpl extends Builder { private CustomerManagedEncryption encryptionConfig; private String defaultLeader; private Dialect dialect = Dialect.GOOGLE_STANDARD_SQL; + private boolean enableDropProtection; + private boolean reconciling; private com.google.spanner.admin.database.v1.Database proto; BuilderImpl(DatabaseId id) { @@ -141,6 +183,18 @@ public Builder setDialect(Dialect dialect) { return this; } + @Override + public Builder setEnableDropProtection(boolean enableDropProtection) { + this.enableDropProtection = enableDropProtection; + return this; + } + + @Override + public Builder setReconciling(boolean reconciling) { + this.reconciling = reconciling; + return this; + } + @Override Builder setProto(@Nullable com.google.spanner.admin.database.v1.Database proto) { this.proto = proto; @@ -169,6 +223,8 @@ public enum State { private final CustomerManagedEncryption encryptionConfig; private final String defaultLeader; private final Dialect dialect; + private final boolean enableDropProtection; + private final boolean reconciling; private final com.google.spanner.admin.database.v1.Database proto; public DatabaseInfo(DatabaseId id, State state) { @@ -181,6 +237,8 @@ public DatabaseInfo(DatabaseId id, State state) { this.encryptionConfig = null; this.defaultLeader = null; this.dialect = null; + this.enableDropProtection = false; + this.reconciling = false; this.proto = null; } @@ -194,6 +252,8 @@ public DatabaseInfo(DatabaseId id, State state) { this.encryptionConfig = builder.encryptionConfig; this.defaultLeader = builder.defaultLeader; this.dialect = builder.dialect; + this.enableDropProtection = builder.enableDropProtection; + this.reconciling = builder.reconciling; this.proto = builder.proto; } @@ -262,6 +322,14 @@ public Timestamp getEarliestVersionTime() { return dialect; } + /** + * + * @return + */ + public boolean getEnableDropProtection() {return enableDropProtection;} + + public boolean getReconciling() {return reconciling;} + /** Returns the raw proto instance that was used to construct this {@link Database}. */ public @Nullable com.google.spanner.admin.database.v1.Database getProto() { return proto; @@ -284,7 +352,9 @@ public boolean equals(Object o) { && Objects.equals(earliestVersionTime, that.earliestVersionTime) && Objects.equals(encryptionConfig, that.encryptionConfig) && Objects.equals(defaultLeader, that.defaultLeader) - && Objects.equals(dialect, that.dialect); + && Objects.equals(dialect, that.dialect) + && enableDropProtection == that.enableDropProtection + && reconciling == that.reconciling; } @Override @@ -298,13 +368,15 @@ public int hashCode() { earliestVersionTime, encryptionConfig, defaultLeader, - dialect); + dialect, + enableDropProtection, + reconciling); } @Override public String toString() { return String.format( - "Database[%s, %s, %s, %s, %s, %s, %s, %s, %s]", + "Database[%s, %s, %s, %s, %s, %s, %s, %s, %s %d %d]", id.getName(), state, createTime, @@ -313,6 +385,8 @@ public String toString() { earliestVersionTime, encryptionConfig, defaultLeader, - dialect); + dialect, + enableDropProtection, + reconciling); } } diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java index 7f325665542..0368f474d0d 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java @@ -133,6 +133,8 @@ import com.google.spanner.admin.database.v1.UpdateBackupRequest; import com.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata; import com.google.spanner.admin.database.v1.UpdateDatabaseDdlRequest; +import com.google.spanner.admin.database.v1.UpdateDatabaseMetadata; +import com.google.spanner.admin.database.v1.UpdateDatabaseRequest; import com.google.spanner.admin.instance.v1.CreateInstanceConfigMetadata; import com.google.spanner.admin.instance.v1.CreateInstanceConfigRequest; import com.google.spanner.admin.instance.v1.CreateInstanceMetadata; @@ -1315,6 +1317,17 @@ public Database getDatabase(String databaseName) throws SpannerException { () -> get(databaseAdminStub.getDatabaseCallable().futureCall(request, context))); } + @Override + public OperationFuture updateDatabase(Database database, + FieldMask updateMask) throws SpannerException { + UpdateDatabaseRequest request = + UpdateDatabaseRequest.newBuilder().setDatabase(database).setUpdateMask(updateMask).build(); + GrpcCallContext context = + newCallContext( + null, database.getName(), request, DatabaseAdminGrpc.getUpdateDatabaseMethod()); + return databaseAdminStub.updateDatabaseOperationCallable().futureCall(request, context); + } + @Override public List getDatabaseDdl(String databaseName) throws SpannerException { acquireAdministrativeRequestsRateLimiter(); diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/SpannerRpc.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/SpannerRpc.java index 2f68b9c1df2..ee20a16ca18 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/SpannerRpc.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/SpannerRpc.java @@ -232,6 +232,9 @@ OperationFuture updateDatabaseDdl( Database getDatabase(String databaseName) throws SpannerException; + OperationFuture updateDatabase( + Database database, FieldMask fieldMask) throws SpannerException; + List getDatabaseDdl(String databaseName) throws SpannerException; /** Lists the backups in the specified instance. */ Paginated listBackups( diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseAdminClientImplTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseAdminClientImplTest.java index 0255f6c1668..ca8d83d73c8 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseAdminClientImplTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseAdminClientImplTest.java @@ -18,7 +18,9 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.spanner.admin.database.v1.DatabaseDialect.GOOGLE_STANDARD_SQL; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.mockito.MockitoAnnotations.initMocks; @@ -27,6 +29,7 @@ import com.google.cloud.Identity; import com.google.cloud.Role; import com.google.cloud.Timestamp; +import com.google.cloud.spanner.DatabaseInfo.DatabaseField; import com.google.cloud.spanner.DatabaseInfo.State; import com.google.cloud.spanner.encryption.EncryptionConfigs; import com.google.cloud.spanner.spi.v1.SpannerRpc; @@ -53,6 +56,7 @@ import com.google.spanner.admin.database.v1.EncryptionInfo; import com.google.spanner.admin.database.v1.RestoreDatabaseMetadata; import com.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata; +import com.google.spanner.admin.database.v1.UpdateDatabaseMetadata; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -246,6 +250,27 @@ public void updateDatabaseDdlOpAlreadyExists() throws Exception { assertThat(op.getName()).isEqualTo(originalOpName); } + @Test + public void updataDatabase() throws Exception { + com.google.cloud.spanner.Database database = client.newDatabaseBuilder(DatabaseId.of(DB_NAME)) + .setEnableDropProtection(true).build(); + Database database_proto = Database.newBuilder() + .setName(DB_NAME) + .setEnableDropProtection(true) + .build(); + OperationFuture rawOperationFuture = + OperationFutureUtil.immediateOperationFuture("updateDatabase", database_proto, + UpdateDatabaseMetadata.getDefaultInstance()); + when(rpc.updateDatabase(database_proto, + DatabaseField.toFieldMask(DatabaseField.ENABLE_DROP_PROTECTION))).thenReturn( + rawOperationFuture); + OperationFuture op = + client.updateDatabase(database, DatabaseField.ENABLE_DROP_PROTECTION); + assertTrue(op.isDone()); + assertEquals(op.get().getId().getName(), DB_NAME); + assertTrue(op.get().getEnableDropProtection()); + } + @Test public void dropDatabase() { client.dropDatabase(INSTANCE_ID, DB_ID); From c225c2cd81ad74c2ae0ae1c1ca1345713a91fb51 Mon Sep 17 00:00:00 2001 From: aayushimalik Date: Mon, 6 Feb 2023 11:32:37 +0000 Subject: [PATCH 02/10] feature: Cloud Spanner Drop Database Protection --- .../com/google/cloud/spanner/Database.java | 45 ++++++- .../cloud/spanner/DatabaseAdminClient.java | 40 ++++++ .../spanner/DatabaseAdminClientImpl.java | 22 ++++ .../google/cloud/spanner/DatabaseInfo.java | 119 ++++++++++++++++-- .../cloud/spanner/spi/v1/GapicSpannerRpc.java | 13 ++ .../cloud/spanner/spi/v1/SpannerRpc.java | 4 + .../spanner/DatabaseAdminClientImplTest.java | 22 ++++ .../google/cloud/spanner/DatabaseTest.java | 22 ++++ .../cloud/spanner/it/ITDatabaseAdminTest.java | 64 ++++++++++ 9 files changed, 340 insertions(+), 11 deletions(-) diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Database.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Database.java index 94c30c9c702..d86bd0330cd 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Database.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Database.java @@ -194,7 +194,7 @@ private String database() { static Database fromProto( com.google.spanner.admin.database.v1.Database proto, DatabaseAdminClient client) { checkArgument(!proto.getName().isEmpty(), "Missing expected 'name' field"); - return new Database.Builder(client, DatabaseId.of(proto.getName())) + DatabaseInfo.Builder builder = new Builder(client, DatabaseId.of(proto.getName())) .setState(fromProtoState(proto.getState())) .setCreateTime(Timestamp.fromProto(proto.getCreateTime())) .setRestoreInfo(RestoreInfo.fromProtoOrNullIfDefaultInstance(proto.getRestoreInfo())) @@ -203,8 +203,47 @@ static Database fromProto( .setEncryptionConfig(CustomerManagedEncryption.fromProtoOrNull(proto.getEncryptionConfig())) .setDefaultLeader(proto.getDefaultLeader()) .setDialect(Dialect.fromProto(proto.getDatabaseDialect())) - .setProto(proto) - .build(); + .setReconciling(proto.getReconciling()) + .setProto(proto); + if (proto.getEnableDropProtection()) { + builder.enableDropProtection(); + } else { + builder.disableDropProtection(); + } + return builder.build(); + } + + public com.google.spanner.admin.database.v1.Database toProto() { + if (getProto() != null) { + return getProto(); + } + com.google.spanner.admin.database.v1.Database.Builder builder = com.google.spanner.admin.database.v1.Database.newBuilder() + .setName(getId().getName()) + .setState(getState().toProto()) + .setEnableDropProtection(isDropProtectionEnabled()) + .setReconciling(getReconciling()); + if (getCreateTime() != null) { + builder.setCreateTime(getCreateTime().toProto()); + } + if (getRestoreInfo() != null) { + builder.setRestoreInfo(getRestoreInfo().getProto()); + } + if (getVersionRetentionPeriod() != null) { + builder.setVersionRetentionPeriod(getVersionRetentionPeriod()); + } + if (getEarliestVersionTime() != null) { + builder.setEarliestVersionTime(getEarliestVersionTime().toProto()); + } + if (getEncryptionConfig() != null) { + builder.setEncryptionConfig(getEncryptionConfig().toProto()); + } + if (getDefaultLeader() != null) { + builder.setDefaultLeader(getDefaultLeader()); + } + if (getDialect() != null) { + builder.setDatabaseDialect(getDialect().toProto()); + } + return builder.build(); } static DatabaseInfo.State fromProtoState( diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseAdminClient.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseAdminClient.java index 1363118e3aa..66c18891447 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseAdminClient.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseAdminClient.java @@ -28,6 +28,7 @@ import com.google.spanner.admin.database.v1.CreateDatabaseRequest; import com.google.spanner.admin.database.v1.RestoreDatabaseMetadata; import com.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata; +import com.google.spanner.admin.database.v1.UpdateDatabaseMetadata; import java.util.List; import javax.annotation.Nullable; @@ -358,6 +359,45 @@ OperationFuture restoreDatabase(Restore resto */ Database getDatabase(String instanceId, String databaseId) throws SpannerException; + /** + * Updates a Cloud Spanner database. The returned {@code Operation} can be used to track the + * progress of the update. Throws SpannerException if the Cloud Spanner database does not exist. + * + *

Until completion of the returned operation: + * + *

    + *
  • Cancelling the operation is best effort and may or may not succeed. + *
  • All other attempts to modify the database are rejected. + *
  • Reading the database via the API continues to give the pre-request field values. + *
+ * + * Upon completion of the returned operation: + * + *
    + *
  • The database's new fields are readable via the API. + *
+ * + *

Example of updating a database. + * + *

{@code
+   * String databaseId = my_database_id;
+   * DatabaseInfo to_update =
+   *    DatabaseInfo.newBuilder(DatabaseId.of(databaseId))
+   *        .enableDropProtection();
+   * OperationFuture op =
+   *    dbAdminClient.updateDatabase(to_update, DatabaseInfo.DatabaseField.ENABLE_DROP_PROTECTION);
+   * Database db = op.waitFor().getResult();
+   * }
+ * + * @param database The database to update to. The current field values of the database will be + * updated to the values specified in this parameter. + * @param fieldsToUpdate The fields that should be updated. Only these fields will have their + * values updated to the values specified in {@param database}, even if there are other fields + * specified in {@param database}. + */ + OperationFuture updateDatabase( + Database database, DatabaseInfo.DatabaseField... fieldsToUpdate) throws SpannerException; + /** * Gets the current state of a Cloud Spanner database backup. * diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseAdminClientImpl.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseAdminClientImpl.java index 73ece214c3a..8a5d0d613a5 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseAdminClientImpl.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseAdminClientImpl.java @@ -23,6 +23,7 @@ import com.google.cloud.Policy; import com.google.cloud.Policy.DefaultMarshaller; import com.google.cloud.Timestamp; +import com.google.cloud.spanner.DatabaseInfo.DatabaseField; import com.google.cloud.spanner.Options.ListOption; import com.google.cloud.spanner.SpannerImpl.PageFetcher; import com.google.cloud.spanner.spi.v1.SpannerRpc; @@ -415,6 +416,27 @@ public Database getDatabase(String instanceId, String databaseId) throws Spanner return Database.fromProto(rpc.getDatabase(dbName), DatabaseAdminClientImpl.this); } + @Override + public OperationFuture updateDatabase( + Database database, DatabaseField... fieldsToUpdate) throws SpannerException { + FieldMask fieldMask = DatabaseInfo.DatabaseField.toFieldMask(fieldsToUpdate); + OperationFuture + rawOperationFuture = rpc.updateDatabase(database.toProto(), fieldMask); + return new OperationFutureImpl<>( + rawOperationFuture.getPollingFuture(), + rawOperationFuture.getInitialFuture(), + snapshot -> + Database.fromProto( + ProtoOperationTransformers.ResponseTransformer.create( + com.google.spanner.admin.database.v1.Database.class) + .apply(snapshot), + DatabaseAdminClientImpl.this), + ProtoOperationTransformers.MetadataTransformer.create(UpdateDatabaseMetadata.class), + e -> { + throw SpannerExceptionFactory.newSpannerException(e); + }); + } + @Override public OperationFuture updateDatabaseDdl( final String instanceId, diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseInfo.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseInfo.java index 565517e3419..fd1bd72a978 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseInfo.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseInfo.java @@ -16,15 +16,43 @@ package com.google.cloud.spanner; + +import com.google.cloud.FieldSelector; import com.google.cloud.Timestamp; import com.google.cloud.spanner.encryption.CustomerManagedEncryption; import com.google.common.base.Preconditions; +import com.google.protobuf.FieldMask; +import com.google.spanner.admin.database.v1.Database.State; import java.util.Objects; import javax.annotation.Nullable; /** Represents a Cloud Spanner database. */ public class DatabaseInfo { + /** Represent an updatable field in a Cloud Spanner database. */ + public enum DatabaseField implements FieldSelector { + ENABLE_DROP_PROTECTION("enable_drop_protection"); + + private final String selector; + + DatabaseField(String selector) { + this.selector = selector; + } + + @Override + public String getSelector() { + return selector; + } + + static FieldMask toFieldMask(DatabaseInfo.DatabaseField... fields) { + FieldMask.Builder builder = FieldMask.newBuilder(); + for (DatabaseInfo.DatabaseField field : fields) { + builder.addPaths(field.getSelector()); + } + return builder.build(); + } + } + public abstract static class Builder { abstract Builder setState(State state); @@ -58,6 +86,18 @@ public Builder setDialect(Dialect dialect) { throw new UnsupportedOperationException("Unimplemented"); } + public Builder enableDropProtection() { + throw new UnsupportedOperationException("Unimplemented"); + } + + public Builder disableDropProtection() { + throw new UnsupportedOperationException("Unimplemented"); + } + + protected Builder setReconciling(boolean reconciling) { + throw new UnsupportedOperationException("Unimplemented"); + } + abstract Builder setProto(com.google.spanner.admin.database.v1.Database proto); /** Builds the database from this builder. */ @@ -74,6 +114,8 @@ abstract static class BuilderImpl extends Builder { private CustomerManagedEncryption encryptionConfig; private String defaultLeader; private Dialect dialect = Dialect.GOOGLE_STANDARD_SQL; + private boolean dropProtectionEnabled; + private boolean reconciling; private com.google.spanner.admin.database.v1.Database proto; BuilderImpl(DatabaseId id) { @@ -141,6 +183,24 @@ public Builder setDialect(Dialect dialect) { return this; } + @Override + public Builder enableDropProtection() { + this.dropProtectionEnabled = true; + return this; + } + + @Override + public Builder disableDropProtection() { + this.dropProtectionEnabled = false; + return this; + } + + @Override + protected Builder setReconciling(boolean reconciling) { + this.reconciling = reconciling; + return this; + } + @Override Builder setProto(@Nullable com.google.spanner.admin.database.v1.Database proto) { this.proto = proto; @@ -151,13 +211,35 @@ Builder setProto(@Nullable com.google.spanner.admin.database.v1.Database proto) /** State of the database. */ public enum State { // Not specified. - UNSPECIFIED, + UNSPECIFIED { + @Override + public com.google.spanner.admin.database.v1.Database.State toProto() { + return com.google.spanner.admin.database.v1.Database.State.STATE_UNSPECIFIED; + } + }, // The database is still being created and is not ready to use. - CREATING, + CREATING { + @Override + public com.google.spanner.admin.database.v1.Database.State toProto() { + return com.google.spanner.admin.database.v1.Database.State.CREATING; + } + }, // The database is fully created and ready to use. - READY, + READY { + @Override + public com.google.spanner.admin.database.v1.Database.State toProto() { + return com.google.spanner.admin.database.v1.Database.State.READY; + } + }, // The database has restored and is being optimized for use. - READY_OPTIMIZING + READY_OPTIMIZING { + @Override + public com.google.spanner.admin.database.v1.Database.State toProto() { + return com.google.spanner.admin.database.v1.Database.State.READY_OPTIMIZING; + } + }; + + public abstract com.google.spanner.admin.database.v1.Database.State toProto(); } private final DatabaseId id; @@ -169,6 +251,8 @@ public enum State { private final CustomerManagedEncryption encryptionConfig; private final String defaultLeader; private final Dialect dialect; + private final boolean dropProtectionEnabled; + private final boolean reconciling; private final com.google.spanner.admin.database.v1.Database proto; public DatabaseInfo(DatabaseId id, State state) { @@ -181,6 +265,8 @@ public DatabaseInfo(DatabaseId id, State state) { this.encryptionConfig = null; this.defaultLeader = null; this.dialect = null; + this.dropProtectionEnabled = false; + this.reconciling = false; this.proto = null; } @@ -194,6 +280,8 @@ public DatabaseInfo(DatabaseId id, State state) { this.encryptionConfig = builder.encryptionConfig; this.defaultLeader = builder.defaultLeader; this.dialect = builder.dialect; + this.dropProtectionEnabled = builder.dropProtectionEnabled; + this.reconciling = builder.reconciling; this.proto = builder.proto; } @@ -262,6 +350,15 @@ public Timestamp getEarliestVersionTime() { return dialect; } + /** @return */ + public boolean isDropProtectionEnabled() { + return dropProtectionEnabled; + } + + public boolean getReconciling() { + return reconciling; + } + /** Returns the raw proto instance that was used to construct this {@link Database}. */ public @Nullable com.google.spanner.admin.database.v1.Database getProto() { return proto; @@ -284,7 +381,9 @@ public boolean equals(Object o) { && Objects.equals(earliestVersionTime, that.earliestVersionTime) && Objects.equals(encryptionConfig, that.encryptionConfig) && Objects.equals(defaultLeader, that.defaultLeader) - && Objects.equals(dialect, that.dialect); + && Objects.equals(dialect, that.dialect) + && dropProtectionEnabled == that.dropProtectionEnabled + && reconciling == that.reconciling; } @Override @@ -298,13 +397,15 @@ public int hashCode() { earliestVersionTime, encryptionConfig, defaultLeader, - dialect); + dialect, + dropProtectionEnabled, + reconciling); } @Override public String toString() { return String.format( - "Database[%s, %s, %s, %s, %s, %s, %s, %s, %s]", + "Database[%s, %s, %s, %s, %s, %s, %s, %s, %s %d %d]", id.getName(), state, createTime, @@ -313,6 +414,8 @@ public String toString() { earliestVersionTime, encryptionConfig, defaultLeader, - dialect); + dialect, + dropProtectionEnabled, + reconciling); } } diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java index 7f325665542..a9a1d1dbc15 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java @@ -133,6 +133,8 @@ import com.google.spanner.admin.database.v1.UpdateBackupRequest; import com.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata; import com.google.spanner.admin.database.v1.UpdateDatabaseDdlRequest; +import com.google.spanner.admin.database.v1.UpdateDatabaseMetadata; +import com.google.spanner.admin.database.v1.UpdateDatabaseRequest; import com.google.spanner.admin.instance.v1.CreateInstanceConfigMetadata; import com.google.spanner.admin.instance.v1.CreateInstanceConfigRequest; import com.google.spanner.admin.instance.v1.CreateInstanceMetadata; @@ -1315,6 +1317,17 @@ public Database getDatabase(String databaseName) throws SpannerException { () -> get(databaseAdminStub.getDatabaseCallable().futureCall(request, context))); } + @Override + public OperationFuture updateDatabase( + Database database, FieldMask updateMask) throws SpannerException { + UpdateDatabaseRequest request = + UpdateDatabaseRequest.newBuilder().setDatabase(database).setUpdateMask(updateMask).build(); + GrpcCallContext context = + newCallContext( + null, database.getName(), request, DatabaseAdminGrpc.getUpdateDatabaseMethod()); + return databaseAdminStub.updateDatabaseOperationCallable().futureCall(request, context); + } + @Override public List getDatabaseDdl(String databaseName) throws SpannerException { acquireAdministrativeRequestsRateLimiter(); diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/SpannerRpc.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/SpannerRpc.java index 2f68b9c1df2..38541f59456 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/SpannerRpc.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/SpannerRpc.java @@ -42,6 +42,7 @@ import com.google.spanner.admin.database.v1.DatabaseRole; import com.google.spanner.admin.database.v1.RestoreDatabaseMetadata; import com.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata; +import com.google.spanner.admin.database.v1.UpdateDatabaseMetadata; import com.google.spanner.admin.instance.v1.CreateInstanceConfigMetadata; import com.google.spanner.admin.instance.v1.CreateInstanceMetadata; import com.google.spanner.admin.instance.v1.Instance; @@ -232,6 +233,9 @@ OperationFuture updateDatabaseDdl( Database getDatabase(String databaseName) throws SpannerException; + OperationFuture updateDatabase( + Database database, FieldMask fieldMask) throws SpannerException; + List getDatabaseDdl(String databaseName) throws SpannerException; /** Lists the backups in the specified instance. */ Paginated listBackups( diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseAdminClientImplTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseAdminClientImplTest.java index 0255f6c1668..59b974e7ff2 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseAdminClientImplTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseAdminClientImplTest.java @@ -18,7 +18,9 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.spanner.admin.database.v1.DatabaseDialect.GOOGLE_STANDARD_SQL; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.mockito.MockitoAnnotations.initMocks; @@ -27,6 +29,7 @@ import com.google.cloud.Identity; import com.google.cloud.Role; import com.google.cloud.Timestamp; +import com.google.cloud.spanner.DatabaseInfo.DatabaseField; import com.google.cloud.spanner.DatabaseInfo.State; import com.google.cloud.spanner.encryption.EncryptionConfigs; import com.google.cloud.spanner.spi.v1.SpannerRpc; @@ -53,6 +56,7 @@ import com.google.spanner.admin.database.v1.EncryptionInfo; import com.google.spanner.admin.database.v1.RestoreDatabaseMetadata; import com.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata; +import com.google.spanner.admin.database.v1.UpdateDatabaseMetadata; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -246,6 +250,24 @@ public void updateDatabaseDdlOpAlreadyExists() throws Exception { assertThat(op.getName()).isEqualTo(originalOpName); } + @Test + public void updateDatabase() throws Exception { + com.google.cloud.spanner.Database database = + client.newDatabaseBuilder(DatabaseId.of(DB_NAME)).enableDropProtection().build(); + Database database_proto = database.toProto(); + OperationFuture rawOperationFuture = + OperationFutureUtil.immediateOperationFuture( + "updateDatabase", database_proto, UpdateDatabaseMetadata.getDefaultInstance()); + when(rpc.updateDatabase( + database_proto, DatabaseField.toFieldMask(DatabaseField.ENABLE_DROP_PROTECTION))) + .thenReturn(rawOperationFuture); + OperationFuture op = + client.updateDatabase(database, DatabaseField.ENABLE_DROP_PROTECTION); + assertTrue(op.isDone()); + assertEquals(op.get().getId().getName(), DB_NAME); + assertTrue(op.get().isDropProtectionEnabled()); + } + @Test public void dropDatabase() { client.dropDatabase(INSTANCE_ID, DB_ID); diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseTest.java index 1eb2794c8ee..5c6d5de40ae 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseTest.java @@ -67,6 +67,10 @@ public class DatabaseTest { private static final String DEFAULT_LEADER = "default-leader"; private static final DatabaseDialect DEFAULT_DIALECT = DatabaseDialect.GOOGLE_STANDARD_SQL; + private static final boolean DEFAULT_DROP_PROTECTION = true; + + private static final boolean DEFAULT_RECONCILING = true; + @Mock DatabaseAdminClient dbClient; @Before @@ -113,6 +117,22 @@ public void testFromProto() { EncryptionConfigs.customerManagedEncryption(KMS_KEY_NAME), database.getEncryptionConfig()); assertEquals(DEFAULT_LEADER, database.getDefaultLeader()); assertEquals(Dialect.GOOGLE_STANDARD_SQL, database.getDialect()); + assertEquals(DEFAULT_DROP_PROTECTION, database.isDropProtectionEnabled()); + assertEquals(DEFAULT_RECONCILING, database.getReconciling()); + } + + @Test + public void testToProto() { + final com.google.spanner.admin.database.v1.Database database = createDatabase().toProto(); + assertEquals(NAME, database.getName()); + assertEquals(com.google.spanner.admin.database.v1.Database.State.CREATING, database.getState()); + assertEquals(VERSION_RETENTION_PERIOD, database.getVersionRetentionPeriod()); + assertEquals(EARLIEST_VERSION_TIME.toProto(), database.getEarliestVersionTime()); + assertEquals(ENCRYPTION_CONFIG, database.getEncryptionConfig()); + assertEquals(DEFAULT_LEADER, database.getDefaultLeader()); + assertEquals(DEFAULT_DIALECT, database.getDatabaseDialect()); + assertEquals(DEFAULT_DROP_PROTECTION, database.getEnableDropProtection()); + assertEquals(DEFAULT_RECONCILING, database.getReconciling()); } @Test @@ -231,6 +251,8 @@ private com.google.spanner.admin.database.v1.Database defaultProtoDatabase() { .addAllEncryptionInfo(ENCRYPTION_INFOS) .setDefaultLeader(DEFAULT_LEADER) .setDatabaseDialect(DEFAULT_DIALECT) + .setEnableDropProtection(DEFAULT_DROP_PROTECTION) + .setReconciling(DEFAULT_RECONCILING) .build(); } } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITDatabaseAdminTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITDatabaseAdminTest.java index e19272a0768..11016556be9 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITDatabaseAdminTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITDatabaseAdminTest.java @@ -19,6 +19,7 @@ import static com.google.cloud.spanner.testing.EmulatorSpannerHelper.isUsingEmulator; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.junit.Assume.assumeFalse; @@ -27,6 +28,8 @@ import com.google.api.gax.paging.Page; import com.google.cloud.spanner.Database; import com.google.cloud.spanner.DatabaseAdminClient; +import com.google.cloud.spanner.DatabaseInfo; +import com.google.cloud.spanner.DatabaseInfo.DatabaseField; import com.google.cloud.spanner.DatabaseRole; import com.google.cloud.spanner.Dialect; import com.google.cloud.spanner.ErrorCode; @@ -39,10 +42,13 @@ import com.google.common.collect.Iterables; import com.google.spanner.admin.database.v1.CreateDatabaseMetadata; import com.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata; +import com.google.spanner.admin.database.v1.UpdateDatabaseMetadata; import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; import org.junit.After; import org.junit.Before; import org.junit.ClassRule; @@ -57,6 +63,8 @@ public class ITDatabaseAdminTest { private static final long TIMEOUT_MINUTES = 5; @ClassRule public static IntegrationTestEnv env = new IntegrationTestEnv(); + + private static final Logger logger = Logger.getLogger(ITDatabaseAdminTest.class.getName()); private DatabaseAdminClient dbAdminClient; private RemoteSpannerHelper testHelper; private List dbs = new ArrayList<>(); @@ -269,4 +277,60 @@ public void createAndListDatabaseRoles() throws Exception { } assertThat(dbRolesRemaining).containsNoneIn(dbRoles); } + + @Test + public void dropDatabaseWithProtectionEnabled() throws Exception { + String instanceId = testHelper.getInstanceId().getInstance(); + Database database = + dbAdminClient + .createDatabase(instanceId, testHelper.getUniqueDatabaseId(), ImmutableList.of()) + .get(); + logger.log(Level.INFO, "Created database: {0}", database.getId().getName()); + + // Enable drop protection for the database. + Database update_to = dbAdminClient.newDatabaseBuilder(database.getId()) + .enableDropProtection().build(); + OperationFuture op = dbAdminClient.updateDatabase(update_to, + DatabaseField.ENABLE_DROP_PROTECTION); + Database updated = op.get(); + assertEquals(updated.getId().getName(), database.getId().getName()); + assertTrue(updated.isDropProtectionEnabled()); + + String[] split = database.getId().getName().split("/"); + String databaseId = split[split.length - 1]; + + // Assert that dropping a database with protection enabled fails due to precondition violation. + try { + dbAdminClient.dropDatabase(instanceId, databaseId); + fail("Expected exception"); + } catch (SpannerException e) { + assertEquals(ErrorCode.FAILED_PRECONDITION, e.getErrorCode()); + assertThat(e.getMessage()).endsWith( + "because the `enable_drop_protection` setting is currently enabled for it. Please " + + "disable the setting and try again."); + } + + // Assert that deleting the instance also fails due to precondition violation. + try { + testHelper.getClient().getInstanceAdminClient().deleteInstance(instanceId); + fail("Expected exception"); + } catch (SpannerException e) { + assertEquals(ErrorCode.FAILED_PRECONDITION, e.getErrorCode()); + assertThat(e.getMessage()).endsWith( + "because it contains databases with drop protection enabled. Please disable drop " + + "protection for all databases in the instance before deleting it."); + } + + // Disable drop protection for the database. + update_to = dbAdminClient.newDatabaseBuilder(database.getId()) + .disableDropProtection().build(); + op = dbAdminClient.updateDatabase(update_to, + DatabaseField.ENABLE_DROP_PROTECTION); + updated = op.get(); + assertEquals(updated.getId().getName(), database.getId().getName()); + assertFalse(updated.isDropProtectionEnabled()); + + // Dropping the database should succeed now. + dbAdminClient.dropDatabase(instanceId, databaseId); + } } From 6f9e0df11aa8160b5de0e7e09f7ee028a2899c6b Mon Sep 17 00:00:00 2001 From: aayushimalik Date: Fri, 24 Mar 2023 08:42:03 +0000 Subject: [PATCH 03/10] Formatting and bug fixes --- .../com/google/cloud/spanner/Database.java | 35 ++++++++++--------- .../google/cloud/spanner/DatabaseInfo.java | 1 - .../encryption/CustomerManagedEncryption.java | 4 +++ .../cloud/spanner/it/ITDatabaseAdminTest.java | 29 ++++++++------- 4 files changed, 37 insertions(+), 32 deletions(-) diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Database.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Database.java index d86bd0330cd..e7747d089e4 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Database.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Database.java @@ -194,17 +194,19 @@ private String database() { static Database fromProto( com.google.spanner.admin.database.v1.Database proto, DatabaseAdminClient client) { checkArgument(!proto.getName().isEmpty(), "Missing expected 'name' field"); - DatabaseInfo.Builder builder = new Builder(client, DatabaseId.of(proto.getName())) - .setState(fromProtoState(proto.getState())) - .setCreateTime(Timestamp.fromProto(proto.getCreateTime())) - .setRestoreInfo(RestoreInfo.fromProtoOrNullIfDefaultInstance(proto.getRestoreInfo())) - .setVersionRetentionPeriod(proto.getVersionRetentionPeriod()) - .setEarliestVersionTime(Timestamp.fromProto(proto.getEarliestVersionTime())) - .setEncryptionConfig(CustomerManagedEncryption.fromProtoOrNull(proto.getEncryptionConfig())) - .setDefaultLeader(proto.getDefaultLeader()) - .setDialect(Dialect.fromProto(proto.getDatabaseDialect())) - .setReconciling(proto.getReconciling()) - .setProto(proto); + DatabaseInfo.Builder builder = + new Builder(client, DatabaseId.of(proto.getName())) + .setState(fromProtoState(proto.getState())) + .setCreateTime(Timestamp.fromProto(proto.getCreateTime())) + .setRestoreInfo(RestoreInfo.fromProtoOrNullIfDefaultInstance(proto.getRestoreInfo())) + .setVersionRetentionPeriod(proto.getVersionRetentionPeriod()) + .setEarliestVersionTime(Timestamp.fromProto(proto.getEarliestVersionTime())) + .setEncryptionConfig( + CustomerManagedEncryption.fromProtoOrNull(proto.getEncryptionConfig())) + .setDefaultLeader(proto.getDefaultLeader()) + .setDialect(Dialect.fromProto(proto.getDatabaseDialect())) + .setReconciling(proto.getReconciling()) + .setProto(proto); if (proto.getEnableDropProtection()) { builder.enableDropProtection(); } else { @@ -217,11 +219,12 @@ public com.google.spanner.admin.database.v1.Database toProto() { if (getProto() != null) { return getProto(); } - com.google.spanner.admin.database.v1.Database.Builder builder = com.google.spanner.admin.database.v1.Database.newBuilder() - .setName(getId().getName()) - .setState(getState().toProto()) - .setEnableDropProtection(isDropProtectionEnabled()) - .setReconciling(getReconciling()); + com.google.spanner.admin.database.v1.Database.Builder builder = + com.google.spanner.admin.database.v1.Database.newBuilder() + .setName(getId().getName()) + .setState(getState().toProto()) + .setEnableDropProtection(isDropProtectionEnabled()) + .setReconciling(getReconciling()); if (getCreateTime() != null) { builder.setCreateTime(getCreateTime().toProto()); } diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseInfo.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseInfo.java index fd1bd72a978..704e1960ffe 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseInfo.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseInfo.java @@ -16,7 +16,6 @@ package com.google.cloud.spanner; - import com.google.cloud.FieldSelector; import com.google.cloud.Timestamp; import com.google.cloud.spanner.encryption.CustomerManagedEncryption; diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/encryption/CustomerManagedEncryption.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/encryption/CustomerManagedEncryption.java index fdc48cf3d2c..b24c4767294 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/encryption/CustomerManagedEncryption.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/encryption/CustomerManagedEncryption.java @@ -42,6 +42,10 @@ public static CustomerManagedEncryption fromProtoOrNull(EncryptionConfig proto) : new CustomerManagedEncryption(proto.getKmsKeyName()); } + public EncryptionConfig toProto() { + return EncryptionConfig.newBuilder().setKmsKeyName(this.getKmsKeyName()).build(); + } + @Override public boolean equals(Object o) { if (this == o) { diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITDatabaseAdminTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITDatabaseAdminTest.java index 11016556be9..39afb06754b 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITDatabaseAdminTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITDatabaseAdminTest.java @@ -28,7 +28,6 @@ import com.google.api.gax.paging.Page; import com.google.cloud.spanner.Database; import com.google.cloud.spanner.DatabaseAdminClient; -import com.google.cloud.spanner.DatabaseInfo; import com.google.cloud.spanner.DatabaseInfo.DatabaseField; import com.google.cloud.spanner.DatabaseRole; import com.google.cloud.spanner.Dialect; @@ -288,10 +287,10 @@ public void dropDatabaseWithProtectionEnabled() throws Exception { logger.log(Level.INFO, "Created database: {0}", database.getId().getName()); // Enable drop protection for the database. - Database update_to = dbAdminClient.newDatabaseBuilder(database.getId()) - .enableDropProtection().build(); - OperationFuture op = dbAdminClient.updateDatabase(update_to, - DatabaseField.ENABLE_DROP_PROTECTION); + Database update_to = + dbAdminClient.newDatabaseBuilder(database.getId()).enableDropProtection().build(); + OperationFuture op = + dbAdminClient.updateDatabase(update_to, DatabaseField.ENABLE_DROP_PROTECTION); Database updated = op.get(); assertEquals(updated.getId().getName(), database.getId().getName()); assertTrue(updated.isDropProtectionEnabled()); @@ -305,9 +304,10 @@ public void dropDatabaseWithProtectionEnabled() throws Exception { fail("Expected exception"); } catch (SpannerException e) { assertEquals(ErrorCode.FAILED_PRECONDITION, e.getErrorCode()); - assertThat(e.getMessage()).endsWith( - "because the `enable_drop_protection` setting is currently enabled for it. Please " + - "disable the setting and try again."); + assertThat(e.getMessage()) + .endsWith( + "because the `enable_drop_protection` setting is currently enabled for it. Please " + + "disable the setting and try again."); } // Assert that deleting the instance also fails due to precondition violation. @@ -316,16 +316,15 @@ public void dropDatabaseWithProtectionEnabled() throws Exception { fail("Expected exception"); } catch (SpannerException e) { assertEquals(ErrorCode.FAILED_PRECONDITION, e.getErrorCode()); - assertThat(e.getMessage()).endsWith( - "because it contains databases with drop protection enabled. Please disable drop " + - "protection for all databases in the instance before deleting it."); + assertThat(e.getMessage()) + .endsWith( + "because it contains databases with drop protection enabled. Please disable drop " + + "protection for all databases in the instance before deleting it."); } // Disable drop protection for the database. - update_to = dbAdminClient.newDatabaseBuilder(database.getId()) - .disableDropProtection().build(); - op = dbAdminClient.updateDatabase(update_to, - DatabaseField.ENABLE_DROP_PROTECTION); + update_to = dbAdminClient.newDatabaseBuilder(database.getId()).disableDropProtection().build(); + op = dbAdminClient.updateDatabase(update_to, DatabaseField.ENABLE_DROP_PROTECTION); updated = op.get(); assertEquals(updated.getId().getName(), database.getId().getName()); assertFalse(updated.isDropProtectionEnabled()); From fd2f7e634b0093ce7dce477ff43726ade70a2487 Mon Sep 17 00:00:00 2001 From: aayushimalik Date: Tue, 4 Apr 2023 06:04:11 +0000 Subject: [PATCH 04/10] Rename database field `ENABLE_DROP_PROTECTION` to `DROP_PROTECTION`. --- .../src/main/java/com/google/cloud/spanner/DatabaseInfo.java | 2 +- .../com/google/cloud/spanner/DatabaseAdminClientImplTest.java | 4 ++-- .../java/com/google/cloud/spanner/it/ITDatabaseAdminTest.java | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseInfo.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseInfo.java index 704e1960ffe..140c3db6b94 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseInfo.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseInfo.java @@ -30,7 +30,7 @@ public class DatabaseInfo { /** Represent an updatable field in a Cloud Spanner database. */ public enum DatabaseField implements FieldSelector { - ENABLE_DROP_PROTECTION("enable_drop_protection"); + DROP_PROTECTION("drop_protection"); private final String selector; diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseAdminClientImplTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseAdminClientImplTest.java index 59b974e7ff2..d6c3da77282 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseAdminClientImplTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseAdminClientImplTest.java @@ -259,10 +259,10 @@ public void updateDatabase() throws Exception { OperationFutureUtil.immediateOperationFuture( "updateDatabase", database_proto, UpdateDatabaseMetadata.getDefaultInstance()); when(rpc.updateDatabase( - database_proto, DatabaseField.toFieldMask(DatabaseField.ENABLE_DROP_PROTECTION))) + database_proto, DatabaseField.toFieldMask(DatabaseField.DROP_PROTECTION))) .thenReturn(rawOperationFuture); OperationFuture op = - client.updateDatabase(database, DatabaseField.ENABLE_DROP_PROTECTION); + client.updateDatabase(database, DatabaseField.DROP_PROTECTION); assertTrue(op.isDone()); assertEquals(op.get().getId().getName(), DB_NAME); assertTrue(op.get().isDropProtectionEnabled()); diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITDatabaseAdminTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITDatabaseAdminTest.java index 39afb06754b..522929e474a 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITDatabaseAdminTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITDatabaseAdminTest.java @@ -290,7 +290,7 @@ public void dropDatabaseWithProtectionEnabled() throws Exception { Database update_to = dbAdminClient.newDatabaseBuilder(database.getId()).enableDropProtection().build(); OperationFuture op = - dbAdminClient.updateDatabase(update_to, DatabaseField.ENABLE_DROP_PROTECTION); + dbAdminClient.updateDatabase(update_to, DatabaseField.DROP_PROTECTION); Database updated = op.get(); assertEquals(updated.getId().getName(), database.getId().getName()); assertTrue(updated.isDropProtectionEnabled()); @@ -324,7 +324,7 @@ public void dropDatabaseWithProtectionEnabled() throws Exception { // Disable drop protection for the database. update_to = dbAdminClient.newDatabaseBuilder(database.getId()).disableDropProtection().build(); - op = dbAdminClient.updateDatabase(update_to, DatabaseField.ENABLE_DROP_PROTECTION); + op = dbAdminClient.updateDatabase(update_to, DatabaseField.DROP_PROTECTION); updated = op.get(); assertEquals(updated.getId().getName(), database.getId().getName()); assertFalse(updated.isDropProtectionEnabled()); From 966794b0b246d9eb4f78160c378507cf2bd6fbd4 Mon Sep 17 00:00:00 2001 From: aayushimalik Date: Mon, 10 Apr 2023 16:06:32 +0000 Subject: [PATCH 05/10] Reviewer suggested changes. --- .../com/google/cloud/spanner/Database.java | 3 -- .../cloud/spanner/DatabaseAdminClient.java | 14 ++++---- .../google/cloud/spanner/DatabaseInfo.java | 5 ++- .../spanner/DatabaseAdminClientImplTest.java | 6 ++-- .../cloud/spanner/it/ITDatabaseAdminTest.java | 32 +++++++------------ 5 files changed, 25 insertions(+), 35 deletions(-) diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Database.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Database.java index e7747d089e4..92c4d55d4d9 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Database.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Database.java @@ -216,9 +216,6 @@ static Database fromProto( } public com.google.spanner.admin.database.v1.Database toProto() { - if (getProto() != null) { - return getProto(); - } com.google.spanner.admin.database.v1.Database.Builder builder = com.google.spanner.admin.database.v1.Database.newBuilder() .setName(getId().getName()) diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseAdminClient.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseAdminClient.java index 66c18891447..9168c2a11dd 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseAdminClient.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseAdminClient.java @@ -380,13 +380,15 @@ OperationFuture restoreDatabase(Restore resto *

Example of updating a database. * *

{@code
+   * String projectId = my_project_id;
+   * String instanceId = my_instance_id;
    * String databaseId = my_database_id;
-   * DatabaseInfo to_update =
-   *    DatabaseInfo.newBuilder(DatabaseId.of(databaseId))
-   *        .enableDropProtection();
-   * OperationFuture op =
-   *    dbAdminClient.updateDatabase(to_update, DatabaseInfo.DatabaseField.ENABLE_DROP_PROTECTION);
-   * Database db = op.waitFor().getResult();
+   * Database databaseToUpdate = databaseAdminClient.newDatabaseBuilder(
+   *         DatabaseId.of(projectId, instanceId, databaseId))
+   *      .enableDropProtection().build();
+   * OperationFuture op = databaseAdminClient.updateDatabase(
+   *           databaseToUpdate, DatabaseField.DROP_PROTECTION);
+   * Database updateDatabase = op.get(5, TimeUnit.MINUTES);
    * }
* * @param database The database to update to. The current field values of the database will be diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseInfo.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseInfo.java index 140c3db6b94..45d1ca2023f 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseInfo.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseInfo.java @@ -30,7 +30,7 @@ public class DatabaseInfo { /** Represent an updatable field in a Cloud Spanner database. */ public enum DatabaseField implements FieldSelector { - DROP_PROTECTION("drop_protection"); + DROP_PROTECTION("enable_drop_protection"); private final String selector; @@ -349,7 +349,6 @@ public Timestamp getEarliestVersionTime() { return dialect; } - /** @return */ public boolean isDropProtectionEnabled() { return dropProtectionEnabled; } @@ -404,7 +403,7 @@ public int hashCode() { @Override public String toString() { return String.format( - "Database[%s, %s, %s, %s, %s, %s, %s, %s, %s %d %d]", + "Database[%s, %s, %s, %s, %s, %s, %s, %s, %s %s %s]", id.getName(), state, createTime, diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseAdminClientImplTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseAdminClientImplTest.java index d6c3da77282..dbb08f51a4c 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseAdminClientImplTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseAdminClientImplTest.java @@ -254,12 +254,12 @@ public void updateDatabaseDdlOpAlreadyExists() throws Exception { public void updateDatabase() throws Exception { com.google.cloud.spanner.Database database = client.newDatabaseBuilder(DatabaseId.of(DB_NAME)).enableDropProtection().build(); - Database database_proto = database.toProto(); + Database databaseProto = database.toProto(); OperationFuture rawOperationFuture = OperationFutureUtil.immediateOperationFuture( - "updateDatabase", database_proto, UpdateDatabaseMetadata.getDefaultInstance()); + "updateDatabase", databaseProto, UpdateDatabaseMetadata.getDefaultInstance()); when(rpc.updateDatabase( - database_proto, DatabaseField.toFieldMask(DatabaseField.DROP_PROTECTION))) + databaseProto, DatabaseField.toFieldMask(DatabaseField.DROP_PROTECTION))) .thenReturn(rawOperationFuture); OperationFuture op = client.updateDatabase(database, DatabaseField.DROP_PROTECTION); diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITDatabaseAdminTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITDatabaseAdminTest.java index 522929e474a..293c57516ae 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITDatabaseAdminTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITDatabaseAdminTest.java @@ -287,16 +287,15 @@ public void dropDatabaseWithProtectionEnabled() throws Exception { logger.log(Level.INFO, "Created database: {0}", database.getId().getName()); // Enable drop protection for the database. - Database update_to = + Database databaseToUpdate = dbAdminClient.newDatabaseBuilder(database.getId()).enableDropProtection().build(); OperationFuture op = - dbAdminClient.updateDatabase(update_to, DatabaseField.DROP_PROTECTION); - Database updated = op.get(); - assertEquals(updated.getId().getName(), database.getId().getName()); - assertTrue(updated.isDropProtectionEnabled()); + dbAdminClient.updateDatabase(databaseToUpdate, DatabaseField.DROP_PROTECTION); + Database updatedDatabase = op.get(5, TimeUnit.MINUTES); + assertEquals(updatedDatabase.getId().getName(), database.getId().getName()); + assertTrue(updatedDatabase.isDropProtectionEnabled()); - String[] split = database.getId().getName().split("/"); - String databaseId = split[split.length - 1]; + String databaseId = database.getId().getDatabase(); // Assert that dropping a database with protection enabled fails due to precondition violation. try { @@ -304,10 +303,6 @@ public void dropDatabaseWithProtectionEnabled() throws Exception { fail("Expected exception"); } catch (SpannerException e) { assertEquals(ErrorCode.FAILED_PRECONDITION, e.getErrorCode()); - assertThat(e.getMessage()) - .endsWith( - "because the `enable_drop_protection` setting is currently enabled for it. Please " - + "disable the setting and try again."); } // Assert that deleting the instance also fails due to precondition violation. @@ -316,18 +311,15 @@ public void dropDatabaseWithProtectionEnabled() throws Exception { fail("Expected exception"); } catch (SpannerException e) { assertEquals(ErrorCode.FAILED_PRECONDITION, e.getErrorCode()); - assertThat(e.getMessage()) - .endsWith( - "because it contains databases with drop protection enabled. Please disable drop " - + "protection for all databases in the instance before deleting it."); } // Disable drop protection for the database. - update_to = dbAdminClient.newDatabaseBuilder(database.getId()).disableDropProtection().build(); - op = dbAdminClient.updateDatabase(update_to, DatabaseField.DROP_PROTECTION); - updated = op.get(); - assertEquals(updated.getId().getName(), database.getId().getName()); - assertFalse(updated.isDropProtectionEnabled()); + databaseToUpdate = + dbAdminClient.newDatabaseBuilder(database.getId()).disableDropProtection().build(); + op = dbAdminClient.updateDatabase(databaseToUpdate, DatabaseField.DROP_PROTECTION); + updatedDatabase = op.get(5, TimeUnit.MINUTES); + assertEquals(updatedDatabase.getId().getName(), database.getId().getName()); + assertFalse(updatedDatabase.isDropProtectionEnabled()); // Dropping the database should succeed now. dbAdminClient.dropDatabase(instanceId, databaseId); From af115fc1dfcb54200b46bdb8515349ca94e4b823 Mon Sep 17 00:00:00 2001 From: aayushimalik Date: Mon, 10 Apr 2023 16:20:36 +0000 Subject: [PATCH 06/10] Reviewer suggested changes. --- .../com/google/cloud/spanner/spi/v1/SpannerRpc.java | 10 ++++++++++ .../java/com/google/cloud/spanner/DatabaseTest.java | 12 ++++++------ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/SpannerRpc.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/SpannerRpc.java index 38541f59456..0d802892c89 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/SpannerRpc.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/SpannerRpc.java @@ -233,6 +233,16 @@ OperationFuture updateDatabaseDdl( Database getDatabase(String databaseName) throws SpannerException; + /** + * Updates the specified fields of a Cloud Spanner database. + * + * @param database The database proto whose field values will be used as the new values in the + * stored database. + * @param fieldMask The fields to update. Currently, only the "enable_drop_protection" field of + * the database supports updates. + * @return an `OperationFuture` that can be used to track the status of the update. + * @throws SpannerException + */ OperationFuture updateDatabase( Database database, FieldMask fieldMask) throws SpannerException; diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseTest.java index 5c6d5de40ae..8723d5cd336 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseTest.java @@ -67,9 +67,9 @@ public class DatabaseTest { private static final String DEFAULT_LEADER = "default-leader"; private static final DatabaseDialect DEFAULT_DIALECT = DatabaseDialect.GOOGLE_STANDARD_SQL; - private static final boolean DEFAULT_DROP_PROTECTION = true; + private static final boolean DROP_PROTECTION_ENABLED = true; - private static final boolean DEFAULT_RECONCILING = true; + private static final boolean RECONCILING = true; @Mock DatabaseAdminClient dbClient; @@ -131,8 +131,8 @@ public void testToProto() { assertEquals(ENCRYPTION_CONFIG, database.getEncryptionConfig()); assertEquals(DEFAULT_LEADER, database.getDefaultLeader()); assertEquals(DEFAULT_DIALECT, database.getDatabaseDialect()); - assertEquals(DEFAULT_DROP_PROTECTION, database.getEnableDropProtection()); - assertEquals(DEFAULT_RECONCILING, database.getReconciling()); + assertEquals(DROP_PROTECTION_ENABLED, database.getEnableDropProtection()); + assertEquals(RECONCILING, database.getReconciling()); } @Test @@ -251,8 +251,8 @@ private com.google.spanner.admin.database.v1.Database defaultProtoDatabase() { .addAllEncryptionInfo(ENCRYPTION_INFOS) .setDefaultLeader(DEFAULT_LEADER) .setDatabaseDialect(DEFAULT_DIALECT) - .setEnableDropProtection(DEFAULT_DROP_PROTECTION) - .setReconciling(DEFAULT_RECONCILING) + .setEnableDropProtection(DROP_PROTECTION_ENABLED) + .setReconciling(RECONCILING) .build(); } } From b514e15ab39374371828a976534c48baf10222cd Mon Sep 17 00:00:00 2001 From: aayushimalik Date: Tue, 11 Apr 2023 06:21:13 +0000 Subject: [PATCH 07/10] Reviewer suggested changes. --- .../src/test/java/com/google/cloud/spanner/DatabaseTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseTest.java index 8723d5cd336..78c04ae61bc 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseTest.java @@ -117,8 +117,8 @@ public void testFromProto() { EncryptionConfigs.customerManagedEncryption(KMS_KEY_NAME), database.getEncryptionConfig()); assertEquals(DEFAULT_LEADER, database.getDefaultLeader()); assertEquals(Dialect.GOOGLE_STANDARD_SQL, database.getDialect()); - assertEquals(DEFAULT_DROP_PROTECTION, database.isDropProtectionEnabled()); - assertEquals(DEFAULT_RECONCILING, database.getReconciling()); + assertEquals(DROP_PROTECTION_ENABLED, database.isDropProtectionEnabled()); + assertEquals(RECONCILING, database.getReconciling()); } @Test From 57fb63d9871a85a1f82fc9221899fad33de2ee8c Mon Sep 17 00:00:00 2001 From: aayushimalik Date: Thu, 13 Apr 2023 07:01:16 +0000 Subject: [PATCH 08/10] Reviewer suggested changes. --- .../google/cloud/spanner/DatabaseInfo.java | 4 +- .../cloud/spanner/it/ITDatabaseAdminTest.java | 43 +++++++++++++------ 2 files changed, 33 insertions(+), 14 deletions(-) diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseInfo.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseInfo.java index 45d1ca2023f..d231ef34e37 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseInfo.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseInfo.java @@ -380,8 +380,8 @@ public boolean equals(Object o) { && Objects.equals(encryptionConfig, that.encryptionConfig) && Objects.equals(defaultLeader, that.defaultLeader) && Objects.equals(dialect, that.dialect) - && dropProtectionEnabled == that.dropProtectionEnabled - && reconciling == that.reconciling; + && Objects.equals(dropProtectionEnabled, that.dropProtectionEnabled) + && Objects.equals(reconciling, that.reconciling); } @Override diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITDatabaseAdminTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITDatabaseAdminTest.java index 293c57516ae..0bd94428465 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITDatabaseAdminTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITDatabaseAdminTest.java @@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.junit.Assume.assumeFalse; @@ -45,6 +46,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; +import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; @@ -277,6 +279,26 @@ public void createAndListDatabaseRoles() throws Exception { assertThat(dbRolesRemaining).containsNoneIn(dbRoles); } + @Test + public void updateDatabaseInvalidFieldsToUpdate() throws Exception { + String instanceId = testHelper.getInstanceId().getInstance(); + Database database = + dbAdminClient + .createDatabase(instanceId, testHelper.getUniqueDatabaseId(), ImmutableList.of()) + .get(); + logger.log(Level.INFO, "Created database: {0}", database.getId().getName()); + + Database databaseToUpdate = + dbAdminClient.newDatabaseBuilder(database.getId()).enableDropProtection().build(); + // Don't provide any fields to update. + OperationFuture op = + dbAdminClient.updateDatabase(databaseToUpdate); + + ExecutionException e = + assertThrows(ExecutionException.class, () -> op.get(5, TimeUnit.MINUTES)); + assertThat(e.getMessage()).contains("Invalid field mask"); + } + @Test public void dropDatabaseWithProtectionEnabled() throws Exception { String instanceId = testHelper.getInstanceId().getInstance(); @@ -298,20 +320,17 @@ public void dropDatabaseWithProtectionEnabled() throws Exception { String databaseId = database.getId().getDatabase(); // Assert that dropping a database with protection enabled fails due to precondition violation. - try { - dbAdminClient.dropDatabase(instanceId, databaseId); - fail("Expected exception"); - } catch (SpannerException e) { - assertEquals(ErrorCode.FAILED_PRECONDITION, e.getErrorCode()); - } + SpannerException e = + assertThrows( + SpannerException.class, () -> dbAdminClient.dropDatabase(instanceId, databaseId)); + assertEquals(ErrorCode.FAILED_PRECONDITION, e.getErrorCode()); // Assert that deleting the instance also fails due to precondition violation. - try { - testHelper.getClient().getInstanceAdminClient().deleteInstance(instanceId); - fail("Expected exception"); - } catch (SpannerException e) { - assertEquals(ErrorCode.FAILED_PRECONDITION, e.getErrorCode()); - } + e = + assertThrows( + SpannerException.class, + () -> testHelper.getClient().getInstanceAdminClient().deleteInstance(instanceId)); + assertEquals(ErrorCode.FAILED_PRECONDITION, e.getErrorCode()); // Disable drop protection for the database. databaseToUpdate = From abca15ca9362944790a7a83c0ef8fedc4c365043 Mon Sep 17 00:00:00 2001 From: Owl Bot Date: Mon, 15 May 2023 10:58:13 +0000 Subject: [PATCH 09/10] =?UTF-8?q?=F0=9F=A6=89=20Updates=20from=20OwlBot=20?= =?UTF-8?q?post-processor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d5db9eb8177..034c0ede481 100644 --- a/README.md +++ b/README.md @@ -49,20 +49,20 @@ If you are using Maven without BOM, add this to your dependencies: If you are using Gradle 5.x or later, add this to your dependencies: ```Groovy -implementation platform('com.google.cloud:libraries-bom:26.6.0') +implementation platform('com.google.cloud:libraries-bom:26.14.0') implementation 'com.google.cloud:google-cloud-spanner' ``` If you are using Gradle without BOM, add this to your dependencies: ```Groovy -implementation 'com.google.cloud:google-cloud-spanner:6.35.2' +implementation 'com.google.cloud:google-cloud-spanner:6.41.0' ``` If you are using SBT, add this to your dependencies: ```Scala -libraryDependencies += "com.google.cloud" % "google-cloud-spanner" % "6.35.2" +libraryDependencies += "com.google.cloud" % "google-cloud-spanner" % "6.41.0" ``` ## Authentication From 11a7609dfa481e525d5ab2a18be1e13901a14b18 Mon Sep 17 00:00:00 2001 From: Rajat Bhatta <93644539+rajatbhatta@users.noreply.github.com> Date: Mon, 15 May 2023 18:43:52 +0530 Subject: [PATCH 10/10] skip emulator tests --- .../java/com/google/cloud/spanner/it/ITDatabaseAdminTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITDatabaseAdminTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITDatabaseAdminTest.java index 0bd94428465..6bb02a7a61f 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITDatabaseAdminTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITDatabaseAdminTest.java @@ -281,6 +281,7 @@ public void createAndListDatabaseRoles() throws Exception { @Test public void updateDatabaseInvalidFieldsToUpdate() throws Exception { + assumeFalse("Emulator does not drop database protection", isUsingEmulator()); String instanceId = testHelper.getInstanceId().getInstance(); Database database = dbAdminClient @@ -301,6 +302,7 @@ public void updateDatabaseInvalidFieldsToUpdate() throws Exception { @Test public void dropDatabaseWithProtectionEnabled() throws Exception { + assumeFalse("Emulator does not drop database protection", isUsingEmulator()); String instanceId = testHelper.getInstanceId().getInstance(); Database database = dbAdminClient