mutations) throws SpannerException;
+ /**
+ * Writes the given mutations atomically to the database without replay protection.
+ *
+ * Since this method does not feature replay protection, it may attempt to apply {@code
+ * mutations} more than once; if the mutations are not idempotent, this may lead to a failure
+ * being reported when the mutation was applied once. For example, an insert may fail with {@link
+ * ErrorCode#ALREADY_EXISTS} even though the row did not exist before this method was called. For
+ * this reason, most users of the library will prefer to use {@link #write(Iterable)} instead.
+ * However, {@code writeAtLeastOnce()} requires only a single RPC, whereas {@code write()}
+ * requires two RPCs (one of which may be performed in advance), and so this method may be
+ * appropriate for latency sensitive and/or high throughput blind writing.
+ *
+ *
Example of unprotected blind write.
+ *
+ *
{@code
+ * long singerId = my_singer_id;
+ * Mutation mutation = Mutation.newInsertBuilder("Singers")
+ * .set("SingerId")
+ * .to(singerId)
+ * .set("FirstName")
+ * .to("Billy")
+ * .set("LastName")
+ * .to("Joel")
+ * .build();
+ * dbClient.writeAtLeastOnce(Collections.singletonList(mutation));
+ * }
+ *
+ * @return a write response with the timestamp at which the write was committed
+ */
+ WriteResponse writeAtLeastOnceWithOptions(Iterable mutations, WriteOption... options)
+ throws SpannerException;
+
/**
* Returns a context in which a single read can be performed using {@link TimestampBound#strong()}
* concurrency. This method will return a {@link ReadContext} that will not return the read
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseClientImpl.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseClientImpl.java
index 4dd10001c7..aa2356787d 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseClientImpl.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseClientImpl.java
@@ -17,6 +17,7 @@
package com.google.cloud.spanner;
import com.google.cloud.Timestamp;
+import com.google.cloud.spanner.Options.WriteOption;
import com.google.cloud.spanner.SessionPool.PooledSessionFuture;
import com.google.cloud.spanner.SpannerImpl.ClosedException;
import com.google.common.annotations.VisibleForTesting;
@@ -81,6 +82,13 @@ public Timestamp apply(Session session) {
}
}
+ @Override
+ public WriteResponse writeWithOptions(Iterable mutations, WriteOption... options)
+ throws SpannerException {
+ final Timestamp commitTimestamp = write(mutations);
+ return new WriteResponse(commitTimestamp);
+ }
+
@Override
public Timestamp writeAtLeastOnce(final Iterable mutations) throws SpannerException {
Span span = tracer.spanBuilder(READ_WRITE_TRANSACTION).startSpan();
@@ -101,6 +109,13 @@ public Timestamp apply(Session session) {
}
}
+ @Override
+ public WriteResponse writeAtLeastOnceWithOptions(
+ Iterable mutations, WriteOption... options) throws SpannerException {
+ final Timestamp commitTimestamp = writeAtLeastOnce(mutations);
+ return new WriteResponse(commitTimestamp);
+ }
+
@Override
public ReadContext singleUse() {
Span span = tracer.spanBuilder(READ_ONLY_TRANSACTION).startSpan();
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Options.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Options.java
index 879b632d17..473882f3d3 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Options.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Options.java
@@ -33,6 +33,9 @@ public interface ReadOption {}
/** Marker interface to mark options applicable to query operation. */
public interface QueryOption {}
+ /** Marker interface to mark options applicable to write operations */
+ public interface WriteOption {}
+
/** Marker interface to mark options applicable to list operations in admin API. */
public interface ListOption {}
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionImpl.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionImpl.java
index 6a91d85fef..9655e6b66d 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionImpl.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionImpl.java
@@ -25,6 +25,7 @@
import com.google.cloud.spanner.AbstractReadContext.MultiUseReadOnlyTransaction;
import com.google.cloud.spanner.AbstractReadContext.SingleReadContext;
import com.google.cloud.spanner.AbstractReadContext.SingleUseReadOnlyTransaction;
+import com.google.cloud.spanner.Options.WriteOption;
import com.google.cloud.spanner.SessionClient.SessionId;
import com.google.cloud.spanner.TransactionRunnerImpl.TransactionContextImpl;
import com.google.cloud.spanner.spi.v1.SpannerRpc;
@@ -139,6 +140,13 @@ public Void run(TransactionContext ctx) {
return runner.getCommitTimestamp();
}
+ @Override
+ public WriteResponse writeWithOptions(Iterable mutations, WriteOption... options)
+ throws SpannerException {
+ final Timestamp commitTimestamp = write(mutations);
+ return new WriteResponse(commitTimestamp);
+ }
+
@Override
public Timestamp writeAtLeastOnce(Iterable mutations) throws SpannerException {
setActive(null);
@@ -168,6 +176,13 @@ public Timestamp writeAtLeastOnce(Iterable mutations) throws SpannerEx
}
}
+ @Override
+ public WriteResponse writeAtLeastOnceWithOptions(
+ Iterable mutations, WriteOption... options) throws SpannerException {
+ final Timestamp commitTimestamp = writeAtLeastOnce(mutations);
+ return new WriteResponse(commitTimestamp);
+ }
+
@Override
public ReadContext singleUse() {
return singleUse(TimestampBound.strong());
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionPool.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionPool.java
index 2512024117..90d737957c 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionPool.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionPool.java
@@ -47,6 +47,7 @@
import com.google.cloud.grpc.GrpcTransportOptions.ExecutorFactory;
import com.google.cloud.spanner.Options.QueryOption;
import com.google.cloud.spanner.Options.ReadOption;
+import com.google.cloud.spanner.Options.WriteOption;
import com.google.cloud.spanner.SessionClient.SessionConsumer;
import com.google.cloud.spanner.SpannerException.ResourceNotFoundException;
import com.google.cloud.spanner.SpannerImpl.ClosedException;
@@ -1103,6 +1104,13 @@ public Timestamp write(Iterable mutations) throws SpannerException {
}
}
+ @Override
+ public WriteResponse writeWithOptions(Iterable mutations, WriteOption... options)
+ throws SpannerException {
+ final Timestamp commitTimestamp = write(mutations);
+ return new WriteResponse(commitTimestamp);
+ }
+
@Override
public Timestamp writeAtLeastOnce(Iterable mutations) throws SpannerException {
try {
@@ -1112,6 +1120,13 @@ public Timestamp writeAtLeastOnce(Iterable mutations) throws SpannerEx
}
}
+ @Override
+ public WriteResponse writeAtLeastOnceWithOptions(
+ Iterable mutations, WriteOption... options) throws SpannerException {
+ final Timestamp commitTimestamp = writeAtLeastOnce(mutations);
+ return new WriteResponse(commitTimestamp);
+ }
+
@Override
public ReadContext singleUse() {
try {
@@ -1347,6 +1362,13 @@ public Timestamp write(Iterable mutations) throws SpannerException {
}
}
+ @Override
+ public WriteResponse writeWithOptions(Iterable mutations, WriteOption... options)
+ throws SpannerException {
+ final Timestamp commitTimestamp = write(mutations);
+ return new WriteResponse(commitTimestamp);
+ }
+
@Override
public Timestamp writeAtLeastOnce(Iterable mutations) throws SpannerException {
try {
@@ -1357,6 +1379,13 @@ public Timestamp writeAtLeastOnce(Iterable mutations) throws SpannerEx
}
}
+ @Override
+ public WriteResponse writeAtLeastOnceWithOptions(
+ Iterable mutations, WriteOption... options) throws SpannerException {
+ final Timestamp commitTimestamp = writeAtLeastOnce(mutations);
+ return new WriteResponse(commitTimestamp);
+ }
+
@Override
public long executePartitionedUpdate(Statement stmt) throws SpannerException {
try {
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/WriteResponse.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/WriteResponse.java
new file mode 100644
index 0000000000..4bd3e18651
--- /dev/null
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/WriteResponse.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.cloud.spanner;
+
+import com.google.cloud.Timestamp;
+import java.util.Objects;
+
+/** Represents a response from a write / writeAtLeast once operation. */
+public class WriteResponse {
+
+ private final Timestamp commitTimestamp;
+
+ public WriteResponse(Timestamp commitTimestamp) {
+ this.commitTimestamp = commitTimestamp;
+ }
+
+ /** Returns a {@link Timestamp} representing the commit time of the write operation. */
+ public Timestamp getCommitTimestamp() {
+ return commitTimestamp;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ WriteResponse that = (WriteResponse) o;
+ return Objects.equals(commitTimestamp, that.commitTimestamp);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(commitTimestamp);
+ }
+}