Coverage Report

Created: 2025-10-09 06:07

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}