/src/postgres/src/backend/commands/portalcmds.c
Line | Count | Source |
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 | 0 | NULL); |
104 | | |
105 | | /* |
106 | | * Create a portal and copy the plan and query string into its memory. |
107 | | */ |
108 | 0 | portal = CreatePortal(cstmt->portalname, false, false); |
109 | |
|
110 | 0 | oldContext = MemoryContextSwitchTo(portal->portalContext); |
111 | |
|
112 | 0 | plan = copyObject(plan); |
113 | |
|
114 | 0 | queryString = pstrdup(pstate->p_sourcetext); |
115 | |
|
116 | 0 | PortalDefineQuery(portal, |
117 | 0 | NULL, |
118 | 0 | queryString, |
119 | 0 | CMDTAG_SELECT, /* cursor's query is always a SELECT */ |
120 | 0 | list_make1(plan), |
121 | 0 | 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 | 0 | params = copyParamList(params); |
134 | |
|
135 | 0 | 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 | 0 | portal->cursorOptions = cstmt->options; |
145 | 0 | if (!(portal->cursorOptions & (CURSOR_OPT_SCROLL | CURSOR_OPT_NO_SCROLL))) |
146 | 0 | { |
147 | 0 | if (plan->rowMarks == NIL && |
148 | 0 | ExecSupportsBackwardScan(plan->planTree)) |
149 | 0 | portal->cursorOptions |= CURSOR_OPT_SCROLL; |
150 | 0 | else |
151 | 0 | portal->cursorOptions |= CURSOR_OPT_NO_SCROLL; |
152 | 0 | } |
153 | | |
154 | | /* |
155 | | * Start execution, inserting parameters if any. |
156 | | */ |
157 | 0 | PortalStart(portal, params, 0, GetActiveSnapshot()); |
158 | |
|
159 | 0 | 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 | 0 | } |
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 | | PerformPortalFetch(FetchStmt *stmt, |
179 | | DestReceiver *dest, |
180 | | QueryCompletion *qc) |
181 | 0 | { |
182 | 0 | Portal portal; |
183 | 0 | uint64 nprocessed; |
184 | | |
185 | | /* |
186 | | * Disallow empty-string cursor name (conflicts with protocol-level |
187 | | * unnamed portal). |
188 | | */ |
189 | 0 | if (!stmt->portalname || stmt->portalname[0] == '\0') |
190 | 0 | ereport(ERROR, |
191 | 0 | (errcode(ERRCODE_INVALID_CURSOR_NAME), |
192 | 0 | errmsg("invalid cursor name: must not be empty"))); |
193 | | |
194 | | /* get the portal from the portal name */ |
195 | 0 | portal = GetPortalByName(stmt->portalname); |
196 | 0 | if (!PortalIsValid(portal)) |
197 | 0 | { |
198 | 0 | ereport(ERROR, |
199 | 0 | (errcode(ERRCODE_UNDEFINED_CURSOR), |
200 | 0 | errmsg("cursor \"%s\" does not exist", stmt->portalname))); |
201 | 0 | return; /* keep compiler happy */ |
202 | 0 | } |
203 | | |
204 | | /* Adjust dest if needed. MOVE wants destination DestNone */ |
205 | 0 | if (stmt->ismove) |
206 | 0 | dest = None_Receiver; |
207 | | |
208 | | /* Do it */ |
209 | 0 | nprocessed = PortalRunFetch(portal, |
210 | 0 | stmt->direction, |
211 | 0 | stmt->howMany, |
212 | 0 | dest); |
213 | | |
214 | | /* Return command status if wanted */ |
215 | 0 | if (qc) |
216 | 0 | SetQueryCompletion(qc, stmt->ismove ? CMDTAG_MOVE : CMDTAG_FETCH, |
217 | 0 | nprocessed); |
218 | 0 | } |
219 | | |
220 | | /* |
221 | | * PerformPortalClose |
222 | | * Close a cursor. |
223 | | */ |
224 | | void |
225 | | PerformPortalClose(const char *name) |
226 | 0 | { |
227 | 0 | Portal portal; |
228 | | |
229 | | /* NULL means CLOSE ALL */ |
230 | 0 | if (name == NULL) |
231 | 0 | { |
232 | 0 | PortalHashTableDeleteAll(); |
233 | 0 | return; |
234 | 0 | } |
235 | | |
236 | | /* |
237 | | * Disallow empty-string cursor name (conflicts with protocol-level |
238 | | * unnamed portal). |
239 | | */ |
240 | 0 | if (name[0] == '\0') |
241 | 0 | ereport(ERROR, |
242 | 0 | (errcode(ERRCODE_INVALID_CURSOR_NAME), |
243 | 0 | errmsg("invalid cursor name: must not be empty"))); |
244 | | |
245 | | /* |
246 | | * get the portal from the portal name |
247 | | */ |
248 | 0 | portal = GetPortalByName(name); |
249 | 0 | if (!PortalIsValid(portal)) |
250 | 0 | { |
251 | 0 | ereport(ERROR, |
252 | 0 | (errcode(ERRCODE_UNDEFINED_CURSOR), |
253 | 0 | errmsg("cursor \"%s\" does not exist", name))); |
254 | 0 | return; /* keep compiler happy */ |
255 | 0 | } |
256 | | |
257 | | /* |
258 | | * Note: PortalCleanup is called as a side-effect, if not already done. |
259 | | */ |
260 | 0 | PortalDrop(portal, false); |
261 | 0 | } |
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 | | PortalCleanup(Portal portal) |
275 | 0 | { |
276 | 0 | QueryDesc *queryDesc; |
277 | | |
278 | | /* |
279 | | * sanity checks |
280 | | */ |
281 | 0 | Assert(PortalIsValid(portal)); |
282 | 0 | 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 | 0 | queryDesc = portal->queryDesc; |
290 | 0 | if (queryDesc) |
291 | 0 | { |
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 | 0 | portal->queryDesc = NULL; |
299 | |
|
300 | 0 | if (portal->status != PORTAL_FAILED) |
301 | 0 | { |
302 | 0 | ResourceOwner saveResourceOwner; |
303 | | |
304 | | /* We must make the portal's resource owner current */ |
305 | 0 | saveResourceOwner = CurrentResourceOwner; |
306 | 0 | if (portal->resowner) |
307 | 0 | CurrentResourceOwner = portal->resowner; |
308 | |
|
309 | 0 | ExecutorFinish(queryDesc); |
310 | 0 | ExecutorEnd(queryDesc); |
311 | 0 | FreeQueryDesc(queryDesc); |
312 | |
|
313 | 0 | CurrentResourceOwner = saveResourceOwner; |
314 | 0 | } |
315 | 0 | } |
316 | 0 | } |
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 | | PersistHoldablePortal(Portal portal) |
328 | 0 | { |
329 | 0 | QueryDesc *queryDesc = portal->queryDesc; |
330 | 0 | Portal saveActivePortal; |
331 | 0 | ResourceOwner saveResourceOwner; |
332 | 0 | MemoryContext savePortalContext; |
333 | 0 | 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 | 0 | Assert(portal->createSubid != InvalidSubTransactionId); |
340 | 0 | Assert(queryDesc != NULL); |
341 | | |
342 | | /* |
343 | | * Caller must have created the tuplestore already ... but not a snapshot. |
344 | | */ |
345 | 0 | Assert(portal->holdContext != NULL); |
346 | 0 | Assert(portal->holdStore != NULL); |
347 | 0 | 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 | 0 | oldcxt = MemoryContextSwitchTo(portal->holdContext); |
354 | |
|
355 | 0 | portal->tupDesc = CreateTupleDescCopy(portal->tupDesc); |
356 | |
|
357 | 0 | MemoryContextSwitchTo(oldcxt); |
358 | | |
359 | | /* |
360 | | * Check for improper portal use, and mark portal active. |
361 | | */ |
362 | 0 | MarkPortalActive(portal); |
363 | | |
364 | | /* |
365 | | * Set up global portal context pointers. |
366 | | */ |
367 | 0 | saveActivePortal = ActivePortal; |
368 | 0 | saveResourceOwner = CurrentResourceOwner; |
369 | 0 | savePortalContext = PortalContext; |
370 | 0 | PG_TRY(); |
371 | 0 | { |
372 | 0 | ScanDirection direction = ForwardScanDirection; |
373 | |
|
374 | 0 | ActivePortal = portal; |
375 | 0 | if (portal->resowner) |
376 | 0 | CurrentResourceOwner = portal->resowner; |
377 | 0 | PortalContext = portal->portalContext; |
378 | |
|
379 | 0 | MemoryContextSwitchTo(PortalContext); |
380 | |
|
381 | 0 | 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 | 0 | if (portal->cursorOptions & CURSOR_OPT_SCROLL) |
400 | 0 | { |
401 | 0 | ExecutorRewind(queryDesc); |
402 | 0 | } |
403 | 0 | else |
404 | 0 | { |
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 | 0 | if (portal->atEnd) |
414 | 0 | direction = NoMovementScanDirection; |
415 | 0 | } |
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 | 0 | queryDesc->dest = CreateDestReceiver(DestTuplestore); |
423 | 0 | SetTuplestoreDestReceiverParams(queryDesc->dest, |
424 | 0 | portal->holdStore, |
425 | 0 | portal->holdContext, |
426 | 0 | true, |
427 | 0 | NULL, |
428 | 0 | NULL); |
429 | | |
430 | | /* Fetch the result set into the tuplestore */ |
431 | 0 | ExecutorRun(queryDesc, direction, 0); |
432 | |
|
433 | 0 | queryDesc->dest->rDestroy(queryDesc->dest); |
434 | 0 | queryDesc->dest = NULL; |
435 | | |
436 | | /* |
437 | | * Now shut down the inner executor. |
438 | | */ |
439 | 0 | portal->queryDesc = NULL; /* prevent double shutdown */ |
440 | 0 | ExecutorFinish(queryDesc); |
441 | 0 | ExecutorEnd(queryDesc); |
442 | 0 | FreeQueryDesc(queryDesc); |
443 | | |
444 | | /* |
445 | | * Set the position in the result set. |
446 | | */ |
447 | 0 | MemoryContextSwitchTo(portal->holdContext); |
448 | |
|
449 | 0 | if (portal->atEnd) |
450 | 0 | { |
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 | 0 | /* continue */ ; |
457 | 0 | } |
458 | 0 | else |
459 | 0 | { |
460 | 0 | 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 | 0 | if (portal->cursorOptions & CURSOR_OPT_SCROLL) |
467 | 0 | { |
468 | 0 | if (!tuplestore_skiptuples(portal->holdStore, |
469 | 0 | portal->portalPos, |
470 | 0 | true)) |
471 | 0 | elog(ERROR, "unexpected end of tuple stream"); |
472 | 0 | } |
473 | 0 | } |
474 | 0 | } |
475 | 0 | PG_CATCH(); |
476 | 0 | { |
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 | 0 | } |
487 | 0 | PG_END_TRY(); |
488 | | |
489 | 0 | MemoryContextSwitchTo(oldcxt); |
490 | | |
491 | | /* Mark portal not active */ |
492 | 0 | portal->status = PORTAL_READY; |
493 | |
|
494 | 0 | ActivePortal = saveActivePortal; |
495 | 0 | CurrentResourceOwner = saveResourceOwner; |
496 | 0 | PortalContext = savePortalContext; |
497 | |
|
498 | 0 | 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 | 0 | MemoryContextDeleteChildren(portal->portalContext); |
507 | 0 | } |