Skip to content

fix: use database default isolation level when none is specified - #5041

Closed
rayudu3745 wants to merge 1 commit into
GoogleCloudPlatform:postgresql-dialectfrom
rayudu3745:fix-default-isolation-level
Closed

rayudu3745 wants to merge 1 commit into
GoogleCloudPlatform:postgresql-dialectfrom
rayudu3745:fix-default-isolation-level

Conversation

@rayudu3745

Copy link
Copy Markdown
Collaborator

Previously, ConnectionHandler explicitly set the connection's default isolation level to IsolationLevel.SERIALIZABLE whenever the Spanner connection had ISOLATION_LEVEL_UNSPECIFIED. This was originally added so that SHOW TRANSACTION ISOLATION LEVEL and SHOW DEFAULT_TRANSACTION_ISOLATION (when executed directly on the Spanner Connection) would not return ISOLATION_LEVEL_UNSPECIFIED to PostgreSQL drivers such as pgjdbc.

However, forcing IsolationLevel.SERIALIZABLE on the Spanner Connection caused every transaction to send SERIALIZABLE in TransactionOptions instead of ISOLATION_LEVEL_UNSPECIFIED, overriding any database-level default isolation level configured on Cloud Spanner (e.g., ALTER DATABASE db SET default_transaction_isolation = 'repeatable read'). In addition, isolation level changes and queries were split between the Spanner Connection API and PGAdapter's SessionState, causing inconsistencies across SHOW, SET, RESET, and pg_settings.

This change:

  • Removes the IsolationLevel.SERIALIZABLE override in ConnectionHandler.connectToSpanner() so connections without an explicit isolation level remain at ISOLATION_LEVEL_UNSPECIFIED and use the database's default isolation level on Cloud Spanner.
  • Tracks the startup (initialIsolationLevel), session (defaultIsolationLevel), and transactional (transactionDefaultIsolationLevel) isolation levels in SessionState, reporting serializable in session settings when the Spanner isolation level is ISOLATION_LEVEL_UNSPECIFIED while preserving ISOLATION_LEVEL_UNSPECIFIED on the underlying Spanner connection.
  • Routes SHOW TRANSACTION ISOLATION LEVEL, SHOW DEFAULT_TRANSACTION_ISOLATION, SET [SESSION | LOCAL] default_transaction_isolation, SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL ..., RESET default_transaction_isolation, and RESET ALL through SessionStatementParser so SessionState and the Spanner Connection stay synchronized and return standard PostgreSQL isolation level names (serializable, repeatable read).
  • Synchronizes per-transaction isolation level changes (BEGIN ISOLATION LEVEL ... and SET TRANSACTION ISOLATION LEVEL ...) into SessionState's local transaction_isolation setting, and defers applying default_transaction_isolation changes to the Spanner Connection until active transactions complete.
  • Strips array-element quotes when loading enum values in PGSetting so multi-word enum values in pg_settings.txt (such as repeatable read) pass validation.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request implements support for setting, resetting, and showing the default_transaction_isolation session setting in PGAdapter, aligning it with PostgreSQL behavior. It introduces tracking of initial, default, and transaction-specific isolation levels in SessionState, and synchronizes these levels with the underlying Spanner connection during transaction boundaries and client-side statements. Additionally, the SessionStatementParser is updated to handle SET and RESET commands for default_transaction_isolation. The feedback suggests refactoring getTransactionIsolationLevelFromConnection in BackendConnection.java to store the result of rs.getString(0) in a local variable to avoid redundant calls and improve readability.

@rayudu3745
rayudu3745 force-pushed the fix-default-isolation-level branch from 9b09b63 to 8464953 Compare October 26, 2026 09:44
@rayudu3745

Copy link
Copy Markdown
Collaborator Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request implements proper tracking and synchronization of transaction isolation levels (such as default_transaction_isolation and transaction_isolation) between PGAdapter's session state and the underlying Spanner connection. It removes the previous hardcoded serializable override, adds parser support for isolation level settings, and updates tests to reflect these changes. The review feedback suggests avoiding a potential NullPointerException by checking !rs.isNull(0) before calling rs.getString(0) in BackendConnection, and recommends enhancing PGSetting.unquote to handle both single and double quotes to simplify the unquoting logic in SessionState.

@rayudu3745
rayudu3745 force-pushed the fix-default-isolation-level branch from 8464953 to 29e24b0 Compare October 26, 2026 10:26
@rayudu3745

Copy link
Copy Markdown
Collaborator Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request implements comprehensive support for managing and tracking transaction isolation levels (such as default_transaction_isolation and transaction_isolation) within PGAdapter's session state, aligning it with PostgreSQL behavior. Instead of unconditionally overriding unspecified isolation levels to SERIALIZABLE at startup, PGAdapter now dynamically tracks the initial, default, and active transaction-level isolation levels. It introduces parsing support for setting and showing these isolation levels (including SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL), synchronizes session state with the underlying Cloud Spanner connection, and updates various mock server and client library tests to reflect these changes. There are no review comments to address, and the changes look solid.

@rayudu3745
rayudu3745 marked this pull request as draft October 26, 2026 11:38
@rayudu3745
rayudu3745 force-pushed the fix-default-isolation-level branch from 29e24b0 to 8cf809e Compare October 27, 2026 08:13
@rayudu3745

Copy link
Copy Markdown
Collaborator Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request refactors and enhances the handling of transaction isolation levels in PGAdapter, aligning it closer to PostgreSQL behavior. It introduces tracking for database default, initial, default, and active transaction isolation levels within SessionState, and synchronizes these states with client-side statements in BackendConnection. Additionally, it updates SessionStatementParser to support SET and RESET operations for default_transaction_isolation and transaction_isolation, and adds comprehensive tests. The review feedback suggests simplifying the manual unquoting logic in BackendConnection by reusing SessionState.toSpannerIsolationLevel, and refactoring the parsing of SET CHARACTERISTICS AS TRANSACTION in SessionStatementParser to support optional SESSION keywords for better compatibility.

Comment on lines +1272 to +1279
String unquoted = value != null ? value.trim() : null;
if (unquoted != null
&& unquoted.length() >= 2
&& unquoted.startsWith("'")
&& unquoted.endsWith("'")) {
unquoted = unquoted.substring(1, unquoted.length() - 1);
}
IsolationLevel level = SessionState.toSpannerIsolationLevel(unquoted);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The manual unquoting and trimming logic here is redundant because SessionState.toSpannerIsolationLevel(value) already internally calls PGSetting.unquote(value.trim()) and handles both single and double quotes, as well as null values. We can simplify this block to a single line.

Suggested change
String unquoted = value != null ? value.trim() : null;
if (unquoted != null
&& unquoted.length() >= 2
&& unquoted.startsWith("'")
&& unquoted.endsWith("'")) {
unquoted = unquoted.substring(1, unquoted.length() - 1);
}
IsolationLevel level = SessionState.toSpannerIsolationLevel(unquoted);
IsolationLevel level = SessionState.toSpannerIsolationLevel(value);

Comment on lines +354 to +363
boolean ateSession = false;
if (parser.eatKeyword("local")) {
builder.setLocal();
} else {
// Ignore, this is the default.
parser.eatKeyword("session");
ateSession = parser.eatKeyword("session");
}
if (!builder.local
&& ateSession
&& eatKeywordsAtomically(
parser, "characteristics", "as", "transaction", "isolation", "level")) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

We can simplify the parsing of SET CHARACTERISTICS AS TRANSACTION by removing the ateSession variable and checking for the keywords atomically. This also has the benefit of supporting both SET SESSION CHARACTERISTICS... and SET CHARACTERISTICS... (where SESSION is optional in PostgreSQL), improving compatibility.

if (parser.eatKeyword("local")) {
  builder.setLocal();
} else {
  parser.eatKeyword("session");
}
if (!builder.local
    && eatKeywordsAtomically(
        parser, "characteristics", "as", "transaction", "isolation", "level")) {

@rayudu3745 rayudu3745 closed this Oct 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant