/src/postgres/src/backend/executor/nodeSetOp.c
Line | Count | Source |
1 | | /*------------------------------------------------------------------------- |
2 | | * |
3 | | * nodeSetOp.c |
4 | | * Routines to handle INTERSECT and EXCEPT selection |
5 | | * |
6 | | * The input of a SetOp node consists of two relations (outer and inner) |
7 | | * with identical column sets. In EXCEPT queries the outer relation is |
8 | | * always the left side, while in INTERSECT cases the planner tries to |
9 | | * make the outer relation be the smaller of the two inputs. |
10 | | * |
11 | | * In SETOP_SORTED mode, each input has been sorted according to all the |
12 | | * grouping columns. The SetOp node essentially performs a merge join on |
13 | | * the grouping columns, except that it is only interested in counting how |
14 | | * many tuples from each input match. Then it is a simple matter to emit |
15 | | * the output demanded by the SQL spec for INTERSECT, INTERSECT ALL, EXCEPT, |
16 | | * or EXCEPT ALL. |
17 | | * |
18 | | * In SETOP_HASHED mode, the inputs are delivered in no particular order. |
19 | | * We read the outer relation and build a hash table in memory with one entry |
20 | | * for each group of identical tuples, counting the number of tuples in the |
21 | | * group. Then we read the inner relation and count the number of tuples |
22 | | * matching each outer group. (We can disregard any tuples appearing only |
23 | | * in the inner relation, since they cannot result in any output.) After |
24 | | * seeing all the input, we scan the hashtable and generate the correct |
25 | | * output using those counts. |
26 | | * |
27 | | * This node type is not used for UNION or UNION ALL, since those can be |
28 | | * implemented more cheaply (there's no need to count the number of |
29 | | * matching tuples). |
30 | | * |
31 | | * Note that SetOp does no qual checking nor projection. The delivered |
32 | | * output tuples are just copies of the first-to-arrive tuple in each |
33 | | * input group. |
34 | | * |
35 | | * |
36 | | * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group |
37 | | * Portions Copyright (c) 1994, Regents of the University of California |
38 | | * |
39 | | * |
40 | | * IDENTIFICATION |
41 | | * src/backend/executor/nodeSetOp.c |
42 | | * |
43 | | *------------------------------------------------------------------------- |
44 | | */ |
45 | | |
46 | | #include "postgres.h" |
47 | | |
48 | | #include "access/htup_details.h" |
49 | | #include "executor/executor.h" |
50 | | #include "executor/nodeSetOp.h" |
51 | | #include "miscadmin.h" |
52 | | #include "utils/memutils.h" |
53 | | |
54 | | |
55 | | /* |
56 | | * SetOpStatePerGroupData - per-group working state |
57 | | * |
58 | | * In SETOP_SORTED mode, we need only one of these structs, and it's just a |
59 | | * local in setop_retrieve_sorted. In SETOP_HASHED mode, the hash table |
60 | | * contains one of these for each tuple group. |
61 | | */ |
62 | | typedef struct SetOpStatePerGroupData |
63 | | { |
64 | | int64 numLeft; /* number of left-input dups in group */ |
65 | | int64 numRight; /* number of right-input dups in group */ |
66 | | } SetOpStatePerGroupData; |
67 | | |
68 | | typedef SetOpStatePerGroupData *SetOpStatePerGroup; |
69 | | |
70 | | |
71 | | static TupleTableSlot *setop_retrieve_sorted(SetOpState *setopstate); |
72 | | static void setop_load_group(SetOpStatePerInput *input, PlanState *inputPlan, |
73 | | SetOpState *setopstate); |
74 | | static int setop_compare_slots(TupleTableSlot *s1, TupleTableSlot *s2, |
75 | | SetOpState *setopstate); |
76 | | static void setop_fill_hash_table(SetOpState *setopstate); |
77 | | static TupleTableSlot *setop_retrieve_hash_table(SetOpState *setopstate); |
78 | | |
79 | | |
80 | | /* |
81 | | * Initialize the hash table to empty. |
82 | | */ |
83 | | static void |
84 | | build_hash_table(SetOpState *setopstate) |
85 | 0 | { |
86 | 0 | SetOp *node = (SetOp *) setopstate->ps.plan; |
87 | 0 | ExprContext *econtext = setopstate->ps.ps_ExprContext; |
88 | 0 | TupleDesc desc = ExecGetResultType(outerPlanState(setopstate)); |
89 | |
|
90 | 0 | Assert(node->strategy == SETOP_HASHED); |
91 | 0 | Assert(node->numGroups > 0); |
92 | | |
93 | | /* |
94 | | * If both child plans deliver the same fixed tuple slot type, we can tell |
95 | | * BuildTupleHashTable to expect that slot type as input. Otherwise, |
96 | | * we'll pass NULL denoting that any slot type is possible. |
97 | | */ |
98 | 0 | setopstate->hashtable = BuildTupleHashTable(&setopstate->ps, |
99 | 0 | desc, |
100 | 0 | ExecGetCommonChildSlotOps(&setopstate->ps), |
101 | 0 | node->numCols, |
102 | 0 | node->cmpColIdx, |
103 | 0 | setopstate->eqfuncoids, |
104 | 0 | setopstate->hashfunctions, |
105 | 0 | node->cmpCollations, |
106 | 0 | node->numGroups, |
107 | 0 | sizeof(SetOpStatePerGroupData), |
108 | 0 | setopstate->ps.state->es_query_cxt, |
109 | 0 | setopstate->tableContext, |
110 | 0 | econtext->ecxt_per_tuple_memory, |
111 | 0 | false); |
112 | 0 | } |
113 | | |
114 | | /* |
115 | | * We've completed processing a tuple group. Decide how many copies (if any) |
116 | | * of its representative row to emit, and store the count into numOutput. |
117 | | * This logic is straight from the SQL92 specification. |
118 | | */ |
119 | | static void |
120 | | set_output_count(SetOpState *setopstate, SetOpStatePerGroup pergroup) |
121 | 0 | { |
122 | 0 | SetOp *plannode = (SetOp *) setopstate->ps.plan; |
123 | |
|
124 | 0 | switch (plannode->cmd) |
125 | 0 | { |
126 | 0 | case SETOPCMD_INTERSECT: |
127 | 0 | if (pergroup->numLeft > 0 && pergroup->numRight > 0) |
128 | 0 | setopstate->numOutput = 1; |
129 | 0 | else |
130 | 0 | setopstate->numOutput = 0; |
131 | 0 | break; |
132 | 0 | case SETOPCMD_INTERSECT_ALL: |
133 | 0 | setopstate->numOutput = |
134 | 0 | (pergroup->numLeft < pergroup->numRight) ? |
135 | 0 | pergroup->numLeft : pergroup->numRight; |
136 | 0 | break; |
137 | 0 | case SETOPCMD_EXCEPT: |
138 | 0 | if (pergroup->numLeft > 0 && pergroup->numRight == 0) |
139 | 0 | setopstate->numOutput = 1; |
140 | 0 | else |
141 | 0 | setopstate->numOutput = 0; |
142 | 0 | break; |
143 | 0 | case SETOPCMD_EXCEPT_ALL: |
144 | 0 | setopstate->numOutput = |
145 | 0 | (pergroup->numLeft < pergroup->numRight) ? |
146 | 0 | 0 : (pergroup->numLeft - pergroup->numRight); |
147 | 0 | break; |
148 | 0 | default: |
149 | 0 | elog(ERROR, "unrecognized set op: %d", (int) plannode->cmd); |
150 | 0 | break; |
151 | 0 | } |
152 | 0 | } |
153 | | |
154 | | |
155 | | /* ---------------------------------------------------------------- |
156 | | * ExecSetOp |
157 | | * ---------------------------------------------------------------- |
158 | | */ |
159 | | static TupleTableSlot * /* return: a tuple or NULL */ |
160 | | ExecSetOp(PlanState *pstate) |
161 | 0 | { |
162 | 0 | SetOpState *node = castNode(SetOpState, pstate); |
163 | 0 | SetOp *plannode = (SetOp *) node->ps.plan; |
164 | 0 | TupleTableSlot *resultTupleSlot = node->ps.ps_ResultTupleSlot; |
165 | |
|
166 | 0 | CHECK_FOR_INTERRUPTS(); |
167 | | |
168 | | /* |
169 | | * If the previously-returned tuple needs to be returned more than once, |
170 | | * keep returning it. |
171 | | */ |
172 | 0 | if (node->numOutput > 0) |
173 | 0 | { |
174 | 0 | node->numOutput--; |
175 | 0 | return resultTupleSlot; |
176 | 0 | } |
177 | | |
178 | | /* Otherwise, we're done if we are out of groups */ |
179 | 0 | if (node->setop_done) |
180 | 0 | return NULL; |
181 | | |
182 | | /* Fetch the next tuple group according to the correct strategy */ |
183 | 0 | if (plannode->strategy == SETOP_HASHED) |
184 | 0 | { |
185 | 0 | if (!node->table_filled) |
186 | 0 | setop_fill_hash_table(node); |
187 | 0 | return setop_retrieve_hash_table(node); |
188 | 0 | } |
189 | 0 | else |
190 | 0 | return setop_retrieve_sorted(node); |
191 | 0 | } |
192 | | |
193 | | /* |
194 | | * ExecSetOp for non-hashed case |
195 | | */ |
196 | | static TupleTableSlot * |
197 | | setop_retrieve_sorted(SetOpState *setopstate) |
198 | 0 | { |
199 | 0 | PlanState *outerPlan; |
200 | 0 | PlanState *innerPlan; |
201 | 0 | TupleTableSlot *resultTupleSlot; |
202 | | |
203 | | /* |
204 | | * get state info from node |
205 | | */ |
206 | 0 | outerPlan = outerPlanState(setopstate); |
207 | 0 | innerPlan = innerPlanState(setopstate); |
208 | 0 | resultTupleSlot = setopstate->ps.ps_ResultTupleSlot; |
209 | | |
210 | | /* |
211 | | * If first time through, establish the invariant that setop_load_group |
212 | | * expects: each side's nextTupleSlot is the next output from the child |
213 | | * plan, or empty if there is no more output from it. |
214 | | */ |
215 | 0 | if (setopstate->need_init) |
216 | 0 | { |
217 | 0 | setopstate->need_init = false; |
218 | |
|
219 | 0 | setopstate->leftInput.nextTupleSlot = ExecProcNode(outerPlan); |
220 | | |
221 | | /* |
222 | | * If the outer relation is empty, then we will emit nothing, and we |
223 | | * don't need to read the inner relation at all. |
224 | | */ |
225 | 0 | if (TupIsNull(setopstate->leftInput.nextTupleSlot)) |
226 | 0 | { |
227 | 0 | setopstate->setop_done = true; |
228 | 0 | return NULL; |
229 | 0 | } |
230 | | |
231 | 0 | setopstate->rightInput.nextTupleSlot = ExecProcNode(innerPlan); |
232 | | |
233 | | /* Set flags that we've not completed either side's group */ |
234 | 0 | setopstate->leftInput.needGroup = true; |
235 | 0 | setopstate->rightInput.needGroup = true; |
236 | 0 | } |
237 | | |
238 | | /* |
239 | | * We loop retrieving groups until we find one we should return |
240 | | */ |
241 | 0 | while (!setopstate->setop_done) |
242 | 0 | { |
243 | 0 | int cmpresult; |
244 | 0 | SetOpStatePerGroupData pergroup; |
245 | | |
246 | | /* |
247 | | * Fetch the rest of the current outer group, if we didn't already. |
248 | | */ |
249 | 0 | if (setopstate->leftInput.needGroup) |
250 | 0 | setop_load_group(&setopstate->leftInput, outerPlan, setopstate); |
251 | | |
252 | | /* |
253 | | * If no more outer groups, we're done, and don't need to look at any |
254 | | * more of the inner relation. |
255 | | */ |
256 | 0 | if (setopstate->leftInput.numTuples == 0) |
257 | 0 | { |
258 | 0 | setopstate->setop_done = true; |
259 | 0 | break; |
260 | 0 | } |
261 | | |
262 | | /* |
263 | | * Fetch the rest of the current inner group, if we didn't already. |
264 | | */ |
265 | 0 | if (setopstate->rightInput.needGroup) |
266 | 0 | setop_load_group(&setopstate->rightInput, innerPlan, setopstate); |
267 | | |
268 | | /* |
269 | | * Determine whether we have matching groups on both sides (this is |
270 | | * basically like the core logic of a merge join). |
271 | | */ |
272 | 0 | if (setopstate->rightInput.numTuples == 0) |
273 | 0 | cmpresult = -1; /* as though left input is lesser */ |
274 | 0 | else |
275 | 0 | cmpresult = setop_compare_slots(setopstate->leftInput.firstTupleSlot, |
276 | 0 | setopstate->rightInput.firstTupleSlot, |
277 | 0 | setopstate); |
278 | |
|
279 | 0 | if (cmpresult < 0) |
280 | 0 | { |
281 | | /* Left group is first, and has no right matches */ |
282 | 0 | pergroup.numLeft = setopstate->leftInput.numTuples; |
283 | 0 | pergroup.numRight = 0; |
284 | | /* We'll need another left group next time */ |
285 | 0 | setopstate->leftInput.needGroup = true; |
286 | 0 | } |
287 | 0 | else if (cmpresult == 0) |
288 | 0 | { |
289 | | /* We have matching groups */ |
290 | 0 | pergroup.numLeft = setopstate->leftInput.numTuples; |
291 | 0 | pergroup.numRight = setopstate->rightInput.numTuples; |
292 | | /* We'll need to read from both sides next time */ |
293 | 0 | setopstate->leftInput.needGroup = true; |
294 | 0 | setopstate->rightInput.needGroup = true; |
295 | 0 | } |
296 | 0 | else |
297 | 0 | { |
298 | | /* Right group has no left matches, so we can ignore it */ |
299 | 0 | setopstate->rightInput.needGroup = true; |
300 | 0 | continue; |
301 | 0 | } |
302 | | |
303 | | /* |
304 | | * Done scanning these input tuple groups. See if we should emit any |
305 | | * copies of result tuple, and if so return the first copy. (Note |
306 | | * that the result tuple is the same as the left input's firstTuple |
307 | | * slot.) |
308 | | */ |
309 | 0 | set_output_count(setopstate, &pergroup); |
310 | |
|
311 | 0 | if (setopstate->numOutput > 0) |
312 | 0 | { |
313 | 0 | setopstate->numOutput--; |
314 | 0 | return resultTupleSlot; |
315 | 0 | } |
316 | 0 | } |
317 | | |
318 | | /* No more groups */ |
319 | 0 | ExecClearTuple(resultTupleSlot); |
320 | 0 | return NULL; |
321 | 0 | } |
322 | | |
323 | | /* |
324 | | * Load next group of tuples from one child plan or the other. |
325 | | * |
326 | | * On entry, we've already read the first tuple of the next group |
327 | | * (if there is one) into input->nextTupleSlot. This invariant |
328 | | * is maintained on exit. |
329 | | */ |
330 | | static void |
331 | | setop_load_group(SetOpStatePerInput *input, PlanState *inputPlan, |
332 | | SetOpState *setopstate) |
333 | 0 | { |
334 | 0 | input->needGroup = false; |
335 | | |
336 | | /* If we've exhausted this child plan, report an empty group */ |
337 | 0 | if (TupIsNull(input->nextTupleSlot)) |
338 | 0 | { |
339 | 0 | ExecClearTuple(input->firstTupleSlot); |
340 | 0 | input->numTuples = 0; |
341 | 0 | return; |
342 | 0 | } |
343 | | |
344 | | /* Make a local copy of the first tuple for comparisons */ |
345 | 0 | ExecStoreMinimalTuple(ExecCopySlotMinimalTuple(input->nextTupleSlot), |
346 | 0 | input->firstTupleSlot, |
347 | 0 | true); |
348 | | /* and count it */ |
349 | 0 | input->numTuples = 1; |
350 | | |
351 | | /* Scan till we find the end-of-group */ |
352 | 0 | for (;;) |
353 | 0 | { |
354 | 0 | int cmpresult; |
355 | | |
356 | | /* Get next input tuple, if there is one */ |
357 | 0 | input->nextTupleSlot = ExecProcNode(inputPlan); |
358 | 0 | if (TupIsNull(input->nextTupleSlot)) |
359 | 0 | break; |
360 | | |
361 | | /* There is; does it belong to same group as firstTuple? */ |
362 | 0 | cmpresult = setop_compare_slots(input->firstTupleSlot, |
363 | 0 | input->nextTupleSlot, |
364 | 0 | setopstate); |
365 | 0 | Assert(cmpresult <= 0); /* else input is mis-sorted */ |
366 | 0 | if (cmpresult != 0) |
367 | 0 | break; |
368 | | |
369 | | /* Still in same group, so count this tuple */ |
370 | 0 | input->numTuples++; |
371 | 0 | } |
372 | 0 | } |
373 | | |
374 | | /* |
375 | | * Compare the tuples in the two given slots. |
376 | | */ |
377 | | static int |
378 | | setop_compare_slots(TupleTableSlot *s1, TupleTableSlot *s2, |
379 | | SetOpState *setopstate) |
380 | 0 | { |
381 | | /* We'll often need to fetch all the columns, so just do it */ |
382 | 0 | slot_getallattrs(s1); |
383 | 0 | slot_getallattrs(s2); |
384 | 0 | for (int nkey = 0; nkey < setopstate->numCols; nkey++) |
385 | 0 | { |
386 | 0 | SortSupport sortKey = setopstate->sortKeys + nkey; |
387 | 0 | AttrNumber attno = sortKey->ssup_attno; |
388 | 0 | Datum datum1 = s1->tts_values[attno - 1], |
389 | 0 | datum2 = s2->tts_values[attno - 1]; |
390 | 0 | bool isNull1 = s1->tts_isnull[attno - 1], |
391 | 0 | isNull2 = s2->tts_isnull[attno - 1]; |
392 | 0 | int compare; |
393 | |
|
394 | 0 | compare = ApplySortComparator(datum1, isNull1, |
395 | 0 | datum2, isNull2, |
396 | 0 | sortKey); |
397 | 0 | if (compare != 0) |
398 | 0 | return compare; |
399 | 0 | } |
400 | 0 | return 0; |
401 | 0 | } |
402 | | |
403 | | /* |
404 | | * ExecSetOp for hashed case: phase 1, read inputs and build hash table |
405 | | */ |
406 | | static void |
407 | | setop_fill_hash_table(SetOpState *setopstate) |
408 | 0 | { |
409 | 0 | PlanState *outerPlan; |
410 | 0 | PlanState *innerPlan; |
411 | 0 | ExprContext *econtext = setopstate->ps.ps_ExprContext; |
412 | 0 | bool have_tuples = false; |
413 | | |
414 | | /* |
415 | | * get state info from node |
416 | | */ |
417 | 0 | outerPlan = outerPlanState(setopstate); |
418 | 0 | innerPlan = innerPlanState(setopstate); |
419 | | |
420 | | /* |
421 | | * Process each outer-plan tuple, and then fetch the next one, until we |
422 | | * exhaust the outer plan. |
423 | | */ |
424 | 0 | for (;;) |
425 | 0 | { |
426 | 0 | TupleTableSlot *outerslot; |
427 | 0 | TupleHashTable hashtable = setopstate->hashtable; |
428 | 0 | TupleHashEntryData *entry; |
429 | 0 | SetOpStatePerGroup pergroup; |
430 | 0 | bool isnew; |
431 | |
|
432 | 0 | outerslot = ExecProcNode(outerPlan); |
433 | 0 | if (TupIsNull(outerslot)) |
434 | 0 | break; |
435 | 0 | have_tuples = true; |
436 | | |
437 | | /* Find or build hashtable entry for this tuple's group */ |
438 | 0 | entry = LookupTupleHashEntry(hashtable, |
439 | 0 | outerslot, |
440 | 0 | &isnew, NULL); |
441 | |
|
442 | 0 | pergroup = TupleHashEntryGetAdditional(hashtable, entry); |
443 | | /* If new tuple group, initialize counts to zero */ |
444 | 0 | if (isnew) |
445 | 0 | { |
446 | 0 | pergroup->numLeft = 0; |
447 | 0 | pergroup->numRight = 0; |
448 | 0 | } |
449 | | |
450 | | /* Advance the counts */ |
451 | 0 | pergroup->numLeft++; |
452 | | |
453 | | /* Must reset expression context after each hashtable lookup */ |
454 | 0 | ResetExprContext(econtext); |
455 | 0 | } |
456 | | |
457 | | /* |
458 | | * If the outer relation is empty, then we will emit nothing, and we don't |
459 | | * need to read the inner relation at all. |
460 | | */ |
461 | 0 | if (have_tuples) |
462 | 0 | { |
463 | | /* |
464 | | * Process each inner-plan tuple, and then fetch the next one, until |
465 | | * we exhaust the inner plan. |
466 | | */ |
467 | 0 | for (;;) |
468 | 0 | { |
469 | 0 | TupleTableSlot *innerslot; |
470 | 0 | TupleHashTable hashtable = setopstate->hashtable; |
471 | 0 | TupleHashEntryData *entry; |
472 | |
|
473 | 0 | innerslot = ExecProcNode(innerPlan); |
474 | 0 | if (TupIsNull(innerslot)) |
475 | 0 | break; |
476 | | |
477 | | /* For tuples not seen previously, do not make hashtable entry */ |
478 | 0 | entry = LookupTupleHashEntry(hashtable, |
479 | 0 | innerslot, |
480 | 0 | NULL, NULL); |
481 | | |
482 | | /* Advance the counts if entry is already present */ |
483 | 0 | if (entry) |
484 | 0 | { |
485 | 0 | SetOpStatePerGroup pergroup = TupleHashEntryGetAdditional(hashtable, entry); |
486 | |
|
487 | 0 | pergroup->numRight++; |
488 | 0 | } |
489 | | |
490 | | /* Must reset expression context after each hashtable lookup */ |
491 | 0 | ResetExprContext(econtext); |
492 | 0 | } |
493 | 0 | } |
494 | |
|
495 | 0 | setopstate->table_filled = true; |
496 | | /* Initialize to walk the hash table */ |
497 | 0 | ResetTupleHashIterator(setopstate->hashtable, &setopstate->hashiter); |
498 | 0 | } |
499 | | |
500 | | /* |
501 | | * ExecSetOp for hashed case: phase 2, retrieving groups from hash table |
502 | | */ |
503 | | static TupleTableSlot * |
504 | | setop_retrieve_hash_table(SetOpState *setopstate) |
505 | 0 | { |
506 | 0 | TupleHashEntry entry; |
507 | 0 | TupleTableSlot *resultTupleSlot; |
508 | | |
509 | | /* |
510 | | * get state info from node |
511 | | */ |
512 | 0 | resultTupleSlot = setopstate->ps.ps_ResultTupleSlot; |
513 | | |
514 | | /* |
515 | | * We loop retrieving groups until we find one we should return |
516 | | */ |
517 | 0 | while (!setopstate->setop_done) |
518 | 0 | { |
519 | 0 | TupleHashTable hashtable = setopstate->hashtable; |
520 | 0 | SetOpStatePerGroup pergroup; |
521 | |
|
522 | 0 | CHECK_FOR_INTERRUPTS(); |
523 | | |
524 | | /* |
525 | | * Find the next entry in the hash table |
526 | | */ |
527 | 0 | entry = ScanTupleHashTable(hashtable, &setopstate->hashiter); |
528 | 0 | if (entry == NULL) |
529 | 0 | { |
530 | | /* No more entries in hashtable, so done */ |
531 | 0 | setopstate->setop_done = true; |
532 | 0 | return NULL; |
533 | 0 | } |
534 | | |
535 | | /* |
536 | | * See if we should emit any copies of this tuple, and if so return |
537 | | * the first copy. |
538 | | */ |
539 | 0 | pergroup = TupleHashEntryGetAdditional(hashtable, entry); |
540 | 0 | set_output_count(setopstate, pergroup); |
541 | |
|
542 | 0 | if (setopstate->numOutput > 0) |
543 | 0 | { |
544 | 0 | setopstate->numOutput--; |
545 | 0 | return ExecStoreMinimalTuple(TupleHashEntryGetTuple(entry), |
546 | 0 | resultTupleSlot, |
547 | 0 | false); |
548 | 0 | } |
549 | 0 | } |
550 | | |
551 | | /* No more groups */ |
552 | 0 | ExecClearTuple(resultTupleSlot); |
553 | 0 | return NULL; |
554 | 0 | } |
555 | | |
556 | | /* ---------------------------------------------------------------- |
557 | | * ExecInitSetOp |
558 | | * |
559 | | * This initializes the setop node state structures and |
560 | | * the node's subplan. |
561 | | * ---------------------------------------------------------------- |
562 | | */ |
563 | | SetOpState * |
564 | | ExecInitSetOp(SetOp *node, EState *estate, int eflags) |
565 | 0 | { |
566 | 0 | SetOpState *setopstate; |
567 | | |
568 | | /* check for unsupported flags */ |
569 | 0 | Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK))); |
570 | | |
571 | | /* |
572 | | * create state structure |
573 | | */ |
574 | 0 | setopstate = makeNode(SetOpState); |
575 | 0 | setopstate->ps.plan = (Plan *) node; |
576 | 0 | setopstate->ps.state = estate; |
577 | 0 | setopstate->ps.ExecProcNode = ExecSetOp; |
578 | |
|
579 | 0 | setopstate->setop_done = false; |
580 | 0 | setopstate->numOutput = 0; |
581 | 0 | setopstate->numCols = node->numCols; |
582 | 0 | setopstate->need_init = true; |
583 | | |
584 | | /* |
585 | | * create expression context |
586 | | */ |
587 | 0 | ExecAssignExprContext(estate, &setopstate->ps); |
588 | | |
589 | | /* |
590 | | * If hashing, we also need a longer-lived context to store the hash |
591 | | * table. The table can't just be kept in the per-query context because |
592 | | * we want to be able to throw it away in ExecReScanSetOp. |
593 | | */ |
594 | 0 | if (node->strategy == SETOP_HASHED) |
595 | 0 | setopstate->tableContext = |
596 | 0 | AllocSetContextCreate(CurrentMemoryContext, |
597 | 0 | "SetOp hash table", |
598 | 0 | ALLOCSET_DEFAULT_SIZES); |
599 | | |
600 | | /* |
601 | | * initialize child nodes |
602 | | * |
603 | | * If we are hashing then the child plans do not need to handle REWIND |
604 | | * efficiently; see ExecReScanSetOp. |
605 | | */ |
606 | 0 | if (node->strategy == SETOP_HASHED) |
607 | 0 | eflags &= ~EXEC_FLAG_REWIND; |
608 | 0 | outerPlanState(setopstate) = ExecInitNode(outerPlan(node), estate, eflags); |
609 | 0 | innerPlanState(setopstate) = ExecInitNode(innerPlan(node), estate, eflags); |
610 | | |
611 | | /* |
612 | | * Initialize locally-allocated slots. In hashed mode, we just need a |
613 | | * result slot. In sorted mode, we need one first-tuple-of-group slot for |
614 | | * each input; we use the result slot for the left input's slot and create |
615 | | * another for the right input. (Note: the nextTupleSlot slots are not |
616 | | * ours, but just point to the last slot returned by the input plan node.) |
617 | | */ |
618 | 0 | ExecInitResultTupleSlotTL(&setopstate->ps, &TTSOpsMinimalTuple); |
619 | 0 | if (node->strategy != SETOP_HASHED) |
620 | 0 | { |
621 | 0 | setopstate->leftInput.firstTupleSlot = |
622 | 0 | setopstate->ps.ps_ResultTupleSlot; |
623 | 0 | setopstate->rightInput.firstTupleSlot = |
624 | 0 | ExecInitExtraTupleSlot(estate, |
625 | 0 | setopstate->ps.ps_ResultTupleDesc, |
626 | 0 | &TTSOpsMinimalTuple); |
627 | 0 | } |
628 | | |
629 | | /* Setop nodes do no projections. */ |
630 | 0 | setopstate->ps.ps_ProjInfo = NULL; |
631 | | |
632 | | /* |
633 | | * Precompute fmgr lookup data for inner loop. We need equality and |
634 | | * hashing functions to do it by hashing, while for sorting we need |
635 | | * SortSupport data. |
636 | | */ |
637 | 0 | if (node->strategy == SETOP_HASHED) |
638 | 0 | execTuplesHashPrepare(node->numCols, |
639 | 0 | node->cmpOperators, |
640 | 0 | &setopstate->eqfuncoids, |
641 | 0 | &setopstate->hashfunctions); |
642 | 0 | else |
643 | 0 | { |
644 | 0 | int nkeys = node->numCols; |
645 | |
|
646 | 0 | setopstate->sortKeys = (SortSupport) |
647 | 0 | palloc0(nkeys * sizeof(SortSupportData)); |
648 | 0 | for (int i = 0; i < nkeys; i++) |
649 | 0 | { |
650 | 0 | SortSupport sortKey = setopstate->sortKeys + i; |
651 | |
|
652 | 0 | sortKey->ssup_cxt = CurrentMemoryContext; |
653 | 0 | sortKey->ssup_collation = node->cmpCollations[i]; |
654 | 0 | sortKey->ssup_nulls_first = node->cmpNullsFirst[i]; |
655 | 0 | sortKey->ssup_attno = node->cmpColIdx[i]; |
656 | | /* abbreviated key conversion is not useful here */ |
657 | 0 | sortKey->abbreviate = false; |
658 | |
|
659 | 0 | PrepareSortSupportFromOrderingOp(node->cmpOperators[i], sortKey); |
660 | 0 | } |
661 | 0 | } |
662 | | |
663 | | /* Create a hash table if needed */ |
664 | 0 | if (node->strategy == SETOP_HASHED) |
665 | 0 | { |
666 | 0 | build_hash_table(setopstate); |
667 | 0 | setopstate->table_filled = false; |
668 | 0 | } |
669 | |
|
670 | 0 | return setopstate; |
671 | 0 | } |
672 | | |
673 | | /* ---------------------------------------------------------------- |
674 | | * ExecEndSetOp |
675 | | * |
676 | | * This shuts down the subplans and frees resources allocated |
677 | | * to this node. |
678 | | * ---------------------------------------------------------------- |
679 | | */ |
680 | | void |
681 | | ExecEndSetOp(SetOpState *node) |
682 | 0 | { |
683 | | /* free subsidiary stuff including hashtable */ |
684 | 0 | if (node->tableContext) |
685 | 0 | MemoryContextDelete(node->tableContext); |
686 | |
|
687 | 0 | ExecEndNode(outerPlanState(node)); |
688 | 0 | ExecEndNode(innerPlanState(node)); |
689 | 0 | } |
690 | | |
691 | | |
692 | | void |
693 | | ExecReScanSetOp(SetOpState *node) |
694 | 0 | { |
695 | 0 | PlanState *outerPlan = outerPlanState(node); |
696 | 0 | PlanState *innerPlan = innerPlanState(node); |
697 | |
|
698 | 0 | ExecClearTuple(node->ps.ps_ResultTupleSlot); |
699 | 0 | node->setop_done = false; |
700 | 0 | node->numOutput = 0; |
701 | |
|
702 | 0 | if (((SetOp *) node->ps.plan)->strategy == SETOP_HASHED) |
703 | 0 | { |
704 | | /* |
705 | | * In the hashed case, if we haven't yet built the hash table then we |
706 | | * can just return; nothing done yet, so nothing to undo. If subnode's |
707 | | * chgParam is not NULL then it will be re-scanned by ExecProcNode, |
708 | | * else no reason to re-scan it at all. |
709 | | */ |
710 | 0 | if (!node->table_filled) |
711 | 0 | return; |
712 | | |
713 | | /* |
714 | | * If we do have the hash table and the subplans do not have any |
715 | | * parameter changes, then we can just rescan the existing hash table; |
716 | | * no need to build it again. |
717 | | */ |
718 | 0 | if (outerPlan->chgParam == NULL && innerPlan->chgParam == NULL) |
719 | 0 | { |
720 | 0 | ResetTupleHashIterator(node->hashtable, &node->hashiter); |
721 | 0 | return; |
722 | 0 | } |
723 | | |
724 | | /* Release any hashtable storage */ |
725 | 0 | if (node->tableContext) |
726 | 0 | MemoryContextReset(node->tableContext); |
727 | | |
728 | | /* And rebuild an empty hashtable */ |
729 | 0 | ResetTupleHashTable(node->hashtable); |
730 | 0 | node->table_filled = false; |
731 | 0 | } |
732 | 0 | else |
733 | 0 | { |
734 | | /* Need to re-read first input from each side */ |
735 | 0 | node->need_init = true; |
736 | 0 | } |
737 | | |
738 | | /* |
739 | | * if chgParam of subnode is not null then plan will be re-scanned by |
740 | | * first ExecProcNode. |
741 | | */ |
742 | 0 | if (outerPlan->chgParam == NULL) |
743 | 0 | ExecReScan(outerPlan); |
744 | 0 | if (innerPlan->chgParam == NULL) |
745 | 0 | ExecReScan(innerPlan); |
746 | 0 | } |