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-2017, 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 "rewrite/rewriteHandler.h"
31 : #include "tcop/pquery.h"
32 : #include "tcop/tcopprot.h"
33 : #include "utils/memutils.h"
34 : #include "utils/snapmgr.h"
35 :
36 :
37 : /*
38 : * PerformCursorOpen
39 : * Execute SQL DECLARE CURSOR command.
40 : */
41 : void
42 95 : PerformCursorOpen(DeclareCursorStmt *cstmt, ParamListInfo params,
43 : const char *queryString, bool isTopLevel)
44 : {
45 95 : Query *query = castNode(Query, cstmt->query);
46 : List *rewritten;
47 : PlannedStmt *plan;
48 : Portal portal;
49 : MemoryContext oldContext;
50 :
51 : /*
52 : * Disallow empty-string cursor name (conflicts with protocol-level
53 : * unnamed portal).
54 : */
55 95 : if (!cstmt->portalname || cstmt->portalname[0] == '\0')
56 0 : ereport(ERROR,
57 : (errcode(ERRCODE_INVALID_CURSOR_NAME),
58 : errmsg("invalid cursor name: must not be empty")));
59 :
60 : /*
61 : * If this is a non-holdable cursor, we require that this statement has
62 : * been executed inside a transaction block (or else, it would have no
63 : * user-visible effect).
64 : */
65 95 : if (!(cstmt->options & CURSOR_OPT_HOLD))
66 88 : RequireTransactionChain(isTopLevel, "DECLARE CURSOR");
67 :
68 : /*
69 : * Parse analysis was done already, but we still have to run the rule
70 : * rewriter. We do not do AcquireRewriteLocks: we assume the query either
71 : * came straight from the parser, or suitable locks were acquired by
72 : * plancache.c.
73 : *
74 : * Because the rewriter and planner tend to scribble on the input, we make
75 : * a preliminary copy of the source querytree. This prevents problems in
76 : * the case that the DECLARE CURSOR is in a portal or plpgsql function and
77 : * is executed repeatedly. (See also the same hack in EXPLAIN and
78 : * PREPARE.) XXX FIXME someday.
79 : */
80 95 : rewritten = QueryRewrite((Query *) copyObject(query));
81 :
82 : /* SELECT should never rewrite to more or less than one query */
83 95 : if (list_length(rewritten) != 1)
84 0 : elog(ERROR, "non-SELECT statement in DECLARE CURSOR");
85 :
86 95 : query = linitial_node(Query, rewritten);
87 :
88 95 : 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 95 : plan = pg_plan_query(query, cstmt->options, params);
93 :
94 : /*
95 : * Create a portal and copy the plan and queryString into its memory.
96 : */
97 95 : portal = CreatePortal(cstmt->portalname, false, false);
98 :
99 95 : oldContext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
100 :
101 95 : plan = copyObject(plan);
102 :
103 95 : queryString = pstrdup(queryString);
104 :
105 95 : PortalDefineQuery(portal,
106 : NULL,
107 : queryString,
108 : "SELECT", /* cursor's query is always a SELECT */
109 : 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 95 : params = copyParamList(params);
123 :
124 95 : 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 95 : portal->cursorOptions = cstmt->options;
134 95 : if (!(portal->cursorOptions & (CURSOR_OPT_SCROLL | CURSOR_OPT_NO_SCROLL)))
135 : {
136 102 : if (plan->rowMarks == NIL &&
137 48 : ExecSupportsBackwardScan(plan->planTree))
138 38 : portal->cursorOptions |= CURSOR_OPT_SCROLL;
139 : else
140 16 : portal->cursorOptions |= CURSOR_OPT_NO_SCROLL;
141 : }
142 :
143 : /*
144 : * Start execution, inserting parameters if any.
145 : */
146 95 : PortalStart(portal, params, 0, GetActiveSnapshot());
147 :
148 95 : 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 95 : }
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 : * completionTag: points to a buffer of size COMPLETION_TAG_BUFSIZE
163 : * in which to store a command completion status string.
164 : *
165 : * completionTag may be NULL if caller doesn't want a status string.
166 : */
167 : void
168 203 : PerformPortalFetch(FetchStmt *stmt,
169 : DestReceiver *dest,
170 : char *completionTag)
171 : {
172 : Portal portal;
173 : uint64 nprocessed;
174 :
175 : /*
176 : * Disallow empty-string cursor name (conflicts with protocol-level
177 : * unnamed portal).
178 : */
179 203 : if (!stmt->portalname || stmt->portalname[0] == '\0')
180 0 : ereport(ERROR,
181 : (errcode(ERRCODE_INVALID_CURSOR_NAME),
182 : errmsg("invalid cursor name: must not be empty")));
183 :
184 : /* get the portal from the portal name */
185 203 : portal = GetPortalByName(stmt->portalname);
186 203 : if (!PortalIsValid(portal))
187 : {
188 4 : ereport(ERROR,
189 : (errcode(ERRCODE_UNDEFINED_CURSOR),
190 : errmsg("cursor \"%s\" does not exist", stmt->portalname)));
191 192 : return; /* keep compiler happy */
192 : }
193 :
194 : /* Adjust dest if needed. MOVE wants destination DestNone */
195 199 : if (stmt->ismove)
196 6 : dest = None_Receiver;
197 :
198 : /* Do it */
199 199 : nprocessed = PortalRunFetch(portal,
200 : stmt->direction,
201 : stmt->howMany,
202 : dest);
203 :
204 : /* Return command status if wanted */
205 192 : if (completionTag)
206 192 : snprintf(completionTag, COMPLETION_TAG_BUFSIZE, "%s " UINT64_FORMAT,
207 192 : stmt->ismove ? "MOVE" : "FETCH",
208 : nprocessed);
209 : }
210 :
211 : /*
212 : * PerformPortalClose
213 : * Close a cursor.
214 : */
215 : void
216 42 : PerformPortalClose(const char *name)
217 : {
218 : Portal portal;
219 :
220 : /* NULL means CLOSE ALL */
221 42 : if (name == NULL)
222 : {
223 2 : PortalHashTableDeleteAll();
224 2 : return;
225 : }
226 :
227 : /*
228 : * Disallow empty-string cursor name (conflicts with protocol-level
229 : * unnamed portal).
230 : */
231 40 : if (name[0] == '\0')
232 0 : ereport(ERROR,
233 : (errcode(ERRCODE_INVALID_CURSOR_NAME),
234 : errmsg("invalid cursor name: must not be empty")));
235 :
236 : /*
237 : * get the portal from the portal name
238 : */
239 40 : portal = GetPortalByName(name);
240 40 : if (!PortalIsValid(portal))
241 : {
242 0 : ereport(ERROR,
243 : (errcode(ERRCODE_UNDEFINED_CURSOR),
244 : errmsg("cursor \"%s\" does not exist", name)));
245 : return; /* keep compiler happy */
246 : }
247 :
248 : /*
249 : * Note: PortalCleanup is called as a side-effect, if not already done.
250 : */
251 40 : PortalDrop(portal, false);
252 : }
253 :
254 : /*
255 : * PortalCleanup
256 : *
257 : * Clean up a portal when it's dropped. This is the standard cleanup hook
258 : * for portals.
259 : *
260 : * Note: if portal->status is PORTAL_FAILED, we are probably being called
261 : * during error abort, and must be careful to avoid doing anything that
262 : * is likely to fail again.
263 : */
264 : void
265 27376 : PortalCleanup(Portal portal)
266 : {
267 : QueryDesc *queryDesc;
268 :
269 : /*
270 : * sanity checks
271 : */
272 27376 : AssertArg(PortalIsValid(portal));
273 27376 : AssertArg(portal->cleanup == PortalCleanup);
274 :
275 : /*
276 : * Shut down executor, if still running. We skip this during error abort,
277 : * since other mechanisms will take care of releasing executor resources,
278 : * and we can't be sure that ExecutorEnd itself wouldn't fail.
279 : */
280 27376 : queryDesc = PortalGetQueryDesc(portal);
281 27376 : if (queryDesc)
282 : {
283 : /*
284 : * Reset the queryDesc before anything else. This prevents us from
285 : * trying to shut down the executor twice, in case of an error below.
286 : * The transaction abort mechanisms will take care of resource cleanup
287 : * in such a case.
288 : */
289 10939 : portal->queryDesc = NULL;
290 :
291 10939 : if (portal->status != PORTAL_FAILED)
292 : {
293 : ResourceOwner saveResourceOwner;
294 :
295 : /* We must make the portal's resource owner current */
296 10510 : saveResourceOwner = CurrentResourceOwner;
297 10510 : PG_TRY();
298 : {
299 10510 : if (portal->resowner)
300 10510 : CurrentResourceOwner = portal->resowner;
301 10510 : ExecutorFinish(queryDesc);
302 10510 : ExecutorEnd(queryDesc);
303 10510 : FreeQueryDesc(queryDesc);
304 : }
305 0 : PG_CATCH();
306 : {
307 : /* Ensure CurrentResourceOwner is restored on error */
308 0 : CurrentResourceOwner = saveResourceOwner;
309 0 : PG_RE_THROW();
310 : }
311 10510 : PG_END_TRY();
312 10510 : CurrentResourceOwner = saveResourceOwner;
313 : }
314 : }
315 27376 : }
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 5 : PersistHoldablePortal(Portal portal)
327 : {
328 5 : QueryDesc *queryDesc = PortalGetQueryDesc(portal);
329 : Portal saveActivePortal;
330 : ResourceOwner saveResourceOwner;
331 : MemoryContext savePortalContext;
332 : 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 5 : Assert(portal->createSubid != InvalidSubTransactionId);
339 5 : Assert(queryDesc != NULL);
340 :
341 : /*
342 : * Caller must have created the tuplestore already ... but not a snapshot.
343 : */
344 5 : Assert(portal->holdContext != NULL);
345 5 : Assert(portal->holdStore != NULL);
346 5 : 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 5 : oldcxt = MemoryContextSwitchTo(portal->holdContext);
353 :
354 5 : portal->tupDesc = CreateTupleDescCopy(portal->tupDesc);
355 :
356 5 : MemoryContextSwitchTo(oldcxt);
357 :
358 : /*
359 : * Check for improper portal use, and mark portal active.
360 : */
361 5 : MarkPortalActive(portal);
362 :
363 : /*
364 : * Set up global portal context pointers.
365 : */
366 5 : saveActivePortal = ActivePortal;
367 5 : saveResourceOwner = CurrentResourceOwner;
368 5 : savePortalContext = PortalContext;
369 5 : PG_TRY();
370 : {
371 5 : ActivePortal = portal;
372 5 : if (portal->resowner)
373 5 : CurrentResourceOwner = portal->resowner;
374 5 : PortalContext = PortalGetHeapMemory(portal);
375 :
376 5 : MemoryContextSwitchTo(PortalContext);
377 :
378 5 : PushActiveSnapshot(queryDesc->snapshot);
379 :
380 : /*
381 : * Rewind the executor: we need to store the entire result set in the
382 : * tuplestore, so that subsequent backward FETCHs can be processed.
383 : */
384 5 : ExecutorRewind(queryDesc);
385 :
386 : /*
387 : * Change the destination to output to the tuplestore. Note we tell
388 : * the tuplestore receiver to detoast all data passed through it; this
389 : * makes it safe to not keep a snapshot associated with the data.
390 : */
391 5 : queryDesc->dest = CreateDestReceiver(DestTuplestore);
392 5 : SetTuplestoreDestReceiverParams(queryDesc->dest,
393 : portal->holdStore,
394 : portal->holdContext,
395 : true);
396 :
397 : /* Fetch the result set into the tuplestore */
398 5 : ExecutorRun(queryDesc, ForwardScanDirection, 0L, false);
399 :
400 5 : (*queryDesc->dest->rDestroy) (queryDesc->dest);
401 5 : queryDesc->dest = NULL;
402 :
403 : /*
404 : * Now shut down the inner executor.
405 : */
406 5 : portal->queryDesc = NULL; /* prevent double shutdown */
407 5 : ExecutorFinish(queryDesc);
408 5 : ExecutorEnd(queryDesc);
409 5 : FreeQueryDesc(queryDesc);
410 :
411 : /*
412 : * Set the position in the result set.
413 : */
414 5 : MemoryContextSwitchTo(portal->holdContext);
415 :
416 5 : if (portal->atEnd)
417 : {
418 : /*
419 : * Just force the tuplestore forward to its end. The size of the
420 : * skip request here is arbitrary.
421 : */
422 0 : while (tuplestore_skiptuples(portal->holdStore, 1000000, true))
423 : /* continue */ ;
424 : }
425 : else
426 : {
427 5 : tuplestore_rescan(portal->holdStore);
428 :
429 5 : if (!tuplestore_skiptuples(portal->holdStore,
430 5 : portal->portalPos,
431 : true))
432 0 : elog(ERROR, "unexpected end of tuple stream");
433 : }
434 : }
435 0 : PG_CATCH();
436 : {
437 : /* Uncaught error while executing portal: mark it dead */
438 0 : MarkPortalFailed(portal);
439 :
440 : /* Restore global vars and propagate error */
441 0 : ActivePortal = saveActivePortal;
442 0 : CurrentResourceOwner = saveResourceOwner;
443 0 : PortalContext = savePortalContext;
444 :
445 0 : PG_RE_THROW();
446 : }
447 5 : PG_END_TRY();
448 :
449 5 : MemoryContextSwitchTo(oldcxt);
450 :
451 : /* Mark portal not active */
452 5 : portal->status = PORTAL_READY;
453 :
454 5 : ActivePortal = saveActivePortal;
455 5 : CurrentResourceOwner = saveResourceOwner;
456 5 : PortalContext = savePortalContext;
457 :
458 5 : PopActiveSnapshot();
459 :
460 : /*
461 : * We can now release any subsidiary memory of the portal's heap context;
462 : * we'll never use it again. The executor already dropped its context,
463 : * but this will clean up anything that glommed onto the portal's heap via
464 : * PortalContext.
465 : */
466 5 : MemoryContextDeleteChildren(PortalGetHeapMemory(portal));
467 5 : }
|