/Users/deen/code/yugabyte-db/src/postgres/src/backend/executor/nodeSetOp.c
Line | Count | Source (jump to first uncovered line) |
1 | | /*------------------------------------------------------------------------- |
2 | | * |
3 | | * nodeSetOp.c |
4 | | * Routines to handle INTERSECT and EXCEPT selection |
5 | | * |
6 | | * The input of a SetOp node consists of tuples from two relations, |
7 | | * which have been combined into one dataset, with a junk attribute added |
8 | | * that shows which relation each tuple came from. In SETOP_SORTED mode, |
9 | | * the input has furthermore been sorted according to all the grouping |
10 | | * columns (ie, all the non-junk attributes). The SetOp node scans each |
11 | | * group of identical tuples to determine how many came from each input |
12 | | * relation. Then it is a simple matter to emit the output demanded by the |
13 | | * SQL spec for INTERSECT, INTERSECT ALL, EXCEPT, or EXCEPT ALL. |
14 | | * |
15 | | * In SETOP_HASHED mode, the input is delivered in no particular order, |
16 | | * except that we know all the tuples from one input relation will come before |
17 | | * all the tuples of the other. The planner guarantees that the first input |
18 | | * relation is the left-hand one for EXCEPT, and tries to make the smaller |
19 | | * input relation come first for INTERSECT. We build a hash table in memory |
20 | | * with one entry for each group of identical tuples, and count the number of |
21 | | * tuples in the group from each relation. After seeing all the input, we |
22 | | * scan the hashtable and generate the correct output using those counts. |
23 | | * We can avoid making hashtable entries for any tuples appearing only in the |
24 | | * second input relation, since they cannot result in any output. |
25 | | * |
26 | | * This node type is not used for UNION or UNION ALL, since those can be |
27 | | * implemented more cheaply (there's no need for the junk attribute to |
28 | | * identify the source relation). |
29 | | * |
30 | | * Note that SetOp does no qual checking nor projection. The delivered |
31 | | * output tuples are just copies of the first-to-arrive tuple in each |
32 | | * input group. |
33 | | * |
34 | | * |
35 | | * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group |
36 | | * Portions Copyright (c) 1994, Regents of the University of California |
37 | | * |
38 | | * |
39 | | * IDENTIFICATION |
40 | | * src/backend/executor/nodeSetOp.c |
41 | | * |
42 | | *------------------------------------------------------------------------- |
43 | | */ |
44 | | |
45 | | #include "postgres.h" |
46 | | |
47 | | #include "access/htup_details.h" |
48 | | #include "executor/executor.h" |
49 | | #include "executor/nodeSetOp.h" |
50 | | #include "miscadmin.h" |
51 | | #include "utils/memutils.h" |
52 | | |
53 | | |
54 | | /* |
55 | | * SetOpStatePerGroupData - per-group working state |
56 | | * |
57 | | * These values are working state that is initialized at the start of |
58 | | * an input tuple group and updated for each input tuple. |
59 | | * |
60 | | * In SETOP_SORTED mode, we need only one of these structs, and it's kept in |
61 | | * the plan state node. In SETOP_HASHED mode, the hash table contains one |
62 | | * of these for each tuple group. |
63 | | */ |
64 | | typedef struct SetOpStatePerGroupData |
65 | | { |
66 | | long numLeft; /* number of left-input dups in group */ |
67 | | long numRight; /* number of right-input dups in group */ |
68 | | } SetOpStatePerGroupData; |
69 | | |
70 | | |
71 | | static TupleTableSlot *setop_retrieve_direct(SetOpState *setopstate); |
72 | | static void setop_fill_hash_table(SetOpState *setopstate); |
73 | | static TupleTableSlot *setop_retrieve_hash_table(SetOpState *setopstate); |
74 | | |
75 | | |
76 | | /* |
77 | | * Initialize state for a new group of input values. |
78 | | */ |
79 | | static inline void |
80 | | initialize_counts(SetOpStatePerGroup pergroup) |
81 | 71 | { |
82 | 71 | pergroup->numLeft = pergroup->numRight = 0; |
83 | 71 | } |
84 | | |
85 | | /* |
86 | | * Advance the appropriate counter for one input tuple. |
87 | | */ |
88 | | static inline void |
89 | | advance_counts(SetOpStatePerGroup pergroup, int flag) |
90 | 111 | { |
91 | 111 | if (flag) |
92 | 41 | pergroup->numRight++; |
93 | 70 | else |
94 | 70 | pergroup->numLeft++; |
95 | 111 | } |
96 | | |
97 | | /* |
98 | | * Fetch the "flag" column from an input tuple. |
99 | | * This is an integer column with value 0 for left side, 1 for right side. |
100 | | */ |
101 | | static int |
102 | | fetch_tuple_flag(SetOpState *setopstate, TupleTableSlot *inputslot) |
103 | 137 | { |
104 | 137 | SetOp *node = (SetOp *) setopstate->ps.plan; |
105 | 137 | int flag; |
106 | 137 | bool isNull; |
107 | | |
108 | 137 | flag = DatumGetInt32(slot_getattr(inputslot, |
109 | 137 | node->flagColIdx, |
110 | 137 | &isNull)); |
111 | 137 | Assert(!isNull); |
112 | 137 | Assert(flag == 0 || flag == 1); |
113 | 137 | return flag; |
114 | 137 | } |
115 | | |
116 | | /* |
117 | | * Initialize the hash table to empty. |
118 | | */ |
119 | | static void |
120 | | build_hash_table(SetOpState *setopstate) |
121 | 18 | { |
122 | 18 | SetOp *node = (SetOp *) setopstate->ps.plan; |
123 | 18 | ExprContext *econtext = setopstate->ps.ps_ExprContext; |
124 | 18 | TupleDesc desc = ExecGetResultType(outerPlanState(setopstate)); |
125 | | |
126 | 18 | Assert(node->strategy == SETOP_HASHED); |
127 | 18 | Assert(node->numGroups > 0); |
128 | | |
129 | 18 | setopstate->hashtable = BuildTupleHashTableExt(&setopstate->ps, |
130 | 18 | desc, |
131 | 18 | node->numCols, |
132 | 18 | node->dupColIdx, |
133 | 18 | setopstate->eqfuncoids, |
134 | 18 | setopstate->hashfunctions, |
135 | 18 | node->numGroups, |
136 | 18 | 0, |
137 | 18 | setopstate->ps.state->es_query_cxt, |
138 | 18 | setopstate->tableContext, |
139 | 18 | econtext->ecxt_per_tuple_memory, |
140 | 18 | false); |
141 | 18 | } |
142 | | |
143 | | /* |
144 | | * We've completed processing a tuple group. Decide how many copies (if any) |
145 | | * of its representative row to emit, and store the count into numOutput. |
146 | | * This logic is straight from the SQL92 specification. |
147 | | */ |
148 | | static void |
149 | | set_output_count(SetOpState *setopstate, SetOpStatePerGroup pergroup) |
150 | 71 | { |
151 | 71 | SetOp *plannode = (SetOp *) setopstate->ps.plan; |
152 | | |
153 | 71 | switch (plannode->cmd) |
154 | 71 | { |
155 | 23 | case SETOPCMD_INTERSECT: |
156 | 23 | if (pergroup->numLeft > 0 && pergroup->numRight > 0) |
157 | 11 | setopstate->numOutput = 1; |
158 | 12 | else |
159 | 12 | setopstate->numOutput = 0; |
160 | 23 | break; |
161 | 0 | case SETOPCMD_INTERSECT_ALL: |
162 | 0 | setopstate->numOutput = |
163 | 0 | (pergroup->numLeft < pergroup->numRight) ? |
164 | 0 | pergroup->numLeft : pergroup->numRight; |
165 | 0 | break; |
166 | 48 | case SETOPCMD_EXCEPT: |
167 | 48 | if (pergroup->numLeft > 0 && pergroup->numRight == 047 ) |
168 | 18 | setopstate->numOutput = 1; |
169 | 30 | else |
170 | 30 | setopstate->numOutput = 0; |
171 | 48 | break; |
172 | 0 | case SETOPCMD_EXCEPT_ALL: |
173 | 0 | setopstate->numOutput = |
174 | 0 | (pergroup->numLeft < pergroup->numRight) ? |
175 | 0 | 0 : (pergroup->numLeft - pergroup->numRight); |
176 | 0 | break; |
177 | 0 | default: |
178 | 0 | elog(ERROR, "unrecognized set op: %d", (int) plannode->cmd); |
179 | 0 | break; |
180 | 71 | } |
181 | 71 | } |
182 | | |
183 | | |
184 | | /* ---------------------------------------------------------------- |
185 | | * ExecSetOp |
186 | | * ---------------------------------------------------------------- |
187 | | */ |
188 | | static TupleTableSlot * /* return: a tuple or NULL */ |
189 | | ExecSetOp(PlanState *pstate) |
190 | 51 | { |
191 | 51 | SetOpState *node = castNode(SetOpState, pstate); |
192 | 51 | SetOp *plannode = (SetOp *) node->ps.plan; |
193 | 51 | TupleTableSlot *resultTupleSlot = node->ps.ps_ResultTupleSlot; |
194 | | |
195 | 51 | CHECK_FOR_INTERRUPTS(); |
196 | | |
197 | | /* |
198 | | * If the previously-returned tuple needs to be returned more than once, |
199 | | * keep returning it. |
200 | | */ |
201 | 51 | if (node->numOutput > 0) |
202 | 0 | { |
203 | 0 | node->numOutput--; |
204 | 0 | return resultTupleSlot; |
205 | 0 | } |
206 | | |
207 | | /* Otherwise, we're done if we are out of groups */ |
208 | 51 | if (node->setop_done) |
209 | 0 | return NULL; |
210 | | |
211 | | /* Fetch the next tuple group according to the correct strategy */ |
212 | 51 | if (plannode->strategy == SETOP_HASHED) |
213 | 46 | { |
214 | 46 | if (!node->table_filled) |
215 | 20 | setop_fill_hash_table(node); |
216 | 46 | return setop_retrieve_hash_table(node); |
217 | 46 | } |
218 | 5 | else |
219 | 5 | return setop_retrieve_direct(node); |
220 | 51 | } |
221 | | |
222 | | /* |
223 | | * ExecSetOp for non-hashed case |
224 | | */ |
225 | | static TupleTableSlot * |
226 | | setop_retrieve_direct(SetOpState *setopstate) |
227 | 5 | { |
228 | 5 | PlanState *outerPlan; |
229 | 5 | SetOpStatePerGroup pergroup; |
230 | 5 | TupleTableSlot *outerslot; |
231 | 5 | TupleTableSlot *resultTupleSlot; |
232 | 5 | ExprContext *econtext = setopstate->ps.ps_ExprContext; |
233 | | |
234 | | /* |
235 | | * get state info from node |
236 | | */ |
237 | 5 | outerPlan = outerPlanState(setopstate); |
238 | 5 | pergroup = (SetOpStatePerGroup) setopstate->pergroup; |
239 | 5 | resultTupleSlot = setopstate->ps.ps_ResultTupleSlot; |
240 | | |
241 | | /* |
242 | | * We loop retrieving groups until we find one we should return |
243 | | */ |
244 | 7 | while (!setopstate->setop_done) |
245 | 5 | { |
246 | | /* |
247 | | * If we don't already have the first tuple of the new group, fetch it |
248 | | * from the outer plan. |
249 | | */ |
250 | 5 | if (setopstate->grp_firstTuple == NULL) |
251 | 2 | { |
252 | 2 | outerslot = ExecProcNode(outerPlan); |
253 | 2 | if (!TupIsNull(outerslot)) |
254 | 2 | { |
255 | | /* Make a copy of the first input tuple */ |
256 | 2 | setopstate->grp_firstTuple = ExecCopySlotTuple(outerslot); |
257 | 2 | } |
258 | 0 | else |
259 | 0 | { |
260 | | /* outer plan produced no tuples at all */ |
261 | 0 | setopstate->setop_done = true; |
262 | 0 | return NULL; |
263 | 0 | } |
264 | 2 | } |
265 | | |
266 | | /* |
267 | | * Store the copied first input tuple in the tuple table slot reserved |
268 | | * for it. The tuple will be deleted when it is cleared from the |
269 | | * slot. |
270 | | */ |
271 | 5 | ExecStoreHeapTuple(setopstate->grp_firstTuple, |
272 | 5 | resultTupleSlot, |
273 | 5 | true); |
274 | 5 | setopstate->grp_firstTuple = NULL; /* don't keep two pointers */ |
275 | | |
276 | | /* Initialize working state for a new input tuple group */ |
277 | 5 | initialize_counts(pergroup); |
278 | | |
279 | | /* Count the first input tuple */ |
280 | 5 | advance_counts(pergroup, |
281 | 5 | fetch_tuple_flag(setopstate, resultTupleSlot)); |
282 | | |
283 | | /* |
284 | | * Scan the outer plan until we exhaust it or cross a group boundary. |
285 | | */ |
286 | 5 | for (;;) |
287 | 7 | { |
288 | 7 | outerslot = ExecProcNode(outerPlan); |
289 | 7 | if (TupIsNull(outerslot)) |
290 | 2 | { |
291 | | /* no more outer-plan tuples available */ |
292 | 2 | setopstate->setop_done = true; |
293 | 2 | break; |
294 | 2 | } |
295 | | |
296 | | /* |
297 | | * Check whether we've crossed a group boundary. |
298 | | */ |
299 | 5 | econtext->ecxt_outertuple = resultTupleSlot; |
300 | 5 | econtext->ecxt_innertuple = outerslot; |
301 | | |
302 | 5 | if (!ExecQualAndReset(setopstate->eqfunction, econtext)) |
303 | 3 | { |
304 | | /* |
305 | | * Save the first input tuple of the next group. |
306 | | */ |
307 | 3 | setopstate->grp_firstTuple = ExecCopySlotTuple(outerslot); |
308 | 3 | break; |
309 | 3 | } |
310 | | |
311 | | /* Still in same group, so count this tuple */ |
312 | 2 | advance_counts(pergroup, |
313 | 2 | fetch_tuple_flag(setopstate, outerslot)); |
314 | 2 | } |
315 | | |
316 | | /* |
317 | | * Done scanning input tuple group. See if we should emit any copies |
318 | | * of result tuple, and if so return the first copy. |
319 | | */ |
320 | 5 | set_output_count(setopstate, pergroup); |
321 | | |
322 | 5 | if (setopstate->numOutput > 0) |
323 | 3 | { |
324 | 3 | setopstate->numOutput--; |
325 | 3 | return resultTupleSlot; |
326 | 3 | } |
327 | 5 | } |
328 | | |
329 | | /* No more groups */ |
330 | 2 | ExecClearTuple(resultTupleSlot); |
331 | 2 | return NULL; |
332 | 5 | } |
333 | | |
334 | | /* |
335 | | * ExecSetOp for hashed case: phase 1, read input and build hash table |
336 | | */ |
337 | | static void |
338 | | setop_fill_hash_table(SetOpState *setopstate) |
339 | 20 | { |
340 | 20 | SetOp *node = (SetOp *) setopstate->ps.plan; |
341 | 20 | PlanState *outerPlan; |
342 | 20 | int firstFlag; |
343 | 20 | bool in_first_rel PG_USED_FOR_ASSERTS_ONLY; |
344 | 20 | ExprContext *econtext = setopstate->ps.ps_ExprContext; |
345 | | |
346 | | /* |
347 | | * get state info from node |
348 | | */ |
349 | 20 | outerPlan = outerPlanState(setopstate); |
350 | 20 | firstFlag = node->firstFlag; |
351 | | /* verify planner didn't mess up */ |
352 | 20 | Assert(firstFlag == 0 || |
353 | 20 | (firstFlag == 1 && |
354 | 20 | (node->cmd == SETOPCMD_INTERSECT || |
355 | 20 | node->cmd == SETOPCMD_INTERSECT_ALL))); |
356 | | |
357 | | /* |
358 | | * Process each outer-plan tuple, and then fetch the next one, until we |
359 | | * exhaust the outer plan. |
360 | | */ |
361 | 20 | in_first_rel = true; |
362 | 20 | for (;;) |
363 | 150 | { |
364 | 150 | TupleTableSlot *outerslot; |
365 | 150 | int flag; |
366 | 150 | TupleHashEntryData *entry; |
367 | 150 | bool isnew; |
368 | | |
369 | 150 | outerslot = ExecProcNode(outerPlan); |
370 | 150 | if (TupIsNull(outerslot)) |
371 | 20 | break; |
372 | | |
373 | | /* Identify whether it's left or right input */ |
374 | 130 | flag = fetch_tuple_flag(setopstate, outerslot); |
375 | | |
376 | 130 | if (flag == firstFlag) |
377 | 66 | { |
378 | | /* (still) in first input relation */ |
379 | 66 | Assert(in_first_rel); |
380 | | |
381 | | /* Find or build hashtable entry for this tuple's group */ |
382 | 66 | entry = LookupTupleHashEntry(setopstate->hashtable, outerslot, |
383 | 66 | &isnew); |
384 | | |
385 | | /* If new tuple group, initialize counts */ |
386 | 66 | if (isnew) |
387 | 66 | { |
388 | 66 | entry->additional = (SetOpStatePerGroup) |
389 | 66 | MemoryContextAlloc(setopstate->hashtable->tablecxt, |
390 | 66 | sizeof(SetOpStatePerGroupData)); |
391 | 66 | initialize_counts((SetOpStatePerGroup) entry->additional); |
392 | 66 | } |
393 | | |
394 | | /* Advance the counts */ |
395 | 66 | advance_counts((SetOpStatePerGroup) entry->additional, flag); |
396 | 66 | } |
397 | 64 | else |
398 | 64 | { |
399 | | /* reached second relation */ |
400 | 64 | in_first_rel = false; |
401 | | |
402 | | /* For tuples not seen previously, do not make hashtable entry */ |
403 | 64 | entry = LookupTupleHashEntry(setopstate->hashtable, outerslot, |
404 | 64 | NULL); |
405 | | |
406 | | /* Advance the counts if entry is already present */ |
407 | 64 | if (entry) |
408 | 38 | advance_counts((SetOpStatePerGroup) entry->additional, flag); |
409 | 64 | } |
410 | | |
411 | | /* Must reset expression context after each hashtable lookup */ |
412 | 130 | ResetExprContext(econtext); |
413 | 130 | } |
414 | | |
415 | 20 | setopstate->table_filled = true; |
416 | | /* Initialize to walk the hash table */ |
417 | 20 | ResetTupleHashIterator(setopstate->hashtable, &setopstate->hashiter); |
418 | 20 | } |
419 | | |
420 | | /* |
421 | | * ExecSetOp for hashed case: phase 2, retrieving groups from hash table |
422 | | */ |
423 | | static TupleTableSlot * |
424 | | setop_retrieve_hash_table(SetOpState *setopstate) |
425 | 46 | { |
426 | 46 | TupleHashEntryData *entry; |
427 | 46 | TupleTableSlot *resultTupleSlot; |
428 | | |
429 | | /* |
430 | | * get state info from node |
431 | | */ |
432 | 46 | resultTupleSlot = setopstate->ps.ps_ResultTupleSlot; |
433 | | |
434 | | /* |
435 | | * We loop retrieving groups until we find one we should return |
436 | | */ |
437 | 86 | while (!setopstate->setop_done) |
438 | 86 | { |
439 | 86 | CHECK_FOR_INTERRUPTS(); |
440 | | |
441 | | /* |
442 | | * Find the next entry in the hash table |
443 | | */ |
444 | 86 | entry = ScanTupleHashTable(setopstate->hashtable, &setopstate->hashiter); |
445 | 86 | if (entry == NULL) |
446 | 20 | { |
447 | | /* No more entries in hashtable, so done */ |
448 | 20 | setopstate->setop_done = true; |
449 | 20 | return NULL; |
450 | 20 | } |
451 | | |
452 | | /* |
453 | | * See if we should emit any copies of this tuple, and if so return |
454 | | * the first copy. |
455 | | */ |
456 | 66 | set_output_count(setopstate, (SetOpStatePerGroup) entry->additional); |
457 | | |
458 | 66 | if (setopstate->numOutput > 0) |
459 | 26 | { |
460 | 26 | setopstate->numOutput--; |
461 | 26 | return ExecStoreMinimalTuple(entry->firstTuple, |
462 | 26 | resultTupleSlot, |
463 | 26 | false); |
464 | 26 | } |
465 | 66 | } |
466 | | |
467 | | /* No more groups */ |
468 | 0 | ExecClearTuple(resultTupleSlot); |
469 | 0 | return NULL; |
470 | 46 | } |
471 | | |
472 | | /* ---------------------------------------------------------------- |
473 | | * ExecInitSetOp |
474 | | * |
475 | | * This initializes the setop node state structures and |
476 | | * the node's subplan. |
477 | | * ---------------------------------------------------------------- |
478 | | */ |
479 | | SetOpState * |
480 | | ExecInitSetOp(SetOp *node, EState *estate, int eflags) |
481 | 21 | { |
482 | 21 | SetOpState *setopstate; |
483 | 21 | TupleDesc outerDesc; |
484 | | |
485 | | /* check for unsupported flags */ |
486 | 21 | Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK))); |
487 | | |
488 | | /* |
489 | | * create state structure |
490 | | */ |
491 | 21 | setopstate = makeNode(SetOpState); |
492 | 0 | setopstate->ps.plan = (Plan *) node; |
493 | 21 | setopstate->ps.state = estate; |
494 | 21 | setopstate->ps.ExecProcNode = ExecSetOp; |
495 | | |
496 | 21 | setopstate->eqfuncoids = NULL; |
497 | 21 | setopstate->hashfunctions = NULL; |
498 | 21 | setopstate->setop_done = false; |
499 | 21 | setopstate->numOutput = 0; |
500 | 21 | setopstate->pergroup = NULL; |
501 | 21 | setopstate->grp_firstTuple = NULL; |
502 | 21 | setopstate->hashtable = NULL; |
503 | 21 | setopstate->tableContext = NULL; |
504 | | |
505 | | /* |
506 | | * create expression context |
507 | | */ |
508 | 21 | ExecAssignExprContext(estate, &setopstate->ps); |
509 | | |
510 | | /* |
511 | | * If hashing, we also need a longer-lived context to store the hash |
512 | | * table. The table can't just be kept in the per-query context because |
513 | | * we want to be able to throw it away in ExecReScanSetOp. |
514 | | */ |
515 | 21 | if (node->strategy == SETOP_HASHED) |
516 | 18 | setopstate->tableContext = |
517 | 18 | AllocSetContextCreate(GetCurrentMemoryContext(), |
518 | 21 | "SetOp hash table", |
519 | 21 | ALLOCSET_DEFAULT_SIZES); |
520 | | |
521 | | /* |
522 | | * initialize child nodes |
523 | | * |
524 | | * If we are hashing then the child plan does not need to handle REWIND |
525 | | * efficiently; see ExecReScanSetOp. |
526 | | */ |
527 | 21 | if (node->strategy == SETOP_HASHED) |
528 | 18 | eflags &= ~EXEC_FLAG_REWIND; |
529 | 21 | outerPlanState(setopstate) = ExecInitNode(outerPlan(node), estate, eflags); |
530 | 21 | outerDesc = ExecGetResultType(outerPlanState(setopstate)); |
531 | | |
532 | | /* |
533 | | * Initialize result slot and type. Setop nodes do no projections, so |
534 | | * initialize projection info for this node appropriately. |
535 | | */ |
536 | 21 | ExecInitResultTupleSlotTL(&setopstate->ps); |
537 | 21 | setopstate->ps.ps_ProjInfo = NULL; |
538 | | |
539 | | /* |
540 | | * Precompute fmgr lookup data for inner loop. We need both equality and |
541 | | * hashing functions to do it by hashing, but only equality if not |
542 | | * hashing. |
543 | | */ |
544 | 21 | if (node->strategy == SETOP_HASHED) |
545 | 18 | execTuplesHashPrepare(node->numCols, |
546 | 18 | node->dupOperators, |
547 | 18 | &setopstate->eqfuncoids, |
548 | 18 | &setopstate->hashfunctions); |
549 | 3 | else |
550 | 3 | setopstate->eqfunction = |
551 | 3 | execTuplesMatchPrepare(outerDesc, |
552 | 3 | node->numCols, |
553 | 3 | node->dupColIdx, |
554 | 3 | node->dupOperators, |
555 | 3 | &setopstate->ps); |
556 | | |
557 | 21 | if (node->strategy == SETOP_HASHED) |
558 | 18 | { |
559 | 18 | build_hash_table(setopstate); |
560 | 18 | setopstate->table_filled = false; |
561 | 18 | } |
562 | 3 | else |
563 | 3 | { |
564 | 3 | setopstate->pergroup = |
565 | 3 | (SetOpStatePerGroup) palloc0(sizeof(SetOpStatePerGroupData)); |
566 | 3 | } |
567 | | |
568 | 21 | return setopstate; |
569 | 21 | } |
570 | | |
571 | | /* ---------------------------------------------------------------- |
572 | | * ExecEndSetOp |
573 | | * |
574 | | * This shuts down the subplan and frees resources allocated |
575 | | * to this node. |
576 | | * ---------------------------------------------------------------- |
577 | | */ |
578 | | void |
579 | | ExecEndSetOp(SetOpState *node) |
580 | 21 | { |
581 | | /* clean up tuple table */ |
582 | 21 | ExecClearTuple(node->ps.ps_ResultTupleSlot); |
583 | | |
584 | | /* free subsidiary stuff including hashtable */ |
585 | 21 | if (node->tableContext) |
586 | 18 | MemoryContextDelete(node->tableContext); |
587 | 21 | ExecFreeExprContext(&node->ps); |
588 | | |
589 | 21 | ExecEndNode(outerPlanState(node)); |
590 | 21 | } |
591 | | |
592 | | |
593 | | void |
594 | | ExecReScanSetOp(SetOpState *node) |
595 | 14 | { |
596 | 14 | ExecClearTuple(node->ps.ps_ResultTupleSlot); |
597 | 14 | node->setop_done = false; |
598 | 14 | node->numOutput = 0; |
599 | | |
600 | 14 | if (((SetOp *) node->ps.plan)->strategy == SETOP_HASHED) |
601 | 14 | { |
602 | | /* |
603 | | * In the hashed case, if we haven't yet built the hash table then we |
604 | | * can just return; nothing done yet, so nothing to undo. If subnode's |
605 | | * chgParam is not NULL then it will be re-scanned by ExecProcNode, |
606 | | * else no reason to re-scan it at all. |
607 | | */ |
608 | 14 | if (!node->table_filled) |
609 | 12 | return; |
610 | | |
611 | | /* |
612 | | * If we do have the hash table and the subplan does not have any |
613 | | * parameter changes, then we can just rescan the existing hash table; |
614 | | * no need to build it again. |
615 | | */ |
616 | 2 | if (node->ps.lefttree->chgParam == NULL) |
617 | 0 | { |
618 | 0 | ResetTupleHashIterator(node->hashtable, &node->hashiter); |
619 | 0 | return; |
620 | 0 | } |
621 | 2 | } |
622 | | |
623 | | /* Release first tuple of group, if we have made a copy */ |
624 | 2 | if (node->grp_firstTuple != NULL) |
625 | 0 | { |
626 | 0 | heap_freetuple(node->grp_firstTuple); |
627 | 0 | node->grp_firstTuple = NULL; |
628 | 0 | } |
629 | | |
630 | | /* Release any hashtable storage */ |
631 | 2 | if (node->tableContext) |
632 | 2 | MemoryContextResetAndDeleteChildren(node->tableContext); |
633 | | |
634 | | /* And rebuild empty hashtable if needed */ |
635 | 2 | if (((SetOp *) node->ps.plan)->strategy == SETOP_HASHED) |
636 | 2 | { |
637 | 2 | ResetTupleHashTable(node->hashtable); |
638 | 2 | node->table_filled = false; |
639 | 2 | } |
640 | | |
641 | | /* |
642 | | * if chgParam of subnode is not null then plan will be re-scanned by |
643 | | * first ExecProcNode. |
644 | | */ |
645 | 2 | if (node->ps.lefttree->chgParam == NULL) |
646 | 0 | ExecReScan(node->ps.lefttree); |
647 | 2 | } |