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
6239 tgl@sss.pgh.pa.us 84 :CBC 190 : build_hash_table(SetOpState *setopstate)
85 : : {
86 : 190 : SetOp *node = (SetOp *) setopstate->ps.plan;
2760 andres@anarazel.de 87 : 190 : ExprContext *econtext = setopstate->ps.ps_ExprContext;
88 : 190 : TupleDesc desc = ExecGetResultType(outerPlanState(setopstate));
89 : :
6239 tgl@sss.pgh.pa.us 90 [ - + ]: 190 : Assert(node->strategy == SETOP_HASHED);
91 [ - + ]: 190 : 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 : : */
261 98 : 190 : setopstate->hashtable = BuildTupleHashTable(&setopstate->ps,
99 : : desc,
100 : : ExecGetCommonChildSlotOps(&setopstate->ps),
101 : : node->numCols,
102 : : node->cmpColIdx,
103 : 190 : setopstate->eqfuncoids,
104 : : setopstate->hashfunctions,
105 : : node->cmpCollations,
106 : : node->numGroups,
107 : : sizeof(SetOpStatePerGroupData),
108 : 190 : setopstate->ps.state->es_query_cxt,
109 : : setopstate->tableContext,
110 : : econtext->ecxt_per_tuple_memory,
111 : : false);
6239 112 : 190 : }
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 : 221466 : set_output_count(SetOpState *setopstate, SetOpStatePerGroup pergroup)
121 : : {
122 : 221466 : SetOp *plannode = (SetOp *) setopstate->ps.plan;
123 : :
124 [ + + + + : 221466 : switch (plannode->cmd)
- ]
125 : : {
126 : 30180 : case SETOPCMD_INTERSECT:
127 [ + - + + ]: 30180 : if (pergroup->numLeft > 0 && pergroup->numRight > 0)
128 : 30114 : setopstate->numOutput = 1;
129 : : else
130 : 66 : setopstate->numOutput = 0;
131 : 30180 : break;
132 : 18 : case SETOPCMD_INTERSECT_ALL:
133 : 18 : setopstate->numOutput =
134 : 18 : (pergroup->numLeft < pergroup->numRight) ?
135 : 18 : pergroup->numLeft : pergroup->numRight;
136 : 18 : break;
137 : 185226 : case SETOPCMD_EXCEPT:
138 [ + - + + ]: 185226 : if (pergroup->numLeft > 0 && pergroup->numRight == 0)
139 : 711 : setopstate->numOutput = 1;
140 : : else
141 : 184515 : setopstate->numOutput = 0;
142 : 185226 : break;
143 : 6042 : case SETOPCMD_EXCEPT_ALL:
144 : 6042 : setopstate->numOutput =
145 : 6042 : (pergroup->numLeft < pergroup->numRight) ?
146 [ + + ]: 6042 : 0 : (pergroup->numLeft - pergroup->numRight);
147 : 6042 : break;
6239 tgl@sss.pgh.pa.us 148 :UBC 0 : default:
149 [ # # ]: 0 : elog(ERROR, "unrecognized set op: %d", (int) plannode->cmd);
150 : : break;
151 : : }
6239 tgl@sss.pgh.pa.us 152 :CBC 221466 : }
153 : :
154 : :
155 : : /* ----------------------------------------------------------------
156 : : * ExecSetOp
157 : : * ----------------------------------------------------------------
158 : : */
159 : : static TupleTableSlot * /* return: a tuple or NULL */
2973 andres@anarazel.de 160 : 31765 : ExecSetOp(PlanState *pstate)
161 : : {
162 : 31765 : SetOpState *node = castNode(SetOpState, pstate);
8311 tgl@sss.pgh.pa.us 163 : 31765 : SetOp *plannode = (SetOp *) node->ps.plan;
6239 164 : 31765 : TupleTableSlot *resultTupleSlot = node->ps.ps_ResultTupleSlot;
165 : :
2965 andres@anarazel.de 166 [ - + ]: 31765 : CHECK_FOR_INTERRUPTS();
167 : :
168 : : /*
169 : : * If the previously-returned tuple needs to be returned more than once,
170 : : * keep returning it.
171 : : */
8311 tgl@sss.pgh.pa.us 172 [ + + ]: 31765 : if (node->numOutput > 0)
173 : : {
174 : 24 : node->numOutput--;
9102 175 : 24 : return resultTupleSlot;
176 : : }
177 : :
178 : : /* Otherwise, we're done if we are out of groups */
6239 179 [ - + ]: 31741 : if (node->setop_done)
6239 tgl@sss.pgh.pa.us 180 :UBC 0 : return NULL;
181 : :
182 : : /* Fetch the next tuple group according to the correct strategy */
6239 tgl@sss.pgh.pa.us 183 [ + + ]:CBC 31741 : if (plannode->strategy == SETOP_HASHED)
184 : : {
185 [ + + ]: 15940 : if (!node->table_filled)
186 : 466 : setop_fill_hash_table(node);
187 : 15940 : return setop_retrieve_hash_table(node);
188 : : }
189 : : else
261 190 : 15801 : return setop_retrieve_sorted(node);
191 : : }
192 : :
193 : : /*
194 : : * ExecSetOp for non-hashed case
195 : : */
196 : : static TupleTableSlot *
197 : 15801 : setop_retrieve_sorted(SetOpState *setopstate)
198 : : {
199 : : PlanState *outerPlan;
200 : : PlanState *innerPlan;
201 : : TupleTableSlot *resultTupleSlot;
202 : :
203 : : /*
204 : : * get state info from node
205 : : */
6239 206 : 15801 : outerPlan = outerPlanState(setopstate);
261 207 : 15801 : innerPlan = innerPlanState(setopstate);
6239 208 : 15801 : 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 : : */
261 215 [ + + ]: 15801 : if (setopstate->need_init)
216 : : {
217 : 405 : setopstate->need_init = false;
218 : :
219 : 405 : 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 [ + - - + ]: 405 : if (TupIsNull(setopstate->leftInput.nextTupleSlot))
226 : : {
261 tgl@sss.pgh.pa.us 227 :UBC 0 : setopstate->setop_done = true;
228 : 0 : return NULL;
229 : : }
230 : :
261 tgl@sss.pgh.pa.us 231 :CBC 405 : setopstate->rightInput.nextTupleSlot = ExecProcNode(innerPlan);
232 : :
233 : : /* Set flags that we've not completed either side's group */
234 : 405 : setopstate->leftInput.needGroup = true;
235 : 405 : setopstate->rightInput.needGroup = true;
236 : : }
237 : :
238 : : /*
239 : : * We loop retrieving groups until we find one we should return
240 : : */
241 [ + - ]: 46041 : while (!setopstate->setop_done)
242 : : {
243 : : int cmpresult;
244 : : SetOpStatePerGroupData pergroup;
245 : :
246 : : /*
247 : : * Fetch the rest of the current outer group, if we didn't already.
248 : : */
249 [ + + ]: 46041 : if (setopstate->leftInput.needGroup)
250 : 45888 : 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 [ + + ]: 46041 : if (setopstate->leftInput.numTuples == 0)
257 : : {
258 : 405 : setopstate->setop_done = true;
259 : 405 : break;
260 : : }
261 : :
262 : : /*
263 : : * Fetch the rest of the current inner group, if we didn't already.
264 : : */
265 [ + + ]: 45636 : if (setopstate->rightInput.needGroup)
266 : 45597 : 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 [ + + ]: 45636 : if (setopstate->rightInput.numTuples == 0)
273 : 129 : cmpresult = -1; /* as though left input is lesser */
274 : : else
275 : 45507 : cmpresult = setop_compare_slots(setopstate->leftInput.firstTupleSlot,
276 : : setopstate->rightInput.firstTupleSlot,
277 : : setopstate);
278 : :
279 [ + + ]: 45636 : if (cmpresult < 0)
280 : : {
281 : : /* Left group is first, and has no right matches */
282 : 366 : pergroup.numLeft = setopstate->leftInput.numTuples;
283 : 366 : pergroup.numRight = 0;
284 : : /* We'll need another left group next time */
285 : 366 : setopstate->leftInput.needGroup = true;
286 : : }
287 [ + + ]: 45270 : else if (cmpresult == 0)
288 : : {
289 : : /* We have matching groups */
290 : 45117 : pergroup.numLeft = setopstate->leftInput.numTuples;
291 : 45117 : pergroup.numRight = setopstate->rightInput.numTuples;
292 : : /* We'll need to read from both sides next time */
293 : 45117 : setopstate->leftInput.needGroup = true;
294 : 45117 : setopstate->rightInput.needGroup = true;
295 : : }
296 : : else
297 : : {
298 : : /* Right group has no left matches, so we can ignore it */
299 : 153 : setopstate->rightInput.needGroup = true;
300 : 153 : continue;
301 : : }
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 : 45483 : set_output_count(setopstate, &pergroup);
310 : :
6239 311 [ + + ]: 45483 : if (setopstate->numOutput > 0)
312 : : {
313 : 15396 : setopstate->numOutput--;
314 : 15396 : return resultTupleSlot;
315 : : }
316 : : }
317 : :
318 : : /* No more groups */
319 : 405 : ExecClearTuple(resultTupleSlot);
320 : 405 : return NULL;
321 : : }
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
261 331 : 91485 : setop_load_group(SetOpStatePerInput *input, PlanState *inputPlan,
332 : : SetOpState *setopstate)
333 : : {
334 : 91485 : input->needGroup = false;
335 : :
336 : : /* If we've exhausted this child plan, report an empty group */
337 [ + + + + ]: 91485 : if (TupIsNull(input->nextTupleSlot))
338 : : {
339 : 531 : ExecClearTuple(input->firstTupleSlot);
340 : 531 : input->numTuples = 0;
341 : 531 : return;
342 : : }
343 : :
344 : : /* Make a local copy of the first tuple for comparisons */
345 : 90954 : ExecStoreMinimalTuple(ExecCopySlotMinimalTuple(input->nextTupleSlot),
346 : : input->firstTupleSlot,
347 : : true);
348 : : /* and count it */
349 : 90954 : input->numTuples = 1;
350 : :
351 : : /* Scan till we find the end-of-group */
352 : : for (;;)
353 : 15165 : {
354 : : int cmpresult;
355 : :
356 : : /* Get next input tuple, if there is one */
357 : 106119 : input->nextTupleSlot = ExecProcNode(inputPlan);
358 [ + + + + ]: 106119 : if (TupIsNull(input->nextTupleSlot))
359 : : break;
360 : :
361 : : /* There is; does it belong to same group as firstTuple? */
362 : 105312 : cmpresult = setop_compare_slots(input->firstTupleSlot,
363 : : input->nextTupleSlot,
364 : : setopstate);
365 [ - + ]: 105312 : Assert(cmpresult <= 0); /* else input is mis-sorted */
366 [ + + ]: 105312 : if (cmpresult != 0)
367 : 90147 : break;
368 : :
369 : : /* Still in same group, so count this tuple */
370 : 15165 : input->numTuples++;
371 : : }
372 : : }
373 : :
374 : : /*
375 : : * Compare the tuples in the two given slots.
376 : : */
377 : : static int
378 : 150819 : setop_compare_slots(TupleTableSlot *s1, TupleTableSlot *s2,
379 : : SetOpState *setopstate)
380 : : {
381 : : /* We'll often need to fetch all the columns, so just do it */
382 : 150819 : slot_getallattrs(s1);
383 : 150819 : slot_getallattrs(s2);
384 [ + + ]: 212031 : for (int nkey = 0; nkey < setopstate->numCols; nkey++)
385 : : {
386 : 151749 : SortSupport sortKey = setopstate->sortKeys + nkey;
387 : 151749 : AttrNumber attno = sortKey->ssup_attno;
388 : 151749 : Datum datum1 = s1->tts_values[attno - 1],
389 : 151749 : datum2 = s2->tts_values[attno - 1];
390 : 151749 : bool isNull1 = s1->tts_isnull[attno - 1],
391 : 151749 : isNull2 = s2->tts_isnull[attno - 1];
392 : : int compare;
393 : :
394 : 151749 : compare = ApplySortComparator(datum1, isNull1,
395 : : datum2, isNull2,
396 : : sortKey);
397 [ + + ]: 151749 : if (compare != 0)
398 : 90537 : return compare;
399 : : }
400 : 60282 : return 0;
401 : : }
402 : :
403 : : /*
404 : : * ExecSetOp for hashed case: phase 1, read inputs and build hash table
405 : : */
406 : : static void
6239 407 : 466 : setop_fill_hash_table(SetOpState *setopstate)
408 : : {
409 : : PlanState *outerPlan;
410 : : PlanState *innerPlan;
2760 andres@anarazel.de 411 : 466 : ExprContext *econtext = setopstate->ps.ps_ExprContext;
261 tgl@sss.pgh.pa.us 412 : 466 : bool have_tuples = false;
413 : :
414 : : /*
415 : : * get state info from node
416 : : */
6239 417 : 466 : outerPlan = outerPlanState(setopstate);
261 418 : 466 : 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 : : for (;;)
6239 425 : 191093 : {
426 : : TupleTableSlot *outerslot;
166 jdavis@postgresql.or 427 : 191559 : TupleHashTable hashtable = setopstate->hashtable;
428 : : TupleHashEntryData *entry;
429 : : SetOpStatePerGroup pergroup;
430 : : bool isnew;
431 : :
6239 tgl@sss.pgh.pa.us 432 : 191559 : outerslot = ExecProcNode(outerPlan);
433 [ + + + + ]: 191559 : if (TupIsNull(outerslot))
434 : : break;
261 435 : 191093 : have_tuples = true;
436 : :
437 : : /* Find or build hashtable entry for this tuple's group */
166 jdavis@postgresql.or 438 : 191093 : entry = LookupTupleHashEntry(hashtable,
439 : : outerslot,
440 : : &isnew, NULL);
441 : :
442 : 191093 : pergroup = TupleHashEntryGetAdditional(hashtable, entry);
443 : : /* If new tuple group, initialize counts to zero */
261 tgl@sss.pgh.pa.us 444 [ + + ]: 191093 : if (isnew)
445 : : {
166 jdavis@postgresql.or 446 : 175983 : pergroup->numLeft = 0;
447 : 175983 : pergroup->numRight = 0;
448 : : }
449 : :
450 : : /* Advance the counts */
451 : 191093 : pergroup->numLeft++;
452 : :
453 : : /* Must reset expression context after each hashtable lookup */
261 tgl@sss.pgh.pa.us 454 : 191093 : ResetExprContext(econtext);
455 : : }
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 [ + - ]: 466 : if (have_tuples)
462 : : {
463 : : /*
464 : : * Process each inner-plan tuple, and then fetch the next one, until
465 : : * we exhaust the inner plan.
466 : : */
467 : : for (;;)
6239 468 : 191057 : {
469 : : TupleTableSlot *innerslot;
166 jdavis@postgresql.or 470 : 191523 : TupleHashTable hashtable = setopstate->hashtable;
471 : : TupleHashEntryData *entry;
472 : :
261 tgl@sss.pgh.pa.us 473 : 191523 : innerslot = ExecProcNode(innerPlan);
474 [ + + + + ]: 191523 : if (TupIsNull(innerslot))
475 : : break;
476 : :
477 : : /* For tuples not seen previously, do not make hashtable entry */
166 jdavis@postgresql.or 478 : 191057 : entry = LookupTupleHashEntry(hashtable,
479 : : innerslot,
480 : : NULL, NULL);
481 : :
482 : : /* Advance the counts if entry is already present */
6239 tgl@sss.pgh.pa.us 483 [ + + ]: 191057 : if (entry)
484 : : {
166 jdavis@postgresql.or 485 : 175655 : SetOpStatePerGroup pergroup = TupleHashEntryGetAdditional(hashtable, entry);
486 : :
487 : 175655 : pergroup->numRight++;
488 : : }
489 : :
490 : : /* Must reset expression context after each hashtable lookup */
261 tgl@sss.pgh.pa.us 491 : 191057 : ResetExprContext(econtext);
492 : : }
493 : : }
494 : :
6239 495 : 466 : setopstate->table_filled = true;
496 : : /* Initialize to walk the hash table */
497 : 466 : ResetTupleHashIterator(setopstate->hashtable, &setopstate->hashiter);
498 : 466 : }
499 : :
500 : : /*
501 : : * ExecSetOp for hashed case: phase 2, retrieving groups from hash table
502 : : */
503 : : static TupleTableSlot *
504 : 15940 : setop_retrieve_hash_table(SetOpState *setopstate)
505 : : {
506 : : TupleHashEntry entry;
507 : : TupleTableSlot *resultTupleSlot;
508 : :
509 : : /*
510 : : * get state info from node
511 : : */
512 : 15940 : resultTupleSlot = setopstate->ps.ps_ResultTupleSlot;
513 : :
514 : : /*
515 : : * We loop retrieving groups until we find one we should return
516 : : */
517 [ + - ]: 176449 : while (!setopstate->setop_done)
518 : : {
166 jdavis@postgresql.or 519 : 176449 : TupleHashTable hashtable = setopstate->hashtable;
520 : : SetOpStatePerGroup pergroup;
521 : :
2965 andres@anarazel.de 522 [ - + ]: 176449 : CHECK_FOR_INTERRUPTS();
523 : :
524 : : /*
525 : : * Find the next entry in the hash table
526 : : */
166 jdavis@postgresql.or 527 : 176449 : entry = ScanTupleHashTable(hashtable, &setopstate->hashiter);
6239 tgl@sss.pgh.pa.us 528 [ + + ]: 176449 : if (entry == NULL)
529 : : {
530 : : /* No more entries in hashtable, so done */
531 : 466 : setopstate->setop_done = true;
532 : 466 : return NULL;
533 : : }
534 : :
535 : : /*
536 : : * See if we should emit any copies of this tuple, and if so return
537 : : * the first copy.
538 : : */
166 jdavis@postgresql.or 539 : 175983 : pergroup = TupleHashEntryGetAdditional(hashtable, entry);
540 : 175983 : set_output_count(setopstate, pergroup);
541 : :
6239 tgl@sss.pgh.pa.us 542 [ + + ]: 175983 : if (setopstate->numOutput > 0)
543 : : {
544 : 15474 : setopstate->numOutput--;
166 jdavis@postgresql.or 545 : 15474 : return ExecStoreMinimalTuple(TupleHashEntryGetTuple(entry),
546 : : resultTupleSlot,
547 : : false);
548 : : }
549 : : }
550 : :
551 : : /* No more groups */
6239 tgl@sss.pgh.pa.us 552 :UBC 0 : ExecClearTuple(resultTupleSlot);
553 : 0 : return NULL;
554 : : }
555 : :
556 : : /* ----------------------------------------------------------------
557 : : * ExecInitSetOp
558 : : *
559 : : * This initializes the setop node state structures and
560 : : * the node's subplan.
561 : : * ----------------------------------------------------------------
562 : : */
563 : : SetOpState *
7130 tgl@sss.pgh.pa.us 564 :CBC 331 : ExecInitSetOp(SetOp *node, EState *estate, int eflags)
565 : : {
566 : : SetOpState *setopstate;
567 : :
568 : : /* check for unsupported flags */
569 [ - + ]: 331 : Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
570 : :
571 : : /*
572 : : * create state structure
573 : : */
9102 574 : 331 : setopstate = makeNode(SetOpState);
8311 575 : 331 : setopstate->ps.plan = (Plan *) node;
576 : 331 : setopstate->ps.state = estate;
2973 andres@anarazel.de 577 : 331 : setopstate->ps.ExecProcNode = ExecSetOp;
578 : :
6239 tgl@sss.pgh.pa.us 579 : 331 : setopstate->setop_done = false;
9102 580 : 331 : setopstate->numOutput = 0;
261 581 : 331 : setopstate->numCols = node->numCols;
582 : 331 : setopstate->need_init = true;
583 : :
584 : : /*
585 : : * create expression context
586 : : */
2760 andres@anarazel.de 587 : 331 : 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 : : */
6239 tgl@sss.pgh.pa.us 594 [ + + ]: 331 : if (node->strategy == SETOP_HASHED)
595 : 190 : setopstate->tableContext =
596 : 190 : AllocSetContextCreate(CurrentMemoryContext,
597 : : "SetOp hash table",
598 : : 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 [ + + ]: 331 : if (node->strategy == SETOP_HASHED)
607 : 190 : eflags &= ~EXEC_FLAG_REWIND;
7130 608 : 331 : outerPlanState(setopstate) = ExecInitNode(outerPlan(node), estate, eflags);
261 609 : 331 : 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 : 331 : ExecInitResultTupleSlotTL(&setopstate->ps, &TTSOpsMinimalTuple);
619 [ + + ]: 331 : if (node->strategy != SETOP_HASHED)
620 : : {
621 : 141 : setopstate->leftInput.firstTupleSlot =
622 : 141 : setopstate->ps.ps_ResultTupleSlot;
623 : 141 : setopstate->rightInput.firstTupleSlot =
624 : 141 : ExecInitExtraTupleSlot(estate,
625 : : setopstate->ps.ps_ResultTupleDesc,
626 : : &TTSOpsMinimalTuple);
627 : : }
628 : :
629 : : /* Setop nodes do no projections. */
8311 630 : 331 : 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 : : */
6239 637 [ + + ]: 331 : if (node->strategy == SETOP_HASHED)
638 : 190 : execTuplesHashPrepare(node->numCols,
261 639 : 190 : node->cmpOperators,
640 : : &setopstate->eqfuncoids,
641 : : &setopstate->hashfunctions);
642 : : else
643 : : {
644 : 141 : int nkeys = node->numCols;
645 : :
646 : 141 : setopstate->sortKeys = (SortSupport)
647 : 141 : palloc0(nkeys * sizeof(SortSupportData));
648 [ + + ]: 720 : for (int i = 0; i < nkeys; i++)
649 : : {
650 : 579 : SortSupport sortKey = setopstate->sortKeys + i;
651 : :
652 : 579 : sortKey->ssup_cxt = CurrentMemoryContext;
653 : 579 : sortKey->ssup_collation = node->cmpCollations[i];
654 : 579 : sortKey->ssup_nulls_first = node->cmpNullsFirst[i];
655 : 579 : sortKey->ssup_attno = node->cmpColIdx[i];
656 : : /* abbreviated key conversion is not useful here */
657 : 579 : sortKey->abbreviate = false;
658 : :
659 : 579 : PrepareSortSupportFromOrderingOp(node->cmpOperators[i], sortKey);
660 : : }
661 : : }
662 : :
663 : : /* Create a hash table if needed */
6239 664 [ + + ]: 331 : if (node->strategy == SETOP_HASHED)
665 : : {
666 : 190 : build_hash_table(setopstate);
667 : 190 : setopstate->table_filled = false;
668 : : }
669 : :
8311 670 : 331 : return setopstate;
671 : : }
672 : :
673 : : /* ----------------------------------------------------------------
674 : : * ExecEndSetOp
675 : : *
676 : : * This shuts down the subplans and frees resources allocated
677 : : * to this node.
678 : : * ----------------------------------------------------------------
679 : : */
680 : : void
681 : 331 : ExecEndSetOp(SetOpState *node)
682 : : {
683 : : /* free subsidiary stuff including hashtable */
6239 684 [ + + ]: 331 : if (node->tableContext)
685 : 190 : MemoryContextDelete(node->tableContext);
686 : :
8301 687 : 331 : ExecEndNode(outerPlanState(node));
261 688 : 331 : ExecEndNode(innerPlanState(node));
9102 689 : 331 : }
690 : :
691 : :
692 : : void
5535 693 : 600 : ExecReScanSetOp(SetOpState *node)
694 : : {
1157 695 : 600 : PlanState *outerPlan = outerPlanState(node);
261 696 : 600 : PlanState *innerPlan = innerPlanState(node);
697 : :
8311 698 : 600 : ExecClearTuple(node->ps.ps_ResultTupleSlot);
6239 699 : 600 : node->setop_done = false;
8311 700 : 600 : node->numOutput = 0;
701 : :
6239 702 [ + + ]: 600 : if (((SetOp *) node->ps.plan)->strategy == SETOP_HASHED)
703 : : {
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 [ + + ]: 300 : if (!node->table_filled)
711 : 3 : 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 : : */
261 718 [ - + - - ]: 297 : if (outerPlan->chgParam == NULL && innerPlan->chgParam == NULL)
719 : : {
6239 tgl@sss.pgh.pa.us 720 :UBC 0 : ResetTupleHashIterator(node->hashtable, &node->hashiter);
721 : 0 : return;
722 : : }
723 : :
724 : : /* Release any hashtable storage */
261 tgl@sss.pgh.pa.us 725 [ + - ]:CBC 297 : if (node->tableContext)
726 : 297 : MemoryContextReset(node->tableContext);
727 : :
728 : : /* And rebuild an empty hashtable */
2401 andres@anarazel.de 729 : 297 : ResetTupleHashTable(node->hashtable);
6239 tgl@sss.pgh.pa.us 730 : 297 : node->table_filled = false;
731 : : }
732 : : else
733 : : {
734 : : /* Need to re-read first input from each side */
261 735 : 300 : node->need_init = true;
736 : : }
737 : :
738 : : /*
739 : : * if chgParam of subnode is not null then plan will be re-scanned by
740 : : * first ExecProcNode.
741 : : */
1157 742 [ - + ]: 597 : if (outerPlan->chgParam == NULL)
1157 tgl@sss.pgh.pa.us 743 :UBC 0 : ExecReScan(outerPlan);
261 tgl@sss.pgh.pa.us 744 [ - + ]:CBC 597 : if (innerPlan->chgParam == NULL)
261 tgl@sss.pgh.pa.us 745 :UBC 0 : ExecReScan(innerPlan);
746 : : }
|