LCOV - code coverage report
Current view: top level - src/backend/commands - portalcmds.c (source / functions) Hit Total Coverage
Test: PostgreSQL 18devel Lines: 115 123 93.5 %
Date: 2024-11-21 08:14:44 Functions: 5 5 100.0 %
Legend: Lines: hit not hit

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

Generated by: LCOV version 1.14