Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -652,17 +652,56 @@ public StatementResult statementShowTransactionIsolationLevel() {
getConnection().isInTransaction()
? getConnection().getTransactionIsolationLevel()
: getConnection().getDefaultIsolationLevel();
return resultSet("transaction_isolation", isolationLevel, SHOW_TRANSACTION_ISOLATION_LEVEL);
return resultSet(
"transaction_isolation",
formatIsolationLevel(getEffectiveIsolationLevel(isolationLevel)),
SHOW_TRANSACTION_ISOLATION_LEVEL);
}

@Override
public StatementResult statementShowDefaultTransactionIsolation() {
return resultSet(
"default_transaction_isolation",
getConnection().getDefaultIsolationLevel(),
formatIsolationLevel(
getEffectiveIsolationLevel(getConnection().getDefaultIsolationLevel())),
SHOW_DEFAULT_TRANSACTION_ISOLATION);
}

/** Resolves an unspecified isolation level to the database default. */
private TransactionOptions.IsolationLevel getEffectiveIsolationLevel(
TransactionOptions.IsolationLevel isolationLevel) {
TransactionOptions.IsolationLevel configuredIsolationLevel =
isolationLevel == TransactionOptions.IsolationLevel.ISOLATION_LEVEL_UNSPECIFIED
? getConnection().getDatabaseClient().getDatabaseDefaultIsolationLevel()
: isolationLevel;
switch (configuredIsolationLevel) {
case ISOLATION_LEVEL_UNSPECIFIED:
return TransactionOptions.IsolationLevel.SERIALIZABLE;
case UNRECOGNIZED:
throw SpannerExceptionFactory.newSpannerException(
ErrorCode.FAILED_PRECONDITION,
"The default isolation level of the database is not supported by this version of the"
+ " client.");
default:
return configuredIsolationLevel;
}
}

private String formatIsolationLevel(TransactionOptions.IsolationLevel isolationLevel) {
if (getConnection().getDialect() != Dialect.POSTGRESQL) {
return isolationLevel.name();
}
switch (isolationLevel) {
case SERIALIZABLE:
return "serializable";
case REPEATABLE_READ:
return "repeatable read";
default:
throw SpannerExceptionFactory.newSpannerException(
ErrorCode.INVALID_ARGUMENT, "Unknown isolation level: " + isolationLevel);
}
}

@Override
public StatementResult statementShowDataBoostEnabled() {
return resultSet(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,15 @@
"method": "statementShowTransactionIsolationLevel",
"exampleStatements": ["show transaction isolation level","show variable transaction isolation level"]
},
{
"name": "SHOW [VARIABLE] TRANSACTION_ISOLATION",
"executorName": "ClientSideStatementNoParamExecutor",
"resultType": "RESULT_SET",
"statementType": "SHOW_TRANSACTION_ISOLATION_LEVEL",
"regex": "(?is)\\A\\s*show\\s+(?:variable\\s+)?transaction_isolation\\s*\\z",
"method": "statementShowTransactionIsolationLevel",
"exampleStatements": ["show transaction_isolation","show variable transaction_isolation"]
},
{
"name": "SHOW [VARIABLE] DEFAULT_TRANSACTION_ISOLATION",
"executorName": "ClientSideStatementNoParamExecutor",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@ public void testClientSideStatementType() {
parser
.parse(Statement.of("ABORT TRANSACTION and no chain"))
.getClientSideStatementType());
assertEquals(
ClientSideStatementType.SHOW_TRANSACTION_ISOLATION_LEVEL,
parser.parse(Statement.of("show transaction_isolation")).getClientSideStatementType());
}

for (ClientSideStatementImpl statement : parser.getClientSideStatements()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
import com.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata;
import com.google.spanner.v1.ExecuteSqlRequest.QueryOptions;
import com.google.spanner.v1.ResultSetStats;
import com.google.spanner.v1.TransactionOptions.IsolationLevel;
import java.io.InputStream;
import java.util.Arrays;
import java.util.HashMap;
Expand Down Expand Up @@ -254,6 +255,8 @@ public static ConnectionImpl createConnection(final ConnectionOptions options, D
DdlClient ddlClient = createDefaultMockDdlClient();
DatabaseClient dbClient = mock(DatabaseClient.class);
when(dbClient.getDialect()).thenReturn(dialect);
when(dbClient.getDatabaseDefaultIsolationLevel())
.thenReturn(IsolationLevel.ISOLATION_LEVEL_UNSPECIFIED);
ReadOnlyTransaction singleUseReadOnlyTx = mock(ReadOnlyTransaction.class);

ResultSet mockResultSetWithStats = createSelect1MockResultSet();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,16 @@

package com.google.cloud.spanner.connection;

import static com.google.spanner.v1.TransactionOptions.IsolationLevel.ISOLATION_LEVEL_UNSPECIFIED;
import static com.google.spanner.v1.TransactionOptions.IsolationLevel.REPEATABLE_READ;
import static com.google.spanner.v1.TransactionOptions.IsolationLevel.SERIALIZABLE;
import static com.google.spanner.v1.TransactionOptions.IsolationLevel.UNRECOGNIZED;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
Expand All @@ -27,10 +34,15 @@
import static org.mockito.Mockito.when;

import com.google.cloud.Timestamp;
import com.google.cloud.spanner.DatabaseClient;
import com.google.cloud.spanner.Dialect;
import com.google.cloud.spanner.ErrorCode;
import com.google.cloud.spanner.ResultSet;
import com.google.cloud.spanner.SpannerException;
import com.google.cloud.spanner.TimestampBound;
import com.google.cloud.spanner.connection.PgTransactionMode.AccessMode;
import com.google.cloud.spanner.connection.PgTransactionMode.IsolationLevel;
import com.google.spanner.v1.TransactionOptions;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
import org.junit.Before;
Expand Down Expand Up @@ -277,4 +289,64 @@ public void testStatementGetProtoDescriptorsFilePath() {
subject.statementShowProtoDescriptorsFilePath();
verify(connection).getProtoDescriptorsFilePath();
}

@Test
public void testStatementShowTransactionIsolationLevel() {
when(connection.getDefaultIsolationLevel()).thenReturn(REPEATABLE_READ);
assertEquals(name(REPEATABLE_READ), value(subject.statementShowTransactionIsolationLevel()));
assertEquals(name(REPEATABLE_READ), value(subject.statementShowDefaultTransactionIsolation()));

when(connection.isInTransaction()).thenReturn(true);
when(connection.getTransactionIsolationLevel()).thenReturn(SERIALIZABLE);
assertEquals(name(SERIALIZABLE), value(subject.statementShowTransactionIsolationLevel()));
assertEquals(name(REPEATABLE_READ), value(subject.statementShowDefaultTransactionIsolation()));
}

@Test
public void testStatementShowIsolationLevelUsesDatabaseDefault() {
when(connection.getDefaultIsolationLevel()).thenReturn(ISOLATION_LEVEL_UNSPECIFIED);
mockDatabaseDefaultIsolationLevel(REPEATABLE_READ);
assertEquals(name(REPEATABLE_READ), value(subject.statementShowTransactionIsolationLevel()));
assertEquals(name(REPEATABLE_READ), value(subject.statementShowDefaultTransactionIsolation()));
}

@Test
public void testStatementShowIsolationLevelFallsBackToSerializable() {
when(connection.getDefaultIsolationLevel()).thenReturn(ISOLATION_LEVEL_UNSPECIFIED);
mockDatabaseDefaultIsolationLevel(ISOLATION_LEVEL_UNSPECIFIED);
assertEquals(name(SERIALIZABLE), value(subject.statementShowTransactionIsolationLevel()));
assertEquals(name(SERIALIZABLE), value(subject.statementShowDefaultTransactionIsolation()));
}

@Test
public void testStatementShowIsolationLevelWithUnrecognizedDatabaseDefault() {
when(connection.getDefaultIsolationLevel()).thenReturn(ISOLATION_LEVEL_UNSPECIFIED);
mockDatabaseDefaultIsolationLevel(UNRECOGNIZED);
SpannerException exception =
assertThrows(SpannerException.class, subject::statementShowTransactionIsolationLevel);
assertEquals(ErrorCode.FAILED_PRECONDITION, exception.getErrorCode());
exception =
assertThrows(SpannerException.class, subject::statementShowDefaultTransactionIsolation);
assertEquals(ErrorCode.FAILED_PRECONDITION, exception.getErrorCode());
}

private void mockDatabaseDefaultIsolationLevel(TransactionOptions.IsolationLevel isolationLevel) {
DatabaseClient databaseClient = mock(DatabaseClient.class);
when(databaseClient.getDatabaseDefaultIsolationLevel()).thenReturn(isolationLevel);
when(connection.getDatabaseClient()).thenReturn(databaseClient);
}

private String name(TransactionOptions.IsolationLevel isolationLevel) {
if (dialect == Dialect.POSTGRESQL) {
return isolationLevel == SERIALIZABLE ? "serializable" : "repeatable read";
}
return isolationLevel.name();
}

private static String value(StatementResult result) {
try (ResultSet resultSet = result.getResultSet()) {
assertTrue(resultSet.next());
return resultSet.getString(0);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,42 @@ public void testSetTransactionIsolationLevel() {
SpannerPool.closeSpannerPool();
}

@Test
public void testShowTransactionIsolationLevel() {
try {
for (IsolationLevel databaseDefault :
new IsolationLevel[] {
IsolationLevel.ISOLATION_LEVEL_UNSPECIFIED, IsolationLevel.REPEATABLE_READ
}) {
SpannerPool.closeSpannerPool();
mockSpanner.putStatementResult(
MockSpannerServiceImpl.StatementResult.detectDialectResult(
Dialect.POSTGRESQL, databaseDefault));
IsolationLevel effective =
isolationLevel == IsolationLevel.ISOLATION_LEVEL_UNSPECIFIED
? databaseDefault
: isolationLevel;
String expected =
effective == IsolationLevel.REPEATABLE_READ ? "repeatable read" : "serializable";

try (Connection connection = createConnection()) {
assertEquals(expected, getSingleString(connection, "show transaction isolation level"));
assertEquals(expected, getSingleString(connection, "show transaction_isolation"));
assertEquals(expected, getSingleString(connection, "show default_transaction_isolation"));

connection.execute(Statement.of("set transaction isolation level repeatable read"));
assertEquals(
"repeatable read", getSingleString(connection, "show transaction_isolation"));
assertEquals(expected, getSingleString(connection, "show default_transaction_isolation"));
}
}
} finally {
SpannerPool.closeSpannerPool();
mockSpanner.putStatementResult(
MockSpannerServiceImpl.StatementResult.detectDialectResult(Dialect.GOOGLE_STANDARD_SQL));
}
}

@Test
public void testTransactionTimeout() {
// Use a fake ticker to be able to advance the clock without having to sleep for X ms.
Expand Down Expand Up @@ -399,4 +435,11 @@ public void testCanUseAllMethodsWithInternalRetriesDisabled() {
connection.commit();
}
}

private static String getSingleString(Connection connection, String sql) {
try (ResultSet resultSet = connection.executeQuery(Statement.of(sql))) {
assertTrue(resultSet.next());
return resultSet.getString(0);
}
}
}
Loading
Loading