Coverage Report

Created: 2025-07-03 06:49

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