1 /*-------------------------------------------------------------------------
4 * routines to handle RecursiveUnion nodes.
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.
10 * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
11 * Portions Copyright (c) 1994, Regents of the University of California
15 * src/backend/executor/nodeRecursiveunion.c
17 *-------------------------------------------------------------------------
21 #include "executor/executor.h"
22 #include "executor/nodeRecursiveunion.h"
23 #include "miscadmin.h"
24 #include "utils/memutils.h"
29 * Initialize the hash table to empty.
32 build_hash_table(RecursiveUnionState
*rustate
)
34 RecursiveUnion
*node
= (RecursiveUnion
*) rustate
->ps
.plan
;
35 TupleDesc desc
= ExecGetResultType(outerPlanState(rustate
));
37 Assert(node
->numCols
> 0);
38 Assert(node
->numGroups
> 0);
40 rustate
->hashtable
= BuildTupleHashTableExt(&rustate
->ps
,
45 rustate
->hashfunctions
,
49 rustate
->ps
.state
->es_query_cxt
,
50 rustate
->tableContext
,
56 /* ----------------------------------------------------------------
57 * ExecRecursiveUnion(node)
59 * Scans the recursive query sequentially and returns the next
62 * 1. evaluate non recursive term and assign the result to RT
64 * 2. execute recursive terms
67 * 2.2 while WT is not empty repeat 2.3 to 2.6. if WT is empty returns RT
68 * 2.3 replace the name of recursive term with WT
69 * 2.4 evaluate the recursive term and store into WT
72 * ----------------------------------------------------------------
74 static TupleTableSlot
*
75 ExecRecursiveUnion(PlanState
*pstate
)
77 RecursiveUnionState
*node
= castNode(RecursiveUnionState
, pstate
);
78 PlanState
*outerPlan
= outerPlanState(node
);
79 PlanState
*innerPlan
= innerPlanState(node
);
80 RecursiveUnion
*plan
= (RecursiveUnion
*) node
->ps
.plan
;
84 CHECK_FOR_INTERRUPTS();
86 /* 1. Evaluate non-recursive term */
91 slot
= ExecProcNode(outerPlan
);
94 if (plan
->numCols
> 0)
96 /* Find or build hashtable entry for this tuple's group */
97 LookupTupleHashEntry(node
->hashtable
, slot
, &isnew
, NULL
);
98 /* Must reset temp context after each hashtable lookup */
99 MemoryContextReset(node
->tempContext
);
100 /* Ignore tuple if already seen */
104 /* Each non-duplicate tuple goes to the working table ... */
105 tuplestore_puttupleslot(node
->working_table
, slot
);
106 /* ... and to the caller */
109 node
->recursing
= true;
112 /* 2. Execute recursive term */
115 slot
= ExecProcNode(innerPlan
);
118 Tuplestorestate
*swaptemp
;
120 /* Done if there's nothing in the intermediate table */
121 if (node
->intermediate_empty
)
125 * Now we let the intermediate table become the work table. We
126 * need a fresh intermediate table, so delete the tuples from the
127 * current working table and use that as the new intermediate
128 * table. This saves a round of free/malloc from creating a new
131 tuplestore_clear(node
->working_table
);
133 swaptemp
= node
->working_table
;
134 node
->working_table
= node
->intermediate_table
;
135 node
->intermediate_table
= swaptemp
;
137 /* mark the intermediate table as empty */
138 node
->intermediate_empty
= true;
140 /* reset the recursive term */
141 innerPlan
->chgParam
= bms_add_member(innerPlan
->chgParam
,
144 /* and continue fetching from recursive term */
148 if (plan
->numCols
> 0)
150 /* Find or build hashtable entry for this tuple's group */
151 LookupTupleHashEntry(node
->hashtable
, slot
, &isnew
, NULL
);
152 /* Must reset temp context after each hashtable lookup */
153 MemoryContextReset(node
->tempContext
);
154 /* Ignore tuple if already seen */
159 /* Else, tuple is good; stash it in intermediate table ... */
160 node
->intermediate_empty
= false;
161 tuplestore_puttupleslot(node
->intermediate_table
, slot
);
162 /* ... and return it */
169 /* ----------------------------------------------------------------
170 * ExecInitRecursiveUnion
171 * ----------------------------------------------------------------
173 RecursiveUnionState
*
174 ExecInitRecursiveUnion(RecursiveUnion
*node
, EState
*estate
, int eflags
)
176 RecursiveUnionState
*rustate
;
177 ParamExecData
*prmdata
;
179 /* check for unsupported flags */
180 Assert(!(eflags
& (EXEC_FLAG_BACKWARD
| EXEC_FLAG_MARK
)));
183 * create state structure
185 rustate
= makeNode(RecursiveUnionState
);
186 rustate
->ps
.plan
= (Plan
*) node
;
187 rustate
->ps
.state
= estate
;
188 rustate
->ps
.ExecProcNode
= ExecRecursiveUnion
;
190 rustate
->eqfuncoids
= NULL
;
191 rustate
->hashfunctions
= NULL
;
192 rustate
->hashtable
= NULL
;
193 rustate
->tempContext
= NULL
;
194 rustate
->tableContext
= NULL
;
196 /* initialize processing state */
197 rustate
->recursing
= false;
198 rustate
->intermediate_empty
= true;
199 rustate
->working_table
= tuplestore_begin_heap(false, false, work_mem
);
200 rustate
->intermediate_table
= tuplestore_begin_heap(false, false, work_mem
);
203 * If hashing, we need a per-tuple memory context for comparisons, and a
204 * longer-lived context to store the hash table. The table can't just be
205 * kept in the per-query context because we want to be able to throw it
206 * away when rescanning.
208 if (node
->numCols
> 0)
210 rustate
->tempContext
=
211 AllocSetContextCreate(CurrentMemoryContext
,
213 ALLOCSET_DEFAULT_SIZES
);
214 rustate
->tableContext
=
215 AllocSetContextCreate(CurrentMemoryContext
,
216 "RecursiveUnion hash table",
217 ALLOCSET_DEFAULT_SIZES
);
221 * Make the state structure available to descendant WorkTableScan nodes
222 * via the Param slot reserved for it.
224 prmdata
= &(estate
->es_param_exec_vals
[node
->wtParam
]);
225 Assert(prmdata
->execPlan
== NULL
);
226 prmdata
->value
= PointerGetDatum(rustate
);
227 prmdata
->isnull
= false;
230 * Miscellaneous initialization
232 * RecursiveUnion plans don't have expression contexts because they never
233 * call ExecQual or ExecProject.
235 Assert(node
->plan
.qual
== NIL
);
238 * RecursiveUnion nodes still have Result slots, which hold pointers to
239 * tuples, so we have to initialize them.
241 ExecInitResultTypeTL(&rustate
->ps
);
244 * Initialize result tuple type. (Note: we have to set up the result type
245 * before initializing child nodes, because nodeWorktablescan.c expects it
248 rustate
->ps
.ps_ProjInfo
= NULL
;
251 * initialize child nodes
253 outerPlanState(rustate
) = ExecInitNode(outerPlan(node
), estate
, eflags
);
254 innerPlanState(rustate
) = ExecInitNode(innerPlan(node
), estate
, eflags
);
257 * If hashing, precompute fmgr lookup data for inner loop, and create the
260 if (node
->numCols
> 0)
262 execTuplesHashPrepare(node
->numCols
,
264 &rustate
->eqfuncoids
,
265 &rustate
->hashfunctions
);
266 build_hash_table(rustate
);
272 /* ----------------------------------------------------------------
273 * ExecEndRecursiveUnion
275 * frees any storage allocated through C routines.
276 * ----------------------------------------------------------------
279 ExecEndRecursiveUnion(RecursiveUnionState
*node
)
281 /* Release tuplestores */
282 tuplestore_end(node
->working_table
);
283 tuplestore_end(node
->intermediate_table
);
285 /* free subsidiary stuff including hashtable */
286 if (node
->tempContext
)
287 MemoryContextDelete(node
->tempContext
);
288 if (node
->tableContext
)
289 MemoryContextDelete(node
->tableContext
);
292 * close down subplans
294 ExecEndNode(outerPlanState(node
));
295 ExecEndNode(innerPlanState(node
));
298 /* ----------------------------------------------------------------
299 * ExecReScanRecursiveUnion
301 * Rescans the relation.
302 * ----------------------------------------------------------------
305 ExecReScanRecursiveUnion(RecursiveUnionState
*node
)
307 PlanState
*outerPlan
= outerPlanState(node
);
308 PlanState
*innerPlan
= innerPlanState(node
);
309 RecursiveUnion
*plan
= (RecursiveUnion
*) node
->ps
.plan
;
312 * Set recursive term's chgParam to tell it that we'll modify the working
313 * table and therefore it has to rescan.
315 innerPlan
->chgParam
= bms_add_member(innerPlan
->chgParam
, plan
->wtParam
);
318 * if chgParam of subnode is not null then plan will be re-scanned by
319 * first ExecProcNode. Because of above, we only have to do this to the
320 * non-recursive term.
322 if (outerPlan
->chgParam
== NULL
)
323 ExecReScan(outerPlan
);
325 /* Release any hashtable storage */
326 if (node
->tableContext
)
327 MemoryContextReset(node
->tableContext
);
329 /* Empty hashtable if needed */
330 if (plan
->numCols
> 0)
331 ResetTupleHashTable(node
->hashtable
);
333 /* reset processing state */
334 node
->recursing
= false;
335 node
->intermediate_empty
= true;
336 tuplestore_clear(node
->working_table
);
337 tuplestore_clear(node
->intermediate_table
);