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