Coverage Report

Created: 2025-09-27 06:52

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-2025, 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
build_hash_table(RecursiveUnionState *rustate)
33
0
{
34
0
  RecursiveUnion *node = (RecursiveUnion *) rustate->ps.plan;
35
0
  TupleDesc desc = ExecGetResultType(outerPlanState(rustate));
36
37
0
  Assert(node->numCols > 0);
38
0
  Assert(node->numGroups > 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->tableContext,
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->tableContext = 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.
213
   */
214
0
  if (node->numCols > 0)
215
0
  {
216
0
    rustate->tempContext =
217
0
      AllocSetContextCreate(CurrentMemoryContext,
218
0
                  "RecursiveUnion",
219
0
                  ALLOCSET_DEFAULT_SIZES);
220
0
    rustate->tableContext =
221
0
      AllocSetContextCreate(CurrentMemoryContext,
222
0
                  "RecursiveUnion hash table",
223
0
                  ALLOCSET_DEFAULT_SIZES);
224
0
  }
225
226
  /*
227
   * Make the state structure available to descendant WorkTableScan nodes
228
   * via the Param slot reserved for it.
229
   */
230
0
  prmdata = &(estate->es_param_exec_vals[node->wtParam]);
231
0
  Assert(prmdata->execPlan == NULL);
232
0
  prmdata->value = PointerGetDatum(rustate);
233
0
  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
0
  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
0
  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
0
  rustate->ps.ps_ProjInfo = NULL;
255
256
  /*
257
   * initialize child nodes
258
   */
259
0
  outerPlanState(rustate) = ExecInitNode(outerPlan(node), estate, eflags);
260
0
  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
0
  if (node->numCols > 0)
267
0
  {
268
0
    execTuplesHashPrepare(node->numCols,
269
0
                node->dupOperators,
270
0
                &rustate->eqfuncoids,
271
0
                &rustate->hashfunctions);
272
0
    build_hash_table(rustate);
273
0
  }
274
275
0
  return rustate;
276
0
}
277
278
/* ----------------------------------------------------------------
279
 *    ExecEndRecursiveUnion
280
 *
281
 *    frees any storage allocated through C routines.
282
 * ----------------------------------------------------------------
283
 */
284
void
285
ExecEndRecursiveUnion(RecursiveUnionState *node)
286
0
{
287
  /* Release tuplestores */
288
0
  tuplestore_end(node->working_table);
289
0
  tuplestore_end(node->intermediate_table);
290
291
  /* free subsidiary stuff including hashtable */
292
0
  if (node->tempContext)
293
0
    MemoryContextDelete(node->tempContext);
294
0
  if (node->tableContext)
295
0
    MemoryContextDelete(node->tableContext);
296
297
  /*
298
   * close down subplans
299
   */
300
0
  ExecEndNode(outerPlanState(node));
301
0
  ExecEndNode(innerPlanState(node));
302
0
}
303
304
/* ----------------------------------------------------------------
305
 *    ExecReScanRecursiveUnion
306
 *
307
 *    Rescans the relation.
308
 * ----------------------------------------------------------------
309
 */
310
void
311
ExecReScanRecursiveUnion(RecursiveUnionState *node)
312
0
{
313
0
  PlanState  *outerPlan = outerPlanState(node);
314
0
  PlanState  *innerPlan = innerPlanState(node);
315
0
  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
0
  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
0
  if (outerPlan->chgParam == NULL)
329
0
    ExecReScan(outerPlan);
330
331
  /* Release any hashtable storage */
332
0
  if (node->tableContext)
333
0
    MemoryContextReset(node->tableContext);
334
335
  /* Empty hashtable if needed */
336
0
  if (plan->numCols > 0)
337
0
    ResetTupleHashTable(node->hashtable);
338
339
  /* reset processing state */
340
0
  node->recursing = false;
341
  node->intermediate_empty = true;
342
0
  tuplestore_clear(node->working_table);
343
0
  tuplestore_clear(node->intermediate_table);
344
0
}