X-Git-Url: https://granicus.if.org/sourcecode?a=blobdiff_plain;f=src%2Fbackend%2Foptimizer%2Fplan%2Fcreateplan.c;h=030f420c90eb37946ee333250de54af61d9b82d7;hb=46c508fbcf98ac334f1e831d21021d731c882fbb;hp=14f1f1a10f4e2071a9db39ec0138657ee054f555;hpb=f99a569a2ee3763b4ae174e81250c95ca0fdcbb6;p=postgresql diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 14f1f1a10f..030f420c90 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -5,54 +5,63 @@ * Planning is complete, we just need to convert the selected * Path into a Plan. * - * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group + * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/plan/createplan.c,v 1.217 2006/10/04 00:29:54 momjian Exp $ + * src/backend/optimizer/plan/createplan.c * *------------------------------------------------------------------------- */ #include "postgres.h" #include +#include +#include "access/skey.h" +#include "foreign/fdwapi.h" +#include "miscadmin.h" #include "nodes/makefuncs.h" +#include "nodes/nodeFuncs.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" +#include "optimizer/paths.h" +#include "optimizer/placeholder.h" #include "optimizer/plancat.h" #include "optimizer/planmain.h" +#include "optimizer/planner.h" #include "optimizer/predtest.h" #include "optimizer/restrictinfo.h" +#include "optimizer/subselect.h" #include "optimizer/tlist.h" #include "optimizer/var.h" #include "parser/parse_clause.h" -#include "parser/parse_expr.h" #include "parser/parsetree.h" #include "utils/lsyscache.h" +static Plan *create_plan_recurse(PlannerInfo *root, Path *best_path); static Plan *create_scan_plan(PlannerInfo *root, Path *best_path); static List *build_relation_tlist(RelOptInfo *rel); -static bool use_physical_tlist(RelOptInfo *rel); +static bool use_physical_tlist(PlannerInfo *root, RelOptInfo *rel); static void disuse_physical_tlist(Plan *plan, Path *path); 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 SeqScan *create_seqscan_plan(PlannerInfo *root, Path *best_path, List *tlist, List *scan_clauses); -static IndexScan *create_indexscan_plan(PlannerInfo *root, IndexPath *best_path, - List *tlist, List *scan_clauses, - List **nonlossy_clauses); +static Scan *create_indexscan_plan(PlannerInfo *root, IndexPath *best_path, + List *tlist, List *scan_clauses, bool indexonly); static BitmapHeapScan *create_bitmap_scan_plan(PlannerInfo *root, BitmapHeapPath *best_path, List *tlist, List *scan_clauses); static Plan *create_bitmap_subplan(PlannerInfo *root, Path *bitmapqual, - List **qual, List **indexqual); + List **qual, List **indexqual, List **indexECs); static TidScan *create_tidscan_plan(PlannerInfo *root, TidPath *best_path, List *tlist, List *scan_clauses); static SubqueryScan *create_subqueryscan_plan(PlannerInfo *root, Path *best_path, @@ -61,19 +70,25 @@ static FunctionScan *create_functionscan_plan(PlannerInfo *root, Path *best_path List *tlist, List *scan_clauses); static ValuesScan *create_valuesscan_plan(PlannerInfo *root, Path *best_path, List *tlist, List *scan_clauses); +static CteScan *create_ctescan_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, + List *tlist, List *scan_clauses); static NestLoop *create_nestloop_plan(PlannerInfo *root, NestPath *best_path, Plan *outer_plan, Plan *inner_plan); static MergeJoin *create_mergejoin_plan(PlannerInfo *root, MergePath *best_path, Plan *outer_plan, Plan *inner_plan); static HashJoin *create_hashjoin_plan(PlannerInfo *root, HashPath *best_path, Plan *outer_plan, Plan *inner_plan); -static void fix_indexqual_references(List *indexquals, IndexPath *index_path, - List **fixed_indexquals, - List **nonlossy_indexquals, - List **indexstrategy, - List **indexsubtype); -static Node *fix_indexqual_operand(Node *node, IndexOptInfo *index, - Oid *opclass); +static Node *replace_nestloop_params(PlannerInfo *root, Node *expr); +static Node *replace_nestloop_params_mutator(Node *node, PlannerInfo *root); +static void process_subquery_nestloop_params(PlannerInfo *root, + List *subplan_params); +static List *fix_indexqual_references(PlannerInfo *root, IndexPath *index_path); +static List *fix_indexorderby_references(PlannerInfo *root, IndexPath *index_path); +static Node *fix_indexqual_operand(Node *node, IndexOptInfo *index, int indexcol); static List *get_switched_clauses(List *clauses, Relids outerrelids); static List *order_qual_clauses(PlannerInfo *root, List *clauses); static void copy_path_costsize(Plan *dest, Path *src); @@ -81,13 +96,16 @@ static void copy_plan_costsize(Plan *dest, Plan *src); static SeqScan *make_seqscan(List *qptlist, List *qpqual, Index scanrelid); static IndexScan *make_indexscan(List *qptlist, List *qpqual, Index scanrelid, Oid indexid, List *indexqual, List *indexqualorig, - List *indexstrategy, List *indexsubtype, + List *indexorderby, List *indexorderbyorig, ScanDirection indexscandir); +static IndexOnlyScan *make_indexonlyscan(List *qptlist, List *qpqual, + Index scanrelid, Oid indexid, + List *indexqual, List *indexorderby, + List *indextlist, + ScanDirection indexscandir); static BitmapIndexScan *make_bitmap_indexscan(Index scanrelid, Oid indexid, List *indexqual, - List *indexqualorig, - List *indexstrategy, - List *indexsubtype); + List *indexqualorig); static BitmapHeapScan *make_bitmap_heapscan(List *qptlist, List *qpqual, Plan *lefttree, @@ -96,13 +114,19 @@ static BitmapHeapScan *make_bitmap_heapscan(List *qptlist, static TidScan *make_tidscan(List *qptlist, List *qpqual, Index scanrelid, List *tidquals); static FunctionScan *make_functionscan(List *qptlist, List *qpqual, - Index scanrelid); + Index scanrelid, Node *funcexpr, List *funccolnames, + List *funccoltypes, List *funccoltypmods, + List *funccolcollations); static ValuesScan *make_valuesscan(List *qptlist, List *qpqual, - Index scanrelid); + Index scanrelid, List *values_lists); +static CteScan *make_ctescan(List *qptlist, List *qpqual, + Index scanrelid, int ctePlanId, int cteParam); +static WorkTableScan *make_worktablescan(List *qptlist, List *qpqual, + Index scanrelid, int wtParam); static BitmapAnd *make_bitmap_and(List *bitmapplans); static BitmapOr *make_bitmap_or(List *bitmapplans); static NestLoop *make_nestloop(List *tlist, - List *joinclauses, List *otherclauses, + List *joinclauses, List *otherclauses, List *nestParams, Plan *lefttree, Plan *righttree, JoinType jointype); static HashJoin *make_hashjoin(List *tlist, @@ -110,28 +134,51 @@ static HashJoin *make_hashjoin(List *tlist, List *hashclauses, Plan *lefttree, Plan *righttree, JoinType jointype); -static Hash *make_hash(Plan *lefttree); +static Hash *make_hash(Plan *lefttree, + Oid skewTable, + AttrNumber skewColumn, + bool skewInherit, + Oid skewColType, + int32 skewColTypmod); static MergeJoin *make_mergejoin(List *tlist, List *joinclauses, List *otherclauses, List *mergeclauses, + Oid *mergefamilies, + Oid *mergecollations, + int *mergestrategies, + bool *mergenullsfirst, Plan *lefttree, Plan *righttree, JoinType jointype); static Sort *make_sort(PlannerInfo *root, Plan *lefttree, int numCols, - AttrNumber *sortColIdx, Oid *sortOperators); -static Sort *make_sort_from_pathkeys(PlannerInfo *root, Plan *lefttree, - List *pathkeys); + AttrNumber *sortColIdx, Oid *sortOperators, + Oid *collations, bool *nullsFirst, + double limit_tuples); +static Plan *prepare_sort_from_pathkeys(PlannerInfo *root, + Plan *lefttree, List *pathkeys, + Relids relids, + const AttrNumber *reqColIdx, + bool adjust_tlist_in_place, + int *p_numsortkeys, + AttrNumber **p_sortColIdx, + Oid **p_sortOperators, + Oid **p_collations, + bool **p_nullsFirst); +static EquivalenceMember *find_ec_member_for_tle(EquivalenceClass *ec, + TargetEntry *tle, + Relids relids); +static Material *make_material(Plan *lefttree); /* * create_plan - * Creates the access plan for a query by tracing backwards through the - * desired chain of pathnodes, starting at the node 'best_path'. For - * every pathnode found: - * (1) Create a corresponding plan node containing appropriate id, - * target list, and qualification information. - * (2) Modify qual clauses of join nodes so that subplan attributes are - * referenced using relative values. - * (3) Target lists are not modified, but will be in setrefs.c. + * Creates the access plan for a query by recursively processing the + * desired tree of pathnodes, starting at the node 'best_path'. For + * every pathnode found, we create a corresponding plan node containing + * appropriate id, target list, and qualification information. + * + * The tlists and quals in the plan tree are still in planner format, + * ie, Vars still correspond to the parser's numbering. This will be + * fixed later by setrefs.c. * * best_path is the best access path * @@ -142,15 +189,51 @@ create_plan(PlannerInfo *root, Path *best_path) { Plan *plan; + /* plan_params should not be in use in current query level */ + Assert(root->plan_params == NIL); + + /* Initialize this module's private workspace in PlannerInfo */ + root->curOuterRels = NULL; + root->curOuterParams = NIL; + + /* Recursively process the path tree */ + plan = create_plan_recurse(root, best_path); + + /* Check we successfully assigned all NestLoopParams to plan nodes */ + if (root->curOuterParams != NIL) + elog(ERROR, "failed to assign all NestLoopParams to plan nodes"); + + /* + * Reset plan_params to ensure param IDs used for nestloop params are not + * re-used later + */ + root->plan_params = NIL; + + return plan; +} + +/* + * create_plan_recurse + * Recursive guts of create_plan(). + */ +static Plan * +create_plan_recurse(PlannerInfo *root, Path *best_path) +{ + Plan *plan; + switch (best_path->pathtype) { case T_SeqScan: case T_IndexScan: + case T_IndexOnlyScan: case T_BitmapHeapScan: case T_TidScan: case T_SubqueryScan: case T_FunctionScan: case T_ValuesScan: + case T_CteScan: + case T_WorkTableScan: + case T_ForeignScan: plan = create_scan_plan(root, best_path); break; case T_HashJoin: @@ -163,6 +246,10 @@ create_plan(PlannerInfo *root, Path *best_path) 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); @@ -205,16 +292,35 @@ create_scan_plan(PlannerInfo *root, Path *best_path) * planner.c may replace the tlist we generate here, forcing projection to * occur.) */ - if (use_physical_tlist(rel)) + if (use_physical_tlist(root, rel)) { - tlist = build_physical_tlist(root, rel); - /* if fail because of dropped cols, use regular method */ - if (tlist == NIL) - tlist = build_relation_tlist(rel); + if (best_path->pathtype == T_IndexOnlyScan) + { + /* For index-only scan, the preferred tlist is the index's */ + tlist = copyObject(((IndexPath *) best_path)->indexinfo->indextlist); + } + else + { + tlist = build_physical_tlist(root, rel); + /* if fail because of dropped cols, use regular method */ + if (tlist == NIL) + tlist = build_relation_tlist(rel); + } } else + { tlist = build_relation_tlist(rel); + /* + * If it's a parameterized otherrel, there might be lateral references + * in the tlist, which need to be replaced with Params. This cannot + * happen for regular baserels, though. Note use_physical_tlist() + * always fails for otherrels, so we don't need to check this above. + */ + if (rel->reloptkind != RELOPT_BASEREL && best_path->param_info) + tlist = (List *) replace_nestloop_params(root, (Node *) tlist); + } + /* * Extract the relevant restriction clauses from the parent relation. The * executor must apply all these restrictions during the scan, except for @@ -222,6 +328,16 @@ create_scan_plan(PlannerInfo *root, Path *best_path) */ scan_clauses = rel->baserestrictinfo; + /* + * If this is a parameterized scan, we also need to enforce all the join + * clauses available from the outer relation(s). + * + * For paranoia's sake, don't modify the stored baserestrictinfo list. + */ + if (best_path->param_info) + scan_clauses = list_concat(list_copy(scan_clauses), + best_path->param_info->ppi_clauses); + switch (best_path->pathtype) { case T_SeqScan: @@ -236,7 +352,15 @@ create_scan_plan(PlannerInfo *root, Path *best_path) (IndexPath *) best_path, tlist, scan_clauses, - NULL); + false); + break; + + case T_IndexOnlyScan: + plan = (Plan *) create_indexscan_plan(root, + (IndexPath *) best_path, + tlist, + scan_clauses, + true); break; case T_BitmapHeapScan: @@ -274,6 +398,27 @@ create_scan_plan(PlannerInfo *root, Path *best_path) scan_clauses); break; + case T_CteScan: + plan = (Plan *) create_ctescan_plan(root, + best_path, + tlist, + scan_clauses); + break; + + case T_WorkTableScan: + plan = (Plan *) create_worktablescan_plan(root, + best_path, + tlist, + scan_clauses); + break; + + case T_ForeignScan: + plan = (Plan *) create_foreignscan_plan(root, + (ForeignPath *) best_path, + tlist, + scan_clauses); + break; + default: elog(ERROR, "unrecognized node type: %d", (int) best_path->pathtype); @@ -305,9 +450,9 @@ build_relation_tlist(RelOptInfo *rel) foreach(v, rel->reltargetlist) { /* Do we really need to copy here? Not sure */ - Var *var = (Var *) copyObject(lfirst(v)); + Node *node = (Node *) copyObject(lfirst(v)); - tlist = lappend(tlist, makeTargetEntry((Expr *) var, + tlist = lappend(tlist, makeTargetEntry((Expr *) node, resno, NULL, false)); @@ -322,18 +467,20 @@ build_relation_tlist(RelOptInfo *rel) * rather than only those Vars actually referenced. */ static bool -use_physical_tlist(RelOptInfo *rel) +use_physical_tlist(PlannerInfo *root, RelOptInfo *rel) { int i; + ListCell *lc; /* * We can do this for real relation scans, subquery scans, function scans, - * and values scans (but not for, eg, joins). + * values scans, and CTE scans (but not for, eg, joins). */ if (rel->rtekind != RTE_RELATION && rel->rtekind != RTE_SUBQUERY && rel->rtekind != RTE_FUNCTION && - rel->rtekind != RTE_VALUES) + rel->rtekind != RTE_VALUES && + rel->rtekind != RTE_CTE) return false; /* @@ -344,9 +491,9 @@ use_physical_tlist(RelOptInfo *rel) return false; /* - * Can't do it if any system columns or whole-row Vars are requested, - * either. (This could possibly be fixed but would take some fragile - * assumptions in setrefs.c, I think.) + * 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 + * in setrefs.c, I think.) */ for (i = rel->min_attr; i <= 0; i++) { @@ -354,6 +501,19 @@ use_physical_tlist(RelOptInfo *rel) return false; } + /* + * Can't do it if the rel is required to emit any placeholder expressions, + * either. + */ + foreach(lc, root->placeholder_list) + { + PlaceHolderInfo *phinfo = (PlaceHolderInfo *) lfirst(lc); + + if (bms_nonempty_difference(phinfo->ph_needed, rel->relids) && + bms_is_subset(phinfo->ph_eval_at, rel->relids)) + return false; + } + return true; } @@ -374,11 +534,15 @@ disuse_physical_tlist(Plan *plan, Path *path) { case T_SeqScan: case T_IndexScan: + case T_IndexOnlyScan: case T_BitmapHeapScan: case T_TidScan: case T_SubqueryScan: case T_FunctionScan: case T_ValuesScan: + case T_CteScan: + case T_WorkTableScan: + case T_ForeignScan: plan->targetlist = build_relation_tlist(path->parent); break; default: @@ -410,15 +574,17 @@ create_gating_plan(PlannerInfo *root, Plan *plan, List *quals) { List *pseudoconstants; + /* Sort into desirable execution order while still in RestrictInfo form */ + quals = order_qual_clauses(root, quals); + /* Pull out any pseudoconstant quals from the RestrictInfo list */ pseudoconstants = extract_actual_clauses(quals, true); if (!pseudoconstants) return plan; - pseudoconstants = order_qual_clauses(root, pseudoconstants); - - return (Plan *) make_result((List *) copyObject(plan->targetlist), + return (Plan *) make_result(root, + plan->targetlist, (Node *) pseudoconstants, plan); } @@ -434,9 +600,16 @@ create_join_plan(PlannerInfo *root, JoinPath *best_path) Plan *outer_plan; Plan *inner_plan; Plan *plan; + Relids saveOuterRels = root->curOuterRels; + + outer_plan = create_plan_recurse(root, best_path->outerjoinpath); + + /* For a nestloop, include outer relids in curOuterRels for inner side */ + if (best_path->path.pathtype == T_NestLoop) + root->curOuterRels = bms_union(root->curOuterRels, + best_path->outerjoinpath->parent->relids); - outer_plan = create_plan(root, best_path->outerjoinpath); - inner_plan = create_plan(root, best_path->innerjoinpath); + inner_plan = create_plan_recurse(root, best_path->innerjoinpath); switch (best_path->path.pathtype) { @@ -453,6 +626,10 @@ create_join_plan(PlannerInfo *root, JoinPath *best_path) inner_plan); break; case T_NestLoop: + /* Restore curOuterRels */ + bms_free(root->curOuterRels); + root->curOuterRels = saveOuterRels; + plan = (Plan *) create_nestloop_plan(root, (NestPath *) best_path, outer_plan, @@ -516,7 +693,8 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path) if (best_path->subpaths == NIL) { /* Generate a Result plan with constant-FALSE gating qual */ - return (Plan *) make_result(tlist, + return (Plan *) make_result(root, + tlist, (Node *) list_make1(makeBoolConst(false, false)), NULL); @@ -527,14 +705,115 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path) { Path *subpath = (Path *) lfirst(subpaths); - subplans = lappend(subplans, create_plan(root, subpath)); + subplans = lappend(subplans, create_plan_recurse(root, subpath)); } - plan = make_append(subplans, false, tlist); + plan = make_append(subplans, tlist); 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, + NULL, + NULL, + true, + &node->numCols, + &node->sortColIdx, + &node->sortOperators, + &node->collations, + &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; + Oid *collations; + 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, + subpath->parent->relids, + node->sortColIdx, + false, + &numsortkeys, + &sortColIdx, + &sortOperators, + &collations, + &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(collations, node->collations, + 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, + collations, nullsFirst, + best_path->limit_tuples); + + subplans = lappend(subplans, subplan); + } + + node->mergeplans = subplans; + + return (Plan *) node; +} + /* * create_result_plan * Create a Result plan for 'best_path'. @@ -552,9 +831,11 @@ create_result_plan(PlannerInfo *root, ResultPath *best_path) Assert(best_path->path.parent == NULL); tlist = NIL; + /* best_path->quals is just bare clauses */ + quals = order_qual_clauses(root, best_path->quals); - return make_result(tlist, (Node *) quals, NULL); + return make_result(root, tlist, (Node *) quals, NULL); } /* @@ -570,7 +851,7 @@ create_material_plan(PlannerInfo *root, MaterialPath *best_path) Material *plan; Plan *subplan; - subplan = create_plan(root, best_path->subpath); + subplan = create_plan_recurse(root, best_path->subpath); /* We don't want any excess columns in the materialized tuples */ disuse_physical_tlist(subplan, best_path->subpath); @@ -594,6 +875,7 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path) { Plan *plan; Plan *subplan; + List *in_operators; List *uniq_exprs; List *newtlist; int nextresno; @@ -603,45 +885,30 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path) int groupColPos; ListCell *l; - subplan = create_plan(root, best_path->subpath); + subplan = create_plan_recurse(root, best_path->subpath); /* Done if we don't need to do any actual unique-ifying */ if (best_path->umethod == UNIQUE_PATH_NOOP) return subplan; - /*---------- - * As constructed, the subplan has a "flat" tlist containing just the - * Vars needed here and at upper levels. The values we are supposed - * to unique-ify may be expressions in these variables. We have to - * add any such expressions to the subplan's tlist. - * - * The subplan may have a "physical" tlist if it is a simple scan plan. - * This should be left as-is if we don't need to add any expressions; - * but if we do have to add expressions, then a projection step will be - * needed at runtime anyway, and so we may as well remove unneeded items. - * Therefore newtlist starts from build_relation_tlist() not just a - * copy of the subplan's tlist; and we don't install it into the subplan - * unless stuff has to be added. + /* + * As constructed, the subplan has a "flat" tlist containing just the Vars + * needed here and at upper levels. The values we are supposed to + * unique-ify may be expressions in these variables. We have to add any + * such expressions to the subplan's tlist. * - * To find the correct list of values to unique-ify, we look in the - * information saved for IN expressions. If this code is ever used in - * other scenarios, some other way of finding what to unique-ify will - * be needed. - *---------- + * The subplan may have a "physical" tlist if it is a simple scan plan. If + * we're going to sort, this should be reduced to the regular tlist, so + * that we don't sort more data than we need to. For hashing, the tlist + * should be left as-is if we don't need to add any expressions; but if we + * do have to add expressions, then a projection step will be needed at + * runtime anyway, so we may as well remove unneeded items. Therefore + * newtlist starts from build_relation_tlist() not just a copy of the + * subplan's tlist; and we don't install it into the subplan unless we are + * sorting or stuff has to be added. */ - uniq_exprs = NIL; /* just to keep compiler quiet */ - foreach(l, root->in_info_list) - { - InClauseInfo *ininfo = (InClauseInfo *) lfirst(l); - - if (bms_equal(ininfo->righthand, best_path->path.parent->relids)) - { - uniq_exprs = ininfo->sub_targetlist; - break; - } - } - if (l == NULL) /* fell out of loop? */ - elog(ERROR, "could not find UniquePath in in_info_list"); + in_operators = best_path->in_operators; + uniq_exprs = best_path->uniq_exprs; /* initialize modified subplan tlist as just the "required" vars */ newtlist = build_relation_tlist(best_path->path.parent); @@ -666,14 +933,14 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path) } } - if (newitems) + if (newitems || best_path->umethod == UNIQUE_PATH_SORT) { /* * If the top plan node can't do projections, we need to add a Result * node to help it along. */ if (!is_projection_capable_plan(subplan)) - subplan = (Plan *) make_result(newtlist, NULL, subplan); + subplan = (Plan *) make_result(root, newtlist, NULL, subplan); else subplan->targetlist = newtlist; } @@ -687,8 +954,8 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path) newtlist = subplan->targetlist; numGroupCols = list_length(uniq_exprs); groupColIdx = (AttrNumber *) palloc(numGroupCols * sizeof(AttrNumber)); - groupColPos = 0; + groupColPos = 0; foreach(l, uniq_exprs) { Node *uniqexpr = lfirst(l); @@ -703,8 +970,28 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path) if (best_path->umethod == UNIQUE_PATH_HASH) { long numGroups; + Oid *groupOperators; + + numGroups = (long) Min(best_path->path.rows, (double) LONG_MAX); + + /* + * Get the hashable equality operators for the Agg node to use. + * Normally these are the same as the IN clause operators, but if + * those are cross-type operators then the equality operators are the + * ones for the IN clause operators' RHS datatype. + */ + groupOperators = (Oid *) palloc(numGroupCols * sizeof(Oid)); + groupColPos = 0; + foreach(l, in_operators) + { + Oid in_oper = lfirst_oid(l); + Oid eq_oper; - numGroups = (long) Min(best_path->rows, (double) LONG_MAX); + if (!get_compatible_hash_operators(in_oper, NULL, &eq_oper)) + elog(ERROR, "could not find compatible hash operator for operator %u", + in_oper); + groupOperators[groupColPos++] = eq_oper; + } /* * Since the Agg node is going to project anyway, we can give it the @@ -715,33 +1002,63 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path) build_relation_tlist(best_path->path.parent), NIL, AGG_HASHED, + NULL, numGroupCols, groupColIdx, + groupOperators, numGroups, - 0, subplan); } else { List *sortList = NIL; - for (groupColPos = 0; groupColPos < numGroupCols; groupColPos++) + /* Create an ORDER BY list to sort the input compatibly */ + groupColPos = 0; + foreach(l, in_operators) { + Oid in_oper = lfirst_oid(l); + Oid sortop; + Oid eqop; TargetEntry *tle; + SortGroupClause *sortcl; + + sortop = get_ordering_op_for_equality_op(in_oper, false); + if (!OidIsValid(sortop)) /* shouldn't happen */ + elog(ERROR, "could not find ordering operator for equality operator %u", + in_oper); + + /* + * The Unique node will need equality operators. Normally these + * are the same as the IN clause operators, but if those are + * cross-type operators then the equality operators are the ones + * for the IN clause operators' RHS datatype. + */ + eqop = get_equality_op_for_ordering_op(sortop, NULL); + if (!OidIsValid(eqop)) /* shouldn't happen */ + elog(ERROR, "could not find equality operator for ordering operator %u", + sortop); tle = get_tle_by_resno(subplan->targetlist, groupColIdx[groupColPos]); Assert(tle != NULL); - sortList = addTargetToSortList(NULL, tle, - sortList, subplan->targetlist, - SORTBY_ASC, NIL, false); + + sortcl = makeNode(SortGroupClause); + sortcl->tleSortGroupRef = assignSortGroupRef(tle, + subplan->targetlist); + sortcl->eqop = eqop; + sortcl->sortop = sortop; + sortcl->nulls_first = false; + sortcl->hashable = false; /* no need to make this accurate */ + sortList = lappend(sortList, sortcl); + groupColPos++; } plan = (Plan *) make_sort_from_sortclauses(root, sortList, subplan); plan = (Plan *) make_unique(plan, sortList); } /* Adjust output size estimate (other fields should be OK already) */ - plan->plan_rows = best_path->rows; + plan->plan_rows = best_path->path.rows; return plan; } @@ -770,11 +1087,18 @@ create_seqscan_plan(PlannerInfo *root, Path *best_path, Assert(scan_relid > 0); Assert(best_path->parent->rtekind == RTE_RELATION); + /* 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); - /* Sort clauses into best execution order */ - scan_clauses = order_qual_clauses(root, scan_clauses); + /* 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_seqscan(tlist, scan_clauses, @@ -790,31 +1114,28 @@ create_seqscan_plan(PlannerInfo *root, Path *best_path, * Returns an indexscan plan for the base relation scanned by 'best_path' * with restriction clauses 'scan_clauses' and targetlist 'tlist'. * - * The indexquals list of the path contains implicitly-ANDed qual conditions. - * The list can be empty --- then no index restrictions will be applied during - * the scan. - * - * If nonlossy_clauses isn't NULL, *nonlossy_clauses receives a list of the - * nonlossy indexquals. + * We use this for both plain IndexScans and IndexOnlyScans, because the + * qual preprocessing work is the same for both. Note that the caller tells + * us which to build --- we don't look at best_path->path.pathtype, because + * create_bitmap_subplan needs to be able to override the prior decision. */ -static IndexScan * +static Scan * create_indexscan_plan(PlannerInfo *root, IndexPath *best_path, List *tlist, List *scan_clauses, - List **nonlossy_clauses) + bool indexonly) { + Scan *scan_plan; List *indexquals = best_path->indexquals; + List *indexorderbys = best_path->indexorderbys; Index baserelid = best_path->path.parent->relid; Oid indexoid = best_path->indexinfo->indexoid; List *qpqual; List *stripped_indexquals; List *fixed_indexquals; - List *nonlossy_indexquals; - List *indexstrategy; - List *indexsubtype; + List *fixed_indexorderbys; ListCell *l; - IndexScan *scan_plan; /* it should be a base rel... */ Assert(baserelid > 0); @@ -828,58 +1149,44 @@ create_indexscan_plan(PlannerInfo *root, /* * The executor needs a copy with the indexkey on the left of each clause - * and with index attr numbers substituted for table ones. This pass also - * gets strategy info and looks for "lossy" operators. - */ - fix_indexqual_references(indexquals, best_path, - &fixed_indexquals, - &nonlossy_indexquals, - &indexstrategy, - &indexsubtype); - - /* pass back nonlossy quals if caller wants 'em */ - if (nonlossy_clauses) - *nonlossy_clauses = nonlossy_indexquals; - - /* - * If this is an innerjoin scan, the indexclauses will contain join - * clauses that are not present in scan_clauses (since the passed-in value - * is just the rel's baserestrictinfo list). We must add these clauses to - * scan_clauses to ensure they get checked. In most cases we will remove - * the join clauses again below, but if a join clause contains a special - * operator, we need to make sure it gets into the scan_clauses. - * - * Note: pointer comparison should be enough to determine RestrictInfo - * matches. + * and with index Vars substituted for table ones. + */ + fixed_indexquals = fix_indexqual_references(root, best_path); + + /* + * Likewise fix up index attr references in the ORDER BY expressions. */ - if (best_path->isjoininner) - scan_clauses = list_union_ptr(scan_clauses, best_path->indexclauses); + fixed_indexorderbys = fix_indexorderby_references(root, best_path); /* * The qpqual list must contain all restrictions not automatically handled - * by the index. All the predicates in the indexquals will be checked - * (either by the index itself, or by nodeIndexscan.c), but if there are - * any "special" operators involved then they must be included in qpqual. - * Also, any lossy index operators must be rechecked in the qpqual. The - * upshot is that qpqual must contain scan_clauses minus whatever appears - * in nonlossy_indexquals. + * by the index, other than pseudoconstant clauses which will be handled + * by a separate gating plan node. All the predicates in the indexquals + * will be checked (either by the index itself, or by nodeIndexscan.c), + * but if there are any "special" operators involved then they must be + * included in qpqual. The upshot is that qpqual must contain + * scan_clauses minus whatever appears in indexquals. * * In normal cases simple pointer equality checks will be enough to spot - * duplicate RestrictInfos, so we try that first. In some situations - * (particularly with OR'd index conditions) we may have scan_clauses that - * are not equal to, but are logically implied by, the index quals; so we - * also try a predicate_implied_by() check to see if we can discard quals - * that way. (predicate_implied_by assumes its first input contains only - * immutable functions, so we have to check that.) + * duplicate RestrictInfos, so we try that first. + * + * Another common case is that a scan_clauses entry is generated from the + * same EquivalenceClass as some indexqual, and is therefore redundant + * with it, though not equal. (This happens when indxpath.c prefers a + * different derived equality than what generate_join_implied_equalities + * picked for a parameterized scan's ppi_clauses.) + * + * In some situations (particularly with OR'd index conditions) we may + * have scan_clauses that are not equal to, but are logically implied by, + * the index quals; so we also try a predicate_implied_by() check to see + * if we can discard quals that way. (predicate_implied_by assumes its + * first input contains only immutable functions, so we have to check + * that.) * * We can also discard quals that are implied by a partial index's * predicate, but only in a plain SELECT; when scanning a target relation * of UPDATE/DELETE/SELECT FOR UPDATE, we must leave such quals in the * plan so that they'll be properly rechecked by EvalPlanQual testing. - * - * While at it, we strip off the RestrictInfos to produce a list of plain - * expressions (this loop replaces extract_actual_clauses used in the - * other routines in this file). We have to ignore pseudoconstants. */ qpqual = NIL; foreach(l, scan_clauses) @@ -888,44 +1195,76 @@ create_indexscan_plan(PlannerInfo *root, Assert(IsA(rinfo, RestrictInfo)); if (rinfo->pseudoconstant) - continue; - if (list_member_ptr(nonlossy_indexquals, rinfo)) - continue; + continue; /* we may drop pseudoconstants here */ + if (list_member_ptr(indexquals, rinfo)) + continue; /* simple duplicate */ + if (is_redundant_derived_clause(rinfo, indexquals)) + continue; /* derived from same EquivalenceClass */ if (!contain_mutable_functions((Node *) rinfo->clause)) { List *clausel = list_make1(rinfo->clause); - if (predicate_implied_by(clausel, nonlossy_indexquals)) - continue; + if (predicate_implied_by(clausel, indexquals)) + continue; /* provably implied by indexquals */ if (best_path->indexinfo->indpred) { if (baserelid != root->parse->resultRelation && - get_rowmark(root->parse, baserelid) == NULL) + get_parse_rowmark(root->parse, baserelid) == NULL) if (predicate_implied_by(clausel, best_path->indexinfo->indpred)) - continue; + continue; /* implied by index predicate */ } } - qpqual = lappend(qpqual, rinfo->clause); + qpqual = lappend(qpqual, rinfo); } /* Sort clauses into best execution order */ qpqual = order_qual_clauses(root, qpqual); - /* Finally ready to build the plan node */ - scan_plan = make_indexscan(tlist, - qpqual, - baserelid, - indexoid, - fixed_indexquals, - stripped_indexquals, - indexstrategy, - indexsubtype, - best_path->indexscandir); + /* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */ + qpqual = extract_actual_clauses(qpqual, false); - copy_path_costsize(&scan_plan->scan.plan, &best_path->path); - /* use the indexscan-specific rows estimate, not the parent rel's */ - scan_plan->scan.plan.plan_rows = best_path->rows; + /* + * We have to replace any outer-relation variables with nestloop params in + * the indexqualorig, qpqual, and indexorderbyorig expressions. A bit + * annoying to have to do this separately from the processing in + * fix_indexqual_references --- rethink this when generalizing the inner + * indexscan support. But note we can't really do this earlier because + * it'd break the comparisons to predicates above ... (or would it? Those + * wouldn't have outer refs) + */ + if (best_path->path.param_info) + { + stripped_indexquals = (List *) + replace_nestloop_params(root, (Node *) stripped_indexquals); + qpqual = (List *) + replace_nestloop_params(root, (Node *) qpqual); + indexorderbys = (List *) + replace_nestloop_params(root, (Node *) indexorderbys); + } + + /* Finally ready to build the plan node */ + if (indexonly) + scan_plan = (Scan *) make_indexonlyscan(tlist, + qpqual, + baserelid, + indexoid, + fixed_indexquals, + fixed_indexorderbys, + best_path->indexinfo->indextlist, + best_path->indexscandir); + else + scan_plan = (Scan *) make_indexscan(tlist, + qpqual, + baserelid, + indexoid, + fixed_indexquals, + stripped_indexquals, + fixed_indexorderbys, + indexorderbys, + best_path->indexscandir); + + copy_path_costsize(&scan_plan->plan, &best_path->path); return scan_plan; } @@ -945,6 +1284,7 @@ create_bitmap_scan_plan(PlannerInfo *root, Plan *bitmapqualplan; List *bitmapqualorig; List *indexquals; + List *indexECs; List *qpqual; ListCell *l; BitmapHeapScan *scan_plan; @@ -955,79 +1295,83 @@ create_bitmap_scan_plan(PlannerInfo *root, /* Process the bitmapqual tree into a Plan tree and qual lists */ bitmapqualplan = create_bitmap_subplan(root, best_path->bitmapqual, - &bitmapqualorig, &indexquals); - - /* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */ - scan_clauses = extract_actual_clauses(scan_clauses, false); - - /* - * If this is a innerjoin scan, the indexclauses will contain join clauses - * that are not present in scan_clauses (since the passed-in value is just - * the rel's baserestrictinfo list). We must add these clauses to - * scan_clauses to ensure they get checked. In most cases we will remove - * the join clauses again below, but if a join clause contains a special - * operator, we need to make sure it gets into the scan_clauses. - */ - if (best_path->isjoininner) - { - scan_clauses = list_concat_unique(scan_clauses, bitmapqualorig); - } + &bitmapqualorig, &indexquals, + &indexECs); /* * The qpqual list must contain all restrictions not automatically handled - * by the index. All the predicates in the indexquals will be checked - * (either by the index itself, or by nodeBitmapHeapscan.c), but if there - * are any "special" or lossy operators involved then they must be added - * to qpqual. The upshot is that qpqual must contain scan_clauses minus - * whatever appears in indexquals. + * by the index, other than pseudoconstant clauses which will be handled + * by a separate gating plan node. All the predicates in the indexquals + * will be checked (either by the index itself, or by + * nodeBitmapHeapscan.c), but if there are any "special" operators + * involved then they must be added to qpqual. The upshot is that qpqual + * must contain scan_clauses minus whatever appears in indexquals. + * + * This loop is similar to the comparable code in create_indexscan_plan(), + * but with some differences because it has to compare the scan clauses to + * stripped (no RestrictInfos) indexquals. See comments there for more + * info. * * In normal cases simple equal() checks will be enough to spot duplicate - * clauses, so we try that first. In some situations (particularly with - * OR'd index conditions) we may have scan_clauses that are not equal to, - * but are logically implied by, the index quals; so we also try a - * predicate_implied_by() check to see if we can discard quals that way. - * (predicate_implied_by assumes its first input contains only immutable - * functions, so we have to check that.) + * clauses, so we try that first. We next see if the scan clause is + * redundant with any top-level indexqual by virtue of being generated + * from the same EC. After that, try predicate_implied_by(). * * Unlike create_indexscan_plan(), we need take no special thought here * for partial index predicates; this is because the predicate conditions * are already listed in bitmapqualorig and indexquals. Bitmap scans have * to do it that way because predicate conditions need to be rechecked if - * the scan becomes lossy. + * the scan becomes lossy, so they have to be included in bitmapqualorig. */ qpqual = NIL; foreach(l, scan_clauses) { - Node *clause = (Node *) lfirst(l); + RestrictInfo *rinfo = (RestrictInfo *) lfirst(l); + Node *clause = (Node *) rinfo->clause; + Assert(IsA(rinfo, RestrictInfo)); + if (rinfo->pseudoconstant) + continue; /* we may drop pseudoconstants here */ if (list_member(indexquals, clause)) - continue; + continue; /* simple duplicate */ + if (rinfo->parent_ec && list_member_ptr(indexECs, rinfo->parent_ec)) + continue; /* derived from same EquivalenceClass */ if (!contain_mutable_functions(clause)) { List *clausel = list_make1(clause); if (predicate_implied_by(clausel, indexquals)) - continue; + continue; /* provably implied by indexquals */ } - qpqual = lappend(qpqual, clause); + qpqual = lappend(qpqual, rinfo); } /* Sort clauses into best execution order */ qpqual = order_qual_clauses(root, qpqual); + /* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */ + qpqual = extract_actual_clauses(qpqual, false); + /* - * When dealing with special or lossy operators, we will at this point - * have duplicate clauses in qpqual and bitmapqualorig. We may as well - * drop 'em from bitmapqualorig, since there's no point in making the - * tests twice. + * When dealing with special operators, we will at this point have + * duplicate clauses in qpqual and bitmapqualorig. We may as well drop + * 'em from bitmapqualorig, since there's no point in making the tests + * twice. */ bitmapqualorig = list_difference_ptr(bitmapqualorig, qpqual); /* - * Copy the finished bitmapqualorig to make sure we have an independent - * copy --- needed in case there are subplans in the index quals + * We have to replace any outer-relation variables with nestloop params in + * the qpqual and bitmapqualorig expressions. (This was already done for + * expressions attached to plan nodes in the bitmapqualplan tree.) */ - bitmapqualorig = copyObject(bitmapqualorig); + if (best_path->path.param_info) + { + qpqual = (List *) + replace_nestloop_params(root, (Node *) qpqual); + bitmapqualorig = (List *) + replace_nestloop_params(root, (Node *) bitmapqualorig); + } /* Finally ready to build the plan node */ scan_plan = make_bitmap_heapscan(tlist, @@ -1037,8 +1381,6 @@ create_bitmap_scan_plan(PlannerInfo *root, baserelid); copy_path_costsize(&scan_plan->scan.plan, &best_path->path); - /* use the indexscan-specific rows estimate, not the parent rel's */ - scan_plan->scan.plan.plan_rows = best_path->rows; return scan_plan; } @@ -1048,17 +1390,27 @@ create_bitmap_scan_plan(PlannerInfo *root, * * As byproducts, we also return in *qual and *indexqual the qual lists * (in implicit-AND form, without RestrictInfos) describing the original index - * conditions and the generated indexqual conditions. The latter is made to - * exclude lossy index operators. Both lists include partial-index predicates, - * because we have to recheck predicates as well as index conditions if the - * bitmap scan becomes lossy. + * conditions and the generated indexqual conditions. (These are the same in + * simple cases, but when special index operators are involved, the former + * list includes the special conditions while the latter includes the actual + * indexable conditions derived from them.) Both lists include partial-index + * predicates, because we have to recheck predicates as well as index + * conditions if the bitmap scan becomes lossy. + * + * In addition, we return a list of EquivalenceClass pointers for all the + * top-level indexquals that were possibly-redundantly derived from ECs. + * This allows removal of scan_clauses that are redundant with such quals. + * (We do not attempt to detect such redundancies for quals that are within + * OR subtrees. This could be done in a less hacky way if we returned the + * indexquals in RestrictInfo form, but that would be slower and still pretty + * messy, since we'd have to build new RestrictInfos in many cases.) * * Note: if you find yourself changing this, you probably need to change * make_restrictinfo_from_bitmapqual too. */ static Plan * create_bitmap_subplan(PlannerInfo *root, Path *bitmapqual, - List **qual, List **indexqual) + List **qual, List **indexqual, List **indexECs) { Plan *plan; @@ -1068,6 +1420,7 @@ create_bitmap_subplan(PlannerInfo *root, Path *bitmapqual, List *subplans = NIL; List *subquals = NIL; List *subindexquals = NIL; + List *subindexECs = NIL; ListCell *l; /* @@ -1082,12 +1435,16 @@ create_bitmap_subplan(PlannerInfo *root, Path *bitmapqual, Plan *subplan; List *subqual; List *subindexqual; + List *subindexEC; subplan = create_bitmap_subplan(root, (Path *) lfirst(l), - &subqual, &subindexqual); + &subqual, &subindexqual, + &subindexEC); subplans = lappend(subplans, subplan); subquals = list_concat_unique(subquals, subqual); subindexquals = list_concat_unique(subindexquals, subindexqual); + /* Duplicates in indexECs aren't worth getting rid of */ + subindexECs = list_concat(subindexECs, subindexEC); } plan = (Plan *) make_bitmap_and(subplans); plan->startup_cost = apath->path.startup_cost; @@ -1097,6 +1454,7 @@ create_bitmap_subplan(PlannerInfo *root, Path *bitmapqual, plan->plan_width = 0; /* meaningless */ *qual = subquals; *indexqual = subindexquals; + *indexECs = subindexECs; } else if (IsA(bitmapqual, BitmapOrPath)) { @@ -1122,9 +1480,11 @@ create_bitmap_subplan(PlannerInfo *root, Path *bitmapqual, Plan *subplan; List *subqual; List *subindexqual; + List *subindexEC; subplan = create_bitmap_subplan(root, (Path *) lfirst(l), - &subqual, &subindexqual); + &subqual, &subindexqual, + &subindexEC); subplans = lappend(subplans, subplan); if (subqual == NIL) const_true_subqual = true; @@ -1173,31 +1533,31 @@ create_bitmap_subplan(PlannerInfo *root, Path *bitmapqual, *indexqual = subindexquals; else *indexqual = list_make1(make_orclause(subindexquals)); + *indexECs = NIL; } else if (IsA(bitmapqual, IndexPath)) { IndexPath *ipath = (IndexPath *) bitmapqual; IndexScan *iscan; - List *nonlossy_clauses; + List *subindexECs; ListCell *l; /* Use the regular indexscan plan build machinery... */ - iscan = create_indexscan_plan(root, ipath, NIL, NIL, - &nonlossy_clauses); + iscan = (IndexScan *) create_indexscan_plan(root, ipath, + NIL, NIL, false); + Assert(IsA(iscan, IndexScan)); /* then convert to a bitmap indexscan */ plan = (Plan *) make_bitmap_indexscan(iscan->scan.scanrelid, iscan->indexid, iscan->indexqual, - iscan->indexqualorig, - iscan->indexstrategy, - iscan->indexsubtype); + iscan->indexqualorig); plan->startup_cost = 0.0; plan->total_cost = ipath->indextotalcost; plan->plan_rows = clamp_row_est(ipath->indexselectivity * ipath->path.parent->tuples); plan->plan_width = 0; /* meaningless */ *qual = get_actual_clauses(ipath->indexclauses); - *indexqual = get_actual_clauses(nonlossy_clauses); + *indexqual = get_actual_clauses(ipath->indexquals); foreach(l, ipath->indexinfo->indpred) { Expr *pred = (Expr *) lfirst(l); @@ -1214,6 +1574,15 @@ create_bitmap_subplan(PlannerInfo *root, Path *bitmapqual, *indexqual = lappend(*indexqual, pred); } } + subindexECs = NIL; + foreach(l, ipath->indexquals) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(l); + + if (rinfo->parent_ec) + subindexECs = lappend(subindexECs, rinfo->parent_ec); + } + *indexECs = subindexECs; } else { @@ -1235,31 +1604,41 @@ create_tidscan_plan(PlannerInfo *root, TidPath *best_path, { 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); + /* 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->path.param_info) + { + tidquals = (List *) + replace_nestloop_params(root, (Node *) tidquals); + scan_clauses = (List *) + 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 = best_path->tidquals; + ortidquals = tidquals; if (list_length(ortidquals) > 1) ortidquals = list_make1(make_orclause(ortidquals)); scan_clauses = list_difference(scan_clauses, ortidquals); - /* Sort clauses into best execution order */ - scan_clauses = order_qual_clauses(root, scan_clauses); - scan_plan = make_tidscan(tlist, scan_clauses, scan_relid, - best_path->tidquals); + tidquals); copy_path_costsize(&scan_plan->scan.plan, &best_path->path); @@ -1282,11 +1661,20 @@ create_subqueryscan_plan(PlannerInfo *root, Path *best_path, Assert(scan_relid > 0); Assert(best_path->parent->rtekind == RTE_SUBQUERY); + /* 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); - /* Sort clauses into best execution order */ - scan_clauses = order_qual_clauses(root, scan_clauses); + /* Replace any outer-relation variables with nestloop params */ + if (best_path->param_info) + { + scan_clauses = (List *) + replace_nestloop_params(root, (Node *) scan_clauses); + process_subquery_nestloop_params(root, + best_path->parent->subplan_params); + } scan_plan = make_subqueryscan(tlist, scan_clauses, @@ -1309,18 +1697,36 @@ create_functionscan_plan(PlannerInfo *root, Path *best_path, { FunctionScan *scan_plan; Index scan_relid = best_path->parent->relid; + RangeTblEntry *rte; + Node *funcexpr; /* it should be a function base rel... */ Assert(scan_relid > 0); - Assert(best_path->parent->rtekind == RTE_FUNCTION); + rte = planner_rt_fetch(scan_relid, root); + Assert(rte->rtekind == RTE_FUNCTION); + funcexpr = rte->funcexpr; + + /* 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); - /* Sort clauses into best execution order */ - scan_clauses = order_qual_clauses(root, scan_clauses); + /* Replace any outer-relation variables with nestloop params */ + if (best_path->param_info) + { + scan_clauses = (List *) + replace_nestloop_params(root, (Node *) scan_clauses); + /* The func expression itself could contain nestloop params, too */ + funcexpr = replace_nestloop_params(root, funcexpr); + } - scan_plan = make_functionscan(tlist, scan_clauses, scan_relid); + scan_plan = make_functionscan(tlist, scan_clauses, scan_relid, + funcexpr, + rte->eref->colnames, + rte->funccoltypes, + rte->funccoltypmods, + rte->funccolcollations); copy_path_costsize(&scan_plan->scan.plan, best_path); @@ -1338,123 +1744,355 @@ create_valuesscan_plan(PlannerInfo *root, Path *best_path, { ValuesScan *scan_plan; Index scan_relid = best_path->parent->relid; + RangeTblEntry *rte; + List *values_lists; /* it should be a values base rel... */ Assert(scan_relid > 0); - Assert(best_path->parent->rtekind == RTE_VALUES); + rte = planner_rt_fetch(scan_relid, root); + Assert(rte->rtekind == RTE_VALUES); + values_lists = rte->values_lists; + + /* 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); - /* Sort clauses into best execution order */ - scan_clauses = order_qual_clauses(root, scan_clauses); + /* Replace any outer-relation variables with nestloop params */ + if (best_path->param_info) + { + scan_clauses = (List *) + replace_nestloop_params(root, (Node *) scan_clauses); + /* The values lists could contain nestloop params, too */ + values_lists = (List *) + replace_nestloop_params(root, (Node *) values_lists); + } - scan_plan = make_valuesscan(tlist, scan_clauses, scan_relid); + scan_plan = make_valuesscan(tlist, scan_clauses, scan_relid, + values_lists); copy_path_costsize(&scan_plan->scan.plan, best_path); return scan_plan; } -/***************************************************************************** - * - * JOIN METHODS - * - *****************************************************************************/ - -static NestLoop * -create_nestloop_plan(PlannerInfo *root, - NestPath *best_path, - Plan *outer_plan, - Plan *inner_plan) +/* + * create_ctescan_plan + * Returns a ctescan plan for the base relation scanned by 'best_path' + * with restriction clauses 'scan_clauses' and targetlist 'tlist'. + */ +static CteScan * +create_ctescan_plan(PlannerInfo *root, Path *best_path, + List *tlist, List *scan_clauses) { - List *tlist = build_relation_tlist(best_path->path.parent); - List *joinrestrictclauses = best_path->joinrestrictinfo; - List *joinclauses; - List *otherclauses; - NestLoop *join_plan; + CteScan *scan_plan; + Index scan_relid = best_path->parent->relid; + RangeTblEntry *rte; + SubPlan *ctesplan = NULL; + int plan_id; + int cte_param_id; + PlannerInfo *cteroot; + Index levelsup; + int ndx; + ListCell *lc; - if (IsA(best_path->innerjoinpath, IndexPath)) - { - /* - * An index is being used to reduce the number of tuples scanned in - * the inner relation. If there are join clauses being used with the - * index, we may remove those join clauses from the list of clauses - * that have to be checked as qpquals at the join node. - * - * We can also remove any join clauses that are redundant with those - * being used in the index scan; prior redundancy checks will not have - * caught this case because the join clauses would never have been put - * in the same joininfo list. - * - * We can skip this if the index path is an ordinary indexpath and not - * a special innerjoin path. - */ - IndexPath *innerpath = (IndexPath *) best_path->innerjoinpath; + Assert(scan_relid > 0); + rte = planner_rt_fetch(scan_relid, root); + Assert(rte->rtekind == RTE_CTE); + Assert(!rte->self_reference); - if (innerpath->isjoininner) - { - joinrestrictclauses = - select_nonredundant_join_clauses(root, - joinrestrictclauses, - innerpath->indexclauses, - IS_OUTER_JOIN(best_path->jointype)); - } - } - else if (IsA(best_path->innerjoinpath, BitmapHeapPath)) + /* + * Find the referenced CTE, and locate the SubPlan previously made for it. + */ + levelsup = rte->ctelevelsup; + cteroot = root; + while (levelsup-- > 0) { - /* - * Same deal for bitmapped index scans. - * - * Note: both here and above, we ignore any implicit index - * restrictions associated with the use of partial indexes. This is - * OK because we're only trying to prove we can dispense with some - * join quals; failing to prove that doesn't result in an incorrect - * plan. It is the right way to proceed because adding more quals to - * the stuff we got from the original query would just make it harder - * to detect duplication. (Also, to change this we'd have to be wary - * of UPDATE/DELETE/SELECT FOR UPDATE target relations; see notes - * above about EvalPlanQual.) - */ - BitmapHeapPath *innerpath = (BitmapHeapPath *) best_path->innerjoinpath; - - if (innerpath->isjoininner) - { - List *bitmapclauses; - - bitmapclauses = - make_restrictinfo_from_bitmapqual(innerpath->bitmapqual, - true, - false); - joinrestrictclauses = - select_nonredundant_join_clauses(root, - joinrestrictclauses, - bitmapclauses, - IS_OUTER_JOIN(best_path->jointype)); - } + cteroot = cteroot->parent_root; + if (!cteroot) /* shouldn't happen */ + elog(ERROR, "bad levelsup for CTE \"%s\"", rte->ctename); } - /* Get the join qual clauses (in plain expression form) */ - /* Any pseudoconstant clauses are ignored here */ - if (IS_OUTER_JOIN(best_path->jointype)) + /* + * Note: cte_plan_ids can be shorter than cteList, if we are still working + * on planning the CTEs (ie, this is a side-reference from another CTE). + * So we mustn't use forboth here. + */ + ndx = 0; + foreach(lc, cteroot->parse->cteList) { - extract_actual_join_clauses(joinrestrictclauses, - &joinclauses, &otherclauses); + CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc); + + if (strcmp(cte->ctename, rte->ctename) == 0) + break; + ndx++; } - else + if (lc == NULL) /* shouldn't happen */ + elog(ERROR, "could not find CTE \"%s\"", rte->ctename); + if (ndx >= list_length(cteroot->cte_plan_ids)) + elog(ERROR, "could not find plan for CTE \"%s\"", rte->ctename); + plan_id = list_nth_int(cteroot->cte_plan_ids, ndx); + Assert(plan_id > 0); + foreach(lc, cteroot->init_plans) { - /* We can treat all clauses alike for an inner join */ - joinclauses = extract_actual_clauses(joinrestrictclauses, false); - otherclauses = NIL; + ctesplan = (SubPlan *) lfirst(lc); + if (ctesplan->plan_id == plan_id) + break; + } + if (lc == NULL) /* shouldn't happen */ + elog(ERROR, "could not find plan for CTE \"%s\"", rte->ctename); + + /* + * We need the CTE param ID, which is the sole member of the SubPlan's + * setParam list. + */ + cte_param_id = linitial_int(ctesplan->setParam); + + /* 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_ctescan(tlist, scan_clauses, scan_relid, + plan_id, cte_param_id); + + copy_path_costsize(&scan_plan->scan.plan, best_path); + + return scan_plan; +} + +/* + * create_worktablescan_plan + * Returns a worktablescan plan for the base relation scanned by 'best_path' + * with restriction clauses 'scan_clauses' and targetlist 'tlist'. + */ +static WorkTableScan * +create_worktablescan_plan(PlannerInfo *root, Path *best_path, + List *tlist, List *scan_clauses) +{ + WorkTableScan *scan_plan; + Index scan_relid = best_path->parent->relid; + RangeTblEntry *rte; + Index levelsup; + PlannerInfo *cteroot; + + Assert(scan_relid > 0); + rte = planner_rt_fetch(scan_relid, root); + Assert(rte->rtekind == RTE_CTE); + Assert(rte->self_reference); + + /* + * We need to find the worktable param ID, which is in the plan level + * that's processing the recursive UNION, which is one level *below* where + * the CTE comes from. + */ + levelsup = rte->ctelevelsup; + if (levelsup == 0) /* shouldn't happen */ + elog(ERROR, "bad levelsup for CTE \"%s\"", rte->ctename); + levelsup--; + cteroot = root; + while (levelsup-- > 0) + { + cteroot = cteroot->parent_root; + if (!cteroot) /* shouldn't happen */ + elog(ERROR, "bad levelsup for CTE \"%s\"", rte->ctename); + } + 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 */ + 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_worktablescan(tlist, scan_clauses, scan_relid, + cteroot->wt_param_id); + + copy_path_costsize(&scan_plan->scan.plan, best_path); + + return scan_plan; +} + +/* + * create_foreignscan_plan + * Returns a foreignscan plan for the base relation scanned by 'best_path' + * with restriction clauses 'scan_clauses' and targetlist 'tlist'. + */ +static ForeignScan * +create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path, + List *tlist, List *scan_clauses) +{ + ForeignScan *scan_plan; + RelOptInfo *rel = best_path->path.parent; + Index scan_relid = rel->relid; + RangeTblEntry *rte; + int i; + + /* it should be a base rel... */ + Assert(scan_relid > 0); + Assert(rel->rtekind == RTE_RELATION); + rte = planner_rt_fetch(scan_relid, root); + Assert(rte->rtekind == RTE_RELATION); + + /* + * Sort clauses into best execution order. We do this first since the FDW + * might have more info than we do and wish to adjust the ordering. + */ + scan_clauses = order_qual_clauses(root, scan_clauses); + + /* + * Let the FDW perform its processing on the restriction clauses and + * generate the plan node. Note that the FDW might remove restriction + * clauses that it intends to execute remotely, or even add more (if it + * has selected some join clauses for remote use but also wants them + * rechecked locally). + */ + scan_plan = rel->fdwroutine->GetForeignPlan(root, rel, rte->relid, + best_path, + tlist, scan_clauses); + + /* Copy cost data from Path to Plan; no need to make FDW do this */ + copy_path_costsize(&scan_plan->scan.plan, &best_path->path); + + /* + * Replace any outer-relation variables with nestloop params in the qual + * and fdw_exprs expressions. We do this last so that the FDW doesn't + * have to be involved. (Note that parts of fdw_exprs could have come + * from join clauses, so doing this beforehand on the scan_clauses + * wouldn't work.) + */ + if (best_path->path.param_info) + { + scan_plan->scan.plan.qual = (List *) + replace_nestloop_params(root, (Node *) scan_plan->scan.plan.qual); + scan_plan->fdw_exprs = (List *) + replace_nestloop_params(root, (Node *) scan_plan->fdw_exprs); + } + + /* + * Detect whether any system columns are requested from rel. This is a + * bit of a kluge and might go away someday, so we intentionally leave it + * out of the API presented to FDWs. + */ + scan_plan->fsSystemCol = false; + for (i = rel->min_attr; i < 0; i++) + { + if (!bms_is_empty(rel->attr_needed[i - rel->min_attr])) + { + scan_plan->fsSystemCol = true; + break; + } + } + + return scan_plan; +} + + +/***************************************************************************** + * + * JOIN METHODS + * + *****************************************************************************/ + +static NestLoop * +create_nestloop_plan(PlannerInfo *root, + NestPath *best_path, + Plan *outer_plan, + Plan *inner_plan) +{ + NestLoop *join_plan; + List *tlist = build_relation_tlist(best_path->path.parent); + List *joinrestrictclauses = best_path->joinrestrictinfo; + List *joinclauses; + List *otherclauses; + Relids outerrelids; + List *nestParams; + ListCell *cell; + ListCell *prev; + ListCell *next; + + /* Sort join qual clauses into best execution order */ + joinrestrictclauses = order_qual_clauses(root, joinrestrictclauses); + + /* Get the join qual clauses (in plain expression form) */ + /* Any pseudoconstant clauses are ignored here */ + if (IS_OUTER_JOIN(best_path->jointype)) + { + extract_actual_join_clauses(joinrestrictclauses, + &joinclauses, &otherclauses); + } + else + { + /* We can treat all clauses alike for an inner join */ + joinclauses = extract_actual_clauses(joinrestrictclauses, false); + otherclauses = NIL; + } + + /* Replace any outer-relation variables with nestloop params */ + if (best_path->path.param_info) + { + joinclauses = (List *) + replace_nestloop_params(root, (Node *) joinclauses); + otherclauses = (List *) + replace_nestloop_params(root, (Node *) otherclauses); + } + + /* + * Identify any nestloop parameters that should be supplied by this join + * node, and move them from root->curOuterParams to the nestParams list. + */ + outerrelids = best_path->outerjoinpath->parent->relids; + nestParams = NIL; + prev = NULL; + for (cell = list_head(root->curOuterParams); cell; cell = next) + { + NestLoopParam *nlp = (NestLoopParam *) lfirst(cell); + + next = lnext(cell); + if (IsA(nlp->paramval, Var) && + bms_is_member(nlp->paramval->varno, outerrelids)) + { + root->curOuterParams = list_delete_cell(root->curOuterParams, + cell, prev); + nestParams = lappend(nestParams, nlp); + } + else if (IsA(nlp->paramval, PlaceHolderVar) && + bms_overlap(((PlaceHolderVar *) nlp->paramval)->phrels, + outerrelids) && + bms_is_subset(find_placeholder_info(root, + (PlaceHolderVar *) nlp->paramval, + false)->ph_eval_at, + outerrelids)) + { + root->curOuterParams = list_delete_cell(root->curOuterParams, + cell, prev); + nestParams = lappend(nestParams, nlp); + } + else + prev = cell; } - /* Sort clauses into best execution order */ - joinclauses = order_qual_clauses(root, joinclauses); - otherclauses = order_qual_clauses(root, otherclauses); - join_plan = make_nestloop(tlist, joinclauses, otherclauses, + nestParams, outer_plan, inner_plan, best_path->jointype); @@ -1474,20 +2112,34 @@ create_mergejoin_plan(PlannerInfo *root, List *joinclauses; List *otherclauses; List *mergeclauses; + List *outerpathkeys; + List *innerpathkeys; + int nClauses; + Oid *mergefamilies; + Oid *mergecollations; + int *mergestrategies; + bool *mergenullsfirst; MergeJoin *join_plan; + int i; + ListCell *lc; + ListCell *lop; + ListCell *lip; + + /* Sort join qual clauses into best execution order */ + /* NB: do NOT reorder the mergeclauses */ + joinclauses = order_qual_clauses(root, best_path->jpath.joinrestrictinfo); /* Get the join qual clauses (in plain expression form) */ /* Any pseudoconstant clauses are ignored here */ if (IS_OUTER_JOIN(best_path->jpath.jointype)) { - extract_actual_join_clauses(best_path->jpath.joinrestrictinfo, + extract_actual_join_clauses(joinclauses, &joinclauses, &otherclauses); } else { /* We can treat all clauses alike for an inner join */ - joinclauses = extract_actual_clauses(best_path->jpath.joinrestrictinfo, - false); + joinclauses = extract_actual_clauses(joinclauses, false); otherclauses = NIL; } @@ -1498,22 +2150,29 @@ create_mergejoin_plan(PlannerInfo *root, mergeclauses = get_actual_clauses(best_path->path_mergeclauses); joinclauses = list_difference(joinclauses, mergeclauses); + /* + * Replace any outer-relation variables with nestloop params. There + * should not be any in the mergeclauses. + */ + if (best_path->jpath.path.param_info) + { + joinclauses = (List *) + replace_nestloop_params(root, (Node *) joinclauses); + otherclauses = (List *) + replace_nestloop_params(root, (Node *) otherclauses); + } + /* * Rearrange mergeclauses, if needed, so that the outer variable is always - * on the left. + * on the left; mark the mergeclause restrictinfos with correct + * outer_is_left status. */ mergeclauses = get_switched_clauses(best_path->path_mergeclauses, best_path->jpath.outerjoinpath->parent->relids); - /* Sort clauses into best execution order */ - /* NB: do NOT reorder the mergeclauses */ - joinclauses = order_qual_clauses(root, joinclauses); - otherclauses = order_qual_clauses(root, otherclauses); - /* - * Create explicit sort nodes for the outer and inner join paths if - * necessary. The sort cost was already accounted for in the path. Make - * sure there are no excess columns in the inputs if sorting. + * Create explicit sort nodes for the outer and inner paths if necessary. + * Make sure there are no excess columns in the inputs if sorting. */ if (best_path->outersortkeys) { @@ -1521,8 +2180,12 @@ create_mergejoin_plan(PlannerInfo *root, outer_plan = (Plan *) make_sort_from_pathkeys(root, outer_plan, - best_path->outersortkeys); + best_path->outersortkeys, + -1.0); + outerpathkeys = best_path->outersortkeys; } + else + outerpathkeys = best_path->jpath.outerjoinpath->pathkeys; if (best_path->innersortkeys) { @@ -1530,9 +2193,189 @@ create_mergejoin_plan(PlannerInfo *root, inner_plan = (Plan *) make_sort_from_pathkeys(root, inner_plan, - best_path->innersortkeys); + best_path->innersortkeys, + -1.0); + innerpathkeys = best_path->innersortkeys; + } + else + innerpathkeys = best_path->jpath.innerjoinpath->pathkeys; + + /* + * If specified, add a materialize node to shield the inner plan from the + * need to handle mark/restore. + */ + if (best_path->materialize_inner) + { + Plan *matplan = (Plan *) make_material(inner_plan); + + /* + * We assume the materialize will not spill to disk, and therefore + * charge just cpu_operator_cost per tuple. (Keep this estimate in + * sync with final_cost_mergejoin.) + */ + copy_plan_costsize(matplan, inner_plan); + matplan->total_cost += cpu_operator_cost * matplan->plan_rows; + + inner_plan = matplan; + } + + /* + * 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()). + */ + nClauses = list_length(mergeclauses); + Assert(nClauses == list_length(best_path->path_mergeclauses)); + mergefamilies = (Oid *) palloc(nClauses * sizeof(Oid)); + mergecollations = (Oid *) palloc(nClauses * sizeof(Oid)); + mergestrategies = (int *) palloc(nClauses * sizeof(int)); + mergenullsfirst = (bool *) palloc(nClauses * sizeof(bool)); + + lop = list_head(outerpathkeys); + lip = list_head(innerpathkeys); + i = 0; + foreach(lc, best_path->path_mergeclauses) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + EquivalenceClass *oeclass; + EquivalenceClass *ieclass; + PathKey *opathkey; + PathKey *ipathkey; + EquivalenceClass *opeclass; + EquivalenceClass *ipeclass; + ListCell *l2; + + /* fetch outer/inner eclass from mergeclause */ + Assert(IsA(rinfo, RestrictInfo)); + if (rinfo->outer_is_left) + { + oeclass = rinfo->left_ec; + ieclass = rinfo->right_ec; + } + else + { + oeclass = rinfo->right_ec; + ieclass = rinfo->left_ec; + } + Assert(oeclass != NULL); + 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. + * + * 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. + */ + if (lop) + { + 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) + elog(ERROR, "outer pathkeys do not match mergeclauses"); + } + + if (lip) + { + ipathkey = (PathKey *) lfirst(lip); + ipeclass = ipathkey->pk_eclass; + if (ieclass == ipeclass) + { + /* fast path for typical case */ + 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"); + } + } + else + { + /* redundant clauses ... must match some already-used pathkey */ + ipathkey = NULL; + ipeclass = NULL; + foreach(l2, innerpathkeys) + { + ipathkey = (PathKey *) lfirst(l2); + ipeclass = ipathkey->pk_eclass; + if (ieclass == ipeclass) + break; + } + if (l2 == NULL) + elog(ERROR, "inner pathkeys do not match mergeclauses"); + } + + /* pathkeys should match each other too (more debugging) */ + 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) + elog(ERROR, "left and right pathkeys do not match in mergejoin"); + + /* OK, save info for executor */ + mergefamilies[i] = opathkey->pk_opfamily; + mergecollations[i] = opathkey->pk_eclass->ec_collation; + mergestrategies[i] = opathkey->pk_strategy; + mergenullsfirst[i] = opathkey->pk_nulls_first; + i++; } + /* + * Note: it is not an error if we have additional pathkey elements (i.e., + * lop or lip isn't NULL here). The input paths might be better-sorted + * than we need for the current mergejoin. + */ + /* * Now we can build the mergejoin node. */ @@ -1540,10 +2383,15 @@ create_mergejoin_plan(PlannerInfo *root, joinclauses, otherclauses, mergeclauses, + mergefamilies, + mergecollations, + mergestrategies, + mergenullsfirst, outer_plan, inner_plan, best_path->jpath.jointype); + /* Costs of sort and material steps are included in path cost already */ copy_path_costsize(&join_plan->join.plan, &best_path->jpath.path); return join_plan; @@ -1559,21 +2407,29 @@ create_hashjoin_plan(PlannerInfo *root, List *joinclauses; List *otherclauses; List *hashclauses; + Oid skewTable = InvalidOid; + AttrNumber skewColumn = InvalidAttrNumber; + bool skewInherit = false; + Oid skewColType = InvalidOid; + int32 skewColTypmod = -1; HashJoin *join_plan; Hash *hash_plan; + /* Sort join qual clauses into best execution order */ + joinclauses = order_qual_clauses(root, best_path->jpath.joinrestrictinfo); + /* There's no point in sorting the hash clauses ... */ + /* Get the join qual clauses (in plain expression form) */ /* Any pseudoconstant clauses are ignored here */ if (IS_OUTER_JOIN(best_path->jpath.jointype)) { - extract_actual_join_clauses(best_path->jpath.joinrestrictinfo, + extract_actual_join_clauses(joinclauses, &joinclauses, &otherclauses); } else { /* We can treat all clauses alike for an inner join */ - joinclauses = extract_actual_clauses(best_path->jpath.joinrestrictinfo, - false); + joinclauses = extract_actual_clauses(joinclauses, false); otherclauses = NIL; } @@ -1584,6 +2440,18 @@ create_hashjoin_plan(PlannerInfo *root, hashclauses = get_actual_clauses(best_path->path_hashclauses); joinclauses = list_difference(joinclauses, hashclauses); + /* + * Replace any outer-relation variables with nestloop params. There + * should not be any in the hashclauses. + */ + if (best_path->jpath.path.param_info) + { + joinclauses = (List *) + replace_nestloop_params(root, (Node *) joinclauses); + otherclauses = (List *) + replace_nestloop_params(root, (Node *) otherclauses); + } + /* * Rearrange hashclauses, if needed, so that the outer variable is always * on the left. @@ -1591,18 +2459,56 @@ create_hashjoin_plan(PlannerInfo *root, hashclauses = get_switched_clauses(best_path->path_hashclauses, best_path->jpath.outerjoinpath->parent->relids); - /* Sort clauses into best execution order */ - joinclauses = order_qual_clauses(root, joinclauses); - otherclauses = order_qual_clauses(root, otherclauses); - hashclauses = order_qual_clauses(root, hashclauses); - /* We don't want any excess columns in the hashed tuples */ disuse_physical_tlist(inner_plan, best_path->jpath.innerjoinpath); + /* If we expect batching, suppress excess columns in outer tuples too */ + if (best_path->num_batches > 1) + disuse_physical_tlist(outer_plan, best_path->jpath.outerjoinpath); + + /* + * If there is a single join clause and we can identify the outer variable + * as a simple column reference, supply its identity for possible use in + * skew optimization. (Note: in principle we could do skew optimization + * with multiple join clauses, but we'd have to be able to determine the + * most common combinations of outer values, which we don't currently have + * enough stats for.) + */ + if (list_length(hashclauses) == 1) + { + OpExpr *clause = (OpExpr *) linitial(hashclauses); + Node *node; + + Assert(is_opclause(clause)); + node = (Node *) linitial(clause->args); + if (IsA(node, RelabelType)) + node = (Node *) ((RelabelType *) node)->arg; + if (IsA(node, Var)) + { + Var *var = (Var *) node; + RangeTblEntry *rte; + + rte = root->simple_rte_array[var->varno]; + if (rte->rtekind == RTE_RELATION) + { + skewTable = rte->relid; + skewColumn = var->varattno; + skewInherit = rte->inh; + skewColType = var->vartype; + skewColTypmod = var->vartypmod; + } + } + } + /* * Build the hash node and hash join node. */ - hash_plan = make_hash(inner_plan); + hash_plan = make_hash(inner_plan, + skewTable, + skewColumn, + skewInherit, + skewColType, + skewColTypmod); join_plan = make_hashjoin(tlist, joinclauses, otherclauses, @@ -1623,74 +2529,237 @@ create_hashjoin_plan(PlannerInfo *root, * *****************************************************************************/ +/* + * replace_nestloop_params + * Replace outer-relation Vars and PlaceHolderVars in the given expression + * with nestloop Params + * + * All Vars and PlaceHolderVars belonging to the relation(s) identified by + * root->curOuterRels are replaced by Params, and entries are added to + * root->curOuterParams if not already present. + */ +static Node * +replace_nestloop_params(PlannerInfo *root, Node *expr) +{ + /* No setup needed for tree walk, so away we go */ + return replace_nestloop_params_mutator(expr, root); +} + +static Node * +replace_nestloop_params_mutator(Node *node, PlannerInfo *root) +{ + if (node == NULL) + return NULL; + if (IsA(node, Var)) + { + Var *var = (Var *) node; + Param *param; + NestLoopParam *nlp; + ListCell *lc; + + /* Upper-level Vars should be long gone at this point */ + Assert(var->varlevelsup == 0); + /* If not to be replaced, we can just return the Var unmodified */ + if (!bms_is_member(var->varno, root->curOuterRels)) + return node; + /* Create a Param representing the Var */ + param = assign_nestloop_param_var(root, var); + /* Is this param already listed in root->curOuterParams? */ + foreach(lc, root->curOuterParams) + { + nlp = (NestLoopParam *) lfirst(lc); + if (nlp->paramno == param->paramid) + { + Assert(equal(var, nlp->paramval)); + /* Present, so we can just return the Param */ + return (Node *) param; + } + } + /* No, so add it */ + nlp = makeNode(NestLoopParam); + nlp->paramno = param->paramid; + nlp->paramval = var; + root->curOuterParams = lappend(root->curOuterParams, nlp); + /* And return the replacement Param */ + return (Node *) param; + } + if (IsA(node, PlaceHolderVar)) + { + PlaceHolderVar *phv = (PlaceHolderVar *) node; + Param *param; + NestLoopParam *nlp; + ListCell *lc; + + /* Upper-level PlaceHolderVars should be long gone at this point */ + Assert(phv->phlevelsup == 0); + + /* + * If not to be replaced, just return the PlaceHolderVar unmodified. + * We use bms_overlap as a cheap/quick test to see if the PHV might be + * evaluated in the outer rels, and then grab its PlaceHolderInfo to + * tell for sure. + */ + if (!bms_overlap(phv->phrels, root->curOuterRels)) + return node; + if (!bms_is_subset(find_placeholder_info(root, phv, false)->ph_eval_at, + root->curOuterRels)) + return node; + /* Create a Param representing the PlaceHolderVar */ + param = assign_nestloop_param_placeholdervar(root, phv); + /* Is this param already listed in root->curOuterParams? */ + foreach(lc, root->curOuterParams) + { + nlp = (NestLoopParam *) lfirst(lc); + if (nlp->paramno == param->paramid) + { + Assert(equal(phv, nlp->paramval)); + /* Present, so we can just return the Param */ + return (Node *) param; + } + } + /* No, so add it */ + nlp = makeNode(NestLoopParam); + nlp->paramno = param->paramid; + nlp->paramval = (Var *) phv; + root->curOuterParams = lappend(root->curOuterParams, nlp); + /* And return the replacement Param */ + return (Node *) param; + } + return expression_tree_mutator(node, + replace_nestloop_params_mutator, + (void *) root); +} + +/* + * process_subquery_nestloop_params + * Handle params of a parameterized subquery that need to be fed + * from an outer nestloop. + * + * Currently, that would be *all* params that a subquery in FROM has demanded + * from the current query level, since they must be LATERAL references. + * + * The subplan's references to the outer variables are already represented + * as PARAM_EXEC Params, so we need not modify the subplan here. What we + * do need to do is add entries to root->curOuterParams to signal the parent + * nestloop plan node that it must provide these values. + */ +static void +process_subquery_nestloop_params(PlannerInfo *root, List *subplan_params) +{ + ListCell *ppl; + + foreach(ppl, subplan_params) + { + PlannerParamItem *pitem = (PlannerParamItem *) lfirst(ppl); + + if (IsA(pitem->item, Var)) + { + Var *var = (Var *) pitem->item; + NestLoopParam *nlp; + ListCell *lc; + + /* If not from a nestloop outer rel, complain */ + if (!bms_is_member(var->varno, root->curOuterRels)) + elog(ERROR, "non-LATERAL parameter required by subquery"); + /* Is this param already listed in root->curOuterParams? */ + foreach(lc, root->curOuterParams) + { + nlp = (NestLoopParam *) lfirst(lc); + if (nlp->paramno == pitem->paramId) + { + Assert(equal(var, nlp->paramval)); + /* Present, so nothing to do */ + break; + } + } + if (lc == NULL) + { + /* No, so add it */ + nlp = makeNode(NestLoopParam); + nlp->paramno = pitem->paramId; + nlp->paramval = copyObject(var); + root->curOuterParams = lappend(root->curOuterParams, nlp); + } + } + else if (IsA(pitem->item, PlaceHolderVar)) + { + PlaceHolderVar *phv = (PlaceHolderVar *) pitem->item; + NestLoopParam *nlp; + ListCell *lc; + + /* If not from a nestloop outer rel, complain */ + if (!bms_is_subset(find_placeholder_info(root, phv, false)->ph_eval_at, + root->curOuterRels)) + elog(ERROR, "non-LATERAL parameter required by subquery"); + /* Is this param already listed in root->curOuterParams? */ + foreach(lc, root->curOuterParams) + { + nlp = (NestLoopParam *) lfirst(lc); + if (nlp->paramno == pitem->paramId) + { + Assert(equal(phv, nlp->paramval)); + /* Present, so nothing to do */ + break; + } + } + if (lc == NULL) + { + /* No, so add it */ + nlp = makeNode(NestLoopParam); + nlp->paramno = pitem->paramId; + nlp->paramval = copyObject(phv); + root->curOuterParams = lappend(root->curOuterParams, nlp); + } + } + else + elog(ERROR, "unexpected type of subquery parameter"); + } +} + /* * fix_indexqual_references * Adjust indexqual clauses to the form the executor's indexqual - * machinery needs, and check for recheckable (lossy) index conditions. + * machinery needs. * - * We have five tasks here: + * We have four tasks here: * * Remove RestrictInfo nodes from the input clauses. + * * Replace any outer-relation Var or PHV nodes with nestloop Params. + * (XXX eventually, that responsibility should go elsewhere?) * * Index keys must be represented by Var nodes with varattno set to the * index's attribute number, not the attribute number in the original rel. * * If the index key is on the right, commute the clause to put it on the * left. - * * We must construct lists of operator strategy numbers and subtypes - * for the top-level operators of each index clause. - * * We must detect any lossy index operators. The API is that we return - * a list of the input clauses whose operators are NOT lossy. * - * fixed_indexquals receives a modified copy of the indexquals list --- the + * The result is a modified copy of the path's indexquals list --- the * original is not changed. Note also that the copy shares no substructure * with the original; this is needed in case there is a subplan in it (we need * two separate copies of the subplan tree, or things will go awry). - * - * nonlossy_indexquals receives a list of the original input clauses (with - * RestrictInfos) that contain non-lossy operators. - * - * indexstrategy receives an integer list of strategy numbers. - * indexsubtype receives an OID list of strategy subtypes. */ -static void -fix_indexqual_references(List *indexquals, IndexPath *index_path, - List **fixed_indexquals, - List **nonlossy_indexquals, - List **indexstrategy, - List **indexsubtype) +static List * +fix_indexqual_references(PlannerInfo *root, IndexPath *index_path) { IndexOptInfo *index = index_path->indexinfo; - ListCell *l; + List *fixed_indexquals; + ListCell *lcc, + *lci; - *fixed_indexquals = NIL; - *nonlossy_indexquals = NIL; - *indexstrategy = NIL; - *indexsubtype = NIL; + fixed_indexquals = NIL; - /* - * For each qual clause, commute if needed to put the indexkey operand on - * the left, and then fix its varattno. (We do not need to change the - * other side of the clause.) Then determine the operator's strategy - * number and subtype number, and check for lossy index behavior. - */ - foreach(l, indexquals) + forboth(lcc, index_path->indexquals, lci, index_path->indexqualcols) { - RestrictInfo *rinfo = (RestrictInfo *) lfirst(l); - Expr *clause; - Oid clause_op; - Oid opclass; - int stratno; - Oid stratsubtype; - bool recheck; + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lcc); + int indexcol = lfirst_int(lci); + Node *clause; Assert(IsA(rinfo, RestrictInfo)); /* - * Make a copy that will become the fixed clause. + * Replace any outer-relation variables with nestloop params. * - * We used to try to do a shallow copy here, but that fails if there - * is a subplan in the arguments of the opclause. So just do a full - * copy. + * This also makes a copy of the clause, so it's safe to modify it + * in-place below. */ - clause = (Expr *) copyObject((Node *) rinfo->clause); + clause = replace_nestloop_params(root, (Node *) rinfo->clause); if (IsA(clause, OpExpr)) { @@ -1701,55 +2770,62 @@ fix_indexqual_references(List *indexquals, IndexPath *index_path, /* * Check to see if the indexkey is on the right; if so, commute - * the clause. The indexkey should be the side that refers to + * the clause. The indexkey should be the side that refers to * (only) the base relation. */ if (!bms_equal(rinfo->left_relids, index->rel->relids)) CommuteOpExpr(op); /* - * Now, determine which index attribute this is, change the - * indexkey operand as needed, and get the index opclass. + * Now replace the indexkey expression with an index Var. */ linitial(op->args) = fix_indexqual_operand(linitial(op->args), index, - &opclass); - clause_op = op->opno; + indexcol); } else if (IsA(clause, RowCompareExpr)) { RowCompareExpr *rc = (RowCompareExpr *) clause; - ListCell *lc; + Expr *newrc; + List *indexcolnos; + bool var_on_left; + ListCell *lca, + *lcai; + + /* + * Re-discover which index columns are used in the rowcompare. + */ + newrc = adjust_rowcompare_for_index(rc, + index, + indexcol, + &indexcolnos, + &var_on_left); + + /* + * Trouble if adjust_rowcompare_for_index thought the + * RowCompareExpr didn't match the index as-is; the clause should + * have gone through that routine already. + */ + if (newrc != (Expr *) rc) + elog(ERROR, "inconsistent results from adjust_rowcompare_for_index"); /* * Check to see if the indexkey is on the right; if so, commute - * the clause. The indexkey should be the side that refers to - * (only) the base relation. + * the clause. */ - if (!bms_overlap(pull_varnos(linitial(rc->largs)), - index->rel->relids)) + if (!var_on_left) CommuteRowCompareExpr(rc); /* - * For each column in the row comparison, determine which index - * attribute this is and change the indexkey operand as needed. - * - * Save the index opclass for only the first column. We will - * return the operator and opclass info for just the first column - * of the row comparison; the executor will have to look up the - * rest if it needs them. + * Now replace the indexkey expressions with index Vars. */ - foreach(lc, rc->largs) + Assert(list_length(rc->largs) == list_length(indexcolnos)); + forboth(lca, rc->largs, lcai, indexcolnos) { - Oid tmp_opclass; - - lfirst(lc) = fix_indexqual_operand(lfirst(lc), - index, - &tmp_opclass); - if (lc == list_head(rc->largs)) - opclass = tmp_opclass; + lfirst(lca) = fix_indexqual_operand(lfirst(lca), + index, + lfirst_int(lcai)); } - clause_op = linitial_oid(rc->opnos); } else if (IsA(clause, ScalarArrayOpExpr)) { @@ -1757,51 +2833,101 @@ fix_indexqual_references(List *indexquals, IndexPath *index_path, /* Never need to commute... */ - /* - * Now, determine which index attribute this is, change the - * indexkey operand as needed, and get the index opclass. - */ + /* Replace the indexkey expression with an index Var. */ linitial(saop->args) = fix_indexqual_operand(linitial(saop->args), index, - &opclass); - clause_op = saop->opno; + indexcol); } - else + else if (IsA(clause, NullTest)) { + NullTest *nt = (NullTest *) clause; + + /* Replace the indexkey expression with an index Var. */ + nt->arg = (Expr *) fix_indexqual_operand((Node *) nt->arg, + index, + indexcol); + } + else elog(ERROR, "unsupported indexqual type: %d", (int) nodeTag(clause)); - continue; /* keep compiler quiet */ - } - *fixed_indexquals = lappend(*fixed_indexquals, clause); + fixed_indexquals = lappend(fixed_indexquals, clause); + } + + return fixed_indexquals; +} + +/* + * fix_indexorderby_references + * Adjust indexorderby clauses to the form the executor's index + * machinery needs. + * + * This is a simplified version of fix_indexqual_references. The input does + * not have RestrictInfo nodes, and we assume that indxpath.c already + * commuted the clauses to put the index keys on the left. Also, we don't + * bother to support any cases except simple OpExprs, since nothing else + * is allowed for ordering operators. + */ +static List * +fix_indexorderby_references(PlannerInfo *root, IndexPath *index_path) +{ + IndexOptInfo *index = index_path->indexinfo; + List *fixed_indexorderbys; + ListCell *lcc, + *lci; + + fixed_indexorderbys = NIL; + + forboth(lcc, index_path->indexorderbys, lci, index_path->indexorderbycols) + { + Node *clause = (Node *) lfirst(lcc); + int indexcol = lfirst_int(lci); /* - * Look up the (possibly commuted) operator in the operator class to - * get its strategy numbers and the recheck indicator. This also - * double-checks that we found an operator matching the index. + * Replace any outer-relation variables with nestloop params. + * + * This also makes a copy of the clause, so it's safe to modify it + * in-place below. */ - get_op_opclass_properties(clause_op, opclass, - &stratno, &stratsubtype, &recheck); + clause = replace_nestloop_params(root, clause); + + if (IsA(clause, OpExpr)) + { + OpExpr *op = (OpExpr *) clause; - *indexstrategy = lappend_int(*indexstrategy, stratno); - *indexsubtype = lappend_oid(*indexsubtype, stratsubtype); + if (list_length(op->args) != 2) + elog(ERROR, "indexorderby clause is not binary opclause"); + + /* + * Now replace the indexkey expression with an index Var. + */ + linitial(op->args) = fix_indexqual_operand(linitial(op->args), + index, + indexcol); + } + else + elog(ERROR, "unsupported indexorderby type: %d", + (int) nodeTag(clause)); - /* If it's not lossy, add to nonlossy_indexquals */ - if (!recheck) - *nonlossy_indexquals = lappend(*nonlossy_indexquals, rinfo); + fixed_indexorderbys = lappend(fixed_indexorderbys, clause); } + + return fixed_indexorderbys; } +/* + * fix_indexqual_operand + * Convert an indexqual expression to a Var referencing the index column. + * + * We represent index keys by Var nodes having varno == INDEX_VAR and varattno + * equal to the index's attribute number (index column position). + * + * Most of the code here is just for sanity cross-checking that the given + * expression actually matches the index column it's claimed to. + */ static Node * -fix_indexqual_operand(Node *node, IndexOptInfo *index, Oid *opclass) +fix_indexqual_operand(Node *node, IndexOptInfo *index, int indexcol) { - /* - * We represent index keys by Var nodes having the varno of the base table - * but varattno equal to the index's attribute number (index column - * position). This is a bit hokey ... would be cleaner to use a - * special-purpose node type that could not be mistaken for a regular Var. - * But it will do for now. - */ Var *result; int pos; ListCell *indexpr_item; @@ -1812,59 +2938,57 @@ fix_indexqual_operand(Node *node, IndexOptInfo *index, Oid *opclass) if (IsA(node, RelabelType)) node = (Node *) ((RelabelType *) node)->arg; - if (IsA(node, Var) && - ((Var *) node)->varno == index->rel->relid) - { - /* Try to match against simple index columns */ - int varatt = ((Var *) node)->varattno; + Assert(indexcol >= 0 && indexcol < index->ncolumns); - if (varatt != 0) + if (index->indexkeys[indexcol] != 0) + { + /* It's a simple index column */ + if (IsA(node, Var) && + ((Var *) node)->varno == index->rel->relid && + ((Var *) node)->varattno == index->indexkeys[indexcol]) { - for (pos = 0; pos < index->ncolumns; pos++) - { - if (index->indexkeys[pos] == varatt) - { - result = (Var *) copyObject(node); - result->varattno = pos + 1; - /* return the correct opclass, too */ - *opclass = index->classlist[pos]; - return (Node *) result; - } - } + result = (Var *) copyObject(node); + result->varno = INDEX_VAR; + result->varattno = indexcol + 1; + return (Node *) result; } + else + elog(ERROR, "index key does not match expected index column"); } - /* Try to match against index expressions */ + /* It's an index expression, so find and cross-check the expression */ indexpr_item = list_head(index->indexprs); for (pos = 0; pos < index->ncolumns; pos++) { if (index->indexkeys[pos] == 0) { - Node *indexkey; - if (indexpr_item == NULL) elog(ERROR, "too few entries in indexprs list"); - indexkey = (Node *) lfirst(indexpr_item); - if (indexkey && IsA(indexkey, RelabelType)) - indexkey = (Node *) ((RelabelType *) indexkey)->arg; - if (equal(node, indexkey)) + if (pos == indexcol) { - /* Found a match */ - result = makeVar(index->rel->relid, pos + 1, - exprType(lfirst(indexpr_item)), -1, - 0); - /* return the correct opclass, too */ - *opclass = index->classlist[pos]; - return (Node *) result; + Node *indexkey; + + indexkey = (Node *) lfirst(indexpr_item); + if (indexkey && IsA(indexkey, RelabelType)) + indexkey = (Node *) ((RelabelType *) indexkey)->arg; + if (equal(node, indexkey)) + { + result = makeVar(INDEX_VAR, indexcol + 1, + exprType(lfirst(indexpr_item)), -1, + exprCollation(lfirst(indexpr_item)), + 0); + return (Node *) result; + } + else + elog(ERROR, "index key does not match expected index column"); } indexpr_item = lnext(indexpr_item); } } /* Ooops... */ - elog(ERROR, "node is not an index attribute"); - *opclass = InvalidOid; /* keep compiler quiet */ - return NULL; + elog(ERROR, "index key does not match expected index column"); + return NULL; /* keep compiler quiet */ } /* @@ -1872,8 +2996,9 @@ fix_indexqual_operand(Node *node, IndexOptInfo *index, Oid *opclass) * Given a list of merge or hash joinclauses (as RestrictInfo nodes), * extract the bare clauses, and rearrange the elements within the * clauses, if needed, so the outer join variable is on the left and - * the inner is on the right. The original data structure is not touched; - * a modified list is returned. + * the inner is on the right. The original clause data structure is not + * touched; a modified list is returned. We do, however, set the transient + * outer_is_left field in each RestrictInfo to show which side was which. */ static List * get_switched_clauses(List *clauses, Relids outerrelids) @@ -1900,13 +3025,21 @@ get_switched_clauses(List *clauses, Relids outerrelids) temp->opfuncid = InvalidOid; temp->opresulttype = clause->opresulttype; temp->opretset = clause->opretset; + temp->opcollid = clause->opcollid; + temp->inputcollid = clause->inputcollid; temp->args = list_copy(clause->args); + temp->location = clause->location; /* Commute it --- note this modifies the temp node in-place. */ CommuteOpExpr(temp); t_list = lappend(t_list, temp); + restrictinfo->outer_is_left = false; } else + { + Assert(bms_is_subset(restrictinfo->left_relids, outerrelids)); t_list = lappend(t_list, clause); + restrictinfo->outer_is_left = true; + } } return t_list; } @@ -1918,40 +3051,87 @@ get_switched_clauses(List *clauses, Relids outerrelids) * in at runtime. * * Ideally the order should be driven by a combination of execution cost and - * selectivity, but unfortunately we have so little information about - * execution cost of operators that it's really hard to do anything smart. - * For now, we just move any quals that contain SubPlan references (but not - * InitPlan references) to the end of the list. + * selectivity, but it's not immediately clear how to account for both, + * and given the uncertainty of the estimates the reliability of the decisions + * would be doubtful anyway. So we just order by estimated per-tuple cost, + * being careful not to change the order when (as is often the case) the + * estimates are identical. + * + * Although this will work on either bare clauses or RestrictInfos, it's + * much faster to apply it to RestrictInfos, since it can re-use cost + * information that is cached in RestrictInfos. + * + * Note: some callers pass lists that contain entries that will later be + * removed; this is the easiest way to let this routine see RestrictInfos + * instead of bare clauses. It's OK because we only sort by cost, but + * a cost/selectivity combination would likely do the wrong thing. */ static List * order_qual_clauses(PlannerInfo *root, List *clauses) { - List *nosubplans; - List *withsubplans; - ListCell *l; + typedef struct + { + Node *clause; + Cost cost; + } QualItem; + int nitems = list_length(clauses); + QualItem *items; + ListCell *lc; + int i; + List *result; - /* No need to work hard if the query is subselect-free */ - if (!root->parse->hasSubLinks) + /* No need to work hard for 0 or 1 clause */ + if (nitems <= 1) return clauses; - nosubplans = NIL; - withsubplans = NIL; - foreach(l, clauses) + /* + * Collect the items and costs into an array. This is to avoid repeated + * cost_qual_eval work if the inputs aren't RestrictInfos. + */ + items = (QualItem *) palloc(nitems * sizeof(QualItem)); + i = 0; + foreach(lc, clauses) { - Node *clause = (Node *) lfirst(l); + Node *clause = (Node *) lfirst(lc); + QualCost qcost; - if (contain_subplans(clause)) - withsubplans = lappend(withsubplans, clause); - else - nosubplans = lappend(nosubplans, clause); + cost_qual_eval_node(&qcost, clause, root); + items[i].clause = clause; + items[i].cost = qcost.per_tuple; + i++; + } + + /* + * Sort. We don't use qsort() because it's not guaranteed stable for + * equal keys. The expected number of entries is small enough that a + * simple insertion sort should be good enough. + */ + for (i = 1; i < nitems; i++) + { + QualItem newitem = items[i]; + int j; + + /* insert newitem into the already-sorted subarray */ + for (j = i; j > 0; j--) + { + if (newitem.cost >= items[j - 1].cost) + break; + items[j] = items[j - 1]; + } + items[j] = newitem; } - return list_concat(nosubplans, withsubplans); + /* Convert back to a list */ + result = NIL; + for (i = 0; i < nitems; i++) + result = lappend(result, items[i].clause); + + return result; } /* * 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) @@ -1960,7 +3140,7 @@ copy_path_costsize(Plan *dest, Path *src) { dest->startup_cost = src->startup_cost; dest->total_cost = src->total_cost; - dest->plan_rows = src->parent->rows; + dest->plan_rows = src->rows; dest->plan_width = src->parent->width; } else @@ -1974,9 +3154,7 @@ 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) @@ -2032,8 +3210,8 @@ make_indexscan(List *qptlist, Oid indexid, List *indexqual, List *indexqualorig, - List *indexstrategy, - List *indexsubtype, + List *indexorderby, + List *indexorderbyorig, ScanDirection indexscandir) { IndexScan *node = makeNode(IndexScan); @@ -2048,8 +3226,36 @@ make_indexscan(List *qptlist, node->indexid = indexid; node->indexqual = indexqual; node->indexqualorig = indexqualorig; - node->indexstrategy = indexstrategy; - node->indexsubtype = indexsubtype; + node->indexorderby = indexorderby; + node->indexorderbyorig = indexorderbyorig; + node->indexorderdir = indexscandir; + + return node; +} + +static IndexOnlyScan * +make_indexonlyscan(List *qptlist, + List *qpqual, + Index scanrelid, + Oid indexid, + List *indexqual, + List *indexorderby, + List *indextlist, + ScanDirection indexscandir) +{ + IndexOnlyScan *node = makeNode(IndexOnlyScan); + 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->indexid = indexid; + node->indexqual = indexqual; + node->indexorderby = indexorderby; + node->indextlist = indextlist; node->indexorderdir = indexscandir; return node; @@ -2059,9 +3265,7 @@ static BitmapIndexScan * make_bitmap_indexscan(Index scanrelid, Oid indexid, List *indexqual, - List *indexqualorig, - List *indexstrategy, - List *indexsubtype) + List *indexqualorig) { BitmapIndexScan *node = makeNode(BitmapIndexScan); Plan *plan = &node->scan.plan; @@ -2075,8 +3279,6 @@ make_bitmap_indexscan(Index scanrelid, node->indexid = indexid; node->indexqual = indexqual; node->indexqualorig = indexqualorig; - node->indexstrategy = indexstrategy; - node->indexsubtype = indexsubtype; return node; } @@ -2152,7 +3354,12 @@ make_subqueryscan(List *qptlist, static FunctionScan * make_functionscan(List *qptlist, List *qpqual, - Index scanrelid) + Index scanrelid, + Node *funcexpr, + List *funccolnames, + List *funccoltypes, + List *funccoltypmods, + List *funccolcollations) { FunctionScan *node = makeNode(FunctionScan); Plan *plan = &node->scan.plan; @@ -2163,6 +3370,11 @@ make_functionscan(List *qptlist, plan->lefttree = NULL; plan->righttree = NULL; node->scan.scanrelid = scanrelid; + node->funcexpr = funcexpr; + node->funccolnames = funccolnames; + node->funccoltypes = funccoltypes; + node->funccoltypmods = funccoltypmods; + node->funccolcollations = funccolcollations; return node; } @@ -2170,7 +3382,8 @@ make_functionscan(List *qptlist, static ValuesScan * make_valuesscan(List *qptlist, List *qpqual, - Index scanrelid) + Index scanrelid, + List *values_lists) { ValuesScan *node = makeNode(ValuesScan); Plan *plan = &node->scan.plan; @@ -2181,26 +3394,100 @@ make_valuesscan(List *qptlist, plan->lefttree = NULL; plan->righttree = NULL; node->scan.scanrelid = scanrelid; + node->values_lists = values_lists; + + return node; +} + +static CteScan * +make_ctescan(List *qptlist, + List *qpqual, + Index scanrelid, + int ctePlanId, + int cteParam) +{ + CteScan *node = makeNode(CteScan); + 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->ctePlanId = ctePlanId; + node->cteParam = cteParam; + + return node; +} + +static WorkTableScan * +make_worktablescan(List *qptlist, + List *qpqual, + Index scanrelid, + int wtParam) +{ + WorkTableScan *node = makeNode(WorkTableScan); + 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->wtParam = wtParam; + + return node; +} + +ForeignScan * +make_foreignscan(List *qptlist, + List *qpqual, + Index scanrelid, + List *fdw_exprs, + List *fdw_private) +{ + ForeignScan *node = makeNode(ForeignScan); + Plan *plan = &node->scan.plan; + + /* cost will be filled in by create_foreignscan_plan */ + plan->targetlist = qptlist; + plan->qual = qpqual; + plan->lefttree = NULL; + plan->righttree = NULL; + node->scan.scanrelid = scanrelid; + node->fdw_exprs = fdw_exprs; + node->fdw_private = fdw_private; + /* fsSystemCol will be filled in by create_foreignscan_plan */ + node->fsSystemCol = false; return node; } Append * -make_append(List *appendplans, bool isTarget, List *tlist) +make_append(List *appendplans, List *tlist) { Append *node = makeNode(Append); Plan *plan = &node->plan; + double total_size; ListCell *subnode; /* * 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; plan->plan_rows = 0; - plan->plan_width = 0; + total_size = 0; foreach(subnode, appendplans) { Plan *subplan = (Plan *) lfirst(subnode); @@ -2209,16 +3496,72 @@ make_append(List *appendplans, bool isTarget, List *tlist) plan->startup_cost = subplan->startup_cost; plan->total_cost += subplan->total_cost; plan->plan_rows += subplan->plan_rows; - if (plan->plan_width < subplan->plan_width) - plan->plan_width = subplan->plan_width; + total_size += subplan->plan_width * subplan->plan_rows; } + if (plan->plan_rows > 0) + plan->plan_width = rint(total_size / plan->plan_rows); + else + plan->plan_width = 0; plan->targetlist = tlist; plan->qual = NIL; plan->lefttree = NULL; plan->righttree = NULL; node->appendplans = appendplans; - node->isTarget = isTarget; + + return node; +} + +RecursiveUnion * +make_recursive_union(List *tlist, + Plan *lefttree, + Plan *righttree, + int wtParam, + List *distinctList, + long numGroups) +{ + RecursiveUnion *node = makeNode(RecursiveUnion); + Plan *plan = &node->plan; + int numCols = list_length(distinctList); + + cost_recursive_union(plan, lefttree, righttree); + + plan->targetlist = tlist; + plan->qual = NIL; + plan->lefttree = lefttree; + plan->righttree = righttree; + node->wtParam = wtParam; + + /* + * convert SortGroupClause list into arrays of attr indexes and equality + * operators, as wanted by executor + */ + node->numCols = numCols; + if (numCols > 0) + { + int keyno = 0; + AttrNumber *dupColIdx; + Oid *dupOperators; + ListCell *slitem; + + dupColIdx = (AttrNumber *) palloc(sizeof(AttrNumber) * numCols); + dupOperators = (Oid *) palloc(sizeof(Oid) * numCols); + + foreach(slitem, distinctList) + { + SortGroupClause *sortcl = (SortGroupClause *) lfirst(slitem); + TargetEntry *tle = get_sortgroupclause_tle(sortcl, + plan->targetlist); + + dupColIdx[keyno] = tle->resno; + dupOperators[keyno] = sortcl->eqop; + Assert(OidIsValid(dupOperators[keyno])); + keyno++; + } + node->dupColIdx = dupColIdx; + node->dupOperators = dupOperators; + } + node->numGroups = numGroups; return node; } @@ -2259,6 +3602,7 @@ static NestLoop * make_nestloop(List *tlist, List *joinclauses, List *otherclauses, + List *nestParams, Plan *lefttree, Plan *righttree, JoinType jointype) @@ -2273,6 +3617,7 @@ make_nestloop(List *tlist, plan->righttree = righttree; node->join.jointype = jointype; node->join.joinqual = joinclauses; + node->nestParams = nestParams; return node; } @@ -2302,7 +3647,12 @@ make_hashjoin(List *tlist, } static Hash * -make_hash(Plan *lefttree) +make_hash(Plan *lefttree, + Oid skewTable, + AttrNumber skewColumn, + bool skewInherit, + Oid skewColType, + int32 skewColTypmod) { Hash *node = makeNode(Hash); Plan *plan = &node->plan; @@ -2314,11 +3664,17 @@ make_hash(Plan *lefttree) * plan; this only affects EXPLAIN display not decisions. */ plan->startup_cost = plan->total_cost; - plan->targetlist = copyObject(lefttree->targetlist); + plan->targetlist = lefttree->targetlist; plan->qual = NIL; plan->lefttree = lefttree; plan->righttree = NULL; + node->skewTable = skewTable; + node->skewColumn = skewColumn; + node->skewInherit = skewInherit; + node->skewColType = skewColType; + node->skewColTypmod = skewColTypmod; + return node; } @@ -2327,6 +3683,10 @@ make_mergejoin(List *tlist, List *joinclauses, List *otherclauses, List *mergeclauses, + Oid *mergefamilies, + Oid *mergecollations, + int *mergestrategies, + bool *mergenullsfirst, Plan *lefttree, Plan *righttree, JoinType jointype) @@ -2340,6 +3700,10 @@ make_mergejoin(List *tlist, plan->lefttree = lefttree; plan->righttree = righttree; node->mergeclauses = mergeclauses; + node->mergeFamilies = mergefamilies; + node->mergeCollations = mergecollations; + node->mergeStrategies = mergestrategies; + node->mergeNullsFirst = mergenullsfirst; node->join.jointype = jointype; node->join.joinqual = joinclauses; @@ -2349,11 +3713,15 @@ make_mergejoin(List *tlist, /* * make_sort --- basic routine to build a Sort plan node * - * Caller must have built the sortColIdx and sortOperators arrays already. + * Caller must have built the sortColIdx, sortOperators, collations, and + * nullsFirst arrays already. + * limit_tuples is as for cost_sort (in particular, pass -1 if no limit) */ static Sort * make_sort(PlannerInfo *root, Plan *lefttree, int numCols, - AttrNumber *sortColIdx, Oid *sortOperators) + AttrNumber *sortColIdx, Oid *sortOperators, + Oid *collations, bool *nullsFirst, + double limit_tuples) { Sort *node = makeNode(Sort); Plan *plan = &node->plan; @@ -2363,74 +3731,83 @@ make_sort(PlannerInfo *root, Plan *lefttree, int numCols, cost_sort(&sort_path, root, NIL, lefttree->total_cost, lefttree->plan_rows, - lefttree->plan_width); + lefttree->plan_width, + 0.0, + work_mem, + limit_tuples); plan->startup_cost = sort_path.startup_cost; plan->total_cost = sort_path.total_cost; - plan->targetlist = copyObject(lefttree->targetlist); + plan->targetlist = lefttree->targetlist; plan->qual = NIL; plan->lefttree = lefttree; plan->righttree = NULL; node->numCols = numCols; node->sortColIdx = sortColIdx; node->sortOperators = sortOperators; + node->collations = collations; + node->nullsFirst = nullsFirst; return node; } /* - * add_sort_column --- utility subroutine for building sort info arrays - * - * We need this routine because the same column might be selected more than - * once as a sort key column; if so, the extra mentions are redundant. + * prepare_sort_from_pathkeys + * Prepare to sort according to given pathkeys * - * Caller is assumed to have allocated the arrays large enough for the - * max possible number of columns. Return value is the new column count. - */ -static int -add_sort_column(AttrNumber colIdx, Oid sortOp, - int numCols, AttrNumber *sortColIdx, Oid *sortOperators) -{ - int i; - - for (i = 0; i < numCols; i++) - { - if (sortColIdx[i] == colIdx) - { - /* Already sorting by this col, so extra sort key is useless */ - return numCols; - } - } - - /* Add the column */ - sortColIdx[numCols] = colIdx; - sortOperators[numCols] = sortOp; - return numCols + 1; -} - -/* - * make_sort_from_pathkeys - * Create sort plan 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. * - * 'lefttree' is the node which yields input tuples + * Input parameters: + * 'lefttree' is the plan node which yields input tuples * '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 * * We must convert the pathkey information into arrays of sort key column - * numbers and sort operator OIDs. + * numbers, sort operator OIDs, collation OIDs, and nulls-first flags, + * which is the representation the executor wants. These are returned into + * 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. + * + * 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; + * it's an error if we can't match the columns. * * 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). */ -static Sort * -make_sort_from_pathkeys(PlannerInfo *root, Plan *lefttree, List *pathkeys) +static Plan * +prepare_sort_from_pathkeys(PlannerInfo *root, Plan *lefttree, List *pathkeys, + Relids relids, + const AttrNumber *reqColIdx, + bool adjust_tlist_in_place, + int *p_numsortkeys, + AttrNumber **p_sortColIdx, + Oid **p_sortOperators, + Oid **p_collations, + bool **p_nullsFirst) { List *tlist = lefttree->targetlist; ListCell *i; int numsortkeys; AttrNumber *sortColIdx; Oid *sortOperators; + Oid *collations; + bool *nullsFirst; /* * We will need at most list_length(pathkeys) sort columns; possibly less @@ -2438,54 +3815,139 @@ make_sort_from_pathkeys(PlannerInfo *root, Plan *lefttree, List *pathkeys) numsortkeys = list_length(pathkeys); sortColIdx = (AttrNumber *) palloc(numsortkeys * sizeof(AttrNumber)); sortOperators = (Oid *) palloc(numsortkeys * sizeof(Oid)); + collations = (Oid *) palloc(numsortkeys * sizeof(Oid)); + nullsFirst = (bool *) palloc(numsortkeys * sizeof(bool)); numsortkeys = 0; foreach(i, pathkeys) { - List *keysublist = (List *) lfirst(i); - PathKeyItem *pathkey = NULL; + PathKey *pathkey = (PathKey *) lfirst(i); + EquivalenceClass *ec = pathkey->pk_eclass; + EquivalenceMember *em; TargetEntry *tle = NULL; + Oid pk_datatype = InvalidOid; + Oid sortop; ListCell *j; - /* - * We can sort by any one of the sort key items listed in this - * sublist. For now, we take the first one that corresponds to an - * available Var in the tlist. If there isn't any, use the first one - * that is an expression in the input's vars. - * - * XXX if we have a choice, is there any way of figuring out which - * might be cheapest to execute? (For example, int4lt is likely much - * cheaper to execute than numericlt, but both might appear in the - * same pathkey sublist...) Not clear that we ever will have a choice - * in practice, so it may not matter. - */ - foreach(j, keysublist) + if (ec->ec_has_volatile) + { + /* + * If the pathkey's EquivalenceClass is volatile, then it must + * have come from an ORDER BY clause, and we have to match it to + * that same targetlist entry. + */ + if (ec->ec_sortref == 0) /* can't happen */ + elog(ERROR, "volatile EquivalenceClass has no sortref"); + tle = get_sortgroupref_tle(ec->ec_sortref, tlist); + Assert(tle); + Assert(list_length(ec->ec_members) == 1); + pk_datatype = ((EquivalenceMember *) linitial(ec->ec_members))->em_datatype; + } + else if (reqColIdx != NULL) { - pathkey = (PathKeyItem *) lfirst(j); - Assert(IsA(pathkey, PathKeyItem)); - tle = tlist_member(pathkey->key, tlist); + /* + * If we are given a sort column number to match, only consider + * the single TLE at that position. It's possible that there is + * no such TLE, in which case fall through and generate a resjunk + * targetentry (we assume this must have happened in the parent + * plan as well). If there is a TLE but it doesn't match the + * pathkey's EC, we do the same, which is probably the wrong thing + * but we'll leave it to caller to complain about the mismatch. + */ + tle = get_tle_by_resno(tlist, reqColIdx[numsortkeys]); if (tle) - break; + { + em = find_ec_member_for_tle(ec, tle, relids); + if (em) + { + /* found expr at right place in tlist */ + pk_datatype = em->em_datatype; + } + else + tle = NULL; + } + } + else + { + /* + * Otherwise, we can sort by any non-constant expression listed in + * the pathkey's EquivalenceClass. For now, we take the first + * tlist item found in the EC. If there's no match, we'll generate + * a resjunk entry using the first EC member that is an expression + * in the input's vars. (The non-const restriction only matters + * if the EC is below_outer_join; but if it isn't, it won't + * contain consts anyway, else we'd have discarded the pathkey as + * redundant.) + * + * XXX if we have a choice, is there any way of figuring out which + * might be cheapest to execute? (For example, int4lt is likely + * much cheaper to execute than numericlt, but both might appear + * in the same equivalence class...) Not clear that we ever will + * have an interesting choice in practice, so it may not matter. + */ + foreach(j, tlist) + { + tle = (TargetEntry *) lfirst(j); + em = find_ec_member_for_tle(ec, tle, relids); + if (em) + { + /* found expr already in tlist */ + pk_datatype = em->em_datatype; + break; + } + tle = NULL; + } } + if (!tle) { - /* No matching Var; look for a computable expression */ - foreach(j, keysublist) + /* + * No matching tlist item; look for a computable expression. Note + * that we treat Aggrefs as if they were variables; this is + * necessary when attempting to sort the output from an Agg node + * for use in a WindowFunc (since grouping_planner will have + * treated the Aggrefs as variables, too). + */ + Expr *sortexpr = NULL; + + foreach(j, ec->ec_members) { + EquivalenceMember *em = (EquivalenceMember *) lfirst(j); List *exprvars; ListCell *k; - pathkey = (PathKeyItem *) lfirst(j); - exprvars = pull_var_clause(pathkey->key, false); + /* + * 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; + + /* + * Ignore child members unless they match the rel being + * sorted. + */ + if (em->em_is_child && + !bms_equal(em->em_relids, relids)) + continue; + + sortexpr = em->em_expr; + exprvars = pull_var_clause((Node *) sortexpr, + PVC_INCLUDE_AGGREGATES, + PVC_INCLUDE_PLACEHOLDERS); foreach(k, exprvars) { - if (!tlist_member(lfirst(k), tlist)) + if (!tlist_member_ignore_relabel(lfirst(k), tlist)) break; } list_free(exprvars); if (!k) + { + pk_datatype = em->em_datatype; break; /* found usable expression */ + } } if (!j) elog(ERROR, "could not find pathkey item to sort"); @@ -2493,16 +3955,22 @@ make_sort_from_pathkeys(PlannerInfo *root, Plan *lefttree, List *pathkeys) /* * 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 = (Plan *) make_result(tlist, NULL, lefttree); + lefttree = (Plan *) make_result(root, tlist, NULL, + lefttree); } + /* Don't bother testing is_projection_capable_plan again */ + adjust_tlist_in_place = true; + /* * Add resjunk entry to input's tlist */ - tle = makeTargetEntry((Expr *) pathkey->key, + tle = makeTargetEntry(sortexpr, list_length(tlist) + 1, NULL, true); @@ -2511,26 +3979,127 @@ make_sort_from_pathkeys(PlannerInfo *root, Plan *lefttree, List *pathkeys) } /* - * The column might already be selected as a sort key, if the pathkeys - * contain duplicate entries. (This can happen in scenarios where - * multiple mergejoinable clauses mention the same var, for example.) - * So enter it only once in the sort arrays. + * Look up the correct sort operator from the PathKey's slightly + * abstracted representation. + */ + sortop = get_opfamily_member(pathkey->pk_opfamily, + pk_datatype, + pk_datatype, + pathkey->pk_strategy); + if (!OidIsValid(sortop)) /* should not happen */ + elog(ERROR, "could not find member %d(%u,%u) of opfamily %u", + pathkey->pk_strategy, pk_datatype, pk_datatype, + pathkey->pk_opfamily); + + /* Add the column to the sort arrays */ + sortColIdx[numsortkeys] = tle->resno; + sortOperators[numsortkeys] = sortop; + collations[numsortkeys] = ec->ec_collation; + nullsFirst[numsortkeys] = pathkey->pk_nulls_first; + numsortkeys++; + } + + /* Return results */ + *p_numsortkeys = numsortkeys; + *p_sortColIdx = sortColIdx; + *p_sortOperators = sortOperators; + *p_collations = collations; + *p_nullsFirst = nullsFirst; + + return lefttree; +} + +/* + * find_ec_member_for_tle + * Locate an EquivalenceClass member matching the given TLE, if any + * + * Child EC members are ignored unless they match 'relids'. + */ +static EquivalenceMember * +find_ec_member_for_tle(EquivalenceClass *ec, + TargetEntry *tle, + Relids relids) +{ + Expr *tlexpr; + ListCell *lc; + + /* We ignore binary-compatible relabeling on both ends */ + tlexpr = tle->expr; + while (tlexpr && IsA(tlexpr, RelabelType)) + tlexpr = ((RelabelType *) tlexpr)->arg; + + foreach(lc, ec->ec_members) + { + EquivalenceMember *em = (EquivalenceMember *) lfirst(lc); + Expr *emexpr; + + /* + * 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; + + /* + * Ignore child members unless they match the rel being sorted. */ - numsortkeys = add_sort_column(tle->resno, pathkey->sortop, - numsortkeys, sortColIdx, sortOperators); + if (em->em_is_child && + !bms_equal(em->em_relids, relids)) + continue; + + /* Match if same expression (after stripping relabel) */ + emexpr = em->em_expr; + while (emexpr && IsA(emexpr, RelabelType)) + emexpr = ((RelabelType *) emexpr)->arg; + + if (equal(emexpr, tlexpr)) + return em; } - Assert(numsortkeys > 0); + return NULL; +} +/* + * 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; + Oid *collations; + bool *nullsFirst; + + /* Compute sort column info, and adjust lefttree as needed */ + lefttree = prepare_sort_from_pathkeys(root, lefttree, pathkeys, + NULL, + NULL, + false, + &numsortkeys, + &sortColIdx, + &sortOperators, + &collations, + &nullsFirst); + + /* Now build the Sort node */ return make_sort(root, lefttree, numsortkeys, - sortColIdx, sortOperators); + sortColIdx, sortOperators, collations, + nullsFirst, limit_tuples); } /* * make_sort_from_sortclauses * Create sort plan to sort according to given sortclauses * - * 'sortcls' is a list of SortClauses + * 'sortcls' is a list of SortGroupClauses * 'lefttree' is the node which yields input tuples */ Sort * @@ -2541,48 +4110,46 @@ make_sort_from_sortclauses(PlannerInfo *root, List *sortcls, Plan *lefttree) int numsortkeys; AttrNumber *sortColIdx; Oid *sortOperators; + Oid *collations; + bool *nullsFirst; - /* - * We will need at most list_length(sortcls) sort columns; possibly less - */ + /* Convert list-ish representation to arrays wanted by executor */ numsortkeys = list_length(sortcls); sortColIdx = (AttrNumber *) palloc(numsortkeys * sizeof(AttrNumber)); sortOperators = (Oid *) palloc(numsortkeys * sizeof(Oid)); + collations = (Oid *) palloc(numsortkeys * sizeof(Oid)); + nullsFirst = (bool *) palloc(numsortkeys * sizeof(bool)); numsortkeys = 0; - foreach(l, sortcls) { - SortClause *sortcl = (SortClause *) lfirst(l); + SortGroupClause *sortcl = (SortGroupClause *) lfirst(l); TargetEntry *tle = get_sortgroupclause_tle(sortcl, sub_tlist); - /* - * Check for the possibility of duplicate order-by clauses --- the - * parser should have removed 'em, but no point in sorting - * redundantly. - */ - numsortkeys = add_sort_column(tle->resno, sortcl->sortop, - numsortkeys, sortColIdx, sortOperators); + sortColIdx[numsortkeys] = tle->resno; + sortOperators[numsortkeys] = sortcl->sortop; + collations[numsortkeys] = exprCollation((Node *) tle->expr); + nullsFirst[numsortkeys] = sortcl->nulls_first; + numsortkeys++; } - Assert(numsortkeys > 0); - return make_sort(root, lefttree, numsortkeys, - sortColIdx, sortOperators); + sortColIdx, sortOperators, collations, + nullsFirst, -1.0); } /* * make_sort_from_groupcols * Create sort plan to sort based on grouping columns * - * 'groupcls' is the list of GroupClauses + * 'groupcls' is the list of SortGroupClauses * 'grpColIdx' gives the column numbers to use * * This might look like it could be merged with make_sort_from_sortclauses, * but presently we *must* use the grpColIdx[] array to locate sort columns, * because the child plan's tlist is not marked with ressortgroupref info - * appropriate to the grouping node. So, only the sortop is used from the - * GroupClause entries. + * appropriate to the grouping node. So, only the sort ordering info + * is used from the SortGroupClause entries. */ Sort * make_sort_from_groupcols(PlannerInfo *root, @@ -2591,50 +4158,46 @@ make_sort_from_groupcols(PlannerInfo *root, Plan *lefttree) { List *sub_tlist = lefttree->targetlist; - int grpno = 0; ListCell *l; int numsortkeys; AttrNumber *sortColIdx; Oid *sortOperators; + Oid *collations; + bool *nullsFirst; - /* - * We will need at most list_length(groupcls) sort columns; possibly less - */ + /* Convert list-ish representation to arrays wanted by executor */ numsortkeys = list_length(groupcls); sortColIdx = (AttrNumber *) palloc(numsortkeys * sizeof(AttrNumber)); sortOperators = (Oid *) palloc(numsortkeys * sizeof(Oid)); + collations = (Oid *) palloc(numsortkeys * sizeof(Oid)); + nullsFirst = (bool *) palloc(numsortkeys * sizeof(bool)); numsortkeys = 0; - foreach(l, groupcls) { - GroupClause *grpcl = (GroupClause *) lfirst(l); - TargetEntry *tle = get_tle_by_resno(sub_tlist, grpColIdx[grpno]); - - /* - * Check for the possibility of duplicate group-by clauses --- the - * parser should have removed 'em, but no point in sorting - * redundantly. - */ - numsortkeys = add_sort_column(tle->resno, grpcl->sortop, - numsortkeys, sortColIdx, sortOperators); - grpno++; + SortGroupClause *grpcl = (SortGroupClause *) lfirst(l); + TargetEntry *tle = get_tle_by_resno(sub_tlist, grpColIdx[numsortkeys]); + + sortColIdx[numsortkeys] = tle->resno; + sortOperators[numsortkeys] = grpcl->sortop; + collations[numsortkeys] = exprCollation((Node *) tle->expr); + nullsFirst[numsortkeys] = grpcl->nulls_first; + numsortkeys++; } - Assert(numsortkeys > 0); - return make_sort(root, lefttree, numsortkeys, - sortColIdx, sortOperators); + sortColIdx, sortOperators, collations, + nullsFirst, -1.0); } -Material * +static Material * make_material(Plan *lefttree) { Material *node = makeNode(Material); Plan *plan = &node->plan; /* cost should be inserted by caller */ - plan->targetlist = copyObject(lefttree->targetlist); + plan->targetlist = lefttree->targetlist; plan->qual = NIL; plan->lefttree = lefttree; plan->righttree = NULL; @@ -2662,6 +4225,7 @@ materialize_finished_plan(Plan *subplan) /* Set cost data */ cost_material(&matpath, + subplan->startup_cost, subplan->total_cost, subplan->plan_rows, subplan->plan_width); @@ -2679,9 +4243,9 @@ materialize_finished_plan(Plan *subplan) Agg * make_agg(PlannerInfo *root, List *tlist, List *qual, - AggStrategy aggstrategy, - int numGroupCols, AttrNumber *grpColIdx, - long numGroups, int numAggs, + AggStrategy aggstrategy, const AggClauseCosts *aggcosts, + int numGroupCols, AttrNumber *grpColIdx, Oid *grpOperators, + long numGroups, Plan *lefttree) { Agg *node = makeNode(Agg); @@ -2692,11 +4256,12 @@ make_agg(PlannerInfo *root, List *tlist, List *qual, node->aggstrategy = aggstrategy; node->numCols = numGroupCols; node->grpColIdx = grpColIdx; + node->grpOperators = grpOperators; node->numGroups = numGroups; copy_plan_costsize(plan, lefttree); /* only care about copying size */ cost_agg(&agg_path, root, - aggstrategy, numAggs, + aggstrategy, aggcosts, numGroupCols, numGroups, lefttree->startup_cost, lefttree->total_cost, @@ -2719,20 +4284,17 @@ make_agg(PlannerInfo *root, List *tlist, List *qual, * anything for Aggref nodes; this is okay since they are really * comparable to Vars. * - * See notes in grouping_planner about why this routine and make_group are - * the only ones in this file that worry about tlist eval cost. + * See notes in add_tlist_costs_to_plan about why only make_agg, + * make_windowagg and make_group worry about tlist eval cost. */ if (qual) { - cost_qual_eval(&qual_cost, qual); + cost_qual_eval(&qual_cost, qual, root); plan->startup_cost += qual_cost.startup; plan->total_cost += qual_cost.startup; plan->total_cost += qual_cost.per_tuple * plan->plan_rows; } - cost_qual_eval(&qual_cost, tlist); - plan->startup_cost += qual_cost.startup; - plan->total_cost += qual_cost.startup; - plan->total_cost += qual_cost.per_tuple * plan->plan_rows; + add_tlist_costs_to_plan(root, plan, tlist); plan->qual = qual; plan->targetlist = tlist; @@ -2742,12 +4304,62 @@ make_agg(PlannerInfo *root, List *tlist, List *qual, return node; } +WindowAgg * +make_windowagg(PlannerInfo *root, List *tlist, + List *windowFuncs, Index winref, + int partNumCols, AttrNumber *partColIdx, Oid *partOperators, + int ordNumCols, AttrNumber *ordColIdx, Oid *ordOperators, + int frameOptions, Node *startOffset, Node *endOffset, + Plan *lefttree) +{ + WindowAgg *node = makeNode(WindowAgg); + Plan *plan = &node->plan; + Path windowagg_path; /* dummy for result of cost_windowagg */ + + node->winref = winref; + node->partNumCols = partNumCols; + node->partColIdx = partColIdx; + node->partOperators = partOperators; + node->ordNumCols = ordNumCols; + node->ordColIdx = ordColIdx; + node->ordOperators = ordOperators; + node->frameOptions = frameOptions; + node->startOffset = startOffset; + node->endOffset = endOffset; + + copy_plan_costsize(plan, lefttree); /* only care about copying size */ + cost_windowagg(&windowagg_path, root, + windowFuncs, partNumCols, ordNumCols, + lefttree->startup_cost, + lefttree->total_cost, + lefttree->plan_rows); + plan->startup_cost = windowagg_path.startup_cost; + plan->total_cost = windowagg_path.total_cost; + + /* + * We also need to account for the cost of evaluation of the tlist. + * + * See notes in add_tlist_costs_to_plan about why only make_agg, + * make_windowagg and make_group worry about tlist eval cost. + */ + add_tlist_costs_to_plan(root, plan, tlist); + + plan->targetlist = tlist; + plan->lefttree = lefttree; + plan->righttree = NULL; + /* WindowAgg nodes never have a qual clause */ + plan->qual = NIL; + + return node; +} + Group * make_group(PlannerInfo *root, List *tlist, List *qual, int numGroupCols, AttrNumber *grpColIdx, + Oid *grpOperators, double numGroups, Plan *lefttree) { @@ -2758,6 +4370,7 @@ make_group(PlannerInfo *root, node->numCols = numGroupCols; node->grpColIdx = grpColIdx; + node->grpOperators = grpOperators; copy_plan_costsize(plan, lefttree); /* only care about copying size */ cost_group(&group_path, root, @@ -2780,20 +4393,17 @@ make_group(PlannerInfo *root, * lower plan level and will only be copied by the Group node. Worth * fixing? * - * See notes in grouping_planner about why this routine and make_agg are - * the only ones in this file that worry about tlist eval cost. + * See notes in add_tlist_costs_to_plan about why only make_agg, + * make_windowagg and make_group worry about tlist eval cost. */ if (qual) { - cost_qual_eval(&qual_cost, qual); + cost_qual_eval(&qual_cost, qual, root); plan->startup_cost += qual_cost.startup; plan->total_cost += qual_cost.startup; plan->total_cost += qual_cost.per_tuple * plan->plan_rows; } - cost_qual_eval(&qual_cost, tlist); - plan->startup_cost += qual_cost.startup; - plan->total_cost += qual_cost.startup; - plan->total_cost += qual_cost.per_tuple * plan->plan_rows; + add_tlist_costs_to_plan(root, plan, tlist); plan->qual = qual; plan->targetlist = tlist; @@ -2804,8 +4414,9 @@ make_group(PlannerInfo *root, } /* - * distinctList is a list of SortClauses, identifying the targetlist items - * that should be considered by the Unique filter. + * distinctList is a list of SortGroupClauses, identifying the targetlist items + * that should be considered by the Unique filter. The input path must + * already be sorted accordingly. */ Unique * make_unique(Plan *lefttree, List *distinctList) @@ -2815,6 +4426,7 @@ make_unique(Plan *lefttree, List *distinctList) int numCols = list_length(distinctList); int keyno = 0; AttrNumber *uniqColIdx; + Oid *uniqOperators; ListCell *slitem; copy_plan_costsize(plan, lefttree); @@ -2832,86 +4444,122 @@ make_unique(Plan *lefttree, List *distinctList) * has a better idea. */ - plan->targetlist = copyObject(lefttree->targetlist); + plan->targetlist = lefttree->targetlist; plan->qual = NIL; plan->lefttree = lefttree; plan->righttree = NULL; /* - * convert SortClause list into array of attr indexes, as wanted by exec + * convert SortGroupClause list into arrays of attr indexes and equality + * operators, as wanted by executor */ Assert(numCols > 0); uniqColIdx = (AttrNumber *) palloc(sizeof(AttrNumber) * numCols); + uniqOperators = (Oid *) palloc(sizeof(Oid) * numCols); foreach(slitem, distinctList) { - SortClause *sortcl = (SortClause *) lfirst(slitem); + SortGroupClause *sortcl = (SortGroupClause *) lfirst(slitem); TargetEntry *tle = get_sortgroupclause_tle(sortcl, plan->targetlist); - uniqColIdx[keyno++] = tle->resno; + uniqColIdx[keyno] = tle->resno; + uniqOperators[keyno] = sortcl->eqop; + Assert(OidIsValid(uniqOperators[keyno])); + keyno++; } node->numCols = numCols; node->uniqColIdx = uniqColIdx; + node->uniqOperators = uniqOperators; return node; } /* - * distinctList is a list of SortClauses, identifying the targetlist items - * that should be considered by the SetOp filter. + * distinctList is a list of SortGroupClauses, identifying the targetlist + * items that should be considered by the SetOp filter. The input path must + * already be sorted accordingly. */ - SetOp * -make_setop(SetOpCmd cmd, Plan *lefttree, - List *distinctList, AttrNumber flagColIdx) +make_setop(SetOpCmd cmd, SetOpStrategy strategy, Plan *lefttree, + List *distinctList, AttrNumber flagColIdx, int firstFlag, + long numGroups, double outputRows) { SetOp *node = makeNode(SetOp); Plan *plan = &node->plan; int numCols = list_length(distinctList); int keyno = 0; AttrNumber *dupColIdx; + Oid *dupOperators; ListCell *slitem; copy_plan_costsize(plan, lefttree); + plan->plan_rows = outputRows; /* * Charge one cpu_operator_cost per comparison per input tuple. We assume * all columns get compared at most of the tuples. */ - plan->total_cost += cpu_operator_cost * plan->plan_rows * numCols; - - /* - * We make the unsupported assumption that there will be 10% as many - * tuples out as in. Any way to do better? - */ - plan->plan_rows *= 0.1; - if (plan->plan_rows < 1) - plan->plan_rows = 1; + plan->total_cost += cpu_operator_cost * lefttree->plan_rows * numCols; - plan->targetlist = copyObject(lefttree->targetlist); + plan->targetlist = lefttree->targetlist; plan->qual = NIL; plan->lefttree = lefttree; plan->righttree = NULL; /* - * convert SortClause list into array of attr indexes, as wanted by exec + * 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); foreach(slitem, distinctList) { - SortClause *sortcl = (SortClause *) lfirst(slitem); + SortGroupClause *sortcl = (SortGroupClause *) lfirst(slitem); TargetEntry *tle = get_sortgroupclause_tle(sortcl, plan->targetlist); - dupColIdx[keyno++] = tle->resno; + dupColIdx[keyno] = tle->resno; + dupOperators[keyno] = sortcl->eqop; + Assert(OidIsValid(dupOperators[keyno])); + keyno++; } node->cmd = cmd; + node->strategy = strategy; node->numCols = numCols; node->dupColIdx = dupColIdx; + node->dupOperators = dupOperators; node->flagColIdx = flagColIdx; + node->firstFlag = firstFlag; + node->numGroups = numGroups; + + return node; +} + +/* + * make_lockrows + * Build a LockRows plan node + */ +LockRows * +make_lockrows(Plan *lefttree, List *rowMarks, int epqParam) +{ + LockRows *node = makeNode(LockRows); + Plan *plan = &node->plan; + + copy_plan_costsize(plan, lefttree); + + /* charge cpu_tuple_cost to reflect locking costs (underestimate?) */ + plan->total_cost += cpu_tuple_cost * plan->plan_rows; + + plan->targetlist = lefttree->targetlist; + plan->qual = NIL; + plan->lefttree = lefttree; + plan->righttree = NULL; + + node->rowMarks = rowMarks; + node->epqParam = epqParam; return node; } @@ -2979,7 +4627,7 @@ make_limit(Plan *lefttree, Node *limitOffset, Node *limitCount, plan->plan_rows = 1; } - plan->targetlist = copyObject(lefttree->targetlist); + plan->targetlist = lefttree->targetlist; plan->qual = NIL; plan->lefttree = lefttree; plan->righttree = NULL; @@ -3000,7 +4648,8 @@ make_limit(Plan *lefttree, Node *limitOffset, Node *limitCount, * cost. In either case, tlist eval cost is not to be included here. */ Result * -make_result(List *tlist, +make_result(PlannerInfo *root, + List *tlist, Node *resconstantqual, Plan *subplan) { @@ -3019,7 +4668,7 @@ make_result(List *tlist, { QualCost qual_cost; - cost_qual_eval(&qual_cost, (List *) resconstantqual); + cost_qual_eval(&qual_cost, (List *) resconstantqual, root); /* resconstantqual is evaluated once at startup */ plan->startup_cost += qual_cost.startup + qual_cost.per_tuple; plan->total_cost += qual_cost.startup + qual_cost.per_tuple; @@ -3035,6 +4684,71 @@ make_result(List *tlist, return node; } +/* + * make_modifytable + * Build a ModifyTable plan node + * + * Currently, we don't charge anything extra for the actual table modification + * work, nor for the RETURNING expressions if any. It would only be window + * dressing, since these are always top-level nodes and there is no way for + * the costs to change any higher-level planning choices. But we might want + * to make it look better sometime. + */ +ModifyTable * +make_modifytable(CmdType operation, bool canSetTag, + List *resultRelations, + List *subplans, List *returningLists, + List *rowMarks, int epqParam) +{ + ModifyTable *node = makeNode(ModifyTable); + Plan *plan = &node->plan; + double total_size; + ListCell *subnode; + + Assert(list_length(resultRelations) == list_length(subplans)); + Assert(returningLists == NIL || + list_length(resultRelations) == list_length(returningLists)); + + /* + * Compute cost as sum of subplan costs. + */ + plan->startup_cost = 0; + plan->total_cost = 0; + plan->plan_rows = 0; + total_size = 0; + foreach(subnode, subplans) + { + Plan *subplan = (Plan *) lfirst(subnode); + + if (subnode == list_head(subplans)) /* first node? */ + plan->startup_cost = subplan->startup_cost; + plan->total_cost += subplan->total_cost; + plan->plan_rows += subplan->plan_rows; + total_size += subplan->plan_width * subplan->plan_rows; + } + if (plan->plan_rows > 0) + plan->plan_width = rint(total_size / plan->plan_rows); + else + plan->plan_width = 0; + + node->plan.lefttree = NULL; + node->plan.righttree = NULL; + node->plan.qual = NIL; + /* setrefs.c will fill in the targetlist, if needed */ + node->plan.targetlist = NIL; + + node->operation = operation; + node->canSetTag = canSetTag; + node->resultRelations = resultRelations; + node->resultRelIndex = -1; /* will be set correctly in setrefs.c */ + node->plans = subplans; + node->returningLists = returningLists; + node->rowMarks = rowMarks; + node->epqParam = epqParam; + + return node; +} + /* * is_projection_capable_plan * Check whether a given Plan node is able to do projection. @@ -3050,8 +4764,12 @@ is_projection_capable_plan(Plan *plan) case T_Sort: case T_Unique: case T_SetOp: + case T_LockRows: case T_Limit: + case T_ModifyTable: case T_Append: + case T_MergeAppend: + case T_RecursiveUnion: return false; default: break;