* Planning is complete, we just need to convert the selected
* Path into a Plan.
*
- * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
#include "optimizer/var.h"
#include "parser/parse_clause.h"
#include "parser/parsetree.h"
+#include "partitioning/partprune.h"
#include "utils/lsyscache.h"
* any sortgrouprefs specified in its pathtarget, with appropriate
* ressortgroupref labels. This is passed down by parent nodes such as Sort
* and Group, which need these values to be available in their inputs.
+ *
+ * CP_IGNORE_TLIST specifies that the caller plans to replace the targetlist,
+ * and therefore it doesn't matter a bit what target list gets generated.
*/
-#define CP_EXACT_TLIST 0x0001 /* Plan must return specified tlist */
-#define CP_SMALL_TLIST 0x0002 /* Prefer narrower tlists */
-#define CP_LABEL_TLIST 0x0004 /* tlist must contain sortgrouprefs */
+#define CP_EXACT_TLIST 0x0001 /* Plan must return specified tlist */
+#define CP_SMALL_TLIST 0x0002 /* Prefer narrower tlists */
+#define CP_LABEL_TLIST 0x0004 /* tlist must contain sortgrouprefs */
+#define CP_IGNORE_TLIST 0x0008 /* caller will replace tlist */
static Plan *create_plan_recurse(PlannerInfo *root, Path *best_path,
static Plan *create_unique_plan(PlannerInfo *root, UniquePath *best_path,
int flags);
static Gather *create_gather_plan(PlannerInfo *root, GatherPath *best_path);
-static Plan *create_projection_plan(PlannerInfo *root, ProjectionPath *best_path);
-static Plan *inject_projection_plan(Plan *subplan, List *tlist);
+static Plan *create_projection_plan(PlannerInfo *root,
+ ProjectionPath *best_path,
+ int flags);
+static Plan *inject_projection_plan(Plan *subplan, List *tlist, bool parallel_safe);
static Sort *create_sort_plan(PlannerInfo *root, SortPath *best_path, int flags);
static Group *create_group_plan(PlannerInfo *root, GroupPath *best_path);
static Unique *create_upper_unique_plan(PlannerInfo *root, UpperUniquePath *best_path,
static SetOp *create_setop_plan(PlannerInfo *root, SetOpPath *best_path,
int flags);
static RecursiveUnion *create_recursiveunion_plan(PlannerInfo *root, RecursiveUnionPath *best_path);
-static void get_column_info_for_window(PlannerInfo *root, WindowClause *wc,
- List *tlist,
- int numSortCols, AttrNumber *sortColIdx,
- int *partNumCols,
- AttrNumber **partColIdx,
- Oid **partOperators,
- int *ordNumCols,
- AttrNumber **ordColIdx,
- Oid **ordOperators);
static LockRows *create_lockrows_plan(PlannerInfo *root, LockRowsPath *best_path,
int flags);
static ModifyTable *create_modifytable_plan(PlannerInfo *root, ModifyTablePath *best_path);
List *tlist, List *scan_clauses);
static CteScan *create_ctescan_plan(PlannerInfo *root, Path *best_path,
List *tlist, List *scan_clauses);
+static NamedTuplestoreScan *create_namedtuplestorescan_plan(PlannerInfo *root,
+ Path *best_path, List *tlist, List *scan_clauses);
static WorkTableScan *create_worktablescan_plan(PlannerInfo *root, Path *best_path,
List *tlist, List *scan_clauses);
static ForeignScan *create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
Index scanrelid, TableFunc *tablefunc);
static CteScan *make_ctescan(List *qptlist, List *qpqual,
Index scanrelid, int ctePlanId, int cteParam);
+static NamedTuplestoreScan *make_namedtuplestorescan(List *qptlist, List *qpqual,
+ Index scanrelid, char *enrname);
static WorkTableScan *make_worktablescan(List *qptlist, List *qpqual,
Index scanrelid, int wtParam);
-static Append *make_append(List *appendplans, List *tlist, List *partitioned_rels);
+static Append *make_append(List *appendplans, int first_partial_plan,
+ List *tlist, PartitionPruneInfo *partpruneinfo);
static RecursiveUnion *make_recursive_union(List *tlist,
Plan *lefttree,
Plan *righttree,
static NestLoop *make_nestloop(List *tlist,
List *joinclauses, List *otherclauses, List *nestParams,
Plan *lefttree, Plan *righttree,
- JoinType jointype);
+ JoinType jointype, bool inner_unique);
static HashJoin *make_hashjoin(List *tlist,
List *joinclauses, List *otherclauses,
List *hashclauses,
Plan *lefttree, Plan *righttree,
- JoinType jointype);
+ JoinType jointype, bool inner_unique);
static Hash *make_hash(Plan *lefttree,
Oid skewTable,
AttrNumber skewColumn,
- bool skewInherit,
- Oid skewColType,
- int32 skewColTypmod);
+ bool skewInherit);
static MergeJoin *make_mergejoin(List *tlist,
List *joinclauses, List *otherclauses,
List *mergeclauses,
int *mergestrategies,
bool *mergenullsfirst,
Plan *lefttree, Plan *righttree,
- JoinType jointype);
+ JoinType jointype, bool inner_unique,
+ bool skip_mark_restore);
static Sort *make_sort(Plan *lefttree, int numCols,
AttrNumber *sortColIdx, Oid *sortOperators,
Oid *collations, bool *nullsFirst);
static EquivalenceMember *find_ec_member_for_tle(EquivalenceClass *ec,
TargetEntry *tle,
Relids relids);
-static Sort *make_sort_from_pathkeys(Plan *lefttree, List *pathkeys);
+static Sort *make_sort_from_pathkeys(Plan *lefttree, List *pathkeys,
+ Relids relids);
static Sort *make_sort_from_groupcols(List *groupcls,
AttrNumber *grpColIdx,
Plan *lefttree);
int partNumCols, AttrNumber *partColIdx, Oid *partOperators,
int ordNumCols, AttrNumber *ordColIdx, Oid *ordOperators,
int frameOptions, Node *startOffset, Node *endOffset,
+ Oid startInRangeFunc, Oid endInRangeFunc,
+ Oid inRangeColl, bool inRangeAsc, bool inRangeNullsFirst,
Plan *lefttree);
static Group *make_group(List *tlist, List *qual, int numGroupCols,
AttrNumber *grpColIdx, Oid *grpOperators,
static Unique *make_unique_from_pathkeys(Plan *lefttree,
List *pathkeys, int numCols);
static Gather *make_gather(List *qptlist, List *qpqual,
- int nworkers, bool single_copy, Plan *subplan);
+ int nworkers, int rescan_param, bool single_copy, Plan *subplan);
static SetOp *make_setop(SetOpCmd cmd, SetOpStrategy strategy, Plan *lefttree,
List *distinctList, AttrNumber flagColIdx, int firstFlag,
long numGroups);
static ProjectSet *make_project_set(List *tlist, Plan *subplan);
static ModifyTable *make_modifytable(PlannerInfo *root,
CmdType operation, bool canSetTag,
- Index nominalRelation, List *partitioned_rels,
- List *resultRelations, List *subplans,
+ Index nominalRelation, Index rootRelation,
+ bool partColsUpdated,
+ List *resultRelations, List *subplans, List *subroots,
List *withCheckOptionLists, List *returningLists,
List *rowMarks, OnConflictExpr *onconflict, int epqParam);
static GatherMerge *create_gather_merge_plan(PlannerInfo *root,
{
Plan *plan;
+ /* Guard against stack overflow due to overly complex plans */
+ check_stack_depth();
+
switch (best_path->pathtype)
{
case T_SeqScan:
case T_ValuesScan:
case T_CteScan:
case T_WorkTableScan:
+ case T_NamedTuplestoreScan:
case T_ForeignScan:
case T_CustomScan:
plan = create_scan_plan(root, best_path, flags);
if (IsA(best_path, ProjectionPath))
{
plan = create_projection_plan(root,
- (ProjectionPath *) best_path);
+ (ProjectionPath *) best_path,
+ flags);
}
else if (IsA(best_path, MinMaxAggPath))
{
plan = (Plan *) create_minmaxagg_plan(root,
- (MinMaxAggPath *) best_path);
+ (MinMaxAggPath *) best_path);
}
else
{
break;
case T_ProjectSet:
plan = (Plan *) create_project_set_plan(root,
- (ProjectSetPath *) best_path);
+ (ProjectSetPath *) best_path);
break;
case T_Material:
plan = (Plan *) create_material_plan(root,
if (IsA(best_path, UpperUniquePath))
{
plan = (Plan *) create_upper_unique_plan(root,
- (UpperUniquePath *) best_path,
+ (UpperUniquePath *) best_path,
flags);
}
else
case T_Agg:
if (IsA(best_path, GroupingSetsPath))
plan = create_groupingsets_plan(root,
- (GroupingSetsPath *) best_path);
+ (GroupingSetsPath *) best_path);
else
{
Assert(IsA(best_path, AggPath));
break;
case T_WindowAgg:
plan = (Plan *) create_windowagg_plan(root,
- (WindowAggPath *) best_path);
+ (WindowAggPath *) best_path);
break;
case T_SetOp:
plan = (Plan *) create_setop_plan(root,
break;
case T_RecursiveUnion:
plan = (Plan *) create_recursiveunion_plan(root,
- (RecursiveUnionPath *) best_path);
+ (RecursiveUnionPath *) best_path);
break;
case T_LockRows:
plan = (Plan *) create_lockrows_plan(root,
break;
case T_ModifyTable:
plan = (Plan *) create_modifytable_plan(root,
- (ModifyTablePath *) best_path);
+ (ModifyTablePath *) best_path);
break;
case T_Limit:
plan = (Plan *) create_limit_plan(root,
break;
case T_GatherMerge:
plan = (Plan *) create_gather_merge_plan(root,
- (GatherMergePath *) best_path);
+ (GatherMergePath *) best_path);
break;
default:
elog(ERROR, "unrecognized node type: %d",
* only those Vars actually needed by the query), we prefer to generate a
* tlist containing all Vars in order. This will allow the executor to
* optimize away projection of the table tuples, if possible.
+ *
+ * But if the caller is going to ignore our tlist anyway, then don't
+ * bother generating one at all. We use an exact equality test here, so
+ * that this only applies when CP_IGNORE_TLIST is the only flag set.
*/
- if (use_physical_tlist(root, best_path, flags))
+ if (flags == CP_IGNORE_TLIST)
+ {
+ tlist = NULL;
+ }
+ else if (use_physical_tlist(root, best_path, flags))
{
if (best_path->pathtype == T_IndexOnlyScan)
{
tlist = copyObject(((IndexPath *) best_path)->indexinfo->indextlist);
/*
- * Transfer any sortgroupref data to the replacement tlist, unless
- * we don't care because the gating Result will handle it.
+ * Transfer sortgroupref data to the replacement tlist, if
+ * requested (use_physical_tlist checked that this will work).
*/
- if (!gating_clauses)
+ if (flags & CP_LABEL_TLIST)
apply_pathtarget_labeling_to_tlist(tlist, best_path->pathtarget);
}
else
else
{
/* As above, transfer sortgroupref data to replacement tlist */
- if (!gating_clauses)
+ if (flags & CP_LABEL_TLIST)
apply_pathtarget_labeling_to_tlist(tlist, best_path->pathtarget);
}
}
case T_BitmapHeapScan:
plan = (Plan *) create_bitmap_scan_plan(root,
- (BitmapHeapPath *) best_path,
+ (BitmapHeapPath *) best_path,
tlist,
scan_clauses);
break;
case T_SubqueryScan:
plan = (Plan *) create_subqueryscan_plan(root,
- (SubqueryScanPath *) best_path,
+ (SubqueryScanPath *) best_path,
tlist,
scan_clauses);
break;
scan_clauses);
break;
+ case T_NamedTuplestoreScan:
+ plan = (Plan *) create_namedtuplestorescan_plan(root,
+ best_path,
+ tlist,
+ scan_clauses);
+ break;
+
case T_WorkTableScan:
plan = (Plan *) create_worktablescan_plan(root,
best_path,
if (rel->reloptkind != RELOPT_BASEREL)
return false;
+ /*
+ * Also, don't do it to a CustomPath; the premise that we're extracting
+ * columns from a simple physical tuple is unlikely to hold for those.
+ * (When it does make sense, the custom path creator can set up the path's
+ * pathtarget that way.)
+ */
+ if (IsA(path, CustomPath))
+ return false;
+
+ /*
+ * If a bitmap scan's tlist is empty, keep it as-is. This may allow the
+ * executor to skip heap page fetches, and in any case, the benefit of
+ * using a physical tlist instead would be minimal.
+ */
+ if (IsA(path, BitmapHeapPath) &&
+ path->pathtarget->exprs == NIL)
+ return false;
+
/*
* Can't do it if any system columns or whole-row Vars are requested.
* (This could possibly be fixed but would take some fragile assumptions
*/
copy_plan_costsize(gplan, plan);
+ /* Gating quals could be unsafe, so better use the Path's safety flag */
+ gplan->parallel_safe = path->parallel_safe;
+
return gplan;
}
if (get_loc_restrictinfo(best_path) != NIL)
set_qpqual((Plan) plan,
list_concat(get_qpqual((Plan) plan),
- get_actual_clauses(get_loc_restrictinfo(best_path))));
+ get_actual_clauses(get_loc_restrictinfo(best_path))));
#endif
return plan;
List *tlist = build_path_tlist(root, &best_path->path);
List *subplans = NIL;
ListCell *subpaths;
+ RelOptInfo *rel = best_path->path.parent;
+ PartitionPruneInfo *partpruneinfo = NULL;
/*
* The subpaths list could be empty, if every child was proven empty by
subplans = lappend(subplans, subplan);
}
+ /*
+ * If any quals exist, they may be useful to perform further partition
+ * pruning during execution. Gather information needed by the executor to
+ * do partition pruning.
+ */
+ if (enable_partition_pruning &&
+ rel->reloptkind == RELOPT_BASEREL &&
+ best_path->partitioned_rels != NIL)
+ {
+ List *prunequal;
+
+ prunequal = extract_actual_clauses(rel->baserestrictinfo, false);
+
+ if (best_path->path.param_info)
+ {
+ List *prmquals = best_path->path.param_info->ppi_clauses;
+
+ prmquals = extract_actual_clauses(prmquals, false);
+ prmquals = (List *) replace_nestloop_params(root,
+ (Node *) prmquals);
+
+ prunequal = list_concat(prunequal, prmquals);
+ }
+
+ if (prunequal != NIL)
+ partpruneinfo =
+ make_partition_pruneinfo(root, rel,
+ best_path->subpaths,
+ best_path->partitioned_rels,
+ prunequal);
+ }
+
/*
* XXX ideally, if there's just one child, we'd not bother to generate an
* Append node but just return the single child. At the moment this does
* parent-rel Vars it'll be asked to emit.
*/
- plan = make_append(subplans, tlist, best_path->partitioned_rels);
+ plan = make_append(subplans, best_path->first_partial_path,
+ tlist, partpruneinfo);
copy_generic_path_info(&plan->plan, (Path *) best_path);
List *pathkeys = best_path->path.pathkeys;
List *subplans = NIL;
ListCell *subpaths;
+ RelOptInfo *rel = best_path->path.parent;
+ PartitionPruneInfo *partpruneinfo = NULL;
/*
* We don't have the actual creation of the MergeAppend node split out
subplans = lappend(subplans, subplan);
}
- node->partitioned_rels = best_path->partitioned_rels;
+ /*
+ * If any quals exist, they may be useful to perform further partition
+ * pruning during execution. Gather information needed by the executor to
+ * do partition pruning.
+ */
+ if (enable_partition_pruning &&
+ rel->reloptkind == RELOPT_BASEREL &&
+ best_path->partitioned_rels != NIL)
+ {
+ List *prunequal;
+
+ prunequal = extract_actual_clauses(rel->baserestrictinfo, false);
+
+ if (best_path->path.param_info)
+ {
+
+ List *prmquals = best_path->path.param_info->ppi_clauses;
+
+ prmquals = extract_actual_clauses(prmquals, false);
+ prmquals = (List *) replace_nestloop_params(root,
+ (Node *) prmquals);
+
+ prunequal = list_concat(prunequal, prmquals);
+ }
+
+ if (prunequal != NIL)
+ partpruneinfo = make_partition_pruneinfo(root, rel,
+ best_path->subpaths,
+ best_path->partitioned_rels,
+ prunequal);
+ }
+
node->mergeplans = subplans;
+ node->part_prune_info = partpruneinfo;
return (Plan *) node;
}
}
}
+ /* Use change_plan_targetlist in case we need to insert a Result node */
if (newitems || best_path->umethod == UNIQUE_PATH_SORT)
- {
- /*
- * If the top plan node can't do projections and its existing target
- * list isn't already what we need, we need to add a Result node to
- * help it along.
- */
- if (!is_projection_capable_plan(subplan) &&
- !tlist_same_exprs(newtlist, subplan->targetlist))
- subplan = inject_projection_plan(subplan, newtlist);
- else
- subplan->targetlist = newtlist;
- }
+ subplan = change_plan_targetlist(subplan, newtlist,
+ best_path->path.parallel_safe);
/*
* Build control information showing which subplan output columns are to
* for the IN clause operators' RHS datatype.
*/
eqop = get_equality_op_for_ordering_op(sortop, NULL);
- if (!OidIsValid(eqop)) /* shouldn't happen */
+ if (!OidIsValid(eqop)) /* shouldn't happen */
elog(ERROR, "could not find equality operator for ordering operator %u",
sortop);
gather_plan = make_gather(tlist,
NIL,
best_path->num_workers,
+ SS_assign_special_param(root),
best_path->single_copy,
subplan);
gm_plan->num_workers = best_path->num_workers;
copy_generic_path_info(&gm_plan->plan, &best_path->path);
+ /* Assign the rescan Param. */
+ gm_plan->rescan_param = SS_assign_special_param(root);
+
/* Gather Merge is pointless with no pathkeys; use Gather instead. */
Assert(pathkeys != NIL);
* but sometimes we can just let the subplan do the work.
*/
static Plan *
-create_projection_plan(PlannerInfo *root, ProjectionPath *best_path)
+create_projection_plan(PlannerInfo *root, ProjectionPath *best_path, int flags)
{
Plan *plan;
Plan *subplan;
List *tlist;
+ bool needs_result_node = false;
- /* Since we intend to project, we don't need to constrain child tlist */
- subplan = create_plan_recurse(root, best_path->subpath, 0);
-
- tlist = build_path_tlist(root, &best_path->path);
+ /*
+ * Convert our subpath to a Plan and determine whether we need a Result
+ * node.
+ *
+ * In most cases where we don't need to project, creation_projection_path
+ * will have set dummypp, but not always. First, some createplan.c
+ * routines change the tlists of their nodes. (An example is that
+ * create_merge_append_plan might add resjunk sort columns to a
+ * MergeAppend.) Second, create_projection_path has no way of knowing
+ * what path node will be placed on top of the projection path and
+ * therefore can't predict whether it will require an exact tlist. For
+ * both of these reasons, we have to recheck here.
+ */
+ if (use_physical_tlist(root, &best_path->path, flags))
+ {
+ /*
+ * Our caller doesn't really care what tlist we return, so we don't
+ * actually need to project. However, we may still need to ensure
+ * proper sortgroupref labels, if the caller cares about those.
+ */
+ subplan = create_plan_recurse(root, best_path->subpath, 0);
+ tlist = subplan->targetlist;
+ if (flags & CP_LABEL_TLIST)
+ apply_pathtarget_labeling_to_tlist(tlist,
+ best_path->path.pathtarget);
+ }
+ else if (is_projection_capable_path(best_path->subpath))
+ {
+ /*
+ * Our caller requires that we return the exact tlist, but no separate
+ * result node is needed because the subpath is projection-capable.
+ * Tell create_plan_recurse that we're going to ignore the tlist it
+ * produces.
+ */
+ subplan = create_plan_recurse(root, best_path->subpath,
+ CP_IGNORE_TLIST);
+ tlist = build_path_tlist(root, &best_path->path);
+ }
+ else
+ {
+ /*
+ * It looks like we need a result node, unless by good fortune the
+ * requested tlist is exactly the one the child wants to produce.
+ */
+ subplan = create_plan_recurse(root, best_path->subpath, 0);
+ tlist = build_path_tlist(root, &best_path->path);
+ needs_result_node = !tlist_same_exprs(tlist, subplan->targetlist);
+ }
/*
- * We might not really need a Result node here, either because the subplan
- * can project or because it's returning the right list of expressions
- * anyway. Usually create_projection_path will have detected that and set
- * dummypp if we don't need a Result; but its decision can't be final,
- * because some createplan.c routines change the tlists of their nodes.
- * (An example is that create_merge_append_plan might add resjunk sort
- * columns to a MergeAppend.) So we have to recheck here. If we do
- * arrive at a different answer than create_projection_path did, we'll
- * have made slightly wrong cost estimates; but label the plan with the
- * cost estimates we actually used, not "corrected" ones. (XXX this could
- * be cleaned up if we moved more of the sortcolumn setup logic into Path
- * creation, but that would add expense to creating Paths we might end up
- * not using.)
+ * If we make a different decision about whether to include a Result node
+ * than create_projection_path did, we'll have made slightly wrong cost
+ * estimates; but label the plan with the cost estimates we actually used,
+ * not "corrected" ones. (XXX this could be cleaned up if we moved more
+ * of the sortcolumn setup logic into Path creation, but that would add
+ * expense to creating Paths we might end up not using.)
*/
- if (is_projection_capable_path(best_path->subpath) ||
- tlist_same_exprs(tlist, subplan->targetlist))
+ if (!needs_result_node)
{
/* Don't need a separate Result, just assign tlist to subplan */
plan = subplan;
plan->total_cost = best_path->path.total_cost;
plan->plan_rows = best_path->path.rows;
plan->plan_width = best_path->path.pathtarget->width;
- /* ... but be careful not to munge subplan's parallel-aware flag */
+ plan->parallel_safe = best_path->path.parallel_safe;
+ /* ... but don't change subplan's parallel_aware flag */
}
else
{
* This is used in a few places where we decide on-the-fly that we need a
* projection step as part of the tree generated for some Path node.
* We should try to get rid of this in favor of doing it more honestly.
+ *
+ * One reason it's ugly is we have to be told the right parallel_safe marking
+ * to apply (since the tlist might be unsafe even if the child plan is safe).
*/
static Plan *
-inject_projection_plan(Plan *subplan, List *tlist)
+inject_projection_plan(Plan *subplan, List *tlist, bool parallel_safe)
{
Plan *plan;
* consistent not more so. Hence, just copy the subplan's cost.
*/
copy_plan_costsize(plan, subplan);
+ plan->parallel_safe = parallel_safe;
return plan;
}
+/*
+ * change_plan_targetlist
+ * Externally available wrapper for inject_projection_plan.
+ *
+ * This is meant for use by FDW plan-generation functions, which might
+ * want to adjust the tlist computed by some subplan tree. In general,
+ * a Result node is needed to compute the new tlist, but we can optimize
+ * some cases.
+ *
+ * In most cases, tlist_parallel_safe can just be passed as the parallel_safe
+ * flag of the FDW's own Path node.
+ */
+Plan *
+change_plan_targetlist(Plan *subplan, List *tlist, bool tlist_parallel_safe)
+{
+ /*
+ * If the top plan node can't do projections and its existing target list
+ * isn't already what we need, we need to add a Result node to help it
+ * along.
+ */
+ if (!is_projection_capable_plan(subplan) &&
+ !tlist_same_exprs(tlist, subplan->targetlist))
+ subplan = inject_projection_plan(subplan, tlist,
+ subplan->parallel_safe &&
+ tlist_parallel_safe);
+ else
+ {
+ /* Else we can just replace the plan node's tlist */
+ subplan->targetlist = tlist;
+ subplan->parallel_safe &= tlist_parallel_safe;
+ }
+ return subplan;
+}
+
/*
* create_sort_plan
*
subplan = create_plan_recurse(root, best_path->subpath,
flags | CP_SMALL_TLIST);
- plan = make_sort_from_pathkeys(subplan, best_path->path.pathkeys);
+ /*
+ * make_sort_from_pathkeys() indirectly calls find_ec_member_for_tle(),
+ * which will ignore any child EC members that don't belong to the given
+ * relids. Thus, if this sort path is based on a child relation, we must
+ * pass its relids.
+ */
+ plan = make_sort_from_pathkeys(subplan, best_path->path.pathkeys,
+ IS_OTHER_REL(best_path->subpath->parent) ?
+ best_path->path.parent->relids : NULL);
copy_generic_path_info(&plan->plan, (Path *) best_path);
* create_modifytable_plan). Fortunately we can't be because there would
* never be grouping in an UPDATE/DELETE; but let's Assert that.
*/
- Assert(!root->hasInheritedTarget);
+ Assert(root->inhTargetKind == INHKIND_NONE);
Assert(root->grouping_map == NULL);
root->grouping_map = grouping_map;
NIL,
strat,
AGGSPLIT_SIMPLE,
- list_length((List *) linitial(rollup->gsets)),
+ list_length((List *) linitial(rollup->gsets)),
new_grpColIdx,
- extract_grouping_ops(rollup->groupClause),
+ extract_grouping_ops(rollup->groupClause),
rollup->gsets,
NIL,
rollup->numGroups,
plan->plan_rows = 1;
plan->plan_width = mminfo->path->pathtarget->width;
plan->parallel_aware = false;
+ plan->parallel_safe = mminfo->path->parallel_safe;
/* Convert the plan into an InitPlan in the outer query. */
SS_make_initplan_from_plan(root, subroot, plan, mminfo->param);
* create_modifytable_plan). Fortunately we can't be because there would
* never be aggregates in an UPDATE/DELETE; but let's Assert that.
*/
- Assert(!root->hasInheritedTarget);
+ Assert(root->inhTargetKind == INHKIND_NONE);
Assert(root->minmax_aggs == NIL);
root->minmax_aggs = best_path->mmaggregates;
{
WindowAgg *plan;
WindowClause *wc = best_path->winclause;
+ int numPart = list_length(wc->partitionClause);
+ int numOrder = list_length(wc->orderClause);
Plan *subplan;
List *tlist;
- int numsortkeys;
- AttrNumber *sortColIdx;
- Oid *sortOperators;
- Oid *collations;
- bool *nullsFirst;
int partNumCols;
AttrNumber *partColIdx;
Oid *partOperators;
int ordNumCols;
AttrNumber *ordColIdx;
Oid *ordOperators;
+ ListCell *lc;
/*
* WindowAgg can project, so no need to be terribly picky about child
tlist = build_path_tlist(root, &best_path->path);
/*
- * We shouldn't need to actually sort, but it's convenient to use
- * prepare_sort_from_pathkeys to identify the input's sort columns.
+ * Convert SortGroupClause lists into arrays of attr indexes and equality
+ * operators, as wanted by executor. (Note: in principle, it's possible
+ * to drop some of the sort columns, if they were proved redundant by
+ * pathkey logic. However, it doesn't seem worth going out of our way to
+ * optimize such cases. In any case, we must *not* remove the ordering
+ * column for RANGE OFFSET cases, as the executor needs that for in_range
+ * tests even if it's known to be equal to some partitioning column.)
*/
- subplan = prepare_sort_from_pathkeys(subplan,
- best_path->winpathkeys,
- NULL,
- NULL,
- false,
- &numsortkeys,
- &sortColIdx,
- &sortOperators,
- &collations,
- &nullsFirst);
-
- /* Now deconstruct that into partition and ordering portions */
- get_column_info_for_window(root,
- wc,
- subplan->targetlist,
- numsortkeys,
- sortColIdx,
- &partNumCols,
- &partColIdx,
- &partOperators,
- &ordNumCols,
- &ordColIdx,
- &ordOperators);
+ partColIdx = (AttrNumber *) palloc(sizeof(AttrNumber) * numPart);
+ partOperators = (Oid *) palloc(sizeof(Oid) * numPart);
+
+ partNumCols = 0;
+ foreach(lc, wc->partitionClause)
+ {
+ SortGroupClause *sgc = (SortGroupClause *) lfirst(lc);
+ TargetEntry *tle = get_sortgroupclause_tle(sgc, subplan->targetlist);
+
+ Assert(OidIsValid(sgc->eqop));
+ partColIdx[partNumCols] = tle->resno;
+ partOperators[partNumCols] = sgc->eqop;
+ partNumCols++;
+ }
+
+ ordColIdx = (AttrNumber *) palloc(sizeof(AttrNumber) * numOrder);
+ ordOperators = (Oid *) palloc(sizeof(Oid) * numOrder);
+
+ ordNumCols = 0;
+ foreach(lc, wc->orderClause)
+ {
+ SortGroupClause *sgc = (SortGroupClause *) lfirst(lc);
+ TargetEntry *tle = get_sortgroupclause_tle(sgc, subplan->targetlist);
+
+ Assert(OidIsValid(sgc->eqop));
+ ordColIdx[ordNumCols] = tle->resno;
+ ordOperators[ordNumCols] = sgc->eqop;
+ ordNumCols++;
+ }
/* And finally we can make the WindowAgg node */
plan = make_windowagg(tlist,
wc->frameOptions,
wc->startOffset,
wc->endOffset,
+ wc->startInRangeFunc,
+ wc->endInRangeFunc,
+ wc->inRangeColl,
+ wc->inRangeAsc,
+ wc->inRangeNullsFirst,
subplan);
copy_generic_path_info(&plan->plan, (Path *) best_path);
return plan;
}
-/*
- * get_column_info_for_window
- * Get the partitioning/ordering column numbers and equality operators
- * for a WindowAgg node.
- *
- * This depends on the behavior of planner.c's make_pathkeys_for_window!
- *
- * We are given the target WindowClause and an array of the input column
- * numbers associated with the resulting pathkeys. In the easy case, there
- * are the same number of pathkey columns as partitioning + ordering columns
- * and we just have to copy some data around. However, it's possible that
- * some of the original partitioning + ordering columns were eliminated as
- * redundant during the transformation to pathkeys. (This can happen even
- * though the parser gets rid of obvious duplicates. A typical scenario is a
- * window specification "PARTITION BY x ORDER BY y" coupled with a clause
- * "WHERE x = y" that causes the two sort columns to be recognized as
- * redundant.) In that unusual case, we have to work a lot harder to
- * determine which keys are significant.
- *
- * The method used here is a bit brute-force: add the sort columns to a list
- * one at a time and note when the resulting pathkey list gets longer. But
- * it's a sufficiently uncommon case that a faster way doesn't seem worth
- * the amount of code refactoring that'd be needed.
- */
-static void
-get_column_info_for_window(PlannerInfo *root, WindowClause *wc, List *tlist,
- int numSortCols, AttrNumber *sortColIdx,
- int *partNumCols,
- AttrNumber **partColIdx,
- Oid **partOperators,
- int *ordNumCols,
- AttrNumber **ordColIdx,
- Oid **ordOperators)
-{
- int numPart = list_length(wc->partitionClause);
- int numOrder = list_length(wc->orderClause);
-
- if (numSortCols == numPart + numOrder)
- {
- /* easy case */
- *partNumCols = numPart;
- *partColIdx = sortColIdx;
- *partOperators = extract_grouping_ops(wc->partitionClause);
- *ordNumCols = numOrder;
- *ordColIdx = sortColIdx + numPart;
- *ordOperators = extract_grouping_ops(wc->orderClause);
- }
- else
- {
- List *sortclauses;
- List *pathkeys;
- int scidx;
- ListCell *lc;
-
- /* first, allocate what's certainly enough space for the arrays */
- *partNumCols = 0;
- *partColIdx = (AttrNumber *) palloc(numPart * sizeof(AttrNumber));
- *partOperators = (Oid *) palloc(numPart * sizeof(Oid));
- *ordNumCols = 0;
- *ordColIdx = (AttrNumber *) palloc(numOrder * sizeof(AttrNumber));
- *ordOperators = (Oid *) palloc(numOrder * sizeof(Oid));
- sortclauses = NIL;
- pathkeys = NIL;
- scidx = 0;
- foreach(lc, wc->partitionClause)
- {
- SortGroupClause *sgc = (SortGroupClause *) lfirst(lc);
- List *new_pathkeys;
-
- sortclauses = lappend(sortclauses, sgc);
- new_pathkeys = make_pathkeys_for_sortclauses(root,
- sortclauses,
- tlist);
- if (list_length(new_pathkeys) > list_length(pathkeys))
- {
- /* this sort clause is actually significant */
- (*partColIdx)[*partNumCols] = sortColIdx[scidx++];
- (*partOperators)[*partNumCols] = sgc->eqop;
- (*partNumCols)++;
- pathkeys = new_pathkeys;
- }
- }
- foreach(lc, wc->orderClause)
- {
- SortGroupClause *sgc = (SortGroupClause *) lfirst(lc);
- List *new_pathkeys;
-
- sortclauses = lappend(sortclauses, sgc);
- new_pathkeys = make_pathkeys_for_sortclauses(root,
- sortclauses,
- tlist);
- if (list_length(new_pathkeys) > list_length(pathkeys))
- {
- /* this sort clause is actually significant */
- (*ordColIdx)[*ordNumCols] = sortColIdx[scidx++];
- (*ordOperators)[*ordNumCols] = sgc->eqop;
- (*ordNumCols)++;
- pathkeys = new_pathkeys;
- }
- }
- /* complain if we didn't eat exactly the right number of sort cols */
- if (scidx != numSortCols)
- elog(ERROR, "failed to deconstruct sort operators into partitioning/ordering operators");
- }
-}
-
/*
* create_setop_plan
*
best_path->operation,
best_path->canSetTag,
best_path->nominalRelation,
- best_path->partitioned_rels,
+ best_path->rootRelation,
+ best_path->partColsUpdated,
best_path->resultRelations,
subplans,
+ best_path->subroots,
best_path->withCheckOptionLists,
best_path->returningLists,
best_path->rowMarks,
qpqual = NIL;
foreach(l, scan_clauses)
{
- RestrictInfo *rinfo = castNode(RestrictInfo, lfirst(l));
+ RestrictInfo *rinfo = lfirst_node(RestrictInfo, l);
if (rinfo->pseudoconstant)
continue; /* we may drop pseudoconstants here */
if (is_redundant_derived_clause(rinfo, indexquals))
continue; /* derived from same EquivalenceClass */
if (!contain_mutable_functions((Node *) rinfo->clause) &&
- predicate_implied_by(list_make1(rinfo->clause), indexquals))
+ predicate_implied_by(list_make1(rinfo->clause), indexquals, false))
continue; /* provably implied by indexquals */
qpqual = lappend(qpqual, rinfo);
}
exprtype,
pathkey->pk_strategy);
if (!OidIsValid(sortop))
- elog(ERROR, "failed to find sort operator for ORDER BY expression");
+ elog(ERROR, "missing operator %d(%u,%u) in opfamily %u",
+ pathkey->pk_strategy, exprtype, exprtype, pathkey->pk_opfamily);
indexorderbyops = lappend_oid(indexorderbyops, sortop);
}
}
indexoid,
fixed_indexquals,
fixed_indexorderbys,
- best_path->indexinfo->indextlist,
+ best_path->indexinfo->indextlist,
best_path->indexscandir);
else
scan_plan = (Scan *) make_indexscan(tlist,
qpqual = NIL;
foreach(l, scan_clauses)
{
- RestrictInfo *rinfo = castNode(RestrictInfo, lfirst(l));
+ RestrictInfo *rinfo = lfirst_node(RestrictInfo, l);
Node *clause = (Node *) rinfo->clause;
if (rinfo->pseudoconstant)
if (rinfo->parent_ec && list_member_ptr(indexECs, rinfo->parent_ec))
continue; /* derived from same EquivalenceClass */
if (!contain_mutable_functions(clause) &&
- predicate_implied_by(list_make1(clause), indexquals))
+ predicate_implied_by(list_make1(clause), indexquals, false))
continue; /* provably implied by indexquals */
qpqual = lappend(qpqual, rinfo);
}
clamp_row_est(apath->bitmapselectivity * apath->path.parent->tuples);
plan->plan_width = 0; /* meaningless */
plan->parallel_aware = false;
+ plan->parallel_safe = apath->path.parallel_safe;
*qual = subquals;
*indexqual = subindexquals;
*indexECs = subindexECs;
plan->total_cost = opath->path.total_cost;
plan->plan_rows =
clamp_row_est(opath->bitmapselectivity * opath->path.parent->tuples);
- plan->plan_width = 0; /* meaningless */
+ plan->plan_width = 0; /* meaningless */
plan->parallel_aware = false;
+ plan->parallel_safe = opath->path.parallel_safe;
}
/*
clamp_row_est(ipath->indexselectivity * ipath->path.parent->tuples);
plan->plan_width = 0; /* meaningless */
plan->parallel_aware = false;
+ plan->parallel_safe = ipath->path.parallel_safe;
*qual = get_actual_clauses(ipath->indexclauses);
*indexqual = get_actual_clauses(ipath->indexquals);
foreach(l, ipath->indexinfo->indpred)
* the conditions that got pushed into the bitmapqual. Avoid
* generating redundant conditions.
*/
- if (!predicate_implied_by(list_make1(pred), ipath->indexclauses))
+ if (!predicate_implied_by(list_make1(pred), ipath->indexclauses,
+ false))
{
*qual = lappend(*qual, pred);
*indexqual = lappend(*indexqual, pred);
TidScan *scan_plan;
Index scan_relid = best_path->path.parent->relid;
List *tidquals = best_path->tidquals;
- List *ortidquals;
/* it should be a base rel... */
Assert(scan_relid > 0);
Assert(best_path->path.parent->rtekind == RTE_RELATION);
+ /*
+ * The qpqual list must contain all restrictions not enforced by the
+ * tidquals list. Since tidquals has OR semantics, we have to be careful
+ * about matching it up to scan_clauses. It's convenient to handle the
+ * single-tidqual case separately from the multiple-tidqual case. In the
+ * single-tidqual case, we look through the scan_clauses while they are
+ * still in RestrictInfo form, and drop any that are redundant with the
+ * tidqual.
+ *
+ * In normal cases simple pointer equality checks will be enough to spot
+ * duplicate RestrictInfos, so we try that first.
+ *
+ * Another common case is that a scan_clauses entry is generated from the
+ * same EquivalenceClass as some tidqual, and is therefore redundant with
+ * it, though not equal.
+ *
+ * Unlike indexpaths, we don't bother with predicate_implied_by(); the
+ * number of cases where it could win are pretty small.
+ */
+ if (list_length(tidquals) == 1)
+ {
+ List *qpqual = NIL;
+ ListCell *l;
+
+ foreach(l, scan_clauses)
+ {
+ RestrictInfo *rinfo = lfirst_node(RestrictInfo, l);
+
+ if (rinfo->pseudoconstant)
+ continue; /* we may drop pseudoconstants here */
+ if (list_member_ptr(tidquals, rinfo))
+ continue; /* simple duplicate */
+ if (is_redundant_derived_clause(rinfo, tidquals))
+ continue; /* derived from same EquivalenceClass */
+ qpqual = lappend(qpqual, rinfo);
+ }
+ scan_clauses = qpqual;
+ }
+
/* Sort clauses into best execution order */
scan_clauses = order_qual_clauses(root, scan_clauses);
- /* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */
+ /* Reduce RestrictInfo lists to bare expressions; ignore pseudoconstants */
+ tidquals = extract_actual_clauses(tidquals, false);
scan_clauses = extract_actual_clauses(scan_clauses, false);
+ /*
+ * If we have multiple tidquals, it's more convenient to remove duplicate
+ * scan_clauses after stripping the RestrictInfos. In this situation,
+ * because the tidquals represent OR sub-clauses, they could not have come
+ * from EquivalenceClasses so we don't have to worry about matching up
+ * non-identical clauses. On the other hand, because tidpath.c will have
+ * extracted those sub-clauses from some OR clause and built its own list,
+ * we will certainly not have pointer equality to any scan clause. So
+ * convert the tidquals list to an explicit OR clause and see if we can
+ * match it via equal() to any scan clause.
+ */
+ if (list_length(tidquals) > 1)
+ scan_clauses = list_difference(scan_clauses,
+ list_make1(make_orclause(tidquals)));
+
/* Replace any outer-relation variables with nestloop params */
if (best_path->path.param_info)
{
replace_nestloop_params(root, (Node *) scan_clauses);
}
- /*
- * Remove any clauses that are TID quals. This is a bit tricky since the
- * tidquals list has implicit OR semantics.
- */
- ortidquals = tidquals;
- if (list_length(ortidquals) > 1)
- ortidquals = list_make1(make_orclause(ortidquals));
- scan_clauses = list_difference(scan_clauses, ortidquals);
-
scan_plan = make_tidscan(tlist,
scan_clauses,
scan_relid,
return scan_plan;
}
+/*
+ * create_namedtuplestorescan_plan
+ * Returns a tuplestorescan plan for the base relation scanned by
+ * 'best_path' with restriction clauses 'scan_clauses' and targetlist
+ * 'tlist'.
+ */
+static NamedTuplestoreScan *
+create_namedtuplestorescan_plan(PlannerInfo *root, Path *best_path,
+ List *tlist, List *scan_clauses)
+{
+ NamedTuplestoreScan *scan_plan;
+ Index scan_relid = best_path->parent->relid;
+ RangeTblEntry *rte;
+
+ Assert(scan_relid > 0);
+ rte = planner_rt_fetch(scan_relid, root);
+ Assert(rte->rtekind == RTE_NAMEDTUPLESTORE);
+
+ /* Sort clauses into best execution order */
+ scan_clauses = order_qual_clauses(root, scan_clauses);
+
+ /* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */
+ scan_clauses = extract_actual_clauses(scan_clauses, false);
+
+ /* Replace any outer-relation variables with nestloop params */
+ if (best_path->param_info)
+ {
+ scan_clauses = (List *)
+ replace_nestloop_params(root, (Node *) scan_clauses);
+ }
+
+ scan_plan = make_namedtuplestorescan(tlist, scan_clauses, scan_relid,
+ rte->enrname);
+
+ copy_generic_path_info(&scan_plan->scan.plan, best_path);
+
+ return scan_plan;
+}
+
/*
* create_worktablescan_plan
* Returns a worktablescan plan for the base relation scanned by 'best_path'
if (!cteroot) /* shouldn't happen */
elog(ERROR, "bad levelsup for CTE \"%s\"", rte->ctename);
}
- if (cteroot->wt_param_id < 0) /* shouldn't happen */
+ if (cteroot->wt_param_id < 0) /* shouldn't happen */
elog(ERROR, "could not find param ID for CTE \"%s\"", rte->ctename);
/* Sort clauses into best execution order */
if (IS_OUTER_JOIN(best_path->jointype))
{
extract_actual_join_clauses(joinrestrictclauses,
+ best_path->path.parent->relids,
&joinclauses, &otherclauses);
}
else
bms_overlap(((PlaceHolderVar *) nlp->paramval)->phrels,
outerrelids) &&
bms_is_subset(find_placeholder_info(root,
- (PlaceHolderVar *) nlp->paramval,
+ (PlaceHolderVar *) nlp->paramval,
false)->ph_eval_at,
outerrelids))
{
nestParams,
outer_plan,
inner_plan,
- best_path->jointype);
+ best_path->jointype,
+ best_path->inner_unique);
copy_generic_path_info(&join_plan->join.plan, &best_path->path);
Oid *mergecollations;
int *mergestrategies;
bool *mergenullsfirst;
+ PathKey *opathkey;
+ EquivalenceClass *opeclass;
int i;
ListCell *lc;
ListCell *lop;
ListCell *lip;
+ Path *outer_path = best_path->jpath.outerjoinpath;
+ Path *inner_path = best_path->jpath.innerjoinpath;
/*
* MergeJoin can project, so we don't have to demand exact tlists from the
* necessary.
*/
outer_plan = create_plan_recurse(root, best_path->jpath.outerjoinpath,
- (best_path->outersortkeys != NIL) ? CP_SMALL_TLIST : 0);
+ (best_path->outersortkeys != NIL) ? CP_SMALL_TLIST : 0);
inner_plan = create_plan_recurse(root, best_path->jpath.innerjoinpath,
- (best_path->innersortkeys != NIL) ? CP_SMALL_TLIST : 0);
+ (best_path->innersortkeys != NIL) ? CP_SMALL_TLIST : 0);
/* Sort join qual clauses into best execution order */
/* NB: do NOT reorder the mergeclauses */
if (IS_OUTER_JOIN(best_path->jpath.jointype))
{
extract_actual_join_clauses(joinclauses,
+ best_path->jpath.path.parent->relids,
&joinclauses, &otherclauses);
}
else
* outer_is_left status.
*/
mergeclauses = get_switched_clauses(best_path->path_mergeclauses,
- best_path->jpath.outerjoinpath->parent->relids);
+ best_path->jpath.outerjoinpath->parent->relids);
/*
* Create explicit sort nodes for the outer and inner paths if necessary.
*/
if (best_path->outersortkeys)
{
+ Relids outer_relids = outer_path->parent->relids;
Sort *sort = make_sort_from_pathkeys(outer_plan,
- best_path->outersortkeys);
+ best_path->outersortkeys,
+ outer_relids);
label_sort_with_costsize(root, sort, -1.0);
outer_plan = (Plan *) sort;
if (best_path->innersortkeys)
{
+ Relids inner_relids = inner_path->parent->relids;
Sort *sort = make_sort_from_pathkeys(inner_plan,
- best_path->innersortkeys);
+ best_path->innersortkeys,
+ inner_relids);
label_sort_with_costsize(root, sort, -1.0);
inner_plan = (Plan *) sort;
* Compute the opfamily/collation/strategy/nullsfirst arrays needed by the
* executor. The information is in the pathkeys for the two inputs, but
* we need to be careful about the possibility of mergeclauses sharing a
- * pathkey (compare find_mergeclauses_for_pathkeys()).
+ * pathkey, as well as the possibility that the inner pathkeys are not in
+ * an order matching the mergeclauses.
*/
nClauses = list_length(mergeclauses);
Assert(nClauses == list_length(best_path->path_mergeclauses));
mergestrategies = (int *) palloc(nClauses * sizeof(int));
mergenullsfirst = (bool *) palloc(nClauses * sizeof(bool));
+ opathkey = NULL;
+ opeclass = NULL;
lop = list_head(outerpathkeys);
lip = list_head(innerpathkeys);
i = 0;
foreach(lc, best_path->path_mergeclauses)
{
- RestrictInfo *rinfo = castNode(RestrictInfo, lfirst(lc));
+ RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc);
EquivalenceClass *oeclass;
EquivalenceClass *ieclass;
- PathKey *opathkey;
- PathKey *ipathkey;
- EquivalenceClass *opeclass;
- EquivalenceClass *ipeclass;
- ListCell *l2;
+ PathKey *ipathkey = NULL;
+ EquivalenceClass *ipeclass = NULL;
+ bool first_inner_match = false;
/* fetch outer/inner eclass from mergeclause */
if (rinfo->outer_is_left)
Assert(ieclass != NULL);
/*
- * For debugging purposes, we check that the eclasses match the paths'
- * pathkeys. In typical cases the merge clauses are one-to-one with
- * the pathkeys, but when dealing with partially redundant query
- * conditions, we might have clauses that re-reference earlier path
- * keys. The case that we need to reject is where a pathkey is
- * entirely skipped over.
+ * We must identify the pathkey elements associated with this clause
+ * by matching the eclasses (which should give a unique match, since
+ * the pathkey lists should be canonical). In typical cases the merge
+ * clauses are one-to-one with the pathkeys, but when dealing with
+ * partially redundant query conditions, things are more complicated.
+ *
+ * lop and lip reference the first as-yet-unmatched pathkey elements.
+ * If they're NULL then all pathkey elements have been matched.
*
- * lop and lip reference the first as-yet-unused pathkey elements;
- * it's okay to match them, or any element before them. If they're
- * NULL then we have found all pathkey elements to be used.
+ * The ordering of the outer pathkeys should match the mergeclauses,
+ * by construction (see find_mergeclauses_for_outer_pathkeys()). There
+ * could be more than one mergeclause for the same outer pathkey, but
+ * no pathkey may be entirely skipped over.
*/
- if (lop)
+ if (oeclass != opeclass) /* multiple matches are not interesting */
{
+ /* doesn't match the current opathkey, so must match the next */
+ if (lop == NULL)
+ elog(ERROR, "outer pathkeys do not match mergeclauses");
opathkey = (PathKey *) lfirst(lop);
opeclass = opathkey->pk_eclass;
- if (oeclass == opeclass)
- {
- /* fast path for typical case */
- lop = lnext(lop);
- }
- else
- {
- /* redundant clauses ... must match something before lop */
- foreach(l2, outerpathkeys)
- {
- if (l2 == lop)
- break;
- opathkey = (PathKey *) lfirst(l2);
- opeclass = opathkey->pk_eclass;
- if (oeclass == opeclass)
- break;
- }
- if (oeclass != opeclass)
- elog(ERROR, "outer pathkeys do not match mergeclauses");
- }
- }
- else
- {
- /* redundant clauses ... must match some already-used pathkey */
- opathkey = NULL;
- opeclass = NULL;
- foreach(l2, outerpathkeys)
- {
- opathkey = (PathKey *) lfirst(l2);
- opeclass = opathkey->pk_eclass;
- if (oeclass == opeclass)
- break;
- }
- if (l2 == NULL)
+ lop = lnext(lop);
+ if (oeclass != opeclass)
elog(ERROR, "outer pathkeys do not match mergeclauses");
}
+ /*
+ * The inner pathkeys likewise should not have skipped-over keys, but
+ * it's possible for a mergeclause to reference some earlier inner
+ * pathkey if we had redundant pathkeys. For example we might have
+ * mergeclauses like "o.a = i.x AND o.b = i.y AND o.c = i.x". The
+ * implied inner ordering is then "ORDER BY x, y, x", but the pathkey
+ * mechanism drops the second sort by x as redundant, and this code
+ * must cope.
+ *
+ * It's also possible for the implied inner-rel ordering to be like
+ * "ORDER BY x, y, x DESC". We still drop the second instance of x as
+ * redundant; but this means that the sort ordering of a redundant
+ * inner pathkey should not be considered significant. So we must
+ * detect whether this is the first clause matching an inner pathkey.
+ */
if (lip)
{
ipathkey = (PathKey *) lfirst(lip);
ipeclass = ipathkey->pk_eclass;
if (ieclass == ipeclass)
{
- /* fast path for typical case */
+ /* successful first match to this inner pathkey */
lip = lnext(lip);
- }
- else
- {
- /* redundant clauses ... must match something before lip */
- foreach(l2, innerpathkeys)
- {
- if (l2 == lip)
- break;
- ipathkey = (PathKey *) lfirst(l2);
- ipeclass = ipathkey->pk_eclass;
- if (ieclass == ipeclass)
- break;
- }
- if (ieclass != ipeclass)
- elog(ERROR, "inner pathkeys do not match mergeclauses");
+ first_inner_match = true;
}
}
- else
+ if (!first_inner_match)
{
- /* redundant clauses ... must match some already-used pathkey */
- ipathkey = NULL;
- ipeclass = NULL;
+ /* redundant clause ... must match something before lip */
+ ListCell *l2;
+
foreach(l2, innerpathkeys)
{
+ if (l2 == lip)
+ break;
ipathkey = (PathKey *) lfirst(l2);
ipeclass = ipathkey->pk_eclass;
if (ieclass == ipeclass)
break;
}
- if (l2 == NULL)
+ if (ieclass != ipeclass)
elog(ERROR, "inner pathkeys do not match mergeclauses");
}
- /* pathkeys should match each other too (more debugging) */
+ /*
+ * The pathkeys should always match each other as to opfamily and
+ * collation (which affect equality), but if we're considering a
+ * redundant inner pathkey, its sort ordering might not match. In
+ * such cases we may ignore the inner pathkey's sort ordering and use
+ * the outer's. (In effect, we're lying to the executor about the
+ * sort direction of this inner column, but it does not matter since
+ * the run-time row comparisons would only reach this column when
+ * there's equality for the earlier column containing the same eclass.
+ * There could be only one value in this column for the range of inner
+ * rows having a given value in the earlier column, so it does not
+ * matter which way we imagine this column to be ordered.) But a
+ * non-redundant inner pathkey had better match outer's ordering too.
+ */
if (opathkey->pk_opfamily != ipathkey->pk_opfamily ||
- opathkey->pk_eclass->ec_collation != ipathkey->pk_eclass->ec_collation ||
- opathkey->pk_strategy != ipathkey->pk_strategy ||
- opathkey->pk_nulls_first != ipathkey->pk_nulls_first)
+ opathkey->pk_eclass->ec_collation != ipathkey->pk_eclass->ec_collation)
+ elog(ERROR, "left and right pathkeys do not match in mergejoin");
+ if (first_inner_match &&
+ (opathkey->pk_strategy != ipathkey->pk_strategy ||
+ opathkey->pk_nulls_first != ipathkey->pk_nulls_first))
elog(ERROR, "left and right pathkeys do not match in mergejoin");
/* OK, save info for executor */
mergenullsfirst,
outer_plan,
inner_plan,
- best_path->jpath.jointype);
+ best_path->jpath.jointype,
+ best_path->jpath.inner_unique,
+ best_path->skip_mark_restore);
/* Costs of sort and material steps are included in path cost already */
copy_generic_path_info(&join_plan->join.plan, &best_path->jpath.path);
Oid skewTable = InvalidOid;
AttrNumber skewColumn = InvalidAttrNumber;
bool skewInherit = false;
- Oid skewColType = InvalidOid;
- int32 skewColTypmod = -1;
/*
* HashJoin can project, so we don't have to demand exact tlists from the
* that we don't put extra data in the outer batch files.
*/
outer_plan = create_plan_recurse(root, best_path->jpath.outerjoinpath,
- (best_path->num_batches > 1) ? CP_SMALL_TLIST : 0);
+ (best_path->num_batches > 1) ? CP_SMALL_TLIST : 0);
inner_plan = create_plan_recurse(root, best_path->jpath.innerjoinpath,
CP_SMALL_TLIST);
if (IS_OUTER_JOIN(best_path->jpath.jointype))
{
extract_actual_join_clauses(joinclauses,
+ best_path->jpath.path.parent->relids,
&joinclauses, &otherclauses);
}
else
* on the left.
*/
hashclauses = get_switched_clauses(best_path->path_hashclauses,
- best_path->jpath.outerjoinpath->parent->relids);
+ best_path->jpath.outerjoinpath->parent->relids);
/*
* If there is a single join clause and we can identify the outer variable
skewTable = rte->relid;
skewColumn = var->varattno;
skewInherit = rte->inh;
- skewColType = var->vartype;
- skewColTypmod = var->vartypmod;
}
}
}
hash_plan = make_hash(inner_plan,
skewTable,
skewColumn,
- skewInherit,
- skewColType,
- skewColTypmod);
+ skewInherit);
/*
* Set Hash node's startup & total costs equal to total cost of input
copy_plan_costsize(&hash_plan->plan, inner_plan);
hash_plan->plan.startup_cost = hash_plan->plan.total_cost;
+ /*
+ * If parallel-aware, the executor will also need an estimate of the total
+ * number of rows expected from all participants so that it can size the
+ * shared hash table.
+ */
+ if (best_path->jpath.path.parallel_aware)
+ {
+ hash_plan->plan.parallel_aware = true;
+ hash_plan->rows_total = best_path->inner_rows_total;
+ }
+
join_plan = make_hashjoin(tlist,
joinclauses,
otherclauses,
hashclauses,
outer_plan,
(Plan *) hash_plan,
- best_path->jpath.jointype);
+ best_path->jpath.jointype,
+ best_path->jpath.inner_unique);
copy_generic_path_info(&join_plan->join.plan, &best_path->jpath.path);
* rels, and then grab its PlaceHolderInfo to tell for sure.
*/
if (!bms_overlap(phv->phrels, root->curOuterRels) ||
- !bms_is_subset(find_placeholder_info(root, phv, false)->ph_eval_at,
- root->curOuterRels))
+ !bms_is_subset(find_placeholder_info(root, phv, false)->ph_eval_at,
+ root->curOuterRels))
{
/*
* We can't replace the whole PHV, but we might still need to
forboth(lcc, index_path->indexquals, lci, index_path->indexqualcols)
{
- RestrictInfo *rinfo = castNode(RestrictInfo, lfirst(lcc));
+ RestrictInfo *rinfo = lfirst_node(RestrictInfo, lcc);
int indexcol = lfirst_int(lci);
Node *clause;
/*
* Copy cost and size info from a Path node to the Plan node created from it.
* The executor usually won't use this info, but it's needed by EXPLAIN.
- * Also copy the parallel-aware flag, which the executor *will* use.
+ * Also copy the parallel-related flags, which the executor *will* use.
*/
static void
copy_generic_path_info(Plan *dest, Path *src)
dest->plan_rows = src->rows;
dest->plan_width = src->pathtarget->width;
dest->parallel_aware = src->parallel_aware;
+ dest->parallel_safe = src->parallel_safe;
}
/*
dest->plan_width = src->plan_width;
/* Assume the inserted node is not parallel-aware. */
dest->parallel_aware = false;
+ /* Assume the inserted node is parallel-safe, if child plan is. */
+ dest->parallel_safe = src->parallel_safe;
}
/*
plan->plan.plan_rows = lefttree->plan_rows;
plan->plan.plan_width = lefttree->plan_width;
plan->plan.parallel_aware = false;
+ plan->plan.parallel_safe = lefttree->parallel_safe;
}
/*
* bitmap_subplan_mark_shared
- * Set isshared flag in bitmap subplan so that it will be created in
+ * Set isshared flag in bitmap subplan so that it will be created in
* shared memory.
*/
static void
{
if (IsA(plan, BitmapAnd))
bitmap_subplan_mark_shared(
- linitial(((BitmapAnd *) plan)->bitmapplans));
+ linitial(((BitmapAnd *) plan)->bitmapplans));
else if (IsA(plan, BitmapOr))
+ {
((BitmapOr *) plan)->isshared = true;
+ bitmap_subplan_mark_shared(
+ linitial(((BitmapOr *) plan)->bitmapplans));
+ }
else if (IsA(plan, BitmapIndexScan))
((BitmapIndexScan *) plan)->isshared = true;
else
return node;
}
+static NamedTuplestoreScan *
+make_namedtuplestorescan(List *qptlist,
+ List *qpqual,
+ Index scanrelid,
+ char *enrname)
+{
+ NamedTuplestoreScan *node = makeNode(NamedTuplestoreScan);
+ Plan *plan = &node->scan.plan;
+
+ /* cost should be inserted by caller */
+ plan->targetlist = qptlist;
+ plan->qual = qpqual;
+ plan->lefttree = NULL;
+ plan->righttree = NULL;
+ node->scan.scanrelid = scanrelid;
+ node->enrname = enrname;
+
+ return node;
+}
+
static WorkTableScan *
make_worktablescan(List *qptlist,
List *qpqual,
}
static Append *
-make_append(List *appendplans, List *tlist, List *partitioned_rels)
+make_append(List *appendplans, int first_partial_plan,
+ List *tlist, PartitionPruneInfo *partpruneinfo)
{
Append *node = makeNode(Append);
Plan *plan = &node->plan;
plan->qual = NIL;
plan->lefttree = NULL;
plan->righttree = NULL;
- node->partitioned_rels = partitioned_rels;
node->appendplans = appendplans;
-
+ node->first_partial_plan = first_partial_plan;
+ node->part_prune_info = partpruneinfo;
return node;
}
List *nestParams,
Plan *lefttree,
Plan *righttree,
- JoinType jointype)
+ JoinType jointype,
+ bool inner_unique)
{
NestLoop *node = makeNode(NestLoop);
Plan *plan = &node->join.plan;
plan->lefttree = lefttree;
plan->righttree = righttree;
node->join.jointype = jointype;
+ node->join.inner_unique = inner_unique;
node->join.joinqual = joinclauses;
node->nestParams = nestParams;
List *hashclauses,
Plan *lefttree,
Plan *righttree,
- JoinType jointype)
+ JoinType jointype,
+ bool inner_unique)
{
HashJoin *node = makeNode(HashJoin);
Plan *plan = &node->join.plan;
plan->righttree = righttree;
node->hashclauses = hashclauses;
node->join.jointype = jointype;
+ node->join.inner_unique = inner_unique;
node->join.joinqual = joinclauses;
return node;
make_hash(Plan *lefttree,
Oid skewTable,
AttrNumber skewColumn,
- bool skewInherit,
- Oid skewColType,
- int32 skewColTypmod)
+ bool skewInherit)
{
Hash *node = makeNode(Hash);
Plan *plan = &node->plan;
node->skewTable = skewTable;
node->skewColumn = skewColumn;
node->skewInherit = skewInherit;
- node->skewColType = skewColType;
- node->skewColTypmod = skewColTypmod;
return node;
}
bool *mergenullsfirst,
Plan *lefttree,
Plan *righttree,
- JoinType jointype)
+ JoinType jointype,
+ bool inner_unique,
+ bool skip_mark_restore)
{
MergeJoin *node = makeNode(MergeJoin);
Plan *plan = &node->join.plan;
plan->qual = otherclauses;
plan->lefttree = lefttree;
plan->righttree = righttree;
+ node->skip_mark_restore = skip_mark_restore;
node->mergeclauses = mergeclauses;
node->mergeFamilies = mergefamilies;
node->mergeCollations = mergecollations;
node->mergeStrategies = mergestrategies;
node->mergeNullsFirst = mergenullsfirst;
node->join.jointype = jointype;
+ node->join.inner_unique = inner_unique;
node->join.joinqual = joinclauses;
return node;
* 'pathkeys' is the list of pathkeys by which the result is to be sorted
* 'relids' identifies the child relation being sorted, if any
* 'reqColIdx' is NULL or an array of required sort key column numbers
- * 'adjust_tlist_in_place' is TRUE if lefttree must be modified in-place
+ * '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, sort operator OIDs, collation OIDs, and nulls-first flags,
* the output parameters *p_numsortkeys etc.
*
* When looking for matches to an EquivalenceClass's members, we will only
- * consider child EC members if they match 'relids'. This protects against
- * possible incorrect matches to child expressions that contain no Vars.
+ * consider child EC members if they belong to given 'relids'. This protects
+ * against possible incorrect matches to child expressions that contain no
+ * Vars.
*
* If reqColIdx isn't NULL then it contains sort key column numbers that
* we should match. This is used when making child plans for a MergeAppend;
* compute these expressions, since a Sort or 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
+ * 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.
*
continue;
/*
- * Ignore child members unless they match the rel being
+ * Ignore child members unless they belong to the rel being
* sorted.
*/
if (em->em_is_child &&
- !bms_equal(em->em_relids, relids))
+ !bms_is_subset(em->em_relids, relids))
continue;
sortexpr = em->em_expr;
{
/* copy needed so we don't modify input's tlist below */
tlist = copyObject(tlist);
- lefttree = inject_projection_plan(lefttree, tlist);
+ lefttree = inject_projection_plan(lefttree, tlist,
+ lefttree->parallel_safe);
}
/* Don't bother testing is_projection_capable_plan again */
NULL,
true);
tlist = lappend(tlist, tle);
- lefttree->targetlist = tlist; /* just in case NIL before */
+ lefttree->targetlist = tlist; /* just in case NIL before */
}
/*
pk_datatype,
pathkey->pk_strategy);
if (!OidIsValid(sortop)) /* should not happen */
- elog(ERROR, "could not find member %d(%u,%u) of opfamily %u",
+ elog(ERROR, "missing operator %d(%u,%u) in opfamily %u",
pathkey->pk_strategy, pk_datatype, pk_datatype,
pathkey->pk_opfamily);
* find_ec_member_for_tle
* Locate an EquivalenceClass member matching the given TLE, if any
*
- * Child EC members are ignored unless they match 'relids'.
+ * Child EC members are ignored unless they belong to given 'relids'.
*/
static EquivalenceMember *
find_ec_member_for_tle(EquivalenceClass *ec,
continue;
/*
- * Ignore child members unless they match the rel being sorted.
+ * Ignore child members unless they belong to the rel being sorted.
*/
if (em->em_is_child &&
- !bms_equal(em->em_relids, relids))
+ !bms_is_subset(em->em_relids, relids))
continue;
/* Match if same expression (after stripping relabel) */
*
* 'lefttree' is the node which yields input tuples
* 'pathkeys' is the list of pathkeys by which the result is to be sorted
+ * 'relids' is the set of relations required by prepare_sort_from_pathkeys()
*/
static Sort *
-make_sort_from_pathkeys(Plan *lefttree, List *pathkeys)
+make_sort_from_pathkeys(Plan *lefttree, List *pathkeys, Relids relids)
{
int numsortkeys;
AttrNumber *sortColIdx;
/* Compute sort column info, and adjust lefttree as needed */
lefttree = prepare_sort_from_pathkeys(lefttree, pathkeys,
- NULL,
+ relids,
NULL,
false,
&numsortkeys,
matplan->plan_rows = subplan->plan_rows;
matplan->plan_width = subplan->plan_width;
matplan->parallel_aware = false;
+ matplan->parallel_safe = subplan->parallel_safe;
return matplan;
}
int partNumCols, AttrNumber *partColIdx, Oid *partOperators,
int ordNumCols, AttrNumber *ordColIdx, Oid *ordOperators,
int frameOptions, Node *startOffset, Node *endOffset,
+ Oid startInRangeFunc, Oid endInRangeFunc,
+ Oid inRangeColl, bool inRangeAsc, bool inRangeNullsFirst,
Plan *lefttree)
{
WindowAgg *node = makeNode(WindowAgg);
node->frameOptions = frameOptions;
node->startOffset = startOffset;
node->endOffset = endOffset;
+ node->startInRangeFunc = startInRangeFunc;
+ node->endInRangeFunc = endInRangeFunc;
+ node->inRangeColl = inRangeColl;
+ node->inRangeAsc = inRangeAsc;
+ node->inRangeNullsFirst = inRangeNullsFirst;
plan->targetlist = tlist;
plan->lefttree = lefttree;
pk_datatype,
BTEqualStrategyNumber);
if (!OidIsValid(eqop)) /* should not happen */
- elog(ERROR, "could not find member %d(%u,%u) of opfamily %u",
+ elog(ERROR, "missing operator %d(%u,%u) in opfamily %u",
BTEqualStrategyNumber, pk_datatype, pk_datatype,
pathkey->pk_opfamily);
make_gather(List *qptlist,
List *qpqual,
int nworkers,
+ int rescan_param,
bool single_copy,
Plan *subplan)
{
plan->lefttree = subplan;
plan->righttree = NULL;
node->num_workers = nworkers;
+ node->rescan_param = rescan_param;
node->single_copy = single_copy;
node->invisible = false;
+ node->initParam = NULL;
return node;
}
* convert SortGroupClause list into arrays of attr indexes and equality
* operators, as wanted by executor
*/
- Assert(numCols > 0);
dupColIdx = (AttrNumber *) palloc(sizeof(AttrNumber) * numCols);
dupOperators = (Oid *) palloc(sizeof(Oid) * numCols);
static ModifyTable *
make_modifytable(PlannerInfo *root,
CmdType operation, bool canSetTag,
- Index nominalRelation, List *partitioned_rels,
- List *resultRelations, List *subplans,
+ Index nominalRelation, Index rootRelation,
+ bool partColsUpdated,
+ List *resultRelations, List *subplans, List *subroots,
List *withCheckOptionLists, List *returningLists,
List *rowMarks, OnConflictExpr *onconflict, int epqParam)
{
List *fdw_private_list;
Bitmapset *direct_modify_plans;
ListCell *lc;
+ ListCell *lc2;
int i;
Assert(list_length(resultRelations) == list_length(subplans));
+ Assert(list_length(resultRelations) == list_length(subroots));
Assert(withCheckOptionLists == NIL ||
list_length(resultRelations) == list_length(withCheckOptionLists));
Assert(returningLists == NIL ||
node->operation = operation;
node->canSetTag = canSetTag;
node->nominalRelation = nominalRelation;
- node->partitioned_rels = partitioned_rels;
+ node->rootRelation = rootRelation;
+ node->partColsUpdated = partColsUpdated;
node->resultRelations = resultRelations;
node->resultRelIndex = -1; /* will be set correctly in setrefs.c */
+ node->rootResultRelIndex = -1; /* will be set correctly in setrefs.c */
node->plans = subplans;
if (!onconflict)
{
fdw_private_list = NIL;
direct_modify_plans = NULL;
i = 0;
- foreach(lc, resultRelations)
+ forboth(lc, resultRelations, lc2, subroots)
{
Index rti = lfirst_int(lc);
+ PlannerInfo *subroot = lfirst_node(PlannerInfo, lc2);
FdwRoutine *fdwroutine;
List *fdw_private;
bool direct_modify;
* so it's not a baserel; and there are also corner cases for
* updatable views where the target rel isn't a baserel.)
*/
- if (rti < root->simple_rel_array_size &&
- root->simple_rel_array[rti] != NULL)
+ if (rti < subroot->simple_rel_array_size &&
+ subroot->simple_rel_array[rti] != NULL)
{
- RelOptInfo *resultRel = root->simple_rel_array[rti];
+ RelOptInfo *resultRel = subroot->simple_rel_array[rti];
fdwroutine = resultRel->fdwroutine;
}
else
{
- RangeTblEntry *rte = planner_rt_fetch(rti, root);
+ RangeTblEntry *rte = planner_rt_fetch(rti, subroot);
Assert(rte->rtekind == RTE_RELATION);
if (rte->relkind == RELKIND_FOREIGN_TABLE)
}
/*
- * If the target foreign table has any row-level triggers, we can't
- * modify the foreign table directly.
+ * Try to modify the foreign table directly if (1) the FDW provides
+ * callback functions needed for that, (2) there are no row-level
+ * triggers on the foreign table, and (3) there are no WITH CHECK
+ * OPTIONs from parent views.
*/
direct_modify = false;
if (fdwroutine != NULL &&
fdwroutine->BeginDirectModify != NULL &&
fdwroutine->IterateDirectModify != NULL &&
fdwroutine->EndDirectModify != NULL &&
- !has_row_triggers(root, rti, operation))
- direct_modify = fdwroutine->PlanDirectModify(root, node, rti, i);
+ withCheckOptionLists == NIL &&
+ !has_row_triggers(subroot, rti, operation))
+ direct_modify = fdwroutine->PlanDirectModify(subroot, node, rti, i);
if (direct_modify)
direct_modify_plans = bms_add_member(direct_modify_plans, i);
if (!direct_modify &&
fdwroutine != NULL &&
fdwroutine->PlanForeignModify != NULL)
- fdw_private = fdwroutine->PlanForeignModify(root, node, rti, i);
+ fdw_private = fdwroutine->PlanForeignModify(subroot, node, rti, i);
else
fdw_private = NIL;
fdw_private_list = lappend(fdw_private_list, fdw_private);