X-Git-Url: http://git.osdn.net/view?a=blobdiff_plain;f=pg_hint_plan.c;h=346950073ad74f3e05a1d4ea63484b596864e4cb;hb=2a9e14e50905decc0d37506284fbfb5bf1e094ac;hp=bc64358072e3b4c8db9e5466d05eaa8b41b0889b;hpb=5268ad9f47ddba75d6821a880c6cc8e8228884b7;p=pghintplan%2Fpg_hint_plan.git diff --git a/pg_hint_plan.c b/pg_hint_plan.c index bc64358..3469500 100644 --- a/pg_hint_plan.c +++ b/pg_hint_plan.c @@ -1,10 +1,9 @@ /*------------------------------------------------------------------------- * * pg_hint_plan.c - * do instructions or hints to the planner using C-style block comments - * of the SQL. + * hinting on how to execute a query for PostgreSQL * - * Copyright (c) 2012-2016, NIPPON TELEGRAPH AND TELEPHONE CORPORATION + * Copyright (c) 2012-2017, NIPPON TELEGRAPH AND TELEPHONE CORPORATION * *------------------------------------------------------------------------- */ @@ -17,6 +16,8 @@ #include "mb/pg_wchar.h" #include "miscadmin.h" #include "nodes/nodeFuncs.h" +#include "nodes/params.h" +#include "nodes/relation.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/geqo.h" @@ -27,12 +28,15 @@ #include "optimizer/planner.h" #include "optimizer/prep.h" #include "optimizer/restrictinfo.h" +#include "parser/analyze.h" +#include "parser/parsetree.h" #include "parser/scansup.h" #include "tcop/utility.h" #include "utils/builtins.h" #include "utils/lsyscache.h" #include "utils/memutils.h" #include "utils/rel.h" +#include "utils/snapmgr.h" #include "utils/syscache.h" #include "utils/resowner.h" @@ -88,10 +92,12 @@ PG_MODULE_MAGIC; #define HINT_ARRAY_DEFAULT_INITSIZE 8 #define hint_ereport(str, detail) \ - ereport(pg_hint_plan_message_level, \ - (errhidestmt(hidestmt), \ - errmsg("pg_hint_plan%s: hint syntax error at or near \"%s\"", qnostr, (str)), \ - errdetail detail)) + do { \ + ereport(pg_hint_plan_message_level, \ + (errmsg("pg_hint_plan%s: hint syntax error at or near \"%s\"", qnostr, (str)), \ + errdetail detail)); \ + msgqno = qno; \ + } while(0) #define skip_space(str) \ while (isspace(*str)) \ @@ -185,6 +191,12 @@ typedef enum HintType HINT_TYPE_PARALLEL } HintType; +typedef enum HintTypeBitmap +{ + HINT_BM_SCAN_METHOD = 1, + HINT_BM_PARALLEL = 2 +} HintTypeBitmap; + static const char *HintTypeName[] = { "scan method", "join method", @@ -208,7 +220,9 @@ typedef enum HintStatus (hint)->base.state == HINT_STATE_USED) static unsigned int qno = 0; +static unsigned int msgqno = 0; static char qnostr[32]; +static const char *current_hint_str = NULL; /* common data for all hints. */ struct Hint @@ -332,12 +346,16 @@ struct HintState /* Initial values of parameters */ int init_scan_mask; /* enable_* mask */ int init_nworkers; /* max_parallel_workers_per_gather */ - int init_min_para_size; /* min_parallel_relation_size*/ + /* min_parallel_table_scan_size*/ + int init_min_para_tablescan_size; + /* min_parallel_index_scan_size*/ + int init_min_para_indexscan_size; int init_paratup_cost; /* parallel_tuple_cost */ int init_parasetup_cost;/* parallel_setup_cost */ Index parent_relid; /* inherit parent of table relid */ - ScanMethodHint *parent_hint; /* inherit parent of table scan hint */ + ScanMethodHint *parent_scan_hint; /* scan hint for the parent */ + ParallelHint *parent_parallel_hint; /* parallel hint for the parent */ List *parent_index_infos; /* list of parent table's index */ JoinMethodHint **join_hints; /* parsed join hints */ @@ -367,16 +385,9 @@ void _PG_fini(void); static void push_hint(HintState *hstate); static void pop_hint(void); -static void pg_hint_plan_ProcessUtility(Node *parsetree, - const char *queryString, - ProcessUtilityContext context, - ParamListInfo params, - DestReceiver *dest, char *completionTag); +static void pg_hint_plan_post_parse_analyze(ParseState *pstate, Query *query); static PlannedStmt *pg_hint_plan_planner(Query *parse, int cursorOptions, ParamListInfo boundParams); -static void pg_hint_plan_get_relation_info(PlannerInfo *root, - Oid relationObjectId, - bool inhparent, RelOptInfo *rel); static RelOptInfo *pg_hint_plan_join_search(PlannerInfo *root, int levels_needed, List *initial_rels); @@ -448,19 +459,22 @@ void pg_hint_plan_set_rel_pathlist(PlannerInfo * root, RelOptInfo *rel, Index rti, RangeTblEntry *rte); static void create_plain_partial_paths(PlannerInfo *root, RelOptInfo *rel); -static int compute_parallel_worker(RelOptInfo *rel, BlockNumber pages); - +static void add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel, + List *live_childrels); static void make_rels_by_clause_joins(PlannerInfo *root, RelOptInfo *old_rel, ListCell *other_rels); static void make_rels_by_clauseless_joins(PlannerInfo *root, RelOptInfo *old_rel, ListCell *other_rels); static bool has_join_restriction(PlannerInfo *root, RelOptInfo *rel); +static void set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, + RangeTblEntry *rte); static void set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte); static void generate_mergeappend_paths(PlannerInfo *root, RelOptInfo *rel, - List *live_childrels, - List *all_child_pathkeys); + List *live_childrels, + List *all_child_pathkeys, + List *partitioned_rels); static Path *get_cheapest_parameterized_child_path(PlannerInfo *root, RelOptInfo *rel, Relids required_outer); @@ -476,12 +490,12 @@ static void plpgsql_query_erase_callback(ResourceReleasePhase phase, bool isCommit, bool isTopLevel, void *arg); -static int set_config_option_wrapper(const char *name, const char *value, +static int set_config_option_noerror(const char *name, const char *value, GucContext context, GucSource source, GucAction action, bool changeVal, int elevel); -static void set_scan_config_options(unsigned char enforce_mask, - GucContext context); -static void set_config_int32_option(const char *name, int32 value, +static void setup_scan_method_enforcement(ScanMethodHint *scanhint, + HintState *state); +static int set_config_int32_option(const char *name, int32 value, GucContext context); /* GUC variables */ @@ -491,9 +505,6 @@ static int pg_hint_plan_message_level = INFO; /* Default is off, to keep backward compatibility. */ static bool pg_hint_plan_enable_hint_table = false; -/* Internal static variables. */ -static bool hidestmt = false; /* Allow or inhibit STATEMENT: output */ - static int plpgsql_recurse_level = 0; /* PLpgSQL recursion level */ static int hint_inhibit_level = 0; /* Inhibit hinting if this is above 0 */ /* (This could not be above 1) */ @@ -535,9 +546,8 @@ static const struct config_enum_entry parse_debug_level_options[] = { }; /* Saved hook values in case of unload */ -static ProcessUtility_hook_type prev_ProcessUtility = NULL; +static post_parse_analyze_hook_type prev_post_parse_analyze_hook = NULL; static planner_hook_type prev_planner = NULL; -static get_relation_info_hook_type prev_get_relation_info = NULL; static join_search_hook_type prev_join_search = NULL; static set_rel_pathlist_hook_type prev_set_rel_pathlist = NULL; @@ -667,12 +677,10 @@ _PG_init(void) NULL); /* Install hooks. */ - prev_ProcessUtility = ProcessUtility_hook; - ProcessUtility_hook = pg_hint_plan_ProcessUtility; + prev_post_parse_analyze_hook = post_parse_analyze_hook; + post_parse_analyze_hook = pg_hint_plan_post_parse_analyze; prev_planner = planner_hook; planner_hook = pg_hint_plan_planner; - prev_get_relation_info = get_relation_info_hook; - get_relation_info_hook = pg_hint_plan_get_relation_info; prev_join_search = join_search_hook; join_search_hook = pg_hint_plan_join_search; prev_set_rel_pathlist = set_rel_pathlist_hook; @@ -695,9 +703,8 @@ _PG_fini(void) PLpgSQL_plugin **var_ptr; /* Uninstall hooks. */ - ProcessUtility_hook = prev_ProcessUtility; + post_parse_analyze_hook = prev_post_parse_analyze_hook; planner_hook = prev_planner; - get_relation_info_hook = prev_get_relation_info; join_search_hook = prev_join_search; set_rel_pathlist_hook = prev_set_rel_pathlist; @@ -961,11 +968,13 @@ HintStateCreate(void) hstate->scan_hints = NULL; hstate->init_scan_mask = 0; hstate->init_nworkers = 0; - hstate->init_min_para_size = 0; + hstate->init_min_para_tablescan_size = 0; + hstate->init_min_para_indexscan_size = 0; hstate->init_paratup_cost = 0; hstate->init_parasetup_cost = 0; hstate->parent_relid = 0; - hstate->parent_hint = NULL; + hstate->parent_scan_hint = NULL; + hstate->parent_parallel_hint = NULL; hstate->parent_index_infos = NIL; hstate->join_hints = NULL; hstate->init_join_mask = 0; @@ -1264,8 +1273,9 @@ HintStateDump2(HintState *hstate) appendStringInfoChar(&buf, '}'); ereport(pg_hint_plan_message_level, - (errhidestmt(true), - errmsg("%s", buf.data))); + (errmsg("%s", buf.data), + errhidestmt(true), + errhidecontext(true))); pfree(buf.data); } @@ -1720,7 +1730,15 @@ get_hints_from_table(const char *client_query, const char *client_application) PG_TRY(); { + bool snapshot_set = false; + hint_inhibit_level++; + + if (!ActiveSnapshotSet()) + { + PushActiveSnapshot(GetTransactionSnapshot()); + snapshot_set = true; + } SPI_connect(); @@ -1757,7 +1775,10 @@ get_hints_from_table(const char *client_query, const char *client_application) } SPI_finish(); - + + if (snapshot_set) + PopActiveSnapshot(); + hint_inhibit_level--; } PG_CATCH(); @@ -1771,30 +1792,92 @@ get_hints_from_table(const char *client_query, const char *client_application) } /* - * Get client-supplied query string. + * Get client-supplied query string. Addtion to that the jumbled query is + * supplied if the caller requested. From the restriction of JumbleQuery, some + * kind of query needs special amendments. Reutrns NULL if this query doesn't + * change the current hint. This function returns NULL also when something + * wrong has happend and let the caller continue using the current hints. */ static const char * -get_query_string(void) +get_query_string(ParseState *pstate, Query *query, Query **jumblequery) { - const char *p; + const char *p = debug_query_string; - if (plpgsql_recurse_level > 0) + if (jumblequery != NULL) + *jumblequery = query; + + if (query->commandType == CMD_UTILITY) { + Query *target_query = (Query *)query->utilityStmt; + /* - * This is quite ugly but this is the only point I could find where - * we can get the query string. + * Some CMD_UTILITY statements have a subquery that we can hint on. + * Since EXPLAIN can be placed before other kind of utility statements + * and EXECUTE can be contained other kind of utility statements, these + * conditions are not mutually exclusive and should be considered in + * this order. */ - p = (char*)error_context_stack->arg; - } - else if (stmt_name) - { - PreparedStatement *entry; + if (IsA(target_query, ExplainStmt)) + { + ExplainStmt *stmt = (ExplainStmt *)target_query; + + Assert(IsA(stmt->query, Query)); + target_query = (Query *)stmt->query; + + /* strip out the top-level query for further processing */ + if (target_query->commandType == CMD_UTILITY && + target_query->utilityStmt != NULL) + target_query = (Query *)target_query->utilityStmt; + } + + if (IsA(target_query, DeclareCursorStmt)) + { + DeclareCursorStmt *stmt = (DeclareCursorStmt *)target_query; + Query *query = (Query *)stmt->query; - entry = FetchPreparedStatement(stmt_name, true); - p = entry->plansource->query_string; + /* the target must be CMD_SELECT in this case */ + Assert(IsA(query, Query) && query->commandType == CMD_SELECT); + target_query = query; + } + + if (IsA(target_query, CreateTableAsStmt)) + { + CreateTableAsStmt *stmt = (CreateTableAsStmt *) target_query; + + Assert(IsA(stmt->query, Query)); + target_query = (Query *) stmt->query; + + /* strip out the top-level query for further processing */ + if (target_query->commandType == CMD_UTILITY && + target_query->utilityStmt != NULL) + target_query = (Query *)target_query->utilityStmt; + } + + if (IsA(target_query, ExecuteStmt)) + { + /* + * Use the prepared query for EXECUTE. The Query for jumble + * also replaced with the corresponding one. + */ + ExecuteStmt *stmt = (ExecuteStmt *)target_query; + PreparedStatement *entry; + + entry = FetchPreparedStatement(stmt->name, true); + p = entry->plansource->query_string; + target_query = (Query *) linitial (entry->plansource->query_list); + } + + /* JumbleQuery accespts only a non-utility Query */ + if (!IsA(target_query, Query) || + target_query->utilityStmt != NULL) + target_query = NULL; + + if (jumblequery) + *jumblequery = target_query; } - else - p = debug_query_string; + /* Return NULL if the pstate is not identical to the top-level query */ + else if (strcmp(pstate->p_sourcetext, p) != 0) + p = NULL; return p; } @@ -2349,9 +2432,9 @@ ParallelHintParse(ParallelHint *hint, HintState *hstate, Query *parse, if (length < 2 || length > 3) { - hint_ereport(str, - ("Wrong number of arguments for %s hint.", - hint->base.keyword)); + hint_ereport(")", + ("wrong number of arguments (%d): %s", + length, hint->base.keyword)); hint->base.state = HINT_STATE_ERROR; return str; } @@ -2361,20 +2444,26 @@ ParallelHintParse(ParallelHint *hint, HintState *hstate, Query *parse, /* The second parameter is number of workers */ hint->nworkers_str = list_nth(name_list, 1); nworkers = strtod(hint->nworkers_str, &end_ptr); - if (*end_ptr || nworkers < 0) + if (*end_ptr || nworkers < 0 || nworkers > max_worker_processes) { - hint_ereport(hint->nworkers_str, - ("number of workers must be a positive integer: %s", - hint->base.keyword)); + if (*end_ptr) + hint_ereport(hint->nworkers_str, + ("number of workers must be a number: %s", + hint->base.keyword)); + else if (nworkers < 0) + hint_ereport(hint->nworkers_str, + ("number of workers must be positive: %s", + hint->base.keyword)); + else if (nworkers > max_worker_processes) + hint_ereport(hint->nworkers_str, + ("number of workers = %d is larger than max_worker_processes(%d): %s", + nworkers, max_worker_processes, hint->base.keyword)); + hint->base.state = HINT_STATE_ERROR; - return str; } hint->nworkers = nworkers; - if (nworkers > max_hint_nworkers) - max_hint_nworkers = nworkers; - /* optional third parameter is specified */ if (length == 3) { @@ -2383,15 +2472,19 @@ ParallelHintParse(ParallelHint *hint, HintState *hstate, Query *parse, force_parallel = true; else if (strcasecmp(modeparam, "soft") != 0) { - hint_ereport(str, - ("The mode of Worker hint must be soft or hard.")); + hint_ereport(modeparam, + ("enforcement must be soft or hard: %s", + hint->base.keyword)); hint->base.state = HINT_STATE_ERROR; - return str; } } - + hint->force_parallel = force_parallel; + if (hint->base.state != HINT_STATE_ERROR && + nworkers > max_hint_nworkers) + max_hint_nworkers = nworkers; + return str; } @@ -2433,8 +2526,12 @@ get_current_join_mask() return mask; } +/* + * Sets GUC prameters without throwing exception. Reutrns false if something + * wrong. + */ static int -set_config_option_wrapper(const char *name, const char *value, +set_config_option_noerror(const char *name, const char *value, GucContext context, GucSource source, GucAction action, bool changeVal, int elevel) { @@ -2457,10 +2554,10 @@ set_config_option_wrapper(const char *name, const char *value, ereport(elevel, (errcode(errdata->sqlerrcode), - errhidestmt(hidestmt), errmsg("%s", errdata->message), errdata->detail ? errdetail("%s", errdata->detail) : 0, errdata->hint ? errhint("%s", errdata->hint) : 0)); + msgqno = qno; FreeErrorData(errdata); } PG_END_TRY(); @@ -2468,13 +2565,34 @@ set_config_option_wrapper(const char *name, const char *value, return result; } +/* + * Sets GUC parameter of int32 type without throwing exceptions. Returns false + * if something wrong. + */ static int -set_config_options(SetHint **options, int noptions, GucContext context) +set_config_int32_option(const char *name, int32 value, GucContext context) { - int i; - int save_nestlevel; + char buf[16]; /* enough for int32 */ - save_nestlevel = NewGUCNestLevel(); + if (snprintf(buf, 16, "%d", value) < 0) + { + ereport(pg_hint_plan_message_level, + (errmsg ("Cannot set integer value: %d: %s", + max_hint_nworkers, strerror(errno)))); + return false; + } + + return + set_config_option_noerror(name, buf, context, + PGC_S_SESSION, GUC_ACTION_SAVE, true, + pg_hint_plan_message_level); +} + +/* setup scan method enforcement according to given options */ +static void +setup_guc_enforcement(SetHint **options, int noptions, GucContext context) +{ + int i; for (i = 0; i < noptions; i++) { @@ -2484,7 +2602,7 @@ set_config_options(SetHint **options, int noptions, GucContext context) if (!hint_state_enabled(hint)) continue; - result = set_config_option_wrapper(hint->name, hint->value, context, + result = set_config_option_noerror(hint->name, hint->value, context, PGC_S_SESSION, GUC_ACTION_SAVE, true, pg_hint_plan_message_level); if (result != 0) @@ -2493,51 +2611,35 @@ set_config_options(SetHint **options, int noptions, GucContext context) hint->base.state = HINT_STATE_ERROR; } - return save_nestlevel; -} - -static void -set_config_int32_option(const char *name, int32 value, GucContext context) -{ - char buf[16]; /* enough for int32 */ - - if (snprintf(buf, 16, "%d", value) < 0) - { - ereport(pg_hint_plan_message_level, - (errmsg ("Cannot set integer value: %d: %s", - max_hint_nworkers, strerror(errno)))); - return; - } - - set_config_option_wrapper(name, buf, context, - PGC_S_SESSION, GUC_ACTION_SAVE, true, - pg_hint_plan_message_level); + return; } - /* - * Setup parallel execution environment. If init == true, it is set to the - * initial values in state, elsewise set to values in hint. + * Setup parallel execution environment. + * + * If hint is not NULL, set up using it, elsewise reset to initial environment. */ static void -setup_parallel_scan(ParallelHint *hint, bool init, HintState *state) +setup_parallel_plan_enforcement(ParallelHint *hint, HintState *state) { - /* !init requires hint */ - Assert(init|| hint); - - if (init) + if (hint) + { + hint->base.state = HINT_STATE_USED; set_config_int32_option("max_parallel_workers_per_gather", - state->init_nworkers, state->context); + hint->nworkers, state->context); + } else set_config_int32_option("max_parallel_workers_per_gather", - hint->nworkers, state->context); + state->init_nworkers, state->context); /* force means that enforce parallel as far as possible */ - if (!init && hint->force_parallel) + if (hint && hint->force_parallel) { set_config_int32_option("parallel_tuple_cost", 0, state->context); set_config_int32_option("parallel_setup_cost", 0, state->context); - set_config_int32_option("min_parallel_relation_size", 0, + set_config_int32_option("min_parallel_table_scan_size", 0, + state->context); + set_config_int32_option("min_parallel_index_scan_size", 0, state->context); } else @@ -2546,21 +2648,38 @@ setup_parallel_scan(ParallelHint *hint, bool init, HintState *state) state->init_paratup_cost, state->context); set_config_int32_option("parallel_setup_cost", state->init_parasetup_cost, state->context); - set_config_int32_option("min_parallel_relation_size", - state->init_min_para_size, state->context); + set_config_int32_option("min_parallel_table_scan_size", + state->init_min_para_tablescan_size, + state->context); + set_config_int32_option("min_parallel_index_scan_size", + state->init_min_para_indexscan_size, + state->context); } } #define SET_CONFIG_OPTION(name, type_bits) \ - set_config_option_wrapper((name), \ + set_config_option_noerror((name), \ (mask & (type_bits)) ? "true" : "false", \ context, PGC_S_SESSION, GUC_ACTION_SAVE, true, ERROR) + +/* + * Setup GUC environment to enforce scan methods. If scanhint is NULL, reset + * GUCs to the saved state in state. + */ static void -set_scan_config_options(unsigned char enforce_mask, GucContext context) +setup_scan_method_enforcement(ScanMethodHint *scanhint, HintState *state) { + unsigned char enforce_mask = state->init_scan_mask; + GucContext context = state->context; unsigned char mask; + if (scanhint) + { + enforce_mask = scanhint->enforce_mask; + scanhint->base.state = HINT_STATE_USED; + } + if (enforce_mask == ENABLE_SEQSCAN || enforce_mask == ENABLE_INDEXSCAN || enforce_mask == ENABLE_BITMAPSCAN || enforce_mask == ENABLE_TIDSCAN || enforce_mask == (ENABLE_INDEXSCAN | ENABLE_INDEXONLYSCAN) @@ -2593,132 +2712,6 @@ set_join_config_options(unsigned char enforce_mask, GucContext context) } /* - * pg_hint_plan hook functions - */ - -static void -pg_hint_plan_ProcessUtility(Node *parsetree, const char *queryString, - ProcessUtilityContext context, - ParamListInfo params, - DestReceiver *dest, char *completionTag) -{ - Node *node; - - /* - * Use standard planner if pg_hint_plan is disabled or current nesting - * depth is nesting depth of SPI calls. - */ - if (!pg_hint_plan_enable_hint || hint_inhibit_level > 0) - { - if (debug_level > 1) - ereport(pg_hint_plan_message_level, - (errmsg ("pg_hint_plan: ProcessUtility:" - " pg_hint_plan.enable_hint = off"))); - if (prev_ProcessUtility) - (*prev_ProcessUtility) (parsetree, queryString, - context, params, - dest, completionTag); - else - standard_ProcessUtility(parsetree, queryString, - context, params, - dest, completionTag); - return; - } - - node = parsetree; - if (IsA(node, ExplainStmt)) - { - /* - * Draw out parse tree of actual query from Query struct of EXPLAIN - * statement. - */ - ExplainStmt *stmt; - Query *query; - - stmt = (ExplainStmt *) node; - - Assert(IsA(stmt->query, Query)); - query = (Query *) stmt->query; - - if (query->commandType == CMD_UTILITY && query->utilityStmt != NULL) - node = query->utilityStmt; - } - - /* - * If the query was a EXECUTE or CREATE TABLE AS EXECUTE, get query string - * specified to preceding PREPARE command to use it as source of hints. - */ - if (IsA(node, ExecuteStmt)) - { - ExecuteStmt *stmt; - - stmt = (ExecuteStmt *) node; - stmt_name = stmt->name; - } - - /* - * CREATE AS EXECUTE behavior has changed since 9.2, so we must handle it - * specially here. - */ - if (IsA(node, CreateTableAsStmt)) - { - CreateTableAsStmt *stmt; - Query *query; - - stmt = (CreateTableAsStmt *) node; - Assert(IsA(stmt->query, Query)); - query = (Query *) stmt->query; - - if (query->commandType == CMD_UTILITY && - IsA(query->utilityStmt, ExecuteStmt)) - { - ExecuteStmt *estmt = (ExecuteStmt *) query->utilityStmt; - stmt_name = estmt->name; - } - } - - if (stmt_name) - { - if (debug_level > 1) - ereport(pg_hint_plan_message_level, - (errmsg ("pg_hint_plan: ProcessUtility:" - " stmt_name = \"%s\", statement=\"%s\"", - stmt_name, queryString))); - - PG_TRY(); - { - if (prev_ProcessUtility) - (*prev_ProcessUtility) (parsetree, queryString, - context, params, - dest, completionTag); - else - standard_ProcessUtility(parsetree, queryString, - context, params, - dest, completionTag); - } - PG_CATCH(); - { - stmt_name = NULL; - PG_RE_THROW(); - } - PG_END_TRY(); - - stmt_name = NULL; - - return; - } - - if (prev_ProcessUtility) - (*prev_ProcessUtility) (parsetree, queryString, - context, params, - dest, completionTag); - else - standard_ProcessUtility(parsetree, queryString, - context, params, - dest, completionTag); -} - -/* * Push a hint into hint stack which is implemented with List struct. Head of * list is top of stack. */ @@ -2752,24 +2745,182 @@ pop_hint(void) current_hint_state = (HintState *) lfirst(list_head(HintStateStack)); } +/* + * 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. + */ +static void +pg_hint_plan_post_parse_analyze(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; + + if (!pg_hint_plan_enable_hint) + { + if (current_hint_str) + { + pfree((void *)current_hint_str); + current_hint_str = NULL; + } + return; + } + + /* increment the query number */ + qnostr[0] = 0; + if (debug_level > 1) + snprintf(qnostr, sizeof(qnostr), "[qno=0x%x]", qno++); + qno++; + + /* search the hint table for a hint if requested */ + if (pg_hint_plan_enable_hint_table) + { + int query_len; + pgssJumbleState jstate; + Query *jumblequery; + char *normalized_query = NULL; + + query_str = get_query_string(pstate, query, &jumblequery); + + /* If this query is not for hint, just return */ + if (!query_str) + return; + + /* clear the previous hint string */ + if (current_hint_str) + { + pfree((void *)current_hint_str); + current_hint_str = NULL; + } + + if (jumblequery) + { + /* + * XXX: normalizing code is copied from pg_stat_statements.c, so be + * careful to PostgreSQL's version up. + */ + jstate.jumble = (unsigned char *) palloc(JUMBLE_SIZE); + jstate.jumble_len = 0; + jstate.clocations_buf_size = 32; + jstate.clocations = (pgssLocationLen *) + palloc(jstate.clocations_buf_size * sizeof(pgssLocationLen)); + jstate.clocations_count = 0; + + JumbleQuery(&jstate, jumblequery); + + /* + * Normalize the query string by replacing constants with '?' + */ + /* + * Search hint string which is stored keyed by query string + * and application name. The query string is normalized to allow + * fuzzy matching. + * + * Adding 1 byte to query_len ensures that the returned string has + * a terminating NULL. + */ + query_len = strlen(query_str) + 1; + normalized_query = + generate_normalized_query(&jstate, query_str, + &query_len, + GetDatabaseEncoding()); + + /* + * find a hint for the normalized query. the result should be in + * TopMemoryContext + */ + oldcontext = MemoryContextSwitchTo(TopMemoryContext); + current_hint_str = + get_hints_from_table(normalized_query, application_name); + MemoryContextSwitchTo(oldcontext); + + if (debug_level > 1) + { + if (current_hint_str) + ereport(pg_hint_plan_message_level, + (errmsg("pg_hint_plan[qno=0x%x]: " + "post_parse_analyze_hook: " + "hints from table: \"%s\": " + "normalized_query=\"%s\", " + "application name =\"%s\"", + qno, current_hint_str, + normalized_query, application_name), + errhidestmt(msgqno != qno), + errhidecontext(msgqno != qno))); + else + ereport(pg_hint_plan_message_level, + (errmsg("pg_hint_plan[qno=0x%x]: " + "no match found in table: " + "application name = \"%s\", " + "normalized_query=\"%s\"", + qno, application_name, + normalized_query), + errhidestmt(msgqno != qno), + errhidecontext(msgqno != qno))); + + msgqno = qno; + } + } + + /* retrun if we have hint here */ + if (current_hint_str) + return; + } + else + query_str = get_query_string(pstate, query, NULL); + + if (query_str) + { + /* + * 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. + */ + if (current_hint_str) + pfree((void *)current_hint_str); + + oldcontext = MemoryContextSwitchTo(TopMemoryContext); + current_hint_str = get_hints_from_comment(query_str); + MemoryContextSwitchTo(oldcontext); + } + + if (debug_level > 1) + { + if (debug_level == 1 && + (stmt_name || strcmp(query_str, debug_query_string))) + ereport(pg_hint_plan_message_level, + (errmsg("hints in comment=\"%s\"", + current_hint_str ? current_hint_str : "(none)"), + errhidestmt(msgqno != qno), + errhidecontext(msgqno != qno))); + else + ereport(pg_hint_plan_message_level, + (errmsg("hints in comment=\"%s\", stmt=\"%s\", query=\"%s\", debug_query_string=\"%s\"", + current_hint_str ? current_hint_str : "(none)", + stmt_name, query_str, debug_query_string), + errhidestmt(msgqno != qno), + errhidecontext(msgqno != qno))); + msgqno = qno; + } +} + +/* + * Read and set up hint information + */ static PlannedStmt * pg_hint_plan_planner(Query *parse, int cursorOptions, ParamListInfo boundParams) { - const char *hints = NULL; - const char *query; - char *norm_query; - pgssJumbleState jstate; - int query_len; int save_nestlevel; PlannedStmt *result; HintState *hstate; - char msgstr[1024]; - - qnostr[0] = 0; - strcpy(msgstr, ""); - if (debug_level > 1) - snprintf(qnostr, sizeof(qnostr), "[qno=0x%x]", qno++); - hidestmt = false; /* * Use standard planner if pg_hint_plan is disabled or current nesting @@ -2779,118 +2930,63 @@ pg_hint_plan_planner(Query *parse, int cursorOptions, ParamListInfo boundParams) if (!pg_hint_plan_enable_hint || hint_inhibit_level > 0) { if (debug_level > 1) - elog(pg_hint_plan_message_level, - "pg_hint_plan%s: planner: enable_hint=%d," - " hint_inhibit_level=%d", - qnostr, pg_hint_plan_enable_hint, hint_inhibit_level); - hidestmt = true; + ereport(pg_hint_plan_message_level, + (errmsg ("pg_hint_plan%s: planner: enable_hint=%d," + " hint_inhibit_level=%d", + qnostr, pg_hint_plan_enable_hint, + hint_inhibit_level), + errhidestmt(msgqno != qno))); + msgqno = qno; goto standard_planner_proc; } - /* Create hint struct from client-supplied query string. */ - query = get_query_string(); - /* - * Create hintstate from hint specified for the query, if any. - * - * First we lookup hint in pg_hint.hints table by normalized query string, - * unless pg_hint_plan.enable_hint_table is OFF. - * This parameter provides option to avoid overhead of table lookup during - * planning. - * - * If no hint was found, then we try to get hint from special query comment. + * Support for nested plpgsql functions. This is quite ugly but this is the + * only point I could find where I can get the query string. */ - if (pg_hint_plan_enable_hint_table) - { - /* - * Search hint information which is stored for the query and the - * application. Query string is normalized before using in condition - * in order to allow fuzzy matching. - * - * XXX: normalizing code is copied from pg_stat_statements.c, so be - * careful when supporting PostgreSQL's version up. - */ - jstate.jumble = (unsigned char *) palloc(JUMBLE_SIZE); - jstate.jumble_len = 0; - jstate.clocations_buf_size = 32; - jstate.clocations = (pgssLocationLen *) - palloc(jstate.clocations_buf_size * sizeof(pgssLocationLen)); - jstate.clocations_count = 0; - JumbleQuery(&jstate, parse); - /* - * generate_normalized_query() copies exact given query_len bytes, so we - * add 1 byte for null-termination here. As comments on - * generate_normalized_query says, generate_normalized_query doesn't - * take care of null-terminate, but additional 1 byte ensures that '\0' - * byte in the source buffer to be copied into norm_query. - */ - query_len = strlen(query) + 1; - norm_query = generate_normalized_query(&jstate, - query, - &query_len, - GetDatabaseEncoding()); - hints = get_hints_from_table(norm_query, application_name); - if (debug_level > 1) - { - if (hints) - snprintf(msgstr, 1024, "hints from table: \"%s\":" - " normalzed_query=\"%s\", application name =\"%s\"", - hints, norm_query, application_name); - else - { - ereport(pg_hint_plan_message_level, - (errhidestmt(hidestmt), - errmsg("pg_hint_plan%s:" - " no match found in table:" - " application name = \"%s\"," - " normalzed_query=\"%s\"", - qnostr, application_name, norm_query))); - hidestmt = true; - } - } - } - if (hints == NULL) + if (plpgsql_recurse_level > 0) { - hints = get_hints_from_comment(query); + MemoryContext oldcontext; - if (debug_level > 1) - { - snprintf(msgstr, 1024, "hints in comment=\"%s\"", - hints ? hints : "(none)"); - if (debug_level > 2 || - stmt_name || strcmp(query, debug_query_string)) - snprintf(msgstr + strlen(msgstr), 1024- strlen(msgstr), - ", stmt=\"%s\", query=\"%s\", debug_query_string=\"%s\"", - stmt_name, query, debug_query_string); - } + 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); } - hstate = create_hintstate(parse, hints); + if (!current_hint_str) + goto standard_planner_proc; - /* - * Use standard planner if the statement has not valid hint. Other hook - * functions try to change plan with current_hint_state if any, so set it - * to NULL. - */ + /* parse the hint into hint state struct */ + hstate = create_hintstate(parse, pstrdup(current_hint_str)); + + /* run standard planner if the statement has not valid hint */ if (!hstate) goto standard_planner_proc; - + /* * Push new hint struct to the hint stack to disable previous hint context. */ push_hint(hstate); - /* Set GUC parameters which are specified with Set hint. */ - save_nestlevel = - set_config_options(current_hint_state->set_hints, + /* Set scan enforcement here. */ + save_nestlevel = NewGUCNestLevel(); + + /* Apply Set hints, then save it as the initial state */ + setup_guc_enforcement(current_hint_state->set_hints, current_hint_state->num_hints[HINT_TYPE_SET], current_hint_state->context); - /* save current status to current_hint_state */ current_hint_state->init_scan_mask = get_current_scan_mask(); current_hint_state->init_join_mask = get_current_join_mask(); - current_hint_state->init_min_para_size = min_parallel_relation_size; + current_hint_state->init_min_para_tablescan_size = + min_parallel_table_scan_size; + current_hint_state->init_min_para_indexscan_size = + min_parallel_index_scan_size; current_hint_state->init_paratup_cost = parallel_tuple_cost; current_hint_state->init_parasetup_cost = parallel_setup_cost; @@ -2898,20 +2994,17 @@ pg_hint_plan_planner(Query *parse, int cursorOptions, ParamListInfo boundParams) * max_parallel_workers_per_gather should be non-zero here if Workers hint * is specified. */ - if (max_hint_nworkers >= 0) - { - current_hint_state->init_nworkers = 0; + if (max_hint_nworkers > 0 && max_parallel_workers_per_gather < 1) set_config_int32_option("max_parallel_workers_per_gather", 1, current_hint_state->context); - } + current_hint_state->init_nworkers = max_parallel_workers_per_gather; if (debug_level > 1) { ereport(pg_hint_plan_message_level, - (errhidestmt(hidestmt), - errmsg("pg_hint_plan%s: planner: %s", - qnostr, msgstr))); - hidestmt = true; + (errhidestmt(msgqno != qno), + errmsg("pg_hint_plan%s: planner", qnostr))); + msgqno = qno; } /* @@ -2956,10 +3049,10 @@ standard_planner_proc: if (debug_level > 1) { ereport(pg_hint_plan_message_level, - (errhidestmt(hidestmt), - errmsg("pg_hint_plan%s: planner: no valid hint (%s)", - qnostr, msgstr))); - hidestmt = true; + (errhidestmt(msgqno != qno), + errmsg("pg_hint_plan%s: planner: no valid hint", + qnostr))); + msgqno = qno; } current_hint_state = NULL; if (prev_planner) @@ -2969,27 +3062,44 @@ standard_planner_proc: } /* - * Return scan method hint which matches given aliasname. + * Find scan method hint to be applied to the given relation + * */ static ScanMethodHint * -find_scan_hint(PlannerInfo *root, Index relid, RelOptInfo *rel) +find_scan_hint(PlannerInfo *root, Index relid) { + RelOptInfo *rel; RangeTblEntry *rte; + ScanMethodHint *real_name_hint = NULL; + ScanMethodHint *alias_hint = NULL; int i; + /* This should not be a join rel */ + Assert(relid > 0); + rel = root->simple_rel_array[relid]; + /* - * We don't apply scan method hint if the relation is: - * - not a base relation - * - not an ordinary relation (such as join and subquery) + * This function is called for any RelOptInfo or its inheritance parent if + * any. If we are called from inheritance planner, the RelOptInfo for the + * parent of target child relation is not set in the planner info. + * + * Otherwise we should check that the reloptinfo is base relation or + * inheritance children. */ - if (rel && (rel->reloptkind != RELOPT_BASEREL || - rel->rtekind != RTE_RELATION)) + if (rel && + rel->reloptkind != RELOPT_BASEREL && + rel->reloptkind != RELOPT_OTHER_MEMBER_REL) return NULL; + /* + * This is baserel or appendrel children. We can refer to RangeTblEntry. + */ rte = root->simple_rte_array[relid]; + Assert(rte); - /* We don't force scan method of foreign tables */ - if (rte->relkind == RELKIND_FOREIGN_TABLE) + /* We don't hint on other than relation and foreign tables */ + if (rte->rtekind != RTE_RELATION || + rte->relkind == RELKIND_FOREIGN_TABLE) return NULL; /* Find scan method hint, which matches given names, from the list. */ @@ -3001,36 +3111,66 @@ find_scan_hint(PlannerInfo *root, Index relid, RelOptInfo *rel) if (!hint_state_enabled(hint)) continue; - if (RelnameCmp(&rte->eref->aliasname, &hint->relname) == 0) - return hint; + if (!alias_hint && + RelnameCmp(&rte->eref->aliasname, &hint->relname) == 0) + alias_hint = hint; + + /* check the real name for appendrel children */ + if (!real_name_hint && + rel && rel->reloptkind == RELOPT_OTHER_MEMBER_REL) + { + char *realname = get_rel_name(rte->relid); + + if (realname && RelnameCmp(&realname, &hint->relname) == 0) + real_name_hint = hint; + } + + /* No more match expected, break */ + if(alias_hint && real_name_hint) + break; } - return NULL; + /* real name match precedes alias match */ + if (real_name_hint) + return real_name_hint; + + return alias_hint; } static ParallelHint * -find_parallel_hint(PlannerInfo *root, Index relid, RelOptInfo *rel) +find_parallel_hint(PlannerInfo *root, Index relid) { + RelOptInfo *rel; RangeTblEntry *rte; + ParallelHint *real_name_hint = NULL; + ParallelHint *alias_hint = NULL; int i; + /* This should not be a join rel */ + Assert(relid > 0); + rel = root->simple_rel_array[relid]; + /* - * We don't apply scan method hint if the relation is: - * - not a base relation and inheritance children - * - not an ordinary relation (such as join and subquery) + * Parallel planning is appliable only on base relation, which has + * RelOptInfo. */ - if (rel && ((rel->reloptkind != RELOPT_BASEREL && - rel->reloptkind != RELOPT_OTHER_MEMBER_REL) || - rel->rtekind != RTE_RELATION)) + if (!rel) return NULL; - rte = root->simple_rte_array[relid]; - - /* We can't force scan method of foreign tables */ - if (rte->relkind == RELKIND_FOREIGN_TABLE) + /* + * We have set root->glob->parallelModeOK if needed. What we should do here + * is just following the decision of planner. + */ + if (!rel->consider_parallel) return NULL; - /* Find scan method hint, which matches given names, from the list. */ + /* + * This is baserel or appendrel children. We can refer to RangeTblEntry. + */ + rte = root->simple_rte_array[relid]; + Assert(rte); + + /* Find parallel method hint, which matches given names, from the list. */ for (i = 0; i < current_hint_state->num_hints[HINT_TYPE_PARALLEL]; i++) { ParallelHint *hint = current_hint_state->parallel_hints[i]; @@ -3039,11 +3179,30 @@ find_parallel_hint(PlannerInfo *root, Index relid, RelOptInfo *rel) if (!hint_state_enabled(hint)) continue; - if (RelnameCmp(&rte->eref->aliasname, &hint->relname) == 0) - return hint; + if (!alias_hint && + RelnameCmp(&rte->eref->aliasname, &hint->relname) == 0) + alias_hint = hint; + + /* check the real name for appendrel children */ + if (!real_name_hint && + rel && rel->reloptkind == RELOPT_OTHER_MEMBER_REL) + { + char *realname = get_rel_name(rte->relid); + + if (realname && RelnameCmp(&realname, &hint->relname) == 0) + real_name_hint = hint; + } + + /* No more match expected, break */ + if(alias_hint && real_name_hint) + break; } - return NULL; + /* real name match precedes alias match */ + if (real_name_hint) + return real_name_hint; + + return alias_hint; } /* @@ -3073,13 +3232,18 @@ regexpeq(const char *s1, const char *s2) return DatumGetBool(result); } + +/* Remove indexes instructed not to use by hint. */ static void -delete_indexes(ScanMethodHint *hint, RelOptInfo *rel, Oid relationObjectId) +restrict_indexes(PlannerInfo *root, ScanMethodHint *hint, RelOptInfo *rel, + bool using_parent_hint) { ListCell *cell; ListCell *prev; ListCell *next; StringInfoData buf; + RangeTblEntry *rte = root->simple_rte_array[rel->relid]; + Oid relationObjectId = rte->relid; /* * We delete all the IndexOptInfo list and prevent you from being usable by @@ -3142,10 +3306,11 @@ delete_indexes(ScanMethodHint *hint, RelOptInfo *rel, Oid relationObjectId) } /* - * to make the index a candidate when definition of this index is - * matched with the index's definition of current_hint_state. + * Apply index restriction of parent hint to children. Since index + * inheritance is not explicitly described we should search for an + * children's index with the same definition to that of the parent. */ - if (OidIsValid(relationObjectId) && !use_index) + if (using_parent_hint && !use_index) { foreach(l, current_hint_state->parent_index_infos) { @@ -3153,71 +3318,61 @@ delete_indexes(ScanMethodHint *hint, RelOptInfo *rel, Oid relationObjectId) HeapTuple ht_idx; ParentIndexInfo *p_info = (ParentIndexInfo *)lfirst(l); - /* check to match the parameter of unique */ - if (p_info->indisunique != info->unique) - continue; - - /* check to match the parameter of index's method */ - if (p_info->method != info->relam) - continue; - - /* to check to match the indexkey's configuration */ - if ((list_length(p_info->column_names)) != - info->ncolumns) + /* + * we check the 'same' index by comparing uniqueness, access + * method and index key columns. + */ + if (p_info->indisunique != info->unique || + p_info->method != info->relam || + list_length(p_info->column_names) != info->ncolumns) continue; - /* check to match the indexkey's configuration */ + /* Check if index key columns match */ for (i = 0; i < info->ncolumns; i++) { char *c_attname = NULL; char *p_attname = NULL; - p_attname = - list_nth(p_info->column_names, i); + p_attname = list_nth(p_info->column_names, i); - /* both are expressions */ + /* + * if both of the key of the same position are expressions, + * ignore them for now and check later. + */ if (info->indexkeys[i] == 0 && !p_attname) continue; - /* one's column is expression, the other is not */ + /* deny if one is expression while another is not */ if (info->indexkeys[i] == 0 || !p_attname) break; c_attname = get_attname(relationObjectId, info->indexkeys[i]); - if (strcmp(p_attname, c_attname) != 0) - break; - - if (p_info->indcollation[i] != info->indexcollations[i]) - break; - - if (p_info->opclass[i] != info->opcintype[i]) - break; - - if (((p_info->indoption[i] & INDOPTION_DESC) != 0) != - info->reverse_sort[i]) - break; - - if (((p_info->indoption[i] & INDOPTION_NULLS_FIRST) != 0) != - info->nulls_first[i]) + /* deny if any of column attributes don't match */ + if (strcmp(p_attname, c_attname) != 0 || + p_info->indcollation[i] != info->indexcollations[i] || + p_info->opclass[i] != info->opcintype[i]|| + ((p_info->indoption[i] & INDOPTION_DESC) != 0) + != info->reverse_sort[i] || + ((p_info->indoption[i] & INDOPTION_NULLS_FIRST) != 0) + != info->nulls_first[i]) break; - } + /* deny this if any difference found */ if (i != info->ncolumns) continue; + /* check on key expressions */ if ((p_info->expression_str && (info->indexprs != NIL)) || (p_info->indpred_str && (info->indpred != NIL))) { - /* - * Fetch the pg_index tuple by the Oid of the index - */ + /* fetch the index of this child */ ht_idx = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(info->indexoid)); - /* check to match the expression's parameter of index */ + /* check expressions if both expressions are available */ if (p_info->expression_str && !heap_attisnull(ht_idx, Anum_pg_index_indexprs)) { @@ -3238,17 +3393,17 @@ delete_indexes(ScanMethodHint *hint, RelOptInfo *rel, Oid relationObjectId) ObjectIdGetDatum( relationObjectId)); + /* deny if expressions don't match */ if (strcmp(p_info->expression_str, text_to_cstring(DatumGetTextP(result))) != 0) { /* Clean up */ ReleaseSysCache(ht_idx); - continue; } } - /* Check to match the predicate's parameter of index */ + /* compare index predicates */ if (p_info->indpred_str && !heap_attisnull(ht_idx, Anum_pg_index_indpred)) { @@ -3256,10 +3411,6 @@ delete_indexes(ScanMethodHint *hint, RelOptInfo *rel, Oid relationObjectId) bool isnull; Datum result; - /* - * to change the predicate's parameter of child's - * index to strings - */ predDatum = SysCacheGetAttr(INDEXRELID, ht_idx, Anum_pg_index_indpred, &isnull); @@ -3274,7 +3425,6 @@ delete_indexes(ScanMethodHint *hint, RelOptInfo *rel, Oid relationObjectId) { /* Clean up */ ReleaseSysCache(ht_idx); - continue; } } @@ -3310,16 +3460,21 @@ delete_indexes(ScanMethodHint *hint, RelOptInfo *rel, Oid relationObjectId) if (debug_level == 1) { - char *relname; StringInfoData rel_buf; + char *disprelname = ""; - if (OidIsValid(relationObjectId)) - relname = get_rel_name(relationObjectId); + /* + * If this hint targetted the parent, use the real name of this + * child. Otherwise use hint specification. + */ + if (using_parent_hint) + disprelname = get_rel_name(rte->relid); else - relname = hint->relname; + disprelname = hint->relname; + initStringInfo(&rel_buf); - quote_value(&rel_buf, relname); + quote_value(&rel_buf, disprelname); ereport(LOG, (errmsg("available indexes for %s(%s):%s", @@ -3410,36 +3565,59 @@ get_parent_index_info(Oid indexoid, Oid relid) return p_info; } +/* + * cancel hint enforcement + */ static void -process_scanmethod_hints(PlannerInfo *root, Oid relationObjectId, - bool inhparent, RelOptInfo *rel) +reset_hint_enforcement() { - Index new_parent_relid = 0; + setup_scan_method_enforcement(NULL, current_hint_state); + setup_parallel_plan_enforcement(NULL, current_hint_state); +} + +/* + * Set planner guc parameters according to corresponding scan hints. Returns + * bitmap of HintTypeBitmap. If shint or phint is not NULL, set used hint + * there respectively. + */ +static bool +setup_hint_enforcement(PlannerInfo *root, RelOptInfo *rel, + ScanMethodHint **rshint, ParallelHint **rphint) +{ + Index new_parent_relid = 0; ListCell *l; - ScanMethodHint *scanhint = NULL; + ScanMethodHint *shint = NULL; + ParallelHint *phint = NULL; + bool inhparent = root->simple_rte_array[rel->relid]->inh; + Oid relationObjectId = root->simple_rte_array[rel->relid]->relid; + int ret = 0; + + /* reset returns if requested */ + if (rshint != NULL) *rshint = NULL; + if (rphint != NULL) *rphint = NULL; /* * We could register the parent relation of the following children here - * when inhparent == true but inheritnce planner doesn't request - * information for inheritance parents. We also cannot distinguish the - * caller so we should always find the parents without this function being - * called for them. + * when inhparent == true but inheritnce planner doesn't call this function + * for parents. Since we cannot distinguish who called this function we + * cannot do other than always seeking the parent regardless of who called + * this function. */ if (inhparent) { if (debug_level > 1) ereport(pg_hint_plan_message_level, (errhidestmt(true), - errmsg ("pg_hint_plan%s: get_relation_info" + errmsg ("pg_hint_plan%s: setup_hint_enforcement" " skipping inh parent: relation=%u(%s), inhparent=%d," " current_hint_state=%p, hint_inhibit_level=%d", qnostr, relationObjectId, get_rel_name(relationObjectId), inhparent, current_hint_state, hint_inhibit_level))); - return; + return 0; } - /* Find the parent for this relation */ + /* Find the parent for this relation other than the registered parent */ foreach (l, root->append_rel_list) { AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); @@ -3456,7 +3634,8 @@ process_scanmethod_hints(PlannerInfo *root, Oid relationObjectId, { /* This relation doesn't have a parent. Cancel current_hint_state. */ current_hint_state->parent_relid = 0; - current_hint_state->parent_hint = NULL; + current_hint_state->parent_scan_hint = NULL; + current_hint_state->parent_parallel_hint = NULL; } if (new_parent_relid > 0) @@ -3466,23 +3645,27 @@ process_scanmethod_hints(PlannerInfo *root, Oid relationObjectId, * hint to other childrens of this parent so remember it * to avoid * hinthintredundant setup cost. */ - bool use_parent_env = false; - ScanMethodHint *parent_hint = NULL; - current_hint_state->parent_relid = new_parent_relid; - /* Check if the parent has a hint */ - current_hint_state->parent_hint = parent_hint = - find_scan_hint(root, current_hint_state->parent_relid, NULL); + /* Find hints for the parent */ + current_hint_state->parent_scan_hint = + find_scan_hint(root, current_hint_state->parent_relid); + + current_hint_state->parent_parallel_hint = + find_parallel_hint(root, current_hint_state->parent_relid); - if (parent_hint) + /* + * If hint is found for the parent, apply it for this child instead + * of its own. + */ + if (current_hint_state->parent_scan_hint) { - /* If hint is found for the parent, apply its hint instead. */ - use_parent_env = true; - parent_hint->base.state = HINT_STATE_USED; + ScanMethodHint * pshint = current_hint_state->parent_scan_hint; - /* Resolve index name mask (if any) using the parent. */ - if (parent_hint->indexnames) + pshint->base.state = HINT_STATE_USED; + + /* Apply index mask in the same manner to the parent. */ + if (pshint->indexnames) { Oid parentrel_oid; Relation parent_rel; @@ -3499,7 +3682,7 @@ process_scanmethod_hints(PlannerInfo *root, Oid relationObjectId, ListCell *lc; ParentIndexInfo *parent_index_info; - foreach(lc, parent_hint->indexnames) + foreach(lc, pshint->indexnames) { if (RelnameCmp(&indexname, &lfirst(lc)) == 0) break; @@ -3510,123 +3693,88 @@ process_scanmethod_hints(PlannerInfo *root, Oid relationObjectId, parent_index_info = get_parent_index_info(indexoid, parentrel_oid); current_hint_state->parent_index_infos = - lappend(current_hint_state->parent_index_infos, parent_index_info); + lappend(current_hint_state->parent_index_infos, + parent_index_info); } heap_close(parent_rel, NoLock); } } - - if (use_parent_env) - set_scan_config_options(parent_hint->enforce_mask, - current_hint_state->context); - else - set_scan_config_options(current_hint_state->init_scan_mask, - current_hint_state->context); } - /* Process index restriction hint inheritance */ - if (current_hint_state->parent_hint != 0) + shint = find_scan_hint(root, rel->relid); + if (!shint) + shint = current_hint_state->parent_scan_hint; + + if (shint) { - delete_indexes(current_hint_state->parent_hint, rel, - relationObjectId); + bool using_parent_hint = + (shint == current_hint_state->parent_scan_hint); - /* Scan fixation status is the same to the parent. */ - if (debug_level > 1) - ereport(pg_hint_plan_message_level, - (errhidestmt(true), - errmsg("pg_hint_plan%s: get_relation_info:" - " index deletion by parent hint: " - "relation=%u(%s), inhparent=%d, current_hint_state=%p," - " hint_inhibit_level=%d", - qnostr, relationObjectId, - get_rel_name(relationObjectId), - inhparent, current_hint_state, hint_inhibit_level))); - return; - } + ret |= HINT_BM_SCAN_METHOD; - /* This table doesn't have a parent hint. Apply its own hint if any. */ - if ((scanhint = find_scan_hint(root, rel->relid, rel)) != NULL) - { - set_scan_config_options(scanhint->enforce_mask, - current_hint_state->context); - scanhint->base.state = HINT_STATE_USED; + /* Setup scan enforcement environment */ + setup_scan_method_enforcement(shint, current_hint_state); - delete_indexes(scanhint, rel, InvalidOid); + /* restrict unwanted inexes */ + restrict_indexes(root, shint, rel, using_parent_hint); if (debug_level > 1) + { + char *additional_message = ""; + + if (shint == current_hint_state->parent_scan_hint) + additional_message = " by parent hint"; + ereport(pg_hint_plan_message_level, (errhidestmt(true), - errmsg ("pg_hint_plan%s: get_relation_info" - " index deletion:" - " relation=%u(%s), inhparent=%d, current_hint=%p," + errmsg ("pg_hint_plan%s: setup_hint_enforcement" + " index deletion%s:" + " relation=%u(%s), inhparent=%d, " + "current_hint_state=%p," " hint_inhibit_level=%d, scanmask=0x%x", - qnostr, relationObjectId, + qnostr, additional_message, + relationObjectId, get_rel_name(relationObjectId), - inhparent, current_hint_state, hint_inhibit_level, - scanhint->enforce_mask))); - return; + inhparent, current_hint_state, + hint_inhibit_level, + shint->enforce_mask))); + } } - /* Nothing to apply. Reset the scan mask to intial state */ - if (debug_level > 1) - ereport(pg_hint_plan_message_level, - (errhidestmt (true), - errmsg ("pg_hint_plan%s: get_relation_info" - " no hint applied:" - " relation=%u(%s), inhparent=%d, current_hint=%p," - " hint_inhibit_level=%d, scanmask=0x%x", - qnostr, relationObjectId, - get_rel_name(relationObjectId), - inhparent, current_hint_state, hint_inhibit_level, - current_hint_state->init_scan_mask))); - set_scan_config_options(current_hint_state->init_scan_mask, - current_hint_state->context); -} + /* Do the same for parallel plan enforcement */ + phint = find_parallel_hint(root, rel->relid); + if (!phint) + phint = current_hint_state->parent_parallel_hint; -static void -pg_hint_plan_get_relation_info(PlannerInfo *root, Oid relationObjectId, - bool inhparent, RelOptInfo *rel) -{ - ParallelHint *parallelhint = NULL; + setup_parallel_plan_enforcement(phint, current_hint_state); - if (prev_get_relation_info) - (*prev_get_relation_info) (root, relationObjectId, inhparent, rel); + if (phint) + ret |= HINT_BM_PARALLEL; - /* - * Do nothing if we don't have a valid hint in this context or current - * nesting depth is at SPI calls. - */ - if (!current_hint_state || hint_inhibit_level > 0) + /* Nothing to apply. Reset the scan mask to intial state */ + if (!shint && ! phint) { if (debug_level > 1) ereport(pg_hint_plan_message_level, (errhidestmt (true), - errmsg ("pg_hint_plan%s: get_relation_info" - " no hint to apply: relation=%u(%s), inhparent=%d," - " current_hint_state=%p, hint_inhibit_level=%d", + errmsg ("pg_hint_plan%s: setup_hint_enforcement" + " no hint applied:" + " relation=%u(%s), inhparent=%d, current_hint=%p," + " hint_inhibit_level=%d, scanmask=0x%x", qnostr, relationObjectId, get_rel_name(relationObjectId), - inhparent, current_hint_state, hint_inhibit_level))); - return; - } + inhparent, current_hint_state, hint_inhibit_level, + current_hint_state->init_scan_mask))); - process_scanmethod_hints(root, relationObjectId, inhparent, rel); + setup_scan_method_enforcement(NULL, current_hint_state); - /* - * Parallel hint doesn't inherit, process separately from other types of - * hint - */ - if ((parallelhint = find_parallel_hint(root, rel->relid, rel)) != NULL) - { - /* Set parallel environment according to the hint */ - setup_parallel_scan(parallelhint, false, current_hint_state); - } - else - { - /* Reset parallel environment */ - setup_parallel_scan(NULL, true, current_hint_state); + return ret; } - return; + + if (rshint != NULL) *rshint = shint; + if (rphint != NULL) *rphint = phint; + + return ret; } /* @@ -4072,165 +4220,6 @@ transform_join_hints(HintState *hstate, PlannerInfo *root, int nbaserel, } /* - * set_plain_rel_pathlist - * Build access paths for a plain relation (no subquery, no inheritance) - * - * This function was copied and edited from set_plain_rel_pathlist() in - * src/backend/optimizer/path/allpaths.c - * - * - removed parallel stuff. - */ -static void -set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) -{ - Relids required_outer; - - /* - * We don't support pushing join clauses into the quals of a seqscan, but - * it could still have required parameterization due to LATERAL refs in - * its tlist. - */ - required_outer = rel->lateral_relids; - - /* Consider sequential scan */ - add_path(rel, create_seqscan_path(root, rel, required_outer, 0)); - - /* If appropriate, consider parallel sequential scan */ - if (rel->consider_parallel && required_outer == NULL) - { - ParallelHint *phint = find_parallel_hint(root, rel->relid, rel); - - /* Consider parallel paths only if not inhibited by hint */ - if (!phint || phint->nworkers > 0) - create_plain_partial_paths(root, rel); - - /* - * Overwirte parallel_workers if requested. partial_pathlist seems to - * have up to one path but looping over all possible paths don't harm. - */ - if (phint && phint->nworkers > 0 && phint->force_parallel) - { - ListCell *l; - foreach (l, rel->partial_pathlist) - { - Path *ppath = (Path *) lfirst(l); - - Assert(ppath->parallel_workers > 0); - ppath->parallel_workers = phint->nworkers; - } - } - } - - /* Consider index scans */ - create_index_paths(root, rel); - - /* Consider TID scans */ - create_tidscan_paths(root, rel); -} - -/* - * Clear exiting access paths and create new ones applying hints. - * This does the similar to set_rel_pathlist - */ -static void -rebuild_scan_path(HintState *hstate, PlannerInfo *root, int level, - List *initial_rels) -{ - ListCell *l; - - foreach(l, initial_rels) - { - RelOptInfo *rel = (RelOptInfo *) lfirst(l); - RangeTblEntry *rte; - ScanMethodHint *scanhint; - ParallelHint *parallelhint; - - /* Skip relations which we can't choose scan method. */ - if (rel->reloptkind != RELOPT_BASEREL || rel->rtekind != RTE_RELATION) - continue; - - rte = root->simple_rte_array[rel->relid]; - - /* We can't force scan method of foreign tables */ - if (rte->relkind == RELKIND_FOREIGN_TABLE) - continue; - - /* - * Create scan paths with GUC parameters which are at the beginning of - * planner if scan method hint is not specified, otherwise use - * specified hints and mark the hint as used. - */ - if ((scanhint = find_scan_hint(root, rel->relid, rel)) == NULL) - set_scan_config_options(hstate->init_scan_mask, - hstate->context); - else - { - set_scan_config_options(scanhint->enforce_mask, hstate->context); - scanhint->base.state = HINT_STATE_USED; - } - - if ((parallelhint = find_parallel_hint(root, rel->relid, rel)) != NULL) - { - /* Set parallel environment according to the hint */ - setup_parallel_scan(parallelhint, false, current_hint_state); - } - else - { - /* Reset parallel environment */ - setup_parallel_scan(NULL, true, current_hint_state); - } - - /* remove existing partial paths from this baserel */ - list_free_deep(rel->partial_pathlist); - rel->partial_pathlist = NIL; - - /* remove existing paths from this baserel */ - list_free_deep(rel->pathlist); - rel->pathlist = NIL; - - if (rte->inh) - { - ListCell *l; - - /* remove partial paths from all chlidren */ - foreach (l, root->append_rel_list) - { - AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); - RelOptInfo *childrel; - - if (appinfo->parent_relid != rel->relid) - continue; - - childrel = root->simple_rel_array[appinfo->child_relid]; - list_free_deep(childrel->partial_pathlist); - childrel->partial_pathlist = NIL; - } - /* It's an "append relation", process accordingly */ - set_append_rel_pathlist(root, rel, rel->relid, rte); - } - else - { - set_plain_rel_pathlist(root, rel, rte); - } - - /* - * If this is a baserel, consider gathering any partial paths we may - * hinthave created for it. - */ - if (rel->reloptkind == RELOPT_BASEREL) - generate_gather_paths(root, rel); - - /* Now find the cheapest of the paths for this rel */ - set_cheapest(rel); - } - - /* - * Restore the GUC variables we set above. - */ - set_scan_config_options(hstate->init_scan_mask, hstate->context); -} - -/* * wrapper of make_join_rel() * * call make_join_rel() after changing enable_* parameters according to given @@ -4284,18 +4273,10 @@ add_paths_to_joinrel_wrapper(PlannerInfo *root, SpecialJoinInfo *sjinfo, List *restrictlist) { - ScanMethodHint *scan_hint = NULL; Relids joinrelids; JoinMethodHint *join_hint; int save_nestlevel; - if ((scan_hint = find_scan_hint(root, innerrel->relid, innerrel)) != NULL) - { - set_scan_config_options(scan_hint->enforce_mask, - current_hint_state->context); - scan_hint->base.state = HINT_STATE_USED; - } - joinrelids = bms_union(outerrel->relids, innerrel->relids); join_hint = find_join_hint(joinrelids); bms_free(joinrelids); @@ -4330,10 +4311,6 @@ add_paths_to_joinrel_wrapper(PlannerInfo *root, else add_paths_to_joinrel(root, joinrel, outerrel, innerrel, jointype, sjinfo, restrictlist); - - if (scan_hint != NULL) - set_scan_config_options(current_hint_state->init_scan_mask, - current_hint_state->context); } static int @@ -4385,9 +4362,6 @@ pg_hint_plan_join_search(PlannerInfo *root, int levels_needed, return standard_join_search(root, levels_needed, initial_rels); } - /* apply scan method hint rebuild scan path. */ - rebuild_scan_path(current_hint_state, root, levels_needed, initial_rels); - /* * In the case using GEQO, only scan method hints and Set hints have * effect. Join method and join order is not controllable by hints. @@ -4433,55 +4407,88 @@ pg_hint_plan_set_rel_pathlist(PlannerInfo * root, RelOptInfo *rel, Index rti, RangeTblEntry *rte) { ParallelHint *phint; - List *oldpathlist; ListCell *l; - bool regenerate = false; + int found_hints; - /* Hint has not been parsed yet, or this is not a parallel'able relation */ - if (current_hint_state == NULL || rel->partial_pathlist == NIL) - return; + /* call the previous hook */ + if (prev_set_rel_pathlist) + prev_set_rel_pathlist(root, rel, rti, rte); - phint = find_parallel_hint(root, rel->relid, rel); + /* Nothing to do if no hint available */ + if (current_hint_state == NULL) + return; - if (phint == NULL || !phint->force_parallel) + /* Don't touch dummy rels. */ + if (IS_DUMMY_REL(rel)) return; /* - * This relation contains gather paths previously created and they prevent - * adding new gahter path with same cost. Remove them. + * We can accept only plain relations, foreign tables and table saples are + * also unacceptable. See set_rel_pathlist. */ - oldpathlist = rel->pathlist; - rel->pathlist = NIL; - foreach (l, oldpathlist) + if (rel->rtekind != RTE_RELATION || + rte->relkind == RELKIND_FOREIGN_TABLE || + rte->tablesample != NULL) + return; + + /* We cannot handle if this requires an outer */ + if (rel->lateral_relids) + return; + + /* Return if this relation gets no enfocement */ + if ((found_hints = setup_hint_enforcement(root, rel, NULL, &phint)) == 0) + return; + + /* Here, we regenerate paths with the current hint restriction */ + if (found_hints & HINT_BM_SCAN_METHOD || found_hints & HINT_BM_PARALLEL) { - Path *path = (Path *) lfirst(l); + /* Just discard all the paths considered so far */ + list_free_deep(rel->pathlist); + rel->pathlist = NIL; - if (IsA(path, GatherPath) && - path->parallel_workers != phint->nworkers) + /* Remove all the partial paths if Parallel hint is specfied */ + if ((found_hints & HINT_BM_PARALLEL) && rel->partial_pathlist) { - pfree(path); - regenerate = true; + list_free_deep(rel->partial_pathlist); + rel->partial_pathlist = NIL; } - else - rel->pathlist = lappend(rel->pathlist, path); - } - list_free(oldpathlist); - if (regenerate) - { - foreach (l, rel->partial_pathlist) + /* Regenerate paths with the current enforcement */ + set_plain_rel_pathlist(root, rel, rte); + + /* Additional work to enforce parallel query execution */ + if (phint && phint->nworkers > 0) { - Path *ppath = (Path *) lfirst(l); - - if (phint && phint->nworkers > 0 && phint->force_parallel) + /* Lower the priorities of non-parallel paths */ + foreach (l, rel->pathlist) + { + Path *path = (Path *) lfirst(l); + + if (path->startup_cost < disable_cost) + { + path->startup_cost += disable_cost; + path->total_cost += disable_cost; + } + } + + /* enforce number of workers if requested */ + if (phint->force_parallel) { - Assert(ppath->parallel_workers > 0); - ppath->parallel_workers = phint->nworkers; - } + foreach (l, rel->partial_pathlist) + { + Path *ppath = (Path *) lfirst(l); + + ppath->parallel_workers = phint->nworkers; + } + } + + /* Generate gather paths for base rels */ + if (rel->reloptkind == RELOPT_BASEREL) + generate_gather_paths(root, rel); } - - generate_gather_paths(root, rel); } + + reset_hint_enforcement(); } /* @@ -4489,7 +4496,8 @@ pg_hint_plan_set_rel_pathlist(PlannerInfo * root, RelOptInfo *rel, * Build access paths for a base relation * * This function was copied and edited from set_rel_pathlist() in - * src/backend/optimizer/path/allpaths.c + * src/backend/optimizer/path/allpaths.c in order not to copy other static + * functions not required here. */ static void set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,