LCOV - differential code coverage report
Current view: top level - src/backend/executor - nodeLimit.c (source / functions) Coverage Total Hit UBC GIC GNC CBC ECB
Current: Differential Code Coverage HEAD vs 15 Lines: 87.5 % 176 154 22 1 3 150 4
Current Date: 2023-04-08 15:15:32 Functions: 100.0 % 6 6 1 5
Baseline: 15
Baseline Date: 2023-04-08 15:09:40
Legend: Lines: hit not hit

           TLA  Line data    Source code
       1                 : /*-------------------------------------------------------------------------
       2                 :  *
       3                 :  * nodeLimit.c
       4                 :  *    Routines to handle limiting of query results where appropriate
       5                 :  *
       6                 :  * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
       7                 :  * Portions Copyright (c) 1994, Regents of the University of California
       8                 :  *
       9                 :  *
      10                 :  * IDENTIFICATION
      11                 :  *    src/backend/executor/nodeLimit.c
      12                 :  *
      13                 :  *-------------------------------------------------------------------------
      14                 :  */
      15                 : /*
      16                 :  * INTERFACE ROUTINES
      17                 :  *      ExecLimit       - extract a limited range of tuples
      18                 :  *      ExecInitLimit   - initialize node and subnodes..
      19                 :  *      ExecEndLimit    - shutdown node and subnodes
      20                 :  */
      21                 : 
      22                 : #include "postgres.h"
      23                 : 
      24                 : #include "executor/executor.h"
      25                 : #include "executor/nodeLimit.h"
      26                 : #include "miscadmin.h"
      27                 : #include "nodes/nodeFuncs.h"
      28                 : 
      29                 : static void recompute_limits(LimitState *node);
      30                 : static int64 compute_tuples_needed(LimitState *node);
      31                 : 
      32                 : 
      33                 : /* ----------------------------------------------------------------
      34                 :  *      ExecLimit
      35                 :  *
      36                 :  *      This is a very simple node which just performs LIMIT/OFFSET
      37                 :  *      filtering on the stream of tuples returned by a subplan.
      38                 :  * ----------------------------------------------------------------
      39                 :  */
      40                 : static TupleTableSlot *         /* return: a tuple or NULL */
      41 CBC       17972 : ExecLimit(PlanState *pstate)
      42                 : {
      43           17972 :     LimitState *node = castNode(LimitState, pstate);
      44           17972 :     ExprContext *econtext = node->ps.ps_ExprContext;
      45                 :     ScanDirection direction;
      46                 :     TupleTableSlot *slot;
      47                 :     PlanState  *outerPlan;
      48                 : 
      49           17972 :     CHECK_FOR_INTERRUPTS();
      50                 : 
      51                 :     /*
      52                 :      * get information from the node
      53                 :      */
      54           17972 :     direction = node->ps.state->es_direction;
      55           17972 :     outerPlan = outerPlanState(node);
      56                 : 
      57                 :     /*
      58                 :      * The main logic is a simple state machine.
      59                 :      */
      60           17972 :     switch (node->lstate)
      61                 :     {
      62            1960 :         case LIMIT_INITIAL:
      63                 : 
      64                 :             /*
      65                 :              * First call for this node, so compute limit/offset. (We can't do
      66                 :              * this any earlier, because parameters from upper nodes will not
      67                 :              * be set during ExecInitLimit.)  This also sets position = 0 and
      68                 :              * changes the state to LIMIT_RESCAN.
      69                 :              */
      70            1960 :             recompute_limits(node);
      71                 : 
      72                 :             /* FALL THRU */
      73                 : 
      74            2410 :         case LIMIT_RESCAN:
      75                 : 
      76                 :             /*
      77                 :              * If backwards scan, just return NULL without changing state.
      78                 :              */
      79            2410 :             if (!ScanDirectionIsForward(direction))
      80 UBC           0 :                 return NULL;
      81                 : 
      82                 :             /*
      83                 :              * Check for empty window; if so, treat like empty subplan.
      84                 :              */
      85 CBC        2410 :             if (node->count <= 0 && !node->noCount)
      86                 :             {
      87              69 :                 node->lstate = LIMIT_EMPTY;
      88              69 :                 return NULL;
      89                 :             }
      90                 : 
      91                 :             /*
      92                 :              * Fetch rows from subplan until we reach position > offset.
      93                 :              */
      94                 :             for (;;)
      95                 :             {
      96          254720 :                 slot = ExecProcNode(outerPlan);
      97          254713 :                 if (TupIsNull(slot))
      98                 :                 {
      99                 :                     /*
     100                 :                      * The subplan returns too few tuples for us to produce
     101                 :                      * any output at all.
     102                 :                      */
     103             125 :                     node->lstate = LIMIT_EMPTY;
     104             125 :                     return NULL;
     105                 :                 }
     106                 : 
     107                 :                 /*
     108                 :                  * Tuple at limit is needed for comparison in subsequent
     109                 :                  * execution to detect ties.
     110                 :                  */
     111          254588 :                 if (node->limitOption == LIMIT_OPTION_WITH_TIES &&
     112              13 :                     node->position - node->offset == node->count - 1)
     113                 :                 {
     114               6 :                     ExecCopySlot(node->last_slot, slot);
     115                 :                 }
     116          254588 :                 node->subSlot = slot;
     117          254588 :                 if (++node->position > node->offset)
     118            2209 :                     break;
     119                 :             }
     120                 : 
     121                 :             /*
     122                 :              * Okay, we have the first tuple of the window.
     123                 :              */
     124            2209 :             node->lstate = LIMIT_INWINDOW;
     125            2209 :             break;
     126                 : 
     127 UBC           0 :         case LIMIT_EMPTY:
     128                 : 
     129                 :             /*
     130                 :              * The subplan is known to return no tuples (or not more than
     131                 :              * OFFSET tuples, in general).  So we return no tuples.
     132                 :              */
     133               0 :             return NULL;
     134                 : 
     135 CBC       15446 :         case LIMIT_INWINDOW:
     136           15446 :             if (ScanDirectionIsForward(direction))
     137                 :             {
     138                 :                 /*
     139                 :                  * Forwards scan, so check for stepping off end of window.  At
     140                 :                  * the end of the window, the behavior depends on whether WITH
     141                 :                  * TIES was specified: if so, we need to change the state
     142                 :                  * machine to WINDOWEND_TIES, and fall through to the code for
     143                 :                  * that case.  If not (nothing was specified, or ONLY was)
     144                 :                  * return NULL without advancing the subplan or the position
     145                 :                  * variable, but change the state machine to record having
     146                 :                  * done so.
     147                 :                  *
     148                 :                  * Once at the end, ideally, we would shut down parallel
     149                 :                  * resources; but that would destroy the parallel context
     150                 :                  * which might be required for rescans.  To do that, we'll
     151                 :                  * need to find a way to pass down more information about
     152                 :                  * whether rescans are possible.
     153                 :                  */
     154           15401 :                 if (!node->noCount &&
     155           15149 :                     node->position - node->offset >= node->count)
     156                 :                 {
     157            1882 :                     if (node->limitOption == LIMIT_OPTION_COUNT)
     158                 :                     {
     159            1863 :                         node->lstate = LIMIT_WINDOWEND;
     160            1863 :                         return NULL;
     161                 :                     }
     162                 :                     else
     163                 :                     {
     164              19 :                         node->lstate = LIMIT_WINDOWEND_TIES;
     165                 :                         /* we'll fall through to the next case */
     166                 :                     }
     167                 :                 }
     168                 :                 else
     169                 :                 {
     170                 :                     /*
     171                 :                      * Get next tuple from subplan, if any.
     172                 :                      */
     173           13519 :                     slot = ExecProcNode(outerPlan);
     174           13516 :                     if (TupIsNull(slot))
     175                 :                     {
     176              82 :                         node->lstate = LIMIT_SUBPLANEOF;
     177              82 :                         return NULL;
     178                 :                     }
     179                 : 
     180                 :                     /*
     181                 :                      * If WITH TIES is active, and this is the last in-window
     182                 :                      * tuple, save it to be used in subsequent WINDOWEND_TIES
     183                 :                      * processing.
     184                 :                      */
     185           13434 :                     if (node->limitOption == LIMIT_OPTION_WITH_TIES &&
     186              13 :                         node->position - node->offset == node->count - 1)
     187                 :                     {
     188              13 :                         ExecCopySlot(node->last_slot, slot);
     189                 :                     }
     190           13434 :                     node->subSlot = slot;
     191           13434 :                     node->position++;
     192           13434 :                     break;
     193                 :                 }
     194                 :             }
     195                 :             else
     196                 :             {
     197                 :                 /*
     198                 :                  * Backwards scan, so check for stepping off start of window.
     199                 :                  * As above, only change state-machine status if so.
     200                 :                  */
     201              45 :                 if (node->position <= node->offset + 1)
     202                 :                 {
     203              15 :                     node->lstate = LIMIT_WINDOWSTART;
     204              15 :                     return NULL;
     205                 :                 }
     206                 : 
     207                 :                 /*
     208                 :                  * Get previous tuple from subplan; there should be one!
     209                 :                  */
     210              30 :                 slot = ExecProcNode(outerPlan);
     211              30 :                 if (TupIsNull(slot))
     212 UBC           0 :                     elog(ERROR, "LIMIT subplan failed to run backwards");
     213 CBC          30 :                 node->subSlot = slot;
     214              30 :                 node->position--;
     215              30 :                 break;
     216                 :             }
     217                 : 
     218              19 :             Assert(node->lstate == LIMIT_WINDOWEND_TIES);
     219                 :             /* FALL THRU */
     220                 : 
     221                 :         case LIMIT_WINDOWEND_TIES:
     222             105 :             if (ScanDirectionIsForward(direction))
     223                 :             {
     224                 :                 /*
     225                 :                  * Advance the subplan until we find the first row with
     226                 :                  * different ORDER BY pathkeys.
     227                 :                  */
     228             105 :                 slot = ExecProcNode(outerPlan);
     229             105 :                 if (TupIsNull(slot))
     230                 :                 {
     231               1 :                     node->lstate = LIMIT_SUBPLANEOF;
     232               1 :                     return NULL;
     233                 :                 }
     234                 : 
     235                 :                 /*
     236                 :                  * Test if the new tuple and the last tuple match. If so we
     237                 :                  * return the tuple.
     238                 :                  */
     239             104 :                 econtext->ecxt_innertuple = slot;
     240             104 :                 econtext->ecxt_outertuple = node->last_slot;
     241             104 :                 if (ExecQualAndReset(node->eqfunction, econtext))
     242                 :                 {
     243              86 :                     node->subSlot = slot;
     244              86 :                     node->position++;
     245                 :                 }
     246                 :                 else
     247                 :                 {
     248              18 :                     node->lstate = LIMIT_WINDOWEND;
     249              18 :                     return NULL;
     250                 :                 }
     251                 :             }
     252                 :             else
     253                 :             {
     254                 :                 /*
     255                 :                  * Backwards scan, so check for stepping off start of window.
     256                 :                  * Change only state-machine status if so.
     257                 :                  */
     258 UBC           0 :                 if (node->position <= node->offset + 1)
     259                 :                 {
     260               0 :                     node->lstate = LIMIT_WINDOWSTART;
     261               0 :                     return NULL;
     262                 :                 }
     263                 : 
     264                 :                 /*
     265                 :                  * Get previous tuple from subplan; there should be one! And
     266                 :                  * change state-machine status.
     267                 :                  */
     268               0 :                 slot = ExecProcNode(outerPlan);
     269               0 :                 if (TupIsNull(slot))
     270               0 :                     elog(ERROR, "LIMIT subplan failed to run backwards");
     271               0 :                 node->subSlot = slot;
     272               0 :                 node->position--;
     273               0 :                 node->lstate = LIMIT_INWINDOW;
     274                 :             }
     275 CBC          86 :             break;
     276                 : 
     277               6 :         case LIMIT_SUBPLANEOF:
     278               6 :             if (ScanDirectionIsForward(direction))
     279 UBC           0 :                 return NULL;
     280                 : 
     281                 :             /*
     282                 :              * Backing up from subplan EOF, so re-fetch previous tuple; there
     283                 :              * should be one!  Note previous tuple must be in window.
     284                 :              */
     285 CBC           6 :             slot = ExecProcNode(outerPlan);
     286               6 :             if (TupIsNull(slot))
     287 UBC           0 :                 elog(ERROR, "LIMIT subplan failed to run backwards");
     288 CBC           6 :             node->subSlot = slot;
     289               6 :             node->lstate = LIMIT_INWINDOW;
     290                 :             /* position does not change 'cause we didn't advance it before */
     291               6 :             break;
     292                 : 
     293              12 :         case LIMIT_WINDOWEND:
     294              12 :             if (ScanDirectionIsForward(direction))
     295 UBC           0 :                 return NULL;
     296                 : 
     297                 :             /*
     298                 :              * We already past one position to detect ties so re-fetch
     299                 :              * previous tuple; there should be one!  Note previous tuple must
     300                 :              * be in window.
     301                 :              */
     302 CBC          12 :             if (node->limitOption == LIMIT_OPTION_WITH_TIES)
     303                 :             {
     304               9 :                 slot = ExecProcNode(outerPlan);
     305               9 :                 if (TupIsNull(slot))
     306 UBC           0 :                     elog(ERROR, "LIMIT subplan failed to run backwards");
     307 CBC           9 :                 node->subSlot = slot;
     308               9 :                 node->lstate = LIMIT_INWINDOW;
     309                 :             }
     310                 :             else
     311                 :             {
     312                 :                 /*
     313                 :                  * Backing up from window end: simply re-return the last tuple
     314                 :                  * fetched from the subplan.
     315                 :                  */
     316               3 :                 slot = node->subSlot;
     317               3 :                 node->lstate = LIMIT_INWINDOW;
     318                 :                 /* position does not change 'cause we didn't advance it before */
     319                 :             }
     320              12 :             break;
     321                 : 
     322              12 :         case LIMIT_WINDOWSTART:
     323              12 :             if (!ScanDirectionIsForward(direction))
     324 UBC           0 :                 return NULL;
     325                 : 
     326                 :             /*
     327                 :              * Advancing after having backed off window start: simply
     328                 :              * re-return the last tuple fetched from the subplan.
     329                 :              */
     330 CBC          12 :             slot = node->subSlot;
     331              12 :             node->lstate = LIMIT_INWINDOW;
     332                 :             /* position does not change 'cause we didn't change it before */
     333              12 :             break;
     334                 : 
     335 UBC           0 :         default:
     336               0 :             elog(ERROR, "impossible LIMIT state: %d",
     337                 :                  (int) node->lstate);
     338                 :             slot = NULL;        /* keep compiler quiet */
     339                 :             break;
     340                 :     }
     341                 : 
     342                 :     /* Return the current tuple */
     343 CBC       15789 :     Assert(!TupIsNull(slot));
     344                 : 
     345           15789 :     return slot;
     346                 : }
     347                 : 
     348                 : /*
     349                 :  * Evaluate the limit/offset expressions --- done at startup or rescan.
     350                 :  *
     351                 :  * This is also a handy place to reset the current-position state info.
     352                 :  */
     353                 : static void
     354            2410 : recompute_limits(LimitState *node)
     355                 : {
     356            2410 :     ExprContext *econtext = node->ps.ps_ExprContext;
     357                 :     Datum       val;
     358                 :     bool        isNull;
     359                 : 
     360            2410 :     if (node->limitOffset)
     361                 :     {
     362             150 :         val = ExecEvalExprSwitchContext(node->limitOffset,
     363                 :                                         econtext,
     364                 :                                         &isNull);
     365                 :         /* Interpret NULL offset as no offset */
     366             150 :         if (isNull)
     367               3 :             node->offset = 0;
     368                 :         else
     369                 :         {
     370             147 :             node->offset = DatumGetInt64(val);
     371             147 :             if (node->offset < 0)
     372 UBC           0 :                 ereport(ERROR,
     373                 :                         (errcode(ERRCODE_INVALID_ROW_COUNT_IN_RESULT_OFFSET_CLAUSE),
     374                 :                          errmsg("OFFSET must not be negative")));
     375                 :         }
     376                 :     }
     377                 :     else
     378                 :     {
     379                 :         /* No OFFSET supplied */
     380 CBC        2260 :         node->offset = 0;
     381                 :     }
     382                 : 
     383            2410 :     if (node->limitCount)
     384                 :     {
     385            2386 :         val = ExecEvalExprSwitchContext(node->limitCount,
     386                 :                                         econtext,
     387                 :                                         &isNull);
     388                 :         /* Interpret NULL count as no count (LIMIT ALL) */
     389            2386 :         if (isNull)
     390                 :         {
     391               3 :             node->count = 0;
     392               3 :             node->noCount = true;
     393                 :         }
     394                 :         else
     395                 :         {
     396            2383 :             node->count = DatumGetInt64(val);
     397            2383 :             if (node->count < 0)
     398 UBC           0 :                 ereport(ERROR,
     399                 :                         (errcode(ERRCODE_INVALID_ROW_COUNT_IN_LIMIT_CLAUSE),
     400                 :                          errmsg("LIMIT must not be negative")));
     401 CBC        2383 :             node->noCount = false;
     402                 :         }
     403                 :     }
     404                 :     else
     405                 :     {
     406                 :         /* No COUNT supplied */
     407              24 :         node->count = 0;
     408              24 :         node->noCount = true;
     409                 :     }
     410                 : 
     411                 :     /* Reset position to start-of-scan */
     412            2410 :     node->position = 0;
     413            2410 :     node->subSlot = NULL;
     414                 : 
     415                 :     /* Set state-machine state */
     416            2410 :     node->lstate = LIMIT_RESCAN;
     417                 : 
     418                 :     /*
     419                 :      * Notify child node about limit.  Note: think not to "optimize" by
     420                 :      * skipping ExecSetTupleBound if compute_tuples_needed returns < 0.  We
     421                 :      * must update the child node anyway, in case this is a rescan and the
     422                 :      * previous time we got a different result.
     423                 :      */
     424            2410 :     ExecSetTupleBound(compute_tuples_needed(node), outerPlanState(node));
     425            2410 : }
     426                 : 
     427                 : /*
     428                 :  * Compute the maximum number of tuples needed to satisfy this Limit node.
     429                 :  * Return a negative value if there is not a determinable limit.
     430                 :  */
     431                 : static int64
     432            2410 : compute_tuples_needed(LimitState *node)
     433                 : {
     434            2410 :     if ((node->noCount) || (node->limitOption == LIMIT_OPTION_WITH_TIES))
     435              40 :         return -1;
     436                 :     /* Note: if this overflows, we'll return a negative value, which is OK */
     437            2370 :     return node->count + node->offset;
     438                 : }
     439                 : 
     440                 : /* ----------------------------------------------------------------
     441                 :  *      ExecInitLimit
     442                 :  *
     443                 :  *      This initializes the limit node state structures and
     444                 :  *      the node's subplan.
     445                 :  * ----------------------------------------------------------------
     446                 :  */
     447                 : LimitState *
     448            2572 : ExecInitLimit(Limit *node, EState *estate, int eflags)
     449                 : {
     450                 :     LimitState *limitstate;
     451                 :     Plan       *outerPlan;
     452                 : 
     453                 :     /* check for unsupported flags */
     454            2572 :     Assert(!(eflags & EXEC_FLAG_MARK));
     455                 : 
     456                 :     /*
     457                 :      * create state structure
     458                 :      */
     459            2572 :     limitstate = makeNode(LimitState);
     460            2572 :     limitstate->ps.plan = (Plan *) node;
     461            2572 :     limitstate->ps.state = estate;
     462            2572 :     limitstate->ps.ExecProcNode = ExecLimit;
     463                 : 
     464            2572 :     limitstate->lstate = LIMIT_INITIAL;
     465                 : 
     466                 :     /*
     467                 :      * Miscellaneous initialization
     468                 :      *
     469                 :      * Limit nodes never call ExecQual or ExecProject, but they need an
     470                 :      * exprcontext anyway to evaluate the limit/offset parameters in.
     471                 :      */
     472            2572 :     ExecAssignExprContext(estate, &limitstate->ps);
     473                 : 
     474                 :     /*
     475                 :      * initialize outer plan
     476                 :      */
     477            2572 :     outerPlan = outerPlan(node);
     478            2572 :     outerPlanState(limitstate) = ExecInitNode(outerPlan, estate, eflags);
     479                 : 
     480                 :     /*
     481                 :      * initialize child expressions
     482                 :      */
     483            2572 :     limitstate->limitOffset = ExecInitExpr((Expr *) node->limitOffset,
     484                 :                                            (PlanState *) limitstate);
     485            2572 :     limitstate->limitCount = ExecInitExpr((Expr *) node->limitCount,
     486                 :                                           (PlanState *) limitstate);
     487            2572 :     limitstate->limitOption = node->limitOption;
     488                 : 
     489                 :     /*
     490                 :      * Initialize result type.
     491                 :      */
     492            2572 :     ExecInitResultTypeTL(&limitstate->ps);
     493                 : 
     494            2572 :     limitstate->ps.resultopsset = true;
     495            2572 :     limitstate->ps.resultops = ExecGetResultSlotOps(outerPlanState(limitstate),
     496                 :                                                     &limitstate->ps.resultopsfixed);
     497                 : 
     498                 :     /*
     499                 :      * limit nodes do no projections, so initialize projection info for this
     500                 :      * node appropriately
     501                 :      */
     502            2572 :     limitstate->ps.ps_ProjInfo = NULL;
     503                 : 
     504                 :     /*
     505                 :      * Initialize the equality evaluation, to detect ties.
     506                 :      */
     507            2572 :     if (node->limitOption == LIMIT_OPTION_WITH_TIES)
     508                 :     {
     509                 :         TupleDesc   desc;
     510                 :         const TupleTableSlotOps *ops;
     511                 : 
     512              13 :         desc = ExecGetResultType(outerPlanState(limitstate));
     513              13 :         ops = ExecGetResultSlotOps(outerPlanState(limitstate), NULL);
     514                 : 
     515              13 :         limitstate->last_slot = ExecInitExtraTupleSlot(estate, desc, ops);
     516              13 :         limitstate->eqfunction = execTuplesMatchPrepare(desc,
     517                 :                                                         node->uniqNumCols,
     518              13 :                                                         node->uniqColIdx,
     519              13 :                                                         node->uniqOperators,
     520              13 :                                                         node->uniqCollations,
     521                 :                                                         &limitstate->ps);
     522                 :     }
     523                 : 
     524            2572 :     return limitstate;
     525                 : }
     526                 : 
     527                 : /* ----------------------------------------------------------------
     528                 :  *      ExecEndLimit
     529                 :  *
     530                 :  *      This shuts down the subplan and frees resources allocated
     531                 :  *      to this node.
     532                 :  * ----------------------------------------------------------------
     533                 :  */
     534                 : void
     535            2541 : ExecEndLimit(LimitState *node)
     536                 : {
     537            2541 :     ExecFreeExprContext(&node->ps);
     538            2541 :     ExecEndNode(outerPlanState(node));
     539            2541 : }
     540                 : 
     541                 : 
     542                 : void
     543             450 : ExecReScanLimit(LimitState *node)
     544                 : {
     545 GNC         450 :     PlanState  *outerPlan = outerPlanState(node);
     546                 : 
     547 ECB             :     /*
     548                 :      * Recompute limit/offset in case parameters changed, and reset the state
     549                 :      * machine.  We must do this before rescanning our child node, in case
     550                 :      * it's a Sort that we are passing the parameters down to.
     551                 :      */
     552 GIC         450 :     recompute_limits(node);
     553                 : 
     554 ECB             :     /*
     555                 :      * if chgParam of subnode is not null then plan will be re-scanned by
     556                 :      * first ExecProcNode.
     557                 :      */
     558 GNC         450 :     if (outerPlan->chgParam == NULL)
     559              60 :         ExecReScan(outerPlan);
     560 CBC         450 : }
        

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