@@ -620,6 +620,36 @@ PortalHashTableDeleteAll(void)
620
620
}
621
621
}
622
622
623
+ /*
624
+ * "Hold" a portal. Prepare it for access by later transactions.
625
+ */
626
+ static void
627
+ HoldPortal (Portal portal )
628
+ {
629
+ /*
630
+ * Note that PersistHoldablePortal() must release all resources
631
+ * used by the portal that are local to the creating transaction.
632
+ */
633
+ PortalCreateHoldStore (portal );
634
+ PersistHoldablePortal (portal );
635
+
636
+ /* drop cached plan reference, if any */
637
+ PortalReleaseCachedPlan (portal );
638
+
639
+ /*
640
+ * Any resources belonging to the portal will be released in the
641
+ * upcoming transaction-wide cleanup; the portal will no longer
642
+ * have its own resources.
643
+ */
644
+ portal -> resowner = NULL ;
645
+
646
+ /*
647
+ * Having successfully exported the holdable cursor, mark it as
648
+ * not belonging to this transaction.
649
+ */
650
+ portal -> createSubid = InvalidSubTransactionId ;
651
+ portal -> activeSubid = InvalidSubTransactionId ;
652
+ }
623
653
624
654
/*
625
655
* Pre-commit processing for portals.
@@ -648,9 +678,10 @@ PreCommit_Portals(bool isPrepare)
648
678
649
679
/*
650
680
* There should be no pinned portals anymore. Complain if someone
651
- * leaked one.
681
+ * leaked one. Auto-held portals are allowed; we assume that whoever
682
+ * pinned them is managing them.
652
683
*/
653
- if (portal -> portalPinned )
684
+ if (portal -> portalPinned && ! portal -> autoHeld )
654
685
elog (ERROR , "cannot commit while a portal is pinned" );
655
686
656
687
/*
@@ -684,29 +715,7 @@ PreCommit_Portals(bool isPrepare)
684
715
(errcode (ERRCODE_FEATURE_NOT_SUPPORTED ),
685
716
errmsg ("cannot PREPARE a transaction that has created a cursor WITH HOLD" )));
686
717
687
- /*
688
- * Note that PersistHoldablePortal() must release all resources
689
- * used by the portal that are local to the creating transaction.
690
- */
691
- PortalCreateHoldStore (portal );
692
- PersistHoldablePortal (portal );
693
-
694
- /* drop cached plan reference, if any */
695
- PortalReleaseCachedPlan (portal );
696
-
697
- /*
698
- * Any resources belonging to the portal will be released in the
699
- * upcoming transaction-wide cleanup; the portal will no longer
700
- * have its own resources.
701
- */
702
- portal -> resowner = NULL ;
703
-
704
- /*
705
- * Having successfully exported the holdable cursor, mark it as
706
- * not belonging to this transaction.
707
- */
708
- portal -> createSubid = InvalidSubTransactionId ;
709
- portal -> activeSubid = InvalidSubTransactionId ;
718
+ HoldPortal (portal );
710
719
711
720
/* Report we changed state */
712
721
result = true;
@@ -771,6 +780,14 @@ AtAbort_Portals(void)
771
780
if (portal -> createSubid == InvalidSubTransactionId )
772
781
continue ;
773
782
783
+ /*
784
+ * Do nothing to auto-held cursors. This is similar to the case of a
785
+ * cursor from a previous transaction, but it could also be that the
786
+ * cursor was auto-held in this transaction, so it wants to live on.
787
+ */
788
+ if (portal -> autoHeld )
789
+ continue ;
790
+
774
791
/*
775
792
* If it was created in the current transaction, we can't do normal
776
793
* shutdown on a READY portal either; it might refer to objects
@@ -834,8 +851,11 @@ AtCleanup_Portals(void)
834
851
if (portal -> status == PORTAL_ACTIVE )
835
852
continue ;
836
853
837
- /* Do nothing to cursors held over from a previous transaction */
838
- if (portal -> createSubid == InvalidSubTransactionId )
854
+ /*
855
+ * Do nothing to cursors held over from a previous transaction or
856
+ * auto-held ones.
857
+ */
858
+ if (portal -> createSubid == InvalidSubTransactionId || portal -> autoHeld )
839
859
{
840
860
Assert (portal -> status != PORTAL_ACTIVE );
841
861
Assert (portal -> resowner == NULL );
@@ -865,6 +885,32 @@ AtCleanup_Portals(void)
865
885
}
866
886
}
867
887
888
+ /*
889
+ * Portal-related cleanup when we return to the main loop on error.
890
+ *
891
+ * This is different from the cleanup at transaction abort. Auto-held portals
892
+ * are cleaned up on error but not on transaction abort.
893
+ */
894
+ void
895
+ PortalErrorCleanup (void )
896
+ {
897
+ HASH_SEQ_STATUS status ;
898
+ PortalHashEnt * hentry ;
899
+
900
+ hash_seq_init (& status , PortalHashTable );
901
+
902
+ while ((hentry = (PortalHashEnt * ) hash_seq_search (& status )) != NULL )
903
+ {
904
+ Portal portal = hentry -> portal ;
905
+
906
+ if (portal -> autoHeld )
907
+ {
908
+ portal -> portalPinned = false;
909
+ PortalDrop (portal , false);
910
+ }
911
+ }
912
+ }
913
+
868
914
/*
869
915
* Pre-subcommit processing for portals.
870
916
*
@@ -1164,8 +1210,19 @@ ThereAreNoReadyPortals(void)
1164
1210
return true;
1165
1211
}
1166
1212
1167
- bool
1168
- ThereArePinnedPortals (void )
1213
+ /*
1214
+ * Hold all pinned portals.
1215
+ *
1216
+ * A procedural language implementation that uses pinned portals for its
1217
+ * internally generated cursors can call this in its COMMIT command to convert
1218
+ * those cursors to held cursors, so that they survive the transaction end.
1219
+ * We mark those portals as "auto-held" so that exception exit knows to clean
1220
+ * them up. (In normal, non-exception code paths, the PL needs to clean those
1221
+ * portals itself, since transaction end won't do it anymore, but that should
1222
+ * be normal practice anyway.)
1223
+ */
1224
+ void
1225
+ HoldPinnedPortals (void )
1169
1226
{
1170
1227
HASH_SEQ_STATUS status ;
1171
1228
PortalHashEnt * hentry ;
@@ -1176,9 +1233,24 @@ ThereArePinnedPortals(void)
1176
1233
{
1177
1234
Portal portal = hentry -> portal ;
1178
1235
1179
- if (portal -> portalPinned )
1180
- return true;
1181
- }
1236
+ if (portal -> portalPinned && !portal -> autoHeld )
1237
+ {
1238
+ /*
1239
+ * Doing transaction control, especially abort, inside a cursor
1240
+ * loop that is not read-only, for example using UPDATE
1241
+ * ... RETURNING, has weird semantics issues. Also, this
1242
+ * implementation wouldn't work, because such portals cannot be
1243
+ * held. (The core grammar enforces that only SELECT statements
1244
+ * can drive a cursor, but for example PL/pgSQL does not restrict
1245
+ * it.)
1246
+ */
1247
+ if (portal -> strategy != PORTAL_ONE_SELECT )
1248
+ ereport (ERROR ,
1249
+ (errcode (ERRCODE_INVALID_TRANSACTION_TERMINATION ),
1250
+ errmsg ("cannot perform transaction commands inside a cursor loop that is not read-only" )));
1182
1251
1183
- return false;
1252
+ portal -> autoHeld = true;
1253
+ HoldPortal (portal );
1254
+ }
1255
+ }
1184
1256
}
0 commit comments