Skip to content

Commit a61c25d

Browse files
authored
feat: allow DDL with autocommit=false (#1600)
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 84ea11a commit a61c25d

File tree

1 file changed

+101
-0
lines changed

1 file changed

+101
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
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.jdbc;
18+
19+
import static org.junit.Assert.assertEquals;
20+
import static org.junit.Assert.assertFalse;
21+
import static org.junit.Assert.assertThrows;
22+
import static org.junit.Assert.assertTrue;
23+
24+
import com.google.cloud.spanner.MockSpannerServiceImpl.StatementResult;
25+
import com.google.cloud.spanner.Statement;
26+
import com.google.cloud.spanner.connection.AbstractMockServerTest;
27+
import com.google.longrunning.Operation;
28+
import com.google.protobuf.Any;
29+
import com.google.protobuf.Empty;
30+
import com.google.rpc.Code;
31+
import com.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata;
32+
import java.sql.Connection;
33+
import java.sql.DriverManager;
34+
import java.sql.SQLException;
35+
import org.junit.Test;
36+
import org.junit.runner.RunWith;
37+
import org.junit.runners.JUnit4;
38+
39+
@RunWith(JUnit4.class)
40+
public class DdlMockServerTest extends AbstractMockServerTest {
41+
42+
private String createUrl(boolean autoCommit) {
43+
return String.format(
44+
"jdbc:cloudspanner://localhost:%d/projects/%s/instances/%s/databases/%s?usePlainText=true;autoCommit=%s",
45+
getPort(), "proj", "inst", "db", autoCommit);
46+
}
47+
48+
private Connection createConnection(boolean autoCommit) throws SQLException {
49+
return DriverManager.getConnection(createUrl(autoCommit));
50+
}
51+
52+
@Test
53+
public void testDdlInAutoCommitIsTrue_succeeds() throws SQLException {
54+
mockDatabaseAdmin.addResponse(
55+
Operation.newBuilder()
56+
.setDone(true)
57+
.setResponse(Any.pack(Empty.getDefaultInstance()))
58+
.setMetadata(Any.pack(UpdateDatabaseDdlMetadata.getDefaultInstance()))
59+
.build());
60+
61+
try (Connection connection = createConnection(/* autoCommit = */ true)) {
62+
assertFalse(
63+
connection.createStatement().execute("create table foo (id int64) primary key (id)"));
64+
}
65+
}
66+
67+
@Test
68+
public void testDdlInAutoCommitIsFalse_succeedsWithNoActiveTransaction() throws SQLException {
69+
mockDatabaseAdmin.addResponse(
70+
Operation.newBuilder()
71+
.setDone(true)
72+
.setResponse(Any.pack(Empty.getDefaultInstance()))
73+
.setMetadata(Any.pack(UpdateDatabaseDdlMetadata.getDefaultInstance()))
74+
.build());
75+
76+
try (Connection connection = createConnection(/* autoCommit = */ false)) {
77+
assertFalse(
78+
connection.createStatement().execute("create table foo (id int64) primary key (id)"));
79+
}
80+
}
81+
82+
@Test
83+
public void testDdlInAutoCommitIsFalse_failsWithActiveTransaction() throws SQLException {
84+
mockSpanner.putStatementResult(
85+
StatementResult.update(Statement.of("update foo set bar=1 where true"), 1L));
86+
87+
try (Connection connection = createConnection(/* autoCommit = */ false)) {
88+
assertFalse(connection.createStatement().execute("update foo set bar=1 where true"));
89+
SQLException exception =
90+
assertThrows(
91+
SQLException.class,
92+
() ->
93+
connection
94+
.createStatement()
95+
.execute("create table foo (id int64) primary key (id)"));
96+
assertTrue(exception instanceof JdbcSqlException);
97+
JdbcSqlException jdbcSqlException = (JdbcSqlException) exception;
98+
assertEquals(Code.FAILED_PRECONDITION, jdbcSqlException.getCode());
99+
}
100+
}
101+
}

0 commit comments

Comments
 (0)