Skip to content
Merged
24 changes: 24 additions & 0 deletions google-cloud-spanner/clirr-ignored-differences.xml
Original file line number Diff line number Diff line change
Expand Up @@ -850,6 +850,30 @@
<className>com/google/cloud/spanner/connection/Connection</className>
<method>java.lang.String getDefaultSequenceKind()</method>
</difference>

<!-- Default isolation level -->
<difference>
<differenceType>7012</differenceType>
<className>com/google/cloud/spanner/connection/Connection</className>
<method>void setDefaultIsolationLevel(com.google.spanner.v1.TransactionOptions$IsolationLevel)</method>
</difference>
<difference>
<differenceType>7012</differenceType>
<className>com/google/cloud/spanner/connection/Connection</className>
<method>com.google.spanner.v1.TransactionOptions$IsolationLevel getDefaultIsolationLevel()</method>
</difference>

<!-- Isolation level per transaction -->
<difference>
<differenceType>7012</differenceType>
<className>com/google/cloud/spanner/connection/Connection</className>
<method>void beginTransaction(com.google.spanner.v1.TransactionOptions$IsolationLevel)</method>
</difference>
<difference>
<differenceType>7012</differenceType>
<className>com/google/cloud/spanner/connection/Connection</className>
<method>com.google.api.core.ApiFuture beginTransactionAsync(com.google.spanner.v1.TransactionOptions$IsolationLevel)</method>
</difference>

<!-- Removed ConnectionOptions$ConnectionProperty in favor of the more generic ConnectionProperty class. -->
<difference>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,14 @@ public static AbstractStatementParser getInstance(Dialect dialect) {
}
}

static final Set<String> ddlStatements =
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These statements have been moved up in this file to make sure that they are executed before the static block on line 151.

ImmutableSet.of("CREATE", "DROP", "ALTER", "ANALYZE", "GRANT", "REVOKE", "RENAME");
static final Set<String> selectStatements =
ImmutableSet.of("SELECT", "WITH", "SHOW", "FROM", "GRAPH");
static final Set<String> SELECT_STATEMENTS_ALLOWING_PRECEDING_BRACKETS =
ImmutableSet.of("SELECT", "FROM");
static final Set<String> dmlStatements = ImmutableSet.of("INSERT", "UPDATE", "DELETE");

/*
* The following fixed pre-parsed statements are used internally by the Connection API. These do
* not need to be parsed using a specific dialect, as they are equal for all dialects, and
Expand Down Expand Up @@ -416,13 +424,6 @@ ClientSideStatement getClientSideStatement() {
}
}

static final Set<String> ddlStatements =
ImmutableSet.of("CREATE", "DROP", "ALTER", "ANALYZE", "GRANT", "REVOKE", "RENAME");
static final Set<String> selectStatements =
ImmutableSet.of("SELECT", "WITH", "SHOW", "FROM", "GRAPH");
static final Set<String> SELECT_STATEMENTS_ALLOWING_PRECEDING_BRACKETS =
ImmutableSet.of("SELECT", "FROM");
static final Set<String> dmlStatements = ImmutableSet.of("INSERT", "UPDATE", "DELETE");
private final Set<ClientSideStatementImpl> statements;

/** The default maximum size of the statement cache in Mb. */
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* Copyright 2025 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.connection;

import com.google.cloud.spanner.ErrorCode;
import com.google.cloud.spanner.SpannerExceptionFactory;
import com.google.cloud.spanner.connection.AbstractStatementParser.ParsedStatement;
import com.google.cloud.spanner.connection.ClientSideStatementImpl.CompileException;
import com.google.cloud.spanner.connection.ClientSideStatementValueConverters.IsolationLevelConverter;
import com.google.spanner.v1.TransactionOptions.IsolationLevel;
import java.lang.reflect.Method;
import java.util.regex.Matcher;

/** Executor for BEGIN TRANSACTION [ISOLATION LEVEL SERIALIZABLE|REPEATABLE READ] statements. */
class ClientSideStatementBeginExecutor implements ClientSideStatementExecutor {
private final ClientSideStatementImpl statement;
private final Method method;
private final IsolationLevelConverter converter;

ClientSideStatementBeginExecutor(ClientSideStatementImpl statement) throws CompileException {
try {
this.statement = statement;
this.converter = new IsolationLevelConverter();
this.method =
ConnectionStatementExecutor.class.getDeclaredMethod(
statement.getMethodName(), converter.getParameterClass());
} catch (Exception e) {
throw new CompileException(e, statement);
}
}

@Override
public StatementResult execute(ConnectionStatementExecutor connection, ParsedStatement statement)
throws Exception {
return (StatementResult)
method.invoke(connection, getParameterValue(statement.getSqlWithoutComments()));
}

IsolationLevel getParameterValue(String sql) {
Matcher matcher = statement.getPattern().matcher(sql);
// Match the 'isolation level (serializable|repeatable read)' part.
// Group 1 is the isolation level.
if (matcher.find() && matcher.groupCount() >= 1) {
String value = matcher.group(1);
if (value != null) {
// Convert the text to an isolation level enum.
// This returns null if the string is not a valid isolation level value.
IsolationLevel res = converter.convert(value.trim());
if (res != null) {
return res;
}
throw SpannerExceptionFactory.newSpannerException(
ErrorCode.INVALID_ARGUMENT, String.format("Unknown isolation level: %s", value));
}
}
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.spanner.v1.DirectedReadOptions;
import com.google.spanner.v1.TransactionOptions;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.time.Duration;
Expand Down Expand Up @@ -382,6 +383,38 @@ public DirectedReadOptions convert(String value) {
}
}

/**
* Converter for converting strings to {@link
* com.google.spanner.v1.TransactionOptions.IsolationLevel} values.
*/
static class IsolationLevelConverter
implements ClientSideStatementValueConverter<TransactionOptions.IsolationLevel> {
static final IsolationLevelConverter INSTANCE = new IsolationLevelConverter();

private final CaseInsensitiveEnumMap<TransactionOptions.IsolationLevel> values =
new CaseInsensitiveEnumMap<>(TransactionOptions.IsolationLevel.class);

IsolationLevelConverter() {}

/** Constructor needed for reflection. */
public IsolationLevelConverter(String allowedValues) {}

@Override
public Class<TransactionOptions.IsolationLevel> getParameterClass() {
return TransactionOptions.IsolationLevel.class;
}

@Override
public TransactionOptions.IsolationLevel convert(String value) {
if (value != null) {
// This ensures that 'repeatable read' is translated to 'repeatable_read'. The text between
// 'repeatable' and 'read' can be any number of valid whitespace characters.
value = value.trim().replaceFirst("\\s+", "_");
}
return values.get(value);
}
}

/** Converter for converting strings to {@link AutocommitDmlMode} values. */
static class AutocommitDmlModeConverter
implements ClientSideStatementValueConverter<AutocommitDmlMode> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import com.google.spanner.v1.DirectedReadOptions;
import com.google.spanner.v1.ExecuteBatchDmlRequest;
import com.google.spanner.v1.ResultSetStats;
import com.google.spanner.v1.TransactionOptions.IsolationLevel;
import java.time.Duration;
import java.util.Iterator;
import java.util.Set;
Expand Down Expand Up @@ -219,6 +220,12 @@ public interface Connection extends AutoCloseable {
/** @return <code>true</code> if this connection is in read-only mode */
boolean isReadOnly();

/** Sets the default isolation level for read/write transactions for this connection. */
void setDefaultIsolationLevel(IsolationLevel isolationLevel);

/** Returns the default isolation level for read/write transactions for this connection. */
IsolationLevel getDefaultIsolationLevel();

/**
* Sets the duration the connection should wait before automatically aborting the execution of a
* statement. The default is no timeout. Statement timeouts are applied all types of statements,
Expand Down Expand Up @@ -289,7 +296,8 @@ public interface Connection extends AutoCloseable {
void cancel();

/**
* Begins a new transaction for this connection.
* Begins a new transaction for this connection. The transaction will use the default isolation
* level of this connection.
*
* <ul>
* <li>Calling this method on a connection that has no transaction and that is
Expand All @@ -306,9 +314,16 @@ public interface Connection extends AutoCloseable {
*/
void beginTransaction();

/**
* Same as {@link #beginTransaction()}, but this transaction will use the given isolation level,
* instead of the default isolation level of this connection.
*/
void beginTransaction(IsolationLevel isolationLevel);

/**
* Begins a new transaction for this connection. This method is guaranteed to be non-blocking. The
* returned {@link ApiFuture} will be done when the transaction has been initialized.
* returned {@link ApiFuture} will be done when the transaction has been initialized. The
* transaction will use the default isolation level of this connection.
*
* <ul>
* <li>Calling this method on a connection that has no transaction and that is
Expand All @@ -325,6 +340,12 @@ public interface Connection extends AutoCloseable {
*/
ApiFuture<Void> beginTransactionAsync();

/**
* Same as {@link #beginTransactionAsync()}, but this transaction will use the given isolation
* level, instead of the default isolation level of this connection.
*/
ApiFuture<Void> beginTransactionAsync(IsolationLevel isolationLevel);

/**
* Sets the transaction mode to use for current transaction. This method may only be called when
* in a transaction, and before the transaction is actually started, i.e. before any statements
Expand Down
Loading