diff options
Diffstat (limited to 'src/backend/optimizer')
-rw-r--r-- | src/backend/optimizer/geqo/geqo_eval.c | 2 | ||||
-rw-r--r-- | src/backend/optimizer/geqo/geqo_main.c | 12 | ||||
-rw-r--r-- | src/backend/optimizer/geqo/geqo_random.c | 7 | ||||
-rw-r--r-- | src/backend/optimizer/path/allpaths.c | 6 | ||||
-rw-r--r-- | src/backend/optimizer/plan/analyzejoins.c | 9 | ||||
-rw-r--r-- | src/backend/optimizer/plan/planagg.c | 3 | ||||
-rw-r--r-- | src/backend/optimizer/plan/planner.c | 79 | ||||
-rw-r--r-- | src/backend/optimizer/plan/setrefs.c | 27 | ||||
-rw-r--r-- | src/backend/optimizer/plan/subselect.c | 87 | ||||
-rw-r--r-- | src/backend/optimizer/prep/prepjointree.c | 2 | ||||
-rw-r--r-- | src/backend/optimizer/prep/prepunion.c | 69 | ||||
-rw-r--r-- | src/backend/optimizer/util/Makefile | 1 | ||||
-rw-r--r-- | src/backend/optimizer/util/extendplan.c | 183 | ||||
-rw-r--r-- | src/backend/optimizer/util/meson.build | 1 |
14 files changed, 435 insertions, 53 deletions
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, ¶mIds); 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', |