/src/postgres/src/backend/utils/mmgr/portalmem.c
Line | Count | Source |
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-2026, 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 "commands/portalcmds.h" |
23 | | #include "funcapi.h" |
24 | | #include "miscadmin.h" |
25 | | #include "storage/ipc.h" |
26 | | #include "utils/builtins.h" |
27 | | #include "utils/hsearch.h" |
28 | | #include "utils/memutils.h" |
29 | | #include "utils/snapmgr.h" |
30 | | #include "utils/timestamp.h" |
31 | | #include "utils/tuplestore.h" |
32 | | |
33 | | /* |
34 | | * Estimate of the maximum number of open portals a user would have, |
35 | | * used in initially sizing the PortalHashTable in EnablePortalManager(). |
36 | | * Since the hash table can expand, there's no need to make this overly |
37 | | * generous, and keeping it small avoids unnecessary overhead in the |
38 | | * hash_seq_search() calls executed during transaction end. |
39 | | */ |
40 | 0 | #define PORTALS_PER_USER 16 |
41 | | |
42 | | |
43 | | /* ---------------- |
44 | | * Global state |
45 | | * ---------------- |
46 | | */ |
47 | | |
48 | 0 | #define MAX_PORTALNAME_LEN NAMEDATALEN |
49 | | |
50 | | typedef struct portalhashent |
51 | | { |
52 | | char portalname[MAX_PORTALNAME_LEN]; |
53 | | Portal portal; |
54 | | } PortalHashEnt; |
55 | | |
56 | | static HTAB *PortalHashTable = NULL; |
57 | | |
58 | 0 | #define PortalHashTableLookup(NAME, PORTAL) \ |
59 | 0 | do { \ |
60 | 0 | PortalHashEnt *hentry; \ |
61 | 0 | \ |
62 | 0 | hentry = (PortalHashEnt *) hash_search(PortalHashTable, \ |
63 | 0 | (NAME), HASH_FIND, NULL); \ |
64 | 0 | if (hentry) \ |
65 | 0 | PORTAL = hentry->portal; \ |
66 | 0 | else \ |
67 | 0 | PORTAL = NULL; \ |
68 | 0 | } while(0) |
69 | | |
70 | 0 | #define PortalHashTableInsert(PORTAL, NAME) \ |
71 | 0 | do { \ |
72 | 0 | PortalHashEnt *hentry; bool found; \ |
73 | 0 | \ |
74 | 0 | hentry = (PortalHashEnt *) hash_search(PortalHashTable, \ |
75 | 0 | (NAME), HASH_ENTER, &found); \ |
76 | 0 | if (found) \ |
77 | 0 | elog(ERROR, "duplicate portal name"); \ |
78 | 0 | hentry->portal = PORTAL; \ |
79 | 0 | /* To avoid duplicate storage, make PORTAL->name point to htab entry */ \ |
80 | 0 | PORTAL->name = hentry->portalname; \ |
81 | 0 | } while(0) |
82 | | |
83 | 0 | #define PortalHashTableDelete(PORTAL) \ |
84 | 0 | do { \ |
85 | 0 | PortalHashEnt *hentry; \ |
86 | 0 | \ |
87 | 0 | hentry = (PortalHashEnt *) hash_search(PortalHashTable, \ |
88 | 0 | PORTAL->name, HASH_REMOVE, NULL); \ |
89 | 0 | if (hentry == NULL) \ |
90 | 0 | elog(WARNING, "trying to delete portal name that does not exist"); \ |
91 | 0 | } while(0) |
92 | | |
93 | | static MemoryContext TopPortalContext = NULL; |
94 | | |
95 | | |
96 | | /* ---------------------------------------------------------------- |
97 | | * public portal interface functions |
98 | | * ---------------------------------------------------------------- |
99 | | */ |
100 | | |
101 | | /* |
102 | | * EnablePortalManager |
103 | | * Enables the portal management module at backend startup. |
104 | | */ |
105 | | void |
106 | | EnablePortalManager(void) |
107 | 0 | { |
108 | 0 | HASHCTL ctl; |
109 | |
|
110 | 0 | Assert(TopPortalContext == NULL); |
111 | |
|
112 | 0 | TopPortalContext = AllocSetContextCreate(TopMemoryContext, |
113 | 0 | "TopPortalContext", |
114 | 0 | ALLOCSET_DEFAULT_SIZES); |
115 | |
|
116 | 0 | ctl.keysize = MAX_PORTALNAME_LEN; |
117 | 0 | ctl.entrysize = sizeof(PortalHashEnt); |
118 | | |
119 | | /* |
120 | | * use PORTALS_PER_USER as a guess of how many hash table entries to |
121 | | * create, initially |
122 | | */ |
123 | 0 | PortalHashTable = hash_create("Portal hash", PORTALS_PER_USER, |
124 | 0 | &ctl, HASH_ELEM | HASH_STRINGS); |
125 | 0 | } |
126 | | |
127 | | /* |
128 | | * GetPortalByName |
129 | | * Returns a portal given a portal name, or NULL if name not found. |
130 | | */ |
131 | | Portal |
132 | | GetPortalByName(const char *name) |
133 | 0 | { |
134 | 0 | Portal portal; |
135 | |
|
136 | 0 | if (name) |
137 | 0 | PortalHashTableLookup(name, portal); |
138 | 0 | else |
139 | 0 | portal = NULL; |
140 | |
|
141 | 0 | return portal; |
142 | 0 | } |
143 | | |
144 | | /* |
145 | | * PortalGetPrimaryStmt |
146 | | * Get the "primary" stmt within a portal, ie, the one marked canSetTag. |
147 | | * |
148 | | * Returns NULL if no such stmt. If multiple PlannedStmt structs within the |
149 | | * portal are marked canSetTag, returns the first one. Neither of these |
150 | | * cases should occur in present usages of this function. |
151 | | */ |
152 | | PlannedStmt * |
153 | | PortalGetPrimaryStmt(Portal portal) |
154 | 0 | { |
155 | 0 | ListCell *lc; |
156 | |
|
157 | 0 | foreach(lc, portal->stmts) |
158 | 0 | { |
159 | 0 | PlannedStmt *stmt = lfirst_node(PlannedStmt, lc); |
160 | |
|
161 | 0 | if (stmt->canSetTag) |
162 | 0 | return stmt; |
163 | 0 | } |
164 | 0 | return NULL; |
165 | 0 | } |
166 | | |
167 | | /* |
168 | | * CreatePortal |
169 | | * Returns a new portal given a name. |
170 | | * |
171 | | * allowDup: if true, automatically drop any pre-existing portal of the |
172 | | * same name (if false, an error is raised). |
173 | | * |
174 | | * dupSilent: if true, don't even emit a WARNING. |
175 | | */ |
176 | | Portal |
177 | | CreatePortal(const char *name, bool allowDup, bool dupSilent) |
178 | 0 | { |
179 | 0 | Portal portal; |
180 | |
|
181 | 0 | Assert(name); |
182 | |
|
183 | 0 | portal = GetPortalByName(name); |
184 | 0 | if (PortalIsValid(portal)) |
185 | 0 | { |
186 | 0 | if (!allowDup) |
187 | 0 | ereport(ERROR, |
188 | 0 | (errcode(ERRCODE_DUPLICATE_CURSOR), |
189 | 0 | errmsg("cursor \"%s\" already exists", name))); |
190 | 0 | if (!dupSilent) |
191 | 0 | ereport(WARNING, |
192 | 0 | (errcode(ERRCODE_DUPLICATE_CURSOR), |
193 | 0 | errmsg("closing existing cursor \"%s\"", |
194 | 0 | name))); |
195 | 0 | PortalDrop(portal, false); |
196 | 0 | } |
197 | | |
198 | | /* make new portal structure */ |
199 | 0 | portal = (Portal) MemoryContextAllocZero(TopPortalContext, sizeof *portal); |
200 | | |
201 | | /* initialize portal context; typically it won't store much */ |
202 | 0 | portal->portalContext = AllocSetContextCreate(TopPortalContext, |
203 | 0 | "PortalContext", |
204 | 0 | ALLOCSET_SMALL_SIZES); |
205 | | |
206 | | /* create a resource owner for the portal */ |
207 | 0 | portal->resowner = ResourceOwnerCreate(CurTransactionResourceOwner, |
208 | 0 | "Portal"); |
209 | | |
210 | | /* initialize portal fields that don't start off zero */ |
211 | 0 | portal->status = PORTAL_NEW; |
212 | 0 | portal->cleanup = PortalCleanup; |
213 | 0 | portal->createSubid = GetCurrentSubTransactionId(); |
214 | 0 | portal->activeSubid = portal->createSubid; |
215 | 0 | portal->createLevel = GetCurrentTransactionNestLevel(); |
216 | 0 | portal->strategy = PORTAL_MULTI_QUERY; |
217 | 0 | portal->cursorOptions = CURSOR_OPT_NO_SCROLL; |
218 | 0 | portal->atStart = true; |
219 | 0 | portal->atEnd = true; /* disallow fetches until query is set */ |
220 | 0 | portal->visible = true; |
221 | 0 | portal->creation_time = GetCurrentStatementStartTimestamp(); |
222 | | |
223 | | /* put portal in table (sets portal->name) */ |
224 | 0 | PortalHashTableInsert(portal, name); |
225 | | |
226 | | /* for named portals reuse portal->name copy */ |
227 | 0 | MemoryContextSetIdentifier(portal->portalContext, portal->name[0] ? portal->name : "<unnamed>"); |
228 | |
|
229 | 0 | return portal; |
230 | 0 | } |
231 | | |
232 | | /* |
233 | | * CreateNewPortal |
234 | | * Create a new portal, assigning it a random nonconflicting name. |
235 | | */ |
236 | | Portal |
237 | | CreateNewPortal(void) |
238 | 0 | { |
239 | 0 | static unsigned int unnamed_portal_count = 0; |
240 | |
|
241 | 0 | char portalname[MAX_PORTALNAME_LEN]; |
242 | | |
243 | | /* Select a nonconflicting name */ |
244 | 0 | for (;;) |
245 | 0 | { |
246 | 0 | unnamed_portal_count++; |
247 | 0 | sprintf(portalname, "<unnamed portal %u>", unnamed_portal_count); |
248 | 0 | if (GetPortalByName(portalname) == NULL) |
249 | 0 | break; |
250 | 0 | } |
251 | |
|
252 | 0 | return CreatePortal(portalname, false, false); |
253 | 0 | } |
254 | | |
255 | | /* |
256 | | * PortalDefineQuery |
257 | | * A simple subroutine to establish a portal's query. |
258 | | * |
259 | | * Notes: as of PG 8.4, caller MUST supply a sourceText string; it is not |
260 | | * allowed anymore to pass NULL. (If you really don't have source text, |
261 | | * you can pass a constant string, perhaps "(query not available)".) |
262 | | * |
263 | | * commandTag shall be NULL if and only if the original query string |
264 | | * (before rewriting) was an empty string. Also, the passed commandTag must |
265 | | * be a pointer to a constant string, since it is not copied. |
266 | | * |
267 | | * If cplan is provided, then it is a cached plan containing the stmts, and |
268 | | * the caller must have done GetCachedPlan(), causing a refcount increment. |
269 | | * The refcount will be released when the portal is destroyed. |
270 | | * |
271 | | * If cplan is NULL, then it is the caller's responsibility to ensure that |
272 | | * the passed plan trees have adequate lifetime. Typically this is done by |
273 | | * copying them into the portal's context. |
274 | | * |
275 | | * The caller is also responsible for ensuring that the passed prepStmtName |
276 | | * (if not NULL) and sourceText have adequate lifetime. |
277 | | * |
278 | | * NB: this function mustn't do much beyond storing the passed values; in |
279 | | * particular don't do anything that risks elog(ERROR). If that were to |
280 | | * happen here before storing the cplan reference, we'd leak the plancache |
281 | | * refcount that the caller is trying to hand off to us. |
282 | | */ |
283 | | void |
284 | | PortalDefineQuery(Portal portal, |
285 | | const char *prepStmtName, |
286 | | const char *sourceText, |
287 | | CommandTag commandTag, |
288 | | List *stmts, |
289 | | CachedPlan *cplan) |
290 | 0 | { |
291 | 0 | Assert(PortalIsValid(portal)); |
292 | 0 | Assert(portal->status == PORTAL_NEW); |
293 | |
|
294 | 0 | Assert(sourceText != NULL); |
295 | 0 | Assert(commandTag != CMDTAG_UNKNOWN || stmts == NIL); |
296 | |
|
297 | 0 | portal->prepStmtName = prepStmtName; |
298 | 0 | portal->sourceText = sourceText; |
299 | 0 | portal->commandTag = commandTag; |
300 | 0 | SetQueryCompletion(&portal->qc, commandTag, 0); |
301 | 0 | portal->stmts = stmts; |
302 | 0 | portal->cplan = cplan; |
303 | 0 | portal->status = PORTAL_DEFINED; |
304 | 0 | } |
305 | | |
306 | | /* |
307 | | * PortalReleaseCachedPlan |
308 | | * Release a portal's reference to its cached plan, if any. |
309 | | */ |
310 | | static void |
311 | | PortalReleaseCachedPlan(Portal portal) |
312 | 0 | { |
313 | 0 | if (portal->cplan) |
314 | 0 | { |
315 | 0 | ReleaseCachedPlan(portal->cplan, NULL); |
316 | 0 | 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 | | */ |
323 | 0 | portal->stmts = NIL; |
324 | 0 | } |
325 | 0 | } |
326 | | |
327 | | /* |
328 | | * PortalCreateHoldStore |
329 | | * Create the tuplestore for a portal. |
330 | | */ |
331 | | void |
332 | | PortalCreateHoldStore(Portal portal) |
333 | 0 | { |
334 | 0 | MemoryContext oldcxt; |
335 | |
|
336 | 0 | Assert(portal->holdContext == NULL); |
337 | 0 | Assert(portal->holdStore == NULL); |
338 | 0 | 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 | | */ |
344 | 0 | portal->holdContext = |
345 | 0 | AllocSetContextCreate(TopPortalContext, |
346 | 0 | "PortalHoldContext", |
347 | 0 | 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 | | */ |
355 | 0 | oldcxt = MemoryContextSwitchTo(portal->holdContext); |
356 | |
|
357 | 0 | portal->holdStore = |
358 | 0 | tuplestore_begin_heap(portal->cursorOptions & CURSOR_OPT_SCROLL, |
359 | 0 | true, work_mem); |
360 | |
|
361 | 0 | MemoryContextSwitchTo(oldcxt); |
362 | 0 | } |
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 |
372 | | PinPortal(Portal portal) |
373 | 0 | { |
374 | 0 | if (portal->portalPinned) |
375 | 0 | elog(ERROR, "portal already pinned"); |
376 | | |
377 | 0 | portal->portalPinned = true; |
378 | 0 | } |
379 | | |
380 | | void |
381 | | UnpinPortal(Portal portal) |
382 | 0 | { |
383 | 0 | if (!portal->portalPinned) |
384 | 0 | elog(ERROR, "portal not pinned"); |
385 | | |
386 | 0 | portal->portalPinned = false; |
387 | 0 | } |
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 |
396 | | MarkPortalActive(Portal portal) |
397 | 0 | { |
398 | | /* For safety, this is a runtime test not just an Assert */ |
399 | 0 | if (portal->status != PORTAL_READY) |
400 | 0 | ereport(ERROR, |
401 | 0 | (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
402 | 0 | errmsg("portal \"%s\" cannot be run", portal->name))); |
403 | | /* Perform the state transition */ |
404 | 0 | portal->status = PORTAL_ACTIVE; |
405 | 0 | portal->activeSubid = GetCurrentSubTransactionId(); |
406 | 0 | } |
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 |
415 | | MarkPortalDone(Portal portal) |
416 | 0 | { |
417 | | /* Perform the state transition */ |
418 | 0 | Assert(portal->status == PORTAL_ACTIVE); |
419 | 0 | 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 | | */ |
429 | 0 | if (portal->cleanup) |
430 | 0 | { |
431 | 0 | portal->cleanup(portal); |
432 | 0 | portal->cleanup = NULL; |
433 | 0 | } |
434 | 0 | } |
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 | | MarkPortalFailed(Portal portal) |
444 | 0 | { |
445 | | /* Perform the state transition */ |
446 | 0 | Assert(portal->status != PORTAL_DONE); |
447 | 0 | 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 | | */ |
457 | 0 | if (portal->cleanup) |
458 | 0 | { |
459 | 0 | portal->cleanup(portal); |
460 | 0 | portal->cleanup = NULL; |
461 | 0 | } |
462 | 0 | } |
463 | | |
464 | | /* |
465 | | * PortalDrop |
466 | | * Destroy the portal. |
467 | | */ |
468 | | void |
469 | | PortalDrop(Portal portal, bool isTopCommit) |
470 | 0 | { |
471 | 0 | Assert(PortalIsValid(portal)); |
472 | | |
473 | | /* |
474 | | * Don't allow dropping a pinned portal, it's still needed by whoever |
475 | | * pinned it. |
476 | | */ |
477 | 0 | if (portal->portalPinned) |
478 | 0 | ereport(ERROR, |
479 | 0 | (errcode(ERRCODE_INVALID_CURSOR_STATE), |
480 | 0 | errmsg("cannot drop pinned portal \"%s\"", portal->name))); |
481 | | |
482 | | /* |
483 | | * Not sure if the PORTAL_ACTIVE case can validly happen or not... |
484 | | */ |
485 | 0 | if (portal->status == PORTAL_ACTIVE) |
486 | 0 | ereport(ERROR, |
487 | 0 | (errcode(ERRCODE_INVALID_CURSOR_STATE), |
488 | 0 | 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 | | */ |
501 | 0 | if (portal->cleanup) |
502 | 0 | { |
503 | 0 | portal->cleanup(portal); |
504 | 0 | portal->cleanup = NULL; |
505 | 0 | } |
506 | | |
507 | | /* There shouldn't be an active snapshot anymore, except after error */ |
508 | 0 | 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 | | */ |
516 | 0 | PortalHashTableDelete(portal); |
517 | | |
518 | | /* drop cached plan reference, if any */ |
519 | 0 | 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 | | */ |
527 | 0 | if (portal->holdSnapshot) |
528 | 0 | { |
529 | 0 | if (portal->resowner) |
530 | 0 | UnregisterSnapshotFromOwner(portal->holdSnapshot, |
531 | 0 | portal->resowner); |
532 | 0 | portal->holdSnapshot = NULL; |
533 | 0 | } |
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 | | */ |
558 | 0 | if (portal->resowner && |
559 | 0 | (!isTopCommit || portal->status == PORTAL_FAILED)) |
560 | 0 | { |
561 | 0 | bool isCommit = (portal->status != PORTAL_FAILED); |
562 | |
|
563 | 0 | ResourceOwnerRelease(portal->resowner, |
564 | 0 | RESOURCE_RELEASE_BEFORE_LOCKS, |
565 | 0 | isCommit, false); |
566 | 0 | ResourceOwnerRelease(portal->resowner, |
567 | 0 | RESOURCE_RELEASE_LOCKS, |
568 | 0 | isCommit, false); |
569 | 0 | ResourceOwnerRelease(portal->resowner, |
570 | 0 | RESOURCE_RELEASE_AFTER_LOCKS, |
571 | 0 | isCommit, false); |
572 | 0 | ResourceOwnerDelete(portal->resowner); |
573 | 0 | } |
574 | 0 | 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 | | */ |
581 | 0 | if (portal->holdStore) |
582 | 0 | { |
583 | 0 | MemoryContext oldcontext; |
584 | |
|
585 | 0 | oldcontext = MemoryContextSwitchTo(portal->holdContext); |
586 | 0 | tuplestore_end(portal->holdStore); |
587 | 0 | MemoryContextSwitchTo(oldcontext); |
588 | 0 | portal->holdStore = NULL; |
589 | 0 | } |
590 | | |
591 | | /* delete tuplestore storage, if any */ |
592 | 0 | if (portal->holdContext) |
593 | 0 | MemoryContextDelete(portal->holdContext); |
594 | | |
595 | | /* release subsidiary storage */ |
596 | 0 | MemoryContextDelete(portal->portalContext); |
597 | | |
598 | | /* release portal struct (it's in TopPortalContext) */ |
599 | 0 | pfree(portal); |
600 | 0 | } |
601 | | |
602 | | /* |
603 | | * Delete all declared cursors. |
604 | | * |
605 | | * Used by commands: CLOSE ALL, DISCARD ALL |
606 | | */ |
607 | | void |
608 | | PortalHashTableDeleteAll(void) |
609 | 0 | { |
610 | 0 | HASH_SEQ_STATUS status; |
611 | 0 | PortalHashEnt *hentry; |
612 | |
|
613 | 0 | if (PortalHashTable == NULL) |
614 | 0 | return; |
615 | | |
616 | 0 | hash_seq_init(&status, PortalHashTable); |
617 | 0 | while ((hentry = hash_seq_search(&status)) != NULL) |
618 | 0 | { |
619 | 0 | Portal portal = hentry->portal; |
620 | | |
621 | | /* Can't close the active portal (the one running the command) */ |
622 | 0 | if (portal->status == PORTAL_ACTIVE) |
623 | 0 | continue; |
624 | | |
625 | 0 | PortalDrop(portal, false); |
626 | | |
627 | | /* Restart the iteration in case that led to other drops */ |
628 | 0 | hash_seq_term(&status); |
629 | 0 | hash_seq_init(&status, PortalHashTable); |
630 | 0 | } |
631 | 0 | } |
632 | | |
633 | | /* |
634 | | * "Hold" a portal. Prepare it for access by later transactions. |
635 | | */ |
636 | | static void |
637 | | HoldPortal(Portal portal) |
638 | 0 | { |
639 | | /* |
640 | | * Note that PersistHoldablePortal() must release all resources used by |
641 | | * the portal that are local to the creating transaction. |
642 | | */ |
643 | 0 | PortalCreateHoldStore(portal); |
644 | 0 | PersistHoldablePortal(portal); |
645 | | |
646 | | /* drop cached plan reference, if any */ |
647 | 0 | 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 | 0 | portal->resowner = NULL; |
655 | | |
656 | | /* |
657 | | * Having successfully exported the holdable cursor, mark it as not |
658 | | * belonging to this transaction. |
659 | | */ |
660 | 0 | portal->createSubid = InvalidSubTransactionId; |
661 | 0 | portal->activeSubid = InvalidSubTransactionId; |
662 | 0 | portal->createLevel = 0; |
663 | 0 | } |
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 |
678 | | PreCommit_Portals(bool isPrepare) |
679 | 0 | { |
680 | 0 | bool result = false; |
681 | 0 | HASH_SEQ_STATUS status; |
682 | 0 | PortalHashEnt *hentry; |
683 | |
|
684 | 0 | hash_seq_init(&status, PortalHashTable); |
685 | |
|
686 | 0 | while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL) |
687 | 0 | { |
688 | 0 | 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 | | */ |
695 | 0 | if (portal->portalPinned && !portal->autoHeld) |
696 | 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 | | */ |
708 | 0 | if (portal->status == PORTAL_ACTIVE) |
709 | 0 | { |
710 | 0 | if (portal->holdSnapshot) |
711 | 0 | { |
712 | 0 | if (portal->resowner) |
713 | 0 | UnregisterSnapshotFromOwner(portal->holdSnapshot, |
714 | 0 | portal->resowner); |
715 | 0 | portal->holdSnapshot = NULL; |
716 | 0 | } |
717 | 0 | portal->resowner = NULL; |
718 | | /* Clear portalSnapshot too, for cleanliness */ |
719 | 0 | portal->portalSnapshot = NULL; |
720 | 0 | continue; |
721 | 0 | } |
722 | | |
723 | | /* Is it a holdable portal created in the current xact? */ |
724 | 0 | if ((portal->cursorOptions & CURSOR_OPT_HOLD) && |
725 | 0 | portal->createSubid != InvalidSubTransactionId && |
726 | 0 | portal->status == PORTAL_READY) |
727 | 0 | { |
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 | | */ |
736 | 0 | if (isPrepare) |
737 | 0 | ereport(ERROR, |
738 | 0 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
739 | 0 | errmsg("cannot PREPARE a transaction that has created a cursor WITH HOLD"))); |
740 | | |
741 | 0 | HoldPortal(portal); |
742 | | |
743 | | /* Report we changed state */ |
744 | 0 | result = true; |
745 | 0 | } |
746 | 0 | else if (portal->createSubid == InvalidSubTransactionId) |
747 | 0 | { |
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 | | */ |
752 | 0 | continue; |
753 | 0 | } |
754 | 0 | else |
755 | 0 | { |
756 | | /* Zap all non-holdable portals */ |
757 | 0 | PortalDrop(portal, true); |
758 | | |
759 | | /* Report we changed state */ |
760 | 0 | result = true; |
761 | 0 | } |
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 | | */ |
768 | 0 | hash_seq_term(&status); |
769 | 0 | hash_seq_init(&status, PortalHashTable); |
770 | 0 | } |
771 | | |
772 | 0 | return result; |
773 | 0 | } |
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 |
782 | | AtAbort_Portals(void) |
783 | 0 | { |
784 | 0 | HASH_SEQ_STATUS status; |
785 | 0 | PortalHashEnt *hentry; |
786 | |
|
787 | 0 | hash_seq_init(&status, PortalHashTable); |
788 | |
|
789 | 0 | while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL) |
790 | 0 | { |
791 | 0 | 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 | | */ |
797 | 0 | if (portal->status == PORTAL_ACTIVE && shmem_exit_inprogress) |
798 | 0 | MarkPortalFailed(portal); |
799 | | |
800 | | /* |
801 | | * Do nothing else to cursors held over from a previous transaction. |
802 | | */ |
803 | 0 | if (portal->createSubid == InvalidSubTransactionId) |
804 | 0 | 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 | | */ |
811 | 0 | if (portal->autoHeld) |
812 | 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 | | */ |
820 | 0 | if (portal->status == PORTAL_READY) |
821 | 0 | MarkPortalFailed(portal); |
822 | | |
823 | | /* |
824 | | * Allow portalcmds.c to clean up the state it knows about, if we |
825 | | * haven't already. |
826 | | */ |
827 | 0 | if (portal->cleanup) |
828 | 0 | { |
829 | 0 | portal->cleanup(portal); |
830 | 0 | portal->cleanup = NULL; |
831 | 0 | } |
832 | | |
833 | | /* drop cached plan reference, if any */ |
834 | 0 | 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 | | */ |
841 | 0 | 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 | | */ |
849 | 0 | if (portal->status != PORTAL_ACTIVE) |
850 | 0 | MemoryContextDeleteChildren(portal->portalContext); |
851 | 0 | } |
852 | 0 | } |
853 | | |
854 | | /* |
855 | | * Post-abort cleanup for portals. |
856 | | * |
857 | | * Delete all portals not held over from prior transactions. |
858 | | */ |
859 | | void |
860 | | AtCleanup_Portals(void) |
861 | | { |
862 | | HASH_SEQ_STATUS status; |
863 | | PortalHashEnt *hentry; |
864 | | |
865 | | hash_seq_init(&status, PortalHashTable); |
866 | | |
867 | | while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL) |
868 | | { |
869 | | Portal portal = hentry->portal; |
870 | | |
871 | | /* |
872 | | * Do not touch active portals --- this can only happen in the case of |
873 | | * a multi-transaction command. |
874 | | */ |
875 | | if (portal->status == PORTAL_ACTIVE) |
876 | | continue; |
877 | | |
878 | | /* |
879 | | * Do nothing to cursors held over from a previous transaction or |
880 | | * auto-held ones. |
881 | | */ |
882 | | if (portal->createSubid == InvalidSubTransactionId || portal->autoHeld) |
883 | | { |
884 | | Assert(portal->status != PORTAL_ACTIVE); |
885 | | Assert(portal->resowner == NULL); |
886 | | continue; |
887 | | } |
888 | | |
889 | | /* |
890 | | * If a portal is still pinned, forcibly unpin it. PortalDrop will not |
891 | | * let us drop the portal otherwise. Whoever pinned the portal was |
892 | | * interrupted by the abort too and won't try to use it anymore. |
893 | | */ |
894 | | if (portal->portalPinned) |
895 | | portal->portalPinned = false; |
896 | | |
897 | | /* |
898 | | * We had better not call any user-defined code during cleanup, so if |
899 | | * the cleanup hook hasn't been run yet, too bad; we'll just skip it. |
900 | | */ |
901 | | if (portal->cleanup) |
902 | | { |
903 | | elog(WARNING, "skipping cleanup for portal \"%s\"", portal->name); |
904 | | portal->cleanup = NULL; |
905 | | } |
906 | | |
907 | | /* Zap it. */ |
908 | | PortalDrop(portal, false); |
909 | | } |
910 | | } |
911 | | |
912 | | /* |
913 | | * Portal-related cleanup when we return to the main loop on error. |
914 | | * |
915 | | * This is different from the cleanup at transaction abort. Auto-held portals |
916 | | * are cleaned up on error but not on transaction abort. |
917 | | */ |
918 | | void |
919 | | PortalErrorCleanup(void) |
920 | 0 | { |
921 | 0 | HASH_SEQ_STATUS status; |
922 | 0 | PortalHashEnt *hentry; |
923 | |
|
924 | 0 | hash_seq_init(&status, PortalHashTable); |
925 | |
|
926 | 0 | while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL) |
927 | 0 | { |
928 | 0 | Portal portal = hentry->portal; |
929 | |
|
930 | 0 | if (portal->autoHeld) |
931 | 0 | { |
932 | 0 | portal->portalPinned = false; |
933 | 0 | PortalDrop(portal, false); |
934 | 0 | } |
935 | 0 | } |
936 | 0 | } |
937 | | |
938 | | /* |
939 | | * Pre-subcommit processing for portals. |
940 | | * |
941 | | * Reassign portals created or used in the current subtransaction to the |
942 | | * parent subtransaction. |
943 | | */ |
944 | | void |
945 | | AtSubCommit_Portals(SubTransactionId mySubid, |
946 | | SubTransactionId parentSubid, |
947 | | int parentLevel, |
948 | | ResourceOwner parentXactOwner) |
949 | 0 | { |
950 | 0 | HASH_SEQ_STATUS status; |
951 | 0 | PortalHashEnt *hentry; |
952 | |
|
953 | 0 | hash_seq_init(&status, PortalHashTable); |
954 | |
|
955 | 0 | while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL) |
956 | 0 | { |
957 | 0 | Portal portal = hentry->portal; |
958 | |
|
959 | 0 | if (portal->createSubid == mySubid) |
960 | 0 | { |
961 | 0 | portal->createSubid = parentSubid; |
962 | 0 | portal->createLevel = parentLevel; |
963 | 0 | if (portal->resowner) |
964 | 0 | ResourceOwnerNewParent(portal->resowner, parentXactOwner); |
965 | 0 | } |
966 | 0 | if (portal->activeSubid == mySubid) |
967 | 0 | portal->activeSubid = parentSubid; |
968 | 0 | } |
969 | 0 | } |
970 | | |
971 | | /* |
972 | | * Subtransaction abort handling for portals. |
973 | | * |
974 | | * Deactivate portals created or used during the failed subtransaction. |
975 | | * Note that per AtSubCommit_Portals, this will catch portals created/used |
976 | | * in descendants of the subtransaction too. |
977 | | * |
978 | | * We don't destroy any portals here; that's done in AtSubCleanup_Portals. |
979 | | */ |
980 | | void |
981 | | AtSubAbort_Portals(SubTransactionId mySubid, |
982 | | SubTransactionId parentSubid, |
983 | | ResourceOwner myXactOwner, |
984 | | ResourceOwner parentXactOwner) |
985 | 0 | { |
986 | 0 | HASH_SEQ_STATUS status; |
987 | 0 | PortalHashEnt *hentry; |
988 | |
|
989 | 0 | hash_seq_init(&status, PortalHashTable); |
990 | |
|
991 | 0 | while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL) |
992 | 0 | { |
993 | 0 | Portal portal = hentry->portal; |
994 | | |
995 | | /* Was it created in this subtransaction? */ |
996 | 0 | if (portal->createSubid != mySubid) |
997 | 0 | { |
998 | | /* No, but maybe it was used in this subtransaction? */ |
999 | 0 | if (portal->activeSubid == mySubid) |
1000 | 0 | { |
1001 | | /* Maintain activeSubid until the portal is removed */ |
1002 | 0 | portal->activeSubid = parentSubid; |
1003 | | |
1004 | | /* |
1005 | | * A MarkPortalActive() caller ran an upper-level portal in |
1006 | | * this subtransaction and left the portal ACTIVE. This can't |
1007 | | * happen, but force the portal into FAILED state for the same |
1008 | | * reasons discussed below. |
1009 | | * |
1010 | | * We assume we can get away without forcing upper-level READY |
1011 | | * portals to fail, even if they were run and then suspended. |
1012 | | * In theory a suspended upper-level portal could have |
1013 | | * acquired some references to objects that are about to be |
1014 | | * destroyed, but there should be sufficient defenses against |
1015 | | * such cases: the portal's original query cannot contain such |
1016 | | * references, and any references within, say, cached plans of |
1017 | | * PL/pgSQL functions are not from active queries and should |
1018 | | * be protected by revalidation logic. |
1019 | | */ |
1020 | 0 | if (portal->status == PORTAL_ACTIVE) |
1021 | 0 | MarkPortalFailed(portal); |
1022 | | |
1023 | | /* |
1024 | | * Also, if we failed it during the current subtransaction |
1025 | | * (either just above, or earlier), reattach its resource |
1026 | | * owner to the current subtransaction's resource owner, so |
1027 | | * that any resources it still holds will be released while |
1028 | | * cleaning up this subtransaction. This prevents some corner |
1029 | | * cases wherein we might get Asserts or worse while cleaning |
1030 | | * up objects created during the current subtransaction |
1031 | | * (because they're still referenced within this portal). |
1032 | | */ |
1033 | 0 | if (portal->status == PORTAL_FAILED && portal->resowner) |
1034 | 0 | { |
1035 | 0 | ResourceOwnerNewParent(portal->resowner, myXactOwner); |
1036 | 0 | portal->resowner = NULL; |
1037 | 0 | } |
1038 | 0 | } |
1039 | | /* Done if it wasn't created in this subtransaction */ |
1040 | 0 | continue; |
1041 | 0 | } |
1042 | | |
1043 | | /* |
1044 | | * Force any live portals of my own subtransaction into FAILED state. |
1045 | | * We have to do this because they might refer to objects created or |
1046 | | * changed in the failed subtransaction, leading to crashes within |
1047 | | * ExecutorEnd when portalcmds.c tries to close down the portal. |
1048 | | * Currently, every MarkPortalActive() caller ensures it updates the |
1049 | | * portal status again before relinquishing control, so ACTIVE can't |
1050 | | * happen here. If it does happen, dispose the portal like existing |
1051 | | * MarkPortalActive() callers would. |
1052 | | */ |
1053 | 0 | if (portal->status == PORTAL_READY || |
1054 | 0 | portal->status == PORTAL_ACTIVE) |
1055 | 0 | MarkPortalFailed(portal); |
1056 | | |
1057 | | /* |
1058 | | * Allow portalcmds.c to clean up the state it knows about, if we |
1059 | | * haven't already. |
1060 | | */ |
1061 | 0 | if (portal->cleanup) |
1062 | 0 | { |
1063 | 0 | portal->cleanup(portal); |
1064 | 0 | portal->cleanup = NULL; |
1065 | 0 | } |
1066 | | |
1067 | | /* drop cached plan reference, if any */ |
1068 | 0 | PortalReleaseCachedPlan(portal); |
1069 | | |
1070 | | /* |
1071 | | * Any resources belonging to the portal will be released in the |
1072 | | * upcoming transaction-wide cleanup; they will be gone before we run |
1073 | | * PortalDrop. |
1074 | | */ |
1075 | 0 | portal->resowner = NULL; |
1076 | | |
1077 | | /* |
1078 | | * Although we can't delete the portal data structure proper, we can |
1079 | | * release any memory in subsidiary contexts, such as executor state. |
1080 | | * The cleanup hook was the last thing that might have needed data |
1081 | | * there. |
1082 | | */ |
1083 | 0 | MemoryContextDeleteChildren(portal->portalContext); |
1084 | 0 | } |
1085 | 0 | } |
1086 | | |
1087 | | /* |
1088 | | * Post-subabort cleanup for portals. |
1089 | | * |
1090 | | * Drop all portals created in the failed subtransaction (but note that |
1091 | | * we will not drop any that were reassigned to the parent above). |
1092 | | */ |
1093 | | void |
1094 | | AtSubCleanup_Portals(SubTransactionId mySubid) |
1095 | | { |
1096 | | HASH_SEQ_STATUS status; |
1097 | | PortalHashEnt *hentry; |
1098 | | |
1099 | | hash_seq_init(&status, PortalHashTable); |
1100 | | |
1101 | | while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL) |
1102 | | { |
1103 | | Portal portal = hentry->portal; |
1104 | | |
1105 | | if (portal->createSubid != mySubid) |
1106 | | continue; |
1107 | | |
1108 | | /* |
1109 | | * If a portal is still pinned, forcibly unpin it. PortalDrop will not |
1110 | | * let us drop the portal otherwise. Whoever pinned the portal was |
1111 | | * interrupted by the abort too and won't try to use it anymore. |
1112 | | */ |
1113 | | if (portal->portalPinned) |
1114 | | portal->portalPinned = false; |
1115 | | |
1116 | | /* |
1117 | | * We had better not call any user-defined code during cleanup, so if |
1118 | | * the cleanup hook hasn't been run yet, too bad; we'll just skip it. |
1119 | | */ |
1120 | | if (portal->cleanup) |
1121 | | { |
1122 | | elog(WARNING, "skipping cleanup for portal \"%s\"", portal->name); |
1123 | | portal->cleanup = NULL; |
1124 | | } |
1125 | | |
1126 | | /* Zap it. */ |
1127 | | PortalDrop(portal, false); |
1128 | | } |
1129 | | } |
1130 | | |
1131 | | /* Find all available cursors */ |
1132 | | Datum |
1133 | | pg_cursor(PG_FUNCTION_ARGS) |
1134 | 0 | { |
1135 | 0 | ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; |
1136 | 0 | HASH_SEQ_STATUS hash_seq; |
1137 | 0 | PortalHashEnt *hentry; |
1138 | | |
1139 | | /* |
1140 | | * We put all the tuples into a tuplestore in one scan of the hashtable. |
1141 | | * This avoids any issue of the hashtable possibly changing between calls. |
1142 | | */ |
1143 | 0 | InitMaterializedSRF(fcinfo, 0); |
1144 | |
|
1145 | 0 | hash_seq_init(&hash_seq, PortalHashTable); |
1146 | 0 | while ((hentry = hash_seq_search(&hash_seq)) != NULL) |
1147 | 0 | { |
1148 | 0 | Portal portal = hentry->portal; |
1149 | 0 | Datum values[6]; |
1150 | 0 | bool nulls[6] = {0}; |
1151 | | |
1152 | | /* report only "visible" entries */ |
1153 | 0 | if (!portal->visible) |
1154 | 0 | continue; |
1155 | | /* also ignore it if PortalDefineQuery hasn't been called yet */ |
1156 | 0 | if (!portal->sourceText) |
1157 | 0 | continue; |
1158 | | |
1159 | 0 | values[0] = CStringGetTextDatum(portal->name); |
1160 | 0 | values[1] = CStringGetTextDatum(portal->sourceText); |
1161 | 0 | values[2] = BoolGetDatum(portal->cursorOptions & CURSOR_OPT_HOLD); |
1162 | 0 | values[3] = BoolGetDatum(portal->cursorOptions & CURSOR_OPT_BINARY); |
1163 | 0 | values[4] = BoolGetDatum(portal->cursorOptions & CURSOR_OPT_SCROLL); |
1164 | 0 | values[5] = TimestampTzGetDatum(portal->creation_time); |
1165 | |
|
1166 | 0 | tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); |
1167 | 0 | } |
1168 | |
|
1169 | 0 | return (Datum) 0; |
1170 | 0 | } |
1171 | | |
1172 | | bool |
1173 | | ThereAreNoReadyPortals(void) |
1174 | 0 | { |
1175 | 0 | HASH_SEQ_STATUS status; |
1176 | 0 | PortalHashEnt *hentry; |
1177 | |
|
1178 | 0 | hash_seq_init(&status, PortalHashTable); |
1179 | |
|
1180 | 0 | while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL) |
1181 | 0 | { |
1182 | 0 | Portal portal = hentry->portal; |
1183 | |
|
1184 | 0 | if (portal->status == PORTAL_READY) |
1185 | 0 | return false; |
1186 | 0 | } |
1187 | | |
1188 | 0 | return true; |
1189 | 0 | } |
1190 | | |
1191 | | /* |
1192 | | * Hold all pinned portals. |
1193 | | * |
1194 | | * When initiating a COMMIT or ROLLBACK inside a procedure, this must be |
1195 | | * called to protect internally-generated cursors from being dropped during |
1196 | | * the transaction shutdown. Currently, SPI calls this automatically; PLs |
1197 | | * that initiate COMMIT or ROLLBACK some other way are on the hook to do it |
1198 | | * themselves. (Note that we couldn't do this in, say, AtAbort_Portals |
1199 | | * because we need to run user-defined code while persisting a portal. |
1200 | | * It's too late to do that once transaction abort has started.) |
1201 | | * |
1202 | | * We protect such portals by converting them to held cursors. We mark them |
1203 | | * as "auto-held" so that exception exit knows to clean them up. (In normal, |
1204 | | * non-exception code paths, the PL needs to clean such portals itself, since |
1205 | | * transaction end won't do it anymore; but that should be normal practice |
1206 | | * anyway.) |
1207 | | */ |
1208 | | void |
1209 | | HoldPinnedPortals(void) |
1210 | 0 | { |
1211 | 0 | HASH_SEQ_STATUS status; |
1212 | 0 | PortalHashEnt *hentry; |
1213 | |
|
1214 | 0 | hash_seq_init(&status, PortalHashTable); |
1215 | |
|
1216 | 0 | while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL) |
1217 | 0 | { |
1218 | 0 | Portal portal = hentry->portal; |
1219 | |
|
1220 | 0 | if (portal->portalPinned && !portal->autoHeld) |
1221 | 0 | { |
1222 | | /* |
1223 | | * Doing transaction control, especially abort, inside a cursor |
1224 | | * loop that is not read-only, for example using UPDATE ... |
1225 | | * RETURNING, has weird semantics issues. Also, this |
1226 | | * implementation wouldn't work, because such portals cannot be |
1227 | | * held. (The core grammar enforces that only SELECT statements |
1228 | | * can drive a cursor, but for example PL/pgSQL does not restrict |
1229 | | * it.) |
1230 | | */ |
1231 | 0 | if (portal->strategy != PORTAL_ONE_SELECT) |
1232 | 0 | ereport(ERROR, |
1233 | 0 | (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
1234 | 0 | errmsg("cannot perform transaction commands inside a cursor loop that is not read-only"))); |
1235 | | |
1236 | | /* Verify it's in a suitable state to be held */ |
1237 | 0 | if (portal->status != PORTAL_READY) |
1238 | 0 | elog(ERROR, "pinned portal is not ready to be auto-held"); |
1239 | | |
1240 | 0 | HoldPortal(portal); |
1241 | 0 | portal->autoHeld = true; |
1242 | 0 | } |
1243 | 0 | } |
1244 | 0 | } |
1245 | | |
1246 | | /* |
1247 | | * Drop the outer active snapshots for all portals, so that no snapshots |
1248 | | * remain active. |
1249 | | * |
1250 | | * Like HoldPinnedPortals, this must be called when initiating a COMMIT or |
1251 | | * ROLLBACK inside a procedure. This has to be separate from that since it |
1252 | | * should not be run until we're done with steps that are likely to fail. |
1253 | | * |
1254 | | * It's tempting to fold this into PreCommit_Portals, but to do so, we'd |
1255 | | * need to clean up snapshot management in VACUUM and perhaps other places. |
1256 | | */ |
1257 | | void |
1258 | | ForgetPortalSnapshots(void) |
1259 | 0 | { |
1260 | 0 | HASH_SEQ_STATUS status; |
1261 | 0 | PortalHashEnt *hentry; |
1262 | 0 | int numPortalSnaps = 0; |
1263 | 0 | int numActiveSnaps = 0; |
1264 | | |
1265 | | /* First, scan PortalHashTable and clear portalSnapshot fields */ |
1266 | 0 | hash_seq_init(&status, PortalHashTable); |
1267 | |
|
1268 | 0 | while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL) |
1269 | 0 | { |
1270 | 0 | Portal portal = hentry->portal; |
1271 | |
|
1272 | 0 | if (portal->portalSnapshot != NULL) |
1273 | 0 | { |
1274 | 0 | portal->portalSnapshot = NULL; |
1275 | 0 | numPortalSnaps++; |
1276 | 0 | } |
1277 | | /* portal->holdSnapshot will be cleaned up in PreCommit_Portals */ |
1278 | 0 | } |
1279 | | |
1280 | | /* |
1281 | | * Now, pop all the active snapshots, which should be just those that were |
1282 | | * portal snapshots. Ideally we'd drive this directly off the portal |
1283 | | * scan, but there's no good way to visit the portals in the correct |
1284 | | * order. So just cross-check after the fact. |
1285 | | */ |
1286 | 0 | while (ActiveSnapshotSet()) |
1287 | 0 | { |
1288 | 0 | PopActiveSnapshot(); |
1289 | 0 | numActiveSnaps++; |
1290 | 0 | } |
1291 | |
|
1292 | 0 | if (numPortalSnaps != numActiveSnaps) |
1293 | 0 | elog(ERROR, "portal snapshots (%d) did not account for all active snapshots (%d)", |
1294 | 0 | numPortalSnaps, numActiveSnaps); |
1295 | 0 | } |