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
45 2704 : PerformCursorOpen(ParseState *pstate, DeclareCursorStmt *cstmt, ParamListInfo params,
46 : bool isTopLevel)
47 : {
48 2704 : Query *query = castNode(Query, cstmt->query);
49 2704 : 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 : */
60 2704 : if (!cstmt->portalname || cstmt->portalname[0] == '\0')
61 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 : */
70 2704 : if (!(cstmt->options & CURSOR_OPT_HOLD))
71 2630 : RequireTransactionBlock(isTopLevel, "DECLARE CURSOR");
72 74 : else if (InSecurityRestrictedOperation())
73 12 : 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 */
78 2690 : if (IsQueryIdEnabled())
79 212 : jstate = JumbleQuery(query);
80 :
81 2690 : if (post_parse_analyze_hook)
82 212 : (*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 : */
90 2690 : rewritten = QueryRewrite(query);
91 :
92 : /* SELECT should never rewrite to more or less than one query */
93 2690 : if (list_length(rewritten) != 1)
94 0 : elog(ERROR, "non-SELECT statement in DECLARE CURSOR");
95 :
96 2690 : query = linitial_node(Query, rewritten);
97 :
98 2690 : if (query->commandType != CMD_SELECT)
99 0 : elog(ERROR, "non-SELECT statement in DECLARE CURSOR");
100 :
101 : /* Plan the query, applying the specified options */
102 2690 : 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 : */
107 2690 : portal = CreatePortal(cstmt->portalname, false, false);
108 :
109 2690 : oldContext = MemoryContextSwitchTo(portal->portalContext);
110 :
111 2690 : plan = copyObject(plan);
112 :
113 2690 : queryString = pstrdup(pstate->p_sourcetext);
114 :
115 2690 : PortalDefineQuery(portal,
116 : NULL,
117 : queryString,
118 : CMDTAG_SELECT, /* cursor's query is always a SELECT */
119 2690 : list_make1(plan),
120 : NULL,
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 : */
133 2690 : params = copyParamList(params);
134 :
135 2690 : 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 : */
144 2690 : portal->cursorOptions = cstmt->options;
145 2690 : if (!(portal->cursorOptions & (CURSOR_OPT_SCROLL | CURSOR_OPT_NO_SCROLL)))
146 : {
147 4760 : if (plan->rowMarks == NIL &&
148 2304 : ExecSupportsBackwardScan(plan->planTree))
149 1564 : portal->cursorOptions |= CURSOR_OPT_SCROLL;
150 : else
151 892 : portal->cursorOptions |= CURSOR_OPT_NO_SCROLL;
152 : }
153 :
154 : /*
155 : * Start execution, inserting parameters if any.
156 : */
157 2690 : PortalStart(portal, params, 0, GetActiveSnapshot());
158 :
159 : 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 : */
165 2690 : }
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
178 5756 : 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 : */
189 5756 : if (!stmt->portalname || stmt->portalname[0] == '\0')
190 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 */
195 5756 : portal = GetPortalByName(stmt->portalname);
196 5756 : if (!PortalIsValid(portal))
197 : {
198 34 : 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 */
205 5722 : if (stmt->ismove)
206 68 : dest = None_Receiver;
207 :
208 : /* Do it */
209 5722 : nprocessed = PortalRunFetch(portal,
210 : stmt->direction,
211 : stmt->howMany,
212 : dest);
213 :
214 : /* Return command status if wanted */
215 5666 : if (qc)
216 5666 : SetQueryCompletion(qc, stmt->ismove ? CMDTAG_MOVE : CMDTAG_FETCH,
217 : nprocessed);
218 : }
219 :
220 : /*
221 : * PerformPortalClose
222 : * Close a cursor.
223 : */
224 : void
225 2198 : PerformPortalClose(const char *name)
226 : {
227 : Portal portal;
228 :
229 : /* NULL means CLOSE ALL */
230 2198 : if (name == NULL)
231 : {
232 12 : PortalHashTableDeleteAll();
233 12 : return;
234 : }
235 :
236 : /*
237 : * Disallow empty-string cursor name (conflicts with protocol-level
238 : * unnamed portal).
239 : */
240 2186 : if (name[0] == '\0')
241 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 : */
248 2186 : portal = GetPortalByName(name);
249 2186 : if (!PortalIsValid(portal))
250 : {
251 2 : 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 : */
260 2184 : 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
274 724056 : PortalCleanup(Portal portal)
275 : {
276 : QueryDesc *queryDesc;
277 :
278 : /*
279 : * sanity checks
280 : */
281 : Assert(PortalIsValid(portal));
282 : 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 : */
289 724056 : queryDesc = portal->queryDesc;
290 724056 : 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 294372 : portal->queryDesc = NULL;
299 :
300 294372 : if (portal->status != PORTAL_FAILED)
301 : {
302 : ResourceOwner saveResourceOwner;
303 :
304 : /* We must make the portal's resource owner current */
305 286984 : saveResourceOwner = CurrentResourceOwner;
306 286984 : if (portal->resowner)
307 286984 : CurrentResourceOwner = portal->resowner;
308 :
309 286984 : ExecutorFinish(queryDesc);
310 286984 : ExecutorEnd(queryDesc);
311 286984 : FreeQueryDesc(queryDesc);
312 :
313 286984 : CurrentResourceOwner = saveResourceOwner;
314 : }
315 : }
316 724056 : }
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 82 : PersistHoldablePortal(Portal portal)
328 : {
329 82 : 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 : */
339 : Assert(portal->createSubid != InvalidSubTransactionId);
340 : Assert(queryDesc != NULL);
341 :
342 : /*
343 : * Caller must have created the tuplestore already ... but not a snapshot.
344 : */
345 : Assert(portal->holdContext != NULL);
346 : Assert(portal->holdStore != NULL);
347 : 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 : */
353 82 : oldcxt = MemoryContextSwitchTo(portal->holdContext);
354 :
355 82 : portal->tupDesc = CreateTupleDescCopy(portal->tupDesc);
356 :
357 82 : MemoryContextSwitchTo(oldcxt);
358 :
359 : /*
360 : * Check for improper portal use, and mark portal active.
361 : */
362 82 : MarkPortalActive(portal);
363 :
364 : /*
365 : * Set up global portal context pointers.
366 : */
367 82 : saveActivePortal = ActivePortal;
368 82 : saveResourceOwner = CurrentResourceOwner;
369 82 : savePortalContext = PortalContext;
370 82 : PG_TRY();
371 : {
372 82 : ScanDirection direction = ForwardScanDirection;
373 :
374 82 : ActivePortal = portal;
375 82 : if (portal->resowner)
376 82 : CurrentResourceOwner = portal->resowner;
377 82 : PortalContext = portal->portalContext;
378 :
379 82 : MemoryContextSwitchTo(PortalContext);
380 :
381 82 : 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 : */
399 82 : if (portal->cursorOptions & CURSOR_OPT_SCROLL)
400 : {
401 40 : 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 : */
413 42 : if (portal->atEnd)
414 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 : */
422 82 : queryDesc->dest = CreateDestReceiver(DestTuplestore);
423 82 : SetTuplestoreDestReceiverParams(queryDesc->dest,
424 : portal->holdStore,
425 : portal->holdContext,
426 : true,
427 : NULL,
428 : NULL);
429 :
430 : /* Fetch the result set into the tuplestore */
431 82 : ExecutorRun(queryDesc, direction, 0);
432 :
433 78 : queryDesc->dest->rDestroy(queryDesc->dest);
434 78 : queryDesc->dest = NULL;
435 :
436 : /*
437 : * Now shut down the inner executor.
438 : */
439 78 : portal->queryDesc = NULL; /* prevent double shutdown */
440 78 : ExecutorFinish(queryDesc);
441 78 : ExecutorEnd(queryDesc);
442 78 : FreeQueryDesc(queryDesc);
443 :
444 : /*
445 : * Set the position in the result set.
446 : */
447 78 : MemoryContextSwitchTo(portal->holdContext);
448 :
449 78 : 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 : */
455 0 : while (tuplestore_skiptuples(portal->holdStore, 1000000, true))
456 : /* continue */ ;
457 : }
458 : else
459 : {
460 78 : 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 : */
466 78 : if (portal->cursorOptions & CURSOR_OPT_SCROLL)
467 : {
468 40 : if (!tuplestore_skiptuples(portal->holdStore,
469 40 : portal->portalPos,
470 : true))
471 0 : elog(ERROR, "unexpected end of tuple stream");
472 : }
473 : }
474 : }
475 4 : PG_CATCH();
476 : {
477 : /* Uncaught error while executing portal: mark it dead */
478 4 : MarkPortalFailed(portal);
479 :
480 : /* Restore global vars and propagate error */
481 4 : ActivePortal = saveActivePortal;
482 4 : CurrentResourceOwner = saveResourceOwner;
483 4 : PortalContext = savePortalContext;
484 :
485 4 : PG_RE_THROW();
486 : }
487 78 : PG_END_TRY();
488 :
489 78 : MemoryContextSwitchTo(oldcxt);
490 :
491 : /* Mark portal not active */
492 78 : portal->status = PORTAL_READY;
493 :
494 78 : ActivePortal = saveActivePortal;
495 78 : CurrentResourceOwner = saveResourceOwner;
496 78 : PortalContext = savePortalContext;
497 :
498 78 : 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 : */
506 78 : MemoryContextDeleteChildren(portal->portalContext);
507 78 : }
|