@@ -445,54 +445,67 @@ const (
445445)
446446
447447func (c * cursorMap ) closeAll (p * planner , reason cursorCloseReason ) error {
448- for n , curs := range c .cursors {
449- switch reason {
450- case cursorCloseForTxnCommit :
451- if curs .withHold {
452- // Cursors declared using WITH HOLD are not closed at transaction
453- // commit, and become the responsibility of the session.
454- curs .committed = true
455- if ! curs .persisted {
456- // Execute the cursor's query to completion and persist the result so
457- // that it can survive the transaction's commit.
458- if err := persistCursor (p , curs ); err != nil {
459- return err
460- }
448+ if reason == cursorCloseForTxnCommit {
449+ for _ , curs := range c .cursors {
450+ if curs .withHold && ! curs .persisted {
451+ // Execute the cursor's query to completion and persist the result so
452+ // that it can survive the transaction's commit. The cursor will become
453+ // the responsibility of the session.
454+ if err := persistCursor (p , curs ); err != nil {
455+ return errors .CombineErrors (err , c .closeAll (p , cursorCloseForTxnRollback ))
461456 }
462- continue
463- }
464- case cursorCloseForTxnRollback :
465- if curs .committed {
466- // Transaction rollback should only remove cursors that were created in
467- // the current transaction.
468- continue
469- }
470- case cursorCloseForTxnPrepare :
471- if curs .withHold && ! curs .committed {
472- // Disallow preparing a transaction that has created a cursor WITH HOLD.
473- // It's fine for previous transactions to have created holdable cursors.
474- // NOTE: Postgres also disallows this.
475- //
476- // Make sure to close all cursors before returning the error.
477- err := c .closeAll (p , cursorCloseForTxnRollback )
478- return errors .CombineErrors (
479- pgerror .New (pgcode .FeatureNotSupported ,
480- "cannot PREPARE a transaction that has created a cursor WITH HOLD" ),
481- err ,
482- )
483457 }
484458 }
485- if err := curs .Close (); err != nil {
486- return err
459+ }
460+ // Close the cursor iterators/containers. Make sure to continue closing even
461+ // if one of them fails.
462+ var retErr error
463+ for _ , curs := range c .cursors {
464+ if reason != cursorCloseForExplicitClose && curs .committed {
465+ // A holdable cursor from a previously committed transaction should remain
466+ // open, except for a session close.
467+ continue
468+ }
469+ if reason == cursorCloseForTxnCommit && curs .withHold {
470+ // A holdable cursor remains open after transaction commit.
471+ continue
487472 }
488- delete (c .cursors , n )
473+ if reason == cursorCloseForTxnPrepare && curs .withHold && ! curs .committed {
474+ // Disallow preparing a transaction that has created a cursor WITH HOLD.
475+ // It's fine for previous transactions to have created holdable cursors.
476+ // NOTE: Postgres also disallows this.
477+ //
478+ // Make sure to close all cursors before returning the error.
479+ err := c .closeAll (p , cursorCloseForTxnRollback )
480+ return errors .CombineErrors (
481+ pgerror .New (pgcode .FeatureNotSupported ,
482+ "cannot PREPARE a transaction that has created a cursor WITH HOLD" ),
483+ err ,
484+ )
485+ }
486+ retErr = errors .CombineErrors (retErr , curs .Close ())
487+ }
488+ if reason == cursorCloseForTxnCommit && retErr != nil {
489+ return errors .CombineErrors (retErr , c .closeAll (p , cursorCloseForTxnRollback ))
489490 }
491+ // Remove closed cursors from the map. Mark holdable cursors as committed
492+ // for a transaction commit.
490493 if reason == cursorCloseForExplicitClose {
491494 // All cursors are closed for explicit close, so we can lose the reference
492495 // to the map.
493496 c .cursors = nil
497+ } else {
498+ for n , curs := range c .cursors {
499+ if reason == cursorCloseForTxnCommit && curs .withHold {
500+ curs .committed = true
501+ }
502+ if curs .committed {
503+ continue
504+ }
505+ delete (c .cursors , n )
506+ }
494507 }
495- return nil
508+ return retErr
496509}
497510
498511func (c * cursorMap ) closeCursor (s tree.Name ) error {
@@ -583,7 +596,7 @@ func (p *planner) checkNoConflictingCursors(stmt tree.Statement) error {
583596
584597// persistCursor runs the given cursor to completion and stores the result in a
585598// row container that can outlive the cursor's transaction.
586- func persistCursor (p * planner , cursor * sqlCursor ) error {
599+ func persistCursor (p * planner , cursor * sqlCursor ) ( retErr error ) {
587600 // Use context.Background() because the cursor can outlive the context in
588601 // which it was created.
589602 helper := persistedCursorHelper {
@@ -602,6 +615,12 @@ func persistCursor(p *planner, cursor *sqlCursor) error {
602615 p .ExtendedEvalContextCopy (),
603616 "persisted_cursor" , /* opName */
604617 )
618+ defer func () {
619+ if retErr != nil {
620+ // Close the container if we encountered an error.
621+ helper .container .Close (helper .ctx )
622+ }
623+ }()
605624 for {
606625 ok , err := cursor .Next (helper .ctx )
607626 if err != nil {
@@ -619,6 +638,7 @@ func persistCursor(p *planner, cursor *sqlCursor) error {
619638 return err
620639 }
621640 cursor .Rows = & helper
641+ cursor .persisted = true
622642 return nil
623643}
624644
0 commit comments