Repository navigation
Conversation
There was a problem hiding this comment.
Code Review
This pull request optimizes transaction handling in PGAdapter by avoiding implicit transactions when only client-side statements (such as SET or SHOW) remain in the buffer. It introduces helper methods to detect client-side statements and adds comprehensive tests. The reviewer identified a performance concern where repeated SQL parsing in isClientSide could lead to O(N^2) complexity, suggesting caching this status on BufferedStatement instead.
…ide statements Previously, when a query was sent in the same pipeline (before a Sync message) along with client-side or session statements (such as SHOW spanner.read_timestamp or SET timezone = 'UTC'), PGAdapter treated the batch as multiple statements and began an implicit read-only transaction. This caused two issues: 1. An unnecessary BeginTransaction RPC was executed. 2. Queries failed when spanner.read_only_staleness was set to bounded staleness (e.g., MAX_STALENESS or MIN_READ_TIMESTAMP), because Spanner only allows bounded staleness on single-use reads outside of a multi-statement transaction. This change: - Updates BackendConnection.maybeBeginImplicitTransaction() to check if all subsequent statements in the pipeline are client-side or session-management statements. If so, it keeps auto-commit mode and executes the query as a single-use read. - Unifies the detection of client-side statements with getSessionManagementStatement() to recognize SET, SHOW, and RESET ALL commands. - Generalizes the Describe + Execute statement check to be index-relative, preventing pipelines that start with a client-side statement from triggering an unintended transaction.
6eae456 to
92e8158
Compare
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request refactors how client-side and session-management statements are identified and handled in BackendConnection. It introduces a pre-computed clientSide flag and caches localStatement and sessionStatement on Execute statements. This optimization prevents PGAdapter from starting unnecessary implicit transactions when queries are followed or surrounded only by client-side statements (such as SET or SHOW commands). Extensive unit and mock server tests have been added to verify these transaction-boundary behaviors. There are no review comments to address, and the changes look solid.
| this.clientSide = true; | ||
| } else { | ||
| this.localStatement = null; | ||
| this.sessionStatement = getSessionManagementStatement(this.statement, this.parsedStatement); |
There was a problem hiding this comment.
Calling getSessionManagementStatement(this.statement, this.parsedStatement) in the Execute constructor means SessionStatementParser.parse(...) now runs when the statement is buffered (backendConnection.execute(...) / backendConnection.analyze(...)) rather than inside doExecute() during flush() / sync().
If a SET, SHOW, or RESET statement has invalid syntax (e.g., SET foo, SHOW, SHOW foo bar, RESET foo bar), SessionStatementParser.parse(...) throws SpannerException(ErrorCode.INVALID_ARGUMENT, ...). Throwing during buffer() breaks error handling in two ways:
- Simple Query Mode (
SimpleQueryStatement.execute()):new ExecuteMessage(...).send()callsthis.buffer(backendConnection)beforehandler.buffer(this). Whennew Execute(...)throws, the message is never added tobufferedStatementsorhandler.messages, andSimpleQueryStatement.execute()catches(Exception ignore)and sendsSyncMessage, resulting inReadyForQuerywith noErrorResponse(the invalid statement silently succeeds). - Extended Query Mode: Throwing during
ExecuteMessage.send()/DescribeMessage.send()sends an immediate out-of-bandErrorResponse+ReadyForQuerybefore buffered messages are flushed and beforeSyncis consumed.
Could we catch SpannerException when pre-parsing sessionStatement in the constructor (or defer throwing the parse error until doExecute()) so that invalid session statements still fail the Future<StatementResult> during doExecute()?
| } | ||
|
|
||
| @Test | ||
| public void testDmlAndClientSideStatement_doesNotStartTransaction() { |
There was a problem hiding this comment.
What happens in this scenario if two INSERT statements are pipelined before show spanner.commit_timestamp (e.g., INSERT 1; INSERT 2; SHOW spanner.commit_timestamp)?
With a single INSERT, hasOnlyClientSideStatementsAfter(0) is true, so no implicit transaction is started and INSERT runs in auto-commit.
However, with two INSERT statements followed by SHOW spanner.commit_timestamp:
hasOnlyClientSideStatementsAfter(0)isfalse(index 1 is the secondINSERT).hasOnlyDmlStatementsAfter(0)inmaybeBeginImplicitTransaction(BackendConnection.java:1325) is alsofalsebecause index 2 isSHOW, notStatementType.UPDATE.
As a result, maybeBeginImplicitTransaction starts an implicit transaction (beginTransaction()), executes both INSERTs in a batch inside that uncommitted transaction, and then executes show spanner.commit_timestamp before endImplicitTransaction() commits the transaction at the end of flush().
a772b0b to
5f032fb
Compare
Previously, when a query was sent in the same pipeline (before a Sync message) along with client-side or session statements (such as SHOW spanner.read_timestamp or SET timezone = 'UTC'), PGAdapter treated the batch as multiple statements and began an implicit read-only transaction.
This caused two issues:
This change: