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-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
1562 peter@eisentraut.org 43 :CBC 1273 : PerformCursorOpen(ParseState *pstate, DeclareCursorStmt *cstmt, ParamListInfo params,
44 : : bool isTopLevel)
45 : : {
2635 tgl@sss.pgh.pa.us 46 : 1273 : 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 : : */
6197 57 [ + - - + ]: 1273 : if (!cstmt->portalname || cstmt->portalname[0] == '\0')
7574 tgl@sss.pgh.pa.us 58 [ # # ]:UBC 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 : : */
6197 tgl@sss.pgh.pa.us 67 [ + + ]:CBC 1273 : if (!(cstmt->options & CURSOR_OPT_HOLD))
2249 peter_e@gmx.net 68 : 1236 : RequireTransactionBlock(isTopLevel, "DECLARE CURSOR");
1252 noah@leadboat.com 69 [ + + ]: 37 : else if (InSecurityRestrictedOperation())
70 [ + - ]: 6 : 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 : : */
1031 tgl@sss.pgh.pa.us 80 : 1266 : rewritten = QueryRewrite(query);
81 : :
82 : : /* SELECT should never rewrite to more or less than one query */
2647 83 [ - + ]: 1266 : if (list_length(rewritten) != 1)
2647 tgl@sss.pgh.pa.us 84 [ # # ]:UBC 0 : elog(ERROR, "non-SELECT statement in DECLARE CURSOR");
85 : :
2561 tgl@sss.pgh.pa.us 86 :CBC 1266 : query = linitial_node(Query, rewritten);
87 : :
2647 88 [ - + ]: 1266 : if (query->commandType != CMD_SELECT)
2647 tgl@sss.pgh.pa.us 89 [ # # ]:UBC 0 : elog(ERROR, "non-SELECT statement in DECLARE CURSOR");
90 : :
91 : : /* Plan the query, applying the specified options */
1476 fujii@postgresql.org 92 :CBC 1266 : 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 : : */
6197 tgl@sss.pgh.pa.us 97 : 1266 : portal = CreatePortal(cstmt->portalname, false, false);
98 : :
2311 peter_e@gmx.net 99 : 1266 : oldContext = MemoryContextSwitchTo(portal->portalContext);
100 : :
2647 tgl@sss.pgh.pa.us 101 : 1266 : plan = copyObject(plan);
102 : :
1562 peter@eisentraut.org 103 : 1266 : queryString = pstrdup(pstate->p_sourcetext);
104 : :
7653 tgl@sss.pgh.pa.us 105 : 1266 : PortalDefineQuery(portal,
106 : : NULL,
107 : : queryString,
108 : : CMDTAG_SELECT, /* cursor's query is always a SELECT */
2647 109 : 1266 : 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 : : */
7195 122 : 1266 : params = copyParamList(params);
123 : :
7653 124 : 1266 : 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 : : */
6197 133 : 1266 : portal->cursorOptions = cstmt->options;
7653 134 [ + + ]: 1266 : if (!(portal->cursorOptions & (CURSOR_OPT_SCROLL | CURSOR_OPT_NO_SCROLL)))
135 : : {
2647 136 [ + + + + ]: 2227 : if (plan->rowMarks == NIL &&
137 : 1078 : ExecSupportsBackwardScan(plan->planTree))
7653 138 : 718 : portal->cursorOptions |= CURSOR_OPT_SCROLL;
139 : : else
140 : 431 : portal->cursorOptions |= CURSOR_OPT_NO_SCROLL;
141 : : }
142 : :
143 : : /*
144 : : * Start execution, inserting parameters if any.
145 : : */
4157 146 : 1266 : PortalStart(portal, params, 0, GetActiveSnapshot());
147 : :
7653 148 [ - + ]: 1266 : 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 : : */
7706 154 : 1266 : }
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
7705 167 : 2700 : 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 : : */
7650 178 [ + - - + ]: 2700 : if (!stmt->portalname || stmt->portalname[0] == '\0')
7574 tgl@sss.pgh.pa.us 179 [ # # ]:UBC 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 */
7705 tgl@sss.pgh.pa.us 184 :CBC 2700 : portal = GetPortalByName(stmt->portalname);
8035 185 [ + + ]: 2700 : if (!PortalIsValid(portal))
186 : : {
7539 peter_e@gmx.net 187 [ + - ]: 17 : 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 */
7653 tgl@sss.pgh.pa.us 194 [ + + ]: 2683 : if (stmt->ismove)
7647 195 : 47 : dest = None_Receiver;
196 : :
197 : : /* Do it */
7653 198 : 2683 : nprocessed = PortalRunFetch(portal,
199 : : stmt->direction,
200 : : stmt->howMany,
201 : : dest);
202 : :
203 : : /* Return command status if wanted */
1504 alvherre@alvh.no-ip. 204 [ + - ]: 2655 : if (qc)
205 [ + + ]: 2655 : SetQueryCompletion(qc, stmt->ismove ? CMDTAG_MOVE : CMDTAG_FETCH,
206 : : nprocessed);
207 : : }
208 : :
209 : : /*
210 : : * PerformPortalClose
211 : : * Close a cursor.
212 : : */
213 : : void
7650 tgl@sss.pgh.pa.us 214 : 1029 : PerformPortalClose(const char *name)
215 : : {
216 : : Portal portal;
217 : :
218 : : /* NULL means CLOSE ALL */
6212 neilc@samurai.com 219 [ + + ]: 1029 : if (name == NULL)
220 : : {
221 : 6 : PortalHashTableDeleteAll();
222 : 6 : return;
223 : : }
224 : :
225 : : /*
226 : : * Disallow empty-string cursor name (conflicts with protocol-level
227 : : * unnamed portal).
228 : : */
229 [ - + ]: 1023 : if (name[0] == '\0')
7574 tgl@sss.pgh.pa.us 230 [ # # ]:UBC 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 : : */
8035 tgl@sss.pgh.pa.us 237 :CBC 1023 : portal = GetPortalByName(name);
238 [ + + ]: 1023 : if (!PortalIsValid(portal))
239 : : {
7539 peter_e@gmx.net 240 [ + - ]: 1 : 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 : : */
7689 bruce@momjian.us 249 : 1022 : 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
7211 tgl@sss.pgh.pa.us 263 : 320031 : PortalCleanup(Portal portal)
264 : : {
265 : : QueryDesc *queryDesc;
266 : :
267 : : /*
268 : : * sanity checks
269 : : */
534 peter@eisentraut.org 270 [ - + ]: 320031 : Assert(PortalIsValid(portal));
271 [ - + ]: 320031 : 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 : : */
2311 peter_e@gmx.net 278 : 320031 : queryDesc = portal->queryDesc;
7653 tgl@sss.pgh.pa.us 279 [ + + ]: 320031 : 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 : 123810 : portal->queryDesc = NULL;
288 : :
7211 289 [ + + ]: 123810 : if (portal->status != PORTAL_FAILED)
290 : : {
291 : : ResourceOwner saveResourceOwner;
292 : :
293 : : /* We must make the portal's resource owner current */
294 : 120445 : saveResourceOwner = CurrentResourceOwner;
2377 295 [ + - ]: 120445 : if (portal->resowner)
296 : 120445 : CurrentResourceOwner = portal->resowner;
297 : :
298 : 120445 : ExecutorFinish(queryDesc);
299 : 120445 : ExecutorEnd(queryDesc);
300 : 120445 : FreeQueryDesc(queryDesc);
301 : :
7211 302 : 120445 : CurrentResourceOwner = saveResourceOwner;
303 : : }
304 : : }
7689 bruce@momjian.us 305 : 320031 : }
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 : 41 : PersistHoldablePortal(Portal portal)
317 : : {
2311 peter_e@gmx.net 318 : 41 : 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 : : */
7150 tgl@sss.pgh.pa.us 328 [ - + ]: 41 : Assert(portal->createSubid != InvalidSubTransactionId);
7653 329 [ - + ]: 41 : Assert(queryDesc != NULL);
330 : :
331 : : /*
332 : : * Caller must have created the tuplestore already ... but not a snapshot.
333 : : */
7656 334 [ - + ]: 41 : Assert(portal->holdContext != NULL);
7649 335 [ - + ]: 41 : Assert(portal->holdStore != NULL);
2807 336 [ - + ]: 41 : 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 : : */
7649 342 : 41 : oldcxt = MemoryContextSwitchTo(portal->holdContext);
343 : :
7653 344 : 41 : portal->tupDesc = CreateTupleDescCopy(portal->tupDesc);
345 : :
346 : 41 : MemoryContextSwitchTo(oldcxt);
347 : :
348 : : /*
349 : : * Check for improper portal use, and mark portal active.
350 : : */
3145 351 : 41 : MarkPortalActive(portal);
352 : :
353 : : /*
354 : : * Set up global portal context pointers.
355 : : */
7329 356 : 41 : saveActivePortal = ActivePortal;
7211 357 : 41 : saveResourceOwner = CurrentResourceOwner;
7653 358 : 41 : savePortalContext = PortalContext;
7197 359 [ + + ]: 41 : PG_TRY();
360 : : {
948 361 : 41 : ScanDirection direction = ForwardScanDirection;
362 : :
7197 363 : 41 : ActivePortal = portal;
3958 364 [ + - ]: 41 : if (portal->resowner)
365 : 41 : CurrentResourceOwner = portal->resowner;
2311 peter_e@gmx.net 366 : 41 : PortalContext = portal->portalContext;
367 : :
7197 tgl@sss.pgh.pa.us 368 : 41 : MemoryContextSwitchTo(PortalContext);
369 : :
5816 alvherre@alvh.no-ip. 370 : 41 : 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 : : */
1041 tgl@sss.pgh.pa.us 388 [ + + ]: 41 : if (portal->cursorOptions & CURSOR_OPT_SCROLL)
389 : : {
390 : 20 : 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 : : */
948 402 [ - + ]: 21 : if (portal->atEnd)
948 tgl@sss.pgh.pa.us 403 :UBC 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 : : */
5614 tgl@sss.pgh.pa.us 411 :CBC 41 : queryDesc->dest = CreateDestReceiver(DestTuplestore);
412 : 41 : SetTuplestoreDestReceiverParams(queryDesc->dest,
413 : : portal->holdStore,
414 : : portal->holdContext,
415 : : true,
416 : : NULL,
417 : : NULL);
418 : :
419 : : /* Fetch the result set into the tuplestore */
382 peter@eisentraut.org 420 : 41 : ExecutorRun(queryDesc, direction, 0, false);
421 : :
2411 peter_e@gmx.net 422 : 39 : queryDesc->dest->rDestroy(queryDesc->dest);
7197 tgl@sss.pgh.pa.us 423 : 39 : queryDesc->dest = NULL;
424 : :
425 : : /*
426 : : * Now shut down the inner executor.
427 : : */
2489 428 : 39 : portal->queryDesc = NULL; /* prevent double shutdown */
4795 429 : 39 : ExecutorFinish(queryDesc);
6960 430 : 39 : ExecutorEnd(queryDesc);
5869 alvherre@alvh.no-ip. 431 : 39 : FreeQueryDesc(queryDesc);
432 : :
433 : : /*
434 : : * Set the position in the result set.
435 : : */
7197 tgl@sss.pgh.pa.us 436 : 39 : MemoryContextSwitchTo(portal->holdContext);
437 : :
6277 438 [ - + ]: 39 : 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 : : */
3654 tgl@sss.pgh.pa.us 444 [ # # ]:UBC 0 : while (tuplestore_skiptuples(portal->holdStore, 1000000, true))
445 : : /* continue */ ;
446 : : }
447 : : else
448 : : {
7197 tgl@sss.pgh.pa.us 449 :CBC 39 : 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 : : */
947 455 [ + + ]: 39 : if (portal->cursorOptions & CURSOR_OPT_SCROLL)
456 : : {
457 [ - + ]: 20 : if (!tuplestore_skiptuples(portal->holdStore,
458 : 20 : portal->portalPos,
459 : : true))
947 tgl@sss.pgh.pa.us 460 [ # # ]:UBC 0 : elog(ERROR, "unexpected end of tuple stream");
461 : : }
462 : : }
463 : : }
7197 tgl@sss.pgh.pa.us 464 :CBC 2 : PG_CATCH();
465 : : {
466 : : /* Uncaught error while executing portal: mark it dead */
4442 467 : 2 : MarkPortalFailed(portal);
468 : :
469 : : /* Restore global vars and propagate error */
7197 470 : 2 : ActivePortal = saveActivePortal;
471 : 2 : CurrentResourceOwner = saveResourceOwner;
472 : 2 : PortalContext = savePortalContext;
473 : :
474 : 2 : PG_RE_THROW();
475 : : }
476 [ - + ]: 39 : PG_END_TRY();
477 : :
7656 478 : 39 : MemoryContextSwitchTo(oldcxt);
479 : :
480 : : /* Mark portal not active */
7211 481 : 39 : portal->status = PORTAL_READY;
482 : :
483 : 39 : ActivePortal = saveActivePortal;
484 : 39 : CurrentResourceOwner = saveResourceOwner;
485 : 39 : PortalContext = savePortalContext;
486 : :
5816 alvherre@alvh.no-ip. 487 : 39 : 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 : : */
2311 peter_e@gmx.net 495 : 39 : MemoryContextDeleteChildren(portal->portalContext);
7706 tgl@sss.pgh.pa.us 496 : 39 : }
|