diff --git a/contrib/file_fdw/file_fdw.c b/contrib/file_fdw/file_fdw.c index 249d82d3a0..d16821f8e1 100644 --- a/contrib/file_fdw/file_fdw.c +++ b/contrib/file_fdw/file_fdw.c @@ -576,6 +576,7 @@ fileGetForeignPaths(PlannerInfo *root, create_foreignscan_path(root, baserel, NULL, /* default pathtarget */ baserel->rows, + 0, startup_cost, total_cost, NIL, /* no pathkeys */ diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c index fc65d81e21..adc62576d1 100644 --- a/contrib/postgres_fdw/postgres_fdw.c +++ b/contrib/postgres_fdw/postgres_fdw.c @@ -430,6 +430,7 @@ static void estimate_path_cost_size(PlannerInfo *root, List *pathkeys, PgFdwPathExtraData *fpextra, double *p_rows, int *p_width, + int *p_disabled_nodes, Cost *p_startup_cost, Cost *p_total_cost); static void get_remote_estimate(const char *sql, PGconn *conn, @@ -442,6 +443,7 @@ static void adjust_foreign_grouping_path_cost(PlannerInfo *root, double retrieved_rows, double width, double limit_tuples, + int *disabled_nodes, Cost *p_startup_cost, Cost *p_run_cost); static bool ec_member_matches_foreign(PlannerInfo *root, RelOptInfo *rel, @@ -735,6 +737,7 @@ postgresGetForeignRelSize(PlannerInfo *root, */ estimate_path_cost_size(root, baserel, NIL, NIL, NULL, &fpinfo->rows, &fpinfo->width, + &fpinfo->disabled_nodes, &fpinfo->startup_cost, &fpinfo->total_cost); /* Report estimated baserel size to planner. */ @@ -765,6 +768,7 @@ postgresGetForeignRelSize(PlannerInfo *root, /* Fill in basically-bogus cost estimates for use later. */ estimate_path_cost_size(root, baserel, NIL, NIL, NULL, &fpinfo->rows, &fpinfo->width, + &fpinfo->disabled_nodes, &fpinfo->startup_cost, &fpinfo->total_cost); } @@ -1030,6 +1034,7 @@ postgresGetForeignPaths(PlannerInfo *root, path = create_foreignscan_path(root, baserel, NULL, /* default pathtarget */ fpinfo->rows, + fpinfo->disabled_nodes, fpinfo->startup_cost, fpinfo->total_cost, NIL, /* no pathkeys */ @@ -1184,13 +1189,14 @@ postgresGetForeignPaths(PlannerInfo *root, ParamPathInfo *param_info = (ParamPathInfo *) lfirst(lc); double rows; int width; + int disabled_nodes; Cost startup_cost; Cost total_cost; /* Get a cost estimate from the remote */ estimate_path_cost_size(root, baserel, param_info->ppi_clauses, NIL, NULL, - &rows, &width, + &rows, &width, &disabled_nodes, &startup_cost, &total_cost); /* @@ -1203,6 +1209,7 @@ postgresGetForeignPaths(PlannerInfo *root, path = create_foreignscan_path(root, baserel, NULL, /* default pathtarget */ rows, + disabled_nodes, startup_cost, total_cost, NIL, /* no pathkeys */ @@ -3079,7 +3086,7 @@ postgresExecForeignTruncate(List *rels, * final sort and the LIMIT restriction. * * The function returns the cost and size estimates in p_rows, p_width, - * p_startup_cost and p_total_cost variables. + * p_disabled_nodes, p_startup_cost and p_total_cost variables. */ static void estimate_path_cost_size(PlannerInfo *root, @@ -3088,12 +3095,14 @@ estimate_path_cost_size(PlannerInfo *root, List *pathkeys, PgFdwPathExtraData *fpextra, double *p_rows, int *p_width, + int *p_disabled_nodes, Cost *p_startup_cost, Cost *p_total_cost) { PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) foreignrel->fdw_private; double rows; double retrieved_rows; int width; + int disabled_nodes = 0; Cost startup_cost; Cost total_cost; @@ -3483,6 +3492,7 @@ estimate_path_cost_size(PlannerInfo *root, adjust_foreign_grouping_path_cost(root, pathkeys, retrieved_rows, width, fpextra->limit_tuples, + &disabled_nodes, &startup_cost, &run_cost); } else @@ -3577,6 +3587,7 @@ estimate_path_cost_size(PlannerInfo *root, /* Return results. */ *p_rows = rows; *p_width = width; + *p_disabled_nodes = disabled_nodes; *p_startup_cost = startup_cost; *p_total_cost = total_cost; } @@ -3637,6 +3648,7 @@ adjust_foreign_grouping_path_cost(PlannerInfo *root, double retrieved_rows, double width, double limit_tuples, + int *p_disabled_nodes, Cost *p_startup_cost, Cost *p_run_cost) { @@ -3656,6 +3668,7 @@ adjust_foreign_grouping_path_cost(PlannerInfo *root, cost_sort(&sort_path, root, pathkeys, + 0, *p_startup_cost + *p_run_cost, retrieved_rows, width, @@ -6147,13 +6160,15 @@ add_paths_with_pathkeys_for_rel(PlannerInfo *root, RelOptInfo *rel, { double rows; int width; + int disabled_nodes; Cost startup_cost; Cost total_cost; List *useful_pathkeys = lfirst(lc); Path *sorted_epq_path; estimate_path_cost_size(root, rel, NIL, useful_pathkeys, NULL, - &rows, &width, &startup_cost, &total_cost); + &rows, &width, &disabled_nodes, + &startup_cost, &total_cost); /* * The EPQ path must be at least as well sorted as the path itself, in @@ -6175,6 +6190,7 @@ add_paths_with_pathkeys_for_rel(PlannerInfo *root, RelOptInfo *rel, create_foreignscan_path(root, rel, NULL, rows, + disabled_nodes, startup_cost, total_cost, useful_pathkeys, @@ -6188,6 +6204,7 @@ add_paths_with_pathkeys_for_rel(PlannerInfo *root, RelOptInfo *rel, create_foreign_join_path(root, rel, NULL, rows, + disabled_nodes, startup_cost, total_cost, useful_pathkeys, @@ -6335,6 +6352,7 @@ postgresGetForeignJoinPaths(PlannerInfo *root, ForeignPath *joinpath; double rows; int width; + int disabled_nodes; Cost startup_cost; Cost total_cost; Path *epq_path; /* Path to create plan to be executed when @@ -6424,12 +6442,14 @@ postgresGetForeignJoinPaths(PlannerInfo *root, /* Estimate costs for bare join relation */ estimate_path_cost_size(root, joinrel, NIL, NIL, NULL, - &rows, &width, &startup_cost, &total_cost); + &rows, &width, &disabled_nodes, + &startup_cost, &total_cost); /* Now update this information in the joinrel */ joinrel->rows = rows; joinrel->reltarget->width = width; fpinfo->rows = rows; fpinfo->width = width; + fpinfo->disabled_nodes = disabled_nodes; fpinfo->startup_cost = startup_cost; fpinfo->total_cost = total_cost; @@ -6441,6 +6461,7 @@ postgresGetForeignJoinPaths(PlannerInfo *root, joinrel, NULL, /* default pathtarget */ rows, + disabled_nodes, startup_cost, total_cost, NIL, /* no pathkeys */ @@ -6768,6 +6789,7 @@ add_foreign_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel, ForeignPath *grouppath; double rows; int width; + int disabled_nodes; Cost startup_cost; Cost total_cost; @@ -6818,11 +6840,13 @@ add_foreign_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel, /* Estimate the cost of push down */ estimate_path_cost_size(root, grouped_rel, NIL, NIL, NULL, - &rows, &width, &startup_cost, &total_cost); + &rows, &width, &disabled_nodes, + &startup_cost, &total_cost); /* Now update this information in the fpinfo */ fpinfo->rows = rows; fpinfo->width = width; + fpinfo->disabled_nodes = disabled_nodes; fpinfo->startup_cost = startup_cost; fpinfo->total_cost = total_cost; @@ -6831,6 +6855,7 @@ add_foreign_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel, grouped_rel, grouped_rel->reltarget, rows, + disabled_nodes, startup_cost, total_cost, NIL, /* no pathkeys */ @@ -6859,6 +6884,7 @@ add_foreign_ordered_paths(PlannerInfo *root, RelOptInfo *input_rel, PgFdwPathExtraData *fpextra; double rows; int width; + int disabled_nodes; Cost startup_cost; Cost total_cost; List *fdw_private; @@ -6952,7 +6978,8 @@ add_foreign_ordered_paths(PlannerInfo *root, RelOptInfo *input_rel, /* Estimate the costs of performing the final sort remotely */ estimate_path_cost_size(root, input_rel, NIL, root->sort_pathkeys, fpextra, - &rows, &width, &startup_cost, &total_cost); + &rows, &width, &disabled_nodes, + &startup_cost, &total_cost); /* * Build the fdw_private list that will be used by postgresGetForeignPlan. @@ -6965,6 +6992,7 @@ add_foreign_ordered_paths(PlannerInfo *root, RelOptInfo *input_rel, input_rel, root->upper_targets[UPPERREL_ORDERED], rows, + disabled_nodes, startup_cost, total_cost, root->sort_pathkeys, @@ -6998,6 +7026,7 @@ add_foreign_final_paths(PlannerInfo *root, RelOptInfo *input_rel, bool save_use_remote_estimate = false; double rows; int width; + int disabled_nodes; Cost startup_cost; Cost total_cost; List *fdw_private; @@ -7082,6 +7111,7 @@ add_foreign_final_paths(PlannerInfo *root, RelOptInfo *input_rel, path->parent, path->pathtarget, path->rows, + path->disabled_nodes, path->startup_cost, path->total_cost, path->pathkeys, @@ -7199,7 +7229,8 @@ add_foreign_final_paths(PlannerInfo *root, RelOptInfo *input_rel, ifpinfo->use_remote_estimate = false; } estimate_path_cost_size(root, input_rel, NIL, pathkeys, fpextra, - &rows, &width, &startup_cost, &total_cost); + &rows, &width, &disabled_nodes, + &startup_cost, &total_cost); if (!fpextra->has_final_sort) ifpinfo->use_remote_estimate = save_use_remote_estimate; @@ -7218,6 +7249,7 @@ add_foreign_final_paths(PlannerInfo *root, RelOptInfo *input_rel, input_rel, root->upper_targets[UPPERREL_FINAL], rows, + disabled_nodes, startup_cost, total_cost, pathkeys, diff --git a/contrib/postgres_fdw/postgres_fdw.h b/contrib/postgres_fdw/postgres_fdw.h index 37c1575af6..9e501660d1 100644 --- a/contrib/postgres_fdw/postgres_fdw.h +++ b/contrib/postgres_fdw/postgres_fdw.h @@ -62,6 +62,7 @@ typedef struct PgFdwRelationInfo /* Estimated size and cost for a scan, join, or grouping/aggregation. */ double rows; int width; + int disabled_nodes; Cost startup_cost; Cost total_cost; diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c index 79991b1980..e1523d15df 100644 --- a/src/backend/optimizer/path/costsize.c +++ b/src/backend/optimizer/path/costsize.c @@ -50,6 +50,17 @@ * so beware of division-by-zero.) The LIMIT is applied as a top-level * plan node. * + * Each path stores the total number of disabled nodes that exist at or + * below that point in the plan tree. This is regarded as a component of + * the cost, and paths with fewer disabled nodes should be regarded as + * cheaper than those with more. Disabled nodes occur when the user sets + * a GUC like enable_seqscan=false. We can't necessarily respect such a + * setting in every part of the plan tree, but we want to respect in as many + * parts of the plan tree as possible. Simpler schemes like storing a Boolean + * here rather than a count fail to do that. We used to disable nodes by + * adding a large constant to the startup cost, but that distorted planning + * in other ways. + * * For largely historical reasons, most of the routines in this module use * the passed result Path only to store their results (rows, startup_cost and * total_cost) into. All the input data they need is passed as separate @@ -301,9 +312,6 @@ cost_seqscan(Path *path, PlannerInfo *root, else path->rows = baserel->rows; - if (!enable_seqscan) - startup_cost += disable_cost; - /* fetch estimated page cost for tablespace containing table */ get_tablespace_page_costs(baserel->reltablespace, NULL, @@ -346,6 +354,7 @@ cost_seqscan(Path *path, PlannerInfo *root, path->rows = clamp_row_est(path->rows / parallel_divisor); } + path->disabled_nodes = enable_seqscan ? 0 : 1; path->startup_cost = startup_cost; path->total_cost = startup_cost + cpu_run_cost + disk_run_cost; } @@ -418,6 +427,7 @@ cost_samplescan(Path *path, PlannerInfo *root, startup_cost += path->pathtarget->cost.startup; run_cost += path->pathtarget->cost.per_tuple * path->rows; + path->disabled_nodes = 0; path->startup_cost = startup_cost; path->total_cost = startup_cost + run_cost; } @@ -456,6 +466,7 @@ cost_gather(GatherPath *path, PlannerInfo *root, startup_cost += parallel_setup_cost; run_cost += parallel_tuple_cost * path->path.rows; + path->path.disabled_nodes = path->subpath->disabled_nodes; path->path.startup_cost = startup_cost; path->path.total_cost = (startup_cost + run_cost); } @@ -473,6 +484,7 @@ cost_gather(GatherPath *path, PlannerInfo *root, void cost_gather_merge(GatherMergePath *path, PlannerInfo *root, RelOptInfo *rel, ParamPathInfo *param_info, + int input_disabled_nodes, Cost input_startup_cost, Cost input_total_cost, double *rows) { @@ -490,9 +502,6 @@ cost_gather_merge(GatherMergePath *path, PlannerInfo *root, else path->path.rows = rel->rows; - if (!enable_gathermerge) - startup_cost += disable_cost; - /* * Add one to the number of workers to account for the leader. This might * be overgenerous since the leader will do less work than other workers @@ -523,6 +532,8 @@ cost_gather_merge(GatherMergePath *path, PlannerInfo *root, startup_cost += parallel_setup_cost; run_cost += parallel_tuple_cost * path->path.rows * 1.05; + path->path.disabled_nodes = input_disabled_nodes + + (enable_gathermerge ? 0 : 1); path->path.startup_cost = startup_cost + input_startup_cost; path->path.total_cost = (startup_cost + run_cost + input_total_cost); } @@ -603,9 +614,8 @@ cost_index(IndexPath *path, PlannerInfo *root, double loop_count, path->indexclauses); } - if (!enable_indexscan) - startup_cost += disable_cost; /* we don't need to check enable_indexonlyscan; indxpath.c does that */ + path->path.disabled_nodes = enable_indexscan ? 0 : 1; /* * Call index-access-method-specific code to estimate the processing cost @@ -1038,9 +1048,6 @@ cost_bitmap_heap_scan(Path *path, PlannerInfo *root, RelOptInfo *baserel, else path->rows = baserel->rows; - if (!enable_bitmapscan) - startup_cost += disable_cost; - pages_fetched = compute_bitmap_pages(root, baserel, bitmapqual, loop_count, &indexTotalCost, &tuples_fetched); @@ -1102,6 +1109,7 @@ cost_bitmap_heap_scan(Path *path, PlannerInfo *root, RelOptInfo *baserel, startup_cost += path->pathtarget->cost.startup; run_cost += path->pathtarget->cost.per_tuple * path->rows; + path->disabled_nodes = enable_bitmapscan ? 0 : 1; path->startup_cost = startup_cost; path->total_cost = startup_cost + run_cost; } @@ -1187,6 +1195,7 @@ cost_bitmap_and_node(BitmapAndPath *path, PlannerInfo *root) } path->bitmapselectivity = selec; path->path.rows = 0; /* per above, not used */ + path->path.disabled_nodes = 0; path->path.startup_cost = totalCost; path->path.total_cost = totalCost; } @@ -1261,6 +1270,7 @@ cost_tidscan(Path *path, PlannerInfo *root, /* Should only be applied to base relations */ Assert(baserel->relid > 0); Assert(baserel->rtekind == RTE_RELATION); + Assert(tidquals != NIL); /* Mark the path with the correct row estimate */ if (param_info) @@ -1275,6 +1285,14 @@ cost_tidscan(Path *path, PlannerInfo *root, RestrictInfo *rinfo = lfirst_node(RestrictInfo, l); Expr *qual = rinfo->clause; + /* + * We must use a TID scan for CurrentOfExpr; in any other case, we + * should be generating a TID scan only if enable_tidscan=true. Also, + * if CurrentOfExpr is the qual, there should be only one. + */ + Assert(enable_tidscan || IsA(qual, CurrentOfExpr)); + Assert(list_length(tidquals) == 1 || !IsA(qual, CurrentOfExpr)); + if (IsA(qual, ScalarArrayOpExpr)) { /* Each element of the array yields 1 tuple */ @@ -1322,6 +1340,12 @@ cost_tidscan(Path *path, PlannerInfo *root, startup_cost += path->pathtarget->cost.startup; run_cost += path->pathtarget->cost.per_tuple * path->rows; + /* + * There are assertions above verifying that we only reach this function + * either when enable_tidscan=true or when the TID scan is the only legal + * path, so it's safe to set disabled_nodes to zero here. + */ + path->disabled_nodes = 0; path->startup_cost = startup_cost; path->total_cost = startup_cost + run_cost; } @@ -1414,6 +1438,9 @@ cost_tidrangescan(Path *path, PlannerInfo *root, startup_cost += path->pathtarget->cost.startup; run_cost += path->pathtarget->cost.per_tuple * path->rows; + /* we should not generate this path type when enable_tidscan=false */ + Assert(enable_tidscan); + path->disabled_nodes = 0; path->startup_cost = startup_cost; path->total_cost = startup_cost + run_cost; } @@ -1466,6 +1493,7 @@ cost_subqueryscan(SubqueryScanPath *path, PlannerInfo *root, * SubqueryScan node, plus cpu_tuple_cost to account for selection and * projection overhead. */ + path->path.disabled_nodes = path->subpath->disabled_nodes; path->path.startup_cost = path->subpath->startup_cost; path->path.total_cost = path->subpath->total_cost; @@ -1556,6 +1584,7 @@ cost_functionscan(Path *path, PlannerInfo *root, startup_cost += path->pathtarget->cost.startup; run_cost += path->pathtarget->cost.per_tuple * path->rows; + path->disabled_nodes = 0; path->startup_cost = startup_cost; path->total_cost = startup_cost + run_cost; } @@ -1612,6 +1641,7 @@ cost_tablefuncscan(Path *path, PlannerInfo *root, startup_cost += path->pathtarget->cost.startup; run_cost += path->pathtarget->cost.per_tuple * path->rows; + path->disabled_nodes = 0; path->startup_cost = startup_cost; path->total_cost = startup_cost + run_cost; } @@ -1659,6 +1689,7 @@ cost_valuesscan(Path *path, PlannerInfo *root, startup_cost += path->pathtarget->cost.startup; run_cost += path->pathtarget->cost.per_tuple * path->rows; + path->disabled_nodes = 0; path->startup_cost = startup_cost; path->total_cost = startup_cost + run_cost; } @@ -1706,6 +1737,7 @@ cost_ctescan(Path *path, PlannerInfo *root, startup_cost += path->pathtarget->cost.startup; run_cost += path->pathtarget->cost.per_tuple * path->rows; + path->disabled_nodes = 0; path->startup_cost = startup_cost; path->total_cost = startup_cost + run_cost; } @@ -1743,6 +1775,7 @@ cost_namedtuplestorescan(Path *path, PlannerInfo *root, cpu_per_tuple += cpu_tuple_cost + qpqual_cost.per_tuple; run_cost += cpu_per_tuple * baserel->tuples; + path->disabled_nodes = 0; path->startup_cost = startup_cost; path->total_cost = startup_cost + run_cost; } @@ -1777,6 +1810,7 @@ cost_resultscan(Path *path, PlannerInfo *root, cpu_per_tuple = cpu_tuple_cost + qpqual_cost.per_tuple; run_cost += cpu_per_tuple * baserel->tuples; + path->disabled_nodes = 0; path->startup_cost = startup_cost; path->total_cost = startup_cost + run_cost; } @@ -1816,6 +1850,7 @@ cost_recursive_union(Path *runion, Path *nrterm, Path *rterm) */ total_cost += cpu_tuple_cost * total_rows; + runion->disabled_nodes = nrterm->disabled_nodes + rterm->disabled_nodes; runion->startup_cost = startup_cost; runion->total_cost = total_cost; runion->rows = total_rows; @@ -1964,6 +1999,7 @@ cost_tuplesort(Cost *startup_cost, Cost *run_cost, void cost_incremental_sort(Path *path, PlannerInfo *root, List *pathkeys, int presorted_keys, + int input_disabled_nodes, Cost input_startup_cost, Cost input_total_cost, double input_tuples, int width, Cost comparison_cost, int sort_mem, double limit_tuples) @@ -2083,6 +2119,11 @@ cost_incremental_sort(Path *path, run_cost += 2.0 * cpu_tuple_cost * input_groups; path->rows = input_tuples; + + /* should not generate these paths when enable_incremental_sort=false */ + Assert(enable_incremental_sort); + path->disabled_nodes = input_disabled_nodes; + path->startup_cost = startup_cost; path->total_cost = startup_cost + run_cost; } @@ -2101,7 +2142,8 @@ cost_incremental_sort(Path *path, */ void cost_sort(Path *path, PlannerInfo *root, - List *pathkeys, Cost input_cost, double tuples, int width, + List *pathkeys, int input_disabled_nodes, + Cost input_cost, double tuples, int width, Cost comparison_cost, int sort_mem, double limit_tuples) @@ -2114,12 +2156,10 @@ cost_sort(Path *path, PlannerInfo *root, comparison_cost, sort_mem, limit_tuples); - if (!enable_sort) - startup_cost += disable_cost; - startup_cost += input_cost; path->rows = tuples; + path->disabled_nodes = input_disabled_nodes + (enable_sort ? 0 : 1); path->startup_cost = startup_cost; path->total_cost = startup_cost + run_cost; } @@ -2211,6 +2251,7 @@ cost_append(AppendPath *apath) { ListCell *l; + apath->path.disabled_nodes = 0; apath->path.startup_cost = 0; apath->path.total_cost = 0; apath->path.rows = 0; @@ -2232,12 +2273,16 @@ cost_append(AppendPath *apath) */ apath->path.startup_cost = firstsubpath->startup_cost; - /* Compute rows and costs as sums of subplan rows and costs. */ + /* + * Compute rows, number of disabled nodes, and total cost as sums + * of underlying subplan values. + */ foreach(l, apath->subpaths) { Path *subpath = (Path *) lfirst(l); apath->path.rows += subpath->rows; + apath->path.disabled_nodes += subpath->disabled_nodes; apath->path.total_cost += subpath->total_cost; } } @@ -2277,6 +2322,7 @@ cost_append(AppendPath *apath) cost_sort(&sort_path, NULL, /* doesn't currently need root */ pathkeys, + subpath->disabled_nodes, subpath->total_cost, subpath->rows, subpath->pathtarget->width, @@ -2287,6 +2333,7 @@ cost_append(AppendPath *apath) } apath->path.rows += subpath->rows; + apath->path.disabled_nodes += subpath->disabled_nodes; apath->path.startup_cost += subpath->startup_cost; apath->path.total_cost += subpath->total_cost; } @@ -2335,6 +2382,7 @@ cost_append(AppendPath *apath) apath->path.total_cost += subpath->total_cost; } + apath->path.disabled_nodes += subpath->disabled_nodes; apath->path.rows = clamp_row_est(apath->path.rows); i++; @@ -2375,6 +2423,7 @@ cost_append(AppendPath *apath) * * 'pathkeys' is a list of sort keys * 'n_streams' is the number of input streams + * 'input_disabled_nodes' is the sum of the input streams' disabled node counts * 'input_startup_cost' is the sum of the input streams' startup costs * 'input_total_cost' is the sum of the input streams' total costs * 'tuples' is the number of tuples in all the streams @@ -2382,6 +2431,7 @@ cost_append(AppendPath *apath) void cost_merge_append(Path *path, PlannerInfo *root, List *pathkeys, int n_streams, + int input_disabled_nodes, Cost input_startup_cost, Cost input_total_cost, double tuples) { @@ -2412,6 +2462,7 @@ cost_merge_append(Path *path, PlannerInfo *root, */ run_cost += cpu_tuple_cost * APPEND_CPU_COST_MULTIPLIER * tuples; + path->disabled_nodes = input_disabled_nodes; path->startup_cost = startup_cost + input_startup_cost; path->total_cost = startup_cost + run_cost + input_total_cost; } @@ -2430,6 +2481,7 @@ cost_merge_append(Path *path, PlannerInfo *root, */ void cost_material(Path *path, + int input_disabled_nodes, Cost input_startup_cost, Cost input_total_cost, double tuples, int width) { @@ -2467,6 +2519,7 @@ cost_material(Path *path, run_cost += seq_page_cost * npages; } + path->disabled_nodes = input_disabled_nodes + (enable_material ? 0 : 1); path->startup_cost = startup_cost; path->total_cost = startup_cost + run_cost; } @@ -2630,6 +2683,7 @@ cost_agg(Path *path, PlannerInfo *root, AggStrategy aggstrategy, const AggClauseCosts *aggcosts, int numGroupCols, double numGroups, List *quals, + int disabled_nodes, Cost input_startup_cost, Cost input_total_cost, double input_tuples, double input_width) { @@ -2685,10 +2739,7 @@ cost_agg(Path *path, PlannerInfo *root, startup_cost = input_startup_cost; total_cost = input_total_cost; if (aggstrategy == AGG_MIXED && !enable_hashagg) - { - startup_cost += disable_cost; - total_cost += disable_cost; - } + ++disabled_nodes; /* calcs phrased this way to match HASHED case, see note above */ total_cost += aggcosts->transCost.startup; total_cost += aggcosts->transCost.per_tuple * input_tuples; @@ -2703,7 +2754,7 @@ cost_agg(Path *path, PlannerInfo *root, /* must be AGG_HASHED */ startup_cost = input_total_cost; if (!enable_hashagg) - startup_cost += disable_cost; + ++disabled_nodes; startup_cost += aggcosts->transCost.startup; startup_cost += aggcosts->transCost.per_tuple * input_tuples; /* cost of computing hash value */ @@ -2812,6 +2863,7 @@ cost_agg(Path *path, PlannerInfo *root, } path->rows = output_tuples; + path->disabled_nodes = disabled_nodes; path->startup_cost = startup_cost; path->total_cost = total_cost; } @@ -3046,6 +3098,7 @@ get_windowclause_startup_tuples(PlannerInfo *root, WindowClause *wc, void cost_windowagg(Path *path, PlannerInfo *root, List *windowFuncs, WindowClause *winclause, + int input_disabled_nodes, Cost input_startup_cost, Cost input_total_cost, double input_tuples) { @@ -3111,6 +3164,7 @@ cost_windowagg(Path *path, PlannerInfo *root, total_cost += cpu_tuple_cost * input_tuples; path->rows = input_tuples; + path->disabled_nodes = input_disabled_nodes; path->startup_cost = startup_cost; path->total_cost = total_cost; @@ -3142,6 +3196,7 @@ void cost_group(Path *path, PlannerInfo *root, int numGroupCols, double numGroups, List *quals, + int input_disabled_nodes, Cost input_startup_cost, Cost input_total_cost, double input_tuples) { @@ -3180,6 +3235,7 @@ cost_group(Path *path, PlannerInfo *root, } path->rows = output_tuples; + path->disabled_nodes = input_disabled_nodes; path->startup_cost = startup_cost; path->total_cost = total_cost; } @@ -3214,6 +3270,7 @@ initial_cost_nestloop(PlannerInfo *root, JoinCostWorkspace *workspace, Path *outer_path, Path *inner_path, JoinPathExtraData *extra) { + int disabled_nodes; Cost startup_cost = 0; Cost run_cost = 0; double outer_path_rows = outer_path->rows; @@ -3222,6 +3279,11 @@ initial_cost_nestloop(PlannerInfo *root, JoinCostWorkspace *workspace, Cost inner_run_cost; Cost inner_rescan_run_cost; + /* Count up disabled nodes. */ + disabled_nodes = enable_nestloop ? 0 : 1; + disabled_nodes += inner_path->disabled_nodes; + disabled_nodes += outer_path->disabled_nodes; + /* estimate costs to rescan the inner relation */ cost_rescan(root, inner_path, &inner_rescan_start_cost, @@ -3269,6 +3331,7 @@ initial_cost_nestloop(PlannerInfo *root, JoinCostWorkspace *workspace, /* CPU costs left for later */ /* Public result fields */ + workspace->disabled_nodes = disabled_nodes; workspace->startup_cost = startup_cost; workspace->total_cost = startup_cost + run_cost; /* Save private data for final_cost_nestloop */ @@ -3298,6 +3361,9 @@ final_cost_nestloop(PlannerInfo *root, NestPath *path, QualCost restrict_qual_cost; double ntuples; + /* Set the number of disabled nodes. */ + path->jpath.path.disabled_nodes = workspace->disabled_nodes; + /* Protect some assumptions below that rowcounts aren't zero */ if (outer_path_rows <= 0) outer_path_rows = 1; @@ -3318,14 +3384,6 @@ final_cost_nestloop(PlannerInfo *root, NestPath *path, clamp_row_est(path->jpath.path.rows / parallel_divisor); } - /* - * We could include disable_cost in the preliminary estimate, but that - * would amount to optimizing for the case where the join method is - * disabled, which doesn't seem like the way to bet. - */ - if (!enable_nestloop) - startup_cost += disable_cost; - /* cost of inner-relation source data (we already dealt with outer rel) */ if (path->jpath.jointype == JOIN_SEMI || path->jpath.jointype == JOIN_ANTI || @@ -3497,6 +3555,7 @@ initial_cost_mergejoin(PlannerInfo *root, JoinCostWorkspace *workspace, List *outersortkeys, List *innersortkeys, JoinPathExtraData *extra) { + int disabled_nodes; Cost startup_cost = 0; Cost run_cost = 0; double outer_path_rows = outer_path->rows; @@ -3617,6 +3676,8 @@ initial_cost_mergejoin(PlannerInfo *root, JoinCostWorkspace *workspace, Assert(outerstartsel <= outerendsel); Assert(innerstartsel <= innerendsel); + disabled_nodes = enable_mergejoin ? 0 : 1; + /* cost of source data */ if (outersortkeys) /* do we need to sort outer? */ @@ -3624,12 +3685,14 @@ initial_cost_mergejoin(PlannerInfo *root, JoinCostWorkspace *workspace, cost_sort(&sort_path, root, outersortkeys, + outer_path->disabled_nodes, outer_path->total_cost, outer_path_rows, outer_path->pathtarget->width, 0.0, work_mem, -1.0); + disabled_nodes += sort_path.disabled_nodes; startup_cost += sort_path.startup_cost; startup_cost += (sort_path.total_cost - sort_path.startup_cost) * outerstartsel; @@ -3638,6 +3701,7 @@ initial_cost_mergejoin(PlannerInfo *root, JoinCostWorkspace *workspace, } else { + disabled_nodes += outer_path->disabled_nodes; startup_cost += outer_path->startup_cost; startup_cost += (outer_path->total_cost - outer_path->startup_cost) * outerstartsel; @@ -3650,12 +3714,14 @@ initial_cost_mergejoin(PlannerInfo *root, JoinCostWorkspace *workspace, cost_sort(&sort_path, root, innersortkeys, + inner_path->disabled_nodes, inner_path->total_cost, inner_path_rows, inner_path->pathtarget->width, 0.0, work_mem, -1.0); + disabled_nodes += sort_path.disabled_nodes; startup_cost += sort_path.startup_cost; startup_cost += (sort_path.total_cost - sort_path.startup_cost) * innerstartsel; @@ -3664,6 +3730,7 @@ initial_cost_mergejoin(PlannerInfo *root, JoinCostWorkspace *workspace, } else { + disabled_nodes += inner_path->disabled_nodes; startup_cost += inner_path->startup_cost; startup_cost += (inner_path->total_cost - inner_path->startup_cost) * innerstartsel; @@ -3682,6 +3749,7 @@ initial_cost_mergejoin(PlannerInfo *root, JoinCostWorkspace *workspace, /* CPU costs left for later */ /* Public result fields */ + workspace->disabled_nodes = disabled_nodes; workspace->startup_cost = startup_cost; workspace->total_cost = startup_cost + run_cost + inner_run_cost; /* Save private data for final_cost_mergejoin */ @@ -3746,6 +3814,9 @@ final_cost_mergejoin(PlannerInfo *root, MergePath *path, rescannedtuples; double rescanratio; + /* Set the number of disabled nodes. */ + path->jpath.path.disabled_nodes = workspace->disabled_nodes; + /* Protect some assumptions below that rowcounts aren't zero */ if (inner_path_rows <= 0) inner_path_rows = 1; @@ -3765,14 +3836,6 @@ final_cost_mergejoin(PlannerInfo *root, MergePath *path, clamp_row_est(path->jpath.path.rows / parallel_divisor); } - /* - * We could include disable_cost in the preliminary estimate, but that - * would amount to optimizing for the case where the join method is - * disabled, which doesn't seem like the way to bet. - */ - if (!enable_mergejoin) - startup_cost += disable_cost; - /* * Compute cost of the mergequals and qpquals (other restriction clauses) * separately. @@ -4056,6 +4119,7 @@ initial_cost_hashjoin(PlannerInfo *root, JoinCostWorkspace *workspace, JoinPathExtraData *extra, bool parallel_hash) { + int disabled_nodes; Cost startup_cost = 0; Cost run_cost = 0; double outer_path_rows = outer_path->rows; @@ -4067,6 +4131,11 @@ initial_cost_hashjoin(PlannerInfo *root, JoinCostWorkspace *workspace, int num_skew_mcvs; size_t space_allowed; /* unused */ + /* Count up disabled nodes. */ + disabled_nodes = enable_hashjoin ? 0 : 1; + disabled_nodes += inner_path->disabled_nodes; + disabled_nodes += outer_path->disabled_nodes; + /* cost of source data */ startup_cost += outer_path->startup_cost; run_cost += outer_path->total_cost - outer_path->startup_cost; @@ -4136,6 +4205,7 @@ initial_cost_hashjoin(PlannerInfo *root, JoinCostWorkspace *workspace, /* CPU costs left for later */ /* Public result fields */ + workspace->disabled_nodes = disabled_nodes; workspace->startup_cost = startup_cost; workspace->total_cost = startup_cost + run_cost; /* Save private data for final_cost_hashjoin */ @@ -4180,6 +4250,9 @@ final_cost_hashjoin(PlannerInfo *root, HashPath *path, Selectivity innermcvfreq; ListCell *hcl; + /* Set the number of disabled nodes. */ + path->jpath.path.disabled_nodes = workspace->disabled_nodes; + /* Mark the path with the correct row estimate */ if (path->jpath.path.param_info) path->jpath.path.rows = path->jpath.path.param_info->ppi_rows; @@ -4195,14 +4268,6 @@ final_cost_hashjoin(PlannerInfo *root, HashPath *path, clamp_row_est(path->jpath.path.rows / parallel_divisor); } - /* - * We could include disable_cost in the preliminary estimate, but that - * would amount to optimizing for the case where the join method is - * disabled, which doesn't seem like the way to bet. - */ - if (!enable_hashjoin) - startup_cost += disable_cost; - /* mark the path with estimated # of batches */ path->num_batches = numbatches; diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c index e858f59600..b0e8c94dfc 100644 --- a/src/backend/optimizer/path/joinpath.c +++ b/src/backend/optimizer/path/joinpath.c @@ -915,7 +915,7 @@ try_nestloop_path(PlannerInfo *root, initial_cost_nestloop(root, &workspace, jointype, outer_path, inner_path, extra); - if (add_path_precheck(joinrel, + if (add_path_precheck(joinrel, workspace.disabled_nodes, workspace.startup_cost, workspace.total_cost, pathkeys, required_outer)) { @@ -999,7 +999,8 @@ try_partial_nestloop_path(PlannerInfo *root, */ initial_cost_nestloop(root, &workspace, jointype, outer_path, inner_path, extra); - if (!add_partial_path_precheck(joinrel, workspace.total_cost, pathkeys)) + if (!add_partial_path_precheck(joinrel, workspace.disabled_nodes, + workspace.total_cost, pathkeys)) return; /* Might be good enough to be worth trying, so let's try it. */ @@ -1096,7 +1097,7 @@ try_mergejoin_path(PlannerInfo *root, outersortkeys, innersortkeys, extra); - if (add_path_precheck(joinrel, + if (add_path_precheck(joinrel, workspace.disabled_nodes, workspace.startup_cost, workspace.total_cost, pathkeys, required_outer)) { @@ -1168,7 +1169,8 @@ try_partial_mergejoin_path(PlannerInfo *root, outersortkeys, innersortkeys, extra); - if (!add_partial_path_precheck(joinrel, workspace.total_cost, pathkeys)) + if (!add_partial_path_precheck(joinrel, workspace.disabled_nodes, + workspace.total_cost, pathkeys)) return; /* Might be good enough to be worth trying, so let's try it. */ @@ -1237,7 +1239,7 @@ try_hashjoin_path(PlannerInfo *root, initial_cost_hashjoin(root, &workspace, jointype, hashclauses, outer_path, inner_path, extra, false); - if (add_path_precheck(joinrel, + if (add_path_precheck(joinrel, workspace.disabled_nodes, workspace.startup_cost, workspace.total_cost, NIL, required_outer)) { @@ -1298,7 +1300,8 @@ try_partial_hashjoin_path(PlannerInfo *root, */ initial_cost_hashjoin(root, &workspace, jointype, hashclauses, outer_path, inner_path, extra, parallel_hash); - if (!add_partial_path_precheck(joinrel, workspace.total_cost, NIL)) + if (!add_partial_path_precheck(joinrel, workspace.disabled_nodes, + workspace.total_cost, NIL)) return; /* Might be good enough to be worth trying, so let's try it. */ diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 28addc1129..1960e59ef2 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -5452,6 +5452,7 @@ label_sort_with_costsize(PlannerInfo *root, Sort *plan, double limit_tuples) cost_sort(&sort_path, root, NIL, lefttree->total_cost, + 0, /* a Plan contains no count of disabled nodes */ lefttree->plan_rows, lefttree->plan_width, 0.0, @@ -6546,6 +6547,7 @@ materialize_finished_plan(Plan *subplan) /* Set cost data */ cost_material(&matpath, + 0, /* a Plan contains no count of disabled nodes */ subplan->startup_cost, subplan->total_cost, subplan->plan_rows, diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 948afd9094..b5827d3980 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -6748,6 +6748,7 @@ plan_cluster_use_sort(Oid tableOid, Oid indexOid) /* Estimate the cost of seq scan + sort */ seqScanPath = create_seqscan_path(root, rel, NULL, 0); cost_sort(&seqScanAndSortPath, root, NIL, + seqScanPath->disabled_nodes, seqScanPath->total_cost, rel->tuples, rel->reltarget->width, comparisonCost, maintenance_work_mem, -1.0); diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index 1c69c6e97e..a0baf6d4a1 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -1346,6 +1346,7 @@ choose_hashed_setop(PlannerInfo *root, List *groupClauses, cost_agg(&hashed_p, root, AGG_HASHED, NULL, numGroupCols, dNumGroups, NIL, + input_path->disabled_nodes, input_path->startup_cost, input_path->total_cost, input_path->rows, input_path->pathtarget->width); @@ -1353,14 +1354,17 @@ choose_hashed_setop(PlannerInfo *root, List *groupClauses, * Now for the sorted case. Note that the input is *always* unsorted, * since it was made by appending unrelated sub-relations together. */ + sorted_p.disabled_nodes = input_path->disabled_nodes; sorted_p.startup_cost = input_path->startup_cost; sorted_p.total_cost = input_path->total_cost; /* XXX cost_sort doesn't actually look at pathkeys, so just pass NIL */ - cost_sort(&sorted_p, root, NIL, sorted_p.total_cost, + cost_sort(&sorted_p, root, NIL, sorted_p.disabled_nodes, + sorted_p.total_cost, input_path->rows, input_path->pathtarget->width, 0.0, work_mem, -1.0); cost_group(&sorted_p, root, numGroupCols, dNumGroups, NIL, + sorted_p.disabled_nodes, sorted_p.startup_cost, sorted_p.total_cost, input_path->rows); diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c index 54e042a8a5..fc97bf6ee2 100644 --- a/src/backend/optimizer/util/pathnode.c +++ b/src/backend/optimizer/util/pathnode.c @@ -68,6 +68,15 @@ static bool pathlist_is_reparameterizable_by_child(List *pathlist, int compare_path_costs(Path *path1, Path *path2, CostSelector criterion) { + /* Number of disabled nodes, if different, trumps all else. */ + if (unlikely(path1->disabled_nodes != path2->disabled_nodes)) + { + if (path1->disabled_nodes < path2->disabled_nodes) + return -1; + else + return +1; + } + if (criterion == STARTUP_COST) { if (path1->startup_cost < path2->startup_cost) @@ -118,6 +127,15 @@ compare_fractional_path_costs(Path *path1, Path *path2, Cost cost1, cost2; + /* Number of disabled nodes, if different, trumps all else. */ + if (unlikely(path1->disabled_nodes != path2->disabled_nodes)) + { + if (path1->disabled_nodes < path2->disabled_nodes) + return -1; + else + return +1; + } + if (fraction <= 0.0 || fraction >= 1.0) return compare_path_costs(path1, path2, TOTAL_COST); cost1 = path1->startup_cost + @@ -166,6 +184,15 @@ compare_path_costs_fuzzily(Path *path1, Path *path2, double fuzz_factor) #define CONSIDER_PATH_STARTUP_COST(p) \ ((p)->param_info == NULL ? (p)->parent->consider_startup : (p)->parent->consider_param_startup) + /* Number of disabled nodes, if different, trumps all else. */ + if (unlikely(path1->disabled_nodes != path2->disabled_nodes)) + { + if (path1->disabled_nodes < path2->disabled_nodes) + return COSTS_BETTER1; + else + return COSTS_BETTER2; + } + /* * Check total cost first since it's more likely to be different; many * paths have zero startup cost. @@ -362,15 +389,29 @@ set_cheapest(RelOptInfo *parent_rel) * add_path * Consider a potential implementation path for the specified parent rel, * and add it to the rel's pathlist if it is worthy of consideration. - * A path is worthy if it has a better sort order (better pathkeys) or - * cheaper cost (on either dimension), or generates fewer rows, than any - * existing path that has the same or superset parameterization rels. - * We also consider parallel-safe paths more worthy than others. * - * We also remove from the rel's pathlist any old paths that are dominated - * by new_path --- that is, new_path is cheaper, at least as well ordered, - * generates no more rows, requires no outer rels not required by the old - * path, and is no less parallel-safe. + * A path is worthy if it has a better sort order (better pathkeys) or + * cheaper cost (as defined below), or generates fewer rows, than any + * existing path that has the same or superset parameterization rels. We + * also consider parallel-safe paths more worthy than others. + * + * Cheaper cost can mean either a cheaper total cost or a cheaper startup + * cost; if one path is cheaper in one of these aspects and another is + * cheaper in the other, we keep both. However, when some path type is + * disabled (e.g. due to enable_seqscan=false), the number of times that + * a disabled path type is used is considered to be a higher-order + * component of the cost. Hence, if path A uses no disabled path type, + * and path B uses 1 or more disabled path types, A is cheaper, no matter + * what we estimate for the startup and total costs. The startup and total + * cost essentially act as a tiebreak when comparing paths that use equal + * numbers of disabled path nodes; but in practice this tiebreak is almost + * always used, since normally no path types are disabled. + * + * In addition to possibly adding new_path, we also remove from the rel's + * pathlist any old paths that are dominated by new_path --- that is, + * new_path is cheaper, at least as well ordered, generates no more rows, + * requires no outer rels not required by the old path, and is no less + * parallel-safe. * * In most cases, a path with a superset parameterization will generate * fewer rows (since it has more join clauses to apply), so that those two @@ -389,10 +430,10 @@ set_cheapest(RelOptInfo *parent_rel) * parent_rel->consider_param_startup is true for a parameterized one. * Again, this allows discarding useless paths sooner. * - * The pathlist is kept sorted by total_cost, with cheaper paths - * at the front. Within this routine, that's simply a speed hack: - * doing it that way makes it more likely that we will reject an inferior - * path after a few comparisons, rather than many comparisons. + * The pathlist is kept sorted by disabled_nodes and then by total_cost, + * with cheaper paths at the front. Within this routine, that's simply a + * speed hack: doing it that way makes it more likely that we will reject + * an inferior path after a few comparisons, rather than many comparisons. * However, add_path_precheck relies on this ordering to exit early * when possible. * @@ -593,8 +634,13 @@ add_path(RelOptInfo *parent_rel, Path *new_path) } else { - /* new belongs after this old path if it has cost >= old's */ - if (new_path->total_cost >= old_path->total_cost) + /* + * new belongs after this old path if it has more disabled nodes + * or if it has the same number of nodes but a greater total cost + */ + if (new_path->disabled_nodes > old_path->disabled_nodes || + (new_path->disabled_nodes == old_path->disabled_nodes && + new_path->total_cost >= old_path->total_cost)) insert_at = foreach_current_index(p1) + 1; } @@ -639,7 +685,7 @@ add_path(RelOptInfo *parent_rel, Path *new_path) * so the required information has to be passed piecemeal. */ bool -add_path_precheck(RelOptInfo *parent_rel, +add_path_precheck(RelOptInfo *parent_rel, int disabled_nodes, Cost startup_cost, Cost total_cost, List *pathkeys, Relids required_outer) { @@ -658,6 +704,20 @@ add_path_precheck(RelOptInfo *parent_rel, Path *old_path = (Path *) lfirst(p1); PathKeysComparison keyscmp; + /* + * Since the pathlist is sorted by disabled_nodes and then by + * total_cost, we can stop looking once we reach a path with more + * disabled nodes, or the same number of disabled nodes plus a + * total_cost larger than the new path's. + */ + if (unlikely(old_path->disabled_nodes != disabled_nodes)) + { + if (disabled_nodes < old_path->disabled_nodes) + break; + } + else if (total_cost <= old_path->total_cost * STD_FUZZ_FACTOR) + break; + /* * We are looking for an old_path with the same parameterization (and * by assumption the same rowcount) that dominates the new path on @@ -666,39 +726,27 @@ add_path_precheck(RelOptInfo *parent_rel, * * Cost comparisons here should match compare_path_costs_fuzzily. */ - if (total_cost > old_path->total_cost * STD_FUZZ_FACTOR) + /* new path can win on startup cost only if consider_startup */ + if (startup_cost > old_path->startup_cost * STD_FUZZ_FACTOR || + !consider_startup) { - /* new path can win on startup cost only if consider_startup */ - if (startup_cost > old_path->startup_cost * STD_FUZZ_FACTOR || - !consider_startup) - { - /* new path loses on cost, so check pathkeys... */ - List *old_path_pathkeys; + /* new path loses on cost, so check pathkeys... */ + List *old_path_pathkeys; - old_path_pathkeys = old_path->param_info ? NIL : old_path->pathkeys; - keyscmp = compare_pathkeys(new_path_pathkeys, - old_path_pathkeys); - if (keyscmp == PATHKEYS_EQUAL || - keyscmp == PATHKEYS_BETTER2) + old_path_pathkeys = old_path->param_info ? NIL : old_path->pathkeys; + keyscmp = compare_pathkeys(new_path_pathkeys, + old_path_pathkeys); + if (keyscmp == PATHKEYS_EQUAL || + keyscmp == PATHKEYS_BETTER2) + { + /* new path does not win on pathkeys... */ + if (bms_equal(required_outer, PATH_REQ_OUTER(old_path))) { - /* new path does not win on pathkeys... */ - if (bms_equal(required_outer, PATH_REQ_OUTER(old_path))) - { - /* Found an old path that dominates the new one */ - return false; - } + /* Found an old path that dominates the new one */ + return false; } } } - else - { - /* - * Since the pathlist is sorted by total_cost, we can stop looking - * once we reach a path with a total_cost larger than the new - * path's. - */ - break; - } } return true; @@ -734,7 +782,7 @@ add_path_precheck(RelOptInfo *parent_rel, * produce the same number of rows. Neither do we need to consider startup * costs: parallelism is only used for plans that will be run to completion. * Therefore, this routine is much simpler than add_path: it needs to - * consider only pathkeys and total cost. + * consider only disabled nodes, pathkeys and total cost. * * As with add_path, we pfree paths that are found to be dominated by * another partial path; this requires that there be no other references to @@ -775,7 +823,15 @@ add_partial_path(RelOptInfo *parent_rel, Path *new_path) /* Unless pathkeys are incompatible, keep just one of the two paths. */ if (keyscmp != PATHKEYS_DIFFERENT) { - if (new_path->total_cost > old_path->total_cost * STD_FUZZ_FACTOR) + if (unlikely(new_path->disabled_nodes != old_path->disabled_nodes)) + { + if (new_path->disabled_nodes > old_path->disabled_nodes) + accept_new = false; + else + remove_old = true; + } + else if (new_path->total_cost > old_path->total_cost + * STD_FUZZ_FACTOR) { /* New path costs more; keep it only if pathkeys are better. */ if (keyscmp != PATHKEYS_BETTER1) @@ -862,8 +918,8 @@ add_partial_path(RelOptInfo *parent_rel, Path *new_path) * is surely a loser. */ bool -add_partial_path_precheck(RelOptInfo *parent_rel, Cost total_cost, - List *pathkeys) +add_partial_path_precheck(RelOptInfo *parent_rel, int disabled_nodes, + Cost total_cost, List *pathkeys) { ListCell *p1; @@ -906,8 +962,8 @@ add_partial_path_precheck(RelOptInfo *parent_rel, Cost total_cost, * partial path; the resulting plans, if run in parallel, will be run to * completion. */ - if (!add_path_precheck(parent_rel, total_cost, total_cost, pathkeys, - NULL)) + if (!add_path_precheck(parent_rel, disabled_nodes, total_cost, total_cost, + pathkeys, NULL)) return false; return true; @@ -1419,6 +1475,7 @@ create_merge_append_path(PlannerInfo *root, Relids required_outer) { MergeAppendPath *pathnode = makeNode(MergeAppendPath); + int input_disabled_nodes; Cost input_startup_cost; Cost input_total_cost; ListCell *l; @@ -1452,6 +1509,7 @@ create_merge_append_path(PlannerInfo *root, * Add up the sizes and costs of the input paths. */ pathnode->path.rows = 0; + input_disabled_nodes = 0; input_startup_cost = 0; input_total_cost = 0; foreach(l, subpaths) @@ -1468,6 +1526,7 @@ create_merge_append_path(PlannerInfo *root, if (pathkeys_contained_in(pathkeys, subpath->pathkeys)) { /* Subpath is adequately ordered, we won't need to sort it */ + input_disabled_nodes += subpath->disabled_nodes; input_startup_cost += subpath->startup_cost; input_total_cost += subpath->total_cost; } @@ -1479,12 +1538,14 @@ create_merge_append_path(PlannerInfo *root, cost_sort(&sort_path, root, pathkeys, + subpath->disabled_nodes, subpath->total_cost, subpath->rows, subpath->pathtarget->width, 0.0, work_mem, pathnode->limit_tuples); + input_disabled_nodes += sort_path.disabled_nodes; input_startup_cost += sort_path.startup_cost; input_total_cost += sort_path.total_cost; } @@ -1500,12 +1561,14 @@ create_merge_append_path(PlannerInfo *root, ((Path *) linitial(subpaths))->parallel_aware == pathnode->path.parallel_aware) { + pathnode->path.disabled_nodes = input_disabled_nodes; pathnode->path.startup_cost = input_startup_cost; pathnode->path.total_cost = input_total_cost; } else cost_merge_append(&pathnode->path, root, pathkeys, list_length(subpaths), + input_disabled_nodes, input_startup_cost, input_total_cost, pathnode->path.rows); @@ -1587,6 +1650,7 @@ create_material_path(RelOptInfo *rel, Path *subpath) pathnode->subpath = subpath; cost_material(&pathnode->path, + subpath->disabled_nodes, subpath->startup_cost, subpath->total_cost, subpath->rows, @@ -1633,6 +1697,10 @@ create_memoize_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath, */ pathnode->est_entries = 0; + /* we should not generate this path type when enable_memoize=false */ + Assert(enable_memoize); + pathnode->path.disabled_nodes = subpath->disabled_nodes; + /* * Add a small additional charge for caching the first entry. All the * harder calculations for rescans are performed in cost_memoize_rescan(). @@ -1732,6 +1800,7 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath, { pathnode->umethod = UNIQUE_PATH_NOOP; pathnode->path.rows = rel->rows; + pathnode->path.disabled_nodes = subpath->disabled_nodes; pathnode->path.startup_cost = subpath->startup_cost; pathnode->path.total_cost = subpath->total_cost; pathnode->path.pathkeys = subpath->pathkeys; @@ -1770,6 +1839,7 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath, { pathnode->umethod = UNIQUE_PATH_NOOP; pathnode->path.rows = rel->rows; + pathnode->path.disabled_nodes = subpath->disabled_nodes; pathnode->path.startup_cost = subpath->startup_cost; pathnode->path.total_cost = subpath->total_cost; pathnode->path.pathkeys = subpath->pathkeys; @@ -1797,6 +1867,7 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath, * Estimate cost for sort+unique implementation */ cost_sort(&sort_path, root, NIL, + subpath->disabled_nodes, subpath->total_cost, rel->rows, subpath->pathtarget->width, @@ -1834,6 +1905,7 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath, AGG_HASHED, NULL, numCols, pathnode->path.rows, NIL, + subpath->disabled_nodes, subpath->startup_cost, subpath->total_cost, rel->rows, @@ -1842,7 +1914,9 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath, if (sjinfo->semi_can_btree && sjinfo->semi_can_hash) { - if (agg_path.total_cost < sort_path.total_cost) + if (agg_path.disabled_nodes < sort_path.disabled_nodes || + (agg_path.disabled_nodes == sort_path.disabled_nodes && + agg_path.total_cost < sort_path.total_cost)) pathnode->umethod = UNIQUE_PATH_HASH; else pathnode->umethod = UNIQUE_PATH_SORT; @@ -1860,11 +1934,13 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath, if (pathnode->umethod == UNIQUE_PATH_HASH) { + pathnode->path.disabled_nodes = agg_path.disabled_nodes; pathnode->path.startup_cost = agg_path.startup_cost; pathnode->path.total_cost = agg_path.total_cost; } else { + pathnode->path.disabled_nodes = sort_path.disabled_nodes; pathnode->path.startup_cost = sort_path.startup_cost; pathnode->path.total_cost = sort_path.total_cost; } @@ -1888,6 +1964,7 @@ create_gather_merge_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath, Relids required_outer, double *rows) { GatherMergePath *pathnode = makeNode(GatherMergePath); + int input_disabled_nodes = 0; Cost input_startup_cost = 0; Cost input_total_cost = 0; @@ -1915,11 +1992,13 @@ create_gather_merge_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath, pathnode->path.pathkeys = pathkeys; pathnode->path.pathtarget = target ? target : rel->reltarget; + input_disabled_nodes += subpath->disabled_nodes; input_startup_cost += subpath->startup_cost; input_total_cost += subpath->total_cost; cost_gather_merge(pathnode, root, rel, pathnode->path.param_info, - input_startup_cost, input_total_cost, rows); + input_disabled_nodes, input_startup_cost, + input_total_cost, rows); return pathnode; } @@ -2227,7 +2306,8 @@ create_worktablescan_path(PlannerInfo *root, RelOptInfo *rel, ForeignPath * create_foreignscan_path(PlannerInfo *root, RelOptInfo *rel, PathTarget *target, - double rows, Cost startup_cost, Cost total_cost, + double rows, int disabled_nodes, + Cost startup_cost, Cost total_cost, List *pathkeys, Relids required_outer, Path *fdw_outerpath, @@ -2248,6 +2328,7 @@ create_foreignscan_path(PlannerInfo *root, RelOptInfo *rel, pathnode->path.parallel_safe = rel->consider_parallel; pathnode->path.parallel_workers = 0; pathnode->path.rows = rows; + pathnode->path.disabled_nodes = disabled_nodes; pathnode->path.startup_cost = startup_cost; pathnode->path.total_cost = total_cost; pathnode->path.pathkeys = pathkeys; @@ -2273,7 +2354,8 @@ create_foreignscan_path(PlannerInfo *root, RelOptInfo *rel, ForeignPath * create_foreign_join_path(PlannerInfo *root, RelOptInfo *rel, PathTarget *target, - double rows, Cost startup_cost, Cost total_cost, + double rows, int disabled_nodes, + Cost startup_cost, Cost total_cost, List *pathkeys, Relids required_outer, Path *fdw_outerpath, @@ -2300,6 +2382,7 @@ create_foreign_join_path(PlannerInfo *root, RelOptInfo *rel, pathnode->path.parallel_safe = rel->consider_parallel; pathnode->path.parallel_workers = 0; pathnode->path.rows = rows; + pathnode->path.disabled_nodes = disabled_nodes; pathnode->path.startup_cost = startup_cost; pathnode->path.total_cost = total_cost; pathnode->path.pathkeys = pathkeys; @@ -2325,7 +2408,8 @@ create_foreign_join_path(PlannerInfo *root, RelOptInfo *rel, ForeignPath * create_foreign_upper_path(PlannerInfo *root, RelOptInfo *rel, PathTarget *target, - double rows, Cost startup_cost, Cost total_cost, + double rows, int disabled_nodes, + Cost startup_cost, Cost total_cost, List *pathkeys, Path *fdw_outerpath, List *fdw_restrictinfo, @@ -2347,6 +2431,7 @@ create_foreign_upper_path(PlannerInfo *root, RelOptInfo *rel, pathnode->path.parallel_safe = rel->consider_parallel; pathnode->path.parallel_workers = 0; pathnode->path.rows = rows; + pathnode->path.disabled_nodes = disabled_nodes; pathnode->path.startup_cost = startup_cost; pathnode->path.total_cost = total_cost; pathnode->path.pathkeys = pathkeys; @@ -2734,6 +2819,7 @@ create_projection_path(PlannerInfo *root, * Set cost of plan as subpath's cost, adjusted for tlist replacement. */ pathnode->path.rows = subpath->rows; + pathnode->path.disabled_nodes = subpath->disabled_nodes; pathnode->path.startup_cost = subpath->startup_cost + (target->cost.startup - oldtarget->cost.startup); pathnode->path.total_cost = subpath->total_cost + @@ -2750,6 +2836,7 @@ create_projection_path(PlannerInfo *root, * evaluating the tlist. There is no qual to worry about. */ pathnode->path.rows = subpath->rows; + pathnode->path.disabled_nodes = subpath->disabled_nodes; pathnode->path.startup_cost = subpath->startup_cost + target->cost.startup; pathnode->path.total_cost = subpath->total_cost + @@ -2917,6 +3004,7 @@ create_set_projection_path(PlannerInfo *root, * This is slightly bizarre maybe, but it's what 9.6 did; we may revisit * this estimate later. */ + pathnode->path.disabled_nodes = subpath->disabled_nodes; pathnode->path.rows = subpath->rows * tlist_rows; pathnode->path.startup_cost = subpath->startup_cost + target->cost.startup; @@ -2967,6 +3055,7 @@ create_incremental_sort_path(PlannerInfo *root, cost_incremental_sort(&pathnode->path, root, pathkeys, presorted_keys, + subpath->disabled_nodes, subpath->startup_cost, subpath->total_cost, subpath->rows, @@ -3013,6 +3102,7 @@ create_sort_path(PlannerInfo *root, pathnode->subpath = subpath; cost_sort(&pathnode->path, root, pathkeys, + subpath->disabled_nodes, subpath->total_cost, subpath->rows, subpath->pathtarget->width, @@ -3065,6 +3155,7 @@ create_group_path(PlannerInfo *root, list_length(groupClause), numGroups, qual, + subpath->disabled_nodes, subpath->startup_cost, subpath->total_cost, subpath->rows); @@ -3122,6 +3213,7 @@ create_upper_unique_path(PlannerInfo *root, * all columns get compared at most of the tuples. (XXX probably this is * an overestimate.) */ + pathnode->path.disabled_nodes = subpath->disabled_nodes; pathnode->path.startup_cost = subpath->startup_cost; pathnode->path.total_cost = subpath->total_cost + cpu_operator_cost * subpath->rows * numCols; @@ -3200,6 +3292,7 @@ create_agg_path(PlannerInfo *root, aggstrategy, aggcosts, list_length(groupClause), numGroups, qual, + subpath->disabled_nodes, subpath->startup_cost, subpath->total_cost, subpath->rows, subpath->pathtarget->width); @@ -3308,6 +3401,7 @@ create_groupingsets_path(PlannerInfo *root, numGroupCols, rollup->numGroups, having_qual, + subpath->disabled_nodes, subpath->startup_cost, subpath->total_cost, subpath->rows, @@ -3333,7 +3427,7 @@ create_groupingsets_path(PlannerInfo *root, numGroupCols, rollup->numGroups, having_qual, - 0.0, 0.0, + 0, 0.0, 0.0, subpath->rows, subpath->pathtarget->width); if (!rollup->is_hashed) @@ -3342,7 +3436,7 @@ create_groupingsets_path(PlannerInfo *root, else { /* Account for cost of sort, but don't charge input cost again */ - cost_sort(&sort_path, root, NIL, + cost_sort(&sort_path, root, NIL, 0, 0.0, subpath->rows, subpath->pathtarget->width, @@ -3358,12 +3452,14 @@ create_groupingsets_path(PlannerInfo *root, numGroupCols, rollup->numGroups, having_qual, + sort_path.disabled_nodes, sort_path.startup_cost, sort_path.total_cost, sort_path.rows, subpath->pathtarget->width); } + pathnode->path.disabled_nodes += agg_path.disabled_nodes; pathnode->path.total_cost += agg_path.total_cost; pathnode->path.rows += agg_path.rows; } @@ -3395,6 +3491,7 @@ create_minmaxagg_path(PlannerInfo *root, { MinMaxAggPath *pathnode = makeNode(MinMaxAggPath); Cost initplan_cost; + int initplan_disabled_nodes = 0; ListCell *lc; /* The topmost generated Plan node will be a Result */ @@ -3419,12 +3516,14 @@ create_minmaxagg_path(PlannerInfo *root, { MinMaxAggInfo *mminfo = (MinMaxAggInfo *) lfirst(lc); + initplan_disabled_nodes += mminfo->path->disabled_nodes; initplan_cost += mminfo->pathcost; if (!mminfo->path->parallel_safe) pathnode->path.parallel_safe = false; } /* add tlist eval cost for each output row, plus cpu_tuple_cost */ + pathnode->path.disabled_nodes = initplan_disabled_nodes; pathnode->path.startup_cost = initplan_cost + target->cost.startup; pathnode->path.total_cost = initplan_cost + target->cost.startup + target->cost.per_tuple + cpu_tuple_cost; @@ -3517,6 +3616,7 @@ create_windowagg_path(PlannerInfo *root, cost_windowagg(&pathnode->path, root, windowFuncs, winclause, + subpath->disabled_nodes, subpath->startup_cost, subpath->total_cost, subpath->rows); @@ -3584,6 +3684,7 @@ create_setop_path(PlannerInfo *root, * Charge one cpu_operator_cost per comparison per input tuple. We assume * all columns get compared at most of the tuples. */ + pathnode->path.disabled_nodes = subpath->disabled_nodes; pathnode->path.startup_cost = subpath->startup_cost; pathnode->path.total_cost = subpath->total_cost + cpu_operator_cost * subpath->rows * list_length(distinctList); @@ -3683,6 +3784,7 @@ create_lockrows_path(PlannerInfo *root, RelOptInfo *rel, * possible refetches, but it's hard to say how much. For now, use * cpu_tuple_cost per row. */ + pathnode->path.disabled_nodes = subpath->disabled_nodes; pathnode->path.startup_cost = subpath->startup_cost; pathnode->path.total_cost = subpath->total_cost + cpu_tuple_cost * subpath->rows; @@ -3759,6 +3861,7 @@ create_modifytable_path(PlannerInfo *root, RelOptInfo *rel, * costs to change any higher-level planning choices. But we might want * to make it look better sometime. */ + pathnode->path.disabled_nodes = subpath->disabled_nodes; pathnode->path.startup_cost = subpath->startup_cost; pathnode->path.total_cost = subpath->total_cost; if (returningLists != NIL) @@ -3835,6 +3938,7 @@ create_limit_path(PlannerInfo *root, RelOptInfo *rel, subpath->parallel_safe; pathnode->path.parallel_workers = subpath->parallel_workers; pathnode->path.rows = subpath->rows; + pathnode->path.disabled_nodes = subpath->disabled_nodes; pathnode->path.startup_cost = subpath->startup_cost; pathnode->path.total_cost = subpath->total_cost; pathnode->path.pathkeys = subpath->pathkeys; diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index 14ccfc1ac1..540d021592 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -1658,6 +1658,7 @@ typedef struct Path /* estimated size/costs for path (see costsize.c for more info) */ Cardinality rows; /* estimated number of result tuples */ + int disabled_nodes; /* count of disabled nodes */ Cost startup_cost; /* cost expended before fetching any tuples */ Cost total_cost; /* total cost (assuming all tuples fetched) */ @@ -3333,6 +3334,7 @@ typedef struct typedef struct JoinCostWorkspace { /* Preliminary cost estimates --- must not be larger than final ones! */ + int disabled_nodes; Cost startup_cost; /* cost expended before fetching any tuples */ Cost total_cost; /* total cost (assuming all tuples fetched) */ diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h index 57861bfb44..854a782944 100644 --- a/src/include/optimizer/cost.h +++ b/src/include/optimizer/cost.h @@ -108,35 +108,42 @@ extern void cost_resultscan(Path *path, PlannerInfo *root, RelOptInfo *baserel, ParamPathInfo *param_info); extern void cost_recursive_union(Path *runion, Path *nrterm, Path *rterm); extern void cost_sort(Path *path, PlannerInfo *root, - List *pathkeys, Cost input_cost, double tuples, int width, + List *pathkeys, int disabled_nodes, + Cost input_cost, double tuples, int width, Cost comparison_cost, int sort_mem, double limit_tuples); extern void cost_incremental_sort(Path *path, PlannerInfo *root, List *pathkeys, int presorted_keys, + int input_disabled_nodes, Cost input_startup_cost, Cost input_total_cost, double input_tuples, int width, Cost comparison_cost, int sort_mem, double limit_tuples); extern void cost_append(AppendPath *apath); extern void cost_merge_append(Path *path, PlannerInfo *root, List *pathkeys, int n_streams, + int input_disabled_nodes, Cost input_startup_cost, Cost input_total_cost, double tuples); extern void cost_material(Path *path, + int input_disabled_nodes, Cost input_startup_cost, Cost input_total_cost, double tuples, int width); extern void cost_agg(Path *path, PlannerInfo *root, AggStrategy aggstrategy, const AggClauseCosts *aggcosts, int numGroupCols, double numGroups, List *quals, + int input_disabled_nodes, Cost input_startup_cost, Cost input_total_cost, double input_tuples, double input_width); extern void cost_windowagg(Path *path, PlannerInfo *root, List *windowFuncs, WindowClause *winclause, + int input_disabled_nodes, Cost input_startup_cost, Cost input_total_cost, double input_tuples); extern void cost_group(Path *path, PlannerInfo *root, int numGroupCols, double numGroups, List *quals, + int input_disabled_nodes, Cost input_startup_cost, Cost input_total_cost, double input_tuples); extern void initial_cost_nestloop(PlannerInfo *root, @@ -171,6 +178,7 @@ extern void cost_gather(GatherPath *path, PlannerInfo *root, RelOptInfo *rel, ParamPathInfo *param_info, double *rows); extern void cost_gather_merge(GatherMergePath *path, PlannerInfo *root, RelOptInfo *rel, ParamPathInfo *param_info, + int input_disabled_nodes, Cost input_startup_cost, Cost input_total_cost, double *rows); extern void cost_subplan(PlannerInfo *root, SubPlan *subplan, Plan *plan); diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h index f00bd55f39..1035e6560c 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -27,11 +27,12 @@ extern int compare_fractional_path_costs(Path *path1, Path *path2, double fraction); extern void set_cheapest(RelOptInfo *parent_rel); extern void add_path(RelOptInfo *parent_rel, Path *new_path); -extern bool add_path_precheck(RelOptInfo *parent_rel, +extern bool add_path_precheck(RelOptInfo *parent_rel, int disabled_nodes, Cost startup_cost, Cost total_cost, List *pathkeys, Relids required_outer); extern void add_partial_path(RelOptInfo *parent_rel, Path *new_path); extern bool add_partial_path_precheck(RelOptInfo *parent_rel, + int disabled_nodes, Cost total_cost, List *pathkeys); extern Path *create_seqscan_path(PlannerInfo *root, RelOptInfo *rel, @@ -124,7 +125,8 @@ extern Path *create_worktablescan_path(PlannerInfo *root, RelOptInfo *rel, Relids required_outer); extern ForeignPath *create_foreignscan_path(PlannerInfo *root, RelOptInfo *rel, PathTarget *target, - double rows, Cost startup_cost, Cost total_cost, + double rows, int disabled_nodes, + Cost startup_cost, Cost total_cost, List *pathkeys, Relids required_outer, Path *fdw_outerpath, @@ -132,7 +134,8 @@ extern ForeignPath *create_foreignscan_path(PlannerInfo *root, RelOptInfo *rel, List *fdw_private); extern ForeignPath *create_foreign_join_path(PlannerInfo *root, RelOptInfo *rel, PathTarget *target, - double rows, Cost startup_cost, Cost total_cost, + double rows, int disabled_nodes, + Cost startup_cost, Cost total_cost, List *pathkeys, Relids required_outer, Path *fdw_outerpath, @@ -140,7 +143,8 @@ extern ForeignPath *create_foreign_join_path(PlannerInfo *root, RelOptInfo *rel, List *fdw_private); extern ForeignPath *create_foreign_upper_path(PlannerInfo *root, RelOptInfo *rel, PathTarget *target, - double rows, Cost startup_cost, Cost total_cost, + double rows, int disabled_nodes, + Cost startup_cost, Cost total_cost, List *pathkeys, Path *fdw_outerpath, List *fdw_restrictinfo, diff --git a/src/test/isolation/specs/horizons.spec b/src/test/isolation/specs/horizons.spec index d5239ff228..3f987f943d 100644 --- a/src/test/isolation/specs/horizons.spec +++ b/src/test/isolation/specs/horizons.spec @@ -40,7 +40,6 @@ session pruner setup { SET enable_seqscan = false; - SET enable_indexscan = false; SET enable_bitmapscan = false; } diff --git a/src/test/regress/expected/btree_index.out b/src/test/regress/expected/btree_index.out index 510646cbce..092233cc9d 100644 --- a/src/test/regress/expected/btree_index.out +++ b/src/test/regress/expected/btree_index.out @@ -332,11 +332,13 @@ select proname from pg_proc where proname ilike '00%foo' order by 1; explain (costs off) select proname from pg_proc where proname ilike 'ri%foo' order by 1; - QUERY PLAN ------------------------------------------------------------------ - Index Only Scan using pg_proc_proname_args_nsp_index on pg_proc - Filter: (proname ~~* 'ri%foo'::text) -(2 rows) + QUERY PLAN +---------------------------------------------- + Sort + Sort Key: proname + -> Seq Scan on pg_proc + Filter: (proname ~~* 'ri%foo'::text) +(4 rows) reset enable_seqscan; reset enable_indexscan; diff --git a/src/test/regress/expected/select_parallel.out b/src/test/regress/expected/select_parallel.out index a61c138bd0..bdd675319f 100644 --- a/src/test/regress/expected/select_parallel.out +++ b/src/test/regress/expected/select_parallel.out @@ -538,15 +538,17 @@ explain (costs off) ------------------------------------------------------------ Aggregate -> Nested Loop - -> Seq Scan on tenk2 - Filter: (thousand = 0) + -> Gather + Workers Planned: 4 + -> Parallel Seq Scan on tenk2 + Filter: (thousand = 0) -> Gather Workers Planned: 4 -> Parallel Bitmap Heap Scan on tenk1 Recheck Cond: (hundred > 1) -> Bitmap Index Scan on tenk1_hundred Index Cond: (hundred > 1) -(10 rows) +(12 rows) select count(*) from tenk1, tenk2 where tenk1.hundred > 1 and tenk2.thousand=0; count