LCOV - code coverage report
Current view: top level - src/backend/executor - nodeSetOp.c (source / functions) Coverage Total Hit
Test: Code coverage Lines: 96.4 % 302 291
Test Date: 2026-01-26 10:56:24 Functions: 100.0 % 12 12
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 82.1 % 140 115

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

Generated by: LCOV version 2.3.2-1