@@ -119,6 +119,44 @@ func TestUpdateAborted(t *testing.T) {
119
119
}
120
120
}
121
121
122
+ func TestBatchUpdateAborted (t * testing.T ) {
123
+ t .Parallel ()
124
+
125
+ db , server , teardown := setupTestDBConnection (t )
126
+ defer teardown ()
127
+
128
+ ctx := context .Background ()
129
+ tx , err := db .BeginTx (ctx , & sql.TxOptions {})
130
+ if err != nil {
131
+ t .Fatalf ("begin failed: %v" , err )
132
+ }
133
+ server .TestSpanner .PutExecutionTime (testutil .MethodExecuteBatchDml , testutil.SimulatedExecutionTime {
134
+ Errors : []error {status .Error (codes .Aborted , "Aborted" )},
135
+ })
136
+ if _ , err := tx .ExecContext (ctx , "START BATCH DML" ); err != nil {
137
+ t .Fatalf ("start batch failed: %v" , err )
138
+ }
139
+ if _ , err := tx .ExecContext (ctx , testutil .UpdateBarSetFoo ); err != nil {
140
+ t .Fatalf ("update failed: %v" , err )
141
+ }
142
+ if _ , err := tx .ExecContext (ctx , "RUN BATCH" ); err != nil {
143
+ t .Fatalf ("run batch failed: %v" , err )
144
+ }
145
+ err = tx .Commit ()
146
+ if err != nil {
147
+ t .Fatalf ("commit failed: %v" , err )
148
+ }
149
+ reqs := drainRequestsFromServer (server .TestSpanner )
150
+ execReqs := requestsOfType (reqs , reflect .TypeOf (& sppb.ExecuteBatchDmlRequest {}))
151
+ if g , w := len (execReqs ), 2 ; g != w {
152
+ t .Fatalf ("batch request count mismatch\n Got: %v\n Want: %v" , g , w )
153
+ }
154
+ commitReqs := requestsOfType (reqs , reflect .TypeOf (& sppb.CommitRequest {}))
155
+ if g , w := len (commitReqs ), 1 ; g != w {
156
+ t .Fatalf ("commit request count mismatch\n Got: %v\n Want: %v" , g , w )
157
+ }
158
+ }
159
+
122
160
func TestQueryAborted (t * testing.T ) {
123
161
testRetryReadWriteTransactionWithQueryWithRetrySuccess (t , func (server testutil.InMemSpannerServer ) {
124
162
server .PutExecutionTime (testutil .MethodExecuteStreamingSql , testutil.SimulatedExecutionTime {
@@ -577,6 +615,58 @@ func TestSecondUpdateAborted(t *testing.T) {
577
615
}
578
616
}
579
617
618
+ func TestSecondBatchUpdateAborted (t * testing.T ) {
619
+ t .Parallel ()
620
+
621
+ db , server , teardown := setupTestDBConnection (t )
622
+ defer teardown ()
623
+
624
+ ctx := context .Background ()
625
+ tx , err := db .BeginTx (ctx , & sql.TxOptions {})
626
+ if err != nil {
627
+ t .Fatalf ("begin failed: %v" , err )
628
+ }
629
+ if _ , err := tx .ExecContext (ctx , "START BATCH DML" ); err != nil {
630
+ t .Fatalf ("failed to start batch: %v" , err )
631
+ }
632
+ if _ , err := tx .ExecContext (ctx , testutil .UpdateSingersSetLastName ); err != nil {
633
+ t .Fatalf ("update singers failed: %v" , err )
634
+ }
635
+ if _ , err := tx .ExecContext (ctx , "RUN BATCH" ); err != nil {
636
+ t .Fatalf ("failed to run batch: %v" , err )
637
+ }
638
+
639
+ server .TestSpanner .PutExecutionTime (testutil .MethodExecuteBatchDml , testutil.SimulatedExecutionTime {
640
+ Errors : []error {status .Error (codes .Aborted , "Aborted" )},
641
+ })
642
+ if _ , err := tx .ExecContext (ctx , "START BATCH DML" ); err != nil {
643
+ t .Fatalf ("failed to start batch: %v" , err )
644
+ }
645
+ if _ , err := tx .ExecContext (ctx , testutil .UpdateBarSetFoo ); err != nil {
646
+ t .Fatalf ("update bar failed: %v" , err )
647
+ }
648
+ // This statement will return Aborted, the transaction will be retried internally and the statement is
649
+ // then executed once more and should return the correct value.
650
+ if _ , err := tx .ExecContext (ctx , "RUN BATCH" ); err != nil {
651
+ t .Fatalf ("failed to run batch: %v" , err )
652
+ }
653
+
654
+ if err := tx .Commit (); err != nil {
655
+ t .Fatalf ("commit failed: %v" , err )
656
+ }
657
+ reqs := drainRequestsFromServer (server .TestSpanner )
658
+ execReqs := requestsOfType (reqs , reflect .TypeOf (& sppb.ExecuteBatchDmlRequest {}))
659
+ // The server should receive 4 batch statements, as each update statement should
660
+ // be executed twice.
661
+ if g , w := len (execReqs ), 4 ; g != w {
662
+ t .Fatalf ("batch request count mismatch\n Got: %v\n Want: %v" , g , w )
663
+ }
664
+ commitReqs := requestsOfType (reqs , reflect .TypeOf (& sppb.CommitRequest {}))
665
+ if g , w := len (commitReqs ), 1 ; g != w {
666
+ t .Fatalf ("commit request count mismatch\n Got: %v\n Want: %v" , g , w )
667
+ }
668
+ }
669
+
580
670
func TestSecondUpdateAborted_FirstStatementWithSameError (t * testing.T ) {
581
671
t .Parallel ()
582
672
@@ -707,6 +797,173 @@ func testSecondUpdateAborted_FirstResultChanged(t *testing.T, firstResult *testu
707
797
}
708
798
}
709
799
800
+ func TestBatchUpdateAbortedWithError (t * testing.T ) {
801
+ t .Parallel ()
802
+
803
+ db , server , teardown := setupTestDBConnection (t )
804
+ defer teardown ()
805
+
806
+ // Make sure that one of the DML statements will return an error.
807
+ server .TestSpanner .PutStatementResult (testutil .UpdateSingersSetLastName , & testutil.StatementResult {
808
+ Type : testutil .StatementResultError ,
809
+ Err : status .Error (codes .NotFound , "Table not found" ),
810
+ })
811
+
812
+ ctx := context .Background ()
813
+ tx , err := db .BeginTx (ctx , & sql.TxOptions {})
814
+ if err != nil {
815
+ t .Fatalf ("begin failed: %v" , err )
816
+ }
817
+ if _ , err := tx .ExecContext (ctx , "START BATCH DML" ); err != nil {
818
+ t .Fatalf ("failed to start batch: %v" , err )
819
+ }
820
+ if _ , err := tx .ExecContext (ctx , testutil .UpdateBarSetFoo ); err != nil {
821
+ t .Fatalf ("dml statement failed: %v" , err )
822
+ }
823
+ // This statement should fail with NotFound when the batch is executed.
824
+ // That will also be the result during the retry. Note that the error
825
+ // will not be returned now, but when the batch is executed.
826
+ if _ , err = tx .ExecContext (ctx , testutil .UpdateSingersSetLastName ); err != nil {
827
+ t .Fatalf ("dml statement failed: %v" , err )
828
+ }
829
+ // Note that even though Spanner returns the row count for a Batch DML that
830
+ // fails halfway, go/sql does not do that, so result will be nil for batch
831
+ // statements that fail. Internally, the driver still keeps track of the row
832
+ // counts that were returned, and uses that in the retry strategy.
833
+ if _ , err := tx .ExecContext (ctx , "RUN BATCH" ); spanner .ErrCode (err ) != codes .NotFound {
834
+ t .Fatalf ("error code mismatch\n Got: %v\n Want: %v" , spanner .ErrCode (err ), codes .NotFound )
835
+ }
836
+
837
+ // Abort the transaction. The internal retry should succeed as teh same error
838
+ // and the same row count is returned during the retry.
839
+ server .TestSpanner .PutExecutionTime (testutil .MethodCommitTransaction , testutil.SimulatedExecutionTime {
840
+ Errors : []error {status .Error (codes .Aborted , "Aborted" )},
841
+ })
842
+ err = tx .Commit ()
843
+ if err != nil {
844
+ t .Fatalf ("commit failed: %v" , err )
845
+ }
846
+ reqs := drainRequestsFromServer (server .TestSpanner )
847
+ execReqs := requestsOfType (reqs , reflect .TypeOf (& sppb.ExecuteBatchDmlRequest {}))
848
+ if g , w := len (execReqs ), 2 ; g != w {
849
+ t .Fatalf ("batch request count mismatch\n Got: %v\n Want: %v" , g , w )
850
+ }
851
+ commitReqs := requestsOfType (reqs , reflect .TypeOf (& sppb.CommitRequest {}))
852
+ if g , w := len (commitReqs ), 2 ; g != w {
853
+ t .Fatalf ("commit request count mismatch\n Got: %v\n Want: %v" , g , w )
854
+ }
855
+ }
856
+
857
+ func TestBatchUpdateAbortedWithError_DifferentRowCountDuringRetry (t * testing.T ) {
858
+ t .Parallel ()
859
+
860
+ db , server , teardown := setupTestDBConnection (t )
861
+ defer teardown ()
862
+
863
+ // Make sure that one of the DML statements will return an error.
864
+ server .TestSpanner .PutStatementResult (testutil .UpdateSingersSetLastName , & testutil.StatementResult {
865
+ Type : testutil .StatementResultError ,
866
+ Err : status .Error (codes .NotFound , "Table not found" ),
867
+ })
868
+
869
+ ctx := context .Background ()
870
+ tx , err := db .BeginTx (ctx , & sql.TxOptions {})
871
+ if err != nil {
872
+ t .Fatalf ("begin failed: %v" , err )
873
+ }
874
+ if _ , err := tx .ExecContext (ctx , "START BATCH DML" ); err != nil {
875
+ t .Fatalf ("failed to start batch: %v" , err )
876
+ }
877
+ if _ , err := tx .ExecContext (ctx , testutil .UpdateBarSetFoo ); err != nil {
878
+ t .Fatalf ("dml statement failed: %v" , err )
879
+ }
880
+ // This statement should fail with NotFound when the batch is executed.
881
+ // That will also be the result during the retry. Note that the error
882
+ // will not be returned now, but when the batch is executed.
883
+ if _ , err = tx .ExecContext (ctx , testutil .UpdateSingersSetLastName ); err != nil {
884
+ t .Fatalf ("dml statement failed: %v" , err )
885
+ }
886
+ if _ , err := tx .ExecContext (ctx , "RUN BATCH" ); spanner .ErrCode (err ) != codes .NotFound {
887
+ t .Fatalf ("error code mismatch\n Got: %v\n Want: %v" , spanner .ErrCode (err ), codes .NotFound )
888
+ }
889
+
890
+ // Change the returned row count of the first DML statement. This will cause
891
+ // the retry to fail.
892
+ server .TestSpanner .PutStatementResult (testutil .UpdateBarSetFoo , & testutil.StatementResult {
893
+ Type : testutil .StatementResultUpdateCount ,
894
+ UpdateCount : testutil .UpdateBarSetFooRowCount + 1 ,
895
+ })
896
+ server .TestSpanner .PutExecutionTime (testutil .MethodCommitTransaction , testutil.SimulatedExecutionTime {
897
+ Errors : []error {status .Error (codes .Aborted , "Aborted" )},
898
+ })
899
+ err = tx .Commit ()
900
+ if err != ErrAbortedDueToConcurrentModification {
901
+ t .Fatalf ("commit error mismatch\n Got: %v\n Want: %v" , err , ErrAbortedDueToConcurrentModification )
902
+ }
903
+ reqs := drainRequestsFromServer (server .TestSpanner )
904
+ execReqs := requestsOfType (reqs , reflect .TypeOf (& sppb.ExecuteBatchDmlRequest {}))
905
+ if g , w := len (execReqs ), 2 ; g != w {
906
+ t .Fatalf ("batch request count mismatch\n Got: %v\n Want: %v" , g , w )
907
+ }
908
+ commitReqs := requestsOfType (reqs , reflect .TypeOf (& sppb.CommitRequest {}))
909
+ // The commit should be attempted only once.
910
+ if g , w := len (commitReqs ), 1 ; g != w {
911
+ t .Fatalf ("commit request count mismatch\n Got: %v\n Want: %v" , g , w )
912
+ }
913
+ }
914
+
915
+ func TestBatchUpdateAbortedWithError_DifferentErrorDuringRetry (t * testing.T ) {
916
+ t .Parallel ()
917
+
918
+ db , server , teardown := setupTestDBConnection (t )
919
+ defer teardown ()
920
+
921
+ // Make sure that one of the DML statements will return an error.
922
+ server .TestSpanner .PutStatementResult (testutil .UpdateSingersSetLastName , & testutil.StatementResult {
923
+ Type : testutil .StatementResultError ,
924
+ Err : status .Error (codes .NotFound , "Table not found" ),
925
+ })
926
+
927
+ ctx := context .Background ()
928
+ tx , err := db .BeginTx (ctx , & sql.TxOptions {})
929
+ if err != nil {
930
+ t .Fatalf ("begin failed: %v" , err )
931
+ }
932
+ if _ , err := tx .ExecContext (ctx , "START BATCH DML" ); err != nil {
933
+ t .Fatalf ("failed to start batch: %v" , err )
934
+ }
935
+ if _ , err = tx .ExecContext (ctx , testutil .UpdateSingersSetLastName ); err != nil {
936
+ t .Fatalf ("dml statement failed: %v" , err )
937
+ }
938
+ if _ , err := tx .ExecContext (ctx , "RUN BATCH" ); spanner .ErrCode (err ) != codes .NotFound {
939
+ t .Fatalf ("error code mismatch\n Got: %v\n Want: %v" , spanner .ErrCode (err ), codes .NotFound )
940
+ }
941
+
942
+ // Remove the error for the DML statement and cause a retry. The missing
943
+ // error for the DML statement should fail the retry.
944
+ server .TestSpanner .PutStatementResult (testutil .UpdateSingersSetLastName , & testutil.StatementResult {
945
+ Type : testutil .StatementResultUpdateCount ,
946
+ UpdateCount : testutil .UpdateSingersSetLastNameRowCount ,
947
+ })
948
+ server .TestSpanner .PutExecutionTime (testutil .MethodCommitTransaction , testutil.SimulatedExecutionTime {
949
+ Errors : []error {status .Error (codes .Aborted , "Aborted" )},
950
+ })
951
+ err = tx .Commit ()
952
+ if err != ErrAbortedDueToConcurrentModification {
953
+ t .Fatalf ("commit error mismatch\n Got: %v\n Want: %v" , err , ErrAbortedDueToConcurrentModification )
954
+ }
955
+ reqs := drainRequestsFromServer (server .TestSpanner )
956
+ execReqs := requestsOfType (reqs , reflect .TypeOf (& sppb.ExecuteBatchDmlRequest {}))
957
+ if g , w := len (execReqs ), 2 ; g != w {
958
+ t .Fatalf ("batch request count mismatch\n Got: %v\n Want: %v" , g , w )
959
+ }
960
+ commitReqs := requestsOfType (reqs , reflect .TypeOf (& sppb.CommitRequest {}))
961
+ // The commit should be attempted only once.
962
+ if g , w := len (commitReqs ), 1 ; g != w {
963
+ t .Fatalf ("commit request count mismatch\n Got: %v\n Want: %v" , g , w )
964
+ }
965
+ }
966
+
710
967
func firstNonZero (values ... int ) int {
711
968
for _ , v := range values {
712
969
if v > 0 {
0 commit comments