/src/postgres/src/backend/commands/portalcmds.c
Line | Count | Source (jump to first uncovered line) |
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 | | PerformCursorOpen(ParseState *pstate, DeclareCursorStmt *cstmt, ParamListInfo params, |
46 | | bool isTopLevel) |
47 | 0 | { |
48 | 0 | Query *query = castNode(Query, cstmt->query); |
49 | 0 | JumbleState *jstate = NULL; |
50 | 0 | List *rewritten; |
51 | 0 | PlannedStmt *plan; |
52 | 0 | Portal portal; |
53 | 0 | MemoryContext oldContext; |
54 | 0 | char *queryString; |
55 | | |
56 | | /* |
57 | | * Disallow empty-string cursor name (conflicts with protocol-level |
58 | | * unnamed portal). |
59 | | */ |
60 | 0 | if (!cstmt->portalname || cstmt->portalname[0] == '\0') |
61 | 0 | ereport(ERROR, |
62 | 0 | (errcode(ERRCODE_INVALID_CURSOR_NAME), |
63 | 0 | 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 | 0 | if (!(cstmt->options & CURSOR_OPT_HOLD)) |
71 | 0 | RequireTransactionBlock(isTopLevel, "DECLARE CURSOR"); |
72 | 0 | else if (InSecurityRestrictedOperation()) |
73 | 0 | ereport(ERROR, |
74 | 0 | (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
75 | 0 | errmsg("cannot create a cursor WITH HOLD within security-restricted operation"))); |
76 | | |
77 | | /* Query contained by DeclareCursor needs to be jumbled if requested */ |
78 | 0 | if (IsQueryIdEnabled()) |
79 | 0 | jstate = JumbleQuery(query); |
80 | |
|
81 | 0 | 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 | 0 | rewritten = QueryRewrite(query); |
91 | | |
92 | | /* SELECT should never rewrite to more or less than one query */ |
93 | 0 | if (list_length(rewritten) != 1) |
94 | 0 | elog(ERROR, "non-SELECT statement in DECLARE CURSOR"); |
95 | | |
96 | 0 | query = linitial_node(Query, rewritten); |
97 | |
|
98 | 0 | 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 | 0 | 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 | 0 | portal = CreatePortal(cstmt->portalname, false, false); |
108 | |
|
109 | 0 | oldContext = MemoryContextSwitchTo(portal->portalContext); |
110 | |
|
111 | 0 | plan = copyObject(plan); |
112 | |
|
113 | 0 | queryString = pstrdup(pstate->p_sourcetext); |
114 | |
|
115 | 0 | PortalDefineQuery(portal, |
116 | 0 | NULL, |
117 | 0 | queryString, |
118 | 0 | CMDTAG_SELECT, /* cursor's query is always a SELECT */ |
119 | 0 | list_make1(plan), |
120 | 0 | 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 | | */ |
132 | 0 | params = copyParamList(params); |
133 | |
|
134 | 0 | 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 | | */ |
143 | 0 | portal->cursorOptions = cstmt->options; |
144 | 0 | if (!(portal->cursorOptions & (CURSOR_OPT_SCROLL | CURSOR_OPT_NO_SCROLL))) |
145 | 0 | { |
146 | 0 | if (plan->rowMarks == NIL && |
147 | 0 | ExecSupportsBackwardScan(plan->planTree)) |
148 | 0 | portal->cursorOptions |= CURSOR_OPT_SCROLL; |
149 | 0 | else |
150 | 0 | portal->cursorOptions |= CURSOR_OPT_NO_SCROLL; |
151 | 0 | } |
152 | | |
153 | | /* |
154 | | * Start execution, inserting parameters if any. |
155 | | */ |
156 | 0 | PortalStart(portal, params, 0, GetActiveSnapshot()); |
157 | |
|
158 | 0 | 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 | | */ |
164 | 0 | } |
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 |
177 | | PerformPortalFetch(FetchStmt *stmt, |
178 | | DestReceiver *dest, |
179 | | QueryCompletion *qc) |
180 | 0 | { |
181 | 0 | Portal portal; |
182 | 0 | uint64 nprocessed; |
183 | | |
184 | | /* |
185 | | * Disallow empty-string cursor name (conflicts with protocol-level |
186 | | * unnamed portal). |
187 | | */ |
188 | 0 | if (!stmt->portalname || stmt->portalname[0] == '\0') |
189 | 0 | ereport(ERROR, |
190 | 0 | (errcode(ERRCODE_INVALID_CURSOR_NAME), |
191 | 0 | errmsg("invalid cursor name: must not be empty"))); |
192 | | |
193 | | /* get the portal from the portal name */ |
194 | 0 | portal = GetPortalByName(stmt->portalname); |
195 | 0 | if (!PortalIsValid(portal)) |
196 | 0 | { |
197 | 0 | ereport(ERROR, |
198 | 0 | (errcode(ERRCODE_UNDEFINED_CURSOR), |
199 | 0 | errmsg("cursor \"%s\" does not exist", stmt->portalname))); |
200 | 0 | return; /* keep compiler happy */ |
201 | 0 | } |
202 | | |
203 | | /* Adjust dest if needed. MOVE wants destination DestNone */ |
204 | 0 | if (stmt->ismove) |
205 | 0 | dest = None_Receiver; |
206 | | |
207 | | /* Do it */ |
208 | 0 | nprocessed = PortalRunFetch(portal, |
209 | 0 | stmt->direction, |
210 | 0 | stmt->howMany, |
211 | 0 | dest); |
212 | | |
213 | | /* Return command status if wanted */ |
214 | 0 | if (qc) |
215 | 0 | SetQueryCompletion(qc, stmt->ismove ? CMDTAG_MOVE : CMDTAG_FETCH, |
216 | 0 | nprocessed); |
217 | 0 | } |
218 | | |
219 | | /* |
220 | | * PerformPortalClose |
221 | | * Close a cursor. |
222 | | */ |
223 | | void |
224 | | PerformPortalClose(const char *name) |
225 | 0 | { |
226 | 0 | Portal portal; |
227 | | |
228 | | /* NULL means CLOSE ALL */ |
229 | 0 | if (name == NULL) |
230 | 0 | { |
231 | 0 | PortalHashTableDeleteAll(); |
232 | 0 | return; |
233 | 0 | } |
234 | | |
235 | | /* |
236 | | * Disallow empty-string cursor name (conflicts with protocol-level |
237 | | * unnamed portal). |
238 | | */ |
239 | 0 | if (name[0] == '\0') |
240 | 0 | ereport(ERROR, |
241 | 0 | (errcode(ERRCODE_INVALID_CURSOR_NAME), |
242 | 0 | errmsg("invalid cursor name: must not be empty"))); |
243 | | |
244 | | /* |
245 | | * get the portal from the portal name |
246 | | */ |
247 | 0 | portal = GetPortalByName(name); |
248 | 0 | if (!PortalIsValid(portal)) |
249 | 0 | { |
250 | 0 | ereport(ERROR, |
251 | 0 | (errcode(ERRCODE_UNDEFINED_CURSOR), |
252 | 0 | errmsg("cursor \"%s\" does not exist", name))); |
253 | 0 | return; /* keep compiler happy */ |
254 | 0 | } |
255 | | |
256 | | /* |
257 | | * Note: PortalCleanup is called as a side-effect, if not already done. |
258 | | */ |
259 | 0 | PortalDrop(portal, false); |
260 | 0 | } |
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 |
273 | | PortalCleanup(Portal portal) |
274 | 0 | { |
275 | 0 | QueryDesc *queryDesc; |
276 | | |
277 | | /* |
278 | | * sanity checks |
279 | | */ |
280 | 0 | Assert(PortalIsValid(portal)); |
281 | 0 | 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 | | */ |
288 | 0 | queryDesc = portal->queryDesc; |
289 | 0 | if (queryDesc) |
290 | 0 | { |
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 | 0 | portal->queryDesc = NULL; |
298 | |
|
299 | 0 | if (portal->status != PORTAL_FAILED) |
300 | 0 | { |
301 | 0 | ResourceOwner saveResourceOwner; |
302 | | |
303 | | /* We must make the portal's resource owner current */ |
304 | 0 | saveResourceOwner = CurrentResourceOwner; |
305 | 0 | if (portal->resowner) |
306 | 0 | CurrentResourceOwner = portal->resowner; |
307 | |
|
308 | 0 | ExecutorFinish(queryDesc); |
309 | 0 | ExecutorEnd(queryDesc); |
310 | 0 | FreeQueryDesc(queryDesc); |
311 | |
|
312 | 0 | CurrentResourceOwner = saveResourceOwner; |
313 | 0 | } |
314 | 0 | } |
315 | 0 | } |
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 | | PersistHoldablePortal(Portal portal) |
327 | 0 | { |
328 | 0 | QueryDesc *queryDesc = portal->queryDesc; |
329 | 0 | Portal saveActivePortal; |
330 | 0 | ResourceOwner saveResourceOwner; |
331 | 0 | MemoryContext savePortalContext; |
332 | 0 | 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 | | */ |
338 | 0 | Assert(portal->createSubid != InvalidSubTransactionId); |
339 | 0 | Assert(queryDesc != NULL); |
340 | | |
341 | | /* |
342 | | * Caller must have created the tuplestore already ... but not a snapshot. |
343 | | */ |
344 | 0 | Assert(portal->holdContext != NULL); |
345 | 0 | Assert(portal->holdStore != NULL); |
346 | 0 | 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 | | */ |
352 | 0 | oldcxt = MemoryContextSwitchTo(portal->holdContext); |
353 | |
|
354 | 0 | portal->tupDesc = CreateTupleDescCopy(portal->tupDesc); |
355 | |
|
356 | 0 | MemoryContextSwitchTo(oldcxt); |
357 | | |
358 | | /* |
359 | | * Check for improper portal use, and mark portal active. |
360 | | */ |
361 | 0 | MarkPortalActive(portal); |
362 | | |
363 | | /* |
364 | | * Set up global portal context pointers. |
365 | | */ |
366 | 0 | saveActivePortal = ActivePortal; |
367 | 0 | saveResourceOwner = CurrentResourceOwner; |
368 | 0 | savePortalContext = PortalContext; |
369 | 0 | PG_TRY(); |
370 | 0 | { |
371 | 0 | ScanDirection direction = ForwardScanDirection; |
372 | |
|
373 | 0 | ActivePortal = portal; |
374 | 0 | if (portal->resowner) |
375 | 0 | CurrentResourceOwner = portal->resowner; |
376 | 0 | PortalContext = portal->portalContext; |
377 | |
|
378 | 0 | MemoryContextSwitchTo(PortalContext); |
379 | |
|
380 | 0 | 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 | | */ |
398 | 0 | if (portal->cursorOptions & CURSOR_OPT_SCROLL) |
399 | 0 | { |
400 | 0 | ExecutorRewind(queryDesc); |
401 | 0 | } |
402 | 0 | else |
403 | 0 | { |
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 | | */ |
412 | 0 | if (portal->atEnd) |
413 | 0 | direction = NoMovementScanDirection; |
414 | 0 | } |
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 | | */ |
421 | 0 | queryDesc->dest = CreateDestReceiver(DestTuplestore); |
422 | 0 | SetTuplestoreDestReceiverParams(queryDesc->dest, |
423 | 0 | portal->holdStore, |
424 | 0 | portal->holdContext, |
425 | 0 | true, |
426 | 0 | NULL, |
427 | 0 | NULL); |
428 | | |
429 | | /* Fetch the result set into the tuplestore */ |
430 | 0 | ExecutorRun(queryDesc, direction, 0); |
431 | |
|
432 | 0 | queryDesc->dest->rDestroy(queryDesc->dest); |
433 | 0 | queryDesc->dest = NULL; |
434 | | |
435 | | /* |
436 | | * Now shut down the inner executor. |
437 | | */ |
438 | 0 | portal->queryDesc = NULL; /* prevent double shutdown */ |
439 | 0 | ExecutorFinish(queryDesc); |
440 | 0 | ExecutorEnd(queryDesc); |
441 | 0 | FreeQueryDesc(queryDesc); |
442 | | |
443 | | /* |
444 | | * Set the position in the result set. |
445 | | */ |
446 | 0 | MemoryContextSwitchTo(portal->holdContext); |
447 | |
|
448 | 0 | if (portal->atEnd) |
449 | 0 | { |
450 | | /* |
451 | | * Just force the tuplestore forward to its end. The size of the |
452 | | * skip request here is arbitrary. |
453 | | */ |
454 | 0 | while (tuplestore_skiptuples(portal->holdStore, 1000000, true)) |
455 | 0 | /* continue */ ; |
456 | 0 | } |
457 | 0 | else |
458 | 0 | { |
459 | 0 | 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 | | */ |
465 | 0 | if (portal->cursorOptions & CURSOR_OPT_SCROLL) |
466 | 0 | { |
467 | 0 | if (!tuplestore_skiptuples(portal->holdStore, |
468 | 0 | portal->portalPos, |
469 | 0 | true)) |
470 | 0 | elog(ERROR, "unexpected end of tuple stream"); |
471 | 0 | } |
472 | 0 | } |
473 | 0 | } |
474 | 0 | PG_CATCH(); |
475 | 0 | { |
476 | | /* Uncaught error while executing portal: mark it dead */ |
477 | 0 | MarkPortalFailed(portal); |
478 | | |
479 | | /* Restore global vars and propagate error */ |
480 | 0 | ActivePortal = saveActivePortal; |
481 | 0 | CurrentResourceOwner = saveResourceOwner; |
482 | 0 | PortalContext = savePortalContext; |
483 | |
|
484 | 0 | PG_RE_THROW(); |
485 | 0 | } |
486 | 0 | PG_END_TRY(); |
487 | | |
488 | 0 | MemoryContextSwitchTo(oldcxt); |
489 | | |
490 | | /* Mark portal not active */ |
491 | 0 | portal->status = PORTAL_READY; |
492 | |
|
493 | 0 | ActivePortal = saveActivePortal; |
494 | 0 | CurrentResourceOwner = saveResourceOwner; |
495 | 0 | PortalContext = savePortalContext; |
496 | |
|
497 | 0 | 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 | | */ |
505 | 0 | MemoryContextDeleteChildren(portal->portalContext); |
506 | 0 | } |