88 *
99 *
1010 * IDENTIFICATION
11- * $PostgreSQL: pgsql/src/backend/executor/nodeRecursiveunion.c,v 1.1 2008/10/04 21:56:53 tgl Exp $
11+ * $PostgreSQL: pgsql/src/backend/executor/nodeRecursiveunion.c,v 1.2 2008/10/07 19:27:04 tgl Exp $
1212 *
1313 *-------------------------------------------------------------------------
1414 */
1717#include "executor/execdebug.h"
1818#include "executor/nodeRecursiveunion.h"
1919#include "miscadmin.h"
20+ #include "utils/memutils.h"
21+
22+
23+ /*
24+ * To implement UNION (without ALL), we need a hashtable that stores tuples
25+ * already seen. The hash key is computed from the grouping columns.
26+ */
27+ typedef struct RUHashEntryData * RUHashEntry ;
28+
29+ typedef struct RUHashEntryData
30+ {
31+ TupleHashEntryData shared ;/* common header for hash table entries */
32+ }RUHashEntryData ;
33+
34+
35+ /*
36+ * Initialize the hash table to empty.
37+ */
38+ static void
39+ build_hash_table (RecursiveUnionState * rustate )
40+ {
41+ RecursiveUnion * node = (RecursiveUnion * )rustate -> ps .plan ;
42+
43+ Assert (node -> numCols > 0 );
44+ Assert (node -> numGroups > 0 );
45+
46+ rustate -> hashtable = BuildTupleHashTable (node -> numCols ,
47+ node -> dupColIdx ,
48+ rustate -> eqfunctions ,
49+ rustate -> hashfunctions ,
50+ node -> numGroups ,
51+ sizeof (RUHashEntryData ),
52+ rustate -> tableContext ,
53+ rustate -> tempContext );
54+ }
2055
2156
2257/* ----------------------------------------------------------------
@@ -44,49 +79,85 @@ ExecRecursiveUnion(RecursiveUnionState *node)
4479PlanState * innerPlan = innerPlanState (node );
4580RecursiveUnion * plan = (RecursiveUnion * )node -> ps .plan ;
4681TupleTableSlot * slot ;
82+ RUHashEntry entry ;
83+ bool isnew ;
4784
4885/* 1. Evaluate non-recursive term */
4986if (!node -> recursing )
5087{
51- slot = ExecProcNode (outerPlan );
52- if (!TupIsNull (slot ))
88+ for (;;)
5389{
90+ slot = ExecProcNode (outerPlan );
91+ if (TupIsNull (slot ))
92+ break ;
93+ if (plan -> numCols > 0 )
94+ {
95+ /* Find or build hashtable entry for this tuple's group */
96+ entry = (RUHashEntry )
97+ LookupTupleHashEntry (node -> hashtable ,slot ,& isnew );
98+ /* Must reset temp context after each hashtable lookup */
99+ MemoryContextReset (node -> tempContext );
100+ /* Ignore tuple if already seen */
101+ if (!isnew )
102+ continue ;
103+ }
104+ /* Each non-duplicate tuple goes to the working table ... */
54105tuplestore_puttupleslot (node -> working_table ,slot );
106+ /* ... and to the caller */
55107return slot ;
56108}
57109node -> recursing = true;
58110}
59111
60- retry :
61112/* 2. Execute recursive term */
62- slot = ExecProcNode (innerPlan );
63- if (TupIsNull (slot ))
113+ for (;;)
64114{
65- if (node -> intermediate_empty )
66- return NULL ;
115+ slot = ExecProcNode (innerPlan );
116+ if (TupIsNull (slot ))
117+ {
118+ /* Done if there's nothing in the intermediate table */
119+ if (node -> intermediate_empty )
120+ break ;
67121
68- /* done with old working table ... */
69- tuplestore_end (node -> working_table );
122+ /* done with old working table ... */
123+ tuplestore_end (node -> working_table );
70124
71- /* intermediate table becomes working table */
72- node -> working_table = node -> intermediate_table ;
125+ /* intermediate table becomes working table */
126+ node -> working_table = node -> intermediate_table ;
73127
74- /* create new empty intermediate table */
75- node -> intermediate_table = tuplestore_begin_heap (false, false,work_mem );
76- node -> intermediate_empty = true;
128+ /* create new empty intermediate table */
129+ node -> intermediate_table = tuplestore_begin_heap (false, false,
130+ work_mem );
131+ node -> intermediate_empty = true;
77132
78- /* and reset the inner plan */
79- innerPlan -> chgParam = bms_add_member (innerPlan -> chgParam ,
80- plan -> wtParam );
81- gotoretry ;
82- }
83- else
84- {
133+ /* reset the recursive term */
134+ innerPlan -> chgParam = bms_add_member (innerPlan -> chgParam ,
135+ plan -> wtParam );
136+
137+ /* and continue fetching from recursive term */
138+ continue ;
139+ }
140+
141+ if (plan -> numCols > 0 )
142+ {
143+ /* Find or build hashtable entry for this tuple's group */
144+ entry = (RUHashEntry )
145+ LookupTupleHashEntry (node -> hashtable ,slot ,& isnew );
146+ /* Must reset temp context after each hashtable lookup */
147+ MemoryContextReset (node -> tempContext );
148+ /* Ignore tuple if already seen */
149+ if (!isnew )
150+ continue ;
151+ }
152+
153+ /* Else, tuple is good; stash it in intermediate table ... */
85154node -> intermediate_empty = false;
86155tuplestore_puttupleslot (node -> intermediate_table ,slot );
87- }
156+ /* ... and return it */
157+ return slot ;
158+ }
88159
89- return slot ;
160+ return NULL ;
90161}
91162
92163/* ----------------------------------------------------------------
@@ -109,12 +180,40 @@ ExecInitRecursiveUnion(RecursiveUnion *node, EState *estate, int eflags)
109180rustate -> ps .plan = (Plan * )node ;
110181rustate -> ps .state = estate ;
111182
183+ rustate -> eqfunctions = NULL ;
184+ rustate -> hashfunctions = NULL ;
185+ rustate -> hashtable = NULL ;
186+ rustate -> tempContext = NULL ;
187+ rustate -> tableContext = NULL ;
188+
112189/* initialize processing state */
113190rustate -> recursing = false;
114191rustate -> intermediate_empty = true;
115192rustate -> working_table = tuplestore_begin_heap (false, false,work_mem );
116193rustate -> intermediate_table = tuplestore_begin_heap (false, false,work_mem );
117194
195+ /*
196+ * If hashing, we need a per-tuple memory context for comparisons, and a
197+ * longer-lived context to store the hash table. The table can't just be
198+ * kept in the per-query context because we want to be able to throw it
199+ * away when rescanning.
200+ */
201+ if (node -> numCols > 0 )
202+ {
203+ rustate -> tempContext =
204+ AllocSetContextCreate (CurrentMemoryContext ,
205+ "RecursiveUnion" ,
206+ ALLOCSET_DEFAULT_MINSIZE ,
207+ ALLOCSET_DEFAULT_INITSIZE ,
208+ ALLOCSET_DEFAULT_MAXSIZE );
209+ rustate -> tableContext =
210+ AllocSetContextCreate (CurrentMemoryContext ,
211+ "RecursiveUnion hash table" ,
212+ ALLOCSET_DEFAULT_MINSIZE ,
213+ ALLOCSET_DEFAULT_INITSIZE ,
214+ ALLOCSET_DEFAULT_MAXSIZE );
215+ }
216+
118217/*
119218 * Make the state structure available to descendant WorkTableScan nodes
120219 * via the Param slot reserved for it.
@@ -154,6 +253,19 @@ ExecInitRecursiveUnion(RecursiveUnion *node, EState *estate, int eflags)
154253outerPlanState (rustate )= ExecInitNode (outerPlan (node ),estate ,eflags );
155254innerPlanState (rustate )= ExecInitNode (innerPlan (node ),estate ,eflags );
156255
256+ /*
257+ * If hashing, precompute fmgr lookup data for inner loop, and create
258+ * the hash table.
259+ */
260+ if (node -> numCols > 0 )
261+ {
262+ execTuplesHashPrepare (node -> numCols ,
263+ node -> dupOperators ,
264+ & rustate -> eqfunctions ,
265+ & rustate -> hashfunctions );
266+ build_hash_table (rustate );
267+ }
268+
157269return rustate ;
158270}
159271
@@ -178,6 +290,12 @@ ExecEndRecursiveUnion(RecursiveUnionState *node)
178290tuplestore_end (node -> working_table );
179291tuplestore_end (node -> intermediate_table );
180292
293+ /* free subsidiary stuff including hashtable */
294+ if (node -> tempContext )
295+ MemoryContextDelete (node -> tempContext );
296+ if (node -> tableContext )
297+ MemoryContextDelete (node -> tableContext );
298+
181299/*
182300 * clean out the upper tuple table
183301 */
@@ -217,6 +335,14 @@ ExecRecursiveUnionReScan(RecursiveUnionState *node, ExprContext *exprCtxt)
217335if (outerPlan -> chgParam == NULL )
218336ExecReScan (outerPlan ,exprCtxt );
219337
338+ /* Release any hashtable storage */
339+ if (node -> tableContext )
340+ MemoryContextResetAndDeleteChildren (node -> tableContext );
341+
342+ /* And rebuild empty hashtable if needed */
343+ if (plan -> numCols > 0 )
344+ build_hash_table (node );
345+
220346/* reset processing state */
221347node -> recursing = false;
222348node -> intermediate_empty = true;