Skip to content

Commit 384a940

Browse files
committed
fix(spanner): support transaction tags in partition DML
1 parent 0a46070 commit 384a940

File tree

8 files changed

+121
-13
lines changed

8 files changed

+121
-13
lines changed

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import com.google.cloud.spanner.Options.TransactionOption;
2222
import com.google.cloud.spanner.Options.UpdateOption;
2323
import com.google.spanner.v1.BatchWriteResponse;
24+
import javax.annotation.Nullable;
2425

2526
/**
2627
* Base class for the Multiplexed Session {@link DatabaseClient} implementation. Throws {@link
@@ -56,4 +57,10 @@ public ServerStream<BatchWriteResponse> batchWriteAtLeastOnce(
5657
public long executePartitionedUpdate(Statement stmt, UpdateOption... options) {
5758
throw new UnsupportedOperationException();
5859
}
60+
61+
@Override
62+
public long executePartitionedUpdate(
63+
Statement stmt, @Nullable String transactionTag, UpdateOption... options) {
64+
throw new UnsupportedOperationException();
65+
}
5966
}

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import com.google.cloud.spanner.Options.TransactionOption;
2323
import com.google.cloud.spanner.Options.UpdateOption;
2424
import com.google.spanner.v1.BatchWriteResponse;
25+
import javax.annotation.Nullable;
2526

2627
/**
2728
* Interface for all the APIs that are used to read/write data into a Cloud Spanner database. An
@@ -601,4 +602,21 @@ ServerStream<BatchWriteResponse> batchWriteAtLeastOnce(
601602
* idempotent, such as deleting old rows from a very large table.
602603
*/
603604
long executePartitionedUpdate(Statement stmt, UpdateOption... options);
605+
606+
/**
607+
* Executes a Partitioned DML statement with the specified transaction tag.
608+
*
609+
* <p>This method has the same behavior as {@link #executePartitionedUpdate(Statement,
610+
* UpdateOption...)} but allows specifying a transaction tag that will be applied to all
611+
* partitioned operations.
612+
*
613+
* @param stmt The Partitioned DML statement to execute
614+
* @param transactionTag The transaction tag to apply to all partitioned operations. The tag must
615+
* be a printable string (ASCII 32-126) with maximum length of 50 characters.
616+
* @param options The options to use for the update operation
617+
* @return The total number of rows modified by the statement
618+
* @throws SpannerException if the operation failed
619+
*/
620+
long executePartitionedUpdate(
621+
Statement stmt, @Nullable String transactionTag, UpdateOption... options);
604622
}

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

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -309,9 +309,21 @@ public AsyncTransactionManager transactionManagerAsync(TransactionOption... opti
309309

310310
@Override
311311
public long executePartitionedUpdate(final Statement stmt, final UpdateOption... options) {
312+
return executePartitionedUpdateWithOptions(stmt, null, options);
313+
}
314+
315+
@Override
316+
public long executePartitionedUpdate(
317+
final Statement stmt, @Nullable String transactionTag, final UpdateOption... options) {
318+
return executePartitionedUpdateWithOptions(stmt, transactionTag, options);
319+
}
320+
321+
private long executePartitionedUpdateWithOptions(
322+
final Statement stmt, @Nullable String transactionTag, final UpdateOption... options) {
312323
ISpan span = tracer.spanBuilder(PARTITION_DML_TRANSACTION);
313324
try (IScope s = tracer.withSpan(span)) {
314-
return runWithSessionRetry(session -> session.executePartitionedUpdate(stmt, options));
325+
return runWithSessionRetry(
326+
session -> session.executePartitionedUpdate(stmt, transactionTag, options));
315327
} catch (RuntimeException e) {
316328
span.setStatus(e);
317329
span.end();

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

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import java.util.concurrent.TimeUnit;
4444
import java.util.logging.Level;
4545
import java.util.logging.Logger;
46+
import javax.annotation.Nullable;
4647
import org.threeten.bp.Duration;
4748
import org.threeten.bp.temporal.ChronoUnit;
4849

@@ -54,13 +55,16 @@ public class PartitionedDmlTransaction implements SessionImpl.SessionTransaction
5455
private final SessionImpl session;
5556
private final SpannerRpc rpc;
5657
private final Ticker ticker;
58+
private final @Nullable String transactionTag;
5759
private final IsRetryableInternalError isRetryableInternalErrorPredicate;
5860
private volatile boolean isValid = true;
5961

60-
PartitionedDmlTransaction(SessionImpl session, SpannerRpc rpc, Ticker ticker) {
62+
PartitionedDmlTransaction(
63+
SessionImpl session, SpannerRpc rpc, Ticker ticker, @Nullable String transactionTag) {
6164
this.session = session;
6265
this.rpc = rpc;
6366
this.ticker = ticker;
67+
this.transactionTag = transactionTag;
6468
this.isRetryableInternalErrorPredicate = new IsRetryableInternalError();
6569
}
6670

@@ -194,22 +198,27 @@ ExecuteSqlRequest newTransactionRequestFrom(final Statement statement, final Opt
194198
if (options.hasTag()) {
195199
requestOptionsBuilder.setRequestTag(options.tag());
196200
}
201+
if (transactionTag != null) {
202+
requestOptionsBuilder.setTransactionTag(transactionTag);
203+
}
197204
builder.setRequestOptions(requestOptionsBuilder.build());
198205
}
199206
return builder.build();
200207
}
201208

202209
private ByteString initTransaction(final Options options) {
203-
final BeginTransactionRequest request =
210+
BeginTransactionRequest.Builder builder =
204211
BeginTransactionRequest.newBuilder()
205212
.setSession(session.getName())
206213
.setOptions(
207214
TransactionOptions.newBuilder()
208-
.setPartitionedDml(TransactionOptions.PartitionedDml.getDefaultInstance())
209-
.setExcludeTxnFromChangeStreams(
210-
options.withExcludeTxnFromChangeStreams() == Boolean.TRUE))
211-
.build();
212-
Transaction tx = rpc.beginTransaction(request, session.getOptions(), true);
215+
.setPartitionedDml(TransactionOptions.PartitionedDml.getDefaultInstance()));
216+
217+
if (transactionTag != null) {
218+
builder.setRequestOptions(
219+
RequestOptions.newBuilder().setTransactionTag(transactionTag).build());
220+
}
221+
Transaction tx = rpc.beginTransaction(builder.build(), session.getOptions(), true);
213222
if (tx.getId().isEmpty()) {
214223
throw SpannerExceptionFactory.newSpannerException(
215224
ErrorCode.INTERNAL,

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

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,18 @@ public DatabaseId getDatabaseId() {
201201
public long executePartitionedUpdate(Statement stmt, UpdateOption... options) {
202202
setActive(null);
203203
PartitionedDmlTransaction txn =
204-
new PartitionedDmlTransaction(this, spanner.getRpc(), Ticker.systemTicker());
204+
new PartitionedDmlTransaction(this, spanner.getRpc(), Ticker.systemTicker(), null);
205+
return txn.executeStreamingPartitionedUpdate(
206+
stmt, spanner.getOptions().getPartitionedDmlTimeout(), options);
207+
}
208+
209+
@Override
210+
public long executePartitionedUpdate(
211+
Statement stmt, @Nullable String transactionTag, UpdateOption... options) {
212+
setActive(null);
213+
PartitionedDmlTransaction txn =
214+
new PartitionedDmlTransaction(
215+
this, spanner.getRpc(), Ticker.systemTicker(), transactionTag);
205216
return txn.executeStreamingPartitionedUpdate(
206217
stmt, spanner.getOptions().getPartitionedDmlTimeout(), options);
207218
}

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

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1273,6 +1273,11 @@ default AsyncTransactionManager transactionManagerAsync(TransactionOption... opt
12731273
default long executePartitionedUpdate(Statement stmt, UpdateOption... options) {
12741274
return get().executePartitionedUpdate(stmt, options);
12751275
}
1276+
1277+
default long executePartitionedUpdate(
1278+
Statement stmt, @Nullable String transactionTag, UpdateOption... options) {
1279+
return get().executePartitionedUpdate(stmt, transactionTag, options);
1280+
}
12761281
}
12771282

12781283
class PooledSessionFutureWrapper implements SessionFutureWrapper<PooledSessionFuture> {
@@ -1494,6 +1499,16 @@ public long executePartitionedUpdate(Statement stmt, UpdateOption... options) {
14941499
}
14951500
}
14961501

1502+
@Override
1503+
public long executePartitionedUpdate(
1504+
Statement stmt, @Nullable String transactionTag, UpdateOption... options) {
1505+
try {
1506+
return get(true).executePartitionedUpdate(stmt, transactionTag, options);
1507+
} finally {
1508+
close();
1509+
}
1510+
}
1511+
14971512
@Override
14981513
public String getName() {
14991514
return get().getName();
@@ -1709,6 +1724,18 @@ public long executePartitionedUpdate(Statement stmt, UpdateOption... options)
17091724
}
17101725
}
17111726

1727+
@Override
1728+
public long executePartitionedUpdate(
1729+
Statement stmt, @Nullable String transactionTag, UpdateOption... options)
1730+
throws SpannerException {
1731+
try {
1732+
markUsed();
1733+
return delegate.executePartitionedUpdate(stmt, transactionTag, options);
1734+
} catch (SpannerException e) {
1735+
throw lastException = e;
1736+
}
1737+
}
1738+
17121739
@Override
17131740
public ReadContext singleUse() {
17141741
return delegate.singleUse();

google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseClientImplTest.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1944,6 +1944,30 @@ public void testPartitionedDMLWithTag() {
19441944
assertThat(request.getRequestOptions().getTransactionTag()).isEmpty();
19451945
}
19461946

1947+
@Test
1948+
public void testPartitionedDMLWithTransactionTag() {
1949+
DatabaseClient client =
1950+
spanner.getDatabaseClient(DatabaseId.of(TEST_PROJECT, TEST_INSTANCE, TEST_DATABASE));
1951+
client.executePartitionedUpdate(
1952+
UPDATE_STATEMENT, "testTransactionTag", Options.tag("app=spanner,env=test,action=dml"));
1953+
1954+
List<BeginTransactionRequest> beginTransactions =
1955+
mockSpanner.getRequestsOfType(BeginTransactionRequest.class);
1956+
assertThat(beginTransactions).hasSize(1);
1957+
BeginTransactionRequest beginTransaction = beginTransactions.get(0);
1958+
assertNotNull(beginTransaction.getOptions());
1959+
assertTrue(beginTransaction.getOptions().hasPartitionedDml());
1960+
assertFalse(beginTransaction.getOptions().getExcludeTxnFromChangeStreams());
1961+
1962+
List<ExecuteSqlRequest> requests = mockSpanner.getRequestsOfType(ExecuteSqlRequest.class);
1963+
assertThat(requests).hasSize(1);
1964+
ExecuteSqlRequest request = requests.get(0);
1965+
assertNotNull(request.getRequestOptions());
1966+
assertThat(request.getRequestOptions().getTransactionTag()).isEqualTo("testTransactionTag");
1967+
assertThat(request.getRequestOptions().getRequestTag())
1968+
.isEqualTo("app=spanner,env=test,action=dml");
1969+
}
1970+
19471971
@Test
19481972
public void testCommitWithTag() {
19491973
DatabaseClient client =

google-cloud-spanner/src/test/java/com/google/cloud/spanner/PartitionedDmlTransactionTest.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ public void setup() {
101101
when(rpc.beginTransaction(any(BeginTransactionRequest.class), anyMap(), eq(true)))
102102
.thenReturn(Transaction.newBuilder().setId(txId).build());
103103

104-
tx = new PartitionedDmlTransaction(session, rpc, ticker);
104+
tx = new PartitionedDmlTransaction(session, rpc, ticker, null);
105105
}
106106

107107
@Test
@@ -332,7 +332,7 @@ public void testExecuteStreamingPartitionedUpdateUnexpectedEOS() {
332332
Mockito.eq(executeRequestWithResumeToken), anyMap(), any(Duration.class)))
333333
.thenReturn(stream2);
334334

335-
PartitionedDmlTransaction tx = new PartitionedDmlTransaction(session, rpc, ticker);
335+
PartitionedDmlTransaction tx = new PartitionedDmlTransaction(session, rpc, ticker, null);
336336
long count = tx.executeStreamingPartitionedUpdate(Statement.of(sql), Duration.ofMinutes(10));
337337

338338
assertThat(count).isEqualTo(1000L);
@@ -371,7 +371,7 @@ public void testExecuteStreamingPartitionedUpdateRSTstream() {
371371
Mockito.eq(executeRequestWithResumeToken), anyMap(), any(Duration.class)))
372372
.thenReturn(stream2);
373373

374-
PartitionedDmlTransaction tx = new PartitionedDmlTransaction(session, rpc, ticker);
374+
PartitionedDmlTransaction tx = new PartitionedDmlTransaction(session, rpc, ticker, null);
375375
long count = tx.executeStreamingPartitionedUpdate(Statement.of(sql), Duration.ofMinutes(10));
376376

377377
assertThat(count).isEqualTo(1000L);
@@ -400,7 +400,7 @@ public void testExecuteStreamingPartitionedUpdateGenericInternalException() {
400400
Mockito.eq(executeRequestWithoutResumeToken), anyMap(), any(Duration.class)))
401401
.thenReturn(stream1);
402402

403-
PartitionedDmlTransaction tx = new PartitionedDmlTransaction(session, rpc, ticker);
403+
PartitionedDmlTransaction tx = new PartitionedDmlTransaction(session, rpc, ticker, null);
404404
SpannerException e =
405405
assertThrows(
406406
SpannerException.class,

0 commit comments

Comments
 (0)