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 : }
|