LCOV - code coverage report
Current view: top level - src/backend/executor - nodeRecursiveunion.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 99.0 % 100 99
Test Date: 2026-03-21 04:16:06 Functions: 100.0 % 5 5
Legend: Lines:     hit not hit

            Line data    Source code
       1              : /*-------------------------------------------------------------------------
       2              :  *
       3              :  * nodeRecursiveunion.c
       4              :  *    routines to handle RecursiveUnion nodes.
       5              :  *
       6              :  * To implement UNION (without ALL), we need a hashtable that stores tuples
       7              :  * already seen.  The hash key is computed from the grouping columns.
       8              :  *
       9              :  *
      10              :  * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
      11              :  * Portions Copyright (c) 1994, Regents of the University of California
      12              :  *
      13              :  *
      14              :  * IDENTIFICATION
      15              :  *    src/backend/executor/nodeRecursiveunion.c
      16              :  *
      17              :  *-------------------------------------------------------------------------
      18              :  */
      19              : #include "postgres.h"
      20              : 
      21              : #include "executor/executor.h"
      22              : #include "executor/nodeRecursiveunion.h"
      23              : #include "miscadmin.h"
      24              : #include "utils/memutils.h"
      25              : #include "utils/tuplestore.h"
      26              : 
      27              : 
      28              : 
      29              : /*
      30              :  * Initialize the hash table to empty.
      31              :  */
      32              : static void
      33          273 : build_hash_table(RecursiveUnionState *rustate)
      34              : {
      35          273 :     RecursiveUnion *node = (RecursiveUnion *) rustate->ps.plan;
      36          273 :     TupleDesc   desc = ExecGetResultType(outerPlanState(rustate));
      37              : 
      38              :     Assert(node->numCols > 0);
      39              : 
      40              :     /*
      41              :      * If both child plans deliver the same fixed tuple slot type, we can tell
      42              :      * BuildTupleHashTable to expect that slot type as input.  Otherwise,
      43              :      * we'll pass NULL denoting that any slot type is possible.
      44              :      */
      45          273 :     rustate->hashtable = BuildTupleHashTable(&rustate->ps,
      46              :                                              desc,
      47              :                                              ExecGetCommonChildSlotOps(&rustate->ps),
      48              :                                              node->numCols,
      49              :                                              node->dupColIdx,
      50          273 :                                              rustate->eqfuncoids,
      51              :                                              rustate->hashfunctions,
      52              :                                              node->dupCollations,
      53              :                                              node->numGroups,
      54              :                                              0,
      55          273 :                                              rustate->ps.state->es_query_cxt,
      56              :                                              rustate->tuplesContext,
      57              :                                              rustate->tempContext,
      58              :                                              false);
      59          273 : }
      60              : 
      61              : 
      62              : /* ----------------------------------------------------------------
      63              :  *      ExecRecursiveUnion(node)
      64              :  *
      65              :  *      Scans the recursive query sequentially and returns the next
      66              :  *      qualifying tuple.
      67              :  *
      68              :  * 1. evaluate non recursive term and assign the result to RT
      69              :  *
      70              :  * 2. execute recursive terms
      71              :  *
      72              :  * 2.1 WT := RT
      73              :  * 2.2 while WT is not empty repeat 2.3 to 2.6. if WT is empty returns RT
      74              :  * 2.3 replace the name of recursive term with WT
      75              :  * 2.4 evaluate the recursive term and store into WT
      76              :  * 2.5 append WT to RT
      77              :  * 2.6 go back to 2.2
      78              :  * ----------------------------------------------------------------
      79              :  */
      80              : static TupleTableSlot *
      81        41348 : ExecRecursiveUnion(PlanState *pstate)
      82              : {
      83        41348 :     RecursiveUnionState *node = castNode(RecursiveUnionState, pstate);
      84        41348 :     PlanState  *outerPlan = outerPlanState(node);
      85        41348 :     PlanState  *innerPlan = innerPlanState(node);
      86        41348 :     RecursiveUnion *plan = (RecursiveUnion *) node->ps.plan;
      87              :     TupleTableSlot *slot;
      88              :     bool        isnew;
      89              : 
      90        41348 :     CHECK_FOR_INTERRUPTS();
      91              : 
      92              :     /* 1. Evaluate non-recursive term */
      93        41348 :     if (!node->recursing)
      94              :     {
      95              :         for (;;)
      96              :         {
      97        13131 :             slot = ExecProcNode(outerPlan);
      98        13131 :             if (TupIsNull(slot))
      99              :                 break;
     100        12548 :             if (plan->numCols > 0)
     101              :             {
     102              :                 /* Find or build hashtable entry for this tuple's group */
     103          293 :                 LookupTupleHashEntry(node->hashtable, slot, &isnew, NULL);
     104              :                 /* Must reset temp context after each hashtable lookup */
     105          293 :                 MemoryContextReset(node->tempContext);
     106              :                 /* Ignore tuple if already seen */
     107          293 :                 if (!isnew)
     108           10 :                     continue;
     109              :             }
     110              :             /* Each non-duplicate tuple goes to the working table ... */
     111        12538 :             tuplestore_puttupleslot(node->working_table, slot);
     112              :             /* ... and to the caller */
     113        12538 :             return slot;
     114              :         }
     115          583 :         node->recursing = true;
     116              :     }
     117              : 
     118              :     /* 2. Execute recursive term */
     119              :     for (;;)
     120              :     {
     121        33132 :         slot = ExecProcNode(innerPlan);
     122        33132 :         if (TupIsNull(slot))
     123         4272 :         {
     124              :             Tuplestorestate *swaptemp;
     125              : 
     126              :             /* Done if there's nothing in the intermediate table */
     127         4827 :             if (node->intermediate_empty)
     128          555 :                 break;
     129              : 
     130              :             /*
     131              :              * Now we let the intermediate table become the work table.  We
     132              :              * need a fresh intermediate table, so delete the tuples from the
     133              :              * current working table and use that as the new intermediate
     134              :              * table.  This saves a round of free/malloc from creating a new
     135              :              * tuple store.
     136              :              */
     137         4272 :             tuplestore_clear(node->working_table);
     138              : 
     139         4272 :             swaptemp = node->working_table;
     140         4272 :             node->working_table = node->intermediate_table;
     141         4272 :             node->intermediate_table = swaptemp;
     142              : 
     143              :             /* mark the intermediate table as empty */
     144         4272 :             node->intermediate_empty = true;
     145              : 
     146              :             /* reset the recursive term */
     147         4272 :             innerPlan->chgParam = bms_add_member(innerPlan->chgParam,
     148              :                                                  plan->wtParam);
     149              : 
     150              :             /* and continue fetching from recursive term */
     151         4272 :             continue;
     152              :         }
     153              : 
     154        28305 :         if (plan->numCols > 0)
     155              :         {
     156              :             /* Find or build hashtable entry for this tuple's group */
     157          558 :             LookupTupleHashEntry(node->hashtable, slot, &isnew, NULL);
     158              :             /* Must reset temp context after each hashtable lookup */
     159          558 :             MemoryContextReset(node->tempContext);
     160              :             /* Ignore tuple if already seen */
     161          558 :             if (!isnew)
     162           50 :                 continue;
     163              :         }
     164              : 
     165              :         /* Else, tuple is good; stash it in intermediate table ... */
     166        28255 :         node->intermediate_empty = false;
     167        28255 :         tuplestore_puttupleslot(node->intermediate_table, slot);
     168              :         /* ... and return it */
     169        28255 :         return slot;
     170              :     }
     171              : 
     172          555 :     return NULL;
     173              : }
     174              : 
     175              : /* ----------------------------------------------------------------
     176              :  *      ExecInitRecursiveUnion
     177              :  * ----------------------------------------------------------------
     178              :  */
     179              : RecursiveUnionState *
     180          615 : ExecInitRecursiveUnion(RecursiveUnion *node, EState *estate, int eflags)
     181              : {
     182              :     RecursiveUnionState *rustate;
     183              :     ParamExecData *prmdata;
     184              : 
     185              :     /* check for unsupported flags */
     186              :     Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
     187              : 
     188              :     /*
     189              :      * create state structure
     190              :      */
     191          615 :     rustate = makeNode(RecursiveUnionState);
     192          615 :     rustate->ps.plan = (Plan *) node;
     193          615 :     rustate->ps.state = estate;
     194          615 :     rustate->ps.ExecProcNode = ExecRecursiveUnion;
     195              : 
     196          615 :     rustate->eqfuncoids = NULL;
     197          615 :     rustate->hashfunctions = NULL;
     198          615 :     rustate->hashtable = NULL;
     199          615 :     rustate->tempContext = NULL;
     200          615 :     rustate->tuplesContext = NULL;
     201              : 
     202              :     /* initialize processing state */
     203          615 :     rustate->recursing = false;
     204          615 :     rustate->intermediate_empty = true;
     205          615 :     rustate->working_table = tuplestore_begin_heap(false, false, work_mem);
     206          615 :     rustate->intermediate_table = tuplestore_begin_heap(false, false, work_mem);
     207              : 
     208              :     /*
     209              :      * If hashing, we need a per-tuple memory context for comparisons, and a
     210              :      * longer-lived context to store the hash table.  The table can't just be
     211              :      * kept in the per-query context because we want to be able to throw it
     212              :      * away when rescanning.  We can use a BumpContext to save storage,
     213              :      * because we will have no need to delete individual table entries.
     214              :      */
     215          615 :     if (node->numCols > 0)
     216              :     {
     217          273 :         rustate->tempContext =
     218          273 :             AllocSetContextCreate(CurrentMemoryContext,
     219              :                                   "RecursiveUnion",
     220              :                                   ALLOCSET_DEFAULT_SIZES);
     221          273 :         rustate->tuplesContext =
     222          273 :             BumpContextCreate(CurrentMemoryContext,
     223              :                               "RecursiveUnion hashed tuples",
     224              :                               ALLOCSET_DEFAULT_SIZES);
     225              :     }
     226              : 
     227              :     /*
     228              :      * Make the state structure available to descendant WorkTableScan nodes
     229              :      * via the Param slot reserved for it.
     230              :      */
     231          615 :     prmdata = &(estate->es_param_exec_vals[node->wtParam]);
     232              :     Assert(prmdata->execPlan == NULL);
     233          615 :     prmdata->value = PointerGetDatum(rustate);
     234          615 :     prmdata->isnull = false;
     235              : 
     236              :     /*
     237              :      * Miscellaneous initialization
     238              :      *
     239              :      * RecursiveUnion plans don't have expression contexts because they never
     240              :      * call ExecQual or ExecProject.
     241              :      */
     242              :     Assert(node->plan.qual == NIL);
     243              : 
     244              :     /*
     245              :      * RecursiveUnion nodes still have Result slots, which hold pointers to
     246              :      * tuples, so we have to initialize them.
     247              :      */
     248          615 :     ExecInitResultTypeTL(&rustate->ps);
     249              : 
     250              :     /*
     251              :      * Initialize result tuple type.  (Note: we have to set up the result type
     252              :      * before initializing child nodes, because nodeWorktablescan.c expects it
     253              :      * to be valid.)
     254              :      */
     255          615 :     rustate->ps.ps_ProjInfo = NULL;
     256              : 
     257              :     /*
     258              :      * initialize child nodes
     259              :      */
     260          615 :     outerPlanState(rustate) = ExecInitNode(outerPlan(node), estate, eflags);
     261          615 :     innerPlanState(rustate) = ExecInitNode(innerPlan(node), estate, eflags);
     262              : 
     263              :     /*
     264              :      * If hashing, precompute fmgr lookup data for inner loop, and create the
     265              :      * hash table.
     266              :      */
     267          615 :     if (node->numCols > 0)
     268              :     {
     269          273 :         execTuplesHashPrepare(node->numCols,
     270          273 :                               node->dupOperators,
     271              :                               &rustate->eqfuncoids,
     272              :                               &rustate->hashfunctions);
     273          273 :         build_hash_table(rustate);
     274              :     }
     275              : 
     276          615 :     return rustate;
     277              : }
     278              : 
     279              : /* ----------------------------------------------------------------
     280              :  *      ExecEndRecursiveUnion
     281              :  *
     282              :  *      frees any storage allocated through C routines.
     283              :  * ----------------------------------------------------------------
     284              :  */
     285              : void
     286          615 : ExecEndRecursiveUnion(RecursiveUnionState *node)
     287              : {
     288              :     /* Release tuplestores */
     289          615 :     tuplestore_end(node->working_table);
     290          615 :     tuplestore_end(node->intermediate_table);
     291              : 
     292              :     /* free subsidiary stuff including hashtable data */
     293          615 :     if (node->tempContext)
     294          273 :         MemoryContextDelete(node->tempContext);
     295          615 :     if (node->tuplesContext)
     296          273 :         MemoryContextDelete(node->tuplesContext);
     297              : 
     298              :     /*
     299              :      * close down subplans
     300              :      */
     301          615 :     ExecEndNode(outerPlanState(node));
     302          615 :     ExecEndNode(innerPlanState(node));
     303          615 : }
     304              : 
     305              : /* ----------------------------------------------------------------
     306              :  *      ExecReScanRecursiveUnion
     307              :  *
     308              :  *      Rescans the relation.
     309              :  * ----------------------------------------------------------------
     310              :  */
     311              : void
     312            8 : ExecReScanRecursiveUnion(RecursiveUnionState *node)
     313              : {
     314            8 :     PlanState  *outerPlan = outerPlanState(node);
     315            8 :     PlanState  *innerPlan = innerPlanState(node);
     316            8 :     RecursiveUnion *plan = (RecursiveUnion *) node->ps.plan;
     317              : 
     318              :     /*
     319              :      * Set recursive term's chgParam to tell it that we'll modify the working
     320              :      * table and therefore it has to rescan.
     321              :      */
     322            8 :     innerPlan->chgParam = bms_add_member(innerPlan->chgParam, plan->wtParam);
     323              : 
     324              :     /*
     325              :      * if chgParam of subnode is not null then plan will be re-scanned by
     326              :      * first ExecProcNode.  Because of above, we only have to do this to the
     327              :      * non-recursive term.
     328              :      */
     329            8 :     if (outerPlan->chgParam == NULL)
     330            0 :         ExecReScan(outerPlan);
     331              : 
     332              :     /* Empty hashtable if needed */
     333            8 :     if (plan->numCols > 0)
     334            8 :         ResetTupleHashTable(node->hashtable);
     335              : 
     336              :     /* reset processing state */
     337            8 :     node->recursing = false;
     338            8 :     node->intermediate_empty = true;
     339            8 :     tuplestore_clear(node->working_table);
     340            8 :     tuplestore_clear(node->intermediate_table);
     341            8 : }
        

Generated by: LCOV version 2.0-1