X-Git-Url: https://granicus.if.org/sourcecode?a=blobdiff_plain;f=src%2Fbackend%2Foptimizer%2Fplan%2Fplanner.c;h=385e64647ea0dea00c15889064421a01ad2dab79;hb=46c508fbcf98ac334f1e831d21021d731c882fbb;hp=07301c77fbf4f2550367ea8ff58c685462835223;hpb=034967bdcbb0c7be61d0500955226e1234ec5f04;p=postgresql diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 07301c77fb..385e64647e 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -3,7 +3,7 @@ * planner.c * The query optimizer external interface. * - * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group + * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * @@ -17,11 +17,14 @@ #include -#include "catalog/pg_operator.h" +#include "access/htup_details.h" #include "executor/executor.h" #include "executor/nodeAgg.h" #include "miscadmin.h" #include "nodes/makefuncs.h" +#ifdef OPTIMIZER_DEBUG +#include "nodes/print.h" +#endif #include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" @@ -32,16 +35,10 @@ #include "optimizer/prep.h" #include "optimizer/subselect.h" #include "optimizer/tlist.h" -#include "optimizer/var.h" -#ifdef OPTIMIZER_DEBUG -#include "nodes/print.h" -#endif #include "parser/analyze.h" -#include "parser/parse_expr.h" -#include "parser/parse_oper.h" #include "parser/parsetree.h" -#include "utils/lsyscache.h" -#include "utils/syscache.h" +#include "rewrite/rewriteManip.h" +#include "utils/rel.h" /* GUC parameter */ @@ -52,19 +49,21 @@ planner_hook_type planner_hook = NULL; /* Expression kind codes for preprocess_expression */ -#define EXPRKIND_QUAL 0 -#define EXPRKIND_TARGET 1 -#define EXPRKIND_RTFUNC 2 -#define EXPRKIND_VALUES 3 -#define EXPRKIND_LIMIT 4 -#define EXPRKIND_APPINFO 5 +#define EXPRKIND_QUAL 0 +#define EXPRKIND_TARGET 1 +#define EXPRKIND_RTFUNC 2 +#define EXPRKIND_RTFUNC_LATERAL 3 +#define EXPRKIND_VALUES 4 +#define EXPRKIND_VALUES_LATERAL 5 +#define EXPRKIND_LIMIT 6 +#define EXPRKIND_APPINFO 7 +#define EXPRKIND_PHV 8 static Node *preprocess_expression(PlannerInfo *root, Node *expr, int kind); static void preprocess_qual_conditions(PlannerInfo *root, Node *jtnode); static Plan *inheritance_planner(PlannerInfo *root); static Plan *grouping_planner(PlannerInfo *root, double tuple_fraction); -static bool is_dummy_plan(Plan *plan); static void preprocess_rowmarks(PlannerInfo *root); static double preprocess_limit(PlannerInfo *root, double tuple_fraction, @@ -74,7 +73,7 @@ static bool choose_hashed_grouping(PlannerInfo *root, double tuple_fraction, double limit_tuples, double path_rows, int path_width, Path *cheapest_path, Path *sorted_path, - double dNumGroups, AggClauseCounts *agg_counts); + double dNumGroups, AggClauseCosts *agg_costs); static bool choose_hashed_distinct(PlannerInfo *root, double tuple_fraction, double limit_tuples, double path_rows, int path_width, @@ -84,6 +83,7 @@ static bool choose_hashed_distinct(PlannerInfo *root, double dNumDistinctRows); static List *make_subplanTargetList(PlannerInfo *root, List *tlist, AttrNumber **groupColIdx, bool *need_tlist_eval); +static int get_grouping_column_index(Query *parse, TargetEntry *tle); static void locate_grouping_columns(PlannerInfo *root, List *tlist, List *sub_tlist, @@ -139,8 +139,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams) PlannerInfo *root; Plan *top_plan; ListCell *lp, - *lrt, - *lrm; + *lr; /* Cursor options may come from caller or from DECLARE CURSOR stmt */ if (parse->utilityStmt && @@ -156,16 +155,17 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams) glob = makeNode(PlannerGlobal); glob->boundParams = boundParams; - glob->paramlist = NIL; glob->subplans = NIL; - glob->subrtables = NIL; - glob->subrowmarks = NIL; + glob->subroots = NIL; glob->rewindPlanIDs = NULL; glob->finalrtable = NIL; glob->finalrowmarks = NIL; + glob->resultRelations = NIL; glob->relationOids = NIL; glob->invalItems = NIL; + glob->nParamExec = 0; glob->lastPHId = 0; + glob->lastRowMarkId = 0; glob->transientPlan = false; /* Determine what fraction of the plan is likely to be scanned */ @@ -213,44 +213,37 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams) /* final cleanup of the plan */ Assert(glob->finalrtable == NIL); Assert(glob->finalrowmarks == NIL); - top_plan = set_plan_references(glob, top_plan, - root->parse->rtable, - root->rowMarks); + Assert(glob->resultRelations == NIL); + top_plan = set_plan_references(root, top_plan); /* ... and the subplans (both regular subplans and initplans) */ - Assert(list_length(glob->subplans) == list_length(glob->subrtables)); - Assert(list_length(glob->subplans) == list_length(glob->subrowmarks)); - lrt = list_head(glob->subrtables); - lrm = list_head(glob->subrowmarks); - foreach(lp, glob->subplans) + Assert(list_length(glob->subplans) == list_length(glob->subroots)); + forboth(lp, glob->subplans, lr, glob->subroots) { Plan *subplan = (Plan *) lfirst(lp); - List *subrtable = (List *) lfirst(lrt); - List *subrowmark = (List *) lfirst(lrm); + PlannerInfo *subroot = (PlannerInfo *) lfirst(lr); - lfirst(lp) = set_plan_references(glob, subplan, - subrtable, subrowmark); - lrt = lnext(lrt); - lrm = lnext(lrm); + lfirst(lp) = set_plan_references(subroot, subplan); } /* build the PlannedStmt result */ result = makeNode(PlannedStmt); result->commandType = parse->commandType; + result->queryId = parse->queryId; result->hasReturning = (parse->returningList != NIL); + result->hasModifyingCTE = parse->hasModifyingCTE; result->canSetTag = parse->canSetTag; result->transientPlan = glob->transientPlan; result->planTree = top_plan; result->rtable = glob->finalrtable; - result->resultRelations = root->resultRelations; + result->resultRelations = glob->resultRelations; result->utilityStmt = parse->utilityStmt; - result->intoClause = parse->intoClause; result->subplans = glob->subplans; result->rewindPlanIDs = glob->rewindPlanIDs; result->rowMarks = glob->finalrowmarks; result->relationOids = glob->relationOids; result->invalItems = glob->invalItems; - result->nParamExec = list_length(glob->paramlist); + result->nParamExec = glob->nParamExec; return result; } @@ -302,6 +295,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse, root->glob = glob; root->query_level = parent_root ? parent_root->query_level + 1 : 1; root->parent_root = parent_root; + root->plan_params = NIL; root->planner_cxt = CurrentMemoryContext; root->init_plans = NIL; root->cte_plan_ids = NIL; @@ -341,19 +335,30 @@ subquery_planner(PlannerGlobal *glob, Query *parse, inline_set_returning_functions(root); /* - * Check to see if any subqueries in the rangetable can be merged into - * this query. + * Check to see if any subqueries in the jointree can be merged into this + * query. */ parse->jointree = (FromExpr *) - pull_up_subqueries(root, (Node *) parse->jointree, NULL, NULL); + pull_up_subqueries(root, (Node *) parse->jointree); + + /* + * If this is a simple UNION ALL query, flatten it into an appendrel. We + * do this now because it requires applying pull_up_subqueries to the leaf + * queries of the UNION ALL, which weren't touched above because they + * weren't referenced by the jointree (they will be after we do this). + */ + if (parse->setOperations) + flatten_simple_union_all(root); /* * Detect whether any rangetable entries are RTE_JOIN kind; if not, we can * avoid the expense of doing flatten_join_alias_vars(). Also check for - * outer joins --- if none, we can skip reduce_outer_joins(). This must be - * done after we have done pull_up_subqueries, of course. + * outer joins --- if none, we can skip reduce_outer_joins(). And check + * for LATERAL RTEs, too. This must be done after we have done + * pull_up_subqueries(), of course. */ root->hasJoinRTEs = false; + root->hasLateralRTEs = false; hasOuterJoins = false; foreach(l, parse->rtable) { @@ -363,12 +368,10 @@ subquery_planner(PlannerGlobal *glob, Query *parse, { root->hasJoinRTEs = true; if (IS_OUTER_JOIN(rte->jointype)) - { hasOuterJoins = true; - /* Can quit scanning once we find an outer join */ - break; - } } + if (rte->lateral) + root->hasLateralRTEs = true; } /* @@ -438,18 +441,38 @@ subquery_planner(PlannerGlobal *glob, Query *parse, preprocess_expression(root, (Node *) root->append_rel_list, EXPRKIND_APPINFO); - /* Also need to preprocess expressions for function and values RTEs */ + /* Also need to preprocess expressions within RTEs */ foreach(l, parse->rtable) { RangeTblEntry *rte = (RangeTblEntry *) lfirst(l); + int kind; - if (rte->rtekind == RTE_FUNCTION) - rte->funcexpr = preprocess_expression(root, rte->funcexpr, - EXPRKIND_RTFUNC); + if (rte->rtekind == RTE_SUBQUERY) + { + /* + * We don't want to do all preprocessing yet on the subquery's + * expressions, since that will happen when we plan it. But if it + * contains any join aliases of our level, those have to get + * expanded now, because planning of the subquery won't do it. + * That's only possible if the subquery is LATERAL. + */ + if (rte->lateral && root->hasJoinRTEs) + rte->subquery = (Query *) + flatten_join_alias_vars(root, (Node *) rte->subquery); + } + else if (rte->rtekind == RTE_FUNCTION) + { + /* Preprocess the function expression fully */ + kind = rte->lateral ? EXPRKIND_RTFUNC_LATERAL : EXPRKIND_RTFUNC; + rte->funcexpr = preprocess_expression(root, rte->funcexpr, kind); + } else if (rte->rtekind == RTE_VALUES) + { + /* Preprocess the values lists fully */ + kind = rte->lateral ? EXPRKIND_VALUES_LATERAL : EXPRKIND_VALUES; rte->values_lists = (List *) - preprocess_expression(root, (Node *) rte->values_lists, - EXPRKIND_VALUES); + preprocess_expression(root, (Node *) rte->values_lists, kind); + } } /* @@ -531,22 +554,10 @@ subquery_planner(PlannerGlobal *glob, Query *parse, List *rowMarks; /* - * Deal with the RETURNING clause if any. It's convenient to pass - * the returningList through setrefs.c now rather than at top - * level (if we waited, handling inherited UPDATE/DELETE would be - * much harder). + * Set up the RETURNING list-of-lists, if needed. */ if (parse->returningList) - { - List *rlist; - - Assert(parse->resultRelation); - rlist = set_returning_clause_references(root->glob, - parse->returningList, - plan, - parse->resultRelation); - returningLists = list_make1(rlist); - } + returningLists = list_make1(parse->returningList); else returningLists = NIL; @@ -561,7 +572,8 @@ subquery_planner(PlannerGlobal *glob, Query *parse, rowMarks = root->rowMarks; plan = (Plan *) make_modifytable(parse->commandType, - copyObject(root->resultRelations), + parse->canSetTag, + list_make1_int(parse->resultRelation), list_make1(plan), returningLists, rowMarks, @@ -575,7 +587,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse, * and attach the initPlans to the top plan node. */ if (list_length(glob->subplans) != num_old_subplans || - root->glob->paramlist != NIL) + root->glob->nParamExec > 0) SS_finalize_plan(root, plan, true); /* Return internal info if caller wants it */ @@ -589,7 +601,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse, * preprocess_expression * Do subquery_planner's preprocessing work for an expression, * which can be a targetlist, a WHERE clause (including JOIN/ON - * conditions), or a HAVING clause. + * conditions), a HAVING clause, or a few other things. */ static Node * preprocess_expression(PlannerInfo *root, Node *expr, int kind) @@ -604,12 +616,13 @@ preprocess_expression(PlannerInfo *root, Node *expr, int kind) /* * If the query has any join RTEs, replace join alias variables with - * base-relation variables. We must do this before sublink processing, - * else sublinks expanded out from join aliases wouldn't get processed. We - * can skip it in VALUES lists, however, since they can't contain any Vars - * at all. + * base-relation variables. We must do this before sublink processing, + * else sublinks expanded out from join aliases would not get processed. + * We can skip it in non-lateral RTE functions and VALUES lists, however, + * since they can't contain any Vars of the current query level. */ - if (root->hasJoinRTEs && kind != EXPRKIND_VALUES) + if (root->hasJoinRTEs && + !(kind == EXPRKIND_RTFUNC || kind == EXPRKIND_VALUES)) expr = flatten_join_alias_vars(root, expr); /* @@ -705,6 +718,23 @@ preprocess_qual_conditions(PlannerInfo *root, Node *jtnode) (int) nodeTag(jtnode)); } +/* + * preprocess_phv_expression + * Do preprocessing on a PlaceHolderVar expression that's been pulled up. + * + * If a LATERAL subquery references an output of another subquery, and that + * output must be wrapped in a PlaceHolderVar because of an intermediate outer + * join, then we'll push the PlaceHolderVar expression down into the subquery + * and later pull it back up during find_lateral_references, which runs after + * subquery_planner has preprocessed all the expressions that were in the + * current query level to start with. So we need to preprocess it then. + */ +Expr * +preprocess_phv_expression(PlannerInfo *root, Expr *expr) +{ + return (Expr *) preprocess_expression(root, (Node *) expr, EXPRKIND_PHV); +} + /* * inheritance_planner * Generate a plan in the case where the result relation is an @@ -725,76 +755,177 @@ inheritance_planner(PlannerInfo *root) { Query *parse = root->parse; int parentRTindex = parse->resultRelation; + List *final_rtable = NIL; + int save_rel_array_size = 0; + RelOptInfo **save_rel_array = NULL; List *subplans = NIL; List *resultRelations = NIL; List *returningLists = NIL; - List *rtable = NIL; List *rowMarks; - List *tlist; - PlannerInfo subroot; - ListCell *l; + ListCell *lc; - foreach(l, root->append_rel_list) + /* + * We generate a modified instance of the original Query for each target + * relation, plan that, and put all the plans into a list that will be + * controlled by a single ModifyTable node. All the instances share the + * same rangetable, but each instance must have its own set of subquery + * RTEs within the finished rangetable because (1) they are likely to get + * scribbled on during planning, and (2) it's not inconceivable that + * subqueries could get planned differently in different cases. We need + * not create duplicate copies of other RTE kinds, in particular not the + * target relations, because they don't have either of those issues. Not + * having to duplicate the target relations is important because doing so + * (1) would result in a rangetable of length O(N^2) for N targets, with + * at least O(N^3) work expended here; and (2) would greatly complicate + * management of the rowMarks list. + */ + foreach(lc, root->append_rel_list) { - AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); + AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(lc); + PlannerInfo subroot; Plan *subplan; + Index rti; /* append_rel_list contains all append rels; ignore others */ if (appinfo->parent_relid != parentRTindex) continue; /* - * Generate modified query with this rel as target. + * We need a working copy of the PlannerInfo so that we can control + * propagation of information back to the main copy. */ memcpy(&subroot, root, sizeof(PlannerInfo)); + + /* + * Generate modified query with this rel as target. We first apply + * adjust_appendrel_attrs, which copies the Query and changes + * references to the parent RTE to refer to the current child RTE, + * then fool around with subquery RTEs. + */ subroot.parse = (Query *) - adjust_appendrel_attrs((Node *) parse, + adjust_appendrel_attrs(root, + (Node *) parse, appinfo); - subroot.init_plans = NIL; - subroot.hasInheritedTarget = true; + + /* + * The rowMarks list might contain references to subquery RTEs, so + * make a copy that we can apply ChangeVarNodes to. (Fortunately, the + * executor doesn't need to see the modified copies --- we can just + * pass it the original rowMarks list.) + */ + subroot.rowMarks = (List *) copyObject(root->rowMarks); + + /* + * Add placeholders to the child Query's rangetable list to fill the + * RT indexes already reserved for subqueries in previous children. + * These won't be referenced, so there's no need to make them very + * valid-looking. + */ + while (list_length(subroot.parse->rtable) < list_length(final_rtable)) + subroot.parse->rtable = lappend(subroot.parse->rtable, + makeNode(RangeTblEntry)); + + /* + * If this isn't the first child Query, generate duplicates of all + * subquery RTEs, and adjust Var numbering to reference the + * duplicates. To simplify the loop logic, we scan the original rtable + * not the copy just made by adjust_appendrel_attrs; that should be OK + * since subquery RTEs couldn't contain any references to the target + * rel. + */ + if (final_rtable != NIL) + { + ListCell *lr; + + rti = 1; + foreach(lr, parse->rtable) + { + RangeTblEntry *rte = (RangeTblEntry *) lfirst(lr); + + if (rte->rtekind == RTE_SUBQUERY) + { + Index newrti; + + /* + * The RTE can't contain any references to its own RT + * index, so we can save a few cycles by applying + * ChangeVarNodes before we append the RTE to the + * rangetable. + */ + newrti = list_length(subroot.parse->rtable) + 1; + ChangeVarNodes((Node *) subroot.parse, rti, newrti, 0); + ChangeVarNodes((Node *) subroot.rowMarks, rti, newrti, 0); + rte = copyObject(rte); + subroot.parse->rtable = lappend(subroot.parse->rtable, + rte); + } + rti++; + } + } + /* We needn't modify the child's append_rel_list */ - /* There shouldn't be any OJ info to translate, as yet */ + /* There shouldn't be any OJ or LATERAL info to translate, as yet */ Assert(subroot.join_info_list == NIL); + Assert(subroot.lateral_info_list == NIL); /* and we haven't created PlaceHolderInfos, either */ Assert(subroot.placeholder_list == NIL); + /* hack to mark target relation as an inheritance partition */ + subroot.hasInheritedTarget = true; /* Generate plan */ subplan = grouping_planner(&subroot, 0.0 /* retrieve all tuples */ ); /* * If this child rel was excluded by constraint exclusion, exclude it - * from the plan. + * from the result plan. */ if (is_dummy_plan(subplan)) continue; - /* Save rtable from first rel for use below */ - if (subplans == NIL) - rtable = subroot.parse->rtable; - subplans = lappend(subplans, subplan); + /* + * If this is the first non-excluded child, its post-planning rtable + * becomes the initial contents of final_rtable; otherwise, append + * just its modified subquery RTEs to final_rtable. + */ + if (final_rtable == NIL) + final_rtable = subroot.parse->rtable; + else + final_rtable = list_concat(final_rtable, + list_copy_tail(subroot.parse->rtable, + list_length(final_rtable))); + + /* + * We need to collect all the RelOptInfos from all child plans into + * the main PlannerInfo, since setrefs.c will need them. We use the + * last child's simple_rel_array (previous ones are too short), so we + * have to propagate forward the RelOptInfos that were already built + * in previous children. + */ + Assert(subroot.simple_rel_array_size >= save_rel_array_size); + for (rti = 1; rti < save_rel_array_size; rti++) + { + RelOptInfo *brel = save_rel_array[rti]; + + if (brel) + subroot.simple_rel_array[rti] = brel; + } + save_rel_array_size = subroot.simple_rel_array_size; + save_rel_array = subroot.simple_rel_array; + /* Make sure any initplans from this rel get into the outer list */ - root->init_plans = list_concat(root->init_plans, subroot.init_plans); + root->init_plans = subroot.init_plans; - /* Build target-relations list for the executor */ + /* Build list of target-relation RT indexes */ resultRelations = lappend_int(resultRelations, appinfo->child_relid); /* Build list of per-relation RETURNING targetlists */ if (parse->returningList) - { - List *rlist; - - rlist = set_returning_clause_references(root->glob, - subroot.parse->returningList, - subplan, - appinfo->child_relid); - returningLists = lappend(returningLists, rlist); - } + returningLists = lappend(returningLists, + subroot.parse->returningList); } - root->resultRelations = resultRelations; - /* Mark result as unordered (probably unnecessary) */ root->query_pathkeys = NIL; @@ -804,8 +935,9 @@ inheritance_planner(PlannerInfo *root) */ if (subplans == NIL) { - root->resultRelations = list_make1_int(parentRTindex); /* although dummy, it must have a valid tlist for executor */ + List *tlist; + tlist = preprocess_targetlist(root, parse->targetList); return (Plan *) make_result(root, tlist, @@ -815,17 +947,11 @@ inheritance_planner(PlannerInfo *root) } /* - * Planning might have modified the rangetable, due to changes of the - * Query structures inside subquery RTEs. We have to ensure that this - * gets propagated back to the master copy. But can't do this until we - * are done planning, because all the calls to grouping_planner need - * virgin sub-Queries to work from. (We are effectively assuming that - * sub-Queries will get planned identically each time, or at least that - * the impacts on their rangetables will be the same each time.) - * - * XXX should clean this up someday + * Put back the final adjusted rtable into the master copy of the Query. */ - parse->rtable = rtable; + parse->rtable = final_rtable; + root->simple_rel_array_size = save_rel_array_size; + root->simple_rel_array = save_rel_array; /* * If there was a FOR UPDATE/SHARE clause, the LockRows node will have @@ -839,7 +965,8 @@ inheritance_planner(PlannerInfo *root) /* And last, tack on a ModifyTable node to do the UPDATE/DELETE work */ return (Plan *) make_modifytable(parse->commandType, - copyObject(root->resultRelations), + parse->canSetTag, + resultRelations, subplans, returningLists, rowMarks, @@ -959,14 +1086,14 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) { /* No set operations, do regular planning */ List *sub_tlist; + double sub_limit_tuples; AttrNumber *groupColIdx = NULL; bool need_tlist_eval = true; - QualCost tlist_cost; Path *cheapest_path; Path *sorted_path; Path *best_path; long numGroups = 0; - AggClauseCounts agg_counts; + AggClauseCosts agg_costs; int numGroupCols; double path_rows; int path_width; @@ -974,7 +1101,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) WindowFuncLists *wflists = NULL; List *activeWindows = NIL; - MemSet(&agg_counts, 0, sizeof(AggClauseCounts)); + MemSet(&agg_costs, 0, sizeof(AggClauseCosts)); /* A recursive query should always have setOperations */ Assert(!root->hasRecursion); @@ -1021,15 +1148,18 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) if (parse->hasAggs) { /* - * Will need actual number of aggregates for estimating costs. - * Note: we do not attempt to detect duplicate aggregates here; a - * somewhat-overestimated count is okay for our present purposes. + * Collect statistics about aggregates for estimating costs. Note: + * we do not attempt to detect duplicate aggregates here; a + * somewhat-overestimated cost is okay for our present purposes. */ - count_agg_clauses((Node *) tlist, &agg_counts); - count_agg_clauses(parse->havingQual, &agg_counts); + count_agg_clauses(root, (Node *) tlist, &agg_costs); + count_agg_clauses(root, parse->havingQual, &agg_costs); /* - * Preprocess MIN/MAX aggregates, if any. + * Preprocess MIN/MAX aggregates, if any. Note: be careful about + * adding logic between here and the optimize_minmax_aggregates + * call. Anything that is needed in MIN/MAX-optimizable cases + * will have to be duplicated in planagg.c. */ preprocess_minmax_aggregates(root, tlist); } @@ -1110,13 +1240,28 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) else root->query_pathkeys = NIL; + /* + * Figure out whether there's a hard limit on the number of rows that + * query_planner's result subplan needs to return. Even if we know a + * hard limit overall, it doesn't apply if the query has any + * grouping/aggregation operations. + */ + if (parse->groupClause || + parse->distinctClause || + parse->hasAggs || + parse->hasWindowFuncs || + root->hasHavingQual) + sub_limit_tuples = -1.0; + else + sub_limit_tuples = limit_tuples; + /* * Generate the best unsorted and presorted paths for this Query (but * note there may not be any presorted path). query_planner will also * estimate the number of groups in the query, and canonicalize all * the pathkeys. */ - query_planner(root, sub_tlist, tuple_fraction, limit_tuples, + query_planner(root, sub_tlist, tuple_fraction, sub_limit_tuples, &cheapest_path, &sorted_path, &dNumGroups); /* @@ -1145,7 +1290,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) tuple_fraction, limit_tuples, path_rows, path_width, cheapest_path, sorted_path, - dNumGroups, &agg_counts); + dNumGroups, &agg_costs); /* Also convert # groups to long int --- but 'ware overflow! */ numGroups = (long) Min(dNumGroups, (double) LONG_MAX); } @@ -1188,6 +1333,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) */ result_plan = optimize_minmax_aggregates(root, tlist, + &agg_costs, best_path); if (result_plan != NULL) { @@ -1215,18 +1361,17 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) need_sort_for_grouping = true; /* - * Always override query_planner's tlist, so that we don't - * sort useless data from a "physical" tlist. + * Always override create_plan's tlist, so that we don't sort + * useless data from a "physical" tlist. */ need_tlist_eval = true; } /* - * create_plan() returns a plan with just a "flat" tlist of - * required Vars. Usually we need to insert the sub_tlist as the - * tlist of the top plan node. However, we can skip that if we - * determined that whatever query_planner chose to return will be - * good enough. + * create_plan returns a plan with just a "flat" tlist of required + * Vars. Usually we need to insert the sub_tlist as the tlist of + * the top plan node. However, we can skip that if we determined + * that whatever create_plan chose to return will be good enough. */ if (need_tlist_eval) { @@ -1253,32 +1398,14 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) /* * Also, account for the cost of evaluation of the sub_tlist. - * - * Up to now, we have only been dealing with "flat" tlists, - * containing just Vars. So their evaluation cost is zero - * according to the model used by cost_qual_eval() (or if you - * prefer, the cost is factored into cpu_tuple_cost). Thus we - * can avoid accounting for tlist cost throughout - * query_planner() and subroutines. But now we've inserted a - * tlist that might contain actual operators, sub-selects, etc - * --- so we'd better account for its cost. - * - * Below this point, any tlist eval cost for added-on nodes - * should be accounted for as we create those nodes. - * Presently, of the node types we can add on, only Agg, - * WindowAgg, and Group project new tlists (the rest just copy - * their input tuples) --- so make_agg(), make_windowagg() and - * make_group() are responsible for computing the added cost. + * See comments for add_tlist_costs_to_plan() for more info. */ - cost_qual_eval(&tlist_cost, sub_tlist, root); - result_plan->startup_cost += tlist_cost.startup; - result_plan->total_cost += tlist_cost.startup + - tlist_cost.per_tuple * result_plan->plan_rows; + add_tlist_costs_to_plan(root, result_plan, sub_tlist); } else { /* - * Since we're using query_planner's tlist and not the one + * Since we're using create_plan's tlist and not the one * make_subplanTargetList calculated, we have to refigure any * grouping-column indexes make_subplanTargetList computed. */ @@ -1299,11 +1426,11 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) tlist, (List *) parse->havingQual, AGG_HASHED, + &agg_costs, numGroupCols, groupColIdx, extract_grouping_ops(parse->groupClause), numGroups, - agg_counts.numAggs, result_plan); /* Hashed aggregation produces randomly-ordered results */ current_pathkeys = NIL; @@ -1342,11 +1469,11 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) tlist, (List *) parse->havingQual, aggstrategy, + &agg_costs, numGroupCols, groupColIdx, extract_grouping_ops(parse->groupClause), numGroups, - agg_counts.numAggs, result_plan); } else if (parse->groupClause) @@ -1441,11 +1568,16 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) * step. That's handled internally by make_sort_from_pathkeys, * but we need the copyObject steps here to ensure that each plan * node has a separately modifiable tlist. + * + * Note: it's essential here to use PVC_INCLUDE_AGGREGATES so that + * Vars mentioned only in aggregate expressions aren't pulled out + * as separate targetlist entries. Otherwise we could be putting + * ungrouped Vars directly into an Agg node's tlist, resulting in + * undefined behavior. */ - window_tlist = flatten_tlist(tlist); - if (parse->hasAggs) - window_tlist = add_to_flat_tlist(window_tlist, - pull_agg_clause((Node *) tlist)); + window_tlist = flatten_tlist(tlist, + PVC_INCLUDE_AGGREGATES, + PVC_INCLUDE_PLACEHOLDERS); window_tlist = add_volatile_sort_exprs(window_tlist, tlist, activeWindows); result_plan->targetlist = (List *) copyObject(window_tlist); @@ -1528,7 +1660,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) result_plan = (Plan *) make_windowagg(root, (List *) copyObject(window_tlist), - list_length(wflists->windowFuncs[wc->winref]), + wflists->windowFuncs[wc->winref], wc->winref, partNumCols, partColIdx, @@ -1594,12 +1726,12 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) result_plan->targetlist, NIL, AGG_HASHED, + NULL, list_length(parse->distinctClause), extract_grouping_cols(parse->distinctClause, result_plan->targetlist), extract_grouping_ops(parse->distinctClause), numDistinctRows, - 0, result_plan); /* Hashed aggregation produces randomly-ordered results */ current_pathkeys = NIL; @@ -1699,12 +1831,6 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) count_est); } - /* Compute result-relations list if needed */ - if (parse->resultRelation) - root->resultRelations = list_make1_int(parse->resultRelation); - else - root->resultRelations = NIL; - /* * Return the actual output ordering in query_pathkeys for possible use by * an outer query level. @@ -1714,14 +1840,71 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) return result_plan; } +/* + * add_tlist_costs_to_plan + * + * Estimate the execution costs associated with evaluating the targetlist + * expressions, and add them to the cost estimates for the Plan node. + * + * If the tlist contains set-returning functions, also inflate the Plan's cost + * and plan_rows estimates accordingly. (Hence, this must be called *after* + * any logic that uses plan_rows to, eg, estimate qual evaluation costs.) + * + * Note: during initial stages of planning, we mostly consider plan nodes with + * "flat" tlists, containing just Vars. So their evaluation cost is zero + * according to the model used by cost_qual_eval() (or if you prefer, the cost + * is factored into cpu_tuple_cost). Thus we can avoid accounting for tlist + * cost throughout query_planner() and subroutines. But once we apply a + * tlist that might contain actual operators, sub-selects, etc, we'd better + * account for its cost. Any set-returning functions in the tlist must also + * affect the estimated rowcount. + * + * Once grouping_planner() has applied a general tlist to the topmost + * scan/join plan node, any tlist eval cost for added-on nodes should be + * accounted for as we create those nodes. Presently, of the node types we + * can add on later, only Agg, WindowAgg, and Group project new tlists (the + * rest just copy their input tuples) --- so make_agg(), make_windowagg() and + * make_group() are responsible for calling this function to account for their + * tlist costs. + */ +void +add_tlist_costs_to_plan(PlannerInfo *root, Plan *plan, List *tlist) +{ + QualCost tlist_cost; + double tlist_rows; + + cost_qual_eval(&tlist_cost, tlist, root); + plan->startup_cost += tlist_cost.startup; + plan->total_cost += tlist_cost.startup + + tlist_cost.per_tuple * plan->plan_rows; + + tlist_rows = tlist_returns_set_rows(tlist); + if (tlist_rows > 1) + { + /* + * We assume that execution costs of the tlist proper were all + * accounted for by cost_qual_eval. However, it still seems + * appropriate to charge something more for the executor's general + * costs of processing the added tuples. The cost is probably less + * than cpu_tuple_cost, though, so we arbitrarily use half of that. + */ + plan->total_cost += plan->plan_rows * (tlist_rows - 1) * + cpu_tuple_cost / 2; + + plan->plan_rows *= tlist_rows; + } +} + /* * Detect whether a plan node is a "dummy" plan created when a relation * is deemed not to need scanning due to constraint exclusion. * * Currently, such dummy plans are Result nodes with constant FALSE - * filter quals. + * filter quals (see set_dummy_rel_pathlist and create_append_plan). + * + * XXX this probably ought to be somewhere else, but not clear where. */ -static bool +bool is_dummy_plan(Plan *plan) { if (IsA(plan, Result)) @@ -1860,16 +2043,13 @@ preprocess_rowmarks(PlannerInfo *root) newrc = makeNode(PlanRowMark); newrc->rti = newrc->prti = rc->rti; + newrc->rowmarkId = ++(root->glob->lastRowMarkId); if (rc->forUpdate) newrc->markType = ROW_MARK_EXCLUSIVE; else newrc->markType = ROW_MARK_SHARE; newrc->noWait = rc->noWait; newrc->isParent = false; - /* attnos will be assigned in preprocess_targetlist */ - newrc->ctidAttNo = InvalidAttrNumber; - newrc->toidAttNo = InvalidAttrNumber; - newrc->wholeAttNo = InvalidAttrNumber; prowmarks = lappend(prowmarks, newrc); } @@ -1889,17 +2069,15 @@ preprocess_rowmarks(PlannerInfo *root) newrc = makeNode(PlanRowMark); newrc->rti = newrc->prti = i; + newrc->rowmarkId = ++(root->glob->lastRowMarkId); /* real tables support REFERENCE, anything else needs COPY */ - if (rte->rtekind == RTE_RELATION) + if (rte->rtekind == RTE_RELATION && + rte->relkind != RELKIND_FOREIGN_TABLE) newrc->markType = ROW_MARK_REFERENCE; else newrc->markType = ROW_MARK_COPY; newrc->noWait = false; /* doesn't matter */ newrc->isParent = false; - /* attnos will be assigned in preprocess_targetlist */ - newrc->ctidAttNo = InvalidAttrNumber; - newrc->toidAttNo = InvalidAttrNumber; - newrc->wholeAttNo = InvalidAttrNumber; prowmarks = lappend(prowmarks, newrc); } @@ -2193,7 +2371,7 @@ choose_hashed_grouping(PlannerInfo *root, double tuple_fraction, double limit_tuples, double path_rows, int path_width, Path *cheapest_path, Path *sorted_path, - double dNumGroups, AggClauseCounts *agg_counts) + double dNumGroups, AggClauseCosts *agg_costs) { Query *parse = root->parse; int numGroupCols = list_length(parse->groupClause); @@ -2211,7 +2389,7 @@ choose_hashed_grouping(PlannerInfo *root, * the hash table, and/or running many sorts in parallel, either of which * seems like a certain loser.) */ - can_hash = (agg_counts->numOrderedAggs == 0 && + can_hash = (agg_costs->numOrderedAggs == 0 && grouping_is_hashable(parse->groupClause)); can_sort = grouping_is_sortable(parse->groupClause); @@ -2241,9 +2419,9 @@ choose_hashed_grouping(PlannerInfo *root, /* Estimate per-hash-entry space at tuple width... */ hashentrysize = MAXALIGN(path_width) + MAXALIGN(sizeof(MinimalTupleData)); /* plus space for pass-by-ref transition values... */ - hashentrysize += agg_counts->transitionSpace; + hashentrysize += agg_costs->transitionSpace; /* plus the per-hash-entry overhead */ - hashentrysize += hash_agg_entry_size(agg_counts->numAggs); + hashentrysize += hash_agg_entry_size(agg_costs->numAggs); if (hashentrysize * dNumGroups > work_mem * 1024L) return false; @@ -2277,7 +2455,7 @@ choose_hashed_grouping(PlannerInfo *root, * These path variables are dummies that just hold cost fields; we don't * make actual Paths for these steps. */ - cost_agg(&hashed_p, root, AGG_HASHED, agg_counts->numAggs, + cost_agg(&hashed_p, root, AGG_HASHED, agg_costs, numGroupCols, dNumGroups, cheapest_path->startup_cost, cheapest_path->total_cost, path_rows); @@ -2308,7 +2486,7 @@ choose_hashed_grouping(PlannerInfo *root, } if (parse->hasAggs) - cost_agg(&sorted_p, root, AGG_SORTED, agg_counts->numAggs, + cost_agg(&sorted_p, root, AGG_SORTED, agg_costs, numGroupCols, dNumGroups, sorted_p.startup_cost, sorted_p.total_cost, path_rows); @@ -2427,7 +2605,7 @@ choose_hashed_distinct(PlannerInfo *root, * These path variables are dummies that just hold cost fields; we don't * make actual Paths for these steps. */ - cost_agg(&hashed_p, root, AGG_HASHED, 0, + cost_agg(&hashed_p, root, AGG_HASHED, NULL, numDistinctCols, dNumDistinctRows, cheapest_startup_cost, cheapest_total_cost, path_rows); @@ -2494,10 +2672,11 @@ choose_hashed_distinct(PlannerInfo *root, * make_subplanTargetList * Generate appropriate target list when grouping is required. * - * When grouping_planner inserts Aggregate, Group, or Result plan nodes - * above the result of query_planner, we typically want to pass a different - * target list to query_planner than the outer plan nodes should have. - * This routine generates the correct target list for the subplan. + * When grouping_planner inserts grouping or aggregation plan nodes + * above the scan/join plan constructed by query_planner+create_plan, + * we typically want the scan/join plan to emit a different target list + * than the outer plan nodes should have. This routine generates the + * correct target list for the scan/join subplan. * * The initial target list passed from the parser already contains entries * for all ORDER BY and GROUP BY expressions, but it will not have entries @@ -2508,27 +2687,25 @@ choose_hashed_distinct(PlannerInfo *root, * For example, given a query like * SELECT a+b,SUM(c+d) FROM table GROUP BY a+b; * we want to pass this targetlist to the subplan: - * a,b,c,d,a+b + * a+b,c,d * where the a+b target will be used by the Sort/Group steps, and the - * other targets will be used for computing the final results. (In the - * above example we could theoretically suppress the a and b targets and - * pass down only c,d,a+b, but it's not really worth the trouble to - * eliminate simple var references from the subplan. We will avoid doing - * the extra computation to recompute a+b at the outer level; see - * fix_upper_expr() in setrefs.c.) + * other targets will be used for computing the final results. * * If we are grouping or aggregating, *and* there are no non-Var grouping * expressions, then the returned tlist is effectively dummy; we do not * need to force it to be evaluated, because all the Vars it contains - * should be present in the output of query_planner anyway. + * should be present in the "flat" tlist generated by create_plan, though + * possibly in a different order. In that case we'll use create_plan's tlist, + * and the tlist made here is only needed as input to query_planner to tell + * it which Vars are needed in the output of the scan/join plan. * * 'tlist' is the query's target list. * 'groupColIdx' receives an array of column numbers for the GROUP BY - * expressions (if there are any) in the subplan's target list. + * expressions (if there are any) in the returned target list. * 'need_tlist_eval' is set true if we really need to evaluate the - * result tlist. + * returned tlist as-is. * - * The result is the targetlist to be passed to the subplan. + * The result is the targetlist to be passed to query_planner. */ static List * make_subplanTargetList(PlannerInfo *root, @@ -2538,7 +2715,8 @@ make_subplanTargetList(PlannerInfo *root, { Query *parse = root->parse; List *sub_tlist; - List *extravars; + List *non_group_cols; + List *non_group_vars; int numCols; *groupColIdx = NULL; @@ -2555,70 +2733,135 @@ make_subplanTargetList(PlannerInfo *root, } /* - * Otherwise, start with a "flattened" tlist (having just the vars - * mentioned in the targetlist and HAVING qual --- but not upper-level - * Vars; they will be replaced by Params later on). Note this includes - * vars used in resjunk items, so we are covering the needs of ORDER BY - * and window specifications. + * Otherwise, we must build a tlist containing all grouping columns, plus + * any other Vars mentioned in the targetlist and HAVING qual. */ - sub_tlist = flatten_tlist(tlist); - extravars = pull_var_clause(parse->havingQual, PVC_INCLUDE_PLACEHOLDERS); - sub_tlist = add_to_flat_tlist(sub_tlist, extravars); - list_free(extravars); + sub_tlist = NIL; + non_group_cols = NIL; *need_tlist_eval = false; /* only eval if not flat tlist */ - /* - * If grouping, create sub_tlist entries for all GROUP BY expressions - * (GROUP BY items that are simple Vars should be in the list already), - * and make an array showing where the group columns are in the sub_tlist. - */ numCols = list_length(parse->groupClause); if (numCols > 0) { - int keyno = 0; + /* + * If grouping, create sub_tlist entries for all GROUP BY columns, and + * make an array showing where the group columns are in the sub_tlist. + * + * Note: with this implementation, the array entries will always be + * 1..N, but we don't want callers to assume that. + */ AttrNumber *grpColIdx; - ListCell *gl; + ListCell *tl; - grpColIdx = (AttrNumber *) palloc(sizeof(AttrNumber) * numCols); + grpColIdx = (AttrNumber *) palloc0(sizeof(AttrNumber) * numCols); *groupColIdx = grpColIdx; - foreach(gl, parse->groupClause) + foreach(tl, tlist) { - SortGroupClause *grpcl = (SortGroupClause *) lfirst(gl); - Node *groupexpr = get_sortgroupclause_expr(grpcl, tlist); - TargetEntry *te; + TargetEntry *tle = (TargetEntry *) lfirst(tl); + int colno; - /* - * Find or make a matching sub_tlist entry. If the groupexpr - * isn't a Var, no point in searching. (Note that the parser - * won't make multiple groupClause entries for the same TLE.) - */ - if (groupexpr && IsA(groupexpr, Var)) - te = tlist_member(groupexpr, sub_tlist); - else - te = NULL; + colno = get_grouping_column_index(parse, tle); + if (colno >= 0) + { + /* + * It's a grouping column, so add it to the result tlist and + * remember its resno in grpColIdx[]. + */ + TargetEntry *newtle; + + newtle = makeTargetEntry(tle->expr, + list_length(sub_tlist) + 1, + NULL, + false); + sub_tlist = lappend(sub_tlist, newtle); + + Assert(grpColIdx[colno] == 0); /* no dups expected */ + grpColIdx[colno] = newtle->resno; - if (!te) + if (!(newtle->expr && IsA(newtle->expr, Var))) + *need_tlist_eval = true; /* tlist contains non Vars */ + } + else { - te = makeTargetEntry((Expr *) groupexpr, - list_length(sub_tlist) + 1, - NULL, - false); - sub_tlist = lappend(sub_tlist, te); - *need_tlist_eval = true; /* it's not flat anymore */ + /* + * Non-grouping column, so just remember the expression for + * later call to pull_var_clause. There's no need for + * pull_var_clause to examine the TargetEntry node itself. + */ + non_group_cols = lappend(non_group_cols, tle->expr); } - - /* and save its resno */ - grpColIdx[keyno++] = te->resno; } } + else + { + /* + * With no grouping columns, just pass whole tlist to pull_var_clause. + * Need (shallow) copy to avoid damaging input tlist below. + */ + non_group_cols = list_copy(tlist); + } + + /* + * If there's a HAVING clause, we'll need the Vars it uses, too. + */ + if (parse->havingQual) + non_group_cols = lappend(non_group_cols, parse->havingQual); + + /* + * Pull out all the Vars mentioned in non-group cols (plus HAVING), and + * add them to the result tlist if not already present. (A Var used + * directly as a GROUP BY item will be present already.) Note this + * includes Vars used in resjunk items, so we are covering the needs of + * ORDER BY and window specifications. Vars used within Aggrefs will be + * pulled out here, too. + */ + non_group_vars = pull_var_clause((Node *) non_group_cols, + PVC_RECURSE_AGGREGATES, + PVC_INCLUDE_PLACEHOLDERS); + sub_tlist = add_to_flat_tlist(sub_tlist, non_group_vars); + + /* clean up cruft */ + list_free(non_group_vars); + list_free(non_group_cols); return sub_tlist; } +/* + * get_grouping_column_index + * Get the GROUP BY column position, if any, of a targetlist entry. + * + * Returns the index (counting from 0) of the TLE in the GROUP BY list, or -1 + * if it's not a grouping column. Note: the result is unique because the + * parser won't make multiple groupClause entries for the same TLE. + */ +static int +get_grouping_column_index(Query *parse, TargetEntry *tle) +{ + int colno = 0; + Index ressortgroupref = tle->ressortgroupref; + ListCell *gl; + + /* No need to search groupClause if TLE hasn't got a sortgroupref */ + if (ressortgroupref == 0) + return -1; + + foreach(gl, parse->groupClause) + { + SortGroupClause *grpcl = (SortGroupClause *) lfirst(gl); + + if (grpcl->tleSortGroupRef == ressortgroupref) + return colno; + colno++; + } + + return -1; +} + /* * locate_grouping_columns - * Locate grouping columns in the tlist chosen by query_planner. + * Locate grouping columns in the tlist chosen by create_plan. * * This is only needed if we don't use the sub_tlist chosen by * make_subplanTargetList. We have to forget the column indexes found @@ -3057,30 +3300,18 @@ plan_cluster_use_sort(Oid tableOid, Oid indexOid) rte = makeNode(RangeTblEntry); rte->rtekind = RTE_RELATION; rte->relid = tableOid; + rte->relkind = RELKIND_RELATION; + rte->lateral = false; rte->inh = false; rte->inFromCl = true; query->rtable = list_make1(rte); - /* ... and insert it into PlannerInfo */ - root->simple_rel_array_size = 2; - root->simple_rel_array = (RelOptInfo **) - palloc0(root->simple_rel_array_size * sizeof(RelOptInfo *)); - root->simple_rte_array = (RangeTblEntry **) - palloc0(root->simple_rel_array_size * sizeof(RangeTblEntry *)); - root->simple_rte_array[1] = rte; + /* Set up RTE/RelOptInfo arrays */ + setup_simple_rel_arrays(root); /* Build RelOptInfo */ rel = build_simple_rel(root, 1, RELOPT_BASEREL); - /* - * Rather than doing all the pushups that would be needed to use - * set_baserel_size_estimates, just do a quick hack for rows and width. - */ - rel->rows = rel->tuples; - rel->width = get_relation_data_width(tableOid); - - root->total_table_pages = rel->pages; - /* Locate IndexOptInfo for the target index */ indexInfo = NULL; foreach(lc, rel->indexlist) @@ -3089,29 +3320,46 @@ plan_cluster_use_sort(Oid tableOid, Oid indexOid) if (indexInfo->indexoid == indexOid) break; } + + /* + * It's possible that get_relation_info did not generate an IndexOptInfo + * for the desired index; this could happen if it's not yet reached its + * indcheckxmin usability horizon, or if it's a system index and we're + * ignoring system indexes. In such cases we should tell CLUSTER to not + * trust the index contents but use seqscan-and-sort. + */ if (lc == NULL) /* not in the list? */ - elog(ERROR, "index %u does not belong to table %u", - indexOid, tableOid); + return true; /* use sort */ + + /* + * Rather than doing all the pushups that would be needed to use + * set_baserel_size_estimates, just do a quick hack for rows and width. + */ + rel->rows = rel->tuples; + rel->width = get_relation_data_width(tableOid, NULL); + + root->total_table_pages = rel->pages; /* * Determine eval cost of the index expressions, if any. We need to - * charge twice that amount for each tuple comparison that happens - * during the sort, since tuplesort.c will have to re-evaluate the - * index expressions each time. (XXX that's pretty inefficient...) + * charge twice that amount for each tuple comparison that happens during + * the sort, since tuplesort.c will have to re-evaluate the index + * expressions each time. (XXX that's pretty inefficient...) */ cost_qual_eval(&indexExprCost, indexInfo->indexprs, root); comparisonCost = 2.0 * (indexExprCost.startup + indexExprCost.per_tuple); /* Estimate the cost of seq scan + sort */ - seqScanPath = create_seqscan_path(root, rel); + seqScanPath = create_seqscan_path(root, rel, NULL); cost_sort(&seqScanAndSortPath, root, NIL, seqScanPath->total_cost, rel->tuples, rel->width, comparisonCost, maintenance_work_mem, -1.0); /* Estimate the cost of index scan */ indexScanPath = create_index_path(root, indexInfo, - NIL, NIL, - ForwardScanDirection, NULL); + NIL, NIL, NIL, NIL, NIL, + ForwardScanDirection, false, + NULL, 1.0); return (seqScanAndSortPath.total_cost < indexScanPath->path.total_cost); }