Skip to content

Commit 22833ac

Browse files
authoredMay 3, 2024··
feat: allow DDL with autocommit=false (#3057)
Adds support for running DDL statements when a connection is in autocommit=false mode. By default, DDL statements are only allowed when no transaction is active. That is; no query or DML statement has been executed which activated a read/write transaction. A new flag is added that can be used to revert the behavior back to the original behavior where DDL is always refused when autocommit=false. The same flag can also be used to make the API behave the same as MySQL and Oracle, where any active transaction is automatically committed whenever a DDL statement is encountered. Concretely this means that the following is now allowed: ``` set autocommit=false; create table Singers (SingerId INT64, Name STRING(MAX)) PRIMARY KEY (SingerId); ``` The following is by default NOT allowed, unless ddlInTransactionMode=AUTO_COMMIT_TRANSACTION ``` set autocommit=false; select * from singers; -- This starts a transaction create table Albums (AlbumId INT64) PRIMARY KEY (AlbumId); -- This is not allowed ```
1 parent e595157 commit 22833ac

13 files changed

+3553
-256
lines changed
 

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

+12
Original file line numberDiff line numberDiff line change
@@ -656,5 +656,17 @@
656656
<className>com/google/cloud/spanner/connection/Connection</className>
657657
<method>com.google.cloud.spanner.Spanner getSpanner()</method>
658658
</difference>
659+
660+
<!-- Add DdlInTransactionMode -->
661+
<difference>
662+
<differenceType>7012</differenceType>
663+
<className>com/google/cloud/spanner/connection/Connection</className>
664+
<method>void setDdlInTransactionMode(com.google.cloud.spanner.connection.DdlInTransactionMode)</method>
665+
</difference>
666+
<difference>
667+
<differenceType>7012</differenceType>
668+
<className>com/google/cloud/spanner/connection/Connection</className>
669+
<method>com.google.cloud.spanner.connection.DdlInTransactionMode getDdlInTransactionMode()</method>
670+
</difference>
659671

660672
</differences>

‎google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/Connection.java

+6
Original file line numberDiff line numberDiff line change
@@ -773,6 +773,12 @@ default boolean isDelayTransactionStartUntilFirstWrite() {
773773
/** Sets how savepoints should be supported on this connection. */
774774
void setSavepointSupport(SavepointSupport savepointSupport);
775775

776+
/** Returns the current {@link DdlInTransactionMode} for this connection. */
777+
DdlInTransactionMode getDdlInTransactionMode();
778+
779+
/** Sets how the connection should behave if a DDL statement is executed during a transaction. */
780+
void setDdlInTransactionMode(DdlInTransactionMode ddlInTransactionMode);
781+
776782
/**
777783
* Creates a savepoint with the given name.
778784
*

‎google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionImpl.java

+64-10
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,7 @@ static UnitOfWorkType of(TransactionMode transactionMode) {
243243
private QueryOptions queryOptions = QueryOptions.getDefaultInstance();
244244
private RpcPriority rpcPriority = null;
245245
private SavepointSupport savepointSupport = SavepointSupport.FAIL_AFTER_ROLLBACK;
246+
private DdlInTransactionMode ddlInTransactionMode;
246247

247248
private String transactionTag;
248249
private String statementTag;
@@ -271,6 +272,7 @@ static UnitOfWorkType of(TransactionMode transactionMode) {
271272
this.autocommit = options.isAutocommit();
272273
this.queryOptions = this.queryOptions.toBuilder().mergeFrom(options.getQueryOptions()).build();
273274
this.rpcPriority = options.getRPCPriority();
275+
this.ddlInTransactionMode = options.getDdlInTransactionMode();
274276
this.returnCommitStats = options.isReturnCommitStats();
275277
this.delayTransactionStartUntilFirstWrite = options.isDelayTransactionStartUntilFirstWrite();
276278
this.dataBoostEnabled = options.isDataBoostEnabled();
@@ -296,6 +298,7 @@ static UnitOfWorkType of(TransactionMode transactionMode) {
296298
new StatementExecutor(options.isUseVirtualThreads(), Collections.emptyList());
297299
this.spannerPool = Preconditions.checkNotNull(spannerPool);
298300
this.options = Preconditions.checkNotNull(options);
301+
this.ddlInTransactionMode = options.getDdlInTransactionMode();
299302
this.spanner = spannerPool.getSpanner(options, this);
300303
this.ddlClient = Preconditions.checkNotNull(ddlClient);
301304
this.dbClient = Preconditions.checkNotNull(dbClient);
@@ -571,6 +574,21 @@ public RpcPriority getRPCPriority() {
571574
return this.rpcPriority;
572575
}
573576

577+
@Override
578+
public DdlInTransactionMode getDdlInTransactionMode() {
579+
return this.ddlInTransactionMode;
580+
}
581+
582+
@Override
583+
public void setDdlInTransactionMode(DdlInTransactionMode ddlInTransactionMode) {
584+
ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG);
585+
ConnectionPreconditions.checkState(
586+
!isBatchActive(), "Cannot set DdlInTransactionMode while in a batch");
587+
ConnectionPreconditions.checkState(
588+
!isTransactionStarted(), "Cannot set DdlInTransactionMode while a transaction is active");
589+
this.ddlInTransactionMode = Preconditions.checkNotNull(ddlInTransactionMode);
590+
}
591+
574592
@Override
575593
public void setStatementTimeout(long timeout, TimeUnit unit) {
576594
Preconditions.checkArgument(timeout > 0L, "Zero or negative timeout values are not allowed");
@@ -1639,28 +1657,55 @@ private ApiFuture<long[]> internalExecuteBatchUpdateAsync(
16391657
}
16401658

16411659
private UnitOfWork getCurrentUnitOfWorkOrStartNewUnitOfWork() {
1642-
return getCurrentUnitOfWorkOrStartNewUnitOfWork(false);
1660+
return getCurrentUnitOfWorkOrStartNewUnitOfWork(StatementType.UNKNOWN, false);
1661+
}
1662+
1663+
@VisibleForTesting
1664+
UnitOfWork getCurrentUnitOfWorkOrStartNewUnitOfWork(boolean isInternalMetadataQuery) {
1665+
return getCurrentUnitOfWorkOrStartNewUnitOfWork(StatementType.UNKNOWN, isInternalMetadataQuery);
1666+
}
1667+
1668+
private UnitOfWork getOrStartDdlUnitOfWork() {
1669+
return getCurrentUnitOfWorkOrStartNewUnitOfWork(StatementType.DDL, false);
16431670
}
16441671

16451672
/**
16461673
* Returns the current {@link UnitOfWork} of this connection, or creates a new one based on the
16471674
* current transaction settings of the connection and returns that.
16481675
*/
16491676
@VisibleForTesting
1650-
UnitOfWork getCurrentUnitOfWorkOrStartNewUnitOfWork(boolean isInternalMetadataQuery) {
1677+
UnitOfWork getCurrentUnitOfWorkOrStartNewUnitOfWork(
1678+
StatementType statementType, boolean isInternalMetadataQuery) {
16511679
if (isInternalMetadataQuery) {
16521680
// Just return a temporary single-use transaction.
1653-
return createNewUnitOfWork(true);
1681+
return createNewUnitOfWork(/* isInternalMetadataQuery = */ true, /* forceSingleUse = */ true);
16541682
}
1683+
maybeAutoCommitCurrentTransaction(statementType);
16551684
if (this.currentUnitOfWork == null || !this.currentUnitOfWork.isActive()) {
1656-
this.currentUnitOfWork = createNewUnitOfWork(false);
1685+
this.currentUnitOfWork =
1686+
createNewUnitOfWork(
1687+
/* isInternalMetadataQuery = */ false,
1688+
/* forceSingleUse = */ statementType == StatementType.DDL
1689+
&& this.ddlInTransactionMode != DdlInTransactionMode.FAIL
1690+
&& !this.transactionBeginMarked);
16571691
}
16581692
return this.currentUnitOfWork;
16591693
}
16601694

1695+
void maybeAutoCommitCurrentTransaction(StatementType statementType) {
1696+
if (this.currentUnitOfWork instanceof ReadWriteTransaction
1697+
&& this.currentUnitOfWork.isActive()
1698+
&& statementType == StatementType.DDL
1699+
&& this.ddlInTransactionMode == DdlInTransactionMode.AUTO_COMMIT_TRANSACTION) {
1700+
commit();
1701+
}
1702+
}
1703+
16611704
@VisibleForTesting
1662-
UnitOfWork createNewUnitOfWork(boolean isInternalMetadataQuery) {
1663-
if (isInternalMetadataQuery || (isAutocommit() && !isInTransaction() && !isInBatch())) {
1705+
UnitOfWork createNewUnitOfWork(boolean isInternalMetadataQuery, boolean forceSingleUse) {
1706+
if (isInternalMetadataQuery
1707+
|| (isAutocommit() && !isInTransaction() && !isInBatch())
1708+
|| forceSingleUse) {
16641709
return SingleUseTransaction.newBuilder()
16651710
.setInternalMetadataQuery(isInternalMetadataQuery)
16661711
.setDdlClient(ddlClient)
@@ -1741,7 +1786,7 @@ private void popUnitOfWorkFromTransactionStack() {
17411786
}
17421787

17431788
private ApiFuture<Void> executeDdlAsync(CallType callType, ParsedStatement ddl) {
1744-
return getCurrentUnitOfWorkOrStartNewUnitOfWork().executeDdlAsync(callType, ddl);
1789+
return getOrStartDdlUnitOfWork().executeDdlAsync(callType, ddl);
17451790
}
17461791

17471792
@Override
@@ -1788,15 +1833,23 @@ public void startBatchDdl() {
17881833
ConnectionPreconditions.checkState(
17891834
!isReadOnly(), "Cannot start a DDL batch when the connection is in read-only mode");
17901835
ConnectionPreconditions.checkState(
1791-
!isTransactionStarted(), "Cannot start a DDL batch while a transaction is active");
1836+
!isTransactionStarted()
1837+
|| getDdlInTransactionMode() == DdlInTransactionMode.AUTO_COMMIT_TRANSACTION,
1838+
"Cannot start a DDL batch while a transaction is active");
17921839
ConnectionPreconditions.checkState(
17931840
!(isAutocommit() && isInTransaction()),
17941841
"Cannot start a DDL batch while in a temporary transaction");
17951842
ConnectionPreconditions.checkState(
17961843
!transactionBeginMarked, "Cannot start a DDL batch when a transaction has begun");
1844+
ConnectionPreconditions.checkState(
1845+
isAutocommit() || getDdlInTransactionMode() != DdlInTransactionMode.FAIL,
1846+
"Cannot start a DDL batch when autocommit=false and ddlInTransactionMode=FAIL");
1847+
1848+
maybeAutoCommitCurrentTransaction(StatementType.DDL);
17971849
this.batchMode = BatchMode.DDL;
17981850
this.unitOfWorkType = UnitOfWorkType.DDL_BATCH;
1799-
this.currentUnitOfWork = createNewUnitOfWork(false);
1851+
this.currentUnitOfWork =
1852+
createNewUnitOfWork(/* isInternalMetadataQuery = */ false, /* forceSingleUse = */ false);
18001853
}
18011854

18021855
@Override
@@ -1814,7 +1867,8 @@ public void startBatchDml() {
18141867
// Then create the DML batch.
18151868
this.batchMode = BatchMode.DML;
18161869
this.unitOfWorkType = UnitOfWorkType.DML_BATCH;
1817-
this.currentUnitOfWork = createNewUnitOfWork(false);
1870+
this.currentUnitOfWork =
1871+
createNewUnitOfWork(/* isInternalMetadataQuery = */ false, /* forceSingleUse = */ false);
18181872
}
18191873

18201874
@Override

‎google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionOptions.java

+23
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,8 @@ public String[] getValidValues() {
183183
private static final String DEFAULT_OPTIMIZER_VERSION = "";
184184
private static final String DEFAULT_OPTIMIZER_STATISTICS_PACKAGE = "";
185185
private static final RpcPriority DEFAULT_RPC_PRIORITY = null;
186+
private static final DdlInTransactionMode DEFAULT_DDL_IN_TRANSACTION_MODE =
187+
DdlInTransactionMode.ALLOW_IN_EMPTY_TRANSACTION;
186188
private static final boolean DEFAULT_RETURN_COMMIT_STATS = false;
187189
private static final boolean DEFAULT_LENIENT = false;
188190
private static final boolean DEFAULT_ROUTE_TO_LEADER = true;
@@ -253,6 +255,8 @@ public String[] getValidValues() {
253255
public static final String LENIENT_PROPERTY_NAME = "lenient";
254256
/** Name of the 'rpcPriority' connection property. */
255257
public static final String RPC_PRIORITY_NAME = "rpcPriority";
258+
259+
public static final String DDL_IN_TRANSACTION_MODE_PROPERTY_NAME = "ddlInTransactionMode";
256260
/** Dialect to use for a connection. */
257261
private static final String DIALECT_PROPERTY_NAME = "dialect";
258262
/** Name of the 'databaseRole' connection property. */
@@ -374,6 +378,11 @@ private static String generateGuardedConnectionPropertyError(
374378
ConnectionProperty.createStringProperty(
375379
RPC_PRIORITY_NAME,
376380
"Sets the priority for all RPC invocations from this connection (HIGH/MEDIUM/LOW). The default is HIGH."),
381+
ConnectionProperty.createStringProperty(
382+
DDL_IN_TRANSACTION_MODE_PROPERTY_NAME,
383+
"Sets the behavior of a connection when a DDL statement is executed in a read/write transaction. The default is "
384+
+ DEFAULT_DDL_IN_TRANSACTION_MODE
385+
+ "."),
377386
ConnectionProperty.createStringProperty(
378387
DIALECT_PROPERTY_NAME,
379388
"Sets the dialect to use for new databases that are created by this connection."),
@@ -697,6 +706,7 @@ public static Builder newBuilder() {
697706
private final boolean autoConfigEmulator;
698707
private final Dialect dialect;
699708
private final RpcPriority rpcPriority;
709+
private final DdlInTransactionMode ddlInTransactionMode;
700710
private final boolean delayTransactionStartUntilFirstWrite;
701711
private final boolean trackSessionLeaks;
702712
private final boolean trackConnectionLeaks;
@@ -757,6 +767,7 @@ private ConnectionOptions(Builder builder) {
757767
determineHost(
758768
matcher, parseEndpoint(this.uri), autoConfigEmulator, usePlainText, System.getenv());
759769
this.rpcPriority = parseRPCPriority(this.uri);
770+
this.ddlInTransactionMode = parseDdlInTransactionMode(this.uri);
760771
this.delayTransactionStartUntilFirstWrite = parseDelayTransactionStartUntilFirstWrite(this.uri);
761772
this.trackSessionLeaks = parseTrackSessionLeaks(this.uri);
762773
this.trackConnectionLeaks = parseTrackConnectionLeaks(this.uri);
@@ -1195,6 +1206,14 @@ static RpcPriority parseRPCPriority(String uri) {
11951206
return value != null ? RpcPriority.valueOf(value) : DEFAULT_RPC_PRIORITY;
11961207
}
11971208

1209+
@VisibleForTesting
1210+
static DdlInTransactionMode parseDdlInTransactionMode(String uri) {
1211+
String value = parseUriProperty(uri, DDL_IN_TRANSACTION_MODE_PROPERTY_NAME);
1212+
return value != null
1213+
? DdlInTransactionMode.valueOf(value.toUpperCase())
1214+
: DEFAULT_DDL_IN_TRANSACTION_MODE;
1215+
}
1216+
11981217
@VisibleForTesting
11991218
static String parseUriProperty(String uri, String property) {
12001219
Pattern pattern = Pattern.compile(String.format("(?is)(?:;|\\?)%s=(.*?)(?:;|$)", property));
@@ -1466,6 +1485,10 @@ RpcPriority getRPCPriority() {
14661485
return rpcPriority;
14671486
}
14681487

1488+
DdlInTransactionMode getDdlInTransactionMode() {
1489+
return this.ddlInTransactionMode;
1490+
}
1491+
14691492
/**
14701493
* Whether connections created by this {@link ConnectionOptions} should delay the actual start of
14711494
* a read/write transaction until the first write operation.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright 2024 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.connection;
18+
19+
/** Enum used for setting the behavior of DDL in read/write transactions. */
20+
public enum DdlInTransactionMode {
21+
/** All DDL statements in a read/write transaction fail. */
22+
FAIL,
23+
/**
24+
* DDL statements in an empty transaction are allowed. That is; if the connection is in
25+
* AutoCommit=false mode and no other statement has been executed, then executing a DDL statement
26+
* or a DDL batch is allowed.
27+
*/
28+
ALLOW_IN_EMPTY_TRANSACTION,
29+
/**
30+
* DDL statements automatically cause the current transaction to be committed and the DDL
31+
* statement is subsequently executed without a transaction. This is equal to how MySQL and Oracle
32+
* behave.
33+
*/
34+
AUTO_COMMIT_TRANSACTION;
35+
}

‎google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionImplTest.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -1751,7 +1751,7 @@ public void testTransactionTagNotAllowedAfterTransactionStarted() {
17511751
new ConnectionImpl(
17521752
connectionOptions, spannerPool, ddlClient, dbClient, mock(BatchClient.class)) {
17531753
@Override
1754-
UnitOfWork createNewUnitOfWork(boolean isInternalMetadataQuery) {
1754+
UnitOfWork createNewUnitOfWork(boolean isInternalMetadataQuery, boolean forceSingleUse) {
17551755
return unitOfWork;
17561756
}
17571757
}) {

‎google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionImplTransactionalReadWriteTest.java

+14-7
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,8 @@ boolean isGetCommitTimestampAllowed() {
151151
boolean isExecuteAllowed(StatementType type) {
152152
return type == StatementType.CLIENT_SIDE
153153
|| type == StatementType.QUERY
154-
|| type == StatementType.UPDATE;
154+
|| type == StatementType.UPDATE
155+
|| type == StatementType.DDL;
155156
}
156157

157158
@Override
@@ -765,7 +766,8 @@ boolean isGetCommitTimestampAllowed() {
765766
boolean isExecuteAllowed(StatementType type) {
766767
return type == StatementType.CLIENT_SIDE
767768
|| type == StatementType.QUERY
768-
|| type == StatementType.UPDATE;
769+
|| type == StatementType.UPDATE
770+
|| type == StatementType.DDL;
769771
}
770772

771773
@Override
@@ -920,7 +922,8 @@ boolean isGetCommitTimestampAllowed() {
920922
boolean isExecuteAllowed(StatementType type) {
921923
return type == StatementType.CLIENT_SIDE
922924
|| type == StatementType.QUERY
923-
|| type == StatementType.UPDATE;
925+
|| type == StatementType.UPDATE
926+
|| type == StatementType.DDL;
924927
}
925928

926929
@Override
@@ -1074,7 +1077,8 @@ boolean isGetCommitTimestampAllowed() {
10741077
boolean isExecuteAllowed(StatementType type) {
10751078
return type == StatementType.CLIENT_SIDE
10761079
|| type == StatementType.QUERY
1077-
|| type == StatementType.UPDATE;
1080+
|| type == StatementType.UPDATE
1081+
|| type == StatementType.DDL;
10781082
}
10791083

10801084
@Override
@@ -1378,7 +1382,8 @@ boolean isGetCommitTimestampAllowed() {
13781382
boolean isExecuteAllowed(StatementType type) {
13791383
return type == StatementType.CLIENT_SIDE
13801384
|| type == StatementType.QUERY
1381-
|| type == StatementType.UPDATE;
1385+
|| type == StatementType.UPDATE
1386+
|| type == StatementType.DDL;
13821387
}
13831388

13841389
@Override
@@ -1829,7 +1834,8 @@ boolean isGetCommitTimestampAllowed() {
18291834
boolean isExecuteAllowed(StatementType type) {
18301835
return type == StatementType.CLIENT_SIDE
18311836
|| type == StatementType.QUERY
1832-
|| type == StatementType.UPDATE;
1837+
|| type == StatementType.UPDATE
1838+
|| type == StatementType.DDL;
18331839
}
18341840

18351841
@Override
@@ -1979,7 +1985,8 @@ boolean isGetCommitTimestampAllowed() {
19791985
boolean isExecuteAllowed(StatementType type) {
19801986
return type == StatementType.CLIENT_SIDE
19811987
|| type == StatementType.QUERY
1982-
|| type == StatementType.UPDATE;
1988+
|| type == StatementType.UPDATE
1989+
|| type == StatementType.DDL;
19831990
}
19841991

19851992
@Override

‎google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/DdlTest.java

+124
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,18 @@
1717
package com.google.cloud.spanner.connection;
1818

1919
import static org.junit.Assert.assertEquals;
20+
import static org.junit.Assert.assertThrows;
2021

22+
import com.google.cloud.spanner.ErrorCode;
23+
import com.google.cloud.spanner.SpannerException;
2124
import com.google.cloud.spanner.Statement;
2225
import com.google.cloud.spanner.connection.StatementResult.ResultType;
2326
import com.google.longrunning.Operation;
2427
import com.google.protobuf.Any;
2528
import com.google.protobuf.Empty;
2629
import com.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata;
2730
import com.google.spanner.admin.database.v1.UpdateDatabaseDdlRequest;
31+
import com.google.spanner.v1.CommitRequest;
2832
import java.util.List;
2933
import java.util.stream.Collectors;
3034
import org.junit.After;
@@ -106,4 +110,124 @@ public void testBatchedAnalyzeStatement() {
106110
assertEquals("create table foo (id int64) primary key (id)", requests.get(0).getStatements(0));
107111
assertEquals("analyze", requests.get(0).getStatements(1));
108112
}
113+
114+
@Test
115+
public void testDdlAtStartOfTransaction() {
116+
Statement statement = Statement.of("create table foo (id int64) primary key (id)");
117+
for (DdlInTransactionMode mode : DdlInTransactionMode.values()) {
118+
mockDatabaseAdmin.getRequests().clear();
119+
if (mode != DdlInTransactionMode.FAIL) {
120+
addUpdateDdlResponse();
121+
}
122+
123+
try (Connection connection = createConnection()) {
124+
connection.setAutocommit(false);
125+
connection.setDdlInTransactionMode(mode);
126+
127+
if (mode == DdlInTransactionMode.FAIL) {
128+
SpannerException exception =
129+
assertThrows(SpannerException.class, () -> connection.execute(statement));
130+
assertEquals(ErrorCode.FAILED_PRECONDITION, exception.getErrorCode());
131+
} else {
132+
assertEquals(ResultType.NO_RESULT, connection.execute(statement).getResultType());
133+
assertEquals(1, mockDatabaseAdmin.getRequests().size());
134+
}
135+
}
136+
}
137+
}
138+
139+
@Test
140+
public void testDdlBatchAtStartOfTransaction() {
141+
for (DdlInTransactionMode mode : DdlInTransactionMode.values()) {
142+
mockDatabaseAdmin.getRequests().clear();
143+
if (mode != DdlInTransactionMode.FAIL) {
144+
addUpdateDdlResponse();
145+
}
146+
147+
try (Connection connection = createConnection()) {
148+
connection.setAutocommit(false);
149+
connection.setDdlInTransactionMode(mode);
150+
151+
if (mode == DdlInTransactionMode.FAIL) {
152+
SpannerException exception =
153+
assertThrows(
154+
SpannerException.class,
155+
() -> connection.execute(Statement.of("start batch ddl")));
156+
assertEquals(ErrorCode.FAILED_PRECONDITION, exception.getErrorCode());
157+
} else {
158+
connection.execute(Statement.of("start batch ddl"));
159+
connection.execute(Statement.of("create table foo"));
160+
connection.execute(Statement.of("alter table bar"));
161+
connection.execute(Statement.of("run batch"));
162+
assertEquals(1, mockDatabaseAdmin.getRequests().size());
163+
UpdateDatabaseDdlRequest request =
164+
(UpdateDatabaseDdlRequest) mockDatabaseAdmin.getRequests().get(0);
165+
assertEquals(2, request.getStatementsCount());
166+
}
167+
}
168+
}
169+
}
170+
171+
@Test
172+
public void testDdlInTransaction() {
173+
Statement statement = Statement.of("create table foo (id int64) primary key (id)");
174+
for (DdlInTransactionMode mode : DdlInTransactionMode.values()) {
175+
mockDatabaseAdmin.getRequests().clear();
176+
if (mode == DdlInTransactionMode.AUTO_COMMIT_TRANSACTION) {
177+
addUpdateDdlResponse();
178+
}
179+
180+
try (Connection connection = createConnection()) {
181+
connection.setAutocommit(false);
182+
connection.setDdlInTransactionMode(mode);
183+
184+
connection.execute(INSERT_STATEMENT);
185+
186+
if (mode != DdlInTransactionMode.AUTO_COMMIT_TRANSACTION) {
187+
SpannerException exception =
188+
assertThrows(SpannerException.class, () -> connection.execute(statement));
189+
assertEquals(ErrorCode.FAILED_PRECONDITION, exception.getErrorCode());
190+
} else {
191+
assertEquals(ResultType.NO_RESULT, connection.execute(statement).getResultType());
192+
assertEquals(1, mockDatabaseAdmin.getRequests().size());
193+
assertEquals(1, mockSpanner.countRequestsOfType(CommitRequest.class));
194+
}
195+
}
196+
}
197+
}
198+
199+
@Test
200+
public void testDdlBatchInTransaction() {
201+
for (DdlInTransactionMode mode : DdlInTransactionMode.values()) {
202+
mockDatabaseAdmin.getRequests().clear();
203+
if (mode == DdlInTransactionMode.AUTO_COMMIT_TRANSACTION) {
204+
addUpdateDdlResponse();
205+
}
206+
207+
try (Connection connection = createConnection()) {
208+
connection.setAutocommit(false);
209+
connection.setDdlInTransactionMode(mode);
210+
211+
connection.execute(INSERT_STATEMENT);
212+
213+
if (mode != DdlInTransactionMode.AUTO_COMMIT_TRANSACTION) {
214+
SpannerException exception =
215+
assertThrows(
216+
SpannerException.class,
217+
() -> connection.execute(Statement.of("start batch ddl")));
218+
assertEquals(ErrorCode.FAILED_PRECONDITION, exception.getErrorCode());
219+
} else {
220+
connection.execute(Statement.of("start batch ddl"));
221+
connection.execute(Statement.of("create table foo"));
222+
connection.execute(Statement.of("alter table bar"));
223+
connection.execute(Statement.of("run batch"));
224+
assertEquals(1, mockDatabaseAdmin.getRequests().size());
225+
UpdateDatabaseDdlRequest request =
226+
(UpdateDatabaseDdlRequest) mockDatabaseAdmin.getRequests().get(0);
227+
assertEquals(2, request.getStatementsCount());
228+
assertEquals(1, mockSpanner.countRequestsOfType(CommitRequest.class));
229+
}
230+
}
231+
}
232+
}
109233
}

‎google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/ClientSideStatementsTest.sql

+1,189
Large diffs are not rendered by default.

‎google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/ConnectionImplGeneratedSqlScriptTest.sql

+112-119
Large diffs are not rendered by default.

‎google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/ITTransactionModeTest.sql

+2
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ NEW_CONNECTION;
3333
SHOW VARIABLE AUTOCOMMIT;
3434
@EXPECT RESULT_SET 'READONLY',false
3535
SHOW VARIABLE READONLY;
36+
@EXPECT RESULT_SET 'C',1
37+
SELECT 1 AS C;
3638

3739
@EXPECT EXCEPTION FAILED_PRECONDITION
3840
CREATE TABLE FOO (ID INT64 NOT NULL, NAME STRING(100) PRIMARY KEY (ID);

‎google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/postgresql/ClientSideStatementsTest.sql

+1,859
Large diffs are not rendered by default.

‎google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/postgresql/ConnectionImplGeneratedSqlScriptTest.sql

+112-119
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)
Please sign in to comment.