Age Owner TLA Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * portalmem.c
4 : * backend portal memory management
5 : *
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.
9 : *
10 : *
11 : * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
12 : * Portions Copyright (c) 1994, Regents of the University of California
13 : *
14 : * IDENTIFICATION
15 : * src/backend/utils/mmgr/portalmem.c
16 : *
17 : *-------------------------------------------------------------------------
18 : */
19 : #include "postgres.h"
20 :
21 : #include "access/xact.h"
22 : #include "catalog/pg_type.h"
23 : #include "commands/portalcmds.h"
24 : #include "funcapi.h"
25 : #include "miscadmin.h"
26 : #include "storage/ipc.h"
27 : #include "utils/builtins.h"
28 : #include "utils/memutils.h"
29 : #include "utils/snapmgr.h"
30 : #include "utils/timestamp.h"
31 :
32 : /*
33 : * Estimate of the maximum number of open portals a user would have,
34 : * used in initially sizing the PortalHashTable in EnablePortalManager().
35 : * Since the hash table can expand, there's no need to make this overly
36 : * generous, and keeping it small avoids unnecessary overhead in the
37 : * hash_seq_search() calls executed during transaction end.
38 : */
39 : #define PORTALS_PER_USER 16
40 :
41 :
42 : /* ----------------
43 : * Global state
44 : * ----------------
45 : */
46 :
47 : #define MAX_PORTALNAME_LEN NAMEDATALEN
48 :
49 : typedef struct portalhashent
50 : {
51 : char portalname[MAX_PORTALNAME_LEN];
52 : Portal portal;
53 : } PortalHashEnt;
54 :
55 : static HTAB *PortalHashTable = NULL;
56 :
57 : #define PortalHashTableLookup(NAME, PORTAL) \
58 : do { \
59 : PortalHashEnt *hentry; \
60 : \
61 : hentry = (PortalHashEnt *) hash_search(PortalHashTable, \
62 : (NAME), HASH_FIND, NULL); \
63 : if (hentry) \
64 : PORTAL = hentry->portal; \
65 : else \
66 : PORTAL = NULL; \
67 : } while(0)
68 :
69 : #define PortalHashTableInsert(PORTAL, NAME) \
70 : do { \
71 : PortalHashEnt *hentry; bool found; \
72 : \
73 : hentry = (PortalHashEnt *) hash_search(PortalHashTable, \
74 : (NAME), HASH_ENTER, &found); \
75 : if (found) \
76 : elog(ERROR, "duplicate portal name"); \
77 : hentry->portal = PORTAL; \
78 : /* To avoid duplicate storage, make PORTAL->name point to htab entry */ \
79 : PORTAL->name = hentry->portalname; \
80 : } while(0)
81 :
82 : #define PortalHashTableDelete(PORTAL) \
83 : do { \
84 : PortalHashEnt *hentry; \
85 : \
86 : hentry = (PortalHashEnt *) hash_search(PortalHashTable, \
87 : PORTAL->name, HASH_REMOVE, NULL); \
88 : if (hentry == NULL) \
89 : elog(WARNING, "trying to delete portal name that does not exist"); \
90 : } while(0)
91 :
92 : static MemoryContext TopPortalContext = NULL;
93 :
94 :
95 : /* ----------------------------------------------------------------
96 : * public portal interface functions
97 : * ----------------------------------------------------------------
98 : */
99 :
100 : /*
101 : * EnablePortalManager
102 : * Enables the portal management module at backend startup.
103 : */
104 : void
8320 tgl 105 CBC 11584 : EnablePortalManager(void)
106 : {
107 : HASHCTL ctl;
108 :
1940 peter_e 109 11584 : Assert(TopPortalContext == NULL);
110 :
111 11584 : TopPortalContext = AllocSetContextCreate(TopMemoryContext,
112 : "TopPortalContext",
113 : ALLOCSET_DEFAULT_SIZES);
114 :
8320 tgl 115 11584 : ctl.keysize = MAX_PORTALNAME_LEN;
7860 116 11584 : ctl.entrysize = sizeof(PortalHashEnt);
117 :
118 : /*
119 : * use PORTALS_PER_USER as a guess of how many hash table entries to
120 : * create, initially
121 : */
7856 122 11584 : PortalHashTable = hash_create("Portal hash", PORTALS_PER_USER,
123 : &ctl, HASH_ELEM | HASH_STRINGS);
9770 scrappy 124 11584 : }
125 :
126 : /*
127 : * GetPortalByName
128 : * Returns a portal given a portal name, or NULL if name not found.
129 : */
130 : Portal
7405 tgl 131 546339 : GetPortalByName(const char *name)
132 : {
133 : Portal portal;
134 :
9345 bruce 135 546339 : if (PointerIsValid(name))
136 546339 : PortalHashTableLookup(name, portal);
137 : else
8320 tgl 138 UBC 0 : portal = NULL;
139 :
8986 bruce 140 CBC 546339 : return portal;
141 : }
142 :
143 : /*
144 : * PortalGetPrimaryStmt
145 : * Get the "primary" stmt within a portal, ie, the one marked canSetTag.
146 : *
147 : * Returns NULL if no such stmt. If multiple PlannedStmt structs within the
148 : * portal are marked canSetTag, returns the first one. Neither of these
149 : * cases should occur in present usages of this function.
150 : */
151 : PlannedStmt *
2276 tgl 152 137410 : PortalGetPrimaryStmt(Portal portal)
153 : {
154 : ListCell *lc;
155 :
156 137410 : foreach(lc, portal->stmts)
157 : {
2190 158 137410 : PlannedStmt *stmt = lfirst_node(PlannedStmt, lc);
159 :
2276 160 137410 : if (stmt->canSetTag)
161 137410 : return stmt;
162 : }
6082 tgl 163 UBC 0 : return NULL;
164 : }
165 :
166 : /*
167 : * CreatePortal
168 : * Returns a new portal given a name.
169 : *
170 : * allowDup: if true, automatically drop any pre-existing portal of the
171 : * same name (if false, an error is raised).
172 : *
173 : * dupSilent: if true, don't even emit a WARNING.
174 : */
175 : Portal
7282 tgl 176 CBC 499824 : CreatePortal(const char *name, bool allowDup, bool dupSilent)
177 : {
178 : Portal portal;
179 :
163 peter 180 GNC 499824 : Assert(PointerIsValid(name));
181 :
9345 bruce 182 CBC 499824 : portal = GetPortalByName(name);
183 499824 : if (PortalIsValid(portal))
184 : {
7282 tgl 185 5201 : if (!allowDup)
7198 tgl 186 UBC 0 : ereport(ERROR,
187 : (errcode(ERRCODE_DUPLICATE_CURSOR),
188 : errmsg("cursor \"%s\" already exists", name)));
7282 tgl 189 CBC 5201 : if (!dupSilent)
7198 tgl 190 UBC 0 : ereport(WARNING,
191 : (errcode(ERRCODE_DUPLICATE_CURSOR),
192 : errmsg("closing existing cursor \"%s\"",
193 : name)));
7282 tgl 194 CBC 5201 : PortalDrop(portal, false);
195 : }
196 :
197 : /* make new portal structure */
1940 peter_e 198 499824 : portal = (Portal) MemoryContextAllocZero(TopPortalContext, sizeof *portal);
199 :
200 : /* initialize portal context; typically it won't store much */
201 499824 : portal->portalContext = AllocSetContextCreate(TopPortalContext,
202 : "PortalContext",
203 : ALLOCSET_SMALL_SIZES);
204 :
205 : /* create a resource owner for the portal */
6840 tgl 206 499824 : portal->resowner = ResourceOwnerCreate(CurTransactionResourceOwner,
207 : "Portal");
208 :
209 : /* initialize portal fields that don't start off zero */
6290 neilc 210 499824 : portal->status = PORTAL_NEW;
7285 tgl 211 499824 : portal->cleanup = PortalCleanup;
6779 212 499824 : portal->createSubid = GetCurrentSubTransactionId();
2774 213 499824 : portal->activeSubid = portal->createSubid;
555 214 499824 : portal->createLevel = GetCurrentTransactionNestLevel();
7282 215 499824 : portal->strategy = PORTAL_MULTI_QUERY;
216 499824 : portal->cursorOptions = CURSOR_OPT_NO_SCROLL;
7334 217 499824 : portal->atStart = true;
218 499824 : portal->atEnd = true; /* disallow fetches until query is set */
6290 neilc 219 499824 : portal->visible = true;
6137 tgl 220 499824 : portal->creation_time = GetCurrentStatementStartTimestamp();
221 :
222 : /* put portal in table (sets portal->name) */
7282 223 499824 : PortalHashTableInsert(portal, name);
224 :
225 : /* for named portals reuse portal->name copy */
943 peter 226 499824 : MemoryContextSetIdentifier(portal->portalContext, portal->name[0] ? portal->name : "<unnamed>");
227 :
8986 bruce 228 499824 : return portal;
229 : }
230 :
231 : /*
232 : * CreateNewPortal
233 : * Create a new portal, assigning it a random nonconflicting name.
234 : */
235 : Portal
7282 tgl 236 10493 : CreateNewPortal(void)
237 : {
238 : static unsigned int unnamed_portal_count = 0;
239 :
240 : char portalname[MAX_PORTALNAME_LEN];
241 :
242 : /* Select a nonconflicting name */
243 : for (;;)
244 : {
245 10493 : unnamed_portal_count++;
246 10493 : sprintf(portalname, "<unnamed portal %u>", unnamed_portal_count);
247 10493 : if (GetPortalByName(portalname) == NULL)
248 10493 : break;
249 : }
250 :
251 10493 : return CreatePortal(portalname, false, false);
252 : }
253 :
254 : /*
255 : * PortalDefineQuery
256 : * A simple subroutine to establish a portal's query.
257 : *
258 : * Notes: as of PG 8.4, caller MUST supply a sourceText string; it is not
259 : * allowed anymore to pass NULL. (If you really don't have source text,
260 : * you can pass a constant string, perhaps "(query not available)".)
261 : *
262 : * commandTag shall be NULL if and only if the original query string
263 : * (before rewriting) was an empty string. Also, the passed commandTag must
264 : * be a pointer to a constant string, since it is not copied.
265 : *
266 : * If cplan is provided, then it is a cached plan containing the stmts, and
267 : * the caller must have done GetCachedPlan(), causing a refcount increment.
268 : * The refcount will be released when the portal is destroyed.
269 : *
270 : * If cplan is NULL, then it is the caller's responsibility to ensure that
271 : * the passed plan trees have adequate lifetime. Typically this is done by
272 : * copying them into the portal's context.
273 : *
274 : * The caller is also responsible for ensuring that the passed prepStmtName
275 : * (if not NULL) and sourceText have adequate lifetime.
276 : *
277 : * NB: this function mustn't do much beyond storing the passed values; in
278 : * particular don't do anything that risks elog(ERROR). If that were to
279 : * happen here before storing the cplan reference, we'd leak the plancache
280 : * refcount that the caller is trying to hand off to us.
281 : */
282 : void
283 499807 : PortalDefineQuery(Portal portal,
284 : const char *prepStmtName,
285 : const char *sourceText,
286 : CommandTag commandTag,
287 : List *stmts,
288 : CachedPlan *cplan)
289 : {
163 peter 290 GNC 499807 : Assert(PortalIsValid(portal));
291 499807 : Assert(portal->status == PORTAL_NEW);
292 :
293 499807 : Assert(sourceText != NULL);
294 499807 : Assert(commandTag != CMDTAG_UNKNOWN || stmts == NIL);
295 :
5485 tgl 296 CBC 499807 : portal->prepStmtName = prepStmtName;
297 499807 : portal->sourceText = sourceText;
1133 alvherre 298 499807 : portal->qc.commandTag = commandTag;
299 499807 : portal->qc.nprocessed = 0;
7282 tgl 300 499807 : portal->commandTag = commandTag;
5892 301 499807 : portal->stmts = stmts;
5871 302 499807 : portal->cplan = cplan;
303 499807 : portal->status = PORTAL_DEFINED;
304 499807 : }
305 :
306 : /*
307 : * PortalReleaseCachedPlan
308 : * Release a portal's reference to its cached plan, if any.
309 : */
310 : static void
311 511760 : PortalReleaseCachedPlan(Portal portal)
312 : {
313 511760 : if (portal->cplan)
314 : {
804 315 16073 : ReleaseCachedPlan(portal->cplan, NULL);
5871 316 16073 : portal->cplan = NULL;
317 :
318 : /*
319 : * We must also clear portal->stmts which is now a dangling reference
320 : * to the cached plan's plan list. This protects any code that might
321 : * try to examine the Portal later.
322 : */
4829 323 16073 : portal->stmts = NIL;
324 : }
7282 325 511760 : }
326 :
327 : /*
328 : * PortalCreateHoldStore
329 : * Create the tuplestore for a portal.
330 : */
331 : void
7278 332 18950 : PortalCreateHoldStore(Portal portal)
333 : {
334 : MemoryContext oldcxt;
335 :
336 18950 : Assert(portal->holdContext == NULL);
337 18950 : Assert(portal->holdStore == NULL);
2436 338 18950 : Assert(portal->holdSnapshot == NULL);
339 :
340 : /*
341 : * Create the memory context that is used for storage of the tuple set.
342 : * Note this is NOT a child of the portal's portalContext.
343 : */
7278 344 18950 : portal->holdContext =
1940 peter_e 345 18950 : AllocSetContextCreate(TopPortalContext,
346 : "PortalHoldContext",
347 : ALLOCSET_DEFAULT_SIZES);
348 :
349 : /*
350 : * Create the tuple store, selecting cross-transaction temp files, and
351 : * enabling random access only if cursor requires scrolling.
352 : *
353 : * XXX: Should maintenance_work_mem be used for the portal size?
354 : */
7278 tgl 355 18950 : oldcxt = MemoryContextSwitchTo(portal->holdContext);
356 :
5275 357 18950 : portal->holdStore =
358 18950 : tuplestore_begin_heap(portal->cursorOptions & CURSOR_OPT_SCROLL,
359 : true, work_mem);
360 :
7278 361 18950 : MemoryContextSwitchTo(oldcxt);
362 18950 : }
363 :
364 : /*
365 : * PinPortal
366 : * Protect a portal from dropping.
367 : *
368 : * A pinned portal is still unpinned and dropped at transaction or
369 : * subtransaction abort.
370 : */
371 : void
4661 heikki.linnakangas 372 5772 : PinPortal(Portal portal)
373 : {
374 5772 : if (portal->portalPinned)
4661 heikki.linnakangas 375 UBC 0 : elog(ERROR, "portal already pinned");
376 :
4661 heikki.linnakangas 377 CBC 5772 : portal->portalPinned = true;
378 5772 : }
379 :
380 : void
381 5748 : UnpinPortal(Portal portal)
382 : {
383 5748 : if (!portal->portalPinned)
4661 heikki.linnakangas 384 UBC 0 : elog(ERROR, "portal not pinned");
385 :
4661 heikki.linnakangas 386 CBC 5748 : portal->portalPinned = false;
387 5748 : }
388 :
389 : /*
390 : * MarkPortalActive
391 : * Transition a portal from READY to ACTIVE state.
392 : *
393 : * NOTE: never set portal->status = PORTAL_ACTIVE directly; call this instead.
394 : */
395 : void
2774 tgl 396 516791 : MarkPortalActive(Portal portal)
397 : {
398 : /* For safety, this is a runtime test not just an Assert */
399 516791 : if (portal->status != PORTAL_READY)
400 9 : ereport(ERROR,
401 : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
402 : errmsg("portal \"%s\" cannot be run", portal->name)));
403 : /* Perform the state transition */
404 516782 : portal->status = PORTAL_ACTIVE;
405 516782 : portal->activeSubid = GetCurrentSubTransactionId();
406 516782 : }
407 :
408 : /*
409 : * MarkPortalDone
410 : * Transition a portal from ACTIVE to DONE state.
411 : *
412 : * NOTE: never set portal->status = PORTAL_DONE directly; call this instead.
413 : */
414 : void
4420 415 362552 : MarkPortalDone(Portal portal)
416 : {
417 : /* Perform the state transition */
418 362552 : Assert(portal->status == PORTAL_ACTIVE);
419 362552 : portal->status = PORTAL_DONE;
420 :
421 : /*
422 : * Allow portalcmds.c to clean up the state it knows about. We might as
423 : * well do that now, since the portal can't be executed any more.
424 : *
425 : * In some cases involving execution of a ROLLBACK command in an already
426 : * aborted transaction, this is necessary, or we'd reach AtCleanup_Portals
427 : * with the cleanup hook still unexecuted.
428 : */
4071 429 362552 : if (PointerIsValid(portal->cleanup))
430 : {
2040 peter_e 431 362529 : portal->cleanup(portal);
4071 tgl 432 362529 : portal->cleanup = NULL;
433 : }
434 362552 : }
435 :
436 : /*
437 : * MarkPortalFailed
438 : * Transition a portal into FAILED state.
439 : *
440 : * NOTE: never set portal->status = PORTAL_FAILED directly; call this instead.
441 : */
442 : void
443 11796 : MarkPortalFailed(Portal portal)
444 : {
445 : /* Perform the state transition */
446 11796 : Assert(portal->status != PORTAL_DONE);
447 11796 : portal->status = PORTAL_FAILED;
448 :
449 : /*
450 : * Allow portalcmds.c to clean up the state it knows about. We might as
451 : * well do that now, since the portal can't be executed any more.
452 : *
453 : * In some cases involving cleanup of an already aborted transaction, this
454 : * is necessary, or we'd reach AtCleanup_Portals with the cleanup hook
455 : * still unexecuted.
456 : */
4420 457 11796 : if (PointerIsValid(portal->cleanup))
458 : {
2040 peter_e 459 11789 : portal->cleanup(portal);
4420 tgl 460 11789 : portal->cleanup = NULL;
461 : }
462 11796 : }
463 :
464 : /*
465 : * PortalDrop
466 : * Destroy the portal.
467 : */
468 : void
6840 469 499815 : PortalDrop(Portal portal, bool isTopCommit)
470 : {
163 peter 471 GNC 499815 : Assert(PortalIsValid(portal));
472 :
473 : /*
474 : * Don't allow dropping a pinned portal, it's still needed by whoever
475 : * pinned it.
476 : */
1915 peter_e 477 CBC 499815 : if (portal->portalPinned)
1915 peter_e 478 UBC 0 : ereport(ERROR,
479 : (errcode(ERRCODE_INVALID_CURSOR_STATE),
480 : errmsg("cannot drop pinned portal \"%s\"", portal->name)));
481 :
482 : /*
483 : * Not sure if the PORTAL_ACTIVE case can validly happen or not...
484 : */
1915 peter_e 485 CBC 499815 : if (portal->status == PORTAL_ACTIVE)
4661 heikki.linnakangas 486 UBC 0 : ereport(ERROR,
487 : (errcode(ERRCODE_INVALID_CURSOR_STATE),
488 : errmsg("cannot drop active portal \"%s\"", portal->name)));
489 :
490 : /*
491 : * Allow portalcmds.c to clean up the state it knows about, in particular
492 : * shutting down the executor if still active. This step potentially runs
493 : * user-defined code so failure has to be expected. It's the cleanup
494 : * hook's responsibility to not try to do that more than once, in the case
495 : * that failure occurs and then we come back to drop the portal again
496 : * during transaction abort.
497 : *
498 : * Note: in most paths of control, this will have been done already in
499 : * MarkPortalDone or MarkPortalFailed. We're just making sure.
500 : */
4424 tgl 501 CBC 499815 : if (PointerIsValid(portal->cleanup))
502 : {
2040 peter_e 503 125449 : portal->cleanup(portal);
4424 tgl 504 125449 : portal->cleanup = NULL;
505 : }
506 :
507 : /* There shouldn't be an active snapshot anymore, except after error */
688 508 499815 : Assert(portal->portalSnapshot == NULL || !isTopCommit);
509 :
510 : /*
511 : * Remove portal from hash table. Because we do this here, we will not
512 : * come back to try to remove the portal again if there's any error in the
513 : * subsequent steps. Better to leak a little memory than to get into an
514 : * infinite error-recovery loop.
515 : */
8320 516 499815 : PortalHashTableDelete(portal);
517 :
518 : /* drop cached plan reference, if any */
4829 519 499815 : PortalReleaseCachedPlan(portal);
520 :
521 : /*
522 : * If portal has a snapshot protecting its data, release that. This needs
523 : * a little care since the registration will be attached to the portal's
524 : * resowner; if the portal failed, we will already have released the
525 : * resowner (and the snapshot) during transaction abort.
526 : */
2436 527 499815 : if (portal->holdSnapshot)
528 : {
529 15765 : if (portal->resowner)
530 15618 : UnregisterSnapshotFromOwner(portal->holdSnapshot,
531 : portal->resowner);
532 15765 : portal->holdSnapshot = NULL;
533 : }
534 :
535 : /*
536 : * Release any resources still attached to the portal. There are several
537 : * cases being covered here:
538 : *
539 : * Top transaction commit (indicated by isTopCommit): normally we should
540 : * do nothing here and let the regular end-of-transaction resource
541 : * releasing mechanism handle these resources too. However, if we have a
542 : * FAILED portal (eg, a cursor that got an error), we'd better clean up
543 : * its resources to avoid resource-leakage warning messages.
544 : *
545 : * Sub transaction commit: never comes here at all, since we don't kill
546 : * any portals in AtSubCommit_Portals().
547 : *
548 : * Main or sub transaction abort: we will do nothing here because
549 : * portal->resowner was already set NULL; the resources were already
550 : * cleaned up in transaction abort.
551 : *
552 : * Ordinary portal drop: must release resources. However, if the portal
553 : * is not FAILED then we do not release its locks. The locks become the
554 : * responsibility of the transaction's ResourceOwner (since it is the
555 : * parent of the portal's owner) and will be released when the transaction
556 : * eventually ends.
557 : */
6840 558 499815 : if (portal->resowner &&
559 484447 : (!isTopCommit || portal->status == PORTAL_FAILED))
560 : {
6797 bruce 561 478627 : bool isCommit = (portal->status != PORTAL_FAILED);
562 :
6840 tgl 563 478627 : ResourceOwnerRelease(portal->resowner,
564 : RESOURCE_RELEASE_BEFORE_LOCKS,
565 : isCommit, false);
566 478627 : ResourceOwnerRelease(portal->resowner,
567 : RESOURCE_RELEASE_LOCKS,
568 : isCommit, false);
569 478627 : ResourceOwnerRelease(portal->resowner,
570 : RESOURCE_RELEASE_AFTER_LOCKS,
571 : isCommit, false);
6801 572 478627 : ResourceOwnerDelete(portal->resowner);
573 : }
6840 574 499815 : portal->resowner = NULL;
575 :
576 : /*
577 : * Delete tuplestore if present. We should do this even under error
578 : * conditions; since the tuplestore would have been using cross-
579 : * transaction storage, its temp files need to be explicitly deleted.
580 : */
7278 581 499815 : if (portal->holdStore)
582 : {
583 : MemoryContext oldcontext;
584 :
585 18941 : oldcontext = MemoryContextSwitchTo(portal->holdContext);
586 18941 : tuplestore_end(portal->holdStore);
587 18941 : MemoryContextSwitchTo(oldcontext);
588 18941 : portal->holdStore = NULL;
589 : }
590 :
591 : /* delete tuplestore storage, if any */
7318 bruce 592 499815 : if (portal->holdContext)
593 18941 : MemoryContextDelete(portal->holdContext);
594 :
595 : /* release subsidiary storage */
1940 peter_e 596 499815 : MemoryContextDelete(portal->portalContext);
597 :
598 : /* release portal struct (it's in TopPortalContext) */
8320 tgl 599 499815 : pfree(portal);
9770 scrappy 600 499815 : }
601 :
602 : /*
603 : * Delete all declared cursors.
604 : *
605 : * Used by commands: CLOSE ALL, DISCARD ALL
606 : */
607 : void
5841 neilc 608 9 : PortalHashTableDeleteAll(void)
609 : {
610 : HASH_SEQ_STATUS status;
611 : PortalHashEnt *hentry;
612 :
613 9 : if (PortalHashTable == NULL)
5841 neilc 614 UBC 0 : return;
615 :
5841 neilc 616 CBC 9 : hash_seq_init(&status, PortalHashTable);
617 36 : while ((hentry = hash_seq_search(&status)) != NULL)
618 : {
5624 bruce 619 27 : Portal portal = hentry->portal;
620 :
621 : /* Can't close the active portal (the one running the command) */
4424 tgl 622 27 : if (portal->status == PORTAL_ACTIVE)
623 15 : continue;
624 :
625 12 : PortalDrop(portal, false);
626 :
627 : /* Restart the iteration in case that led to other drops */
628 12 : hash_seq_term(&status);
629 12 : hash_seq_init(&status, PortalHashTable);
630 : }
631 : }
632 :
633 : /*
634 : * "Hold" a portal. Prepare it for access by later transactions.
635 : */
636 : static void
1838 peter_e 637 41 : HoldPortal(Portal portal)
638 : {
639 : /*
640 : * Note that PersistHoldablePortal() must release all resources used by
641 : * the portal that are local to the creating transaction.
642 : */
643 41 : PortalCreateHoldStore(portal);
644 41 : PersistHoldablePortal(portal);
645 :
646 : /* drop cached plan reference, if any */
647 39 : PortalReleaseCachedPlan(portal);
648 :
649 : /*
650 : * Any resources belonging to the portal will be released in the upcoming
651 : * transaction-wide cleanup; the portal will no longer have its own
652 : * resources.
653 : */
654 39 : portal->resowner = NULL;
655 :
656 : /*
657 : * Having successfully exported the holdable cursor, mark it as not
658 : * belonging to this transaction.
659 : */
660 39 : portal->createSubid = InvalidSubTransactionId;
661 39 : portal->activeSubid = InvalidSubTransactionId;
555 tgl 662 39 : portal->createLevel = 0;
1838 peter_e 663 39 : }
664 :
665 : /*
666 : * Pre-commit processing for portals.
667 : *
668 : * Holdable cursors created in this transaction need to be converted to
669 : * materialized form, since we are going to close down the executor and
670 : * release locks. Non-holdable portals created in this transaction are
671 : * simply removed. Portals remaining from prior transactions should be
672 : * left untouched.
673 : *
674 : * Returns true if any portals changed state (possibly causing user-defined
675 : * code to be run), false if not.
676 : */
677 : bool
4424 tgl 678 472800 : PreCommit_Portals(bool isPrepare)
679 : {
6385 bruce 680 472800 : bool result = false;
681 : HASH_SEQ_STATUS status;
682 : PortalHashEnt *hentry;
683 :
7856 tgl 684 472800 : hash_seq_init(&status, PortalHashTable);
685 :
686 545972 : while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
687 : {
7188 bruce 688 73172 : Portal portal = hentry->portal;
689 :
690 : /*
691 : * There should be no pinned portals anymore. Complain if someone
692 : * leaked one. Auto-held portals are allowed; we assume that whoever
693 : * pinned them is managing them.
694 : */
1838 peter_e 695 73172 : if (portal->portalPinned && !portal->autoHeld)
4424 tgl 696 UBC 0 : elog(ERROR, "cannot commit while a portal is pinned");
697 :
698 : /*
699 : * Do not touch active portals --- this can only happen in the case of
700 : * a multi-transaction utility command, such as VACUUM, or a commit in
701 : * a procedure.
702 : *
703 : * Note however that any resource owner attached to such a portal is
704 : * still going to go away, so don't leave a dangling pointer. Also
705 : * unregister any snapshots held by the portal, mainly to avoid
706 : * snapshot leak warnings from ResourceOwnerRelease().
707 : */
4424 tgl 708 CBC 73172 : if (portal->status == PORTAL_ACTIVE)
709 : {
1690 peter_e 710 67064 : if (portal->holdSnapshot)
711 : {
712 1 : if (portal->resowner)
713 1 : UnregisterSnapshotFromOwner(portal->holdSnapshot,
714 : portal->resowner);
715 1 : portal->holdSnapshot = NULL;
716 : }
4424 tgl 717 67064 : portal->resowner = NULL;
718 : /* Clear portalSnapshot too, for cleanliness */
688 719 67064 : portal->portalSnapshot = NULL;
4424 720 67064 : continue;
721 : }
722 :
723 : /* Is it a holdable portal created in the current xact? */
6840 724 6108 : if ((portal->cursorOptions & CURSOR_OPT_HOLD) &&
6572 725 246 : portal->createSubid != InvalidSubTransactionId &&
6840 726 23 : portal->status == PORTAL_READY)
727 : {
728 : /*
729 : * We are exiting the transaction that created a holdable cursor.
730 : * Instead of dropping the portal, prepare it for access by later
731 : * transactions.
732 : *
733 : * However, if this is PREPARE TRANSACTION rather than COMMIT,
734 : * refuse PREPARE, because the semantics seem pretty unclear.
735 : */
4424 736 23 : if (isPrepare)
4424 tgl 737 UBC 0 : ereport(ERROR,
738 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
739 : errmsg("cannot PREPARE a transaction that has created a cursor WITH HOLD")));
740 :
1838 peter_e 741 CBC 23 : HoldPortal(portal);
742 :
743 : /* Report we changed state */
6572 tgl 744 23 : result = true;
745 : }
4424 746 6085 : else if (portal->createSubid == InvalidSubTransactionId)
747 : {
748 : /*
749 : * Do nothing to cursors held over from a previous transaction
750 : * (including ones we just froze in a previous cycle of this loop)
751 : */
6572 752 265 : continue;
753 : }
754 : else
755 : {
756 : /* Zap all non-holdable portals */
4424 757 5820 : PortalDrop(portal, true);
758 :
759 : /* Report we changed state */
760 5820 : result = true;
761 : }
762 :
763 : /*
764 : * After either freezing or dropping a portal, we have to restart the
765 : * iteration, because we could have invoked user-defined code that
766 : * caused a drop of the next portal in the hash chain.
767 : */
5827 768 5843 : hash_seq_term(&status);
6542 bruce 769 5843 : hash_seq_init(&status, PortalHashTable);
770 : }
771 :
4424 tgl 772 472800 : return result;
773 : }
774 :
775 : /*
776 : * Abort processing for portals.
777 : *
778 : * At this point we run the cleanup hook if present, but we can't release the
779 : * portal's memory until the cleanup call.
780 : */
781 : void
7282 782 20176 : AtAbort_Portals(void)
783 : {
784 : HASH_SEQ_STATUS status;
785 : PortalHashEnt *hentry;
786 :
787 20176 : hash_seq_init(&status, PortalHashTable);
788 :
789 32030 : while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
790 : {
7188 bruce 791 11854 : Portal portal = hentry->portal;
792 :
793 : /*
794 : * When elog(FATAL) is progress, we need to set the active portal to
795 : * failed, so that PortalCleanup() doesn't run the executor shutdown.
796 : */
1893 peter_e 797 11854 : if (portal->status == PORTAL_ACTIVE && shmem_exit_inprogress)
798 3 : MarkPortalFailed(portal);
799 :
800 : /*
801 : * Do nothing else to cursors held over from a previous transaction.
802 : */
6779 tgl 803 11854 : if (portal->createSubid == InvalidSubTransactionId)
7282 804 68 : continue;
805 :
806 : /*
807 : * Do nothing to auto-held cursors. This is similar to the case of a
808 : * cursor from a previous transaction, but it could also be that the
809 : * cursor was auto-held in this transaction, so it wants to live on.
810 : */
1838 peter_e 811 11786 : if (portal->autoHeld)
1838 peter_e 812 UBC 0 : continue;
813 :
814 : /*
815 : * If it was created in the current transaction, we can't do normal
816 : * shutdown on a READY portal either; it might refer to objects
817 : * created in the failed transaction. See comments in
818 : * AtSubAbort_Portals.
819 : */
4798 tgl 820 CBC 11786 : if (portal->status == PORTAL_READY)
4071 821 172 : MarkPortalFailed(portal);
822 :
823 : /*
824 : * Allow portalcmds.c to clean up the state it knows about, if we
825 : * haven't already.
826 : */
7282 827 11786 : if (PointerIsValid(portal->cleanup))
828 : {
2040 peter_e 829 48 : portal->cleanup(portal);
7282 tgl 830 48 : portal->cleanup = NULL;
831 : }
832 :
833 : /* drop cached plan reference, if any */
4829 834 11786 : PortalReleaseCachedPlan(portal);
835 :
836 : /*
837 : * Any resources belonging to the portal will be released in the
838 : * upcoming transaction-wide cleanup; they will be gone before we run
839 : * PortalDrop.
840 : */
6840 841 11786 : portal->resowner = NULL;
842 :
843 : /*
844 : * Although we can't delete the portal data structure proper, we can
845 : * release any memory in subsidiary contexts, such as executor state.
846 : * The cleanup hook was the last thing that might have needed data
847 : * there. But leave active portals alone.
848 : */
1903 peter_e 849 11786 : if (portal->status != PORTAL_ACTIVE)
850 11698 : MemoryContextDeleteChildren(portal->portalContext);
851 : }
8132 tgl 852 20176 : }
853 :
854 : /*
855 : * Post-abort cleanup for portals.
856 : *
857 : * Delete all portals not held over from prior transactions. */
858 : void
7282 859 20166 : AtCleanup_Portals(void)
860 : {
861 : HASH_SEQ_STATUS status;
862 : PortalHashEnt *hentry;
863 :
864 20166 : hash_seq_init(&status, PortalHashTable);
865 :
866 31448 : while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
867 : {
7188 bruce 868 11282 : Portal portal = hentry->portal;
869 :
870 : /*
871 : * Do not touch active portals --- this can only happen in the case of
872 : * a multi-transaction command.
873 : */
1903 peter_e 874 11282 : if (portal->status == PORTAL_ACTIVE)
875 88 : continue;
876 :
877 : /*
878 : * Do nothing to cursors held over from a previous transaction or
879 : * auto-held ones.
880 : */
1838 881 11194 : if (portal->createSubid == InvalidSubTransactionId || portal->autoHeld)
882 : {
6824 tgl 883 68 : Assert(portal->status != PORTAL_ACTIVE);
884 68 : Assert(portal->resowner == NULL);
7282 885 68 : continue;
886 : }
887 :
888 : /*
889 : * If a portal is still pinned, forcibly unpin it. PortalDrop will not
890 : * let us drop the portal otherwise. Whoever pinned the portal was
891 : * interrupted by the abort too and won't try to use it anymore.
892 : */
4661 heikki.linnakangas 893 11126 : if (portal->portalPinned)
894 19 : portal->portalPinned = false;
895 :
896 : /*
897 : * We had better not call any user-defined code during cleanup, so if
898 : * the cleanup hook hasn't been run yet, too bad; we'll just skip it.
899 : */
2064 tgl 900 11126 : if (PointerIsValid(portal->cleanup))
901 : {
2064 tgl 902 UBC 0 : elog(WARNING, "skipping cleanup for portal \"%s\"", portal->name);
903 0 : portal->cleanup = NULL;
904 : }
905 :
906 : /* Zap it. */
6840 tgl 907 CBC 11126 : PortalDrop(portal, false);
908 : }
7282 909 20166 : }
910 :
911 : /*
912 : * Portal-related cleanup when we return to the main loop on error.
913 : *
914 : * This is different from the cleanup at transaction abort. Auto-held portals
915 : * are cleaned up on error but not on transaction abort.
916 : */
917 : void
1838 peter_e 918 17748 : PortalErrorCleanup(void)
919 : {
920 : HASH_SEQ_STATUS status;
921 : PortalHashEnt *hentry;
922 :
923 17748 : hash_seq_init(&status, PortalHashTable);
924 :
925 36329 : while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
926 : {
927 833 : Portal portal = hentry->portal;
928 :
929 833 : if (portal->autoHeld)
930 : {
931 2 : portal->portalPinned = false;
932 2 : PortalDrop(portal, false);
933 : }
934 : }
935 17748 : }
936 :
937 : /*
938 : * Pre-subcommit processing for portals.
939 : *
940 : * Reassign portals created or used in the current subtransaction to the
941 : * parent subtransaction.
942 : */
943 : void
6779 tgl 944 4332 : AtSubCommit_Portals(SubTransactionId mySubid,
945 : SubTransactionId parentSubid,
946 : int parentLevel,
947 : ResourceOwner parentXactOwner)
948 : {
949 : HASH_SEQ_STATUS status;
950 : PortalHashEnt *hentry;
951 :
6856 952 4332 : hash_seq_init(&status, PortalHashTable);
953 :
954 12337 : while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
955 : {
6797 bruce 956 3673 : Portal portal = hentry->portal;
957 :
6779 tgl 958 3673 : if (portal->createSubid == mySubid)
959 : {
960 29 : portal->createSubid = parentSubid;
555 961 29 : portal->createLevel = parentLevel;
6840 962 29 : if (portal->resowner)
963 29 : ResourceOwnerNewParent(portal->resowner, parentXactOwner);
964 : }
2774 965 3673 : if (portal->activeSubid == mySubid)
966 109 : portal->activeSubid = parentSubid;
967 : }
6856 968 4332 : }
969 :
970 : /*
971 : * Subtransaction abort handling for portals.
972 : *
973 : * Deactivate portals created or used during the failed subtransaction.
974 : * Note that per AtSubCommit_Portals, this will catch portals created/used
975 : * in descendants of the subtransaction too.
976 : *
977 : * We don't destroy any portals here; that's done in AtSubCleanup_Portals.
978 : */
979 : void
6779 980 4483 : AtSubAbort_Portals(SubTransactionId mySubid,
981 : SubTransactionId parentSubid,
982 : ResourceOwner myXactOwner,
983 : ResourceOwner parentXactOwner)
984 : {
985 : HASH_SEQ_STATUS status;
986 : PortalHashEnt *hentry;
987 :
6856 988 4483 : hash_seq_init(&status, PortalHashTable);
989 :
990 10414 : while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
991 : {
6797 bruce 992 5931 : Portal portal = hentry->portal;
993 :
994 : /* Was it created in this subtransaction? */
6779 tgl 995 5931 : if (portal->createSubid != mySubid)
996 : {
997 : /* No, but maybe it was used in this subtransaction? */
2774 998 5811 : if (portal->activeSubid == mySubid)
999 : {
1000 : /* Maintain activeSubid until the portal is removed */
1001 21 : portal->activeSubid = parentSubid;
1002 :
1003 : /*
1004 : * A MarkPortalActive() caller ran an upper-level portal in
1005 : * this subtransaction and left the portal ACTIVE. This can't
1006 : * happen, but force the portal into FAILED state for the same
1007 : * reasons discussed below.
1008 : *
1009 : * We assume we can get away without forcing upper-level READY
1010 : * portals to fail, even if they were run and then suspended.
1011 : * In theory a suspended upper-level portal could have
1012 : * acquired some references to objects that are about to be
1013 : * destroyed, but there should be sufficient defenses against
1014 : * such cases: the portal's original query cannot contain such
1015 : * references, and any references within, say, cached plans of
1016 : * PL/pgSQL functions are not from active queries and should
1017 : * be protected by revalidation logic.
1018 : */
1019 21 : if (portal->status == PORTAL_ACTIVE)
2774 tgl 1020 UBC 0 : MarkPortalFailed(portal);
1021 :
1022 : /*
1023 : * Also, if we failed it during the current subtransaction
1024 : * (either just above, or earlier), reattach its resource
1025 : * owner to the current subtransaction's resource owner, so
1026 : * that any resources it still holds will be released while
1027 : * cleaning up this subtransaction. This prevents some corner
1028 : * cases wherein we might get Asserts or worse while cleaning
1029 : * up objects created during the current subtransaction
1030 : * (because they're still referenced within this portal).
1031 : */
2774 tgl 1032 CBC 21 : if (portal->status == PORTAL_FAILED && portal->resowner)
1033 : {
1034 6 : ResourceOwnerNewParent(portal->resowner, myXactOwner);
1035 6 : portal->resowner = NULL;
1036 : }
1037 : }
1038 : /* Done if it wasn't created in this subtransaction */
6856 1039 5811 : continue;
1040 : }
1041 :
1042 : /*
1043 : * Force any live portals of my own subtransaction into FAILED state.
1044 : * We have to do this because they might refer to objects created or
1045 : * changed in the failed subtransaction, leading to crashes within
1046 : * ExecutorEnd when portalcmds.c tries to close down the portal.
1047 : * Currently, every MarkPortalActive() caller ensures it updates the
1048 : * portal status again before relinquishing control, so ACTIVE can't
1049 : * happen here. If it does happen, dispose the portal like existing
1050 : * MarkPortalActive() callers would.
1051 : */
4798 1052 120 : if (portal->status == PORTAL_READY ||
1053 114 : portal->status == PORTAL_ACTIVE)
4071 1054 6 : MarkPortalFailed(portal);
1055 :
1056 : /*
1057 : * Allow portalcmds.c to clean up the state it knows about, if we
1058 : * haven't already.
1059 : */
4798 1060 120 : if (PointerIsValid(portal->cleanup))
1061 : {
2040 peter_e 1062 UBC 0 : portal->cleanup(portal);
4798 tgl 1063 0 : portal->cleanup = NULL;
1064 : }
1065 :
1066 : /* drop cached plan reference, if any */
4798 tgl 1067 CBC 120 : PortalReleaseCachedPlan(portal);
1068 :
1069 : /*
1070 : * Any resources belonging to the portal will be released in the
1071 : * upcoming transaction-wide cleanup; they will be gone before we run
1072 : * PortalDrop.
1073 : */
1074 120 : portal->resowner = NULL;
1075 :
1076 : /*
1077 : * Although we can't delete the portal data structure proper, we can
1078 : * release any memory in subsidiary contexts, such as executor state.
1079 : * The cleanup hook was the last thing that might have needed data
1080 : * there.
1081 : */
1940 peter_e 1082 120 : MemoryContextDeleteChildren(portal->portalContext);
1083 : }
6856 tgl 1084 4483 : }
1085 :
1086 : /*
1087 : * Post-subabort cleanup for portals.
1088 : *
1089 : * Drop all portals created in the failed subtransaction (but note that
1090 : * we will not drop any that were reassigned to the parent above).
1091 : */
1092 : void
6779 1093 4483 : AtSubCleanup_Portals(SubTransactionId mySubid)
1094 : {
1095 : HASH_SEQ_STATUS status;
1096 : PortalHashEnt *hentry;
1097 :
6856 1098 4483 : hash_seq_init(&status, PortalHashTable);
1099 :
1100 10301 : while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
1101 : {
1102 5818 : Portal portal = hentry->portal;
1103 :
6779 1104 5818 : if (portal->createSubid != mySubid)
6856 1105 5811 : continue;
1106 :
1107 : /*
1108 : * If a portal is still pinned, forcibly unpin it. PortalDrop will not
1109 : * let us drop the portal otherwise. Whoever pinned the portal was
1110 : * interrupted by the abort too and won't try to use it anymore.
1111 : */
4653 heikki.linnakangas 1112 7 : if (portal->portalPinned)
1113 3 : portal->portalPinned = false;
1114 :
1115 : /*
1116 : * We had better not call any user-defined code during cleanup, so if
1117 : * the cleanup hook hasn't been run yet, too bad; we'll just skip it.
1118 : */
2064 tgl 1119 7 : if (PointerIsValid(portal->cleanup))
1120 : {
2064 tgl 1121 UBC 0 : elog(WARNING, "skipping cleanup for portal \"%s\"", portal->name);
1122 0 : portal->cleanup = NULL;
1123 : }
1124 :
1125 : /* Zap it. */
6840 tgl 1126 CBC 7 : PortalDrop(portal, false);
1127 : }
6856 1128 4483 : }
1129 :
1130 : /* Find all available cursors */
1131 : Datum
6290 neilc 1132 60 : pg_cursor(PG_FUNCTION_ARGS)
1133 : {
5827 tgl 1134 60 : ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
1135 : HASH_SEQ_STATUS hash_seq;
1136 : PortalHashEnt *hentry;
1137 :
1138 : /*
1139 : * We put all the tuples into a tuplestore in one scan of the hashtable.
1140 : * This avoids any issue of the hashtable possibly changing between calls.
1141 : */
173 michael 1142 60 : InitMaterializedSRF(fcinfo, 0);
1143 :
5827 tgl 1144 60 : hash_seq_init(&hash_seq, PortalHashTable);
1145 186 : while ((hentry = hash_seq_search(&hash_seq)) != NULL)
1146 : {
1147 126 : Portal portal = hentry->portal;
1148 : Datum values[6];
267 peter 1149 GNC 126 : bool nulls[6] = {0};
1150 :
1151 : /* report only "visible" entries */
5827 tgl 1152 CBC 126 : if (!portal->visible)
1153 63 : continue;
1154 :
5493 1155 63 : values[0] = CStringGetTextDatum(portal->name);
5378 1156 63 : values[1] = CStringGetTextDatum(portal->sourceText);
6290 neilc 1157 63 : values[2] = BoolGetDatum(portal->cursorOptions & CURSOR_OPT_HOLD);
1158 63 : values[3] = BoolGetDatum(portal->cursorOptions & CURSOR_OPT_BINARY);
6290 neilc 1159 GIC 63 : values[4] = BoolGetDatum(portal->cursorOptions & CURSOR_OPT_SCROLL);
6290 neilc 1160 CBC 63 : values[5] = TimestampTzGetDatum(portal->creation_time);
1161 :
398 michael 1162 GIC 63 : tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
6290 neilc 1163 ECB : }
1164 :
5827 tgl 1165 GIC 60 : return (Datum) 0;
1166 : }
3781 simon 1167 ECB :
1168 : bool
3781 simon 1169 GIC 26 : ThereAreNoReadyPortals(void)
1170 : {
1171 : HASH_SEQ_STATUS status;
3781 simon 1172 ECB : PortalHashEnt *hentry;
1173 :
3781 simon 1174 CBC 26 : hash_seq_init(&status, PortalHashTable);
1175 :
1176 52 : while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
1177 : {
1178 26 : Portal portal = hentry->portal;
3781 simon 1179 EUB :
3781 simon 1180 GIC 26 : if (portal->status == PORTAL_READY)
3781 simon 1181 UIC 0 : return false;
3781 simon 1182 ECB : }
1183 :
3781 simon 1184 GIC 26 : return true;
1185 : }
1186 :
1187 : /*
1188 : * Hold all pinned portals.
1189 : *
1190 : * When initiating a COMMIT or ROLLBACK inside a procedure, this must be
1191 : * called to protect internally-generated cursors from being dropped during
1192 : * the transaction shutdown. Currently, SPI calls this automatically; PLs
1193 : * that initiate COMMIT or ROLLBACK some other way are on the hook to do it
1194 : * themselves. (Note that we couldn't do this in, say, AtAbort_Portals
1195 : * because we need to run user-defined code while persisting a portal.
1196 : * It's too late to do that once transaction abort has started.)
1197 : *
1198 : * We protect such portals by converting them to held cursors. We mark them
1199 : * as "auto-held" so that exception exit knows to clean them up. (In normal,
1200 : * non-exception code paths, the PL needs to clean such portals itself, since
1201 : * transaction end won't do it anymore; but that should be normal practice
1202 : * anyway.)
1838 peter_e 1203 ECB : */
1204 : void
1838 peter_e 1205 GIC 2203 : HoldPinnedPortals(void)
1206 : {
1207 : HASH_SEQ_STATUS status;
1903 peter_e 1208 ECB : PortalHashEnt *hentry;
1209 :
1903 peter_e 1210 CBC 2203 : hash_seq_init(&status, PortalHashTable);
1211 :
1212 4466 : while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
1213 : {
1214 2266 : Portal portal = hentry->portal;
1215 :
1838 peter_e 1216 GIC 2266 : if (portal->portalPinned && !portal->autoHeld)
1217 : {
1218 : /*
1219 : * Doing transaction control, especially abort, inside a cursor
1220 : * loop that is not read-only, for example using UPDATE ...
1221 : * RETURNING, has weird semantics issues. Also, this
1222 : * implementation wouldn't work, because such portals cannot be
1223 : * held. (The core grammar enforces that only SELECT statements
1224 : * can drive a cursor, but for example PL/pgSQL does not restrict
1838 peter_e 1225 ECB : * it.)
1226 : */
1838 peter_e 1227 GIC 19 : if (portal->strategy != PORTAL_ONE_SELECT)
1228 1 : ereport(ERROR,
1229 : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
1230 : errmsg("cannot perform transaction commands inside a cursor loop that is not read-only")));
1903 peter_e 1231 ECB :
1451 tgl 1232 EUB : /* Verify it's in a suitable state to be held */
1451 tgl 1233 GIC 18 : if (portal->status != PORTAL_READY)
1451 tgl 1234 LBC 0 : elog(ERROR, "pinned portal is not ready to be auto-held");
1451 tgl 1235 ECB :
1838 peter_e 1236 GIC 18 : HoldPortal(portal);
1451 tgl 1237 16 : portal->autoHeld = true;
1838 peter_e 1238 ECB : }
1239 : }
1903 peter_e 1240 GIC 2200 : }
1241 :
1242 : /*
1243 : * Drop the outer active snapshots for all portals, so that no snapshots
1244 : * remain active.
1245 : *
1246 : * Like HoldPinnedPortals, this must be called when initiating a COMMIT or
1247 : * ROLLBACK inside a procedure. This has to be separate from that since it
1248 : * should not be run until we're done with steps that are likely to fail.
1249 : *
1250 : * It's tempting to fold this into PreCommit_Portals, but to do so, we'd
1251 : * need to clean up snapshot management in VACUUM and perhaps other places.
688 tgl 1252 ECB : */
1253 : void
688 tgl 1254 GIC 2200 : ForgetPortalSnapshots(void)
1255 : {
688 tgl 1256 ECB : HASH_SEQ_STATUS status;
1257 : PortalHashEnt *hentry;
688 tgl 1258 GIC 2200 : int numPortalSnaps = 0;
1259 2200 : int numActiveSnaps = 0;
688 tgl 1260 ECB :
1261 : /* First, scan PortalHashTable and clear portalSnapshot fields */
688 tgl 1262 CBC 2200 : hash_seq_init(&status, PortalHashTable);
1263 :
1264 6663 : while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
1265 : {
1266 2263 : Portal portal = hentry->portal;
1267 :
1268 2263 : if (portal->portalSnapshot != NULL)
688 tgl 1269 ECB : {
688 tgl 1270 GIC 2200 : portal->portalSnapshot = NULL;
1271 2200 : numPortalSnaps++;
1272 : }
1273 : /* portal->holdSnapshot will be cleaned up in PreCommit_Portals */
1274 : }
1275 :
1276 : /*
1277 : * Now, pop all the active snapshots, which should be just those that were
1278 : * portal snapshots. Ideally we'd drive this directly off the portal
1279 : * scan, but there's no good way to visit the portals in the correct
688 tgl 1280 ECB : * order. So just cross-check after the fact.
1281 : */
688 tgl 1282 CBC 4400 : while (ActiveSnapshotSet())
688 tgl 1283 ECB : {
688 tgl 1284 GIC 2200 : PopActiveSnapshot();
1285 2200 : numActiveSnaps++;
688 tgl 1286 ECB : }
688 tgl 1287 EUB :
688 tgl 1288 GIC 2200 : if (numPortalSnaps != numActiveSnaps)
688 tgl 1289 LBC 0 : elog(ERROR, "portal snapshots (%d) did not account for all active snapshots (%d)",
1290 : numPortalSnaps, numActiveSnaps);
688 tgl 1291 GIC 2200 : }
|