From c83ac02ec7309edb7561eee93895c31a54b93d3d Mon Sep 17 00:00:00 2001 From: Robert Haas Date: Wed, 8 Oct 2025 08:33:29 -0400 Subject: [PATCH] Add ExplainState argument to pg_plan_query() and planner(). This allows extensions to have access to any data they've stored in the ExplainState during planning. Unfortunately, it won't help with EXPLAIN EXECUTE is used, but since that case is less common, this still seems like an improvement. Since planner() has quite a few arguments now, also add some documentation of those arguments and the return value. Author: Robert Haas Co-authored-by: Tom Lane Reviewed-by: Andrei Lepikhov Reviewed-by: Tom Lane Discussion: http://postgr.es/m/CA+TgmoYWKHU2hKr62Toyzh-kTDEnMDeLw7gkOOnjL-TnOUq0kQ@mail.gmail.com --- .../pg_stat_statements/pg_stat_statements.c | 14 +++++----- src/backend/commands/copyto.c | 2 +- src/backend/commands/createas.c | 2 +- src/backend/commands/explain.c | 2 +- src/backend/commands/matview.c | 2 +- src/backend/commands/portalcmds.c | 3 ++- src/backend/optimizer/plan/planner.c | 27 ++++++++++++++++--- src/backend/tcop/postgres.c | 7 ++--- src/include/optimizer/optimizer.h | 5 +++- src/include/optimizer/planner.h | 8 ++++-- src/include/tcop/tcopprot.h | 4 ++- .../modules/delay_execution/delay_execution.c | 7 ++--- 12 files changed, 58 insertions(+), 25 deletions(-) diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c index db1af36a705..f2187167c5c 100644 --- a/contrib/pg_stat_statements/pg_stat_statements.c +++ b/contrib/pg_stat_statements/pg_stat_statements.c @@ -338,7 +338,8 @@ static void pgss_post_parse_analyze(ParseState *pstate, Query *query, static PlannedStmt *pgss_planner(Query *parse, const char *query_string, int cursorOptions, - ParamListInfo boundParams); + ParamListInfo boundParams, + ExplainState *es); static void pgss_ExecutorStart(QueryDesc *queryDesc, int eflags); static void pgss_ExecutorRun(QueryDesc *queryDesc, ScanDirection direction, @@ -894,7 +895,8 @@ static PlannedStmt * pgss_planner(Query *parse, const char *query_string, int cursorOptions, - ParamListInfo boundParams) + ParamListInfo boundParams, + ExplainState *es) { PlannedStmt *result; @@ -929,10 +931,10 @@ pgss_planner(Query *parse, { if (prev_planner_hook) result = prev_planner_hook(parse, query_string, cursorOptions, - boundParams); + boundParams, es); else result = standard_planner(parse, query_string, cursorOptions, - boundParams); + boundParams, es); } PG_FINALLY(); { @@ -978,10 +980,10 @@ pgss_planner(Query *parse, { if (prev_planner_hook) result = prev_planner_hook(parse, query_string, cursorOptions, - boundParams); + boundParams, es); else result = standard_planner(parse, query_string, cursorOptions, - boundParams); + boundParams, es); } PG_FINALLY(); { diff --git a/src/backend/commands/copyto.c b/src/backend/commands/copyto.c index 67b94b91cae..e5781155cdf 100644 --- a/src/backend/commands/copyto.c +++ b/src/backend/commands/copyto.c @@ -796,7 +796,7 @@ BeginCopyTo(ParseState *pstate, /* plan the query */ plan = pg_plan_query(query, pstate->p_sourcetext, - CURSOR_OPT_PARALLEL_OK, NULL); + CURSOR_OPT_PARALLEL_OK, NULL, NULL); /* * With row-level security and a user using "COPY relation TO", we diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c index dfd2ab8e862..1ccc2e55c64 100644 --- a/src/backend/commands/createas.c +++ b/src/backend/commands/createas.c @@ -321,7 +321,7 @@ ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *stmt, /* plan the query */ plan = pg_plan_query(query, pstate->p_sourcetext, - CURSOR_OPT_PARALLEL_OK, params); + CURSOR_OPT_PARALLEL_OK, params, NULL); /* * Use a snapshot with an updated command ID to ensure this query sees diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index 06191cd8a85..e6edae0845c 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -351,7 +351,7 @@ standard_ExplainOneQuery(Query *query, int cursorOptions, INSTR_TIME_SET_CURRENT(planstart); /* plan the query */ - plan = pg_plan_query(query, queryString, cursorOptions, params); + plan = pg_plan_query(query, queryString, cursorOptions, params, es); INSTR_TIME_SET_CURRENT(planduration); INSTR_TIME_SUBTRACT(planduration, planstart); diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c index 188e26f0e6e..441de55ac24 100644 --- a/src/backend/commands/matview.c +++ b/src/backend/commands/matview.c @@ -426,7 +426,7 @@ refresh_matview_datafill(DestReceiver *dest, Query *query, CHECK_FOR_INTERRUPTS(); /* Plan the query which will generate data for the refresh. */ - plan = pg_plan_query(query, queryString, CURSOR_OPT_PARALLEL_OK, NULL); + plan = pg_plan_query(query, queryString, CURSOR_OPT_PARALLEL_OK, NULL, NULL); /* * Use a snapshot with an updated command ID to ensure this query sees diff --git a/src/backend/commands/portalcmds.c b/src/backend/commands/portalcmds.c index e7c8171c102..ec96c2efcd3 100644 --- a/src/backend/commands/portalcmds.c +++ b/src/backend/commands/portalcmds.c @@ -99,7 +99,8 @@ PerformCursorOpen(ParseState *pstate, DeclareCursorStmt *cstmt, ParamListInfo pa elog(ERROR, "non-SELECT statement in DECLARE CURSOR"); /* Plan the query, applying the specified options */ - plan = pg_plan_query(query, pstate->p_sourcetext, cstmt->options, params); + plan = pg_plan_query(query, pstate->p_sourcetext, cstmt->options, params, + NULL); /* * Create a portal and copy the plan and query string into its memory. diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 6f06174cdab..c250433a2ef 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -279,6 +279,23 @@ static void create_partial_unique_paths(PlannerInfo *root, RelOptInfo *input_rel * * Query optimizer entry point * + * Inputs: + * parse: an analyzed-and-rewritten query tree for an optimizable statement + * query_string: source text for the query tree (used for error reports) + * cursorOptions: bitmask of CURSOR_OPT_XXX flags, see parsenodes.h + * boundParams: passed-in parameter values, or NULL if none + * es: ExplainState if being called from EXPLAIN, else NULL + * + * The result is a PlannedStmt tree. + * + * PARAM_EXTERN Param nodes within the parse tree can be replaced by Consts + * using values from boundParams, if those values are marked PARAM_FLAG_CONST. + * Parameter values not so marked are still relied on for estimation purposes. + * + * The ExplainState pointer is not currently used by the core planner, but it + * is passed through to some planner hooks so that they can report information + * back to EXPLAIN extension hooks. + * * To support loadable plugins that monitor or modify planner behavior, * we provide a hook variable that lets a plugin get control before and * after the standard planning process. The plugin would normally call @@ -290,14 +307,16 @@ static void create_partial_unique_paths(PlannerInfo *root, RelOptInfo *input_rel *****************************************************************************/ PlannedStmt * planner(Query *parse, const char *query_string, int cursorOptions, - ParamListInfo boundParams) + ParamListInfo boundParams, ExplainState *es) { PlannedStmt *result; if (planner_hook) - result = (*planner_hook) (parse, query_string, cursorOptions, boundParams); + result = (*planner_hook) (parse, query_string, cursorOptions, + boundParams, es); else - result = standard_planner(parse, query_string, cursorOptions, boundParams); + result = standard_planner(parse, query_string, cursorOptions, + boundParams, es); pgstat_report_plan_id(result->planId, false); @@ -306,7 +325,7 @@ planner(Query *parse, const char *query_string, int cursorOptions, PlannedStmt * standard_planner(Query *parse, const char *query_string, int cursorOptions, - ParamListInfo boundParams) + ParamListInfo boundParams, ExplainState *es) { PlannedStmt *result; PlannerGlobal *glob; diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index d356830f756..7dd75a490aa 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -37,6 +37,7 @@ #include "catalog/pg_type.h" #include "commands/async.h" #include "commands/event_trigger.h" +#include "commands/explain_state.h" #include "commands/prepare.h" #include "common/pg_prng.h" #include "jit/jit.h" @@ -884,7 +885,7 @@ pg_rewrite_query(Query *query) */ PlannedStmt * pg_plan_query(Query *querytree, const char *query_string, int cursorOptions, - ParamListInfo boundParams) + ParamListInfo boundParams, ExplainState *es) { PlannedStmt *plan; @@ -901,7 +902,7 @@ pg_plan_query(Query *querytree, const char *query_string, int cursorOptions, ResetUsage(); /* call the optimizer */ - plan = planner(querytree, query_string, cursorOptions, boundParams); + plan = planner(querytree, query_string, cursorOptions, boundParams, es); if (log_planner_stats) ShowUsage("PLANNER STATISTICS"); @@ -997,7 +998,7 @@ pg_plan_queries(List *querytrees, const char *query_string, int cursorOptions, else { stmt = pg_plan_query(query, query_string, cursorOptions, - boundParams); + boundParams, NULL); } stmt_list = lappend(stmt_list, stmt); diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h index 04878f1f1c2..a34113903c0 100644 --- a/src/include/optimizer/optimizer.h +++ b/src/include/optimizer/optimizer.h @@ -24,6 +24,8 @@ #include "nodes/parsenodes.h" +typedef struct ExplainState ExplainState; /* defined in explain_state.h */ + /* * We don't want to include nodes/pathnodes.h here, because non-planner * code should generally treat PlannerInfo as an opaque typedef. @@ -104,7 +106,8 @@ extern PGDLLIMPORT bool enable_distinct_reordering; extern PlannedStmt *planner(Query *parse, const char *query_string, int cursorOptions, - ParamListInfo boundParams); + ParamListInfo boundParams, + ExplainState *es); extern Expr *expression_planner(Expr *expr); extern Expr *expression_planner_with_deps(Expr *expr, diff --git a/src/include/optimizer/planner.h b/src/include/optimizer/planner.h index 1bbef0018d5..c7ab466f5f1 100644 --- a/src/include/optimizer/planner.h +++ b/src/include/optimizer/planner.h @@ -22,11 +22,14 @@ #include "nodes/plannodes.h" +typedef struct ExplainState ExplainState; /* defined in explain_state.h */ + /* Hook for plugins to get control in planner() */ typedef PlannedStmt *(*planner_hook_type) (Query *parse, const char *query_string, int cursorOptions, - ParamListInfo boundParams); + ParamListInfo boundParams, + ExplainState *es); extern PGDLLIMPORT planner_hook_type planner_hook; /* Hook for plugins to get control when grouping_planner() plans upper rels */ @@ -40,7 +43,8 @@ extern PGDLLIMPORT create_upper_paths_hook_type create_upper_paths_hook; extern PlannedStmt *standard_planner(Query *parse, const char *query_string, int cursorOptions, - ParamListInfo boundParams); + ParamListInfo boundParams, + ExplainState *es); extern PlannerInfo *subquery_planner(PlannerGlobal *glob, Query *parse, char *plan_name, diff --git a/src/include/tcop/tcopprot.h b/src/include/tcop/tcopprot.h index a83cc4f4850..c1bcfdec673 100644 --- a/src/include/tcop/tcopprot.h +++ b/src/include/tcop/tcopprot.h @@ -20,6 +20,7 @@ #include "utils/guc.h" #include "utils/queryenvironment.h" +typedef struct ExplainState ExplainState; /* defined in explain_state.h */ extern PGDLLIMPORT CommandDest whereToSendOutput; extern PGDLLIMPORT const char *debug_query_string; @@ -63,7 +64,8 @@ extern List *pg_analyze_and_rewrite_withcb(RawStmt *parsetree, QueryEnvironment *queryEnv); extern PlannedStmt *pg_plan_query(Query *querytree, const char *query_string, int cursorOptions, - ParamListInfo boundParams); + ParamListInfo boundParams, + ExplainState *es); extern List *pg_plan_queries(List *querytrees, const char *query_string, int cursorOptions, ParamListInfo boundParams); diff --git a/src/test/modules/delay_execution/delay_execution.c b/src/test/modules/delay_execution/delay_execution.c index 7bc97f84a1c..d933e9a6e53 100644 --- a/src/test/modules/delay_execution/delay_execution.c +++ b/src/test/modules/delay_execution/delay_execution.c @@ -40,17 +40,18 @@ static planner_hook_type prev_planner_hook = NULL; /* planner_hook function to provide the desired delay */ static PlannedStmt * delay_execution_planner(Query *parse, const char *query_string, - int cursorOptions, ParamListInfo boundParams) + int cursorOptions, ParamListInfo boundParams, + ExplainState *es) { PlannedStmt *result; /* Invoke the planner, possibly via a previous hook user */ if (prev_planner_hook) result = prev_planner_hook(parse, query_string, cursorOptions, - boundParams); + boundParams, es); else result = standard_planner(parse, query_string, cursorOptions, - boundParams); + boundParams, es); /* If enabled, delay by taking and releasing the specified lock */ if (post_planning_lock_id != 0) -- 2.47.3