From 02599b89bf3514be3b3ede69e4203379e4e2d1fb Mon Sep 17 00:00:00 2001 From: "Andrei V. Lepikhov" Date: Wed, 13 Aug 2025 12:24:20 +0200 Subject: [PATCH v0] Introduce a create plan hook. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit      The optimisation process involves multiple parameters and estimations. Sometimes it is not apparent why one strategy is chosen over another. Postgres continuously extends the number of parameters, as reflected in the EXPLAIN output. The last time this was extended was when the Memoize node representation was updated by commit 4bc62b86849. Sometimes a parameter has a default value for EXPLAIN, but its usage may be too narrow. Extensibility, added to Postgres 18, allows changing the summary part as well as each node representation. A slight adjustment is required to enable extensions to transfer information from the planning stage and display it in an explanation covered by an option. With this patch, a hook is added that lets extensions do something during the transformation of a path node to the final plan node. The Plan structure is extended by an extlist pointer, which is designated to store the extension's data in the form of an ExtensibleNode. The example on exposing predicted ngroups in an IncrementalSort node is added to the pg_overexplain extension. --- .../expected/pg_overexplain.out | 20 ++ contrib/pg_overexplain/pg_overexplain.c | 211 ++++++++++++++++++ contrib/pg_overexplain/sql/pg_overexplain.sql | 13 ++ src/backend/optimizer/plan/createplan.c | 95 ++++---- src/include/nodes/plannodes.h | 6 + src/include/optimizer/planmain.h | 5 + src/tools/pgindent/typedefs.list | 1 + 7 files changed, 306 insertions(+), 45 deletions(-) diff --git a/contrib/pg_overexplain/expected/pg_overexplain.out b/contrib/pg_overexplain/expected/pg_overexplain.out index 6de02323d7c..be2ed2aad52 100644 --- a/contrib/pg_overexplain/expected/pg_overexplain.out +++ b/contrib/pg_overexplain/expected/pg_overexplain.out @@ -487,3 +487,23 @@ INSERT INTO vegetables (name, genus) VALUES ('broccoflower', 'brassica'); Result RTIs: 1 (14 rows) +-- A test case for the number of groups used in cost estimation of +-- an incremental sort node +CREATE TABLE incremental_groups (x integer, y integer); +INSERT INTO incremental_groups (x,y) + SELECT gs,gs FROM generate_series(1,1000) AS gs; +VACUUM ANALYZE incremental_groups; +CREATE INDEX ON incremental_groups (x); +EXPLAIN (COSTS OFF, PLAN_DETAILS) +SELECT * FROM incremental_groups ORDER BY x, y LIMIT 10; + QUERY PLAN +----------------------------------------------------------------------------- + Limit + -> Incremental Sort + Sort Key: x, y + Presorted Key: x + Estimated Groups: 1000 + -> Index Scan using incremental_groups_x_idx on incremental_groups +(6 rows) + +DROP TABLE incremental_groups; diff --git a/contrib/pg_overexplain/pg_overexplain.c b/contrib/pg_overexplain/pg_overexplain.c index de824566f8c..8010474fdd0 100644 --- a/contrib/pg_overexplain/pg_overexplain.c +++ b/contrib/pg_overexplain/pg_overexplain.c @@ -16,10 +16,15 @@ #include "commands/explain_format.h" #include "commands/explain_state.h" #include "fmgr.h" +#include "nodes/extensible.h" +#include "nodes/readfuncs.h" +#include "optimizer/optimizer.h" +#include "optimizer/planmain.h" #include "parser/parsetree.h" #include "storage/lock.h" #include "utils/builtins.h" #include "utils/lsyscache.h" +#include "utils/selfuncs.h" PG_MODULE_MAGIC_EXT( .name = "pg_overexplain", @@ -30,6 +35,7 @@ typedef struct { bool debug; bool range_table; + bool plan_details; } overexplain_options; static overexplain_options *overexplain_ensure_options(ExplainState *es); @@ -37,6 +43,10 @@ static void overexplain_debug_handler(ExplainState *es, DefElem *opt, ParseState *pstate); static void overexplain_range_table_handler(ExplainState *es, DefElem *opt, ParseState *pstate); +static void overexplain_plan_details_handler(ExplainState *es, DefElem *opt, + ParseState *pstate); +static void overexplain_copy_path_info_hook(PlannerInfo *root, + Plan *dest, Path *src); static void overexplain_per_node_hook(PlanState *planstate, List *ancestors, const char *relationship, const char *plan_name, @@ -50,6 +60,7 @@ static void overexplain_per_plan_hook(PlannedStmt *plannedstmt, static void overexplain_debug(PlannedStmt *plannedstmt, ExplainState *es); static void overexplain_range_table(PlannedStmt *plannedstmt, ExplainState *es); +static void overexplain_node_plan_details(Plan *plan, ExplainState *es); static void overexplain_alias(const char *qlabel, Alias *alias, ExplainState *es); static void overexplain_bitmapset(const char *qlabel, Bitmapset *bms, @@ -58,9 +69,90 @@ static void overexplain_intlist(const char *qlabel, List *list, ExplainState *es); static int es_extension_id; +static copy_path_info_hook_type prev_copy_path_info_hook; static explain_per_node_hook_type prev_explain_per_node_hook; static explain_per_plan_hook_type prev_explain_per_plan_hook; +/* + * The extension's ExtensibleNode stuff to store planner data into the Plan node + * XXX: there are a lot of stuff that must be copied each time we want to + * introduce an extensible node. Maybe just export all that macroses? + */ +typedef struct OverExplainNode +{ + ExtensibleNode node; + + double input_groups; +} OverExplainNode; + + +#define strtobool(x) ((*(x) == 't') ? true : false) + +#define nullable_string(token,length) \ + ((length) == 0 ? NULL : debackslash(token, length)) + +#define booltostr(x) ((x) ? "true" : "false") + + +static void +OEnodeCopy(struct ExtensibleNode *enew, const struct ExtensibleNode *eold) +{ + OverExplainNode *new = (OverExplainNode *) enew; + OverExplainNode *old = (OverExplainNode *) eold; + + new->input_groups = old->input_groups; + + enew = (ExtensibleNode *) new; +} + +static bool +OEnodeEqual(const struct ExtensibleNode *a, const struct ExtensibleNode *b) +{ + OverExplainNode *a1 = (OverExplainNode *) a; + OverExplainNode *b1 = (OverExplainNode *) b; + + return (a1->input_groups == b1->input_groups); +} + + +/* Write a float field --- caller must give format to define precision */ +#define WRITE_FLOAT_FIELD(fldname,format) \ + appendStringInfo(str, " :" CppAsString(fldname) " " format, node->fldname) + +static void +OEnodeOut(struct StringInfoData *str, const struct ExtensibleNode *enode) +{ + OverExplainNode *node = (OverExplainNode *) enode; + + WRITE_FLOAT_FIELD(input_groups, "%.0f"); +} + +/* Read a float field */ +#define READ_FLOAT_FIELD(fldname) \ + token = pg_strtok(&length); /* skip :fldname */ \ + token = pg_strtok(&length); /* get field value */ \ + node->fldname = atof(token) + +static void +OEnodeRead(struct ExtensibleNode *enode) +{ + OverExplainNode *node = (OverExplainNode *) enode; + const char *token; + int length; + + READ_FLOAT_FIELD(input_groups); +} + +static const ExtensibleNodeMethods method = +{ + .extnodename = "pg_overexplain", + .node_size = sizeof(OverExplainNode), + .nodeCopy = OEnodeCopy, + .nodeEqual = OEnodeEqual, + .nodeOut = OEnodeOut, + .nodeRead = OEnodeRead +}; + /* * Initialization we do when this module is loaded. */ @@ -74,12 +166,19 @@ _PG_init(void) RegisterExtensionExplainOption("debug", overexplain_debug_handler); RegisterExtensionExplainOption("range_table", overexplain_range_table_handler); + RegisterExtensionExplainOption("plan_details", + overexplain_plan_details_handler); + + prev_copy_path_info_hook = copy_path_info_hook; + copy_path_info_hook = overexplain_copy_path_info_hook; /* Use the per-node and per-plan hooks to make our options do something. */ prev_explain_per_node_hook = explain_per_node_hook; explain_per_node_hook = overexplain_per_node_hook; prev_explain_per_plan_hook = explain_per_plan_hook; explain_per_plan_hook = overexplain_per_plan_hook; + + RegisterExtensibleNodeMethods(&method); } /* @@ -125,6 +224,18 @@ overexplain_range_table_handler(ExplainState *es, DefElem *opt, options->range_table = defGetBoolean(opt); } +/* + * Parse handler for EXPLAIN (PLAN_DETAILS). + */ +static void +overexplain_plan_details_handler(ExplainState *es, DefElem *opt, + ParseState *pstate) +{ + overexplain_options *options = overexplain_ensure_options(es); + + options->plan_details = defGetBoolean(opt); +} + /* * Print out additional per-node information as appropriate. If the user didn't * specify any of the options we support, do nothing; else, print whatever is @@ -240,6 +351,13 @@ overexplain_per_node_hook(PlanState *planstate, List *ancestors, break; } } + + /* + * If the "plan_details" option was specified, display information about + * the node planning decisions. + */ + if (options->plan_details) + overexplain_node_plan_details(plan, es); } /* @@ -671,6 +789,99 @@ overexplain_range_table(PlannedStmt *plannedstmt, ExplainState *es) ExplainCloseGroup("Range Table", "Range Table", false, es); } +/* + * Provide detailed information about planning decision has been made by the + * optimiser + */ +static void +overexplain_node_plan_details(Plan *plan, ExplainState *es) +{ + if (!IsA(plan, IncrementalSort) || plan->extlist == NIL) + return; + + /* + * Pass through the extension list. By convention, each element must be + * an extensible node that enables the owner's identification. + */ + foreach_node(ExtensibleNode, enode, plan->extlist) + { + OverExplainNode *data; + + if (strcmp(enode->extnodename, "pg_overexplain") != 0) + continue; + + /* Add the information to the explain */ + data = (OverExplainNode *) enode; + ExplainPropertyFloat("Estimated Groups", NULL, data->input_groups, 0, es); + } + + return; +} + +/* + * Gather/calculate necessary optimisation information about the path and + * store it into the Plan node. + * + * At the moment here is an adopted copy of the optimiser code that allows + * the extension to calculate real numbers used during optimisation phase. + */ +static void +overexplain_copy_path_info_hook(PlannerInfo *root, Plan *dest, Path *src) +{ + IncrementalSortPath *sort_path; + double input_tuples; + double input_groups; + ListCell *lc; + List *presortedExprs = NIL; + bool unknown_varno = false; + OverExplainNode *data = (OverExplainNode *) newNode(sizeof(OverExplainNode), + T_ExtensibleNode); + + if (!IsA(src, IncrementalSortPath)) + return; + + sort_path = (IncrementalSortPath *) src; + Assert(sort_path->spath.subpath->pathkeys != NIL); + + input_tuples = sort_path->spath.subpath->rows; + + if (input_tuples < 2.0) + input_tuples = 2.0; + input_groups = Min(input_tuples, DEFAULT_NUM_DISTINCT); + + foreach(lc, sort_path->spath.subpath->pathkeys) + { + PathKey *key = (PathKey *) lfirst(lc); + EquivalenceMember *member = (EquivalenceMember *) + linitial(key->pk_eclass->ec_members); + + /* + * Check if the expression contains Var with "varno 0" so that we + * don't call estimate_num_groups in that case. + */ + if (bms_is_member(0, pull_varnos(root, (Node *) member->em_expr))) + { + unknown_varno = true; + break; + } + + /* expression not containing any Vars with "varno 0" */ + presortedExprs = lappend(presortedExprs, member->em_expr); + + if (foreach_current_index(lc) + 1 >= sort_path->nPresortedCols) + break; + } + + /* Estimate the number of groups with equal presorted keys. */ + if (!unknown_varno) + input_groups = estimate_num_groups(root, presortedExprs, input_tuples, + NULL, NULL); + + data->node.extnodename = "pg_overexplain"; + data->input_groups = input_groups; + dest->extlist = lappend(dest->extlist, data); +} + /* * Emit a text property describing the contents of an Alias. * diff --git a/contrib/pg_overexplain/sql/pg_overexplain.sql b/contrib/pg_overexplain/sql/pg_overexplain.sql index 42e275ac2f9..ec7e344f42a 100644 --- a/contrib/pg_overexplain/sql/pg_overexplain.sql +++ b/contrib/pg_overexplain/sql/pg_overexplain.sql @@ -110,3 +110,16 @@ SELECT * FROM vegetables WHERE genus = 'daucus'; -- Also test a case that involves a write. EXPLAIN (RANGE_TABLE, COSTS OFF) INSERT INTO vegetables (name, genus) VALUES ('broccoflower', 'brassica'); + +-- A test case for the number of groups used in cost estimation of +-- an incremental sort node +CREATE TABLE incremental_groups (x integer, y integer); +INSERT INTO incremental_groups (x,y) + SELECT gs,gs FROM generate_series(1,1000) AS gs; +VACUUM ANALYZE incremental_groups; +CREATE INDEX ON incremental_groups (x); + +EXPLAIN (COSTS OFF, PLAN_DETAILS) +SELECT * FROM incremental_groups ORDER BY x, y LIMIT 10; + +DROP TABLE incremental_groups; diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 9fd5c31edf2..c2f7b1ecf31 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -72,6 +72,7 @@ #define CP_LABEL_TLIST 0x0004 /* tlist must contain sortgrouprefs */ #define CP_IGNORE_TLIST 0x0008 /* caller will replace tlist */ +copy_path_info_hook_type copy_path_info_hook = NULL; static Plan *create_plan_recurse(PlannerInfo *root, Path *best_path, int flags); @@ -175,7 +176,7 @@ static Node *fix_indexqual_clause(PlannerInfo *root, static Node *fix_indexqual_operand(Node *node, IndexOptInfo *index, int indexcol); static List *get_switched_clauses(List *clauses, Relids outerrelids); static List *order_qual_clauses(PlannerInfo *root, List *clauses); -static void copy_generic_path_info(Plan *dest, Path *src); +static void copy_generic_path_info(PlannerInfo *root, Plan *dest, Path *src); static void copy_plan_costsize(Plan *dest, Plan *src); static void label_sort_with_costsize(PlannerInfo *root, Sort *plan, double limit_tuples); @@ -1253,7 +1254,7 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path, int flags) false)), NULL); - copy_generic_path_info(plan, (Path *) best_path); + copy_generic_path_info(root, plan, (Path *) best_path); return plan; } @@ -1437,7 +1438,7 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path, int flags) plan->nasyncplans = nasyncplans; plan->first_partial_plan = best_path->first_partial_path; - copy_generic_path_info(&plan->plan, (Path *) best_path); + copy_generic_path_info(root, &plan->plan, (Path *) best_path); /* * If prepare_sort_from_pathkeys added sort columns, but we were told to @@ -1481,7 +1482,7 @@ create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path, * prepare_sort_from_pathkeys on it before we do so on the individual * child plans, to make cross-checking the sort info easier. */ - copy_generic_path_info(plan, (Path *) best_path); + copy_generic_path_info(root, plan, (Path *) best_path); plan->targetlist = tlist; plan->qual = NIL; plan->lefttree = NULL; @@ -1651,7 +1652,7 @@ create_group_result_plan(PlannerInfo *root, GroupResultPath *best_path) plan = make_result(tlist, (Node *) quals, NULL); - copy_generic_path_info(&plan->plan, (Path *) best_path); + copy_generic_path_info(root, &plan->plan, (Path *) best_path); return plan; } @@ -1676,7 +1677,7 @@ create_project_set_plan(PlannerInfo *root, ProjectSetPath *best_path) plan = make_project_set(tlist, subplan); - copy_generic_path_info(&plan->plan, (Path *) best_path); + copy_generic_path_info(root, &plan->plan, (Path *) best_path); return plan; } @@ -1704,7 +1705,7 @@ create_material_plan(PlannerInfo *root, MaterialPath *best_path, int flags) plan = make_material(subplan); - copy_generic_path_info(&plan->plan, (Path *) best_path); + copy_generic_path_info(root, &plan->plan, (Path *) best_path); return plan; } @@ -1759,7 +1760,7 @@ create_memoize_plan(PlannerInfo *root, MemoizePath *best_path, int flags) best_path->est_entries, keyparamids, best_path->est_calls, best_path->est_unique_keys, best_path->est_hit_ratio); - copy_generic_path_info(&plan->plan, (Path *) best_path); + copy_generic_path_info(root, &plan->plan, (Path *) best_path); return plan; } @@ -1960,7 +1961,7 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path, int flags) } /* Copy cost data from Path to Plan */ - copy_generic_path_info(plan, &best_path->path); + copy_generic_path_info(root, plan, &best_path->path); return plan; } @@ -1995,7 +1996,7 @@ create_gather_plan(PlannerInfo *root, GatherPath *best_path) best_path->single_copy, subplan); - copy_generic_path_info(&gather_plan->plan, &best_path->path); + copy_generic_path_info(root, &gather_plan->plan, &best_path->path); /* use parallel mode for parallel plans. */ root->glob->parallelModeNeeded = true; @@ -2024,7 +2025,7 @@ create_gather_merge_plan(PlannerInfo *root, GatherMergePath *best_path) gm_plan = makeNode(GatherMerge); gm_plan->plan.targetlist = tlist; gm_plan->num_workers = best_path->num_workers; - copy_generic_path_info(&gm_plan->plan, &best_path->path); + copy_generic_path_info(root, &gm_plan->plan, &best_path->path); /* Assign the rescan Param. */ gm_plan->rescan_param = assign_special_exec_param(root); @@ -2150,7 +2151,7 @@ create_projection_plan(PlannerInfo *root, ProjectionPath *best_path, int flags) /* We need a Result node */ plan = (Plan *) make_result(tlist, NULL, subplan); - copy_generic_path_info(plan, (Path *) best_path); + copy_generic_path_info(root, plan, (Path *) best_path); } return plan; @@ -2251,7 +2252,7 @@ create_sort_plan(PlannerInfo *root, SortPath *best_path, int flags) IS_OTHER_REL(best_path->subpath->parent) ? best_path->path.parent->relids : NULL); - copy_generic_path_info(&plan->plan, (Path *) best_path); + copy_generic_path_info(root, &plan->plan, (Path *) best_path); return plan; } @@ -2277,7 +2278,7 @@ create_incrementalsort_plan(PlannerInfo *root, IncrementalSortPath *best_path, best_path->spath.path.parent->relids : NULL, best_path->nPresortedCols); - copy_generic_path_info(&plan->sort.plan, (Path *) best_path); + copy_generic_path_info(root, &plan->sort.plan, (Path *) best_path); return plan; } @@ -2316,7 +2317,7 @@ create_group_plan(PlannerInfo *root, GroupPath *best_path) subplan->targetlist), subplan); - copy_generic_path_info(&plan->plan, (Path *) best_path); + copy_generic_path_info(root, &plan->plan, (Path *) best_path); return plan; } @@ -2344,7 +2345,7 @@ create_upper_unique_plan(PlannerInfo *root, UpperUniquePath *best_path, int flag best_path->path.pathkeys, best_path->numkeys); - copy_generic_path_info(&plan->plan, (Path *) best_path); + copy_generic_path_info(root, &plan->plan, (Path *) best_path); return plan; } @@ -2388,7 +2389,7 @@ create_agg_plan(PlannerInfo *root, AggPath *best_path) best_path->transitionSpace, subplan); - copy_generic_path_info(&plan->plan, (Path *) best_path); + copy_generic_path_info(root, &plan->plan, (Path *) best_path); return plan; } @@ -2585,7 +2586,7 @@ create_groupingsets_plan(PlannerInfo *root, GroupingSetsPath *best_path) subplan); /* Copy cost data from Path to Plan */ - copy_generic_path_info(&plan->plan, &best_path->path); + copy_generic_path_info(root, &plan->plan, &best_path->path); } return (Plan *) plan; @@ -2644,7 +2645,7 @@ create_minmaxagg_plan(PlannerInfo *root, MinMaxAggPath *best_path) plan = make_result(tlist, (Node *) best_path->quals, NULL); - copy_generic_path_info(&plan->plan, (Path *) best_path); + copy_generic_path_info(root, &plan->plan, (Path *) best_path); /* * During setrefs.c, we'll need to replace references to the Agg nodes @@ -2748,7 +2749,7 @@ create_windowagg_plan(PlannerInfo *root, WindowAggPath *best_path) best_path->topwindow, subplan); - copy_generic_path_info(&plan->plan, (Path *) best_path); + copy_generic_path_info(root, &plan->plan, (Path *) best_path); return plan; } @@ -2788,7 +2789,7 @@ create_setop_plan(PlannerInfo *root, SetOpPath *best_path, int flags) best_path->groupList, numGroups); - copy_generic_path_info(&plan->plan, (Path *) best_path); + copy_generic_path_info(root, &plan->plan, (Path *) best_path); return plan; } @@ -2824,7 +2825,7 @@ create_recursiveunion_plan(PlannerInfo *root, RecursiveUnionPath *best_path) best_path->distinctList, numGroups); - copy_generic_path_info(&plan->plan, (Path *) best_path); + copy_generic_path_info(root, &plan->plan, (Path *) best_path); return plan; } @@ -2847,7 +2848,7 @@ create_lockrows_plan(PlannerInfo *root, LockRowsPath *best_path, plan = make_lockrows(subplan, best_path->rowMarks, best_path->epqParam); - copy_generic_path_info(&plan->plan, (Path *) best_path); + copy_generic_path_info(root, &plan->plan, (Path *) best_path); return plan; } @@ -2888,7 +2889,7 @@ create_modifytable_plan(PlannerInfo *root, ModifyTablePath *best_path) best_path->mergeJoinConditions, best_path->epqParam); - copy_generic_path_info(&plan->plan, &best_path->path); + copy_generic_path_info(root, &plan->plan, &best_path->path); return plan; } @@ -2942,7 +2943,7 @@ create_limit_plan(PlannerInfo *root, LimitPath *best_path, int flags) best_path->limitOption, numUniqkeys, uniqColIdx, uniqOperators, uniqCollations); - copy_generic_path_info(&plan->plan, (Path *) best_path); + copy_generic_path_info(root, &plan->plan, (Path *) best_path); return plan; } @@ -2988,7 +2989,7 @@ create_seqscan_plan(PlannerInfo *root, Path *best_path, scan_clauses, scan_relid); - copy_generic_path_info(&scan_plan->scan.plan, best_path); + copy_generic_path_info(root, &scan_plan->scan.plan, best_path); return scan_plan; } @@ -3034,7 +3035,7 @@ create_samplescan_plan(PlannerInfo *root, Path *best_path, scan_relid, tsc); - copy_generic_path_info(&scan_plan->scan.plan, best_path); + copy_generic_path_info(root, &scan_plan->scan.plan, best_path); return scan_plan; } @@ -3235,7 +3236,7 @@ create_indexscan_plan(PlannerInfo *root, indexorderbyops, best_path->indexscandir); - copy_generic_path_info(&scan_plan->plan, &best_path->path); + copy_generic_path_info(root, &scan_plan->plan, &best_path->path); return scan_plan; } @@ -3350,7 +3351,7 @@ create_bitmap_scan_plan(PlannerInfo *root, bitmapqualorig, baserelid); - copy_generic_path_info(&scan_plan->scan.plan, &best_path->path); + copy_generic_path_info(root, &scan_plan->scan.plan, &best_path->path); return scan_plan; } @@ -3670,7 +3671,7 @@ create_tidscan_plan(PlannerInfo *root, TidPath *best_path, scan_relid, tidquals); - copy_generic_path_info(&scan_plan->scan.plan, &best_path->path); + copy_generic_path_info(root, &scan_plan->scan.plan, &best_path->path); return scan_plan; } @@ -3735,7 +3736,7 @@ create_tidrangescan_plan(PlannerInfo *root, TidRangePath *best_path, scan_relid, tidrangequals); - copy_generic_path_info(&scan_plan->scan.plan, &best_path->path); + copy_generic_path_info(root, &scan_plan->scan.plan, &best_path->path); return scan_plan; } @@ -3794,7 +3795,7 @@ create_subqueryscan_plan(PlannerInfo *root, SubqueryScanPath *best_path, scan_relid, subplan); - copy_generic_path_info(&scan_plan->scan.plan, &best_path->path); + copy_generic_path_info(root, &scan_plan->scan.plan, &best_path->path); return scan_plan; } @@ -3837,7 +3838,7 @@ create_functionscan_plan(PlannerInfo *root, Path *best_path, scan_plan = make_functionscan(tlist, scan_clauses, scan_relid, functions, rte->funcordinality); - copy_generic_path_info(&scan_plan->scan.plan, best_path); + copy_generic_path_info(root, &scan_plan->scan.plan, best_path); return scan_plan; } @@ -3880,7 +3881,7 @@ create_tablefuncscan_plan(PlannerInfo *root, Path *best_path, scan_plan = make_tablefuncscan(tlist, scan_clauses, scan_relid, tablefunc); - copy_generic_path_info(&scan_plan->scan.plan, best_path); + copy_generic_path_info(root, &scan_plan->scan.plan, best_path); return scan_plan; } @@ -3924,7 +3925,7 @@ create_valuesscan_plan(PlannerInfo *root, Path *best_path, scan_plan = make_valuesscan(tlist, scan_clauses, scan_relid, values_lists); - copy_generic_path_info(&scan_plan->scan.plan, best_path); + copy_generic_path_info(root, &scan_plan->scan.plan, best_path); return scan_plan; } @@ -4018,7 +4019,7 @@ create_ctescan_plan(PlannerInfo *root, Path *best_path, scan_plan = make_ctescan(tlist, scan_clauses, scan_relid, plan_id, cte_param_id); - copy_generic_path_info(&scan_plan->scan.plan, best_path); + copy_generic_path_info(root, &scan_plan->scan.plan, best_path); return scan_plan; } @@ -4057,7 +4058,7 @@ create_namedtuplestorescan_plan(PlannerInfo *root, Path *best_path, scan_plan = make_namedtuplestorescan(tlist, scan_clauses, scan_relid, rte->enrname); - copy_generic_path_info(&scan_plan->scan.plan, best_path); + copy_generic_path_info(root, &scan_plan->scan.plan, best_path); return scan_plan; } @@ -4095,7 +4096,7 @@ create_resultscan_plan(PlannerInfo *root, Path *best_path, scan_plan = make_result(tlist, (Node *) scan_clauses, NULL); - copy_generic_path_info(&scan_plan->plan, best_path); + copy_generic_path_info(root, &scan_plan->plan, best_path); return scan_plan; } @@ -4155,7 +4156,7 @@ create_worktablescan_plan(PlannerInfo *root, Path *best_path, scan_plan = make_worktablescan(tlist, scan_clauses, scan_relid, cteroot->wt_param_id); - copy_generic_path_info(&scan_plan->scan.plan, best_path); + copy_generic_path_info(root, &scan_plan->scan.plan, best_path); return scan_plan; } @@ -4215,7 +4216,7 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path, outer_plan); /* Copy cost data from Path to Plan; no need to make FDW do this */ - copy_generic_path_info(&scan_plan->scan.plan, &best_path->path); + copy_generic_path_info(root, &scan_plan->scan.plan, &best_path->path); /* Copy user OID to access as; likewise no need to make FDW do this */ scan_plan->checkAsUser = rel->userid; @@ -4360,7 +4361,7 @@ create_customscan_plan(PlannerInfo *root, CustomPath *best_path, * Copy cost data from Path to Plan; no need to make custom-plan providers * do this */ - copy_generic_path_info(&cplan->scan.plan, &best_path->path); + copy_generic_path_info(root, &cplan->scan.plan, &best_path->path); /* Likewise, copy the relids that are represented by this custom scan */ cplan->custom_relids = best_path->path.parent->relids; @@ -4538,7 +4539,7 @@ create_nestloop_plan(PlannerInfo *root, best_path->jpath.jointype, best_path->jpath.inner_unique); - copy_generic_path_info(&join_plan->join.plan, &best_path->jpath.path); + copy_generic_path_info(root, &join_plan->join.plan, &best_path->jpath.path); return join_plan; } @@ -4892,7 +4893,7 @@ create_mergejoin_plan(PlannerInfo *root, best_path->skip_mark_restore); /* Costs of sort and material steps are included in path cost already */ - copy_generic_path_info(&join_plan->join.plan, &best_path->jpath.path); + copy_generic_path_info(root, &join_plan->join.plan, &best_path->jpath.path); return join_plan; } @@ -5065,7 +5066,7 @@ create_hashjoin_plan(PlannerInfo *root, best_path->jpath.jointype, best_path->jpath.inner_unique); - copy_generic_path_info(&join_plan->join.plan, &best_path->jpath.path); + copy_generic_path_info(root, &join_plan->join.plan, &best_path->jpath.path); return join_plan; } @@ -5559,7 +5560,7 @@ order_qual_clauses(PlannerInfo *root, List *clauses) * Also copy the parallel-related flags, which the executor *will* use. */ static void -copy_generic_path_info(Plan *dest, Path *src) +copy_generic_path_info(PlannerInfo *root, Plan *dest, Path *src) { dest->disabled_nodes = src->disabled_nodes; dest->startup_cost = src->startup_cost; @@ -5568,6 +5569,10 @@ copy_generic_path_info(Plan *dest, Path *src) dest->plan_width = src->pathtarget->width; dest->parallel_aware = src->parallel_aware; dest->parallel_safe = src->parallel_safe; + + /* Let an extension to do an additional job before finalizing the plan node */ + if (copy_path_info_hook) + (copy_path_info_hook)(root, dest, src); } /* diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h index 29d7732d6a0..843a4ced763 100644 --- a/src/include/nodes/plannodes.h +++ b/src/include/nodes/plannodes.h @@ -239,6 +239,12 @@ typedef struct Plan */ Bitmapset *extParam; Bitmapset *allParam; + + /* + * Is intended to store all additional information needed to an extension + * during or after execution phase. + */ + List *extlist; } Plan; /* ---------------- diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h index 9d3debcab28..24143fc5514 100644 --- a/src/include/optimizer/planmain.h +++ b/src/include/optimizer/planmain.h @@ -25,6 +25,11 @@ extern PGDLLIMPORT bool enable_self_join_elimination; /* query_planner callback to compute query_pathkeys */ typedef void (*query_pathkeys_callback) (PlannerInfo *root, void *extra); +/* Hook for plugins to do something converting final path to the plan node */ +typedef void (*copy_path_info_hook_type) (PlannerInfo *root, + Plan *dest, Path *src); +extern PGDLLIMPORT copy_path_info_hook_type copy_path_info_hook; + /* * prototypes for plan/planmain.c */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index e6f2e93b2d6..31da2aaaac0 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3525,6 +3525,7 @@ contain_placeholder_references_context convert_testexpr_context copy_data_dest_cb copy_data_source_cb +copy_path_info_hook_type core_YYSTYPE core_yy_extra_type core_yyscan_t -- 2.50.1