X-Git-Url: http://git.osdn.net/view?a=blobdiff_plain;f=pg_hint_plan.c;h=72231233a2079f836289ac90c47d682cc24b86dd;hb=d7296d7ffb44a9e197c908885f7383b63d5a08da;hp=6b747c8c712891a570272458dba56adba27dae44;hpb=c43304db0df8b36ebf1ad205697789d1ff6c36ff;p=pghintplan%2Fpg_hint_plan.git diff --git a/pg_hint_plan.c b/pg_hint_plan.c index 6b747c8..7223123 100644 --- a/pg_hint_plan.c +++ b/pg_hint_plan.c @@ -3,35 +3,46 @@ * pg_hint_plan.c * hinting on how to execute a query for PostgreSQL * - * Copyright (c) 2012-2017, NIPPON TELEGRAPH AND TELEPHONE CORPORATION + * Copyright (c) 2012-2021, 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" #include "mb/pg_wchar.h" #include "miscadmin.h" #include "nodes/nodeFuncs.h" +#include "nodes/params.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" #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 "partitioning/partbounds.h" #include "tcop/utility.h" #include "utils/builtins.h" +#include "utils/float.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" @@ -83,14 +94,18 @@ PG_MODULE_MAGIC; #define HINT_LEADING "Leading" #define HINT_SET "Set" #define HINT_ROWS "Rows" +#define HINT_MEMOIZE "Memoize" +#define HINT_NOMEMOIZE "NoMemoize" #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)) +#define hint_ereport(str, detail) hint_parse_ereport(str, detail) +#define hint_parse_ereport(str, detail) \ + do { \ + ereport(pg_hint_plan_parse_message_level, \ + (errmsg("pg_hint_plan: hint syntax error at or near \"%s\"", (str)), \ + errdetail detail)); \ + } while(0) #define skip_space(str) \ while (isspace(*str)) \ @@ -109,7 +124,8 @@ enum { ENABLE_NESTLOOP = 0x01, ENABLE_MERGEJOIN = 0x02, - ENABLE_HASHJOIN = 0x04 + ENABLE_HASHJOIN = 0x04, + ENABLE_MEMOIZE = 0x08 } JOIN_TYPE_BITS; #define ENABLE_ALL_SCAN (ENABLE_SEQSCAN | ENABLE_INDEXSCAN | \ @@ -147,6 +163,8 @@ typedef enum HintKeyword HINT_KEYWORD_SET, HINT_KEYWORD_ROWS, HINT_KEYWORD_PARALLEL, + HINT_KEYWORD_MEMOIZE, + HINT_KEYWORD_NOMEMOIZE, HINT_KEYWORD_UNRECOGNIZED } HintKeyword; @@ -173,7 +191,6 @@ typedef const char *(*HintParseFunction) (Hint *hint, HintState *hstate, Query *parse, const char *str); /* hint types */ -#define NUM_HINT_TYPE 6 typedef enum HintType { HINT_TYPE_SCAN_METHOD, @@ -181,7 +198,10 @@ typedef enum HintType HINT_TYPE_LEADING, HINT_TYPE_SET, HINT_TYPE_ROWS, - HINT_TYPE_PARALLEL + HINT_TYPE_PARALLEL, + HINT_TYPE_MEMOIZE, + + NUM_HINT_TYPE } HintType; typedef enum HintTypeBitmap @@ -213,7 +233,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 @@ -337,10 +359,14 @@ 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*/ - int init_paratup_cost; /* parallel_tuple_cost */ - int init_parasetup_cost;/* parallel_setup_cost */ - + /* min_parallel_table_scan_size*/ + int init_min_para_tablescan_size; + /* min_parallel_index_scan_size*/ + int init_min_para_indexscan_size; + double init_paratup_cost; /* parallel_tuple_cost */ + double 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 */ @@ -349,11 +375,13 @@ struct HintState JoinMethodHint **join_hints; /* parsed join hints */ int init_join_mask; /* initial value join parameter */ List **join_hint_level; + List **memoize_hint_level; LeadingHint **leading_hint; /* parsed Leading hints */ SetHint **set_hints; /* parsed Set hints */ GucContext context; /* which GUC parameters can we set? */ RowsHint **rows_hints; /* parsed Rows hints */ ParallelHint **parallel_hints; /* parsed Parallel hints */ + JoinMethodHint **memoize_hints; /* parsed Memoize hints */ }; /* @@ -373,12 +401,8 @@ 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 PlannedStmt *pg_hint_plan_planner(Query *parse, int cursorOptions, +static PlannedStmt *pg_hint_plan_planner(Query *parse, const char *query_string, + int cursorOptions, ParamListInfo boundParams); static RelOptInfo *pg_hint_plan_join_search(PlannerInfo *root, int levels_needed, @@ -438,6 +462,9 @@ static int ParallelHintCmp(const ParallelHint *a, const ParallelHint *b); static const char *ParallelHintParse(ParallelHint *hint, HintState *hstate, Query *parse, const char *str); +static Hint *MemoizeHintCreate(const char *hint_str, const char *keyword, + HintKeyword hint_keyword); + static void quote_value(StringInfo buf, const char *value); static const char *parse_quoted_value(const char *str, char **word, @@ -451,25 +478,15 @@ 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 make_rels_by_clause_joins(PlannerInfo *root, RelOptInfo *old_rel, + List *other_rels_list, ListCell *other_rels); static void make_rels_by_clauseless_joins(PlannerInfo *root, RelOptInfo *old_rel, - ListCell *other_rels); + List *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); -static Path *get_cheapest_parameterized_child_path(PlannerInfo *root, - RelOptInfo *rel, - Relids required_outer); -static List *accumulate_append_subpath(List *subpaths, Path *path); RelOptInfo *pg_hint_plan_make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2); @@ -488,18 +505,19 @@ static void setup_scan_method_enforcement(ScanMethodHint *scanhint, HintState *state); static int set_config_int32_option(const char *name, int32 value, GucContext context); +static int set_config_double_option(const char *name, double value, + GucContext context); /* GUC variables */ static bool pg_hint_plan_enable_hint = true; static int debug_level = 0; -static int pg_hint_plan_message_level = INFO; +static int pg_hint_plan_parse_message_level = INFO; +static int pg_hint_plan_debug_message_level = LOG; /* 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 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 */ @@ -540,7 +558,6 @@ 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 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; @@ -555,12 +572,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}, @@ -589,6 +600,8 @@ static const HintParser parsers[] = { {HINT_SET, SetHintCreate, HINT_KEYWORD_SET}, {HINT_ROWS, RowsHintCreate, HINT_KEYWORD_ROWS}, {HINT_PARALLEL, ParallelHintCreate, HINT_KEYWORD_PARALLEL}, + {HINT_MEMOIZE, MemoizeHintCreate, HINT_KEYWORD_MEMOIZE}, + {HINT_NOMEMOIZE, MemoizeHintCreate, HINT_KEYWORD_NOMEMOIZE}, {NULL, NULL, HINT_KEYWORD_UNRECOGNIZED} }; @@ -638,7 +651,7 @@ _PG_init(void) DefineCustomEnumVariable("pg_hint_plan.parse_messages", "Message level of parse errors.", NULL, - &pg_hint_plan_message_level, + &pg_hint_plan_parse_message_level, INFO, parse_messages_level_options, PGC_USERSET, @@ -650,8 +663,8 @@ _PG_init(void) DefineCustomEnumVariable("pg_hint_plan.message_level", "Message level of debug messages.", NULL, - &pg_hint_plan_message_level, - INFO, + &pg_hint_plan_debug_message_level, + LOG, parse_messages_level_options, PGC_USERSET, 0, @@ -671,8 +684,6 @@ _PG_init(void) NULL); /* Install hooks. */ - prev_ProcessUtility = ProcessUtility_hook; - ProcessUtility_hook = pg_hint_plan_ProcessUtility; prev_planner = planner_hook; planner_hook = pg_hint_plan_planner; prev_join_search = join_search_hook; @@ -697,7 +708,6 @@ _PG_fini(void) PLpgSQL_plugin **var_ptr; /* Uninstall hooks. */ - ProcessUtility_hook = prev_ProcessUtility; planner_hook = prev_planner; join_search_hook = prev_join_search; set_rel_pathlist_hook = prev_set_rel_pathlist; @@ -919,7 +929,7 @@ ParallelHintCreate(const char *hint_str, const char *keyword, { ParallelHint *hint; - hint = palloc(sizeof(ScanMethodHint)); + hint = palloc(sizeof(ParallelHint)); hint->base.hint_str = hint_str; hint->base.keyword = keyword; hint->base.hint_keyword = hint_keyword; @@ -948,6 +958,23 @@ ParallelHintDelete(ParallelHint *hint) } +static Hint * +MemoizeHintCreate(const char *hint_str, const char *keyword, + HintKeyword hint_keyword) +{ + /* + * MemoizeHintCreate shares the same struct with JoinMethodHint and the + * only difference is the hint type. + */ + JoinMethodHint *hint = + (JoinMethodHint *)JoinMethodHintCreate(hint_str, keyword, hint_keyword); + + hint->base.type = HINT_TYPE_MEMOIZE; + + return (Hint *) hint; +} + + static HintState * HintStateCreate(void) { @@ -962,9 +989,11 @@ 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->current_root = NULL; hstate->parent_relid = 0; hstate->parent_scan_hint = NULL; hstate->parent_parallel_hint = NULL; @@ -972,6 +1001,7 @@ HintStateCreate(void) hstate->join_hints = NULL; hstate->init_join_mask = 0; hstate->join_hint_level = NULL; + hstate->memoize_hint_level = NULL; hstate->leading_hint = NULL; hstate->context = superuser() ? PGC_SUSET : PGC_USERSET; hstate->set_hints = NULL; @@ -1163,7 +1193,8 @@ RowsHintDesc(RowsHint *hint, StringInfo buf, bool nolf) quote_value(buf, hint->relnames[i]); } } - appendStringInfo(buf, " %s", hint->rows_str); + if (hint->rows_str != NULL) + appendStringInfo(buf, " %s", hint->rows_str); appendStringInfoString(buf, ")"); if (!nolf) appendStringInfoChar(buf, '\n'); @@ -1228,7 +1259,7 @@ HintStateDump(HintState *hstate) if (!hstate) { - elog(LOG, "pg_hint_plan:\nno hint"); + elog(pg_hint_plan_debug_message_level, "pg_hint_plan:\nno hint"); return; } @@ -1240,7 +1271,8 @@ HintStateDump(HintState *hstate) desc_hint_in_state(hstate, &buf, "duplication hint", HINT_STATE_DUPLICATION, false); desc_hint_in_state(hstate, &buf, "error hint", HINT_STATE_ERROR, false); - elog(LOG, "%s", buf.data); + ereport(pg_hint_plan_debug_message_level, + (errmsg ("%s", buf.data))); pfree(buf.data); } @@ -1252,7 +1284,7 @@ HintStateDump2(HintState *hstate) if (!hstate) { - elog(pg_hint_plan_message_level, + elog(pg_hint_plan_debug_message_level, "pg_hint_plan%s: HintStateDump: no hint", qnostr); return; } @@ -1265,9 +1297,10 @@ HintStateDump2(HintState *hstate) desc_hint_in_state(hstate, &buf, "}, {error hints", HINT_STATE_ERROR, true); appendStringInfoChar(&buf, '}'); - ereport(pg_hint_plan_message_level, - (errhidestmt(true), - errmsg("%s", buf.data))); + ereport(pg_hint_plan_debug_message_level, + (errmsg("%s", buf.data), + errhidestmt(true), + errhidecontext(true))); pfree(buf.data); } @@ -1647,7 +1680,7 @@ parse_hints(HintState *hstate, Query *parse, const char *str) char *keyword = parser->keyword; Hint *hint; - if (strcasecmp(buf.data, keyword) != 0) + if (pg_strcasecmp(buf.data, keyword) != 0) continue; hint = parser->create_func(head, keyword, parser->hint_keyword); @@ -1716,13 +1749,21 @@ get_hints_from_table(const char *client_query, const char *client_application) char *hints = NULL; Oid argtypes[2] = { TEXTOID, TEXTOID }; Datum values[2]; - bool nulls[2] = { false, false }; + char nulls[2] = {' ', ' '}; text *qry; text *app; PG_TRY(); { + bool snapshot_set = false; + hint_inhibit_level++; + + if (!ActiveSnapshotSet()) + { + PushActiveSnapshot(GetTransactionSnapshot()); + snapshot_set = true; + } SPI_connect(); @@ -1759,7 +1800,10 @@ get_hints_from_table(const char *client_query, const char *client_application) } SPI_finish(); - + + if (snapshot_set) + PopActiveSnapshot(); + hint_inhibit_level--; } PG_CATCH(); @@ -1773,35 +1817,6 @@ get_hints_from_table(const char *client_query, const char *client_application) } /* - * Get client-supplied query string. - */ -static const char * -get_query_string(void) -{ - const char *p; - - if (plpgsql_recurse_level > 0) - { - /* - * This is quite ugly but this is the only point I could find where - * we can get the query string. - */ - p = (char*)error_context_stack->arg; - } - else if (stmt_name) - { - PreparedStatement *entry; - - entry = FetchPreparedStatement(stmt_name, true); - p = entry->plansource->query_string; - } - else - p = debug_query_string; - - return p; -} - -/* * Get hints from the head block comment in client-supplied query string. */ static const char * @@ -1955,8 +1970,10 @@ create_hintstate(Query *parse, const char *hints) hstate->num_hints[HINT_TYPE_LEADING]); hstate->rows_hints = (RowsHint **) (hstate->set_hints + hstate->num_hints[HINT_TYPE_SET]); - hstate->parallel_hints = (ParallelHint **) (hstate->set_hints + + hstate->parallel_hints = (ParallelHint **) (hstate->rows_hints + hstate->num_hints[HINT_TYPE_ROWS]); + hstate->memoize_hints = (JoinMethodHint **) (hstate->parallel_hints + + hstate->num_hints[HINT_TYPE_PARALLEL]); return hstate; } @@ -2120,6 +2137,10 @@ JoinMethodHintParse(JoinMethodHint *hint, HintState *hstate, Query *parse, case HINT_KEYWORD_NOHASHJOIN: hint->enforce_mask = ENABLE_ALL_JOIN ^ ENABLE_HASHJOIN; break; + case HINT_KEYWORD_MEMOIZE: + case HINT_KEYWORD_NOMEMOIZE: + /* nothing to do here */ + break; default: hint_ereport(str, ("Unrecognized hint keyword \"%s\".", keyword)); return NULL; @@ -2249,6 +2270,8 @@ RowsHintParse(RowsHint *hint, HintState *hstate, Query *parse, List *name_list = NIL; char *rows_str; char *end_ptr; + ListCell *l; + int i = 0; if ((str = parse_parentheses(str, &name_list, hint_keyword)) == NULL) return NULL; @@ -2256,23 +2279,28 @@ RowsHintParse(RowsHint *hint, HintState *hstate, Query *parse, /* Last element must be rows specification */ hint->nrels = list_length(name_list) - 1; - if (hint->nrels > 0) + if (hint->nrels < 1) { - ListCell *l; - int i = 0; + hint_ereport(str, + ("%s hint needs at least one relation followed by one correction term.", + hint->base.keyword)); + hint->base.state = HINT_STATE_ERROR; - /* - * Transform relation names from list to array to sort them with qsort - * after. - */ - hint->relnames = palloc(sizeof(char *) * hint->nrels); - foreach (l, name_list) - { - if (hint->nrels <= i) - break; - hint->relnames[i] = lfirst(l); - i++; - } + return str; + } + + + /* + * Transform relation names from list to array to sort them with qsort + * after. + */ + hint->relnames = palloc(sizeof(char *) * hint->nrels); + foreach (l, name_list) + { + if (hint->nrels <= i) + break; + hint->relnames[i] = lfirst(l); + i++; } /* Retieve rows estimation */ @@ -2387,9 +2415,9 @@ ParallelHintParse(ParallelHint *hint, HintState *hstate, Query *parse, if (length == 3) { const char *modeparam = (const char *)list_nth(name_list, 2); - if (strcasecmp(modeparam, "hard") == 0) + if (pg_strcasecmp(modeparam, "hard") == 0) force_parallel = true; - else if (strcasecmp(modeparam, "soft") != 0) + else if (pg_strcasecmp(modeparam, "soft") != 0) { hint_ereport(modeparam, ("enforcement must be soft or hard: %s", @@ -2441,6 +2469,8 @@ get_current_join_mask() mask |= ENABLE_MERGEJOIN; if (enable_hashjoin) mask |= ENABLE_HASHJOIN; + if (enable_memoize) + mask |= ENABLE_MEMOIZE; return mask; } @@ -2473,10 +2503,10 @@ set_config_option_noerror(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(); @@ -2495,16 +2525,32 @@ set_config_int32_option(const char *name, int32 value, GucContext context) 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)))); + ereport(pg_hint_plan_parse_message_level, + (errmsg ("Failed to convert integer to string: %d", value))); return false; } return set_config_option_noerror(name, buf, context, PGC_S_SESSION, GUC_ACTION_SAVE, true, - pg_hint_plan_message_level); + pg_hint_plan_parse_message_level); +} + +/* + * Sets GUC parameter of double type without throwing exceptions. Returns false + * if something wrong. + */ +static int +set_config_double_option(const char *name, double value, GucContext context) +{ + char *buf = float8out_internal(value); + int result; + + result = set_config_option_noerror(name, buf, context, + PGC_S_SESSION, GUC_ACTION_SAVE, true, + pg_hint_plan_parse_message_level); + pfree(buf); + return result; } /* setup scan method enforcement according to given options */ @@ -2523,7 +2569,7 @@ setup_guc_enforcement(SetHint **options, int noptions, GucContext context) result = set_config_option_noerror(hint->name, hint->value, context, PGC_S_SESSION, GUC_ACTION_SAVE, true, - pg_hint_plan_message_level); + pg_hint_plan_parse_message_level); if (result != 0) hint->base.state = HINT_STATE_USED; else @@ -2552,21 +2598,27 @@ setup_parallel_plan_enforcement(ParallelHint *hint, HintState *state) state->init_nworkers, state->context); /* force means that enforce parallel as far as possible */ - if (hint && hint->force_parallel) + if (hint && hint->force_parallel && hint->nworkers > 0) { - 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_double_option("parallel_tuple_cost", 0.0, state->context); + set_config_double_option("parallel_setup_cost", 0.0, state->context); + 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 { - set_config_int32_option("parallel_tuple_cost", + set_config_double_option("parallel_tuple_cost", state->init_paratup_cost, state->context); - set_config_int32_option("parallel_setup_cost", + set_config_double_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); } } @@ -2609,7 +2661,8 @@ setup_scan_method_enforcement(ScanMethodHint *scanhint, HintState *state) } static void -set_join_config_options(unsigned char enforce_mask, GucContext context) +set_join_config_options(unsigned char enforce_mask, bool set_memoize, + GucContext context) { unsigned char mask; @@ -2622,132 +2675,34 @@ set_join_config_options(unsigned char enforce_mask, GucContext context) SET_CONFIG_OPTION("enable_nestloop", ENABLE_NESTLOOP); SET_CONFIG_OPTION("enable_mergejoin", ENABLE_MERGEJOIN); SET_CONFIG_OPTION("enable_hashjoin", ENABLE_HASHJOIN); -} - -/* - * 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; - } + if (set_memoize) + SET_CONFIG_OPTION("enable_memoize", ENABLE_MEMOIZE); /* - * CREATE AS EXECUTE behavior has changed since 9.2, so we must handle it - * specially here. + * Hash join may be rejected for the reason of estimated memory usage. Try + * getting rid of that limitation. */ - if (IsA(node, CreateTableAsStmt)) + if (enforce_mask == ENABLE_HASHJOIN) { - CreateTableAsStmt *stmt; - Query *query; + char buf[32]; + int new_multipler; - 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; - } - } + /* See final_cost_hashjoin(). */ + new_multipler = MAX_KILOBYTES / work_mem; - 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))); + /* See guc.c for the upper limit */ + if (new_multipler >= 1000) + new_multipler = 1000; - PG_TRY(); + if (new_multipler > hash_mem_multiplier) { - 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(); + snprintf(buf, sizeof(buf), UINT64_FORMAT, (uint64)new_multipler); + set_config_option_noerror("hash_mem_multiplier", buf, + context, PGC_S_SESSION, GUC_ACTION_SAVE, + true, ERROR); } - 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); } /* @@ -2784,130 +2739,184 @@ pop_hint(void) current_hint_state = (HintState *) lfirst(list_head(HintStateStack)); } -static PlannedStmt * -pg_hint_plan_planner(Query *parse, int cursorOptions, ParamListInfo boundParams) +/* + * Retrieve and store hint string from given query or from the hint table. + */ +static void +get_current_hint_string(Query *query, const char *query_str) { - 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]; + MemoryContext oldcontext; - qnostr[0] = 0; - strcpy(msgstr, ""); - if (debug_level > 1) - snprintf(qnostr, sizeof(qnostr), "[qno=0x%x]", qno++); - hidestmt = false; + /* do nothing while scanning hint table */ + if (hint_inhibit_level > 0) + return; - /* - * Use standard planner if pg_hint_plan is disabled or current nesting - * depth is nesting depth of SPI calls. Other hook functions try to change - * plan with current_hint_state if any, so set it to NULL. - */ - if (!pg_hint_plan_enable_hint || hint_inhibit_level > 0) + /* Make sure trashing old hint string */ + if (current_hint_str) { - 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; - - goto standard_planner_proc; + pfree((void *)current_hint_str); + current_hint_str = NULL; } - /* Create hint struct from client-supplied query string. */ - query = get_query_string(); + /* Return if nothing to do. */ + if (!pg_hint_plan_enable_hint || !query_str) + return; - /* - * 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. - */ + /* 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) { + JumbleState *jstate; + int query_len; + char *normalized_query; + + jstate = JumbleQuery(query, query_str); + /* - * 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. + * 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. * - * XXX: normalizing code is copied from pg_stat_statements.c, so be - * careful when supporting PostgreSQL's version up. + * Adding 1 byte to query_len ensures that the returned string has + * a terminating NULL. */ - 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); + query_len = strlen(query_str) + 1; + normalized_query = + generate_normalized_query(jstate, query_str, 0, &query_len); + /* - * 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. + * find a hint for the normalized query. the result should be in + * TopMemoryContext */ - query_len = strlen(query) + 1; - norm_query = generate_normalized_query(&jstate, - query, - &query_len, - GetDatabaseEncoding()); - hints = get_hints_from_table(norm_query, application_name); + oldcontext = MemoryContextSwitchTo(TopMemoryContext); + current_hint_str = + get_hints_from_table(normalized_query, application_name); + MemoryContextSwitchTo(oldcontext); + if (debug_level > 1) { - if (hints) - snprintf(msgstr, 1024, "hints from table: \"%s\":" - " normalzed_query=\"%s\", application name =\"%s\"", - hints, norm_query, application_name); + if (current_hint_str) + ereport(pg_hint_plan_debug_message_level, + (errmsg("pg_hint_plan[qno=0x%x]: " + "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, - (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; - } + ereport(pg_hint_plan_debug_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 string here */ + if (current_hint_str) + return; } - if (hints == NULL) - { - hints = get_hints_from_comment(query); - 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); - } + /* get hints from the comment */ + oldcontext = MemoryContextSwitchTo(TopMemoryContext); + current_hint_str = get_hints_from_comment(query_str); + MemoryContextSwitchTo(oldcontext); + + if (debug_level > 1) + { + 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)"), + errhidestmt(msgqno != qno), + errhidecontext(msgqno != qno))); + else + ereport(pg_hint_plan_debug_message_level, + (errmsg("hints in comment=\"%s\", query=\"%s\", debug_query_string=\"%s\"", + current_hint_str ? current_hint_str : "(none)", + query_str ? query_str : "(none)", + debug_query_string ? debug_query_string : "(none)"), + errhidestmt(msgqno != qno), + errhidecontext(msgqno != qno))); + msgqno = qno; } +} - hstate = create_hintstate(parse, hints); +/* + * Read and set up hint information + */ +static PlannedStmt * +pg_hint_plan_planner(Query *parse, const char *query_string, int cursorOptions, ParamListInfo boundParams) +{ + int save_nestlevel; + PlannedStmt *result; + HintState *hstate; + const char *prev_hint_str = NULL; /* - * 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. + * Use standard planner if pg_hint_plan is disabled or current nesting + * depth is nesting depth of SPI calls. Other hook functions try to change + * plan with current_hint_state if any, so set it to NULL. */ - if (!hstate) + if (!pg_hint_plan_enable_hint || hint_inhibit_level > 0) + { + if (debug_level > 1) + ereport(pg_hint_plan_debug_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; + } + + /* always retrieve hint from the top-level query string */ + if (plpgsql_recurse_level == 0 && current_hint_str) + { + pfree((void *)current_hint_str); + current_hint_str = NULL; + } + + get_current_hint_string(parse, query_string); + + /* No hint, go the normal way */ + if (!current_hint_str) goto standard_planner_proc; + /* parse the hint into hint state struct */ + hstate = create_hintstate(parse, pstrdup(current_hint_str)); + + /* run standard planner if we're given with no valid hints */ + if (!hstate) + { + /* forget invalid hint string */ + if (current_hint_str) + { + pfree((void *)current_hint_str); + current_hint_str = NULL; + } + + goto standard_planner_proc; + } + /* * Push new hint struct to the hint stack to disable previous hint context. */ @@ -2923,7 +2932,10 @@ pg_hint_plan_planner(Query *parse, int cursorOptions, ParamListInfo boundParams) 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; @@ -2938,23 +2950,36 @@ pg_hint_plan_planner(Query *parse, int cursorOptions, ParamListInfo boundParams) if (debug_level > 1) { - ereport(pg_hint_plan_message_level, - (errhidestmt(hidestmt), - errmsg("pg_hint_plan%s: planner: %s", - qnostr, msgstr))); - hidestmt = true; + ereport(pg_hint_plan_debug_message_level, + (errhidestmt(msgqno != qno), + errmsg("pg_hint_plan%s: planner", qnostr))); + msgqno = qno; } /* + * 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; + current_hint_str = NULL; + + /* * Use PG_TRY mechanism to recover GUC parameters and current_hint_state to * the state when this planner started when error occurred in planner. */ PG_TRY(); { if (prev_planner) - result = (*prev_planner) (parse, cursorOptions, boundParams); + result = (*prev_planner) (parse, query_string, + cursorOptions, boundParams); else - result = standard_planner(parse, cursorOptions, boundParams); + result = standard_planner(parse, query_string, + cursorOptions, boundParams); + + current_hint_str = prev_hint_str; + recurse_level--; } PG_CATCH(); { @@ -2962,12 +2987,24 @@ 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; + } + /* Print hint in debug mode. */ if (debug_level == 1) HintStateDump(current_hint_state); @@ -2986,17 +3023,25 @@ pg_hint_plan_planner(Query *parse, int cursorOptions, ParamListInfo boundParams) 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; + ereport(pg_hint_plan_debug_message_level, + (errhidestmt(msgqno != qno), + errmsg("pg_hint_plan%s: planner: no valid hint", + qnostr))); + msgqno = qno; } current_hint_state = NULL; if (prev_planner) - return (*prev_planner) (parse, cursorOptions, boundParams); + result = (*prev_planner) (parse, query_string, + cursorOptions, boundParams); else - return standard_planner(parse, cursorOptions, boundParams); + result = standard_planner(parse, query_string, + cursorOptions, boundParams); + + /* The upper-level planner still needs the current hint state */ + if (HintStateStack != NIL) + current_hint_state = (HintState *) lfirst(list_head(HintStateStack)); + + return result; } /* @@ -3089,16 +3134,17 @@ find_parallel_hint(PlannerInfo *root, Index relid) rel = root->simple_rel_array[relid]; /* - * 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. + * Parallel planning is appliable only on base relation, which has + * RelOptInfo. */ - if (rel && - rel->reloptkind != RELOPT_BASEREL && - rel->reloptkind != RELOPT_OTHER_MEMBER_REL) + if (!rel) + return NULL; + + /* + * 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; /* @@ -3107,11 +3153,6 @@ find_parallel_hint(PlannerInfo *root, Index relid) rte = root->simple_rte_array[relid]; Assert(rte); - /* We don't hint on other than relation and foreign tables */ - if (rte->rtekind != RTE_RELATION || - rte->relkind == RELKIND_FOREIGN_TABLE) - return NULL; - /* Find parallel method hint, which matches given names, from the list. */ for (i = 0; i < current_hint_state->num_hints[HINT_TYPE_PARALLEL]; i++) { @@ -3181,7 +3222,6 @@ 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]; @@ -3211,7 +3251,6 @@ restrict_indexes(PlannerInfo *root, ScanMethodHint *hint, RelOptInfo *rel, * Leaving only an specified index, we delete it from a IndexOptInfo list * other than it. */ - prev = NULL; if (debug_level > 0) initStringInfo(&buf); @@ -3222,8 +3261,7 @@ restrict_indexes(PlannerInfo *root, ScanMethodHint *hint, RelOptInfo *rel, ListCell *l; bool use_index = false; - next = lnext(cell); - + next = lnext(rel->indexlist, cell); foreach(l, hint->indexnames) { char *hintname = (char *) lfirst(l); @@ -3289,7 +3327,7 @@ restrict_indexes(PlannerInfo *root, ScanMethodHint *hint, RelOptInfo *rel, break; c_attname = get_attname(relationObjectId, - info->indexkeys[i]); + info->indexkeys[i], false); /* deny if any of column attributes don't match */ if (strcmp(p_attname, c_attname) != 0 || @@ -3316,7 +3354,7 @@ restrict_indexes(PlannerInfo *root, ScanMethodHint *hint, RelOptInfo *rel, /* check expressions if both expressions are available */ if (p_info->expression_str && - !heap_attisnull(ht_idx, Anum_pg_index_indexprs)) + !heap_attisnull(ht_idx, Anum_pg_index_indexprs, NULL)) { Datum exprsDatum; bool isnull; @@ -3347,7 +3385,7 @@ restrict_indexes(PlannerInfo *root, ScanMethodHint *hint, RelOptInfo *rel, /* compare index predicates */ if (p_info->indpred_str && - !heap_attisnull(ht_idx, Anum_pg_index_indpred)) + !heap_attisnull(ht_idx, Anum_pg_index_indpred, NULL)) { Datum predDatum; bool isnull; @@ -3393,10 +3431,18 @@ restrict_indexes(PlannerInfo *root, ScanMethodHint *hint, RelOptInfo *rel, } if (!use_index) - rel->indexlist = list_delete_cell(rel->indexlist, cell, prev); - else - prev = cell; + { + rel->indexlist = list_delete_cell(rel->indexlist, cell); + /* + * The cells after the deleted cell have been moved towards the + * list head by 1 element. the next iteration should visit the + * cell at the same address if any. + */ + if (next) + next = cell; + } + pfree(indexname); } @@ -3418,7 +3464,7 @@ restrict_indexes(PlannerInfo *root, ScanMethodHint *hint, RelOptInfo *rel, initStringInfo(&rel_buf); quote_value(&rel_buf, disprelname); - ereport(LOG, + ereport(pg_hint_plan_debug_message_level, (errmsg("available indexes for %s(%s):%s", hint->base.keyword, rel_buf.data, @@ -3452,9 +3498,14 @@ get_parent_index_info(Oid indexoid, Oid relid) p_info->opclass = (Oid *) palloc(sizeof(Oid) * index->indnatts); p_info->indoption = (int16 *) palloc(sizeof(Oid) * index->indnatts); + /* + * Collect relation attribute names of index columns for index + * identification, not index attribute names. NULL means expression index + * columns. + */ for (i = 0; i < index->indnatts; i++) { - attname = get_attname(relid, index->indkey.values[i]); + attname = get_attname(relid, index->indkey.values[i], true); p_info->column_names = lappend(p_info->column_names, attname); p_info->indcollation[i] = indexRelation->rd_indcollation[i]; @@ -3466,7 +3517,8 @@ get_parent_index_info(Oid indexoid, Oid relid) * to check to match the expression's parameter of index with child indexes */ p_info->expression_str = NULL; - if(!heap_attisnull(indexRelation->rd_indextuple, Anum_pg_index_indexprs)) + if(!heap_attisnull(indexRelation->rd_indextuple, Anum_pg_index_indexprs, + NULL)) { Datum exprsDatum; bool isnull; @@ -3486,7 +3538,8 @@ get_parent_index_info(Oid indexoid, Oid relid) * to check to match the predicate's parameter of index with child indexes */ p_info->indpred_str = NULL; - if(!heap_attisnull(indexRelation->rd_indextuple, Anum_pg_index_indpred)) + if(!heap_attisnull(indexRelation->rd_indextuple, Anum_pg_index_indpred, + NULL)) { Datum predDatum; bool isnull; @@ -3522,7 +3575,7 @@ reset_hint_enforcement() * bitmap of HintTypeBitmap. If shint or phint is not NULL, set used hint * there respectively. */ -static bool +static int setup_hint_enforcement(PlannerInfo *root, RelOptInfo *rel, ScanMethodHint **rshint, ParallelHint **rphint) { @@ -3547,8 +3600,18 @@ setup_hint_enforcement(PlannerInfo *root, RelOptInfo *rel, */ if (inhparent) { + /* set up only parallel hints for parent relation */ + phint = find_parallel_hint(root, rel->relid); + if (phint) + { + setup_parallel_plan_enforcement(phint, current_hint_state); + if (rphint) *rphint = phint; + ret |= HINT_BM_PARALLEL; + return ret; + } + if (debug_level > 1) - ereport(pg_hint_plan_message_level, + ereport(pg_hint_plan_debug_message_level, (errhidestmt(true), errmsg ("pg_hint_plan%s: setup_hint_enforcement" " skipping inh parent: relation=%u(%s), inhparent=%d," @@ -3559,20 +3622,13 @@ setup_hint_enforcement(PlannerInfo *root, RelOptInfo *rel, return 0; } - /* Find the parent for this relation other than the registered parent */ - foreach (l, root->append_rel_list) + if (bms_num_members(rel->top_parent_relids) == 1) { - AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); - - if (appinfo->child_relid == rel->relid) - { - if (current_hint_state->parent_relid != appinfo->parent_relid) - new_parent_relid = appinfo->parent_relid; - break; - } + new_parent_relid = bms_next_member(rel->top_parent_relids, -1); + current_hint_state->current_root = root; + Assert(new_parent_relid > 0); } - - if (!l) + else { /* This relation doesn't have a parent. Cancel current_hint_state. */ current_hint_state->parent_relid = 0; @@ -3584,8 +3640,8 @@ setup_hint_enforcement(PlannerInfo *root, RelOptInfo *rel, { /* * Here we found a new parent for the current relation. Scan continues - * hint to other childrens of this parent so remember it * to avoid - * hinthintredundant setup cost. + * hint to other childrens of this parent so remember it to avoid + * redundant setup cost. */ current_hint_state->parent_relid = new_parent_relid; @@ -3614,7 +3670,7 @@ setup_hint_enforcement(PlannerInfo *root, RelOptInfo *rel, parentrel_oid = root->simple_rte_array[current_hint_state->parent_relid]->relid; - parent_rel = heap_open(parentrel_oid, NoLock); + parent_rel = table_open(parentrel_oid, NoLock); /* Search the parent relation for indexes match the hint spec */ foreach(l, RelationGetIndexList(parent_rel)) @@ -3638,7 +3694,7 @@ setup_hint_enforcement(PlannerInfo *root, RelOptInfo *rel, lappend(current_hint_state->parent_index_infos, parent_index_info); } - heap_close(parent_rel, NoLock); + table_close(parent_rel, NoLock); } } } @@ -3667,7 +3723,7 @@ setup_hint_enforcement(PlannerInfo *root, RelOptInfo *rel, if (shint == current_hint_state->parent_scan_hint) additional_message = " by parent hint"; - ereport(pg_hint_plan_message_level, + ereport(pg_hint_plan_debug_message_level, (errhidestmt(true), errmsg ("pg_hint_plan%s: setup_hint_enforcement" " index deletion%s:" @@ -3697,7 +3753,7 @@ setup_hint_enforcement(PlannerInfo *root, RelOptInfo *rel, if (!shint && ! phint) { if (debug_level > 1) - ereport(pg_hint_plan_message_level, + ereport(pg_hint_plan_debug_message_level, (errhidestmt (true), errmsg ("pg_hint_plan%s: setup_hint_enforcement" " no hint applied:" @@ -3799,6 +3855,30 @@ find_join_hint(Relids joinrelids) return NULL; } + +/* + * Return memoize hint which matches given joinrelids. + */ +static JoinMethodHint * +find_memoize_hint(Relids joinrelids) +{ + List *join_hint; + ListCell *l; + + join_hint = current_hint_state->memoize_hint_level[bms_num_members(joinrelids)]; + + foreach(l, join_hint) + { + JoinMethodHint *hint = (JoinMethodHint *) lfirst(l); + + if (bms_equal(joinrelids, hint->joinrelids)) + return hint; + } + + return NULL; +} + + static Relids OuterInnerJoinCreate(OuterInnerRels *outer_inner, LeadingHint *leading_hint, PlannerInfo *root, List *initial_rels, HintState *hstate, int nbaserel) @@ -3818,8 +3898,8 @@ OuterInnerJoinCreate(OuterInnerRels *outer_inner, LeadingHint *leading_hint, leading_hint->base.hint_str)); } - outer_rels = lfirst(outer_inner->outer_inner_pair->head); - inner_rels = lfirst(outer_inner->outer_inner_pair->tail); + outer_rels = linitial(outer_inner->outer_inner_pair); + inner_rels = llast(outer_inner->outer_inner_pair); outer_relids = OuterInnerJoinCreate(outer_rels, leading_hint, @@ -3954,6 +4034,24 @@ transform_join_hints(HintState *hstate, PlannerInfo *root, int nbaserel, lappend(hstate->join_hint_level[hint->nrels], hint); } + /* ditto for memoize hints */ + for (i = 0; i < hstate->num_hints[HINT_TYPE_MEMOIZE]; i++) + { + JoinMethodHint *hint = hstate->join_hints[i]; + + if (!hint_state_enabled(hint) || hint->nrels > nbaserel) + continue; + + hint->joinrelids = create_bms_of_relids(&(hint->base), root, + initial_rels, hint->nrels, hint->relnames); + + if (hint->joinrelids == NULL || hint->base.state == HINT_STATE_ERROR) + continue; + + hstate->memoize_hint_level[hint->nrels] = + lappend(hstate->memoize_hint_level[hint->nrels], hint); + } + /* * Create bitmap of relids from alias names for each rows hint. * Bitmaps are more handy than strings in join searching. @@ -4126,14 +4224,13 @@ transform_join_hints(HintState *hstate, PlannerInfo *root, int nbaserel, { if (hstate->join_hint_level[i] != NIL) { - ListCell *prev = NULL; ListCell *next = NULL; for(l = list_head(hstate->join_hint_level[i]); l; l = next) { JoinMethodHint *hint = (JoinMethodHint *)lfirst(l); - next = lnext(l); + next = lnext(hstate->join_hint_level[i], l); if (hint->inner_nrels == 0 && !(bms_intersect(hint->joinrelids, joinrelids) == NULL || @@ -4141,11 +4238,16 @@ transform_join_hints(HintState *hstate, PlannerInfo *root, int nbaserel, hint->joinrelids))) { hstate->join_hint_level[i] = - list_delete_cell(hstate->join_hint_level[i], l, - prev); + list_delete_cell(hstate->join_hint_level[i], l); + /* + * The cells after the deleted cell have been moved + * towards the list head by 1 element. the next + * iteration should visit the cell at the same address + * if any. + */ + if (next) + next = l; } - else - prev = l; } } } @@ -4155,7 +4257,8 @@ transform_join_hints(HintState *hstate, PlannerInfo *root, int nbaserel, if (hint_state_enabled(lhint)) { - set_join_config_options(DISABLE_ALL_JOIN, current_hint_state->context); + set_join_config_options(DISABLE_ALL_JOIN, false, + current_hint_state->context); return true; } return false; @@ -4171,34 +4274,57 @@ static RelOptInfo * make_join_rel_wrapper(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2) { Relids joinrelids; - JoinMethodHint *hint; + JoinMethodHint *join_hint; + JoinMethodHint *memoize_hint; RelOptInfo *rel; int save_nestlevel; joinrelids = bms_union(rel1->relids, rel2->relids); - hint = find_join_hint(joinrelids); + join_hint = find_join_hint(joinrelids); + memoize_hint = find_memoize_hint(joinrelids); bms_free(joinrelids); - if (!hint) - return pg_hint_plan_make_join_rel(root, rel1, rel2); + /* reject non-matching hints */ + if (join_hint && join_hint->inner_nrels != 0) + join_hint = NULL; + + if (memoize_hint && memoize_hint->inner_nrels != 0) + memoize_hint = NULL; - if (hint->inner_nrels == 0) + if (join_hint || memoize_hint) { save_nestlevel = NewGUCNestLevel(); - set_join_config_options(hint->enforce_mask, - current_hint_state->context); + if (join_hint) + set_join_config_options(join_hint->enforce_mask, false, + current_hint_state->context); - rel = pg_hint_plan_make_join_rel(root, rel1, rel2); - hint->base.state = HINT_STATE_USED; + if (memoize_hint) + { + bool memoize = + memoize_hint->base.hint_keyword == HINT_KEYWORD_MEMOIZE; + set_config_option_noerror("enable_memoize", + memoize ? "true" : "false", + current_hint_state->context, + PGC_S_SESSION, GUC_ACTION_SAVE, + true, ERROR); + } + } + + /* do the work */ + rel = pg_hint_plan_make_join_rel(root, rel1, rel2); + + /* Restore the GUC variables we set above. */ + if (join_hint || memoize_hint) + { + if (join_hint) + join_hint->base.state = HINT_STATE_USED; + + if (memoize_hint) + memoize_hint->base.state = HINT_STATE_USED; - /* - * Restore the GUC variables we set above. - */ AtEOXact_GUC(true, save_nestlevel); } - else - rel = pg_hint_plan_make_join_rel(root, rel1, rel2); return rel; } @@ -4217,42 +4343,63 @@ add_paths_to_joinrel_wrapper(PlannerInfo *root, { Relids joinrelids; JoinMethodHint *join_hint; + JoinMethodHint *memoize_hint; int save_nestlevel; joinrelids = bms_union(outerrel->relids, innerrel->relids); join_hint = find_join_hint(joinrelids); + memoize_hint = find_memoize_hint(joinrelids); bms_free(joinrelids); - if (join_hint && join_hint->inner_nrels != 0) + /* reject the found hints if they don't match this join */ + if (join_hint && join_hint->inner_nrels == 0) + join_hint = NULL; + + if (memoize_hint && memoize_hint->inner_nrels == 0) + memoize_hint = NULL; + + /* set up configuration if needed */ + if (join_hint || memoize_hint) { save_nestlevel = NewGUCNestLevel(); - if (bms_equal(join_hint->inner_joinrelids, innerrel->relids)) + if (join_hint) { - - set_join_config_options(join_hint->enforce_mask, - current_hint_state->context); - - add_paths_to_joinrel(root, joinrel, outerrel, innerrel, jointype, - sjinfo, restrictlist); - join_hint->base.state = HINT_STATE_USED; + if (bms_equal(join_hint->inner_joinrelids, innerrel->relids)) + set_join_config_options(join_hint->enforce_mask, false, + current_hint_state->context); + else + set_join_config_options(DISABLE_ALL_JOIN, false, + current_hint_state->context); } - else + + if (memoize_hint) { - set_join_config_options(DISABLE_ALL_JOIN, - current_hint_state->context); - add_paths_to_joinrel(root, joinrel, outerrel, innerrel, jointype, - sjinfo, restrictlist); + bool memoize = + memoize_hint->base.hint_keyword == HINT_KEYWORD_MEMOIZE; + set_config_option_noerror("enable_memoize", + memoize ? "true" : "false", + current_hint_state->context, + PGC_S_SESSION, GUC_ACTION_SAVE, + true, ERROR); } + } + + /* generate paths */ + add_paths_to_joinrel(root, joinrel, outerrel, innerrel, jointype, + sjinfo, restrictlist); + + /* restore GUC variables */ + if (join_hint || memoize_hint) + { + if (join_hint) + join_hint->base.state = HINT_STATE_USED; + + if (memoize_hint) + memoize_hint->base.state = HINT_STATE_USED; - /* - * Restore the GUC variables we set above. - */ AtEOXact_GUC(true, save_nestlevel); } - else - add_paths_to_joinrel(root, joinrel, outerrel, innerrel, jointype, - sjinfo, restrictlist); } static int @@ -4315,6 +4462,8 @@ pg_hint_plan_join_search(PlannerInfo *root, int levels_needed, current_hint_state->join_hint_level = palloc0(sizeof(List *) * (nbaserel + 1)); join_method_hints = palloc0(sizeof(JoinMethodHint *) * (nbaserel + 1)); + current_hint_state->memoize_hint_level = + palloc0(sizeof(List *) * (nbaserel + 1)); leading_hint_enable = transform_join_hints(current_hint_state, root, nbaserel, @@ -4322,6 +4471,38 @@ pg_hint_plan_join_search(PlannerInfo *root, int levels_needed, rel = pg_hint_plan_standard_join_search(root, levels_needed, initial_rels); + /* + * Adjust number of parallel workers of the result rel to the largest + * number of the component paths. + */ + if (current_hint_state->num_hints[HINT_TYPE_PARALLEL] > 0) + { + ListCell *lc; + int nworkers = 0; + + foreach (lc, initial_rels) + { + ListCell *lcp; + RelOptInfo *rel = (RelOptInfo *) lfirst(lc); + + foreach (lcp, rel->partial_pathlist) + { + Path *path = (Path *) lfirst(lcp); + + if (nworkers < path-> parallel_workers) + nworkers = path-> parallel_workers; + } + } + + foreach (lc, rel->partial_pathlist) + { + Path *path = (Path *) lfirst(lc); + + if (path->parallel_safe && path->parallel_workers < nworkers) + path->parallel_workers = nworkers; + } + } + for (i = 2; i <= nbaserel; i++) { list_free(current_hint_state->join_hint_level[i]); @@ -4335,7 +4516,7 @@ pg_hint_plan_join_search(PlannerInfo *root, int levels_needed, pfree(join_method_hints); if (leading_hint_enable) - set_join_config_options(current_hint_state->init_join_mask, + set_join_config_options(current_hint_state->init_join_mask, true, current_hint_state->context); return rel; @@ -4368,68 +4549,53 @@ pg_hint_plan_set_rel_pathlist(PlannerInfo * root, RelOptInfo *rel, * We can accept only plain relations, foreign tables and table saples are * also unacceptable. See set_rel_pathlist. */ - if (rel->rtekind != RTE_RELATION || + if ((rel->rtekind != RTE_RELATION && + rel->rtekind != RTE_SUBQUERY)|| 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) + /* + * Even though UNION ALL node doesn't have particular name so usually it is + * unhintable, turn on parallel when it contains parallel nodes. + */ + if (rel->rtekind == RTE_SUBQUERY) { - /* - * With scan hints, we regenerate paths for this relation from the - * first under the restricion. - */ - list_free_deep(rel->pathlist); - rel->pathlist = NIL; + ListCell *lc; + bool inhibit_nonparallel = false; - set_plain_rel_pathlist(root, rel, rte); - } - - if (found_hints & HINT_BM_PARALLEL) - { - Assert (phint); + if (rel->partial_pathlist == NIL) + return; - /* the partial_pathlist may be for different parameters, discard it */ - if (rel->partial_pathlist) + foreach(lc, rel->partial_pathlist) { - list_free_deep(rel->partial_pathlist); - rel->partial_pathlist = NIL; - } + ListCell *lcp; + AppendPath *apath = (AppendPath *) lfirst(lc); + int parallel_workers = 0; - /* also remove gather path */ - if (rel->pathlist) - { - ListCell *cell, *prev = NULL; + if (!IsA(apath, AppendPath)) + continue; - foreach (cell, rel->pathlist) + foreach (lcp, apath->subpaths) { - Path *path = (Path *) lfirst(cell); - - if (IsA(path, GatherPath)) - rel->pathlist = list_delete_cell(rel->pathlist, - cell, prev); - else - prev = cell; + Path *spath = (Path *) lfirst(lcp); + + if (spath->parallel_aware && + parallel_workers < spath->parallel_workers) + parallel_workers = spath->parallel_workers; } + + apath->path.parallel_workers = parallel_workers; + inhibit_nonparallel = true; } - /* then generate new paths if needed */ - if (phint->nworkers > 0) + if (inhibit_nonparallel) { - /* Lower the priorities of non-parallel paths */ - foreach (l, rel->pathlist) + ListCell *lc; + + foreach(lc, rel->pathlist) { - Path *path = (Path *) lfirst(l); + Path *path = (Path *) lfirst(lc); if (path->startup_cost < disable_cost) { @@ -4437,86 +4603,116 @@ pg_hint_plan_set_rel_pathlist(PlannerInfo * root, RelOptInfo *rel, path->total_cost += disable_cost; } } + } - /* - * generate partial paths with enforcement, this is affected by - * scan method enforcement. Specifically, the cost of this partial - * seqscan path will be disabled_cost if seqscan is inhibited by - * hint or GUC parameters. - */ - Assert (rel->partial_pathlist == NIL); - create_plain_partial_paths(root, rel); + 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) + { + /* + * When hint is specified on non-parent relations, discard existing + * paths and regenerate based on the hint considered. Otherwise we + * already have hinted childx paths then just adjust the number of + * planned number of workers. + */ + if (root->simple_rte_array[rel->relid]->inh) + { /* enforce number of workers if requested */ - if (phint->force_parallel) + if (phint && phint->force_parallel) { - foreach (l, rel->partial_pathlist) + if (phint->nworkers == 0) + { + list_free_deep(rel->partial_pathlist); + rel->partial_pathlist = NIL; + } + else { - Path *ppath = (Path *) lfirst(l); + /* prioritize partial paths */ + foreach (l, rel->partial_pathlist) + { + Path *ppath = (Path *) lfirst(l); - ppath->parallel_workers = phint->nworkers; + if (ppath->parallel_safe) + { + ppath->parallel_workers = phint->nworkers; + ppath->startup_cost = 0; + ppath->total_cost = 0; + } + } + + /* disable non-partial paths */ + foreach (l, rel->pathlist) + { + Path *ppath = (Path *) lfirst(l); + + if (ppath->startup_cost < disable_cost) + { + ppath->startup_cost += disable_cost; + ppath->total_cost += disable_cost; + } + } } } - - /* Generate gather paths for base rels */ - if (rel->reloptkind == RELOPT_BASEREL) - generate_gather_paths(root, rel); } - } + else + { + /* Just discard all the paths considered so far */ + list_free_deep(rel->pathlist); + rel->pathlist = NIL; + list_free_deep(rel->partial_pathlist); + rel->partial_pathlist = NIL; - reset_hint_enforcement(); -} + /* Regenerate paths with the current enforcement */ + set_plain_rel_pathlist(root, rel, rte); -/* - * set_rel_pathlist - * Build access paths for a base relation - * - * This function was copied and edited from set_rel_pathlist() in - * 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, - Index rti, RangeTblEntry *rte) -{ - if (IS_DUMMY_REL(rel)) - { - /* We already proved the relation empty, so nothing more to do */ - } - else if (rte->inh) - { - /* It's an "append relation", process accordingly */ - set_append_rel_pathlist(root, rel, rti, rte); - } - else - { - if (rel->rtekind == RTE_RELATION) - { - if (rte->relkind == RELKIND_RELATION) + /* Additional work to enforce parallel query execution */ + if (phint && phint->nworkers > 0) { - if(rte->tablesample != NULL) - elog(ERROR, "sampled relation is not supported"); + /* + * For Parallel Append to be planned properly, we shouldn't set + * the costs of non-partial paths to disable-value. Lower the + * priority of non-parallel paths by setting partial path costs + * to 0 instead. + */ + foreach (l, rel->partial_pathlist) + { + Path *path = (Path *) lfirst(l); - /* Plain relation */ - set_plain_rel_pathlist(root, rel, rte); + path->startup_cost = 0; + path->total_cost = 0; + } + + /* enforce number of workers if requested */ + if (phint->force_parallel) + { + foreach (l, rel->partial_pathlist) + { + Path *ppath = (Path *) lfirst(l); + + if (ppath->parallel_safe) + ppath->parallel_workers = phint->nworkers; + } + } + + /* Generate gather paths */ + if (rel->reloptkind == RELOPT_BASEREL && + bms_membership(root->all_baserels) != BMS_SINGLETON) + generate_gather_paths(root, rel, false); } - else - elog(ERROR, "unexpected relkind: %c", rte->relkind); } - else - elog(ERROR, "unexpected rtekind: %d", (int) rel->rtekind); } - /* - * Allow a plugin to editorialize on the set of Paths for this base - * relation. It could add new paths (such as CustomPaths) by calling - * add_path(), or delete or modify paths added by the core code. - */ - if (set_rel_pathlist_hook) - (*set_rel_pathlist_hook) (root, rel, rti, rte); - - /* Now find the cheapest of the paths for this rel */ - set_cheapest(rel); + reset_hint_enforcement(); } /* @@ -4549,12 +4745,20 @@ void plpgsql_query_erase_callback(ResourceReleasePhase phase, bool isTopLevel, void *arg) { - if (phase != RESOURCE_RELEASE_AFTER_LOCKS) + if (!isTopLevel || phase != RESOURCE_RELEASE_AFTER_LOCKS) return; /* Cancel plpgsql nest level*/ plpgsql_recurse_level = 0; } + +/* include core static functions */ +static void populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1, + RelOptInfo *rel2, RelOptInfo *joinrel, + SpecialJoinInfo *sjinfo, List *restrictlist); +static void set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, + Index rti, RangeTblEntry *rte); + #define standard_join_search pg_hint_plan_standard_join_search #define join_search_one_level pg_hint_plan_join_search_one_level #define make_join_rel make_join_rel_wrapper