Add ALL_CANDIDATES option to EXPLAIN
Hi,
I have a prototype for an ALL_CANDIDATES option for EXPLAIN. The goal
of this option is to print all plan candidates instead of only the
cheapest plan. It will output the plans from the most expensive at the
top to the cheapest. Here's an example:
explain (all_candidates) select * from pgbench_accounts where aid=1;
QUERY PLAN
-----------------------------------------------------------------------------------------------------
Plan 1
-> Gather (cost=1000.00..3375.39 rows=1 width=97)
Workers Planned: 1
-> Parallel Seq Scan on pgbench_accounts
(cost=0.00..2375.29 rows=1 width=97)
Filter: (aid = 1)
Plan 2
-> Seq Scan on pgbench_accounts (cost=0.00..2890.00 rows=1 width=97)
Filter: (aid = 1)
Plan 3
-> Bitmap Heap Scan on pgbench_accounts (cost=4.30..8.31 rows=1 width=97)
Recheck Cond: (aid = 1)
-> Bitmap Index Scan on pgbench_accounts_pkey
(cost=0.00..4.30 rows=1 width=0)
Index Cond: (aid = 1)
Plan 4
-> Index Scan using pgbench_accounts_pkey on pgbench_accounts
(cost=0.29..8.31 rows=1 width=97)
Index Cond: (aid = 1)
This can provide very useful insight on the planner's decisions like
whether it tried to use a specific index and how much cost difference
there is with the top plan. Additionally, it makes it possible to spot
discrepancies in generated plans like incorrect row estimation [1]/messages/by-id/CAO6_Xqr9+51NxgO=XospEkUeAg-p=EjAWmtpdcZwjRgGKJ53iA@mail.gmail.com.
The plan list is generated from the upper_rel's pathlist. However, due
to how planning mutates the PlannerGlobal state, we can't directly
iterate the path list generated by the subquery_planner and create a
planned statement for them. Instead, we need to plan from scratch for
every path in the pathlist to generate the list of PlannedStmt.
The patch is split in two mostly to ease the review:
001: Propagate PlannerInfo root to add_path. This is needed to prevent
add_path from discarding path if all_candidates is enabled which will
be stored in PlannerGlobal.
002: Add the planner_all_candidates function and print of candidate
list in explain
[1]: /messages/by-id/CAO6_Xqr9+51NxgO=XospEkUeAg-p=EjAWmtpdcZwjRgGKJ53iA@mail.gmail.com
Regards,
Anthonin
Attachments:
v1-0002-Add-ALL_CANDIDATES-explain-option.patchapplication/octet-stream; name=v1-0002-Add-ALL_CANDIDATES-explain-option.patchDownload
From be4c08ea278ac9c506a87a5a0a6e6b19f3f2d610 Mon Sep 17 00:00:00 2001
From: Anthonin Bonnefoy <anthonin.bonnefoy@gmail.com>
Date: Mon, 15 Jul 2024 07:23:39 +0200
Subject: Add ALL_CANDIDATES explain option
When ALL_CANDIDATES is provided, the query planner is forced to keep all
paths instead of discarding the more expensive option. From those paths,
a list of PlannedStmt is generated which is then outputed by explain.
Due to how planning mutates the PlannerGlobal, we can't directly iterate
the path list generated by the subquery_planner and create a planned
statement for them. Instead, we need to plan from scratch for every path
in the pathlist to generate the list of PlannedStmt.
---
doc/src/sgml/ref/explain.sgml | 38 +++
src/backend/commands/explain.c | 57 +++-
src/backend/optimizer/plan/planner.c | 402 ++++++++++++++++----------
src/backend/optimizer/util/pathnode.c | 18 ++
src/backend/tcop/postgres.c | 44 +++
src/bin/psql/tab-complete.c | 4 +-
src/include/commands/explain.h | 1 +
src/include/nodes/pathnodes.h | 3 +
src/include/optimizer/optimizer.h | 2 +
src/include/tcop/tcopprot.h | 3 +
src/test/regress/expected/explain.out | 115 ++++++++
src/test/regress/sql/explain.sql | 12 +
12 files changed, 536 insertions(+), 163 deletions(-)
diff --git a/doc/src/sgml/ref/explain.sgml b/doc/src/sgml/ref/explain.sgml
index db9d3a8549a..d12bc80efb4 100644
--- a/doc/src/sgml/ref/explain.sgml
+++ b/doc/src/sgml/ref/explain.sgml
@@ -46,6 +46,7 @@ EXPLAIN [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] <rep
TIMING [ <replaceable class="parameter">boolean</replaceable> ]
SUMMARY [ <replaceable class="parameter">boolean</replaceable> ]
MEMORY [ <replaceable class="parameter">boolean</replaceable> ]
+ ALL_CANDIDATES [ <replaceable class="parameter">boolean</replaceable> ]
FORMAT { TEXT | XML | JSON | YAML }
</synopsis>
</refsynopsisdiv>
@@ -293,6 +294,22 @@ ROLLBACK;
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>ALL_CANDIDATES</literal></term>
+ <listitem>
+ <para>
+ Print all plan candidates. By default, the planner discards more expensive
+ plans. <literal>ALL_CANDIDATES</literal> forces the planner to keep all
+ plans which will be printed by <command>EXPLAIN</command>. The plans are
+ sorted from the most expensive at the top to the cheapest at the bottom.
+ The last plan and is the plan that is displayed by a normal
+ <command>EXPLAIN</command>.
+ This parameter may only be used when <literal>ANALYZE</literal> is not
+ enabled. It defaults to <literal>FALSE</literal>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>FORMAT</literal></term>
<listitem>
@@ -465,6 +482,27 @@ EXPLAIN (COSTS FALSE) SELECT * FROM foo WHERE i = 4;
Index Scan using fi on foo
Index Cond: (i = 4)
(2 rows)
+</programlisting>
+ </para>
+ <para>
+ Here is the same plan with all plan candidates:
+
+<programlisting>
+EXPLAIN (ALL_CANDIDATES) SELECT * FROM foo where id = 4;
+ QUERY PLAN
+---------------------------------------------------------------------------------
+ Plan 1
+ -> Seq Scan on foo (cost=0.00..170.00 rows=1 width=4)
+ Filter: (id = 4)
+ Plan 2
+ -> Bitmap Heap Scan on foo (cost=4.29..8.31 rows=1 width=4)
+ Recheck Cond: (id = 4)
+ -> Bitmap Index Scan on foo_id_idx (cost=0.00..4.29 rows=1 width=0)
+ Index Cond: (id = 4)
+ Plan 3
+ -> Index Only Scan using foo_id_idx on foo (cost=0.29..4.30 rows=1 width=4)
+ Index Cond: (id = 4)
+(11 rows)
</programlisting>
</para>
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 5771aabf40a..b850a8f7cbd 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -212,6 +212,8 @@ ExplainQuery(ParseState *pstate, ExplainStmt *stmt,
es->settings = defGetBoolean(opt);
else if (strcmp(opt->defname, "generic_plan") == 0)
es->generic = defGetBoolean(opt);
+ else if (strcmp(opt->defname, "all_candidates") == 0)
+ es->all_candidates = defGetBoolean(opt);
else if (strcmp(opt->defname, "timing") == 0)
{
timing_set = true;
@@ -303,6 +305,11 @@ ExplainQuery(ParseState *pstate, ExplainStmt *stmt,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("EXPLAIN options ANALYZE and GENERIC_PLAN cannot be used together")));
+ if (es->all_candidates && es->analyze)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("EXPLAIN options ANALYZE and ALL_CANDIDATES cannot be used together")));
+
/* if the summary was not set explicitly, set default value */
es->summary = (summary_set) ? es->summary : es->analyze;
@@ -458,7 +465,10 @@ standard_ExplainOneQuery(Query *query, int cursorOptions,
const char *queryString, ParamListInfo params,
QueryEnvironment *queryEnv)
{
- PlannedStmt *plan;
+ ListCell *plan_list;
+ List *plans = NULL;
+ PlannedStmt *plan = NULL;
+ int plan_number = 0;
instr_time planstart,
planduration;
BufferUsage bufusage_start,
@@ -488,7 +498,10 @@ standard_ExplainOneQuery(Query *query, int cursorOptions,
INSTR_TIME_SET_CURRENT(planstart);
/* plan the query */
- plan = pg_plan_query(query, queryString, cursorOptions, params);
+ if (es->all_candidates)
+ plans = pg_plan_query_all_candidates(query, queryString, cursorOptions, params);
+ else
+ plan = pg_plan_query(query, queryString, cursorOptions, params);
INSTR_TIME_SET_CURRENT(planduration);
INSTR_TIME_SUBTRACT(planduration, planstart);
@@ -507,9 +520,43 @@ standard_ExplainOneQuery(Query *query, int cursorOptions,
}
/* run it (if needed) and produce output */
- ExplainOnePlan(plan, into, es, queryString, params, queryEnv,
- &planduration, (es->buffers ? &bufusage : NULL),
- es->memory ? &mem_counters : NULL);
+ if (es->all_candidates)
+ {
+ /* We have a list of plan candidates, print them */
+ foreach(plan_list, plans)
+ {
+ char *plan_name;
+
+ plan = lfirst(plan_list);
+
+ plan_number++;
+ plan_name = psprintf("Plan %d", plan_number);
+
+ ExplainOpenGroup(plan_name, NULL, true, es);
+ if (es->format == EXPLAIN_FORMAT_TEXT)
+ {
+ ExplainIndentText(es);
+ appendStringInfoString(es->str, plan_name);
+ appendStringInfoString(es->str, "\n");
+ es->indent++;
+ }
+
+ ExplainOnePlan(plan, into, es, queryString, params, queryEnv,
+ &planduration, (es->buffers ? &bufusage : NULL),
+ es->memory ? &mem_counters : NULL);
+
+ ExplainCloseGroup(plan_name, NULL, true, es);
+ if (es->format == EXPLAIN_FORMAT_TEXT)
+ es->indent--;
+ }
+ }
+ else
+ {
+ /* We have the top plan from the planner, print it */
+ ExplainOnePlan(plan, into, es, queryString, params, queryEnv,
+ &planduration, (es->buffers ? &bufusage : NULL),
+ es->memory ? &mem_counters : NULL);
+ }
}
/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 8e6dd23df94..9a57d4f07bc 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -283,142 +283,13 @@ planner(Query *parse, const char *query_string, int cursorOptions,
return result;
}
-PlannedStmt *
-standard_planner(Query *parse, const char *query_string, int cursorOptions,
- ParamListInfo boundParams)
+static PlannedStmt *
+create_planned_stmt(PlannerInfo *root, Query *parse, Plan *plan, int cursorOptions)
{
PlannedStmt *result;
- PlannerGlobal *glob;
- double tuple_fraction;
- PlannerInfo *root;
- RelOptInfo *final_rel;
- Path *best_path;
- Plan *top_plan;
ListCell *lp,
*lr;
-
- /*
- * Set up global state for this planner invocation. This data is needed
- * across all levels of sub-Query that might exist in the given command,
- * so we keep it in a separate struct that's linked to by each per-Query
- * PlannerInfo.
- */
- glob = makeNode(PlannerGlobal);
-
- glob->boundParams = boundParams;
- glob->subplans = NIL;
- glob->subpaths = NIL;
- glob->subroots = NIL;
- glob->rewindPlanIDs = NULL;
- glob->finalrtable = NIL;
- glob->finalrteperminfos = NIL;
- glob->finalrowmarks = NIL;
- glob->resultRelations = NIL;
- glob->appendRelations = NIL;
- glob->relationOids = NIL;
- glob->invalItems = NIL;
- glob->paramExecTypes = NIL;
- glob->lastPHId = 0;
- glob->lastRowMarkId = 0;
- glob->lastPlanNodeId = 0;
- glob->transientPlan = false;
- glob->dependsOnRole = false;
-
- /*
- * Assess whether it's feasible to use parallel mode for this query. We
- * can't do this in a standalone backend, or if the command will try to
- * modify any data, or if this is a cursor operation, or if GUCs are set
- * to values that don't permit parallelism, or if parallel-unsafe
- * functions are present in the query tree.
- *
- * (Note that we do allow CREATE TABLE AS, SELECT INTO, and CREATE
- * MATERIALIZED VIEW to use parallel plans, but this is safe only because
- * the command is writing into a completely new table which workers won't
- * be able to see. If the workers could see the table, the fact that
- * group locking would cause them to ignore the leader's heavyweight GIN
- * page locks would make this unsafe. We'll have to fix that somehow if
- * we want to allow parallel inserts in general; updates and deletes have
- * additional problems especially around combo CIDs.)
- *
- * For now, we don't try to use parallel mode if we're running inside a
- * parallel worker. We might eventually be able to relax this
- * restriction, but for now it seems best not to have parallel workers
- * trying to create their own parallel workers.
- */
- if ((cursorOptions & CURSOR_OPT_PARALLEL_OK) != 0 &&
- IsUnderPostmaster &&
- parse->commandType == CMD_SELECT &&
- !parse->hasModifyingCTE &&
- max_parallel_workers_per_gather > 0 &&
- !IsParallelWorker())
- {
- /* all the cheap tests pass, so scan the query tree */
- glob->maxParallelHazard = max_parallel_hazard(parse);
- glob->parallelModeOK = (glob->maxParallelHazard != PROPARALLEL_UNSAFE);
- }
- else
- {
- /* skip the query tree scan, just assume it's unsafe */
- glob->maxParallelHazard = PROPARALLEL_UNSAFE;
- glob->parallelModeOK = false;
- }
-
- /*
- * glob->parallelModeNeeded is normally set to false here and changed to
- * true during plan creation if a Gather or Gather Merge plan is actually
- * created (cf. create_gather_plan, create_gather_merge_plan).
- *
- * However, if debug_parallel_query = on or debug_parallel_query =
- * regress, then we impose parallel mode whenever it's safe to do so, even
- * if the final plan doesn't use parallelism. It's not safe to do so if
- * the query contains anything parallel-unsafe; parallelModeOK will be
- * false in that case. Note that parallelModeOK can't change after this
- * point. Otherwise, everything in the query is either parallel-safe or
- * parallel-restricted, and in either case it should be OK to impose
- * parallel-mode restrictions. If that ends up breaking something, then
- * either some function the user included in the query is incorrectly
- * labeled as parallel-safe or parallel-restricted when in reality it's
- * parallel-unsafe, or else the query planner itself has a bug.
- */
- glob->parallelModeNeeded = glob->parallelModeOK &&
- (debug_parallel_query != DEBUG_PARALLEL_OFF);
-
- /* Determine what fraction of the plan is likely to be scanned */
- if (cursorOptions & CURSOR_OPT_FAST_PLAN)
- {
- /*
- * We have no real idea how many tuples the user will ultimately FETCH
- * from a cursor, but it is often the case that he doesn't want 'em
- * all, or would prefer a fast-start plan anyway so that he can
- * process some of the tuples sooner. Use a GUC parameter to decide
- * what fraction to optimize for.
- */
- tuple_fraction = cursor_tuple_fraction;
-
- /*
- * We document cursor_tuple_fraction as simply being a fraction, which
- * means the edge cases 0 and 1 have to be treated specially here. We
- * convert 1 to 0 ("all the tuples") and 0 to a very small fraction.
- */
- if (tuple_fraction >= 1.0)
- tuple_fraction = 0.0;
- else if (tuple_fraction <= 0.0)
- tuple_fraction = 1e-10;
- }
- else
- {
- /* Default assumption is we need all the tuples */
- tuple_fraction = 0.0;
- }
-
- /* primary planning entry point (may recurse for subqueries) */
- root = subquery_planner(glob, parse, NULL, false, tuple_fraction, NULL);
-
- /* Select best Path and turn it into a Plan */
- final_rel = fetch_upper_rel(root, UPPERREL_FINAL, NULL);
- best_path = get_cheapest_fractional_path(final_rel, tuple_fraction);
-
- top_plan = create_plan(root, best_path);
+ PlannerGlobal *glob = root->glob;
/*
* If creating a plan for a scrollable cursor, make sure it can run
@@ -426,8 +297,8 @@ 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);
+ if (!ExecSupportsBackwardScan(plan))
+ plan = materialize_finished_plan(plan);
}
/*
@@ -443,25 +314,25 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
* EXPLAIN to hide, so skip it when debug_parallel_query = regress.
*/
if (debug_parallel_query != DEBUG_PARALLEL_OFF &&
- top_plan->parallel_safe &&
- (top_plan->initPlan == NIL ||
+ plan->parallel_safe &&
+ (plan->initPlan == NIL ||
debug_parallel_query != DEBUG_PARALLEL_REGRESS))
{
Gather *gather = makeNode(Gather);
Cost initplan_cost;
bool unsafe_initplans;
- gather->plan.targetlist = top_plan->targetlist;
+ gather->plan.targetlist = plan->targetlist;
gather->plan.qual = NIL;
- gather->plan.lefttree = top_plan;
+ gather->plan.lefttree = plan;
gather->plan.righttree = NULL;
gather->num_workers = 1;
gather->single_copy = true;
gather->invisible = (debug_parallel_query == DEBUG_PARALLEL_REGRESS);
/* Transfer any initPlans to the new top node */
- gather->plan.initPlan = top_plan->initPlan;
- top_plan->initPlan = NIL;
+ gather->plan.initPlan = plan->initPlan;
+ plan->initPlan = NIL;
/*
* Since this Gather has no parallel-aware descendants to signal to,
@@ -473,28 +344,28 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
* Ideally we'd use cost_gather here, but setting up dummy path data
* to satisfy it doesn't seem much cleaner than knowing what it does.
*/
- gather->plan.startup_cost = top_plan->startup_cost +
+ gather->plan.startup_cost = plan->startup_cost +
parallel_setup_cost;
- gather->plan.total_cost = top_plan->total_cost +
- parallel_setup_cost + parallel_tuple_cost * top_plan->plan_rows;
- gather->plan.plan_rows = top_plan->plan_rows;
- gather->plan.plan_width = top_plan->plan_width;
+ gather->plan.total_cost = plan->total_cost +
+ parallel_setup_cost + parallel_tuple_cost * plan->plan_rows;
+ gather->plan.plan_rows = plan->plan_rows;
+ gather->plan.plan_width = plan->plan_width;
gather->plan.parallel_aware = false;
gather->plan.parallel_safe = false;
/*
- * Delete the initplans' cost from top_plan. We needn't add it to the
+ * Delete the initplans' cost from plan. We needn't add it to the
* Gather node, since the above coding already included it there.
*/
SS_compute_initplan_cost(gather->plan.initPlan,
&initplan_cost, &unsafe_initplans);
- top_plan->startup_cost -= initplan_cost;
- top_plan->total_cost -= initplan_cost;
+ plan->startup_cost -= initplan_cost;
+ plan->total_cost -= initplan_cost;
/* use parallel mode for parallel plans. */
- root->glob->parallelModeNeeded = true;
+ glob->parallelModeNeeded = true;
- top_plan = &gather->plan;
+ plan = &gather->plan;
}
/*
@@ -513,7 +384,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
SS_finalize_plan(subroot, subplan);
}
- SS_finalize_plan(root, top_plan);
+ SS_finalize_plan(root, plan);
}
/* final cleanup of the plan */
@@ -522,7 +393,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
Assert(glob->finalrowmarks == NIL);
Assert(glob->resultRelations == NIL);
Assert(glob->appendRelations == NIL);
- top_plan = set_plan_references(root, top_plan);
+ plan = set_plan_references(root, plan);
/* ... and the subplans (both regular subplans and initplans) */
Assert(list_length(glob->subplans) == list_length(glob->subroots));
forboth(lp, glob->subplans, lr, glob->subroots)
@@ -544,7 +415,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
result->transientPlan = glob->transientPlan;
result->dependsOnRole = glob->dependsOnRole;
result->parallelModeNeeded = glob->parallelModeNeeded;
- result->planTree = top_plan;
+ result->planTree = plan;
result->rtable = glob->finalrtable;
result->permInfos = glob->finalrteperminfos;
result->resultRelations = glob->resultRelations;
@@ -562,7 +433,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
result->jitFlags = PGJIT_NONE;
if (jit_enabled && jit_above_cost >= 0 &&
- top_plan->total_cost > jit_above_cost)
+ plan->total_cost > jit_above_cost)
{
result->jitFlags |= PGJIT_PERFORM;
@@ -570,10 +441,10 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
* Decide how much effort should be put into generating better code.
*/
if (jit_optimize_above_cost >= 0 &&
- top_plan->total_cost > jit_optimize_above_cost)
+ plan->total_cost > jit_optimize_above_cost)
result->jitFlags |= PGJIT_OPT3;
if (jit_inline_above_cost >= 0 &&
- top_plan->total_cost > jit_inline_above_cost)
+ plan->total_cost > jit_inline_above_cost)
result->jitFlags |= PGJIT_INLINE;
/*
@@ -591,6 +462,225 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
return result;
}
+static PlannerGlobal *
+setup_planner_global(Query *parse, int cursorOptions, ParamListInfo boundParams, bool keepAllCandidates)
+{
+ PlannerGlobal *glob;
+
+ /*
+ * Set up global state for this planner invocation. This data is needed
+ * across all levels of sub-Query that might exist in the given command,
+ * so we keep it in a separate struct that's linked to by each per-Query
+ * PlannerInfo.
+ */
+ glob = makeNode(PlannerGlobal);
+
+ glob->boundParams = boundParams;
+ glob->subplans = NIL;
+ glob->subpaths = NIL;
+ glob->subroots = NIL;
+ glob->rewindPlanIDs = NULL;
+ glob->finalrtable = NIL;
+ glob->finalrteperminfos = NIL;
+ glob->finalrowmarks = NIL;
+ glob->resultRelations = NIL;
+ glob->appendRelations = NIL;
+ glob->relationOids = NIL;
+ glob->invalItems = NIL;
+ glob->paramExecTypes = NIL;
+ glob->lastPHId = 0;
+ glob->lastRowMarkId = 0;
+ glob->lastPlanNodeId = 0;
+ glob->transientPlan = false;
+ glob->dependsOnRole = false;
+ glob->keepAllCandidates = keepAllCandidates;
+
+ /*
+ * Assess whether it's feasible to use parallel mode for this query. We
+ * can't do this in a standalone backend, or if the command will try to
+ * modify any data, or if this is a cursor operation, or if GUCs are set
+ * to values that don't permit parallelism, or if parallel-unsafe
+ * functions are present in the query tree.
+ *
+ * (Note that we do allow CREATE TABLE AS, SELECT INTO, and CREATE
+ * MATERIALIZED VIEW to use parallel plans, but this is safe only because
+ * the command is writing into a completely new table which workers won't
+ * be able to see. If the workers could see the table, the fact that
+ * group locking would cause them to ignore the leader's heavyweight GIN
+ * page locks would make this unsafe. We'll have to fix that somehow if
+ * we want to allow parallel inserts in general; updates and deletes have
+ * additional problems especially around combo CIDs.)
+ *
+ * For now, we don't try to use parallel mode if we're running inside a
+ * parallel worker. We might eventually be able to relax this
+ * restriction, but for now it seems best not to have parallel workers
+ * trying to create their own parallel workers.
+ */
+ if ((cursorOptions & CURSOR_OPT_PARALLEL_OK) != 0 &&
+ IsUnderPostmaster &&
+ parse->commandType == CMD_SELECT &&
+ !parse->hasModifyingCTE &&
+ max_parallel_workers_per_gather > 0 &&
+ !IsParallelWorker())
+ {
+ /* all the cheap tests pass, so scan the query tree */
+ glob->maxParallelHazard = max_parallel_hazard(parse);
+ glob->parallelModeOK = (glob->maxParallelHazard != PROPARALLEL_UNSAFE);
+ }
+ else
+ {
+ /* skip the query tree scan, just assume it's unsafe */
+ glob->maxParallelHazard = PROPARALLEL_UNSAFE;
+ glob->parallelModeOK = false;
+ }
+
+ /*
+ * glob->parallelModeNeeded is normally set to false here and changed to
+ * true during plan creation if a Gather or Gather Merge plan is actually
+ * created (cf. create_gather_plan, create_gather_merge_plan).
+ *
+ * However, if debug_parallel_query = on or debug_parallel_query =
+ * regress, then we impose parallel mode whenever it's safe to do so, even
+ * if the final plan doesn't use parallelism. It's not safe to do so if
+ * the query contains anything parallel-unsafe; parallelModeOK will be
+ * false in that case. Note that parallelModeOK can't change after this
+ * point. Otherwise, everything in the query is either parallel-safe or
+ * parallel-restricted, and in either case it should be OK to impose
+ * parallel-mode restrictions. If that ends up breaking something, then
+ * either some function the user included in the query is incorrectly
+ * labeled as parallel-safe or parallel-restricted when in reality it's
+ * parallel-unsafe, or else the query planner itself has a bug.
+ */
+ glob->parallelModeNeeded = glob->parallelModeOK &&
+ (debug_parallel_query != DEBUG_PARALLEL_OFF);
+ return glob;
+}
+
+static double
+compute_tuple_fraction(int cursorOptions)
+{
+ double tuple_fraction;
+
+ /* Determine what fraction of the plan is likely to be scanned */
+ if (cursorOptions & CURSOR_OPT_FAST_PLAN)
+ {
+ /*
+ * We have no real idea how many tuples the user will ultimately FETCH
+ * from a cursor, but it is often the case that he doesn't want 'em
+ * all, or would prefer a fast-start plan anyway so that he can
+ * process some of the tuples sooner. Use a GUC parameter to decide
+ * what fraction to optimize for.
+ */
+ tuple_fraction = cursor_tuple_fraction;
+
+ /*
+ * We document cursor_tuple_fraction as simply being a fraction, which
+ * means the edge cases 0 and 1 have to be treated specially here. We
+ * convert 1 to 0 ("all the tuples") and 0 to a very small fraction.
+ */
+ if (tuple_fraction >= 1.0)
+ tuple_fraction = 0.0;
+ else if (tuple_fraction <= 0.0)
+ tuple_fraction = 1e-10;
+ }
+ else
+ {
+ /* Default assumption is we need all the tuples */
+ tuple_fraction = 0.0;
+ }
+ return tuple_fraction;
+}
+
+PlannedStmt *
+standard_planner(Query *parse, const char *query_string, int cursorOptions,
+ ParamListInfo boundParams)
+{
+ PlannedStmt *result;
+ PlannerGlobal *glob;
+ double tuple_fraction;
+ PlannerInfo *root;
+ RelOptInfo *final_rel;
+ Path *best_path;
+ Plan *top_plan;
+
+ glob = setup_planner_global(parse, cursorOptions, boundParams, false);
+ tuple_fraction = compute_tuple_fraction(cursorOptions);
+
+ /* primary planning entry point (may recurse for subqueries) */
+ root = subquery_planner(glob, parse, NULL, false, tuple_fraction, NULL);
+
+ /* Select best Path and turn it into a Plan */
+ final_rel = fetch_upper_rel(root, UPPERREL_FINAL, NULL);
+
+
+ best_path = get_cheapest_fractional_path(final_rel, tuple_fraction);
+
+ top_plan = create_plan(root, best_path);
+ result = create_planned_stmt(root, parse, top_plan, cursorOptions);
+
+ return result;
+}
+
+/*
+ * planner_all_candidates
+ * Build the list of all candidate PlannedStmt
+ *
+ * With ALL_CANDIDATES explain option, a list of PlannedStmt is built for
+ * all possible path from the upper_rel.
+ */
+List *
+planner_all_candidates(Query *parse, int cursorOptions, ParamListInfo boundParams)
+{
+ List *result = NULL;
+ double tuple_fraction;
+ Query *base_parse;
+ RelOptInfo *upper_rel;
+ int current_path_index = 0;
+
+ /*
+ * Parse may be modified by planner, save a copy of the initial parse
+ * state
+ */
+ base_parse = copyObject(parse);
+ tuple_fraction = compute_tuple_fraction(cursorOptions);
+
+ do
+ {
+ Plan *plan;
+ PlannedStmt *planned_stmt;
+ Path *path;
+ PlannerGlobal *glob;
+ PlannerInfo *root;
+
+ /*
+ * During planning, parse, glob and path may be modified. We need to
+ * create on a copy of those for all planned_stmt we create. However,
+ * we can't use copyObject on PlannerInfo. The current solution is to
+ * redo the whole planning for every plan we create.
+ */
+ parse = copyObject(base_parse);
+ glob = setup_planner_global(parse, cursorOptions, boundParams, true);
+ /* Run the planner */
+ root = subquery_planner(glob, parse, NULL, false, tuple_fraction, NULL);
+
+ /* Get the path */
+ upper_rel = fetch_upper_rel(root, UPPERREL_FINAL, NULL);
+ if (current_path_index >= upper_rel->pathlist->length)
+ {
+ /* Planner generated a different number of path, bail out */
+ break;
+ }
+ path = (Path *) list_nth(upper_rel->pathlist, current_path_index++);
+
+ /* Create the plan from the path */
+ plan = create_plan(root, path);
+ planned_stmt = create_planned_stmt(root, parse, plan, cursorOptions);
+ /* Add it to the list */
+ result = lcons(planned_stmt, result);
+ } while (current_path_index < upper_rel->pathlist->length);
+
+ return result;
+}
/*--------------------
* subquery_planner
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 91d394ce62e..263a9eca0fb 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -430,6 +430,24 @@ add_path(PlannerInfo *root, RelOptInfo *parent_rel, Path *new_path)
*/
CHECK_FOR_INTERRUPTS();
+ /*
+ * When keepAllCandidates is enabled, we always add the path in the
+ * pathlist
+ */
+ if (root->glob->keepAllCandidates)
+ {
+ foreach(p1, parent_rel->pathlist)
+ {
+ Path *old_path = (Path *) lfirst(p1);
+
+ if (new_path->total_cost >= old_path->total_cost)
+ insert_at = foreach_current_index(p1) + 1;
+ }
+ parent_rel->pathlist =
+ list_insert_nth(parent_rel->pathlist, insert_at, new_path);
+ return;
+ }
+
/* Pretend parameterized paths have no pathkeys, per comment above */
new_path_pathkeys = new_path->param_info ? NIL : new_path->pathkeys;
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index ea517f4b9bb..cbff66f7796 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -877,6 +877,50 @@ pg_rewrite_query(Query *query)
return querytree_list;
}
+/*
+ * Generate a list of all plan candidates for a single already-rewritten query.
+ * This is a thin wrapper around planner() and takes the same parameters.
+ */
+List *
+pg_plan_query_all_candidates(Query *querytree, const char *query_string, int cursorOptions,
+ ParamListInfo boundParams)
+{
+ List *plans;
+
+ /* Utility commands have no plans. */
+ if (querytree->commandType == CMD_UTILITY)
+ return NULL;
+
+ /* Planner must have a snapshot in case it calls user-defined functions. */
+ Assert(ActiveSnapshotSet());
+
+ TRACE_POSTGRESQL_QUERY_PLAN_START();
+
+ if (log_planner_stats)
+ ResetUsage();
+
+ /* call the optimizer */
+ plans = planner_all_candidates(querytree, cursorOptions, boundParams);
+
+ /*
+ * Print plan list if debugging.
+ */
+ if (Debug_print_plan)
+ {
+ ListCell *lc;
+
+ foreach(lc, plans)
+ {
+ Plan *plan = lfirst(lc);
+
+ elog_node_display(LOG, "plan", plan, Debug_pretty_print);
+ }
+ }
+
+ TRACE_POSTGRESQL_QUERY_PLAN_DONE();
+
+ return plans;
+}
/*
* Generate a plan for a single already-rewritten query.
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 891face1b65..c172ce7eb94 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -3871,8 +3871,8 @@ psql_completion(const char *text, int start, int end)
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
COMPLETE_WITH("ANALYZE", "VERBOSE", "COSTS", "SETTINGS", "GENERIC_PLAN",
"BUFFERS", "SERIALIZE", "WAL", "TIMING", "SUMMARY",
- "MEMORY", "FORMAT");
- else if (TailMatches("ANALYZE|VERBOSE|COSTS|SETTINGS|GENERIC_PLAN|BUFFERS|WAL|TIMING|SUMMARY|MEMORY"))
+ "MEMORY", "ALL_CANDIDATES", "FORMAT");
+ else if (TailMatches("ANALYZE|VERBOSE|COSTS|SETTINGS|GENERIC_PLAN|BUFFERS|WAL|TIMING|SUMMARY|MEMORY|ALL_CANDIDATES"))
COMPLETE_WITH("ON", "OFF");
else if (TailMatches("SERIALIZE"))
COMPLETE_WITH("TEXT", "NONE", "BINARY");
diff --git a/src/include/commands/explain.h b/src/include/commands/explain.h
index 9b8b351d9a2..5303f76239c 100644
--- a/src/include/commands/explain.h
+++ b/src/include/commands/explain.h
@@ -55,6 +55,7 @@ typedef struct ExplainState
bool memory; /* print planner's memory usage information */
bool settings; /* print modified settings */
bool generic; /* generate a generic plan */
+ bool all_candidates; /* print all plan candidates */
ExplainSerializeOption serialize; /* serialize the query's output? */
ExplainFormat format; /* output format */
/* state for output formatting --- not reset for each new plan tree */
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 14ccfc1ac1c..721dc2fe993 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -158,6 +158,9 @@ typedef struct PlannerGlobal
/* parallel mode actually required? */
bool parallelModeNeeded;
+ /* do no discard more expensive plans from pathlist */
+ bool keepAllCandidates;
+
/* worst PROPARALLEL hazard level */
char maxParallelHazard;
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index 7b63c5cf718..5d928267dac 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -115,6 +115,8 @@ extern PGDLLIMPORT bool parallel_leader_participation;
extern struct PlannedStmt *planner(Query *parse, const char *query_string,
int cursorOptions,
struct ParamListInfoData *boundParams);
+extern List *planner_all_candidates(Query *parse, int cursorOptions,
+ struct ParamListInfoData *boundParams);
extern Expr *expression_planner(Expr *expr);
extern Expr *expression_planner_with_deps(Expr *expr,
diff --git a/src/include/tcop/tcopprot.h b/src/include/tcop/tcopprot.h
index 147a294950e..54e33cf0e1d 100644
--- a/src/include/tcop/tcopprot.h
+++ b/src/include/tcop/tcopprot.h
@@ -62,6 +62,9 @@ extern List *pg_analyze_and_rewrite_withcb(RawStmt *parsetree,
extern PlannedStmt *pg_plan_query(Query *querytree, const char *query_string,
int cursorOptions,
ParamListInfo boundParams);
+extern List *pg_plan_query_all_candidates(Query *querytree, const char *query_string,
+ int cursorOptions,
+ ParamListInfo boundParams);
extern List *pg_plan_queries(List *querytrees, const char *query_string,
int cursorOptions,
ParamListInfo boundParams);
diff --git a/src/test/regress/expected/explain.out b/src/test/regress/expected/explain.out
index 6585c6a69ef..418a7b12621 100644
--- a/src/test/regress/expected/explain.out
+++ b/src/test/regress/expected/explain.out
@@ -416,6 +416,121 @@ select explain_filter('explain (memory) execute int8_query');
Memory: used=NkB allocated=NkB
(2 rows)
+-- ALL_CANDIDATES option
+begin;
+-- encourage planner to compute parallel plan
+set local min_parallel_table_scan_size=0;
+select explain_filter('explain (all_candidates) select * from int8_tbl i8');
+ explain_filter
+------------------------------------------------------------------------------
+ Plan N
+ -> Gather (cost=N.N..N.N rows=N width=N)
+ Workers Planned: N
+ -> Parallel Seq Scan on int8_tbl i8 (cost=N.N..N.N rows=N width=N)
+ Plan N
+ -> Seq Scan on int8_tbl i8 (cost=N.N..N.N rows=N width=N)
+(6 rows)
+
+select explain_filter('explain (all_candidates, summary, format yaml) select * from int8_tbl i8');
+ explain_filter
+----------------------------------------
+ - - Plan: +
+ Node Type: "Gather" +
+ Parallel Aware: false +
+ Async Capable: false +
+ Startup Cost: N.N +
+ Total Cost: N.N +
+ Plan Rows: N +
+ Plan Width: N +
+ Workers Planned: N +
+ Single Copy: false +
+ Plans: +
+ - Node Type: "Seq Scan" +
+ Parent Relationship: "Outer"+
+ Parallel Aware: true +
+ Async Capable: false +
+ Relation Name: "int8_tbl" +
+ Alias: "i8" +
+ Startup Cost: N.N +
+ Total Cost: N.N +
+ Plan Rows: N +
+ Plan Width: N +
+ Planning Time: N.N +
+ - - Plan: +
+ Node Type: "Seq Scan" +
+ Parallel Aware: false +
+ Async Capable: false +
+ Relation Name: "int8_tbl" +
+ Alias: "i8" +
+ Startup Cost: N.N +
+ Total Cost: N.N +
+ Plan Rows: N +
+ Plan Width: N +
+ Planning Time: N.N
+(1 row)
+
+select explain_filter('explain (all_candidates, format json) select * from int8_tbl i8');
+ explain_filter
+---------------------------------------------
+ [ +
+ { +
+ { +
+ "Plan": { +
+ "Node Type": "Gather", +
+ "Parallel Aware": false, +
+ "Async Capable": false, +
+ "Startup Cost": N.N, +
+ "Total Cost": N.N, +
+ "Plan Rows": N, +
+ "Plan Width": N, +
+ "Workers Planned": N, +
+ "Single Copy": false, +
+ "Plans": [ +
+ { +
+ "Node Type": "Seq Scan", +
+ "Parent Relationship": "Outer",+
+ "Parallel Aware": true, +
+ "Async Capable": false, +
+ "Relation Name": "int8_tbl", +
+ "Alias": "i8", +
+ "Startup Cost": N.N, +
+ "Total Cost": N.N, +
+ "Plan Rows": N, +
+ "Plan Width": N +
+ } +
+ ] +
+ } +
+ } +
+ }, +
+ { +
+ { +
+ "Plan": { +
+ "Node Type": "Seq Scan", +
+ "Parallel Aware": false, +
+ "Async Capable": false, +
+ "Relation Name": "int8_tbl", +
+ "Alias": "i8", +
+ "Startup Cost": N.N, +
+ "Total Cost": N.N, +
+ "Plan Rows": N, +
+ "Plan Width": N +
+ } +
+ } +
+ } +
+ ]
+(1 row)
+
+select explain_filter('explain (all_candidates) execute int8_query');
+ explain_filter
+---------------------------------------------------------
+ Seq Scan on int8_tbl i8 (cost=N.N..N.N rows=N width=N)
+(1 row)
+
+rollback;
+-- should fail
+select explain_filter('explain (all_candidates, analyze) select * from int8_tbl i8');
+ERROR: EXPLAIN options ANALYZE and ALL_CANDIDATES cannot be used together
+CONTEXT: PL/pgSQL function explain_filter(text) line 5 at FOR over EXECUTE statement
-- Test EXPLAIN (GENERIC_PLAN) with partition pruning
-- partitions should be pruned at plan time, based on constants,
-- but there should be no pruning based on parameter placeholders
diff --git a/src/test/regress/sql/explain.sql b/src/test/regress/sql/explain.sql
index c7055f850c5..d6b1439824b 100644
--- a/src/test/regress/sql/explain.sql
+++ b/src/test/regress/sql/explain.sql
@@ -102,6 +102,18 @@ select explain_filter('explain (memory, analyze, format json) select * from int8
prepare int8_query as select * from int8_tbl i8;
select explain_filter('explain (memory) execute int8_query');
+-- ALL_CANDIDATES option
+begin;
+-- encourage planner to compute parallel plan
+set local min_parallel_table_scan_size=0;
+select explain_filter('explain (all_candidates) select * from int8_tbl i8');
+select explain_filter('explain (all_candidates, summary, format yaml) select * from int8_tbl i8');
+select explain_filter('explain (all_candidates, format json) select * from int8_tbl i8');
+select explain_filter('explain (all_candidates) execute int8_query');
+rollback;
+-- should fail
+select explain_filter('explain (all_candidates, analyze) select * from int8_tbl i8');
+
-- Test EXPLAIN (GENERIC_PLAN) with partition pruning
-- partitions should be pruned at plan time, based on constants,
-- but there should be no pruning based on parameter placeholders
--
2.39.3 (Apple Git-146)
v1-0001-Propagate-root-PlannerInfo-to-add_path.patchapplication/octet-stream; name=v1-0001-Propagate-root-PlannerInfo-to-add_path.patchDownload
From 0d42c1c80282d9f92f03e6684cf8e6a060e02fed Mon Sep 17 00:00:00 2001
From: Anthonin Bonnefoy <anthonin.bonnefoy@gmail.com>
Date: Mon, 15 Jul 2024 06:43:36 +0200
Subject: Propagate root PlannerInfo to add_path
---
contrib/file_fdw/file_fdw.c | 2 +-
contrib/postgres_fdw/postgres_fdw.c | 18 ++--
src/backend/optimizer/path/allpaths.c | 144 ++++++++++++-------------
src/backend/optimizer/path/indxpath.c | 6 +-
src/backend/optimizer/path/joinpath.c | 6 +-
src/backend/optimizer/path/joinrels.c | 24 ++---
src/backend/optimizer/path/tidpath.c | 12 +--
src/backend/optimizer/plan/planagg.c | 2 +-
src/backend/optimizer/plan/planmain.c | 2 +-
src/backend/optimizer/plan/planner.c | 42 ++++----
src/backend/optimizer/prep/prepunion.c | 42 ++++----
src/backend/optimizer/util/pathnode.c | 2 +-
src/backend/optimizer/util/relnode.c | 2 +-
src/include/optimizer/pathnode.h | 2 +-
src/include/optimizer/paths.h | 2 +-
15 files changed, 154 insertions(+), 154 deletions(-)
diff --git a/contrib/file_fdw/file_fdw.c b/contrib/file_fdw/file_fdw.c
index 249d82d3a05..5d1d8faf31b 100644
--- a/contrib/file_fdw/file_fdw.c
+++ b/contrib/file_fdw/file_fdw.c
@@ -572,7 +572,7 @@ fileGetForeignPaths(PlannerInfo *root,
* it could still have required parameterization due to LATERAL refs in
* its tlist.
*/
- add_path(baserel, (Path *)
+ add_path(root, baserel, (Path *)
create_foreignscan_path(root, baserel,
NULL, /* default pathtarget */
baserel->rows,
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index fc65d81e217..47a3ebc4e0b 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -1037,7 +1037,7 @@ postgresGetForeignPaths(PlannerInfo *root,
NULL, /* no extra plan */
NIL, /* no fdw_restrictinfo list */
NIL); /* no fdw_private list */
- add_path(baserel, (Path *) path);
+ add_path(root, baserel, (Path *) path);
/* Add paths with pathkeys */
add_paths_with_pathkeys_for_rel(root, baserel, NULL, NIL);
@@ -1210,7 +1210,7 @@ postgresGetForeignPaths(PlannerInfo *root,
NULL,
NIL, /* no fdw_restrictinfo list */
NIL); /* no fdw_private list */
- add_path(baserel, (Path *) path);
+ add_path(root, baserel, (Path *) path);
}
}
@@ -6171,7 +6171,7 @@ add_paths_with_pathkeys_for_rel(PlannerInfo *root, RelOptInfo *rel,
-1.0);
if (IS_SIMPLE_REL(rel))
- add_path(rel, (Path *)
+ add_path(root, rel, (Path *)
create_foreignscan_path(root, rel,
NULL,
rows,
@@ -6184,7 +6184,7 @@ add_paths_with_pathkeys_for_rel(PlannerInfo *root, RelOptInfo *rel,
* list */
NIL));
else
- add_path(rel, (Path *)
+ add_path(root, rel, (Path *)
create_foreign_join_path(root, rel,
NULL,
rows,
@@ -6450,7 +6450,7 @@ postgresGetForeignJoinPaths(PlannerInfo *root,
NIL); /* no fdw_private */
/* Add generated path into joinrel by add_path(). */
- add_path(joinrel, (Path *) joinpath);
+ add_path(root, joinrel, (Path *) joinpath);
/* Consider pathkeys for the join relation */
add_paths_with_pathkeys_for_rel(root, joinrel, epq_path,
@@ -6839,7 +6839,7 @@ add_foreign_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel,
NIL); /* no fdw_private */
/* Add generated path into grouped_rel by add_path(). */
- add_path(grouped_rel, (Path *) grouppath);
+ add_path(root, grouped_rel, (Path *) grouppath);
}
/*
@@ -6974,7 +6974,7 @@ add_foreign_ordered_paths(PlannerInfo *root, RelOptInfo *input_rel,
fdw_private);
/* and add it to the ordered_rel */
- add_path(ordered_rel, (Path *) ordered_path);
+ add_path(root, ordered_rel, (Path *) ordered_path);
}
/*
@@ -7091,7 +7091,7 @@ add_foreign_final_paths(PlannerInfo *root, RelOptInfo *input_rel,
NIL); /* no fdw_private */
/* and add it to the final_rel */
- add_path(final_rel, (Path *) final_path);
+ add_path(root, final_rel, (Path *) final_path);
/* Safe to push down */
fpinfo->pushdown_safe = true;
@@ -7226,7 +7226,7 @@ add_foreign_final_paths(PlannerInfo *root, RelOptInfo *input_rel,
fdw_private);
/* and add it to the final_rel */
- add_path(final_rel, (Path *) final_path);
+ add_path(root, final_rel, (Path *) final_path);
}
/*
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 057b4b79ebb..dff4eb6491b 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -124,7 +124,7 @@ static void accumulate_append_subpath(Path *path,
List **subpaths,
List **special_subpaths);
static Path *get_singleton_append_subpath(Path *path);
-static void set_dummy_rel_pathlist(RelOptInfo *rel);
+static void set_dummy_rel_pathlist(PlannerInfo *root, RelOptInfo *rel);
static void set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
Index rti, RangeTblEntry *rte);
static void set_function_pathlist(PlannerInfo *root, RelOptInfo *rel,
@@ -374,7 +374,7 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel,
* we don't have a convention for marking a rel as dummy except by
* assigning a dummy path to it.
*/
- set_dummy_rel_pathlist(rel);
+ set_dummy_rel_pathlist(root, rel);
}
else if (rte->inh)
{
@@ -398,7 +398,7 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel,
* with ONLY. In that case we shouldn't scan any of the
* partitions, so mark it as a dummy rel.
*/
- set_dummy_rel_pathlist(rel);
+ set_dummy_rel_pathlist(root, rel);
}
else if (rte->tablesample != NULL)
{
@@ -784,7 +784,7 @@ set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
return;
/* Consider sequential scan */
- add_path(rel, create_seqscan_path(root, rel, required_outer, 0));
+ add_path(root, rel, create_seqscan_path(root, rel, required_outer, 0));
/* If appropriate, consider parallel sequential scan */
if (rel->consider_parallel && required_outer == NULL)
@@ -897,7 +897,7 @@ set_tablesample_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *
path = (Path *) create_material_path(rel, path);
}
- add_path(rel, path);
+ add_path(root, rel, path);
/* For the moment, at least, there are no other paths to consider */
}
@@ -1038,7 +1038,7 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel,
* This child need not be scanned, so we can omit it from the
* appendrel.
*/
- set_dummy_rel_pathlist(childrel);
+ set_dummy_rel_pathlist(root, childrel);
continue;
}
@@ -1226,7 +1226,7 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel,
* appendrel dummy. We must do this in this phase so that the rel's
* dummy-ness is visible when we generate paths for other rels.
*/
- set_dummy_rel_pathlist(rel);
+ set_dummy_rel_pathlist(root, rel);
}
pfree(parent_attrsizes);
@@ -1498,14 +1498,14 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
* if we have zero or one live subpath due to constraint exclusion.)
*/
if (subpaths_valid)
- add_path(rel, (Path *) create_append_path(root, rel, subpaths, NIL,
- NIL, NULL, 0, false,
- -1));
+ add_path(root, rel, (Path *) create_append_path(root, rel, subpaths, NIL,
+ NIL, NULL, 0, false,
+ -1));
/* build an AppendPath for the cheap startup paths, if valid */
if (startup_subpaths_valid)
- add_path(rel, (Path *) create_append_path(root, rel, startup_subpaths,
- NIL, NIL, NULL, 0, false, -1));
+ add_path(root, rel, (Path *) create_append_path(root, rel, startup_subpaths,
+ NIL, NIL, NULL, 0, false, -1));
/*
* Consider an append of unordered, unparameterized partial paths. Make
@@ -1655,7 +1655,7 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
}
if (subpaths_valid)
- add_path(rel, (Path *)
+ add_path(root, rel, (Path *)
create_append_path(root, rel, subpaths, NIL,
NIL, required_outer, 0, false,
-1));
@@ -1940,58 +1940,58 @@ generate_orderedappend_paths(PlannerInfo *root, RelOptInfo *rel,
if (match_partition_order)
{
/* We only need Append */
- add_path(rel, (Path *) create_append_path(root,
- rel,
- startup_subpaths,
- NIL,
- pathkeys,
- NULL,
- 0,
- false,
- -1));
- if (startup_neq_total)
- add_path(rel, (Path *) create_append_path(root,
- rel,
- total_subpaths,
- NIL,
- pathkeys,
- NULL,
- 0,
- false,
- -1));
-
- if (fractional_subpaths)
- add_path(rel, (Path *) create_append_path(root,
- rel,
- fractional_subpaths,
- NIL,
- pathkeys,
- NULL,
- 0,
- false,
- -1));
- }
- else
- {
- /* We need MergeAppend */
- add_path(rel, (Path *) create_merge_append_path(root,
+ add_path(root, rel, (Path *) create_append_path(root,
rel,
startup_subpaths,
+ NIL,
pathkeys,
- NULL));
+ NULL,
+ 0,
+ false,
+ -1));
if (startup_neq_total)
- add_path(rel, (Path *) create_merge_append_path(root,
+ add_path(root, rel, (Path *) create_append_path(root,
rel,
total_subpaths,
+ NIL,
pathkeys,
- NULL));
+ NULL,
+ 0,
+ false,
+ -1));
if (fractional_subpaths)
- add_path(rel, (Path *) create_merge_append_path(root,
+ add_path(root, rel, (Path *) create_append_path(root,
rel,
fractional_subpaths,
+ NIL,
pathkeys,
- NULL));
+ NULL,
+ 0,
+ false,
+ -1));
+ }
+ else
+ {
+ /* We need MergeAppend */
+ add_path(root, rel, (Path *) create_merge_append_path(root,
+ rel,
+ startup_subpaths,
+ pathkeys,
+ NULL));
+ if (startup_neq_total)
+ add_path(root, rel, (Path *) create_merge_append_path(root,
+ rel,
+ total_subpaths,
+ pathkeys,
+ NULL));
+
+ if (fractional_subpaths)
+ add_path(root, rel, (Path *) create_merge_append_path(root,
+ rel,
+ fractional_subpaths,
+ pathkeys,
+ NULL));
}
}
}
@@ -2171,7 +2171,7 @@ get_singleton_append_subpath(Path *path)
* paths for it.)
*/
static void
-set_dummy_rel_pathlist(RelOptInfo *rel)
+set_dummy_rel_pathlist(PlannerInfo *root, RelOptInfo *rel)
{
/* Set dummy size estimates --- we leave attr_widths[] as zeroes */
rel->rows = 0;
@@ -2182,9 +2182,9 @@ set_dummy_rel_pathlist(RelOptInfo *rel)
rel->partial_pathlist = NIL;
/* Set up the dummy path */
- add_path(rel, (Path *) create_append_path(NULL, rel, NIL, NIL,
- NIL, rel->lateral_relids,
- 0, false, -1));
+ add_path(root, rel, (Path *) create_append_path(NULL, rel, NIL, NIL,
+ NIL, rel->lateral_relids,
+ 0, false, -1));
/*
* We set the cheapest-path fields immediately, just in case they were
@@ -2657,7 +2657,7 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
if (IS_DUMMY_REL(sub_final_rel))
{
- set_dummy_rel_pathlist(rel);
+ set_dummy_rel_pathlist(root, rel);
return;
}
@@ -2714,7 +2714,7 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
make_tlist_from_pathtarget(subpath->pathtarget));
/* Generate outer path using this subpath */
- add_path(rel, (Path *)
+ add_path(root, rel, (Path *)
create_subqueryscan_path(root, rel, subpath,
trivial_pathtarget,
pathkeys, required_outer));
@@ -2812,8 +2812,8 @@ set_function_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
}
/* Generate appropriate path */
- add_path(rel, create_functionscan_path(root, rel,
- pathkeys, required_outer));
+ add_path(root, rel, create_functionscan_path(root, rel,
+ pathkeys, required_outer));
}
/*
@@ -2833,7 +2833,7 @@ set_values_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
required_outer = rel->lateral_relids;
/* Generate appropriate path */
- add_path(rel, create_valuesscan_path(root, rel, required_outer));
+ add_path(root, rel, create_valuesscan_path(root, rel, required_outer));
}
/*
@@ -2853,8 +2853,8 @@ set_tablefunc_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
required_outer = rel->lateral_relids;
/* Generate appropriate path */
- add_path(rel, create_tablefuncscan_path(root, rel,
- required_outer));
+ add_path(root, rel, create_tablefuncscan_path(root, rel,
+ required_outer));
}
/*
@@ -2933,7 +2933,7 @@ set_cte_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
required_outer = rel->lateral_relids;
/* Generate appropriate path */
- add_path(rel, create_ctescan_path(root, rel, pathkeys, required_outer));
+ add_path(root, rel, create_ctescan_path(root, rel, pathkeys, required_outer));
}
/*
@@ -2960,7 +2960,7 @@ set_namedtuplestore_pathlist(PlannerInfo *root, RelOptInfo *rel,
required_outer = rel->lateral_relids;
/* Generate appropriate path */
- add_path(rel, create_namedtuplestorescan_path(root, rel, required_outer));
+ add_path(root, rel, create_namedtuplestorescan_path(root, rel, required_outer));
}
/*
@@ -2987,7 +2987,7 @@ set_result_pathlist(PlannerInfo *root, RelOptInfo *rel,
required_outer = rel->lateral_relids;
/* Generate appropriate path */
- add_path(rel, create_resultscan_path(root, rel, required_outer));
+ add_path(root, rel, create_resultscan_path(root, rel, required_outer));
}
/*
@@ -3037,7 +3037,7 @@ set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
required_outer = rel->lateral_relids;
/* Generate appropriate path */
- add_path(rel, create_worktablescan_path(root, rel, required_outer));
+ add_path(root, rel, create_worktablescan_path(root, rel, required_outer));
}
/*
@@ -3083,7 +3083,7 @@ generate_gather_paths(PlannerInfo *root, RelOptInfo *rel, bool override_rows)
simple_gather_path = (Path *)
create_gather_path(root, rel, cheapest_partial_path, rel->reltarget,
NULL, rowsp);
- add_path(rel, simple_gather_path);
+ add_path(root, rel, simple_gather_path);
/*
* For each useful ordering, we can consider an order-preserving Gather
@@ -3100,7 +3100,7 @@ generate_gather_paths(PlannerInfo *root, RelOptInfo *rel, bool override_rows)
rows = compute_gather_rows(subpath);
path = create_gather_merge_path(root, rel, subpath, rel->reltarget,
subpath->pathkeys, NULL, rowsp);
- add_path(rel, &path->path);
+ add_path(root, rel, &path->path);
}
}
@@ -3297,7 +3297,7 @@ generate_useful_gather_paths(PlannerInfo *root, RelOptInfo *rel, bool override_r
NULL,
rowsp);
- add_path(rel, &path->path);
+ add_path(root, rel, &path->path);
}
}
}
@@ -4359,7 +4359,7 @@ generate_partitionwise_join_paths(PlannerInfo *root, RelOptInfo *rel)
/* If all child-joins are dummy, parent join is also dummy. */
if (!live_children)
{
- mark_dummy_rel(rel);
+ mark_dummy_rel(root, rel);
return;
}
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index c0fcc7d78df..ac74217636a 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -340,7 +340,7 @@ create_index_paths(PlannerInfo *root, RelOptInfo *rel)
bitmapqual = choose_bitmap_and(root, rel, bitindexpaths);
bpath = create_bitmap_heap_path(root, rel, bitmapqual,
rel->lateral_relids, 1.0, 0);
- add_path(rel, (Path *) bpath);
+ add_path(root, rel, (Path *) bpath);
/* create a partial bitmap heap path */
if (rel->consider_parallel && rel->lateral_relids == NULL)
@@ -407,7 +407,7 @@ create_index_paths(PlannerInfo *root, RelOptInfo *rel)
loop_count = get_loop_count(root, rel->relid, required_outer);
bpath = create_bitmap_heap_path(root, rel, bitmapqual,
required_outer, loop_count, 0);
- add_path(rel, (Path *) bpath);
+ add_path(root, rel, (Path *) bpath);
}
}
}
@@ -742,7 +742,7 @@ get_index_paths(PlannerInfo *root, RelOptInfo *rel,
IndexPath *ipath = (IndexPath *) lfirst(lc);
if (index->amhasgettuple)
- add_path(rel, (Path *) ipath);
+ add_path(root, rel, (Path *) ipath);
if (index->amhasgetbitmap &&
(ipath->path.pathkeys == NIL ||
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index 7a2c20b1450..e92b8359470 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -919,7 +919,7 @@ try_nestloop_path(PlannerInfo *root,
workspace.startup_cost, workspace.total_cost,
pathkeys, required_outer))
{
- add_path(joinrel, (Path *)
+ add_path(root, joinrel, (Path *)
create_nestloop_path(root,
joinrel,
jointype,
@@ -1099,7 +1099,7 @@ try_mergejoin_path(PlannerInfo *root,
workspace.startup_cost, workspace.total_cost,
pathkeys, required_outer))
{
- add_path(joinrel, (Path *)
+ add_path(root, joinrel, (Path *)
create_mergejoin_path(root,
joinrel,
jointype,
@@ -1244,7 +1244,7 @@ try_hashjoin_path(PlannerInfo *root,
workspace.startup_cost, workspace.total_cost,
NIL, required_outer))
{
- add_path(joinrel, (Path *)
+ add_path(root, joinrel, (Path *)
create_hashjoin_path(root,
joinrel,
jointype,
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index a3677f824fe..a5f3b7996ae 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -918,7 +918,7 @@ populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1,
if (is_dummy_rel(rel1) || is_dummy_rel(rel2) ||
restriction_is_constant_false(restrictlist, joinrel, false))
{
- mark_dummy_rel(joinrel);
+ mark_dummy_rel(root, joinrel);
break;
}
add_paths_to_joinrel(root, joinrel, rel1, rel2,
@@ -932,12 +932,12 @@ populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1,
if (is_dummy_rel(rel1) ||
restriction_is_constant_false(restrictlist, joinrel, true))
{
- mark_dummy_rel(joinrel);
+ mark_dummy_rel(root, joinrel);
break;
}
if (restriction_is_constant_false(restrictlist, joinrel, false) &&
bms_is_subset(rel2->relids, sjinfo->syn_righthand))
- mark_dummy_rel(rel2);
+ mark_dummy_rel(root, rel2);
add_paths_to_joinrel(root, joinrel, rel1, rel2,
JOIN_LEFT, sjinfo,
restrictlist);
@@ -949,7 +949,7 @@ populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1,
if ((is_dummy_rel(rel1) && is_dummy_rel(rel2)) ||
restriction_is_constant_false(restrictlist, joinrel, true))
{
- mark_dummy_rel(joinrel);
+ mark_dummy_rel(root, joinrel);
break;
}
add_paths_to_joinrel(root, joinrel, rel1, rel2,
@@ -985,7 +985,7 @@ populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1,
if (is_dummy_rel(rel1) || is_dummy_rel(rel2) ||
restriction_is_constant_false(restrictlist, joinrel, false))
{
- mark_dummy_rel(joinrel);
+ mark_dummy_rel(root, joinrel);
break;
}
add_paths_to_joinrel(root, joinrel, rel1, rel2,
@@ -1011,7 +1011,7 @@ populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1,
if (is_dummy_rel(rel1) || is_dummy_rel(rel2) ||
restriction_is_constant_false(restrictlist, joinrel, false))
{
- mark_dummy_rel(joinrel);
+ mark_dummy_rel(root, joinrel);
break;
}
add_paths_to_joinrel(root, joinrel, rel1, rel2,
@@ -1026,12 +1026,12 @@ populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1,
if (is_dummy_rel(rel1) ||
restriction_is_constant_false(restrictlist, joinrel, true))
{
- mark_dummy_rel(joinrel);
+ mark_dummy_rel(root, joinrel);
break;
}
if (restriction_is_constant_false(restrictlist, joinrel, false) &&
bms_is_subset(rel2->relids, sjinfo->syn_righthand))
- mark_dummy_rel(rel2);
+ mark_dummy_rel(root, rel2);
add_paths_to_joinrel(root, joinrel, rel1, rel2,
JOIN_ANTI, sjinfo,
restrictlist);
@@ -1381,7 +1381,7 @@ is_dummy_rel(RelOptInfo *rel)
* context the given RelOptInfo is in.
*/
void
-mark_dummy_rel(RelOptInfo *rel)
+mark_dummy_rel(PlannerInfo *root, RelOptInfo *rel)
{
MemoryContext oldcontext;
@@ -1400,9 +1400,9 @@ mark_dummy_rel(RelOptInfo *rel)
rel->partial_pathlist = NIL;
/* Set up the dummy path */
- add_path(rel, (Path *) create_append_path(NULL, rel, NIL, NIL,
- NIL, rel->lateral_relids,
- 0, false, -1));
+ add_path(root, rel, (Path *) create_append_path(NULL, rel, NIL, NIL,
+ NIL, rel->lateral_relids,
+ 0, false, -1));
/* Set or update cheapest_total_path and related fields */
set_cheapest(rel);
diff --git a/src/backend/optimizer/path/tidpath.c b/src/backend/optimizer/path/tidpath.c
index b0323b26eca..2e4094599eb 100644
--- a/src/backend/optimizer/path/tidpath.c
+++ b/src/backend/optimizer/path/tidpath.c
@@ -467,8 +467,8 @@ BuildParameterizedTidPaths(PlannerInfo *root, RelOptInfo *rel, List *clauses)
required_outer = bms_union(rinfo->required_relids, rel->lateral_relids);
required_outer = bms_del_member(required_outer, rel->relid);
- add_path(rel, (Path *) create_tidscan_path(root, rel, tidquals,
- required_outer));
+ add_path(root, rel, (Path *) create_tidscan_path(root, rel, tidquals,
+ required_outer));
}
}
@@ -519,7 +519,7 @@ create_tidscan_paths(PlannerInfo *root, RelOptInfo *rel)
*/
Relids required_outer = rel->lateral_relids;
- add_path(rel, (Path *) create_tidscan_path(root, rel, tidquals,
+ add_path(root, rel, (Path *) create_tidscan_path(root, rel, tidquals,
required_outer));
/*
@@ -551,9 +551,9 @@ create_tidscan_paths(PlannerInfo *root, RelOptInfo *rel)
*/
Relids required_outer = rel->lateral_relids;
- add_path(rel, (Path *) create_tidrangescan_path(root, rel,
- tidrangequals,
- required_outer));
+ add_path(root, rel, (Path *) create_tidrangescan_path(root, rel,
+ tidrangequals,
+ required_outer));
}
/*
diff --git a/src/backend/optimizer/plan/planagg.c b/src/backend/optimizer/plan/planagg.c
index afb5445b77b..92ab5f5ab94 100644
--- a/src/backend/optimizer/plan/planagg.c
+++ b/src/backend/optimizer/plan/planagg.c
@@ -216,7 +216,7 @@ preprocess_minmax_aggregates(PlannerInfo *root)
* doesn't need to change anymore, so making the pathtarget now is safe.
*/
grouped_rel = fetch_upper_rel(root, UPPERREL_GROUP_AGG, NULL);
- add_path(grouped_rel, (Path *)
+ add_path(root, grouped_rel, (Path *)
create_minmaxagg_path(root, grouped_rel,
create_pathtarget(root,
root->processed_tlist),
diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c
index e17d31a5c3e..50207381d1f 100644
--- a/src/backend/optimizer/plan/planmain.c
+++ b/src/backend/optimizer/plan/planmain.c
@@ -133,7 +133,7 @@ query_planner(PlannerInfo *root,
* SELECT is a kind of degenerate-grouping case, so it's not
* that much of a cheat.)
*/
- add_path(final_rel, (Path *)
+ add_path(root, final_rel, (Path *)
create_group_result_path(root, final_rel,
final_rel->reltarget,
(List *) parse->jointree->quals));
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 948afd90948..8e6dd23df94 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -1992,7 +1992,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction,
}
/* And shove it into final_rel */
- add_path(final_rel, path);
+ add_path(root, final_rel, path);
}
/*
@@ -3975,7 +3975,7 @@ create_degenerate_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel,
(List *) parse->havingQual);
}
- add_path(grouped_rel, path);
+ add_path(root, grouped_rel, path);
}
/*
@@ -4296,7 +4296,7 @@ consider_groupingsets_paths(PlannerInfo *root,
strat = AGG_MIXED;
}
- add_path(grouped_rel, (Path *)
+ add_path(root, grouped_rel, (Path *)
create_groupingsets_path(root,
grouped_rel,
path,
@@ -4454,7 +4454,7 @@ consider_groupingsets_paths(PlannerInfo *root,
if (rollups)
{
- add_path(grouped_rel, (Path *)
+ add_path(root, grouped_rel, (Path *)
create_groupingsets_path(root,
grouped_rel,
path,
@@ -4469,7 +4469,7 @@ consider_groupingsets_paths(PlannerInfo *root,
* Now try the simple sorted case.
*/
if (!gd->unsortable_sets)
- add_path(grouped_rel, (Path *)
+ add_path(root, grouped_rel, (Path *)
create_groupingsets_path(root,
grouped_rel,
path,
@@ -4735,7 +4735,7 @@ create_one_window_path(PlannerInfo *root,
topwindow ? topqual : NIL, topwindow);
}
- add_path(window_rel, path);
+ add_path(root, window_rel, path);
}
/*
@@ -5154,14 +5154,14 @@ create_final_distinct_paths(PlannerInfo *root, RelOptInfo *input_rel,
* up with a duplicate LimitPath in the final plan. That does
* not seem worth troubling over too much.
*/
- add_path(distinct_rel, (Path *)
+ add_path(root, distinct_rel, (Path *)
create_limit_path(root, distinct_rel, sorted_path,
NULL, limitCount,
LIMIT_OPTION_COUNT, 0, 1));
}
else
{
- add_path(distinct_rel, (Path *)
+ add_path(root, distinct_rel, (Path *)
create_upper_unique_path(root, distinct_rel,
sorted_path,
list_length(root->distinct_pathkeys),
@@ -5192,7 +5192,7 @@ create_final_distinct_paths(PlannerInfo *root, RelOptInfo *input_rel,
if (allow_hash && grouping_is_hashable(root->processed_distinctClause))
{
/* Generate hashed aggregate path --- no sort needed */
- add_path(distinct_rel, (Path *)
+ add_path(root, distinct_rel, (Path *)
create_agg_path(root,
distinct_rel,
cheapest_input_path,
@@ -5305,7 +5305,7 @@ create_ordered_paths(PlannerInfo *root,
sorted_path = apply_projection_to_path(root, ordered_rel,
sorted_path, target);
- add_path(ordered_rel, sorted_path);
+ add_path(root, ordered_rel, sorted_path);
}
/*
@@ -5383,7 +5383,7 @@ create_ordered_paths(PlannerInfo *root,
sorted_path = apply_projection_to_path(root, ordered_rel,
sorted_path, target);
- add_path(ordered_rel, sorted_path);
+ add_path(root, ordered_rel, sorted_path);
}
}
@@ -7024,7 +7024,7 @@ add_paths_to_grouping_rel(PlannerInfo *root, RelOptInfo *input_rel,
* We have aggregation, possibly with plain GROUP BY. Make
* an AggPath.
*/
- add_path(grouped_rel, (Path *)
+ add_path(root, grouped_rel, (Path *)
create_agg_path(root,
grouped_rel,
path,
@@ -7042,7 +7042,7 @@ add_paths_to_grouping_rel(PlannerInfo *root, RelOptInfo *input_rel,
* We have GROUP BY without aggregation or grouping sets.
* Make a GroupPath.
*/
- add_path(grouped_rel, (Path *)
+ add_path(root, grouped_rel, (Path *)
create_group_path(root,
grouped_rel,
path,
@@ -7094,7 +7094,7 @@ add_paths_to_grouping_rel(PlannerInfo *root, RelOptInfo *input_rel,
continue;
if (parse->hasAggs)
- add_path(grouped_rel, (Path *)
+ add_path(root, grouped_rel, (Path *)
create_agg_path(root,
grouped_rel,
path,
@@ -7106,7 +7106,7 @@ add_paths_to_grouping_rel(PlannerInfo *root, RelOptInfo *input_rel,
agg_final_costs,
dNumGroups));
else
- add_path(grouped_rel, (Path *)
+ add_path(root, grouped_rel, (Path *)
create_group_path(root,
grouped_rel,
path,
@@ -7136,7 +7136,7 @@ add_paths_to_grouping_rel(PlannerInfo *root, RelOptInfo *input_rel,
* Generate a HashAgg Path. We just need an Agg over the
* cheapest-total input path, since input order won't matter.
*/
- add_path(grouped_rel, (Path *)
+ add_path(root, grouped_rel, (Path *)
create_agg_path(root, grouped_rel,
cheapest_path,
grouped_rel->reltarget,
@@ -7156,7 +7156,7 @@ add_paths_to_grouping_rel(PlannerInfo *root, RelOptInfo *input_rel,
{
Path *path = partially_grouped_rel->cheapest_total_path;
- add_path(grouped_rel, (Path *)
+ add_path(root, grouped_rel, (Path *)
create_agg_path(root,
grouped_rel,
path,
@@ -7345,7 +7345,7 @@ create_partial_grouping_paths(PlannerInfo *root,
continue;
if (parse->hasAggs)
- add_path(partially_grouped_rel, (Path *)
+ add_path(root, partially_grouped_rel, (Path *)
create_agg_path(root,
partially_grouped_rel,
path,
@@ -7357,7 +7357,7 @@ create_partial_grouping_paths(PlannerInfo *root,
agg_partial_costs,
dNumPartialGroups));
else
- add_path(partially_grouped_rel, (Path *)
+ add_path(root, partially_grouped_rel, (Path *)
create_group_path(root,
partially_grouped_rel,
path,
@@ -7433,7 +7433,7 @@ create_partial_grouping_paths(PlannerInfo *root,
/* Checked above */
Assert(parse->hasAggs || parse->groupClause);
- add_path(partially_grouped_rel, (Path *)
+ add_path(root, partially_grouped_rel, (Path *)
create_agg_path(root,
partially_grouped_rel,
cheapest_total_path,
@@ -7568,7 +7568,7 @@ gather_grouping_paths(PlannerInfo *root, RelOptInfo *rel)
NULL,
&total_groups);
- add_path(rel, path);
+ add_path(root, rel, path);
}
}
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 1c69c6e97e8..f4875de8b45 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -486,7 +486,7 @@ generate_recursion_path(SetOperationStmt *setOp, PlannerInfo *root,
root->wt_param_id,
dNumGroups);
- add_path(result_rel, path);
+ add_path(root, result_rel, path);
postprocess_setop_rel(root, result_rel);
return result_rel;
}
@@ -554,12 +554,12 @@ build_setop_child_paths(PlannerInfo *root, RelOptInfo *rel,
make_tlist_from_pathtarget(subpath->pathtarget));
/* Generate outer path using this subpath */
- add_path(rel, (Path *) create_subqueryscan_path(root,
- rel,
- subpath,
- trivial_tlist,
- pathkeys,
- NULL));
+ add_path(root, rel, (Path *) create_subqueryscan_path(root,
+ rel,
+ subpath,
+ trivial_tlist,
+ pathkeys,
+ NULL));
}
/* skip dealing with sorted paths if the setop doesn't need them */
@@ -622,12 +622,12 @@ build_setop_child_paths(PlannerInfo *root, RelOptInfo *rel,
make_tlist_from_pathtarget(subpath->pathtarget));
/* Generate outer path using this subpath */
- add_path(rel, (Path *) create_subqueryscan_path(root,
- rel,
- subpath,
- trivial_tlist,
- pathkeys,
- NULL));
+ add_path(root, rel, (Path *) create_subqueryscan_path(root,
+ rel,
+ subpath,
+ trivial_tlist,
+ pathkeys,
+ NULL));
}
}
@@ -919,7 +919,7 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
NIL,
NULL,
dNumGroups);
- add_path(result_rel, path);
+ add_path(root, result_rel, path);
/* Try hash aggregate on the Gather path, if valid */
if (gpath != NULL)
@@ -935,7 +935,7 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
NIL,
NULL,
dNumGroups);
- add_path(result_rel, path);
+ add_path(root, result_rel, path);
}
}
@@ -955,7 +955,7 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
list_length(path->pathkeys),
dNumGroups);
- add_path(result_rel, path);
+ add_path(root, result_rel, path);
/* Try Sort -> Unique on the Gather path, if set */
if (gpath != NULL)
@@ -971,7 +971,7 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
path,
list_length(path->pathkeys),
dNumGroups);
- add_path(result_rel, path);
+ add_path(root, result_rel, path);
}
}
@@ -996,16 +996,16 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
list_length(tlist),
dNumGroups);
- add_path(result_rel, path);
+ add_path(root, result_rel, path);
}
}
else
{
/* UNION ALL */
- add_path(result_rel, apath);
+ add_path(root, result_rel, apath);
if (gpath != NULL)
- add_path(result_rel, gpath);
+ add_path(root, result_rel, gpath);
}
return result_rel;
@@ -1188,7 +1188,7 @@ generate_nonunion_paths(SetOperationStmt *op, PlannerInfo *root,
dNumOutputRows);
result_rel->rows = path->rows;
- add_path(result_rel, path);
+ add_path(root, result_rel, path);
return result_rel;
}
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index b8b1eae295e..91d394ce62e 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -417,7 +417,7 @@ set_cheapest(RelOptInfo *parent_rel)
* Returns nothing, but modifies parent_rel->pathlist.
*/
void
-add_path(RelOptInfo *parent_rel, Path *new_path)
+add_path(PlannerInfo *root, RelOptInfo *parent_rel, Path *new_path)
{
bool accept_new = true; /* unless we find a superior old path */
int insert_at = 0; /* where to insert new item */
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index e05b21c884e..e3ac85d53dc 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -399,7 +399,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent)
* Restriction clause reduced to constant FALSE or NULL. Mark as
* dummy so we won't scan this relation.
*/
- mark_dummy_rel(rel);
+ mark_dummy_rel(root, rel);
}
}
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index 112e7c23d4e..15766e489fd 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -26,7 +26,7 @@ extern int compare_path_costs(Path *path1, Path *path2,
extern int compare_fractional_path_costs(Path *path1, Path *path2,
double fraction);
extern void set_cheapest(RelOptInfo *parent_rel);
-extern void add_path(RelOptInfo *parent_rel, Path *new_path);
+extern void add_path(PlannerInfo *root, RelOptInfo *parent_rel, Path *new_path);
extern bool add_path_precheck(RelOptInfo *parent_rel,
Cost startup_cost, Cost total_cost,
List *pathkeys, Relids required_outer);
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 5c029b6b620..6fd45bc9dcd 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -108,7 +108,7 @@ extern bool have_join_order_restriction(PlannerInfo *root,
RelOptInfo *rel1, RelOptInfo *rel2);
extern bool have_dangerous_phv(PlannerInfo *root,
Relids outer_relids, Relids inner_params);
-extern void mark_dummy_rel(RelOptInfo *rel);
+extern void mark_dummy_rel(PlannerInfo *root, RelOptInfo *rel);
extern void init_dummy_sjinfo(SpecialJoinInfo *sjinfo, Relids left_relids,
Relids right_relids);
--
2.39.3 (Apple Git-146)
Anthonin Bonnefoy <anthonin.bonnefoy@datadoghq.com> writes:
I have a prototype for an ALL_CANDIDATES option for EXPLAIN. The goal
of this option is to print all plan candidates instead of only the
cheapest plan. It will output the plans from the most expensive at the
top to the cheapest.
This doesn't seem feasible at all to me. If we don't prune plans
fairly aggressively at lower plan levels, we'll have a combinatorial
explosion in the amount of work the planner has to do. Have you
tried this on even slightly complex plans --- say, a dozen-way join?
I do not think you'll like the runtime, the amount of memory consumed,
or the verboseness of the output. (I wonder how it interacts with
GEQO, too.)
regards, tom lane
On Fri, Jul 26, 2024 at 12:59 PM Anthonin Bonnefoy
<anthonin.bonnefoy@datadoghq.com> wrote:
I have a prototype for an ALL_CANDIDATES option for EXPLAIN. The goal
of this option is to print all plan candidates instead of only the
cheapest plan. It will output the plans from the most expensive at the
top to the cheapest. Here's an example:
It's difficult for me to understand how this can work. Either it's not
really going to print out all candidates, or it's going to print out
gigabytes of output for moderately complex queries.
I've thought about trying to figure out some way of identifying and
printing out plans that are "interestingly different" from the chosen
plan, with the costs they would have had, but I haven't been able to
come up with a good algorithm. Printing out absolutely everything
doesn't seem viable, because planning would be slow and use amazing
amounts of memory and the output would be so large as to be useless.
--
Robert Haas
EDB: http://www.enterprisedb.com
Robert Haas <robertmhaas@gmail.com> writes:
I've thought about trying to figure out some way of identifying and
printing out plans that are "interestingly different" from the chosen
plan, with the costs they would have had, but I haven't been able to
come up with a good algorithm.
I wonder how far you'd get by just printing the surviving paths
(that is, something like Anthonin's patch, but without lobotomizing
add_path). The survivors would have to dominate the cheapest-total
path along one of the other metrics add_path considers, which
seems like a rough approximation of "interestingly different".
regards, tom lane
On Fri, Jul 26, 2024 at 1:40 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
I wonder how far you'd get by just printing the surviving paths
(that is, something like Anthonin's patch, but without lobotomizing
add_path). The survivors would have to dominate the cheapest-total
path along one of the other metrics add_path considers, which
seems like a rough approximation of "interestingly different".
My guess is it wouldn't be that great. It seems easy to imagine that
the key decision for a particular plan might be whether to use table A
or B as the driving table, or whether to sequential scan or index scan
some table involved in the query. It could well be that you end up
with the same output ordering either way (or no output ordering) for
one reason or another. I'm actually not sure "interestingly different"
can be defined in a useful, general way, because how is the computer
to know what the human being cares about in a particular case? In
practice, it feels like what you'd often like to do is say "show me
the plan if you { started with table | used scan method Y on table X |
did not use index X | whatever }". I haven't given up on the idea that
there could be some way of defining interesting-ness that avoids the
need for the user to say what they think is interesting, but it
certainly feels like one needs to be a lot more clever to make it work
without user input.
--
Robert Haas
EDB: http://www.enterprisedb.com
On Fri, Jul 26, 2024 at 10:47 PM Robert Haas <robertmhaas@gmail.com> wrote:
On Fri, Jul 26, 2024 at 12:59 PM Anthonin Bonnefoy
<anthonin.bonnefoy@datadoghq.com> wrote:I have a prototype for an ALL_CANDIDATES option for EXPLAIN. The goal
of this option is to print all plan candidates instead of only the
cheapest plan. It will output the plans from the most expensive at the
top to the cheapest. Here's an example:It's difficult for me to understand how this can work. Either it's not
really going to print out all candidates, or it's going to print out
gigabytes of output for moderately complex queries.I've thought about trying to figure out some way of identifying and
printing out plans that are "interestingly different" from the chosen
plan, with the costs they would have had, but I haven't been able to
come up with a good algorithm. Printing out absolutely everything
doesn't seem viable, because planning would be slow and use amazing
amounts of memory and the output would be so large as to be useless.
If we print the path forest as a forest as against individual path
trees, we will be able to cut down on the size but it will still be
huge. Irrespective of that even with slightly non-trivial queries it's
going to be difficult to analyze these paths. The way I think of it is
dumping this information in the form of tables. Roughly something like
a table containing RelOptInfo id and RelOptInfo itself and another
containing all the paths identified by id and RelOptInfo id. The path
linkages are stored as path ids. That's a minimum. We will need more
tables to store query, and other metadata. If we do so we can use SQL
to carry out investigations.
--
Best Wishes,
Ashutosh Bapat