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
2072 peter@eisentraut.org 45 :CBC 2210 : PerformCursorOpen(ParseState *pstate, DeclareCursorStmt *cstmt, ParamListInfo params,
46 : : bool isTopLevel)
47 : : {
3145 tgl@sss.pgh.pa.us 48 : 2210 : Query *query = castNode(Query, cstmt->query);
313 michael@paquier.xyz 49 : 2210 : 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 : : */
6707 tgl@sss.pgh.pa.us 60 [ + - - + ]: 2210 : if (!cstmt->portalname || cstmt->portalname[0] == '\0')
8084 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 : : */
6707 tgl@sss.pgh.pa.us 70 [ + + ]:CBC 2210 : if (!(cstmt->options & CURSOR_OPT_HOLD))
2759 peter_e@gmx.net 71 : 2173 : RequireTransactionBlock(isTopLevel, "DECLARE CURSOR");
1762 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 */
313 michael@paquier.xyz 78 [ + + ]: 2203 : if (IsQueryIdEnabled())
79 : 396 : jstate = JumbleQuery(query);
80 : :
81 [ + + ]: 2203 : 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 : : */
1541 tgl@sss.pgh.pa.us 90 : 2203 : rewritten = QueryRewrite(query);
91 : :
92 : : /* SELECT should never rewrite to more or less than one query */
3157 93 [ - + ]: 2203 : if (list_length(rewritten) != 1)
3157 tgl@sss.pgh.pa.us 94 [ # # ]:UBC 0 : elog(ERROR, "non-SELECT statement in DECLARE CURSOR");
95 : :
3071 tgl@sss.pgh.pa.us 96 :CBC 2203 : query = linitial_node(Query, rewritten);
97 : :
3157 98 [ - + ]: 2203 : if (query->commandType != CMD_SELECT)
3157 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 */
1986 fujii@postgresql.org 102 :CBC 2203 : plan = pg_plan_query(query, pstate->p_sourcetext, cstmt->options, params);
103 : :
104 : : /*
105 : : * Create a portal and copy the plan and query string into its memory.
106 : : */
6707 tgl@sss.pgh.pa.us 107 : 2203 : portal = CreatePortal(cstmt->portalname, false, false);
108 : :
2821 peter_e@gmx.net 109 : 2203 : oldContext = MemoryContextSwitchTo(portal->portalContext);
110 : :
3157 tgl@sss.pgh.pa.us 111 : 2203 : plan = copyObject(plan);
112 : :
2072 peter@eisentraut.org 113 : 2203 : queryString = pstrdup(pstate->p_sourcetext);
114 : :
8163 tgl@sss.pgh.pa.us 115 : 2203 : PortalDefineQuery(portal,
116 : : NULL,
117 : : queryString,
118 : : CMDTAG_SELECT, /* cursor's query is always a SELECT */
3157 119 : 2203 : list_make1(plan),
120 : : NULL);
121 : :
122 : : /*----------
123 : : * Also copy the outer portal's parameter list into the inner portal's
124 : : * memory context. We want to pass down the parameter values in case we
125 : : * had a command like
126 : : * DECLARE c CURSOR FOR SELECT ... WHERE foo = $1
127 : : * This will have been parsed using the outer parameter set and the
128 : : * parameter value needs to be preserved for use when the cursor is
129 : : * executed.
130 : : *----------
131 : : */
7705 132 : 2203 : params = copyParamList(params);
133 : :
8163 134 : 2203 : MemoryContextSwitchTo(oldContext);
135 : :
136 : : /*
137 : : * Set up options for portal.
138 : : *
139 : : * If the user didn't specify a SCROLL type, allow or disallow scrolling
140 : : * based on whether it would require any additional runtime overhead to do
141 : : * so. Also, we disallow scrolling for FOR UPDATE cursors.
142 : : */
6707 143 : 2203 : portal->cursorOptions = cstmt->options;
8163 144 [ + + ]: 2203 : if (!(portal->cursorOptions & (CURSOR_OPT_SCROLL | CURSOR_OPT_NO_SCROLL)))
145 : : {
3157 146 [ + + + + ]: 4089 : if (plan->rowMarks == NIL &&
147 : 2004 : ExecSupportsBackwardScan(plan->planTree))
8163 148 : 1634 : portal->cursorOptions |= CURSOR_OPT_SCROLL;
149 : : else
150 : 451 : portal->cursorOptions |= CURSOR_OPT_NO_SCROLL;
151 : : }
152 : :
153 : : /*
154 : : * Start execution, inserting parameters if any.
155 : : */
4667 156 : 2203 : PortalStart(portal, params, 0, GetActiveSnapshot());
157 : :
8163 158 [ - + ]: 2203 : Assert(portal->strategy == PORTAL_ONE_SELECT);
159 : :
160 : : /*
161 : : * We're done; the query won't actually be run until PerformPortalFetch is
162 : : * called.
163 : : */
8216 164 : 2203 : }
165 : :
166 : : /*
167 : : * PerformPortalFetch
168 : : * Execute SQL FETCH or MOVE command.
169 : : *
170 : : * stmt: parsetree node for command
171 : : * dest: where to send results
172 : : * qc: where to store a command completion status data.
173 : : *
174 : : * qc may be NULL if caller doesn't want status data.
175 : : */
176 : : void
8215 177 : 3725 : PerformPortalFetch(FetchStmt *stmt,
178 : : DestReceiver *dest,
179 : : QueryCompletion *qc)
180 : : {
181 : : Portal portal;
182 : : uint64 nprocessed;
183 : :
184 : : /*
185 : : * Disallow empty-string cursor name (conflicts with protocol-level
186 : : * unnamed portal).
187 : : */
8160 188 [ + - - + ]: 3725 : if (!stmt->portalname || stmt->portalname[0] == '\0')
8084 tgl@sss.pgh.pa.us 189 [ # # ]:UBC 0 : ereport(ERROR,
190 : : (errcode(ERRCODE_INVALID_CURSOR_NAME),
191 : : errmsg("invalid cursor name: must not be empty")));
192 : :
193 : : /* get the portal from the portal name */
8215 tgl@sss.pgh.pa.us 194 :CBC 3725 : portal = GetPortalByName(stmt->portalname);
8545 195 [ + + ]: 3725 : if (!PortalIsValid(portal))
196 : : {
8049 peter_e@gmx.net 197 [ + - ]: 17 : ereport(ERROR,
198 : : (errcode(ERRCODE_UNDEFINED_CURSOR),
199 : : errmsg("cursor \"%s\" does not exist", stmt->portalname)));
200 : : return; /* keep compiler happy */
201 : : }
202 : :
203 : : /* Adjust dest if needed. MOVE wants destination DestNone */
8163 tgl@sss.pgh.pa.us 204 [ + + ]: 3708 : if (stmt->ismove)
8157 205 : 29 : dest = None_Receiver;
206 : :
207 : : /* Do it */
8163 208 : 3708 : nprocessed = PortalRunFetch(portal,
209 : : stmt->direction,
210 : : stmt->howMany,
211 : : dest);
212 : :
213 : : /* Return command status if wanted */
2014 alvherre@alvh.no-ip. 214 [ + - ]: 3673 : if (qc)
215 [ + + ]: 3673 : SetQueryCompletion(qc, stmt->ismove ? CMDTAG_MOVE : CMDTAG_FETCH,
216 : : nprocessed);
217 : : }
218 : :
219 : : /*
220 : : * PerformPortalClose
221 : : * Close a cursor.
222 : : */
223 : : void
8160 tgl@sss.pgh.pa.us 224 : 1085 : PerformPortalClose(const char *name)
225 : : {
226 : : Portal portal;
227 : :
228 : : /* NULL means CLOSE ALL */
6722 neilc@samurai.com 229 [ + + ]: 1085 : if (name == NULL)
230 : : {
231 : 6 : PortalHashTableDeleteAll();
232 : 6 : return;
233 : : }
234 : :
235 : : /*
236 : : * Disallow empty-string cursor name (conflicts with protocol-level
237 : : * unnamed portal).
238 : : */
239 [ - + ]: 1079 : if (name[0] == '\0')
8084 tgl@sss.pgh.pa.us 240 [ # # ]:UBC 0 : ereport(ERROR,
241 : : (errcode(ERRCODE_INVALID_CURSOR_NAME),
242 : : errmsg("invalid cursor name: must not be empty")));
243 : :
244 : : /*
245 : : * get the portal from the portal name
246 : : */
8545 tgl@sss.pgh.pa.us 247 :CBC 1079 : portal = GetPortalByName(name);
248 [ + + ]: 1079 : if (!PortalIsValid(portal))
249 : : {
8049 peter_e@gmx.net 250 [ + - ]: 1 : ereport(ERROR,
251 : : (errcode(ERRCODE_UNDEFINED_CURSOR),
252 : : errmsg("cursor \"%s\" does not exist", name)));
253 : : return; /* keep compiler happy */
254 : : }
255 : :
256 : : /*
257 : : * Note: PortalCleanup is called as a side-effect, if not already done.
258 : : */
8199 bruce@momjian.us 259 : 1078 : PortalDrop(portal, false);
260 : : }
261 : :
262 : : /*
263 : : * PortalCleanup
264 : : *
265 : : * Clean up a portal when it's dropped. This is the standard cleanup hook
266 : : * for portals.
267 : : *
268 : : * Note: if portal->status is PORTAL_FAILED, we are probably being called
269 : : * during error abort, and must be careful to avoid doing anything that
270 : : * is likely to fail again.
271 : : */
272 : : void
7721 tgl@sss.pgh.pa.us 273 : 365000 : PortalCleanup(Portal portal)
274 : : {
275 : : QueryDesc *queryDesc;
276 : :
277 : : /*
278 : : * sanity checks
279 : : */
1044 peter@eisentraut.org 280 [ - + ]: 365000 : Assert(PortalIsValid(portal));
281 [ - + ]: 365000 : Assert(portal->cleanup == PortalCleanup);
282 : :
283 : : /*
284 : : * Shut down executor, if still running. We skip this during error abort,
285 : : * since other mechanisms will take care of releasing executor resources,
286 : : * and we can't be sure that ExecutorEnd itself wouldn't fail.
287 : : */
2821 peter_e@gmx.net 288 : 365000 : queryDesc = portal->queryDesc;
8163 tgl@sss.pgh.pa.us 289 [ + + ]: 365000 : if (queryDesc)
290 : : {
291 : : /*
292 : : * Reset the queryDesc before anything else. This prevents us from
293 : : * trying to shut down the executor twice, in case of an error below.
294 : : * The transaction abort mechanisms will take care of resource cleanup
295 : : * in such a case.
296 : : */
297 : 144632 : portal->queryDesc = NULL;
298 : :
7721 299 [ + + ]: 144632 : if (portal->status != PORTAL_FAILED)
300 : : {
301 : : ResourceOwner saveResourceOwner;
302 : :
303 : : /* We must make the portal's resource owner current */
304 : 140004 : saveResourceOwner = CurrentResourceOwner;
2887 305 [ + - ]: 140004 : if (portal->resowner)
306 : 140004 : CurrentResourceOwner = portal->resowner;
307 : :
308 : 140004 : ExecutorFinish(queryDesc);
309 : 140004 : ExecutorEnd(queryDesc);
310 : 140004 : FreeQueryDesc(queryDesc);
311 : :
7721 312 : 140004 : CurrentResourceOwner = saveResourceOwner;
313 : : }
314 : : }
8199 bruce@momjian.us 315 : 365000 : }
316 : :
317 : : /*
318 : : * PersistHoldablePortal
319 : : *
320 : : * Prepare the specified Portal for access outside of the current
321 : : * transaction. When this function returns, all future accesses to the
322 : : * portal must be done via the Tuplestore (not by invoking the
323 : : * executor).
324 : : */
325 : : void
326 : 41 : PersistHoldablePortal(Portal portal)
327 : : {
2821 peter_e@gmx.net 328 : 41 : QueryDesc *queryDesc = portal->queryDesc;
329 : : Portal saveActivePortal;
330 : : ResourceOwner saveResourceOwner;
331 : : MemoryContext savePortalContext;
332 : : MemoryContext oldcxt;
333 : :
334 : : /*
335 : : * If we're preserving a holdable portal, we had better be inside the
336 : : * transaction that originally created it.
337 : : */
7660 tgl@sss.pgh.pa.us 338 [ - + ]: 41 : Assert(portal->createSubid != InvalidSubTransactionId);
8163 339 [ - + ]: 41 : Assert(queryDesc != NULL);
340 : :
341 : : /*
342 : : * Caller must have created the tuplestore already ... but not a snapshot.
343 : : */
8166 344 [ - + ]: 41 : Assert(portal->holdContext != NULL);
8159 345 [ - + ]: 41 : Assert(portal->holdStore != NULL);
3317 346 [ - + ]: 41 : Assert(portal->holdSnapshot == NULL);
347 : :
348 : : /*
349 : : * Before closing down the executor, we must copy the tupdesc into
350 : : * long-term memory, since it was created in executor memory.
351 : : */
8159 352 : 41 : oldcxt = MemoryContextSwitchTo(portal->holdContext);
353 : :
8163 354 : 41 : portal->tupDesc = CreateTupleDescCopy(portal->tupDesc);
355 : :
356 : 41 : MemoryContextSwitchTo(oldcxt);
357 : :
358 : : /*
359 : : * Check for improper portal use, and mark portal active.
360 : : */
3655 361 : 41 : MarkPortalActive(portal);
362 : :
363 : : /*
364 : : * Set up global portal context pointers.
365 : : */
7839 366 : 41 : saveActivePortal = ActivePortal;
7721 367 : 41 : saveResourceOwner = CurrentResourceOwner;
8163 368 : 41 : savePortalContext = PortalContext;
7707 369 [ + + ]: 41 : PG_TRY();
370 : : {
1458 371 : 41 : ScanDirection direction = ForwardScanDirection;
372 : :
7707 373 : 41 : ActivePortal = portal;
4468 374 [ + - ]: 41 : if (portal->resowner)
375 : 41 : CurrentResourceOwner = portal->resowner;
2821 peter_e@gmx.net 376 : 41 : PortalContext = portal->portalContext;
377 : :
7707 tgl@sss.pgh.pa.us 378 : 41 : MemoryContextSwitchTo(PortalContext);
379 : :
6326 alvherre@alvh.no-ip. 380 : 41 : PushActiveSnapshot(queryDesc->snapshot);
381 : :
382 : : /*
383 : : * If the portal is marked scrollable, we need to store the entire
384 : : * result set in the tuplestore, so that subsequent backward FETCHs
385 : : * can be processed. Otherwise, store only the not-yet-fetched rows.
386 : : * (The latter is not only more efficient, but avoids semantic
387 : : * problems if the query's output isn't stable.)
388 : : *
389 : : * In the no-scroll case, tuple indexes in the tuplestore will not
390 : : * match the cursor's nominal position (portalPos). Currently this
391 : : * causes no difficulty because we only navigate in the tuplestore by
392 : : * relative position, except for the tuplestore_skiptuples call below
393 : : * and the tuplestore_rescan call in DoPortalRewind, both of which are
394 : : * disabled for no-scroll cursors. But someday we might need to track
395 : : * the offset between the holdStore and the cursor's nominal position
396 : : * explicitly.
397 : : */
1551 tgl@sss.pgh.pa.us 398 [ + + ]: 41 : if (portal->cursorOptions & CURSOR_OPT_SCROLL)
399 : : {
400 : 20 : ExecutorRewind(queryDesc);
401 : : }
402 : : else
403 : : {
404 : : /*
405 : : * If we already reached end-of-query, set the direction to
406 : : * NoMovement to avoid trying to fetch any tuples. (This check
407 : : * exists because not all plan node types are robust about being
408 : : * called again if they've already returned NULL once.) We'll
409 : : * still set up an empty tuplestore, though, to keep this from
410 : : * being a special case later.
411 : : */
1458 412 [ - + ]: 21 : if (portal->atEnd)
1458 tgl@sss.pgh.pa.us 413 :UBC 0 : direction = NoMovementScanDirection;
414 : : }
415 : :
416 : : /*
417 : : * Change the destination to output to the tuplestore. Note we tell
418 : : * the tuplestore receiver to detoast all data passed through it; this
419 : : * makes it safe to not keep a snapshot associated with the data.
420 : : */
6124 tgl@sss.pgh.pa.us 421 :CBC 41 : queryDesc->dest = CreateDestReceiver(DestTuplestore);
422 : 41 : SetTuplestoreDestReceiverParams(queryDesc->dest,
423 : : portal->holdStore,
424 : : portal->holdContext,
425 : : true,
426 : : NULL,
427 : : NULL);
428 : :
429 : : /* Fetch the result set into the tuplestore */
271 430 : 41 : ExecutorRun(queryDesc, direction, 0);
431 : :
2921 peter_e@gmx.net 432 : 39 : queryDesc->dest->rDestroy(queryDesc->dest);
7707 tgl@sss.pgh.pa.us 433 : 39 : queryDesc->dest = NULL;
434 : :
435 : : /*
436 : : * Now shut down the inner executor.
437 : : */
2999 438 : 39 : portal->queryDesc = NULL; /* prevent double shutdown */
5305 439 : 39 : ExecutorFinish(queryDesc);
7470 440 : 39 : ExecutorEnd(queryDesc);
6379 alvherre@alvh.no-ip. 441 : 39 : FreeQueryDesc(queryDesc);
442 : :
443 : : /*
444 : : * Set the position in the result set.
445 : : */
7707 tgl@sss.pgh.pa.us 446 : 39 : MemoryContextSwitchTo(portal->holdContext);
447 : :
6787 448 [ - + ]: 39 : if (portal->atEnd)
449 : : {
450 : : /*
451 : : * Just force the tuplestore forward to its end. The size of the
452 : : * skip request here is arbitrary.
453 : : */
4164 tgl@sss.pgh.pa.us 454 [ # # ]:UBC 0 : while (tuplestore_skiptuples(portal->holdStore, 1000000, true))
455 : : /* continue */ ;
456 : : }
457 : : else
458 : : {
7707 tgl@sss.pgh.pa.us 459 :CBC 39 : tuplestore_rescan(portal->holdStore);
460 : :
461 : : /*
462 : : * In the no-scroll case, the start of the tuplestore is exactly
463 : : * where we want to be, so no repositioning is wanted.
464 : : */
1457 465 [ + + ]: 39 : if (portal->cursorOptions & CURSOR_OPT_SCROLL)
466 : : {
467 [ - + ]: 20 : if (!tuplestore_skiptuples(portal->holdStore,
468 : 20 : portal->portalPos,
469 : : true))
1457 tgl@sss.pgh.pa.us 470 [ # # ]:UBC 0 : elog(ERROR, "unexpected end of tuple stream");
471 : : }
472 : : }
473 : : }
7707 tgl@sss.pgh.pa.us 474 :CBC 2 : PG_CATCH();
475 : : {
476 : : /* Uncaught error while executing portal: mark it dead */
4952 477 : 2 : MarkPortalFailed(portal);
478 : :
479 : : /* Restore global vars and propagate error */
7707 480 : 2 : ActivePortal = saveActivePortal;
481 : 2 : CurrentResourceOwner = saveResourceOwner;
482 : 2 : PortalContext = savePortalContext;
483 : :
484 : 2 : PG_RE_THROW();
485 : : }
486 [ - + ]: 39 : PG_END_TRY();
487 : :
8166 488 : 39 : MemoryContextSwitchTo(oldcxt);
489 : :
490 : : /* Mark portal not active */
7721 491 : 39 : portal->status = PORTAL_READY;
492 : :
493 : 39 : ActivePortal = saveActivePortal;
494 : 39 : CurrentResourceOwner = saveResourceOwner;
495 : 39 : PortalContext = savePortalContext;
496 : :
6326 alvherre@alvh.no-ip. 497 : 39 : PopActiveSnapshot();
498 : :
499 : : /*
500 : : * We can now release any subsidiary memory of the portal's context; we'll
501 : : * never use it again. The executor already dropped its context, but this
502 : : * will clean up anything that glommed onto the portal's context via
503 : : * PortalContext.
504 : : */
2821 peter_e@gmx.net 505 : 39 : MemoryContextDeleteChildren(portal->portalContext);
8216 tgl@sss.pgh.pa.us 506 : 39 : }
|