summaryrefslogtreecommitdiff
path: root/src/backend
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend')
-rw-r--r--src/backend/access/gin/gininsert.c22
-rw-r--r--src/backend/commands/explain.c15
-rw-r--r--src/backend/executor/nodeWindowAgg.c77
-rw-r--r--src/backend/optimizer/geqo/geqo_eval.c2
-rw-r--r--src/backend/optimizer/geqo/geqo_main.c12
-rw-r--r--src/backend/optimizer/geqo/geqo_random.c7
-rw-r--r--src/backend/optimizer/path/allpaths.c6
-rw-r--r--src/backend/optimizer/plan/analyzejoins.c9
-rw-r--r--src/backend/optimizer/plan/planagg.c3
-rw-r--r--src/backend/optimizer/plan/planner.c79
-rw-r--r--src/backend/optimizer/plan/setrefs.c27
-rw-r--r--src/backend/optimizer/plan/subselect.c87
-rw-r--r--src/backend/optimizer/prep/prepjointree.c2
-rw-r--r--src/backend/optimizer/prep/prepunion.c69
-rw-r--r--src/backend/optimizer/util/Makefile1
-rw-r--r--src/backend/optimizer/util/extendplan.c183
-rw-r--r--src/backend/optimizer/util/meson.build1
-rw-r--r--src/backend/utils/adt/ruleutils.c33
18 files changed, 528 insertions, 107 deletions
diff --git a/src/backend/access/gin/gininsert.c b/src/backend/access/gin/gininsert.c
index e9d4b27427e..1d3ab22556d 100644
--- a/src/backend/access/gin/gininsert.c
+++ b/src/backend/access/gin/gininsert.c
@@ -218,7 +218,8 @@ addItemPointersToLeafTuple(GinState *ginstate,
ItemPointerData *newItems,
*oldItems;
int oldNPosting,
- newNPosting;
+ newNPosting,
+ nwritten;
GinPostingList *compressedList;
Assert(!GinIsPostingTree(old));
@@ -235,18 +236,19 @@ addItemPointersToLeafTuple(GinState *ginstate,
/* Compress the posting list, and try to a build tuple with room for it */
res = NULL;
- compressedList = ginCompressPostingList(newItems, newNPosting, GinMaxItemSize,
- NULL);
- pfree(newItems);
- if (compressedList)
+ compressedList = ginCompressPostingList(newItems, newNPosting, GinMaxItemSize, &nwritten);
+ if (nwritten == newNPosting)
{
res = GinFormTuple(ginstate, attnum, key, category,
(char *) compressedList,
SizeOfGinPostingList(compressedList),
newNPosting,
false);
- pfree(compressedList);
}
+
+ pfree(newItems);
+ pfree(compressedList);
+
if (!res)
{
/* posting list would be too big, convert to posting tree */
@@ -293,17 +295,19 @@ buildFreshLeafTuple(GinState *ginstate,
{
IndexTuple res = NULL;
GinPostingList *compressedList;
+ int nwritten;
/* try to build a posting list tuple with all the items */
- compressedList = ginCompressPostingList(items, nitem, GinMaxItemSize, NULL);
- if (compressedList)
+ compressedList = ginCompressPostingList(items, nitem, GinMaxItemSize, &nwritten);
+ if (nwritten == nitem)
{
res = GinFormTuple(ginstate, attnum, key, category,
(char *) compressedList,
SizeOfGinPostingList(compressedList),
nitem, false);
- pfree(compressedList);
}
+ pfree(compressedList);
+
if (!res)
{
/* posting list would be too big, build posting tree */
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 207f86f1d39..06191cd8a85 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -4901,6 +4901,7 @@ ExplainSubPlans(List *plans, List *ancestors,
{
SubPlanState *sps = (SubPlanState *) lfirst(lst);
SubPlan *sp = sps->subplan;
+ char *cooked_plan_name;
/*
* There can be multiple SubPlan nodes referencing the same physical
@@ -4924,8 +4925,20 @@ ExplainSubPlans(List *plans, List *ancestors,
*/
ancestors = lcons(sp, ancestors);
+ /*
+ * The plan has a name like exists_1 or rowcompare_2, but here we want
+ * to prefix that with CTE, InitPlan, or SubPlan, as appropriate, for
+ * display purposes.
+ */
+ if (sp->subLinkType == CTE_SUBLINK)
+ cooked_plan_name = psprintf("CTE %s", sp->plan_name);
+ else if (sp->isInitPlan)
+ cooked_plan_name = psprintf("InitPlan %s", sp->plan_name);
+ else
+ cooked_plan_name = psprintf("SubPlan %s", sp->plan_name);
+
ExplainNode(sps->planstate, ancestors,
- relationship, sp->plan_name, es);
+ relationship, cooked_plan_name, es);
ancestors = list_delete_first(ancestors);
}
diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index cf667c81211..0698aae37a7 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -1501,8 +1501,9 @@ row_is_in_frame(WindowObject winobj, int64 pos, TupleTableSlot *slot,
/* following row that is not peer is out of frame */
if (pos > winstate->currentpos)
{
- if (fetch_tuple)
- window_gettupleslot(winobj, pos, slot);
+ if (fetch_tuple) /* need to fetch tuple? */
+ if (!window_gettupleslot(winobj, pos, slot))
+ return -1;
if (!are_peers(winstate, slot, winstate->ss.ss_ScanTupleSlot))
return -1;
}
@@ -3721,6 +3722,7 @@ WinGetFuncArgInPartition(WindowObject winobj, int argno,
int notnull_offset;
int notnull_relpos;
int forward;
+ bool myisout;
Assert(WindowObjectIsValid(winobj));
winstate = winobj->winstate;
@@ -3759,63 +3761,60 @@ WinGetFuncArgInPartition(WindowObject winobj, int argno,
if (!null_treatment) /* IGNORE NULLS is not specified */
{
+ /* get tupple and evaluate in a partition */
datum = gettuple_eval_partition(winobj, argno,
- abs_pos, isnull, isout);
- if (!*isout && set_mark)
+ abs_pos, isnull, &myisout);
+ if (!myisout && set_mark)
WinSetMarkPosition(winobj, abs_pos);
+ if (isout)
+ *isout = myisout;
return datum;
}
+ myisout = false;
+ datum = 0;
+
/*
* Get the next nonnull value in the partition, moving forward or backward
* until we find a value or reach the partition's end.
*/
do
{
+ int nn_info; /* NOT NULL info */
+
abs_pos += forward;
- if (abs_pos < 0)
- {
- /* out of partition */
- if (isout)
- *isout = true;
- *isnull = true;
- datum = 0;
+ if (abs_pos < 0) /* apparently out of partition */
break;
- }
- switch (get_notnull_info(winobj, abs_pos))
+ /* check NOT NULL cached info */
+ nn_info = get_notnull_info(winobj, abs_pos);
+ if (nn_info == NN_NOTNULL) /* this row is known to be NOT NULL */
+ notnull_offset++;
+
+ else if (nn_info == NN_NULL) /* this row is known to be NULL */
+ continue; /* keep on moving forward or backward */
+
+ else /* need to check NULL or not */
{
- case NN_NOTNULL: /* this row is known to be NOT NULL */
- notnull_offset++;
- if (notnull_offset >= notnull_relpos)
- {
- /* prepare to exit this loop */
- datum = gettuple_eval_partition(winobj, argno,
- abs_pos, isnull, isout);
- }
- break;
- case NN_NULL: /* this row is known to be NULL */
- if (isout)
- *isout = false;
- *isnull = true;
- datum = 0;
- break;
- default: /* need to check NULL or not */
- datum = gettuple_eval_partition(winobj, argno,
- abs_pos, isnull, isout);
- if (*isout) /* out of partition? */
- return datum;
-
- if (!*isnull)
- notnull_offset++;
- /* record the row status */
- put_notnull_info(winobj, abs_pos, *isnull);
+ /* get tupple and evaluate in a partition */
+ datum = gettuple_eval_partition(winobj, argno,
+ abs_pos, isnull, &myisout);
+ if (myisout) /* out of partition? */
break;
+ if (!*isnull)
+ notnull_offset++;
+ /* record the row status */
+ put_notnull_info(winobj, abs_pos, *isnull);
}
} while (notnull_offset < notnull_relpos);
- if (!*isout && set_mark)
+ /* get tupple and evaluate in a partition */
+ datum = gettuple_eval_partition(winobj, argno,
+ abs_pos, isnull, &myisout);
+ if (!myisout && set_mark)
WinSetMarkPosition(winobj, abs_pos);
+ if (isout)
+ *isout = myisout;
return datum;
}
diff --git a/src/backend/optimizer/geqo/geqo_eval.c b/src/backend/optimizer/geqo/geqo_eval.c
index f07d1dc8ac6..7fcb1aa70d1 100644
--- a/src/backend/optimizer/geqo/geqo_eval.c
+++ b/src/backend/optimizer/geqo/geqo_eval.c
@@ -162,7 +162,7 @@ geqo_eval(PlannerInfo *root, Gene *tour, int num_gene)
RelOptInfo *
gimme_tree(PlannerInfo *root, Gene *tour, int num_gene)
{
- GeqoPrivateData *private = (GeqoPrivateData *) root->join_search_private;
+ GeqoPrivateData *private = GetGeqoPrivateData(root);
List *clumps;
int rel_count;
diff --git a/src/backend/optimizer/geqo/geqo_main.c b/src/backend/optimizer/geqo/geqo_main.c
index 38402ce58db..0064556087a 100644
--- a/src/backend/optimizer/geqo/geqo_main.c
+++ b/src/backend/optimizer/geqo/geqo_main.c
@@ -47,6 +47,8 @@ int Geqo_generations;
double Geqo_selection_bias;
double Geqo_seed;
+/* GEQO is treated as an in-core planner extension */
+int Geqo_planner_extension_id = -1;
static int gimme_pool_size(int nr_rel);
static int gimme_number_generations(int pool_size);
@@ -98,10 +100,16 @@ geqo(PlannerInfo *root, int number_of_rels, List *initial_rels)
int mutations = 0;
#endif
+ if (Geqo_planner_extension_id < 0)
+ Geqo_planner_extension_id = GetPlannerExtensionId("geqo");
+
/* set up private information */
- root->join_search_private = &private;
+ SetPlannerInfoExtensionState(root, Geqo_planner_extension_id, &private);
private.initial_rels = initial_rels;
+/* inform core planner that we may replan */
+ root->assumeReplanning = true;
+
/* initialize private number generator */
geqo_set_seed(root, Geqo_seed);
@@ -304,7 +312,7 @@ geqo(PlannerInfo *root, int number_of_rels, List *initial_rels)
free_pool(root, pool);
/* ... clear root pointer to our private storage */
- root->join_search_private = NULL;
+ SetPlannerInfoExtensionState(root, Geqo_planner_extension_id, NULL);
return best_rel;
}
diff --git a/src/backend/optimizer/geqo/geqo_random.c b/src/backend/optimizer/geqo/geqo_random.c
index 6c7a411f69f..46d28baa2e6 100644
--- a/src/backend/optimizer/geqo/geqo_random.c
+++ b/src/backend/optimizer/geqo/geqo_random.c
@@ -15,11 +15,10 @@
#include "optimizer/geqo_random.h"
-
void
geqo_set_seed(PlannerInfo *root, double seed)
{
- GeqoPrivateData *private = (GeqoPrivateData *) root->join_search_private;
+ GeqoPrivateData *private = GetGeqoPrivateData(root);
pg_prng_fseed(&private->random_state, seed);
}
@@ -27,7 +26,7 @@ geqo_set_seed(PlannerInfo *root, double seed)
double
geqo_rand(PlannerInfo *root)
{
- GeqoPrivateData *private = (GeqoPrivateData *) root->join_search_private;
+ GeqoPrivateData *private = GetGeqoPrivateData(root);
return pg_prng_double(&private->random_state);
}
@@ -35,7 +34,7 @@ geqo_rand(PlannerInfo *root)
int
geqo_randint(PlannerInfo *root, int upper, int lower)
{
- GeqoPrivateData *private = (GeqoPrivateData *) root->join_search_private;
+ GeqoPrivateData *private = GetGeqoPrivateData(root);
/*
* In current usage, "lower" is never negative so we can just use
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index d7ff36d89be..1f82239b4e0 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -2529,6 +2529,7 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
RelOptInfo *sub_final_rel;
Bitmapset *run_cond_attrs = NULL;
ListCell *lc;
+ char *plan_name;
/*
* Must copy the Query so that planning doesn't mess up the RTE contents
@@ -2671,8 +2672,9 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
Assert(root->plan_params == NIL);
/* Generate a subroot and Paths for the subquery */
- rel->subroot = subquery_planner(root->glob, subquery, root, false,
- tuple_fraction, NULL);
+ plan_name = choose_plan_name(root->glob, rte->eref->aliasname, false);
+ rel->subroot = subquery_planner(root->glob, subquery, plan_name,
+ root, false, tuple_fraction, NULL);
/* Isolate the params needed by this specific subplan */
rel->subplan_params = root->plan_params;
diff --git a/src/backend/optimizer/plan/analyzejoins.c b/src/backend/optimizer/plan/analyzejoins.c
index 2a3dea88a94..6a3c030e8ef 100644
--- a/src/backend/optimizer/plan/analyzejoins.c
+++ b/src/backend/optimizer/plan/analyzejoins.c
@@ -1425,17 +1425,14 @@ innerrel_is_unique_ext(PlannerInfo *root,
*
* However, in normal planning mode, caching this knowledge is totally
* pointless; it won't be queried again, because we build up joinrels
- * from smaller to larger. It is useful in GEQO mode, where the
- * knowledge can be carried across successive planning attempts; and
- * it's likely to be useful when using join-search plugins, too. Hence
- * cache when join_search_private is non-NULL. (Yeah, that's a hack,
- * but it seems reasonable.)
+ * from smaller to larger. It's only useful when using GEQO or
+ * another planner extension that attempts planning multiple times.
*
* Also, allow callers to override that heuristic and force caching;
* that's useful for reduce_unique_semijoins, which calls here before
* the normal join search starts.
*/
- if (force_cache || root->join_search_private)
+ if (force_cache || root->assumeReplanning)
{
old_context = MemoryContextSwitchTo(root->planner_cxt);
innerrel->non_unique_for_rels =
diff --git a/src/backend/optimizer/plan/planagg.c b/src/backend/optimizer/plan/planagg.c
index 2ef0bb7f663..a2ac58d246e 100644
--- a/src/backend/optimizer/plan/planagg.c
+++ b/src/backend/optimizer/plan/planagg.c
@@ -38,6 +38,7 @@
#include "optimizer/pathnode.h"
#include "optimizer/paths.h"
#include "optimizer/planmain.h"
+#include "optimizer/planner.h"
#include "optimizer/subselect.h"
#include "optimizer/tlist.h"
#include "parser/parse_clause.h"
@@ -339,6 +340,8 @@ build_minmax_path(PlannerInfo *root, MinMaxAggInfo *mminfo,
memcpy(subroot, root, sizeof(PlannerInfo));
subroot->query_level++;
subroot->parent_root = root;
+ subroot->plan_name = choose_plan_name(root->glob, "minmax", true);
+
/* reset subplan-related stuff */
subroot->plan_params = NIL;
subroot->outer_params = NULL;
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 41bd8353430..0c9397a36c3 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -439,7 +439,8 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
}
/* primary planning entry point (may recurse for subqueries) */
- root = subquery_planner(glob, parse, NULL, false, tuple_fraction, NULL);
+ root = subquery_planner(glob, parse, NULL, NULL, false, tuple_fraction,
+ NULL);
/* Select best Path and turn it into a Plan */
final_rel = fetch_upper_rel(root, UPPERREL_FINAL, NULL);
@@ -630,6 +631,7 @@ 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.
+ * plan_name is the name to assign to this subplan (NULL at the top level).
* 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.
@@ -656,9 +658,9 @@ 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,
- SetOperationStmt *setops)
+subquery_planner(PlannerGlobal *glob, Query *parse, char *plan_name,
+ PlannerInfo *parent_root, bool hasRecursion,
+ double tuple_fraction, SetOperationStmt *setops)
{
PlannerInfo *root;
List *newWithCheckOptions;
@@ -673,6 +675,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse, PlannerInfo *parent_root,
root->parse = parse;
root->glob = glob;
root->query_level = parent_root ? parent_root->query_level + 1 : 1;
+ root->plan_name = plan_name;
root->parent_root = parent_root;
root->plan_params = NIL;
root->outer_params = NULL;
@@ -703,6 +706,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse, PlannerInfo *parent_root,
root->hasAlternativeSubPlans = false;
root->placeholdersFrozen = false;
root->hasRecursion = hasRecursion;
+ root->assumeReplanning = false;
if (hasRecursion)
root->wt_param_id = assign_special_exec_param(root);
else
@@ -8833,3 +8837,70 @@ create_partial_unique_paths(PlannerInfo *root, RelOptInfo *input_rel,
sjinfo, unique_rel);
}
}
+
+/*
+ * Choose a unique name for some subroot.
+ *
+ * Modifies glob->subplanNames to track names already used.
+ */
+char *
+choose_plan_name(PlannerGlobal *glob, const char *name, bool always_number)
+{
+ unsigned n;
+
+ /*
+ * If a numeric suffix is not required, then search the list of
+ * previously-assigned names for a match. If none is found, then we can
+ * use the provided name without modification.
+ */
+ if (!always_number)
+ {
+ bool found = false;
+
+ foreach_ptr(char, subplan_name, glob->subplanNames)
+ {
+ if (strcmp(subplan_name, name) == 0)
+ {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found)
+ {
+ /* pstrdup here is just to avoid cast-away-const */
+ char *chosen_name = pstrdup(name);
+
+ glob->subplanNames = lappend(glob->subplanNames, chosen_name);
+ return chosen_name;
+ }
+ }
+
+ /*
+ * If a numeric suffix is required or if the un-suffixed name is already
+ * in use, then loop until we find a positive integer that produces a
+ * novel name.
+ */
+ for (n = 1; true; ++n)
+ {
+ char *proposed_name = psprintf("%s_%u", name, n);
+ bool found = false;
+
+ foreach_ptr(char, subplan_name, glob->subplanNames)
+ {
+ if (strcmp(subplan_name, proposed_name) == 0)
+ {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found)
+ {
+ glob->subplanNames = lappend(glob->subplanNames, proposed_name);
+ return proposed_name;
+ }
+
+ pfree(proposed_name);
+ }
+}
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 6950eff2c5b..ccdc9bc264a 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -1034,16 +1034,35 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
* expected to occur here, it seems safer to special-case
* it here and keep the assertions that ROWID_VARs
* shouldn't be seen by fix_scan_expr.
+ *
+ * We also must handle the case where set operations have
+ * been short-circuited resulting in a dummy Result node.
+ * prepunion.c uses varno==0 for the set op targetlist.
+ * See generate_setop_tlist() and generate_setop_tlist().
+ * Here we rewrite these to use varno==1, which is the
+ * varno of the first set-op child. Without this, EXPLAIN
+ * will have trouble displaying targetlists of dummy set
+ * operations.
*/
foreach(l, splan->plan.targetlist)
{
TargetEntry *tle = (TargetEntry *) lfirst(l);
Var *var = (Var *) tle->expr;
- if (var && IsA(var, Var) && var->varno == ROWID_VAR)
- tle->expr = (Expr *) makeNullConst(var->vartype,
- var->vartypmod,
- var->varcollid);
+ if (var && IsA(var, Var))
+ {
+ if (var->varno == ROWID_VAR)
+ tle->expr = (Expr *) makeNullConst(var->vartype,
+ var->vartypmod,
+ var->varcollid);
+ else if (var->varno == 0)
+ tle->expr = (Expr *) makeVar(1,
+ var->varattno,
+ var->vartype,
+ var->vartypmod,
+ var->varcollid,
+ var->varlevelsup);
+ }
}
splan->plan.targetlist =
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index fae18548e07..14192a13236 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -103,6 +103,7 @@ static Bitmapset *finalize_plan(PlannerInfo *root,
Bitmapset *scan_params);
static bool finalize_primnode(Node *node, finalize_primnode_context *context);
static bool finalize_agg_primnode(Node *node, finalize_primnode_context *context);
+static const char *sublinktype_to_string(SubLinkType subLinkType);
/*
@@ -172,6 +173,7 @@ make_subplan(PlannerInfo *root, Query *orig_subquery,
Plan *plan;
List *plan_params;
Node *result;
+ const char *sublinkstr = sublinktype_to_string(subLinkType);
/*
* Copy the source Query node. This is a quick and dirty kluge to resolve
@@ -218,8 +220,9 @@ make_subplan(PlannerInfo *root, Query *orig_subquery,
Assert(root->plan_params == NIL);
/* Generate Paths for the subquery */
- subroot = subquery_planner(root->glob, subquery, root, false,
- tuple_fraction, NULL);
+ subroot = subquery_planner(root->glob, subquery,
+ choose_plan_name(root->glob, sublinkstr, true),
+ root, false, tuple_fraction, NULL);
/* Isolate the params needed by this specific subplan */
plan_params = root->plan_params;
@@ -264,9 +267,12 @@ make_subplan(PlannerInfo *root, Query *orig_subquery,
&newtestexpr, &paramIds);
if (subquery)
{
+ char *plan_name;
+
/* Generate Paths for the ANY subquery; we'll need all rows */
- subroot = subquery_planner(root->glob, subquery, root, false, 0.0,
- NULL);
+ plan_name = choose_plan_name(root->glob, sublinkstr, true);
+ subroot = subquery_planner(root->glob, subquery, plan_name,
+ root, false, 0.0, NULL);
/* Isolate the params needed by this specific subplan */
plan_params = root->plan_params;
@@ -324,15 +330,16 @@ build_subplan(PlannerInfo *root, Plan *plan, Path *path,
{
Node *result;
SubPlan *splan;
- bool isInitPlan;
ListCell *lc;
/*
- * Initialize the SubPlan node. Note plan_id, plan_name, and cost fields
- * are set further down.
+ * Initialize the SubPlan node.
+ *
+ * Note: plan_id and cost fields are set further down.
*/
splan = makeNode(SubPlan);
splan->subLinkType = subLinkType;
+ splan->plan_name = subroot->plan_name;
splan->testexpr = NULL;
splan->paramIds = NIL;
get_first_col_type(plan, &splan->firstColType, &splan->firstColTypmod,
@@ -391,7 +398,7 @@ build_subplan(PlannerInfo *root, Plan *plan, Path *path,
Assert(testexpr == NULL);
prm = generate_new_exec_param(root, BOOLOID, -1, InvalidOid);
splan->setParam = list_make1_int(prm->paramid);
- isInitPlan = true;
+ splan->isInitPlan = true;
result = (Node *) prm;
}
else if (splan->parParam == NIL && subLinkType == EXPR_SUBLINK)
@@ -406,7 +413,7 @@ build_subplan(PlannerInfo *root, Plan *plan, Path *path,
exprTypmod((Node *) te->expr),
exprCollation((Node *) te->expr));
splan->setParam = list_make1_int(prm->paramid);
- isInitPlan = true;
+ splan->isInitPlan = true;
result = (Node *) prm;
}
else if (splan->parParam == NIL && subLinkType == ARRAY_SUBLINK)
@@ -426,7 +433,7 @@ build_subplan(PlannerInfo *root, Plan *plan, Path *path,
exprTypmod((Node *) te->expr),
exprCollation((Node *) te->expr));
splan->setParam = list_make1_int(prm->paramid);
- isInitPlan = true;
+ splan->isInitPlan = true;
result = (Node *) prm;
}
else if (splan->parParam == NIL && subLinkType == ROWCOMPARE_SUBLINK)
@@ -442,7 +449,7 @@ build_subplan(PlannerInfo *root, Plan *plan, Path *path,
testexpr,
params);
splan->setParam = list_copy(splan->paramIds);
- isInitPlan = true;
+ splan->isInitPlan = true;
/*
* The executable expression is returned to become part of the outer
@@ -476,12 +483,12 @@ build_subplan(PlannerInfo *root, Plan *plan, Path *path,
/* It can be an initplan if there are no parParams. */
if (splan->parParam == NIL)
{
- isInitPlan = true;
+ splan->isInitPlan = true;
result = (Node *) makeNullConst(RECORDOID, -1, InvalidOid);
}
else
{
- isInitPlan = false;
+ splan->isInitPlan = false;
result = (Node *) splan;
}
}
@@ -536,7 +543,7 @@ build_subplan(PlannerInfo *root, Plan *plan, Path *path,
plan = materialize_finished_plan(plan);
result = (Node *) splan;
- isInitPlan = false;
+ splan->isInitPlan = false;
}
/*
@@ -547,7 +554,7 @@ build_subplan(PlannerInfo *root, Plan *plan, Path *path,
root->glob->subroots = lappend(root->glob->subroots, subroot);
splan->plan_id = list_length(root->glob->subplans);
- if (isInitPlan)
+ if (splan->isInitPlan)
root->init_plans = lappend(root->init_plans, splan);
/*
@@ -557,15 +564,10 @@ build_subplan(PlannerInfo *root, Plan *plan, Path *path,
* there's no point since it won't get re-run without parameter changes
* anyway. The input of a hashed subplan doesn't need REWIND either.
*/
- if (splan->parParam == NIL && !isInitPlan && !splan->useHashTable)
+ if (splan->parParam == NIL && !splan->isInitPlan && !splan->useHashTable)
root->glob->rewindPlanIDs = bms_add_member(root->glob->rewindPlanIDs,
splan->plan_id);
- /* Label the subplan for EXPLAIN purposes */
- splan->plan_name = psprintf("%s %d",
- isInitPlan ? "InitPlan" : "SubPlan",
- splan->plan_id);
-
/* Lastly, fill in the cost estimates for use later */
cost_subplan(root, splan, plan);
@@ -965,8 +967,9 @@ SS_process_ctes(PlannerInfo *root)
* Generate Paths for the CTE query. 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, NULL);
+ subroot = subquery_planner(root->glob, subquery,
+ choose_plan_name(root->glob, cte->ctename, false),
+ root, cte->cterecursive, 0.0, NULL);
/*
* Since the current query level doesn't yet contain any RTEs, it
@@ -989,10 +992,11 @@ SS_process_ctes(PlannerInfo *root)
* Make a SubPlan node for it. This is just enough unlike
* build_subplan that we can't share code.
*
- * Note plan_id, plan_name, and cost fields are set further down.
+ * Note: plan_id and cost fields are set further down.
*/
splan = makeNode(SubPlan);
splan->subLinkType = CTE_SUBLINK;
+ splan->plan_name = subroot->plan_name;
splan->testexpr = NULL;
splan->paramIds = NIL;
get_first_col_type(plan, &splan->firstColType, &splan->firstColTypmod,
@@ -1039,9 +1043,6 @@ SS_process_ctes(PlannerInfo *root)
root->cte_plan_ids = lappend_int(root->cte_plan_ids, splan->plan_id);
- /* Label the subplan for EXPLAIN purposes */
- splan->plan_name = psprintf("CTE %s", cte->ctename);
-
/* Lastly, fill in the cost estimates for use later */
cost_subplan(root, splan, plan);
}
@@ -3185,7 +3186,8 @@ SS_make_initplan_from_plan(PlannerInfo *root,
node = makeNode(SubPlan);
node->subLinkType = EXPR_SUBLINK;
node->plan_id = list_length(root->glob->subplans);
- node->plan_name = psprintf("InitPlan %d", node->plan_id);
+ node->plan_name = subroot->plan_name;
+ node->isInitPlan = true;
get_first_col_type(plan, &node->firstColType, &node->firstColTypmod,
&node->firstColCollation);
node->parallel_safe = plan->parallel_safe;
@@ -3201,3 +3203,32 @@ SS_make_initplan_from_plan(PlannerInfo *root,
/* Set costs of SubPlan using info from the plan tree */
cost_subplan(subroot, node, plan);
}
+
+/*
+ * Get a string equivalent of a given subLinkType.
+ */
+static const char *
+sublinktype_to_string(SubLinkType subLinkType)
+{
+ switch (subLinkType)
+ {
+ case EXISTS_SUBLINK:
+ return "exists";
+ case ALL_SUBLINK:
+ return "all";
+ case ANY_SUBLINK:
+ return "any";
+ case ROWCOMPARE_SUBLINK:
+ return "rowcompare";
+ case EXPR_SUBLINK:
+ return "expr";
+ case MULTIEXPR_SUBLINK:
+ return "multiexpr";
+ case ARRAY_SUBLINK:
+ return "array";
+ case CTE_SUBLINK:
+ return "cte";
+ }
+ Assert(false);
+ return "???";
+}
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 35e8d3c183b..481d8011791 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -1356,6 +1356,7 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
subroot->parse = subquery;
subroot->glob = root->glob;
subroot->query_level = root->query_level;
+ subroot->plan_name = root->plan_name;
subroot->parent_root = root->parent_root;
subroot->plan_params = NIL;
subroot->outer_params = NULL;
@@ -1383,6 +1384,7 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
subroot->qual_security_level = 0;
subroot->placeholdersFrozen = false;
subroot->hasRecursion = false;
+ subroot->assumeReplanning = false;
subroot->wt_param_id = -1;
subroot->non_recursive_path = NULL;
/* We don't currently need a top JoinDomain for the subroot */
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 547dbd53540..55665824179 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -232,6 +232,7 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
PlannerInfo *subroot;
List *tlist;
bool trivial_tlist;
+ char *plan_name;
Assert(subquery != NULL);
@@ -246,7 +247,9 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
* parentOp, pass that down to encourage subquery_planner to consider
* suitably-sorted Paths.
*/
- subroot = rel->subroot = subquery_planner(root->glob, subquery, root,
+ plan_name = choose_plan_name(root->glob, "setop", true);
+ subroot = rel->subroot = subquery_planner(root->glob, subquery,
+ plan_name, root,
false, root->tuple_fraction,
parentOp);
@@ -826,7 +829,6 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
/* If all UNION children were dummy rels, make the resulting rel dummy */
if (cheapest_pathlist == NIL)
{
- result_rel->reltarget = create_pathtarget(root, list_nth(tlist_list, 0));
mark_dummy_rel(result_rel);
return result_rel;
@@ -1186,6 +1188,69 @@ generate_nonunion_paths(SetOperationStmt *op, PlannerInfo *root,
result_rel->reltarget = create_setop_pathtarget(root, tlist,
list_make2(lpath, rpath));
+ /* Check for provably empty setop inputs and add short-circuit paths. */
+ if (op->op == SETOP_EXCEPT)
+ {
+ /*
+ * For EXCEPTs, if the left side is dummy then there's no need to
+ * inspect the right-hand side as scanning the right to find tuples to
+ * remove won't make the left-hand input any more empty.
+ */
+ if (is_dummy_rel(lrel))
+ {
+ mark_dummy_rel(result_rel);
+
+ return result_rel;
+ }
+
+ /* Handle EXCEPTs with dummy right input */
+ if (is_dummy_rel(rrel))
+ {
+ if (op->all)
+ {
+ Path *apath;
+
+ /*
+ * EXCEPT ALL: If the right-hand input is dummy then we can
+ * simply scan the left-hand input. To keep createplan.c
+ * happy, use a single child Append to handle the translation
+ * between the set op targetlist and the targetlist of the
+ * left input. The Append will be removed in setrefs.c.
+ */
+ apath = (Path *) create_append_path(root, result_rel, list_make1(lpath),
+ NIL, NIL, NULL, 0, false, -1);
+
+ add_path(result_rel, apath);
+
+ return result_rel;
+ }
+ else
+ {
+ /*
+ * To make EXCEPT with a dummy RHS work means having to
+ * deduplicate the left input. That could be done with
+ * AggPaths, but it doesn't seem worth the effort. Let the
+ * normal path generation code below handle this one.
+ */
+ }
+ }
+ }
+ else
+ {
+ /*
+ * For INTERSECT, if either input is a dummy rel then we can mark the
+ * result_rel as dummy since intersecting with an empty relation can
+ * never yield any results. This is true regardless of INTERSECT or
+ * INTERSECT ALL.
+ */
+ if (is_dummy_rel(lrel) || is_dummy_rel(rrel))
+ {
+ mark_dummy_rel(result_rel);
+
+ return result_rel;
+ }
+ }
+
/*
* Estimate number of distinct groups that we'll need hashtable entries
* for; this is the size of the left-hand input for EXCEPT, or the smaller
diff --git a/src/backend/optimizer/util/Makefile b/src/backend/optimizer/util/Makefile
index 4fb115cb118..87b4c3c0869 100644
--- a/src/backend/optimizer/util/Makefile
+++ b/src/backend/optimizer/util/Makefile
@@ -15,6 +15,7 @@ include $(top_builddir)/src/Makefile.global
OBJS = \
appendinfo.o \
clauses.o \
+ extendplan.o \
inherit.o \
joininfo.o \
orclauses.o \
diff --git a/src/backend/optimizer/util/extendplan.c b/src/backend/optimizer/util/extendplan.c
new file mode 100644
index 00000000000..03d32277ba1
--- /dev/null
+++ b/src/backend/optimizer/util/extendplan.c
@@ -0,0 +1,183 @@
+/*-------------------------------------------------------------------------
+ *
+ * extendplan.c
+ * Extend core planner objects with additional private state
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * The interfaces defined in this file make it possible for loadable
+ * modules to store their own private state inside of key planner data
+ * structures -- specifically, the PlannerGlobal, PlannerInfo, and
+ * RelOptInfo structures. This can make it much easier to write
+ * reasonably efficient planner extensions; for instance, code that
+ * uses set_join_pathlist_hook can arrange to compute a key intermediate
+ * result once per joinrel rather than on every call.
+ *
+ * IDENTIFICATION
+ * src/backend/optimizer/util/extendplan.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "optimizer/extendplan.h"
+#include "port/pg_bitutils.h"
+#include "utils/memutils.h"
+
+static const char **PlannerExtensionNameArray = NULL;
+static int PlannerExtensionNamesAssigned = 0;
+static int PlannerExtensionNamesAllocated = 0;
+
+/*
+ * Map the name of a planner extension to an integer ID.
+ *
+ * Within the lifetime of a particular backend, the same name will be mapped
+ * to the same ID every time. IDs are not stable across backends. Use the ID
+ * that you get from this function to call the remaining functions in this
+ * file.
+ */
+int
+GetPlannerExtensionId(const char *extension_name)
+{
+ /* Search for an existing extension by this name; if found, return ID. */
+ for (int i = 0; i < PlannerExtensionNamesAssigned; ++i)
+ if (strcmp(PlannerExtensionNameArray[i], extension_name) == 0)
+ return i;
+
+ /* If there is no array yet, create one. */
+ if (PlannerExtensionNameArray == NULL)
+ {
+ PlannerExtensionNamesAllocated = 16;
+ PlannerExtensionNameArray = (const char **)
+ MemoryContextAlloc(TopMemoryContext,
+ PlannerExtensionNamesAllocated
+ * sizeof(char *));
+ }
+
+ /* If there's an array but it's currently full, expand it. */
+ if (PlannerExtensionNamesAssigned >= PlannerExtensionNamesAllocated)
+ {
+ int i = pg_nextpower2_32(PlannerExtensionNamesAssigned + 1);
+
+ PlannerExtensionNameArray = (const char **)
+ repalloc(PlannerExtensionNameArray, i * sizeof(char *));
+ PlannerExtensionNamesAllocated = i;
+ }
+
+ /* Assign and return new ID. */
+ PlannerExtensionNameArray[PlannerExtensionNamesAssigned] = extension_name;
+ return PlannerExtensionNamesAssigned++;
+}
+
+/*
+ * Store extension-specific state into a PlannerGlobal.
+ */
+void
+SetPlannerGlobalExtensionState(PlannerGlobal *glob, int extension_id,
+ void *opaque)
+{
+ Assert(extension_id >= 0);
+
+ /* If there is no array yet, create one. */
+ if (glob->extension_state == NULL)
+ {
+ MemoryContext planner_cxt;
+ Size sz;
+
+ planner_cxt = GetMemoryChunkContext(glob);
+ glob->extension_state_allocated =
+ Max(4, pg_nextpower2_32(extension_id + 1));
+ sz = glob->extension_state_allocated * sizeof(void *);
+ glob->extension_state = MemoryContextAllocZero(planner_cxt, sz);
+ }
+
+ /* If there's an array but it's currently full, expand it. */
+ if (extension_id >= glob->extension_state_allocated)
+ {
+ int i;
+
+ i = pg_nextpower2_32(extension_id + 1);
+ glob->extension_state = (void **)
+ repalloc0(glob->extension_state,
+ glob->extension_state_allocated * sizeof(void *),
+ i * sizeof(void *));
+ glob->extension_state_allocated = i;
+ }
+
+ glob->extension_state[extension_id] = opaque;
+}
+
+/*
+ * Store extension-specific state into a PlannerInfo.
+ */
+void
+SetPlannerInfoExtensionState(PlannerInfo *root, int extension_id,
+ void *opaque)
+{
+ Assert(extension_id >= 0);
+
+ /* If there is no array yet, create one. */
+ if (root->extension_state == NULL)
+ {
+ Size sz;
+
+ root->extension_state_allocated =
+ Max(4, pg_nextpower2_32(extension_id + 1));
+ sz = root->extension_state_allocated * sizeof(void *);
+ root->extension_state = MemoryContextAllocZero(root->planner_cxt, sz);
+ }
+
+ /* If there's an array but it's currently full, expand it. */
+ if (extension_id >= root->extension_state_allocated)
+ {
+ int i;
+
+ i = pg_nextpower2_32(extension_id + 1);
+ root->extension_state = (void **)
+ repalloc0(root->extension_state,
+ root->extension_state_allocated * sizeof(void *),
+ i * sizeof(void *));
+ root->extension_state_allocated = i;
+ }
+
+ root->extension_state[extension_id] = opaque;
+}
+
+/*
+ * Store extension-specific state into a RelOptInfo.
+ */
+void
+SetRelOptInfoExtensionState(RelOptInfo *rel, int extension_id,
+ void *opaque)
+{
+ Assert(extension_id >= 0);
+
+ /* If there is no array yet, create one. */
+ if (rel->extension_state == NULL)
+ {
+ MemoryContext planner_cxt;
+ Size sz;
+
+ planner_cxt = GetMemoryChunkContext(rel);
+ rel->extension_state_allocated =
+ Max(4, pg_nextpower2_32(extension_id + 1));
+ sz = rel->extension_state_allocated * sizeof(void *);
+ rel->extension_state = MemoryContextAllocZero(planner_cxt, sz);
+ }
+
+ /* If there's an array but it's currently full, expand it. */
+ if (extension_id >= rel->extension_state_allocated)
+ {
+ int i;
+
+ i = pg_nextpower2_32(extension_id + 1);
+ rel->extension_state = (void **)
+ repalloc0(rel->extension_state,
+ rel->extension_state_allocated * sizeof(void *),
+ i * sizeof(void *));
+ rel->extension_state_allocated = i;
+ }
+
+ rel->extension_state[extension_id] = opaque;
+}
diff --git a/src/backend/optimizer/util/meson.build b/src/backend/optimizer/util/meson.build
index b3bf913d096..f71f56e37a1 100644
--- a/src/backend/optimizer/util/meson.build
+++ b/src/backend/optimizer/util/meson.build
@@ -3,6 +3,7 @@
backend_sources += files(
'appendinfo.c',
'clauses.c',
+ 'extendplan.c',
'inherit.c',
'joininfo.c',
'orclauses.c',
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 21663af6979..050eef97a4c 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8751,8 +8751,16 @@ get_parameter(Param *param, deparse_context *context)
subplan = find_param_generator(param, context, &column);
if (subplan)
{
- appendStringInfo(context->buf, "(%s%s).col%d",
+ const char *nameprefix;
+
+ if (subplan->isInitPlan)
+ nameprefix = "InitPlan ";
+ else
+ nameprefix = "SubPlan ";
+
+ appendStringInfo(context->buf, "(%s%s%s).col%d",
subplan->useHashTable ? "hashed " : "",
+ nameprefix,
subplan->plan_name, column + 1);
return;
@@ -9589,11 +9597,19 @@ get_rule_expr(Node *node, deparse_context *context,
}
else
{
+ const char *nameprefix;
+
/* No referencing Params, so show the SubPlan's name */
+ if (subplan->isInitPlan)
+ nameprefix = "InitPlan ";
+ else
+ nameprefix = "SubPlan ";
if (subplan->useHashTable)
- appendStringInfo(buf, "hashed %s)", subplan->plan_name);
+ appendStringInfo(buf, "hashed %s%s)",
+ nameprefix, subplan->plan_name);
else
- appendStringInfo(buf, "%s)", subplan->plan_name);
+ appendStringInfo(buf, "%s%s)",
+ nameprefix, subplan->plan_name);
}
}
break;
@@ -9613,11 +9629,18 @@ get_rule_expr(Node *node, deparse_context *context,
foreach(lc, asplan->subplans)
{
SubPlan *splan = lfirst_node(SubPlan, lc);
+ const char *nameprefix;
+ if (splan->isInitPlan)
+ nameprefix = "InitPlan ";
+ else
+ nameprefix = "SubPlan ";
if (splan->useHashTable)
- appendStringInfo(buf, "hashed %s", splan->plan_name);
+ appendStringInfo(buf, "hashed %s%s", nameprefix,
+ splan->plan_name);
else
- appendStringInfoString(buf, splan->plan_name);
+ appendStringInfo(buf, "%s%s", nameprefix,
+ splan->plan_name);
if (lnext(asplan->subplans, lc))
appendStringInfoString(buf, " or ");
}