Skip to content

Commit

Permalink
feat: allow DDL with autocommit=false (#1600)
Browse files Browse the repository at this point in the history
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
```
  • Loading branch information
olavloite authored May 4, 2024
1 parent 84ea11a commit a61c25d
Showing 1 changed file with 101 additions and 0 deletions.
101 changes: 101 additions & 0 deletions src/test/java/com/google/cloud/spanner/jdbc/DdlMockServerTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* Copyright 2024 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.jdbc;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;

import com.google.cloud.spanner.MockSpannerServiceImpl.StatementResult;
import com.google.cloud.spanner.Statement;
import com.google.cloud.spanner.connection.AbstractMockServerTest;
import com.google.longrunning.Operation;
import com.google.protobuf.Any;
import com.google.protobuf.Empty;
import com.google.rpc.Code;
import com.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

@RunWith(JUnit4.class)
public class DdlMockServerTest extends AbstractMockServerTest {

private String createUrl(boolean autoCommit) {
return String.format(
"jdbc:cloudspanner://localhost:%d/projects/%s/instances/%s/databases/%s?usePlainText=true;autoCommit=%s",
getPort(), "proj", "inst", "db", autoCommit);
}

private Connection createConnection(boolean autoCommit) throws SQLException {
return DriverManager.getConnection(createUrl(autoCommit));
}

@Test
public void testDdlInAutoCommitIsTrue_succeeds() throws SQLException {
mockDatabaseAdmin.addResponse(
Operation.newBuilder()
.setDone(true)
.setResponse(Any.pack(Empty.getDefaultInstance()))
.setMetadata(Any.pack(UpdateDatabaseDdlMetadata.getDefaultInstance()))
.build());

try (Connection connection = createConnection(/* autoCommit = */ true)) {
assertFalse(
connection.createStatement().execute("create table foo (id int64) primary key (id)"));
}
}

@Test
public void testDdlInAutoCommitIsFalse_succeedsWithNoActiveTransaction() throws SQLException {
mockDatabaseAdmin.addResponse(
Operation.newBuilder()
.setDone(true)
.setResponse(Any.pack(Empty.getDefaultInstance()))
.setMetadata(Any.pack(UpdateDatabaseDdlMetadata.getDefaultInstance()))
.build());

try (Connection connection = createConnection(/* autoCommit = */ false)) {
assertFalse(
connection.createStatement().execute("create table foo (id int64) primary key (id)"));
}
}

@Test
public void testDdlInAutoCommitIsFalse_failsWithActiveTransaction() throws SQLException {
mockSpanner.putStatementResult(
StatementResult.update(Statement.of("update foo set bar=1 where true"), 1L));

try (Connection connection = createConnection(/* autoCommit = */ false)) {
assertFalse(connection.createStatement().execute("update foo set bar=1 where true"));
SQLException exception =
assertThrows(
SQLException.class,
() ->
connection
.createStatement()
.execute("create table foo (id int64) primary key (id)"));
assertTrue(exception instanceof JdbcSqlException);
JdbcSqlException jdbcSqlException = (JdbcSqlException) exception;
assertEquals(Code.FAILED_PRECONDITION, jdbcSqlException.getCode());
}
}
}

0 comments on commit a61c25d

Please sign in to comment.