X-Git-Url: http://git.osdn.net/view?a=blobdiff_plain;f=pg_hint_plan.c;h=c6cd055a337d78153bb7748d5be049c5d15374b8;hb=000c325874cc07eb944447d046a122dcc8642520;hp=3afa34c0951ab6a2bf8d48d00b2dcf7b522e2b0b;hpb=b6ebe309a92328110bc66786476b29879ee1272f;p=pghintplan%2Fpg_hint_plan.git diff --git a/pg_hint_plan.c b/pg_hint_plan.c index 3afa34c..c6cd055 100644 --- a/pg_hint_plan.c +++ b/pg_hint_plan.c @@ -3,13 +3,16 @@ * pg_hint_plan.c * hinting on how to execute a query for PostgreSQL * - * Copyright (c) 2012-2018, NIPPON TELEGRAPH AND TELEPHONE CORPORATION + * Copyright (c) 2012-2019, NIPPON TELEGRAPH AND TELEPHONE CORPORATION * *------------------------------------------------------------------------- */ #include #include "postgres.h" +#include "access/genam.h" +#include "access/heapam.h" +#include "access/relation.h" #include "catalog/pg_collation.h" #include "catalog/pg_index.h" #include "commands/prepare.h" @@ -17,11 +20,12 @@ #include "miscadmin.h" #include "nodes/nodeFuncs.h" #include "nodes/params.h" -#include "nodes/relation.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/geqo.h" #include "optimizer/joininfo.h" +#include "optimizer/optimizer.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/plancat.h" @@ -225,6 +229,14 @@ static unsigned int msgqno = 0; static char qnostr[32]; static const char *current_hint_str = NULL; +/* + * However we usually take a hint stirng in post_parse_analyze_hook, we still + * need to do so in planner_hook when client starts query execution from the + * bind message on a prepared query. This variable prevent duplicate and + * sometimes harmful hint string retrieval. + */ +static bool current_hint_retrieved = false; + /* common data for all hints. */ struct Hint { @@ -354,6 +366,7 @@ struct HintState int init_paratup_cost; /* parallel_tuple_cost */ int init_parasetup_cost;/* parallel_setup_cost */ + PlannerInfo *current_root; /* PlannerInfo for the followings */ Index parent_relid; /* inherit parent of table relid */ ScanMethodHint *parent_scan_hint; /* scan hint for the parent */ ParallelHint *parent_parallel_hint; /* parallel hint for the parent */ @@ -387,6 +400,11 @@ static void push_hint(HintState *hstate); static void pop_hint(void); static void pg_hint_plan_post_parse_analyze(ParseState *pstate, Query *query); +static void pg_hint_plan_ProcessUtility(PlannedStmt *pstmt, + const char *queryString, + ProcessUtilityContext context, + ParamListInfo params, QueryEnvironment *queryEnv, + DestReceiver *dest, char *completionTag); static PlannedStmt *pg_hint_plan_planner(Query *parse, int cursorOptions, ParamListInfo boundParams); static RelOptInfo *pg_hint_plan_join_search(PlannerInfo *root, @@ -498,6 +516,7 @@ static int pg_hint_plan_debug_message_level = LOG; static bool pg_hint_plan_enable_hint_table = false; static int plpgsql_recurse_level = 0; /* PLpgSQL recursion level */ +static int recurse_level = 0; /* recursion level incl. direct SPI calls */ static int hint_inhibit_level = 0; /* Inhibit hinting if this is above 0 */ /* (This could not be above 1) */ static int max_hint_nworkers = -1; /* Maximum nworkers of Workers hints */ @@ -542,6 +561,7 @@ static post_parse_analyze_hook_type prev_post_parse_analyze_hook = NULL; static planner_hook_type prev_planner = NULL; static join_search_hook_type prev_join_search = NULL; static set_rel_pathlist_hook_type prev_set_rel_pathlist = NULL; +static ProcessUtility_hook_type prev_ProcessUtility_hook = NULL; /* Hold reference to currently active hint */ static HintState *current_hint_state = NULL; @@ -553,12 +573,6 @@ static HintState *current_hint_state = NULL; */ static List *HintStateStack = NIL; -/* - * Holds statement name during executing EXECUTE command. NULL for other - * statements. - */ -static char *stmt_name = NULL; - static const HintParser parsers[] = { {HINT_SEQSCAN, ScanMethodHintCreate, HINT_KEYWORD_SEQSCAN}, {HINT_INDEXSCAN, ScanMethodHintCreate, HINT_KEYWORD_INDEXSCAN}, @@ -677,6 +691,8 @@ _PG_init(void) join_search_hook = pg_hint_plan_join_search; prev_set_rel_pathlist = set_rel_pathlist_hook; set_rel_pathlist_hook = pg_hint_plan_set_rel_pathlist; + prev_ProcessUtility_hook = ProcessUtility_hook; + ProcessUtility_hook = pg_hint_plan_ProcessUtility; /* setup PL/pgSQL plugin hook */ var_ptr = (PLpgSQL_plugin **) find_rendezvous_variable("PLpgSQL_plugin"); @@ -699,6 +715,7 @@ _PG_fini(void) planner_hook = prev_planner; join_search_hook = prev_join_search; set_rel_pathlist_hook = prev_set_rel_pathlist; + ProcessUtility_hook = prev_ProcessUtility_hook; /* uninstall PL/pgSQL plugin hook */ var_ptr = (PLpgSQL_plugin **) find_rendezvous_variable("PLpgSQL_plugin"); @@ -964,6 +981,7 @@ HintStateCreate(void) hstate->init_min_para_indexscan_size = 0; hstate->init_paratup_cost = 0; hstate->init_parasetup_cost = 0; + hstate->current_root = NULL; hstate->parent_relid = 0; hstate->parent_scan_hint = NULL; hstate->parent_parallel_hint = NULL; @@ -1799,17 +1817,15 @@ get_query_string(ParseState *pstate, Query *query, Query **jumblequery) /* * If debug_query_string is set, it is the top level statement. But in some * cases we reach here with debug_query_string set NULL for example in the - * case of DESCRIBE message handling. We may still see a candidate - * top-level query in pstate in the case. + * case of DESCRIBE message handling or EXECUTE command. We may still see a + * candidate top-level query in pstate in the case. */ - if (!p) - { - /* We don't see a query string, return NULL */ - if (!pstate->p_sourcetext) - return NULL; - + if (!p && pstate) p = pstate->p_sourcetext; - } + + /* We don't see a query string, return NULL */ + if (!p) + return NULL; if (jumblequery != NULL) *jumblequery = query; @@ -1883,8 +1899,12 @@ get_query_string(ParseState *pstate, Query *query, Query **jumblequery) if (jumblequery) *jumblequery = target_query; } - /* Return NULL if the pstate is not identical to the top-level query */ - else if (strcmp(pstate->p_sourcetext, p) != 0) + /* + * Return NULL if pstate is not of top-level query. We don't need this + * when jumble info is not requested or cannot do this when pstate is NULL. + */ + else if (!jumblequery && pstate && pstate->p_sourcetext != p && + strcmp(pstate->p_sourcetext, p) != 0) p = NULL; return p; @@ -2753,24 +2773,25 @@ pop_hint(void) } /* - * Retrieve and store a hint string from given query or from the hint table. - * If we are using the hint table, the query string is needed to be normalized. - * However, ParseState, which is not available in planner_hook, is required to - * check if the query tree (Query) is surely corresponding to the target query. + * Retrieve and store hint string from given query or from the hint table. */ static void -pg_hint_plan_post_parse_analyze(ParseState *pstate, Query *query) +get_current_hint_string(ParseState *pstate, Query *query) { const char *query_str; MemoryContext oldcontext; - if (prev_post_parse_analyze_hook) - prev_post_parse_analyze_hook(pstate, query); - /* do nothing under hint table search */ if (hint_inhibit_level > 0) return; + /* We alredy have one, don't parse it again. */ + if (current_hint_retrieved) + return; + + /* Don't parse the current query hereafter */ + current_hint_retrieved = true; + if (!pg_hint_plan_enable_hint) { if (current_hint_str) @@ -2811,8 +2832,8 @@ pg_hint_plan_post_parse_analyze(ParseState *pstate, Query *query) if (jumblequery) { /* - * XXX: normalizing code is copied from pg_stat_statements.c, so be - * careful to PostgreSQL's version up. + * XXX: normalization code is copied from pg_stat_statements.c. + * Make sure to keep up-to-date with it. */ jstate.jumble = (unsigned char *) palloc(JUMBLE_SIZE); jstate.jumble_len = 0; @@ -2889,8 +2910,8 @@ pg_hint_plan_post_parse_analyze(ParseState *pstate, Query *query) { /* * get hints from the comment. However we may have the same query - * string with the previous call, but just retrieving hints is expected - * to be faster than checking for identicalness before retrieval. + * string with the previous call, but the extra comparison seems no + * use.. */ if (current_hint_str) pfree((void *)current_hint_str); @@ -2902,8 +2923,8 @@ pg_hint_plan_post_parse_analyze(ParseState *pstate, Query *query) if (debug_level > 1) { - if (debug_level == 1 && - (stmt_name || strcmp(query_str, debug_query_string))) + if (debug_level == 1 && query_str && debug_query_string && + strcmp(query_str, debug_query_string)) ereport(pg_hint_plan_debug_message_level, (errmsg("hints in comment=\"%s\"", current_hint_str ? current_hint_str : "(none)"), @@ -2911,9 +2932,10 @@ pg_hint_plan_post_parse_analyze(ParseState *pstate, Query *query) errhidecontext(msgqno != qno))); else ereport(pg_hint_plan_debug_message_level, - (errmsg("hints in comment=\"%s\", stmt=\"%s\", query=\"%s\", debug_query_string=\"%s\"", + (errmsg("hints in comment=\"%s\", query=\"%s\", debug_query_string=\"%s\"", current_hint_str ? current_hint_str : "(none)", - stmt_name, query_str, debug_query_string), + query_str ? query_str : "(none)", + debug_query_string ? debug_query_string : "(none)"), errhidestmt(msgqno != qno), errhidecontext(msgqno != qno))); msgqno = qno; @@ -2921,6 +2943,44 @@ pg_hint_plan_post_parse_analyze(ParseState *pstate, Query *query) } /* + * Retrieve hint string from the current query. + */ +static void +pg_hint_plan_post_parse_analyze(ParseState *pstate, Query *query) +{ + if (prev_post_parse_analyze_hook) + prev_post_parse_analyze_hook(pstate, query); + + /* always retrieve hint from the top-level query string */ + if (plpgsql_recurse_level == 0) + current_hint_retrieved = false; + + get_current_hint_string(pstate, query); +} + +/* + * We need to reset current_hint_retrieved flag always when a command execution + * is finished. This is true even for a pure utility command that doesn't + * involve planning phase. + */ +static void +pg_hint_plan_ProcessUtility(PlannedStmt *pstmt, const char *queryString, + ProcessUtilityContext context, + ParamListInfo params, QueryEnvironment *queryEnv, + DestReceiver *dest, char *completionTag) +{ + if (prev_ProcessUtility_hook) + prev_ProcessUtility_hook(pstmt, queryString, context, params, queryEnv, + dest, completionTag); + else + standard_ProcessUtility(pstmt, queryString, context, params, queryEnv, + dest, completionTag); + + if (plpgsql_recurse_level == 0) + current_hint_retrieved = false; +} + +/* * Read and set up hint information */ static PlannedStmt * @@ -2929,6 +2989,7 @@ pg_hint_plan_planner(Query *parse, int cursorOptions, ParamListInfo boundParams) int save_nestlevel; PlannedStmt *result; HintState *hstate; + const char *prev_hint_str; /* * Use standard planner if pg_hint_plan is disabled or current nesting @@ -2957,15 +3018,20 @@ pg_hint_plan_planner(Query *parse, int cursorOptions, ParamListInfo boundParams) { MemoryContext oldcontext; - if (current_hint_str) - pfree((void *)current_hint_str); - oldcontext = MemoryContextSwitchTo(TopMemoryContext); current_hint_str = get_hints_from_comment((char *)error_context_stack->arg); MemoryContextSwitchTo(oldcontext); } + /* + * Query execution in extended protocol can be started without the analyze + * phase. In the case retrieve hint string here. + */ + if (!current_hint_str) + get_current_hint_string(NULL, parse); + + /* No hint, go the normal way */ if (!current_hint_str) goto standard_planner_proc; @@ -3021,10 +3087,21 @@ pg_hint_plan_planner(Query *parse, int cursorOptions, ParamListInfo boundParams) */ PG_TRY(); { + /* + * The planner call below may replace current_hint_str. Store and + * restore it so that the subsequent planning in the upper level + * doesn't get confused. + */ + recurse_level++; + prev_hint_str = current_hint_str; + if (prev_planner) result = (*prev_planner) (parse, cursorOptions, boundParams); else result = standard_planner(parse, cursorOptions, boundParams); + + current_hint_str = prev_hint_str; + recurse_level--; } PG_CATCH(); { @@ -3032,12 +3109,25 @@ pg_hint_plan_planner(Query *parse, int cursorOptions, ParamListInfo boundParams) * Rollback changes of GUC parameters, and pop current hint context * from hint stack to rewind the state. */ + current_hint_str = prev_hint_str; + recurse_level--; AtEOXact_GUC(true, save_nestlevel); pop_hint(); PG_RE_THROW(); } PG_END_TRY(); + + /* + * current_hint_str is useless after planning of the top-level query. + */ + if (recurse_level < 1 && current_hint_str) + { + pfree((void *)current_hint_str); + current_hint_str = NULL; + current_hint_retrieved = false; + } + /* Print hint in debug mode. */ if (debug_level == 1) HintStateDump(current_hint_state); @@ -3642,6 +3732,10 @@ setup_hint_enforcement(PlannerInfo *root, RelOptInfo *rel, return 0; } + /* Forget about the parent of another subquery */ + if (root != current_hint_state->current_root) + current_hint_state->parent_relid = 0; + /* Find the parent for this relation other than the registered parent */ foreach (l, root->append_rel_list) { @@ -3650,7 +3744,10 @@ setup_hint_enforcement(PlannerInfo *root, RelOptInfo *rel, if (appinfo->child_relid == rel->relid) { if (current_hint_state->parent_relid != appinfo->parent_relid) + { new_parent_relid = appinfo->parent_relid; + current_hint_state->current_root = root; + } break; } } @@ -4639,7 +4736,8 @@ pg_hint_plan_set_rel_pathlist(PlannerInfo * root, RelOptInfo *rel, } /* Generate gather paths */ - if (rel->reloptkind == RELOPT_BASEREL) + if (rel->reloptkind == RELOPT_BASEREL && + bms_membership(root->all_baserels) != BMS_SINGLETON) generate_gather_paths(root, rel, false); } }