17 files changed

+935
-88
lines changed
Original file line numberDiff line numberDiff line change
@@ -453,7 +453,7 @@
453453
<className>com/google/cloud/spanner/TransactionContext</className>
454454
<method>com.google.api.core.ApiFuture executeUpdateAsync(com.google.cloud.spanner.Statement)</method>
455455
</difference>
456-
456+
457457
<!-- Support for CommitStats added -->
458458
<difference>
459459
<differenceType>7012</differenceType>
@@ -475,5 +475,16 @@
475475
<className>com/google/cloud/spanner/TransactionRunner</className>
476476
<method>com.google.cloud.spanner.CommitResponse getCommitResponse()</method>
477477
</difference>
478-
478+
479+
<!-- PITR -->
480+
<difference>
481+
<differenceType>7013</differenceType>
482+
<className>com/google/cloud/spanner/BackupInfo$Builder</className>
483+
<method>com.google.cloud.spanner.BackupInfo$Builder setVersionTime(com.google.cloud.Timestamp)</method>
484+
</difference>
485+
<difference>
486+
<differenceType>7012</differenceType>
487+
<className>com/google/cloud/spanner/DatabaseAdminClient</className>
488+
<method>com.google.api.gax.longrunning.OperationFuture createBackup(com.google.cloud.spanner.Backup)</method>
489+
</difference>
479490
</differences>
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ public OperationFuture<Backup, CreateBackupMetadata> create() {
6565
getExpireTime() != null, "Cannot create a backup without an expire time");
6666
Preconditions.checkState(
6767
getDatabase() != null, "Cannot create a backup without a source database");
68-
return dbClient.createBackup(instance(), backup(), sourceDatabase(), getExpireTime());
68+
return dbClient.createBackup(this);
6969
}
7070

7171
/**
@@ -182,6 +182,7 @@ static Backup fromProto(
182182
.setState(fromProtoState(proto.getState()))
183183
.setSize(proto.getSizeBytes())
184184
.setExpireTime(Timestamp.fromProto(proto.getExpireTime()))
185+
.setVersionTime(Timestamp.fromProto(proto.getVersionTime()))
185186
.setDatabase(DatabaseId.of(proto.getDatabase()))
186187
.setProto(proto)
187188
.build();
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import com.google.api.client.util.Preconditions;
2020
import com.google.cloud.Timestamp;
21+
import com.google.spanner.admin.database.v1.Database;
2122
import java.util.Objects;
2223
import javax.annotation.Nullable;
2324

@@ -40,6 +41,17 @@ public abstract static class Builder {
4041
*/
4142
public abstract Builder setExpireTime(Timestamp expireTime);
4243

44+
/**
45+
* Optional for creating a new backup.
46+
*
47+
* <p>Specifies the timestamp to have an externally consistent copy of the database. If no
48+
* version time is specified, it will be automatically set to the backup create time.
49+
*
50+
* <p>The version time can be as far in the past as specified by the database earliest version
51+
* time (see {@link Database#getEarliestVersionTime()}).
52+
*/
53+
public abstract Builder setVersionTime(Timestamp versionTime);
54+
4355
/**
4456
* Required for creating a new backup.
4557
*
@@ -55,6 +67,7 @@ abstract static class BuilderImpl extends Builder {
5567
protected final BackupId id;
5668
private State state = State.UNSPECIFIED;
5769
private Timestamp expireTime;
70+
private Timestamp versionTime;
5871
private DatabaseId database;
5972
private long size;
6073
private com.google.spanner.admin.database.v1.Backup proto;
@@ -67,6 +80,7 @@ abstract static class BuilderImpl extends Builder {
6780
this.id = other.id;
6881
this.state = other.state;
6982
this.expireTime = other.expireTime;
83+
this.versionTime = other.versionTime;
7084
this.database = other.database;
7185
this.size = other.size;
7286
this.proto = other.proto;
@@ -84,6 +98,12 @@ public Builder setExpireTime(Timestamp expireTime) {
8498
return this;
8599
}
86100

101+
@Override
102+
public Builder setVersionTime(Timestamp versionTime) {
103+
this.versionTime = versionTime;
104+
return this;
105+
}
106+
87107
@Override
88108
public Builder setDatabase(DatabaseId database) {
89109
Preconditions.checkArgument(
@@ -119,6 +139,7 @@ public enum State {
119139
private final BackupId id;
120140
private final State state;
121141
private final Timestamp expireTime;
142+
private final Timestamp versionTime;
122143
private final DatabaseId database;
123144
private final long size;
124145
private final com.google.spanner.admin.database.v1.Backup proto;
@@ -128,6 +149,7 @@ public enum State {
128149
this.state = builder.state;
129150
this.size = builder.size;
130151
this.expireTime = builder.expireTime;
152+
this.versionTime = builder.versionTime;
131153
this.database = builder.database;
132154
this.proto = builder.proto;
133155
}
@@ -157,6 +179,11 @@ public Timestamp getExpireTime() {
157179
return expireTime;
158180
}
159181

182+
/** Returns the version time of the backup. */
183+
public Timestamp getVersionTime() {
184+
return versionTime;
185+
}
186+
160187
/** Returns the id of the database that was used to create the backup. */
161188
public DatabaseId getDatabase() {
162189
return database;
@@ -180,17 +207,19 @@ public boolean equals(Object o) {
180207
&& state == that.state
181208
&& size == that.size
182209
&& Objects.equals(expireTime, that.expireTime)
210+
&& Objects.equals(versionTime, that.versionTime)
183211
&& Objects.equals(database, that.database);
184212
}
185213

186214
@Override
187215
public int hashCode() {
188-
return Objects.hash(id, state, size, expireTime, database);
216+
return Objects.hash(id, state, size, expireTime, versionTime, database);
189217
}
190218

191219
@Override
192220
public String toString() {
193221
return String.format(
194-
"Backup[%s, %s, %d, %s, %s]", id.getName(), state, size, expireTime, database);
222+
"Backup[%s, %s, %d, %s, %s, %s]",
223+
id.getName(), state, size, expireTime, versionTime, database);
195224
}
196225
}
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,14 @@ public OperationFuture<Backup, CreateBackupMetadata> backup(Backup backup) {
118118
Preconditions.checkArgument(
119119
backup.getInstanceId().equals(getId().getInstanceId()),
120120
"The instance of the backup must be equal to the instance of this database.");
121+
121122
return dbClient.createBackup(
122-
instance(), backup.getId().getBackup(), database(), backup.getExpireTime());
123+
dbClient
124+
.newBackupBuilder(backup.getId())
125+
.setDatabase(getId())
126+
.setExpireTime(backup.getExpireTime())
127+
.setVersionTime(backup.getVersionTime())
128+
.build());
123129
}
124130

125131
/**
@@ -177,6 +183,8 @@ static Database fromProto(
177183
.setState(fromProtoState(proto.getState()))
178184
.setCreateTime(Timestamp.fromProto(proto.getCreateTime()))
179185
.setRestoreInfo(RestoreInfo.fromProtoOrNullIfDefaultInstance(proto.getRestoreInfo()))
186+
.setVersionRetentionPeriod(proto.getVersionRetentionPeriod())
187+
.setEarliestVersionTime(Timestamp.fromProto(proto.getEarliestVersionTime()))
180188
.setProto(proto)
181189
.build();
182190
}
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,32 @@ OperationFuture<Backup, CreateBackupMetadata> createBackup(
101101
String sourceInstanceId, String backupId, String databaseId, Timestamp expireTime)
102102
throws SpannerException;
103103

104+
/**
105+
* Creates a new backup from a database in a Cloud Spanner instance.
106+
*
107+
* <p>Example to create a backup.
108+
*
109+
* <pre>{@code
110+
* BackupId backupId = BackupId.of("project", "instance", "backup-id");
111+
* DatabaseId databaseId = DatabaseId.of("project", "instance", "database-id");
112+
* Timestamp expireTime = Timestamp.ofTimeMicroseconds(expireTimeMicros);
113+
* Timestamp versionTime = Timestamp.ofTimeMicroseconds(versionTimeMicros);
114+
*
115+
* Backup backupToCreate = dbAdminClient
116+
* .newBackupBuilder(backupId)
117+
* .setDatabase(databaseId)
118+
* .setExpireTime(expireTime)
119+
* .setVersionTime(versionTime)
120+
* .build();
121+
*
122+
* OperationFuture<Backup, CreateBackupMetadata> op = dbAdminClient.createBackup(backupToCreate);
123+
* Backup createdBackup = op.get();
124+
* }</pre>
125+
*
126+
* @param backup the backup to be created
127+
*/
128+
OperationFuture<Backup, CreateBackupMetadata> createBackup(Backup backup) throws SpannerException;
129+
104130
/**
105131
* Restore a database from a backup. The database that is restored will be created and may not
106132
* already exist.
Original file line numberDiff line numberDiff line change
@@ -114,16 +114,31 @@ public Database apply(Exception e) {
114114
public OperationFuture<Backup, CreateBackupMetadata> createBackup(
115115
String instanceId, String backupId, String databaseId, Timestamp expireTime)
116116
throws SpannerException {
117-
com.google.spanner.admin.database.v1.Backup backup =
117+
final Backup backup =
118+
newBackupBuilder(BackupId.of(projectId, instanceId, backupId))
119+
.setDatabase(DatabaseId.of(projectId, instanceId, databaseId))
120+
.setExpireTime(expireTime)
121+
.build();
122+
return createBackup(backup);
123+
}
124+
125+
@Override
126+
public OperationFuture<Backup, CreateBackupMetadata> createBackup(final Backup backup) {
127+
final String instanceId = backup.getInstanceId().getInstance();
128+
final String databaseId = backup.getDatabase().getDatabase();
129+
final String backupId = backup.getId().getBackup();
130+
final com.google.spanner.admin.database.v1.Backup.Builder backupBuilder =
118131
com.google.spanner.admin.database.v1.Backup.newBuilder()
119132
.setDatabase(getDatabaseName(instanceId, databaseId))
120-
.setExpireTime(expireTime.toProto())
121-
.build();
122-
String instanceName = getInstanceName(instanceId);
123-
OperationFuture<com.google.spanner.admin.database.v1.Backup, CreateBackupMetadata>
124-
rawOperationFuture = rpc.createBackup(instanceName, backupId, backup);
133+
.setExpireTime(backup.getExpireTime().toProto());
134+
if (backup.getVersionTime() != null) {
135+
backupBuilder.setVersionTime(backup.getVersionTime().toProto());
136+
}
137+
final String instanceName = getInstanceName(instanceId);
138+
final OperationFuture<com.google.spanner.admin.database.v1.Backup, CreateBackupMetadata>
139+
rawOperationFuture = rpc.createBackup(instanceName, backupId, backupBuilder.build());
125140

126-
return new OperationFutureImpl<Backup, CreateBackupMetadata>(
141+
return new OperationFutureImpl<>(
127142
rawOperationFuture.getPollingFuture(),
128143
rawOperationFuture.getInitialFuture(),
129144
new ApiFunction<OperationSnapshot, Backup>() {
@@ -137,6 +152,7 @@ public Backup apply(OperationSnapshot snapshot) {
137152
com.google.spanner.admin.database.v1.Backup.newBuilder(proto)
138153
.setName(proto.getName())
139154
.setExpireTime(proto.getExpireTime())
155+
.setVersionTime(proto.getVersionTime())
140156
.setState(proto.getState())
141157
.build(),
142158
DatabaseAdminClientImpl.this);
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ public abstract static class Builder {
3030

3131
abstract Builder setRestoreInfo(RestoreInfo restoreInfo);
3232

33+
abstract Builder setVersionRetentionPeriod(String versionRetentionPeriod);
34+
35+
abstract Builder setEarliestVersionTime(Timestamp earliestVersionTime);
36+
3337
abstract Builder setProto(com.google.spanner.admin.database.v1.Database proto);
3438

3539
/** Builds the database from this builder. */
@@ -41,6 +45,8 @@ abstract static class BuilderImpl extends Builder {
4145
private State state = State.UNSPECIFIED;
4246
private Timestamp createTime;
4347
private RestoreInfo restoreInfo;
48+
private String versionRetentionPeriod;
49+
private Timestamp earliestVersionTime;
4450
private com.google.spanner.admin.database.v1.Database proto;
4551

4652
BuilderImpl(DatabaseId id) {
@@ -52,6 +58,8 @@ abstract static class BuilderImpl extends Builder {
5258
this.state = other.state;
5359
this.createTime = other.createTime;
5460
this.restoreInfo = other.restoreInfo;
61+
this.versionRetentionPeriod = other.versionRetentionPeriod;
62+
this.earliestVersionTime = other.earliestVersionTime;
5563
this.proto = other.proto;
5664
}
5765

@@ -73,6 +81,18 @@ Builder setRestoreInfo(@Nullable RestoreInfo restoreInfo) {
7381
return this;
7482
}
7583

84+
@Override
85+
Builder setVersionRetentionPeriod(String versionRetentionPeriod) {
86+
this.versionRetentionPeriod = versionRetentionPeriod;
87+
return this;
88+
}
89+
90+
@Override
91+
Builder setEarliestVersionTime(Timestamp earliestVersionTime) {
92+
this.earliestVersionTime = earliestVersionTime;
93+
return this;
94+
}
95+
7696
@Override
7797
Builder setProto(@Nullable com.google.spanner.admin.database.v1.Database proto) {
7898
this.proto = proto;
@@ -96,13 +116,17 @@ public enum State {
96116
private final State state;
97117
private final Timestamp createTime;
98118
private final RestoreInfo restoreInfo;
119+
private final String versionRetentionPeriod;
120+
private final Timestamp earliestVersionTime;
99121
private final com.google.spanner.admin.database.v1.Database proto;
100122

101123
public DatabaseInfo(DatabaseId id, State state) {
102124
this.id = id;
103125
this.state = state;
104126
this.createTime = null;
105127
this.restoreInfo = null;
128+
this.versionRetentionPeriod = null;
129+
this.earliestVersionTime = null;
106130
this.proto = null;
107131
}
108132

@@ -111,6 +135,8 @@ public DatabaseInfo(DatabaseId id, State state) {
111135
this.state = builder.state;
112136
this.createTime = builder.createTime;
113137
this.restoreInfo = builder.restoreInfo;
138+
this.versionRetentionPeriod = builder.versionRetentionPeriod;
139+
this.earliestVersionTime = builder.earliestVersionTime;
114140
this.proto = builder.proto;
115141
}
116142

@@ -129,6 +155,23 @@ public Timestamp getCreateTime() {
129155
return createTime;
130156
}
131157

158+
/**
159+
* Returns the version retention period of the database. This is the period for which Cloud
160+
* Spanner retains all versions of data for the database. For instance, if set to 3 days, Cloud
161+
* Spanner will retain data versions that are up to 3 days old.
162+
*/
163+
public String getVersionRetentionPeriod() {
164+
return versionRetentionPeriod;
165+
}
166+
167+
/**
168+
* Returns the earliest version time of the database. This is the oldest timestamp that can be
169+
* used to read old versions of the data.
170+
*/
171+
public Timestamp getEarliestVersionTime() {
172+
return earliestVersionTime;
173+
}
174+
132175
/**
133176
* Returns the {@link RestoreInfo} of the database if any is available, or <code>null</code> if no
134177
* {@link RestoreInfo} is available for this database.
@@ -154,16 +197,21 @@ public boolean equals(Object o) {
154197
return id.equals(that.id)
155198
&& state == that.state
156199
&& Objects.equals(createTime, that.createTime)
157-
&& Objects.equals(restoreInfo, that.restoreInfo);
200+
&& Objects.equals(restoreInfo, that.restoreInfo)
201+
&& Objects.equals(versionRetentionPeriod, that.versionRetentionPeriod)
202+
&& Objects.equals(earliestVersionTime, that.earliestVersionTime);
158203
}
159204

160205
@Override
161206
public int hashCode() {
162-
return Objects.hash(id, state, createTime, restoreInfo);
207+
return Objects.hash(
208+
id, state, createTime, restoreInfo, versionRetentionPeriod, earliestVersionTime);
163209
}
164210

165211
@Override
166212
public String toString() {
167-
return String.format("Database[%s, %s, %s, %s]", id.getName(), state, createTime, restoreInfo);
213+
return String.format(
214+
"Database[%s, %s, %s, %s, %s, %s]",
215+
id.getName(), state, createTime, restoreInfo, versionRetentionPeriod, earliestVersionTime);
168216
}
169217
}

0 commit comments

Comments
 (0)