On Wed, 27 Mar 2024 at 22:47, David Rowley <[email protected]> wrote:
> I did wonder when first working on this patch if subquery_planner()
> should grow an extra parameter, or maybe consolidate some existing
> ones by passing some struct that provides the planner with a bit more
> context about the query. A few of the existing parameters are likely
> candidates for being in such a struct. e.g. hasRecursion and
> tuple_fraction. A SetOperationStmt could go in there too.
The attached is roughly what I had in mind. I've not taken the time
to see what comments need to be updated, so the attached aims only to
assist discussion.
David
diff --git a/src/backend/optimizer/path/allpaths.c
b/src/backend/optimizer/path/allpaths.c
index 7bad404458..26cbe2c9a2 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -2494,6 +2494,7 @@ static void
set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
Index rti, RangeTblEntry *rte)
{
+ PlannerContext context;
Query *parse = root->parse;
Query *subquery = rte->subquery;
bool trivial_pathtarget;
@@ -2644,10 +2645,12 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo
*rel,
/* plan_params should not be in use in current query level */
Assert(root->plan_params == NIL);
+ context.hasRecursion = false;
+ context.tuple_fraction = tuple_fraction;
+ context.setops = NULL;
+
/* Generate a subroot and Paths for the subquery */
- rel->subroot = subquery_planner(root->glob, subquery,
- root,
- false,
tuple_fraction);
+ rel->subroot = subquery_planner(root->glob, subquery, root, &context);
/* Isolate the params needed by this specific subplan */
rel->subplan_params = root->plan_params;
diff --git a/src/backend/optimizer/plan/planner.c
b/src/backend/optimizer/plan/planner.c
index 38d070fa00..56fa445ae8 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -127,7 +127,7 @@ typedef struct
/* Local functions */
static Node *preprocess_expression(PlannerInfo *root, Node *expr, int kind);
static void preprocess_qual_conditions(PlannerInfo *root, Node *jtnode);
-static void grouping_planner(PlannerInfo *root, double tuple_fraction);
+static void grouping_planner(PlannerInfo *root, PlannerContext *context);
static grouping_sets_data *preprocess_grouping_sets(PlannerInfo *root);
static List *remap_to_groupclause_idx(List *groupClause, List *gsets,
int
*tleref_to_colnum_map);
@@ -288,6 +288,7 @@ standard_planner(Query *parse, const char *query_string,
int cursorOptions,
{
PlannedStmt *result;
PlannerGlobal *glob;
+ PlannerContext context;
double tuple_fraction;
PlannerInfo *root;
RelOptInfo *final_rel;
@@ -410,9 +411,12 @@ standard_planner(Query *parse, const char *query_string,
int cursorOptions,
tuple_fraction = 0.0;
}
+ context.hasRecursion = false;
+ context.tuple_fraction = tuple_fraction;
+ context.setops = NULL;
+
/* primary planning entry point (may recurse for subqueries) */
- root = subquery_planner(glob, parse, NULL,
- false, tuple_fraction);
+ root = subquery_planner(glob, parse, NULL, &context);
/* Select best Path and turn it into a Plan */
final_rel = fetch_upper_rel(root, UPPERREL_FINAL, NULL);
@@ -600,9 +604,6 @@ standard_planner(Query *parse, const char *query_string,
int cursorOptions,
* glob is the global state for the current planner run.
* parse is the querytree produced by the parser & rewriter.
* parent_root is the immediate parent Query's info (NULL at the top level).
- * hasRecursion is true if this is a recursive WITH query.
- * tuple_fraction is the fraction of tuples we expect will be retrieved.
- * tuple_fraction is interpreted as explained for grouping_planner, below.
*
* Basically, this routine does the stuff that should only be done once
* per Query object. It then calls grouping_planner. At one time,
@@ -623,7 +624,7 @@ standard_planner(Query *parse, const char *query_string,
int cursorOptions,
PlannerInfo *
subquery_planner(PlannerGlobal *glob, Query *parse,
PlannerInfo *parent_root,
- bool hasRecursion, double tuple_fraction)
+ PlannerContext *context)
{
PlannerInfo *root;
List *newWithCheckOptions;
@@ -667,8 +668,8 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
root->hasPseudoConstantQuals = false;
root->hasAlternativeSubPlans = false;
root->placeholdersFrozen = false;
- root->hasRecursion = hasRecursion;
- if (hasRecursion)
+ root->hasRecursion = context->hasRecursion;
+ if (context->hasRecursion)
root->wt_param_id = assign_special_exec_param(root);
else
root->wt_param_id = -1;
@@ -1082,7 +1083,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
/*
* Do the main planning.
*/
- grouping_planner(root, tuple_fraction);
+ grouping_planner(root, context);
/*
* Capture the set of outer-level param IDs we have access to, for use
in
@@ -1274,14 +1275,6 @@ preprocess_phv_expression(PlannerInfo *root, Expr *expr)
* This function adds all required top-level processing to the scan/join
* Path(s) produced by query_planner.
*
- * tuple_fraction is the fraction of tuples we expect will be retrieved.
- * tuple_fraction is interpreted as follows:
- * 0: expect all tuples to be retrieved (normal case)
- * 0 < tuple_fraction < 1: expect the given fraction of tuples available
- * from the plan to be retrieved
- * tuple_fraction >= 1: tuple_fraction is the absolute number of tuples
- * expected to be retrieved (ie, a LIMIT specification)
- *
* Returns nothing; the useful output is in the Paths we attach to the
* (UPPERREL_FINAL, NULL) upperrel in *root. In addition,
* root->processed_tlist contains the final processed targetlist.
@@ -1291,7 +1284,7 @@ preprocess_phv_expression(PlannerInfo *root, Expr *expr)
*--------------------
*/
static void
-grouping_planner(PlannerInfo *root, double tuple_fraction)
+grouping_planner(PlannerInfo *root, PlannerContext *context)
{
Query *parse = root->parse;
int64 offset_est = 0;
@@ -1305,6 +1298,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
RelOptInfo *current_rel;
RelOptInfo *final_rel;
FinalPathExtraData extra;
+ double tuple_fraction = context->tuple_fraction;
ListCell *lc;
/* Tweak caller-supplied tuple_fraction if have LIMIT/OFFSET */
@@ -1507,16 +1501,10 @@ grouping_planner(PlannerInfo *root, double
tuple_fraction)
qp_extra.gset_data = gset_data;
/*
- * Check if we're a subquery for a set operation. If we are,
store
- * the SetOperationStmt in qp_extra.
+ * If we're a subquery for a set operation, store the
SetOperationStmt in
+ * qp_extra.
*/
- if (root->parent_root != NULL &&
- root->parent_root->parse->setOperations != NULL &&
- IsA(root->parent_root->parse->setOperations,
SetOperationStmt))
- qp_extra.setop =
- (SetOperationStmt *)
root->parent_root->parse->setOperations;
- else
- qp_extra.setop = NULL;
+ qp_extra.setop = (SetOperationStmt *) context->setops;
/*
* Generate the best unsorted and presorted paths for the
scan/join
diff --git a/src/backend/optimizer/plan/subselect.c
b/src/backend/optimizer/plan/subselect.c
index d5fa281b10..49be10e29b 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -167,6 +167,7 @@ make_subplan(PlannerInfo *root, Query *orig_subquery,
bool simple_exists = false;
double tuple_fraction;
PlannerInfo *subroot;
+ PlannerContext context;
RelOptInfo *final_rel;
Path *best_path;
Plan *plan;
@@ -217,10 +218,12 @@ make_subplan(PlannerInfo *root, Query *orig_subquery,
/* plan_params should not be in use in current query level */
Assert(root->plan_params == NIL);
+ context.hasRecursion = false;
+ context.tuple_fraction = tuple_fraction;
+ context.setops = NULL;
+
/* Generate Paths for the subquery */
- subroot = subquery_planner(root->glob, subquery,
- root,
- false,
tuple_fraction);
+ subroot = subquery_planner(root->glob, subquery, root, &context);
/* Isolate the params needed by this specific subplan */
plan_params = root->plan_params;
@@ -265,10 +268,14 @@ make_subplan(PlannerInfo *root, Query *orig_subquery,
&newtestexpr, ¶mIds);
if (subquery)
{
+ PlannerContext context;
+
+ context.hasRecursion = false;
+ context.tuple_fraction = 0.0;
+ context.setops = NULL;
+
/* Generate Paths for the ANY subquery; we'll need all
rows */
- subroot = subquery_planner(root->glob, subquery,
- root,
-
false, 0.0);
+ subroot = subquery_planner(root->glob, subquery, root,
&context);
/* Isolate the params needed by this specific subplan */
plan_params = root->plan_params;
@@ -891,6 +898,7 @@ SS_process_ctes(PlannerInfo *root)
CmdType cmdType = ((Query *)
cte->ctequery)->commandType;
Query *subquery;
PlannerInfo *subroot;
+ PlannerContext context;
RelOptInfo *final_rel;
Path *best_path;
Plan *plan;
@@ -963,13 +971,16 @@ SS_process_ctes(PlannerInfo *root)
/* plan_params should not be in use in current query level */
Assert(root->plan_params == NIL);
+ context.hasRecursion = cte->cterecursive;
/*
- * Generate Paths for the CTE query. Always plan for full
retrieval
- * --- we don't have enough info to predict otherwise.
+ * Always plan for full retrieval --- we don't have enough info
to
+ * predict otherwise.
*/
- subroot = subquery_planner(root->glob, subquery,
- root,
-
cte->cterecursive, 0.0);
+ context.tuple_fraction = 0.0;
+ context.setops = NULL;
+
+ /* Generate Paths for the CTE query. */
+ subroot = subquery_planner(root->glob, subquery, root,
&context);
/*
* Since the current query level doesn't yet contain any RTEs,
it
diff --git a/src/backend/optimizer/prep/prepunion.c
b/src/backend/optimizer/prep/prepunion.c
index 944afc7192..113c721602 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -242,6 +242,7 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
if (IsA(setOp, RangeTblRef))
{
+ PlannerContext context;
RangeTblRef *rtr = (RangeTblRef *) setOp;
RangeTblEntry *rte = root->simple_rte_array[rtr->rtindex];
Query *subquery = rte->subquery;
@@ -257,11 +258,14 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
/* plan_params should not be in use in current query level */
Assert(root->plan_params == NIL);
+ context.hasRecursion = false;
+ context.tuple_fraction = root->tuple_fraction;
+ context.setops = castNode(SetOperationStmt,
+
root->parse->setOperations);
+
/* Generate a subroot and Paths for the subquery */
- subroot = rel->subroot = subquery_planner(root->glob, subquery,
-
root,
-
false,
-
root->tuple_fraction);
+ subroot = rel->subroot = subquery_planner(root->glob, subquery,
root,
+
&context);
/*
* It should not be possible for the primitive query to contain
any
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 595eec2cbb..a3727ba2f1 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -551,6 +551,37 @@ struct PlannerInfo
bool partColsUpdated;
};
+/*----------
+ * PlannerContext
+ * Per-query context to provide additional information to
+ * subquery_planner() and grouping_planner().
+ */
+typedef struct PlannerContext
+{
+ /*
+ * hasRecursion: must be given as true if planning recursive WITH query.
+ */
+ bool hasRecursion;
+
+ /*--------------------
+ * tuple_fraction is the fraction of tuples we expect will be retrieved.
+ * tuple_fraction is interpreted as follows:
+ * 0: expect all tuples to be retrieved (normal case)
+ * 0 < tuple_fraction < 1: expect the given fraction of tuples
available
+ * from the plan to be retrieved
+ * tuple_fraction >= 1: tuple_fraction is the absolute number of
tuples
+ * expected to be retrieved (ie, a LIMIT specification)
+ *--------------------
+ */
+ double tuple_fraction;
+
+ /*
+ * Can be set for set operation child queries to provide information on
+ * the parent-level operation to allow them to optimize for this context
+ */
+ SetOperationStmt *setops;
+
+} PlannerContext;
/*
* In places where it's known that simple_rte_array[] must have been prepared
diff --git a/src/include/optimizer/planner.h b/src/include/optimizer/planner.h
index e1d79ffdf3..e3c9a4e3ae 100644
--- a/src/include/optimizer/planner.h
+++ b/src/include/optimizer/planner.h
@@ -44,7 +44,7 @@ extern PlannedStmt *standard_planner(Query *parse, const char
*query_string,
extern PlannerInfo *subquery_planner(PlannerGlobal *glob, Query *parse,
PlannerInfo *parent_root,
- bool
hasRecursion, double tuple_fraction);
+
PlannerContext *context);
extern RowMarkType select_rowmark_type(RangeTblEntry *rte,
LockClauseStrength strength);