@@ -1294,6 +1294,204 @@ public void testMutationOnlyUsingAsyncTransactionManager() {
12941294 request .getPrecommitToken ().getPrecommitToken ());
12951295 }
12961296
1297+  private  Spanner  setupSpannerForAbortedBeginTransactionTests () {
1298+  // Force the BeginTransaction RPC to return Aborted the first time it is called. The exception 
1299+  // is cleared after the first call, so the retry should succeed. 
1300+  mockSpanner .setBeginTransactionExecutionTime (
1301+  SimulatedExecutionTime .ofException (
1302+  mockSpanner .createAbortedException (ByteString .copyFromUtf8 ("test" ))));
1303+ 
1304+  return  SpannerOptions .newBuilder ()
1305+  .setProjectId ("test-project" )
1306+  .setChannelProvider (channelProvider )
1307+  .setCredentials (NoCredentials .getInstance ())
1308+  .setSessionPoolOption (
1309+  SessionPoolOptions .newBuilder ()
1310+  .setUseMultiplexedSession (true )
1311+  .setUseMultiplexedSessionForRW (true )
1312+  .setSkipVerifyingBeginTransactionForMuxRW (true )
1313+  .build ())
1314+  .build ()
1315+  .getService ();
1316+  }
1317+ 
1318+  private  void  verifyMutationKeySetInBeginTransactionRequests (
1319+  List <BeginTransactionRequest > beginTransactionRequests ) {
1320+  assertEquals (2 , beginTransactionRequests .size ());
1321+  // Verify the requests are executed using multiplexed sessions 
1322+  for  (BeginTransactionRequest  request  : beginTransactionRequests ) {
1323+  assertTrue (mockSpanner .getSession (request .getSession ()).getMultiplexed ());
1324+  assertTrue (request .hasMutationKey ());
1325+  assertTrue (request .getMutationKey ().hasInsert ());
1326+  }
1327+  }
1328+ 
1329+  private  void  verifyPreCommitTokenSetInCommitRequest (List <CommitRequest > commitRequests ) {
1330+  assertEquals (1L , commitRequests .size ());
1331+  for  (CommitRequest  request  : commitRequests ) {
1332+  assertTrue (mockSpanner .getSession (request .getSession ()).getMultiplexed ());
1333+  assertNotNull (request .getPrecommitToken ());
1334+  assertEquals (
1335+  ByteString .copyFromUtf8 ("TransactionPrecommitToken" ),
1336+  request .getPrecommitToken ().getPrecommitToken ());
1337+  }
1338+  }
1339+ 
1340+  // The following 4 tests validate mutation-only cases where the BeginTransaction RPC fails with an 
1341+  // ABORTED or retryable error 
1342+  @ Test 
1343+  public  void  testMutationOnlyCaseAbortedDuringBeginTransaction () {
1344+  // This test ensures that when a transaction containing only mutations is retried after an 
1345+  // ABORT error in the BeginTransaction RPC: 
1346+  // 1. The mutation key is correctly included in the BeginTransaction request. 
1347+  // 2. The precommit token is properly set in the Commit request. 
1348+  Spanner  spanner  = setupSpannerForAbortedBeginTransactionTests ();
1349+  DatabaseClientImpl  client  =
1350+  (DatabaseClientImpl ) spanner .getDatabaseClient (DatabaseId .of ("p" , "i" , "d" ));
1351+ 
1352+  client 
1353+  .readWriteTransaction ()
1354+  .run (
1355+  transaction  -> {
1356+  Mutation  mutation  =
1357+  Mutation .newInsertBuilder ("FOO" ).set ("ID" ).to (1L ).set ("NAME" ).to ("Bar" ).build ();
1358+  transaction .buffer (mutation );
1359+  return  null ;
1360+  });
1361+ 
1362+  // Verify that for mutation only case, a mutation key is set in BeginTransactionRequest. 
1363+  List <BeginTransactionRequest > beginTransactionRequests  =
1364+  mockSpanner .getRequestsOfType (BeginTransactionRequest .class );
1365+  verifyMutationKeySetInBeginTransactionRequests (beginTransactionRequests );
1366+ 
1367+  // Verify that the latest precommit token is set in the CommitRequest 
1368+  List <CommitRequest > commitRequests  = mockSpanner .getRequestsOfType (CommitRequest .class );
1369+  verifyPreCommitTokenSetInCommitRequest (commitRequests );
1370+ 
1371+  spanner .close ();
1372+  }
1373+ 
1374+  @ Test 
1375+  public  void  testMutationOnlyUsingTransactionManagerAbortedDuringBeginTransaction () {
1376+  // This test ensures that when a transaction containing only mutations is retried after an 
1377+  // ABORT error in the BeginTransaction RPC: 
1378+  // 1. The mutation key is correctly included in the BeginTransaction request. 
1379+  // 2. The precommit token is properly set in the Commit request. 
1380+  Spanner  spanner  = setupSpannerForAbortedBeginTransactionTests ();
1381+  DatabaseClientImpl  client  =
1382+  (DatabaseClientImpl ) spanner .getDatabaseClient (DatabaseId .of ("p" , "i" , "d" ));
1383+ 
1384+  try  (TransactionManager  manager  = client .transactionManager ()) {
1385+  TransactionContext  transaction  = manager .begin ();
1386+  while  (true ) {
1387+  try  {
1388+  Mutation  mutation  =
1389+  Mutation .newInsertBuilder ("FOO" ).set ("ID" ).to (1L ).set ("NAME" ).to ("Bar" ).build ();
1390+  transaction .buffer (mutation );
1391+  manager .commit ();
1392+  assertNotNull (manager .getCommitTimestamp ());
1393+  break ;
1394+  } catch  (AbortedException  e ) {
1395+  transaction  = manager .resetForRetry ();
1396+  }
1397+  }
1398+  }
1399+ 
1400+  // Verify that for mutation only case, a mutation key is set in BeginTransactionRequest. 
1401+  List <BeginTransactionRequest > beginTransactionRequests  =
1402+  mockSpanner .getRequestsOfType (BeginTransactionRequest .class );
1403+  verifyMutationKeySetInBeginTransactionRequests (beginTransactionRequests );
1404+ 
1405+  // Verify that the latest precommit token is set in the CommitRequest 
1406+  List <CommitRequest > commitRequests  = mockSpanner .getRequestsOfType (CommitRequest .class );
1407+  verifyPreCommitTokenSetInCommitRequest (commitRequests );
1408+ 
1409+  spanner .close ();
1410+  }
1411+ 
1412+  @ Test 
1413+  public  void  testMutationOnlyUsingAsyncRunnerAbortedDuringBeginTransaction () {
1414+  // This test ensures that when a transaction containing only mutations is retried after an 
1415+  // ABORT error in the BeginTransaction RPC: 
1416+  // 1. The mutation key is correctly included in the BeginTransaction request. 
1417+  // 2. The precommit token is properly set in the Commit request. 
1418+ 
1419+  Spanner  spanner  = setupSpannerForAbortedBeginTransactionTests ();
1420+  DatabaseClientImpl  client  =
1421+  (DatabaseClientImpl ) spanner .getDatabaseClient (DatabaseId .of ("p" , "i" , "d" ));
1422+ 
1423+  AsyncRunner  runner  = client .runAsync ();
1424+  get (
1425+  runner .runAsync (
1426+  txn  -> {
1427+  txn .buffer (
1428+  Mutation .newInsertBuilder ("FOO" ).set ("ID" ).to (1L ).set ("NAME" ).to ("Bar" ).build ());
1429+  return  ApiFutures .immediateFuture (null );
1430+  },
1431+  MoreExecutors .directExecutor ()));
1432+ 
1433+  // Verify that for mutation only case, a mutation key is set in BeginTransactionRequest. 
1434+  List <BeginTransactionRequest > beginTransactionRequests  =
1435+  mockSpanner .getRequestsOfType (BeginTransactionRequest .class );
1436+  verifyMutationKeySetInBeginTransactionRequests (beginTransactionRequests );
1437+ 
1438+  // Verify that the latest precommit token is set in the CommitRequest 
1439+  List <CommitRequest > commitRequests  = mockSpanner .getRequestsOfType (CommitRequest .class );
1440+  verifyPreCommitTokenSetInCommitRequest (commitRequests );
1441+ 
1442+  spanner .close ();
1443+  }
1444+ 
1445+  @ Test 
1446+  public  void  testMutationOnlyUsingTransactionManagerAsyncAbortedDuringBeginTransaction ()
1447+  throws  Exception  {
1448+  // This test verifies that in the case of mutations-only, when a transaction is retried after an 
1449+  // ABORT in BeginTransaction RPC, the mutation key is correctly set in the BeginTransaction 
1450+  // request 
1451+  // and precommit token is set in Commit request. 
1452+  Spanner  spanner  = setupSpannerForAbortedBeginTransactionTests ();
1453+  DatabaseClientImpl  client  =
1454+  (DatabaseClientImpl ) spanner .getDatabaseClient (DatabaseId .of ("p" , "i" , "d" ));
1455+ 
1456+  try  (AsyncTransactionManager  manager  = client .transactionManagerAsync ()) {
1457+  TransactionContextFuture  transaction  = manager .beginAsync ();
1458+  while  (true ) {
1459+  CommitTimestampFuture  commitTimestamp  =
1460+  transaction 
1461+  .then (
1462+  (txn , input ) -> {
1463+  txn .buffer (
1464+  Mutation .newInsertBuilder ("FOO" )
1465+  .set ("ID" )
1466+  .to (1L )
1467+  .set ("NAME" )
1468+  .to ("Bar" )
1469+  .build ());
1470+  return  ApiFutures .immediateFuture (null );
1471+  },
1472+  MoreExecutors .directExecutor ())
1473+  .commitAsync ();
1474+  try  {
1475+  assertThat (commitTimestamp .get ()).isNotNull ();
1476+  break ;
1477+  } catch  (AbortedException  e ) {
1478+  transaction  = manager .resetForRetryAsync ();
1479+  }
1480+  }
1481+  }
1482+ 
1483+  // Verify that for mutation only case, a mutation key is set in BeginTransactionRequest. 
1484+  List <BeginTransactionRequest > beginTransactionRequests  =
1485+  mockSpanner .getRequestsOfType (BeginTransactionRequest .class );
1486+  verifyMutationKeySetInBeginTransactionRequests (beginTransactionRequests );
1487+ 
1488+  // Verify that the latest precommit token is set in the CommitRequest 
1489+  List <CommitRequest > commitRequests  = mockSpanner .getRequestsOfType (CommitRequest .class );
1490+  verifyPreCommitTokenSetInCommitRequest (commitRequests );
1491+ 
1492+  spanner .close ();
1493+  }
1494+ 
12971495 // Tests the behavior of the server-side kill switch for read-write multiplexed sessions.. 
12981496 @ Test 
12991497 public  void  testInitialBeginTransactionWithRW_receivesUnimplemented_fallsBackToRegularSession () {
0 commit comments