1 /*-------------------------------------------------------------------------
4 * backend portal memory management
6 * Portals are objects representing the execution state of a query.
7 * This module provides memory management services for portals, but it
8 * doesn't actually run the executor for them.
11 * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
12 * Portions Copyright (c) 1994, Regents of the University of California
15 * $PostgreSQL: pgsql/src/backend/utils/mmgr/portalmem.c,v 1.91 2006/08/08 01:23:15 momjian Exp $
17 *-------------------------------------------------------------------------
21 #include "access/heapam.h"
22 #include "access/xact.h"
23 #include "catalog/pg_type.h"
24 #include "commands/portalcmds.h"
26 #include "miscadmin.h"
27 #include "utils/builtins.h"
28 #include "utils/memutils.h"
31 * Estimate of the maximum number of open portals a user would have,
32 * used in initially sizing the PortalHashTable in EnablePortalManager().
33 * Since the hash table can expand, there's no need to make this overly
34 * generous, and keeping it small avoids unnecessary overhead in the
35 * hash_seq_search() calls executed during transaction end.
37 #define PORTALS_PER_USER 16
45 #define MAX_PORTALNAME_LEN NAMEDATALEN
47 typedef struct portalhashent
49 char portalname[MAX_PORTALNAME_LEN];
53 static HTAB *PortalHashTable = NULL;
55 #define PortalHashTableLookup(NAME, PORTAL) \
57 PortalHashEnt *hentry; char key[MAX_PORTALNAME_LEN]; \
59 MemSet(key, 0, MAX_PORTALNAME_LEN); \
60 StrNCpy(key, NAME, MAX_PORTALNAME_LEN); \
61 hentry = (PortalHashEnt *) hash_search(PortalHashTable, \
62 key, HASH_FIND, NULL); \
64 PORTAL = hentry->portal; \
69 #define PortalHashTableInsert(PORTAL, NAME) \
71 PortalHashEnt *hentry; bool found; char key[MAX_PORTALNAME_LEN]; \
73 MemSet(key, 0, MAX_PORTALNAME_LEN); \
74 StrNCpy(key, NAME, MAX_PORTALNAME_LEN); \
75 hentry = (PortalHashEnt *) hash_search(PortalHashTable, \
76 key, HASH_ENTER, &found); \
78 elog(ERROR, "duplicate portal name"); \
79 hentry->portal = PORTAL; \
80 /* To avoid duplicate storage, make PORTAL->name point to htab entry */ \
81 PORTAL->name = hentry->portalname; \
84 #define PortalHashTableDelete(PORTAL) \
86 PortalHashEnt *hentry; char key[MAX_PORTALNAME_LEN]; \
88 MemSet(key, 0, MAX_PORTALNAME_LEN); \
89 StrNCpy(key, PORTAL->name, MAX_PORTALNAME_LEN); \
90 hentry = (PortalHashEnt *) hash_search(PortalHashTable, \
91 key, HASH_REMOVE, NULL); \
93 elog(WARNING, "trying to delete portal name that does not exist"); \
96 static MemoryContext PortalMemory = NULL;
99 /* ----------------------------------------------------------------
100 * public portal interface functions
101 * ----------------------------------------------------------------
105 * EnablePortalManager
106 * Enables the portal management module at backend startup.
109 EnablePortalManager(void)
113 Assert(PortalMemory == NULL);
115 PortalMemory = AllocSetContextCreate(TopMemoryContext,
117 ALLOCSET_DEFAULT_MINSIZE,
118 ALLOCSET_DEFAULT_INITSIZE,
119 ALLOCSET_DEFAULT_MAXSIZE);
121 ctl.keysize = MAX_PORTALNAME_LEN;
122 ctl.entrysize = sizeof(PortalHashEnt);
125 * use PORTALS_PER_USER as a guess of how many hash table entries to
128 PortalHashTable = hash_create("Portal hash", PORTALS_PER_USER,
134 * Returns a portal given a portal name, or NULL if name not found.
137 GetPortalByName(const char *name)
141 if (PointerIsValid(name))
142 PortalHashTableLookup(name, portal);
151 * Returns a new portal given a name.
153 * allowDup: if true, automatically drop any pre-existing portal of the
154 * same name (if false, an error is raised).
156 * dupSilent: if true, don't even emit a WARNING.
159 CreatePortal(const char *name, bool allowDup, bool dupSilent)
163 AssertArg(PointerIsValid(name));
165 portal = GetPortalByName(name);
166 if (PortalIsValid(portal))
170 (errcode(ERRCODE_DUPLICATE_CURSOR),
171 errmsg("cursor \"%s\" already exists", name)));
174 (errcode(ERRCODE_DUPLICATE_CURSOR),
175 errmsg("closing existing cursor \"%s\"",
177 PortalDrop(portal, false);
180 /* make new portal structure */
181 portal = (Portal) MemoryContextAllocZero(PortalMemory, sizeof *portal);
183 /* initialize portal heap context; typically it won't store much */
184 portal->heap = AllocSetContextCreate(PortalMemory,
186 ALLOCSET_SMALL_MINSIZE,
187 ALLOCSET_SMALL_INITSIZE,
188 ALLOCSET_SMALL_MAXSIZE);
190 /* create a resource owner for the portal */
191 portal->resowner = ResourceOwnerCreate(CurTransactionResourceOwner,
194 /* initialize portal fields that don't start off zero */
195 portal->status = PORTAL_NEW;
196 portal->cleanup = PortalCleanup;
197 portal->createSubid = GetCurrentSubTransactionId();
198 portal->strategy = PORTAL_MULTI_QUERY;
199 portal->cursorOptions = CURSOR_OPT_NO_SCROLL;
200 portal->atStart = true;
201 portal->atEnd = true; /* disallow fetches until query is set */
202 portal->visible = true;
203 portal->creation_time = GetCurrentStatementStartTimestamp();
205 /* put portal in table (sets portal->name) */
206 PortalHashTableInsert(portal, name);
213 * Create a new portal, assigning it a random nonconflicting name.
216 CreateNewPortal(void)
218 static unsigned int unnamed_portal_count = 0;
220 char portalname[MAX_PORTALNAME_LEN];
222 /* Select a nonconflicting name */
225 unnamed_portal_count++;
226 sprintf(portalname, "<unnamed portal %u>", unnamed_portal_count);
227 if (GetPortalByName(portalname) == NULL)
231 return CreatePortal(portalname, false, false);
236 * A simple subroutine to establish a portal's query.
238 * Notes: commandTag shall be NULL if and only if the original query string
239 * (before rewriting) was an empty string. Also, the passed commandTag must
240 * be a pointer to a constant string, since it is not copied. The caller is
241 * responsible for ensuring that the passed sourceText (if any), parse and
242 * plan trees have adequate lifetime. Also, queryContext must accurately
243 * describe the location of the parse and plan trees.
246 PortalDefineQuery(Portal portal,
247 const char *prepStmtName,
248 const char *sourceText,
249 const char *commandTag,
252 MemoryContext queryContext)
254 AssertArg(PortalIsValid(portal));
255 AssertState(portal->queryContext == NULL); /* else defined already */
257 Assert(list_length(parseTrees) == list_length(planTrees));
259 Assert(commandTag != NULL || parseTrees == NIL);
261 portal->prepStmtName = prepStmtName;
262 portal->sourceText = sourceText;
263 portal->commandTag = commandTag;
264 portal->parseTrees = parseTrees;
265 portal->planTrees = planTrees;
266 portal->queryContext = queryContext;
270 * PortalCreateHoldStore
271 * Create the tuplestore for a portal.
274 PortalCreateHoldStore(Portal portal)
276 MemoryContext oldcxt;
278 Assert(portal->holdContext == NULL);
279 Assert(portal->holdStore == NULL);
282 * Create the memory context that is used for storage of the tuple set.
283 * Note this is NOT a child of the portal's heap memory.
285 portal->holdContext =
286 AllocSetContextCreate(PortalMemory,
288 ALLOCSET_DEFAULT_MINSIZE,
289 ALLOCSET_DEFAULT_INITSIZE,
290 ALLOCSET_DEFAULT_MAXSIZE);
292 /* Create the tuple store, selecting cross-transaction temp files. */
293 oldcxt = MemoryContextSwitchTo(portal->holdContext);
295 /* XXX: Should maintenance_work_mem be used for the portal size? */
296 portal->holdStore = tuplestore_begin_heap(true, true, work_mem);
298 MemoryContextSwitchTo(oldcxt);
303 * Destroy the portal.
306 PortalDrop(Portal portal, bool isTopCommit)
308 AssertArg(PortalIsValid(portal));
310 /* Not sure if this case can validly happen or not... */
311 if (portal->status == PORTAL_ACTIVE)
312 elog(ERROR, "cannot drop active portal");
315 * Remove portal from hash table. Because we do this first, we will not
316 * come back to try to remove the portal again if there's any error in the
317 * subsequent steps. Better to leak a little memory than to get into an
318 * infinite error-recovery loop.
320 PortalHashTableDelete(portal);
322 /* let portalcmds.c clean up the state it knows about */
323 if (PointerIsValid(portal->cleanup))
324 (*portal->cleanup) (portal);
327 * Release any resources still attached to the portal. There are several
328 * cases being covered here:
330 * Top transaction commit (indicated by isTopCommit): normally we should
331 * do nothing here and let the regular end-of-transaction resource
332 * releasing mechanism handle these resources too. However, if we have a
333 * FAILED portal (eg, a cursor that got an error), we'd better clean up
334 * its resources to avoid resource-leakage warning messages.
336 * Sub transaction commit: never comes here at all, since we don't kill
337 * any portals in AtSubCommit_Portals().
339 * Main or sub transaction abort: we will do nothing here because
340 * portal->resowner was already set NULL; the resources were already
341 * cleaned up in transaction abort.
343 * Ordinary portal drop: must release resources. However, if the portal
344 * is not FAILED then we do not release its locks. The locks become the
345 * responsibility of the transaction's ResourceOwner (since it is the
346 * parent of the portal's owner) and will be released when the transaction
349 if (portal->resowner &&
350 (!isTopCommit || portal->status == PORTAL_FAILED))
352 bool isCommit = (portal->status != PORTAL_FAILED);
354 ResourceOwnerRelease(portal->resowner,
355 RESOURCE_RELEASE_BEFORE_LOCKS,
357 ResourceOwnerRelease(portal->resowner,
358 RESOURCE_RELEASE_LOCKS,
360 ResourceOwnerRelease(portal->resowner,
361 RESOURCE_RELEASE_AFTER_LOCKS,
363 ResourceOwnerDelete(portal->resowner);
365 portal->resowner = NULL;
368 * Delete tuplestore if present. We should do this even under error
369 * conditions; since the tuplestore would have been using cross-
370 * transaction storage, its temp files need to be explicitly deleted.
372 if (portal->holdStore)
374 MemoryContext oldcontext;
376 oldcontext = MemoryContextSwitchTo(portal->holdContext);
377 tuplestore_end(portal->holdStore);
378 MemoryContextSwitchTo(oldcontext);
379 portal->holdStore = NULL;
382 /* delete tuplestore storage, if any */
383 if (portal->holdContext)
384 MemoryContextDelete(portal->holdContext);
386 /* release subsidiary storage */
387 MemoryContextDelete(PortalGetHeapMemory(portal));
389 /* release portal struct (it's in PortalMemory) */
394 * DropDependentPortals
395 * Drop any portals using the specified context as queryContext.
397 * This is normally used to make sure we can safely drop a prepared statement.
400 DropDependentPortals(MemoryContext queryContext)
402 HASH_SEQ_STATUS status;
403 PortalHashEnt *hentry;
405 hash_seq_init(&status, PortalHashTable);
407 while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
409 Portal portal = hentry->portal;
411 if (portal->queryContext == queryContext)
412 PortalDrop(portal, false);
418 * Pre-commit processing for portals.
420 * Any holdable cursors created in this transaction need to be converted to
421 * materialized form, since we are going to close down the executor and
422 * release locks. Other portals are not touched yet.
424 * Returns TRUE if any holdable cursors were processed, FALSE if not.
427 CommitHoldablePortals(void)
430 HASH_SEQ_STATUS status;
431 PortalHashEnt *hentry;
433 hash_seq_init(&status, PortalHashTable);
435 while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
437 Portal portal = hentry->portal;
439 /* Is it a holdable portal created in the current xact? */
440 if ((portal->cursorOptions & CURSOR_OPT_HOLD) &&
441 portal->createSubid != InvalidSubTransactionId &&
442 portal->status == PORTAL_READY)
445 * We are exiting the transaction that created a holdable cursor.
446 * Instead of dropping the portal, prepare it for access by later
449 * Note that PersistHoldablePortal() must release all resources
450 * used by the portal that are local to the creating transaction.
452 PortalCreateHoldStore(portal);
453 PersistHoldablePortal(portal);
456 * Any resources belonging to the portal will be released in the
457 * upcoming transaction-wide cleanup; the portal will no longer
458 * have its own resources.
460 portal->resowner = NULL;
463 * Having successfully exported the holdable cursor, mark it as
464 * not belonging to this transaction.
466 portal->createSubid = InvalidSubTransactionId;
476 * Pre-prepare processing for portals.
478 * Currently we refuse PREPARE if the transaction created any holdable
479 * cursors, since it's quite unclear what to do with one. However, this
480 * has the same API as CommitHoldablePortals and is invoked in the same
481 * way by xact.c, so that we can easily do something reasonable if anyone
482 * comes up with something reasonable to do.
484 * Returns TRUE if any holdable cursors were processed, FALSE if not.
487 PrepareHoldablePortals(void)
490 HASH_SEQ_STATUS status;
491 PortalHashEnt *hentry;
493 hash_seq_init(&status, PortalHashTable);
495 while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
497 Portal portal = hentry->portal;
499 /* Is it a holdable portal created in the current xact? */
500 if ((portal->cursorOptions & CURSOR_OPT_HOLD) &&
501 portal->createSubid != InvalidSubTransactionId &&
502 portal->status == PORTAL_READY)
505 * We are exiting the transaction that created a holdable cursor.
509 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
510 errmsg("cannot PREPARE a transaction that has created a cursor WITH HOLD")));
518 * Pre-commit processing for portals.
520 * Remove all non-holdable portals created in this transaction.
521 * Portals remaining from prior transactions should be left untouched.
524 AtCommit_Portals(void)
526 HASH_SEQ_STATUS status;
527 PortalHashEnt *hentry;
529 hash_seq_init(&status, PortalHashTable);
531 while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
533 Portal portal = hentry->portal;
536 * Do not touch active portals --- this can only happen in the case of
537 * a multi-transaction utility command, such as VACUUM.
539 * Note however that any resource owner attached to such a portal is
540 * still going to go away, so don't leave a dangling pointer.
542 if (portal->status == PORTAL_ACTIVE)
544 portal->resowner = NULL;
549 * Do nothing to cursors held over from a previous transaction
550 * (including holdable ones just frozen by CommitHoldablePortals).
552 if (portal->createSubid == InvalidSubTransactionId)
555 /* Zap all non-holdable portals */
556 PortalDrop(portal, true);
558 /* Restart the iteration */
559 hash_seq_init(&status, PortalHashTable);
564 * Abort processing for portals.
566 * At this point we reset "active" status and run the cleanup hook if
567 * present, but we can't release memory until the cleanup call.
569 * The reason we need to reset active is so that we can replace the unnamed
570 * portal, else we'll fail to execute ROLLBACK when it arrives.
573 AtAbort_Portals(void)
575 HASH_SEQ_STATUS status;
576 PortalHashEnt *hentry;
578 hash_seq_init(&status, PortalHashTable);
580 while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
582 Portal portal = hentry->portal;
584 if (portal->status == PORTAL_ACTIVE)
585 portal->status = PORTAL_FAILED;
588 * Do nothing else to cursors held over from a previous transaction.
590 if (portal->createSubid == InvalidSubTransactionId)
593 /* let portalcmds.c clean up the state it knows about */
594 if (PointerIsValid(portal->cleanup))
596 (*portal->cleanup) (portal);
597 portal->cleanup = NULL;
601 * Any resources belonging to the portal will be released in the
602 * upcoming transaction-wide cleanup; they will be gone before we run
605 portal->resowner = NULL;
610 * Post-abort cleanup for portals.
612 * Delete all portals not held over from prior transactions. */
614 AtCleanup_Portals(void)
616 HASH_SEQ_STATUS status;
617 PortalHashEnt *hentry;
619 hash_seq_init(&status, PortalHashTable);
621 while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
623 Portal portal = hentry->portal;
625 /* Do nothing to cursors held over from a previous transaction */
626 if (portal->createSubid == InvalidSubTransactionId)
628 Assert(portal->status != PORTAL_ACTIVE);
629 Assert(portal->resowner == NULL);
634 PortalDrop(portal, false);
639 * Pre-subcommit processing for portals.
641 * Reassign the portals created in the current subtransaction to the parent
645 AtSubCommit_Portals(SubTransactionId mySubid,
646 SubTransactionId parentSubid,
647 ResourceOwner parentXactOwner)
649 HASH_SEQ_STATUS status;
650 PortalHashEnt *hentry;
652 hash_seq_init(&status, PortalHashTable);
654 while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
656 Portal portal = hentry->portal;
658 if (portal->createSubid == mySubid)
660 portal->createSubid = parentSubid;
661 if (portal->resowner)
662 ResourceOwnerNewParent(portal->resowner, parentXactOwner);
668 * Subtransaction abort handling for portals.
670 * Deactivate portals created during the failed subtransaction.
671 * Note that per AtSubCommit_Portals, this will catch portals created
672 * in descendants of the subtransaction too.
674 * We don't destroy any portals here; that's done in AtSubCleanup_Portals.
677 AtSubAbort_Portals(SubTransactionId mySubid,
678 SubTransactionId parentSubid,
679 ResourceOwner parentXactOwner)
681 HASH_SEQ_STATUS status;
682 PortalHashEnt *hentry;
684 hash_seq_init(&status, PortalHashTable);
686 while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
688 Portal portal = hentry->portal;
690 if (portal->createSubid != mySubid)
694 * Force any active portals of my own transaction into FAILED state.
695 * This is mostly to ensure that a portal running a FETCH will go
696 * FAILED if the underlying cursor fails. (Note we do NOT want to do
697 * this to upper-level portals, since they may be able to continue.)
699 * This is only needed to dodge the sanity check in PortalDrop.
701 if (portal->status == PORTAL_ACTIVE)
702 portal->status = PORTAL_FAILED;
705 * If the portal is READY then allow it to survive into the parent
706 * transaction; otherwise shut it down.
708 * Currently, we can't actually support that because the portal's
709 * query might refer to objects created or changed in the failed
710 * subtransaction, leading to crashes if execution is resumed. So,
711 * even READY portals are deleted. It would be nice to detect whether
712 * the query actually depends on any such object, instead.
715 if (portal->status == PORTAL_READY)
717 portal->createSubid = parentSubid;
718 if (portal->resowner)
719 ResourceOwnerNewParent(portal->resowner, parentXactOwner);
724 /* let portalcmds.c clean up the state it knows about */
725 if (PointerIsValid(portal->cleanup))
727 (*portal->cleanup) (portal);
728 portal->cleanup = NULL;
732 * Any resources belonging to the portal will be released in the
733 * upcoming transaction-wide cleanup; they will be gone before we
736 portal->resowner = NULL;
742 * Post-subabort cleanup for portals.
744 * Drop all portals created in the failed subtransaction (but note that
745 * we will not drop any that were reassigned to the parent above).
748 AtSubCleanup_Portals(SubTransactionId mySubid)
750 HASH_SEQ_STATUS status;
751 PortalHashEnt *hentry;
753 hash_seq_init(&status, PortalHashTable);
755 while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
757 Portal portal = hentry->portal;
759 if (portal->createSubid != mySubid)
763 PortalDrop(portal, false);
767 /* Find all available cursors */
769 pg_cursor(PG_FUNCTION_ARGS)
771 FuncCallContext *funcctx;
772 HASH_SEQ_STATUS *hash_seq;
773 PortalHashEnt *hentry;
775 /* stuff done only on the first call of the function */
776 if (SRF_IS_FIRSTCALL())
778 MemoryContext oldcontext;
781 /* create a function context for cross-call persistence */
782 funcctx = SRF_FIRSTCALL_INIT();
785 * switch to memory context appropriate for multiple function
788 oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
792 hash_seq = (HASH_SEQ_STATUS *) palloc(sizeof(HASH_SEQ_STATUS));
793 hash_seq_init(hash_seq, PortalHashTable);
794 funcctx->user_fctx = (void *) hash_seq;
797 funcctx->user_fctx = NULL;
800 * build tupdesc for result tuples. This must match the
801 * definition of the pg_cursors view in system_views.sql
803 tupdesc = CreateTemplateTupleDesc(6, false);
804 TupleDescInitEntry(tupdesc, (AttrNumber) 1, "name",
806 TupleDescInitEntry(tupdesc, (AttrNumber) 2, "statement",
808 TupleDescInitEntry(tupdesc, (AttrNumber) 3, "is_holdable",
810 TupleDescInitEntry(tupdesc, (AttrNumber) 4, "is_binary",
812 TupleDescInitEntry(tupdesc, (AttrNumber) 5, "is_scrollable",
814 TupleDescInitEntry(tupdesc, (AttrNumber) 6, "creation_time",
815 TIMESTAMPTZOID, -1, 0);
817 funcctx->tuple_desc = BlessTupleDesc(tupdesc);
818 MemoryContextSwitchTo(oldcontext);
821 /* stuff done on every call of the function */
822 funcctx = SRF_PERCALL_SETUP();
823 hash_seq = (HASH_SEQ_STATUS *) funcctx->user_fctx;
825 /* if the hash table is uninitialized, we're done */
826 if (hash_seq == NULL)
827 SRF_RETURN_DONE(funcctx);
829 /* loop until we find a visible portal or hit the end of the list */
830 while ((hentry = hash_seq_search(hash_seq)) != NULL)
832 if (hentry->portal->visible)
844 portal = hentry->portal;
845 MemSet(nulls, 0, sizeof(nulls));
847 values[0] = DirectFunctionCall1(textin, CStringGetDatum(portal->name));
848 if (!portal->sourceText)
851 values[1] = DirectFunctionCall1(textin,
852 CStringGetDatum(portal->sourceText));
853 values[2] = BoolGetDatum(portal->cursorOptions & CURSOR_OPT_HOLD);
854 values[3] = BoolGetDatum(portal->cursorOptions & CURSOR_OPT_BINARY);
855 values[4] = BoolGetDatum(portal->cursorOptions & CURSOR_OPT_SCROLL);
856 values[5] = TimestampTzGetDatum(portal->creation_time);
858 tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls);
859 result = HeapTupleGetDatum(tuple);
860 SRF_RETURN_NEXT(funcctx, result);
863 SRF_RETURN_DONE(funcctx);