Coverage Report

Created: 2025-10-09 06:07

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}