public inbox for [email protected]
help / color / mirror / Atom feed[PATCH v4] fix HOT tuples while scanning for index builds
5+ messages / 4 participants
[nested] [flat]
* [PATCH v4] fix HOT tuples while scanning for index builds
@ 2020-08-12 18:02 Alvaro Herrera <[email protected]>
0 siblings, 0 replies; 5+ messages in thread
From: Alvaro Herrera @ 2020-08-12 18:02 UTC (permalink / raw)
---
src/backend/access/heap/heapam_handler.c | 20 ++++++++++++++++++++
src/backend/access/heap/pruneheap.c | 5 +++--
2 files changed, 23 insertions(+), 2 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 267a6ee25a..ba44e30035 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -1324,6 +1324,12 @@ heapam_index_build_range_scan(Relation heapRelation,
* buffer continuously while visiting the page, so no pruning
* operation can occur either.
*
+ * In cases with only ShareUpdateExclusiveLock on the table, it's
+ * possible for some HOT tuples to appear that we didn't know about
+ * when we first read the page. To handle that case, we re-obtain the
+ * list of root offsets when a HOT tuple points to a root item that we
+ * don't know about.
+ *
* Also, although our opinions about tuple liveness could change while
* we scan the page (due to concurrent transaction commits/aborts),
* the chain root locations won't, so this info doesn't need to be
@@ -1625,6 +1631,20 @@ heapam_index_build_range_scan(Relation heapRelation,
offnum = ItemPointerGetOffsetNumber(&heapTuple->t_self);
+ /*
+ * If a HOT tuple points to a root that we don't know
+ * about, obtain root items afresh. If that still fails,
+ * report it as corruption.
+ */
+ if (root_offsets[offnum - 1] == InvalidOffsetNumber)
+ {
+ Page page = BufferGetPage(hscan->rs_cbuf);
+
+ LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_SHARE);
+ heap_get_root_tuples(page, root_offsets);
+ LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_UNLOCK);
+ }
+
if (!OffsetNumberIsValid(root_offsets[offnum - 1]))
ereport(ERROR,
(errcode(ERRCODE_DATA_CORRUPTED),
diff --git a/src/backend/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c
index 256df4de10..7e3d44dfd6 100644
--- a/src/backend/access/heap/pruneheap.c
+++ b/src/backend/access/heap/pruneheap.c
@@ -732,7 +732,7 @@ heap_page_prune_execute(Buffer buffer,
* root_offsets[k - 1] = j.
*
* The passed-in root_offsets array must have MaxHeapTuplesPerPage entries.
- * We zero out all unused entries.
+ * Unused entries are filled with InvalidOffsetNumber (zero).
*
* The function must be called with at least share lock on the buffer, to
* prevent concurrent prune operations.
@@ -747,7 +747,8 @@ heap_get_root_tuples(Page page, OffsetNumber *root_offsets)
OffsetNumber offnum,
maxoff;
- MemSet(root_offsets, 0, MaxHeapTuplesPerPage * sizeof(OffsetNumber));
+ MemSet(root_offsets, InvalidOffsetNumber,
+ MaxHeapTuplesPerPage * sizeof(OffsetNumber));
maxoff = PageGetMaxOffsetNumber(page);
for (offnum = FirstOffsetNumber; offnum <= maxoff; offnum = OffsetNumberNext(offnum))
--
2.20.1
--opJtzjQTFsWo+cga--
^ permalink raw reply [nested|flat] 5+ messages in thread
* allowing extensions to control planner behavior
@ 2024-08-26 16:32 Robert Haas <[email protected]>
0 siblings, 2 replies; 5+ messages in thread
From: Robert Haas @ 2024-08-26 16:32 UTC (permalink / raw)
To: pgsql-hackers
I'm somewhat expecting to be flamed to a well-done crisp for saying
this, but I think we need better ways for extensions to control the
behavior of PostgreSQL's query planner. I know of two major reasons
why somebody might want to do this. First, you might want to do
something like what pg_hint_plan does, where it essentially implements
Oracle-style hints that can be either inline or stored in a side table
and automatically applied to queries.[1] In addition to supporting
Oracle-style hints, it also supports some other kinds of hints so that
you can, for example, try to fix broken cardinality estimates. Second,
you might want to convince the planner to keep producing the same kind
of plan that it produced previously. I believe this is what Amazon's
query plan management feature[2] does, although since it is closed
source and I don't work at Amazon maybe it's actually implemented
completely differently. Regardless of what Amazon did in this case,
plan stability is a feature people want. Just trying to keep using the
same plan data structure forever doesn't seem like a good strategy,
because for example it would be fragile in the case of any DDL
changes, like dropping and recreating an index, or dropping or adding
a column. But you might want conceptually the same plan. Although it's
not frequently admitted on this mailing list, unexpected plan changes
are a frequent cause of sudden database outages, and wanting to
prevent that is a legitimate thing for a user to try to do. Naturally,
there is a risk that you might in so doing also prevent plan changes
that would have dramatically improved performance, or stick with a
plan long after you've outgrown it, but that doesn't stop people from
wanting it, or other databases (or proprietary forks of this database)
from offering it, and I don't think it should.
We have some hooks right now that offer a few options in this area,
but there are problems. The hook that I believe to be closest to the
right thing is this one:
/*
* Allow a plugin to editorialize on the set of Paths for this base
* relation. It could add new paths (such as CustomPaths) by calling
* add_path(), or add_partial_path() if parallel aware. It could also
* delete or modify paths added by the core code.
*/
if (set_rel_pathlist_hook)
(*set_rel_pathlist_hook) (root, rel, rti, rte);
Unfortunately, the part about the hook having the freedom to delete
paths isn't really true. Perhaps technically you can delete a path
that you don't want to be chosen, but any paths that were dominated by
the path you deleted have already been thrown away and it's too late
to get them back. You can modify paths if you don't want to change
their costs, but if you change their costs then you have the same
problem: the contents of the pathlist at the time that you see it are
determined by the costs that each path had when it was initially
added, and it's really too late to editorialize on that. So all you
can really do here in practice is add new paths.
set_join_pathlist_hook, which applies to joinrels, is similarly
limited. appendrels don't even have an equivalent of this hook.
So, how could we do better?
I think there are two basic approaches that are possible here. If
someone sees a third option, let me know. First, we could allow users
to hook add_path() and add_partial_path(). That certainly provides the
flexibility on paper to accept or reject whatever paths you do or do
not want. However, I don't find this approach very appealing. One
problem is that it's likely to be fairly expensive, because add_path()
gets called A LOT. A second problem is that you don't actually get an
awful lot of context: I think anybody writing a hook would have to
write code to basically analyze each proposed path and figure out why
it was getting added and then decide what to do. In some cases that
might be fine, because for example accepting or rejecting paths based
on path type seems fairly straightforward with this approach, but as
soon as you want to do anything more complicated than that it starts
to seem difficult. If, for example, you want relation R1 to be the
driving table for the whole query plan, you're going to have to
determine whether or not that is the case for every single candidate
(partial) path that someone hands you, so you're going to end up
making that decision a whole lot of times. It doesn't sound
particularly fun. Third, even if you are doing something really simple
like trying to reject mergejoins, you've already lost the opportunity
to skip a bunch of work. If you had known when you started planning
the joinrel that you didn't care about mergejoins, you could have
skipped looking for merge-joinable clauses. Overall, while I wouldn't
be completely against further exploration of this option, I suspect
it's pretty hard to do anything useful with it.
The other possible approach is to allow extensions to feed some
information into the planner before path generation and let that
influence which paths are generated. This is essentially what
pg_hint_plan is doing: it implements plan type hints by arranging to
flip the various enable_* GUCs on and off during the planning of
various rels. That's clever but ugly, and it ends up duplicating
substantial chunks of planner code due to the inadequacy of the
existing hooks. With some refactoring and some additional hooks, we
could make this much less ugly. But that gets at what I believe to be
the core difficulty of this approach, which is that the core planner
code needs to be somewhat aware of and on board with what the user or
the extension is trying to do. If an extension wants to force the join
order, that is likely to require different scaffolding than if it
wants to force the join methods which is again different from if a
hook wants to bias the query planner towards or against particular
indexes. Putting in hooks or other infrastructure that allows an
extension to control a particular aspect of planner behavior is to
some extent an endorsement of controlling the planner behavior in that
particular way. Since any amount of allowing the user to control the
planner tends to be controversial around here, that opens up the
spectre of putting a whole lot of effort into arguing about which
things extensions should be allowed to do, getting most of the patches
rejected, and ending up with nothing that's actually useful.
But on the other hand, it's not like we have to design everything in a
greenfield. Other database systems have provided in-core, user-facing
features to control the planner for decades, and we can look at those
offerings -- and existing offerings in the PG space -- as we try to
judge whether a particular use case is totally insane. I am not here
to argue that everything that every system has done is completely
perfect and without notable flaws, but our own system has its own
share of flaws, and the fact that you can do very little when a
previously unproblematic query starts suddenly producing a bad plan is
definitely one of them. I believe we are long past the point where we
can simply hold our breath and pretend like there's no issue here. At
the very least, allowing extensions to control scan methods (including
choice of indexes), join methods, and join order (including which
table ends up on which side of a given join) and similar things for
aggregates and appendrels seems to me like it ought to be table
stakes. And those extensions shouldn't have to duplicate large chunks
of code or resort to gross hacks to do it. Eventually, maybe we'll
even want to have directly user-facing features to do some of this
stuff (in query hints, out of query hints, or whatever) but I think
opening the door up to extensions doing it is a good first step,
because (1) that allows different extensions to do different things
without taking a position on what the One Right Thing To Do is and (2)
if it becomes clear that something improvident has been done, it is a
lot easier to back out a hook or some C API change than it is to
back-out a user-visible feature. Or maybe we'll never want to expose a
user-visible feature here, but it can still be useful to enable
extensions.
The attached patch, briefly mentioned above, essentially converts the
enable_* GUCs into RelOptInfo properties where the defaults are set by
the corresponding GUCs. The idea is that a hook could then change this
on a per-RelOptInfo basis before path generation happens. For
baserels, I believe that could be done from get_relation_info_hook for
baserels, and we could introduce something similar for other kinds of
rels. I don't think this is in any way the perfect approach. On the
one hand, it doesn't give you all the kinds of control over path
generation that you might want. On the other hand, the more I look at
what our enable_* GUCs actually do, the less impressed I am. IMHO,
things like enable_hashjoin make a lot of sense, but enable_sort seems
like it just controls an absolutely random smattering of behaviors in
a way that seems to me to have very little to recommend it, and I've
complained elsewhere about how enable_indexscan and
enable_indexonlyscan are really quite odd when you look at how they're
implemented. Still, this seemed like a fairly easy thing to do as a
way of demonstrating the kind of thing that we could do to provide
extensions with more control over planner behavior, and I believe it
would be concretely useful to pg_hint_plan in particular. But all that
said, as much as anything, I want to get some feedback on what
approaches and trade-offs people think might be acceptable here,
because there's not much point in me spending a bunch of time writing
code that everyone (or a critical mass of people) are going to hate.
Thanks,
--
Robert Haas
EDB: http://www.enterprisedb.com
[1] https://github.com/ossc-db/pg_hint_plan
[2] https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/AuroraPostgreSQL.Optimize.html
Attachments:
[application/octet-stream] v1-0001-Convert-enable_-GUCs-into-per-RelOptInfo-values-w.patch (60.7K, ../../CA+TgmoZY+baV-T-5ifDn6P=L=aV-VkVBrPmi0TQkcEq-5Finww@mail.gmail.com/2-v1-0001-Convert-enable_-GUCs-into-per-RelOptInfo-values-w.patch)
download | inline diff:
From baee6cd9e575728650813152af0d4d2d9c96674f Mon Sep 17 00:00:00 2001
From: Robert Haas <[email protected]>
Date: Mon, 26 Aug 2024 12:27:08 -0400
Subject: [PATCH v1] Convert enable_* GUCs into per-RelOptInfo values with GUCs
setting defaults.
---
contrib/postgres_fdw/postgres_fdw.c | 5 +-
src/backend/optimizer/path/allpaths.c | 15 ++--
src/backend/optimizer/path/costsize.c | 107 +++++++++++++++++-------
src/backend/optimizer/path/indxpath.c | 2 +-
src/backend/optimizer/path/joinpath.c | 53 ++++++------
src/backend/optimizer/path/pathkeys.c | 3 +-
src/backend/optimizer/path/tidpath.c | 11 +--
src/backend/optimizer/plan/createplan.c | 26 +++---
src/backend/optimizer/plan/planner.c | 58 ++++++++-----
src/backend/optimizer/plan/subselect.c | 9 +-
src/backend/optimizer/prep/prepunion.c | 20 +++--
src/backend/optimizer/util/pathnode.c | 21 ++---
src/backend/optimizer/util/plancat.c | 3 +-
src/backend/optimizer/util/relnode.c | 6 +-
src/backend/utils/misc/guc_tables.c | 37 ++++----
src/include/nodes/pathnodes.h | 29 +++++++
src/include/optimizer/cost.h | 29 ++++++-
src/include/optimizer/planmain.h | 2 +-
18 files changed, 288 insertions(+), 148 deletions(-)
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index adc62576d1..1df4ddf268 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -439,6 +439,7 @@ static void get_remote_estimate(const char *sql,
Cost *startup_cost,
Cost *total_cost);
static void adjust_foreign_grouping_path_cost(PlannerInfo *root,
+ RelOptInfo *rel,
List *pathkeys,
double retrieved_rows,
double width,
@@ -3489,7 +3490,7 @@ estimate_path_cost_size(PlannerInfo *root,
{
Assert(foreignrel->reloptkind == RELOPT_UPPER_REL &&
fpinfo->stage == UPPERREL_GROUP_AGG);
- adjust_foreign_grouping_path_cost(root, pathkeys,
+ adjust_foreign_grouping_path_cost(root, foreignrel, pathkeys,
retrieved_rows, width,
fpextra->limit_tuples,
&disabled_nodes,
@@ -3644,6 +3645,7 @@ get_remote_estimate(const char *sql, PGconn *conn,
*/
static void
adjust_foreign_grouping_path_cost(PlannerInfo *root,
+ RelOptInfo *rel,
List *pathkeys,
double retrieved_rows,
double width,
@@ -3667,6 +3669,7 @@ adjust_foreign_grouping_path_cost(PlannerInfo *root,
cost_sort(&sort_path,
root,
+ rel,
pathkeys,
0,
*p_startup_cost + *p_run_cost,
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 057b4b79eb..8645244f84 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -970,7 +970,7 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel,
* flag; currently, we only consider partitionwise joins with the baserel
* if its targetlist doesn't contain a whole-row Var.
*/
- if (enable_partitionwise_join &&
+ if (REL_CAN_USE_PATH(rel, PartitionwiseJoin) &&
rel->reloptkind == RELOPT_BASEREL &&
rte->relkind == RELKIND_PARTITIONED_TABLE &&
bms_is_empty(rel->attr_needed[InvalidAttrNumber - rel->min_attr]))
@@ -1325,7 +1325,8 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
double partial_rows = -1;
/* If appropriate, consider parallel append */
- pa_subpaths_valid = enable_parallel_append && rel->consider_parallel;
+ pa_subpaths_valid = REL_CAN_USE_PATH(rel, ParallelAppend)
+ && rel->consider_parallel;
/*
* For every non-dummy child, remember the cheapest path. Also, identify
@@ -1535,7 +1536,7 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
* partitions vs. an unpartitioned table with the same data, so the
* use of some kind of log-scaling here seems to make some sense.
*/
- if (enable_parallel_append)
+ if (REL_CAN_USE_PATH(rel, ParallelAppend))
{
parallel_workers = Max(parallel_workers,
pg_leftmost_one_pos32(list_length(live_childrels)) + 1);
@@ -1547,7 +1548,7 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
/* Generate a partial append path. */
appendpath = create_append_path(root, rel, NIL, partial_subpaths,
NIL, NULL, parallel_workers,
- enable_parallel_append,
+ REL_CAN_USE_PATH(rel, ParallelAppend),
-1);
/*
@@ -3259,7 +3260,8 @@ generate_useful_gather_paths(PlannerInfo *root, RelOptInfo *rel, bool override_r
* input path).
*/
if (subpath != cheapest_partial_path &&
- (presorted_keys == 0 || !enable_incremental_sort))
+ (presorted_keys == 0 ||
+ !REL_CAN_USE_PATH(rel, IncrementalSort)))
continue;
/*
@@ -3274,7 +3276,8 @@ generate_useful_gather_paths(PlannerInfo *root, RelOptInfo *rel, bool override_r
* output. Here we add an explicit sort to match the useful
* ordering.
*/
- if (presorted_keys == 0 || !enable_incremental_sort)
+ if (presorted_keys == 0 ||
+ !REL_CAN_USE_PATH(rel, IncrementalSort))
{
subpath = (Path *) create_sort_path(root,
rel,
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index e1523d15df..3dfa22fdcd 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -163,6 +163,7 @@ bool enable_parallel_hash = true;
bool enable_partition_pruning = true;
bool enable_presorted_aggregate = true;
bool enable_async_append = true;
+uint32 default_path_type_mask = PathTypeMaskAll;
typedef struct
{
@@ -283,6 +284,38 @@ clamp_cardinality_to_long(Cardinality x)
return (x < (double) LONG_MAX) ? (long) x : LONG_MAX;
}
+/*
+ * Define assign hooks for each enable_<whatever> GUC that affects
+ * default_path_type_mask. These are all basically identical, so we use
+ * a templating macro to define them.
+ */
+#define define_assign_hook(gucname, type) \
+ void \
+ gucname ## _assign_hook(bool newval, void *extra) \
+ { \
+ if (newval) \
+ default_path_type_mask |= PathType ## type; \
+ else \
+ default_path_type_mask &= ~(PathType ## type); \
+ }
+define_assign_hook(enable_bitmapscan, BitmapScan)
+define_assign_hook(enable_gathermerge, GatherMerge)
+define_assign_hook(enable_hashagg, HashAgg)
+define_assign_hook(enable_hashjoin, HashJoin)
+define_assign_hook(enable_incremental_sort, IncrementalSort)
+define_assign_hook(enable_indexscan, IndexScan)
+define_assign_hook(enable_indexonlyscan, IndexOnlyScan)
+define_assign_hook(enable_material, Material)
+define_assign_hook(enable_memoize, Memoize)
+define_assign_hook(enable_mergejoin, MergeJoin)
+define_assign_hook(enable_nestloop, NestLoop)
+define_assign_hook(enable_parallel_append, ParallelAppend)
+define_assign_hook(enable_parallel_hash, ParallelHash)
+define_assign_hook(enable_partitionwise_join, PartitionwiseJoin)
+define_assign_hook(enable_partitionwise_aggregate, PartitionwiseAggregate)
+define_assign_hook(enable_seqscan, SeqScan)
+define_assign_hook(enable_sort, Sort)
+define_assign_hook(enable_tidscan, TIDScan)
/*
* cost_seqscan
@@ -354,7 +387,7 @@ cost_seqscan(Path *path, PlannerInfo *root,
path->rows = clamp_row_est(path->rows / parallel_divisor);
}
- path->disabled_nodes = enable_seqscan ? 0 : 1;
+ path->disabled_nodes = REL_CAN_USE_PATH(baserel, SeqScan) ? 0 : 1;
path->startup_cost = startup_cost;
path->total_cost = startup_cost + cpu_run_cost + disk_run_cost;
}
@@ -533,7 +566,7 @@ cost_gather_merge(GatherMergePath *path, PlannerInfo *root,
run_cost += parallel_tuple_cost * path->path.rows * 1.05;
path->path.disabled_nodes = input_disabled_nodes
- + (enable_gathermerge ? 0 : 1);
+ + (REL_CAN_USE_PATH(rel, GatherMerge) ? 0 : 1);
path->path.startup_cost = startup_cost + input_startup_cost;
path->path.total_cost = (startup_cost + run_cost + input_total_cost);
}
@@ -615,7 +648,8 @@ cost_index(IndexPath *path, PlannerInfo *root, double loop_count,
}
/* we don't need to check enable_indexonlyscan; indxpath.c does that */
- path->path.disabled_nodes = enable_indexscan ? 0 : 1;
+ path->path.disabled_nodes =
+ REL_CAN_USE_PATH(baserel, IndexScan) ? 0 : 1;
/*
* Call index-access-method-specific code to estimate the processing cost
@@ -1109,7 +1143,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->disabled_nodes = REL_CAN_USE_PATH(baserel, BitmapScan) ? 0 : 1;
path->startup_cost = startup_cost;
path->total_cost = startup_cost + run_cost;
}
@@ -1287,10 +1321,11 @@ 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 this TID scans are enabled
+ * for this rel. Also, if CurrentOfExpr is the qual, there should be
+ * only one.
*/
- Assert(enable_tidscan || IsA(qual, CurrentOfExpr));
+ Assert(REL_CAN_USE_PATH(baserel, TIDScan) || IsA(qual, CurrentOfExpr));
Assert(list_length(tidquals) == 1 || !IsA(qual, CurrentOfExpr));
if (IsA(qual, ScalarArrayOpExpr))
@@ -1342,8 +1377,8 @@ 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 TID scans are enabled for this rel or when a 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;
@@ -1438,8 +1473,8 @@ 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);
+ /* we should not generate this path type when TID scans are disabled */
+ Assert(REL_CAN_USE_PATH(baserel, TIDScan));
path->disabled_nodes = 0;
path->startup_cost = startup_cost;
path->total_cost = startup_cost + run_cost;
@@ -2120,8 +2155,11 @@ cost_incremental_sort(Path *path,
path->rows = input_tuples;
- /* should not generate these paths when enable_incremental_sort=false */
- Assert(enable_incremental_sort);
+ /*
+ * If incremental sort is not enabled here, we should not have generated a
+ * path of this type.
+ */
+ Assert(REL_CAN_USE_PATH(path->parent, IncrementalSort));
path->disabled_nodes = input_disabled_nodes;
path->startup_cost = startup_cost;
@@ -2141,7 +2179,7 @@ cost_incremental_sort(Path *path,
* of sort keys, which all callers *could* supply.)
*/
void
-cost_sort(Path *path, PlannerInfo *root,
+cost_sort(Path *path, PlannerInfo *root, RelOptInfo *rel,
List *pathkeys, int input_disabled_nodes,
Cost input_cost, double tuples, int width,
Cost comparison_cost, int sort_mem,
@@ -2159,7 +2197,8 @@ cost_sort(Path *path, PlannerInfo *root,
startup_cost += input_cost;
path->rows = tuples;
- path->disabled_nodes = input_disabled_nodes + (enable_sort ? 0 : 1);
+ path->disabled_nodes = input_disabled_nodes +
+ (REL_CAN_USE_PATH(rel, Sort) ? 0 : 1);
path->startup_cost = startup_cost;
path->total_cost = startup_cost + run_cost;
}
@@ -2321,6 +2360,7 @@ cost_append(AppendPath *apath)
*/
cost_sort(&sort_path,
NULL, /* doesn't currently need root */
+ apath->path.parent,
pathkeys,
subpath->disabled_nodes,
subpath->total_cost,
@@ -2480,7 +2520,7 @@ cost_merge_append(Path *path, PlannerInfo *root,
* occur only on rescan, which is estimated in cost_rescan.
*/
void
-cost_material(Path *path,
+cost_material(Path *path, RelOptInfo *rel,
int input_disabled_nodes,
Cost input_startup_cost, Cost input_total_cost,
double tuples, int width)
@@ -2519,7 +2559,8 @@ 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 +
+ (REL_CAN_USE_PATH(rel, Material) ? 0 : 1);
path->startup_cost = startup_cost;
path->total_cost = startup_cost + run_cost;
}
@@ -2679,7 +2720,7 @@ cost_memoize_rescan(PlannerInfo *root, MemoizePath *mpath,
* are for appropriately-sorted input.
*/
void
-cost_agg(Path *path, PlannerInfo *root,
+cost_agg(Path *path, PlannerInfo *root, RelOptInfo *rel,
AggStrategy aggstrategy, const AggClauseCosts *aggcosts,
int numGroupCols, double numGroups,
List *quals,
@@ -2738,7 +2779,7 @@ cost_agg(Path *path, PlannerInfo *root,
/* Here we are able to deliver output on-the-fly */
startup_cost = input_startup_cost;
total_cost = input_total_cost;
- if (aggstrategy == AGG_MIXED && !enable_hashagg)
+ if (aggstrategy == AGG_MIXED && !REL_CAN_USE_PATH(rel, HashAgg))
++disabled_nodes;
/* calcs phrased this way to match HASHED case, see note above */
total_cost += aggcosts->transCost.startup;
@@ -2753,7 +2794,7 @@ cost_agg(Path *path, PlannerInfo *root,
{
/* must be AGG_HASHED */
startup_cost = input_total_cost;
- if (!enable_hashagg)
+ if (!REL_CAN_USE_PATH(rel, HashAgg))
++disabled_nodes;
startup_cost += aggcosts->transCost.startup;
startup_cost += aggcosts->transCost.per_tuple * input_tuples;
@@ -3266,7 +3307,7 @@ cost_group(Path *path, PlannerInfo *root,
*/
void
initial_cost_nestloop(PlannerInfo *root, JoinCostWorkspace *workspace,
- JoinType jointype,
+ RelOptInfo *joinrel, JoinType jointype,
Path *outer_path, Path *inner_path,
JoinPathExtraData *extra)
{
@@ -3280,7 +3321,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 = REL_CAN_USE_PATH(joinrel, NestLoop) ? 0 : 1;
disabled_nodes += inner_path->disabled_nodes;
disabled_nodes += outer_path->disabled_nodes;
@@ -3549,7 +3590,7 @@ final_cost_nestloop(PlannerInfo *root, NestPath *path,
*/
void
initial_cost_mergejoin(PlannerInfo *root, JoinCostWorkspace *workspace,
- JoinType jointype,
+ RelOptInfo *joinrel, JoinType jointype,
List *mergeclauses,
Path *outer_path, Path *inner_path,
List *outersortkeys, List *innersortkeys,
@@ -3676,7 +3717,7 @@ initial_cost_mergejoin(PlannerInfo *root, JoinCostWorkspace *workspace,
Assert(outerstartsel <= outerendsel);
Assert(innerstartsel <= innerendsel);
- disabled_nodes = enable_mergejoin ? 0 : 1;
+ disabled_nodes = REL_CAN_USE_PATH(joinrel, MergeJoin) ? 0 : 1;
/* cost of source data */
@@ -3684,6 +3725,7 @@ initial_cost_mergejoin(PlannerInfo *root, JoinCostWorkspace *workspace,
{
cost_sort(&sort_path,
root,
+ outer_path->parent,
outersortkeys,
outer_path->disabled_nodes,
outer_path->total_cost,
@@ -3713,6 +3755,7 @@ initial_cost_mergejoin(PlannerInfo *root, JoinCostWorkspace *workspace,
{
cost_sort(&sort_path,
root,
+ inner_path->parent,
innersortkeys,
inner_path->disabled_nodes,
inner_path->total_cost,
@@ -3793,6 +3836,7 @@ final_cost_mergejoin(PlannerInfo *root, MergePath *path,
JoinCostWorkspace *workspace,
JoinPathExtraData *extra)
{
+ RelOptInfo *joinrel = path->jpath.path.parent;
Path *outer_path = path->jpath.outerjoinpath;
Path *inner_path = path->jpath.innerjoinpath;
double inner_path_rows = inner_path->rows;
@@ -3946,7 +3990,8 @@ final_cost_mergejoin(PlannerInfo *root, MergePath *path,
* Prefer materializing if it looks cheaper, unless the user has asked to
* suppress materialization.
*/
- else if (enable_material && mat_inner_cost < bare_inner_cost)
+ else if (REL_CAN_USE_PATH(joinrel, Material) &&
+ mat_inner_cost < bare_inner_cost)
path->materialize_inner = true;
/*
@@ -3961,7 +4006,7 @@ final_cost_mergejoin(PlannerInfo *root, MergePath *path,
* 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
+ * We don't test whether a MaterialPath is allowed here, because
* materialization is required for correctness in this case, and turning
* it off does not entitle us to deliver an invalid plan.
*/
@@ -3977,10 +4022,10 @@ 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 MaterialPath is
+ * not allowed here.
*/
- else if (enable_material && innersortkeys != NIL &&
+ else if (REL_CAN_USE_PATH(joinrel, Material) && innersortkeys != NIL &&
relation_byte_size(inner_path_rows,
inner_path->pathtarget->width) >
(work_mem * 1024L))
@@ -4113,7 +4158,7 @@ cached_scansel(PlannerInfo *root, RestrictInfo *rinfo, PathKey *pathkey)
*/
void
initial_cost_hashjoin(PlannerInfo *root, JoinCostWorkspace *workspace,
- JoinType jointype,
+ RelOptInfo *joinrel, JoinType jointype,
List *hashclauses,
Path *outer_path, Path *inner_path,
JoinPathExtraData *extra,
@@ -4132,7 +4177,7 @@ initial_cost_hashjoin(PlannerInfo *root, JoinCostWorkspace *workspace,
size_t space_allowed; /* unused */
/* Count up disabled nodes. */
- disabled_nodes = enable_hashjoin ? 0 : 1;
+ disabled_nodes = REL_CAN_USE_PATH(joinrel, HashJoin) ? 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 c0fcc7d78d..be183db4ce 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -1736,7 +1736,7 @@ check_index_only(RelOptInfo *rel, IndexOptInfo *index)
int i;
/* Index-only scans must be enabled */
- if (!enable_indexonlyscan)
+ if (!REL_CAN_USE_PATH(rel, IndexOnlyScan))
return false;
/*
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index b0e8c94dfc..5f0040cd1b 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -205,10 +205,10 @@ add_paths_to_joinrel(PlannerInfo *root,
/*
* 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 disregard the result of
+ * REL_CAN_USE_PATH() if it's a full join.
*/
- if (enable_mergejoin || jointype == JOIN_FULL)
+ if (REL_CAN_USE_PATH(joinrel, MergeJoin) || jointype == JOIN_FULL)
extra.mergeclause_list = select_mergejoin_clauses(root,
joinrel,
outerrel,
@@ -316,10 +316,11 @@ 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, the result of REL_CAN_USE_PATH()
+ * doesn't matter for full joins, because there may be no other
+ * alternative.
*/
- if (enable_hashjoin || jointype == JOIN_FULL)
+ if (REL_CAN_USE_PATH(joinrel, HashJoin) || jointype == JOIN_FULL)
hash_inner_and_outer(root, joinrel, outerrel, innerrel,
jointype, &extra);
@@ -672,7 +673,7 @@ extract_lateral_vars_from_PHVs(PlannerInfo *root, Relids innerrelids)
* we do not have a way to extract cache keys from joinrels.
*/
static Path *
-get_memoize_path(PlannerInfo *root, RelOptInfo *innerrel,
+get_memoize_path(PlannerInfo *root, RelOptInfo *joinrel, RelOptInfo *innerrel,
RelOptInfo *outerrel, Path *inner_path,
Path *outer_path, JoinType jointype,
JoinPathExtraData *extra)
@@ -684,7 +685,7 @@ get_memoize_path(PlannerInfo *root, RelOptInfo *innerrel,
List *ph_lateral_vars;
/* Obviously not if it's disabled */
- if (!enable_memoize)
+ if (!REL_CAN_USE_PATH(joinrel, Memoize))
return NULL;
/*
@@ -912,7 +913,7 @@ try_nestloop_path(PlannerInfo *root,
* The latter two steps are expensive enough to make this two-phase
* methodology worthwhile.
*/
- initial_cost_nestloop(root, &workspace, jointype,
+ initial_cost_nestloop(root, &workspace, joinrel, jointype,
outer_path, inner_path, extra);
if (add_path_precheck(joinrel, workspace.disabled_nodes,
@@ -997,7 +998,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, joinrel, jointype,
outer_path, inner_path, extra);
if (!add_partial_path_precheck(joinrel, workspace.disabled_nodes,
workspace.total_cost, pathkeys))
@@ -1092,7 +1093,7 @@ try_mergejoin_path(PlannerInfo *root,
/*
* See comments in try_nestloop_path().
*/
- initial_cost_mergejoin(root, &workspace, jointype, mergeclauses,
+ initial_cost_mergejoin(root, &workspace, joinrel, jointype, mergeclauses,
outer_path, inner_path,
outersortkeys, innersortkeys,
extra);
@@ -1164,7 +1165,7 @@ try_partial_mergejoin_path(PlannerInfo *root,
/*
* See comments in try_partial_nestloop_path().
*/
- initial_cost_mergejoin(root, &workspace, jointype, mergeclauses,
+ initial_cost_mergejoin(root, &workspace, joinrel, jointype, mergeclauses,
outer_path, inner_path,
outersortkeys, innersortkeys,
extra);
@@ -1236,7 +1237,7 @@ try_hashjoin_path(PlannerInfo *root,
* See comments in try_nestloop_path(). Also note that hashjoin paths
* never have any output pathkeys, per comments in create_hashjoin_path.
*/
- initial_cost_hashjoin(root, &workspace, jointype, hashclauses,
+ initial_cost_hashjoin(root, &workspace, joinrel, jointype, hashclauses,
outer_path, inner_path, extra, false);
if (add_path_precheck(joinrel, workspace.disabled_nodes,
@@ -1298,7 +1299,7 @@ try_partial_hashjoin_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_hashjoin(root, &workspace, jointype, hashclauses,
+ initial_cost_hashjoin(root, &workspace, joinrel, jointype, hashclauses,
outer_path, inner_path, extra, parallel_hash);
if (!add_partial_path_precheck(joinrel, workspace.disabled_nodes,
workspace.total_cost, NIL))
@@ -1899,10 +1900,11 @@ match_unsorted_outer(PlannerInfo *root,
{
/*
* Consider materializing the cheapest inner path, unless
- * enable_material is off or the path in question materializes its
- * output anyway.
+ * materialization is disabled or the path in question materializes
+ * its output anyway.
*/
- if (enable_material && inner_cheapest_total != NULL &&
+ if (REL_CAN_USE_PATH(innerrel, Material) &&
+ inner_cheapest_total != NULL &&
!ExecMaterializesOutput(inner_cheapest_total->pathtype))
matpath = (Path *)
create_material_path(innerrel, inner_cheapest_total);
@@ -1982,7 +1984,7 @@ match_unsorted_outer(PlannerInfo *root,
* Try generating a memoize path and see if that makes the
* nested loop any cheaper.
*/
- mpath = get_memoize_path(root, innerrel, outerrel,
+ mpath = get_memoize_path(root, joinrel, innerrel, outerrel,
innerpath, outerpath, jointype,
extra);
if (mpath != NULL)
@@ -2134,13 +2136,14 @@ consider_parallel_nestloop(PlannerInfo *root,
/*
* Consider materializing the cheapest inner path, unless: 1) we're doing
* JOIN_UNIQUE_INNER, because in this case we have to unique-ify the
- * cheapest inner path, 2) enable_material is off, 3) the cheapest inner
- * path is not parallel-safe, 4) the cheapest inner path is parameterized
- * by the outer rel, or 5) the cheapest inner path materializes its output
- * anyway.
+ * cheapest inner path, 2) MaterialPath is allowed for this rel, 3) the
+ * cheapest inner path is not parallel-safe, 4) the cheapest inner path is
+ * parameterized by the outer rel, or 5) the cheapest inner path
+ * materializes its output anyway.
*/
if (save_jointype != JOIN_UNIQUE_INNER &&
- enable_material && inner_cheapest_total->parallel_safe &&
+ REL_CAN_USE_PATH(joinrel, Material) &&
+ inner_cheapest_total->parallel_safe &&
!PATH_PARAM_BY_REL(inner_cheapest_total, outerrel) &&
!ExecMaterializesOutput(inner_cheapest_total->pathtype))
{
@@ -2198,7 +2201,7 @@ consider_parallel_nestloop(PlannerInfo *root,
* Try generating a memoize path and see if that makes the nested
* loop any cheaper.
*/
- mpath = get_memoize_path(root, innerrel, outerrel,
+ mpath = get_memoize_path(root, joinrel, innerrel, outerrel,
innerpath, outerpath, jointype,
extra);
if (mpath != NULL)
@@ -2416,7 +2419,7 @@ hash_inner_and_outer(PlannerInfo *root,
*/
if (innerrel->partial_pathlist != NIL &&
save_jointype != JOIN_UNIQUE_INNER &&
- enable_parallel_hash)
+ REL_CAN_USE_PATH(joinrel, ParallelHash))
{
cheapest_partial_inner =
(Path *) linitial(innerrel->partial_pathlist);
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index e25798972f..26a0f5c0c3 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -505,7 +505,8 @@ get_useful_group_keys_orderings(PlannerInfo *root, Path *path)
root->num_groupby_pathkeys);
if (n > 0 &&
- (enable_incremental_sort || n == root->num_groupby_pathkeys) &&
+ (REL_CAN_USE_PATH(path->parent, IncrementalSort) ||
+ n == root->num_groupby_pathkeys) &&
compare_pathkeys(pathkeys, root->group_pathkeys) != PATHKEYS_EQUAL)
{
info = makeNode(GroupByOrdering);
diff --git a/src/backend/optimizer/path/tidpath.c b/src/backend/optimizer/path/tidpath.c
index b0323b26ec..ed447916ec 100644
--- a/src/backend/optimizer/path/tidpath.c
+++ b/src/backend/optimizer/path/tidpath.c
@@ -505,13 +505,14 @@ create_tidscan_paths(PlannerInfo *root, RelOptInfo *rel)
* 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
- * CurrentOfExpr. In that case, a TID scan is the only correct path.
+ * We skip this when TID scans are disabled for this rel, 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 && (REL_CAN_USE_PATH(rel, TIDScan) || isCurrentOf))
{
/*
* This path uses no join clauses, but it could still have required
@@ -532,8 +533,8 @@ create_tidscan_paths(PlannerInfo *root, RelOptInfo *rel)
return true;
}
- /* Skip the rest if TID scans are disabled. */
- if (!enable_tidscan)
+ /* Skip the rest if TID scans are disabled for this rel. */
+ if (!REL_CAN_USE_PATH(rel, TIDScan))
return false;
/*
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 8e0e5977a9..3f3770bfa7 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -177,8 +177,8 @@ static List *get_switched_clauses(List *clauses, Relids outerrelids);
static List *order_qual_clauses(PlannerInfo *root, List *clauses);
static void copy_generic_path_info(Plan *dest, Path *src);
static void copy_plan_costsize(Plan *dest, Plan *src);
-static void label_sort_with_costsize(PlannerInfo *root, Sort *plan,
- double limit_tuples);
+static void label_sort_with_costsize(PlannerInfo *root, RelOptInfo *rel,
+ Sort *plan, double limit_tuples);
static SeqScan *make_seqscan(List *qptlist, List *qpqual, Index scanrelid);
static SampleScan *make_samplescan(List *qptlist, List *qpqual, Index scanrelid,
TableSampleClause *tsc);
@@ -1361,7 +1361,8 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path, int flags)
sortColIdx, sortOperators,
collations, nullsFirst);
- label_sort_with_costsize(root, sort, best_path->limit_tuples);
+ label_sort_with_costsize(root, rel, sort,
+ best_path->limit_tuples);
subplan = (Plan *) sort;
}
}
@@ -1533,7 +1534,7 @@ create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path,
sortColIdx, sortOperators,
collations, nullsFirst);
- label_sort_with_costsize(root, sort, best_path->limit_tuples);
+ label_sort_with_costsize(root, rel, sort, best_path->limit_tuples);
subplan = (Plan *) sort;
}
@@ -1900,7 +1901,7 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path, int flags)
groupColPos++;
}
sort = make_sort_from_sortclauses(sortList, subplan);
- label_sort_with_costsize(root, sort, -1.0);
+ label_sort_with_costsize(root, best_path->path.parent, sort, -1.0);
plan = (Plan *) make_unique_from_sortclauses((Plan *) sort, sortList);
}
@@ -4527,7 +4528,8 @@ create_mergejoin_plan(PlannerInfo *root,
best_path->outersortkeys,
outer_relids);
- label_sort_with_costsize(root, sort, -1.0);
+ label_sort_with_costsize(root, best_path->jpath.path.parent,
+ sort, -1.0);
outer_plan = (Plan *) sort;
outerpathkeys = best_path->outersortkeys;
}
@@ -4541,7 +4543,8 @@ create_mergejoin_plan(PlannerInfo *root,
best_path->innersortkeys,
inner_relids);
- label_sort_with_costsize(root, sort, -1.0);
+ label_sort_with_costsize(root, best_path->jpath.path.parent,
+ sort, -1.0);
inner_plan = (Plan *) sort;
innerpathkeys = best_path->innersortkeys;
}
@@ -5442,7 +5445,8 @@ copy_plan_costsize(Plan *dest, Plan *src)
* limit_tuples is as for cost_sort (in particular, pass -1 if no limit)
*/
static void
-label_sort_with_costsize(PlannerInfo *root, Sort *plan, double limit_tuples)
+label_sort_with_costsize(PlannerInfo *root, RelOptInfo *rel, Sort *plan,
+ double limit_tuples)
{
Plan *lefttree = plan->plan.lefttree;
Path sort_path; /* dummy for result of cost_sort */
@@ -5453,7 +5457,7 @@ label_sort_with_costsize(PlannerInfo *root, Sort *plan, double limit_tuples)
*/
Assert(IsA(plan, Sort));
- cost_sort(&sort_path, root, NIL,
+ cost_sort(&sort_path, root, rel, NIL,
lefttree->total_cost,
plan->plan.disabled_nodes,
lefttree->plan_rows,
@@ -6524,7 +6528,7 @@ make_material(Plan *lefttree)
* Path representation, but it's not worth the trouble yet.
*/
Plan *
-materialize_finished_plan(Plan *subplan)
+materialize_finished_plan(Plan *subplan, RelOptInfo *rel)
{
Plan *matplan;
Path matpath; /* dummy for result of cost_material */
@@ -6549,7 +6553,7 @@ materialize_finished_plan(Plan *subplan)
subplan->total_cost -= initplan_cost;
/* Set cost data */
- cost_material(&matpath,
+ cost_material(&matpath, rel,
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 b5827d3980..c0f431cf0d 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -427,7 +427,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
if (cursorOptions & CURSOR_OPT_SCROLL)
{
if (!ExecSupportsBackwardScan(top_plan))
- top_plan = materialize_finished_plan(top_plan);
+ top_plan = materialize_finished_plan(top_plan, final_rel);
}
/*
@@ -3831,7 +3831,8 @@ create_grouping_paths(PlannerInfo *root,
* support grouping sets. create_ordinary_grouping_paths() will check
* additional conditions, such as whether input_rel is partitioned.
*/
- if (enable_partitionwise_aggregate && !parse->groupingSets)
+ if (REL_CAN_USE_PATH(grouped_rel, PartitionwiseAggregate) &&
+ !parse->groupingSets)
extra.patype = PARTITIONWISE_AGGREGATE_FULL;
else
extra.patype = PARTITIONWISE_AGGREGATE_NONE;
@@ -4634,7 +4635,8 @@ create_one_window_path(PlannerInfo *root,
* No presorted keys or incremental sort disabled, just perform a
* complete sort.
*/
- if (presorted_keys == 0 || !enable_incremental_sort)
+ if (presorted_keys == 0 ||
+ !REL_CAN_USE_PATH(window_rel, IncrementalSort))
path = (Path *) create_sort_path(root, window_rel,
path,
window_pathkeys,
@@ -4893,7 +4895,8 @@ create_partial_distinct_paths(PlannerInfo *root, RelOptInfo *input_rel,
* cheapest partial path).
*/
if (input_path != cheapest_partial_path &&
- (presorted_keys == 0 || !enable_incremental_sort))
+ (presorted_keys == 0 ||
+ !REL_CAN_USE_PATH(partial_distinct_rel, IncrementalSort)))
continue;
/*
@@ -4901,7 +4904,8 @@ create_partial_distinct_paths(PlannerInfo *root, RelOptInfo *input_rel,
* We'll just do a sort if there are no presorted keys and an
* incremental sort when there are presorted keys.
*/
- if (presorted_keys == 0 || !enable_incremental_sort)
+ if (presorted_keys == 0 ||
+ !REL_CAN_USE_PATH(partial_distinct_rel, IncrementalSort))
sorted_path = (Path *) create_sort_path(root,
partial_distinct_rel,
input_path,
@@ -4961,10 +4965,12 @@ create_partial_distinct_paths(PlannerInfo *root, RelOptInfo *input_rel,
/*
* Now try hash aggregate paths, if enabled and hashing is possible. Since
* we're not on the hook to ensure we do our best to create at least one
- * path here, we treat enable_hashagg as a hard off-switch rather than the
- * slightly softer variant in create_final_distinct_paths.
+ * path here, we treat completely skip this if hash aggregation is not
+ * enabled. (In contrast, create_final_distinct_paths sometimes considers
+ * hash aggregation even when it's disabled, to avoid failing completely.)
*/
- if (enable_hashagg && grouping_is_hashable(root->processed_distinctClause))
+ if (REL_CAN_USE_PATH(partial_distinct_rel, HashAgg) &&
+ grouping_is_hashable(root->processed_distinctClause))
{
add_partial_path(partial_distinct_rel, (Path *)
create_agg_path(root,
@@ -5105,7 +5111,8 @@ create_final_distinct_paths(PlannerInfo *root, RelOptInfo *input_rel,
* cheapest input path).
*/
if (input_path != cheapest_input_path &&
- (presorted_keys == 0 || !enable_incremental_sort))
+ (presorted_keys == 0 ||
+ !REL_CAN_USE_PATH(distinct_rel, IncrementalSort)))
continue;
/*
@@ -5113,7 +5120,8 @@ create_final_distinct_paths(PlannerInfo *root, RelOptInfo *input_rel,
* We'll just do a sort if there are no presorted keys and an
* incremental sort when there are presorted keys.
*/
- if (presorted_keys == 0 || !enable_incremental_sort)
+ if (presorted_keys == 0 ||
+ !REL_CAN_USE_PATH(distinct_rel, IncrementalSort))
sorted_path = (Path *) create_sort_path(root,
distinct_rel,
input_path,
@@ -5177,14 +5185,14 @@ create_final_distinct_paths(PlannerInfo *root, RelOptInfo *input_rel,
* die trying. If we do have other choices, there are two things that
* should prevent selection of hashing: if the query uses DISTINCT ON
* (because it won't really have the expected behavior if we hash), or if
- * enable_hashagg is off.
+ * hash aggregation is disabled.
*
* Note: grouping_is_hashable() is much more expensive to check than the
* other gating conditions, so we want to do it last.
*/
if (distinct_rel->pathlist == NIL)
allow_hash = true; /* we have no alternatives */
- else if (parse->hasDistinctOn || !enable_hashagg)
+ else if (parse->hasDistinctOn || !REL_CAN_USE_PATH(distinct_rel, HashAgg))
allow_hash = false; /* policy-based decision not to hash */
else
allow_hash = true; /* default */
@@ -5277,7 +5285,8 @@ create_ordered_paths(PlannerInfo *root,
* input path).
*/
if (input_path != cheapest_input_path &&
- (presorted_keys == 0 || !enable_incremental_sort))
+ (presorted_keys == 0 ||
+ !REL_CAN_USE_PATH(ordered_rel, IncrementalSort)))
continue;
/*
@@ -5285,7 +5294,8 @@ create_ordered_paths(PlannerInfo *root,
* We'll just do a sort if there are no presorted keys and an
* incremental sort when there are presorted keys.
*/
- if (presorted_keys == 0 || !enable_incremental_sort)
+ if (presorted_keys == 0 ||
+ !REL_CAN_USE_PATH(ordered_rel, IncrementalSort))
sorted_path = (Path *) create_sort_path(root,
ordered_rel,
input_path,
@@ -5349,7 +5359,8 @@ create_ordered_paths(PlannerInfo *root,
* partial path).
*/
if (input_path != cheapest_partial_path &&
- (presorted_keys == 0 || !enable_incremental_sort))
+ (presorted_keys == 0 ||
+ !REL_CAN_USE_PATH(ordered_rel, IncrementalSort)))
continue;
/*
@@ -5357,7 +5368,8 @@ create_ordered_paths(PlannerInfo *root,
* We'll just do a sort if there are no presorted keys and an
* incremental sort when there are presorted keys.
*/
- if (presorted_keys == 0 || !enable_incremental_sort)
+ if (presorted_keys == 0 ||
+ !REL_CAN_USE_PATH(ordered_rel, IncrementalSort))
sorted_path = (Path *) create_sort_path(root,
ordered_rel,
input_path,
@@ -6747,7 +6759,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,
+ cost_sort(&seqScanAndSortPath, root, rel, NIL,
seqScanPath->disabled_nodes,
seqScanPath->total_cost, rel->tuples, rel->reltarget->width,
comparisonCost, maintenance_work_mem, -1.0);
@@ -6931,7 +6943,8 @@ make_ordered_path(PlannerInfo *root, RelOptInfo *rel, Path *path,
* disabled unless it's the cheapest input path).
*/
if (path != cheapest_path &&
- (presorted_keys == 0 || !enable_incremental_sort))
+ (presorted_keys == 0 ||
+ !REL_CAN_USE_PATH(rel, IncrementalSort)))
return NULL;
/*
@@ -6939,7 +6952,8 @@ make_ordered_path(PlannerInfo *root, RelOptInfo *rel, Path *path,
* just do a sort if there are no presorted keys and an incremental
* sort when there are presorted keys.
*/
- if (presorted_keys == 0 || !enable_incremental_sort)
+ if (presorted_keys == 0 ||
+ !REL_CAN_USE_PATH(rel, IncrementalSort))
path = (Path *) create_sort_path(root,
rel,
path,
@@ -7540,7 +7554,8 @@ gather_grouping_paths(PlannerInfo *root, RelOptInfo *rel)
* disabled unless it's the cheapest input path).
*/
if (path != cheapest_partial_path &&
- (presorted_keys == 0 || !enable_incremental_sort))
+ (presorted_keys == 0 ||
+ !REL_CAN_USE_PATH(rel, IncrementalSort)))
continue;
/*
@@ -7548,7 +7563,8 @@ gather_grouping_paths(PlannerInfo *root, RelOptInfo *rel)
* just do a sort if there are no presorted keys and an incremental
* sort when there are presorted keys.
*/
- if (presorted_keys == 0 || !enable_incremental_sort)
+ if (presorted_keys == 0 ||
+ !REL_CAN_USE_PATH(rel, IncrementalSort))
path = (Path *) create_sort_path(root, rel, path,
groupby_pathkeys,
-1.0);
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 6d003cc8e5..6dbcf6e0aa 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -525,13 +525,14 @@ build_subplan(PlannerInfo *root, Plan *plan, Path *path,
* is pointless for a direct-correlated subplan, since we'd have to
* recompute its results each time anyway. For uncorrelated/undirect
* correlated subplans, we add Material unless the subplan's top plan
- * node would materialize its output anyway. Also, if enable_material
- * is false, then the user does not want us to materialize anything
+ * node would materialize its output anyway. Also, if Materialize is
+ * dissabled, then the user does not want us to materialize anything
* unnecessarily, so we don't.
*/
- else if (splan->parParam == NIL && enable_material &&
+ else if (splan->parParam == NIL &&
+ REL_CAN_USE_PATH(path->parent, Material) &&
!ExecMaterializesOutput(nodeTag(plan)))
- plan = materialize_finished_plan(plan);
+ plan = materialize_finished_plan(plan, path->parent);
result = (Node *) splan;
isInitPlan = false;
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index a0baf6d4a1..f8fd5db66b 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -587,7 +587,8 @@ build_setop_child_paths(PlannerInfo *root, RelOptInfo *rel,
* input path).
*/
if (subpath != cheapest_input_path &&
- (presorted_keys == 0 || !enable_incremental_sort))
+ (presorted_keys == 0 ||
+ !REL_CAN_USE_PATH(final_rel, IncrementalSort)))
continue;
/*
@@ -595,7 +596,8 @@ build_setop_child_paths(PlannerInfo *root, RelOptInfo *rel,
* We'll just do a sort if there are no presorted keys and an
* incremental sort when there are presorted keys.
*/
- if (presorted_keys == 0 || !enable_incremental_sort)
+ if (presorted_keys == 0 ||
+ !REL_CAN_USE_PATH(final_rel, IncrementalSort))
subpath = (Path *) create_sort_path(rel->subroot,
final_rel,
subpath,
@@ -867,7 +869,7 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
* the children. The precise formula is just a guess; see
* add_paths_to_append_rel.
*/
- if (enable_parallel_append)
+ if (REL_CAN_USE_PATH(result_rel, ParallelAppend))
{
parallel_workers = Max(parallel_workers,
pg_leftmost_one_pos32(list_length(partial_pathlist)) + 1);
@@ -879,7 +881,8 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
papath = (Path *)
create_append_path(root, result_rel, NIL, partial_pathlist,
NIL, NULL, parallel_workers,
- enable_parallel_append, -1);
+ REL_CAN_USE_PATH(result_rel, ParallelAppend),
+ -1);
gpath = (Path *)
create_gather_path(root, result_rel, papath,
result_rel->reltarget, NULL, NULL);
@@ -1319,8 +1322,8 @@ choose_hashed_setop(PlannerInfo *root, List *groupClauses,
errmsg("could not implement %s", construct),
errdetail("Some of the datatypes only support hashing, while others only support sorting.")));
- /* Prefer sorting when enable_hashagg is off */
- if (!enable_hashagg)
+ /* Prefer sorting when hash aggregation is disabled */
+ if (!REL_CAN_USE_PATH(input_path->parent, HashAgg))
return false;
/*
@@ -1343,7 +1346,7 @@ choose_hashed_setop(PlannerInfo *root, List *groupClauses,
* These path variables are dummies that just hold cost fields; we don't
* make actual Paths for these steps.
*/
- cost_agg(&hashed_p, root, AGG_HASHED, NULL,
+ cost_agg(&hashed_p, root, input_path->parent, AGG_HASHED, NULL,
numGroupCols, dNumGroups,
NIL,
input_path->disabled_nodes,
@@ -1358,7 +1361,8 @@ choose_hashed_setop(PlannerInfo *root, List *groupClauses,
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.disabled_nodes,
+ cost_sort(&sorted_p, root, input_path->parent, NIL,
+ sorted_p.disabled_nodes,
sorted_p.total_cost,
input_path->rows, input_path->pathtarget->width,
0.0, work_mem, -1.0);
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index fc97bf6ee2..77ed747437 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -1537,6 +1537,7 @@ create_merge_append_path(PlannerInfo *root,
cost_sort(&sort_path,
root,
+ rel,
pathkeys,
subpath->disabled_nodes,
subpath->total_cost,
@@ -1649,7 +1650,7 @@ create_material_path(RelOptInfo *rel, Path *subpath)
pathnode->subpath = subpath;
- cost_material(&pathnode->path,
+ cost_material(&pathnode->path, rel,
subpath->disabled_nodes,
subpath->startup_cost,
subpath->total_cost,
@@ -1698,7 +1699,7 @@ 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);
+ Assert(REL_CAN_USE_PATH(rel, Memoize));
pathnode->path.disabled_nodes = subpath->disabled_nodes;
/*
@@ -1866,7 +1867,7 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath,
/*
* Estimate cost for sort+unique implementation
*/
- cost_sort(&sort_path, root, NIL,
+ cost_sort(&sort_path, root, rel, NIL,
subpath->disabled_nodes,
subpath->total_cost,
rel->rows,
@@ -1901,7 +1902,7 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath,
sjinfo->semi_can_hash = false;
}
else
- cost_agg(&agg_path, root,
+ cost_agg(&agg_path, root, rel,
AGG_HASHED, NULL,
numCols, pathnode->path.rows,
NIL,
@@ -3101,7 +3102,7 @@ create_sort_path(PlannerInfo *root,
pathnode->subpath = subpath;
- cost_sort(&pathnode->path, root, pathkeys,
+ cost_sort(&pathnode->path, root, rel, pathkeys,
subpath->disabled_nodes,
subpath->total_cost,
subpath->rows,
@@ -3288,7 +3289,7 @@ create_agg_path(PlannerInfo *root,
pathnode->groupClause = groupClause;
pathnode->qual = qual;
- cost_agg(&pathnode->path, root,
+ cost_agg(&pathnode->path, root, rel,
aggstrategy, aggcosts,
list_length(groupClause), numGroups,
qual,
@@ -3395,7 +3396,7 @@ create_groupingsets_path(PlannerInfo *root,
*/
if (is_first)
{
- cost_agg(&pathnode->path, root,
+ cost_agg(&pathnode->path, root, rel,
aggstrategy,
agg_costs,
numGroupCols,
@@ -3421,7 +3422,7 @@ create_groupingsets_path(PlannerInfo *root,
* Account for cost of aggregation, but don't charge input
* cost again
*/
- cost_agg(&agg_path, root,
+ cost_agg(&agg_path, root, rel,
rollup->is_hashed ? AGG_HASHED : AGG_SORTED,
agg_costs,
numGroupCols,
@@ -3436,7 +3437,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, 0,
+ cost_sort(&sort_path, root, rel, NIL, 0,
0.0,
subpath->rows,
subpath->pathtarget->width,
@@ -3446,7 +3447,7 @@ create_groupingsets_path(PlannerInfo *root,
/* Account for cost of aggregation */
- cost_agg(&agg_path, root,
+ cost_agg(&agg_path, root, rel,
AGG_SORTED,
agg_costs,
numGroupCols,
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 78a3cfafde..3b9a3746ee 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -570,7 +570,8 @@ 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.
+ * removing an index, adding a hypothetical index to the indexlist, or
+ * changing the path type mask.
*/
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 d7266e4cdb..88e468795a 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -211,6 +211,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->path_type_mask = default_path_type_mask;
rel->reltarget = create_empty_pathtarget();
rel->pathlist = NIL;
rel->ppilist = NIL;
@@ -707,6 +708,7 @@ build_join_rel(PlannerInfo *root,
joinrel->consider_startup = (root->tuple_fraction > 0);
joinrel->consider_param_startup = false;
joinrel->consider_parallel = false;
+ joinrel->path_type_mask = default_path_type_mask;
joinrel->reltarget = create_empty_pathtarget();
joinrel->pathlist = NIL;
joinrel->ppilist = NIL;
@@ -900,6 +902,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->path_type_mask = default_path_type_mask;
joinrel->reltarget = create_empty_pathtarget();
joinrel->pathlist = NIL;
joinrel->ppilist = NIL;
@@ -1484,6 +1487,7 @@ fetch_upper_rel(PlannerInfo *root, UpperRelationKind kind, Relids relids)
upperrel->consider_startup = (root->tuple_fraction > 0);
upperrel->consider_param_startup = false;
upperrel->consider_parallel = false; /* might get changed later */
+ upperrel->path_type_mask = default_path_type_mask;
upperrel->reltarget = create_empty_pathtarget();
upperrel->pathlist = NIL;
upperrel->cheapest_startup_path = NULL;
@@ -2010,7 +2014,7 @@ build_joinrel_partition_info(PlannerInfo *root,
PartitionScheme part_scheme;
/* Nothing to do if partitionwise join technique is disabled. */
- if (!enable_partitionwise_join)
+ if (!REL_CAN_USE_PATH(joinrel, PartitionwiseJoin))
{
Assert(!IS_PARTITIONED_REL(joinrel));
return;
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index af227b1f24..1bd21bbe34 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -55,6 +55,7 @@
#include "optimizer/geqo.h"
#include "optimizer/optimizer.h"
#include "optimizer/paths.h"
+#include "optimizer/pathnode.h"
#include "optimizer/planmain.h"
#include "parser/parse_expr.h"
#include "parser/parser.h"
@@ -777,7 +778,7 @@ struct config_bool ConfigureNamesBool[] =
},
&enable_seqscan,
true,
- NULL, NULL, NULL
+ NULL, enable_seqscan_assign_hook, NULL
},
{
{"enable_indexscan", PGC_USERSET, QUERY_TUNING_METHOD,
@@ -787,7 +788,7 @@ struct config_bool ConfigureNamesBool[] =
},
&enable_indexscan,
true,
- NULL, NULL, NULL
+ NULL, enable_indexscan_assign_hook, NULL
},
{
{"enable_indexonlyscan", PGC_USERSET, QUERY_TUNING_METHOD,
@@ -797,7 +798,7 @@ struct config_bool ConfigureNamesBool[] =
},
&enable_indexonlyscan,
true,
- NULL, NULL, NULL
+ NULL, enable_indexonlyscan_assign_hook, NULL
},
{
{"enable_bitmapscan", PGC_USERSET, QUERY_TUNING_METHOD,
@@ -807,7 +808,7 @@ struct config_bool ConfigureNamesBool[] =
},
&enable_bitmapscan,
true,
- NULL, NULL, NULL
+ NULL, enable_bitmapscan_assign_hook, NULL
},
{
{"enable_tidscan", PGC_USERSET, QUERY_TUNING_METHOD,
@@ -817,7 +818,7 @@ struct config_bool ConfigureNamesBool[] =
},
&enable_tidscan,
true,
- NULL, NULL, NULL
+ NULL, enable_tidscan_assign_hook, NULL
},
{
{"enable_sort", PGC_USERSET, QUERY_TUNING_METHOD,
@@ -827,7 +828,7 @@ struct config_bool ConfigureNamesBool[] =
},
&enable_sort,
true,
- NULL, NULL, NULL
+ NULL, enable_sort_assign_hook, NULL
},
{
{"enable_incremental_sort", PGC_USERSET, QUERY_TUNING_METHOD,
@@ -837,7 +838,7 @@ struct config_bool ConfigureNamesBool[] =
},
&enable_incremental_sort,
true,
- NULL, NULL, NULL
+ NULL, enable_incremental_sort_assign_hook, NULL
},
{
{"enable_hashagg", PGC_USERSET, QUERY_TUNING_METHOD,
@@ -847,7 +848,7 @@ struct config_bool ConfigureNamesBool[] =
},
&enable_hashagg,
true,
- NULL, NULL, NULL
+ NULL, enable_hashagg_assign_hook, NULL
},
{
{"enable_material", PGC_USERSET, QUERY_TUNING_METHOD,
@@ -857,7 +858,7 @@ struct config_bool ConfigureNamesBool[] =
},
&enable_material,
true,
- NULL, NULL, NULL
+ NULL, enable_material_assign_hook, NULL
},
{
{"enable_memoize", PGC_USERSET, QUERY_TUNING_METHOD,
@@ -867,7 +868,7 @@ struct config_bool ConfigureNamesBool[] =
},
&enable_memoize,
true,
- NULL, NULL, NULL
+ NULL, enable_memoize_assign_hook, NULL
},
{
{"enable_nestloop", PGC_USERSET, QUERY_TUNING_METHOD,
@@ -877,7 +878,7 @@ struct config_bool ConfigureNamesBool[] =
},
&enable_nestloop,
true,
- NULL, NULL, NULL
+ NULL, enable_nestloop_assign_hook, NULL
},
{
{"enable_mergejoin", PGC_USERSET, QUERY_TUNING_METHOD,
@@ -887,7 +888,7 @@ struct config_bool ConfigureNamesBool[] =
},
&enable_mergejoin,
true,
- NULL, NULL, NULL
+ NULL, enable_mergejoin_assign_hook, NULL
},
{
{"enable_hashjoin", PGC_USERSET, QUERY_TUNING_METHOD,
@@ -897,7 +898,7 @@ struct config_bool ConfigureNamesBool[] =
},
&enable_hashjoin,
true,
- NULL, NULL, NULL
+ NULL, enable_hashjoin_assign_hook, NULL
},
{
{"enable_gathermerge", PGC_USERSET, QUERY_TUNING_METHOD,
@@ -907,7 +908,7 @@ struct config_bool ConfigureNamesBool[] =
},
&enable_gathermerge,
true,
- NULL, NULL, NULL
+ NULL, enable_gathermerge_assign_hook, NULL
},
{
{"enable_partitionwise_join", PGC_USERSET, QUERY_TUNING_METHOD,
@@ -917,7 +918,7 @@ struct config_bool ConfigureNamesBool[] =
},
&enable_partitionwise_join,
false,
- NULL, NULL, NULL
+ NULL, enable_partitionwise_join_assign_hook, NULL
},
{
{"enable_partitionwise_aggregate", PGC_USERSET, QUERY_TUNING_METHOD,
@@ -927,7 +928,7 @@ struct config_bool ConfigureNamesBool[] =
},
&enable_partitionwise_aggregate,
false,
- NULL, NULL, NULL
+ NULL, enable_partitionwise_aggregate_assign_hook, NULL
},
{
{"enable_parallel_append", PGC_USERSET, QUERY_TUNING_METHOD,
@@ -937,7 +938,7 @@ struct config_bool ConfigureNamesBool[] =
},
&enable_parallel_append,
true,
- NULL, NULL, NULL
+ NULL, enable_parallel_append_assign_hook, NULL
},
{
{"enable_parallel_hash", PGC_USERSET, QUERY_TUNING_METHOD,
@@ -947,7 +948,7 @@ struct config_bool ConfigureNamesBool[] =
},
&enable_parallel_hash,
true,
- NULL, NULL, NULL
+ NULL, enable_parallel_hash_assign_hook, NULL
},
{
{"enable_partition_pruning", PGC_USERSET, QUERY_TUNING_METHOD,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 540d021592..bbff482cec 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -80,6 +80,26 @@ typedef enum UpperRelationKind
/* NB: UPPERREL_FINAL must be last enum entry; it's used to size arrays */
} UpperRelationKind;
+#define PathTypeBitmapScan 0x00000001
+#define PathTypeGatherMerge 0x00000002
+#define PathTypeHashAgg 0x00000004
+#define PathTypeHashJoin 0x00000008
+#define PathTypeIncrementalSort 0x00000010
+#define PathTypeIndexScan 0x00000020
+#define PathTypeIndexOnlyScan 0x00000040
+#define PathTypeMaterial 0x00000080
+#define PathTypeMemoize 0x00000100
+#define PathTypeMergeJoin 0x00000200
+#define PathTypeNestLoop 0x00000400
+#define PathTypeParallelAppend 0x00000800
+#define PathTypeParallelHash 0x00001000
+#define PathTypePartitionwiseJoin 0x00002000
+#define PathTypePartitionwiseAggregate 0x00004000
+#define PathTypeSeqScan 0x00008000
+#define PathTypeSort 0x00010000
+#define PathTypeTIDScan 0x00020000
+#define PathTypeMaskAll 0x0003FFFF
+
/*----------
* PlannerGlobal
* Global information for planning/optimization
@@ -879,6 +899,8 @@ typedef struct RelOptInfo
bool consider_param_startup;
/* consider parallel paths? */
bool consider_parallel;
+ /* path type mask for this rel */
+ uint32 path_type_mask;
/*
* default result targetlist for Paths scanning this relation; list of
@@ -1065,6 +1087,13 @@ typedef struct RelOptInfo
((rel)->part_scheme && (rel)->boundinfo && (rel)->nparts > 0 && \
(rel)->part_rels && (rel)->partexprs && (rel)->nullable_partexprs)
+/*
+ * Convenience macro to test for whether a certain a PathTypeXXX bit is
+ * set in a relation's path_type_mask.
+ */
+#define REL_CAN_USE_PATH(rel, type) \
+ (((rel)->path_type_mask & PathType##type) != 0)
+
/*
* IndexOptInfo
* Per-index information for planning/optimization
diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h
index 854a782944..2cd1d8c34c 100644
--- a/src/include/optimizer/cost.h
+++ b/src/include/optimizer/cost.h
@@ -71,6 +71,26 @@ extern PGDLLIMPORT bool enable_partition_pruning;
extern PGDLLIMPORT bool enable_presorted_aggregate;
extern PGDLLIMPORT bool enable_async_append;
extern PGDLLIMPORT int constraint_exclusion;
+extern PGDLLIMPORT uint32 default_path_type_mask;
+
+extern void enable_bitmapscan_assign_hook(bool newval, void *extra);
+extern void enable_gathermerge_assign_hook(bool newval, void *extra);
+extern void enable_hashagg_assign_hook(bool newval, void *extra);
+extern void enable_hashjoin_assign_hook(bool newval, void *extra);
+extern void enable_incremental_sort_assign_hook(bool newval, void *extra);
+extern void enable_indexscan_assign_hook(bool newval, void *extra);
+extern void enable_indexonlyscan_assign_hook(bool newval, void *extra);
+extern void enable_material_assign_hook(bool newval, void *extra);
+extern void enable_memoize_assign_hook(bool newval, void *extra);
+extern void enable_mergejoin_assign_hook(bool newval, void *extra);
+extern void enable_nestloop_assign_hook(bool newval, void *extra);
+extern void enable_parallel_append_assign_hook(bool newval, void *extra);
+extern void enable_parallel_hash_assign_hook(bool newval, void *extra);
+extern void enable_partitionwise_join_assign_hook(bool newval, void *extra);
+extern void enable_partitionwise_aggregate_assign_hook(bool newval, void *extra);
+extern void enable_seqscan_assign_hook(bool newval, void *extra);
+extern void enable_sort_assign_hook(bool newval, void *extra);
+extern void enable_tidscan_assign_hook(bool newval, void *extra);
extern double index_pages_fetched(double tuples_fetched, BlockNumber pages,
double index_pages, PlannerInfo *root);
@@ -107,7 +127,7 @@ extern void cost_namedtuplestorescan(Path *path, PlannerInfo *root,
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,
+extern void cost_sort(Path *path, PlannerInfo *root, RelOptInfo *rel,
List *pathkeys, int disabled_nodes,
Cost input_cost, double tuples, int width,
Cost comparison_cost, int sort_mem,
@@ -124,11 +144,11 @@ extern void cost_merge_append(Path *path, PlannerInfo *root,
int input_disabled_nodes,
Cost input_startup_cost, Cost input_total_cost,
double tuples);
-extern void cost_material(Path *path,
+extern void cost_material(Path *path, RelOptInfo *rel,
int input_disabled_nodes,
Cost input_startup_cost, Cost input_total_cost,
double tuples, int width);
-extern void cost_agg(Path *path, PlannerInfo *root,
+extern void cost_agg(Path *path, PlannerInfo *root, RelOptInfo *rel,
AggStrategy aggstrategy, const AggClauseCosts *aggcosts,
int numGroupCols, double numGroups,
List *quals,
@@ -148,6 +168,7 @@ extern void cost_group(Path *path, PlannerInfo *root,
double input_tuples);
extern void initial_cost_nestloop(PlannerInfo *root,
JoinCostWorkspace *workspace,
+ RelOptInfo *joinrel,
JoinType jointype,
Path *outer_path, Path *inner_path,
JoinPathExtraData *extra);
@@ -156,6 +177,7 @@ extern void final_cost_nestloop(PlannerInfo *root, NestPath *path,
JoinPathExtraData *extra);
extern void initial_cost_mergejoin(PlannerInfo *root,
JoinCostWorkspace *workspace,
+ RelOptInfo *joinrel,
JoinType jointype,
List *mergeclauses,
Path *outer_path, Path *inner_path,
@@ -166,6 +188,7 @@ extern void final_cost_mergejoin(PlannerInfo *root, MergePath *path,
JoinPathExtraData *extra);
extern void initial_cost_hashjoin(PlannerInfo *root,
JoinCostWorkspace *workspace,
+ RelOptInfo *joinrel,
JoinType jointype,
List *hashclauses,
Path *outer_path, Path *inner_path,
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index aafc173792..07623eff79 100644
--- a/src/include/optimizer/planmain.h
+++ b/src/include/optimizer/planmain.h
@@ -45,7 +45,7 @@ extern ForeignScan *make_foreignscan(List *qptlist, List *qpqual,
Plan *outer_plan);
extern Plan *change_plan_targetlist(Plan *subplan, List *tlist,
bool tlist_parallel_safe);
-extern Plan *materialize_finished_plan(Plan *subplan);
+extern Plan *materialize_finished_plan(Plan *subplan, RelOptInfo *rel);
extern bool is_projection_capable_path(Path *path);
extern bool is_projection_capable_plan(Plan *plan);
--
2.39.3 (Apple Git-145)
^ permalink raw reply [nested|flat] 5+ messages in thread
* Re: allowing extensions to control planner behavior
@ 2024-08-28 13:46 Jakub Wartak <[email protected]>
parent: Robert Haas <[email protected]>
1 sibling, 0 replies; 5+ messages in thread
From: Jakub Wartak @ 2024-08-28 13:46 UTC (permalink / raw)
To: Robert Haas <[email protected]>; +Cc: pgsql-hackers
Hi Robert,
On Mon, Aug 26, 2024 at 6:33 PM Robert Haas <[email protected]> wrote:
>
> I'm somewhat expecting to be flamed to a well-done crisp for saying
> this, but I think we need better ways for extensions to control the
> behavior of PostgreSQL's query planner.
[..]
> [..] But all that
> said, as much as anything, I want to get some feedback on what
> approaches and trade-offs people think might be acceptable here,
> because there's not much point in me spending a bunch of time writing
> code that everyone (or a critical mass of people) are going to hate.
As a somewhat tiny culprit of the self-flaming done by Robert (due to
nagging him about this in the past on various occasions), I'm of
course obligated to +1 to any work related to giving end-users/DBA the
ability to cage the plans generated by the optimizer.
When dealing with issues like those, I have a feeling we have 2
classes of most frequent issues being reported (that's my subjective
experience):
a. cardinality misestimate leading usually to nest loop plans (e.g.
JOIN estimates thread [1] could also somehow help and it also has nice
reproducers)
b. issues after upgrades
So the "selectivity estimation hook(s)" mentioned by Andrei seems to
be a must, but also the ability not to just guess & tweak (shape) the
plan, but a way to export all SQL plans before upgrade with capability
to import and override(lock) specific SQL query to specific plan from
before upgrade.
I'm not into the internals of optimizer at all, but here are other
random thoughts/questions:
- I do think that "hints" words have bad connotations and should not
be used. It might be because of embedding them in SQL query text of
the application itself. On one front they are localized to the SQL
(good), but the PG operator has no realistic way of altering that once
it's embedded in binary (bad), as the application team is usually
separate if not from an external company (very bad situation, but
happens almost always).
- Would stacking of such extensions, each overriding the same planner
hooks, be allowed or not in the long run ? Technically there's nothing
preventing it and I think I could imagine someone attempting to run
multiple planner hook hotfixes for multiple issues, all at once?
- Shouldn't EXPLAIN contain additional information that for that
specific plan the optimizer hooks changed at least 1 thing ? (e.g.
"Plan was tainted" or something like that). Maybe extension could mark
it politely that it did by setting a certain flag, or maybe there
should be routines exposed by the core to do that ?
- the "b" (upgrade) seems like a much more heavy duty issue, as that
would require transfer of version-independent and textualized dump of
SQL plan that could be back-filled into a newer version of optimizer.
Is such a big thing realistic at all and it's better to just
concentrate on the hooks approach?
-J.
[1] - https://www.postgresql.org/message-id/flat/c8c0ff31-3a8a-7562-bbd3-78b2ec65f16c%40enterprisedb.com
^ permalink raw reply [nested|flat] 5+ messages in thread
* Re: allowing extensions to control planner behavior
@ 2024-08-28 20:29 Jeff Davis <[email protected]>
parent: Robert Haas <[email protected]>
1 sibling, 1 reply; 5+ messages in thread
From: Jeff Davis @ 2024-08-28 20:29 UTC (permalink / raw)
To: Robert Haas <[email protected]>; pgsql-hackers
On Mon, 2024-08-26 at 12:32 -0400, Robert Haas wrote:
> I think there are two basic approaches that are possible here. If
> someone sees a third option, let me know. First, we could allow users
> to hook add_path() and add_partial_path().
...
> The other possible approach is to allow extensions to feed some
> information into the planner before path generation and let that
> influence which paths are generated.
Preserving a path for the right amount of time seems like the primary
challenge for most of the use cases you raised (removing paths is
easier than resurrecting one that was pruned too early). If we try to
keep a path around, that implies that we need to keep parent paths
around too, which leads to an explosion if we aren't careful.
But we already solved all of that for pathkeys. We keep the paths
around if there's a reason to (a useful pathkey) and there's not some
other cheaper path that also satisfies the same reason.
Idea: generalize the idea of "pathkeys" to work for other reasons to
preserve a path.
Mechanically, a hint to use an index could work very similarly: come up
with a custom reason to keep a path around, such as "a hint suggests we
use index foo_idx for table foo", and assign it a unique number. If
there's another hint that says we should also use index bar_idx for
table bar, then that reason would get a different unique reason number.
(In other words, the number of reasons would not be fixed; there could
be one reason for each hint specified in the query, kind of like there
could be many interesting pathkeys for a query.)
Each Path would have a "preserve_for_these_reasons" bitmapset holding
all of the non-cost reasons we are preserving that path. If two paths
have exactly the same set of reasons, then add_path() would only keep
the cheaper one.
We could get fancy and have a compare_reasons_hook that would allow you
to take two paths with the same reason and see if there are other
factors to consider that would cause both to still be preserved
(similar to pathkey length).
I suspect that we might see interesting applications of this mechanism
in core as well: for instance, track partition keys or other properties
relevant to parallelism. That could allow us to keep parallel-friendly
paths around and then decide later in the planning process whether to
actually parallelize them or not.
Once we've generalized the "reasons" mechnism, it would be easy enough
to have a hook to add reasons to a path as it's being generated to be
sure it's not lost. These hooks should probably be called in the
individual create_*_path() functions where there's enough context to
know what's happening. There could be many such hooks, but I suspect
only a handful of important ones.
This idea allows the extension author to preserve the right paths long
enough to use set_rel_pathlist_hook/set_join_pathlist_hook, which can
editorialize on costs or do its own pruning.
Regards,
Jeff Davis
^ permalink raw reply [nested|flat] 5+ messages in thread
* Re: allowing extensions to control planner behavior
@ 2024-08-28 20:35 Robert Haas <[email protected]>
parent: Jeff Davis <[email protected]>
0 siblings, 0 replies; 5+ messages in thread
From: Robert Haas @ 2024-08-28 20:35 UTC (permalink / raw)
To: Jeff Davis <[email protected]>; +Cc: pgsql-hackers
On Wed, Aug 28, 2024 at 4:29 PM Jeff Davis <[email protected]> wrote:
> Preserving a path for the right amount of time seems like the primary
> challenge for most of the use cases you raised (removing paths is
> easier than resurrecting one that was pruned too early). If we try to
> keep a path around, that implies that we need to keep parent paths
> around too, which leads to an explosion if we aren't careful.
>
> But we already solved all of that for pathkeys. We keep the paths
> around if there's a reason to (a useful pathkey) and there's not some
> other cheaper path that also satisfies the same reason.
But we've already solved it for this case, too. This is exactly what
incrementing disabled_nodes does. This very recently replaced what we
did previously, which was adding disable_cost to the cost of every
path. Either way, you just need a hook that lets you disable the paths
that you don't prefer. Once you do that, add_path() takes care of the
rest: disabled paths lose to non-disabled paths, and disabled paths
lose to more expensive disabled paths.
--
Robert Haas
EDB: http://www.enterprisedb.com
^ permalink raw reply [nested|flat] 5+ messages in thread
end of thread, other threads:[~2024-08-28 20:35 UTC | newest]
Thread overview: 5+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2020-08-12 18:02 [PATCH v4] fix HOT tuples while scanning for index builds Alvaro Herrera <[email protected]>
2024-08-26 16:32 allowing extensions to control planner behavior Robert Haas <[email protected]>
2024-08-28 13:46 ` Re: allowing extensions to control planner behavior Jakub Wartak <[email protected]>
2024-08-28 20:29 ` Re: allowing extensions to control planner behavior Jeff Davis <[email protected]>
2024-08-28 20:35 ` Re: allowing extensions to control planner behavior Robert Haas <[email protected]>
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox