LCOV - code coverage report
Current view: top level - src/backend/executor - nodeRecursiveunion.c (source / functions) Coverage Total Hit
Test: PostgreSQL 20devel Lines: 99.0 % 100 99
Test Date: 2026-07-12 21:15:34 Functions: 100.0 % 5 5
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 91.2 % 34 31

             Branch data     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                 :         203 : build_hash_table(RecursiveUnionState *rustate)
      34                 :             : {
      35                 :         203 :     RecursiveUnion *node = (RecursiveUnion *) rustate->ps.plan;
      36                 :         203 :     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                 :         203 :     rustate->hashtable = BuildTupleHashTable(&rustate->ps,
      46                 :             :                                              desc,
      47                 :             :                                              ExecGetCommonChildSlotOps(&rustate->ps),
      48                 :             :                                              node->numCols,
      49                 :             :                                              node->dupColIdx,
      50                 :         203 :                                              rustate->eqfuncoids,
      51                 :             :                                              rustate->hashfunctions,
      52                 :             :                                              node->dupCollations,
      53                 :             :                                              node->numGroups,
      54                 :             :                                              0,
      55                 :         203 :                                              rustate->ps.state->es_query_cxt,
      56                 :             :                                              rustate->tuplesContext,
      57                 :             :                                              rustate->tempContext,
      58                 :             :                                              false);
      59                 :         203 : }
      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                 :       42286 : ExecRecursiveUnion(PlanState *pstate)
      82                 :             : {
      83                 :       42286 :     RecursiveUnionState *node = castNode(RecursiveUnionState, pstate);
      84                 :       42286 :     PlanState  *outerPlan = outerPlanState(node);
      85                 :       42286 :     PlanState  *innerPlan = innerPlanState(node);
      86                 :       42286 :     RecursiveUnion *plan = (RecursiveUnion *) node->ps.plan;
      87                 :             :     TupleTableSlot *slot;
      88                 :             :     bool        isnew;
      89                 :             : 
      90         [ -  + ]:       42286 :     CHECK_FOR_INTERRUPTS();
      91                 :             : 
      92                 :             :     /* 1. Evaluate non-recursive term */
      93         [ +  + ]:       42286 :     if (!node->recursing)
      94                 :             :     {
      95                 :             :         for (;;)
      96                 :             :         {
      97                 :       13427 :             slot = ExecProcNode(outerPlan);
      98   [ +  +  +  + ]:       13427 :             if (TupIsNull(slot))
      99                 :             :                 break;
     100         [ +  + ]:       12902 :             if (plan->numCols > 0)
     101                 :             :             {
     102                 :             :                 /* Find or build hashtable entry for this tuple's group */
     103                 :         299 :                 LookupTupleHashEntry(node->hashtable, slot, &isnew, NULL);
     104                 :             :                 /* Must reset temp context after each hashtable lookup */
     105                 :         299 :                 MemoryContextReset(node->tempContext);
     106                 :             :                 /* Ignore tuple if already seen */
     107         [ +  + ]:         299 :                 if (!isnew)
     108                 :          10 :                     continue;
     109                 :             :             }
     110                 :             :             /* Each non-duplicate tuple goes to the working table ... */
     111                 :       12892 :             tuplestore_puttupleslot(node->working_table, slot);
     112                 :             :             /* ... and to the caller */
     113                 :       12892 :             return slot;
     114                 :             :         }
     115                 :         525 :         node->recursing = true;
     116                 :             :     }
     117                 :             : 
     118                 :             :     /* 2. Execute recursive term */
     119                 :             :     for (;;)
     120                 :             :     {
     121                 :       33756 :         slot = ExecProcNode(innerPlan);
     122   [ +  +  +  + ]:       33756 :         if (TupIsNull(slot))
     123                 :        4312 :         {
     124                 :             :             Tuplestorestate *swaptemp;
     125                 :             : 
     126                 :             :             /* Done if there's nothing in the intermediate table */
     127         [ +  + ]:        4809 :             if (node->intermediate_empty)
     128                 :         497 :                 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                 :        4312 :             tuplestore_clear(node->working_table);
     138                 :             : 
     139                 :        4312 :             swaptemp = node->working_table;
     140                 :        4312 :             node->working_table = node->intermediate_table;
     141                 :        4312 :             node->intermediate_table = swaptemp;
     142                 :             : 
     143                 :             :             /* mark the intermediate table as empty */
     144                 :        4312 :             node->intermediate_empty = true;
     145                 :             : 
     146                 :             :             /* reset the recursive term */
     147                 :        4312 :             innerPlan->chgParam = bms_add_member(innerPlan->chgParam,
     148                 :             :                                                  plan->wtParam);
     149                 :             : 
     150                 :             :             /* and continue fetching from recursive term */
     151                 :        4312 :             continue;
     152                 :             :         }
     153                 :             : 
     154         [ +  + ]:       28947 :         if (plan->numCols > 0)
     155                 :             :         {
     156                 :             :             /* Find or build hashtable entry for this tuple's group */
     157                 :         564 :             LookupTupleHashEntry(node->hashtable, slot, &isnew, NULL);
     158                 :             :             /* Must reset temp context after each hashtable lookup */
     159                 :         564 :             MemoryContextReset(node->tempContext);
     160                 :             :             /* Ignore tuple if already seen */
     161         [ +  + ]:         564 :             if (!isnew)
     162                 :          50 :                 continue;
     163                 :             :         }
     164                 :             : 
     165                 :             :         /* Else, tuple is good; stash it in intermediate table ... */
     166                 :       28897 :         node->intermediate_empty = false;
     167                 :       28897 :         tuplestore_puttupleslot(node->intermediate_table, slot);
     168                 :             :         /* ... and return it */
     169                 :       28897 :         return slot;
     170                 :             :     }
     171                 :             : 
     172                 :         497 :     return NULL;
     173                 :             : }
     174                 :             : 
     175                 :             : /* ----------------------------------------------------------------
     176                 :             :  *      ExecInitRecursiveUnion
     177                 :             :  * ----------------------------------------------------------------
     178                 :             :  */
     179                 :             : RecursiveUnionState *
     180                 :         557 : 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                 :         557 :     rustate = makeNode(RecursiveUnionState);
     192                 :         557 :     rustate->ps.plan = (Plan *) node;
     193                 :         557 :     rustate->ps.state = estate;
     194                 :         557 :     rustate->ps.ExecProcNode = ExecRecursiveUnion;
     195                 :             : 
     196                 :         557 :     rustate->eqfuncoids = NULL;
     197                 :         557 :     rustate->hashfunctions = NULL;
     198                 :         557 :     rustate->hashtable = NULL;
     199                 :         557 :     rustate->tempContext = NULL;
     200                 :         557 :     rustate->tuplesContext = NULL;
     201                 :             : 
     202                 :             :     /* initialize processing state */
     203                 :         557 :     rustate->recursing = false;
     204                 :         557 :     rustate->intermediate_empty = true;
     205                 :         557 :     rustate->working_table = tuplestore_begin_heap(false, false, work_mem);
     206                 :         557 :     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         [ +  + ]:         557 :     if (node->numCols > 0)
     216                 :             :     {
     217                 :         203 :         rustate->tempContext =
     218                 :         203 :             AllocSetContextCreate(CurrentMemoryContext,
     219                 :             :                                   "RecursiveUnion",
     220                 :             :                                   ALLOCSET_DEFAULT_SIZES);
     221                 :         203 :         rustate->tuplesContext =
     222                 :         203 :             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                 :         557 :     prmdata = &(estate->es_param_exec_vals[node->wtParam]);
     232                 :             :     Assert(prmdata->execPlan == NULL);
     233                 :         557 :     prmdata->value = PointerGetDatum(rustate);
     234                 :         557 :     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                 :         557 :     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                 :         557 :     rustate->ps.ps_ProjInfo = NULL;
     256                 :             : 
     257                 :             :     /*
     258                 :             :      * initialize child nodes
     259                 :             :      */
     260                 :         557 :     outerPlanState(rustate) = ExecInitNode(outerPlan(node), estate, eflags);
     261                 :         557 :     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         [ +  + ]:         557 :     if (node->numCols > 0)
     268                 :             :     {
     269                 :         203 :         execTuplesHashPrepare(node->numCols,
     270                 :         203 :                               node->dupOperators,
     271                 :             :                               &rustate->eqfuncoids,
     272                 :             :                               &rustate->hashfunctions);
     273                 :         203 :         build_hash_table(rustate);
     274                 :             :     }
     275                 :             : 
     276                 :         557 :     return rustate;
     277                 :             : }
     278                 :             : 
     279                 :             : /* ----------------------------------------------------------------
     280                 :             :  *      ExecEndRecursiveUnion
     281                 :             :  *
     282                 :             :  *      frees any storage allocated through C routines.
     283                 :             :  * ----------------------------------------------------------------
     284                 :             :  */
     285                 :             : void
     286                 :         557 : ExecEndRecursiveUnion(RecursiveUnionState *node)
     287                 :             : {
     288                 :             :     /* Release tuplestores */
     289                 :         557 :     tuplestore_end(node->working_table);
     290                 :         557 :     tuplestore_end(node->intermediate_table);
     291                 :             : 
     292                 :             :     /* free subsidiary stuff including hashtable data */
     293         [ +  + ]:         557 :     if (node->tempContext)
     294                 :         203 :         MemoryContextDelete(node->tempContext);
     295         [ +  + ]:         557 :     if (node->tuplesContext)
     296                 :         203 :         MemoryContextDelete(node->tuplesContext);
     297                 :             : 
     298                 :             :     /*
     299                 :             :      * close down subplans
     300                 :             :      */
     301                 :         557 :     ExecEndNode(outerPlanState(node));
     302                 :         557 :     ExecEndNode(innerPlanState(node));
     303                 :         557 : }
     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