X-Git-Url: http://git.osdn.net/view?a=blobdiff_plain;f=pg_hint_plan.c;h=63866f51af2129ac9098df7f043dd2fc841d6068;hb=f1d7d8d1ad80227bf9241286bf27738a09bc700d;hp=5d9e68980b017f937cfce5fd350942691c79e687;hpb=765ed29846f4744757c8fe1a14b9ea6b17c9acdb;p=pghintplan%2Fpg_hint_plan.git diff --git a/pg_hint_plan.c b/pg_hint_plan.c index 5d9e689..63866f5 100644 --- a/pg_hint_plan.c +++ b/pg_hint_plan.c @@ -4,7 +4,7 @@ * do instructions or hints to the planner using C-style block comments * of the SQL. * - * Copyright (c) 2012-2013, NIPPON TELEGRAPH AND TELEPHONE CORPORATION + * Copyright (c) 2012-2017, NIPPON TELEGRAPH AND TELEPHONE CORPORATION * *------------------------------------------------------------------------- */ @@ -15,6 +15,7 @@ #include "mb/pg_wchar.h" #include "miscadmin.h" #include "nodes/nodeFuncs.h" +#include "nodes/params.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/geqo.h" @@ -25,19 +26,22 @@ #include "optimizer/planner.h" #include "optimizer/prep.h" #include "optimizer/restrictinfo.h" +#include "parser/analyze.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" -#if PG_VERSION_NUM >= 90200 +#include "utils/resowner.h" + #include "catalog/pg_class.h" -#endif #include "executor/spi.h" #include "catalog/pg_type.h" + /* * We have our own header file "plpgsql-9.1", which is necessary to support * hints for queries in PL/pgSQL blocks, in pg_hint_plan source package, @@ -48,23 +52,18 @@ * On the other hand, 9.2 installation provides that header file for external * modules, so we include the header in ordinary place. */ -#if PG_VERSION_NUM >= 90200 #include "plpgsql.h" -#else -#include "plpgsql-9.1.h" -#endif /* partially copied from pg_stat_statements */ #include "normalize_query.h" +/* PostgreSQL 9.3 */ +#include "access/htup_details.h" + #ifdef PG_MODULE_MAGIC PG_MODULE_MAGIC; #endif -#if PG_VERSION_NUM < 90100 -#error unsupported PostgreSQL version -#endif - #define BLOCK_COMMENT_START "/*" #define BLOCK_COMMENT_END "*/" #define HINT_COMMENT_KEYWORD "+" @@ -82,11 +81,9 @@ PG_MODULE_MAGIC; #define HINT_NOINDEXSCAN "NoIndexScan" #define HINT_NOBITMAPSCAN "NoBitmapScan" #define HINT_NOTIDSCAN "NoTidScan" -#if PG_VERSION_NUM >= 90200 #define HINT_INDEXONLYSCAN "IndexOnlyScan" #define HINT_INDEXONLYSCANREGEXP "IndexOnlyScanRegexp" #define HINT_NOINDEXONLYSCAN "NoIndexOnlyScan" -#endif #define HINT_NESTLOOP "NestLoop" #define HINT_MERGEJOIN "MergeJoin" #define HINT_HASHJOIN "HashJoin" @@ -95,13 +92,17 @@ PG_MODULE_MAGIC; #define HINT_NOHASHJOIN "NoHashJoin" #define HINT_LEADING "Leading" #define HINT_SET "Set" +#define HINT_ROWS "Rows" #define HINT_ARRAY_DEFAULT_INITSIZE 8 #define hint_ereport(str, detail) \ - ereport(pg_hint_plan_parse_messages, \ - (errmsg("hint syntax error at or near \"%s\"", (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)) \ @@ -113,9 +114,7 @@ enum ENABLE_INDEXSCAN = 0x02, ENABLE_BITMAPSCAN = 0x04, ENABLE_TIDSCAN = 0x08, -#if PG_VERSION_NUM >= 90200 ENABLE_INDEXONLYSCAN = 0x10 -#endif } SCAN_TYPE_BITS; enum @@ -125,14 +124,9 @@ enum ENABLE_HASHJOIN = 0x04 } JOIN_TYPE_BITS; -#if PG_VERSION_NUM >= 90200 #define ENABLE_ALL_SCAN (ENABLE_SEQSCAN | ENABLE_INDEXSCAN | \ ENABLE_BITMAPSCAN | ENABLE_TIDSCAN | \ ENABLE_INDEXONLYSCAN) -#else -#define ENABLE_ALL_SCAN (ENABLE_SEQSCAN | ENABLE_INDEXSCAN | \ - ENABLE_BITMAPSCAN | ENABLE_TIDSCAN) -#endif #define ENABLE_ALL_JOIN (ENABLE_NESTLOOP | ENABLE_MERGEJOIN | ENABLE_HASHJOIN) #define DISABLE_ALL_SCAN 0 #define DISABLE_ALL_JOIN 0 @@ -150,11 +144,9 @@ typedef enum HintKeyword HINT_KEYWORD_NOINDEXSCAN, HINT_KEYWORD_NOBITMAPSCAN, HINT_KEYWORD_NOTIDSCAN, -#if PG_VERSION_NUM >= 90200 HINT_KEYWORD_INDEXONLYSCAN, HINT_KEYWORD_INDEXONLYSCANREGEXP, HINT_KEYWORD_NOINDEXONLYSCAN, -#endif HINT_KEYWORD_NESTLOOP, HINT_KEYWORD_MERGEJOIN, HINT_KEYWORD_HASHJOIN, @@ -163,6 +155,7 @@ typedef enum HintKeyword HINT_KEYWORD_NOHASHJOIN, HINT_KEYWORD_LEADING, HINT_KEYWORD_SET, + HINT_KEYWORD_ROWS, HINT_KEYWORD_UNRECOGNIZED } HintKeyword; @@ -173,26 +166,28 @@ typedef Hint *(*HintCreateFunction) (const char *hint_str, const char *keyword, HintKeyword hint_keyword); typedef void (*HintDeleteFunction) (Hint *hint); -typedef void (*HintDescFunction) (Hint *hint, StringInfo buf); +typedef void (*HintDescFunction) (Hint *hint, StringInfo buf, bool nolf); typedef int (*HintCmpFunction) (const Hint *a, const Hint *b); typedef const char *(*HintParseFunction) (Hint *hint, HintState *hstate, Query *parse, const char *str); /* hint types */ -#define NUM_HINT_TYPE 4 +#define NUM_HINT_TYPE 5 typedef enum HintType { HINT_TYPE_SCAN_METHOD, HINT_TYPE_JOIN_METHOD, HINT_TYPE_LEADING, - HINT_TYPE_SET + HINT_TYPE_SET, + HINT_TYPE_ROWS, } HintType; static const char *HintTypeName[] = { "scan method", "join method", "leading", - "set" + "set", + "rows", }; /* hint status */ @@ -208,6 +203,11 @@ typedef enum HintStatus #define hint_state_enabled(hint) ((hint)->base.state == HINT_STATE_NOTUSED || \ (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 { @@ -279,6 +279,26 @@ typedef struct SetHint List *words; } SetHint; +/* rows hints */ +typedef enum RowsValueType { + RVT_ABSOLUTE, /* Rows(... #1000) */ + RVT_ADD, /* Rows(... +1000) */ + RVT_SUB, /* Rows(... -1000) */ + RVT_MULTI, /* Rows(... *1.2) */ +} RowsValueType; +typedef struct RowsHint +{ + Hint base; + int nrels; + int inner_nrels; + char **relnames; + Relids joinrelids; + Relids inner_joinrelids; + char *rows_str; + RowsValueType value_type; + double rows; +} RowsHint; + /* * Describes a context of hint processing. */ @@ -298,9 +318,8 @@ struct HintState ScanMethodHint **scan_hints; /* parsed scan hints */ int init_scan_mask; /* initial value scan parameter */ Index parent_relid; /* inherit parent table relid */ - Oid parent_rel_oid; /* inherit parent table relid */ ScanMethodHint *parent_hint; /* inherit parent table scan hint */ - List *parent_index_infos; /* infomation of inherit parent table's + List *parent_index_infos; /* information of inherit parent table's * index */ /* for join method hints */ @@ -314,6 +333,9 @@ struct HintState /* for Set hints */ SetHint **set_hints; /* parsed Set hints */ GucContext context; /* which GUC parameters can we set? */ + + /* for Rows hints */ + RowsHint **rows_hints; /* parsed Rows hints */ }; /* @@ -333,11 +355,7 @@ 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, - ParamListInfo params, bool isTopLevel, - 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, @@ -350,31 +368,40 @@ static RelOptInfo *pg_hint_plan_join_search(PlannerInfo *root, static Hint *ScanMethodHintCreate(const char *hint_str, const char *keyword, HintKeyword hint_keyword); static void ScanMethodHintDelete(ScanMethodHint *hint); -static void ScanMethodHintDesc(ScanMethodHint *hint, StringInfo buf); +static void ScanMethodHintDesc(ScanMethodHint *hint, StringInfo buf, bool nolf); static int ScanMethodHintCmp(const ScanMethodHint *a, const ScanMethodHint *b); static const char *ScanMethodHintParse(ScanMethodHint *hint, HintState *hstate, Query *parse, const char *str); static Hint *JoinMethodHintCreate(const char *hint_str, const char *keyword, HintKeyword hint_keyword); static void JoinMethodHintDelete(JoinMethodHint *hint); -static void JoinMethodHintDesc(JoinMethodHint *hint, StringInfo buf); +static void JoinMethodHintDesc(JoinMethodHint *hint, StringInfo buf, bool nolf); static int JoinMethodHintCmp(const JoinMethodHint *a, const JoinMethodHint *b); static const char *JoinMethodHintParse(JoinMethodHint *hint, HintState *hstate, Query *parse, const char *str); static Hint *LeadingHintCreate(const char *hint_str, const char *keyword, HintKeyword hint_keyword); static void LeadingHintDelete(LeadingHint *hint); -static void LeadingHintDesc(LeadingHint *hint, StringInfo buf); +static void LeadingHintDesc(LeadingHint *hint, StringInfo buf, bool nolf); static int LeadingHintCmp(const LeadingHint *a, const LeadingHint *b); static const char *LeadingHintParse(LeadingHint *hint, HintState *hstate, Query *parse, const char *str); static Hint *SetHintCreate(const char *hint_str, const char *keyword, HintKeyword hint_keyword); static void SetHintDelete(SetHint *hint); -static void SetHintDesc(SetHint *hint, StringInfo buf); +static void SetHintDesc(SetHint *hint, StringInfo buf, bool nolf); static int SetHintCmp(const SetHint *a, const SetHint *b); static const char *SetHintParse(SetHint *hint, HintState *hstate, Query *parse, const char *str); +static Hint *RowsHintCreate(const char *hint_str, const char *keyword, + HintKeyword hint_keyword); +static void RowsHintDelete(RowsHint *hint); +static void RowsHintDesc(RowsHint *hint, StringInfo buf, bool nolf); +static int RowsHintCmp(const RowsHint *a, const RowsHint *b); +static const char *RowsHintParse(RowsHint *hint, HintState *hstate, + Query *parse, const char *str); +static Hint *LeadingHintCreate(const char *hint_str, const char *keyword, + HintKeyword hint_keyword); static void quote_value(StringInfo buf, const char *value); @@ -393,15 +420,13 @@ static void make_rels_by_clauseless_joins(PlannerInfo *root, static bool has_join_restriction(PlannerInfo *root, RelOptInfo *rel); static void set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte); -#if PG_VERSION_NUM >= 90200 static void generate_mergeappend_paths(PlannerInfo *root, RelOptInfo *rel, List *live_childrels, List *all_child_pathkeys); -#endif +static Path *get_cheapest_parameterized_child_path(PlannerInfo *root, + RelOptInfo *rel, + Relids required_outer); static List *accumulate_append_subpath(List *subpaths, Path *path); -#if PG_VERSION_NUM < 90200 -static void set_dummy_rel_pathlist(RelOptInfo *rel); -#endif RelOptInfo *pg_hint_plan_make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2); @@ -409,11 +434,21 @@ static void pg_hint_plan_plpgsql_stmt_beg(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt); static void pg_hint_plan_plpgsql_stmt_end(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt); +static void plpgsql_query_erase_callback(ResourceReleasePhase phase, + bool isCommit, + bool isTopLevel, + void *arg); /* GUC variables */ static bool pg_hint_plan_enable_hint = true; -static bool pg_hint_plan_debug_print = false; -static int pg_hint_plan_parse_messages = INFO; +static int debug_level = 0; +static int pg_hint_plan_message_level = INFO; +/* Default is off, to keep backward compatibility. */ +static bool pg_hint_plan_enable_hint_table = false; + +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) */ static const struct config_enum_entry parse_messages_level_options[] = { {"debug", DEBUG2, true}, @@ -434,8 +469,24 @@ static const struct config_enum_entry parse_messages_level_options[] = { {NULL, 0, false} }; +static const struct config_enum_entry parse_debug_level_options[] = { + {"off", 0, false}, + {"on", 1, false}, + {"detailed", 2, false}, + {"verbose", 3, false}, + {"0", 0, true}, + {"1", 1, true}, + {"2", 2, true}, + {"3", 3, true}, + {"no", 0, true}, + {"yes", 1, true}, + {"false", 0, true}, + {"true", 1, true}, + {NULL, 0, false} +}; + /* 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; @@ -467,12 +518,10 @@ static const HintParser parsers[] = { {HINT_NOINDEXSCAN, ScanMethodHintCreate, HINT_KEYWORD_NOINDEXSCAN}, {HINT_NOBITMAPSCAN, ScanMethodHintCreate, HINT_KEYWORD_NOBITMAPSCAN}, {HINT_NOTIDSCAN, ScanMethodHintCreate, HINT_KEYWORD_NOTIDSCAN}, -#if PG_VERSION_NUM >= 90200 {HINT_INDEXONLYSCAN, ScanMethodHintCreate, HINT_KEYWORD_INDEXONLYSCAN}, {HINT_INDEXONLYSCANREGEXP, ScanMethodHintCreate, HINT_KEYWORD_INDEXONLYSCANREGEXP}, {HINT_NOINDEXONLYSCAN, ScanMethodHintCreate, HINT_KEYWORD_NOINDEXONLYSCAN}, -#endif {HINT_NESTLOOP, JoinMethodHintCreate, HINT_KEYWORD_NESTLOOP}, {HINT_MERGEJOIN, JoinMethodHintCreate, HINT_KEYWORD_MERGEJOIN}, {HINT_HASHJOIN, JoinMethodHintCreate, HINT_KEYWORD_HASHJOIN}, @@ -481,14 +530,10 @@ static const HintParser parsers[] = { {HINT_NOHASHJOIN, JoinMethodHintCreate, HINT_KEYWORD_NOHASHJOIN}, {HINT_LEADING, LeadingHintCreate, HINT_KEYWORD_LEADING}, {HINT_SET, SetHintCreate, HINT_KEYWORD_SET}, + {HINT_ROWS, RowsHintCreate, HINT_KEYWORD_ROWS}, {NULL, NULL, HINT_KEYWORD_UNRECOGNIZED} }; -/* - * PL/pgSQL plugin for retrieving string representation of each query during - * function execution. - */ -const char *plpgsql_query_string = NULL; PLpgSQL_plugin plugin_funcs = { NULL, NULL, @@ -513,17 +558,18 @@ _PG_init(void) NULL, &pg_hint_plan_enable_hint, true, - PGC_USERSET, + PGC_USERSET, 0, NULL, NULL, NULL); - DefineCustomBoolVariable("pg_hint_plan.debug_print", + DefineCustomEnumVariable("pg_hint_plan.debug_print", "Logs results of hint parsing.", NULL, - &pg_hint_plan_debug_print, + &debug_level, false, + parse_debug_level_options, PGC_USERSET, 0, NULL, @@ -533,7 +579,19 @@ _PG_init(void) DefineCustomEnumVariable("pg_hint_plan.parse_messages", "Message level of parse errors.", NULL, - &pg_hint_plan_parse_messages, + &pg_hint_plan_message_level, + INFO, + parse_messages_level_options, + PGC_USERSET, + 0, + NULL, + NULL, + NULL); + + DefineCustomEnumVariable("pg_hint_plan.message_level", + "Message level of debug messages.", + NULL, + &pg_hint_plan_message_level, INFO, parse_messages_level_options, PGC_USERSET, @@ -542,9 +600,20 @@ _PG_init(void) NULL, NULL); + DefineCustomBoolVariable("pg_hint_plan.enable_hint_table", + "Force planner to not get hint by using table lookups.", + NULL, + &pg_hint_plan_enable_hint_table, + false, + PGC_USERSET, + 0, + NULL, + NULL, + 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; @@ -555,6 +624,8 @@ _PG_init(void) /* setup PL/pgSQL plugin hook */ var_ptr = (PLpgSQL_plugin **) find_rendezvous_variable("PLpgSQL_plugin"); *var_ptr = &plugin_funcs; + + RegisterResourceReleaseCallback(plpgsql_query_erase_callback, NULL); } /* @@ -567,7 +638,7 @@ _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; @@ -735,6 +806,54 @@ SetHintDelete(SetHint *hint) pfree(hint); } +static Hint * +RowsHintCreate(const char *hint_str, const char *keyword, + HintKeyword hint_keyword) +{ + RowsHint *hint; + + hint = palloc(sizeof(RowsHint)); + hint->base.hint_str = hint_str; + hint->base.keyword = keyword; + hint->base.hint_keyword = hint_keyword; + hint->base.type = HINT_TYPE_ROWS; + hint->base.state = HINT_STATE_NOTUSED; + hint->base.delete_func = (HintDeleteFunction) RowsHintDelete; + hint->base.desc_func = (HintDescFunction) RowsHintDesc; + hint->base.cmp_func = (HintCmpFunction) RowsHintCmp; + hint->base.parse_func = (HintParseFunction) RowsHintParse; + hint->nrels = 0; + hint->inner_nrels = 0; + hint->relnames = NULL; + hint->joinrelids = NULL; + hint->inner_joinrelids = NULL; + hint->rows_str = NULL; + hint->value_type = RVT_ABSOLUTE; + hint->rows = 0; + + return (Hint *) hint; +} + +static void +RowsHintDelete(RowsHint *hint) +{ + if (!hint) + return; + + if (hint->relnames) + { + int i; + + for (i = 0; i < hint->nrels; i++) + pfree(hint->relnames[i]); + pfree(hint->relnames); + } + + bms_free(hint->joinrelids); + bms_free(hint->inner_joinrelids); + pfree(hint); +} + static HintState * HintStateCreate(void) { @@ -749,7 +868,6 @@ HintStateCreate(void) hstate->scan_hints = NULL; hstate->init_scan_mask = 0; hstate->parent_relid = 0; - hstate->parent_rel_oid = InvalidOid; hstate->parent_hint = NULL; hstate->parent_index_infos = NIL; hstate->join_hints = NULL; @@ -758,6 +876,7 @@ HintStateCreate(void) hstate->leading_hint = NULL; hstate->context = superuser() ? PGC_SUSET : PGC_USERSET; hstate->set_hints = NULL; + hstate->rows_hints = NULL; return hstate; } @@ -813,7 +932,7 @@ quote_value(StringInfo buf, const char *value) } static void -ScanMethodHintDesc(ScanMethodHint *hint, StringInfo buf) +ScanMethodHintDesc(ScanMethodHint *hint, StringInfo buf, bool nolf) { ListCell *l; @@ -827,11 +946,13 @@ ScanMethodHintDesc(ScanMethodHint *hint, StringInfo buf) quote_value(buf, (char *) lfirst(l)); } } - appendStringInfoString(buf, ")\n"); + appendStringInfoString(buf, ")"); + if (!nolf) + appendStringInfoChar(buf, '\n'); } static void -JoinMethodHintDesc(JoinMethodHint *hint, StringInfo buf) +JoinMethodHintDesc(JoinMethodHint *hint, StringInfo buf, bool nolf) { int i; @@ -845,8 +966,9 @@ JoinMethodHintDesc(JoinMethodHint *hint, StringInfo buf) quote_value(buf, hint->relnames[i]); } } - appendStringInfoString(buf, ")\n"); - + appendStringInfoString(buf, ")"); + if (!nolf) + appendStringInfoChar(buf, '\n'); } static void @@ -877,7 +999,7 @@ OuterInnerDesc(OuterInnerRels *outer_inner, StringInfo buf) } static void -LeadingHintDesc(LeadingHint *hint, StringInfo buf) +LeadingHintDesc(LeadingHint *hint, StringInfo buf, bool nolf) { appendStringInfo(buf, "%s(", HINT_LEADING); if (hint->outer_inner == NULL) @@ -900,11 +1022,13 @@ LeadingHintDesc(LeadingHint *hint, StringInfo buf) else OuterInnerDesc(hint->outer_inner, buf); - appendStringInfoString(buf, ")\n"); + appendStringInfoString(buf, ")"); + if (!nolf) + appendStringInfoChar(buf, '\n'); } static void -SetHintDesc(SetHint *hint, StringInfo buf) +SetHintDesc(SetHint *hint, StringInfo buf, bool nolf) { bool is_first = true; ListCell *l; @@ -919,27 +1043,58 @@ SetHintDesc(SetHint *hint, StringInfo buf) quote_value(buf, (char *) lfirst(l)); } - appendStringInfo(buf, ")\n"); + appendStringInfo(buf, ")"); + if (!nolf) + appendStringInfoChar(buf, '\n'); +} + +static void +RowsHintDesc(RowsHint *hint, StringInfo buf, bool nolf) +{ + int i; + + appendStringInfo(buf, "%s(", hint->base.keyword); + if (hint->relnames != NULL) + { + quote_value(buf, hint->relnames[0]); + for (i = 1; i < hint->nrels; i++) + { + appendStringInfoCharMacro(buf, ' '); + quote_value(buf, hint->relnames[i]); + } + } + appendStringInfo(buf, " %s", hint->rows_str); + appendStringInfoString(buf, ")"); + if (!nolf) + appendStringInfoChar(buf, '\n'); } /* - * Append string which repserents all hints in a given state to buf, with + * Append string which represents all hints in a given state to buf, with * preceding title with them. */ static void desc_hint_in_state(HintState *hstate, StringInfo buf, const char *title, - HintStatus state) + HintStatus state, bool nolf) { - int i; + int i, nshown; + + appendStringInfo(buf, "%s:", title); + if (!nolf) + appendStringInfoChar(buf, '\n'); - appendStringInfo(buf, "%s:\n", title); + nshown = 0; for (i = 0; i < hstate->nall_hints; i++) { if (hstate->all_hints[i]->state != state) continue; - hstate->all_hints[i]->desc_func(hstate->all_hints[i], buf); + hstate->all_hints[i]->desc_func(hstate->all_hints[i], buf, nolf); + nshown++; } + + if (nolf && nshown == 0) + appendStringInfoString(buf, "(none)"); } /* @@ -959,16 +1114,43 @@ HintStateDump(HintState *hstate) initStringInfo(&buf); appendStringInfoString(&buf, "pg_hint_plan:\n"); - desc_hint_in_state(hstate, &buf, "used hint", HINT_STATE_USED); - desc_hint_in_state(hstate, &buf, "not used hint", HINT_STATE_NOTUSED); - desc_hint_in_state(hstate, &buf, "duplication hint", HINT_STATE_DUPLICATION); - desc_hint_in_state(hstate, &buf, "error hint", HINT_STATE_ERROR); + desc_hint_in_state(hstate, &buf, "used hint", HINT_STATE_USED, false); + desc_hint_in_state(hstate, &buf, "not used hint", HINT_STATE_NOTUSED, false); + 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); pfree(buf.data); } +static void +HintStateDump2(HintState *hstate) +{ + StringInfoData buf; + + if (!hstate) + { + elog(pg_hint_plan_message_level, + "pg_hint_plan%s: HintStateDump: no hint", qnostr); + return; + } + + initStringInfo(&buf); + appendStringInfo(&buf, "pg_hint_plan%s: HintStateDump: ", qnostr); + desc_hint_in_state(hstate, &buf, "{used hints", HINT_STATE_USED, true); + desc_hint_in_state(hstate, &buf, "}, {not used hints", HINT_STATE_NOTUSED, true); + desc_hint_in_state(hstate, &buf, "}, {duplicate hints", HINT_STATE_DUPLICATION, true); + desc_hint_in_state(hstate, &buf, "}, {error hints", HINT_STATE_ERROR, true); + appendStringInfoChar(&buf, '}'); + + ereport(pg_hint_plan_message_level, + (errmsg("%s", buf.data), + errhidestmt(true))); + + pfree(buf.data); +} + /* * compare functions */ @@ -1019,6 +1201,24 @@ SetHintCmp(const SetHint *a, const SetHint *b) } static int +RowsHintCmp(const RowsHint *a, const RowsHint *b) +{ + int i; + + if (a->nrels != b->nrels) + return a->nrels - b->nrels; + + for (i = 0; i < a->nrels; i++) + { + int result; + if ((result = RelnameCmp(&a->relnames[i], &b->relnames[i])) != 0) + return result; + } + + return 0; +} + +static int HintCmp(const void *a, const void *b) { const Hint *hinta = *((const Hint **) a); @@ -1284,9 +1484,7 @@ parse_parentheses(const char *str, List **name_list, HintKeyword keyword) skip_space(str); if (keyword == HINT_KEYWORD_INDEXSCANREGEXP || -#if PG_VERSION_NUM >= 90200 keyword == HINT_KEYWORD_INDEXONLYSCANREGEXP || -#endif keyword == HINT_KEYWORD_BITMAPSCANREGEXP || keyword == HINT_KEYWORD_SET) { @@ -1322,7 +1520,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); @@ -1388,7 +1586,6 @@ get_hints_from_table(const char *client_query, const char *client_application) " OR application_name = '' ) " " ORDER BY application_name DESC"; static SPIPlanPtr plan = NULL; - int ret; char *hints = NULL; Oid argtypes[2] = { TEXTOID, TEXTOID }; Datum values[2]; @@ -1396,71 +1593,182 @@ get_hints_from_table(const char *client_query, const char *client_application) text *qry; text *app; - ret = SPI_connect(); - if (ret != SPI_OK_CONNECT) - elog(ERROR, "pg_hint_plan: SPI_connect => %d", ret); - - if (plan == NULL) + PG_TRY(); { - SPIPlanPtr p; - p = SPI_prepare(search_query, 2, argtypes); - if (p == NULL) - elog(ERROR, "pg_hint_plan: SPI_prepare => %d", SPI_result); - plan = SPI_saveplan(p); - SPI_freeplan(p); - } + bool snapshot_set = false; - qry = cstring_to_text(client_query); - app = cstring_to_text(client_application); - values[0] = PointerGetDatum(qry); - values[1] = PointerGetDatum(app); + hint_inhibit_level++; - pg_hint_plan_enable_hint = false; - ret = SPI_execute_plan(plan, values, nulls, true, 1); - pg_hint_plan_enable_hint = true; - if (ret != SPI_OK_SELECT) - elog(ERROR, "pg_hint_plan: SPI_execute_plan => %d", ret); + if (!ActiveSnapshotSet()) + { + PushActiveSnapshot(GetTransactionSnapshot()); + snapshot_set = true; + } + + SPI_connect(); + + if (plan == NULL) + { + SPIPlanPtr p; + p = SPI_prepare(search_query, 2, argtypes); + plan = SPI_saveplan(p); + SPI_freeplan(p); + } + + qry = cstring_to_text(client_query); + app = cstring_to_text(client_application); + values[0] = PointerGetDatum(qry); + values[1] = PointerGetDatum(app); + + SPI_execute_plan(plan, values, nulls, true, 1); + + if (SPI_processed > 0) + { + char *buf; + + hints = SPI_getvalue(SPI_tuptable->vals[0], + SPI_tuptable->tupdesc, 1); + /* + * Here we use SPI_palloc to ensure that hints string is valid even + * after SPI_finish call. We can't use simple palloc because it + * allocates memory in SPI's context and that context is deleted in + * SPI_finish. + */ + buf = SPI_palloc(strlen(hints) + 1); + strcpy(buf, hints); + hints = buf; + } + + SPI_finish(); - if (SPI_processed > 0) - { - char *buf; + if (snapshot_set) + PopActiveSnapshot(); - hints = SPI_getvalue(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1); - /* - * Here we use SPI_palloc to ensure that hints string is valid even - * after SPI_finish call. We can't use simple palloc because it - * allocates memory in SPI's context and that context is deleted in - * SPI_finish. - */ - buf = SPI_palloc(strlen(hints) + 1); - strcpy(buf, hints); - hints = buf; + hint_inhibit_level--; } - - SPI_finish(); + PG_CATCH(); + { + hint_inhibit_level--; + PG_RE_THROW(); + } + PG_END_TRY(); return hints; } /* - * 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 (stmt_name) + /* + * If debug_query_string is set, it is the top level statement. But in some + * cases we reach here with debug_query_string set NULL for example in the + * case of DESCRIBE message handling. We may still see a candidate + * top-level query in pstate in the case. + */ + if (!p) { - PreparedStatement *entry; + /* We don't see a query string, return NULL */ + if (!pstate->p_sourcetext) + return NULL; - entry = FetchPreparedStatement(stmt_name, true); - p = entry->plansource->query_string; + p = pstate->p_sourcetext; + } + + if (jumblequery != NULL) + *jumblequery = query; + + /* Query for DeclareCursorStmt is CMD_SELECT and has query->utilityStmt */ + if (query->commandType == CMD_UTILITY || query->utilityStmt) + { + Query *target_query = query; + + /* + * Some 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. + */ + if (IsA(target_query->utilityStmt, ExplainStmt)) + { + ExplainStmt *stmt = (ExplainStmt *)target_query->utilityStmt; + + 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; + } + + /* + * JumbleQuery does not accept a Query that has utilityStmt. On the + * other hand DeclareCursorStmt is in a bit strange shape that is + * flipped upside down. + */ + if (IsA(target_query, Query) && + target_query->utilityStmt && + IsA(target_query->utilityStmt, DeclareCursorStmt)) + { + /* + * The given Query cannot be modified so copy it and modify so that + * JumbleQuery can accept it. + */ + Assert(IsA(target_query, Query) && + target_query->commandType == CMD_SELECT); + target_query = copyObject(target_query); + target_query->utilityStmt = NULL; + } + + 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 if (plpgsql_query_string) - p = plpgsql_query_string; - 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; } @@ -1576,7 +1884,7 @@ create_hintstate(Query *parse, const char *hints) /* * If an object (or a set of objects) has multiple hints of same hint-type, - * only the last hint is valid and others are igonred in planning. + * only the last hint is valid and others are ignored in planning. * Hints except the last are marked as 'duplicated' to remember the order. */ for (i = 0; i < hstate->nall_hints - 1; i++) @@ -1614,6 +1922,8 @@ create_hintstate(Query *parse, const char *hints) hstate->num_hints[HINT_TYPE_JOIN_METHOD]); hstate->set_hints = (SetHint **) (hstate->leading_hint + hstate->num_hints[HINT_TYPE_LEADING]); + hstate->rows_hints = (RowsHint **) (hstate->set_hints + + hstate->num_hints[HINT_TYPE_SET]); return hstate; } @@ -1644,10 +1954,8 @@ ScanMethodHintParse(ScanMethodHint *hint, HintState *hstate, Query *parse, if (length != 1 && hint_keyword != HINT_KEYWORD_INDEXSCAN && hint_keyword != HINT_KEYWORD_INDEXSCANREGEXP && -#if PG_VERSION_NUM >= 90200 hint_keyword != HINT_KEYWORD_INDEXONLYSCAN && hint_keyword != HINT_KEYWORD_INDEXONLYSCANREGEXP && -#endif hint_keyword != HINT_KEYWORD_BITMAPSCAN && hint_keyword != HINT_KEYWORD_BITMAPSCANREGEXP) { @@ -1702,7 +2010,6 @@ ScanMethodHintParse(ScanMethodHint *hint, HintState *hstate, Query *parse, case HINT_KEYWORD_NOTIDSCAN: hint->enforce_mask = ENABLE_ALL_SCAN ^ ENABLE_TIDSCAN; break; -#if PG_VERSION_NUM >= 90200 case HINT_KEYWORD_INDEXONLYSCAN: hint->enforce_mask = ENABLE_INDEXSCAN | ENABLE_INDEXONLYSCAN; break; @@ -1713,7 +2020,6 @@ ScanMethodHintParse(ScanMethodHint *hint, HintState *hstate, Query *parse, case HINT_KEYWORD_NOINDEXONLYSCAN: hint->enforce_mask = ENABLE_ALL_SCAN ^ ENABLE_INDEXONLYSCAN; break; -#endif default: hint_ereport(str, ("Unrecognized hint keyword \"%s\".", keyword)); return NULL; @@ -1909,6 +2215,97 @@ SetHintParse(SetHint *hint, HintState *hstate, Query *parse, const char *str) return str; } +static const char * +RowsHintParse(RowsHint *hint, HintState *hstate, Query *parse, + const char *str) +{ + HintKeyword hint_keyword = hint->base.hint_keyword; + List *name_list = NIL; + char *rows_str; + char *end_ptr; + + if ((str = parse_parentheses(str, &name_list, hint_keyword)) == NULL) + return NULL; + + /* Last element must be rows specification */ + hint->nrels = list_length(name_list) - 1; + + if (hint->nrels > 0) + { + ListCell *l; + int i = 0; + + /* + * 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 */ + rows_str = list_nth(name_list, hint->nrels); + hint->rows_str = rows_str; /* store as-is for error logging */ + if (rows_str[0] == '#') + { + hint->value_type = RVT_ABSOLUTE; + rows_str++; + } + else if (rows_str[0] == '+') + { + hint->value_type = RVT_ADD; + rows_str++; + } + else if (rows_str[0] == '-') + { + hint->value_type = RVT_SUB; + rows_str++; + } + else if (rows_str[0] == '*') + { + hint->value_type = RVT_MULTI; + rows_str++; + } + else + { + hint_ereport(rows_str, ("Unrecognized rows value type notation.")); + hint->base.state = HINT_STATE_ERROR; + return str; + } + hint->rows = strtod(rows_str, &end_ptr); + if (*end_ptr) + { + hint_ereport(rows_str, + ("%s hint requires valid number as rows estimation.", + hint->base.keyword)); + hint->base.state = HINT_STATE_ERROR; + return str; + } + + /* A join hint requires at least two relations */ + if (hint->nrels < 2) + { + hint_ereport(str, + ("%s hint requires at least two relations.", + hint->base.keyword)); + hint->base.state = HINT_STATE_ERROR; + return str; + } + + list_free(name_list); + + /* Sort relnames in alphabetical order. */ + qsort(hint->relnames, hint->nrels, sizeof(char *), RelnameCmp); + + return str; +} + /* * set GUC parameter functions */ @@ -1923,13 +2320,8 @@ set_config_option_wrapper(const char *name, const char *value, PG_TRY(); { -#if PG_VERSION_NUM >= 90200 result = set_config_option(name, value, context, source, action, changeVal, 0); -#else - result = set_config_option(name, value, context, source, - action, changeVal); -#endif } PG_CATCH(); { @@ -1940,10 +2332,12 @@ set_config_option_wrapper(const char *name, const char *value, errdata = CopyErrorData(); FlushErrorState(); - ereport(elevel, (errcode(errdata->sqlerrcode), - errmsg("%s", errdata->message), - errdata->detail ? errdetail("%s", errdata->detail) : 0, - errdata->hint ? errhint("%s", errdata->hint) : 0)); + ereport(elevel, + (errcode(errdata->sqlerrcode), + 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(); @@ -1969,7 +2363,7 @@ set_config_options(SetHint **options, int noptions, GucContext context) result = set_config_option_wrapper(hint->name, hint->value, context, PGC_S_SESSION, GUC_ACTION_SAVE, true, - pg_hint_plan_parse_messages); + pg_hint_plan_message_level); if (result != 0) hint->base.state = HINT_STATE_USED; else @@ -1991,9 +2385,7 @@ set_scan_config_options(unsigned char enforce_mask, GucContext context) if (enforce_mask == ENABLE_SEQSCAN || enforce_mask == ENABLE_INDEXSCAN || enforce_mask == ENABLE_BITMAPSCAN || enforce_mask == ENABLE_TIDSCAN -#if PG_VERSION_NUM >= 90200 || enforce_mask == (ENABLE_INDEXSCAN | ENABLE_INDEXONLYSCAN) -#endif ) mask = enforce_mask; else @@ -2003,9 +2395,7 @@ set_scan_config_options(unsigned char enforce_mask, GucContext context) SET_CONFIG_OPTION("enable_indexscan", ENABLE_INDEXSCAN); SET_CONFIG_OPTION("enable_bitmapscan", ENABLE_BITMAPSCAN); SET_CONFIG_OPTION("enable_tidscan", ENABLE_TIDSCAN); -#if PG_VERSION_NUM >= 90200 SET_CONFIG_OPTION("enable_indexonlyscan", ENABLE_INDEXONLYSCAN); -#endif } static void @@ -2025,112 +2415,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, - ParamListInfo params, bool isTopLevel, - DestReceiver *dest, char *completionTag) -{ - Node *node; - - if (!pg_hint_plan_enable_hint) - { - if (prev_ProcessUtility) - (*prev_ProcessUtility) (parsetree, queryString, params, - isTopLevel, dest, completionTag); - else - standard_ProcessUtility(parsetree, queryString, params, - isTopLevel, 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 PG_VERSION_NUM >= 90200 - /* - * 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; - } - } -#endif - if (stmt_name) - { - PG_TRY(); - { - if (prev_ProcessUtility) - (*prev_ProcessUtility) (parsetree, queryString, params, - isTopLevel, dest, completionTag); - else - standard_ProcessUtility(parsetree, queryString, params, - isTopLevel, dest, completionTag); - } - PG_CATCH(); - { - stmt_name = NULL; - PG_RE_THROW(); - } - PG_END_TRY(); - - stmt_name = NULL; - - return; - } - - if (prev_ProcessUtility) - (*prev_ProcessUtility) (parsetree, queryString, params, - isTopLevel, dest, completionTag); - else - standard_ProcessUtility(parsetree, queryString, params, - isTopLevel, dest, completionTag); -} - -/* * Push a hint into hint stack which is implemented with List struct. Head of * list is top of stack. */ @@ -2164,72 +2448,225 @@ pop_hint(void) current_hint = (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))); + 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))); + + 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))); + 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))); + msgqno = qno; + } +} + +/* + * Read and set up hint information + */ static PlannedStmt * pg_hint_plan_planner(Query *parse, int cursorOptions, ParamListInfo boundParams) { - const char *hints; - const char *query; - char *norm_query; - pgssJumbleState jstate; - int query_len; int save_nestlevel; PlannedStmt *result; HintState *hstate; /* - * Use standard planner if pg_hint_plan is disabled. Other hook functions - * try to change plan with current_hint 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 if any, so set it to NULL. */ - if (!pg_hint_plan_enable_hint) - goto standard_planner_proc; + if (!pg_hint_plan_enable_hint || hint_inhibit_level > 0) + { + if (debug_level > 1) + 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; - /* Create hint struct from client-supplied query string. */ - query = get_query_string(); + goto standard_planner_proc; + } /* - * 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. + * 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. */ - 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); - elog(DEBUG1, - "pg_hint_plan: get_hints_from_table [%s][%s]=>[%s]", - norm_query, application_name, - hints ? hints : "(none)"); - if (hints == NULL) - hints = get_hints_from_comment(query); - hstate = create_hintstate(parse, hints); + if (plpgsql_recurse_level > 0) + { + MemoryContext oldcontext; - /* - * Use standard planner if the statement has not valid hint. Other hook - * functions try to change plan with current_hint if any, so set it to - * NULL. - */ - if (!hstate) + 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); + } + + 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 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. */ @@ -2248,10 +2685,8 @@ pg_hint_plan_planner(Query *parse, int cursorOptions, ParamListInfo boundParams) current_hint->init_scan_mask |= ENABLE_BITMAPSCAN; if (enable_tidscan) current_hint->init_scan_mask |= ENABLE_TIDSCAN; -#if PG_VERSION_NUM >= 90200 if (enable_indexonlyscan) current_hint->init_scan_mask |= ENABLE_INDEXONLYSCAN; -#endif if (enable_nestloop) current_hint->init_join_mask |= ENABLE_NESTLOOP; if (enable_mergejoin) @@ -2259,6 +2694,14 @@ pg_hint_plan_planner(Query *parse, int cursorOptions, ParamListInfo boundParams) if (enable_hashjoin) current_hint->init_join_mask |= ENABLE_HASHJOIN; + if (debug_level > 1) + { + ereport(pg_hint_plan_message_level, + (errhidestmt(msgqno != qno), + errmsg("pg_hint_plan%s: planner", qnostr))); + msgqno = qno; + } + /* * Use PG_TRY mechanism to recover GUC parameters and current_hint to the * state when this planner started when error occurred in planner. @@ -2283,8 +2726,10 @@ pg_hint_plan_planner(Query *parse, int cursorOptions, ParamListInfo boundParams) PG_END_TRY(); /* Print hint in debug mode. */ - if (pg_hint_plan_debug_print) + if (debug_level == 1) HintStateDump(current_hint); + else if (debug_level > 1) + HintStateDump2(current_hint); /* * Rollback changes of GUC parameters, and pop current hint context from @@ -2296,6 +2741,14 @@ pg_hint_plan_planner(Query *parse, int cursorOptions, ParamListInfo boundParams) return result; standard_planner_proc: + if (debug_level > 1) + { + ereport(pg_hint_plan_message_level, + (errhidestmt(msgqno != qno), + errmsg("pg_hint_plan%s: planner: no valid hint", + qnostr))); + msgqno = qno; + } current_hint = NULL; if (prev_planner) return (*prev_planner) (parse, cursorOptions, boundParams); @@ -2307,7 +2760,7 @@ standard_planner_proc: * Return scan method hint which matches given aliasname. */ static ScanMethodHint * -find_scan_hint(PlannerInfo *root, RelOptInfo *rel) +find_scan_hint(PlannerInfo *root, Index relid, RelOptInfo *rel) { RangeTblEntry *rte; int i; @@ -2317,10 +2770,10 @@ find_scan_hint(PlannerInfo *root, RelOptInfo *rel) * - not a base relation * - not an ordinary relation (such as join and subquery) */ - if (rel->reloptkind != RELOPT_BASEREL || rel->rtekind != RTE_RELATION) + if (rel && (rel->reloptkind != RELOPT_BASEREL || rel->rtekind != RTE_RELATION)) return NULL; - rte = root->simple_rte_array[rel->relid]; + rte = root->simple_rte_array[relid]; /* We can't force scan method of foreign tables */ if (rte->relkind == RELKIND_FOREIGN_TABLE) @@ -2402,7 +2855,7 @@ delete_indexes(ScanMethodHint *hint, RelOptInfo *rel, Oid relationObjectId) * other than it. */ prev = NULL; - if (pg_hint_plan_debug_print) + if (debug_level > 0) initStringInfo(&buf); for (cell = list_head(rel->indexlist); cell; cell = next) @@ -2427,7 +2880,7 @@ delete_indexes(ScanMethodHint *hint, RelOptInfo *rel, Oid relationObjectId) if (result) { use_index = true; - if (pg_hint_plan_debug_print) + if (debug_level > 0) { appendStringInfoCharMacro(&buf, ' '); quote_value(&buf, indexname); @@ -2544,7 +2997,7 @@ delete_indexes(ScanMethodHint *hint, RelOptInfo *rel, Oid relationObjectId) } } - /* Check to match the predicate's paraameter of index */ + /* Check to match the predicate's parameter of index */ if (p_info->indpred_str && !heap_attisnull(ht_idx, Anum_pg_index_indpred)) { @@ -2553,7 +3006,7 @@ delete_indexes(ScanMethodHint *hint, RelOptInfo *rel, Oid relationObjectId) Datum result; /* - * to change the predicate's parabeter of child's + * to change the predicate's parameter of child's * index to strings */ predDatum = SysCacheGetAttr(INDEXRELID, ht_idx, @@ -2586,7 +3039,7 @@ delete_indexes(ScanMethodHint *hint, RelOptInfo *rel, Oid relationObjectId) use_index = true; /* to log the candidate of index */ - if (pg_hint_plan_debug_print) + if (debug_level > 0) { appendStringInfoCharMacro(&buf, ' '); quote_value(&buf, indexname); @@ -2604,7 +3057,7 @@ delete_indexes(ScanMethodHint *hint, RelOptInfo *rel, Oid relationObjectId) pfree(indexname); } - if (pg_hint_plan_debug_print) + if (debug_level == 1) { char *relname; StringInfoData rel_buf; @@ -2662,7 +3115,7 @@ get_parent_index_info(Oid indexoid, Oid relid) } /* - * to check to match the expression's paraameter of index with child indexes + * 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)) @@ -2682,7 +3135,7 @@ get_parent_index_info(Oid indexoid, Oid relid) } /* - * to check to match the predicate's paraameter of index with child indexes + * 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)) @@ -2710,104 +3163,191 @@ static void pg_hint_plan_get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, RelOptInfo *rel) { - ScanMethodHint *hint; + ScanMethodHint *hint = NULL; + ListCell *l; + Index new_parent_relid = 0; if (prev_get_relation_info) (*prev_get_relation_info) (root, relationObjectId, inhparent, rel); - /* Do nothing if we don't have valid hint in this context. */ - if (!current_hint) + /* + * Do nothing if we don't have a valid hint in this context or current + * nesting depth is at SPI calls. + */ + if (!current_hint || hint_inhibit_level > 0) + { + 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=%p, hint_inhibit_level=%d", + qnostr, relationObjectId, + get_rel_name(relationObjectId), + inhparent, current_hint, hint_inhibit_level))); return; + } + /* + * 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. + */ if (inhparent) { - /* store does relids of parent table. */ - current_hint->parent_relid = rel->relid; - current_hint->parent_rel_oid = relationObjectId; + if (debug_level > 1) + ereport(pg_hint_plan_message_level, + (errhidestmt(true), + errmsg ("pg_hint_plan%s: get_relation_info" + " skipping inh parent: relation=%u(%s), inhparent=%d," + " current_hint=%p, hint_inhibit_level=%d", + qnostr, relationObjectId, + get_rel_name(relationObjectId), + inhparent, current_hint, hint_inhibit_level))); + return; } - else if (current_hint->parent_relid != 0) + + /* Find the parent for this relation */ + foreach (l, root->append_rel_list) { - /* - * We use the same GUC parameter if this table is the child table of a - * table called pg_hint_plan_get_relation_info just before that. - */ - ListCell *l; + AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); - /* append_rel_list contains all append rels; ignore others */ - foreach(l, root->append_rel_list) + if (appinfo->child_relid == rel->relid) { - AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); - - /* This rel is child table. */ - if (appinfo->parent_relid == current_hint->parent_relid && - appinfo->child_relid == rel->relid) - { - if (current_hint->parent_hint) - delete_indexes(current_hint->parent_hint, rel, - relationObjectId); - - return; - } + if (current_hint->parent_relid != appinfo->parent_relid) + new_parent_relid = appinfo->parent_relid; + break; } - - /* This rel is not inherit table. */ - current_hint->parent_relid = 0; - current_hint->parent_rel_oid = InvalidOid; - current_hint->parent_hint = NULL; } - /* - * If scan method hint was given, reset GUC parameters which control - * planner behavior about choosing scan methods. - */ - if ((hint = find_scan_hint(root, rel)) == NULL) + if (!l) { - set_scan_config_options(current_hint->init_scan_mask, - current_hint->context); - return; + /* This relation doesn't have a parent. Cancel current_hint. */ + current_hint->parent_relid = 0; + current_hint->parent_hint = NULL; } - set_scan_config_options(hint->enforce_mask, current_hint->context); - hint->base.state = HINT_STATE_USED; - if (inhparent) + if (new_parent_relid > 0) { - Relation relation; - List *indexoidlist; - ListCell *l; - - current_hint->parent_hint = hint; + /* + * Here we found a parent relation different from the remembered one. + * Remember it, apply the scan mask of it and then resolve the index + * restriction in order to be used by its children. + */ + int scanmask = current_hint->init_scan_mask; + ScanMethodHint *parent_hint; - relation = heap_open(relationObjectId, NoLock); - indexoidlist = RelationGetIndexList(relation); + current_hint->parent_relid = new_parent_relid; + + /* + * Get and apply the hint for the new parent relation. It should be an + * ordinary relation so calling find_scan_hint with rel == NULL is + * safe. + */ + current_hint->parent_hint = parent_hint = + find_scan_hint(root, current_hint->parent_relid, NULL); - foreach(l, indexoidlist) + if (parent_hint) { - Oid indexoid = lfirst_oid(l); - char *indexname = get_rel_name(indexoid); - bool use_index = false; - ListCell *lc; - ParentIndexInfo *parent_index_info; + scanmask = current_hint->parent_hint->enforce_mask; + parent_hint->base.state = HINT_STATE_USED; - foreach(lc, hint->indexnames) + /* Resolve index name mask (if any) using the parent. */ + if (parent_hint->indexnames) { - if (RelnameCmp(&indexname, &lfirst(lc)) == 0) + Oid parentrel_oid; + Relation parent_rel; + + parentrel_oid = + root->simple_rte_array[current_hint->parent_relid]->relid; + parent_rel = heap_open(parentrel_oid, NoLock); + + /* Search the parent relation for indexes match the hint spec */ + foreach(l, RelationGetIndexList(parent_rel)) { - use_index = true; - break; + Oid indexoid = lfirst_oid(l); + char *indexname = get_rel_name(indexoid); + ListCell *lc; + ParentIndexInfo *parent_index_info; + + foreach(lc, parent_hint->indexnames) + { + if (RelnameCmp(&indexname, &lfirst(lc)) == 0) + break; + } + if (!lc) + continue; + + parent_index_info = + get_parent_index_info(indexoid, parentrel_oid); + current_hint->parent_index_infos = + lappend(current_hint->parent_index_infos, parent_index_info); } + heap_close(parent_rel, NoLock); } - if (!use_index) - continue; - - parent_index_info = get_parent_index_info(indexoid, - relationObjectId); - current_hint->parent_index_infos = - lappend(current_hint->parent_index_infos, parent_index_info); } - heap_close(relation, NoLock); + + set_scan_config_options(scanmask, current_hint->context); } - else + + if (current_hint->parent_hint != 0) + { + delete_indexes(current_hint->parent_hint, rel, + relationObjectId); + + /* 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=%p," + " hint_inhibit_level=%d", + qnostr, relationObjectId, + get_rel_name(relationObjectId), + inhparent, current_hint, hint_inhibit_level))); + return; + } + + /* This table doesn't have a parent hint. Apply its own hint if any. */ + if ((hint = find_scan_hint(root, rel->relid, rel)) != NULL) + { + set_scan_config_options(hint->enforce_mask, current_hint->context); + hint->base.state = HINT_STATE_USED; + delete_indexes(hint, rel, InvalidOid); + + if (debug_level > 1) + 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," + " hint_inhibit_level=%d, scanmask=0x%x", + qnostr, relationObjectId, + get_rel_name(relationObjectId), + inhparent, current_hint, hint_inhibit_level, + hint->enforce_mask))); + } + else + { + 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, hint_inhibit_level, + current_hint->init_scan_mask))); + set_scan_config_options(current_hint->init_scan_mask, + current_hint->context); + } + return; } /* @@ -2964,6 +3504,46 @@ OuterInnerJoinCreate(OuterInnerRels *outer_inner, LeadingHint *leading_hint, return join_relids; } +static Relids +create_bms_of_relids(Hint *base, PlannerInfo *root, List *initial_rels, + int nrels, char **relnames) +{ + int relid; + Relids relids = NULL; + int j; + char *relname; + + for (j = 0; j < nrels; j++) + { + relname = relnames[j]; + + relid = find_relid_aliasname(root, relname, initial_rels, + base->hint_str); + + if (relid == -1) + base->state = HINT_STATE_ERROR; + + /* + * the aliasname is not found(relid == 0) or same aliasname was used + * multiple times in a query(relid == -1) + */ + if (relid <= 0) + { + relids = NULL; + break; + } + if (bms_is_member(relid, relids)) + { + hint_ereport(base->hint_str, + ("Relation name \"%s\" is duplicated.", relname)); + base->state = HINT_STATE_ERROR; + break; + } + + relids = bms_add_member(relids, relid); + } + return relids; +} /* * Transform join method hint into handy form. * @@ -2991,43 +3571,33 @@ transform_join_hints(HintState *hstate, PlannerInfo *root, int nbaserel, for (i = 0; i < hstate->num_hints[HINT_TYPE_JOIN_METHOD]; i++) { JoinMethodHint *hint = hstate->join_hints[i]; - int j; if (!hint_state_enabled(hint) || hint->nrels > nbaserel) continue; - bms_free(hint->joinrelids); - hint->joinrelids = NULL; - relid = 0; - for (j = 0; j < hint->nrels; j++) - { - relname = hint->relnames[j]; - - relid = find_relid_aliasname(root, relname, initial_rels, - hint->base.hint_str); - - if (relid == -1) - hint->base.state = HINT_STATE_ERROR; + hint->joinrelids = create_bms_of_relids(&(hint->base), root, + initial_rels, hint->nrels, hint->relnames); - if (relid <= 0) - break; + if (hint->joinrelids == NULL || hint->base.state == HINT_STATE_ERROR) + continue; - if (bms_is_member(relid, hint->joinrelids)) - { - hint_ereport(hint->base.hint_str, - ("Relation name \"%s\" is duplicated.", relname)); - hint->base.state = HINT_STATE_ERROR; - break; - } + hstate->join_hint_level[hint->nrels] = + lappend(hstate->join_hint_level[hint->nrels], hint); + } - hint->joinrelids = bms_add_member(hint->joinrelids, relid); - } + /* + * Create bitmap of relids from alias names for each rows hint. + * Bitmaps are more handy than strings in join searching. + */ + for (i = 0; i < hstate->num_hints[HINT_TYPE_ROWS]; i++) + { + RowsHint *hint = hstate->rows_hints[i]; - if (relid <= 0 || hint->base.state == HINT_STATE_ERROR) + if (!hint_state_enabled(hint) || hint->nrels > nbaserel) continue; - hstate->join_hint_level[hint->nrels] = - lappend(hstate->join_hint_level[hint->nrels], hint); + hint->joinrelids = create_bms_of_relids(&(hint->base), root, + initial_rels, hint->nrels, hint->relnames); } /* Do nothing if no Leading hint was supplied. */ @@ -3233,11 +3803,7 @@ static void set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) { /* Consider sequential scan */ -#if PG_VERSION_NUM >= 90200 add_path(rel, create_seqscan_path(root, rel, NULL)); -#else - add_path(rel, create_seqscan_path(root, rel)); -#endif /* Consider index scans */ create_index_paths(root, rel); @@ -3276,7 +3842,7 @@ rebuild_scan_path(HintState *hstate, PlannerInfo *root, int level, * planner if scan method hint is not specified, otherwise use * specified hints and mark the hint as used. */ - if ((hint = find_scan_hint(root, rel)) == NULL) + if ((hint = find_scan_hint(root, rel->relid, rel)) == NULL) set_scan_config_options(hstate->init_scan_mask, hstate->context); else @@ -3362,7 +3928,7 @@ add_paths_to_joinrel_wrapper(PlannerInfo *root, JoinMethodHint *join_hint; int save_nestlevel; - if ((scan_hint = find_scan_hint(root, innerrel)) != NULL) + if ((scan_hint = find_scan_hint(root, innerrel->relid, innerrel)) != NULL) { set_scan_config_options(scan_hint->enforce_mask, current_hint->context); scan_hint->base.state = HINT_STATE_USED; @@ -3443,9 +4009,10 @@ pg_hint_plan_join_search(PlannerInfo *root, int levels_needed, /* * Use standard planner (or geqo planner) if pg_hint_plan is disabled or no - * valid hint is supplied. + * valid hint is supplied or current nesting depth is nesting depth of SPI + * calls. */ - if (!current_hint) + if (!current_hint || hint_inhibit_level > 0) { if (prev_join_search) return (*prev_join_search) (root, levels_needed, initial_rels); @@ -3504,15 +4071,11 @@ static void set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte) { -#if PG_VERSION_NUM >= 90200 if (IS_DUMMY_REL(rel)) { /* We already proved the relation empty, so nothing more to do */ } else if (rte->inh) -#else - if (rte->inh) -#endif { /* It's an "append relation", process accordingly */ set_append_rel_pathlist(root, rel, rti, rte); @@ -3540,16 +4103,12 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, * plpgsql_query_string to use it in planner hook. It's safe to use one global * variable for the purpose, because its content is only necessary until * planner hook is called for the query, so recursive PL/pgSQL function calls - * don't harm this mechanismk. + * don't harm this mechanism. */ static void pg_hint_plan_plpgsql_stmt_beg(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt) { - if ((enum PLpgSQL_stmt_types) stmt->cmd_type == PLPGSQL_STMT_EXECSQL) - { - PLpgSQL_expr *expr = ((PLpgSQL_stmt_execsql *) stmt)->sqlstmt; - plpgsql_query_string = expr->query; - } + plpgsql_recurse_level++; } /* @@ -3560,8 +4119,18 @@ pg_hint_plan_plpgsql_stmt_beg(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt) static void pg_hint_plan_plpgsql_stmt_end(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt) { - if ((enum PLpgSQL_stmt_types) stmt->cmd_type == PLPGSQL_STMT_EXECSQL) - plpgsql_query_string = NULL; + plpgsql_recurse_level--; +} + +void plpgsql_query_erase_callback(ResourceReleasePhase phase, + bool isCommit, + bool isTopLevel, + void *arg) +{ + if (phase != RESOURCE_RELEASE_AFTER_LOCKS) + return; + /* Cancel plpgsql nest level*/ + plpgsql_recurse_level = 0; } #define standard_join_search pg_hint_plan_standard_join_search