99using System . IO ;
1010using System . Linq ;
1111using System . Runtime . CompilerServices ;
12+ using System . Runtime . ExceptionServices ;
1213using System . Text ;
1314using System . Threading ;
1415using System . Threading . Tasks ;
@@ -283,9 +284,24 @@ async Task<bool> Read(bool async, CancellationToken cancellationToken = default)
283284 throw new ArgumentOutOfRangeException ( ) ;
284285 }
285286
286- var msg2 = await ReadMessage ( async ) ;
287- ProcessMessage ( msg2 ) ;
288- return msg2 . Code == BackendMessageCode . DataRow ;
287+ var msg = await ReadMessage ( async ) ;
288+
289+ switch ( msg . Code )
290+ {
291+ case BackendMessageCode . DataRow :
292+ ProcessMessage ( msg ) ;
293+ return true ;
294+
295+ case BackendMessageCode . CommandComplete :
296+ case BackendMessageCode . EmptyQueryResponse :
297+ ProcessMessage ( msg ) ;
298+ if ( _statements [ StatementIndex ] . AppendErrorBarrier ?? Command . EnableErrorBarriers )
299+ Expect < ReadyForQueryMessage > ( await Connector . ReadMessage ( async ) , Connector ) ;
300+ return false ;
301+
302+ default :
303+ throw Connector . UnexpectedMessageReceived ( msg . Code ) ;
304+ }
289305 }
290306 catch
291307 {
@@ -335,10 +351,11 @@ public override bool NextResult() => (_isSchemaOnly ? NextResultSchemaOnly(false
335351 /// <returns>A task representing the asynchronous operation.</returns>
336352 public override Task < bool > NextResultAsync ( CancellationToken cancellationToken )
337353 {
338- using ( NoSynchronizationContextScope . Enter ( ) )
339- return _isSchemaOnly
340- ? NextResultSchemaOnly ( async: true , cancellationToken : cancellationToken )
341- : NextResult ( async: true , cancellationToken : cancellationToken ) ;
354+ using var _ = NoSynchronizationContextScope . Enter ( ) ;
355+
356+ return _isSchemaOnly
357+ ? NextResultSchemaOnly ( async: true , cancellationToken : cancellationToken )
358+ : NextResult ( async: true , cancellationToken : cancellationToken ) ;
342359 }
343360
344361 /// <summary>
@@ -370,7 +387,12 @@ async Task<bool> NextResult(bool async, bool isConsuming = false, CancellationTo
370387 case BackendMessageCode . CommandComplete :
371388 case BackendMessageCode . EmptyQueryResponse :
372389 ProcessMessage ( completedMsg ) ;
390+
391+ if ( _statements [ StatementIndex ] . AppendErrorBarrier ?? Command . EnableErrorBarriers )
392+ Expect< ReadyForQueryMessage > ( await Connector . ReadMessage ( async ) , Connector ) ;
393+
373394 break ;
395+
374396 default :
375397 continue ;
376398 }
@@ -472,6 +494,10 @@ async Task<bool> NextResult(bool async, bool isConsuming = false, CancellationTo
472494 }
473495
474496 ProcessMessage( msg ) ;
497+
498+ if ( statement . AppendErrorBarrier ?? Command . EnableErrorBarriers )
499+ Expect< ReadyForQueryMessage > ( await Connector . ReadMessage ( async ) , Connector ) ;
500+
475501 continue ;
476502 }
477503
@@ -494,30 +520,34 @@ async Task<bool> NextResult(bool async, bool isConsuming = false, CancellationTo
494520 switch ( msg . Code )
495521 {
496522 case BackendMessageCode . DataRow :
523+ return true;
497524 case BackendMessageCode . CommandComplete :
498- break ;
525+ if ( statement . AppendErrorBarrier ?? Command . EnableErrorBarriers )
526+ Expect< ReadyForQueryMessage> ( await Connector. ReadMessage( async) , Connector ) ;
527+ return true;
499528 default :
500529 throw Connector. UnexpectedMessageReceived( msg . Code ) ;
501530 }
502-
503- return true;
504531 }
505532
506533 // There are no more queries, we're done. Read the RFQ.
507- ProcessMessage ( Expect < ReadyForQueryMessage > ( await Connector . ReadMessage( async ) , Connector ) ) ;
534+ if ( _statements . Count = = 0 ||
535+ ! Command . EnableErrorBarriers && _statements [ _statements . Count - 1 ] . AppendErrorBarrier ! = true )
536+ {
537+ Expect< ReadyForQueryMessage> ( await Connector . ReadMessage( async ) , Connector ) ;
538+ }
539+ State = ReaderState . Consumed ;
508540 RowDescription = null ;
509541 return false ;
510542 }
511543 catch ( Exception e )
512544 {
513- State = ReaderState . Consumed ;
514-
515545 // Reference the triggering statement from the exception
516546 if ( e is PostgresException postgresException && StatementIndex >= 0 && StatementIndex < _statements . Count )
517547 {
518548 postgresException. BatchCommand = _statements [ StatementIndex ] ;
519549
520- // Prevent the command or batch from by recycled (by the connection) when it's disposed. This is important since
550+ // Prevent the command or batch from being recycled (by the connection) when it's disposed. This is important since
521551 // the exception is very likely to escape the using statement of the command, and by that time some other user may
522552 // already be using the recycled instance.
523553 if ( ! Command . IsWrappedByBatch )
@@ -526,9 +556,8 @@ async Task<bool> NextResult(bool async, bool isConsuming = false, CancellationTo
526556 }
527557 }
528558
529- // An error means all subsequent statements were skipped by PostgreSQL.
530- // If any of them were being prepared, we need to update our bookkeeping to put
531- // them back in unprepared state.
559+ // For the statement that errored, if it was being prepared we need to update our bookkeeping to put them back in unprepared
560+ // state.
532561 for ( ; StatementIndex < _statements . Count ; StatementIndex++ )
533562 {
534563 var statement = _statements [ StatementIndex ] ;
@@ -537,6 +566,34 @@ async Task<bool> NextResult(bool async, bool isConsuming = false, CancellationTo
537566 statement . IsPreparing = false;
538567 statement . PreparedStatement ! . AbortPrepare ( ) ;
539568 }
569+
570+ if ( statement . AppendErrorBarrier ?? Command . EnableErrorBarriers )
571+ break;
572+ }
573+
574+ // In normal, non-isolated batching, we've consumed the result set and are done.
575+ // However, if an isolated command was present after the error, we now have to consume the rest of the result set.
576+ // Note that Consume calls NextResult (this method) recursively, the isConsuming flag tells us we're in this mode.
577+ if ( StatementIndex == _statements . Count )
578+ {
579+ State = ReaderState. Consumed ;
580+ }
581+ else if ( ! isConsuming )
582+ {
583+ switch ( State )
584+ {
585+ case ReaderState. Consumed :
586+ case ReaderState. Closed :
587+ case ReaderState. Disposed :
588+ // The exception may have caused the connector to break (e.g. I/O), and so the reader is already closed.
589+ break ;
590+ default :
591+ // We provide Consume with the first exception which we've just caught.
592+ // If it encounters other exceptions while consuming the rest of the result set, it will raise an AggregateException,
593+ // otherwise it will rethrow this first exception.
594+ await Consume( async, firstException: e) ;
595+ break ;
596+ }
540597 }
541598
542599 throw ;
@@ -672,8 +729,9 @@ async Task<bool> NextResultSchemaOnly(bool async, bool isConsuming = false, Canc
672729 // There are no more queries, we're done. Read to the RFQ.
673730 if ( ! _statements . All ( s => s . IsPrepared ) )
674731 {
675- ProcessMessage ( Expect< ReadyForQueryMessage> ( await Connector. ReadMessage( async) , Connector ) ) ;
732+ Expect< ReadyForQueryMessage > ( await Connector . ReadMessage ( async ) , Connector ) ;
676733 RowDescription = null ;
734+ State = ReaderState. Consumed;
677735 }
678736
679737 return false;
@@ -748,10 +806,6 @@ internal void ProcessMessage(IBackendMessage msg)
748806 State = ReaderState . BetweenResults ;
749807 return ;
750808
751- case BackendMessageCode. ReadyForQuery:
752- State = ReaderState. Consumed;
753- return ;
754-
755809 default :
756810 throw new Exception ( "Received unexpected backend message of type " + msg . Code ) ;
757811 }
@@ -901,14 +955,44 @@ public override int FieldCount
901955 /// Consumes all result sets for this reader, leaving the connector ready for sending and processing further
902956 /// queries
903957 /// </summary>
904- async Task Consume ( bool async )
958+ async Task Consume ( bool async , Exception ? firstException = null )
905959 {
906- // Skip over the other result sets. Note that this does tally records affected
907- // from CommandComplete messages, and properly sets state for auto-prepared statements
908- if ( _isSchemaOnly )
909- while ( await NextResultSchemaOnly ( async , isConsuming : true) ) { }
910- else
911- while ( await NextResult ( async , isConsuming : true) ) { }
960+ var exceptions = firstException is null ? null : new List < Exception > { firstException } ;
961+
962+ // Skip over the other result sets. Note that this does tally records affected from CommandComplete messages, and properly sets
963+ // state for auto-prepared statements
964+ while ( true )
965+ {
966+ try
967+ {
968+ if ( ! ( _isSchemaOnly
969+ ? await NextResultSchemaOnly ( async , isConsuming : true)
970+ : await NextResult( async , isConsuming : true) ) )
971+ {
972+ break ;
973+ }
974+ }
975+ catch ( Exception e )
976+ {
977+ exceptions ??= new ( ) ;
978+ exceptions . Add ( e ) ;
979+ }
980+ }
981+
982+ Debug . Assert ( exceptions ? . Count != 0 ) ;
983+
984+ switch ( exceptions ? . Count )
985+ {
986+ case null :
987+ return ;
988+ case 1 :
989+ ExceptionDispatchInfo . Capture ( exceptions [ 0 ] ) . Throw ( ) ;
990+ return ;
991+ default :
992+ throw new NpgsqlException(
993+ "Multiple exceptions occurred when consuming the result set" ,
994+ new AggregateException( exceptions ) ) ;
995+ }
912996 }
913997
914998 /// <summary>
0 commit comments