LCOV - differential code coverage report
Current view: top level - src/backend/commands - portalcmds.c (source / functions) Coverage Total Hit UBC GNC CBC DCB
Current: Differential Code Coverage HEAD vs 15 Lines: 93.7 % 126 118 8 3 115 3
Current Date: 2023-04-08 15:15:32 Functions: 100.0 % 5 5 2 3
Baseline: 15
Baseline Date: 2023-04-08 15:09:40
Legend: Lines: hit not hit

           TLA  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-2023, 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 "rewrite/rewriteHandler.h"
      32                 : #include "tcop/pquery.h"
      33                 : #include "tcop/tcopprot.h"
      34                 : #include "utils/memutils.h"
      35                 : #include "utils/snapmgr.h"
      36                 : 
      37                 : 
      38                 : /*
      39                 :  * PerformCursorOpen
      40                 :  *      Execute SQL DECLARE CURSOR command.
      41                 :  */
      42                 : void
      43 CBC        1331 : PerformCursorOpen(ParseState *pstate, DeclareCursorStmt *cstmt, ParamListInfo params,
      44                 :                   bool isTopLevel)
      45                 : {
      46            1331 :     Query      *query = castNode(Query, cstmt->query);
      47                 :     List       *rewritten;
      48                 :     PlannedStmt *plan;
      49                 :     Portal      portal;
      50                 :     MemoryContext oldContext;
      51                 :     char       *queryString;
      52                 : 
      53                 :     /*
      54                 :      * Disallow empty-string cursor name (conflicts with protocol-level
      55                 :      * unnamed portal).
      56                 :      */
      57            1331 :     if (!cstmt->portalname || cstmt->portalname[0] == '\0')
      58 UBC           0 :         ereport(ERROR,
      59                 :                 (errcode(ERRCODE_INVALID_CURSOR_NAME),
      60                 :                  errmsg("invalid cursor name: must not be empty")));
      61                 : 
      62                 :     /*
      63                 :      * If this is a non-holdable cursor, we require that this statement has
      64                 :      * been executed inside a transaction block (or else, it would have no
      65                 :      * user-visible effect).
      66                 :      */
      67 CBC        1331 :     if (!(cstmt->options & CURSOR_OPT_HOLD))
      68            1294 :         RequireTransactionBlock(isTopLevel, "DECLARE CURSOR");
      69              37 :     else if (InSecurityRestrictedOperation())
      70               6 :         ereport(ERROR,
      71                 :                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
      72                 :                  errmsg("cannot create a cursor WITH HOLD within security-restricted operation")));
      73                 : 
      74                 :     /*
      75                 :      * Parse analysis was done already, but we still have to run the rule
      76                 :      * rewriter.  We do not do AcquireRewriteLocks: we assume the query either
      77                 :      * came straight from the parser, or suitable locks were acquired by
      78                 :      * plancache.c.
      79                 :      */
      80            1324 :     rewritten = QueryRewrite(query);
      81                 : 
      82                 :     /* SELECT should never rewrite to more or less than one query */
      83            1324 :     if (list_length(rewritten) != 1)
      84 UBC           0 :         elog(ERROR, "non-SELECT statement in DECLARE CURSOR");
      85                 : 
      86 CBC        1324 :     query = linitial_node(Query, rewritten);
      87                 : 
      88            1324 :     if (query->commandType != CMD_SELECT)
      89 UBC           0 :         elog(ERROR, "non-SELECT statement in DECLARE CURSOR");
      90                 : 
      91                 :     /* Plan the query, applying the specified options */
      92 CBC        1324 :     plan = pg_plan_query(query, pstate->p_sourcetext, cstmt->options, params);
      93                 : 
      94                 :     /*
      95                 :      * Create a portal and copy the plan and query string into its memory.
      96                 :      */
      97            1324 :     portal = CreatePortal(cstmt->portalname, false, false);
      98                 : 
      99            1324 :     oldContext = MemoryContextSwitchTo(portal->portalContext);
     100                 : 
     101            1324 :     plan = copyObject(plan);
     102                 : 
     103            1324 :     queryString = pstrdup(pstate->p_sourcetext);
     104                 : 
     105            1324 :     PortalDefineQuery(portal,
     106                 :                       NULL,
     107                 :                       queryString,
     108                 :                       CMDTAG_SELECT,    /* cursor's query is always a SELECT */
     109            1324 :                       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            1324 :     params = copyParamList(params);
     123                 : 
     124            1324 :     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            1324 :     portal->cursorOptions = cstmt->options;
     134            1324 :     if (!(portal->cursorOptions & (CURSOR_OPT_SCROLL | CURSOR_OPT_NO_SCROLL)))
     135                 :     {
     136            2265 :         if (plan->rowMarks == NIL &&
     137            1097 :             ExecSupportsBackwardScan(plan->planTree))
     138             730 :             portal->cursorOptions |= CURSOR_OPT_SCROLL;
     139                 :         else
     140             438 :             portal->cursorOptions |= CURSOR_OPT_NO_SCROLL;
     141                 :     }
     142                 : 
     143                 :     /*
     144                 :      * Start execution, inserting parameters if any.
     145                 :      */
     146            1324 :     PortalStart(portal, params, 0, GetActiveSnapshot());
     147                 : 
     148            1324 :     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            1324 : }
     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                 :  *  qc: where to store a command completion status data.
     163                 :  *
     164                 :  * qc may be NULL if caller doesn't want status data.
     165                 :  */
     166                 : void
     167            2839 : PerformPortalFetch(FetchStmt *stmt,
     168                 :                    DestReceiver *dest,
     169                 :                    QueryCompletion *qc)
     170                 : {
     171                 :     Portal      portal;
     172                 :     uint64      nprocessed;
     173                 : 
     174                 :     /*
     175                 :      * Disallow empty-string cursor name (conflicts with protocol-level
     176                 :      * unnamed portal).
     177                 :      */
     178            2839 :     if (!stmt->portalname || stmt->portalname[0] == '\0')
     179 UBC           0 :         ereport(ERROR,
     180                 :                 (errcode(ERRCODE_INVALID_CURSOR_NAME),
     181                 :                  errmsg("invalid cursor name: must not be empty")));
     182                 : 
     183                 :     /* get the portal from the portal name */
     184 CBC        2839 :     portal = GetPortalByName(stmt->portalname);
     185            2839 :     if (!PortalIsValid(portal))
     186                 :     {
     187              17 :         ereport(ERROR,
     188                 :                 (errcode(ERRCODE_UNDEFINED_CURSOR),
     189                 :                  errmsg("cursor \"%s\" does not exist", stmt->portalname)));
     190                 :         return;                 /* keep compiler happy */
     191                 :     }
     192                 : 
     193                 :     /* Adjust dest if needed.  MOVE wants destination DestNone */
     194            2822 :     if (stmt->ismove)
     195              47 :         dest = None_Receiver;
     196                 : 
     197                 :     /* Do it */
     198            2822 :     nprocessed = PortalRunFetch(portal,
     199                 :                                 stmt->direction,
     200                 :                                 stmt->howMany,
     201                 :                                 dest);
     202                 : 
     203                 :     /* Return command status if wanted */
     204            2790 :     if (qc)
     205            2790 :         SetQueryCompletion(qc, stmt->ismove ? CMDTAG_MOVE : CMDTAG_FETCH,
     206                 :                            nprocessed);
     207                 : }
     208                 : 
     209                 : /*
     210                 :  * PerformPortalClose
     211                 :  *      Close a cursor.
     212                 :  */
     213                 : void
     214            1047 : PerformPortalClose(const char *name)
     215                 : {
     216                 :     Portal      portal;
     217                 : 
     218                 :     /* NULL means CLOSE ALL */
     219            1047 :     if (name == NULL)
     220                 :     {
     221               6 :         PortalHashTableDeleteAll();
     222               6 :         return;
     223                 :     }
     224                 : 
     225                 :     /*
     226                 :      * Disallow empty-string cursor name (conflicts with protocol-level
     227                 :      * unnamed portal).
     228                 :      */
     229            1041 :     if (name[0] == '\0')
     230 UBC           0 :         ereport(ERROR,
     231                 :                 (errcode(ERRCODE_INVALID_CURSOR_NAME),
     232                 :                  errmsg("invalid cursor name: must not be empty")));
     233                 : 
     234                 :     /*
     235                 :      * get the portal from the portal name
     236                 :      */
     237 CBC        1041 :     portal = GetPortalByName(name);
     238            1041 :     if (!PortalIsValid(portal))
     239                 :     {
     240               1 :         ereport(ERROR,
     241                 :                 (errcode(ERRCODE_UNDEFINED_CURSOR),
     242                 :                  errmsg("cursor \"%s\" does not exist", name)));
     243                 :         return;                 /* keep compiler happy */
     244                 :     }
     245                 : 
     246                 :     /*
     247                 :      * Note: PortalCleanup is called as a side-effect, if not already done.
     248                 :      */
     249            1040 :     PortalDrop(portal, false);
     250                 : }
     251                 : 
     252                 : /*
     253                 :  * PortalCleanup
     254                 :  *
     255                 :  * Clean up a portal when it's dropped.  This is the standard cleanup hook
     256                 :  * for portals.
     257                 :  *
     258                 :  * Note: if portal->status is PORTAL_FAILED, we are probably being called
     259                 :  * during error abort, and must be careful to avoid doing anything that
     260                 :  * is likely to fail again.
     261                 :  */
     262                 : void
     263          498848 : PortalCleanup(Portal portal)
     264                 : {
     265                 :     QueryDesc  *queryDesc;
     266                 : 
     267                 :     /*
     268                 :      * sanity checks
     269                 :      */
     270 GNC      498848 :     Assert(PortalIsValid(portal));
     271          498848 :     Assert(portal->cleanup == PortalCleanup);
     272                 : 
     273                 :     /*
     274                 :      * Shut down executor, if still running.  We skip this during error abort,
     275                 :      * since other mechanisms will take care of releasing executor resources,
     276                 :      * and we can't be sure that ExecutorEnd itself wouldn't fail.
     277                 :      */
     278 CBC      498848 :     queryDesc = portal->queryDesc;
     279          498848 :     if (queryDesc)
     280                 :     {
     281                 :         /*
     282                 :          * Reset the queryDesc before anything else.  This prevents us from
     283                 :          * trying to shut down the executor twice, in case of an error below.
     284                 :          * The transaction abort mechanisms will take care of resource cleanup
     285                 :          * in such a case.
     286                 :          */
     287          108716 :         portal->queryDesc = NULL;
     288                 : 
     289          108716 :         if (portal->status != PORTAL_FAILED)
     290                 :         {
     291                 :             ResourceOwner saveResourceOwner;
     292                 : 
     293                 :             /* We must make the portal's resource owner current */
     294          106062 :             saveResourceOwner = CurrentResourceOwner;
     295          106062 :             if (portal->resowner)
     296          106062 :                 CurrentResourceOwner = portal->resowner;
     297                 : 
     298          106062 :             ExecutorFinish(queryDesc);
     299          106062 :             ExecutorEnd(queryDesc);
     300          106062 :             FreeQueryDesc(queryDesc);
     301                 : 
     302          106062 :             CurrentResourceOwner = saveResourceOwner;
     303                 :         }
     304                 :     }
     305          498848 : }
     306                 : 
     307                 : /*
     308                 :  * PersistHoldablePortal
     309                 :  *
     310                 :  * Prepare the specified Portal for access outside of the current
     311                 :  * transaction. When this function returns, all future accesses to the
     312                 :  * portal must be done via the Tuplestore (not by invoking the
     313                 :  * executor).
     314                 :  */
     315                 : void
     316              41 : PersistHoldablePortal(Portal portal)
     317                 : {
     318              41 :     QueryDesc  *queryDesc = portal->queryDesc;
     319                 :     Portal      saveActivePortal;
     320                 :     ResourceOwner saveResourceOwner;
     321                 :     MemoryContext savePortalContext;
     322                 :     MemoryContext oldcxt;
     323                 : 
     324                 :     /*
     325                 :      * If we're preserving a holdable portal, we had better be inside the
     326                 :      * transaction that originally created it.
     327                 :      */
     328              41 :     Assert(portal->createSubid != InvalidSubTransactionId);
     329              41 :     Assert(queryDesc != NULL);
     330                 : 
     331                 :     /*
     332                 :      * Caller must have created the tuplestore already ... but not a snapshot.
     333                 :      */
     334              41 :     Assert(portal->holdContext != NULL);
     335              41 :     Assert(portal->holdStore != NULL);
     336              41 :     Assert(portal->holdSnapshot == NULL);
     337                 : 
     338                 :     /*
     339                 :      * Before closing down the executor, we must copy the tupdesc into
     340                 :      * long-term memory, since it was created in executor memory.
     341                 :      */
     342              41 :     oldcxt = MemoryContextSwitchTo(portal->holdContext);
     343                 : 
     344              41 :     portal->tupDesc = CreateTupleDescCopy(portal->tupDesc);
     345                 : 
     346              41 :     MemoryContextSwitchTo(oldcxt);
     347                 : 
     348                 :     /*
     349                 :      * Check for improper portal use, and mark portal active.
     350                 :      */
     351              41 :     MarkPortalActive(portal);
     352                 : 
     353                 :     /*
     354                 :      * Set up global portal context pointers.
     355                 :      */
     356              41 :     saveActivePortal = ActivePortal;
     357              41 :     saveResourceOwner = CurrentResourceOwner;
     358              41 :     savePortalContext = PortalContext;
     359              41 :     PG_TRY();
     360                 :     {
     361              41 :         ScanDirection direction = ForwardScanDirection;
     362                 : 
     363              41 :         ActivePortal = portal;
     364              41 :         if (portal->resowner)
     365              41 :             CurrentResourceOwner = portal->resowner;
     366              41 :         PortalContext = portal->portalContext;
     367                 : 
     368              41 :         MemoryContextSwitchTo(PortalContext);
     369                 : 
     370              41 :         PushActiveSnapshot(queryDesc->snapshot);
     371                 : 
     372                 :         /*
     373                 :          * If the portal is marked scrollable, we need to store the entire
     374                 :          * result set in the tuplestore, so that subsequent backward FETCHs
     375                 :          * can be processed.  Otherwise, store only the not-yet-fetched rows.
     376                 :          * (The latter is not only more efficient, but avoids semantic
     377                 :          * problems if the query's output isn't stable.)
     378                 :          *
     379                 :          * In the no-scroll case, tuple indexes in the tuplestore will not
     380                 :          * match the cursor's nominal position (portalPos).  Currently this
     381                 :          * causes no difficulty because we only navigate in the tuplestore by
     382                 :          * relative position, except for the tuplestore_skiptuples call below
     383                 :          * and the tuplestore_rescan call in DoPortalRewind, both of which are
     384                 :          * disabled for no-scroll cursors.  But someday we might need to track
     385                 :          * the offset between the holdStore and the cursor's nominal position
     386                 :          * explicitly.
     387                 :          */
     388              41 :         if (portal->cursorOptions & CURSOR_OPT_SCROLL)
     389                 :         {
     390              20 :             ExecutorRewind(queryDesc);
     391                 :         }
     392                 :         else
     393                 :         {
     394                 :             /*
     395                 :              * If we already reached end-of-query, set the direction to
     396                 :              * NoMovement to avoid trying to fetch any tuples.  (This check
     397                 :              * exists because not all plan node types are robust about being
     398                 :              * called again if they've already returned NULL once.)  We'll
     399                 :              * still set up an empty tuplestore, though, to keep this from
     400                 :              * being a special case later.
     401                 :              */
     402              21 :             if (portal->atEnd)
     403 UBC           0 :                 direction = NoMovementScanDirection;
     404                 :         }
     405                 : 
     406                 :         /*
     407                 :          * Change the destination to output to the tuplestore.  Note we tell
     408                 :          * the tuplestore receiver to detoast all data passed through it; this
     409                 :          * makes it safe to not keep a snapshot associated with the data.
     410                 :          */
     411 CBC          41 :         queryDesc->dest = CreateDestReceiver(DestTuplestore);
     412              41 :         SetTuplestoreDestReceiverParams(queryDesc->dest,
     413                 :                                         portal->holdStore,
     414                 :                                         portal->holdContext,
     415                 :                                         true,
     416                 :                                         NULL,
     417                 :                                         NULL);
     418                 : 
     419                 :         /* Fetch the result set into the tuplestore */
     420 GNC          41 :         ExecutorRun(queryDesc, direction, 0, false);
     421                 : 
     422 CBC          39 :         queryDesc->dest->rDestroy(queryDesc->dest);
     423              39 :         queryDesc->dest = NULL;
     424                 : 
     425                 :         /*
     426                 :          * Now shut down the inner executor.
     427                 :          */
     428              39 :         portal->queryDesc = NULL;    /* prevent double shutdown */
     429              39 :         ExecutorFinish(queryDesc);
     430              39 :         ExecutorEnd(queryDesc);
     431              39 :         FreeQueryDesc(queryDesc);
     432                 : 
     433                 :         /*
     434                 :          * Set the position in the result set.
     435                 :          */
     436              39 :         MemoryContextSwitchTo(portal->holdContext);
     437                 : 
     438              39 :         if (portal->atEnd)
     439                 :         {
     440                 :             /*
     441                 :              * Just force the tuplestore forward to its end.  The size of the
     442                 :              * skip request here is arbitrary.
     443                 :              */
     444 UBC           0 :             while (tuplestore_skiptuples(portal->holdStore, 1000000, true))
     445                 :                  /* continue */ ;
     446                 :         }
     447                 :         else
     448                 :         {
     449 CBC          39 :             tuplestore_rescan(portal->holdStore);
     450                 : 
     451                 :             /*
     452                 :              * In the no-scroll case, the start of the tuplestore is exactly
     453                 :              * where we want to be, so no repositioning is wanted.
     454                 :              */
     455              39 :             if (portal->cursorOptions & CURSOR_OPT_SCROLL)
     456                 :             {
     457              20 :                 if (!tuplestore_skiptuples(portal->holdStore,
     458              20 :                                            portal->portalPos,
     459                 :                                            true))
     460 UBC           0 :                     elog(ERROR, "unexpected end of tuple stream");
     461                 :             }
     462                 :         }
     463                 :     }
     464 CBC           2 :     PG_CATCH();
     465                 :     {
     466                 :         /* Uncaught error while executing portal: mark it dead */
     467               2 :         MarkPortalFailed(portal);
     468                 : 
     469                 :         /* Restore global vars and propagate error */
     470               2 :         ActivePortal = saveActivePortal;
     471               2 :         CurrentResourceOwner = saveResourceOwner;
     472               2 :         PortalContext = savePortalContext;
     473                 : 
     474               2 :         PG_RE_THROW();
     475                 :     }
     476              39 :     PG_END_TRY();
     477                 : 
     478              39 :     MemoryContextSwitchTo(oldcxt);
     479                 : 
     480                 :     /* Mark portal not active */
     481              39 :     portal->status = PORTAL_READY;
     482                 : 
     483              39 :     ActivePortal = saveActivePortal;
     484              39 :     CurrentResourceOwner = saveResourceOwner;
     485              39 :     PortalContext = savePortalContext;
     486                 : 
     487              39 :     PopActiveSnapshot();
     488                 : 
     489                 :     /*
     490                 :      * We can now release any subsidiary memory of the portal's context; we'll
     491                 :      * never use it again.  The executor already dropped its context, but this
     492                 :      * will clean up anything that glommed onto the portal's context via
     493                 :      * PortalContext.
     494                 :      */
     495              39 :     MemoryContextDeleteChildren(portal->portalContext);
     496              39 : }
        

Generated by: LCOV version v1.16-55-g56c0a2a