@@ -596,6 +596,131 @@ func TestCommitWithMultiplexedSessionRetry(t *testing.T) {
596596 }
597597}
598598
599+ func TestClient_ReadWriteTransaction_PreviousTransactionID (t * testing.T ) {
600+ t .Parallel ()
601+ ctx := context .Background ()
602+ cfg := SessionPoolConfig {
603+ MinOpened : 1 ,
604+ MaxOpened : 1 ,
605+ enableMultiplexSession : true ,
606+ enableMultiplexedSessionForRW : true ,
607+ }
608+ server , client , teardown := setupMockedTestServerWithConfig (t , ClientConfig {
609+ DisableNativeMetrics : true ,
610+ SessionPoolConfig : cfg ,
611+ })
612+ defer teardown ()
613+
614+ // First ExecuteSql will fail with InvalidArgument to force explicit BeginTransaction
615+ invalidSQL := "Update FOO Set BAR=1"
616+ server .TestSpanner .PutStatementResult (
617+ invalidSQL ,
618+ & StatementResult {
619+ Type : StatementResultError ,
620+ Err : status .Error (codes .InvalidArgument , "Invalid update" ),
621+ },
622+ )
623+
624+ // First Commit will fail with Aborted to force transaction retry
625+ server .TestSpanner .PutExecutionTime (MethodCommitTransaction ,
626+ SimulatedExecutionTime {
627+ Errors : []error {status .Error (codes .Aborted , "Transaction aborted" )},
628+ })
629+
630+ var attempts int
631+ expectedAttempts := 4
632+ _ , err := client .ReadWriteTransaction (ctx , func (ctx context.Context , tx * ReadWriteTransaction ) error {
633+ if attempts == 1 || attempts == 3 {
634+ // Replace the aborted result with a real result to prevent the
635+ // transaction from aborting indefinitely.
636+ server .TestSpanner .PutStatementResult (
637+ invalidSQL ,
638+ & StatementResult {
639+ Type : StatementResultUpdateCount ,
640+ UpdateCount : 3 ,
641+ },
642+ )
643+ }
644+ if attempts == 2 {
645+ server .TestSpanner .PutStatementResult (
646+ invalidSQL ,
647+ & StatementResult {
648+ Type : StatementResultError ,
649+ Err : status .Error (codes .InvalidArgument , "Invalid update" ),
650+ },
651+ )
652+ }
653+ attempts ++
654+ // First attempt: Inline begin fails due to invalid update
655+ // Second attempt: Explicit begin succeeds but commit aborts
656+ // Third attempt: Inline begin fails, previousTx set to txn2
657+ // Fourth attempt: Explicit begin succeeds with previousTx=txn2
658+ _ , err := tx .Update (ctx , NewStatement (invalidSQL ))
659+ if err != nil {
660+ return err
661+ }
662+ return nil
663+ })
664+ if err != nil {
665+ t .Fatal (err )
666+ }
667+ if attempts != expectedAttempts {
668+ t .Fatalf ("got %d attempts, want %d" , attempts , expectedAttempts )
669+ }
670+
671+ requests := drainRequestsFromServer (server .TestSpanner )
672+ var txID2 []byte
673+ var foundPrevTxID bool
674+
675+ // Verify the sequence of requests and transaction IDs
676+ for i , req := range requests {
677+ switch r := req .(type ) {
678+ case * sppb.ExecuteSqlRequest :
679+ // First and third attempts use inline begin
680+ if i == 1 || i == 5 {
681+ if _ , ok := r .Transaction .GetSelector ().(* sppb.TransactionSelector_Begin ); ! ok {
682+ t .Errorf ("Request %d: got %T, want Begin" , i , r .Transaction .GetSelector ())
683+ }
684+ }
685+ if txID2 == nil && r .Transaction .GetId () != nil {
686+ txID2 = r .Transaction .GetId ()
687+ }
688+ case * sppb.BeginTransactionRequest :
689+ if i == 7 {
690+ opts := r .Options .GetReadWrite ()
691+ if opts == nil {
692+ t .Fatal ("Request 7: missing ReadWrite options" )
693+ }
694+ if ! testEqual (opts .MultiplexedSessionPreviousTransactionId , txID2 ) {
695+ t .Errorf ("Request 7: got prev txID %v, want %v" ,
696+ opts .MultiplexedSessionPreviousTransactionId , txID2 )
697+ }
698+ foundPrevTxID = true
699+ }
700+ }
701+ }
702+
703+ if ! foundPrevTxID {
704+ t .Error ("Did not find BeginTransaction request with previous transaction ID" )
705+ }
706+
707+ // Verify the complete sequence of requests
708+ wantRequests := []interface {}{
709+ & sppb.BatchCreateSessionsRequest {},
710+ & sppb.ExecuteSqlRequest {}, // Attempt 1: Inline begin fails
711+ & sppb.BeginTransactionRequest {}, // Attempt 2: Explicit begin
712+ & sppb.ExecuteSqlRequest {}, // Attempt 2: Update succeeds
713+ & sppb.CommitRequest {}, // Attempt 2: Commit aborts
714+ & sppb.ExecuteSqlRequest {}, // Attempt 3: Inline begin fails
715+ & sppb.BeginTransactionRequest {}, // Attempt 4: Explicit begin with prev txID
716+ & sppb.ExecuteSqlRequest {}, // Attempt 4: Update succeeds
717+ & sppb.CommitRequest {}, // Attempt 4: Commit succeeds
718+ }
719+ if err := compareRequestsWithConfig (wantRequests , requests , & cfg ); err != nil {
720+ t .Fatal (err )
721+ }
722+ }
723+
599724func TestMutationOnlyCaseAborted (t * testing.T ) {
600725 t .Parallel ()
601726 ctx := context .Background ()
@@ -1229,6 +1354,10 @@ func shouldHaveReceived(server InMemSpannerServer, want []interface{}) ([]interf
12291354
12301355// Compares expected requests (want) with actual requests (got).
12311356func compareRequests (want []interface {}, got []interface {}) error {
1357+ return compareRequestsWithConfig (want , got , nil )
1358+ }
1359+
1360+ func compareRequestsWithConfig (want []interface {}, got []interface {}, config * SessionPoolConfig ) error {
12321361 if reflect .TypeOf (want [0 ]) != reflect .TypeOf (& sppb.BatchCreateSessionsRequest {}) {
12331362 sessReq := 0
12341363 for i := 0 ; i < len (want ); i ++ {
@@ -1239,7 +1368,7 @@ func compareRequests(want []interface{}, got []interface{}) error {
12391368 }
12401369 want [0 ], want [sessReq ] = want [sessReq ], want [0 ]
12411370 }
1242- if isMultiplexEnabled {
1371+ if isMultiplexEnabled || ( config != nil && config . enableMultiplexSession ) {
12431372 if reflect .TypeOf (want [0 ]) != reflect .TypeOf (& sppb.CreateSessionRequest {}) {
12441373 want = append ([]interface {}{& sppb.CreateSessionRequest {}}, want ... )
12451374 }
0 commit comments