Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ If you are using Maven without the BOM, add this to your dependencies:
If you are using Gradle 5.x or later, add this to your dependencies:

```Groovy
implementation platform('com.google.cloud:libraries-bom:26.41.0')
implementation platform('com.google.cloud:libraries-bom:26.42.0')

implementation 'com.google.cloud:google-cloud-spanner'
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1142,22 +1142,29 @@ public ApiFuture<Void> rollbackAsync(CallType callType) {
}
}

private ApiFuture<Void> rollbackAsync(CallType callType, boolean updateStatus) {
private ApiFuture<Void> rollbackAsync(CallType callType, boolean updateStatusAndEndSpan) {
ConnectionPreconditions.checkState(
state == UnitOfWorkState.STARTED || state == UnitOfWorkState.ABORTED,
"This transaction has status " + state.name());
if (updateStatus) {
if (updateStatusAndEndSpan) {
state = UnitOfWorkState.ROLLED_BACK;
asyncEndUnitOfWorkSpan();
}
if (txContextFuture != null && state != UnitOfWorkState.ABORTED) {
ApiFuture<Void> result =
executeStatementAsync(
callType, ROLLBACK_STATEMENT, rollbackCallable, SpannerGrpc.getRollbackMethod());
asyncEndUnitOfWorkSpan();
if (updateStatusAndEndSpan) {
// Note: We end the transaction span after executing the rollback to include the rollback in
// the transaction span. Even though both methods are executed asynchronously, they are both
// executed using the same single-threaded executor, meaning that the span will only be
// ended after the rollback has finished.
asyncEndUnitOfWorkSpan();
}
return result;
} else {
} else if (updateStatusAndEndSpan) {
return asyncEndUnitOfWorkSpan();
} else {
return ApiFutures.immediateFuture(null);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import com.google.cloud.spanner.MockSpannerServiceImpl;
import com.google.cloud.spanner.ResultSet;
import com.google.cloud.spanner.SpannerOptions;
import com.google.cloud.spanner.SpannerOptions.SpannerEnvironment;
Expand Down Expand Up @@ -472,6 +473,59 @@ public void testMultiUseReadWriteAborted() {
assertParent("CloudSpanner.ReadWriteTransaction", "CloudSpannerOperation.Commit", spans);
}

@Test
public void testSavepoint() {
Statement statement1 = Statement.of("insert into foo (id) values (1)");
Statement statement2 = Statement.of("insert into foo (id) values (2)");
mockSpanner.putStatementResult(MockSpannerServiceImpl.StatementResult.update(statement1, 1));
mockSpanner.putStatementResult(MockSpannerServiceImpl.StatementResult.update(statement2, 1));

try (Connection connection = createTestConnection()) {
connection.setAutocommit(false);
connection.setReadOnly(false);
connection.setSavepointSupport(SavepointSupport.ENABLED);
assertEquals(1L, connection.executeUpdate(statement1));
connection.savepoint("test");
assertEquals(1L, connection.executeUpdate(statement2));
connection.rollbackToSavepoint("test");
connection.commit();
}
assertEquals(CompletableResultCode.ofSuccess(), spanExporter.flush());
List<SpanData> spans = spanExporter.getFinishedSpanItems();
assertContains("CloudSpannerJdbc.ReadWriteTransaction", spans);
assertContains("CloudSpanner.ReadWriteTransaction", spans);
// Statement 1 is executed 2 times, because the original transaction needs to be
// retried after the transaction was rolled back to the savepoint.
assertContains(
"CloudSpannerOperation.ExecuteUpdate",
2,
Attributes.of(AttributeKey.stringKey("db.statement"), statement1.getSql()),
spans);
assertContains(
"CloudSpannerOperation.ExecuteUpdate",
1,
Attributes.of(AttributeKey.stringKey("db.statement"), statement2.getSql()),
spans);
assertContains("CloudSpannerOperation.Commit", spans);

// Verify that we have two Cloud Spanner transactions, and that these are both children of one
// JDBC transaction.
List<SpanData> transactionSpans =
getSpans("CloudSpanner.ReadWriteTransaction", Attributes.empty(), spans);
assertEquals(2, transactionSpans.size());
assertEquals(
transactionSpans.get(0).getParentSpanId(), transactionSpans.get(1).getParentSpanId());
List<SpanData> jdbcTransactionSpans =
getSpans("CloudSpannerJdbc.ReadWriteTransaction", Attributes.empty(), spans);
assertEquals(1, jdbcTransactionSpans.size());
assertEquals(
jdbcTransactionSpans.get(0).getSpanId(), transactionSpans.get(0).getParentSpanId());
List<SpanData> commitSpans =
getSpans("CloudSpannerOperation.Commit", Attributes.empty(), spans);
assertEquals(1, commitSpans.size());
assertEquals(transactionSpans.get(1).getSpanId(), commitSpans.get(0).getParentSpanId());
}

@Test
public void testTransactionTag() {
try (Connection connection = createTestConnection()) {
Expand Down