@@ -242,6 +242,10 @@ boolean isBatchingPossible() {
242242
243243 abstract boolean isUpdate ();
244244
245+ boolean isClientSide () {
246+ return false ;
247+ }
248+
245249 void execute () {
246250 Span span = createSpan (toString (), statement );
247251 try (Scope ignore = span .makeCurrent ()) {
@@ -272,6 +276,10 @@ private final class Execute extends BufferedStatement<StatementResult> {
272276 private final String command ;
273277 private final Function <Statement , Statement > statementBinder ;
274278 private final boolean analyze ;
279+ private final LocalStatement localStatement ;
280+ private final SessionStatement sessionStatement ;
281+ private final Exception parseException ;
282+ private final boolean clientSide ;
275283
276284 Execute (
277285 String command ,
@@ -291,6 +299,29 @@ private final class Execute extends BufferedStatement<StatementResult> {
291299 this .command = command ;
292300 this .statementBinder = statementBinder ;
293301 this .analyze = analyze ;
302+ LocalStatement localStatement = getLocalStatement (this .statement );
303+ if (localStatement != null && !localStatement .hasReplacementStatement ()) {
304+ this .localStatement = localStatement ;
305+ this .sessionStatement = null ;
306+ this .parseException = null ;
307+ this .clientSide = true ;
308+ } else {
309+ this .localStatement = null ;
310+ SessionStatement sessionStatement = null ;
311+ Exception parseException = null ;
312+ try {
313+ sessionStatement = getSessionManagementStatement (this .statement , this .parsedStatement );
314+ } catch (Exception exception ) {
315+ parseException = exception ;
316+ }
317+ this .sessionStatement = sessionStatement ;
318+ this .parseException = parseException ;
319+ this .clientSide =
320+ !isTransactionStatement (this .parsedStatement )
321+ && (this .parsedStatement .getType () == StatementType .CLIENT_SIDE
322+ || this .sessionStatement != null
323+ || this .parseException != null );
324+ }
294325 }
295326
296327 @ Override
@@ -308,23 +339,28 @@ boolean isUpdate() {
308339 return this .parsedStatement .isUpdate ();
309340 }
310341
342+ @ Override
343+ boolean isClientSide () {
344+ return this .clientSide ;
345+ }
346+
311347 @ Override
312348 void doExecute () {
313349 Statement updatedStatement = statement ;
314350 try {
315351 checkConnectionState ();
352+ if (this .parseException != null ) {
353+ throw SpannerExceptionFactory .asSpannerException (this .parseException );
354+ }
316355 // TODO(b/235719478): If the statement is a BEGIN statement and there is a COMMIT statement
317356 // at a later point in the batch, and all the statements in the transaction block are
318357 // SELECT statements, then we should create a read-only transaction. Also, if a transaction
319358 // block always ends with a ROLLBACK, PGAdapter should skip the entire execution of that
320359 // block.
321- SessionStatement sessionStatement =
322- getSessionManagementStatement (updatedStatement , parsedStatement );
323- LocalStatement localStatement = getLocalStatement (statement );
324- if (localStatement != null && !localStatement .hasReplacementStatement ()) {
325- result .set (localStatement .execute (BackendConnection .this , statement ));
326- } else if (sessionStatement != null ) {
327- result .set (sessionStatement .execute (sessionState , spannerConnection ));
360+ if (this .localStatement != null ) {
361+ result .set (this .localStatement .execute (BackendConnection .this , statement ));
362+ } else if (this .sessionStatement != null ) {
363+ result .set (this .sessionStatement .execute (sessionState , spannerConnection ));
328364 } else if (connectionState == ConnectionState .ABORTED
329365 && !spannerConnection .isInTransaction ()
330366 && (isRollback (parsedStatement ) || isCommit (parsedStatement ))) {
@@ -539,20 +575,6 @@ boolean isUnsupportedConcurrencyModeException(SpannerException spannerException)
539575 .startsWith (
540576 "INVALID_ARGUMENT: io.grpc.StatusRuntimeException: INVALID_ARGUMENT: Unsupported concurrency mode in query using INFORMATION_SCHEMA." );
541577 }
542-
543- @ Nullable
544- SessionStatement getSessionManagementStatement (
545- Statement statement , ParsedStatement parsedStatement ) {
546- if (parsedStatement .getType () == StatementType .UNKNOWN
547- || (parsedStatement .getType () == StatementType .QUERY
548- && isCommand (SHOW , statement .getSql ()))
549- || (parsedStatement .getType () == StatementType .CLIENT_SIDE
550- && parsedStatement .getClientSideStatementType ()
551- == ClientSideStatementType .RESET_ALL )) {
552- return SessionStatementParser .parse (parsedStatement , statement .getSql ());
553- }
554- return null ;
555- }
556578 }
557579
558580 public static final int MAX_PARTITIONS =
@@ -1299,9 +1321,10 @@ private void maybeBeginImplicitTransaction(int index, boolean isSync) {
12991321 return ;
13001322 }
13011323
1302- // Only start an implicit transaction if we have more than one statement left. Otherwise, just
1303- // let the Spanner connection execute the statement in auto-commit mode.
1304- if (isSync && index == bufferedStatements .size () - 1 ) {
1324+ // Only start an implicit transaction if we have more than one statement left that can be
1325+ // executed in a transaction. Otherwise, just let the Spanner connection execute the statement
1326+ // in auto-commit mode.
1327+ if (isSync && hasOnlyClientSideStatementsAfter (index )) {
13051328 return ;
13061329 }
13071330 // Don't start an implicit transaction if this is already a transaction statement.
@@ -1310,7 +1333,7 @@ private void maybeBeginImplicitTransaction(int index, boolean isSync) {
13101333 }
13111334 // No need to start a transaction for DDL or client side statements.
13121335 if (bufferedStatements .get (index ).parsedStatement .getType () == StatementType .DDL
1313- || bufferedStatements .get (index ).parsedStatement . getType () == StatementType . CLIENT_SIDE ) {
1336+ || bufferedStatements .get (index ).isClientSide () ) {
13141337 return ;
13151338 }
13161339 // If there are only DML statements left, those can be executed as an auto-commit dml batch.
@@ -1322,18 +1345,20 @@ private void maybeBeginImplicitTransaction(int index, boolean isSync) {
13221345 return ;
13231346 }
13241347 // Do not start an implicit transaction if all that is in the buffer is a DESCRIBE and an
1325- // EXECUTE message for the same statement.
1348+ // EXECUTE message for the same statement (optionally surrounded only by client-side
1349+ // statements).
13261350 if (isSync
1327- && bufferedStatements .size () == 2
1328- && bufferedStatements .get (0 ) instanceof Execute
1329- && bufferedStatements .get (1 ) instanceof Execute
1330- && ((Execute ) bufferedStatements .get (0 )).analyze
1331- && !((Execute ) bufferedStatements .get (1 )).analyze
1351+ && bufferedStatements .size () > index + 1
1352+ && bufferedStatements .get (index ) instanceof Execute
1353+ && bufferedStatements .get (index + 1 ) instanceof Execute
1354+ && ((Execute ) bufferedStatements .get (index )).analyze
1355+ && !((Execute ) bufferedStatements .get (index + 1 )).analyze
13321356 && bufferedStatements
1333- .get (0 )
1357+ .get (index )
13341358 .statement
13351359 .getSql ()
1336- .equals (bufferedStatements .get (1 ).statement .getSql ())) {
1360+ .equals (bufferedStatements .get (index + 1 ).statement .getSql ())
1361+ && hasOnlyClientSideStatementsAfter (index + 1 )) {
13371362 return ;
13381363 }
13391364
@@ -1505,9 +1530,36 @@ private boolean isTransactionStatement(int index) {
15051530 return isBegin (index ) || isCommit (index ) || isRollback (index );
15061531 }
15071532
1508- private boolean hasOnlyDmlStatementsAfter (int index ) {
1509- return bufferedStatements .subList (index , bufferedStatements .size ()).stream ()
1510- .allMatch (statement -> statement .parsedStatement .getType () == StatementType .UPDATE );
1533+ @ VisibleForTesting
1534+ boolean hasOnlyDmlStatementsAfter (int index ) {
1535+ boolean seenExecute = false ;
1536+ boolean seenClientSide = false ;
1537+ for (int i = index ; i < bufferedStatements .size (); i ++) {
1538+ BufferedStatement <?> statement = bufferedStatements .get (i );
1539+ if (statement .isClientSide ()) {
1540+ seenClientSide = true ;
1541+ } else if (statement .isUpdate () && !statement .parsedStatement .hasReturningClause ()) {
1542+ if (seenClientSide ) {
1543+ // A DML statement appeared after a client-side statement.
1544+ // These cannot be batched together in a single auto-commit DML batch.
1545+ return false ;
1546+ }
1547+ boolean isAnalyze = statement instanceof Execute && ((Execute ) statement ).analyze ;
1548+ if (isAnalyze ) {
1549+ if (seenExecute || i > index ) {
1550+ // An analyze/describe statement appeared after an execute statement,
1551+ // or there are multiple analyze statements. These cannot be executed
1552+ // in a single auto-commit DML batch.
1553+ return false ;
1554+ }
1555+ } else {
1556+ seenExecute = true ;
1557+ }
1558+ } else {
1559+ return false ;
1560+ }
1561+ }
1562+ return true ;
15111563 }
15121564
15131565 @ VisibleForTesting
@@ -1516,7 +1568,43 @@ boolean hasUpdateStatementsAfter(int index) {
15161568 .anyMatch (BufferedStatement ::isUpdate );
15171569 }
15181570
1519- private int getStatementCount () {
1571+ @ Nullable
1572+ SessionStatement getSessionManagementStatement (
1573+ Statement statement , ParsedStatement parsedStatement ) {
1574+ if (parsedStatement .getType () == StatementType .UNKNOWN
1575+ || (parsedStatement .getType () == StatementType .QUERY && isCommand (SHOW , statement .getSql ()))
1576+ || (parsedStatement .getType () == StatementType .CLIENT_SIDE
1577+ && parsedStatement .getClientSideStatementType () == ClientSideStatementType .RESET_ALL )) {
1578+ return SessionStatementParser .parse (parsedStatement , statement .getSql ());
1579+ }
1580+ return null ;
1581+ }
1582+
1583+ @ VisibleForTesting
1584+ boolean hasOnlyClientSideStatementsAfter (int index ) {
1585+ if (index >= bufferedStatements .size () - 1 ) {
1586+ return true ;
1587+ }
1588+ for (int i = index + 1 ; i < bufferedStatements .size (); i ++) {
1589+ if (!bufferedStatements .get (i ).isClientSide ()) {
1590+ return false ;
1591+ }
1592+ }
1593+ return true ;
1594+ }
1595+
1596+ @ VisibleForTesting
1597+ boolean isClientSide (BufferedStatement <?> statement ) {
1598+ return statement .isClientSide ();
1599+ }
1600+
1601+ @ VisibleForTesting
1602+ BufferedStatement <?> getBufferedStatement (int index ) {
1603+ return bufferedStatements .get (index );
1604+ }
1605+
1606+ @ VisibleForTesting
1607+ int getStatementCount () {
15201608 return bufferedStatements .size ();
15211609 }
15221610
0 commit comments