Branch data 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-2026, 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 : 390 : PerformCursorOpen(ParseState *pstate, DeclareCursorStmt *cstmt, ParamListInfo params,
46 : : bool isTopLevel)
47 : : {
48 : 390 : Query *query = castNode(Query, cstmt->query);
49 : 390 : JumbleState *jstate = NULL;
50 : 390 : List *rewritten;
51 : 390 : PlannedStmt *plan;
52 : 390 : Portal portal;
53 : 390 : MemoryContext oldContext;
54 : 390 : char *queryString;
55 : :
56 : : /*
57 : : * Disallow empty-string cursor name (conflicts with protocol-level
58 : : * unnamed portal).
59 : : */
60 [ + - ]: 390 : 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 [ + + ]: 390 : if (!(cstmt->options & CURSOR_OPT_HOLD))
71 : 379 : RequireTransactionBlock(isTopLevel, "DECLARE CURSOR");
72 [ + + ]: 11 : else if (InSecurityRestrictedOperation())
73 [ + - + - ]: 2 : 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 [ + - ]: 388 : if (IsQueryIdEnabled())
79 : 0 : jstate = JumbleQuery(query);
80 : :
81 [ + - ]: 388 : if (post_parse_analyze_hook)
82 : 0 : (*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 : 388 : rewritten = QueryRewrite(query);
91 : :
92 : : /* SELECT should never rewrite to more or less than one query */
93 [ + - ]: 388 : if (list_length(rewritten) != 1)
94 [ # # # # ]: 0 : elog(ERROR, "non-SELECT statement in DECLARE CURSOR");
95 : :
96 : 388 : query = linitial_node(Query, rewritten);
97 : :
98 [ + - ]: 388 : 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 : 388 : 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 : : */
108 : 388 : portal = CreatePortal(cstmt->portalname, false, false);
109 : :
110 : 388 : oldContext = MemoryContextSwitchTo(portal->portalContext);
111 : :
112 : 388 : plan = copyObject(plan);
113 : :
114 : 388 : queryString = pstrdup(pstate->p_sourcetext);
115 : :
116 : 776 : PortalDefineQuery(portal,
117 : : NULL,
118 : 388 : queryString,
119 : : CMDTAG_SELECT, /* cursor's query is always a SELECT */
120 : 388 : 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 : : */
133 : 388 : params = copyParamList(params);
134 : :
135 : 388 : 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 : 388 : portal->cursorOptions = cstmt->options;
145 [ + + ]: 388 : if (!(portal->cursorOptions & (CURSOR_OPT_SCROLL | CURSOR_OPT_NO_SCROLL)))
146 : : {
147 [ + + + + ]: 349 : if (plan->rowMarks == NIL &&
148 : 343 : ExecSupportsBackwardScan(plan->planTree))
149 : 334 : portal->cursorOptions |= CURSOR_OPT_SCROLL;
150 : : else
151 : 15 : portal->cursorOptions |= CURSOR_OPT_NO_SCROLL;
152 : 349 : }
153 : :
154 : : /*
155 : : * Start execution, inserting parameters if any.
156 : : */
157 : 388 : PortalStart(portal, params, 0, GetActiveSnapshot());
158 : :
159 [ + - ]: 388 : 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 : 388 : }
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 : 538 : PerformPortalFetch(FetchStmt *stmt,
179 : : DestReceiver *dest,
180 : : QueryCompletion *qc)
181 : : {
182 : 538 : Portal portal;
183 : 538 : uint64 nprocessed;
184 : :
185 : : /*
186 : : * Disallow empty-string cursor name (conflicts with protocol-level
187 : : * unnamed portal).
188 : : */
189 [ + - ]: 538 : 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 : 538 : portal = GetPortalByName(stmt->portalname);
196 [ + + ]: 538 : if (!PortalIsValid(portal))
197 : : {
198 [ + - + - ]: 4 : ereport(ERROR,
199 : : (errcode(ERRCODE_UNDEFINED_CURSOR),
200 : : errmsg("cursor \"%s\" does not exist", stmt->portalname)));
201 : 0 : return; /* keep compiler happy */
202 : : }
203 : :
204 : : /* Adjust dest if needed. MOVE wants destination DestNone */
205 [ + + ]: 534 : if (stmt->ismove)
206 : 8 : dest = None_Receiver;
207 : :
208 : : /* Do it */
209 : 1068 : nprocessed = PortalRunFetch(portal,
210 : 534 : stmt->direction,
211 : 534 : stmt->howMany,
212 : 534 : dest);
213 : :
214 : : /* Return command status if wanted */
215 [ + + ]: 534 : if (qc)
216 : 1046 : SetQueryCompletion(qc, stmt->ismove ? CMDTAG_MOVE : CMDTAG_FETCH,
217 : 523 : nprocessed);
218 [ - + ]: 534 : }
219 : :
220 : : /*
221 : : * PerformPortalClose
222 : : * Close a cursor.
223 : : */
224 : : void
225 : 32 : PerformPortalClose(const char *name)
226 : : {
227 : 32 : Portal portal;
228 : :
229 : : /* NULL means CLOSE ALL */
230 [ + + ]: 32 : if (name == NULL)
231 : : {
232 : 2 : PortalHashTableDeleteAll();
233 : 2 : return;
234 : : }
235 : :
236 : : /*
237 : : * Disallow empty-string cursor name (conflicts with protocol-level
238 : : * unnamed portal).
239 : : */
240 [ + - ]: 30 : 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 : 30 : portal = GetPortalByName(name);
249 [ + - ]: 30 : if (!PortalIsValid(portal))
250 : : {
251 [ # # # # ]: 0 : ereport(ERROR,
252 : : (errcode(ERRCODE_UNDEFINED_CURSOR),
253 : : errmsg("cursor \"%s\" does not exist", name)));
254 : 0 : return; /* keep compiler happy */
255 : : }
256 : :
257 : : /*
258 : : * Note: PortalCleanup is called as a side-effect, if not already done.
259 : : */
260 : 30 : PortalDrop(portal, false);
261 [ - + ]: 32 : }
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 : 59187 : PortalCleanup(Portal portal)
275 : : {
276 : 59187 : QueryDesc *queryDesc;
277 : :
278 : : /*
279 : : * sanity checks
280 : : */
281 [ + - ]: 59187 : Assert(PortalIsValid(portal));
282 [ + - ]: 59187 : 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 : 59187 : queryDesc = portal->queryDesc;
290 [ + + ]: 59187 : 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 : 24967 : portal->queryDesc = NULL;
299 : :
300 [ + + ]: 24967 : if (portal->status != PORTAL_FAILED)
301 : : {
302 : 23694 : ResourceOwner saveResourceOwner;
303 : :
304 : : /* We must make the portal's resource owner current */
305 : 23694 : saveResourceOwner = CurrentResourceOwner;
306 [ - + ]: 23694 : if (portal->resowner)
307 : 23694 : CurrentResourceOwner = portal->resowner;
308 : :
309 : 23694 : ExecutorFinish(queryDesc);
310 : 23694 : ExecutorEnd(queryDesc);
311 : 23694 : FreeQueryDesc(queryDesc);
312 : :
313 : 23694 : CurrentResourceOwner = saveResourceOwner;
314 : 23694 : }
315 : 24967 : }
316 : 59187 : }
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 : 7 : PersistHoldablePortal(Portal portal)
328 : : {
329 : 7 : QueryDesc *queryDesc = portal->queryDesc;
330 : 7 : Portal saveActivePortal;
331 : 7 : ResourceOwner saveResourceOwner;
332 : 7 : MemoryContext savePortalContext;
333 : 7 : 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 [ + - ]: 7 : Assert(portal->createSubid != InvalidSubTransactionId);
340 [ + - ]: 7 : Assert(queryDesc != NULL);
341 : :
342 : : /*
343 : : * Caller must have created the tuplestore already ... but not a snapshot.
344 : : */
345 [ + - ]: 7 : Assert(portal->holdContext != NULL);
346 [ + - ]: 7 : Assert(portal->holdStore != NULL);
347 [ + - ]: 7 : 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 : 7 : oldcxt = MemoryContextSwitchTo(portal->holdContext);
354 : :
355 : 7 : portal->tupDesc = CreateTupleDescCopy(portal->tupDesc);
356 : :
357 : 7 : MemoryContextSwitchTo(oldcxt);
358 : :
359 : : /*
360 : : * Check for improper portal use, and mark portal active.
361 : : */
362 : 7 : MarkPortalActive(portal);
363 : :
364 : : /*
365 : : * Set up global portal context pointers.
366 : : */
367 : 7 : saveActivePortal = ActivePortal;
368 : 7 : saveResourceOwner = CurrentResourceOwner;
369 : 7 : savePortalContext = PortalContext;
370 [ + - ]: 7 : PG_TRY();
371 : : {
372 : 7 : ScanDirection direction = ForwardScanDirection;
373 : :
374 : 7 : ActivePortal = portal;
375 [ - + ]: 7 : if (portal->resowner)
376 : 7 : CurrentResourceOwner = portal->resowner;
377 : 7 : PortalContext = portal->portalContext;
378 : :
379 : 7 : MemoryContextSwitchTo(PortalContext);
380 : :
381 : 7 : 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 [ + + ]: 7 : if (portal->cursorOptions & CURSOR_OPT_SCROLL)
400 : : {
401 : 4 : ExecutorRewind(queryDesc);
402 : 4 : }
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 [ + - ]: 3 : 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 : 7 : queryDesc->dest = CreateDestReceiver(DestTuplestore);
423 : 14 : SetTuplestoreDestReceiverParams(queryDesc->dest,
424 : 7 : portal->holdStore,
425 : 7 : portal->holdContext,
426 : : true,
427 : : NULL,
428 : : NULL);
429 : :
430 : : /* Fetch the result set into the tuplestore */
431 : 7 : ExecutorRun(queryDesc, direction, 0);
432 : :
433 : 7 : queryDesc->dest->rDestroy(queryDesc->dest);
434 : 7 : queryDesc->dest = NULL;
435 : :
436 : : /*
437 : : * Now shut down the inner executor.
438 : : */
439 : 7 : portal->queryDesc = NULL; /* prevent double shutdown */
440 : 7 : ExecutorFinish(queryDesc);
441 : 7 : ExecutorEnd(queryDesc);
442 : 7 : FreeQueryDesc(queryDesc);
443 : :
444 : : /*
445 : : * Set the position in the result set.
446 : : */
447 : 7 : MemoryContextSwitchTo(portal->holdContext);
448 : :
449 [ - + ]: 7 : 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 : 0 : }
458 : : else
459 : : {
460 : 7 : 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 [ + + ]: 7 : if (portal->cursorOptions & CURSOR_OPT_SCROLL)
467 : : {
468 [ + - + - ]: 8 : if (!tuplestore_skiptuples(portal->holdStore,
469 : 4 : portal->portalPos,
470 : : true))
471 [ # # # # ]: 0 : elog(ERROR, "unexpected end of tuple stream");
472 : 4 : }
473 : : }
474 : 7 : }
475 : 7 : PG_CATCH();
476 : : {
477 : : /* Uncaught error while executing portal: mark it dead */
478 : 0 : MarkPortalFailed(portal);
479 : :
480 : : /* Restore global vars and propagate error */
481 : 0 : ActivePortal = saveActivePortal;
482 : 0 : CurrentResourceOwner = saveResourceOwner;
483 : 0 : PortalContext = savePortalContext;
484 : :
485 : 0 : PG_RE_THROW();
486 : : }
487 [ - + ]: 7 : PG_END_TRY();
488 : :
489 : 7 : MemoryContextSwitchTo(oldcxt);
490 : :
491 : : /* Mark portal not active */
492 : 7 : portal->status = PORTAL_READY;
493 : :
494 : 7 : ActivePortal = saveActivePortal;
495 : 7 : CurrentResourceOwner = saveResourceOwner;
496 : 7 : PortalContext = savePortalContext;
497 : :
498 : 7 : 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 : 7 : MemoryContextDeleteChildren(portal->portalContext);
507 : 7 : }
|