Age Owner Branch data TLA 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-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
6340 tgl@sss.pgh.pa.us 84 :CBC 193 : build_hash_table(SetOpState *setopstate)
85 : : {
86 : 193 : SetOp *node = (SetOp *) setopstate->ps.plan;
2861 andres@anarazel.de 87 : 193 : ExprContext *econtext = setopstate->ps.ps_ExprContext;
88 : 193 : TupleDesc desc = ExecGetResultType(outerPlanState(setopstate));
89 : :
6340 tgl@sss.pgh.pa.us 90 [ - + ]: 193 : 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 : : */
362 97 : 193 : setopstate->hashtable = BuildTupleHashTable(&setopstate->ps,
98 : : desc,
99 : : ExecGetCommonChildSlotOps(&setopstate->ps),
100 : : node->numCols,
101 : : node->cmpColIdx,
102 : 193 : setopstate->eqfuncoids,
103 : : setopstate->hashfunctions,
104 : : node->cmpCollations,
105 : : node->numGroups,
106 : : sizeof(SetOpStatePerGroupData),
107 : 193 : setopstate->ps.state->es_query_cxt,
108 : : setopstate->tuplesContext,
109 : : econtext->ecxt_per_tuple_memory,
110 : : false);
6340 111 : 193 : }
112 : :
113 : : /* Planner support routine to estimate space needed for hash table */
114 : : Size
44 tgl@sss.pgh.pa.us 115 :GNC 304 : EstimateSetOpHashTableSpace(double nentries, Size tupleWidth)
116 : : {
117 : 304 : return EstimateTupleHashTableSpace(nentries,
118 : : 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
6340 tgl@sss.pgh.pa.us 128 :CBC 221514 : set_output_count(SetOpState *setopstate, SetOpStatePerGroup pergroup)
129 : : {
130 : 221514 : SetOp *plannode = (SetOp *) setopstate->ps.plan;
131 : :
132 [ + + + + : 221514 : switch (plannode->cmd)
- ]
133 : : {
134 : 30180 : case SETOPCMD_INTERSECT:
135 [ + - + + ]: 30180 : if (pergroup->numLeft > 0 && pergroup->numRight > 0)
136 : 30114 : setopstate->numOutput = 1;
137 : : else
138 : 66 : setopstate->numOutput = 0;
139 : 30180 : break;
140 : 18 : case SETOPCMD_INTERSECT_ALL:
141 : 18 : setopstate->numOutput =
142 : 18 : (pergroup->numLeft < pergroup->numRight) ?
143 : 18 : pergroup->numLeft : pergroup->numRight;
144 : 18 : break;
145 : 185274 : case SETOPCMD_EXCEPT:
146 [ + - + + ]: 185274 : if (pergroup->numLeft > 0 && pergroup->numRight == 0)
147 : 711 : setopstate->numOutput = 1;
148 : : else
149 : 184563 : setopstate->numOutput = 0;
150 : 185274 : break;
151 : 6042 : case SETOPCMD_EXCEPT_ALL:
152 : 6042 : setopstate->numOutput =
153 : 6042 : (pergroup->numLeft < pergroup->numRight) ?
154 [ + + ]: 6042 : 0 : (pergroup->numLeft - pergroup->numRight);
155 : 6042 : break;
6340 tgl@sss.pgh.pa.us 156 :UBC 0 : default:
157 [ # # ]: 0 : elog(ERROR, "unrecognized set op: %d", (int) plannode->cmd);
158 : : break;
159 : : }
6340 tgl@sss.pgh.pa.us 160 :CBC 221514 : }
161 : :
162 : :
163 : : /* ----------------------------------------------------------------
164 : : * ExecSetOp
165 : : * ----------------------------------------------------------------
166 : : */
167 : : static TupleTableSlot * /* return: a tuple or NULL */
3074 andres@anarazel.de 168 : 31765 : ExecSetOp(PlanState *pstate)
169 : : {
170 : 31765 : SetOpState *node = castNode(SetOpState, pstate);
8412 tgl@sss.pgh.pa.us 171 : 31765 : SetOp *plannode = (SetOp *) node->ps.plan;
6340 172 : 31765 : TupleTableSlot *resultTupleSlot = node->ps.ps_ResultTupleSlot;
173 : :
3066 andres@anarazel.de 174 [ - + ]: 31765 : CHECK_FOR_INTERRUPTS();
175 : :
176 : : /*
177 : : * If the previously-returned tuple needs to be returned more than once,
178 : : * keep returning it.
179 : : */
8412 tgl@sss.pgh.pa.us 180 [ + + ]: 31765 : if (node->numOutput > 0)
181 : : {
182 : 24 : node->numOutput--;
9203 183 : 24 : return resultTupleSlot;
184 : : }
185 : :
186 : : /* Otherwise, we're done if we are out of groups */
6340 187 [ - + ]: 31741 : if (node->setop_done)
6340 tgl@sss.pgh.pa.us 188 :UBC 0 : return NULL;
189 : :
190 : : /* Fetch the next tuple group according to the correct strategy */
6340 tgl@sss.pgh.pa.us 191 [ + + ]:CBC 31741 : if (plannode->strategy == SETOP_HASHED)
192 : : {
193 [ + + ]: 15940 : if (!node->table_filled)
194 : 466 : setop_fill_hash_table(node);
195 : 15940 : return setop_retrieve_hash_table(node);
196 : : }
197 : : else
362 198 : 15801 : return setop_retrieve_sorted(node);
199 : : }
200 : :
201 : : /*
202 : : * ExecSetOp for non-hashed case
203 : : */
204 : : static TupleTableSlot *
205 : 15801 : setop_retrieve_sorted(SetOpState *setopstate)
206 : : {
207 : : PlanState *outerPlan;
208 : : PlanState *innerPlan;
209 : : TupleTableSlot *resultTupleSlot;
210 : :
211 : : /*
212 : : * get state info from node
213 : : */
6340 214 : 15801 : outerPlan = outerPlanState(setopstate);
362 215 : 15801 : innerPlan = innerPlanState(setopstate);
6340 216 : 15801 : 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 : : */
362 223 [ + + ]: 15801 : if (setopstate->need_init)
224 : : {
225 : 405 : setopstate->need_init = false;
226 : :
227 : 405 : 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 [ + - - + ]: 405 : if (TupIsNull(setopstate->leftInput.nextTupleSlot))
234 : : {
362 tgl@sss.pgh.pa.us 235 :UBC 0 : setopstate->setop_done = true;
236 : 0 : return NULL;
237 : : }
238 : :
362 tgl@sss.pgh.pa.us 239 :CBC 405 : setopstate->rightInput.nextTupleSlot = ExecProcNode(innerPlan);
240 : :
241 : : /* Set flags that we've not completed either side's group */
242 : 405 : setopstate->leftInput.needGroup = true;
243 : 405 : setopstate->rightInput.needGroup = true;
244 : : }
245 : :
246 : : /*
247 : : * We loop retrieving groups until we find one we should return
248 : : */
249 [ + - ]: 46041 : while (!setopstate->setop_done)
250 : : {
251 : : int cmpresult;
252 : : SetOpStatePerGroupData pergroup;
253 : :
254 : : /*
255 : : * Fetch the rest of the current outer group, if we didn't already.
256 : : */
257 [ + + ]: 46041 : if (setopstate->leftInput.needGroup)
258 : 45888 : 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 [ + + ]: 46041 : if (setopstate->leftInput.numTuples == 0)
265 : : {
266 : 405 : setopstate->setop_done = true;
267 : 405 : break;
268 : : }
269 : :
270 : : /*
271 : : * Fetch the rest of the current inner group, if we didn't already.
272 : : */
273 [ + + ]: 45636 : if (setopstate->rightInput.needGroup)
274 : 45597 : 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 [ + + ]: 45636 : if (setopstate->rightInput.numTuples == 0)
281 : 129 : cmpresult = -1; /* as though left input is lesser */
282 : : else
283 : 45507 : cmpresult = setop_compare_slots(setopstate->leftInput.firstTupleSlot,
284 : : setopstate->rightInput.firstTupleSlot,
285 : : setopstate);
286 : :
287 [ + + ]: 45636 : if (cmpresult < 0)
288 : : {
289 : : /* Left group is first, and has no right matches */
290 : 366 : pergroup.numLeft = setopstate->leftInput.numTuples;
291 : 366 : pergroup.numRight = 0;
292 : : /* We'll need another left group next time */
293 : 366 : setopstate->leftInput.needGroup = true;
294 : : }
295 [ + + ]: 45270 : else if (cmpresult == 0)
296 : : {
297 : : /* We have matching groups */
298 : 45117 : pergroup.numLeft = setopstate->leftInput.numTuples;
299 : 45117 : pergroup.numRight = setopstate->rightInput.numTuples;
300 : : /* We'll need to read from both sides next time */
301 : 45117 : setopstate->leftInput.needGroup = true;
302 : 45117 : setopstate->rightInput.needGroup = true;
303 : : }
304 : : else
305 : : {
306 : : /* Right group has no left matches, so we can ignore it */
307 : 153 : setopstate->rightInput.needGroup = true;
308 : 153 : 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 : 45483 : set_output_count(setopstate, &pergroup);
318 : :
6340 319 [ + + ]: 45483 : if (setopstate->numOutput > 0)
320 : : {
321 : 15396 : setopstate->numOutput--;
322 : 15396 : return resultTupleSlot;
323 : : }
324 : : }
325 : :
326 : : /* No more groups */
327 : 405 : ExecClearTuple(resultTupleSlot);
328 : 405 : return NULL;
329 : : }
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
362 339 : 91485 : setop_load_group(SetOpStatePerInput *input, PlanState *inputPlan,
340 : : SetOpState *setopstate)
341 : : {
342 : 91485 : input->needGroup = false;
343 : :
344 : : /* If we've exhausted this child plan, report an empty group */
345 [ + + + + ]: 91485 : if (TupIsNull(input->nextTupleSlot))
346 : : {
347 : 531 : ExecClearTuple(input->firstTupleSlot);
348 : 531 : input->numTuples = 0;
349 : 531 : return;
350 : : }
351 : :
352 : : /* Make a local copy of the first tuple for comparisons */
353 : 90954 : ExecStoreMinimalTuple(ExecCopySlotMinimalTuple(input->nextTupleSlot),
354 : : input->firstTupleSlot,
355 : : true);
356 : : /* and count it */
357 : 90954 : input->numTuples = 1;
358 : :
359 : : /* Scan till we find the end-of-group */
360 : : for (;;)
361 : 15165 : {
362 : : int cmpresult;
363 : :
364 : : /* Get next input tuple, if there is one */
365 : 106119 : input->nextTupleSlot = ExecProcNode(inputPlan);
366 [ + + + + ]: 106119 : if (TupIsNull(input->nextTupleSlot))
367 : : break;
368 : :
369 : : /* There is; does it belong to same group as firstTuple? */
370 : 105312 : cmpresult = setop_compare_slots(input->firstTupleSlot,
371 : : input->nextTupleSlot,
372 : : setopstate);
373 [ - + ]: 105312 : Assert(cmpresult <= 0); /* else input is mis-sorted */
374 [ + + ]: 105312 : if (cmpresult != 0)
375 : 90147 : break;
376 : :
377 : : /* Still in same group, so count this tuple */
378 : 15165 : input->numTuples++;
379 : : }
380 : : }
381 : :
382 : : /*
383 : : * Compare the tuples in the two given slots.
384 : : */
385 : : static int
386 : 150819 : 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 : 150819 : slot_getallattrs(s1);
391 : 150819 : slot_getallattrs(s2);
392 [ + + ]: 212031 : for (int nkey = 0; nkey < setopstate->numCols; nkey++)
393 : : {
394 : 151749 : SortSupport sortKey = setopstate->sortKeys + nkey;
395 : 151749 : AttrNumber attno = sortKey->ssup_attno;
396 : 151749 : Datum datum1 = s1->tts_values[attno - 1],
397 : 151749 : datum2 = s2->tts_values[attno - 1];
398 : 151749 : bool isNull1 = s1->tts_isnull[attno - 1],
399 : 151749 : isNull2 = s2->tts_isnull[attno - 1];
400 : : int compare;
401 : :
402 : 151749 : compare = ApplySortComparator(datum1, isNull1,
403 : : datum2, isNull2,
404 : : sortKey);
405 [ + + ]: 151749 : if (compare != 0)
406 : 90537 : return compare;
407 : : }
408 : 60282 : return 0;
409 : : }
410 : :
411 : : /*
412 : : * ExecSetOp for hashed case: phase 1, read inputs and build hash table
413 : : */
414 : : static void
6340 415 : 466 : setop_fill_hash_table(SetOpState *setopstate)
416 : : {
417 : : PlanState *outerPlan;
418 : : PlanState *innerPlan;
2861 andres@anarazel.de 419 : 466 : ExprContext *econtext = setopstate->ps.ps_ExprContext;
362 tgl@sss.pgh.pa.us 420 : 466 : bool have_tuples = false;
421 : :
422 : : /*
423 : : * get state info from node
424 : : */
6340 425 : 466 : outerPlan = outerPlanState(setopstate);
362 426 : 466 : 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 : : for (;;)
6340 433 : 191141 : {
434 : : TupleTableSlot *outerslot;
267 jdavis@postgresql.or 435 : 191607 : TupleHashTable hashtable = setopstate->hashtable;
436 : : TupleHashEntryData *entry;
437 : : SetOpStatePerGroup pergroup;
438 : : bool isnew;
439 : :
6340 tgl@sss.pgh.pa.us 440 : 191607 : outerslot = ExecProcNode(outerPlan);
441 [ + + + + ]: 191607 : if (TupIsNull(outerslot))
442 : : break;
362 443 : 191141 : have_tuples = true;
444 : :
445 : : /* Find or build hashtable entry for this tuple's group */
267 jdavis@postgresql.or 446 : 191141 : entry = LookupTupleHashEntry(hashtable,
447 : : outerslot,
448 : : &isnew, NULL);
449 : :
450 : 191141 : pergroup = TupleHashEntryGetAdditional(hashtable, entry);
451 : : /* If new tuple group, initialize counts to zero */
362 tgl@sss.pgh.pa.us 452 [ + + ]: 191141 : if (isnew)
453 : : {
267 jdavis@postgresql.or 454 : 176031 : pergroup->numLeft = 0;
455 : 176031 : pergroup->numRight = 0;
456 : : }
457 : :
458 : : /* Advance the counts */
459 : 191141 : pergroup->numLeft++;
460 : :
461 : : /* Must reset expression context after each hashtable lookup */
362 tgl@sss.pgh.pa.us 462 : 191141 : ResetExprContext(econtext);
463 : : }
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 [ + - ]: 466 : 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 : : for (;;)
6340 476 : 191105 : {
477 : : TupleTableSlot *innerslot;
267 jdavis@postgresql.or 478 : 191571 : TupleHashTable hashtable = setopstate->hashtable;
479 : : TupleHashEntryData *entry;
480 : :
362 tgl@sss.pgh.pa.us 481 : 191571 : innerslot = ExecProcNode(innerPlan);
482 [ + + + + ]: 191571 : if (TupIsNull(innerslot))
483 : : break;
484 : :
485 : : /* For tuples not seen previously, do not make hashtable entry */
267 jdavis@postgresql.or 486 : 191105 : entry = LookupTupleHashEntry(hashtable,
487 : : innerslot,
488 : : NULL, NULL);
489 : :
490 : : /* Advance the counts if entry is already present */
6340 tgl@sss.pgh.pa.us 491 [ + + ]: 191105 : if (entry)
492 : : {
267 jdavis@postgresql.or 493 : 175703 : SetOpStatePerGroup pergroup = TupleHashEntryGetAdditional(hashtable, entry);
494 : :
495 : 175703 : pergroup->numRight++;
496 : : }
497 : :
498 : : /* Must reset expression context after each hashtable lookup */
362 tgl@sss.pgh.pa.us 499 : 191105 : ResetExprContext(econtext);
500 : : }
501 : : }
502 : :
6340 503 : 466 : setopstate->table_filled = true;
504 : : /* Initialize to walk the hash table */
505 : 466 : ResetTupleHashIterator(setopstate->hashtable, &setopstate->hashiter);
506 : 466 : }
507 : :
508 : : /*
509 : : * ExecSetOp for hashed case: phase 2, retrieving groups from hash table
510 : : */
511 : : static TupleTableSlot *
512 : 15940 : setop_retrieve_hash_table(SetOpState *setopstate)
513 : : {
514 : : TupleHashEntry entry;
515 : : TupleTableSlot *resultTupleSlot;
516 : :
517 : : /*
518 : : * get state info from node
519 : : */
520 : 15940 : resultTupleSlot = setopstate->ps.ps_ResultTupleSlot;
521 : :
522 : : /*
523 : : * We loop retrieving groups until we find one we should return
524 : : */
525 [ + - ]: 176497 : while (!setopstate->setop_done)
526 : : {
267 jdavis@postgresql.or 527 : 176497 : TupleHashTable hashtable = setopstate->hashtable;
528 : : SetOpStatePerGroup pergroup;
529 : :
3066 andres@anarazel.de 530 [ - + ]: 176497 : CHECK_FOR_INTERRUPTS();
531 : :
532 : : /*
533 : : * Find the next entry in the hash table
534 : : */
267 jdavis@postgresql.or 535 : 176497 : entry = ScanTupleHashTable(hashtable, &setopstate->hashiter);
6340 tgl@sss.pgh.pa.us 536 [ + + ]: 176497 : if (entry == NULL)
537 : : {
538 : : /* No more entries in hashtable, so done */
539 : 466 : setopstate->setop_done = true;
540 : 466 : 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 : : */
267 jdavis@postgresql.or 547 : 176031 : pergroup = TupleHashEntryGetAdditional(hashtable, entry);
548 : 176031 : set_output_count(setopstate, pergroup);
549 : :
6340 tgl@sss.pgh.pa.us 550 [ + + ]: 176031 : if (setopstate->numOutput > 0)
551 : : {
552 : 15474 : setopstate->numOutput--;
267 jdavis@postgresql.or 553 : 15474 : return ExecStoreMinimalTuple(TupleHashEntryGetTuple(entry),
554 : : resultTupleSlot,
555 : : false);
556 : : }
557 : : }
558 : :
559 : : /* No more groups */
6340 tgl@sss.pgh.pa.us 560 :UBC 0 : ExecClearTuple(resultTupleSlot);
561 : 0 : return NULL;
562 : : }
563 : :
564 : : /* ----------------------------------------------------------------
565 : : * ExecInitSetOp
566 : : *
567 : : * This initializes the setop node state structures and
568 : : * the node's subplan.
569 : : * ----------------------------------------------------------------
570 : : */
571 : : SetOpState *
7231 tgl@sss.pgh.pa.us 572 :CBC 334 : ExecInitSetOp(SetOp *node, EState *estate, int eflags)
573 : : {
574 : : SetOpState *setopstate;
575 : :
576 : : /* check for unsupported flags */
577 [ - + ]: 334 : Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
578 : :
579 : : /*
580 : : * create state structure
581 : : */
9203 582 : 334 : setopstate = makeNode(SetOpState);
8412 583 : 334 : setopstate->ps.plan = (Plan *) node;
584 : 334 : setopstate->ps.state = estate;
3074 andres@anarazel.de 585 : 334 : setopstate->ps.ExecProcNode = ExecSetOp;
586 : :
6340 tgl@sss.pgh.pa.us 587 : 334 : setopstate->setop_done = false;
9203 588 : 334 : setopstate->numOutput = 0;
362 589 : 334 : setopstate->numCols = node->numCols;
590 : 334 : setopstate->need_init = true;
591 : :
592 : : /*
593 : : * create expression context
594 : : */
2861 andres@anarazel.de 595 : 334 : 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 : : */
6340 tgl@sss.pgh.pa.us 604 [ + + ]: 334 : if (node->strategy == SETOP_HASHED)
47 tgl@sss.pgh.pa.us 605 :GNC 193 : setopstate->tuplesContext =
606 : 193 : 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 : : */
6340 tgl@sss.pgh.pa.us 616 [ + + ]:CBC 334 : if (node->strategy == SETOP_HASHED)
617 : 193 : eflags &= ~EXEC_FLAG_REWIND;
7231 618 : 334 : outerPlanState(setopstate) = ExecInitNode(outerPlan(node), estate, eflags);
362 619 : 334 : 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 : 334 : ExecInitResultTupleSlotTL(&setopstate->ps, &TTSOpsMinimalTuple);
629 [ + + ]: 334 : if (node->strategy != SETOP_HASHED)
630 : : {
631 : 141 : setopstate->leftInput.firstTupleSlot =
632 : 141 : setopstate->ps.ps_ResultTupleSlot;
633 : 141 : setopstate->rightInput.firstTupleSlot =
634 : 141 : ExecInitExtraTupleSlot(estate,
635 : : setopstate->ps.ps_ResultTupleDesc,
636 : : &TTSOpsMinimalTuple);
637 : : }
638 : :
639 : : /* Setop nodes do no projections. */
8412 640 : 334 : 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 : : */
6340 647 [ + + ]: 334 : if (node->strategy == SETOP_HASHED)
648 : 193 : execTuplesHashPrepare(node->numCols,
362 649 : 193 : node->cmpOperators,
650 : : &setopstate->eqfuncoids,
651 : : &setopstate->hashfunctions);
652 : : else
653 : : {
654 : 141 : int nkeys = node->numCols;
655 : :
656 : 141 : setopstate->sortKeys = (SortSupport)
657 : 141 : palloc0(nkeys * sizeof(SortSupportData));
658 [ + + ]: 720 : for (int i = 0; i < nkeys; i++)
659 : : {
660 : 579 : SortSupport sortKey = setopstate->sortKeys + i;
661 : :
662 : 579 : sortKey->ssup_cxt = CurrentMemoryContext;
663 : 579 : sortKey->ssup_collation = node->cmpCollations[i];
664 : 579 : sortKey->ssup_nulls_first = node->cmpNullsFirst[i];
665 : 579 : sortKey->ssup_attno = node->cmpColIdx[i];
666 : : /* abbreviated key conversion is not useful here */
667 : 579 : sortKey->abbreviate = false;
668 : :
669 : 579 : PrepareSortSupportFromOrderingOp(node->cmpOperators[i], sortKey);
670 : : }
671 : : }
672 : :
673 : : /* Create a hash table if needed */
6340 674 [ + + ]: 334 : if (node->strategy == SETOP_HASHED)
675 : : {
676 : 193 : build_hash_table(setopstate);
677 : 193 : setopstate->table_filled = false;
678 : : }
679 : :
8412 680 : 334 : return setopstate;
681 : : }
682 : :
683 : : /* ----------------------------------------------------------------
684 : : * ExecEndSetOp
685 : : *
686 : : * This shuts down the subplans and frees resources allocated
687 : : * to this node.
688 : : * ----------------------------------------------------------------
689 : : */
690 : : void
691 : 334 : ExecEndSetOp(SetOpState *node)
692 : : {
693 : : /* free subsidiary stuff including hashtable data */
47 tgl@sss.pgh.pa.us 694 [ + + ]:GNC 334 : if (node->tuplesContext)
695 : 193 : MemoryContextDelete(node->tuplesContext);
696 : :
8402 tgl@sss.pgh.pa.us 697 :CBC 334 : ExecEndNode(outerPlanState(node));
362 698 : 334 : ExecEndNode(innerPlanState(node));
9203 699 : 334 : }
700 : :
701 : :
702 : : void
5636 703 : 600 : ExecReScanSetOp(SetOpState *node)
704 : : {
1258 705 : 600 : PlanState *outerPlan = outerPlanState(node);
362 706 : 600 : PlanState *innerPlan = innerPlanState(node);
707 : :
8412 708 : 600 : ExecClearTuple(node->ps.ps_ResultTupleSlot);
6340 709 : 600 : node->setop_done = false;
8412 710 : 600 : node->numOutput = 0;
711 : :
6340 712 [ + + ]: 600 : 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 [ + + ]: 300 : if (!node->table_filled)
721 : 3 : 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 : : */
362 728 [ - + - - ]: 297 : if (outerPlan->chgParam == NULL && innerPlan->chgParam == NULL)
729 : : {
6340 tgl@sss.pgh.pa.us 730 :UBC 0 : ResetTupleHashIterator(node->hashtable, &node->hashiter);
731 : 0 : return;
732 : : }
733 : :
734 : : /* Else, we must rebuild the hashtable */
2502 andres@anarazel.de 735 :CBC 297 : ResetTupleHashTable(node->hashtable);
6340 tgl@sss.pgh.pa.us 736 : 297 : node->table_filled = false;
737 : : }
738 : : else
739 : : {
740 : : /* Need to re-read first input from each side */
362 741 : 300 : 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 : : */
1258 748 [ - + ]: 597 : if (outerPlan->chgParam == NULL)
1258 tgl@sss.pgh.pa.us 749 :UBC 0 : ExecReScan(outerPlan);
362 tgl@sss.pgh.pa.us 750 [ - + ]:CBC 597 : if (innerPlan->chgParam == NULL)
362 tgl@sss.pgh.pa.us 751 :UBC 0 : ExecReScan(innerPlan);
752 : : }
|