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