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-2003, 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.64 2004/02/03 17:34:03 tgl Exp $
17 *-------------------------------------------------------------------------
21 #include "miscadmin.h"
22 #include "commands/portalcmds.h"
23 #include "executor/executor.h"
24 #include "utils/hsearch.h"
25 #include "utils/memutils.h"
26 #include "utils/portal.h"
29 * estimate of the maximum number of open portals a user would have,
30 * used in initially sizing the PortalHashTable in EnablePortalManager()
32 #define PORTALS_PER_USER 64
40 #define MAX_PORTALNAME_LEN NAMEDATALEN
42 typedef struct portalhashent
44 char portalname[MAX_PORTALNAME_LEN];
48 static HTAB *PortalHashTable = NULL;
50 #define PortalHashTableLookup(NAME, PORTAL) \
52 PortalHashEnt *hentry; char key[MAX_PORTALNAME_LEN]; \
54 MemSet(key, 0, MAX_PORTALNAME_LEN); \
55 StrNCpy(key, NAME, MAX_PORTALNAME_LEN); \
56 hentry = (PortalHashEnt*)hash_search(PortalHashTable, \
57 key, HASH_FIND, NULL); \
59 PORTAL = hentry->portal; \
64 #define PortalHashTableInsert(PORTAL, NAME) \
66 PortalHashEnt *hentry; bool found; char key[MAX_PORTALNAME_LEN]; \
68 MemSet(key, 0, MAX_PORTALNAME_LEN); \
69 StrNCpy(key, NAME, MAX_PORTALNAME_LEN); \
70 hentry = (PortalHashEnt*)hash_search(PortalHashTable, \
71 key, HASH_ENTER, &found); \
74 (errcode(ERRCODE_OUT_OF_MEMORY), \
75 errmsg("out of memory"))); \
77 elog(ERROR, "duplicate portal name"); \
78 hentry->portal = PORTAL; \
79 /* To avoid duplicate storage, make PORTAL->name point to htab entry */ \
80 PORTAL->name = hentry->portalname; \
83 #define PortalHashTableDelete(PORTAL) \
85 PortalHashEnt *hentry; char key[MAX_PORTALNAME_LEN]; \
87 MemSet(key, 0, MAX_PORTALNAME_LEN); \
88 StrNCpy(key, PORTAL->name, MAX_PORTALNAME_LEN); \
89 hentry = (PortalHashEnt*)hash_search(PortalHashTable, \
90 key, HASH_REMOVE, NULL); \
92 elog(WARNING, "trying to delete portal name that does not exist"); \
95 static MemoryContext PortalMemory = NULL;
98 /* ----------------------------------------------------------------
99 * public portal interface functions
100 * ----------------------------------------------------------------
104 * EnablePortalManager
105 * Enables the portal management module at backend startup.
108 EnablePortalManager(void)
112 Assert(PortalMemory == NULL);
114 PortalMemory = AllocSetContextCreate(TopMemoryContext,
116 ALLOCSET_DEFAULT_MINSIZE,
117 ALLOCSET_DEFAULT_INITSIZE,
118 ALLOCSET_DEFAULT_MAXSIZE);
120 ctl.keysize = MAX_PORTALNAME_LEN;
121 ctl.entrysize = sizeof(PortalHashEnt);
124 * use PORTALS_PER_USER as a guess of how many hash table entries to
127 PortalHashTable = hash_create("Portal hash", PORTALS_PER_USER,
133 * Returns a portal given a portal name, or NULL if name not found.
136 GetPortalByName(const char *name)
140 if (PointerIsValid(name))
141 PortalHashTableLookup(name, portal);
150 * Returns a new portal given a name.
152 * allowDup: if true, automatically drop any pre-existing portal of the
153 * same name (if false, an error is raised).
155 * dupSilent: if true, don't even emit a WARNING.
158 CreatePortal(const char *name, bool allowDup, bool dupSilent)
162 AssertArg(PointerIsValid(name));
164 portal = GetPortalByName(name);
165 if (PortalIsValid(portal))
169 (errcode(ERRCODE_DUPLICATE_CURSOR),
170 errmsg("cursor \"%s\" already exists", name)));
173 (errcode(ERRCODE_DUPLICATE_CURSOR),
174 errmsg("closing existing cursor \"%s\"",
176 PortalDrop(portal, false);
179 /* make new portal structure */
180 portal = (Portal) MemoryContextAllocZero(PortalMemory, sizeof *portal);
182 /* initialize portal heap context; typically it won't store much */
183 portal->heap = AllocSetContextCreate(PortalMemory,
185 ALLOCSET_SMALL_MINSIZE,
186 ALLOCSET_SMALL_INITSIZE,
187 ALLOCSET_SMALL_MAXSIZE);
189 /* initialize portal fields that don't start off zero */
190 portal->cleanup = PortalCleanup;
191 portal->createXact = GetCurrentTransactionId();
192 portal->strategy = PORTAL_MULTI_QUERY;
193 portal->cursorOptions = CURSOR_OPT_NO_SCROLL;
194 portal->atStart = true;
195 portal->atEnd = true; /* disallow fetches until query is set */
197 /* put portal in table (sets portal->name) */
198 PortalHashTableInsert(portal, name);
205 * Create a new portal, assigning it a random nonconflicting name.
208 CreateNewPortal(void)
210 static unsigned int unnamed_portal_count = 0;
212 char portalname[MAX_PORTALNAME_LEN];
214 /* Select a nonconflicting name */
217 unnamed_portal_count++;
218 sprintf(portalname, "<unnamed portal %u>", unnamed_portal_count);
219 if (GetPortalByName(portalname) == NULL)
223 return CreatePortal(portalname, false, false);
228 * A simple subroutine to establish a portal's query.
230 * Notes: commandTag shall be NULL if and only if the original query string
231 * (before rewriting) was an empty string. Also, the passed commandTag must
232 * be a pointer to a constant string, since it is not copied. The caller is
233 * responsible for ensuring that the passed sourceText (if any), parse and
234 * plan trees have adequate lifetime. Also, queryContext must accurately
235 * describe the location of the parse and plan trees.
238 PortalDefineQuery(Portal portal,
239 const char *sourceText,
240 const char *commandTag,
243 MemoryContext queryContext)
245 AssertArg(PortalIsValid(portal));
246 AssertState(portal->queryContext == NULL); /* else defined already */
248 Assert(length(parseTrees) == length(planTrees));
250 Assert(commandTag != NULL || parseTrees == NIL);
252 portal->sourceText = sourceText;
253 portal->commandTag = commandTag;
254 portal->parseTrees = parseTrees;
255 portal->planTrees = planTrees;
256 portal->queryContext = queryContext;
260 * PortalCreateHoldStore
261 * Create the tuplestore for a portal.
264 PortalCreateHoldStore(Portal portal)
266 MemoryContext oldcxt;
268 Assert(portal->holdContext == NULL);
269 Assert(portal->holdStore == NULL);
272 * Create the memory context that is used for storage of the tuple
273 * set. Note this is NOT a child of the portal's heap memory.
275 portal->holdContext =
276 AllocSetContextCreate(PortalMemory,
278 ALLOCSET_DEFAULT_MINSIZE,
279 ALLOCSET_DEFAULT_INITSIZE,
280 ALLOCSET_DEFAULT_MAXSIZE);
282 /* Create the tuple store, selecting cross-transaction temp files. */
283 oldcxt = MemoryContextSwitchTo(portal->holdContext);
285 /* XXX: Should maintenance_work_mem be used for the portal size? */
286 portal->holdStore = tuplestore_begin_heap(true, true, work_mem);
288 MemoryContextSwitchTo(oldcxt);
293 * Destroy the portal.
295 * isError: if true, we are destroying portals at the end of a failed
296 * transaction. (This causes PortalCleanup to skip unneeded steps.)
299 PortalDrop(Portal portal, bool isError)
301 AssertArg(PortalIsValid(portal));
303 /* Not sure if this case can validly happen or not... */
304 if (portal->portalActive)
305 elog(ERROR, "cannot drop active portal");
308 * Remove portal from hash table. Because we do this first, we will
309 * not come back to try to remove the portal again if there's any
310 * error in the subsequent steps. Better to leak a little memory than
311 * to get into an infinite error-recovery loop.
313 PortalHashTableDelete(portal);
315 /* let portalcmds.c clean up the state it knows about */
316 if (PointerIsValid(portal->cleanup))
317 (*portal->cleanup) (portal, isError);
320 * Delete tuplestore if present. We should do this even under error
321 * conditions; since the tuplestore would have been using cross-
322 * transaction storage, its temp files need to be explicitly deleted.
324 if (portal->holdStore)
326 MemoryContext oldcontext;
328 oldcontext = MemoryContextSwitchTo(portal->holdContext);
329 tuplestore_end(portal->holdStore);
330 MemoryContextSwitchTo(oldcontext);
331 portal->holdStore = NULL;
334 /* delete tuplestore storage, if any */
335 if (portal->holdContext)
336 MemoryContextDelete(portal->holdContext);
338 /* release subsidiary storage */
339 MemoryContextDelete(PortalGetHeapMemory(portal));
341 /* release portal struct (it's in PortalMemory) */
346 * DropDependentPortals
347 * Drop any portals using the specified context as queryContext.
349 * This is normally used to make sure we can safely drop a prepared statement.
352 DropDependentPortals(MemoryContext queryContext)
354 HASH_SEQ_STATUS status;
355 PortalHashEnt *hentry;
357 hash_seq_init(&status, PortalHashTable);
359 while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
361 Portal portal = hentry->portal;
363 if (portal->queryContext == queryContext)
364 PortalDrop(portal, false);
370 * Pre-commit processing for portals.
372 * Any holdable cursors created in this transaction need to be converted to
373 * materialized form, since we are going to close down the executor and
374 * release locks. Remove all other portals created in this transaction.
375 * Portals remaining from prior transactions should be left untouched.
377 * XXX This assumes that portals can be deleted in a random order, ie,
378 * no portal has a reference to any other (at least not one that will be
379 * exercised during deletion). I think this is okay at the moment, but
380 * we've had bugs of that ilk in the past. Keep a close eye on cursor
384 AtCommit_Portals(void)
386 HASH_SEQ_STATUS status;
387 PortalHashEnt *hentry;
388 TransactionId xact = GetCurrentTransactionId();
390 hash_seq_init(&status, PortalHashTable);
392 while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
394 Portal portal = hentry->portal;
397 * Do not touch active portals --- this can only happen in the
398 * case of a multi-transaction utility command, such as VACUUM.
400 if (portal->portalActive)
403 if (portal->cursorOptions & CURSOR_OPT_HOLD)
406 * Do nothing to cursors held over from a previous
409 if (portal->createXact != xact)
413 * We are exiting the transaction that created a holdable
414 * cursor. Instead of dropping the portal, prepare it for
415 * access by later transactions.
417 * Note that PersistHoldablePortal() must release all resources
418 * used by the portal that are local to the creating
421 PortalCreateHoldStore(portal);
422 PersistHoldablePortal(portal);
426 /* Zap all non-holdable portals */
427 PortalDrop(portal, false);
433 * Abort processing for portals.
435 * At this point we reset the "active" flags and run the cleanup hook if
436 * present, but we can't release memory until the cleanup call.
438 * The reason we need to reset active is so that we can replace the unnamed
439 * portal, else we'll fail to execute ROLLBACK when it arrives. Also, we
440 * want to run the cleanup hook now to be certain it knows that we had an
441 * error abort and not successful conclusion.
444 AtAbort_Portals(void)
446 HASH_SEQ_STATUS status;
447 PortalHashEnt *hentry;
448 TransactionId xact = GetCurrentTransactionId();
450 hash_seq_init(&status, PortalHashTable);
452 while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
454 Portal portal = hentry->portal;
456 portal->portalActive = false;
459 * Do nothing else to cursors held over from a previous
460 * transaction. (This test must include checking CURSOR_OPT_HOLD,
461 * else we will fail to clean up a VACUUM portal if it fails after
462 * its first sub-transaction.)
464 if (portal->createXact != xact &&
465 (portal->cursorOptions & CURSOR_OPT_HOLD))
468 /* let portalcmds.c clean up the state it knows about */
469 if (PointerIsValid(portal->cleanup))
471 (*portal->cleanup) (portal, true);
472 portal->cleanup = NULL;
478 * Post-abort cleanup for portals.
480 * Delete all portals not held over from prior transactions.
483 AtCleanup_Portals(void)
485 HASH_SEQ_STATUS status;
486 PortalHashEnt *hentry;
487 TransactionId xact = GetCurrentTransactionId();
489 hash_seq_init(&status, PortalHashTable);
491 while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
493 Portal portal = hentry->portal;
496 * Let's just make sure no one's active...
498 portal->portalActive = false;
501 * Do nothing else to cursors held over from a previous
502 * transaction. (This test must include checking CURSOR_OPT_HOLD,
503 * else we will fail to clean up a VACUUM portal if it fails after
504 * its first sub-transaction.)
506 if (portal->createXact != xact &&
507 (portal->cursorOptions & CURSOR_OPT_HOLD))
510 /* Else zap it with prejudice. */
511 PortalDrop(portal, true);