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

Generated by: LCOV version 2.0-1