Age Owner TLA Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * pquery.c
4 : * POSTGRES process query command code
5 : *
6 : * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
7 : * Portions Copyright (c) 1994, Regents of the University of California
8 : *
9 : *
10 : * IDENTIFICATION
11 : * src/backend/tcop/pquery.c
12 : *
13 : *-------------------------------------------------------------------------
14 : */
15 :
16 : #include "postgres.h"
17 :
18 : #include <limits.h>
19 :
20 : #include "access/xact.h"
21 : #include "commands/prepare.h"
22 : #include "executor/tstoreReceiver.h"
23 : #include "miscadmin.h"
24 : #include "pg_trace.h"
25 : #include "tcop/pquery.h"
26 : #include "tcop/utility.h"
27 : #include "utils/memutils.h"
28 : #include "utils/snapmgr.h"
29 :
30 :
31 : /*
32 : * ActivePortal is the currently executing Portal (the most closely nested,
33 : * if there are several).
34 : */
35 : Portal ActivePortal = NULL;
36 :
37 :
38 : static void ProcessQuery(PlannedStmt *plan,
39 : const char *sourceText,
40 : ParamListInfo params,
41 : QueryEnvironment *queryEnv,
42 : DestReceiver *dest,
43 : QueryCompletion *qc);
44 : static void FillPortalStore(Portal portal, bool isTopLevel);
45 : static uint64 RunFromStore(Portal portal, ScanDirection direction, uint64 count,
46 : DestReceiver *dest);
47 : static uint64 PortalRunSelect(Portal portal, bool forward, long count,
48 : DestReceiver *dest);
49 : static void PortalRunUtility(Portal portal, PlannedStmt *pstmt,
50 : bool isTopLevel, bool setHoldSnapshot,
51 : DestReceiver *dest, QueryCompletion *qc);
52 : static void PortalRunMulti(Portal portal,
53 : bool isTopLevel, bool setHoldSnapshot,
54 : DestReceiver *dest, DestReceiver *altdest,
55 : QueryCompletion *qc);
56 : static uint64 DoPortalRunFetch(Portal portal,
57 : FetchDirection fdirection,
58 : long count,
59 : DestReceiver *dest);
60 : static void DoPortalRewind(Portal portal);
61 :
62 :
63 : /*
64 : * CreateQueryDesc
65 : */
66 : QueryDesc *
5624 bruce 67 CBC 266215 : CreateQueryDesc(PlannedStmt *plannedstmt,
68 : const char *sourceText,
69 : Snapshot snapshot,
70 : Snapshot crosscheck_snapshot,
71 : DestReceiver *dest,
72 : ParamListInfo params,
73 : QueryEnvironment *queryEnv,
74 : int instrument_options)
75 : {
9344 76 266215 : QueryDesc *qd = (QueryDesc *) palloc(sizeof(QueryDesc));
77 :
5892 tgl 78 266215 : qd->operation = plannedstmt->commandType; /* operation */
2118 79 266215 : qd->plannedstmt = plannedstmt; /* plan */
5050 bruce 80 266215 : qd->sourceText = sourceText; /* query text */
5445 alvherre 81 266215 : qd->snapshot = RegisterSnapshot(snapshot); /* snapshot */
82 : /* RI check snapshot */
83 266215 : qd->crosscheck_snapshot = RegisterSnapshot(crosscheck_snapshot);
9345 bruce 84 266215 : qd->dest = dest; /* output dest */
7430 tgl 85 266215 : qd->params = params; /* parameter values passed into query */
2200 kgrittn 86 266215 : qd->queryEnv = queryEnv;
2118 tgl 87 266215 : qd->instrument_options = instrument_options; /* instrumentation wanted? */
88 :
89 : /* null these fields until set by ExecutorStart */
7430 90 266215 : qd->tupDesc = NULL;
91 266215 : qd->estate = NULL;
92 266215 : qd->planstate = NULL;
5254 93 266215 : qd->totaltime = NULL;
94 :
95 : /* not yet executed */
2208 rhaas 96 266215 : qd->already_executed = false;
97 :
7430 tgl 98 266215 : return qd;
99 : }
100 :
101 : /*
102 : * FreeQueryDesc
103 : */
104 : void
7420 105 253908 : FreeQueryDesc(QueryDesc *qdesc)
106 : {
107 : /* Can't be a live query */
108 253908 : Assert(qdesc->estate == NULL);
109 :
110 : /* forget our snapshots */
5445 alvherre 111 253908 : UnregisterSnapshot(qdesc->snapshot);
112 253908 : UnregisterSnapshot(qdesc->crosscheck_snapshot);
113 :
114 : /* Only the QueryDesc itself need be freed */
7420 tgl 115 253908 : pfree(qdesc);
116 253908 : }
117 :
118 :
119 : /*
120 : * ProcessQuery
121 : * Execute a single plannable query within a PORTAL_MULTI_QUERY,
122 : * PORTAL_ONE_RETURNING, or PORTAL_ONE_MOD_WITH portal
123 : *
124 : * plan: the plan tree for the query
125 : * sourceText: the source text of the query
126 : * params: any parameters needed
127 : * dest: where to send results
128 : * qc: where to store the command completion status data.
129 : *
130 : * qc may be NULL if caller doesn't want a status string.
131 : *
132 : * Must be called in a memory context that will be reset or deleted on
133 : * error; otherwise the executor's memory usage will be leaked.
134 : */
135 : static void
5624 bruce 136 52101 : ProcessQuery(PlannedStmt *plan,
137 : const char *sourceText,
138 : ParamListInfo params,
139 : QueryEnvironment *queryEnv,
140 : DestReceiver *dest,
141 : QueryCompletion *qc)
142 : {
143 : QueryDesc *queryDesc;
144 :
145 : /*
146 : * Create the QueryDesc object
147 : */
5210 tgl 148 52101 : queryDesc = CreateQueryDesc(plan, sourceText,
149 : GetActiveSnapshot(), InvalidSnapshot,
150 : dest, params, queryEnv, 0);
151 :
152 : /*
153 : * Call ExecutorStart to prepare the plan for execution
154 : */
6249 155 52101 : ExecutorStart(queryDesc, 0);
156 :
157 : /*
158 : * Run the plan to completion.
159 : */
11 peter 160 GNC 51622 : ExecutorRun(queryDesc, ForwardScanDirection, 0, true);
161 :
162 : /*
163 : * Build command completion status data, if caller wants one.
164 : */
1133 alvherre 165 CBC 50269 : if (qc)
166 : {
5892 tgl 167 49960 : switch (queryDesc->operation)
168 : {
7712 169 55 : case CMD_SELECT:
1133 alvherre 170 55 : SetQueryCompletion(qc, CMDTAG_SELECT, queryDesc->estate->es_processed);
7712 tgl 171 55 : break;
172 41716 : case CMD_INSERT:
1133 alvherre 173 41716 : SetQueryCompletion(qc, CMDTAG_INSERT, queryDesc->estate->es_processed);
7712 tgl 174 41716 : break;
175 6300 : case CMD_UPDATE:
1133 alvherre 176 6300 : SetQueryCompletion(qc, CMDTAG_UPDATE, queryDesc->estate->es_processed);
7712 tgl 177 6300 : break;
178 1555 : case CMD_DELETE:
1133 alvherre 179 1555 : SetQueryCompletion(qc, CMDTAG_DELETE, queryDesc->estate->es_processed);
7712 tgl 180 1555 : break;
377 alvherre 181 334 : case CMD_MERGE:
182 334 : SetQueryCompletion(qc, CMDTAG_MERGE, queryDesc->estate->es_processed);
183 334 : break;
7712 tgl 184 UBC 0 : default:
1133 alvherre 185 0 : SetQueryCompletion(qc, CMDTAG_UNKNOWN, queryDesc->estate->es_processed);
7712 tgl 186 0 : break;
187 : }
188 : }
189 :
190 : /*
191 : * Now, we close down all the scans and free allocated resources.
192 : */
4424 tgl 193 CBC 50269 : ExecutorFinish(queryDesc);
7430 194 49838 : ExecutorEnd(queryDesc);
195 :
7420 196 49838 : FreeQueryDesc(queryDesc);
9770 scrappy 197 49838 : }
198 :
199 : /*
200 : * ChoosePortalStrategy
201 : * Select portal execution strategy given the intended statement list.
202 : *
203 : * The list elements can be Querys or PlannedStmts.
204 : * That's more general than portals need, but plancache.c uses this too.
205 : *
206 : * See the comments in portal.h.
207 : */
208 : PortalStrategy
5892 tgl 209 530386 : ChoosePortalStrategy(List *stmts)
210 : {
211 : int nSetTag;
212 : ListCell *lc;
213 :
214 : /*
215 : * PORTAL_ONE_SELECT and PORTAL_UTIL_SELECT need only consider the
216 : * single-statement case, since there are no rewrite rules that can add
217 : * auxiliary queries to a SELECT or a utility command. PORTAL_ONE_MOD_WITH
218 : * likewise allows only one top-level statement.
219 : */
220 530386 : if (list_length(stmts) == 1)
221 : {
222 530224 : Node *stmt = (Node *) linitial(stmts);
223 :
224 530224 : if (IsA(stmt, Query))
225 : {
226 31403 : Query *query = (Query *) stmt;
227 :
228 31403 : if (query->canSetTag)
229 : {
2276 230 31403 : if (query->commandType == CMD_SELECT)
231 : {
4426 232 23198 : if (query->hasModifyingCTE)
4426 tgl 233 UBC 0 : return PORTAL_ONE_MOD_WITH;
234 : else
4426 tgl 235 CBC 23198 : return PORTAL_ONE_SELECT;
236 : }
2276 237 8205 : if (query->commandType == CMD_UTILITY)
238 : {
5892 239 5652 : if (UtilityReturnsTuples(query->utilityStmt))
240 4020 : return PORTAL_UTIL_SELECT;
241 : /* it can't be ONE_RETURNING, so give up */
242 1632 : return PORTAL_MULTI_QUERY;
243 : }
244 : }
245 : }
246 498821 : else if (IsA(stmt, PlannedStmt))
247 : {
248 498821 : PlannedStmt *pstmt = (PlannedStmt *) stmt;
249 :
250 498821 : if (pstmt->canSetTag)
251 : {
2276 252 498806 : if (pstmt->commandType == CMD_SELECT)
253 : {
4426 254 109169 : if (pstmt->hasModifyingCTE)
255 61 : return PORTAL_ONE_MOD_WITH;
256 : else
257 109108 : return PORTAL_ONE_SELECT;
258 : }
2276 259 389637 : if (pstmt->commandType == CMD_UTILITY)
260 : {
261 338068 : if (UtilityReturnsTuples(pstmt->utilityStmt))
262 17602 : return PORTAL_UTIL_SELECT;
263 : /* it can't be ONE_RETURNING, so give up */
264 320466 : return PORTAL_MULTI_QUERY;
265 : }
266 : }
267 : }
268 : else
2276 tgl 269 UBC 0 : elog(ERROR, "unrecognized node type: %d", (int) nodeTag(stmt));
270 : }
271 :
272 : /*
273 : * PORTAL_ONE_RETURNING has to allow auxiliary queries added by rewrite.
274 : * Choose PORTAL_ONE_RETURNING if there is exactly one canSetTag query and
275 : * it has a RETURNING list.
276 : */
6082 tgl 277 CBC 54299 : nSetTag = 0;
5892 278 55762 : foreach(lc, stmts)
279 : {
280 54383 : Node *stmt = (Node *) lfirst(lc);
281 :
282 54383 : if (IsA(stmt, Query))
283 : {
284 2553 : Query *query = (Query *) stmt;
285 :
286 2553 : if (query->canSetTag)
287 : {
288 2553 : if (++nSetTag > 1)
289 52920 : return PORTAL_MULTI_QUERY; /* no need to look further */
2276 290 2553 : if (query->commandType == CMD_UTILITY ||
291 2553 : query->returningList == NIL)
5892 292 2451 : return PORTAL_MULTI_QUERY; /* no need to look further */
293 : }
294 : }
295 51830 : else if (IsA(stmt, PlannedStmt))
296 : {
297 51830 : PlannedStmt *pstmt = (PlannedStmt *) stmt;
298 :
299 51830 : if (pstmt->canSetTag)
300 : {
301 51728 : if (++nSetTag > 1)
5892 tgl 302 UBC 0 : return PORTAL_MULTI_QUERY; /* no need to look further */
2276 tgl 303 CBC 51728 : if (pstmt->commandType == CMD_UTILITY ||
304 51728 : !pstmt->hasReturning)
5892 305 50469 : return PORTAL_MULTI_QUERY; /* no need to look further */
306 : }
307 : }
308 : else
2276 tgl 309 UBC 0 : elog(ERROR, "unrecognized node type: %d", (int) nodeTag(stmt));
310 : }
6082 tgl 311 CBC 1379 : if (nSetTag == 1)
312 1361 : return PORTAL_ONE_RETURNING;
313 :
314 : /* Else, it's the general case... */
315 18 : return PORTAL_MULTI_QUERY;
316 : }
317 :
318 : /*
319 : * FetchPortalTargetList
320 : * Given a portal that returns tuples, extract the query targetlist.
321 : * Returns NIL if the portal doesn't have a determinable targetlist.
322 : *
323 : * Note: do not modify the result.
324 : */
325 : List *
6500 326 117897 : FetchPortalTargetList(Portal portal)
327 : {
328 : /* no point in looking if we determined it doesn't return tuples */
5892 329 117897 : if (portal->strategy == PORTAL_MULTI_QUERY)
330 12 : return NIL;
331 : /* get the primary statement and find out what it returns */
2276 332 117885 : return FetchStatementTargetList((Node *) PortalGetPrimaryStmt(portal));
333 : }
334 :
335 : /*
336 : * FetchStatementTargetList
337 : * Given a statement that returns tuples, extract the query targetlist.
338 : * Returns NIL if the statement doesn't have a determinable targetlist.
339 : *
340 : * This can be applied to a Query or a PlannedStmt.
341 : * That's more general than portals need, but plancache.c uses this too.
342 : *
343 : * Note: do not modify the result.
344 : *
345 : * XXX be careful to keep this in sync with UtilityReturnsTuples.
346 : */
347 : List *
5892 348 122444 : FetchStatementTargetList(Node *stmt)
349 : {
350 122444 : if (stmt == NULL)
5892 tgl 351 UBC 0 : return NIL;
5892 tgl 352 CBC 122444 : if (IsA(stmt, Query))
353 : {
354 4559 : Query *query = (Query *) stmt;
355 :
2276 356 4559 : if (query->commandType == CMD_UTILITY)
357 : {
358 : /* transfer attention to utility statement */
5892 359 6 : stmt = query->utilityStmt;
360 : }
361 : else
362 : {
2276 363 4553 : if (query->commandType == CMD_SELECT)
5892 364 4553 : return query->targetList;
5892 tgl 365 UBC 0 : if (query->returningList)
366 0 : return query->returningList;
367 0 : return NIL;
368 : }
369 : }
5892 tgl 370 CBC 117891 : if (IsA(stmt, PlannedStmt))
371 : {
372 117885 : PlannedStmt *pstmt = (PlannedStmt *) stmt;
373 :
2276 374 117885 : if (pstmt->commandType == CMD_UTILITY)
375 : {
376 : /* transfer attention to utility statement */
377 13559 : stmt = pstmt->utilityStmt;
378 : }
379 : else
380 : {
381 104326 : if (pstmt->commandType == CMD_SELECT)
382 103117 : return pstmt->planTree->targetlist;
383 1209 : if (pstmt->hasReturning)
384 1209 : return pstmt->planTree->targetlist;
2276 tgl 385 UBC 0 : return NIL;
386 : }
387 : }
5892 tgl 388 CBC 13565 : if (IsA(stmt, FetchStmt))
389 : {
390 2743 : FetchStmt *fstmt = (FetchStmt *) stmt;
391 : Portal subportal;
392 :
393 2743 : Assert(!fstmt->ismove);
394 2743 : subportal = GetPortalByName(fstmt->portalname);
395 2743 : Assert(PortalIsValid(subportal));
396 2743 : return FetchPortalTargetList(subportal);
397 : }
398 10822 : if (IsA(stmt, ExecuteStmt))
399 : {
400 4516 : ExecuteStmt *estmt = (ExecuteStmt *) stmt;
401 : PreparedStatement *entry;
402 :
403 4516 : entry = FetchPreparedStatement(estmt->name, true);
404 4516 : return FetchPreparedStatementTargetList(entry);
405 : }
6500 406 6306 : return NIL;
407 : }
408 :
409 : /*
410 : * PortalStart
411 : * Prepare a portal for execution.
412 : *
413 : * Caller must already have created the portal, done PortalDefineQuery(),
414 : * and adjusted portal options if needed.
415 : *
416 : * If parameters are needed by the query, they must be passed in "params"
417 : * (caller is responsible for giving them appropriate lifetime).
418 : *
419 : * The caller can also provide an initial set of "eflags" to be passed to
420 : * ExecutorStart (but note these can be modified internally, and they are
421 : * currently only honored for PORTAL_ONE_SELECT portals). Most callers
422 : * should simply pass zero.
423 : *
424 : * The caller can optionally pass a snapshot to be used; pass InvalidSnapshot
425 : * for the normal behavior of setting a new snapshot. This parameter is
426 : * presently ignored for non-PORTAL_ONE_SELECT portals (it's only intended
427 : * to be used for cursors).
428 : *
429 : * On return, portal is ready to accept PortalRun() calls, and the result
430 : * tupdesc (if any) is known.
431 : */
432 : void
4038 433 498983 : PortalStart(Portal portal, ParamListInfo params,
434 : int eflags, Snapshot snapshot)
435 : {
436 : Portal saveActivePortal;
437 : ResourceOwner saveResourceOwner;
438 : MemoryContext savePortalContext;
439 : MemoryContext oldContext;
440 : QueryDesc *queryDesc;
441 : int myeflags;
442 :
163 peter 443 GNC 498983 : Assert(PortalIsValid(portal));
444 498983 : Assert(portal->status == PORTAL_DEFINED);
445 :
446 : /*
447 : * Set up global portal context pointers.
448 : */
6840 tgl 449 CBC 498983 : saveActivePortal = ActivePortal;
450 498983 : saveResourceOwner = CurrentResourceOwner;
451 498983 : savePortalContext = PortalContext;
6826 452 498983 : PG_TRY();
453 : {
454 498983 : ActivePortal = portal;
3587 455 498983 : if (portal->resowner)
456 498983 : CurrentResourceOwner = portal->resowner;
1940 peter_e 457 498983 : PortalContext = portal->portalContext;
458 :
459 498983 : oldContext = MemoryContextSwitchTo(PortalContext);
460 :
461 : /* Must remember portal param list, if any */
6826 tgl 462 498983 : portal->portalParams = params;
463 :
464 : /*
465 : * Determine the portal execution strategy
466 : */
5892 467 498983 : portal->strategy = ChoosePortalStrategy(portal->stmts);
468 :
469 : /*
470 : * Fire her up according to the strategy
471 : */
6826 472 498983 : switch (portal->strategy)
473 : {
474 109108 : case PORTAL_ONE_SELECT:
475 :
476 : /* Must set snapshot before starting executor. */
3786 477 109108 : if (snapshot)
478 7861 : PushActiveSnapshot(snapshot);
479 : else
5445 alvherre 480 101247 : PushActiveSnapshot(GetTransactionSnapshot());
481 :
482 : /*
483 : * We could remember the snapshot in portal->portalSnapshot,
484 : * but presently there seems no need to, as this code path
485 : * cannot be used for non-atomic execution. Hence there can't
486 : * be any commit/abort that might destroy the snapshot. Since
487 : * we don't do that, there's also no need to force a
488 : * non-default nesting level for the snapshot.
489 : */
490 :
491 : /*
492 : * Create QueryDesc in portal's context; for the moment, set
493 : * the destination to DestNone.
494 : */
2190 tgl 495 109108 : queryDesc = CreateQueryDesc(linitial_node(PlannedStmt, portal->stmts),
496 : portal->sourceText,
497 : GetActiveSnapshot(),
498 : InvalidSnapshot,
499 : None_Receiver,
500 : params,
501 : portal->queryEnv,
502 : 0);
503 :
504 : /*
505 : * If it's a scrollable cursor, executor needs to support
506 : * REWIND and backwards scan, as well as whatever the caller
507 : * might've asked for.
508 : */
6249 509 109108 : if (portal->cursorOptions & CURSOR_OPT_SCROLL)
4038 510 1041 : myeflags = eflags | EXEC_FLAG_REWIND | EXEC_FLAG_BACKWARD;
511 : else
512 108067 : myeflags = eflags;
513 :
514 : /*
515 : * Call ExecutorStart to prepare the plan for execution
516 : */
517 109108 : ExecutorStart(queryDesc, myeflags);
518 :
519 : /*
520 : * This tells PortalCleanup to shut down the executor
521 : */
6826 522 108785 : portal->queryDesc = queryDesc;
523 :
524 : /*
525 : * Remember tuple descriptor (computed by ExecutorStart)
526 : */
527 108785 : portal->tupDesc = queryDesc->tupDesc;
528 :
529 : /*
530 : * Reset cursor position data to "start of query"
531 : */
532 108785 : portal->atStart = true;
6797 bruce 533 108785 : portal->atEnd = false; /* allow fetches */
6826 tgl 534 108785 : portal->portalPos = 0;
535 :
5445 alvherre 536 108785 : PopActiveSnapshot();
6826 tgl 537 108785 : break;
538 :
6084 539 1320 : case PORTAL_ONE_RETURNING:
540 : case PORTAL_ONE_MOD_WITH:
541 :
542 : /*
543 : * We don't start the executor until we are told to run the
544 : * portal. We do need to set up the result tupdesc.
545 : */
546 : {
547 : PlannedStmt *pstmt;
548 :
2276 549 1320 : pstmt = PortalGetPrimaryStmt(portal);
5892 550 1320 : portal->tupDesc =
1601 andres 551 1320 : ExecCleanTypeFromTL(pstmt->planTree->targetlist);
552 : }
553 :
554 : /*
555 : * Reset cursor position data to "start of query"
556 : */
6084 tgl 557 1320 : portal->atStart = true;
558 1320 : portal->atEnd = false; /* allow fetches */
559 1320 : portal->portalPos = 0;
560 1320 : break;
561 :
6826 562 17602 : case PORTAL_UTIL_SELECT:
563 :
564 : /*
565 : * We don't set snapshot here, because PortalRunUtility will
566 : * take care of it if needed.
567 : */
568 : {
2276 569 17602 : PlannedStmt *pstmt = PortalGetPrimaryStmt(portal);
570 :
571 17602 : Assert(pstmt->commandType == CMD_UTILITY);
572 17602 : portal->tupDesc = UtilityTupleDescriptor(pstmt->utilityStmt);
573 : }
574 :
575 : /*
576 : * Reset cursor position data to "start of query"
577 : */
6826 578 17589 : portal->atStart = true;
6797 bruce 579 17589 : portal->atEnd = false; /* allow fetches */
6826 tgl 580 17589 : portal->portalPos = 0;
581 17589 : break;
582 :
583 370953 : case PORTAL_MULTI_QUERY:
584 : /* Need do nothing now */
585 370953 : portal->tupDesc = NULL;
586 370953 : break;
587 : }
588 : }
589 336 : PG_CATCH();
590 : {
591 : /* Uncaught error while executing portal: mark it dead */
4071 592 336 : MarkPortalFailed(portal);
593 :
594 : /* Restore global vars and propagate error */
6826 595 336 : ActivePortal = saveActivePortal;
596 336 : CurrentResourceOwner = saveResourceOwner;
597 336 : PortalContext = savePortalContext;
598 :
599 336 : PG_RE_THROW();
600 : }
601 498647 : PG_END_TRY();
602 :
7282 603 498647 : MemoryContextSwitchTo(oldContext);
604 :
6840 605 498647 : ActivePortal = saveActivePortal;
606 498647 : CurrentResourceOwner = saveResourceOwner;
607 498647 : PortalContext = savePortalContext;
608 :
609 498647 : portal->status = PORTAL_READY;
7282 610 498647 : }
611 :
612 : /*
613 : * PortalSetResultFormat
614 : * Select the format codes for a portal's output.
615 : *
616 : * This must be run after PortalStart for a portal that will be read by
617 : * a DestRemote or DestRemoteExecute destination. It is not presently needed
618 : * for other destination types.
619 : *
620 : * formats[] is the client format request, as per Bind message conventions.
621 : */
622 : void
7276 623 486821 : PortalSetResultFormat(Portal portal, int nFormats, int16 *formats)
624 : {
625 : int natts;
626 : int i;
627 :
628 : /* Do nothing if portal won't return tuples */
629 486821 : if (portal->tupDesc == NULL)
630 370902 : return;
631 115919 : natts = portal->tupDesc->natts;
632 115919 : portal->formats = (int16 *)
1940 peter_e 633 115919 : MemoryContextAlloc(portal->portalContext,
634 : natts * sizeof(int16));
7276 tgl 635 115919 : if (nFormats > 1)
636 : {
637 : /* format specified for each column */
7276 tgl 638 UBC 0 : if (nFormats != natts)
7201 639 0 : ereport(ERROR,
640 : (errcode(ERRCODE_PROTOCOL_VIOLATION),
641 : errmsg("bind message has %d result formats but query has %d columns",
642 : nFormats, natts)));
7276 643 0 : memcpy(portal->formats, formats, natts * sizeof(int16));
644 : }
7188 bruce 645 CBC 115919 : else if (nFormats > 0)
646 : {
647 : /* single format specified, use for all columns */
7276 tgl 648 115919 : int16 format1 = formats[0];
649 :
650 451585 : for (i = 0; i < natts; i++)
651 335666 : portal->formats[i] = format1;
652 : }
653 : else
654 : {
655 : /* use default format for all columns */
7276 tgl 656 UBC 0 : for (i = 0; i < natts; i++)
657 0 : portal->formats[i] = 0;
658 : }
659 : }
660 :
661 : /*
662 : * PortalRun
663 : * Run a portal's query or queries.
664 : *
665 : * count <= 0 is interpreted as a no-op: the destination gets started up
666 : * and shut down, but nothing else happens. Also, count == FETCH_ALL is
667 : * interpreted as "all rows". Note that count is ignored in multi-query
668 : * situations, where we always run the portal to completion.
669 : *
670 : * isTopLevel: true if query is being executed at backend "top level"
671 : * (that is, directly from a client command message)
672 : *
673 : * dest: where to send output of primary (canSetTag) query
674 : *
675 : * altdest: where to send output of non-primary queries
676 : *
677 : * qc: where to store command completion status data.
678 : * May be NULL if caller doesn't want status data.
679 : *
680 : * Returns true if the portal's execution is complete, false if it was
681 : * suspended due to exhaustion of the count parameter.
682 : */
683 : bool
2208 rhaas 684 CBC 491407 : PortalRun(Portal portal, long count, bool isTopLevel, bool run_once,
685 : DestReceiver *dest, DestReceiver *altdest,
686 : QueryCompletion *qc)
687 : {
688 : bool result;
689 : uint64 nprocessed;
690 : ResourceOwner saveTopTransactionResourceOwner;
691 : MemoryContext saveTopTransactionContext;
692 : Portal saveActivePortal;
693 : ResourceOwner saveResourceOwner;
694 : MemoryContext savePortalContext;
695 : MemoryContext saveMemoryContext;
696 :
163 peter 697 GNC 491407 : Assert(PortalIsValid(portal));
698 :
699 : TRACE_POSTGRESQL_QUERY_EXECUTE_START();
700 :
701 : /* Initialize empty completion data */
1133 alvherre 702 CBC 491407 : if (qc)
703 491407 : InitializeQueryCompletion(qc);
704 :
6961 bruce 705 491407 : if (log_executor_stats && portal->strategy != PORTAL_MULTI_QUERY)
706 : {
5871 tgl 707 UBC 0 : elog(DEBUG3, "PortalRun");
708 : /* PORTAL_MULTI_QUERY logs its own stats per query */
6961 bruce 709 0 : ResetUsage();
710 : }
711 :
712 : /*
713 : * Check for improper portal use, and mark portal active.
714 : */
2774 tgl 715 CBC 491407 : MarkPortalActive(portal);
716 :
717 : /* Set run_once flag. Shouldn't be clear if previously set. */
2208 rhaas 718 491407 : Assert(!portal->run_once || run_once);
719 491407 : portal->run_once = run_once;
720 :
721 : /*
722 : * Set up global portal context pointers.
723 : *
724 : * We have to play a special game here to support utility commands like
725 : * VACUUM and CLUSTER, which internally start and commit transactions.
726 : * When we are called to execute such a command, CurrentResourceOwner will
727 : * be pointing to the TopTransactionResourceOwner --- which will be
728 : * destroyed and replaced in the course of the internal commit and
729 : * restart. So we need to be prepared to restore it as pointing to the
730 : * exit-time TopTransactionResourceOwner. (Ain't that ugly? This idea of
731 : * internally starting whole new transactions is not good.)
732 : * CurrentMemoryContext has a similar problem, but the other pointers we
733 : * save here will be NULL or pointing to longer-lived objects.
734 : */
6761 tgl 735 491407 : saveTopTransactionResourceOwner = TopTransactionResourceOwner;
736 491407 : saveTopTransactionContext = TopTransactionContext;
6958 737 491407 : saveActivePortal = ActivePortal;
6840 738 491407 : saveResourceOwner = CurrentResourceOwner;
7282 739 491407 : savePortalContext = PortalContext;
6761 740 491407 : saveMemoryContext = CurrentMemoryContext;
6826 741 491407 : PG_TRY();
742 : {
743 491407 : ActivePortal = portal;
3587 744 491407 : if (portal->resowner)
745 491407 : CurrentResourceOwner = portal->resowner;
1940 peter_e 746 491407 : PortalContext = portal->portalContext;
747 :
6761 tgl 748 491407 : MemoryContextSwitchTo(PortalContext);
749 :
6826 750 491407 : switch (portal->strategy)
751 : {
752 120454 : case PORTAL_ONE_SELECT:
753 : case PORTAL_ONE_RETURNING:
754 : case PORTAL_ONE_MOD_WITH:
755 : case PORTAL_UTIL_SELECT:
756 :
757 : /*
758 : * If we have not yet run the command, do so, storing its
759 : * results in the portal's tuplestore. But we don't do that
760 : * for the PORTAL_ONE_SELECT case.
761 : */
4800 bruce 762 120454 : if (portal->strategy != PORTAL_ONE_SELECT && !portal->holdStore)
5871 tgl 763 14995 : FillPortalStore(portal, isTopLevel);
764 :
765 : /*
766 : * Now fetch desired portion of results.
767 : */
4800 bruce 768 120280 : nprocessed = PortalRunSelect(portal, true, count, dest);
769 :
770 : /*
771 : * If the portal result contains a command tag and the caller
772 : * gave us a pointer to store it, copy it and update the
773 : * rowcount.
774 : */
1133 alvherre 775 117827 : if (qc && portal->qc.commandTag != CMDTAG_UNKNOWN)
776 : {
777 117827 : CopyQueryCompletion(qc, &portal->qc);
778 117827 : qc->nprocessed = nprocessed;
779 : }
780 :
781 : /* Mark portal not active */
6826 tgl 782 117827 : portal->status = PORTAL_READY;
783 :
784 : /*
785 : * Since it's a forward fetch, say DONE iff atEnd is now true.
786 : */
787 117827 : result = portal->atEnd;
788 117827 : break;
789 :
790 370953 : case PORTAL_MULTI_QUERY:
2436 791 370953 : PortalRunMulti(portal, isTopLevel, false,
792 : dest, altdest, qc);
793 :
794 : /* Prevent portal's commands from being re-executed */
4420 795 362332 : MarkPortalDone(portal);
796 :
797 : /* Always complete at end of RunMulti */
6826 798 362332 : result = true;
799 362332 : break;
800 :
6826 tgl 801 UBC 0 : default:
802 0 : elog(ERROR, "unrecognized portal strategy: %d",
803 : (int) portal->strategy);
804 : result = false; /* keep compiler quiet */
805 : break;
806 : }
807 : }
6826 tgl 808 CBC 11245 : PG_CATCH();
809 : {
810 : /* Uncaught error while executing portal: mark it dead */
4071 811 11245 : MarkPortalFailed(portal);
812 :
813 : /* Restore global vars and propagate error */
6761 814 11245 : if (saveMemoryContext == saveTopTransactionContext)
815 11102 : MemoryContextSwitchTo(TopTransactionContext);
816 : else
817 143 : MemoryContextSwitchTo(saveMemoryContext);
6826 818 11245 : ActivePortal = saveActivePortal;
6761 819 11245 : if (saveResourceOwner == saveTopTransactionResourceOwner)
820 11117 : CurrentResourceOwner = TopTransactionResourceOwner;
821 : else
822 128 : CurrentResourceOwner = saveResourceOwner;
6826 823 11245 : PortalContext = savePortalContext;
824 :
825 11245 : PG_RE_THROW();
826 : }
827 480159 : PG_END_TRY();
828 :
6761 829 480159 : if (saveMemoryContext == saveTopTransactionContext)
830 462256 : MemoryContextSwitchTo(TopTransactionContext);
831 : else
832 17903 : MemoryContextSwitchTo(saveMemoryContext);
6958 833 480159 : ActivePortal = saveActivePortal;
6761 834 480159 : if (saveResourceOwner == saveTopTransactionResourceOwner)
835 472477 : CurrentResourceOwner = TopTransactionResourceOwner;
836 : else
837 7682 : CurrentResourceOwner = saveResourceOwner;
7282 838 480159 : PortalContext = savePortalContext;
839 :
6974 bruce 840 480159 : if (log_executor_stats && portal->strategy != PORTAL_MULTI_QUERY)
6974 bruce 841 UBC 0 : ShowUsage("EXECUTOR STATISTICS");
842 :
843 : TRACE_POSTGRESQL_QUERY_EXECUTE_DONE();
844 :
7282 tgl 845 CBC 480159 : return result;
846 : }
847 :
848 : /*
849 : * PortalRunSelect
850 : * Execute a portal's query in PORTAL_ONE_SELECT mode, and also
851 : * when fetching from a completed holdStore in PORTAL_ONE_RETURNING,
852 : * PORTAL_ONE_MOD_WITH, and PORTAL_UTIL_SELECT cases.
853 : *
854 : * This handles simple N-rows-forward-or-backward cases. For more complex
855 : * nonsequential access to a portal, see PortalRunFetch.
856 : *
857 : * count <= 0 is interpreted as a no-op: the destination gets started up
858 : * and shut down, but nothing else happens. Also, count == FETCH_ALL is
859 : * interpreted as "all rows". (cf FetchStmt.howMany)
860 : *
861 : * Caller must already have validated the Portal and done appropriate
862 : * setup (cf. PortalRun).
863 : *
864 : * Returns number of rows processed (suitable for use in result tag)
865 : */
866 : static uint64
867 144817 : PortalRunSelect(Portal portal,
868 : bool forward,
869 : long count,
870 : DestReceiver *dest)
871 : {
872 : QueryDesc *queryDesc;
873 : ScanDirection direction;
874 : uint64 nprocessed;
875 :
876 : /*
877 : * NB: queryDesc will be NULL if we are fetching from a held cursor or a
878 : * completed utility query; can't use it in that path.
879 : */
1940 peter_e 880 144817 : queryDesc = portal->queryDesc;
881 :
882 : /* Caller messed up if we have neither a ready query nor held data. */
7282 tgl 883 144817 : Assert(queryDesc || portal->holdStore);
884 :
885 : /*
886 : * Force the queryDesc destination to the right thing. This supports
887 : * MOVE, for example, which will pass in dest = DestNone. This is okay to
888 : * change as long as we do it on every fetch. (The Executor must not
889 : * assume that dest never changes.)
890 : */
891 144817 : if (queryDesc)
892 114306 : queryDesc->dest = dest;
893 :
894 : /*
895 : * Determine which direction to go in, and check to see if we're already
896 : * at the end of the available tuples in that direction. If so, set the
897 : * direction to NoMovement to avoid trying to fetch any tuples. (This
898 : * check exists because not all plan node types are robust about being
899 : * called again if they've already returned NULL once.) Then call the
900 : * executor (we must not skip this, because the destination needs to see a
901 : * setup and shutdown even if no tuples are available). Finally, update
902 : * the portal position state depending on the number of tuples that were
903 : * retrieved.
904 : */
905 144817 : if (forward)
906 : {
907 144516 : if (portal->atEnd || count <= 0)
908 : {
909 1877 : direction = NoMovementScanDirection;
2584 910 1877 : count = 0; /* don't pass negative count to executor */
911 : }
912 : else
7282 913 142639 : direction = ForwardScanDirection;
914 :
915 : /* In the executor, zero count processes all rows */
916 144516 : if (count == FETCH_ALL)
917 120440 : count = 0;
918 :
919 144516 : if (portal->holdStore)
2584 920 30502 : nprocessed = RunFromStore(portal, direction, (uint64) count, dest);
921 : else
922 : {
5445 alvherre 923 114014 : PushActiveSnapshot(queryDesc->snapshot);
2208 rhaas 924 114014 : ExecutorRun(queryDesc, direction, (uint64) count,
925 114014 : portal->run_once);
7282 tgl 926 111550 : nprocessed = queryDesc->estate->es_processed;
5445 alvherre 927 111550 : PopActiveSnapshot();
928 : }
929 :
6256 neilc 930 142052 : if (!ScanDirectionIsNoMovement(direction))
931 : {
7282 tgl 932 140175 : if (nprocessed > 0)
2118 933 121732 : portal->atStart = false; /* OK to go backward now */
2584 934 140175 : if (count == 0 || nprocessed < (uint64) count)
7188 bruce 935 124825 : portal->atEnd = true; /* we retrieved 'em all */
7282 tgl 936 140175 : portal->portalPos += nprocessed;
937 : }
938 : }
939 : else
940 : {
941 301 : if (portal->cursorOptions & CURSOR_OPT_NO_SCROLL)
7201 942 12 : ereport(ERROR,
943 : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
944 : errmsg("cursor can only scan forward"),
945 : errhint("Declare it with SCROLL option to enable backward scan.")));
946 :
7282 947 289 : if (portal->atStart || count <= 0)
948 : {
949 36 : direction = NoMovementScanDirection;
2584 950 36 : count = 0; /* don't pass negative count to executor */
951 : }
952 : else
7282 953 253 : direction = BackwardScanDirection;
954 :
955 : /* In the executor, zero count processes all rows */
956 289 : if (count == FETCH_ALL)
957 30 : count = 0;
958 :
959 289 : if (portal->holdStore)
2584 960 6 : nprocessed = RunFromStore(portal, direction, (uint64) count, dest);
961 : else
962 : {
5445 alvherre 963 283 : PushActiveSnapshot(queryDesc->snapshot);
2208 rhaas 964 283 : ExecutorRun(queryDesc, direction, (uint64) count,
965 283 : portal->run_once);
7282 tgl 966 283 : nprocessed = queryDesc->estate->es_processed;
5445 alvherre 967 283 : PopActiveSnapshot();
968 : }
969 :
6256 neilc 970 289 : if (!ScanDirectionIsNoMovement(direction))
971 : {
7282 tgl 972 253 : if (nprocessed > 0 && portal->atEnd)
973 : {
7188 bruce 974 76 : portal->atEnd = false; /* OK to go forward now */
975 76 : portal->portalPos++; /* adjust for endpoint case */
976 : }
2584 tgl 977 253 : if (count == 0 || nprocessed < (uint64) count)
978 : {
7188 bruce 979 93 : portal->atStart = true; /* we retrieved 'em all */
7282 tgl 980 93 : portal->portalPos = 0;
981 : }
982 : else
983 : {
984 160 : portal->portalPos -= nprocessed;
985 : }
986 : }
987 : }
988 :
989 142341 : return nprocessed;
990 : }
991 :
992 : /*
993 : * FillPortalStore
994 : * Run the query and load result tuples into the portal's tuple store.
995 : *
996 : * This is used for PORTAL_ONE_RETURNING, PORTAL_ONE_MOD_WITH, and
997 : * PORTAL_UTIL_SELECT cases only.
998 : */
999 : static void
5871 1000 18909 : FillPortalStore(Portal portal, bool isTopLevel)
1001 : {
1002 : DestReceiver *treceiver;
1003 : QueryCompletion qc;
1004 :
1133 alvherre 1005 18909 : InitializeQueryCompletion(&qc);
6084 tgl 1006 18909 : PortalCreateHoldStore(portal);
5243 1007 18909 : treceiver = CreateDestReceiver(DestTuplestore);
1008 18909 : SetTuplestoreDestReceiverParams(treceiver,
1009 : portal->holdStore,
1010 : portal->holdContext,
1011 : false,
1012 : NULL,
1013 : NULL);
1014 :
6084 1015 18909 : switch (portal->strategy)
1016 : {
1017 1320 : case PORTAL_ONE_RETURNING:
1018 : case PORTAL_ONE_MOD_WITH:
1019 :
1020 : /*
1021 : * Run the portal to completion just as for the default
1022 : * PORTAL_MULTI_QUERY case, but send the primary query's output to
1023 : * the tuplestore. Auxiliary query outputs are discarded. Set the
1024 : * portal's holdSnapshot to the snapshot used (or a copy of it).
1025 : */
2436 1026 1320 : PortalRunMulti(portal, isTopLevel, true,
1027 : treceiver, None_Receiver, &qc);
6084 1028 1263 : break;
1029 :
1030 17589 : case PORTAL_UTIL_SELECT:
2190 1031 17589 : PortalRunUtility(portal, linitial_node(PlannedStmt, portal->stmts),
1032 : isTopLevel, true, treceiver, &qc);
6084 1033 17469 : break;
1034 :
6084 tgl 1035 UBC 0 : default:
1036 0 : elog(ERROR, "unsupported portal strategy: %d",
1037 : (int) portal->strategy);
1038 : break;
1039 : }
1040 :
1041 : /* Override portal completion data with actual command results */
1133 alvherre 1042 CBC 18732 : if (qc.commandTag != CMDTAG_UNKNOWN)
1043 8519 : CopyQueryCompletion(&portal->qc, &qc);
1044 :
2040 peter_e 1045 18732 : treceiver->rDestroy(treceiver);
6084 tgl 1046 18732 : }
1047 :
1048 : /*
1049 : * RunFromStore
1050 : * Fetch tuples from the portal's tuple store.
1051 : *
1052 : * Calling conventions are similar to ExecutorRun, except that we
1053 : * do not depend on having a queryDesc or estate. Therefore we return the
1054 : * number of tuples processed as the result, not in estate->es_processed.
1055 : *
1056 : * One difference from ExecutorRun is that the destination receiver functions
1057 : * are run in the caller's memory context (since we have no estate). Watch
1058 : * out for memory leaks.
1059 : */
1060 : static uint64
2584 1061 30508 : RunFromStore(Portal portal, ScanDirection direction, uint64 count,
1062 : DestReceiver *dest)
1063 : {
1064 30508 : uint64 current_tuple_count = 0;
1065 : TupleTableSlot *slot;
1066 :
1606 andres 1067 30508 : slot = MakeSingleTupleTableSlot(portal->tupDesc, &TTSOpsMinimalTuple);
1068 :
2040 peter_e 1069 30508 : dest->rStartup(dest, CMD_SELECT, portal->tupDesc);
1070 :
6256 neilc 1071 30508 : if (ScanDirectionIsNoMovement(direction))
1072 : {
1073 : /* do nothing except start/stop the destination */
1074 : }
1075 : else
1076 : {
1077 29178 : bool forward = ScanDirectionIsForward(direction);
1078 :
1079 : for (;;)
7282 tgl 1080 184123 : {
1081 : MemoryContext oldcontext;
1082 : bool ok;
1083 :
1084 213301 : oldcontext = MemoryContextSwitchTo(portal->holdContext);
1085 :
5126 1086 213301 : ok = tuplestore_gettupleslot(portal->holdStore, forward, false,
1087 : slot);
1088 :
7282 1089 213301 : MemoryContextSwitchTo(oldcontext);
1090 :
6130 1091 213301 : if (!ok)
7282 1092 18759 : break;
1093 :
1094 : /*
1095 : * If we are not able to send the tuple, we assume the destination
1096 : * has closed and no more tuples can be sent. If that's the case,
1097 : * end the loop.
1098 : */
2040 peter_e 1099 194542 : if (!dest->receiveSlot(slot, dest))
2498 rhaas 1100 UBC 0 : break;
1101 :
6598 tgl 1102 CBC 194542 : ExecClearTuple(slot);
1103 :
1104 : /*
1105 : * check our tuple count.. if we've processed the proper number
1106 : * then quit, else loop again and process more tuples. Zero count
1107 : * means no limit.
1108 : */
7282 1109 194542 : current_tuple_count++;
1110 194542 : if (count && count == current_tuple_count)
1111 10419 : break;
1112 : }
1113 : }
1114 :
2040 peter_e 1115 30508 : dest->rShutdown(dest);
1116 :
6598 tgl 1117 30508 : ExecDropSingleTupleTableSlot(slot);
1118 :
2584 1119 30508 : return current_tuple_count;
1120 : }
1121 :
1122 : /*
1123 : * PortalRunUtility
1124 : * Execute a utility statement inside a portal.
1125 : */
1126 : static void
2276 1127 338055 : PortalRunUtility(Portal portal, PlannedStmt *pstmt,
1128 : bool isTopLevel, bool setHoldSnapshot,
1129 : DestReceiver *dest, QueryCompletion *qc)
1130 : {
1131 : /*
1132 : * Set snapshot if utility stmt needs one.
1133 : */
688 1134 338055 : if (PlannedStmtRequiresSnapshot(pstmt))
1135 : {
1136 306637 : Snapshot snapshot = GetTransactionSnapshot();
1137 :
1138 : /* If told to, register the snapshot we're using and save in portal */
2436 1139 306637 : if (setHoldSnapshot)
1140 : {
1141 14446 : snapshot = RegisterSnapshot(snapshot);
1142 14446 : portal->holdSnapshot = snapshot;
1143 : }
1144 :
1145 : /*
1146 : * In any case, make the snapshot active and remember it in portal.
1147 : * Because the portal now references the snapshot, we must tell
1148 : * snapmgr.c that the snapshot belongs to the portal's transaction
1149 : * level, else we risk portalSnapshot becoming a dangling pointer.
1150 : */
555 1151 306637 : PushActiveSnapshotWithLevel(snapshot, portal->createLevel);
1152 : /* PushActiveSnapshotWithLevel might have copied the snapshot */
688 1153 306637 : portal->portalSnapshot = GetActiveSnapshot();
1154 : }
1155 : else
1156 31418 : portal->portalSnapshot = NULL;
1157 :
2276 1158 338055 : ProcessUtility(pstmt,
1159 : portal->sourceText,
660 1160 338055 : (portal->cplan != NULL), /* protect tree if in plancache */
2118 1161 338055 : isTopLevel ? PROCESS_UTILITY_TOPLEVEL : PROCESS_UTILITY_QUERY,
1162 : portal->portalParams,
1163 : portal->queryEnv,
1164 : dest,
1165 : qc);
1166 :
1167 : /* Some utility statements may change context on us */
1940 peter_e 1168 331520 : MemoryContextSwitchTo(portal->portalContext);
1169 :
1170 : /*
1171 : * Some utility commands (e.g., VACUUM) pop the ActiveSnapshot stack from
1172 : * under us, so don't complain if it's now empty. Otherwise, our snapshot
1173 : * should be the top one; pop it. Note that this could be a different
1174 : * snapshot from the one we made above; see EnsurePortalSnapshotExists.
1175 : */
688 tgl 1176 331520 : if (portal->portalSnapshot != NULL && ActiveSnapshotSet())
1177 : {
1178 296824 : Assert(portal->portalSnapshot == GetActiveSnapshot());
5445 alvherre 1179 296824 : PopActiveSnapshot();
1180 : }
688 tgl 1181 331520 : portal->portalSnapshot = NULL;
7282 1182 331520 : }
1183 :
1184 : /*
1185 : * PortalRunMulti
1186 : * Execute a portal's queries in the general case (multi queries
1187 : * or non-SELECT-like queries)
1188 : */
1189 : static void
2436 1190 372273 : PortalRunMulti(Portal portal,
1191 : bool isTopLevel, bool setHoldSnapshot,
1192 : DestReceiver *dest, DestReceiver *altdest,
1193 : QueryCompletion *qc)
1194 : {
4423 1195 372273 : bool active_snapshot_set = false;
1196 : ListCell *stmtlist_item;
1197 :
1198 : /*
1199 : * If the destination is DestRemoteExecute, change to DestNone. The
1200 : * reason is that the client won't be expecting any tuples, and indeed has
1201 : * no way to know what they are, since there is no provision for Describe
1202 : * to send a RowDescription message when this portal execution strategy is
1203 : * in effect. This presently will only affect SELECT commands added to
1204 : * non-SELECT queries by rewrite rules: such commands will be executed,
1205 : * but the results will be discarded unless you use "simple Query"
1206 : * protocol.
1207 : */
6366 alvherre 1208 372273 : if (dest->mydest == DestRemoteExecute)
7278 tgl 1209 4665 : dest = None_Receiver;
6366 alvherre 1210 372273 : if (altdest->mydest == DestRemoteExecute)
7278 tgl 1211 4665 : altdest = None_Receiver;
1212 :
1213 : /*
1214 : * Loop to handle the individual queries generated from a single parsetree
1215 : * by analysis and rewrite.
1216 : */
5892 1217 736162 : foreach(stmtlist_item, portal->stmts)
1218 : {
2190 1219 372567 : PlannedStmt *pstmt = lfirst_node(PlannedStmt, stmtlist_item);
1220 :
1221 : /*
1222 : * If we got a cancel signal in prior command, quit
1223 : */
7282 1224 372567 : CHECK_FOR_INTERRUPTS();
1225 :
2276 1226 372567 : if (pstmt->utilityStmt == NULL)
1227 : {
1228 : /*
1229 : * process a plannable query.
1230 : */
1231 : TRACE_POSTGRESQL_QUERY_EXECUTE_START();
1232 :
7282 1233 52101 : if (log_executor_stats)
7282 tgl 1234 UBC 0 : ResetUsage();
1235 :
1236 : /*
1237 : * Must always have a snapshot for plannable queries. First time
1238 : * through, take a new snapshot; for subsequent queries in the
1239 : * same portal, just update the snapshot's copy of the command
1240 : * counter.
1241 : */
4423 tgl 1242 CBC 52101 : if (!active_snapshot_set)
1243 : {
2436 1244 51807 : Snapshot snapshot = GetTransactionSnapshot();
1245 :
1246 : /* If told to, register the snapshot and save in portal */
1247 51807 : if (setHoldSnapshot)
1248 : {
1249 1320 : snapshot = RegisterSnapshot(snapshot);
1250 1320 : portal->holdSnapshot = snapshot;
1251 : }
1252 :
1253 : /*
1254 : * We can't have the holdSnapshot also be the active one,
1255 : * because UpdateActiveSnapshotCommandId would complain. So
1256 : * force an extra snapshot copy. Plain PushActiveSnapshot
1257 : * would have copied the transaction snapshot anyway, so this
1258 : * only adds a copy step when setHoldSnapshot is true. (It's
1259 : * okay for the command ID of the active snapshot to diverge
1260 : * from what holdSnapshot has.)
1261 : */
1262 51807 : PushCopiedSnapshot(snapshot);
1263 :
1264 : /*
1265 : * As for PORTAL_ONE_SELECT portals, it does not seem
1266 : * necessary to maintain portal->portalSnapshot here.
1267 : */
1268 :
4423 1269 51807 : active_snapshot_set = true;
1270 : }
1271 : else
1272 294 : UpdateActiveSnapshotCommandId();
1273 :
5892 1274 52101 : if (pstmt->canSetTag)
1275 : {
1276 : /* statement can set tag string */
1277 51789 : ProcessQuery(pstmt,
1278 : portal->sourceText,
1279 : portal->portalParams,
1280 : portal->queryEnv,
1281 : dest, qc);
1282 : }
1283 : else
1284 : {
1285 : /* stmt added by rewrite cannot set tag */
1286 312 : ProcessQuery(pstmt,
1287 : portal->sourceText,
1288 : portal->portalParams,
1289 : portal->queryEnv,
1290 : altdest, NULL);
1291 : }
1292 :
7282 1293 49838 : if (log_executor_stats)
7282 tgl 1294 UBC 0 : ShowUsage("EXECUTOR STATISTICS");
1295 :
1296 : TRACE_POSTGRESQL_QUERY_EXECUTE_DONE();
1297 : }
1298 : else
1299 : {
1300 : /*
1301 : * process utility functions (create, destroy, etc..)
1302 : *
1303 : * We must not set a snapshot here for utility commands (if one is
1304 : * needed, PortalRunUtility will do it). If a utility command is
1305 : * alone in a portal then everything's fine. The only case where
1306 : * a utility command can be part of a longer list is that rules
1307 : * are allowed to include NotifyStmt. NotifyStmt doesn't care
1308 : * whether it has a snapshot or not, so we just leave the current
1309 : * snapshot alone if we have one.
1310 : */
2276 tgl 1311 CBC 320466 : if (pstmt->canSetTag)
1312 : {
4423 1313 320466 : Assert(!active_snapshot_set);
1314 : /* statement can set tag string */
2276 1315 320466 : PortalRunUtility(portal, pstmt, isTopLevel, false,
1316 : dest, qc);
1317 : }
1318 : else
1319 : {
2276 tgl 1320 UBC 0 : Assert(IsA(pstmt->utilityStmt, NotifyStmt));
1321 : /* stmt added by rewrite cannot set tag */
1322 0 : PortalRunUtility(portal, pstmt, isTopLevel, false,
1323 : altdest, NULL);
1324 : }
1325 : }
1326 :
1327 : /*
1328 : * Clear subsidiary contexts to recover temporary memory.
1329 : */
1940 peter_e 1330 CBC 363889 : Assert(portal->portalContext == CurrentMemoryContext);
1331 :
1332 363889 : MemoryContextDeleteChildren(portal->portalContext);
1333 :
1334 : /*
1335 : * Avoid crashing if portal->stmts has been reset. This can only
1336 : * occur if a CALL or DO utility statement executed an internal
1337 : * COMMIT/ROLLBACK (cf PortalReleaseCachedPlan). The CALL or DO must
1338 : * have been the only statement in the portal, so there's nothing left
1339 : * for us to do; but we don't want to dereference a now-dangling list
1340 : * pointer.
1341 : */
795 tgl 1342 363889 : if (portal->stmts == NIL)
795 tgl 1343 UBC 0 : break;
1344 :
1345 : /*
1346 : * Increment command counter between queries, but not after the last
1347 : * one.
1348 : */
795 tgl 1349 CBC 363889 : if (lnext(portal->stmts, stmtlist_item) != NULL)
1350 294 : CommandCounterIncrement();
1351 : }
1352 :
1353 : /* Pop the snapshot if we pushed one. */
4423 1354 363595 : if (active_snapshot_set)
1355 49544 : PopActiveSnapshot();
1356 :
1357 : /*
1358 : * If a query completion data was supplied, use it. Otherwise use the
1359 : * portal's query completion data.
1360 : *
1361 : * Exception: Clients expect INSERT/UPDATE/DELETE tags to have counts, so
1362 : * fake them with zeros. This can happen with DO INSTEAD rules if there
1363 : * is no replacement query of the same type as the original. We print "0
1364 : * 0" here because technically there is no query of the matching tag type,
1365 : * and printing a non-zero count for a different query type seems wrong,
1366 : * e.g. an INSERT that does an UPDATE instead should not print "0 1" if
1367 : * one row was updated. See QueryRewrite(), step 3, for details.
1368 : */
1133 alvherre 1369 363595 : if (qc && qc->commandTag == CMDTAG_UNKNOWN)
1370 : {
1371 308722 : if (portal->qc.commandTag != CMDTAG_UNKNOWN)
1372 308722 : CopyQueryCompletion(qc, &portal->qc);
1373 : /* If the caller supplied a qc, we should have set it by now. */
1374 308722 : Assert(qc->commandTag != CMDTAG_UNKNOWN);
1375 : }
7282 tgl 1376 363595 : }
1377 :
1378 : /*
1379 : * PortalRunFetch
1380 : * Variant form of PortalRun that supports SQL FETCH directions.
1381 : *
1382 : * Note: we presently assume that no callers of this want isTopLevel = true.
1383 : *
1384 : * count <= 0 is interpreted as a no-op: the destination gets started up
1385 : * and shut down, but nothing else happens. Also, count == FETCH_ALL is
1386 : * interpreted as "all rows". (cf FetchStmt.howMany)
1387 : *
1388 : * Returns number of rows processed (suitable for use in result tag)
1389 : */
1390 : uint64
1391 24519 : PortalRunFetch(Portal portal,
1392 : FetchDirection fdirection,
1393 : long count,
1394 : DestReceiver *dest)
1395 : {
1396 : uint64 result;
1397 : Portal saveActivePortal;
1398 : ResourceOwner saveResourceOwner;
1399 : MemoryContext savePortalContext;
1400 : MemoryContext oldContext;
1401 :
163 peter 1402 GNC 24519 : Assert(PortalIsValid(portal));
1403 :
1404 : /*
1405 : * Check for improper portal use, and mark portal active.
1406 : */
2774 tgl 1407 CBC 24519 : MarkPortalActive(portal);
1408 :
1409 : /* If supporting FETCH, portal can't be run-once. */
2208 rhaas 1410 24510 : Assert(!portal->run_once);
1411 :
1412 : /*
1413 : * Set up global portal context pointers.
1414 : */
6958 tgl 1415 24510 : saveActivePortal = ActivePortal;
6840 1416 24510 : saveResourceOwner = CurrentResourceOwner;
7282 1417 24510 : savePortalContext = PortalContext;
6826 1418 24510 : PG_TRY();
1419 : {
1420 24510 : ActivePortal = portal;
3587 1421 24510 : if (portal->resowner)
1422 24414 : CurrentResourceOwner = portal->resowner;
1940 peter_e 1423 24510 : PortalContext = portal->portalContext;
1424 :
6826 tgl 1425 24510 : oldContext = MemoryContextSwitchTo(PortalContext);
1426 :
1427 24510 : switch (portal->strategy)
1428 : {
1429 8913 : case PORTAL_ONE_SELECT:
1430 8913 : result = DoPortalRunFetch(portal, fdirection, count, dest);
1431 8887 : break;
1432 :
6084 1433 15597 : case PORTAL_ONE_RETURNING:
1434 : case PORTAL_ONE_MOD_WITH:
1435 : case PORTAL_UTIL_SELECT:
1436 :
1437 : /*
1438 : * If we have not yet run the command, do so, storing its
1439 : * results in the portal's tuplestore.
1440 : */
1441 15597 : if (!portal->holdStore)
5624 bruce 1442 3914 : FillPortalStore(portal, false /* isTopLevel */ );
1443 :
1444 : /*
1445 : * Now fetch desired portion of results.
1446 : */
6632 tgl 1447 15594 : result = DoPortalRunFetch(portal, fdirection, count, dest);
1448 15594 : break;
1449 :
6826 tgl 1450 UBC 0 : default:
1451 0 : elog(ERROR, "unsupported portal strategy");
1452 : result = 0; /* keep compiler quiet */
1453 : break;
1454 : }
1455 : }
6826 tgl 1456 CBC 29 : PG_CATCH();
1457 : {
1458 : /* Uncaught error while executing portal: mark it dead */
4071 1459 29 : MarkPortalFailed(portal);
1460 :
1461 : /* Restore global vars and propagate error */
6826 1462 29 : ActivePortal = saveActivePortal;
1463 29 : CurrentResourceOwner = saveResourceOwner;
1464 29 : PortalContext = savePortalContext;
1465 :
1466 29 : PG_RE_THROW();
1467 : }
1468 24481 : PG_END_TRY();
1469 :
7282 1470 24481 : MemoryContextSwitchTo(oldContext);
1471 :
1472 : /* Mark portal not active */
6840 1473 24481 : portal->status = PORTAL_READY;
1474 :
6958 1475 24481 : ActivePortal = saveActivePortal;
6840 1476 24481 : CurrentResourceOwner = saveResourceOwner;
7282 1477 24481 : PortalContext = savePortalContext;
1478 :
1479 24481 : return result;
1480 : }
1481 :
1482 : /*
1483 : * DoPortalRunFetch
1484 : * Guts of PortalRunFetch --- the portal context is already set up
1485 : *
1486 : * Here, count < 0 typically reverses the direction. Also, count == FETCH_ALL
1487 : * is interpreted as "all rows". (cf FetchStmt.howMany)
1488 : *
1489 : * Returns number of rows processed (suitable for use in result tag)
1490 : */
1491 : static uint64
1492 24507 : DoPortalRunFetch(Portal portal,
1493 : FetchDirection fdirection,
1494 : long count,
1495 : DestReceiver *dest)
1496 : {
1497 : bool forward;
1498 :
6632 1499 24507 : Assert(portal->strategy == PORTAL_ONE_SELECT ||
1500 : portal->strategy == PORTAL_ONE_RETURNING ||
1501 : portal->strategy == PORTAL_ONE_MOD_WITH ||
1502 : portal->strategy == PORTAL_UTIL_SELECT);
1503 :
1504 : /*
1505 : * Note: we disallow backwards fetch (including re-fetch of current row)
1506 : * for NO SCROLL cursors, but we interpret that very loosely: you can use
1507 : * any of the FetchDirection options, so long as the end result is to move
1508 : * forwards by at least one row. Currently it's sufficient to check for
1509 : * NO SCROLL in DoPortalRewind() and in the forward == false path in
1510 : * PortalRunSelect(); but someday we might prefer to account for that
1511 : * restriction explicitly here.
1512 : */
7282 1513 24507 : switch (fdirection)
1514 : {
1515 24122 : case FETCH_FORWARD:
1516 24122 : if (count < 0)
1517 : {
7282 tgl 1518 UBC 0 : fdirection = FETCH_BACKWARD;
1519 0 : count = -count;
1520 : }
1521 : /* fall out of switch to share code with FETCH_BACKWARD */
7282 tgl 1522 CBC 24122 : break;
1523 265 : case FETCH_BACKWARD:
1524 265 : if (count < 0)
1525 : {
7282 tgl 1526 UBC 0 : fdirection = FETCH_FORWARD;
1527 0 : count = -count;
1528 : }
1529 : /* fall out of switch to share code with FETCH_FORWARD */
7282 tgl 1530 CBC 265 : break;
1531 78 : case FETCH_ABSOLUTE:
1532 78 : if (count > 0)
1533 : {
1534 : /*
1535 : * Definition: Rewind to start, advance count-1 rows, return
1536 : * next row (if any).
1537 : *
1538 : * In practice, if the goal is less than halfway back to the
1539 : * start, it's better to scan from where we are.
1540 : *
1541 : * Also, if current portalPos is outside the range of "long",
1542 : * do it the hard way to avoid possible overflow of the count
1543 : * argument to PortalRunSelect. We must exclude exactly
1544 : * LONG_MAX, as well, lest the count look like FETCH_ALL.
1545 : *
1546 : * In any case, we arrange to fetch the target row going
1547 : * forwards.
1548 : */
2584 1549 47 : if ((uint64) (count - 1) <= portal->portalPos / 2 ||
1550 18 : portal->portalPos >= (uint64) LONG_MAX)
1551 : {
7282 1552 29 : DoPortalRewind(portal);
1553 26 : if (count > 1)
7188 bruce 1554 UBC 0 : PortalRunSelect(portal, true, count - 1,
1555 : None_Receiver);
1556 : }
1557 : else
1558 : {
2584 tgl 1559 CBC 18 : long pos = (long) portal->portalPos;
1560 :
7282 1561 18 : if (portal->atEnd)
7282 tgl 1562 UBC 0 : pos++; /* need one extra fetch if off end */
7282 tgl 1563 CBC 18 : if (count <= pos)
7188 bruce 1564 6 : PortalRunSelect(portal, false, pos - count + 1,
1565 : None_Receiver);
1566 12 : else if (count > pos + 1)
1567 6 : PortalRunSelect(portal, true, count - pos - 1,
1568 : None_Receiver);
1569 : }
7282 tgl 1570 41 : return PortalRunSelect(portal, true, 1L, dest);
1571 : }
1572 31 : else if (count < 0)
1573 : {
1574 : /*
1575 : * Definition: Advance to end, back up abs(count)-1 rows,
1576 : * return prior row (if any). We could optimize this if we
1577 : * knew in advance where the end was, but typically we won't.
1578 : * (Is it worth considering case where count > half of size of
1579 : * query? We could rewind once we know the size ...)
1580 : */
7278 1581 27 : PortalRunSelect(portal, true, FETCH_ALL, None_Receiver);
7282 1582 27 : if (count < -1)
7188 bruce 1583 UBC 0 : PortalRunSelect(portal, false, -count - 1, None_Receiver);
7282 tgl 1584 CBC 27 : return PortalRunSelect(portal, false, 1L, dest);
1585 : }
1586 : else
1587 : {
1588 : /* count == 0 */
1589 : /* Rewind to start, return zero rows */
1590 4 : DoPortalRewind(portal);
1591 4 : return PortalRunSelect(portal, true, 0L, dest);
1592 : }
1593 : break;
1594 42 : case FETCH_RELATIVE:
1595 42 : if (count > 0)
1596 : {
1597 : /*
1598 : * Definition: advance count-1 rows, return next row (if any).
1599 : */
1600 18 : if (count > 1)
7188 bruce 1601 12 : PortalRunSelect(portal, true, count - 1, None_Receiver);
7282 tgl 1602 18 : return PortalRunSelect(portal, true, 1L, dest);
1603 : }
1604 24 : else if (count < 0)
1605 : {
1606 : /*
1607 : * Definition: back up abs(count)-1 rows, return prior row (if
1608 : * any).
1609 : */
1610 15 : if (count < -1)
7188 bruce 1611 9 : PortalRunSelect(portal, false, -count - 1, None_Receiver);
7282 tgl 1612 15 : return PortalRunSelect(portal, false, 1L, dest);
1613 : }
1614 : else
1615 : {
1616 : /* count == 0 */
1617 : /* Same as FETCH FORWARD 0, so fall out of switch */
1618 9 : fdirection = FETCH_FORWARD;
1619 : }
1620 9 : break;
7282 tgl 1621 UBC 0 : default:
7201 1622 0 : elog(ERROR, "bogus direction");
1623 : break;
1624 : }
1625 :
1626 : /*
1627 : * Get here with fdirection == FETCH_FORWARD or FETCH_BACKWARD, and count
1628 : * >= 0.
1629 : */
7282 tgl 1630 CBC 24396 : forward = (fdirection == FETCH_FORWARD);
1631 :
1632 : /*
1633 : * Zero count means to re-fetch the current row, if any (per SQL)
1634 : */
1635 24396 : if (count == 0)
1636 : {
1637 : bool on_row;
1638 :
1639 : /* Are we sitting on a row? */
1640 9 : on_row = (!portal->atStart && !portal->atEnd);
1641 :
6366 alvherre 1642 9 : if (dest->mydest == DestNone)
1643 : {
1644 : /* MOVE 0 returns 0/1 based on if FETCH 0 would return a row */
2584 tgl 1645 UBC 0 : return on_row ? 1 : 0;
1646 : }
1647 : else
1648 : {
1649 : /*
1650 : * If we are sitting on a row, back up one so we can re-fetch it.
1651 : * If we are not sitting on a row, we still have to start up and
1652 : * shut down the executor so that the destination is initialized
1653 : * and shut down correctly; so keep going. To PortalRunSelect,
1654 : * count == 0 means we will retrieve no row.
1655 : */
7282 tgl 1656 CBC 9 : if (on_row)
1657 : {
7278 1658 9 : PortalRunSelect(portal, false, 1L, None_Receiver);
1659 : /* Set up to fetch one row forward */
7282 1660 6 : count = 1;
1661 6 : forward = true;
1662 : }
1663 : }
1664 : }
1665 :
1666 : /*
1667 : * Optimize MOVE BACKWARD ALL into a Rewind.
1668 : */
6366 alvherre 1669 24393 : if (!forward && count == FETCH_ALL && dest->mydest == DestNone)
1670 : {
2584 tgl 1671 30 : uint64 result = portal->portalPos;
1672 :
7282 1673 30 : if (result > 0 && !portal->atEnd)
7282 tgl 1674 UBC 0 : result--;
7282 tgl 1675 CBC 30 : DoPortalRewind(portal);
1676 30 : return result;
1677 : }
1678 :
1679 24363 : return PortalRunSelect(portal, forward, count, dest);
1680 : }
1681 :
1682 : /*
1683 : * DoPortalRewind - rewind a Portal to starting point
1684 : */
1685 : static void
1686 63 : DoPortalRewind(Portal portal)
1687 : {
1688 : QueryDesc *queryDesc;
1689 :
1690 : /*
1691 : * No work is needed if we've not advanced nor attempted to advance the
1692 : * cursor (and we don't want to throw a NO SCROLL error in this case).
1693 : */
576 1694 63 : if (portal->atStart && !portal->atEnd)
1695 10 : return;
1696 :
1697 : /* Otherwise, cursor must allow scrolling */
1698 53 : if (portal->cursorOptions & CURSOR_OPT_NO_SCROLL)
1699 3 : ereport(ERROR,
1700 : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
1701 : errmsg("cursor can only scan forward"),
1702 : errhint("Declare it with SCROLL option to enable backward scan.")));
1703 :
1704 : /* Rewind holdStore, if we have one */
7282 1705 50 : if (portal->holdStore)
1706 : {
1707 : MemoryContext oldcontext;
1708 :
1709 3 : oldcontext = MemoryContextSwitchTo(portal->holdContext);
1710 3 : tuplestore_rescan(portal->holdStore);
1711 3 : MemoryContextSwitchTo(oldcontext);
1712 : }
1713 :
1714 : /* Rewind executor, if active */
1940 peter_e 1715 50 : queryDesc = portal->queryDesc;
3259 tgl 1716 50 : if (queryDesc)
1717 : {
1718 47 : PushActiveSnapshot(queryDesc->snapshot);
1719 47 : ExecutorRewind(queryDesc);
1720 47 : PopActiveSnapshot();
1721 : }
1722 :
7282 1723 50 : portal->atStart = true;
1724 50 : portal->atEnd = false;
1725 50 : portal->portalPos = 0;
1726 : }
1727 :
1728 : /*
1729 : * PlannedStmtRequiresSnapshot - what it says on the tin
1730 : */
1731 : bool
688 1732 379155 : PlannedStmtRequiresSnapshot(PlannedStmt *pstmt)
1733 : {
1734 379155 : Node *utilityStmt = pstmt->utilityStmt;
1735 :
1736 : /* If it's not a utility statement, it definitely needs a snapshot */
1737 379155 : if (utilityStmt == NULL)
1738 35162 : return true;
1739 :
1740 : /*
1741 : * Most utility statements need a snapshot, and the default presumption
1742 : * about new ones should be that they do too. Hence, enumerate those that
1743 : * do not need one.
1744 : *
1745 : * Transaction control, LOCK, and SET must *not* set a snapshot, since
1746 : * they need to be executable at the start of a transaction-snapshot-mode
1747 : * transaction without freezing a snapshot. By extension we allow SHOW
1748 : * not to set a snapshot. The other stmts listed are just efficiency
1749 : * hacks. Beware of listing anything that can modify the database --- if,
1750 : * say, it has to update an index with expressions that invoke
1751 : * user-defined functions, then it had better have a snapshot.
1752 : */
1753 343993 : if (IsA(utilityStmt, TransactionStmt) ||
1754 327419 : IsA(utilityStmt, LockStmt) ||
1755 326897 : IsA(utilityStmt, VariableSetStmt) ||
1756 310675 : IsA(utilityStmt, VariableShowStmt) ||
1757 310307 : IsA(utilityStmt, ConstraintsSetStmt) ||
1758 : /* efficiency hacks from here down */
1759 310256 : IsA(utilityStmt, FetchStmt) ||
1760 307417 : IsA(utilityStmt, ListenStmt) ||
1761 307380 : IsA(utilityStmt, NotifyStmt) ||
1762 307336 : IsA(utilityStmt, UnlistenStmt) ||
1763 307317 : IsA(utilityStmt, CheckPointStmt))
1764 36753 : return false;
1765 :
1766 307240 : return true;
1767 : }
1768 :
1769 : /*
1770 : * EnsurePortalSnapshotExists - recreate Portal-level snapshot, if needed
1771 : *
1772 : * Generally, we will have an active snapshot whenever we are executing
1773 : * inside a Portal, unless the Portal's query is one of the utility
1774 : * statements exempted from that rule (see PlannedStmtRequiresSnapshot).
1775 : * However, procedures and DO blocks can commit or abort the transaction,
1776 : * and thereby destroy all snapshots. This function can be called to
1777 : * re-establish the Portal-level snapshot when none exists.
1778 : */
1779 : void
1780 163882 : EnsurePortalSnapshotExists(void)
1781 : {
1782 : Portal portal;
1783 :
1784 : /*
1785 : * Nothing to do if a snapshot is set. (We take it on faith that the
1786 : * outermost active snapshot belongs to some Portal; or if there is no
1787 : * Portal, it's somebody else's responsibility to manage things.)
1788 : */
1789 163882 : if (ActiveSnapshotSet())
1790 161714 : return;
1791 :
1792 : /* Otherwise, we'd better have an active Portal */
1793 2168 : portal = ActivePortal;
617 1794 2168 : if (unlikely(portal == NULL))
617 tgl 1795 UBC 0 : elog(ERROR, "cannot execute SQL without an outer snapshot or portal");
688 tgl 1796 CBC 2168 : Assert(portal->portalSnapshot == NULL);
1797 :
1798 : /*
1799 : * Create a new snapshot, make it active, and remember it in portal.
1800 : * Because the portal now references the snapshot, we must tell snapmgr.c
1801 : * that the snapshot belongs to the portal's transaction level, else we
1802 : * risk portalSnapshot becoming a dangling pointer.
1803 : */
555 1804 2168 : PushActiveSnapshotWithLevel(GetTransactionSnapshot(), portal->createLevel);
1805 : /* PushActiveSnapshotWithLevel might have copied the snapshot */
688 1806 2168 : portal->portalSnapshot = GetActiveSnapshot();
1807 : }
|