ExplainState *es);
static void show_sort_keys(SortState *sortstate, List *ancestors,
ExplainState *es);
+static void show_merge_append_keys(MergeAppendState *mstate, List *ancestors,
+ ExplainState *es);
+static void show_sort_keys_common(PlanState *planstate,
+ int nkeys, AttrNumber *keycols,
+ List *ancestors, ExplainState *es);
static void show_sort_info(SortState *sortstate, ExplainState *es);
static void show_hash_info(HashState *hashstate, ExplainState *es);
static const char *explain_get_index_name(Oid indexId);
case T_Append:
pname = sname = "Append";
break;
+ case T_MergeAppend:
+ pname = sname = "Merge Append";
+ break;
case T_RecursiveUnion:
pname = sname = "Recursive Union";
break;
show_sort_keys((SortState *) planstate, ancestors, es);
show_sort_info((SortState *) planstate, es);
break;
+ case T_MergeAppend:
+ show_merge_append_keys((MergeAppendState *) planstate,
+ ancestors, es);
+ break;
case T_Result:
show_upper_qual((List *) ((Result *) plan)->resconstantqual,
"One-Time Filter", planstate, ancestors, es);
innerPlanState(planstate) ||
IsA(plan, ModifyTable) ||
IsA(plan, Append) ||
+ IsA(plan, MergeAppend) ||
IsA(plan, BitmapAnd) ||
IsA(plan, BitmapOr) ||
IsA(plan, SubqueryScan) ||
((AppendState *) planstate)->appendplans,
ancestors, es);
break;
+ case T_MergeAppend:
+ ExplainMemberNodes(((MergeAppend *) plan)->mergeplans,
+ ((MergeAppendState *) planstate)->mergeplans,
+ ancestors, es);
+ break;
case T_BitmapAnd:
ExplainMemberNodes(((BitmapAnd *) plan)->bitmapplans,
((BitmapAndState *) planstate)->bitmapplans,
/* The tlist of an Append isn't real helpful, so suppress it */
if (IsA(plan, Append))
return;
- /* Likewise for RecursiveUnion */
+ /* Likewise for MergeAppend and RecursiveUnion */
+ if (IsA(plan, MergeAppend))
+ return;
if (IsA(plan, RecursiveUnion))
return;
show_sort_keys(SortState *sortstate, List *ancestors, ExplainState *es)
{
Sort *plan = (Sort *) sortstate->ss.ps.plan;
- int nkeys = plan->numCols;
- AttrNumber *keycols = plan->sortColIdx;
+
+ show_sort_keys_common((PlanState *) sortstate,
+ plan->numCols, plan->sortColIdx,
+ ancestors, es);
+}
+
+/*
+ * Likewise, for a MergeAppend node.
+ */
+static void
+show_merge_append_keys(MergeAppendState *mstate, List *ancestors,
+ ExplainState *es)
+{
+ MergeAppend *plan = (MergeAppend *) mstate->ps.plan;
+
+ show_sort_keys_common((PlanState *) mstate,
+ plan->numCols, plan->sortColIdx,
+ ancestors, es);
+}
+
+static void
+show_sort_keys_common(PlanState *planstate, int nkeys, AttrNumber *keycols,
+ List *ancestors, ExplainState *es)
+{
+ Plan *plan = planstate->plan;
List *context;
List *result = NIL;
bool useprefix;
return;
/* Set up deparsing context */
- context = deparse_context_for_planstate((Node *) sortstate,
+ context = deparse_context_for_planstate((Node *) planstate,
ancestors,
es->rtable);
useprefix = (list_length(es->rtable) > 1 || es->verbose);
{
/* find key expression in tlist */
AttrNumber keyresno = keycols[keyno];
- TargetEntry *target = get_tle_by_resno(plan->plan.targetlist,
+ TargetEntry *target = get_tle_by_resno(plan->targetlist,
keyresno);
if (!target)
}
/*
- * Explain the constituent plans of a ModifyTable, Append, BitmapAnd,
- * or BitmapOr node.
+ * Explain the constituent plans of a ModifyTable, Append, MergeAppend,
+ * BitmapAnd, or BitmapOr node.
*
* The ancestors list should already contain the immediate parent of these
* plans.
nodeBitmapAnd.o nodeBitmapOr.o \
nodeBitmapHeapscan.o nodeBitmapIndexscan.o nodeHash.o \
nodeHashjoin.o nodeIndexscan.o nodeLimit.o nodeLockRows.o \
- nodeMaterial.o nodeMergejoin.o nodeModifyTable.o \
+ nodeMaterial.o nodeMergeAppend.o nodeMergejoin.o nodeModifyTable.o \
nodeNestloop.o nodeFunctionscan.o nodeRecursiveunion.o nodeResult.o \
nodeSeqscan.o nodeSetOp.o nodeSort.o nodeUnique.o \
nodeValuesscan.o nodeCtescan.o nodeWorktablescan.o \
#include "executor/nodeLimit.h"
#include "executor/nodeLockRows.h"
#include "executor/nodeMaterial.h"
+#include "executor/nodeMergeAppend.h"
#include "executor/nodeMergejoin.h"
#include "executor/nodeModifyTable.h"
#include "executor/nodeNestloop.h"
ExecReScanAppend((AppendState *) node);
break;
+ case T_MergeAppendState:
+ ExecReScanMergeAppend((MergeAppendState *) node);
+ break;
+
case T_RecursiveUnionState:
ExecReScanRecursiveUnion((RecursiveUnionState *) node);
break;
return result;
}
+ /*
+ * Similarly for MergeAppend
+ */
+ case T_MergeAppendState:
+ {
+ MergeAppendState *mstate = (MergeAppendState *) node;
+ ScanState *result = NULL;
+ int i;
+
+ for (i = 0; i < mstate->ms_nplans; i++)
+ {
+ ScanState *elem = search_plan_tree(mstate->mergeplans[i],
+ table_oid);
+
+ if (!elem)
+ continue;
+ if (result)
+ return NULL; /* multiple matches */
+ result = elem;
+ }
+ return result;
+ }
+
/*
* Result and Limit can be descended through (these are safe
* because they always return their input's current row)
#include "executor/nodeLimit.h"
#include "executor/nodeLockRows.h"
#include "executor/nodeMaterial.h"
+#include "executor/nodeMergeAppend.h"
#include "executor/nodeMergejoin.h"
#include "executor/nodeModifyTable.h"
#include "executor/nodeNestloop.h"
estate, eflags);
break;
+ case T_MergeAppend:
+ result = (PlanState *) ExecInitMergeAppend((MergeAppend *) node,
+ estate, eflags);
+ break;
+
case T_RecursiveUnion:
result = (PlanState *) ExecInitRecursiveUnion((RecursiveUnion *) node,
estate, eflags);
result = ExecAppend((AppendState *) node);
break;
+ case T_MergeAppendState:
+ result = ExecMergeAppend((MergeAppendState *) node);
+ break;
+
case T_RecursiveUnionState:
result = ExecRecursiveUnion((RecursiveUnionState *) node);
break;
ExecEndAppend((AppendState *) node);
break;
+ case T_MergeAppendState:
+ ExecEndMergeAppend((MergeAppendState *) node);
+ break;
+
case T_RecursiveUnionState:
ExecEndRecursiveUnion((RecursiveUnionState *) node);
break;
--- /dev/null
+/*-------------------------------------------------------------------------
+ *
+ * nodeMergeAppend.c
+ * routines to handle MergeAppend nodes.
+ *
+ * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/executor/nodeMergeAppend.c
+ *
+ *-------------------------------------------------------------------------
+ */
+/* INTERFACE ROUTINES
+ * ExecInitMergeAppend - initialize the MergeAppend node
+ * ExecMergeAppend - retrieve the next tuple from the node
+ * ExecEndMergeAppend - shut down the MergeAppend node
+ * ExecReScanMergeAppend - rescan the MergeAppend node
+ *
+ * NOTES
+ * A MergeAppend node contains a list of one or more subplans.
+ * These are each expected to deliver tuples that are sorted according
+ * to a common sort key. The MergeAppend node merges these streams
+ * to produce output sorted the same way.
+ *
+ * MergeAppend nodes don't make use of their left and right
+ * subtrees, rather they maintain a list of subplans so
+ * a typical MergeAppend node looks like this in the plan tree:
+ *
+ * ...
+ * /
+ * MergeAppend---+------+------+--- nil
+ * / \ | | |
+ * nil nil ... ... ...
+ * subplans
+ */
+
+#include "postgres.h"
+
+#include "access/nbtree.h"
+#include "executor/execdebug.h"
+#include "executor/nodeMergeAppend.h"
+#include "utils/lsyscache.h"
+
+/*
+ * It gets quite confusing having a heap array (indexed by integers) which
+ * contains integers which index into the slots array. These typedefs try to
+ * clear it up, but they're only documentation.
+ */
+typedef int SlotNumber;
+typedef int HeapPosition;
+
+static void heap_insert_slot(MergeAppendState *node, SlotNumber new_slot);
+static void heap_siftup_slot(MergeAppendState *node);
+static int32 heap_compare_slots(MergeAppendState *node, SlotNumber slot1, SlotNumber slot2);
+
+
+/* ----------------------------------------------------------------
+ * ExecInitMergeAppend
+ *
+ * Begin all of the subscans of the MergeAppend node.
+ * ----------------------------------------------------------------
+ */
+MergeAppendState *
+ExecInitMergeAppend(MergeAppend *node, EState *estate, int eflags)
+{
+ MergeAppendState *mergestate = makeNode(MergeAppendState);
+ PlanState **mergeplanstates;
+ int nplans;
+ int i;
+ ListCell *lc;
+
+ /* check for unsupported flags */
+ Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
+
+ /*
+ * Set up empty vector of subplan states
+ */
+ nplans = list_length(node->mergeplans);
+
+ mergeplanstates = (PlanState **) palloc0(nplans * sizeof(PlanState *));
+
+ /*
+ * create new MergeAppendState for our node
+ */
+ mergestate->ps.plan = (Plan *) node;
+ mergestate->ps.state = estate;
+ mergestate->mergeplans = mergeplanstates;
+ mergestate->ms_nplans = nplans;
+
+ mergestate->ms_slots = (TupleTableSlot **) palloc0(sizeof(TupleTableSlot *) * nplans);
+ mergestate->ms_heap = (int *) palloc0(sizeof(int) * nplans);
+
+ /*
+ * Miscellaneous initialization
+ *
+ * MergeAppend plans don't have expression contexts because they never
+ * call ExecQual or ExecProject.
+ */
+
+ /*
+ * MergeAppend nodes do have Result slots, which hold pointers to tuples,
+ * so we have to initialize them.
+ */
+ ExecInitResultTupleSlot(estate, &mergestate->ps);
+
+ /*
+ * call ExecInitNode on each of the plans to be executed and save the
+ * results into the array "mergeplans".
+ */
+ i = 0;
+ foreach(lc, node->mergeplans)
+ {
+ Plan *initNode = (Plan *) lfirst(lc);
+
+ mergeplanstates[i] = ExecInitNode(initNode, estate, eflags);
+ i++;
+ }
+
+ /*
+ * initialize output tuple type
+ */
+ ExecAssignResultTypeFromTL(&mergestate->ps);
+ mergestate->ps.ps_ProjInfo = NULL;
+
+ /*
+ * initialize sort-key information
+ */
+ mergestate->ms_nkeys = node->numCols;
+ mergestate->ms_scankeys = palloc0(sizeof(ScanKeyData) * node->numCols);
+
+ for (i = 0; i < node->numCols; i++)
+ {
+ Oid sortFunction;
+ bool reverse;
+
+ if (!get_compare_function_for_ordering_op(node->sortOperators[i],
+ &sortFunction, &reverse))
+ elog(ERROR, "operator %u is not a valid ordering operator",
+ node->sortOperators[i]);
+
+ /*
+ * We needn't fill in sk_strategy or sk_subtype since these scankeys
+ * will never be passed to an index.
+ */
+ ScanKeyInit(&mergestate->ms_scankeys[i],
+ node->sortColIdx[i],
+ InvalidStrategy,
+ sortFunction,
+ (Datum) 0);
+
+ /* However, we use btree's conventions for encoding directionality */
+ if (reverse)
+ mergestate->ms_scankeys[i].sk_flags |= SK_BT_DESC;
+ if (node->nullsFirst[i])
+ mergestate->ms_scankeys[i].sk_flags |= SK_BT_NULLS_FIRST;
+ }
+
+ /*
+ * initialize to show we have not run the subplans yet
+ */
+ mergestate->ms_heap_size = 0;
+ mergestate->ms_initialized = false;
+ mergestate->ms_last_slot = -1;
+
+ return mergestate;
+}
+
+/* ----------------------------------------------------------------
+ * ExecMergeAppend
+ *
+ * Handles iteration over multiple subplans.
+ * ----------------------------------------------------------------
+ */
+TupleTableSlot *
+ExecMergeAppend(MergeAppendState *node)
+{
+ TupleTableSlot *result;
+ SlotNumber i;
+
+ if (!node->ms_initialized)
+ {
+ /*
+ * First time through: pull the first tuple from each subplan,
+ * and set up the heap.
+ */
+ for (i = 0; i < node->ms_nplans; i++)
+ {
+ node->ms_slots[i] = ExecProcNode(node->mergeplans[i]);
+ if (!TupIsNull(node->ms_slots[i]))
+ heap_insert_slot(node, i);
+ }
+ node->ms_initialized = true;
+ }
+ else
+ {
+ /*
+ * Otherwise, pull the next tuple from whichever subplan we returned
+ * from last time, and insert it into the heap. (We could simplify
+ * the logic a bit by doing this before returning from the prior call,
+ * but it's better to not pull tuples until necessary.)
+ */
+ i = node->ms_last_slot;
+ node->ms_slots[i] = ExecProcNode(node->mergeplans[i]);
+ if (!TupIsNull(node->ms_slots[i]))
+ heap_insert_slot(node, i);
+ }
+
+ if (node->ms_heap_size > 0)
+ {
+ /* Return the topmost heap node, and sift up the remaining nodes */
+ i = node->ms_heap[0];
+ result = node->ms_slots[i];
+ node->ms_last_slot = i;
+ heap_siftup_slot(node);
+ }
+ else
+ {
+ /* All the subplans are exhausted, and so is the heap */
+ result = ExecClearTuple(node->ps.ps_ResultTupleSlot);
+ }
+
+ return result;
+}
+
+/*
+ * Insert a new slot into the heap. The slot must contain a valid tuple.
+ */
+static void
+heap_insert_slot(MergeAppendState *node, SlotNumber new_slot)
+{
+ SlotNumber *heap = node->ms_heap;
+ HeapPosition j;
+
+ Assert(!TupIsNull(node->ms_slots[new_slot]));
+
+ j = node->ms_heap_size++; /* j is where the "hole" is */
+ while (j > 0)
+ {
+ int i = (j-1)/2;
+
+ if (heap_compare_slots(node, new_slot, node->ms_heap[i]) >= 0)
+ break;
+ heap[j] = heap[i];
+ j = i;
+ }
+ heap[j] = new_slot;
+}
+
+/*
+ * Delete the heap top (the slot in heap[0]), and sift up.
+ */
+static void
+heap_siftup_slot(MergeAppendState *node)
+{
+ SlotNumber *heap = node->ms_heap;
+ HeapPosition i,
+ n;
+
+ if (--node->ms_heap_size <= 0)
+ return;
+ n = node->ms_heap_size; /* heap[n] needs to be reinserted */
+ i = 0; /* i is where the "hole" is */
+ for (;;)
+ {
+ int j = 2 * i + 1;
+
+ if (j >= n)
+ break;
+ if (j+1 < n && heap_compare_slots(node, heap[j], heap[j+1]) > 0)
+ j++;
+ if (heap_compare_slots(node, heap[n], heap[j]) <= 0)
+ break;
+ heap[i] = heap[j];
+ i = j;
+ }
+ heap[i] = heap[n];
+}
+
+/*
+ * Compare the tuples in the two given slots.
+ */
+static int32
+heap_compare_slots(MergeAppendState *node, SlotNumber slot1, SlotNumber slot2)
+{
+ TupleTableSlot *s1 = node->ms_slots[slot1];
+ TupleTableSlot *s2 = node->ms_slots[slot2];
+ int nkey;
+
+ Assert(!TupIsNull(s1));
+ Assert(!TupIsNull(s2));
+
+ for (nkey = 0; nkey < node->ms_nkeys; nkey++)
+ {
+ ScanKey scankey = node->ms_scankeys + nkey;
+ AttrNumber attno = scankey->sk_attno;
+ Datum datum1,
+ datum2;
+ bool isNull1,
+ isNull2;
+ int32 compare;
+
+ datum1 = slot_getattr(s1, attno, &isNull1);
+ datum2 = slot_getattr(s2, attno, &isNull2);
+
+ if (isNull1)
+ {
+ if (isNull2)
+ continue; /* NULL "=" NULL */
+ else if (scankey->sk_flags & SK_BT_NULLS_FIRST)
+ return -1; /* NULL "<" NOT_NULL */
+ else
+ return 1; /* NULL ">" NOT_NULL */
+ }
+ else if (isNull2)
+ {
+ if (scankey->sk_flags & SK_BT_NULLS_FIRST)
+ return 1; /* NOT_NULL ">" NULL */
+ else
+ return -1; /* NOT_NULL "<" NULL */
+ }
+ else
+ {
+ compare = DatumGetInt32(FunctionCall2(&scankey->sk_func,
+ datum1, datum2));
+ if (compare != 0)
+ {
+ if (scankey->sk_flags & SK_BT_DESC)
+ compare = -compare;
+ return compare;
+ }
+ }
+ }
+ return 0;
+}
+
+/* ----------------------------------------------------------------
+ * ExecEndMergeAppend
+ *
+ * Shuts down the subscans of the MergeAppend node.
+ *
+ * Returns nothing of interest.
+ * ----------------------------------------------------------------
+ */
+void
+ExecEndMergeAppend(MergeAppendState *node)
+{
+ PlanState **mergeplans;
+ int nplans;
+ int i;
+
+ /*
+ * get information from the node
+ */
+ mergeplans = node->mergeplans;
+ nplans = node->ms_nplans;
+
+ /*
+ * shut down each of the subscans
+ */
+ for (i = 0; i < nplans; i++)
+ ExecEndNode(mergeplans[i]);
+}
+
+void
+ExecReScanMergeAppend(MergeAppendState *node)
+{
+ int i;
+
+ for (i = 0; i < node->ms_nplans; i++)
+ {
+ PlanState *subnode = node->mergeplans[i];
+
+ /*
+ * ExecReScan doesn't know about my subplans, so I have to do
+ * changed-parameter signaling myself.
+ */
+ if (node->ps.chgParam != NULL)
+ UpdateChangedParamSet(subnode, node->ps.chgParam);
+
+ /*
+ * If chgParam of subnode is not null then plan will be re-scanned by
+ * first ExecProcNode.
+ */
+ if (subnode->chgParam == NULL)
+ ExecReScan(subnode);
+ }
+ node->ms_heap_size = 0;
+ node->ms_initialized = false;
+ node->ms_last_slot = -1;
+}
return newnode;
}
+/*
+ * _copyMergeAppend
+ */
+static MergeAppend *
+_copyMergeAppend(MergeAppend *from)
+{
+ MergeAppend *newnode = makeNode(MergeAppend);
+
+ /*
+ * copy node superclass fields
+ */
+ CopyPlanFields((Plan *) from, (Plan *) newnode);
+
+ /*
+ * copy remainder of node
+ */
+ COPY_NODE_FIELD(mergeplans);
+ COPY_SCALAR_FIELD(numCols);
+ COPY_POINTER_FIELD(sortColIdx, from->numCols * sizeof(AttrNumber));
+ COPY_POINTER_FIELD(sortOperators, from->numCols * sizeof(Oid));
+ COPY_POINTER_FIELD(nullsFirst, from->numCols * sizeof(bool));
+
+ return newnode;
+}
+
/*
* _copyRecursiveUnion
*/
case T_Append:
retval = _copyAppend(from);
break;
+ case T_MergeAppend:
+ retval = _copyMergeAppend(from);
+ break;
case T_RecursiveUnion:
retval = _copyRecursiveUnion(from);
break;
WRITE_NODE_FIELD(appendplans);
}
+static void
+_outMergeAppend(StringInfo str, MergeAppend *node)
+{
+ int i;
+
+ WRITE_NODE_TYPE("MERGEAPPEND");
+
+ _outPlanInfo(str, (Plan *) node);
+
+ WRITE_NODE_FIELD(mergeplans);
+
+ WRITE_INT_FIELD(numCols);
+
+ appendStringInfo(str, " :sortColIdx");
+ for (i = 0; i < node->numCols; i++)
+ appendStringInfo(str, " %d", node->sortColIdx[i]);
+
+ appendStringInfo(str, " :sortOperators");
+ for (i = 0; i < node->numCols; i++)
+ appendStringInfo(str, " %u", node->sortOperators[i]);
+
+ appendStringInfo(str, " :nullsFirst");
+ for (i = 0; i < node->numCols; i++)
+ appendStringInfo(str, " %s", booltostr(node->nullsFirst[i]));
+}
+
static void
_outRecursiveUnion(StringInfo str, RecursiveUnion *node)
{
WRITE_NODE_FIELD(subpaths);
}
+static void
+_outMergeAppendPath(StringInfo str, MergeAppendPath *node)
+{
+ WRITE_NODE_TYPE("MERGEAPPENDPATH");
+
+ _outPathInfo(str, (Path *) node);
+
+ WRITE_NODE_FIELD(subpaths);
+}
+
static void
_outResultPath(StringInfo str, ResultPath *node)
{
case T_Append:
_outAppend(str, obj);
break;
+ case T_MergeAppend:
+ _outMergeAppend(str, obj);
+ break;
case T_RecursiveUnion:
_outRecursiveUnion(str, obj);
break;
case T_AppendPath:
_outAppendPath(str, obj);
break;
+ case T_MergeAppendPath:
+ _outMergeAppendPath(str, obj);
+ break;
case T_ResultPath:
_outResultPath(str, obj);
break;
join clauses)
Path - every way to generate a RelOptInfo(sequential,index,joins)
- SeqScan - a plain Path node with pathtype = T_SeqScan
- IndexPath - index scans
+ SeqScan - represents a sequential scan plan
+ IndexPath - index scan
BitmapHeapPath - top of a bitmapped index scan
TidPath - scan by CTID
AppendPath - append multiple subpaths together
+ MergeAppendPath - merge multiple subpaths, preserving their common sort order
ResultPath - a Result plan node (used for FROM-less SELECT)
MaterialPath - a Material plan node
UniquePath - remove duplicate rows
RangeTblEntry *rte);
static void set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
Index rti, RangeTblEntry *rte);
+static List *accumulate_append_subpath(List *subpaths, Path *path);
static void set_dummy_rel_pathlist(RelOptInfo *rel);
static void set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
Index rti, RangeTblEntry *rte);
Index rti, RangeTblEntry *rte)
{
int parentRTindex = rti;
+ List *live_childrels = NIL;
List *subpaths = NIL;
+ List *all_child_pathkeys = NIL;
double parent_rows;
double parent_size;
double *parent_attrsizes;
RelOptInfo *childrel;
List *childquals;
Node *childqual;
- Path *childpath;
+ ListCell *lcp;
ListCell *parentvars;
ListCell *childvars;
/*
* We have to make child entries in the EquivalenceClass data
- * structures as well.
+ * structures as well. This is needed either if the parent
+ * participates in some eclass joins (because we will want to
+ * consider inner-indexscan joins on the individual children)
+ * or if the parent has useful pathkeys (because we should try
+ * to build MergeAppend paths that produce those sort orderings).
*/
- if (rel->has_eclass_joins)
- {
+ if (rel->has_eclass_joins || has_useful_pathkeys(root, rel))
add_child_rel_equivalences(root, appinfo, rel, childrel);
- childrel->has_eclass_joins = true;
- }
+ childrel->has_eclass_joins = rel->has_eclass_joins;
/*
* Note: we could compute appropriate attr_needed data for the child's
* otherrels. So we just leave the child's attr_needed empty.
*/
+ /* Remember which childrels are live, for MergeAppend logic below */
+ live_childrels = lappend(live_childrels, childrel);
+
/*
* Compute the child's access paths, and add the cheapest one to the
* Append path we are constructing for the parent.
- *
- * It's possible that the child is itself an appendrel, in which case
- * we can "cut out the middleman" and just add its child paths to our
- * own list. (We don't try to do this earlier because we need to
- * apply both levels of transformation to the quals.)
*/
set_rel_pathlist(root, childrel, childRTindex, childRTE);
- childpath = childrel->cheapest_total_path;
- if (IsA(childpath, AppendPath))
- subpaths = list_concat(subpaths,
- list_copy(((AppendPath *) childpath)->subpaths));
- else
- subpaths = lappend(subpaths, childpath);
+ subpaths = accumulate_append_subpath(subpaths,
+ childrel->cheapest_total_path);
+
+ /*
+ * Collect a list of all the available path orderings for all the
+ * children. We use this as a heuristic to indicate which sort
+ * orderings we should build MergeAppend paths for.
+ */
+ foreach(lcp, childrel->pathlist)
+ {
+ Path *childpath = (Path *) lfirst(lcp);
+ List *childkeys = childpath->pathkeys;
+ ListCell *lpk;
+ bool found = false;
+
+ /* Ignore unsorted paths */
+ if (childkeys == NIL)
+ continue;
+
+ /* Have we already seen this ordering? */
+ foreach(lpk, all_child_pathkeys)
+ {
+ List *existing_pathkeys = (List *) lfirst(lpk);
+
+ if (compare_pathkeys(existing_pathkeys,
+ childkeys) == PATHKEYS_EQUAL)
+ {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ {
+ /* No, so add it to all_child_pathkeys */
+ all_child_pathkeys = lappend(all_child_pathkeys, childkeys);
+ }
+ }
/*
* Accumulate size information from each child.
pfree(parent_attrsizes);
/*
- * Finally, build Append path and install it as the only access path for
- * the parent rel. (Note: this is correct even if we have zero or one
- * live subpath due to constraint exclusion.)
+ * Next, build an unordered Append path for the rel. (Note: this is
+ * correct even if we have zero or one live subpath due to constraint
+ * exclusion.)
*/
add_path(rel, (Path *) create_append_path(rel, subpaths));
- /* Select cheapest path (pretty easy in this case...) */
+ /*
+ * Next, build MergeAppend paths based on the collected list of child
+ * pathkeys. We consider both cheapest-startup and cheapest-total
+ * cases, ie, for each interesting ordering, collect all the cheapest
+ * startup subpaths and all the cheapest total paths, and build a
+ * MergeAppend path for each list.
+ */
+ foreach(l, all_child_pathkeys)
+ {
+ List *pathkeys = (List *) lfirst(l);
+ List *startup_subpaths = NIL;
+ List *total_subpaths = NIL;
+ bool startup_neq_total = false;
+ ListCell *lcr;
+
+ /* Select the child paths for this ordering... */
+ foreach(lcr, live_childrels)
+ {
+ RelOptInfo *childrel = (RelOptInfo *) lfirst(lcr);
+ Path *cheapest_startup,
+ *cheapest_total;
+
+ /* Locate the right paths, if they are available. */
+ cheapest_startup =
+ get_cheapest_path_for_pathkeys(childrel->pathlist,
+ pathkeys,
+ STARTUP_COST);
+ cheapest_total =
+ get_cheapest_path_for_pathkeys(childrel->pathlist,
+ pathkeys,
+ TOTAL_COST);
+
+ /*
+ * If we can't find any paths with the right order just add the
+ * cheapest-total path; we'll have to sort it.
+ */
+ if (cheapest_startup == NULL)
+ cheapest_startup = childrel->cheapest_total_path;
+ if (cheapest_total == NULL)
+ cheapest_total = childrel->cheapest_total_path;
+
+ /*
+ * Notice whether we actually have different paths for the
+ * "cheapest" and "total" cases; frequently there will be no
+ * point in two create_merge_append_path() calls.
+ */
+ if (cheapest_startup != cheapest_total)
+ startup_neq_total = true;
+
+ startup_subpaths =
+ accumulate_append_subpath(startup_subpaths, cheapest_startup);
+ total_subpaths =
+ accumulate_append_subpath(total_subpaths, cheapest_total);
+ }
+
+ /* ... and build the MergeAppend paths */
+ add_path(rel, (Path *) create_merge_append_path(root,
+ rel,
+ startup_subpaths,
+ pathkeys));
+ if (startup_neq_total)
+ add_path(rel, (Path *) create_merge_append_path(root,
+ rel,
+ total_subpaths,
+ pathkeys));
+ }
+
+ /* Select cheapest path */
set_cheapest(rel);
}
+/*
+ * accumulate_append_subpath
+ * Add a subpath to the list being built for an Append or MergeAppend
+ *
+ * It's possible that the child is itself an Append path, in which case
+ * we can "cut out the middleman" and just add its child paths to our
+ * own list. (We don't try to do this earlier because we need to
+ * apply both levels of transformation to the quals.)
+ */
+static List *
+accumulate_append_subpath(List *subpaths, Path *path)
+{
+ if (IsA(path, AppendPath))
+ {
+ AppendPath *apath = (AppendPath *) path;
+
+ /* list_copy is important here to avoid sharing list substructure */
+ return list_concat(subpaths, list_copy(apath->subpaths));
+ }
+ else
+ return lappend(subpaths, path);
+}
+
/*
* set_dummy_rel_pathlist
* Build a dummy path for a relation that's been excluded by constraints
case T_AppendPath:
ptype = "Append";
break;
+ case T_MergeAppendPath:
+ ptype = "MergeAppend";
+ break;
case T_ResultPath:
ptype = "Result";
break;
path->total_cost = startup_cost + run_cost;
}
+/*
+ * cost_merge_append
+ * Determines and returns the cost of a MergeAppend node.
+ *
+ * MergeAppend merges several pre-sorted input streams, using a heap that
+ * at any given instant holds the next tuple from each stream. If there
+ * are N streams, we need about N*log2(N) tuple comparisons to construct
+ * the heap at startup, and then for each output tuple, about log2(N)
+ * comparisons to delete the top heap entry and another log2(N) comparisons
+ * to insert its successor from the same stream.
+ *
+ * (The effective value of N will drop once some of the input streams are
+ * exhausted, but it seems unlikely to be worth trying to account for that.)
+ *
+ * The heap is never spilled to disk, since we assume N is not very large.
+ * So this is much simpler than cost_sort.
+ *
+ * As in cost_sort, we charge two operator evals per tuple comparison.
+ *
+ * 'pathkeys' is a list of sort keys
+ * 'n_streams' is the number of input streams
+ * 'input_startup_cost' is the sum of the input streams' startup costs
+ * 'input_total_cost' is the sum of the input streams' total costs
+ * 'tuples' is the number of tuples in all the streams
+ */
+void
+cost_merge_append(Path *path, PlannerInfo *root,
+ List *pathkeys, int n_streams,
+ Cost input_startup_cost, Cost input_total_cost,
+ double tuples)
+{
+ Cost startup_cost = 0;
+ Cost run_cost = 0;
+ Cost comparison_cost;
+ double N;
+ double logN;
+
+ /*
+ * Avoid log(0)...
+ */
+ N = (n_streams < 2) ? 2.0 : (double) n_streams;
+ logN = LOG2(N);
+
+ /* Assumed cost per tuple comparison */
+ comparison_cost = 2.0 * cpu_operator_cost;
+
+ /* Heap creation cost */
+ startup_cost += comparison_cost * N * logN;
+
+ /* Per-tuple heap maintenance cost */
+ run_cost += tuples * comparison_cost * 2.0 * logN;
+
+ /*
+ * Also charge a small amount (arbitrarily set equal to operator cost) per
+ * extracted tuple. We don't charge cpu_tuple_cost because a MergeAppend
+ * node doesn't do qual-checking or projection, so it has less overhead
+ * than most plan nodes.
+ */
+ run_cost += cpu_operator_cost * tuples;
+
+ path->startup_cost = startup_cost + input_startup_cost;
+ path->total_cost = startup_cost + run_cost + input_total_cost;
+}
+
/*
* cost_material
* Determines and returns the cost of materializing a relation, including
* output row count, which may be lower than the restriction-clause-only row
* count of its parent. (We don't include this case in the PATH_ROWS macro
* because it applies *only* to a nestloop's inner relation.) We have to
- * be prepared to recurse through Append nodes in case of an appendrel.
+ * be prepared to recurse through Append or MergeAppend nodes in case of an
+ * appendrel. (It's not clear MergeAppend can be seen here, but we may as
+ * well handle it if so.)
*/
static double
nestloop_inner_path_rows(Path *path)
result += nestloop_inner_path_rows((Path *) lfirst(l));
}
}
+ else if (IsA(path, MergeAppendPath))
+ {
+ ListCell *l;
+
+ result = 0;
+ foreach(l, ((MergeAppendPath *) path)->subpaths)
+ {
+ result += nestloop_inner_path_rows((Path *) lfirst(l));
+ }
+ }
else
result = PATH_ROWS(path);
* Search for EC members that reference (only) the parent_rel, and
* add transformed members referencing the child_rel.
*
- * We only need to do this for ECs that could generate join conditions,
- * since the child members are only used for creating inner-indexscan paths.
+ * Note that this function won't be called at all unless we have at least some
+ * reason to believe that the EC members it generates will be useful.
*
* parent_rel and child_rel could be derived from appinfo, but since the
* caller has already computed them, we might as well just pass them in.
ListCell *lc2;
/*
- * Won't generate joinclauses if const or single-member (the latter
- * test covers the volatile case too)
+ * If this EC contains a constant, then it's not useful for sorting
+ * or driving an inner index-scan, so we skip generating child EMs.
+ *
+ * If this EC contains a volatile expression, then generating child
+ * EMs would be downright dangerous. We rely on a volatile EC having
+ * only one EM.
*/
- if (cur_ec->ec_has_const || list_length(cur_ec->ec_members) <= 1)
+ if (cur_ec->ec_has_const || cur_ec->ec_has_volatile)
continue;
/* No point in searching if parent rel not mentioned in eclass */
#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
#include "optimizer/cost.h"
+#include "optimizer/paths.h"
#include "optimizer/plancat.h"
#include "optimizer/planmain.h"
#include "optimizer/predtest.h"
static Plan *create_gating_plan(PlannerInfo *root, Plan *plan, List *quals);
static Plan *create_join_plan(PlannerInfo *root, JoinPath *best_path);
static Plan *create_append_plan(PlannerInfo *root, AppendPath *best_path);
+static Plan *create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path);
static Result *create_result_plan(PlannerInfo *root, ResultPath *best_path);
static Material *create_material_plan(PlannerInfo *root, MaterialPath *best_path);
static Plan *create_unique_plan(PlannerInfo *root, UniquePath *best_path);
static Sort *make_sort(PlannerInfo *root, Plan *lefttree, int numCols,
AttrNumber *sortColIdx, Oid *sortOperators, bool *nullsFirst,
double limit_tuples);
+static Plan *prepare_sort_from_pathkeys(PlannerInfo *root,
+ Plan *lefttree, List *pathkeys,
+ bool adjust_tlist_in_place,
+ int *p_numsortkeys,
+ AttrNumber **p_sortColIdx,
+ Oid **p_sortOperators,
+ bool **p_nullsFirst);
static Material *make_material(Plan *lefttree);
plan = create_append_plan(root,
(AppendPath *) best_path);
break;
+ case T_MergeAppend:
+ plan = create_merge_append_plan(root,
+ (MergeAppendPath *) best_path);
+ break;
case T_Result:
plan = (Plan *) create_result_plan(root,
(ResultPath *) best_path);
return (Plan *) plan;
}
+/*
+ * create_merge_append_plan
+ * Create a MergeAppend plan for 'best_path' and (recursively) plans
+ * for its subpaths.
+ *
+ * Returns a Plan node.
+ */
+static Plan *
+create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path)
+{
+ MergeAppend *node = makeNode(MergeAppend);
+ Plan *plan = &node->plan;
+ List *tlist = build_relation_tlist(best_path->path.parent);
+ List *pathkeys = best_path->path.pathkeys;
+ List *subplans = NIL;
+ ListCell *subpaths;
+
+ /*
+ * We don't have the actual creation of the MergeAppend node split out
+ * into a separate make_xxx function. This is because we want to run
+ * prepare_sort_from_pathkeys on it before we do so on the individual
+ * child plans, to make cross-checking the sort info easier.
+ */
+ copy_path_costsize(plan, (Path *) best_path);
+ plan->targetlist = tlist;
+ plan->qual = NIL;
+ plan->lefttree = NULL;
+ plan->righttree = NULL;
+
+ /* Compute sort column info, and adjust MergeAppend's tlist as needed */
+ (void) prepare_sort_from_pathkeys(root, plan, pathkeys,
+ true,
+ &node->numCols,
+ &node->sortColIdx,
+ &node->sortOperators,
+ &node->nullsFirst);
+
+ /*
+ * Now prepare the child plans. We must apply prepare_sort_from_pathkeys
+ * even to subplans that don't need an explicit sort, to make sure they
+ * are returning the same sort key columns the MergeAppend expects.
+ */
+ foreach(subpaths, best_path->subpaths)
+ {
+ Path *subpath = (Path *) lfirst(subpaths);
+ Plan *subplan;
+ int numsortkeys;
+ AttrNumber *sortColIdx;
+ Oid *sortOperators;
+ bool *nullsFirst;
+
+ /* Build the child plan */
+ subplan = create_plan_recurse(root, subpath);
+
+ /* Compute sort column info, and adjust subplan's tlist as needed */
+ subplan = prepare_sort_from_pathkeys(root, subplan, pathkeys,
+ false,
+ &numsortkeys,
+ &sortColIdx,
+ &sortOperators,
+ &nullsFirst);
+
+ /*
+ * Check that we got the same sort key information. We just Assert
+ * that the sortops match, since those depend only on the pathkeys;
+ * but it seems like a good idea to check the sort column numbers
+ * explicitly, to ensure the tlists really do match up.
+ */
+ Assert(numsortkeys == node->numCols);
+ if (memcmp(sortColIdx, node->sortColIdx,
+ numsortkeys * sizeof(AttrNumber)) != 0)
+ elog(ERROR, "MergeAppend child's targetlist doesn't match MergeAppend");
+ Assert(memcmp(sortOperators, node->sortOperators,
+ numsortkeys * sizeof(Oid)) == 0);
+ Assert(memcmp(nullsFirst, node->nullsFirst,
+ numsortkeys * sizeof(bool)) == 0);
+
+ /* Now, insert a Sort node if subplan isn't sufficiently ordered */
+ if (!pathkeys_contained_in(pathkeys, subpath->pathkeys))
+ subplan = (Plan *) make_sort(root, subplan, numsortkeys,
+ sortColIdx, sortOperators, nullsFirst,
+ -1.0);
+
+ subplans = lappend(subplans, subplan);
+ }
+
+ node->mergeplans = subplans;
+
+ return (Plan *) node;
+}
+
/*
* create_result_plan
* Create a Result plan for 'best_path'.
/*
* Copy cost and size info from a Path node to the Plan node created from it.
- * The executor won't use this info, but it's needed by EXPLAIN.
+ * The executor usually won't use this info, but it's needed by EXPLAIN.
*/
static void
copy_path_costsize(Plan *dest, Path *src)
/*
* Copy cost and size info from a lower plan node to an inserted node.
- * This is not critical, since the decisions have already been made,
- * but it helps produce more reasonable-looking EXPLAIN output.
- * (Some callers alter the info after copying it.)
+ * (Most callers alter the info after copying it.)
*/
static void
copy_plan_costsize(Plan *dest, Plan *src)
* Compute cost as sum of subplan costs. We charge nothing extra for the
* Append itself, which perhaps is too optimistic, but since it doesn't do
* any selection or projection, it is a pretty cheap node.
+ *
+ * If you change this, see also create_append_path(). Also, the size
+ * calculations should match set_append_rel_pathlist(). It'd be better
+ * not to duplicate all this logic, but some callers of this function
+ * aren't working from an appendrel or AppendPath, so there's noplace
+ * to copy the data from.
*/
plan->startup_cost = 0;
plan->total_cost = 0;
}
/*
- * make_sort_from_pathkeys
- * Create sort plan to sort according to given pathkeys
+ * prepare_sort_from_pathkeys
+ * Prepare to sort according to given pathkeys
+ *
+ * This is used to set up for both Sort and MergeAppend nodes. It calculates
+ * the executor's representation of the sort key information, and adjusts the
+ * plan targetlist if needed to add resjunk sort columns.
*
+ * Input parameters:
* 'lefttree' is the node which yields input tuples
* 'pathkeys' is the list of pathkeys by which the result is to be sorted
- * 'limit_tuples' is the bound on the number of output tuples;
- * -1 if no bound
+ * 'adjust_tlist_in_place' is TRUE if lefttree must be modified in-place
*
* We must convert the pathkey information into arrays of sort key column
- * numbers and sort operator OIDs.
+ * numbers and sort operator OIDs, which is the representation the executor
+ * wants. These are returned into the output parameters *p_numsortkeys etc.
*
* If the pathkeys include expressions that aren't simple Vars, we will
* usually need to add resjunk items to the input plan's targetlist to
- * compute these expressions (since the Sort node itself won't do it).
- * If the input plan type isn't one that can do projections, this means
- * adding a Result node just to do the projection.
+ * compute these expressions, since the Sort/MergeAppend node itself won't
+ * do any such calculations. If the input plan type isn't one that can do
+ * projections, this means adding a Result node just to do the projection.
+ * However, the caller can pass adjust_tlist_in_place = TRUE to force the
+ * lefttree tlist to be modified in-place regardless of whether the node type
+ * can project --- we use this for fixing the tlist of MergeAppend itself.
+ *
+ * Returns the node which is to be the input to the Sort (either lefttree,
+ * or a Result stacked atop lefttree).
*/
-Sort *
-make_sort_from_pathkeys(PlannerInfo *root, Plan *lefttree, List *pathkeys,
- double limit_tuples)
+static Plan *
+prepare_sort_from_pathkeys(PlannerInfo *root, Plan *lefttree, List *pathkeys,
+ bool adjust_tlist_in_place,
+ int *p_numsortkeys,
+ AttrNumber **p_sortColIdx,
+ Oid **p_sortOperators,
+ bool **p_nullsFirst)
{
List *tlist = lefttree->targetlist;
ListCell *i;
{
EquivalenceMember *em = (EquivalenceMember *) lfirst(j);
- if (em->em_is_const || em->em_is_child)
+ /*
+ * We shouldn't be trying to sort by an equivalence class that
+ * contains a constant, so no need to consider such cases any
+ * further.
+ */
+ if (em->em_is_const)
continue;
tle = tlist_member((Node *) em->em_expr, tlist);
List *exprvars;
ListCell *k;
- if (em->em_is_const || em->em_is_child)
+ if (em->em_is_const)
continue;
sortexpr = em->em_expr;
exprvars = pull_var_clause((Node *) sortexpr,
/*
* Do we need to insert a Result node?
*/
- if (!is_projection_capable_plan(lefttree))
+ if (!adjust_tlist_in_place &&
+ !is_projection_capable_plan(lefttree))
{
/* copy needed so we don't modify input's tlist below */
tlist = copyObject(tlist);
lefttree);
}
+ /* Don't bother testing is_projection_capable_plan again */
+ adjust_tlist_in_place = true;
+
/*
* Add resjunk entry to input's tlist
*/
Assert(numsortkeys > 0);
+ /* Return results */
+ *p_numsortkeys = numsortkeys;
+ *p_sortColIdx = sortColIdx;
+ *p_sortOperators = sortOperators;
+ *p_nullsFirst = nullsFirst;
+
+ return lefttree;
+}
+
+/*
+ * make_sort_from_pathkeys
+ * Create sort plan to sort according to given pathkeys
+ *
+ * 'lefttree' is the node which yields input tuples
+ * 'pathkeys' is the list of pathkeys by which the result is to be sorted
+ * 'limit_tuples' is the bound on the number of output tuples;
+ * -1 if no bound
+ */
+Sort *
+make_sort_from_pathkeys(PlannerInfo *root, Plan *lefttree, List *pathkeys,
+ double limit_tuples)
+{
+ int numsortkeys;
+ AttrNumber *sortColIdx;
+ Oid *sortOperators;
+ bool *nullsFirst;
+
+ /* Compute sort column info, and adjust lefttree as needed */
+ lefttree = prepare_sort_from_pathkeys(root, lefttree, pathkeys,
+ false,
+ &numsortkeys,
+ &sortColIdx,
+ &sortOperators,
+ &nullsFirst);
+
+ /* Now build the Sort node */
return make_sort(root, lefttree, numsortkeys,
sortColIdx, sortOperators, nullsFirst, limit_tuples);
}
case T_Limit:
case T_ModifyTable:
case T_Append:
+ case T_MergeAppend:
case T_RecursiveUnion:
return false;
default:
}
}
break;
+ case T_MergeAppend:
+ {
+ MergeAppend *splan = (MergeAppend *) plan;
+
+ /*
+ * MergeAppend, like Sort et al, doesn't actually evaluate its
+ * targetlist or check quals.
+ */
+ set_dummy_tlist_references(plan, rtoffset);
+ Assert(splan->plan.qual == NIL);
+ foreach(l, splan->mergeplans)
+ {
+ lfirst(l) = set_plan_refs(glob,
+ (Plan *) lfirst(l),
+ rtoffset);
+ }
+ }
+ break;
case T_RecursiveUnion:
/* This doesn't evaluate targetlist or check quals either */
set_dummy_tlist_references(plan, rtoffset);
}
break;
+ case T_MergeAppend:
+ {
+ ListCell *l;
+
+ foreach(l, ((MergeAppend *) plan)->mergeplans)
+ {
+ context.paramids =
+ bms_add_members(context.paramids,
+ finalize_plan(root,
+ (Plan *) lfirst(l),
+ valid_params,
+ scan_params));
+ }
+ }
+ break;
+
case T_BitmapAnd:
{
ListCell *l;
* create_append_path
* Creates a path corresponding to an Append plan, returning the
* pathnode.
+ *
+ * Note that we must handle subpaths = NIL, representing a dummy access path.
*/
AppendPath *
create_append_path(RelOptInfo *rel, List *subpaths)
* unsorted */
pathnode->subpaths = subpaths;
+ /*
+ * Compute cost as sum of subplan costs. We charge nothing extra for the
+ * Append itself, which perhaps is too optimistic, but since it doesn't do
+ * any selection or projection, it is a pretty cheap node.
+ *
+ * If you change this, see also make_append().
+ */
pathnode->path.startup_cost = 0;
pathnode->path.total_cost = 0;
foreach(l, subpaths)
return pathnode;
}
+/*
+ * create_merge_append_path
+ * Creates a path corresponding to a MergeAppend plan, returning the
+ * pathnode.
+ */
+MergeAppendPath *
+create_merge_append_path(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *subpaths,
+ List *pathkeys)
+{
+ MergeAppendPath *pathnode = makeNode(MergeAppendPath);
+ Cost input_startup_cost;
+ Cost input_total_cost;
+ ListCell *l;
+
+ pathnode->path.pathtype = T_MergeAppend;
+ pathnode->path.parent = rel;
+ pathnode->path.pathkeys = pathkeys;
+ pathnode->subpaths = subpaths;
+
+ /* Add up all the costs of the input paths */
+ input_startup_cost = 0;
+ input_total_cost = 0;
+ foreach(l, subpaths)
+ {
+ Path *subpath = (Path *) lfirst(l);
+
+ if (pathkeys_contained_in(pathkeys, subpath->pathkeys))
+ {
+ /* Subpath is adequately ordered, we won't need to sort it */
+ input_startup_cost += subpath->startup_cost;
+ input_total_cost += subpath->total_cost;
+ }
+ else
+ {
+ /* We'll need to insert a Sort node, so include cost for that */
+ Path sort_path; /* dummy for result of cost_sort */
+
+ cost_sort(&sort_path,
+ root,
+ pathkeys,
+ subpath->total_cost,
+ subpath->parent->tuples,
+ subpath->parent->width,
+ 0.0,
+ work_mem,
+ -1.0);
+ input_startup_cost += sort_path.startup_cost;
+ input_total_cost += sort_path.total_cost;
+ }
+ }
+
+ /* Now we can compute total costs of the MergeAppend */
+ cost_merge_append(&pathnode->path, root,
+ pathkeys, list_length(subpaths),
+ input_startup_cost, input_total_cost,
+ rel->tuples);
+
+ return pathnode;
+}
+
/*
* create_result_path
* Creates a path representing a Result-and-nothing-else plan.
dpns->planstate = ps;
/*
- * We special-case Append to pretend that the first child plan is the
- * OUTER referent; we have to interpret OUTER Vars in the Append's tlist
- * according to one of the children, and the first one is the most natural
- * choice. Likewise special-case ModifyTable to pretend that the first
- * child plan is the OUTER referent; this is to support RETURNING lists
- * containing references to non-target relations.
+ * We special-case Append and MergeAppend to pretend that the first child
+ * plan is the OUTER referent; we have to interpret OUTER Vars in their
+ * tlists according to one of the children, and the first one is the most
+ * natural choice. Likewise special-case ModifyTable to pretend that the
+ * first child plan is the OUTER referent; this is to support RETURNING
+ * lists containing references to non-target relations.
*/
if (IsA(ps, AppendState))
dpns->outer_planstate = ((AppendState *) ps)->appendplans[0];
+ else if (IsA(ps, MergeAppendState))
+ dpns->outer_planstate = ((MergeAppendState *) ps)->mergeplans[0];
else if (IsA(ps, ModifyTableState))
dpns->outer_planstate = ((ModifyTableState *) ps)->mt_plans[0];
else
--- /dev/null
+/*-------------------------------------------------------------------------
+ *
+ * nodeMergeAppend.h
+ *
+ *
+ *
+ * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/executor/nodeMergeAppend.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef NODEMERGEAPPEND_H
+#define NODEMERGEAPPEND_H
+
+#include "nodes/execnodes.h"
+
+extern MergeAppendState *ExecInitMergeAppend(MergeAppend *node, EState *estate, int eflags);
+extern TupleTableSlot *ExecMergeAppend(MergeAppendState *node);
+extern void ExecEndMergeAppend(MergeAppendState *node);
+extern void ExecReScanMergeAppend(MergeAppendState *node);
+
+#endif /* NODEMERGEAPPEND_H */
int as_whichplan;
} AppendState;
+/* ----------------
+ * MergeAppendState information
+ *
+ * nplans how many plans are in the array
+ * nkeys number of sort key columns
+ * scankeys sort keys in ScanKey representation
+ * slots current output tuple of each subplan
+ * heap heap of active tuples (represented as array indexes)
+ * heap_size number of active heap entries
+ * initialized true if we have fetched first tuple from each subplan
+ * last_slot last subplan fetched from (which must be re-called)
+ * ----------------
+ */
+typedef struct MergeAppendState
+{
+ PlanState ps; /* its first field is NodeTag */
+ PlanState **mergeplans; /* array of PlanStates for my inputs */
+ int ms_nplans;
+ int ms_nkeys;
+ ScanKey ms_scankeys; /* array of length ms_nkeys */
+ TupleTableSlot **ms_slots; /* array of length ms_nplans */
+ int *ms_heap; /* array of length ms_nplans */
+ int ms_heap_size; /* current active length of ms_heap[] */
+ bool ms_initialized; /* are subplans started? */
+ int ms_last_slot; /* last subplan slot we returned from */
+} MergeAppendState;
+
/* ----------------
* RecursiveUnionState information
*
T_Result,
T_ModifyTable,
T_Append,
+ T_MergeAppend,
T_RecursiveUnion,
T_BitmapAnd,
T_BitmapOr,
T_ResultState,
T_ModifyTableState,
T_AppendState,
+ T_MergeAppendState,
T_RecursiveUnionState,
T_BitmapAndState,
T_BitmapOrState,
T_HashPath,
T_TidPath,
T_AppendPath,
+ T_MergeAppendPath,
T_ResultPath,
T_MaterialPath,
T_UniquePath,
List *appendplans;
} Append;
+/* ----------------
+ * MergeAppend node -
+ * Merge the results of pre-sorted sub-plans to preserve the ordering.
+ * ----------------
+ */
+typedef struct MergeAppend
+{
+ Plan plan;
+ List *mergeplans;
+ /* remaining fields are just like the sort-key info in struct Sort */
+ int numCols; /* number of sort-key columns */
+ AttrNumber *sortColIdx; /* their indexes in the target list */
+ Oid *sortOperators; /* OIDs of operators to sort them by */
+ bool *nullsFirst; /* NULLS FIRST/LAST directions */
+} MergeAppend;
+
/* ----------------
* RecursiveUnion node -
* Generate a recursive union of two subplans.
* the entire append relation. The member RTEs are otherrels. The parent
* is present in the query join tree but the members are not. The member
* RTEs and otherrels are used to plan the scans of the individual tables or
- * subqueries of the append set; then the parent baserel is given an Append
- * plan comprising the best plans for the individual member rels. (See
- * comments for AppendRelInfo for more information.)
+ * subqueries of the append set; then the parent baserel is given Append
+ * and/or MergeAppend paths comprising the best paths for the individual
+ * member rels. (See comments for AppendRelInfo for more information.)
*
* At one time we also made otherrels to represent join RTEs, for use in
* handling join alias Vars. Currently this is not needed because all join
*
* em_is_child signifies that this element was built by transposing a member
* for an inheritance parent relation to represent the corresponding expression
- * on an inheritance child. The element should be ignored for all purposes
- * except constructing inner-indexscan paths for the child relation. (Other
- * types of join are driven from transposed joininfo-list entries.) Note
- * that the EC's ec_relids field does NOT include the child relation.
+ * on an inheritance child. These elements are used for constructing
+ * inner-indexscan paths for the child relation (other types of join are
+ * driven from transposed joininfo-list entries) and for constructing
+ * MergeAppend paths for the whole inheritance tree. Note that the EC's
+ * ec_relids field does NOT include the child relation.
*
* em_datatype is usually the same as exprType(em_expr), but can be
* different when dealing with a binary-compatible opfamily; in particular
#define IS_DUMMY_PATH(p) \
(IsA((p), AppendPath) && ((AppendPath *) (p))->subpaths == NIL)
+/*
+ * MergeAppendPath represents a MergeAppend plan, ie, the merging of sorted
+ * results from several member plans to produce similarly-sorted output.
+ */
+typedef struct MergeAppendPath
+{
+ Path path;
+ List *subpaths; /* list of component Paths */
+} MergeAppendPath;
+
/*
* ResultPath represents use of a Result plan node to compute a variable-free
* targetlist with no underlying tables (a "SELECT expressions" query).
List *pathkeys, Cost input_cost, double tuples, int width,
Cost comparison_cost, int sort_mem,
double limit_tuples);
+extern void cost_merge_append(Path *path, PlannerInfo *root,
+ List *pathkeys, int n_streams,
+ Cost input_startup_cost, Cost input_total_cost,
+ double tuples);
extern void cost_material(Path *path,
Cost input_startup_cost, Cost input_total_cost,
double tuples, int width);
extern TidPath *create_tidscan_path(PlannerInfo *root, RelOptInfo *rel,
List *tidquals);
extern AppendPath *create_append_path(RelOptInfo *rel, List *subpaths);
+extern MergeAppendPath *create_merge_append_path(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *subpaths,
+ List *pathkeys);
extern ResultPath *create_result_path(List *quals);
extern MaterialPath *create_material_path(RelOptInfo *rel, Path *subpath);
extern UniquePath *create_unique_path(PlannerInfo *root, RelOptInfo *rel,
drop cascades to table ts
drop cascades to table t3
drop cascades to table t4
+--
+-- Test merge-append plans for inheritance trees
+--
+create table matest0 (id serial primary key, name text);
+NOTICE: CREATE TABLE will create implicit sequence "matest0_id_seq" for serial column "matest0.id"
+NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "matest0_pkey" for table "matest0"
+create table matest1 (id integer primary key) inherits (matest0);
+NOTICE: merging column "id" with inherited definition
+NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "matest1_pkey" for table "matest1"
+create table matest2 (id integer primary key) inherits (matest0);
+NOTICE: merging column "id" with inherited definition
+NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "matest2_pkey" for table "matest2"
+create table matest3 (id integer primary key) inherits (matest0);
+NOTICE: merging column "id" with inherited definition
+NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "matest3_pkey" for table "matest3"
+create index matest0i on matest0 ((1-id));
+create index matest1i on matest1 ((1-id));
+-- create index matest2i on matest2 ((1-id)); -- intentionally missing
+create index matest3i on matest3 ((1-id));
+insert into matest1 (name) values ('Test 1');
+insert into matest1 (name) values ('Test 2');
+insert into matest2 (name) values ('Test 3');
+insert into matest2 (name) values ('Test 4');
+insert into matest3 (name) values ('Test 5');
+insert into matest3 (name) values ('Test 6');
+set enable_indexscan = off; -- force use of seqscan/sort, so no merge
+explain (verbose, costs off) select * from matest0 order by 1-id;
+ QUERY PLAN
+---------------------------------------------------------------------------------
+ Sort
+ Output: public.matest0.id, public.matest0.name, ((1 - public.matest0.id))
+ Sort Key: ((1 - public.matest0.id))
+ -> Result
+ Output: public.matest0.id, public.matest0.name, (1 - public.matest0.id)
+ -> Append
+ -> Seq Scan on public.matest0
+ Output: public.matest0.id, public.matest0.name
+ -> Seq Scan on public.matest1 matest0
+ Output: public.matest0.id, public.matest0.name
+ -> Seq Scan on public.matest2 matest0
+ Output: public.matest0.id, public.matest0.name
+ -> Seq Scan on public.matest3 matest0
+ Output: public.matest0.id, public.matest0.name
+(14 rows)
+
+select * from matest0 order by 1-id;
+ id | name
+----+--------
+ 6 | Test 6
+ 5 | Test 5
+ 4 | Test 4
+ 3 | Test 3
+ 2 | Test 2
+ 1 | Test 1
+(6 rows)
+
+reset enable_indexscan;
+set enable_seqscan = off; -- plan with fewest seqscans should be merge
+explain (verbose, costs off) select * from matest0 order by 1-id;
+ QUERY PLAN
+---------------------------------------------------------------------------------------------
+ Result
+ Output: public.matest0.id, public.matest0.name, ((1 - public.matest0.id))
+ -> Merge Append
+ Sort Key: ((1 - public.matest0.id))
+ -> Index Scan using matest0i on public.matest0
+ Output: public.matest0.id, public.matest0.name, (1 - public.matest0.id)
+ -> Index Scan using matest1i on public.matest1 matest0
+ Output: public.matest0.id, public.matest0.name, (1 - public.matest0.id)
+ -> Sort
+ Output: public.matest0.id, public.matest0.name, ((1 - public.matest0.id))
+ Sort Key: ((1 - public.matest0.id))
+ -> Seq Scan on public.matest2 matest0
+ Output: public.matest0.id, public.matest0.name, (1 - public.matest0.id)
+ -> Index Scan using matest3i on public.matest3 matest0
+ Output: public.matest0.id, public.matest0.name, (1 - public.matest0.id)
+(15 rows)
+
+select * from matest0 order by 1-id;
+ id | name
+----+--------
+ 6 | Test 6
+ 5 | Test 5
+ 4 | Test 4
+ 3 | Test 3
+ 2 | Test 2
+ 1 | Test 1
+(6 rows)
+
+reset enable_seqscan;
+drop table matest0 cascade;
+NOTICE: drop cascades to 3 other objects
+DETAIL: drop cascades to table matest1
+drop cascades to table matest2
+drop cascades to table matest3
ORDER BY a.attrelid::regclass::name, a.attnum;
DROP TABLE t1, s1 CASCADE;
+
+--
+-- Test merge-append plans for inheritance trees
+--
+
+create table matest0 (id serial primary key, name text);
+create table matest1 (id integer primary key) inherits (matest0);
+create table matest2 (id integer primary key) inherits (matest0);
+create table matest3 (id integer primary key) inherits (matest0);
+
+create index matest0i on matest0 ((1-id));
+create index matest1i on matest1 ((1-id));
+-- create index matest2i on matest2 ((1-id)); -- intentionally missing
+create index matest3i on matest3 ((1-id));
+
+insert into matest1 (name) values ('Test 1');
+insert into matest1 (name) values ('Test 2');
+insert into matest2 (name) values ('Test 3');
+insert into matest2 (name) values ('Test 4');
+insert into matest3 (name) values ('Test 5');
+insert into matest3 (name) values ('Test 6');
+
+set enable_indexscan = off; -- force use of seqscan/sort, so no merge
+explain (verbose, costs off) select * from matest0 order by 1-id;
+select * from matest0 order by 1-id;
+reset enable_indexscan;
+
+set enable_seqscan = off; -- plan with fewest seqscans should be merge
+explain (verbose, costs off) select * from matest0 order by 1-id;
+select * from matest0 order by 1-id;
+reset enable_seqscan;
+
+drop table matest0 cascade;