LCOV - code coverage report
Current view: top level - src/backend/executor - nodeNestloop.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 97.8 % 92 90
Test Date: 2026-03-21 04:16:06 Functions: 100.0 % 4 4
Legend: Lines:     hit not hit

            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/execdebug.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      1947351 : ExecNestLoop(PlanState *pstate)
      62              : {
      63      1947351 :     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      1947351 :     CHECK_FOR_INTERRUPTS();
      75              : 
      76              :     /*
      77              :      * get information from the node
      78              :      */
      79              :     ENL1_printf("getting info from node");
      80              : 
      81      1947351 :     nl = (NestLoop *) node->js.ps.plan;
      82      1947351 :     joinqual = node->js.joinqual;
      83      1947351 :     otherqual = node->js.ps.qual;
      84      1947351 :     outerPlan = outerPlanState(node);
      85      1947351 :     innerPlan = innerPlanState(node);
      86      1947351 :     econtext = node->js.ps.ps_ExprContext;
      87              : 
      88              :     /*
      89              :      * Reset per-tuple memory context to free any expression evaluation
      90              :      * storage allocated in the previous tuple cycle.
      91              :      */
      92      1947351 :     ResetExprContext(econtext);
      93              : 
      94              :     /*
      95              :      * Ok, everything is setup for the join so now loop until we return a
      96              :      * qualifying join tuple.
      97              :      */
      98              :     ENL1_printf("entering main loop");
      99              : 
     100              :     for (;;)
     101              :     {
     102              :         /*
     103              :          * If we don't have an outer tuple, get the next one and reset the
     104              :          * inner scan.
     105              :          */
     106      6858216 :         if (node->nl_NeedNewOuter)
     107              :         {
     108              :             ENL1_printf("getting new outer tuple");
     109      1010611 :             outerTupleSlot = ExecProcNode(outerPlan);
     110              : 
     111              :             /*
     112              :              * if there are no more outer tuples, then the join is complete..
     113              :              */
     114      1010607 :             if (TupIsNull(outerTupleSlot))
     115              :             {
     116              :                 ENL1_printf("no outer tuple, ending join");
     117        71355 :                 return NULL;
     118              :             }
     119              : 
     120              :             ENL1_printf("saving new outer tuple information");
     121       939252 :             econtext->ecxt_outertuple = outerTupleSlot;
     122       939252 :             node->nl_NeedNewOuter = false;
     123       939252 :             node->nl_MatchedOuter = false;
     124              : 
     125              :             /*
     126              :              * fetch the values of any outer Vars that must be passed to the
     127              :              * inner scan, and store them in the appropriate PARAM_EXEC slots.
     128              :              */
     129      1899662 :             foreach(lc, nl->nestParams)
     130              :             {
     131       960410 :                 NestLoopParam *nlp = (NestLoopParam *) lfirst(lc);
     132       960410 :                 int         paramno = nlp->paramno;
     133              :                 ParamExecData *prm;
     134              : 
     135       960410 :                 prm = &(econtext->ecxt_param_exec_vals[paramno]);
     136              :                 /* Param value should be an OUTER_VAR var */
     137              :                 Assert(IsA(nlp->paramval, Var));
     138              :                 Assert(nlp->paramval->varno == OUTER_VAR);
     139              :                 Assert(nlp->paramval->varattno > 0);
     140      1920820 :                 prm->value = slot_getattr(outerTupleSlot,
     141       960410 :                                           nlp->paramval->varattno,
     142              :                                           &(prm->isnull));
     143              :                 /* Flag parameter value as changed */
     144       960410 :                 innerPlan->chgParam = bms_add_member(innerPlan->chgParam,
     145              :                                                      paramno);
     146              :             }
     147              : 
     148              :             /*
     149              :              * now rescan the inner plan
     150              :              */
     151              :             ENL1_printf("rescanning inner plan");
     152       939252 :             ExecReScan(innerPlan);
     153              :         }
     154              : 
     155              :         /*
     156              :          * we have an outerTuple, try to get the next inner tuple.
     157              :          */
     158              :         ENL1_printf("getting new inner tuple");
     159              : 
     160      6786857 :         innerTupleSlot = ExecProcNode(innerPlan);
     161      6786823 :         econtext->ecxt_innertuple = innerTupleSlot;
     162              : 
     163      6786823 :         if (TupIsNull(innerTupleSlot))
     164              :         {
     165              :             ENL1_printf("no inner tuple, need new outer tuple");
     166              : 
     167       582915 :             node->nl_NeedNewOuter = true;
     168              : 
     169       582915 :             if (!node->nl_MatchedOuter &&
     170       378284 :                 (node->js.jointype == JOIN_LEFT ||
     171       356746 :                  node->js.jointype == JOIN_ANTI))
     172              :             {
     173              :                 /*
     174              :                  * We are doing an outer join and there were no join matches
     175              :                  * for this outer tuple.  Generate a fake join tuple with
     176              :                  * nulls for the inner tuple, and return it if it passes the
     177              :                  * non-join quals.
     178              :                  */
     179        60589 :                 econtext->ecxt_innertuple = node->nl_NullInnerTupleSlot;
     180              : 
     181              :                 ENL1_printf("testing qualification for outer-join tuple");
     182              : 
     183        60589 :                 if (otherqual == NULL || ExecQual(otherqual, econtext))
     184              :                 {
     185              :                     /*
     186              :                      * qualification was satisfied so we project and return
     187              :                      * the slot containing the result tuple using
     188              :                      * ExecProject().
     189              :                      */
     190              :                     ENL1_printf("qualification succeeded, projecting tuple");
     191              : 
     192        59615 :                     return ExecProject(node->js.ps.ps_ProjInfo);
     193              :                 }
     194              :                 else
     195          974 :                     InstrCountFiltered2(node, 1);
     196              :             }
     197              : 
     198              :             /*
     199              :              * Otherwise just return to top of loop for a new outer tuple.
     200              :              */
     201       523300 :             continue;
     202              :         }
     203              : 
     204              :         /*
     205              :          * at this point we have a new pair of inner and outer tuples so we
     206              :          * test the inner and outer tuples to see if they satisfy the node's
     207              :          * qualification.
     208              :          *
     209              :          * Only the joinquals determine MatchedOuter status, but all quals
     210              :          * must pass to actually return the tuple.
     211              :          */
     212              :         ENL1_printf("testing qualification");
     213              : 
     214      6203908 :         if (ExecQual(joinqual, econtext))
     215              :         {
     216      2008090 :             node->nl_MatchedOuter = true;
     217              : 
     218              :             /* In an antijoin, we never return a matched tuple */
     219      2008090 :             if (node->js.jointype == JOIN_ANTI)
     220              :             {
     221       184381 :                 node->nl_NeedNewOuter = true;
     222       184381 :                 continue;       /* return to top of loop */
     223              :             }
     224              : 
     225              :             /*
     226              :              * If we only need to join to the first matching inner tuple, then
     227              :              * consider returning this one, but after that continue with next
     228              :              * outer tuple.
     229              :              */
     230      1823709 :             if (node->js.single_match)
     231       171825 :                 node->nl_NeedNewOuter = true;
     232              : 
     233      1823709 :             if (otherqual == NULL || ExecQual(otherqual, econtext))
     234              :             {
     235              :                 /*
     236              :                  * qualification was satisfied so we project and return the
     237              :                  * slot containing the result tuple using ExecProject().
     238              :                  */
     239              :                 ENL1_printf("qualification succeeded, projecting tuple");
     240              : 
     241      1816343 :                 return ExecProject(node->js.ps.ps_ProjInfo);
     242              :             }
     243              :             else
     244         7366 :                 InstrCountFiltered2(node, 1);
     245              :         }
     246              :         else
     247      4195818 :             InstrCountFiltered1(node, 1);
     248              : 
     249              :         /*
     250              :          * Tuple fails qual, so free per-tuple memory and try again.
     251              :          */
     252      4203184 :         ResetExprContext(econtext);
     253              : 
     254              :         ENL1_printf("qualification failed, looping");
     255              :     }
     256              : }
     257              : 
     258              : /* ----------------------------------------------------------------
     259              :  *      ExecInitNestLoop
     260              :  * ----------------------------------------------------------------
     261              :  */
     262              : NestLoopState *
     263        69031 : ExecInitNestLoop(NestLoop *node, EState *estate, int eflags)
     264              : {
     265              :     NestLoopState *nlstate;
     266              : 
     267              :     /* check for unsupported flags */
     268              :     Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
     269              : 
     270              :     NL1_printf("ExecInitNestLoop: %s\n",
     271              :                "initializing node");
     272              : 
     273              :     /*
     274              :      * create state structure
     275              :      */
     276        69031 :     nlstate = makeNode(NestLoopState);
     277        69031 :     nlstate->js.ps.plan = (Plan *) node;
     278        69031 :     nlstate->js.ps.state = estate;
     279        69031 :     nlstate->js.ps.ExecProcNode = ExecNestLoop;
     280              : 
     281              :     /*
     282              :      * Miscellaneous initialization
     283              :      *
     284              :      * create expression context for node
     285              :      */
     286        69031 :     ExecAssignExprContext(estate, &nlstate->js.ps);
     287              : 
     288              :     /*
     289              :      * initialize child nodes
     290              :      *
     291              :      * If we have no parameters to pass into the inner rel from the outer,
     292              :      * tell the inner child that cheap rescans would be good.  If we do have
     293              :      * such parameters, then there is no point in REWIND support at all in the
     294              :      * inner child, because it will always be rescanned with fresh parameter
     295              :      * values.
     296              :      */
     297        69031 :     outerPlanState(nlstate) = ExecInitNode(outerPlan(node), estate, eflags);
     298        69031 :     if (node->nestParams == NIL)
     299        36018 :         eflags |= EXEC_FLAG_REWIND;
     300              :     else
     301        33013 :         eflags &= ~EXEC_FLAG_REWIND;
     302        69031 :     innerPlanState(nlstate) = ExecInitNode(innerPlan(node), estate, eflags);
     303              : 
     304              :     /*
     305              :      * Initialize result slot, type and projection.
     306              :      */
     307        69031 :     ExecInitResultTupleSlotTL(&nlstate->js.ps, &TTSOpsVirtual);
     308        69031 :     ExecAssignProjectionInfo(&nlstate->js.ps, NULL);
     309              : 
     310              :     /*
     311              :      * initialize child expressions
     312              :      */
     313        69031 :     nlstate->js.ps.qual =
     314        69031 :         ExecInitQual(node->join.plan.qual, (PlanState *) nlstate);
     315        69031 :     nlstate->js.jointype = node->join.jointype;
     316        69031 :     nlstate->js.joinqual =
     317        69031 :         ExecInitQual(node->join.joinqual, (PlanState *) nlstate);
     318              : 
     319              :     /*
     320              :      * detect whether we need only consider the first matching inner tuple
     321              :      */
     322        99459 :     nlstate->js.single_match = (node->join.inner_unique ||
     323        30428 :                                 node->join.jointype == JOIN_SEMI);
     324              : 
     325              :     /* set up null tuples for outer joins, if needed */
     326        69031 :     switch (node->join.jointype)
     327              :     {
     328        53164 :         case JOIN_INNER:
     329              :         case JOIN_SEMI:
     330        53164 :             break;
     331        15867 :         case JOIN_LEFT:
     332              :         case JOIN_ANTI:
     333        15867 :             nlstate->nl_NullInnerTupleSlot =
     334        15867 :                 ExecInitNullTupleSlot(estate,
     335              :                                       ExecGetResultType(innerPlanState(nlstate)),
     336              :                                       &TTSOpsVirtual);
     337        15867 :             break;
     338            0 :         default:
     339            0 :             elog(ERROR, "unrecognized join type: %d",
     340              :                  (int) node->join.jointype);
     341              :     }
     342              : 
     343              :     /*
     344              :      * finally, wipe the current outer tuple clean.
     345              :      */
     346        69031 :     nlstate->nl_NeedNewOuter = true;
     347        69031 :     nlstate->nl_MatchedOuter = false;
     348              : 
     349              :     NL1_printf("ExecInitNestLoop: %s\n",
     350              :                "node initialized");
     351              : 
     352        69031 :     return nlstate;
     353              : }
     354              : 
     355              : /* ----------------------------------------------------------------
     356              :  *      ExecEndNestLoop
     357              :  *
     358              :  *      closes down scans and frees allocated storage
     359              :  * ----------------------------------------------------------------
     360              :  */
     361              : void
     362        68885 : ExecEndNestLoop(NestLoopState *node)
     363              : {
     364              :     NL1_printf("ExecEndNestLoop: %s\n",
     365              :                "ending node processing");
     366              : 
     367              :     /*
     368              :      * close down subplans
     369              :      */
     370        68885 :     ExecEndNode(outerPlanState(node));
     371        68885 :     ExecEndNode(innerPlanState(node));
     372              : 
     373              :     NL1_printf("ExecEndNestLoop: %s\n",
     374              :                "node processing ended");
     375        68885 : }
     376              : 
     377              : /* ----------------------------------------------------------------
     378              :  *      ExecReScanNestLoop
     379              :  * ----------------------------------------------------------------
     380              :  */
     381              : void
     382         9430 : ExecReScanNestLoop(NestLoopState *node)
     383              : {
     384         9430 :     PlanState  *outerPlan = outerPlanState(node);
     385              : 
     386              :     /*
     387              :      * If outerPlan->chgParam is not null then plan will be automatically
     388              :      * re-scanned by first ExecProcNode.
     389              :      */
     390         9430 :     if (outerPlan->chgParam == NULL)
     391          398 :         ExecReScan(outerPlan);
     392              : 
     393              :     /*
     394              :      * innerPlan is re-scanned for each new outer tuple and MUST NOT be
     395              :      * re-scanned from here or you'll get troubles from inner index scans when
     396              :      * outer Vars are used as run-time keys...
     397              :      */
     398              : 
     399         9430 :     node->nl_NeedNewOuter = true;
     400         9430 :     node->nl_MatchedOuter = false;
     401         9430 : }
        

Generated by: LCOV version 2.0-1