Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * portalcmds.c
4 : : * Utility commands affecting portals (that is, SQL cursor commands)
5 : : *
6 : : * Note: see also tcop/pquery.c, which implements portal operations for
7 : : * the FE/BE protocol. This module uses pquery.c for some operations.
8 : : * And both modules depend on utils/mmgr/portalmem.c, which controls
9 : : * storage management for portals (but doesn't run any queries in them).
10 : : *
11 : : *
12 : : * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
13 : : * Portions Copyright (c) 1994, Regents of the University of California
14 : : *
15 : : *
16 : : * IDENTIFICATION
17 : : * src/backend/commands/portalcmds.c
18 : : *
19 : : *-------------------------------------------------------------------------
20 : : */
21 : :
22 : : #include "postgres.h"
23 : :
24 : : #include <limits.h>
25 : :
26 : : #include "access/xact.h"
27 : : #include "commands/portalcmds.h"
28 : : #include "executor/executor.h"
29 : : #include "executor/tstoreReceiver.h"
30 : : #include "miscadmin.h"
31 : : #include "nodes/queryjumble.h"
32 : : #include "parser/analyze.h"
33 : : #include "rewrite/rewriteHandler.h"
34 : : #include "tcop/pquery.h"
35 : : #include "tcop/tcopprot.h"
36 : : #include "utils/memutils.h"
37 : : #include "utils/snapmgr.h"
38 : :
39 : :
40 : : /*
41 : : * PerformCursorOpen
42 : : * Execute SQL DECLARE CURSOR command.
43 : : */
44 : : void
2123 peter@eisentraut.org 45 :CBC 2209 : PerformCursorOpen(ParseState *pstate, DeclareCursorStmt *cstmt, ParamListInfo params,
46 : : bool isTopLevel)
47 : : {
3196 tgl@sss.pgh.pa.us 48 : 2209 : Query *query = castNode(Query, cstmt->query);
364 michael@paquier.xyz 49 : 2209 : JumbleState *jstate = NULL;
50 : : List *rewritten;
51 : : PlannedStmt *plan;
52 : : Portal portal;
53 : : MemoryContext oldContext;
54 : : char *queryString;
55 : :
56 : : /*
57 : : * Disallow empty-string cursor name (conflicts with protocol-level
58 : : * unnamed portal).
59 : : */
6758 tgl@sss.pgh.pa.us 60 [ + - - + ]: 2209 : if (!cstmt->portalname || cstmt->portalname[0] == '\0')
8135 tgl@sss.pgh.pa.us 61 [ # # ]:UBC 0 : ereport(ERROR,
62 : : (errcode(ERRCODE_INVALID_CURSOR_NAME),
63 : : errmsg("invalid cursor name: must not be empty")));
64 : :
65 : : /*
66 : : * If this is a non-holdable cursor, we require that this statement has
67 : : * been executed inside a transaction block (or else, it would have no
68 : : * user-visible effect).
69 : : */
6758 tgl@sss.pgh.pa.us 70 [ + + ]:CBC 2209 : if (!(cstmt->options & CURSOR_OPT_HOLD))
2810 peter_e@gmx.net 71 : 2172 : RequireTransactionBlock(isTopLevel, "DECLARE CURSOR");
1813 noah@leadboat.com 72 [ + + ]: 37 : else if (InSecurityRestrictedOperation())
73 [ + - ]: 6 : ereport(ERROR,
74 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
75 : : errmsg("cannot create a cursor WITH HOLD within security-restricted operation")));
76 : :
77 : : /* Query contained by DeclareCursor needs to be jumbled if requested */
364 michael@paquier.xyz 78 [ + + ]: 2202 : if (IsQueryIdEnabled())
79 : 396 : jstate = JumbleQuery(query);
80 : :
81 [ + + ]: 2202 : if (post_parse_analyze_hook)
82 : 396 : (*post_parse_analyze_hook) (pstate, query, jstate);
83 : :
84 : : /*
85 : : * Parse analysis was done already, but we still have to run the rule
86 : : * rewriter. We do not do AcquireRewriteLocks: we assume the query either
87 : : * came straight from the parser, or suitable locks were acquired by
88 : : * plancache.c.
89 : : */
1592 tgl@sss.pgh.pa.us 90 : 2202 : rewritten = QueryRewrite(query);
91 : :
92 : : /* SELECT should never rewrite to more or less than one query */
3208 93 [ - + ]: 2202 : if (list_length(rewritten) != 1)
3208 tgl@sss.pgh.pa.us 94 [ # # ]:UBC 0 : elog(ERROR, "non-SELECT statement in DECLARE CURSOR");
95 : :
3122 tgl@sss.pgh.pa.us 96 :CBC 2202 : query = linitial_node(Query, rewritten);
97 : :
3208 98 [ - + ]: 2202 : if (query->commandType != CMD_SELECT)
3208 tgl@sss.pgh.pa.us 99 [ # # ]:UBC 0 : elog(ERROR, "non-SELECT statement in DECLARE CURSOR");
100 : :
101 : : /* Plan the query, applying the specified options */
19 rhaas@postgresql.org 102 :GNC 2202 : plan = pg_plan_query(query, pstate->p_sourcetext, cstmt->options, params,
103 : : NULL);
104 : :
105 : : /*
106 : : * Create a portal and copy the plan and query string into its memory.
107 : : */
6758 tgl@sss.pgh.pa.us 108 :CBC 2202 : portal = CreatePortal(cstmt->portalname, false, false);
109 : :
2872 peter_e@gmx.net 110 : 2202 : oldContext = MemoryContextSwitchTo(portal->portalContext);
111 : :
3208 tgl@sss.pgh.pa.us 112 : 2202 : plan = copyObject(plan);
113 : :
2123 peter@eisentraut.org 114 : 2202 : queryString = pstrdup(pstate->p_sourcetext);
115 : :
8214 tgl@sss.pgh.pa.us 116 : 2202 : PortalDefineQuery(portal,
117 : : NULL,
118 : : queryString,
119 : : CMDTAG_SELECT, /* cursor's query is always a SELECT */
3208 120 : 2202 : list_make1(plan),
121 : : NULL);
122 : :
123 : : /*----------
124 : : * Also copy the outer portal's parameter list into the inner portal's
125 : : * memory context. We want to pass down the parameter values in case we
126 : : * had a command like
127 : : * DECLARE c CURSOR FOR SELECT ... WHERE foo = $1
128 : : * This will have been parsed using the outer parameter set and the
129 : : * parameter value needs to be preserved for use when the cursor is
130 : : * executed.
131 : : *----------
132 : : */
7756 133 : 2202 : params = copyParamList(params);
134 : :
8214 135 : 2202 : MemoryContextSwitchTo(oldContext);
136 : :
137 : : /*
138 : : * Set up options for portal.
139 : : *
140 : : * If the user didn't specify a SCROLL type, allow or disallow scrolling
141 : : * based on whether it would require any additional runtime overhead to do
142 : : * so. Also, we disallow scrolling for FOR UPDATE cursors.
143 : : */
6758 144 : 2202 : portal->cursorOptions = cstmt->options;
8214 145 [ + + ]: 2202 : if (!(portal->cursorOptions & (CURSOR_OPT_SCROLL | CURSOR_OPT_NO_SCROLL)))
146 : : {
3208 147 [ + + + + ]: 4087 : if (plan->rowMarks == NIL &&
148 : 2003 : ExecSupportsBackwardScan(plan->planTree))
8214 149 : 1631 : portal->cursorOptions |= CURSOR_OPT_SCROLL;
150 : : else
151 : 453 : portal->cursorOptions |= CURSOR_OPT_NO_SCROLL;
152 : : }
153 : :
154 : : /*
155 : : * Start execution, inserting parameters if any.
156 : : */
4718 157 : 2202 : PortalStart(portal, params, 0, GetActiveSnapshot());
158 : :
8214 159 [ - + ]: 2202 : Assert(portal->strategy == PORTAL_ONE_SELECT);
160 : :
161 : : /*
162 : : * We're done; the query won't actually be run until PerformPortalFetch is
163 : : * called.
164 : : */
8267 165 : 2202 : }
166 : :
167 : : /*
168 : : * PerformPortalFetch
169 : : * Execute SQL FETCH or MOVE command.
170 : : *
171 : : * stmt: parsetree node for command
172 : : * dest: where to send results
173 : : * qc: where to store a command completion status data.
174 : : *
175 : : * qc may be NULL if caller doesn't want status data.
176 : : */
177 : : void
8266 178 : 3722 : PerformPortalFetch(FetchStmt *stmt,
179 : : DestReceiver *dest,
180 : : QueryCompletion *qc)
181 : : {
182 : : Portal portal;
183 : : uint64 nprocessed;
184 : :
185 : : /*
186 : : * Disallow empty-string cursor name (conflicts with protocol-level
187 : : * unnamed portal).
188 : : */
8211 189 [ + - - + ]: 3722 : if (!stmt->portalname || stmt->portalname[0] == '\0')
8135 tgl@sss.pgh.pa.us 190 [ # # ]:UBC 0 : ereport(ERROR,
191 : : (errcode(ERRCODE_INVALID_CURSOR_NAME),
192 : : errmsg("invalid cursor name: must not be empty")));
193 : :
194 : : /* get the portal from the portal name */
8266 tgl@sss.pgh.pa.us 195 :CBC 3722 : portal = GetPortalByName(stmt->portalname);
8596 196 [ + + ]: 3722 : if (!PortalIsValid(portal))
197 : : {
8100 peter_e@gmx.net 198 [ + - ]: 17 : ereport(ERROR,
199 : : (errcode(ERRCODE_UNDEFINED_CURSOR),
200 : : errmsg("cursor \"%s\" does not exist", stmt->portalname)));
201 : : return; /* keep compiler happy */
202 : : }
203 : :
204 : : /* Adjust dest if needed. MOVE wants destination DestNone */
8214 tgl@sss.pgh.pa.us 205 [ + + ]: 3705 : if (stmt->ismove)
8208 206 : 29 : dest = None_Receiver;
207 : :
208 : : /* Do it */
8214 209 : 3705 : nprocessed = PortalRunFetch(portal,
210 : : stmt->direction,
211 : : stmt->howMany,
212 : : dest);
213 : :
214 : : /* Return command status if wanted */
2065 alvherre@alvh.no-ip. 215 [ + - ]: 3670 : if (qc)
216 [ + + ]: 3670 : SetQueryCompletion(qc, stmt->ismove ? CMDTAG_MOVE : CMDTAG_FETCH,
217 : : nprocessed);
218 : : }
219 : :
220 : : /*
221 : : * PerformPortalClose
222 : : * Close a cursor.
223 : : */
224 : : void
8211 tgl@sss.pgh.pa.us 225 : 1084 : PerformPortalClose(const char *name)
226 : : {
227 : : Portal portal;
228 : :
229 : : /* NULL means CLOSE ALL */
6773 neilc@samurai.com 230 [ + + ]: 1084 : if (name == NULL)
231 : : {
232 : 6 : PortalHashTableDeleteAll();
233 : 6 : return;
234 : : }
235 : :
236 : : /*
237 : : * Disallow empty-string cursor name (conflicts with protocol-level
238 : : * unnamed portal).
239 : : */
240 [ - + ]: 1078 : if (name[0] == '\0')
8135 tgl@sss.pgh.pa.us 241 [ # # ]:UBC 0 : ereport(ERROR,
242 : : (errcode(ERRCODE_INVALID_CURSOR_NAME),
243 : : errmsg("invalid cursor name: must not be empty")));
244 : :
245 : : /*
246 : : * get the portal from the portal name
247 : : */
8596 tgl@sss.pgh.pa.us 248 :CBC 1078 : portal = GetPortalByName(name);
249 [ + + ]: 1078 : if (!PortalIsValid(portal))
250 : : {
8100 peter_e@gmx.net 251 [ + - ]: 1 : ereport(ERROR,
252 : : (errcode(ERRCODE_UNDEFINED_CURSOR),
253 : : errmsg("cursor \"%s\" does not exist", name)));
254 : : return; /* keep compiler happy */
255 : : }
256 : :
257 : : /*
258 : : * Note: PortalCleanup is called as a side-effect, if not already done.
259 : : */
8250 bruce@momjian.us 260 : 1077 : PortalDrop(portal, false);
261 : : }
262 : :
263 : : /*
264 : : * PortalCleanup
265 : : *
266 : : * Clean up a portal when it's dropped. This is the standard cleanup hook
267 : : * for portals.
268 : : *
269 : : * Note: if portal->status is PORTAL_FAILED, we are probably being called
270 : : * during error abort, and must be careful to avoid doing anything that
271 : : * is likely to fail again.
272 : : */
273 : : void
7772 tgl@sss.pgh.pa.us 274 : 366798 : PortalCleanup(Portal portal)
275 : : {
276 : : QueryDesc *queryDesc;
277 : :
278 : : /*
279 : : * sanity checks
280 : : */
1095 peter@eisentraut.org 281 [ - + ]: 366798 : Assert(PortalIsValid(portal));
282 [ - + ]: 366798 : Assert(portal->cleanup == PortalCleanup);
283 : :
284 : : /*
285 : : * Shut down executor, if still running. We skip this during error abort,
286 : : * since other mechanisms will take care of releasing executor resources,
287 : : * and we can't be sure that ExecutorEnd itself wouldn't fail.
288 : : */
2872 peter_e@gmx.net 289 : 366798 : queryDesc = portal->queryDesc;
8214 tgl@sss.pgh.pa.us 290 [ + + ]: 366798 : if (queryDesc)
291 : : {
292 : : /*
293 : : * Reset the queryDesc before anything else. This prevents us from
294 : : * trying to shut down the executor twice, in case of an error below.
295 : : * The transaction abort mechanisms will take care of resource cleanup
296 : : * in such a case.
297 : : */
298 : 145332 : portal->queryDesc = NULL;
299 : :
7772 300 [ + + ]: 145332 : if (portal->status != PORTAL_FAILED)
301 : : {
302 : : ResourceOwner saveResourceOwner;
303 : :
304 : : /* We must make the portal's resource owner current */
305 : 140623 : saveResourceOwner = CurrentResourceOwner;
2938 306 [ + - ]: 140623 : if (portal->resowner)
307 : 140623 : CurrentResourceOwner = portal->resowner;
308 : :
309 : 140623 : ExecutorFinish(queryDesc);
310 : 140623 : ExecutorEnd(queryDesc);
311 : 140623 : FreeQueryDesc(queryDesc);
312 : :
7772 313 : 140623 : CurrentResourceOwner = saveResourceOwner;
314 : : }
315 : : }
8250 bruce@momjian.us 316 : 366798 : }
317 : :
318 : : /*
319 : : * PersistHoldablePortal
320 : : *
321 : : * Prepare the specified Portal for access outside of the current
322 : : * transaction. When this function returns, all future accesses to the
323 : : * portal must be done via the Tuplestore (not by invoking the
324 : : * executor).
325 : : */
326 : : void
327 : 41 : PersistHoldablePortal(Portal portal)
328 : : {
2872 peter_e@gmx.net 329 : 41 : QueryDesc *queryDesc = portal->queryDesc;
330 : : Portal saveActivePortal;
331 : : ResourceOwner saveResourceOwner;
332 : : MemoryContext savePortalContext;
333 : : MemoryContext oldcxt;
334 : :
335 : : /*
336 : : * If we're preserving a holdable portal, we had better be inside the
337 : : * transaction that originally created it.
338 : : */
7711 tgl@sss.pgh.pa.us 339 [ - + ]: 41 : Assert(portal->createSubid != InvalidSubTransactionId);
8214 340 [ - + ]: 41 : Assert(queryDesc != NULL);
341 : :
342 : : /*
343 : : * Caller must have created the tuplestore already ... but not a snapshot.
344 : : */
8217 345 [ - + ]: 41 : Assert(portal->holdContext != NULL);
8210 346 [ - + ]: 41 : Assert(portal->holdStore != NULL);
3368 347 [ - + ]: 41 : Assert(portal->holdSnapshot == NULL);
348 : :
349 : : /*
350 : : * Before closing down the executor, we must copy the tupdesc into
351 : : * long-term memory, since it was created in executor memory.
352 : : */
8210 353 : 41 : oldcxt = MemoryContextSwitchTo(portal->holdContext);
354 : :
8214 355 : 41 : portal->tupDesc = CreateTupleDescCopy(portal->tupDesc);
356 : :
357 : 41 : MemoryContextSwitchTo(oldcxt);
358 : :
359 : : /*
360 : : * Check for improper portal use, and mark portal active.
361 : : */
3706 362 : 41 : MarkPortalActive(portal);
363 : :
364 : : /*
365 : : * Set up global portal context pointers.
366 : : */
7890 367 : 41 : saveActivePortal = ActivePortal;
7772 368 : 41 : saveResourceOwner = CurrentResourceOwner;
8214 369 : 41 : savePortalContext = PortalContext;
7758 370 [ + + ]: 41 : PG_TRY();
371 : : {
1509 372 : 41 : ScanDirection direction = ForwardScanDirection;
373 : :
7758 374 : 41 : ActivePortal = portal;
4519 375 [ + - ]: 41 : if (portal->resowner)
376 : 41 : CurrentResourceOwner = portal->resowner;
2872 peter_e@gmx.net 377 : 41 : PortalContext = portal->portalContext;
378 : :
7758 tgl@sss.pgh.pa.us 379 : 41 : MemoryContextSwitchTo(PortalContext);
380 : :
6377 alvherre@alvh.no-ip. 381 : 41 : PushActiveSnapshot(queryDesc->snapshot);
382 : :
383 : : /*
384 : : * If the portal is marked scrollable, we need to store the entire
385 : : * result set in the tuplestore, so that subsequent backward FETCHs
386 : : * can be processed. Otherwise, store only the not-yet-fetched rows.
387 : : * (The latter is not only more efficient, but avoids semantic
388 : : * problems if the query's output isn't stable.)
389 : : *
390 : : * In the no-scroll case, tuple indexes in the tuplestore will not
391 : : * match the cursor's nominal position (portalPos). Currently this
392 : : * causes no difficulty because we only navigate in the tuplestore by
393 : : * relative position, except for the tuplestore_skiptuples call below
394 : : * and the tuplestore_rescan call in DoPortalRewind, both of which are
395 : : * disabled for no-scroll cursors. But someday we might need to track
396 : : * the offset between the holdStore and the cursor's nominal position
397 : : * explicitly.
398 : : */
1602 tgl@sss.pgh.pa.us 399 [ + + ]: 41 : if (portal->cursorOptions & CURSOR_OPT_SCROLL)
400 : : {
401 : 20 : ExecutorRewind(queryDesc);
402 : : }
403 : : else
404 : : {
405 : : /*
406 : : * If we already reached end-of-query, set the direction to
407 : : * NoMovement to avoid trying to fetch any tuples. (This check
408 : : * exists because not all plan node types are robust about being
409 : : * called again if they've already returned NULL once.) We'll
410 : : * still set up an empty tuplestore, though, to keep this from
411 : : * being a special case later.
412 : : */
1509 413 [ - + ]: 21 : if (portal->atEnd)
1509 tgl@sss.pgh.pa.us 414 :UBC 0 : direction = NoMovementScanDirection;
415 : : }
416 : :
417 : : /*
418 : : * Change the destination to output to the tuplestore. Note we tell
419 : : * the tuplestore receiver to detoast all data passed through it; this
420 : : * makes it safe to not keep a snapshot associated with the data.
421 : : */
6175 tgl@sss.pgh.pa.us 422 :CBC 41 : queryDesc->dest = CreateDestReceiver(DestTuplestore);
423 : 41 : SetTuplestoreDestReceiverParams(queryDesc->dest,
424 : : portal->holdStore,
425 : : portal->holdContext,
426 : : true,
427 : : NULL,
428 : : NULL);
429 : :
430 : : /* Fetch the result set into the tuplestore */
322 431 : 41 : ExecutorRun(queryDesc, direction, 0);
432 : :
2972 peter_e@gmx.net 433 : 39 : queryDesc->dest->rDestroy(queryDesc->dest);
7758 tgl@sss.pgh.pa.us 434 : 39 : queryDesc->dest = NULL;
435 : :
436 : : /*
437 : : * Now shut down the inner executor.
438 : : */
3050 439 : 39 : portal->queryDesc = NULL; /* prevent double shutdown */
5356 440 : 39 : ExecutorFinish(queryDesc);
7521 441 : 39 : ExecutorEnd(queryDesc);
6430 alvherre@alvh.no-ip. 442 : 39 : FreeQueryDesc(queryDesc);
443 : :
444 : : /*
445 : : * Set the position in the result set.
446 : : */
7758 tgl@sss.pgh.pa.us 447 : 39 : MemoryContextSwitchTo(portal->holdContext);
448 : :
6838 449 [ - + ]: 39 : if (portal->atEnd)
450 : : {
451 : : /*
452 : : * Just force the tuplestore forward to its end. The size of the
453 : : * skip request here is arbitrary.
454 : : */
4215 tgl@sss.pgh.pa.us 455 [ # # ]:UBC 0 : while (tuplestore_skiptuples(portal->holdStore, 1000000, true))
456 : : /* continue */ ;
457 : : }
458 : : else
459 : : {
7758 tgl@sss.pgh.pa.us 460 :CBC 39 : tuplestore_rescan(portal->holdStore);
461 : :
462 : : /*
463 : : * In the no-scroll case, the start of the tuplestore is exactly
464 : : * where we want to be, so no repositioning is wanted.
465 : : */
1508 466 [ + + ]: 39 : if (portal->cursorOptions & CURSOR_OPT_SCROLL)
467 : : {
468 [ - + ]: 20 : if (!tuplestore_skiptuples(portal->holdStore,
469 : 20 : portal->portalPos,
470 : : true))
1508 tgl@sss.pgh.pa.us 471 [ # # ]:UBC 0 : elog(ERROR, "unexpected end of tuple stream");
472 : : }
473 : : }
474 : : }
7758 tgl@sss.pgh.pa.us 475 :CBC 2 : PG_CATCH();
476 : : {
477 : : /* Uncaught error while executing portal: mark it dead */
5003 478 : 2 : MarkPortalFailed(portal);
479 : :
480 : : /* Restore global vars and propagate error */
7758 481 : 2 : ActivePortal = saveActivePortal;
482 : 2 : CurrentResourceOwner = saveResourceOwner;
483 : 2 : PortalContext = savePortalContext;
484 : :
485 : 2 : PG_RE_THROW();
486 : : }
487 [ - + ]: 39 : PG_END_TRY();
488 : :
8217 489 : 39 : MemoryContextSwitchTo(oldcxt);
490 : :
491 : : /* Mark portal not active */
7772 492 : 39 : portal->status = PORTAL_READY;
493 : :
494 : 39 : ActivePortal = saveActivePortal;
495 : 39 : CurrentResourceOwner = saveResourceOwner;
496 : 39 : PortalContext = savePortalContext;
497 : :
6377 alvherre@alvh.no-ip. 498 : 39 : PopActiveSnapshot();
499 : :
500 : : /*
501 : : * We can now release any subsidiary memory of the portal's context; we'll
502 : : * never use it again. The executor already dropped its context, but this
503 : : * will clean up anything that glommed onto the portal's context via
504 : : * PortalContext.
505 : : */
2872 peter_e@gmx.net 506 : 39 : MemoryContextDeleteChildren(portal->portalContext);
8267 tgl@sss.pgh.pa.us 507 : 39 : }
|