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