From: Robert Haas Date: Fri, 24 Oct 2025 19:11:47 +0000 (-0400) Subject: Allow for plugin control over path generation strategies. X-Git-Url: http://git.postgresql.org/gitweb/static/gitweb.js?a=commitdiff_plain;h=4bac72516fc801886a43c0d2ebb88b1a8bff9c8a;p=users%2Frhaas%2Fpostgres.git Allow for plugin control over path generation strategies. Each RelOptInfo now has a pgs_mask member which is a mask of acceptable strategies. For most rels, this is populated from PlannerGlobal's default_pgs_mask, which is computed from the values of the enable_* GUCs at the start of planning. For baserels, get_relation_info_hook can be used to adjust pgs_mask for each new RelOptInfo, at least for rels of type RTE_RELATION. Adjusting pgs_mask is less useful for other types of rels, but if it proves to be necessary, we can revisit the way this hook works or add a new one. For joinrels, two new hooks are added. joinrel_setup_hook is called each time a joinrel is created, and one thing that can be done from that hook is to manipulate pgs_mask for the new joinrel. join_path_setup_hook is called each time we're about to add paths to a joinrel by considering some particular combination of an outer rel, an inner rel, and a join type. It can modify the pgs_mask propagated into JoinPathExtraData to restrict strategy choice for that paricular combination of rels. To make joinrel_setup_hook work as intended, the existing calls to build_joinrel_partition_info are moved later in the calling functions; this is because that function checks whether the rel's pgs_mask includes PGS_CONSIDER_PARTITIONWISE, so we want it to only be called after plugins have had a chance to alter pgs_mask. Upper rels currently inherit pgs_mask from the input relation. It's unclear that this is the most useful behavior, but at the moment there are no hooks to allow the mask to be set in any other way. --- diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 928b8d84ad..8e9dde3d19 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -954,7 +954,7 @@ set_tablesample_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry * bms_membership(root->all_query_rels) != BMS_SINGLETON) && !(GetTsmRoutine(rte->tablesample->tsmhandler)->repeatable_across_scans)) { - path = (Path *) create_material_path(rel, path); + path = (Path *) create_material_path(rel, path, true); } add_path(rel, path); diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c index a39cc793b4..51940aec82 100644 --- a/src/backend/optimizer/path/costsize.c +++ b/src/backend/optimizer/path/costsize.c @@ -275,6 +275,7 @@ cost_seqscan(Path *path, PlannerInfo *root, double spc_seq_page_cost; QualCost qpqual_cost; Cost cpu_per_tuple; + uint64 enable_mask = PGS_SEQSCAN; /* Should only be applied to base relations */ Assert(baserel->relid > 0); @@ -327,8 +328,11 @@ cost_seqscan(Path *path, PlannerInfo *root, */ path->rows = clamp_row_est(path->rows / parallel_divisor); } + else + enable_mask |= PGS_CONSIDER_NONPARTIAL; - path->disabled_nodes = enable_seqscan ? 0 : 1; + path->disabled_nodes = + (baserel->pgs_mask & enable_mask) == enable_mask ? 0 : 1; path->startup_cost = startup_cost; path->total_cost = startup_cost + cpu_run_cost + disk_run_cost; } @@ -354,6 +358,7 @@ cost_samplescan(Path *path, PlannerInfo *root, spc_page_cost; QualCost qpqual_cost; Cost cpu_per_tuple; + uint64 enable_mask = 0; /* Should only be applied to base relations with tablesample clauses */ Assert(baserel->relid > 0); @@ -401,7 +406,11 @@ 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; + if (path->parallel_workers == 0) + enable_mask |= PGS_CONSIDER_NONPARTIAL; + + path->disabled_nodes = + (baserel->pgs_mask & enable_mask) == enable_mask ? 0 : 1; path->startup_cost = startup_cost; path->total_cost = startup_cost + run_cost; } @@ -440,7 +449,8 @@ 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.disabled_nodes = path->subpath->disabled_nodes + + ((rel->pgs_mask & PGS_GATHER) != 0 ? 0 : 1); path->path.startup_cost = startup_cost; path->path.total_cost = (startup_cost + run_cost); } @@ -506,8 +516,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.disabled_nodes = path->subpath->disabled_nodes + + ((rel->pgs_mask & PGS_GATHER_MERGE) != 0 ? 0 : 1); path->path.startup_cost = startup_cost + input_startup_cost; path->path.total_cost = (startup_cost + run_cost + input_total_cost); } @@ -557,6 +567,7 @@ cost_index(IndexPath *path, PlannerInfo *root, double loop_count, double pages_fetched; double rand_heap_pages; double index_pages; + uint64 enable_mask; /* Should only be applied to base relations */ Assert(IsA(baserel, RelOptInfo) && @@ -588,8 +599,11 @@ cost_index(IndexPath *path, PlannerInfo *root, double loop_count, path->indexclauses); } - /* we don't need to check enable_indexonlyscan; indxpath.c does that */ - path->path.disabled_nodes = enable_indexscan ? 0 : 1; + /* is this scan type disabled? */ + enable_mask = (indexonly ? PGS_INDEXONLYSCAN : PGS_INDEXSCAN) + | (path->path.parallel_workers == 0 ? PGS_CONSIDER_NONPARTIAL : 0); + path->path.disabled_nodes = + (baserel->pgs_mask & enable_mask) == enable_mask ? 0 : 1; /* * Call index-access-method-specific code to estimate the processing cost @@ -1010,6 +1024,7 @@ cost_bitmap_heap_scan(Path *path, PlannerInfo *root, RelOptInfo *baserel, double spc_seq_page_cost, spc_random_page_cost; double T; + uint64 enable_mask = PGS_BITMAPSCAN; /* Should only be applied to base relations */ Assert(IsA(baserel, RelOptInfo)); @@ -1075,6 +1090,8 @@ cost_bitmap_heap_scan(Path *path, PlannerInfo *root, RelOptInfo *baserel, path->rows = clamp_row_est(path->rows / parallel_divisor); } + else + enable_mask |= PGS_CONSIDER_NONPARTIAL; run_cost += cpu_run_cost; @@ -1083,7 +1100,8 @@ 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->disabled_nodes = + (baserel->pgs_mask & enable_mask) == enable_mask ? 0 : 1; path->startup_cost = startup_cost; path->total_cost = startup_cost + run_cost; } @@ -1240,6 +1258,7 @@ cost_tidscan(Path *path, PlannerInfo *root, double ntuples; ListCell *l; double spc_random_page_cost; + uint64 enable_mask = 0; /* Should only be applied to base relations */ Assert(baserel->relid > 0); @@ -1261,10 +1280,10 @@ cost_tidscan(Path *path, PlannerInfo *root, /* * 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. + * should be generating a TID scan only if TID scans are allowed. + * Also, if CurrentOfExpr is the qual, there should be only one. */ - Assert(enable_tidscan || IsA(qual, CurrentOfExpr)); + Assert((baserel->pgs_mask & PGS_TIDSCAN) != 0 || IsA(qual, CurrentOfExpr)); Assert(list_length(tidquals) == 1 || !IsA(qual, CurrentOfExpr)); if (IsA(qual, ScalarArrayOpExpr)) @@ -1316,10 +1335,14 @@ cost_tidscan(Path *path, PlannerInfo *root, /* * 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. + * either when baserel->pgs_mask includes PGS_TIDSCAN or when the TID scan + * is the only legal path, so we only need to consider the effects of + * PGS_CONSIDER_NONPARTIAL here. */ - path->disabled_nodes = 0; + if (path->parallel_workers == 0) + enable_mask |= PGS_CONSIDER_NONPARTIAL; + path->disabled_nodes = + (baserel->pgs_mask & enable_mask) != enable_mask ? 1 : 0; path->startup_cost = startup_cost; path->total_cost = startup_cost + run_cost; } @@ -1350,6 +1373,7 @@ cost_tidrangescan(Path *path, PlannerInfo *root, double nseqpages; double spc_random_page_cost; double spc_seq_page_cost; + uint64 enable_mask = PGS_TIDSCAN; /* Should only be applied to base relations */ Assert(baserel->relid > 0); @@ -1428,8 +1452,15 @@ cost_tidrangescan(Path *path, PlannerInfo *root, path->rows = clamp_row_est(path->rows / parallel_divisor); } - /* we should not generate this path type when enable_tidscan=false */ - Assert(enable_tidscan); + /* + * We should not generate this path type when PGS_TIDSCAN is unset, but we + * might need to disable this path due to PGS_CONSIDER_NONPARTIAL. + */ + Assert((baserel->pgs_mask & PGS_TIDSCAN) != 0); + if (path->parallel_workers == 0) + enable_mask |= PGS_CONSIDER_NONPARTIAL; + path->disabled_nodes = + (baserel->pgs_mask & enable_mask) != enable_mask ? 1 : 0; path->disabled_nodes = 0; path->startup_cost = startup_cost; path->total_cost = startup_cost + cpu_run_cost + disk_run_cost; @@ -1453,6 +1484,7 @@ cost_subqueryscan(SubqueryScanPath *path, PlannerInfo *root, List *qpquals; QualCost qpqual_cost; Cost cpu_per_tuple; + uint64 enable_mask = 0; /* Should only be applied to base relations that are subqueries */ Assert(baserel->relid > 0); @@ -1483,7 +1515,10 @@ 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; + if (path->path.parallel_workers == 0) + enable_mask |= PGS_CONSIDER_NONPARTIAL; + path->path.disabled_nodes = path->subpath->disabled_nodes + + (((baserel->pgs_mask & enable_mask) != enable_mask) ? 1 : 0); path->path.startup_cost = path->subpath->startup_cost; path->path.total_cost = path->subpath->total_cost; @@ -1534,6 +1569,7 @@ cost_functionscan(Path *path, PlannerInfo *root, Cost cpu_per_tuple; RangeTblEntry *rte; QualCost exprcost; + uint64 enable_mask = 0; /* Should only be applied to base relations that are functions */ Assert(baserel->relid > 0); @@ -1574,7 +1610,10 @@ 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; + if (path->parallel_workers == 0) + enable_mask |= PGS_CONSIDER_NONPARTIAL; + path->disabled_nodes = + (baserel->pgs_mask & enable_mask) != enable_mask ? 1 : 0; path->startup_cost = startup_cost; path->total_cost = startup_cost + run_cost; } @@ -1596,6 +1635,7 @@ cost_tablefuncscan(Path *path, PlannerInfo *root, Cost cpu_per_tuple; RangeTblEntry *rte; QualCost exprcost; + uint64 enable_mask = 0; /* Should only be applied to base relations that are functions */ Assert(baserel->relid > 0); @@ -1631,7 +1671,10 @@ 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; + if (path->parallel_workers == 0) + enable_mask |= PGS_CONSIDER_NONPARTIAL; + path->disabled_nodes = + (baserel->pgs_mask & enable_mask) != enable_mask ? 1 : 0; path->startup_cost = startup_cost; path->total_cost = startup_cost + run_cost; } @@ -1651,6 +1694,7 @@ cost_valuesscan(Path *path, PlannerInfo *root, Cost run_cost = 0; QualCost qpqual_cost; Cost cpu_per_tuple; + uint64 enable_mask = 0; /* Should only be applied to base relations that are values lists */ Assert(baserel->relid > 0); @@ -1679,7 +1723,10 @@ 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; + if (path->parallel_workers == 0) + enable_mask |= PGS_CONSIDER_NONPARTIAL; + path->disabled_nodes = + (baserel->pgs_mask & enable_mask) != enable_mask ? 1 : 0; path->startup_cost = startup_cost; path->total_cost = startup_cost + run_cost; } @@ -1702,6 +1749,7 @@ cost_ctescan(Path *path, PlannerInfo *root, Cost run_cost = 0; QualCost qpqual_cost; Cost cpu_per_tuple; + uint64 enable_mask = 0; /* Should only be applied to base relations that are CTEs */ Assert(baserel->relid > 0); @@ -1727,7 +1775,10 @@ 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; + if (path->parallel_workers == 0) + enable_mask |= PGS_CONSIDER_NONPARTIAL; + path->disabled_nodes = + (baserel->pgs_mask & enable_mask) != enable_mask ? 1 : 0; path->startup_cost = startup_cost; path->total_cost = startup_cost + run_cost; } @@ -1744,6 +1795,7 @@ cost_namedtuplestorescan(Path *path, PlannerInfo *root, Cost run_cost = 0; QualCost qpqual_cost; Cost cpu_per_tuple; + uint64 enable_mask = 0; /* Should only be applied to base relations that are Tuplestores */ Assert(baserel->relid > 0); @@ -1765,7 +1817,10 @@ 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; + if (path->parallel_workers == 0) + enable_mask |= PGS_CONSIDER_NONPARTIAL; + path->disabled_nodes = + (baserel->pgs_mask & enable_mask) != enable_mask ? 1 : 0; path->startup_cost = startup_cost; path->total_cost = startup_cost + run_cost; } @@ -1782,6 +1837,7 @@ cost_resultscan(Path *path, PlannerInfo *root, Cost run_cost = 0; QualCost qpqual_cost; Cost cpu_per_tuple; + uint64 enable_mask = 0; /* Should only be applied to RTE_RESULT base relations */ Assert(baserel->relid > 0); @@ -1800,7 +1856,10 @@ 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; + if (path->parallel_workers == 0) + enable_mask |= PGS_CONSIDER_NONPARTIAL; + path->disabled_nodes = + (baserel->pgs_mask & enable_mask) != enable_mask ? 1 : 0; path->startup_cost = startup_cost; path->total_cost = startup_cost + run_cost; } @@ -1818,6 +1877,7 @@ cost_recursive_union(Path *runion, Path *nrterm, Path *rterm) Cost startup_cost; Cost total_cost; double total_rows; + uint64 enable_mask = 0; /* We probably have decent estimates for the non-recursive term */ startup_cost = nrterm->startup_cost; @@ -1840,7 +1900,10 @@ 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; + if (runion->parallel_workers == 0) + enable_mask |= PGS_CONSIDER_NONPARTIAL; + runion->disabled_nodes = + (runion->parent->pgs_mask & enable_mask) != enable_mask ? 1 : 0; runion->startup_cost = startup_cost; runion->total_cost = total_cost; runion->rows = total_rows; @@ -2110,7 +2173,11 @@ cost_incremental_sort(Path *path, path->rows = input_tuples; - /* should not generate these paths when enable_incremental_sort=false */ + /* + * We should not generate these paths when enable_incremental_sort=false. + * We can ignore PGS_CONSIDER_NONPARTIAL here, because if it's relevant, + * it will have already affected the input path. + */ Assert(enable_incremental_sort); path->disabled_nodes = input_disabled_nodes; @@ -2148,6 +2215,10 @@ cost_sort(Path *path, PlannerInfo *root, startup_cost += input_cost; + /* + * We can ignore PGS_CONSIDER_NONPARTIAL here, because if it's relevant, + * it will have already affected the input path. + */ path->rows = tuples; path->disabled_nodes = input_disabled_nodes + (enable_sort ? 0 : 1); path->startup_cost = startup_cost; @@ -2239,9 +2310,15 @@ append_nonpartial_cost(List *subpaths, int numpaths, int parallel_workers) void cost_append(AppendPath *apath, PlannerInfo *root) { + RelOptInfo *rel = apath->path.parent; ListCell *l; + uint64 enable_mask = PGS_APPEND; + + if (apath->path.parallel_workers == 0) + enable_mask |= PGS_CONSIDER_NONPARTIAL; - apath->path.disabled_nodes = 0; + apath->path.disabled_nodes = + (rel->pgs_mask & enable_mask) == enable_mask ? 0 : 1; apath->path.startup_cost = 0; apath->path.total_cost = 0; apath->path.rows = 0; @@ -2451,11 +2528,16 @@ cost_merge_append(Path *path, PlannerInfo *root, Cost input_startup_cost, Cost input_total_cost, double tuples) { + RelOptInfo *rel = path->parent; Cost startup_cost = 0; Cost run_cost = 0; Cost comparison_cost; double N; double logN; + uint64 enable_mask = PGS_MERGE_APPEND; + + if (path->parallel_workers == 0) + enable_mask |= PGS_CONSIDER_NONPARTIAL; /* * Avoid log(0)... @@ -2478,7 +2560,9 @@ cost_merge_append(Path *path, PlannerInfo *root, */ run_cost += cpu_tuple_cost * APPEND_CPU_COST_MULTIPLIER * tuples; - path->disabled_nodes = input_disabled_nodes; + path->disabled_nodes = + (rel->pgs_mask & enable_mask) == enable_mask ? 0 : 1; + path->disabled_nodes += input_disabled_nodes; path->startup_cost = startup_cost + input_startup_cost; path->total_cost = startup_cost + run_cost + input_total_cost; } @@ -2497,7 +2581,7 @@ cost_merge_append(Path *path, PlannerInfo *root, */ void cost_material(Path *path, - int input_disabled_nodes, + bool enabled, int input_disabled_nodes, Cost input_startup_cost, Cost input_total_cost, double tuples, int width) { @@ -2506,6 +2590,11 @@ cost_material(Path *path, double nbytes = relation_byte_size(tuples, width); double work_mem_bytes = work_mem * (Size) 1024; + if (path->parallel_workers == 0 && + path->parent != NULL && + (path->parent->pgs_mask & PGS_CONSIDER_NONPARTIAL) == 0) + enabled = false; + path->rows = tuples; /* @@ -2535,7 +2624,7 @@ cost_material(Path *path, run_cost += seq_page_cost * npages; } - path->disabled_nodes = input_disabled_nodes + (enable_material ? 0 : 1); + path->disabled_nodes = input_disabled_nodes + (enabled ? 0 : 1); path->startup_cost = startup_cost; path->total_cost = startup_cost + run_cost; } @@ -3287,7 +3376,7 @@ cost_group(Path *path, PlannerInfo *root, */ void initial_cost_nestloop(PlannerInfo *root, JoinCostWorkspace *workspace, - JoinType jointype, + JoinType jointype, uint64 enable_mask, Path *outer_path, Path *inner_path, JoinPathExtraData *extra) { @@ -3301,7 +3390,7 @@ initial_cost_nestloop(PlannerInfo *root, JoinCostWorkspace *workspace, Cost inner_rescan_run_cost; /* Count up disabled nodes. */ - disabled_nodes = enable_nestloop ? 0 : 1; + disabled_nodes = (extra->pgs_mask & enable_mask) == enable_mask ? 0 : 1; disabled_nodes += inner_path->disabled_nodes; disabled_nodes += outer_path->disabled_nodes; @@ -3701,7 +3790,19 @@ initial_cost_mergejoin(PlannerInfo *root, JoinCostWorkspace *workspace, Assert(outerstartsel <= outerendsel); Assert(innerstartsel <= innerendsel); - disabled_nodes = enable_mergejoin ? 0 : 1; + /* + * We don't decide whether to materialize the inner path until we get to + * final_cost_mergejoin(), so we don't know whether to check the pgs_mask + * again PGS_MERGEJOIN_PLAIN or PGS_MERGEJOIN_MATERIALIZE. Instead, we + * just account for any child nodes here and assume that this node is not + * itslef disabled; we can sort out the details in final_cost_mergejoin(). + * + * (We could be more precise here by setting disabled_nodes to 1 at this + * stage if both PGS_MERGEJOIN_PLAIN and PGS_MERGEJOIN_MATERIALIZE are + * disabled, but that seems to against the idea of making this function + * produce a quick, optimistic approximation of the final cost.) + */ + disabled_nodes = 0; /* cost of source data */ @@ -3880,9 +3981,7 @@ final_cost_mergejoin(PlannerInfo *root, MergePath *path, double mergejointuples, rescannedtuples; double rescanratio; - - /* Set the number of disabled nodes. */ - path->jpath.path.disabled_nodes = workspace->disabled_nodes; + uint64 enable_mask = 0; /* Protect some assumptions below that rowcounts aren't zero */ if (inner_path_rows <= 0) @@ -4012,16 +4111,20 @@ final_cost_mergejoin(PlannerInfo *root, MergePath *path, path->materialize_inner = false; /* - * Prefer materializing if it looks cheaper, unless the user has asked to - * suppress materialization. + * If merge joins with materialization are enabled, then choose + * materialization if either (a) it looks cheaper or (b) merge joins + * without materialization are disabled. */ - else if (enable_material && mat_inner_cost < bare_inner_cost) + else if ((extra->pgs_mask & PGS_MERGEJOIN_MATERIALIZE) != 0 && + (mat_inner_cost < bare_inner_cost || + (extra->pgs_mask & PGS_MERGEJOIN_PLAIN) == 0)) path->materialize_inner = true; /* - * Even if materializing doesn't look cheaper, we *must* do it if the - * inner path is to be used directly (without sorting) and it doesn't - * support mark/restore. + * Regardless of what plan shapes are enabled and what the costs seem to + * be, we *must* materialize it if the inner path is to be used directly + * (without sorting) and it doesn't support mark/restore. Planner failure + * is not an option! * * Since the inner side must be ordered, and only Sorts and IndexScans can * create order to begin with, and they both support mark/restore, you @@ -4029,10 +4132,6 @@ final_cost_mergejoin(PlannerInfo *root, MergePath *path, * merge joins can *preserve* the order of their inputs, so they can be * selected as the input of a mergejoin, and they don't support * mark/restore at present. - * - * We don't test the value of enable_material here, because - * materialization is required for correctness in this case, and turning - * it off does not entitle us to deliver an invalid plan. */ else if (innersortkeys == NIL && !ExecSupportsMarkRestore(inner_path)) @@ -4046,10 +4145,11 @@ final_cost_mergejoin(PlannerInfo *root, MergePath *path, * though. * * Since materialization is a performance optimization in this case, - * rather than necessary for correctness, we skip it if enable_material is - * off. + * rather than necessary for correctness, we skip it if materialization is + * switched off. */ - else if (enable_material && innersortkeys != NIL && + else if ((extra->pgs_mask & PGS_MERGEJOIN_MATERIALIZE) != 0 && + innersortkeys != NIL && relation_byte_size(inner_path_rows, inner_path->pathtarget->width) > work_mem * (Size) 1024) @@ -4057,11 +4157,29 @@ final_cost_mergejoin(PlannerInfo *root, MergePath *path, else path->materialize_inner = false; - /* Charge the right incremental cost for the chosen case */ + /* Get the number of disabled nodes, not yet including this one. */ + path->jpath.path.disabled_nodes = workspace->disabled_nodes; + + /* + * Charge the right incremental cost for the chosen case, and update + * enable_mask as appropriate. + */ if (path->materialize_inner) + { run_cost += mat_inner_cost; + enable_mask |= PGS_MERGEJOIN_MATERIALIZE; + } else + { run_cost += bare_inner_cost; + enable_mask |= PGS_MERGEJOIN_PLAIN; + } + + /* Incremental count of disabled nodes if this node is disabled. */ + if (path->jpath.path.parallel_workers == 0) + enable_mask |= PGS_CONSIDER_NONPARTIAL; + if ((extra->pgs_mask & enable_mask) != enable_mask) + ++path->jpath.path.disabled_nodes; /* CPU costs */ @@ -4199,9 +4317,13 @@ initial_cost_hashjoin(PlannerInfo *root, JoinCostWorkspace *workspace, int numbatches; int num_skew_mcvs; size_t space_allowed; /* unused */ + uint64 enable_mask = PGS_HASHJOIN; + + if (outer_path->parallel_workers == 0) + enable_mask |= PGS_CONSIDER_NONPARTIAL; /* Count up disabled nodes. */ - disabled_nodes = enable_hashjoin ? 0 : 1; + disabled_nodes = (extra->pgs_mask & enable_mask) == enable_mask ? 0 : 1; disabled_nodes += inner_path->disabled_nodes; disabled_nodes += outer_path->disabled_nodes; diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c index 5d4f81ee77..8922b68033 100644 --- a/src/backend/optimizer/path/indxpath.c +++ b/src/backend/optimizer/path/indxpath.c @@ -2232,8 +2232,8 @@ check_index_only(RelOptInfo *rel, IndexOptInfo *index) ListCell *lc; int i; - /* Index-only scans must be enabled */ - if (!enable_indexonlyscan) + /* If we're not allowed to consider index-only scans, give up now */ + if ((rel->pgs_mask & PGS_CONSIDER_INDEXONLY) == 0) return false; /* diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c index ea5b641518..388d8456ff 100644 --- a/src/backend/optimizer/path/joinpath.c +++ b/src/backend/optimizer/path/joinpath.c @@ -29,8 +29,9 @@ #include "utils/lsyscache.h" #include "utils/typcache.h" -/* Hook for plugins to get control in add_paths_to_joinrel() */ +/* Hooks for plugins to get control in add_paths_to_joinrel() */ set_join_pathlist_hook_type set_join_pathlist_hook = NULL; +join_path_setup_hook_type join_path_setup_hook = NULL; /* * Paths parameterized by a parent rel can be considered to be parameterized @@ -151,6 +152,7 @@ add_paths_to_joinrel(PlannerInfo *root, extra.mergeclause_list = NIL; extra.sjinfo = sjinfo; extra.param_source_rels = NULL; + extra.pgs_mask = joinrel->pgs_mask; /* * See if the inner relation is provably unique for this outer rel. @@ -207,13 +209,38 @@ add_paths_to_joinrel(PlannerInfo *root, if (jointype == JOIN_UNIQUE_OUTER || jointype == JOIN_UNIQUE_INNER) jointype = JOIN_INNER; + /* + * Give extensions a chance to take control. In particular, an extension + * might want to modify extra.pgs_mask. It's possible to override pgs_mask + * on a query-wide basis using join_search_hook, or for a particular + * relation using joinrel_setup_hook, but extensions that want to provide + * different advice for the same joinrel based on the choice of innerrel + * and outerrel will need to use this hook. + * + * A very simple way for an extension to use this hook is to set + * extra.pgs_mask = 0, if it simply doesn't want any of the paths + * generated by this call to add_paths_to_joinrel() to be selected. An + * extension could use this technique to constrain the join order, since + * it could thereby arrange to reject all paths from join orders that it + * does not like. An extension can also selectively clear bits from + * extra.pgs_mask to rule out specific techniques for specific joins, or + * even replace the mask entirely. + * + * NB: Below this point, this function should be careful to reference + * extra.pgs_mask rather than rel->pgs_mask to avoid disregarding any + * changes made by the hook we're about to call. + */ + if (join_path_setup_hook) + join_path_setup_hook(root, joinrel, outerrel, innerrel, + jointype, &extra); + /* * Find potential mergejoin clauses. We can skip this if we are not * interested in doing a mergejoin. However, mergejoin may be our only - * way of implementing a full outer join, so override enable_mergejoin if - * it's a full join. + * way of implementing a full outer join, so in that case we don't care + * whether mergejoins are disabled. */ - if (enable_mergejoin || jointype == JOIN_FULL) + if ((extra.pgs_mask & PGS_MERGEJOIN_ANY) != 0 || jointype == JOIN_FULL) extra.mergeclause_list = select_mergejoin_clauses(root, joinrel, outerrel, @@ -321,10 +348,10 @@ add_paths_to_joinrel(PlannerInfo *root, /* * 4. Consider paths where both outer and inner relations must be hashed - * before being joined. As above, disregard enable_hashjoin for full - * joins, because there may be no other alternative. + * before being joined. As above, when it's a full join, we must try this + * even when the path type is disabled, because it may be our only option. */ - if (enable_hashjoin || jointype == JOIN_FULL) + if ((extra.pgs_mask & PGS_HASHJOIN) != 0 || jointype == JOIN_FULL) hash_inner_and_outer(root, joinrel, outerrel, innerrel, jointype, &extra); @@ -333,7 +360,7 @@ add_paths_to_joinrel(PlannerInfo *root, * to the same server and assigned to the same user to check access * permissions as, give the FDW a chance to push down joins. */ - if (joinrel->fdwroutine && + if ((extra.pgs_mask & PGS_FOREIGNJOIN) != 0 && joinrel->fdwroutine && joinrel->fdwroutine->GetForeignJoinPaths) joinrel->fdwroutine->GetForeignJoinPaths(root, joinrel, outerrel, innerrel, @@ -342,8 +369,13 @@ add_paths_to_joinrel(PlannerInfo *root, /* * 6. Finally, give extensions a chance to manipulate the path list. They * could add new paths (such as CustomPaths) by calling add_path(), or - * add_partial_path() if parallel aware. They could also delete or modify - * paths added by the core code. + * add_partial_path() if parallel aware. + * + * In theory, extensions could also use this hook to delete or modify + * paths added by the core code, but in practice this is difficult to make + * work, since it's too late to get back any paths that have already been + * discarded by add_path() or add_partial_path(). If you're trying to + * suppress paths, consider using join_path_setup_hook instead. */ if (set_join_pathlist_hook) set_join_pathlist_hook(root, joinrel, outerrel, innerrel, @@ -690,7 +722,7 @@ get_memoize_path(PlannerInfo *root, RelOptInfo *innerrel, List *ph_lateral_vars; /* Obviously not if it's disabled */ - if (!enable_memoize) + if ((extra->pgs_mask & PGS_NESTLOOP_MEMOIZE) == 0) return NULL; /* @@ -845,6 +877,7 @@ try_nestloop_path(PlannerInfo *root, Path *inner_path, List *pathkeys, JoinType jointype, + uint64 nestloop_subtype, JoinPathExtraData *extra) { Relids required_outer; @@ -927,6 +960,7 @@ try_nestloop_path(PlannerInfo *root, * methodology worthwhile. */ initial_cost_nestloop(root, &workspace, jointype, + nestloop_subtype | PGS_CONSIDER_NONPARTIAL, outer_path, inner_path, extra); if (add_path_precheck(joinrel, workspace.disabled_nodes, @@ -964,6 +998,7 @@ try_partial_nestloop_path(PlannerInfo *root, Path *inner_path, List *pathkeys, JoinType jointype, + uint64 nestloop_subtype, JoinPathExtraData *extra) { JoinCostWorkspace workspace; @@ -1011,7 +1046,7 @@ try_partial_nestloop_path(PlannerInfo *root, * Before creating a path, get a quick lower bound on what it is likely to * cost. Bail out right away if it looks terrible. */ - initial_cost_nestloop(root, &workspace, jointype, + initial_cost_nestloop(root, &workspace, jointype, nestloop_subtype, outer_path, inner_path, extra); if (!add_partial_path_precheck(joinrel, workspace.disabled_nodes, workspace.total_cost, pathkeys)) @@ -1859,14 +1894,14 @@ match_unsorted_outer(PlannerInfo *root, if (nestjoinOK) { /* - * Consider materializing the cheapest inner path, unless - * enable_material is off or the path in question materializes its - * output anyway. + * Consider materializing the cheapest inner path, unless that is + * disabled or the path in question materializes its output anyway. */ - if (enable_material && inner_cheapest_total != NULL && + if ((extra->pgs_mask & PGS_NESTLOOP_MATERIALIZE) != 0 && + inner_cheapest_total != NULL && !ExecMaterializesOutput(inner_cheapest_total->pathtype)) matpath = (Path *) - create_material_path(innerrel, inner_cheapest_total); + create_material_path(innerrel, inner_cheapest_total, true); } foreach(lc1, outerrel->pathlist) @@ -1909,6 +1944,7 @@ match_unsorted_outer(PlannerInfo *root, innerpath, merge_pathkeys, jointype, + PGS_NESTLOOP_PLAIN, extra); /* @@ -1925,6 +1961,7 @@ match_unsorted_outer(PlannerInfo *root, mpath, merge_pathkeys, jointype, + PGS_NESTLOOP_MEMOIZE, extra); } @@ -1936,6 +1973,7 @@ match_unsorted_outer(PlannerInfo *root, matpath, merge_pathkeys, jointype, + PGS_NESTLOOP_MATERIALIZE, extra); } @@ -2052,16 +2090,17 @@ consider_parallel_nestloop(PlannerInfo *root, /* * Consider materializing the cheapest inner path, unless: 1) - * enable_material is off, 2) the cheapest inner path is not + * materialization is disabled here, 2) the cheapest inner path is not * parallel-safe, 3) the cheapest inner path is parameterized by the outer * rel, or 4) the cheapest inner path materializes its output anyway. */ - if (enable_material && inner_cheapest_total->parallel_safe && + if ((extra->pgs_mask & PGS_NESTLOOP_MATERIALIZE) != 0 && + inner_cheapest_total->parallel_safe && !PATH_PARAM_BY_REL(inner_cheapest_total, outerrel) && !ExecMaterializesOutput(inner_cheapest_total->pathtype)) { matpath = (Path *) - create_material_path(innerrel, inner_cheapest_total); + create_material_path(innerrel, inner_cheapest_total, true); Assert(matpath->parallel_safe); } @@ -2091,7 +2130,8 @@ consider_parallel_nestloop(PlannerInfo *root, continue; try_partial_nestloop_path(root, joinrel, outerpath, innerpath, - pathkeys, jointype, extra); + pathkeys, jointype, + PGS_NESTLOOP_PLAIN, extra); /* * Try generating a memoize path and see if that makes the nested @@ -2102,13 +2142,15 @@ consider_parallel_nestloop(PlannerInfo *root, extra); if (mpath != NULL) try_partial_nestloop_path(root, joinrel, outerpath, mpath, - pathkeys, jointype, extra); + pathkeys, jointype, + PGS_NESTLOOP_MEMOIZE, extra); } /* Also consider materialized form of the cheapest inner path */ if (matpath != NULL) try_partial_nestloop_path(root, joinrel, outerpath, matpath, - pathkeys, jointype, extra); + pathkeys, jointype, + PGS_NESTLOOP_MATERIALIZE, extra); } } diff --git a/src/backend/optimizer/path/tidpath.c b/src/backend/optimizer/path/tidpath.c index 3ddbc10bbd..150115c293 100644 --- a/src/backend/optimizer/path/tidpath.c +++ b/src/backend/optimizer/path/tidpath.c @@ -499,18 +499,19 @@ create_tidscan_paths(PlannerInfo *root, RelOptInfo *rel) List *tidquals; List *tidrangequals; bool isCurrentOf; + bool enabled = (rel->pgs_mask & PGS_TIDSCAN) != 0; /* * If any suitable quals exist in the rel's baserestrict list, generate a * plain (unparameterized) TidPath with them. * - * We skip this when enable_tidscan = false, except when the qual is + * We skip this when TID scans are disabled, except when the qual is * CurrentOfExpr. In that case, a TID scan is the only correct path. */ tidquals = TidQualFromRestrictInfoList(root, rel->baserestrictinfo, rel, &isCurrentOf); - if (tidquals != NIL && (enable_tidscan || isCurrentOf)) + if (tidquals != NIL && (enabled || isCurrentOf)) { /* * This path uses no join clauses, but it could still have required @@ -532,7 +533,7 @@ create_tidscan_paths(PlannerInfo *root, RelOptInfo *rel) } /* Skip the rest if TID scans are disabled. */ - if (!enable_tidscan) + if (!enabled) return false; /* diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index e3f27a586c..66d491ecb1 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -6503,7 +6503,7 @@ Plan * materialize_finished_plan(Plan *subplan) { Plan *matplan; - Path matpath; /* dummy for result of cost_material */ + Path matpath; /* dummy for cost_material */ Cost initplan_cost; bool unsafe_initplans; @@ -6525,7 +6525,9 @@ materialize_finished_plan(Plan *subplan) subplan->total_cost -= initplan_cost; /* Set cost data */ + matpath.parent = NULL; cost_material(&matpath, + enable_material, subplan->disabled_nodes, subplan->startup_cost, subplan->total_cost, diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 94e1ac96ed..36a2935510 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -462,6 +462,53 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions, tuple_fraction = 0.0; } + /* + * Compute the initial path generation strategy mask. + * + * Some strategies, such as PGS_FOREIGNJOIN, have no corresponding enable_* + * GUC, and so the corresponding bits are always set in the default + * strategy mask. + * + * It may seem surprising that enable_indexscan sets both PGS_INDEXSCAN + * and PGS_INDEXONLYSCAN. However, the historical behavior of this GUC + * corresponds to this exactly: enable_indexscan=off disables both + * index-scan and index-only scan paths, whereas enable_indexonlyscan=off + * converts the index-only scan paths that we would have considered into + * index scan paths. + */ + glob->default_pgs_mask = PGS_APPEND | PGS_MERGE_APPEND | PGS_FOREIGNJOIN | + PGS_GATHER | PGS_CONSIDER_NONPARTIAL; + if (enable_tidscan) + glob->default_pgs_mask |= PGS_TIDSCAN; + if (enable_seqscan) + glob->default_pgs_mask |= PGS_SEQSCAN; + if (enable_indexscan) + glob->default_pgs_mask |= PGS_INDEXSCAN | PGS_INDEXONLYSCAN; + if (enable_indexonlyscan) + glob->default_pgs_mask |= PGS_CONSIDER_INDEXONLY; + if (enable_bitmapscan) + glob->default_pgs_mask |= PGS_BITMAPSCAN; + if (enable_mergejoin) + { + glob->default_pgs_mask |= PGS_MERGEJOIN_PLAIN; + if (enable_material) + glob->default_pgs_mask |= PGS_MERGEJOIN_MATERIALIZE; + } + if (enable_nestloop) + { + glob->default_pgs_mask |= PGS_NESTLOOP_PLAIN; + if (enable_material) + glob->default_pgs_mask |= PGS_NESTLOOP_MATERIALIZE; + if (enable_memoize) + glob->default_pgs_mask |= PGS_NESTLOOP_MEMOIZE; + } + if (enable_hashjoin) + glob->default_pgs_mask |= PGS_HASHJOIN; + if (enable_gathermerge) + glob->default_pgs_mask |= PGS_GATHER_MERGE; + if (enable_partitionwise_join) + glob->default_pgs_mask |= PGS_CONSIDER_PARTITIONWISE; + /* Allow plugins to take control after we've initialized "glob" */ if (planner_setup_hook) (*planner_setup_hook) (glob, parse, query_string, &tuple_fraction, es); @@ -3954,6 +4001,9 @@ make_grouping_rel(PlannerInfo *root, RelOptInfo *input_rel, is_parallel_safe(root, havingQual)) grouped_rel->consider_parallel = true; + /* Assume that the same path generation strategies are allowed */ + grouped_rel->pgs_mask = input_rel->pgs_mask; + /* * If the input rel belongs to a single FDW, so does the grouped rel. */ @@ -5348,6 +5398,9 @@ create_ordered_paths(PlannerInfo *root, if (input_rel->consider_parallel && target_parallel_safe) ordered_rel->consider_parallel = true; + /* Assume that the same path generation strategies are allowed. */ + ordered_rel->pgs_mask = input_rel->pgs_mask; + /* * If the input rel belongs to a single FDW, so does the ordered_rel. */ @@ -7428,6 +7481,7 @@ create_partial_grouping_paths(PlannerInfo *root, grouped_rel->relids); partially_grouped_rel->consider_parallel = grouped_rel->consider_parallel; + partially_grouped_rel->pgs_mask = grouped_rel->pgs_mask; partially_grouped_rel->reloptkind = grouped_rel->reloptkind; partially_grouped_rel->serverid = grouped_rel->serverid; partially_grouped_rel->userid = grouped_rel->userid; diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c index 33ce34f008..7dd9a7c460 100644 --- a/src/backend/optimizer/util/pathnode.c +++ b/src/backend/optimizer/util/pathnode.c @@ -1659,7 +1659,7 @@ create_group_result_path(PlannerInfo *root, RelOptInfo *rel, * pathnode. */ MaterialPath * -create_material_path(RelOptInfo *rel, Path *subpath) +create_material_path(RelOptInfo *rel, Path *subpath, bool enabled) { MaterialPath *pathnode = makeNode(MaterialPath); @@ -1678,6 +1678,7 @@ create_material_path(RelOptInfo *rel, Path *subpath) pathnode->subpath = subpath; cost_material(&pathnode->path, + enabled, subpath->disabled_nodes, subpath->startup_cost, subpath->total_cost, @@ -1730,8 +1731,15 @@ create_memoize_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath, pathnode->est_unique_keys = 0.0; pathnode->est_hit_ratio = 0.0; - /* we should not generate this path type when enable_memoize=false */ - Assert(enable_memoize); + /* + * We should not be asked to generate this path type when memoization is + * disabled, so set our count of disabled nodes equal to the subpath's + * count. + * + * It would be nice to also Assert that memoization is enabled, but the + * value of enable_memoize is not controlling: what we would need to check + * is that the JoinPathExtraData's pgs_mask included PGS_NESTLOOP_MEMOIZE. + */ pathnode->path.disabled_nodes = subpath->disabled_nodes; /* @@ -3965,13 +3973,16 @@ reparameterize_path(PlannerInfo *root, Path *path, { MaterialPath *mpath = (MaterialPath *) path; Path *spath = mpath->subpath; + bool enabled; spath = reparameterize_path(root, spath, required_outer, loop_count); + enabled = + (mpath->path.disabled_nodes <= spath->disabled_nodes); if (spath == NULL) return NULL; - return (Path *) create_material_path(rel, spath); + return (Path *) create_material_path(rel, spath, enabled); } case T_Memoize: { diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index e553afb7f0..138a88281e 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -576,6 +576,9 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, * Allow a plugin to editorialize on the info we obtained from the * catalogs. Actions might include altering the assumed relation size, * removing an index, or adding a hypothetical index to the indexlist. + * + * An extension can also modify rel->pgs_mask here to control path + * generation. */ if (get_relation_info_hook) (*get_relation_info_hook) (root, relationObjectId, inhparent, rel); diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 405f4dae10..1d2d7292fe 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -47,6 +47,9 @@ typedef struct JoinHashEntry RelOptInfo *join_rel; } JoinHashEntry; +/* Hook for plugins to get control during joinrel setup */ +joinrel_setup_hook_type joinrel_setup_hook = NULL; + static void build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel, RelOptInfo *input_rel, SpecialJoinInfo *sjinfo, @@ -225,6 +228,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) rel->consider_startup = (root->tuple_fraction > 0); rel->consider_param_startup = false; /* might get changed later */ rel->consider_parallel = false; /* might get changed later */ + rel->pgs_mask = root->glob->default_pgs_mask; rel->reltarget = create_empty_pathtarget(); rel->pathlist = NIL; rel->ppilist = NIL; @@ -822,6 +826,7 @@ build_join_rel(PlannerInfo *root, joinrel->consider_startup = (root->tuple_fraction > 0); joinrel->consider_param_startup = false; joinrel->consider_parallel = false; + joinrel->pgs_mask = root->glob->default_pgs_mask; joinrel->reltarget = create_empty_pathtarget(); joinrel->pathlist = NIL; joinrel->ppilist = NIL; @@ -934,10 +939,6 @@ build_join_rel(PlannerInfo *root, */ joinrel->has_eclass_joins = has_relevant_eclass_joinclause(root, joinrel); - /* Store the partition information. */ - build_joinrel_partition_info(root, joinrel, outer_rel, inner_rel, sjinfo, - restrictlist); - /* * Set estimates of the joinrel's size. */ @@ -963,6 +964,18 @@ build_join_rel(PlannerInfo *root, is_parallel_safe(root, (Node *) joinrel->reltarget->exprs)) joinrel->consider_parallel = true; + /* + * Allow a plugin to editorialize on the new joinrel's properties. Actions + * might include altering the size estimate or clearing consider_parallel. + */ + if (joinrel_setup_hook) + (*joinrel_setup_hook) (root, joinrel, outer_rel, inner_rel, sjinfo, + restrictlist); + + /* Store the partition information. */ + build_joinrel_partition_info(root, joinrel, outer_rel, inner_rel, sjinfo, + restrictlist); + /* Add the joinrel to the PlannerInfo. */ add_join_rel(root, joinrel); @@ -1019,6 +1032,7 @@ build_child_join_rel(PlannerInfo *root, RelOptInfo *outer_rel, joinrel->consider_startup = (root->tuple_fraction > 0); joinrel->consider_param_startup = false; joinrel->consider_parallel = false; + joinrel->pgs_mask = root->glob->default_pgs_mask; joinrel->reltarget = create_empty_pathtarget(); joinrel->pathlist = NIL; joinrel->ppilist = NIL; @@ -1102,10 +1116,6 @@ build_child_join_rel(PlannerInfo *root, RelOptInfo *outer_rel, */ joinrel->has_eclass_joins = parent_joinrel->has_eclass_joins; - /* Is the join between partitions itself partitioned? */ - build_joinrel_partition_info(root, joinrel, outer_rel, inner_rel, sjinfo, - restrictlist); - /* Child joinrel is parallel safe if parent is parallel safe. */ joinrel->consider_parallel = parent_joinrel->consider_parallel; @@ -1113,6 +1123,20 @@ build_child_join_rel(PlannerInfo *root, RelOptInfo *outer_rel, set_joinrel_size_estimates(root, joinrel, outer_rel, inner_rel, sjinfo, restrictlist); + /* + * Allow a plugin to editorialize on the new joinrel's properties. Actions + * might include altering the size estimate or clearing consider_parallel, + * although the latter would be better done in the parent joinrel rather + * than here. + */ + if (joinrel_setup_hook) + (*joinrel_setup_hook) (root, joinrel, outer_rel, inner_rel, sjinfo, + restrictlist); + + /* Is the join between partitions itself partitioned? */ + build_joinrel_partition_info(root, joinrel, outer_rel, inner_rel, sjinfo, + restrictlist); + /* We build the join only once. */ Assert(!find_join_rel(root, joinrel->relids)); @@ -1602,6 +1626,7 @@ fetch_upper_rel(PlannerInfo *root, UpperRelationKind kind, Relids relids) upperrel = makeNode(RelOptInfo); upperrel->reloptkind = RELOPT_UPPER_REL; upperrel->relids = bms_copy(relids); + upperrel->pgs_mask = root->glob->default_pgs_mask; /* cheap startup cost is interesting iff not all tuples to be retrieved */ upperrel->consider_startup = (root->tuple_fraction > 0); @@ -2118,7 +2143,7 @@ build_joinrel_partition_info(PlannerInfo *root, PartitionScheme part_scheme; /* Nothing to do if partitionwise join technique is disabled. */ - if (!enable_partitionwise_join) + if ((joinrel->pgs_mask & PGS_CONSIDER_PARTITIONWISE) == 0) { Assert(!IS_PARTITIONED_REL(joinrel)); return; diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index ce623da71c..c18424b458 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -22,6 +22,79 @@ #include "nodes/parsenodes.h" #include "storage/block.h" +/* + * Path generation strategies. + * + * These constants are used to specify the set of strategies that the planner + * should use, either for the query as a whole or for a specific baserel or + * joinrel. The various planner-related enable_* GUCs are used to set the + * PlannerGlobal's default_pgs_mask, and that in turn is used to set each + * RelOptInfo's pgs_mask. In both cases, extensions can use hooks to modify the + * default value. Not every strategy listed here has a corresponding enable_* + * GUC; those that don't are always allowed unless disabled by an extension. + * Not all strategies are relevant for every RelOptInfo; e.g. PGS_SEQSCAN + * doesn't affect joinrels one way or the other. + * + * In most cases, disabling a path generation strategy merely means that any + * paths generated using that strategy are marked as disabled, but in some + * cases, path generation is skipped altogether. The latter strategy is only + * permissible when it can't result in planner failure -- for instance, we + * couldn't do this for sequential scans on a plain rel, because there might + * not be any other possible path. Nevertheless, the behaviors in each + * individual case are to some extent the result of historical accident, + * chosen to match the preexisting behaviors of the enable_* GUCs. + * + * In a few cases, we have more than one bit for the same strategy, controlling + * different aspects of the planner behavior. When PGS_CONSIDER_INDEXONLY is + * unset, we don't even consider index-only scans, and any such scans that + * would have been generated become index scans instead. On the other hand, + * unsetting PGS_INDEXSCAN or PGS_INDEXONLYSCAN causes generated paths of the + * corresponding types to be marked as disabled. Similarly, unsetting + * PGS_CONSIDER_PARTITIONWISE prevents any sort of thinking about partitionwise + * joins for the current rel, which incidentally will preclude higher-level + * joinrels from building parititonwise paths using paths taken from the + * current rel's children. On the other hand, unsetting PGS_APPEND or + * PGS_MERGE_APPEND will only arrange to disable paths of the corresponding + * types if they are generated at the level of the current rel. + * + * Finally, unsetting PGS_CONSIDER_NONPARTIAL disables all non-partial paths + * except those that use Gather or Gather Merge. In most other cases, a + * plugin can nudge the planner toward a particular strategy by disabling + * all of the others, but that doesn't work here: unsetting PGS_SEQSCAN, + * for instance, would disable both partial and non-partial sequential scans. + */ +#define PGS_SEQSCAN 0x00000001 +#define PGS_INDEXSCAN 0x00000002 +#define PGS_INDEXONLYSCAN 0x00000004 +#define PGS_BITMAPSCAN 0x00000008 +#define PGS_TIDSCAN 0x00000010 +#define PGS_FOREIGNJOIN 0x00000020 +#define PGS_MERGEJOIN_PLAIN 0x00000040 +#define PGS_MERGEJOIN_MATERIALIZE 0x00000080 +#define PGS_NESTLOOP_PLAIN 0x00000100 +#define PGS_NESTLOOP_MATERIALIZE 0x00000200 +#define PGS_NESTLOOP_MEMOIZE 0x00000400 +#define PGS_HASHJOIN 0x00000800 +#define PGS_APPEND 0x00001000 +#define PGS_MERGE_APPEND 0x00002000 +#define PGS_GATHER 0x00004000 +#define PGS_GATHER_MERGE 0x00008000 +#define PGS_CONSIDER_INDEXONLY 0x00010000 +#define PGS_CONSIDER_PARTITIONWISE 0x00020000 +#define PGS_CONSIDER_NONPARTIAL 0x00040000 + +/* + * Convenience macros for useful combination of the bits defined above. + */ +#define PGS_SCAN_ANY \ + (PGS_SEQSCAN | PGS_INDEXSCAN | PGS_INDEXONLYSCAN | PGS_BITMAPSCAN | \ + PGS_TIDSCAN) +#define PGS_MERGEJOIN_ANY \ + (PGS_MERGEJOIN_PLAIN | PGS_MERGEJOIN_MATERIALIZE) +#define PGS_NESTLOOP_ANY \ + (PGS_NESTLOOP_PLAIN | PGS_NESTLOOP_MATERIALIZE | PGS_NESTLOOP_MEMOIZE) +#define PGS_JOIN_ANY \ + (PGS_FOREIGNJOIN | PGS_MERGEJOIN_ANY | PGS_NESTLOOP_ANY | PGS_HASHJOIN) /* * Relids @@ -186,6 +259,9 @@ typedef struct PlannerGlobal /* worst PROPARALLEL hazard level */ char maxParallelHazard; + /* mask of allowed path generation strategies */ + uint64 default_pgs_mask; + /* partition descriptors */ PartitionDirectory partition_directory pg_node_attr(read_write_ignore); @@ -939,7 +1015,7 @@ typedef struct RelOptInfo Cardinality rows; /* - * per-relation planner control flags + * per-relation planner control */ /* keep cheap-startup-cost paths? */ bool consider_startup; @@ -947,6 +1023,8 @@ typedef struct RelOptInfo bool consider_param_startup; /* consider parallel paths? */ bool consider_parallel; + /* path generation strategy mask */ + uint64 pgs_mask; /* * default result targetlist for Paths scanning this relation; list of @@ -3506,6 +3584,7 @@ typedef struct SemiAntiJoinFactors * sjinfo is extra info about special joins for selectivity estimation * semifactors is as shown above (only valid for SEMI/ANTI/inner_unique joins) * param_source_rels are OK targets for parameterization of result paths + * pgs_mask is a bitmask of PGS_* constants to limit the join strategy */ typedef struct JoinPathExtraData { @@ -3515,6 +3594,7 @@ typedef struct JoinPathExtraData SpecialJoinInfo *sjinfo; SemiAntiJoinFactors semifactors; Relids param_source_rels; + uint64 pgs_mask; } JoinPathExtraData; /* diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h index b523bcda8f..2d80462bec 100644 --- a/src/include/optimizer/cost.h +++ b/src/include/optimizer/cost.h @@ -125,7 +125,7 @@ extern void cost_merge_append(Path *path, PlannerInfo *root, Cost input_startup_cost, Cost input_total_cost, double tuples); extern void cost_material(Path *path, - int input_disabled_nodes, + bool enabled, int input_disabled_nodes, Cost input_startup_cost, Cost input_total_cost, double tuples, int width); extern void cost_agg(Path *path, PlannerInfo *root, @@ -148,7 +148,7 @@ extern void cost_group(Path *path, PlannerInfo *root, double input_tuples); extern void initial_cost_nestloop(PlannerInfo *root, JoinCostWorkspace *workspace, - JoinType jointype, + JoinType jointype, uint64 enable_mask, Path *outer_path, Path *inner_path, JoinPathExtraData *extra); extern void final_cost_nestloop(PlannerInfo *root, NestPath *path, diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h index dbf4702acc..123b78cbf1 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -17,6 +17,14 @@ #include "nodes/bitmapset.h" #include "nodes/pathnodes.h" +/* Hook for plugins to get control during joinrel setup */ +typedef void (*joinrel_setup_hook_type) (PlannerInfo *root, + RelOptInfo *joinrel, + RelOptInfo *outer_rel, + RelOptInfo *inner_rel, + SpecialJoinInfo *sjinfo, + List *restrictlist); +extern PGDLLIMPORT joinrel_setup_hook_type joinrel_setup_hook; /* * prototypes for pathnode.c @@ -85,7 +93,8 @@ extern GroupResultPath *create_group_result_path(PlannerInfo *root, RelOptInfo *rel, PathTarget *target, List *havingqual); -extern MaterialPath *create_material_path(RelOptInfo *rel, Path *subpath); +extern MaterialPath *create_material_path(RelOptInfo *rel, Path *subpath, + bool enabled); extern MemoizePath *create_memoize_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath, diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h index f6a62df0b4..61c1607f87 100644 --- a/src/include/optimizer/paths.h +++ b/src/include/optimizer/paths.h @@ -28,7 +28,14 @@ extern PGDLLIMPORT int min_parallel_table_scan_size; extern PGDLLIMPORT int min_parallel_index_scan_size; extern PGDLLIMPORT bool enable_group_by_reordering; -/* Hook for plugins to get control in set_rel_pathlist() */ +/* Hooks for plugins to get control in set_rel_pathlist() */ +typedef void (*join_path_setup_hook_type) (PlannerInfo *root, + RelOptInfo *joinrel, + RelOptInfo *outerrel, + RelOptInfo *innerrel, + JoinType jointype, + JoinPathExtraData *extra); +extern PGDLLIMPORT join_path_setup_hook_type join_path_setup_hook; typedef void (*set_rel_pathlist_hook_type) (PlannerInfo *root, RelOptInfo *rel, Index rti,