aboutsummaryrefslogtreecommitdiff
path: root/src/backend/utils/mmgr/portalmem.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/utils/mmgr/portalmem.c')
-rw-r--r--src/backend/utils/mmgr/portalmem.c138
1 files changed, 105 insertions, 33 deletions
diff --git a/src/backend/utils/mmgr/portalmem.c b/src/backend/utils/mmgr/portalmem.c
index 75a6dde32b3..4307f5cc70f 100644
--- a/src/backend/utils/mmgr/portalmem.c
+++ b/src/backend/utils/mmgr/portalmem.c
@@ -620,6 +620,36 @@ PortalHashTableDeleteAll(void)
}
}
+/*
+ * "Hold" a portal. Prepare it for access by later transactions.
+ */
+static void
+HoldPortal(Portal portal)
+{
+ /*
+ * Note that PersistHoldablePortal() must release all resources
+ * used by the portal that are local to the creating transaction.
+ */
+ PortalCreateHoldStore(portal);
+ PersistHoldablePortal(portal);
+
+ /* drop cached plan reference, if any */
+ PortalReleaseCachedPlan(portal);
+
+ /*
+ * Any resources belonging to the portal will be released in the
+ * upcoming transaction-wide cleanup; the portal will no longer
+ * have its own resources.
+ */
+ portal->resowner = NULL;
+
+ /*
+ * Having successfully exported the holdable cursor, mark it as
+ * not belonging to this transaction.
+ */
+ portal->createSubid = InvalidSubTransactionId;
+ portal->activeSubid = InvalidSubTransactionId;
+}
/*
* Pre-commit processing for portals.
@@ -648,9 +678,10 @@ PreCommit_Portals(bool isPrepare)
/*
* There should be no pinned portals anymore. Complain if someone
- * leaked one.
+ * leaked one. Auto-held portals are allowed; we assume that whoever
+ * pinned them is managing them.
*/
- if (portal->portalPinned)
+ if (portal->portalPinned && !portal->autoHeld)
elog(ERROR, "cannot commit while a portal is pinned");
/*
@@ -684,29 +715,7 @@ PreCommit_Portals(bool isPrepare)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot PREPARE a transaction that has created a cursor WITH HOLD")));
- /*
- * Note that PersistHoldablePortal() must release all resources
- * used by the portal that are local to the creating transaction.
- */
- PortalCreateHoldStore(portal);
- PersistHoldablePortal(portal);
-
- /* drop cached plan reference, if any */
- PortalReleaseCachedPlan(portal);
-
- /*
- * Any resources belonging to the portal will be released in the
- * upcoming transaction-wide cleanup; the portal will no longer
- * have its own resources.
- */
- portal->resowner = NULL;
-
- /*
- * Having successfully exported the holdable cursor, mark it as
- * not belonging to this transaction.
- */
- portal->createSubid = InvalidSubTransactionId;
- portal->activeSubid = InvalidSubTransactionId;
+ HoldPortal(portal);
/* Report we changed state */
result = true;
@@ -772,6 +781,14 @@ AtAbort_Portals(void)
continue;
/*
+ * Do nothing to auto-held cursors. This is similar to the case of a
+ * cursor from a previous transaction, but it could also be that the
+ * cursor was auto-held in this transaction, so it wants to live on.
+ */
+ if (portal->autoHeld)
+ continue;
+
+ /*
* If it was created in the current transaction, we can't do normal
* shutdown on a READY portal either; it might refer to objects
* created in the failed transaction. See comments in
@@ -834,8 +851,11 @@ AtCleanup_Portals(void)
if (portal->status == PORTAL_ACTIVE)
continue;
- /* Do nothing to cursors held over from a previous transaction */
- if (portal->createSubid == InvalidSubTransactionId)
+ /*
+ * Do nothing to cursors held over from a previous transaction or
+ * auto-held ones.
+ */
+ if (portal->createSubid == InvalidSubTransactionId || portal->autoHeld)
{
Assert(portal->status != PORTAL_ACTIVE);
Assert(portal->resowner == NULL);
@@ -866,6 +886,32 @@ AtCleanup_Portals(void)
}
/*
+ * Portal-related cleanup when we return to the main loop on error.
+ *
+ * This is different from the cleanup at transaction abort. Auto-held portals
+ * are cleaned up on error but not on transaction abort.
+ */
+void
+PortalErrorCleanup(void)
+{
+ HASH_SEQ_STATUS status;
+ PortalHashEnt *hentry;
+
+ hash_seq_init(&status, PortalHashTable);
+
+ while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
+ {
+ Portal portal = hentry->portal;
+
+ if (portal->autoHeld)
+ {
+ portal->portalPinned = false;
+ PortalDrop(portal, false);
+ }
+ }
+}
+
+/*
* Pre-subcommit processing for portals.
*
* Reassign portals created or used in the current subtransaction to the
@@ -1164,8 +1210,19 @@ ThereAreNoReadyPortals(void)
return true;
}
-bool
-ThereArePinnedPortals(void)
+/*
+ * Hold all pinned portals.
+ *
+ * A procedural language implementation that uses pinned portals for its
+ * internally generated cursors can call this in its COMMIT command to convert
+ * those cursors to held cursors, so that they survive the transaction end.
+ * We mark those portals as "auto-held" so that exception exit knows to clean
+ * them up. (In normal, non-exception code paths, the PL needs to clean those
+ * portals itself, since transaction end won't do it anymore, but that should
+ * be normal practice anyway.)
+ */
+void
+HoldPinnedPortals(void)
{
HASH_SEQ_STATUS status;
PortalHashEnt *hentry;
@@ -1176,9 +1233,24 @@ ThereArePinnedPortals(void)
{
Portal portal = hentry->portal;
- if (portal->portalPinned)
- return true;
- }
+ if (portal->portalPinned && !portal->autoHeld)
+ {
+ /*
+ * Doing transaction control, especially abort, inside a cursor
+ * loop that is not read-only, for example using UPDATE
+ * ... RETURNING, has weird semantics issues. Also, this
+ * implementation wouldn't work, because such portals cannot be
+ * held. (The core grammar enforces that only SELECT statements
+ * can drive a cursor, but for example PL/pgSQL does not restrict
+ * it.)
+ */
+ if (portal->strategy != PORTAL_ONE_SELECT)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TRANSACTION_TERMINATION),
+ errmsg("cannot perform transaction commands inside a cursor loop that is not read-only")));
- return false;
+ portal->autoHeld = true;
+ HoldPortal(portal);
+ }
+ }
}