/*-------------------------------------------------------------------------
*
* portalmem.c
- * backend portal memory context management stuff
+ * backend portal memory management
+ *
+ * Portals are objects representing the execution state of a query.
+ * This module provides memory management services for portals, but it
+ * doesn't actually run the executor for them.
*
- * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
- * Portions Copyright (c) 1994, Regents of the University of California
*
+ * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/utils/mmgr/portalmem.c,v 1.43 2001/10/05 17:28:13 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/utils/mmgr/portalmem.c,v 1.61 2003/08/04 02:40:08 momjian Exp $
*
*-------------------------------------------------------------------------
*/
-/*
- * NOTES
- * A "Portal" is a structure used to keep track of cursor queries.
- *
- * When the backend sees a "declare cursor" query, it allocates a
- * "PortalData" structure, plans the query and then stores the query
- * in the portal without executing it. Later, when the backend
- * sees a
- * fetch 1 from FOO
- * the system looks up the portal named "FOO" in the portal table,
- * gets the planned query and then calls the executor with a feature of
- * '(EXEC_FOR 1). The executor then runs the query and returns a single
- * tuple. The problem is that we have to hold onto the state of the
- * portal query until we see a "close". This means we have to be
- * careful about memory management.
- *
- * I hope this makes things clearer to whoever reads this -cim 2/22/91
- */
-
#include "postgres.h"
+#include "miscadmin.h"
+#include "commands/portalcmds.h"
+#include "executor/executor.h"
#include "utils/hsearch.h"
#include "utils/memutils.h"
#include "utils/portal.h"
+/*
+ * estimate of the maximum number of open portals a user would have,
+ * used in initially sizing the PortalHashTable in EnablePortalManager()
+ */
+#define PORTALS_PER_USER 64
+
+
/* ----------------
* Global state
* ----------------
*/
-#define MAX_PORTALNAME_LEN 64
+#define MAX_PORTALNAME_LEN NAMEDATALEN
typedef struct portalhashent
{
PortalHashEnt *hentry; char key[MAX_PORTALNAME_LEN]; \
\
MemSet(key, 0, MAX_PORTALNAME_LEN); \
- snprintf(key, MAX_PORTALNAME_LEN - 1, "%s", NAME); \
+ StrNCpy(key, NAME, MAX_PORTALNAME_LEN); \
hentry = (PortalHashEnt*)hash_search(PortalHashTable, \
key, HASH_FIND, NULL); \
if (hentry) \
PORTAL = NULL; \
} while(0)
-#define PortalHashTableInsert(PORTAL) \
+#define PortalHashTableInsert(PORTAL, NAME) \
do { \
PortalHashEnt *hentry; bool found; char key[MAX_PORTALNAME_LEN]; \
\
MemSet(key, 0, MAX_PORTALNAME_LEN); \
- snprintf(key, MAX_PORTALNAME_LEN - 1, "%s", PORTAL->name); \
+ StrNCpy(key, NAME, MAX_PORTALNAME_LEN); \
hentry = (PortalHashEnt*)hash_search(PortalHashTable, \
key, HASH_ENTER, &found); \
if (hentry == NULL) \
- elog(ERROR, "out of memory in PortalHashTable"); \
+ ereport(ERROR, \
+ (errcode(ERRCODE_OUT_OF_MEMORY), \
+ errmsg("out of memory"))); \
if (found) \
- elog(NOTICE, "trying to insert a portal name that exists."); \
+ elog(ERROR, "duplicate portal name"); \
hentry->portal = PORTAL; \
+ /* To avoid duplicate storage, make PORTAL->name point to htab entry */ \
+ PORTAL->name = hentry->portalname; \
} while(0)
#define PortalHashTableDelete(PORTAL) \
PortalHashEnt *hentry; char key[MAX_PORTALNAME_LEN]; \
\
MemSet(key, 0, MAX_PORTALNAME_LEN); \
- snprintf(key, MAX_PORTALNAME_LEN - 1, "%s", PORTAL->name); \
+ StrNCpy(key, PORTAL->name, MAX_PORTALNAME_LEN); \
hentry = (PortalHashEnt*)hash_search(PortalHashTable, \
key, HASH_REMOVE, NULL); \
if (hentry == NULL) \
- elog(NOTICE, "trying to delete portal name that does not exist."); \
+ elog(WARNING, "trying to delete portal name that does not exist"); \
} while(0)
static MemoryContext PortalMemory = NULL;
ctl.entrysize = sizeof(PortalHashEnt);
/*
- * use PORTALS_PER_USER, defined in utils/portal.h as a guess of how
- * many hash table entries to create, initially
+ * use PORTALS_PER_USER as a guess of how many hash table entries to
+ * create, initially
*/
PortalHashTable = hash_create("Portal hash", PORTALS_PER_USER,
&ctl, HASH_ELEM);
* Returns a portal given a portal name, or NULL if name not found.
*/
Portal
-GetPortalByName(char *name)
+GetPortalByName(const char *name)
{
Portal portal;
return portal;
}
-/*
- * PortalSetQuery
- * Attaches a "query" to portal.
- *
- * Exceptions:
- * BadState if called when disabled.
- * BadArg if portal is invalid.
- * BadArg if queryDesc is "invalid."
- * BadArg if state is "invalid."
- */
-void
-PortalSetQuery(Portal portal,
- QueryDesc *queryDesc,
- TupleDesc attinfo,
- EState *state,
- void (*cleanup) (Portal portal))
-{
- AssertArg(PortalIsValid(portal));
- AssertArg(IsA((Node *) state, EState));
-
- portal->queryDesc = queryDesc;
- portal->attinfo = attinfo;
- portal->state = state;
- portal->atStart = true; /* Allow fetch forward only */
- portal->atEnd = false;
- portal->cleanup = cleanup;
-}
-
/*
* CreatePortal
* Returns a new portal given a name.
*
- * Exceptions:
- * BadState if called when disabled.
- * BadArg if portal name is invalid.
- * "NOTICE" if portal name is in use (existing portal is returned!)
+ * allowDup: if true, automatically drop any pre-existing portal of the
+ * same name (if false, an error is raised).
+ *
+ * dupSilent: if true, don't even emit a WARNING.
*/
Portal
-CreatePortal(char *name)
+CreatePortal(const char *name, bool allowDup, bool dupSilent)
{
Portal portal;
portal = GetPortalByName(name);
if (PortalIsValid(portal))
{
- elog(NOTICE, "CreatePortal: portal \"%s\" already exists", name);
- return portal;
+ if (!allowDup)
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_CURSOR),
+ errmsg("portal \"%s\" already exists", name)));
+ if (!dupSilent)
+ ereport(WARNING,
+ (errcode(ERRCODE_DUPLICATE_CURSOR),
+ errmsg("closing pre-existing portal \"%s\"",
+ name)));
+ PortalDrop(portal, false);
}
/* make new portal structure */
- portal = (Portal) MemoryContextAlloc(PortalMemory, sizeof *portal);
+ portal = (Portal) MemoryContextAllocZero(PortalMemory, sizeof *portal);
- /* initialize portal name */
- portal->name = MemoryContextStrdup(PortalMemory, name);
-
- /* initialize portal heap context */
+ /* initialize portal heap context; typically it won't store much */
portal->heap = AllocSetContextCreate(PortalMemory,
"PortalHeapMemory",
- ALLOCSET_DEFAULT_MINSIZE,
- ALLOCSET_DEFAULT_INITSIZE,
- ALLOCSET_DEFAULT_MAXSIZE);
+ ALLOCSET_SMALL_MINSIZE,
+ ALLOCSET_SMALL_INITSIZE,
+ ALLOCSET_SMALL_MAXSIZE);
- /* initialize portal query */
- portal->queryDesc = NULL;
- portal->attinfo = NULL;
- portal->state = NULL;
- portal->atStart = true; /* disallow fetches until query is set */
- portal->atEnd = true;
- portal->cleanup = NULL;
+ /* initialize portal fields that don't start off zero */
+ portal->cleanup = PortalCleanup;
+ portal->createXact = GetCurrentTransactionId();
+ portal->strategy = PORTAL_MULTI_QUERY;
+ portal->cursorOptions = CURSOR_OPT_NO_SCROLL;
+ portal->atStart = true;
+ portal->atEnd = true; /* disallow fetches until query is set */
- /* put portal in table */
- PortalHashTableInsert(portal);
+ /* put portal in table (sets portal->name) */
+ PortalHashTableInsert(portal, name);
return portal;
}
+/*
+ * CreateNewPortal
+ * Create a new portal, assigning it a random nonconflicting name.
+ */
+Portal
+CreateNewPortal(void)
+{
+ static unsigned int unnamed_portal_count = 0;
+
+ char portalname[MAX_PORTALNAME_LEN];
+
+ /* Select a nonconflicting name */
+ for (;;)
+ {
+ unnamed_portal_count++;
+ sprintf(portalname, "<unnamed portal %u>", unnamed_portal_count);
+ if (GetPortalByName(portalname) == NULL)
+ break;
+ }
+
+ return CreatePortal(portalname, false, false);
+}
+
+/*
+ * PortalDefineQuery
+ * A simple subroutine to establish a portal's query.
+ *
+ * Notes: commandTag shall be NULL if and only if the original query string
+ * (before rewriting) was an empty string. Also, the passed commandTag must
+ * be a pointer to a constant string, since it is not copied. The caller is
+ * responsible for ensuring that the passed sourceText (if any), parse and
+ * plan trees have adequate lifetime. Also, queryContext must accurately
+ * describe the location of the parse and plan trees.
+ */
+void
+PortalDefineQuery(Portal portal,
+ const char *sourceText,
+ const char *commandTag,
+ List *parseTrees,
+ List *planTrees,
+ MemoryContext queryContext)
+{
+ AssertArg(PortalIsValid(portal));
+ AssertState(portal->queryContext == NULL); /* else defined already */
+
+ Assert(length(parseTrees) == length(planTrees));
+
+ Assert(commandTag != NULL || parseTrees == NIL);
+
+ portal->sourceText = sourceText;
+ portal->commandTag = commandTag;
+ portal->parseTrees = parseTrees;
+ portal->planTrees = planTrees;
+ portal->queryContext = queryContext;
+}
+
+/*
+ * PortalCreateHoldStore
+ * Create the tuplestore for a portal.
+ */
+void
+PortalCreateHoldStore(Portal portal)
+{
+ MemoryContext oldcxt;
+
+ Assert(portal->holdContext == NULL);
+ Assert(portal->holdStore == NULL);
+
+ /*
+ * Create the memory context that is used for storage of the tuple
+ * set. Note this is NOT a child of the portal's heap memory.
+ */
+ portal->holdContext =
+ AllocSetContextCreate(PortalMemory,
+ "PortalHeapMemory",
+ ALLOCSET_DEFAULT_MINSIZE,
+ ALLOCSET_DEFAULT_INITSIZE,
+ ALLOCSET_DEFAULT_MAXSIZE);
+
+ /* Create the tuple store, selecting cross-transaction temp files. */
+ oldcxt = MemoryContextSwitchTo(portal->holdContext);
+
+ /* XXX: Should SortMem be used for this? */
+ portal->holdStore = tuplestore_begin_heap(true, true, SortMem);
+
+ MemoryContextSwitchTo(oldcxt);
+}
+
/*
* PortalDrop
- * Destroys portal.
+ * Destroy the portal.
*
- * Exceptions:
- * BadState if called when disabled.
- * BadArg if portal is invalid.
+ * isError: if true, we are destroying portals at the end of a failed
+ * transaction. (This causes PortalCleanup to skip unneeded steps.)
*/
void
-PortalDrop(Portal portal)
+PortalDrop(Portal portal, bool isError)
{
AssertArg(PortalIsValid(portal));
- /* remove portal from hash table */
+ /* Not sure if this case can validly happen or not... */
+ if (portal->portalActive)
+ elog(ERROR, "cannot drop active portal");
+
+ /*
+ * Remove portal from hash table. Because we do this first, we will
+ * not come back to try to remove the portal again if there's any
+ * error in the subsequent steps. Better to leak a little memory than
+ * to get into an infinite error-recovery loop.
+ */
PortalHashTableDelete(portal);
- /* reset portal */
+ /* let portalcmds.c clean up the state it knows about */
if (PointerIsValid(portal->cleanup))
- (*portal->cleanup) (portal);
+ (*portal->cleanup) (portal, isError);
+
+ /*
+ * Delete tuplestore if present. We should do this even under error
+ * conditions; since the tuplestore would have been using cross-
+ * transaction storage, its temp files need to be explicitly deleted.
+ */
+ if (portal->holdStore)
+ {
+ MemoryContext oldcontext;
+
+ oldcontext = MemoryContextSwitchTo(portal->holdContext);
+ tuplestore_end(portal->holdStore);
+ MemoryContextSwitchTo(oldcontext);
+ portal->holdStore = NULL;
+ }
+
+ /* delete tuplestore storage, if any */
+ if (portal->holdContext)
+ MemoryContextDelete(portal->holdContext);
/* release subsidiary storage */
MemoryContextDelete(PortalGetHeapMemory(portal));
- /* release name and portal data (both are in PortalMemory) */
- pfree(portal->name);
+ /* release portal struct (it's in PortalMemory) */
pfree(portal);
}
/*
- * Destroy all portals created in the current transaction (ie, all of them).
+ * DropDependentPortals
+ * Drop any portals using the specified context as queryContext.
+ *
+ * This is normally used to make sure we can safely drop a prepared statement.
+ */
+void
+DropDependentPortals(MemoryContext queryContext)
+{
+ 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->queryContext == queryContext)
+ PortalDrop(portal, false);
+ }
+}
+
+
+/*
+ * Pre-commit processing for portals.
+ *
+ * Any holdable cursors created in this transaction need to be converted to
+ * materialized form, since we are going to close down the executor and
+ * release locks. Remove all other portals created in this transaction.
+ * Portals remaining from prior transactions should be left untouched.
*
* XXX This assumes that portals can be deleted in a random order, ie,
* no portal has a reference to any other (at least not one that will be
- * exercised during deletion). I think this is okay at the moment, but
+ * exercised during deletion). I think this is okay at the moment, but
* we've had bugs of that ilk in the past. Keep a close eye on cursor
* references...
*/
void
-AtEOXact_portals(void)
+AtCommit_Portals(void)
+{
+ HASH_SEQ_STATUS status;
+ PortalHashEnt *hentry;
+ TransactionId xact = GetCurrentTransactionId();
+
+ hash_seq_init(&status, PortalHashTable);
+
+ while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
+ {
+ Portal portal = hentry->portal;
+
+ /*
+ * Do not touch active portals --- this can only happen in the
+ * case of a multi-transaction utility command, such as VACUUM.
+ */
+ if (portal->portalActive)
+ continue;
+
+ if (portal->cursorOptions & CURSOR_OPT_HOLD)
+ {
+ /*
+ * Do nothing to cursors held over from a previous
+ * transaction.
+ */
+ if (portal->createXact != xact)
+ continue;
+
+ /*
+ * We are exiting the transaction that created a holdable
+ * cursor. Instead of dropping the portal, prepare it for
+ * access by later transactions.
+ *
+ * Note that PersistHoldablePortal() must release all resources
+ * used by the portal that are local to the creating
+ * transaction.
+ */
+ PortalCreateHoldStore(portal);
+ PersistHoldablePortal(portal);
+ }
+ else
+ {
+ /* Zap all non-holdable portals */
+ PortalDrop(portal, false);
+ }
+ }
+}
+
+/*
+ * Abort processing for portals.
+ *
+ * At this point we reset the "active" flags and run the cleanup hook if
+ * present, but we can't release memory until the cleanup call.
+ *
+ * The reason we need to reset active is so that we can replace the unnamed
+ * portal, else we'll fail to execute ROLLBACK when it arrives. Also, we
+ * want to run the cleanup hook now to be certain it knows that we had an
+ * error abort and not successful conclusion.
+ */
+void
+AtAbort_Portals(void)
+{
+ HASH_SEQ_STATUS status;
+ PortalHashEnt *hentry;
+ TransactionId xact = GetCurrentTransactionId();
+
+ hash_seq_init(&status, PortalHashTable);
+
+ while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
+ {
+ Portal portal = hentry->portal;
+
+ portal->portalActive = false;
+
+ /*
+ * Do nothing else to cursors held over from a previous
+ * transaction. (This test must include checking CURSOR_OPT_HOLD,
+ * else we will fail to clean up a VACUUM portal if it fails after
+ * its first sub-transaction.)
+ */
+ if (portal->createXact != xact &&
+ (portal->cursorOptions & CURSOR_OPT_HOLD))
+ continue;
+
+ /* let portalcmds.c clean up the state it knows about */
+ if (PointerIsValid(portal->cleanup))
+ {
+ (*portal->cleanup) (portal, true);
+ portal->cleanup = NULL;
+ }
+ }
+}
+
+/*
+ * Post-abort cleanup for portals.
+ *
+ * Delete all portals not held over from prior transactions.
+ */
+void
+AtCleanup_Portals(void)
{
HASH_SEQ_STATUS status;
PortalHashEnt *hentry;
+ TransactionId xact = GetCurrentTransactionId();
hash_seq_init(&status, PortalHashTable);
while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
{
- PortalDrop(hentry->portal);
+ Portal portal = hentry->portal;
+
+ /*
+ * Let's just make sure no one's active...
+ */
+ portal->portalActive = false;
+
+ /*
+ * Do nothing else to cursors held over from a previous
+ * transaction. (This test must include checking CURSOR_OPT_HOLD,
+ * else we will fail to clean up a VACUUM portal if it fails after
+ * its first sub-transaction.)
+ */
+ if (portal->createXact != xact &&
+ (portal->cursorOptions & CURSOR_OPT_HOLD))
+ continue;
+
+ /* Else zap it with prejudice. */
+ PortalDrop(portal, true);
}
}