mirror of
https://github.com/hanzoai/postgres.git
synced 2026-10-30 18:33:07 +00:00
Similar to3d18f4e46andb747c150e, here we adjust a few new locations which were not using the most suitable appendStringInfo* or appendPQExpBuffer* function for the intended purpose. Author: David Rowley <[email protected]> Discussion: https://postgr.es/m/CAApHDvohYOdrvhVxXzCJNX_GYMSWBfjTTtB6hgDauEtZ8Nar2A@mail.gmail.com
458 lines
12 KiB
C
458 lines
12 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* pg_plan_advice.c
|
|
* main entrypoints for generating and applying planner advice
|
|
*
|
|
* Copyright (c) 2016-2026, PostgreSQL Global Development Group
|
|
*
|
|
* contrib/pg_plan_advice/pg_plan_advice.c
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
#include "postgres.h"
|
|
|
|
#include "pg_plan_advice.h"
|
|
#include "pgpa_ast.h"
|
|
#include "pgpa_identifier.h"
|
|
#include "pgpa_output.h"
|
|
#include "pgpa_planner.h"
|
|
#include "pgpa_trove.h"
|
|
#include "pgpa_walker.h"
|
|
|
|
#include "commands/defrem.h"
|
|
#include "commands/explain.h"
|
|
#include "commands/explain_format.h"
|
|
#include "commands/explain_state.h"
|
|
#include "funcapi.h"
|
|
#include "optimizer/planner.h"
|
|
#include "storage/dsm_registry.h"
|
|
#include "utils/guc.h"
|
|
|
|
PG_MODULE_MAGIC;
|
|
|
|
/* GUC variables */
|
|
char *pg_plan_advice_advice = NULL;
|
|
bool pg_plan_advice_always_store_advice_details = false;
|
|
static bool pg_plan_advice_always_explain_supplied_advice = true;
|
|
bool pg_plan_advice_feedback_warnings = false;
|
|
bool pg_plan_advice_trace_mask = false;
|
|
|
|
/* Saved hook value */
|
|
static explain_per_plan_hook_type prev_explain_per_plan = NULL;
|
|
|
|
/* Other file-level globals */
|
|
static int es_extension_id;
|
|
static MemoryContext pgpa_memory_context = NULL;
|
|
static List *advisor_hook_list = NIL;
|
|
|
|
static void pg_plan_advice_explain_option_handler(ExplainState *es,
|
|
DefElem *opt,
|
|
ParseState *pstate);
|
|
static void pg_plan_advice_explain_per_plan_hook(PlannedStmt *plannedstmt,
|
|
IntoClause *into,
|
|
ExplainState *es,
|
|
const char *queryString,
|
|
ParamListInfo params,
|
|
QueryEnvironment *queryEnv);
|
|
static bool pg_plan_advice_advice_check_hook(char **newval, void **extra,
|
|
GucSource source);
|
|
static DefElem *find_defelem_by_defname(List *deflist, char *defname);
|
|
|
|
/*
|
|
* Initialize this module.
|
|
*/
|
|
void
|
|
_PG_init(void)
|
|
{
|
|
DefineCustomStringVariable("pg_plan_advice.advice",
|
|
"advice to apply during query planning",
|
|
NULL,
|
|
&pg_plan_advice_advice,
|
|
NULL,
|
|
PGC_USERSET,
|
|
0,
|
|
pg_plan_advice_advice_check_hook,
|
|
NULL,
|
|
NULL);
|
|
|
|
DefineCustomBoolVariable("pg_plan_advice.always_explain_supplied_advice",
|
|
"EXPLAIN output includes supplied advice even without EXPLAIN (PLAN_ADVICE)",
|
|
NULL,
|
|
&pg_plan_advice_always_explain_supplied_advice,
|
|
true,
|
|
PGC_USERSET,
|
|
0,
|
|
NULL,
|
|
NULL,
|
|
NULL);
|
|
|
|
DefineCustomBoolVariable("pg_plan_advice.always_store_advice_details",
|
|
"Generate advice strings even when seemingly not required",
|
|
"Use this option to see generated advice for prepared queries.",
|
|
&pg_plan_advice_always_store_advice_details,
|
|
false,
|
|
PGC_USERSET,
|
|
0,
|
|
NULL,
|
|
NULL,
|
|
NULL);
|
|
|
|
DefineCustomBoolVariable("pg_plan_advice.feedback_warnings",
|
|
"Warn when supplied advice does not apply cleanly",
|
|
NULL,
|
|
&pg_plan_advice_feedback_warnings,
|
|
false,
|
|
PGC_USERSET,
|
|
0,
|
|
NULL,
|
|
NULL,
|
|
NULL);
|
|
|
|
DefineCustomBoolVariable("pg_plan_advice.trace_mask",
|
|
"Emit debugging messages showing the computed strategy mask for each relation",
|
|
NULL,
|
|
&pg_plan_advice_trace_mask,
|
|
false,
|
|
PGC_USERSET,
|
|
0,
|
|
NULL,
|
|
NULL,
|
|
NULL);
|
|
|
|
MarkGUCPrefixReserved("pg_plan_advice");
|
|
|
|
/* Get an ID that we can use to cache data in an ExplainState. */
|
|
es_extension_id = GetExplainExtensionId("pg_plan_advice");
|
|
|
|
/* Register the new EXPLAIN options implemented by this module. */
|
|
RegisterExtensionExplainOption("plan_advice",
|
|
pg_plan_advice_explain_option_handler,
|
|
GUCCheckBooleanExplainOption);
|
|
|
|
/* Install hooks */
|
|
pgpa_planner_install_hooks();
|
|
prev_explain_per_plan = explain_per_plan_hook;
|
|
explain_per_plan_hook = pg_plan_advice_explain_per_plan_hook;
|
|
}
|
|
|
|
/*
|
|
* Return a pointer to a memory context where long-lived data managed by this
|
|
* module can be stored.
|
|
*/
|
|
MemoryContext
|
|
pg_plan_advice_get_mcxt(void)
|
|
{
|
|
if (pgpa_memory_context == NULL)
|
|
pgpa_memory_context = AllocSetContextCreate(TopMemoryContext,
|
|
"pg_plan_advice",
|
|
ALLOCSET_DEFAULT_SIZES);
|
|
|
|
return pgpa_memory_context;
|
|
}
|
|
|
|
/*
|
|
* Was the PLAN_ADVICE option specified and not set to false?
|
|
*/
|
|
bool
|
|
pg_plan_advice_should_explain(ExplainState *es)
|
|
{
|
|
bool *plan_advice = NULL;
|
|
|
|
if (es != NULL)
|
|
plan_advice = GetExplainExtensionState(es, es_extension_id);
|
|
return plan_advice != NULL && *plan_advice;
|
|
}
|
|
|
|
/*
|
|
* Get the advice that should be used while planning a particular query.
|
|
*/
|
|
char *
|
|
pg_plan_advice_get_supplied_query_advice(PlannerGlobal *glob,
|
|
Query *parse,
|
|
const char *query_string,
|
|
int cursorOptions,
|
|
ExplainState *es)
|
|
{
|
|
ListCell *lc;
|
|
|
|
/*
|
|
* If any advisors are loaded, consult them. The first one that produces a
|
|
* non-NULL string wins.
|
|
*/
|
|
foreach(lc, advisor_hook_list)
|
|
{
|
|
pg_plan_advice_advisor_hook hook = lfirst(lc);
|
|
char *advice_string;
|
|
|
|
advice_string = (*hook) (glob, parse, query_string, cursorOptions, es);
|
|
if (advice_string != NULL)
|
|
return advice_string;
|
|
}
|
|
|
|
/* Otherwise, just use the value of the GUC. */
|
|
return pg_plan_advice_advice;
|
|
}
|
|
|
|
/*
|
|
* Add an advisor, which can supply advice strings to be used during future
|
|
* query planning operations.
|
|
*
|
|
* The advisor should return NULL if it has no advice string to offer for a
|
|
* given query. If multiple advisors are added, they will be consulted in the
|
|
* order added until one of them returns a non-NULL value.
|
|
*/
|
|
void
|
|
pg_plan_advice_add_advisor(pg_plan_advice_advisor_hook hook)
|
|
{
|
|
MemoryContext oldcontext;
|
|
|
|
oldcontext = MemoryContextSwitchTo(pg_plan_advice_get_mcxt());
|
|
advisor_hook_list = lappend(advisor_hook_list, hook);
|
|
MemoryContextSwitchTo(oldcontext);
|
|
}
|
|
|
|
/*
|
|
* Remove an advisor.
|
|
*/
|
|
void
|
|
pg_plan_advice_remove_advisor(pg_plan_advice_advisor_hook hook)
|
|
{
|
|
MemoryContext oldcontext;
|
|
|
|
oldcontext = MemoryContextSwitchTo(pg_plan_advice_get_mcxt());
|
|
advisor_hook_list = list_delete_ptr(advisor_hook_list, hook);
|
|
MemoryContextSwitchTo(oldcontext);
|
|
}
|
|
|
|
/*
|
|
* Other loadable modules can use this function to trigger advice generation.
|
|
*
|
|
* Calling this function with activate = true requests that any queries
|
|
* planned afterwards should generate plan advice, which will be stored in the
|
|
* PlannedStmt. Calling this function with activate = false revokes that
|
|
* request. Multiple loadable modules could be using this simultaneously, so
|
|
* make sure to only revoke your own requests.
|
|
*
|
|
* Note that you can't use this function to *suppress* advice generation,
|
|
* which can occur for other reasons, such as the use of EXPLAIN (PLAN_ADVICE),
|
|
* regardless. It's a way of turning advice generation on, not a way of turning
|
|
* it off.
|
|
*/
|
|
void
|
|
pg_plan_advice_request_advice_generation(bool activate)
|
|
{
|
|
if (activate)
|
|
pgpa_planner_generate_advice++;
|
|
else
|
|
{
|
|
Assert(pgpa_planner_generate_advice > 0);
|
|
pgpa_planner_generate_advice--;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Handler for EXPLAIN (PLAN_ADVICE).
|
|
*/
|
|
static void
|
|
pg_plan_advice_explain_option_handler(ExplainState *es, DefElem *opt,
|
|
ParseState *pstate)
|
|
{
|
|
bool *plan_advice;
|
|
|
|
plan_advice = GetExplainExtensionState(es, es_extension_id);
|
|
|
|
if (plan_advice == NULL)
|
|
{
|
|
plan_advice = palloc0_object(bool);
|
|
SetExplainExtensionState(es, es_extension_id, plan_advice);
|
|
}
|
|
|
|
*plan_advice = defGetBoolean(opt);
|
|
}
|
|
|
|
/*
|
|
* Display a string that is likely to consist of multiple lines in EXPLAIN
|
|
* output.
|
|
*/
|
|
static void
|
|
pg_plan_advice_explain_text_multiline(ExplainState *es, char *qlabel,
|
|
char *value)
|
|
{
|
|
char *s;
|
|
|
|
/* For non-text formats, it's best not to add any special handling. */
|
|
if (es->format != EXPLAIN_FORMAT_TEXT)
|
|
{
|
|
ExplainPropertyText(qlabel, value, es);
|
|
return;
|
|
}
|
|
|
|
/* In text format, if there is no data, display nothing. */
|
|
if (*value == '\0')
|
|
return;
|
|
|
|
/*
|
|
* It looks nicest to indent each line of the advice separately, beginning
|
|
* on the line below the label.
|
|
*/
|
|
ExplainIndentText(es);
|
|
appendStringInfo(es->str, "%s:\n", qlabel);
|
|
es->indent++;
|
|
while ((s = strchr(value, '\n')) != NULL)
|
|
{
|
|
ExplainIndentText(es);
|
|
appendBinaryStringInfo(es->str, value, (s - value) + 1);
|
|
value = s + 1;
|
|
}
|
|
|
|
/* Don't interpret a terminal newline as a request for an empty line. */
|
|
if (*value != '\0')
|
|
{
|
|
ExplainIndentText(es);
|
|
appendStringInfo(es->str, "%s\n", value);
|
|
}
|
|
|
|
es->indent--;
|
|
}
|
|
|
|
/*
|
|
* Add advice feedback to the EXPLAIN output.
|
|
*/
|
|
static void
|
|
pg_plan_advice_explain_feedback(ExplainState *es, List *feedback)
|
|
{
|
|
StringInfoData buf;
|
|
|
|
initStringInfo(&buf);
|
|
foreach_node(DefElem, item, feedback)
|
|
{
|
|
int flags = defGetInt32(item);
|
|
|
|
appendStringInfo(&buf, "%s /* ", item->defname);
|
|
pgpa_trove_append_flags(&buf, flags);
|
|
appendStringInfoString(&buf, " */\n");
|
|
}
|
|
|
|
pg_plan_advice_explain_text_multiline(es, "Supplied Plan Advice",
|
|
buf.data);
|
|
}
|
|
|
|
/*
|
|
* Add relevant details, if any, to the EXPLAIN output for a single plan.
|
|
*/
|
|
static void
|
|
pg_plan_advice_explain_per_plan_hook(PlannedStmt *plannedstmt,
|
|
IntoClause *into,
|
|
ExplainState *es,
|
|
const char *queryString,
|
|
ParamListInfo params,
|
|
QueryEnvironment *queryEnv)
|
|
{
|
|
bool should_explain;
|
|
DefElem *pgpa_item;
|
|
List *pgpa_list;
|
|
|
|
if (prev_explain_per_plan)
|
|
prev_explain_per_plan(plannedstmt, into, es, queryString, params,
|
|
queryEnv);
|
|
|
|
/* Should an advice string be part of the EXPLAIN output? */
|
|
should_explain = pg_plan_advice_should_explain(es);
|
|
|
|
/* Find any data pgpa_planner_shutdown stashed in the PlannedStmt. */
|
|
pgpa_item = find_defelem_by_defname(plannedstmt->extension_state,
|
|
"pg_plan_advice");
|
|
pgpa_list = pgpa_item == NULL ? NULL : (List *) pgpa_item->arg;
|
|
|
|
/*
|
|
* By default, if there is a record of attempting to apply advice during
|
|
* query planning, we always output that information, but the user can set
|
|
* pg_plan_advice.always_explain_supplied_advice = false to suppress that
|
|
* behavior. If they do, we'll only display it when the PLAN_ADVICE option
|
|
* was specified and not set to false.
|
|
*
|
|
* NB: If we're explaining a query planned beforehand -- i.e. a prepared
|
|
* statement -- the application of query advice may not have been
|
|
* recorded, and therefore this won't be able to show anything. Use
|
|
* pg_plan_advice.always_store_advice_details = true to work around this.
|
|
*/
|
|
if (pgpa_list != NULL && (pg_plan_advice_always_explain_supplied_advice ||
|
|
should_explain))
|
|
{
|
|
DefElem *feedback;
|
|
|
|
feedback = find_defelem_by_defname(pgpa_list, "feedback");
|
|
if (feedback != NULL)
|
|
pg_plan_advice_explain_feedback(es, (List *) feedback->arg);
|
|
}
|
|
|
|
/*
|
|
* If the PLAN_ADVICE option was specified -- and not set to FALSE -- show
|
|
* generated advice.
|
|
*/
|
|
if (should_explain)
|
|
{
|
|
DefElem *advice_string_item;
|
|
char *advice_string = NULL;
|
|
|
|
advice_string_item =
|
|
find_defelem_by_defname(pgpa_list, "advice_string");
|
|
if (advice_string_item != NULL)
|
|
{
|
|
advice_string = strVal(advice_string_item->arg);
|
|
pg_plan_advice_explain_text_multiline(es, "Generated Plan Advice",
|
|
advice_string);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Check hook for pg_plan_advice.advice
|
|
*/
|
|
static bool
|
|
pg_plan_advice_advice_check_hook(char **newval, void **extra, GucSource source)
|
|
{
|
|
MemoryContext oldcontext;
|
|
MemoryContext tmpcontext;
|
|
char *error;
|
|
|
|
if (*newval == NULL)
|
|
return true;
|
|
|
|
tmpcontext = AllocSetContextCreate(CurrentMemoryContext,
|
|
"pg_plan_advice.advice",
|
|
ALLOCSET_DEFAULT_SIZES);
|
|
oldcontext = MemoryContextSwitchTo(tmpcontext);
|
|
|
|
/*
|
|
* It would be nice to save the parse tree that we construct here for
|
|
* eventual use when planning with this advice, but *extra can only point
|
|
* to a single guc_malloc'd chunk, and our parse tree involves an
|
|
* arbitrary number of memory allocations.
|
|
*/
|
|
(void) pgpa_parse(*newval, &error);
|
|
|
|
if (error != NULL)
|
|
GUC_check_errdetail("Could not parse advice: %s", error);
|
|
|
|
MemoryContextSwitchTo(oldcontext);
|
|
MemoryContextDelete(tmpcontext);
|
|
|
|
return (error == NULL);
|
|
}
|
|
|
|
/*
|
|
* Search a list of DefElem objects for a given defname.
|
|
*/
|
|
static DefElem *
|
|
find_defelem_by_defname(List *deflist, char *defname)
|
|
{
|
|
foreach_node(DefElem, item, deflist)
|
|
{
|
|
if (strcmp(item->defname, defname) == 0)
|
|
return item;
|
|
}
|
|
|
|
return NULL;
|
|
}
|