LCOV - code coverage report
Current view: top level - src/backend/executor - nodeNestloop.c (source / functions) Coverage Total Hit
Test: PostgreSQL 20devel Lines: 97.8 % 92 90
Test Date: 2026-08-22 10:15:50 Functions: 100.0 % 4 4
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 91.2 % 57 52

             Branch data     Line data    Source code
       1                 :             : /*-------------------------------------------------------------------------
       2                 :             :  *
       3                 :             :  * nodeNestloop.c
       4                 :             :  *    routines to support nest-loop joins
       5                 :             :  *
       6                 :             :  * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
       7                 :             :  * Portions Copyright (c) 1994, Regents of the University of California
       8                 :             :  *
       9                 :             :  *
      10                 :             :  * IDENTIFICATION
      11                 :             :  *    src/backend/executor/nodeNestloop.c
      12                 :             :  *
      13                 :             :  *-------------------------------------------------------------------------
      14                 :             :  */
      15                 :             : /*
      16                 :             :  *   INTERFACE ROUTINES
      17                 :             :  *      ExecNestLoop     - process a nestloop join of two plans
      18                 :             :  *      ExecInitNestLoop - initialize the join
      19                 :             :  *      ExecEndNestLoop  - shut down the join
      20                 :             :  */
      21                 :             : 
      22                 :             : #include "postgres.h"
      23                 :             : 
      24                 :             : #include "executor/executor.h"
      25                 :             : #include "executor/instrument.h"
      26                 :             : #include "executor/nodeNestloop.h"
      27                 :             : #include "miscadmin.h"
      28                 :             : 
      29                 :             : 
      30                 :             : /* ----------------------------------------------------------------
      31                 :             :  *      ExecNestLoop(node)
      32                 :             :  *
      33                 :             :  * old comments
      34                 :             :  *      Returns the tuple joined from inner and outer tuples which
      35                 :             :  *      satisfies the qualification clause.
      36                 :             :  *
      37                 :             :  *      It scans the inner relation to join with current outer tuple.
      38                 :             :  *
      39                 :             :  *      If none is found, next tuple from the outer relation is retrieved
      40                 :             :  *      and the inner relation is scanned from the beginning again to join
      41                 :             :  *      with the outer tuple.
      42                 :             :  *
      43                 :             :  *      NULL is returned if all the remaining outer tuples are tried and
      44                 :             :  *      all fail to join with the inner tuples.
      45                 :             :  *
      46                 :             :  *      NULL is also returned if there is no tuple from inner relation.
      47                 :             :  *
      48                 :             :  *      Conditions:
      49                 :             :  *        -- outerTuple contains current tuple from outer relation and
      50                 :             :  *           the right son(inner relation) maintains "cursor" at the tuple
      51                 :             :  *           returned previously.
      52                 :             :  *              This is achieved by maintaining a scan position on the outer
      53                 :             :  *              relation.
      54                 :             :  *
      55                 :             :  *      Initial States:
      56                 :             :  *        -- the outer child and the inner child
      57                 :             :  *             are prepared to return the first tuple.
      58                 :             :  * ----------------------------------------------------------------
      59                 :             :  */
      60                 :             : static TupleTableSlot *
      61                 :     2012110 : ExecNestLoop(PlanState *pstate)
      62                 :             : {
      63                 :     2012110 :     NestLoopState *node = castNode(NestLoopState, pstate);
      64                 :             :     NestLoop   *nl;
      65                 :             :     PlanState  *innerPlan;
      66                 :             :     PlanState  *outerPlan;
      67                 :             :     TupleTableSlot *outerTupleSlot;
      68                 :             :     TupleTableSlot *innerTupleSlot;
      69                 :             :     ExprState  *joinqual;
      70                 :             :     ExprState  *otherqual;
      71                 :             :     ExprContext *econtext;
      72                 :             :     ListCell   *lc;
      73                 :             : 
      74         [ +  + ]:     2012110 :     CHECK_FOR_INTERRUPTS();
      75                 :             : 
      76                 :             :     /*
      77                 :             :      * get information from the node
      78                 :             :      */
      79                 :     2012109 :     nl = (NestLoop *) node->js.ps.plan;
      80                 :     2012109 :     joinqual = node->js.joinqual;
      81                 :     2012109 :     otherqual = node->js.ps.qual;
      82                 :     2012109 :     outerPlan = outerPlanState(node);
      83                 :     2012109 :     innerPlan = innerPlanState(node);
      84                 :     2012109 :     econtext = node->js.ps.ps_ExprContext;
      85                 :             : 
      86                 :             :     /*
      87                 :             :      * Reset per-tuple memory context to free any expression evaluation
      88                 :             :      * storage allocated in the previous tuple cycle.
      89                 :             :      */
      90                 :     2012109 :     ResetExprContext(econtext);
      91                 :             : 
      92                 :             :     /*
      93                 :             :      * Ok, everything is setup for the join so now loop until we return a
      94                 :             :      * qualifying join tuple.
      95                 :             :      */
      96                 :             :     for (;;)
      97                 :             :     {
      98                 :             :         /*
      99                 :             :          * If we don't have an outer tuple, get the next one and reset the
     100                 :             :          * inner scan.
     101                 :             :          */
     102         [ +  + ]:     7008693 :         if (node->nl_NeedNewOuter)
     103                 :             :         {
     104                 :      962823 :             outerTupleSlot = ExecProcNode(outerPlan);
     105                 :             : 
     106                 :             :             /*
     107                 :             :              * if there are no more outer tuples, then the join is complete..
     108                 :             :              */
     109   [ +  +  +  + ]:      962819 :             if (TupIsNull(outerTupleSlot))
     110                 :       69074 :                 return NULL;
     111                 :             : 
     112                 :      893745 :             econtext->ecxt_outertuple = outerTupleSlot;
     113                 :      893745 :             node->nl_NeedNewOuter = false;
     114                 :      893745 :             node->nl_MatchedOuter = false;
     115                 :             : 
     116                 :             :             /*
     117                 :             :              * fetch the values of any outer Vars that must be passed to the
     118                 :             :              * inner scan, and store them in the appropriate PARAM_EXEC slots.
     119                 :             :              */
     120   [ +  +  +  +  :     1835678 :             foreach(lc, nl->nestParams)
                   +  + ]
     121                 :             :             {
     122                 :      941933 :                 NestLoopParam *nlp = (NestLoopParam *) lfirst(lc);
     123                 :      941933 :                 int         paramno = nlp->paramno;
     124                 :             :                 ParamExecData *prm;
     125                 :             : 
     126                 :      941933 :                 prm = &(econtext->ecxt_param_exec_vals[paramno]);
     127                 :             :                 /* Param value should be an OUTER_VAR var */
     128                 :             :                 Assert(IsA(nlp->paramval, Var));
     129                 :             :                 Assert(nlp->paramval->varno == OUTER_VAR);
     130                 :             :                 Assert(nlp->paramval->varattno > 0);
     131                 :     1883866 :                 prm->value = slot_getattr(outerTupleSlot,
     132                 :      941933 :                                           nlp->paramval->varattno,
     133                 :             :                                           &(prm->isnull));
     134                 :             :                 /* Flag parameter value as changed */
     135                 :      941933 :                 innerPlan->chgParam = bms_add_member(innerPlan->chgParam,
     136                 :             :                                                      paramno);
     137                 :             :             }
     138                 :             : 
     139                 :             :             /*
     140                 :             :              * now rescan the inner plan
     141                 :             :              */
     142                 :      893745 :             ExecReScan(innerPlan);
     143                 :             :         }
     144                 :             : 
     145                 :             :         /*
     146                 :             :          * we have an outerTuple, try to get the next inner tuple.
     147                 :             :          */
     148                 :     6939615 :         innerTupleSlot = ExecProcNode(innerPlan);
     149                 :     6939581 :         econtext->ecxt_innertuple = innerTupleSlot;
     150                 :             : 
     151   [ +  +  +  + ]:     6939581 :         if (TupIsNull(innerTupleSlot))
     152                 :             :         {
     153                 :      561734 :             node->nl_NeedNewOuter = true;
     154                 :             : 
     155         [ +  + ]:      561734 :             if (!node->nl_MatchedOuter &&
     156         [ +  + ]:      361051 :                 (node->js.jointype == JOIN_LEFT ||
     157         [ +  + ]:      346445 :                  node->js.jointype == JOIN_ANTI))
     158                 :             :             {
     159                 :             :                 /*
     160                 :             :                  * We are doing an outer join and there were no join matches
     161                 :             :                  * for this outer tuple.  Generate a fake join tuple with
     162                 :             :                  * nulls for the inner tuple, and return it if it passes the
     163                 :             :                  * non-join quals.
     164                 :             :                  */
     165                 :       57827 :                 econtext->ecxt_innertuple = node->nl_NullInnerTupleSlot;
     166                 :             : 
     167   [ +  +  +  + ]:       57827 :                 if (otherqual == NULL || ExecQual(otherqual, econtext))
     168                 :             :                 {
     169                 :             :                     /*
     170                 :             :                      * qualification was satisfied so we project and return
     171                 :             :                      * the slot containing the result tuple using
     172                 :             :                      * ExecProject().
     173                 :             :                      */
     174                 :       56821 :                     return ExecProject(node->js.ps.ps_ProjInfo);
     175                 :             :                 }
     176                 :             :                 else
     177         [ -  + ]:        1006 :                     InstrCountFiltered2(node, 1);
     178                 :             :             }
     179                 :             : 
     180                 :             :             /*
     181                 :             :              * Otherwise just return to top of loop for a new outer tuple.
     182                 :             :              */
     183                 :      504913 :             continue;
     184                 :             :         }
     185                 :             : 
     186                 :             :         /*
     187                 :             :          * at this point we have a new pair of inner and outer tuples so we
     188                 :             :          * test the inner and outer tuples to see if they satisfy the node's
     189                 :             :          * qualification.
     190                 :             :          *
     191                 :             :          * Only the joinquals determine MatchedOuter status, but all quals
     192                 :             :          * must pass to actually return the tuple.
     193                 :             :          */
     194         [ +  + ]:     6377847 :         if (ExecQual(joinqual, econtext))
     195                 :             :         {
     196                 :     2059356 :             node->nl_MatchedOuter = true;
     197                 :             : 
     198                 :             :             /* In an antijoin, we never return a matched tuple */
     199         [ +  + ]:     2059356 :             if (node->js.jointype == JOIN_ANTI)
     200                 :             :             {
     201                 :      165814 :                 node->nl_NeedNewOuter = true;
     202                 :      165814 :                 continue;       /* return to top of loop */
     203                 :             :             }
     204                 :             : 
     205                 :             :             /*
     206                 :             :              * If we only need to join to the first matching inner tuple, then
     207                 :             :              * consider returning this one, but after that continue with next
     208                 :             :              * outer tuple.
     209                 :             :              */
     210         [ +  + ]:     1893542 :             if (node->js.single_match)
     211                 :      166061 :                 node->nl_NeedNewOuter = true;
     212                 :             : 
     213   [ +  +  +  + ]:     1893542 :             if (otherqual == NULL || ExecQual(otherqual, econtext))
     214                 :             :             {
     215                 :             :                 /*
     216                 :             :                  * qualification was satisfied so we project and return the
     217                 :             :                  * slot containing the result tuple using ExecProject().
     218                 :             :                  */
     219                 :     1886176 :                 return ExecProject(node->js.ps.ps_ProjInfo);
     220                 :             :             }
     221                 :             :             else
     222         [ -  + ]:        7366 :                 InstrCountFiltered2(node, 1);
     223                 :             :         }
     224                 :             :         else
     225         [ +  + ]:     4318491 :             InstrCountFiltered1(node, 1);
     226                 :             : 
     227                 :             :         /*
     228                 :             :          * Tuple fails qual, so free per-tuple memory and try again.
     229                 :             :          */
     230                 :     4325857 :         ResetExprContext(econtext);
     231                 :             :     }
     232                 :             : }
     233                 :             : 
     234                 :             : /* ----------------------------------------------------------------
     235                 :             :  *      ExecInitNestLoop
     236                 :             :  * ----------------------------------------------------------------
     237                 :             :  */
     238                 :             : NestLoopState *
     239                 :       66664 : ExecInitNestLoop(NestLoop *node, EState *estate, int eflags)
     240                 :             : {
     241                 :             :     NestLoopState *nlstate;
     242                 :             : 
     243                 :             :     /* check for unsupported flags */
     244                 :             :     Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
     245                 :             : 
     246                 :             :     /*
     247                 :             :      * create state structure
     248                 :             :      */
     249                 :       66664 :     nlstate = makeNode(NestLoopState);
     250                 :       66664 :     nlstate->js.ps.plan = (Plan *) node;
     251                 :       66664 :     nlstate->js.ps.state = estate;
     252                 :       66664 :     nlstate->js.ps.ExecProcNode = ExecNestLoop;
     253                 :             : 
     254                 :             :     /*
     255                 :             :      * Miscellaneous initialization
     256                 :             :      *
     257                 :             :      * create expression context for node
     258                 :             :      */
     259                 :       66664 :     ExecAssignExprContext(estate, &nlstate->js.ps);
     260                 :             : 
     261                 :             :     /*
     262                 :             :      * initialize child nodes
     263                 :             :      *
     264                 :             :      * If we have no parameters to pass into the inner rel from the outer,
     265                 :             :      * tell the inner child that cheap rescans would be good.  If we do have
     266                 :             :      * such parameters, then there is no point in REWIND support at all in the
     267                 :             :      * inner child, because it will always be rescanned with fresh parameter
     268                 :             :      * values.
     269                 :             :      */
     270                 :       66664 :     outerPlanState(nlstate) = ExecInitNode(outerPlan(node), estate, eflags);
     271         [ +  + ]:       66664 :     if (node->nestParams == NIL)
     272                 :       33640 :         eflags |= EXEC_FLAG_REWIND;
     273                 :             :     else
     274                 :       33024 :         eflags &= ~EXEC_FLAG_REWIND;
     275                 :       66664 :     innerPlanState(nlstate) = ExecInitNode(innerPlan(node), estate, eflags);
     276                 :             : 
     277                 :             :     /*
     278                 :             :      * Initialize result slot, type and projection.
     279                 :             :      */
     280                 :       66664 :     ExecInitResultTupleSlotTL(&nlstate->js.ps, &TTSOpsVirtual);
     281                 :       66664 :     ExecAssignProjectionInfo(&nlstate->js.ps, NULL);
     282                 :             : 
     283                 :             :     /*
     284                 :             :      * initialize child expressions
     285                 :             :      */
     286                 :       66664 :     nlstate->js.ps.qual =
     287                 :       66664 :         ExecInitQual(node->join.plan.qual, (PlanState *) nlstate);
     288                 :       66664 :     nlstate->js.jointype = node->join.jointype;
     289                 :       66664 :     nlstate->js.joinqual =
     290                 :       66664 :         ExecInitQual(node->join.joinqual, (PlanState *) nlstate);
     291                 :             : 
     292                 :             :     /*
     293                 :             :      * detect whether we need only consider the first matching inner tuple
     294                 :             :      */
     295         [ +  + ]:       95594 :     nlstate->js.single_match = (node->join.inner_unique ||
     296         [ +  + ]:       28930 :                                 node->join.jointype == JOIN_SEMI);
     297                 :             : 
     298                 :             :     /* set up null tuples for outer joins, if needed */
     299      [ +  +  - ]:       66664 :     switch (node->join.jointype)
     300                 :             :     {
     301                 :       50114 :         case JOIN_INNER:
     302                 :             :         case JOIN_SEMI:
     303                 :       50114 :             break;
     304                 :       16550 :         case JOIN_LEFT:
     305                 :             :         case JOIN_ANTI:
     306                 :       16550 :             nlstate->nl_NullInnerTupleSlot =
     307                 :       16550 :                 ExecInitNullTupleSlot(estate,
     308                 :             :                                       ExecGetResultType(innerPlanState(nlstate)),
     309                 :             :                                       &TTSOpsVirtual);
     310                 :       16550 :             break;
     311                 :           0 :         default:
     312         [ #  # ]:           0 :             elog(ERROR, "unrecognized join type: %d",
     313                 :             :                  (int) node->join.jointype);
     314                 :             :     }
     315                 :             : 
     316                 :             :     /*
     317                 :             :      * finally, wipe the current outer tuple clean.
     318                 :             :      */
     319                 :       66664 :     nlstate->nl_NeedNewOuter = true;
     320                 :       66664 :     nlstate->nl_MatchedOuter = false;
     321                 :             : 
     322                 :       66664 :     return nlstate;
     323                 :             : }
     324                 :             : 
     325                 :             : /* ----------------------------------------------------------------
     326                 :             :  *      ExecEndNestLoop
     327                 :             :  *
     328                 :             :  *      closes down scans and frees allocated storage
     329                 :             :  * ----------------------------------------------------------------
     330                 :             :  */
     331                 :             : void
     332                 :       66516 : ExecEndNestLoop(NestLoopState *node)
     333                 :             : {
     334                 :             :     /*
     335                 :             :      * close down subplans
     336                 :             :      */
     337                 :       66516 :     ExecEndNode(outerPlanState(node));
     338                 :       66516 :     ExecEndNode(innerPlanState(node));
     339                 :       66516 : }
     340                 :             : 
     341                 :             : /* ----------------------------------------------------------------
     342                 :             :  *      ExecReScanNestLoop
     343                 :             :  * ----------------------------------------------------------------
     344                 :             :  */
     345                 :             : void
     346                 :        9466 : ExecReScanNestLoop(NestLoopState *node)
     347                 :             : {
     348                 :        9466 :     PlanState  *outerPlan = outerPlanState(node);
     349                 :             : 
     350                 :             :     /*
     351                 :             :      * If outerPlan->chgParam is not null then plan will be automatically
     352                 :             :      * re-scanned by first ExecProcNode.
     353                 :             :      */
     354         [ +  + ]:        9466 :     if (outerPlan->chgParam == NULL)
     355                 :         185 :         ExecReScan(outerPlan);
     356                 :             : 
     357                 :             :     /*
     358                 :             :      * innerPlan is re-scanned for each new outer tuple and MUST NOT be
     359                 :             :      * re-scanned from here or you'll get troubles from inner index scans when
     360                 :             :      * outer Vars are used as run-time keys...
     361                 :             :      */
     362                 :             : 
     363                 :        9466 :     node->nl_NeedNewOuter = true;
     364                 :        9466 :     node->nl_MatchedOuter = false;
     365                 :        9466 : }
        

Generated by: LCOV version 2.0-1