@@ -25,6 +25,7 @@ import (
2525 "github.com/cockroachdb/cockroach/pkg/kv"
2626 "github.com/cockroachdb/cockroach/pkg/roachpb"
2727 "github.com/cockroachdb/cockroach/pkg/security"
28+ "github.com/cockroachdb/cockroach/pkg/sql/distsqlrun"
2829 "github.com/cockroachdb/cockroach/pkg/sql/parser"
2930 "github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
3031 "github.com/cockroachdb/cockroach/pkg/testutils"
@@ -180,3 +181,85 @@ func TestDistSQLRunningInAbortedTxn(t *testing.T) {
180181 t .Fatalf ("didn't find expected message in trace: %s" , clientRejectedMsg )
181182 }
182183}
184+
185+ // Test that the DistSQLReceiver overwrites previous errors as "better" errors
186+ // come along.
187+ func TestDistSQLReceiverErrorRanking (t * testing.T ) {
188+ defer leaktest .AfterTest (t )()
189+
190+ // This test goes through the trouble of creating a server because it wants to
191+ // create a txn. It creates the txn because it wants to test an interaction
192+ // between the DistSQLReceiver and the TxnCoordSender: the DistSQLReceiver
193+ // will feed retriable errors to the TxnCoordSender which will change those
194+ // errors to TransactionRetryWithProtoRefreshError.
195+ ctx := context .Background ()
196+ s , _ , db := serverutils .StartServer (t , base.TestServerArgs {})
197+ defer s .Stopper ().Stop (ctx )
198+
199+ txn := client .NewTxn (ctx , db , s .NodeID (), client .RootTxn )
200+
201+ // We're going to use a rowResultWriter to which only errors will be passed.
202+ rw := newCallbackResultWriter (nil /* fn */ )
203+ recv := MakeDistSQLReceiver (
204+ ctx ,
205+ rw ,
206+ tree .Rows , /* StatementType */
207+ nil , /* rangeCache */
208+ nil , /* leaseCache */
209+ txn ,
210+ func (hlc.Timestamp ) {}, /* updateClock */
211+ & SessionTracing {},
212+ )
213+
214+ retryErr := roachpb .NewErrorWithTxn (
215+ roachpb .NewTransactionRetryError (
216+ roachpb .RETRY_SERIALIZABLE ),
217+ txn .Serialize ()).GoError ()
218+
219+ abortErr := roachpb .NewErrorWithTxn (
220+ roachpb .NewTransactionAbortedError (
221+ roachpb .ABORT_REASON_ABORTED_RECORD_FOUND ),
222+ txn .Serialize ()).GoError ()
223+
224+ errs := []struct {
225+ err error
226+ expErr string
227+ }{
228+ {
229+ // Initial error, retriable.
230+ err : retryErr ,
231+ expErr : "TransactionRetryWithProtoRefreshError: TransactionRetryError" ,
232+ },
233+ {
234+ // A TransactionAbortedError overwrites another retriable one.
235+ err : abortErr ,
236+ expErr : "TransactionRetryWithProtoRefreshError: TransactionAbortedError" ,
237+ },
238+ {
239+ // A non-aborted retriable error does not overried the
240+ // TransactionAbortedError.
241+ err : retryErr ,
242+ expErr : "TransactionRetryWithProtoRefreshError: TransactionAbortedError" ,
243+ },
244+ {
245+ // A non-retriable error overwrites a retriable one.
246+ err : fmt .Errorf ("err1" ),
247+ expErr : "err1" ,
248+ },
249+ {
250+ // Another non-retriable error doesn't overwrite the previous one.
251+ err : fmt .Errorf ("err2" ),
252+ expErr : "err1" ,
253+ },
254+ }
255+
256+ for i , tc := range errs {
257+ recv .Push (nil , /* row */
258+ & distsqlrun.ProducerMetadata {
259+ Err : tc .err ,
260+ })
261+ if ! testutils .IsError (rw .Err (), tc .expErr ) {
262+ t .Fatalf ("%d: expected %s, got %s" , i , tc .expErr , rw .Err ())
263+ }
264+ }
265+ }
0 commit comments