YugabyteDB (2.13.1.0-b60, 21121d69985fbf76aa6958d8f04a9bfa936293b5)

Coverage Report

Created: 2022-03-22 16:43

/Users/deen/code/yugabyte-db/src/postgres/src/backend/executor/nodeSetOp.c
Line
Count
Source (jump to first uncovered line)
1
/*-------------------------------------------------------------------------
2
 *
3
 * nodeSetOp.c
4
 *    Routines to handle INTERSECT and EXCEPT selection
5
 *
6
 * The input of a SetOp node consists of tuples from two relations,
7
 * which have been combined into one dataset, with a junk attribute added
8
 * that shows which relation each tuple came from.  In SETOP_SORTED mode,
9
 * the input has furthermore been sorted according to all the grouping
10
 * columns (ie, all the non-junk attributes).  The SetOp node scans each
11
 * group of identical tuples to determine how many came from each input
12
 * relation.  Then it is a simple matter to emit the output demanded by the
13
 * SQL spec for INTERSECT, INTERSECT ALL, EXCEPT, or EXCEPT ALL.
14
 *
15
 * In SETOP_HASHED mode, the input is delivered in no particular order,
16
 * except that we know all the tuples from one input relation will come before
17
 * all the tuples of the other.  The planner guarantees that the first input
18
 * relation is the left-hand one for EXCEPT, and tries to make the smaller
19
 * input relation come first for INTERSECT.  We build a hash table in memory
20
 * with one entry for each group of identical tuples, and count the number of
21
 * tuples in the group from each relation.  After seeing all the input, we
22
 * scan the hashtable and generate the correct output using those counts.
23
 * We can avoid making hashtable entries for any tuples appearing only in the
24
 * second input relation, since they cannot result in any output.
25
 *
26
 * This node type is not used for UNION or UNION ALL, since those can be
27
 * implemented more cheaply (there's no need for the junk attribute to
28
 * identify the source relation).
29
 *
30
 * Note that SetOp does no qual checking nor projection.  The delivered
31
 * output tuples are just copies of the first-to-arrive tuple in each
32
 * input group.
33
 *
34
 *
35
 * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
36
 * Portions Copyright (c) 1994, Regents of the University of California
37
 *
38
 *
39
 * IDENTIFICATION
40
 *    src/backend/executor/nodeSetOp.c
41
 *
42
 *-------------------------------------------------------------------------
43
 */
44
45
#include "postgres.h"
46
47
#include "access/htup_details.h"
48
#include "executor/executor.h"
49
#include "executor/nodeSetOp.h"
50
#include "miscadmin.h"
51
#include "utils/memutils.h"
52
53
54
/*
55
 * SetOpStatePerGroupData - per-group working state
56
 *
57
 * These values are working state that is initialized at the start of
58
 * an input tuple group and updated for each input tuple.
59
 *
60
 * In SETOP_SORTED mode, we need only one of these structs, and it's kept in
61
 * the plan state node.  In SETOP_HASHED mode, the hash table contains one
62
 * of these for each tuple group.
63
 */
64
typedef struct SetOpStatePerGroupData
65
{
66
  long    numLeft;    /* number of left-input dups in group */
67
  long    numRight;   /* number of right-input dups in group */
68
}     SetOpStatePerGroupData;
69
70
71
static TupleTableSlot *setop_retrieve_direct(SetOpState *setopstate);
72
static void setop_fill_hash_table(SetOpState *setopstate);
73
static TupleTableSlot *setop_retrieve_hash_table(SetOpState *setopstate);
74
75
76
/*
77
 * Initialize state for a new group of input values.
78
 */
79
static inline void
80
initialize_counts(SetOpStatePerGroup pergroup)
81
71
{
82
71
  pergroup->numLeft = pergroup->numRight = 0;
83
71
}
84
85
/*
86
 * Advance the appropriate counter for one input tuple.
87
 */
88
static inline void
89
advance_counts(SetOpStatePerGroup pergroup, int flag)
90
111
{
91
111
  if (flag)
92
41
    pergroup->numRight++;
93
70
  else
94
70
    pergroup->numLeft++;
95
111
}
96
97
/*
98
 * Fetch the "flag" column from an input tuple.
99
 * This is an integer column with value 0 for left side, 1 for right side.
100
 */
101
static int
102
fetch_tuple_flag(SetOpState *setopstate, TupleTableSlot *inputslot)
103
137
{
104
137
  SetOp    *node = (SetOp *) setopstate->ps.plan;
105
137
  int     flag;
106
137
  bool    isNull;
107
108
137
  flag = DatumGetInt32(slot_getattr(inputslot,
109
137
                    node->flagColIdx,
110
137
                    &isNull));
111
137
  Assert(!isNull);
112
137
  Assert(flag == 0 || flag == 1);
113
137
  return flag;
114
137
}
115
116
/*
117
 * Initialize the hash table to empty.
118
 */
119
static void
120
build_hash_table(SetOpState *setopstate)
121
18
{
122
18
  SetOp    *node = (SetOp *) setopstate->ps.plan;
123
18
  ExprContext *econtext = setopstate->ps.ps_ExprContext;
124
18
  TupleDesc desc = ExecGetResultType(outerPlanState(setopstate));
125
126
18
  Assert(node->strategy == SETOP_HASHED);
127
18
  Assert(node->numGroups > 0);
128
129
18
  setopstate->hashtable = BuildTupleHashTableExt(&setopstate->ps,
130
18
                           desc,
131
18
                           node->numCols,
132
18
                           node->dupColIdx,
133
18
                           setopstate->eqfuncoids,
134
18
                           setopstate->hashfunctions,
135
18
                           node->numGroups,
136
18
                           0,
137
18
                           setopstate->ps.state->es_query_cxt,
138
18
                           setopstate->tableContext,
139
18
                           econtext->ecxt_per_tuple_memory,
140
18
                           false);
141
18
}
142
143
/*
144
 * We've completed processing a tuple group.  Decide how many copies (if any)
145
 * of its representative row to emit, and store the count into numOutput.
146
 * This logic is straight from the SQL92 specification.
147
 */
148
static void
149
set_output_count(SetOpState *setopstate, SetOpStatePerGroup pergroup)
150
71
{
151
71
  SetOp    *plannode = (SetOp *) setopstate->ps.plan;
152
153
71
  switch (plannode->cmd)
154
71
  {
155
23
    case SETOPCMD_INTERSECT:
156
23
      if (pergroup->numLeft > 0 && pergroup->numRight > 0)
157
11
        setopstate->numOutput = 1;
158
12
      else
159
12
        setopstate->numOutput = 0;
160
23
      break;
161
0
    case SETOPCMD_INTERSECT_ALL:
162
0
      setopstate->numOutput =
163
0
        (pergroup->numLeft < pergroup->numRight) ?
164
0
        pergroup->numLeft : pergroup->numRight;
165
0
      break;
166
48
    case SETOPCMD_EXCEPT:
167
48
      if (pergroup->numLeft > 0 && 
pergroup->numRight == 047
)
168
18
        setopstate->numOutput = 1;
169
30
      else
170
30
        setopstate->numOutput = 0;
171
48
      break;
172
0
    case SETOPCMD_EXCEPT_ALL:
173
0
      setopstate->numOutput =
174
0
        (pergroup->numLeft < pergroup->numRight) ?
175
0
        0 : (pergroup->numLeft - pergroup->numRight);
176
0
      break;
177
0
    default:
178
0
      elog(ERROR, "unrecognized set op: %d", (int) plannode->cmd);
179
0
      break;
180
71
  }
181
71
}
182
183
184
/* ----------------------------------------------------------------
185
 *    ExecSetOp
186
 * ----------------------------------------------------------------
187
 */
188
static TupleTableSlot *     /* return: a tuple or NULL */
189
ExecSetOp(PlanState *pstate)
190
51
{
191
51
  SetOpState *node = castNode(SetOpState, pstate);
192
51
  SetOp    *plannode = (SetOp *) node->ps.plan;
193
51
  TupleTableSlot *resultTupleSlot = node->ps.ps_ResultTupleSlot;
194
195
51
  CHECK_FOR_INTERRUPTS();
196
197
  /*
198
   * If the previously-returned tuple needs to be returned more than once,
199
   * keep returning it.
200
   */
201
51
  if (node->numOutput > 0)
202
0
  {
203
0
    node->numOutput--;
204
0
    return resultTupleSlot;
205
0
  }
206
207
  /* Otherwise, we're done if we are out of groups */
208
51
  if (node->setop_done)
209
0
    return NULL;
210
211
  /* Fetch the next tuple group according to the correct strategy */
212
51
  if (plannode->strategy == SETOP_HASHED)
213
46
  {
214
46
    if (!node->table_filled)
215
20
      setop_fill_hash_table(node);
216
46
    return setop_retrieve_hash_table(node);
217
46
  }
218
5
  else
219
5
    return setop_retrieve_direct(node);
220
51
}
221
222
/*
223
 * ExecSetOp for non-hashed case
224
 */
225
static TupleTableSlot *
226
setop_retrieve_direct(SetOpState *setopstate)
227
5
{
228
5
  PlanState  *outerPlan;
229
5
  SetOpStatePerGroup pergroup;
230
5
  TupleTableSlot *outerslot;
231
5
  TupleTableSlot *resultTupleSlot;
232
5
  ExprContext *econtext = setopstate->ps.ps_ExprContext;
233
234
  /*
235
   * get state info from node
236
   */
237
5
  outerPlan = outerPlanState(setopstate);
238
5
  pergroup = (SetOpStatePerGroup) setopstate->pergroup;
239
5
  resultTupleSlot = setopstate->ps.ps_ResultTupleSlot;
240
241
  /*
242
   * We loop retrieving groups until we find one we should return
243
   */
244
7
  while (!setopstate->setop_done)
245
5
  {
246
    /*
247
     * If we don't already have the first tuple of the new group, fetch it
248
     * from the outer plan.
249
     */
250
5
    if (setopstate->grp_firstTuple == NULL)
251
2
    {
252
2
      outerslot = ExecProcNode(outerPlan);
253
2
      if (!TupIsNull(outerslot))
254
2
      {
255
        /* Make a copy of the first input tuple */
256
2
        setopstate->grp_firstTuple = ExecCopySlotTuple(outerslot);
257
2
      }
258
0
      else
259
0
      {
260
        /* outer plan produced no tuples at all */
261
0
        setopstate->setop_done = true;
262
0
        return NULL;
263
0
      }
264
2
    }
265
266
    /*
267
     * Store the copied first input tuple in the tuple table slot reserved
268
     * for it.  The tuple will be deleted when it is cleared from the
269
     * slot.
270
     */
271
5
    ExecStoreHeapTuple(setopstate->grp_firstTuple,
272
5
               resultTupleSlot,
273
5
               true);
274
5
    setopstate->grp_firstTuple = NULL;  /* don't keep two pointers */
275
276
    /* Initialize working state for a new input tuple group */
277
5
    initialize_counts(pergroup);
278
279
    /* Count the first input tuple */
280
5
    advance_counts(pergroup,
281
5
             fetch_tuple_flag(setopstate, resultTupleSlot));
282
283
    /*
284
     * Scan the outer plan until we exhaust it or cross a group boundary.
285
     */
286
5
    for (;;)
287
7
    {
288
7
      outerslot = ExecProcNode(outerPlan);
289
7
      if (TupIsNull(outerslot))
290
2
      {
291
        /* no more outer-plan tuples available */
292
2
        setopstate->setop_done = true;
293
2
        break;
294
2
      }
295
296
      /*
297
       * Check whether we've crossed a group boundary.
298
       */
299
5
      econtext->ecxt_outertuple = resultTupleSlot;
300
5
      econtext->ecxt_innertuple = outerslot;
301
302
5
      if (!ExecQualAndReset(setopstate->eqfunction, econtext))
303
3
      {
304
        /*
305
         * Save the first input tuple of the next group.
306
         */
307
3
        setopstate->grp_firstTuple = ExecCopySlotTuple(outerslot);
308
3
        break;
309
3
      }
310
311
      /* Still in same group, so count this tuple */
312
2
      advance_counts(pergroup,
313
2
               fetch_tuple_flag(setopstate, outerslot));
314
2
    }
315
316
    /*
317
     * Done scanning input tuple group.  See if we should emit any copies
318
     * of result tuple, and if so return the first copy.
319
     */
320
5
    set_output_count(setopstate, pergroup);
321
322
5
    if (setopstate->numOutput > 0)
323
3
    {
324
3
      setopstate->numOutput--;
325
3
      return resultTupleSlot;
326
3
    }
327
5
  }
328
329
  /* No more groups */
330
2
  ExecClearTuple(resultTupleSlot);
331
2
  return NULL;
332
5
}
333
334
/*
335
 * ExecSetOp for hashed case: phase 1, read input and build hash table
336
 */
337
static void
338
setop_fill_hash_table(SetOpState *setopstate)
339
20
{
340
20
  SetOp    *node = (SetOp *) setopstate->ps.plan;
341
20
  PlanState  *outerPlan;
342
20
  int     firstFlag;
343
20
  bool    in_first_rel PG_USED_FOR_ASSERTS_ONLY;
344
20
  ExprContext *econtext = setopstate->ps.ps_ExprContext;
345
346
  /*
347
   * get state info from node
348
   */
349
20
  outerPlan = outerPlanState(setopstate);
350
20
  firstFlag = node->firstFlag;
351
  /* verify planner didn't mess up */
352
20
  Assert(firstFlag == 0 ||
353
20
       (firstFlag == 1 &&
354
20
      (node->cmd == SETOPCMD_INTERSECT ||
355
20
       node->cmd == SETOPCMD_INTERSECT_ALL)));
356
357
  /*
358
   * Process each outer-plan tuple, and then fetch the next one, until we
359
   * exhaust the outer plan.
360
   */
361
20
  in_first_rel = true;
362
20
  for (;;)
363
150
  {
364
150
    TupleTableSlot *outerslot;
365
150
    int     flag;
366
150
    TupleHashEntryData *entry;
367
150
    bool    isnew;
368
369
150
    outerslot = ExecProcNode(outerPlan);
370
150
    if (TupIsNull(outerslot))
371
20
      break;
372
373
    /* Identify whether it's left or right input */
374
130
    flag = fetch_tuple_flag(setopstate, outerslot);
375
376
130
    if (flag == firstFlag)
377
66
    {
378
      /* (still) in first input relation */
379
66
      Assert(in_first_rel);
380
381
      /* Find or build hashtable entry for this tuple's group */
382
66
      entry = LookupTupleHashEntry(setopstate->hashtable, outerslot,
383
66
                     &isnew);
384
385
      /* If new tuple group, initialize counts */
386
66
      if (isnew)
387
66
      {
388
66
        entry->additional = (SetOpStatePerGroup)
389
66
          MemoryContextAlloc(setopstate->hashtable->tablecxt,
390
66
                     sizeof(SetOpStatePerGroupData));
391
66
        initialize_counts((SetOpStatePerGroup) entry->additional);
392
66
      }
393
394
      /* Advance the counts */
395
66
      advance_counts((SetOpStatePerGroup) entry->additional, flag);
396
66
    }
397
64
    else
398
64
    {
399
      /* reached second relation */
400
64
      in_first_rel = false;
401
402
      /* For tuples not seen previously, do not make hashtable entry */
403
64
      entry = LookupTupleHashEntry(setopstate->hashtable, outerslot,
404
64
                     NULL);
405
406
      /* Advance the counts if entry is already present */
407
64
      if (entry)
408
38
        advance_counts((SetOpStatePerGroup) entry->additional, flag);
409
64
    }
410
411
    /* Must reset expression context after each hashtable lookup */
412
130
    ResetExprContext(econtext);
413
130
  }
414
415
20
  setopstate->table_filled = true;
416
  /* Initialize to walk the hash table */
417
20
  ResetTupleHashIterator(setopstate->hashtable, &setopstate->hashiter);
418
20
}
419
420
/*
421
 * ExecSetOp for hashed case: phase 2, retrieving groups from hash table
422
 */
423
static TupleTableSlot *
424
setop_retrieve_hash_table(SetOpState *setopstate)
425
46
{
426
46
  TupleHashEntryData *entry;
427
46
  TupleTableSlot *resultTupleSlot;
428
429
  /*
430
   * get state info from node
431
   */
432
46
  resultTupleSlot = setopstate->ps.ps_ResultTupleSlot;
433
434
  /*
435
   * We loop retrieving groups until we find one we should return
436
   */
437
86
  while (!setopstate->setop_done)
438
86
  {
439
86
    CHECK_FOR_INTERRUPTS();
440
441
    /*
442
     * Find the next entry in the hash table
443
     */
444
86
    entry = ScanTupleHashTable(setopstate->hashtable, &setopstate->hashiter);
445
86
    if (entry == NULL)
446
20
    {
447
      /* No more entries in hashtable, so done */
448
20
      setopstate->setop_done = true;
449
20
      return NULL;
450
20
    }
451
452
    /*
453
     * See if we should emit any copies of this tuple, and if so return
454
     * the first copy.
455
     */
456
66
    set_output_count(setopstate, (SetOpStatePerGroup) entry->additional);
457
458
66
    if (setopstate->numOutput > 0)
459
26
    {
460
26
      setopstate->numOutput--;
461
26
      return ExecStoreMinimalTuple(entry->firstTuple,
462
26
                     resultTupleSlot,
463
26
                     false);
464
26
    }
465
66
  }
466
467
  /* No more groups */
468
0
  ExecClearTuple(resultTupleSlot);
469
0
  return NULL;
470
46
}
471
472
/* ----------------------------------------------------------------
473
 *    ExecInitSetOp
474
 *
475
 *    This initializes the setop node state structures and
476
 *    the node's subplan.
477
 * ----------------------------------------------------------------
478
 */
479
SetOpState *
480
ExecInitSetOp(SetOp *node, EState *estate, int eflags)
481
21
{
482
21
  SetOpState *setopstate;
483
21
  TupleDesc outerDesc;
484
485
  /* check for unsupported flags */
486
21
  Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
487
488
  /*
489
   * create state structure
490
   */
491
21
  setopstate = makeNode(SetOpState);
492
0
  setopstate->ps.plan = (Plan *) node;
493
21
  setopstate->ps.state = estate;
494
21
  setopstate->ps.ExecProcNode = ExecSetOp;
495
496
21
  setopstate->eqfuncoids = NULL;
497
21
  setopstate->hashfunctions = NULL;
498
21
  setopstate->setop_done = false;
499
21
  setopstate->numOutput = 0;
500
21
  setopstate->pergroup = NULL;
501
21
  setopstate->grp_firstTuple = NULL;
502
21
  setopstate->hashtable = NULL;
503
21
  setopstate->tableContext = NULL;
504
505
  /*
506
   * create expression context
507
   */
508
21
  ExecAssignExprContext(estate, &setopstate->ps);
509
510
  /*
511
   * If hashing, we also need a longer-lived context to store the hash
512
   * table.  The table can't just be kept in the per-query context because
513
   * we want to be able to throw it away in ExecReScanSetOp.
514
   */
515
21
  if (node->strategy == SETOP_HASHED)
516
18
    setopstate->tableContext =
517
18
      AllocSetContextCreate(GetCurrentMemoryContext(),
518
21
                  "SetOp hash table",
519
21
                  ALLOCSET_DEFAULT_SIZES);
520
521
  /*
522
   * initialize child nodes
523
   *
524
   * If we are hashing then the child plan does not need to handle REWIND
525
   * efficiently; see ExecReScanSetOp.
526
   */
527
21
  if (node->strategy == SETOP_HASHED)
528
18
    eflags &= ~EXEC_FLAG_REWIND;
529
21
  outerPlanState(setopstate) = ExecInitNode(outerPlan(node), estate, eflags);
530
21
  outerDesc = ExecGetResultType(outerPlanState(setopstate));
531
532
  /*
533
   * Initialize result slot and type. Setop nodes do no projections, so
534
   * initialize projection info for this node appropriately.
535
   */
536
21
  ExecInitResultTupleSlotTL(&setopstate->ps);
537
21
  setopstate->ps.ps_ProjInfo = NULL;
538
539
  /*
540
   * Precompute fmgr lookup data for inner loop. We need both equality and
541
   * hashing functions to do it by hashing, but only equality if not
542
   * hashing.
543
   */
544
21
  if (node->strategy == SETOP_HASHED)
545
18
    execTuplesHashPrepare(node->numCols,
546
18
                node->dupOperators,
547
18
                &setopstate->eqfuncoids,
548
18
                &setopstate->hashfunctions);
549
3
  else
550
3
    setopstate->eqfunction =
551
3
      execTuplesMatchPrepare(outerDesc,
552
3
                   node->numCols,
553
3
                   node->dupColIdx,
554
3
                   node->dupOperators,
555
3
                   &setopstate->ps);
556
557
21
  if (node->strategy == SETOP_HASHED)
558
18
  {
559
18
    build_hash_table(setopstate);
560
18
    setopstate->table_filled = false;
561
18
  }
562
3
  else
563
3
  {
564
3
    setopstate->pergroup =
565
3
      (SetOpStatePerGroup) palloc0(sizeof(SetOpStatePerGroupData));
566
3
  }
567
568
21
  return setopstate;
569
21
}
570
571
/* ----------------------------------------------------------------
572
 *    ExecEndSetOp
573
 *
574
 *    This shuts down the subplan and frees resources allocated
575
 *    to this node.
576
 * ----------------------------------------------------------------
577
 */
578
void
579
ExecEndSetOp(SetOpState *node)
580
21
{
581
  /* clean up tuple table */
582
21
  ExecClearTuple(node->ps.ps_ResultTupleSlot);
583
584
  /* free subsidiary stuff including hashtable */
585
21
  if (node->tableContext)
586
18
    MemoryContextDelete(node->tableContext);
587
21
  ExecFreeExprContext(&node->ps);
588
589
21
  ExecEndNode(outerPlanState(node));
590
21
}
591
592
593
void
594
ExecReScanSetOp(SetOpState *node)
595
14
{
596
14
  ExecClearTuple(node->ps.ps_ResultTupleSlot);
597
14
  node->setop_done = false;
598
14
  node->numOutput = 0;
599
600
14
  if (((SetOp *) node->ps.plan)->strategy == SETOP_HASHED)
601
14
  {
602
    /*
603
     * In the hashed case, if we haven't yet built the hash table then we
604
     * can just return; nothing done yet, so nothing to undo. If subnode's
605
     * chgParam is not NULL then it will be re-scanned by ExecProcNode,
606
     * else no reason to re-scan it at all.
607
     */
608
14
    if (!node->table_filled)
609
12
      return;
610
611
    /*
612
     * If we do have the hash table and the subplan does not have any
613
     * parameter changes, then we can just rescan the existing hash table;
614
     * no need to build it again.
615
     */
616
2
    if (node->ps.lefttree->chgParam == NULL)
617
0
    {
618
0
      ResetTupleHashIterator(node->hashtable, &node->hashiter);
619
0
      return;
620
0
    }
621
2
  }
622
623
  /* Release first tuple of group, if we have made a copy */
624
2
  if (node->grp_firstTuple != NULL)
625
0
  {
626
0
    heap_freetuple(node->grp_firstTuple);
627
0
    node->grp_firstTuple = NULL;
628
0
  }
629
630
  /* Release any hashtable storage */
631
2
  if (node->tableContext)
632
2
    MemoryContextResetAndDeleteChildren(node->tableContext);
633
634
  /* And rebuild empty hashtable if needed */
635
2
  if (((SetOp *) node->ps.plan)->strategy == SETOP_HASHED)
636
2
  {
637
2
    ResetTupleHashTable(node->hashtable);
638
2
    node->table_filled = false;
639
2
  }
640
641
  /*
642
   * if chgParam of subnode is not null then plan will be re-scanned by
643
   * first ExecProcNode.
644
   */
645
2
  if (node->ps.lefttree->chgParam == NULL)
646
0
    ExecReScan(node->ps.lefttree);
647
2
}