Coverage Report

Created: 2026-08-13 07:12

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/postgres/src/backend/executor/nodeRecursiveunion.c
Line
Count
Source
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
build_hash_table(RecursiveUnionState *rustate)
34
0
{
35
0
  RecursiveUnion *node = (RecursiveUnion *) rustate->ps.plan;
36
0
  TupleDesc desc = ExecGetResultType(outerPlanState(rustate));
37
38
0
  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
0
  rustate->hashtable = BuildTupleHashTable(&rustate->ps,
46
0
                       desc,
47
0
                       ExecGetCommonChildSlotOps(&rustate->ps),
48
0
                       node->numCols,
49
0
                       node->dupColIdx,
50
0
                       rustate->eqfuncoids,
51
0
                       rustate->hashfunctions,
52
0
                       node->dupCollations,
53
0
                       node->numGroups,
54
0
                       0,
55
0
                       rustate->ps.state->es_query_cxt,
56
0
                       rustate->tuplesContext,
57
0
                       rustate->tempContext,
58
0
                       false);
59
0
}
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
ExecRecursiveUnion(PlanState *pstate)
82
0
{
83
0
  RecursiveUnionState *node = castNode(RecursiveUnionState, pstate);
84
0
  PlanState  *outerPlan = outerPlanState(node);
85
0
  PlanState  *innerPlan = innerPlanState(node);
86
0
  RecursiveUnion *plan = (RecursiveUnion *) node->ps.plan;
87
0
  TupleTableSlot *slot;
88
0
  bool    isnew;
89
90
0
  CHECK_FOR_INTERRUPTS();
91
92
  /* 1. Evaluate non-recursive term */
93
0
  if (!node->recursing)
94
0
  {
95
0
    for (;;)
96
0
    {
97
0
      slot = ExecProcNode(outerPlan);
98
0
      if (TupIsNull(slot))
99
0
        break;
100
0
      if (plan->numCols > 0)
101
0
      {
102
        /* Find or build hashtable entry for this tuple's group */
103
0
        LookupTupleHashEntry(node->hashtable, slot, &isnew, NULL);
104
        /* Must reset temp context after each hashtable lookup */
105
0
        MemoryContextReset(node->tempContext);
106
        /* Ignore tuple if already seen */
107
0
        if (!isnew)
108
0
          continue;
109
0
      }
110
      /* Each non-duplicate tuple goes to the working table ... */
111
0
      tuplestore_puttupleslot(node->working_table, slot);
112
      /* ... and to the caller */
113
0
      return slot;
114
0
    }
115
0
    node->recursing = true;
116
0
  }
117
118
  /* 2. Execute recursive term */
119
0
  for (;;)
120
0
  {
121
0
    slot = ExecProcNode(innerPlan);
122
0
    if (TupIsNull(slot))
123
0
    {
124
0
      Tuplestorestate *swaptemp;
125
126
      /* Done if there's nothing in the intermediate table */
127
0
      if (node->intermediate_empty)
128
0
        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
0
      tuplestore_clear(node->working_table);
138
139
0
      swaptemp = node->working_table;
140
0
      node->working_table = node->intermediate_table;
141
0
      node->intermediate_table = swaptemp;
142
143
      /* mark the intermediate table as empty */
144
0
      node->intermediate_empty = true;
145
146
      /* reset the recursive term */
147
0
      innerPlan->chgParam = bms_add_member(innerPlan->chgParam,
148
0
                         plan->wtParam);
149
150
      /* and continue fetching from recursive term */
151
0
      continue;
152
0
    }
153
154
0
    if (plan->numCols > 0)
155
0
    {
156
      /* Find or build hashtable entry for this tuple's group */
157
0
      LookupTupleHashEntry(node->hashtable, slot, &isnew, NULL);
158
      /* Must reset temp context after each hashtable lookup */
159
0
      MemoryContextReset(node->tempContext);
160
      /* Ignore tuple if already seen */
161
0
      if (!isnew)
162
0
        continue;
163
0
    }
164
165
    /* Else, tuple is good; stash it in intermediate table ... */
166
0
    node->intermediate_empty = false;
167
0
    tuplestore_puttupleslot(node->intermediate_table, slot);
168
    /* ... and return it */
169
0
    return slot;
170
0
  }
171
172
0
  return NULL;
173
0
}
174
175
/* ----------------------------------------------------------------
176
 *    ExecInitRecursiveUnion
177
 * ----------------------------------------------------------------
178
 */
179
RecursiveUnionState *
180
ExecInitRecursiveUnion(RecursiveUnion *node, EState *estate, int eflags)
181
0
{
182
0
  RecursiveUnionState *rustate;
183
0
  ParamExecData *prmdata;
184
185
  /* check for unsupported flags */
186
0
  Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
187
188
  /*
189
   * create state structure
190
   */
191
0
  rustate = makeNode(RecursiveUnionState);
192
0
  rustate->ps.plan = (Plan *) node;
193
0
  rustate->ps.state = estate;
194
0
  rustate->ps.ExecProcNode = ExecRecursiveUnion;
195
196
0
  rustate->eqfuncoids = NULL;
197
0
  rustate->hashfunctions = NULL;
198
0
  rustate->hashtable = NULL;
199
0
  rustate->tempContext = NULL;
200
0
  rustate->tuplesContext = NULL;
201
202
  /* initialize processing state */
203
0
  rustate->recursing = false;
204
0
  rustate->intermediate_empty = true;
205
0
  rustate->working_table = tuplestore_begin_heap(false, false, work_mem);
206
0
  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
0
  if (node->numCols > 0)
216
0
  {
217
0
    rustate->tempContext =
218
0
      AllocSetContextCreate(CurrentMemoryContext,
219
0
                  "RecursiveUnion",
220
0
                  ALLOCSET_DEFAULT_SIZES);
221
0
    rustate->tuplesContext =
222
0
      BumpContextCreate(CurrentMemoryContext,
223
0
                "RecursiveUnion hashed tuples",
224
0
                ALLOCSET_DEFAULT_SIZES);
225
0
  }
226
227
  /*
228
   * Make the state structure available to descendant WorkTableScan nodes
229
   * via the Param slot reserved for it.
230
   */
231
0
  prmdata = &(estate->es_param_exec_vals[node->wtParam]);
232
0
  Assert(prmdata->execPlan == NULL);
233
0
  prmdata->value = PointerGetDatum(rustate);
234
0
  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
0
  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
0
  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
0
  rustate->ps.ps_ProjInfo = NULL;
256
257
  /*
258
   * initialize child nodes
259
   */
260
0
  outerPlanState(rustate) = ExecInitNode(outerPlan(node), estate, eflags);
261
0
  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
0
  if (node->numCols > 0)
268
0
  {
269
0
    execTuplesHashPrepare(node->numCols,
270
0
                node->dupOperators,
271
0
                &rustate->eqfuncoids,
272
0
                &rustate->hashfunctions);
273
0
    build_hash_table(rustate);
274
0
  }
275
276
0
  return rustate;
277
0
}
278
279
/* ----------------------------------------------------------------
280
 *    ExecEndRecursiveUnion
281
 *
282
 *    frees any storage allocated through C routines.
283
 * ----------------------------------------------------------------
284
 */
285
void
286
ExecEndRecursiveUnion(RecursiveUnionState *node)
287
0
{
288
  /* Release tuplestores */
289
0
  tuplestore_end(node->working_table);
290
0
  tuplestore_end(node->intermediate_table);
291
292
  /* free subsidiary stuff including hashtable data */
293
0
  if (node->tempContext)
294
0
    MemoryContextDelete(node->tempContext);
295
0
  if (node->tuplesContext)
296
0
    MemoryContextDelete(node->tuplesContext);
297
298
  /*
299
   * close down subplans
300
   */
301
0
  ExecEndNode(outerPlanState(node));
302
0
  ExecEndNode(innerPlanState(node));
303
0
}
304
305
/* ----------------------------------------------------------------
306
 *    ExecReScanRecursiveUnion
307
 *
308
 *    Rescans the relation.
309
 * ----------------------------------------------------------------
310
 */
311
void
312
ExecReScanRecursiveUnion(RecursiveUnionState *node)
313
0
{
314
0
  PlanState  *outerPlan = outerPlanState(node);
315
0
  PlanState  *innerPlan = innerPlanState(node);
316
0
  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
0
  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
0
  if (outerPlan->chgParam == NULL)
330
0
    ExecReScan(outerPlan);
331
332
  /* Empty hashtable if needed */
333
0
  if (plan->numCols > 0)
334
0
    ResetTupleHashTable(node->hashtable);
335
336
  /* reset processing state */
337
0
  node->recursing = false;
338
  node->intermediate_empty = true;
339
0
  tuplestore_clear(node->working_table);
340
0
  tuplestore_clear(node->intermediate_table);
341
0
}