Skip to content

Commit 2ea06e7

Browse files
aayushimalikgcf-owl-bot[bot]rajatbhatta
authored
feat: add support for UpdateDatabase in Cloud Spanner (#2265)
* feature: Cloud Spanner Drop Database Protection Co-authored-by: Owl Bot <gcf-owl-bot[bot]@users.noreply.github.com> Co-authored-by: Rajat Bhatta <93644539+rajatbhatta@users.noreply.github.com>
1 parent 09f20bd commit 2ea06e7

File tree

10 files changed

+374
-19
lines changed

10 files changed

+374
-19
lines changed

google-cloud-spanner/src/main/java/com/google/cloud/spanner/Database.java

+50-11
Original file line numberDiff line numberDiff line change
@@ -194,17 +194,56 @@ private String database() {
194194
static Database fromProto(
195195
com.google.spanner.admin.database.v1.Database proto, DatabaseAdminClient client) {
196196
checkArgument(!proto.getName().isEmpty(), "Missing expected 'name' field");
197-
return new Database.Builder(client, DatabaseId.of(proto.getName()))
198-
.setState(fromProtoState(proto.getState()))
199-
.setCreateTime(Timestamp.fromProto(proto.getCreateTime()))
200-
.setRestoreInfo(RestoreInfo.fromProtoOrNullIfDefaultInstance(proto.getRestoreInfo()))
201-
.setVersionRetentionPeriod(proto.getVersionRetentionPeriod())
202-
.setEarliestVersionTime(Timestamp.fromProto(proto.getEarliestVersionTime()))
203-
.setEncryptionConfig(CustomerManagedEncryption.fromProtoOrNull(proto.getEncryptionConfig()))
204-
.setDefaultLeader(proto.getDefaultLeader())
205-
.setDialect(Dialect.fromProto(proto.getDatabaseDialect()))
206-
.setProto(proto)
207-
.build();
197+
DatabaseInfo.Builder builder =
198+
new Builder(client, DatabaseId.of(proto.getName()))
199+
.setState(fromProtoState(proto.getState()))
200+
.setCreateTime(Timestamp.fromProto(proto.getCreateTime()))
201+
.setRestoreInfo(RestoreInfo.fromProtoOrNullIfDefaultInstance(proto.getRestoreInfo()))
202+
.setVersionRetentionPeriod(proto.getVersionRetentionPeriod())
203+
.setEarliestVersionTime(Timestamp.fromProto(proto.getEarliestVersionTime()))
204+
.setEncryptionConfig(
205+
CustomerManagedEncryption.fromProtoOrNull(proto.getEncryptionConfig()))
206+
.setDefaultLeader(proto.getDefaultLeader())
207+
.setDialect(Dialect.fromProto(proto.getDatabaseDialect()))
208+
.setReconciling(proto.getReconciling())
209+
.setProto(proto);
210+
if (proto.getEnableDropProtection()) {
211+
builder.enableDropProtection();
212+
} else {
213+
builder.disableDropProtection();
214+
}
215+
return builder.build();
216+
}
217+
218+
public com.google.spanner.admin.database.v1.Database toProto() {
219+
com.google.spanner.admin.database.v1.Database.Builder builder =
220+
com.google.spanner.admin.database.v1.Database.newBuilder()
221+
.setName(getId().getName())
222+
.setState(getState().toProto())
223+
.setEnableDropProtection(isDropProtectionEnabled())
224+
.setReconciling(getReconciling());
225+
if (getCreateTime() != null) {
226+
builder.setCreateTime(getCreateTime().toProto());
227+
}
228+
if (getRestoreInfo() != null) {
229+
builder.setRestoreInfo(getRestoreInfo().getProto());
230+
}
231+
if (getVersionRetentionPeriod() != null) {
232+
builder.setVersionRetentionPeriod(getVersionRetentionPeriod());
233+
}
234+
if (getEarliestVersionTime() != null) {
235+
builder.setEarliestVersionTime(getEarliestVersionTime().toProto());
236+
}
237+
if (getEncryptionConfig() != null) {
238+
builder.setEncryptionConfig(getEncryptionConfig().toProto());
239+
}
240+
if (getDefaultLeader() != null) {
241+
builder.setDefaultLeader(getDefaultLeader());
242+
}
243+
if (getDialect() != null) {
244+
builder.setDatabaseDialect(getDialect().toProto());
245+
}
246+
return builder.build();
208247
}
209248

210249
static DatabaseInfo.State fromProtoState(

google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseAdminClient.java

+42
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import com.google.spanner.admin.database.v1.CreateDatabaseRequest;
2929
import com.google.spanner.admin.database.v1.RestoreDatabaseMetadata;
3030
import com.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata;
31+
import com.google.spanner.admin.database.v1.UpdateDatabaseMetadata;
3132
import java.util.List;
3233
import javax.annotation.Nullable;
3334

@@ -358,6 +359,47 @@ OperationFuture<Database, RestoreDatabaseMetadata> restoreDatabase(Restore resto
358359
*/
359360
Database getDatabase(String instanceId, String databaseId) throws SpannerException;
360361

362+
/**
363+
* Updates a Cloud Spanner database. The returned {@code Operation} can be used to track the
364+
* progress of the update. Throws SpannerException if the Cloud Spanner database does not exist.
365+
*
366+
* <p>Until completion of the returned operation:
367+
*
368+
* <ul>
369+
* <li>Cancelling the operation is best effort and may or may not succeed.
370+
* <li>All other attempts to modify the database are rejected.
371+
* <li>Reading the database via the API continues to give the pre-request field values.
372+
* </ul>
373+
*
374+
* Upon completion of the returned operation:
375+
*
376+
* <ul>
377+
* <li>The database's new fields are readable via the API.
378+
* </ul>
379+
*
380+
* <p>Example of updating a database.
381+
*
382+
* <pre>{@code
383+
* String projectId = my_project_id;
384+
* String instanceId = my_instance_id;
385+
* String databaseId = my_database_id;
386+
* Database databaseToUpdate = databaseAdminClient.newDatabaseBuilder(
387+
* DatabaseId.of(projectId, instanceId, databaseId))
388+
* .enableDropProtection().build();
389+
* OperationFuture<Database, UpdateDatabaseMetadata> op = databaseAdminClient.updateDatabase(
390+
* databaseToUpdate, DatabaseField.DROP_PROTECTION);
391+
* Database updateDatabase = op.get(5, TimeUnit.MINUTES);
392+
* }</pre>
393+
*
394+
* @param database The database to update to. The current field values of the database will be
395+
* updated to the values specified in this parameter.
396+
* @param fieldsToUpdate The fields that should be updated. Only these fields will have their
397+
* values updated to the values specified in {@param database}, even if there are other fields
398+
* specified in {@param database}.
399+
*/
400+
OperationFuture<Database, UpdateDatabaseMetadata> updateDatabase(
401+
Database database, DatabaseInfo.DatabaseField... fieldsToUpdate) throws SpannerException;
402+
361403
/**
362404
* Gets the current state of a Cloud Spanner database backup.
363405
*

google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseAdminClientImpl.java

+22
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import com.google.cloud.Policy;
2424
import com.google.cloud.Policy.DefaultMarshaller;
2525
import com.google.cloud.Timestamp;
26+
import com.google.cloud.spanner.DatabaseInfo.DatabaseField;
2627
import com.google.cloud.spanner.Options.ListOption;
2728
import com.google.cloud.spanner.SpannerImpl.PageFetcher;
2829
import com.google.cloud.spanner.spi.v1.SpannerRpc;
@@ -415,6 +416,27 @@ public Database getDatabase(String instanceId, String databaseId) throws Spanner
415416
return Database.fromProto(rpc.getDatabase(dbName), DatabaseAdminClientImpl.this);
416417
}
417418

419+
@Override
420+
public OperationFuture<Database, UpdateDatabaseMetadata> updateDatabase(
421+
Database database, DatabaseField... fieldsToUpdate) throws SpannerException {
422+
FieldMask fieldMask = DatabaseInfo.DatabaseField.toFieldMask(fieldsToUpdate);
423+
OperationFuture<com.google.spanner.admin.database.v1.Database, UpdateDatabaseMetadata>
424+
rawOperationFuture = rpc.updateDatabase(database.toProto(), fieldMask);
425+
return new OperationFutureImpl<>(
426+
rawOperationFuture.getPollingFuture(),
427+
rawOperationFuture.getInitialFuture(),
428+
snapshot ->
429+
Database.fromProto(
430+
ProtoOperationTransformers.ResponseTransformer.create(
431+
com.google.spanner.admin.database.v1.Database.class)
432+
.apply(snapshot),
433+
DatabaseAdminClientImpl.this),
434+
ProtoOperationTransformers.MetadataTransformer.create(UpdateDatabaseMetadata.class),
435+
e -> {
436+
throw SpannerExceptionFactory.newSpannerException(e);
437+
});
438+
}
439+
418440
@Override
419441
public OperationFuture<Void, UpdateDatabaseDdlMetadata> updateDatabaseDdl(
420442
final String instanceId,

google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseInfo.java

+109-8
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,42 @@
1616

1717
package com.google.cloud.spanner;
1818

19+
import com.google.cloud.FieldSelector;
1920
import com.google.cloud.Timestamp;
2021
import com.google.cloud.spanner.encryption.CustomerManagedEncryption;
2122
import com.google.common.base.Preconditions;
23+
import com.google.protobuf.FieldMask;
24+
import com.google.spanner.admin.database.v1.Database.State;
2225
import java.util.Objects;
2326
import javax.annotation.Nullable;
2427

2528
/** Represents a Cloud Spanner database. */
2629
public class DatabaseInfo {
2730

31+
/** Represent an updatable field in a Cloud Spanner database. */
32+
public enum DatabaseField implements FieldSelector {
33+
DROP_PROTECTION("enable_drop_protection");
34+
35+
private final String selector;
36+
37+
DatabaseField(String selector) {
38+
this.selector = selector;
39+
}
40+
41+
@Override
42+
public String getSelector() {
43+
return selector;
44+
}
45+
46+
static FieldMask toFieldMask(DatabaseInfo.DatabaseField... fields) {
47+
FieldMask.Builder builder = FieldMask.newBuilder();
48+
for (DatabaseInfo.DatabaseField field : fields) {
49+
builder.addPaths(field.getSelector());
50+
}
51+
return builder.build();
52+
}
53+
}
54+
2855
public abstract static class Builder {
2956
abstract Builder setState(State state);
3057

@@ -58,6 +85,18 @@ public Builder setDialect(Dialect dialect) {
5885
throw new UnsupportedOperationException("Unimplemented");
5986
}
6087

88+
public Builder enableDropProtection() {
89+
throw new UnsupportedOperationException("Unimplemented");
90+
}
91+
92+
public Builder disableDropProtection() {
93+
throw new UnsupportedOperationException("Unimplemented");
94+
}
95+
96+
protected Builder setReconciling(boolean reconciling) {
97+
throw new UnsupportedOperationException("Unimplemented");
98+
}
99+
61100
abstract Builder setProto(com.google.spanner.admin.database.v1.Database proto);
62101

63102
/** Builds the database from this builder. */
@@ -74,6 +113,8 @@ abstract static class BuilderImpl extends Builder {
74113
private CustomerManagedEncryption encryptionConfig;
75114
private String defaultLeader;
76115
private Dialect dialect = Dialect.GOOGLE_STANDARD_SQL;
116+
private boolean dropProtectionEnabled;
117+
private boolean reconciling;
77118
private com.google.spanner.admin.database.v1.Database proto;
78119

79120
BuilderImpl(DatabaseId id) {
@@ -141,6 +182,24 @@ public Builder setDialect(Dialect dialect) {
141182
return this;
142183
}
143184

185+
@Override
186+
public Builder enableDropProtection() {
187+
this.dropProtectionEnabled = true;
188+
return this;
189+
}
190+
191+
@Override
192+
public Builder disableDropProtection() {
193+
this.dropProtectionEnabled = false;
194+
return this;
195+
}
196+
197+
@Override
198+
protected Builder setReconciling(boolean reconciling) {
199+
this.reconciling = reconciling;
200+
return this;
201+
}
202+
144203
@Override
145204
Builder setProto(@Nullable com.google.spanner.admin.database.v1.Database proto) {
146205
this.proto = proto;
@@ -151,13 +210,35 @@ Builder setProto(@Nullable com.google.spanner.admin.database.v1.Database proto)
151210
/** State of the database. */
152211
public enum State {
153212
// Not specified.
154-
UNSPECIFIED,
213+
UNSPECIFIED {
214+
@Override
215+
public com.google.spanner.admin.database.v1.Database.State toProto() {
216+
return com.google.spanner.admin.database.v1.Database.State.STATE_UNSPECIFIED;
217+
}
218+
},
155219
// The database is still being created and is not ready to use.
156-
CREATING,
220+
CREATING {
221+
@Override
222+
public com.google.spanner.admin.database.v1.Database.State toProto() {
223+
return com.google.spanner.admin.database.v1.Database.State.CREATING;
224+
}
225+
},
157226
// The database is fully created and ready to use.
158-
READY,
227+
READY {
228+
@Override
229+
public com.google.spanner.admin.database.v1.Database.State toProto() {
230+
return com.google.spanner.admin.database.v1.Database.State.READY;
231+
}
232+
},
159233
// The database has restored and is being optimized for use.
160-
READY_OPTIMIZING
234+
READY_OPTIMIZING {
235+
@Override
236+
public com.google.spanner.admin.database.v1.Database.State toProto() {
237+
return com.google.spanner.admin.database.v1.Database.State.READY_OPTIMIZING;
238+
}
239+
};
240+
241+
public abstract com.google.spanner.admin.database.v1.Database.State toProto();
161242
}
162243

163244
private final DatabaseId id;
@@ -169,6 +250,8 @@ public enum State {
169250
private final CustomerManagedEncryption encryptionConfig;
170251
private final String defaultLeader;
171252
private final Dialect dialect;
253+
private final boolean dropProtectionEnabled;
254+
private final boolean reconciling;
172255
private final com.google.spanner.admin.database.v1.Database proto;
173256

174257
public DatabaseInfo(DatabaseId id, State state) {
@@ -181,6 +264,8 @@ public DatabaseInfo(DatabaseId id, State state) {
181264
this.encryptionConfig = null;
182265
this.defaultLeader = null;
183266
this.dialect = null;
267+
this.dropProtectionEnabled = false;
268+
this.reconciling = false;
184269
this.proto = null;
185270
}
186271

@@ -194,6 +279,8 @@ public DatabaseInfo(DatabaseId id, State state) {
194279
this.encryptionConfig = builder.encryptionConfig;
195280
this.defaultLeader = builder.defaultLeader;
196281
this.dialect = builder.dialect;
282+
this.dropProtectionEnabled = builder.dropProtectionEnabled;
283+
this.reconciling = builder.reconciling;
197284
this.proto = builder.proto;
198285
}
199286

@@ -262,6 +349,14 @@ public Timestamp getEarliestVersionTime() {
262349
return dialect;
263350
}
264351

352+
public boolean isDropProtectionEnabled() {
353+
return dropProtectionEnabled;
354+
}
355+
356+
public boolean getReconciling() {
357+
return reconciling;
358+
}
359+
265360
/** Returns the raw proto instance that was used to construct this {@link Database}. */
266361
public @Nullable com.google.spanner.admin.database.v1.Database getProto() {
267362
return proto;
@@ -284,7 +379,9 @@ public boolean equals(Object o) {
284379
&& Objects.equals(earliestVersionTime, that.earliestVersionTime)
285380
&& Objects.equals(encryptionConfig, that.encryptionConfig)
286381
&& Objects.equals(defaultLeader, that.defaultLeader)
287-
&& Objects.equals(dialect, that.dialect);
382+
&& Objects.equals(dialect, that.dialect)
383+
&& Objects.equals(dropProtectionEnabled, that.dropProtectionEnabled)
384+
&& Objects.equals(reconciling, that.reconciling);
288385
}
289386

290387
@Override
@@ -298,13 +395,15 @@ public int hashCode() {
298395
earliestVersionTime,
299396
encryptionConfig,
300397
defaultLeader,
301-
dialect);
398+
dialect,
399+
dropProtectionEnabled,
400+
reconciling);
302401
}
303402

304403
@Override
305404
public String toString() {
306405
return String.format(
307-
"Database[%s, %s, %s, %s, %s, %s, %s, %s, %s]",
406+
"Database[%s, %s, %s, %s, %s, %s, %s, %s, %s %s %s]",
308407
id.getName(),
309408
state,
310409
createTime,
@@ -313,6 +412,8 @@ public String toString() {
313412
earliestVersionTime,
314413
encryptionConfig,
315414
defaultLeader,
316-
dialect);
415+
dialect,
416+
dropProtectionEnabled,
417+
reconciling);
317418
}
318419
}

google-cloud-spanner/src/main/java/com/google/cloud/spanner/encryption/CustomerManagedEncryption.java

+4
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ public static CustomerManagedEncryption fromProtoOrNull(EncryptionConfig proto)
4242
: new CustomerManagedEncryption(proto.getKmsKeyName());
4343
}
4444

45+
public EncryptionConfig toProto() {
46+
return EncryptionConfig.newBuilder().setKmsKeyName(this.getKmsKeyName()).build();
47+
}
48+
4549
@Override
4650
public boolean equals(Object o) {
4751
if (this == o) {

0 commit comments

Comments
 (0)