Skip to content

Commit 659719d

Browse files
authored
feat: adds options to the write operations (#531)
* feat: adds options to the write operations Adds the possibility of adding options to the write operations and encapsulates the write response into it's own class so that we can augment the response with more fields than the commit timestamp.
1 parent 5322c95 commit 659719d

File tree

7 files changed

+189
-3
lines changed

7 files changed

+189
-3
lines changed

google-cloud-spanner/clirr-ignored-differences.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,18 @@
378378
<method>com.google.api.core.ApiFuture closeAsync()</method>
379379
</difference>
380380

381+
<!-- Write with options API -->
382+
<difference>
383+
<differenceType>7012</differenceType>
384+
<className>com/google/cloud/spanner/DatabaseClient</className>
385+
<method>com.google.cloud.spanner.CommitResponse writeWithOptions(java.lang.Iterable, com.google.cloud.spanner.Options$TransactionOption[])</method>
386+
</difference>
387+
<difference>
388+
<differenceType>7012</differenceType>
389+
<className>com/google/cloud/spanner/DatabaseClient</className>
390+
<method>com.google.cloud.spanner.CommitResponse writeAtLeastOnceWithOptions(java.lang.Iterable, com.google.cloud.spanner.Options$TransactionOption[])</method>
391+
</difference>
392+
381393
<!-- Note: The following change for the LazySpannerInitializer.initialize() method must be specified twice, both with return type java.lang.Object and with com.google.cloud.spanner.Spanner. -->
382394
<difference>
383395
<differenceType>7009</differenceType>
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Copyright 2020 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.cloud.spanner;
18+
19+
import com.google.cloud.Timestamp;
20+
import java.util.Objects;
21+
22+
/** Represents a response from a commit operation. */
23+
public class CommitResponse {
24+
25+
private final Timestamp commitTimestamp;
26+
27+
public CommitResponse(Timestamp commitTimestamp) {
28+
this.commitTimestamp = commitTimestamp;
29+
}
30+
31+
/** Returns a {@link Timestamp} representing the commit time of the write operation. */
32+
public Timestamp getCommitTimestamp() {
33+
return commitTimestamp;
34+
}
35+
36+
@Override
37+
public boolean equals(Object o) {
38+
if (this == o) {
39+
return true;
40+
}
41+
if (o == null || getClass() != o.getClass()) {
42+
return false;
43+
}
44+
CommitResponse that = (CommitResponse) o;
45+
return Objects.equals(commitTimestamp, that.commitTimestamp);
46+
}
47+
48+
@Override
49+
public int hashCode() {
50+
return Objects.hash(commitTimestamp);
51+
}
52+
}

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

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package com.google.cloud.spanner;
1818

1919
import com.google.cloud.Timestamp;
20+
import com.google.cloud.spanner.Options.TransactionOption;
2021

2122
/**
2223
* Interface for all the APIs that are used to read/write data into a Cloud Spanner database. An
@@ -52,6 +53,35 @@ public interface DatabaseClient {
5253
*/
5354
Timestamp write(Iterable<Mutation> mutations) throws SpannerException;
5455

56+
/**
57+
* Writes the given mutations atomically to the database with the given options.
58+
*
59+
* <p>This method uses retries and replay protection internally, which means that the mutations
60+
* are applied exactly once on success, or not at all if an error is returned, regardless of any
61+
* failures in the underlying network. Note that if the call is cancelled or reaches deadline, it
62+
* is not possible to know whether the mutations were applied without performing a subsequent
63+
* database operation, but the mutations will have been applied at most once.
64+
*
65+
* <p>Example of blind write.
66+
*
67+
* <pre>{@code
68+
* long singerId = my_singer_id;
69+
* Mutation mutation = Mutation.newInsertBuilder("Singer")
70+
* .set("SingerId")
71+
* .to(singerId)
72+
* .set("FirstName")
73+
* .to("Billy")
74+
* .set("LastName")
75+
* .to("Joel")
76+
* .build();
77+
* dbClient.writeWithOptions(Collections.singletonList(mutation));
78+
* }</pre>
79+
*
80+
* @return a response with the timestamp at which the write was committed
81+
*/
82+
CommitResponse writeWithOptions(Iterable<Mutation> mutations, TransactionOption... options)
83+
throws SpannerException;
84+
5585
/**
5686
* Writes the given mutations atomically to the database without replay protection.
5787
*
@@ -83,6 +113,38 @@ public interface DatabaseClient {
83113
*/
84114
Timestamp writeAtLeastOnce(Iterable<Mutation> mutations) throws SpannerException;
85115

116+
/**
117+
* Writes the given mutations atomically to the database without replay protection.
118+
*
119+
* <p>Since this method does not feature replay protection, it may attempt to apply {@code
120+
* mutations} more than once; if the mutations are not idempotent, this may lead to a failure
121+
* being reported when the mutation was applied once. For example, an insert may fail with {@link
122+
* ErrorCode#ALREADY_EXISTS} even though the row did not exist before this method was called. For
123+
* this reason, most users of the library will prefer to use {@link #write(Iterable)} instead.
124+
* However, {@code writeAtLeastOnce()} requires only a single RPC, whereas {@code write()}
125+
* requires two RPCs (one of which may be performed in advance), and so this method may be
126+
* appropriate for latency sensitive and/or high throughput blind writing.
127+
*
128+
* <p>Example of unprotected blind write.
129+
*
130+
* <pre>{@code
131+
* long singerId = my_singer_id;
132+
* Mutation mutation = Mutation.newInsertBuilder("Singers")
133+
* .set("SingerId")
134+
* .to(singerId)
135+
* .set("FirstName")
136+
* .to("Billy")
137+
* .set("LastName")
138+
* .to("Joel")
139+
* .build();
140+
* dbClient.writeAtLeastOnce(Collections.singletonList(mutation));
141+
* }</pre>
142+
*
143+
* @return a response with the timestamp at which the write was committed
144+
*/
145+
CommitResponse writeAtLeastOnceWithOptions(
146+
Iterable<Mutation> mutations, TransactionOption... options) throws SpannerException;
147+
86148
/**
87149
* Returns a context in which a single read can be performed using {@link TimestampBound#strong()}
88150
* concurrency. This method will return a {@link ReadContext} that will not return the read

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package com.google.cloud.spanner;
1818

1919
import com.google.cloud.Timestamp;
20+
import com.google.cloud.spanner.Options.TransactionOption;
2021
import com.google.cloud.spanner.SessionPool.PooledSessionFuture;
2122
import com.google.cloud.spanner.SpannerImpl.ClosedException;
2223
import com.google.common.annotations.VisibleForTesting;
@@ -81,6 +82,13 @@ public Timestamp apply(Session session) {
8182
}
8283
}
8384

85+
@Override
86+
public CommitResponse writeWithOptions(Iterable<Mutation> mutations, TransactionOption... options)
87+
throws SpannerException {
88+
final Timestamp commitTimestamp = write(mutations);
89+
return new CommitResponse(commitTimestamp);
90+
}
91+
8492
@Override
8593
public Timestamp writeAtLeastOnce(final Iterable<Mutation> mutations) throws SpannerException {
8694
Span span = tracer.spanBuilder(READ_WRITE_TRANSACTION).startSpan();
@@ -101,6 +109,13 @@ public Timestamp apply(Session session) {
101109
}
102110
}
103111

112+
@Override
113+
public CommitResponse writeAtLeastOnceWithOptions(
114+
Iterable<Mutation> mutations, TransactionOption... options) throws SpannerException {
115+
final Timestamp commitTimestamp = writeAtLeastOnce(mutations);
116+
return new CommitResponse(commitTimestamp);
117+
}
118+
104119
@Override
105120
public ReadContext singleUse() {
106121
Span span = tracer.spanBuilder(READ_ONLY_TRANSACTION).startSpan();

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ public interface ReadOption {}
3333
/** Marker interface to mark options applicable to query operation. */
3434
public interface QueryOption {}
3535

36+
/** Marker interface to mark options applicable to write operations */
37+
public interface TransactionOption {}
38+
3639
/** Marker interface to mark options applicable to list operations in admin API. */
3740
public interface ListOption {}
3841

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

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import com.google.cloud.spanner.AbstractReadContext.MultiUseReadOnlyTransaction;
2626
import com.google.cloud.spanner.AbstractReadContext.SingleReadContext;
2727
import com.google.cloud.spanner.AbstractReadContext.SingleUseReadOnlyTransaction;
28+
import com.google.cloud.spanner.Options.TransactionOption;
2829
import com.google.cloud.spanner.SessionClient.SessionId;
2930
import com.google.cloud.spanner.TransactionRunnerImpl.TransactionContextImpl;
3031
import com.google.cloud.spanner.spi.v1.SpannerRpc;
@@ -35,7 +36,6 @@
3536
import com.google.protobuf.Empty;
3637
import com.google.spanner.v1.BeginTransactionRequest;
3738
import com.google.spanner.v1.CommitRequest;
38-
import com.google.spanner.v1.CommitResponse;
3939
import com.google.spanner.v1.Transaction;
4040
import com.google.spanner.v1.TransactionOptions;
4141
import io.opencensus.common.Scope;
@@ -139,6 +139,13 @@ public Void run(TransactionContext ctx) {
139139
return runner.getCommitTimestamp();
140140
}
141141

142+
@Override
143+
public CommitResponse writeWithOptions(Iterable<Mutation> mutations, TransactionOption... options)
144+
throws SpannerException {
145+
final Timestamp commitTimestamp = write(mutations);
146+
return new CommitResponse(commitTimestamp);
147+
}
148+
142149
@Override
143150
public Timestamp writeAtLeastOnce(Iterable<Mutation> mutations) throws SpannerException {
144151
setActive(null);
@@ -154,7 +161,7 @@ public Timestamp writeAtLeastOnce(Iterable<Mutation> mutations) throws SpannerEx
154161
.build();
155162
Span span = tracer.spanBuilder(SpannerImpl.COMMIT).startSpan();
156163
try (Scope s = tracer.withSpan(span)) {
157-
CommitResponse response = spanner.getRpc().commit(request, options);
164+
com.google.spanner.v1.CommitResponse response = spanner.getRpc().commit(request, options);
158165
Timestamp t = Timestamp.fromProto(response.getCommitTimestamp());
159166
return t;
160167
} catch (IllegalArgumentException e) {
@@ -168,6 +175,13 @@ public Timestamp writeAtLeastOnce(Iterable<Mutation> mutations) throws SpannerEx
168175
}
169176
}
170177

178+
@Override
179+
public CommitResponse writeAtLeastOnceWithOptions(
180+
Iterable<Mutation> mutations, TransactionOption... options) throws SpannerException {
181+
final Timestamp commitTimestamp = writeAtLeastOnce(mutations);
182+
return new CommitResponse(commitTimestamp);
183+
}
184+
171185
@Override
172186
public ReadContext singleUse() {
173187
return singleUse(TimestampBound.strong());

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

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,10 @@
4747
import com.google.cloud.grpc.GrpcTransportOptions.ExecutorFactory;
4848
import com.google.cloud.spanner.Options.QueryOption;
4949
import com.google.cloud.spanner.Options.ReadOption;
50+
import com.google.cloud.spanner.Options.TransactionOption;
5051
import com.google.cloud.spanner.SessionClient.SessionConsumer;
5152
import com.google.cloud.spanner.SpannerException.ResourceNotFoundException;
5253
import com.google.cloud.spanner.SpannerImpl.ClosedException;
53-
import com.google.cloud.spanner.TransactionManager.TransactionState;
5454
import com.google.common.annotations.VisibleForTesting;
5555
import com.google.common.base.Function;
5656
import com.google.common.base.MoreObjects;
@@ -1103,6 +1103,13 @@ public Timestamp write(Iterable<Mutation> mutations) throws SpannerException {
11031103
}
11041104
}
11051105

1106+
@Override
1107+
public CommitResponse writeWithOptions(
1108+
Iterable<Mutation> mutations, TransactionOption... options) throws SpannerException {
1109+
final Timestamp commitTimestamp = write(mutations);
1110+
return new CommitResponse(commitTimestamp);
1111+
}
1112+
11061113
@Override
11071114
public Timestamp writeAtLeastOnce(Iterable<Mutation> mutations) throws SpannerException {
11081115
try {
@@ -1112,6 +1119,13 @@ public Timestamp writeAtLeastOnce(Iterable<Mutation> mutations) throws SpannerEx
11121119
}
11131120
}
11141121

1122+
@Override
1123+
public CommitResponse writeAtLeastOnceWithOptions(
1124+
Iterable<Mutation> mutations, TransactionOption... options) throws SpannerException {
1125+
final Timestamp commitTimestamp = writeAtLeastOnce(mutations);
1126+
return new CommitResponse(commitTimestamp);
1127+
}
1128+
11151129
@Override
11161130
public ReadContext singleUse() {
11171131
try {
@@ -1347,6 +1361,13 @@ public Timestamp write(Iterable<Mutation> mutations) throws SpannerException {
13471361
}
13481362
}
13491363

1364+
@Override
1365+
public CommitResponse writeWithOptions(
1366+
Iterable<Mutation> mutations, TransactionOption... options) throws SpannerException {
1367+
final Timestamp commitTimestamp = write(mutations);
1368+
return new CommitResponse(commitTimestamp);
1369+
}
1370+
13501371
@Override
13511372
public Timestamp writeAtLeastOnce(Iterable<Mutation> mutations) throws SpannerException {
13521373
try {
@@ -1357,6 +1378,13 @@ public Timestamp writeAtLeastOnce(Iterable<Mutation> mutations) throws SpannerEx
13571378
}
13581379
}
13591380

1381+
@Override
1382+
public CommitResponse writeAtLeastOnceWithOptions(
1383+
Iterable<Mutation> mutations, TransactionOption... options) throws SpannerException {
1384+
final Timestamp commitTimestamp = writeAtLeastOnce(mutations);
1385+
return new CommitResponse(commitTimestamp);
1386+
}
1387+
13601388
@Override
13611389
public long executePartitionedUpdate(Statement stmt) throws SpannerException {
13621390
try {

0 commit comments

Comments
 (0)