X-Git-Url: http://git.osdn.net/view?a=blobdiff_plain;f=pg_hint_plan.c;h=26586a288708d7e798e2df7757652bb29734061b;hb=aa499a1f127c33c27218f5ddfa9d8392a5adb29f;hp=4af7cb14d7bb3aa657a8a46a52844e2f80e982e6;hpb=bd90c43702fc890be52ca3caa917a84736f61fe5;p=pghintplan%2Fpg_hint_plan.git diff --git a/pg_hint_plan.c b/pg_hint_plan.c index 4af7cb1..26586a2 100644 --- a/pg_hint_plan.c +++ b/pg_hint_plan.c @@ -27,6 +27,9 @@ #include "tcop/utility.h" #include "utils/lsyscache.h" #include "utils/memutils.h" +#if PG_VERSION_NUM >= 90200 +#include "catalog/pg_class.h" +#endif #ifdef PG_MODULE_MAGIC PG_MODULE_MAGIC; @@ -51,6 +54,10 @@ 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_NOINDEXONLYSCAN "NoIndexOnlyScan" +#endif #define HINT_NESTLOOP "NestLoop" #define HINT_MERGEJOIN "MergeJoin" #define HINT_HASHJOIN "HashJoin" @@ -60,10 +67,9 @@ PG_MODULE_MAGIC; #define HINT_LEADING "Leading" #define HINT_SET "Set" - #define HINT_ARRAY_DEFAULT_INITSIZE 8 -#define parse_ereport(str, detail) \ +#define hint_ereport(str, detail) \ ereport(pg_hint_plan_parse_messages, \ (errmsg("hint syntax error at or near \"%s\"", (str)), \ errdetail detail)) @@ -78,6 +84,9 @@ enum ENABLE_INDEXSCAN = 0x02, ENABLE_BITMAPSCAN = 0x04, ENABLE_TIDSCAN = 0x08, +#if PG_VERSION_NUM >= 90200 + ENABLE_INDEXONLYSCAN = 0x10 +#endif } SCAN_TYPE_BITS; enum @@ -87,22 +96,54 @@ enum ENABLE_HASHJOIN = 0x04 } JOIN_TYPE_BITS; -#define ENABLE_ALL_SCAN (ENABLE_SEQSCAN | ENABLE_INDEXSCAN | ENABLE_BITMAPSCAN \ - | ENABLE_TIDSCAN) +#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 +/* hint keyword of enum type*/ +typedef enum HintKeyword +{ + HINT_KEYWORD_SEQSCAN, + HINT_KEYWORD_INDEXSCAN, + HINT_KEYWORD_BITMAPSCAN, + HINT_KEYWORD_TIDSCAN, + HINT_KEYWORD_NOSEQSCAN, + HINT_KEYWORD_NOINDEXSCAN, + HINT_KEYWORD_NOBITMAPSCAN, + HINT_KEYWORD_NOTIDSCAN, +#if PG_VERSION_NUM >= 90200 + HINT_KEYWORD_INDEXONLYSCAN, + HINT_KEYWORD_NOINDEXONLYSCAN, +#endif + HINT_KEYWORD_NESTLOOP, + HINT_KEYWORD_MERGEJOIN, + HINT_KEYWORD_HASHJOIN, + HINT_KEYWORD_NONESTLOOP, + HINT_KEYWORD_NOMERGEJOIN, + HINT_KEYWORD_NOHASHJOIN, + HINT_KEYWORD_LEADING, + HINT_KEYWORD_SET, + HINT_KEYWORD_UNRECOGNIZED +} HintKeyword; + typedef struct Hint Hint; -typedef struct PlanHint PlanHint; -typedef struct PlanHintStack PlanHintStack; +typedef struct HintState HintState; typedef Hint *(*HintCreateFunction) (const char *hint_str, - const char *keyword); + const char *keyword, + HintKeyword hint_keyword); typedef void (*HintDeleteFunction) (Hint *hint); typedef void (*HintDumpFunction) (Hint *hint, StringInfo buf); typedef int (*HintCmpFunction) (const Hint *a, const Hint *b); -typedef const char *(*HintParseFunction) (Hint *hint, PlanHint *plan, +typedef const char *(*HintParseFunction) (Hint *hint, HintState *hstate, Query *parse, const char *str); /* hint types */ @@ -115,6 +156,13 @@ typedef enum HintType HINT_TYPE_SET } HintType; +static const char *HintTypeName[] = { + "scan method", + "join method", + "leading", + "set" +}; + /* hint status */ typedef enum HintStatus { @@ -133,12 +181,13 @@ struct Hint { const char *hint_str; /* must not do pfree */ const char *keyword; /* must not do pfree */ + HintKeyword hint_keyword; HintType type; HintStatus state; HintDeleteFunction delete_func; HintDumpFunction dump_func; HintCmpFunction cmp_func; - HintParseFunction parser_func; + HintParseFunction parse_func; }; /* scan method hints */ @@ -155,16 +204,25 @@ typedef struct JoinMethodHint { Hint base; int nrels; + int inner_nrels; char **relnames; unsigned char enforce_mask; Relids joinrelids; + Relids inner_joinrelids; } JoinMethodHint; /* join order hints */ +typedef struct OuterInnerRels +{ + char *relation; + List *outer_inner_pair; +} OuterInnerRels; + typedef struct LeadingHint { - Hint base; - List *relations; /* relation names specified in Leading hint */ + Hint base; + List *relations; /* relation names specified in Leading hint */ + OuterInnerRels *outer_inner; } LeadingHint; /* change a run-time parameter hints */ @@ -173,12 +231,13 @@ typedef struct SetHint Hint base; char *name; /* name of variable */ char *value; + List *words; } SetHint; /* * Describes a context of hint processing. */ -struct PlanHint +struct HintState { char *hint_str; /* original hint string */ @@ -202,22 +261,13 @@ struct PlanHint List **join_hint_level; /* for Leading hint */ - LeadingHint *leading_hint; /* parsed last specified Leading hint */ + LeadingHint **leading_hint; /* parsed last specified Leading hint */ /* for Set hints */ SetHint **set_hints; /* parsed Set hints */ GucContext context; /* which GUC parameters can we set? */ }; -struct PlanHintStack -{ - PlanHint *plan_hint; - PlanHintStack *next; -}; - -struct PlanHintStack *head = NULL; - - /* * Describes a hint parser module which is bound with particular hint keyword. */ @@ -225,14 +275,15 @@ typedef struct HintParser { char *keyword; HintCreateFunction create_func; + HintKeyword hint_keyword; } HintParser; /* Module callbacks */ void _PG_init(void); void _PG_fini(void); -static void push_stack(PlanHint *plan); -static void pop_stack(void); +static void push_hint(HintState *hstate); +static void pop_hint(void); static void pg_hint_plan_ProcessUtility(Node *parsetree, const char *queryString, @@ -248,31 +299,40 @@ static RelOptInfo *pg_hint_plan_join_search(PlannerInfo *root, int levels_needed, List *initial_rels); -static Hint *ScanMethodHintCreate(const char *hint_str, const char *keyword); +static Hint *ScanMethodHintCreate(const char *hint_str, const char *keyword, + HintKeyword hint_keyword); static void ScanMethodHintDelete(ScanMethodHint *hint); static void ScanMethodHintDump(ScanMethodHint *hint, StringInfo buf); static int ScanMethodHintCmp(const ScanMethodHint *a, const ScanMethodHint *b); -static const char *ScanMethodHintParse(ScanMethodHint *hint, PlanHint *plan, +static const char *ScanMethodHintParse(ScanMethodHint *hint, HintState *hstate, Query *parse, const char *str); -static Hint *JoinMethodHintCreate(const char *hint_str, const char *keyword); +static Hint *JoinMethodHintCreate(const char *hint_str, const char *keyword, + HintKeyword hint_keyword); static void JoinMethodHintDelete(JoinMethodHint *hint); static void JoinMethodHintDump(JoinMethodHint *hint, StringInfo buf); static int JoinMethodHintCmp(const JoinMethodHint *a, const JoinMethodHint *b); -static const char *JoinMethodHintParse(JoinMethodHint *hint, PlanHint *plan, +static const char *JoinMethodHintParse(JoinMethodHint *hint, HintState *hstate, Query *parse, const char *str); -static Hint *LeadingHintCreate(const char *hint_str, const char *keyword); +static Hint *LeadingHintCreate(const char *hint_str, const char *keyword, + HintKeyword hint_keyword); static void LeadingHintDelete(LeadingHint *hint); static void LeadingHintDump(LeadingHint *hint, StringInfo buf); static int LeadingHintCmp(const LeadingHint *a, const LeadingHint *b); -static const char *LeadingHintParse(LeadingHint *hint, PlanHint *plan, +static const char *LeadingHintParse(LeadingHint *hint, HintState *hstate, Query *parse, const char *str); -static Hint *SetHintCreate(const char *hint_str, const char *keyword); +static Hint *SetHintCreate(const char *hint_str, const char *keyword, + HintKeyword hint_keyword); static void SetHintDelete(SetHint *hint); static void SetHintDump(SetHint *hint, StringInfo buf); static int SetHintCmp(const SetHint *a, const SetHint *b); -static const char *SetHintParse(SetHint *hint, PlanHint *plan, Query *parse, +static const char *SetHintParse(SetHint *hint, HintState *hstate, Query *parse, const char *str); +static void dump_quote_value_quote_char(StringInfo buf, const char *value, + char quote_char); +static const char *parse_quote_value_term_char(const char *str, char **word, + bool truncate, char term_char); + RelOptInfo *pg_hint_plan_standard_join_search(PlannerInfo *root, int levels_needed, List *initial_rels); @@ -285,14 +345,20 @@ 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 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); - /* GUC variables */ -static bool pg_hint_plan_enable = true; +static bool pg_hint_plan_enable_hint = true; static bool pg_hint_plan_debug_print = false; static int pg_hint_plan_parse_messages = INFO; @@ -321,33 +387,43 @@ static planner_hook_type prev_planner = NULL; static get_relation_info_hook_type prev_get_relation_info = NULL; static join_search_hook_type prev_join_search = NULL; -/* フック関数をまたがって使用する情報を管理する */ -static PlanHint *current_hint = NULL; +/* Hold reference to currently active hint */ +static HintState *current_hint = NULL; + +/* + * List of hint contexts. We treat the head of the list as the Top of the + * context stack, so current_hint always points the first element of this list. + */ +static List *HintStateStack = NIL; /* - * EXECUTEコマンド実行時に、ステートメント名を格納する。 - * その他のコマンドの場合は、NULLに設定する。 + * Holds statement name during executing EXECUTE command. NULL for other + * statements. */ static char *stmt_name = NULL; static const HintParser parsers[] = { - {HINT_SEQSCAN, ScanMethodHintCreate}, - {HINT_INDEXSCAN, ScanMethodHintCreate}, - {HINT_BITMAPSCAN, ScanMethodHintCreate}, - {HINT_TIDSCAN, ScanMethodHintCreate}, - {HINT_NOSEQSCAN, ScanMethodHintCreate}, - {HINT_NOINDEXSCAN, ScanMethodHintCreate}, - {HINT_NOBITMAPSCAN, ScanMethodHintCreate}, - {HINT_NOTIDSCAN, ScanMethodHintCreate}, - {HINT_NESTLOOP, JoinMethodHintCreate}, - {HINT_MERGEJOIN, JoinMethodHintCreate}, - {HINT_HASHJOIN, JoinMethodHintCreate}, - {HINT_NONESTLOOP, JoinMethodHintCreate}, - {HINT_NOMERGEJOIN, JoinMethodHintCreate}, - {HINT_NOHASHJOIN, JoinMethodHintCreate}, - {HINT_LEADING, LeadingHintCreate}, - {HINT_SET, SetHintCreate}, - {NULL, NULL} + {HINT_SEQSCAN, ScanMethodHintCreate, HINT_KEYWORD_SEQSCAN}, + {HINT_INDEXSCAN, ScanMethodHintCreate, HINT_KEYWORD_INDEXSCAN}, + {HINT_BITMAPSCAN, ScanMethodHintCreate, HINT_KEYWORD_BITMAPSCAN}, + {HINT_TIDSCAN, ScanMethodHintCreate, HINT_KEYWORD_TIDSCAN}, + {HINT_NOSEQSCAN, ScanMethodHintCreate, HINT_KEYWORD_NOSEQSCAN}, + {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_NOINDEXONLYSCAN, ScanMethodHintCreate, HINT_KEYWORD_NOINDEXONLYSCAN}, +#endif + {HINT_NESTLOOP, JoinMethodHintCreate, HINT_KEYWORD_NESTLOOP}, + {HINT_MERGEJOIN, JoinMethodHintCreate, HINT_KEYWORD_MERGEJOIN}, + {HINT_HASHJOIN, JoinMethodHintCreate, HINT_KEYWORD_HASHJOIN}, + {HINT_NONESTLOOP, JoinMethodHintCreate, HINT_KEYWORD_NONESTLOOP}, + {HINT_NOMERGEJOIN, JoinMethodHintCreate, HINT_KEYWORD_NOMERGEJOIN}, + {HINT_NOHASHJOIN, JoinMethodHintCreate, HINT_KEYWORD_NOHASHJOIN}, + {HINT_LEADING, LeadingHintCreate, HINT_KEYWORD_LEADING}, + {HINT_SET, SetHintCreate, HINT_KEYWORD_SET}, + {NULL, NULL, HINT_KEYWORD_UNRECOGNIZED} }; /* @@ -357,10 +433,10 @@ void _PG_init(void) { /* Define custom GUC variables. */ - DefineCustomBoolVariable("pg_hint_plan.enable", + DefineCustomBoolVariable("pg_hint_plan.enable_hint", "Force planner to use plans specified in the hint comment preceding to the query.", NULL, - &pg_hint_plan_enable, + &pg_hint_plan_enable_hint, true, PGC_USERSET, 0, @@ -380,7 +456,7 @@ _PG_init(void) NULL); DefineCustomEnumVariable("pg_hint_plan.parse_messages", - "Messege level of parse errors.", + "Message level of parse errors.", NULL, &pg_hint_plan_parse_messages, INFO, @@ -421,19 +497,21 @@ _PG_fini(void) */ static Hint * -ScanMethodHintCreate(const char *hint_str, const char *keyword) +ScanMethodHintCreate(const char *hint_str, const char *keyword, + HintKeyword hint_keyword) { ScanMethodHint *hint; hint = palloc(sizeof(ScanMethodHint)); hint->base.hint_str = hint_str; hint->base.keyword = keyword; + hint->base.hint_keyword = hint_keyword; hint->base.type = HINT_TYPE_SCAN_METHOD; hint->base.state = HINT_STATE_NOTUSED; hint->base.delete_func = (HintDeleteFunction) ScanMethodHintDelete; hint->base.dump_func = (HintDumpFunction) ScanMethodHintDump; hint->base.cmp_func = (HintCmpFunction) ScanMethodHintCmp; - hint->base.parser_func = (HintParseFunction) ScanMethodHintParse; + hint->base.parse_func = (HintParseFunction) ScanMethodHintParse; hint->relname = NULL; hint->indexnames = NIL; hint->enforce_mask = 0; @@ -454,23 +532,27 @@ ScanMethodHintDelete(ScanMethodHint *hint) } static Hint * -JoinMethodHintCreate(const char *hint_str, const char *keyword) +JoinMethodHintCreate(const char *hint_str, const char *keyword, + HintKeyword hint_keyword) { JoinMethodHint *hint; hint = palloc(sizeof(JoinMethodHint)); hint->base.hint_str = hint_str; hint->base.keyword = keyword; + hint->base.hint_keyword = hint_keyword; hint->base.type = HINT_TYPE_JOIN_METHOD; hint->base.state = HINT_STATE_NOTUSED; hint->base.delete_func = (HintDeleteFunction) JoinMethodHintDelete; hint->base.dump_func = (HintDumpFunction) JoinMethodHintDump; hint->base.cmp_func = (HintCmpFunction) JoinMethodHintCmp; - hint->base.parser_func = (HintParseFunction) JoinMethodHintParse; + hint->base.parse_func = (HintParseFunction) JoinMethodHintParse; hint->nrels = 0; + hint->inner_nrels = 0; hint->relnames = NULL; hint->enforce_mask = 0; hint->joinrelids = NULL; + hint->inner_joinrelids = NULL; return (Hint *) hint; } @@ -489,25 +571,30 @@ JoinMethodHintDelete(JoinMethodHint *hint) pfree(hint->relnames[i]); pfree(hint->relnames); } + bms_free(hint->joinrelids); + bms_free(hint->inner_joinrelids); pfree(hint); } static Hint * -LeadingHintCreate(const char *hint_str, const char *keyword) +LeadingHintCreate(const char *hint_str, const char *keyword, + HintKeyword hint_keyword) { LeadingHint *hint; hint = palloc(sizeof(LeadingHint)); hint->base.hint_str = hint_str; hint->base.keyword = keyword; + hint->base.hint_keyword = hint_keyword; hint->base.type = HINT_TYPE_LEADING; hint->base.state = HINT_STATE_NOTUSED; hint->base.delete_func = (HintDeleteFunction)LeadingHintDelete; hint->base.dump_func = (HintDumpFunction) LeadingHintDump; hint->base.cmp_func = (HintCmpFunction) LeadingHintCmp; - hint->base.parser_func = (HintParseFunction) LeadingHintParse; + hint->base.parse_func = (HintParseFunction) LeadingHintParse; hint->relations = NIL; + hint->outer_inner = NULL; return (Hint *) hint; } @@ -519,25 +606,29 @@ LeadingHintDelete(LeadingHint *hint) return; list_free_deep(hint->relations); + free(hint->outer_inner); pfree(hint); } static Hint * -SetHintCreate(const char *hint_str, const char *keyword) +SetHintCreate(const char *hint_str, const char *keyword, + HintKeyword hint_keyword) { SetHint *hint; hint = palloc(sizeof(SetHint)); hint->base.hint_str = hint_str; hint->base.keyword = keyword; + hint->base.hint_keyword = hint_keyword; hint->base.type = HINT_TYPE_SET; hint->base.state = HINT_STATE_NOTUSED; hint->base.delete_func = (HintDeleteFunction) SetHintDelete; hint->base.dump_func = (HintDumpFunction) SetHintDump; hint->base.cmp_func = (HintCmpFunction) SetHintCmp; - hint->base.parser_func = (HintParseFunction) SetHintParse; + hint->base.parse_func = (HintParseFunction) SetHintParse; hint->name = NULL; hint->value = NULL; + hint->words = NIL; return (Hint *) hint; } @@ -552,64 +643,79 @@ SetHintDelete(SetHint *hint) pfree(hint->name); if (hint->value) pfree(hint->value); + if (hint->words) + list_free(hint->words); pfree(hint); } -static PlanHint * -PlanHintCreate(void) -{ - PlanHint *hint; - - hint = palloc(sizeof(PlanHint)); - hint->hint_str = NULL; - hint->nall_hints = 0; - hint->max_all_hints = 0; - hint->all_hints = NULL; - memset(hint->num_hints, 0, sizeof(hint->num_hints)); - hint->scan_hints = NULL; - hint->init_scan_mask = 0; - hint->parent_relid = 0; - hint->parent_hint = NULL; - hint->join_hints = NULL; - hint->init_join_mask = 0; - hint->join_hint_level = NULL; - hint->leading_hint = NULL; - hint->context = superuser() ? PGC_SUSET : PGC_USERSET; - hint->set_hints = NULL; - - return hint; +static HintState * +HintStateCreate(void) +{ + HintState *hstate; + + hstate = palloc(sizeof(HintState)); + hstate->hint_str = NULL; + hstate->nall_hints = 0; + hstate->max_all_hints = 0; + hstate->all_hints = NULL; + memset(hstate->num_hints, 0, sizeof(hstate->num_hints)); + hstate->scan_hints = NULL; + hstate->init_scan_mask = 0; + hstate->parent_relid = 0; + hstate->parent_hint = NULL; + hstate->join_hints = NULL; + hstate->init_join_mask = 0; + hstate->join_hint_level = NULL; + hstate->leading_hint = NULL; + hstate->context = superuser() ? PGC_SUSET : PGC_USERSET; + hstate->set_hints = NULL; + + return hstate; } static void -PlanHintDelete(PlanHint *hint) +HintStateDelete(HintState *hstate) { int i; - if (!hint) + if (!hstate) return; - if (hint->hint_str) - pfree(hint->hint_str); + if (hstate->hint_str) + pfree(hstate->hint_str); - for (i = 0; i < hint->num_hints[HINT_TYPE_SCAN_METHOD]; i++) - hint->all_hints[i]->delete_func(hint->all_hints[i]); - if (hint->all_hints) - pfree(hint->all_hints); + for (i = 0; i < hstate->num_hints[HINT_TYPE_SCAN_METHOD]; i++) + hstate->all_hints[i]->delete_func(hstate->all_hints[i]); + if (hstate->all_hints) + pfree(hstate->all_hints); } /* * dump functions */ +/* + * Quote value as needed and dump it to buf. + */ static void dump_quote_value(StringInfo buf, const char *value) { + dump_quote_value_quote_char(buf, value, '"'); +} + +/* + * We specified a character to quote for by addition in quote_char. + * If there is not a character to quote, we specified '"'. + */ +static void +dump_quote_value_quote_char(StringInfo buf, const char *value, char quote_char) +{ bool need_quote = false; const char *str; for (str = value; *str != '\0'; str++) { - if (isspace(*str) || *str == ')' || *str == '"') + if (isspace(*str) || *str == ')' || *str == '"' || *str == quote_char) { need_quote = true; appendStringInfoCharMacro(buf, '"'); @@ -635,11 +741,14 @@ ScanMethodHintDump(ScanMethodHint *hint, StringInfo buf) ListCell *l; appendStringInfo(buf, "%s(", hint->base.keyword); - dump_quote_value(buf, hint->relname); - foreach(l, hint->indexnames) + if (hint->relname != NULL) { - appendStringInfoCharMacro(buf, ' '); - dump_quote_value(buf, (char *) lfirst(l)); + dump_quote_value(buf, hint->relname); + foreach(l, hint->indexnames) + { + appendStringInfoCharMacro(buf, ' '); + dump_quote_value(buf, (char *) lfirst(l)); + } } appendStringInfoString(buf, ")\n"); } @@ -650,17 +759,47 @@ JoinMethodHintDump(JoinMethodHint *hint, StringInfo buf) int i; appendStringInfo(buf, "%s(", hint->base.keyword); - dump_quote_value(buf, hint->relnames[0]); - for (i = 1; i < hint->nrels; i++) + if (hint->relnames != NULL) { - appendStringInfoCharMacro(buf, ' '); - dump_quote_value(buf, hint->relnames[i]); + dump_quote_value(buf, hint->relnames[0]); + for (i = 1; i < hint->nrels; i++) + { + appendStringInfoCharMacro(buf, ' '); + dump_quote_value(buf, hint->relnames[i]); + } } appendStringInfoString(buf, ")\n"); } static void +OuterInnerDump(OuterInnerRels *outer_inner, StringInfo buf) +{ + if (outer_inner->relation == NULL) + { + bool is_first; + ListCell *l; + + is_first = true; + + appendStringInfoCharMacro(buf, '('); + foreach(l, outer_inner->outer_inner_pair) + { + if (is_first) + is_first = false; + else + appendStringInfoCharMacro(buf, ' '); + + OuterInnerDump(lfirst(l), buf); + } + + appendStringInfoCharMacro(buf, ')'); + } + else + dump_quote_value_quote_char(buf, outer_inner->relation, '('); +} + +static void LeadingHintDump(LeadingHint *hint, StringInfo buf) { bool is_first; @@ -668,15 +807,20 @@ LeadingHintDump(LeadingHint *hint, StringInfo buf) appendStringInfo(buf, "%s(", HINT_LEADING); is_first = true; - foreach(l, hint->relations) + if (hint->outer_inner == NULL) { - if (is_first) - is_first = false; - else - appendStringInfoCharMacro(buf, ' '); + foreach(l, hint->relations) + { + if (is_first) + is_first = false; + else + appendStringInfoCharMacro(buf, ' '); - dump_quote_value(buf, (char *) lfirst(l)); + dump_quote_value(buf, (char *) lfirst(l)); + } } + else + OuterInnerDump(hint->outer_inner, buf); appendStringInfoString(buf, ")\n"); } @@ -684,35 +828,44 @@ LeadingHintDump(LeadingHint *hint, StringInfo buf) static void SetHintDump(SetHint *hint, StringInfo buf) { + bool is_first = true; + ListCell *l; + appendStringInfo(buf, "%s(", HINT_SET); - dump_quote_value(buf, hint->name); - appendStringInfoCharMacro(buf, ' '); - dump_quote_value(buf, hint->value); + foreach(l, hint->words) + { + if (is_first) + is_first = false; + else + appendStringInfoCharMacro(buf, ' '); + + dump_quote_value(buf, (char *) lfirst(l)); + } appendStringInfo(buf, ")\n"); } static void -all_hint_dump(PlanHint *hint, StringInfo buf, const char *title, +all_hint_dump(HintState *hstate, StringInfo buf, const char *title, HintStatus state) { int i; appendStringInfo(buf, "%s:\n", title); - for (i = 0; i < hint->nall_hints; i++) + for (i = 0; i < hstate->nall_hints; i++) { - if (hint->all_hints[i]->state != state) + if (hstate->all_hints[i]->state != state) continue; - hint->all_hints[i]->dump_func(hint->all_hints[i], buf); + hstate->all_hints[i]->dump_func(hstate->all_hints[i], buf); } } static void -PlanHintDump(PlanHint *hint) +HintStateDump(HintState *hstate) { StringInfoData buf; - if (!hint) + if (!hstate) { elog(LOG, "pg_hint_plan:\nno hint"); return; @@ -721,10 +874,10 @@ PlanHintDump(PlanHint *hint) initStringInfo(&buf); appendStringInfoString(&buf, "pg_hint_plan:\n"); - all_hint_dump(hint, &buf, "used hint", HINT_STATE_USED); - all_hint_dump(hint, &buf, "not used hint", HINT_STATE_NOTUSED); - all_hint_dump(hint, &buf, "duplication hint", HINT_STATE_DUPLICATION); - all_hint_dump(hint, &buf, "error hint", HINT_STATE_ERROR); + all_hint_dump(hstate, &buf, "used hint", HINT_STATE_USED); + all_hint_dump(hstate, &buf, "not used hint", HINT_STATE_NOTUSED); + all_hint_dump(hstate, &buf, "duplication hint", HINT_STATE_DUPLICATION); + all_hint_dump(hstate, &buf, "error hint", HINT_STATE_ERROR); elog(LOG, "%s", buf.data); @@ -781,32 +934,41 @@ SetHintCmp(const SetHint *a, const SetHint *b) } static int -AllHintCmp(const void *a, const void *b, bool order) +HintCmp(const void *a, const void *b) { const Hint *hinta = *((const Hint **) a); const Hint *hintb = *((const Hint **) b); - int result = 0; if (hinta->type != hintb->type) return hinta->type - hintb->type; - - if ((result = hinta->cmp_func(hinta, hintb)) != 0 || !order) - return result; - - /* ヒント句で指定した順を返す */ - return hinta->hint_str - hintb->hint_str; + if (hinta->state == HINT_STATE_ERROR) + return -1; + if (hintb->state == HINT_STATE_ERROR) + return 1; + return hinta->cmp_func(hinta, hintb); } +/* + * Returns byte offset of hint b from hint a. If hint a was specified before + * b, positive value is returned. + */ static int -AllHintCmpIsOrder(const void *a, const void *b) +HintCmpWithPos(const void *a, const void *b) { - return AllHintCmp(a, b, true); + const Hint *hinta = *((const Hint **) a); + const Hint *hintb = *((const Hint **) b); + int result; + + result = HintCmp(a, b); + if (result == 0) + result = hinta->hint_str - hintb->hint_str; + + return result; } /* * parse functions */ - static const char * parse_keyword(const char *str, StringInfo buf) { @@ -819,13 +981,17 @@ parse_keyword(const char *str, StringInfo buf) } static const char * -skip_opened_parenthesis(const char *str) +skip_parenthesis(const char *str, char parenthesis) { skip_space(str); - if (*str != '(') + if (*str != parenthesis) { - parse_ereport(str, ("Opening parenthesis is necessary.")); + if (parenthesis == '(') + hint_ereport(str, ("Opening parenthesis is necessary.")); + else if (parenthesis == ')') + hint_ereport(str, ("Closing parenthesis is necessary.")); + return NULL; } @@ -834,37 +1000,31 @@ skip_opened_parenthesis(const char *str) return str; } +/* + * Parse a token from str, and store malloc'd copy into word. A token can be + * quoted with '"'. Return value is pointer to unparsed portion of original + * string, or NULL if an error occurred. + * + * Parsed token is truncated within NAMEDATALEN-1 bytes, when truncate is true. + */ static const char * -skip_closed_parenthesis(const char *str) +parse_quote_value(const char *str, char **word, bool truncate) { - skip_space(str); - - if (*str != ')') - { - parse_ereport(str, ("Closing parenthesis is necessary.")); - return NULL; - } - - str++; - - return str; + return parse_quote_value_term_char(str, word, truncate, '\0'); } /* - * 二重引用符で囲まれているかもしれないトークンを読み取り word 引数に palloc - * で確保したバッファに格納してそのポインタを返す。 - * - * 正常にパースできた場合は残りの文字列の先頭位置を、異常があった場合は NULL を - * 返す。 - * truncateがtrueの場合は、NAMEDATALENに切り詰める。 + * In term_char, We specified the special character which a terminal of a word. + * When we do not have a special character, We specified '\0'. */ static const char * -parse_quote_value(const char *str, char **word, char *value_type, bool truncate) +parse_quote_value_term_char(const char *str, char **word, bool truncate, + char term_char) { StringInfoData buf; bool in_quote; - /* 先頭のスペースは読み飛ばす。 */ + /* Skip leading spaces. */ skip_space(str); initStringInfo(&buf); @@ -880,20 +1040,23 @@ parse_quote_value(const char *str, char **word, char *value_type, bool truncate) { if (in_quote) { - /* 二重引用符が閉じられていない場合はパース中断 */ + /* Double quotation must be closed. */ if (*str == '\0') { pfree(buf.data); - parse_ereport(str, ("Unterminated quoted %s.", value_type)); + hint_ereport(str, ("Unterminated quoted string.")); return NULL; } /* - * エスケープ対象のダブルクウォートをスキップする。 - * もしブロックコメントの開始文字列や終了文字列もオブジェクト名とし - * て使用したい場合は、/ と * もエスケープ対象とすることで使用できる - * が、処理対象としていない。もしテーブル名にこれらの文字が含まれる - * 場合は、エイリアスを指定する必要がある。 + * Skip escaped double quotation. + * + * We don't allow slash-asterisk and asterisk-slash (delimiters of + * block comments) to be an object name, so users must specify + * alias for such object names. + * + * Those special names can be allowed if we care escaped slashes + * and asterisks, but we don't. */ if (*str == '"') { @@ -902,7 +1065,8 @@ parse_quote_value(const char *str, char **word, char *value_type, bool truncate) break; } } - else if (isspace(*str) || *str == ')' || *str == '"' || *str == '\0') + else if (isspace(*str) || *str == ')' || *str == '"' || *str == '\0' || + *str == term_char) break; appendStringInfoCharMacro(&buf, *str++); @@ -910,19 +1074,14 @@ parse_quote_value(const char *str, char **word, char *value_type, bool truncate) if (buf.len == 0) { - char *type; - - type = pstrdup(value_type); - type[0] = toupper(type[0]); - parse_ereport(str, ("%s is necessary.", type)); + hint_ereport(str, ("Zero-length delimited string.")); pfree(buf.data); - pfree(type); return NULL; } - /* Truncate name if it's overlength */ + /* Truncate name if it's too long */ if (truncate) truncate_identifier(buf.data, strlen(buf.data), true); @@ -931,8 +1090,132 @@ parse_quote_value(const char *str, char **word, char *value_type, bool truncate) return str; } +static OuterInnerRels * +OuterInnerRelsCreate(OuterInnerRels *outer_inner, char *name, List *outer_inner_list) +{ + outer_inner = palloc(sizeof(OuterInnerRels)); + outer_inner->relation = name; + outer_inner->outer_inner_pair = outer_inner_list; + + return outer_inner; +} + +static const char * +parse_parentheses_Leading_in(const char *str, OuterInnerRels **outer_inner) +{ + char *name; + bool truncate = true; + + OuterInnerRels *outer_inner_rels; + outer_inner_rels = NULL; + + skip_space(str); + + /* Store words in parentheses into outer_inner_list. */ + while(*str != ')' && *str != '\0') + { + if (*str == '(') + { + str++; + outer_inner_rels = OuterInnerRelsCreate(outer_inner_rels, + NULL, + NIL); + str = parse_parentheses_Leading_in(str, &outer_inner_rels); + } + else if ((str = parse_quote_value_term_char(str, &name, truncate, '(')) == NULL) + { + list_free((*outer_inner)->outer_inner_pair); + return NULL; + } + else + outer_inner_rels = OuterInnerRelsCreate(outer_inner_rels, name, NIL); + + (*outer_inner)->outer_inner_pair = lappend( + (*outer_inner)->outer_inner_pair, + outer_inner_rels); + skip_space(str); + } + + str++; + + return str; +} + +static const char * +parse_parentheses_Leading(const char *str, List **name_list, + OuterInnerRels **outer_inner) +{ + char *name; + bool truncate = true; + + if ((str = skip_parenthesis(str, '(')) == NULL) + return NULL; + + skip_space(str); + if (*str =='(') + { + str++; + + *outer_inner = OuterInnerRelsCreate(*outer_inner, NULL, NIL); + str = parse_parentheses_Leading_in(str, outer_inner); + } + else + { + /* Store words in parentheses into name_list. */ + while(*str != ')' && *str != '\0') + { + if ((str = parse_quote_value(str, &name, truncate)) == NULL) + { + list_free(*name_list); + return NULL; + } + + *name_list = lappend(*name_list, name); + skip_space(str); + } + } + + if ((str = skip_parenthesis(str, ')')) == NULL) + return NULL; + return str; +} + +static const char * +parse_parentheses(const char *str, List **name_list, HintType type) +{ + char *name; + bool truncate = true; + + if ((str = skip_parenthesis(str, '(')) == NULL) + return NULL; + + skip_space(str); + + /* Store words in parentheses into name_list. */ + while(*str != ')' && *str != '\0') + { + if ((str = parse_quote_value(str, &name, truncate)) == NULL) + { + list_free(*name_list); + return NULL; + } + + *name_list = lappend(*name_list, name); + skip_space(str); + + if (type == HINT_TYPE_SET) + { + truncate = false; + } + } + + if ((str = skip_parenthesis(str, ')')) == NULL) + return NULL; + return str; +} + static void -parse_hints(PlanHint *plan, Query *parse, const char *str) +parse_hints(HintState *hstate, Query *parse, const char *str) { StringInfoData buf; char *head; @@ -957,12 +1240,10 @@ parse_hints(PlanHint *plan, Query *parse, const char *str) if (strcasecmp(buf.data, keyword) != 0) continue; - hint = parser->create_func(head, keyword); + hint = parser->create_func(head, keyword, parser->hint_keyword); /* parser of each hint does parse in a parenthesis. */ - if ((str = skip_opened_parenthesis(str)) == NULL || - (str = hint->parser_func(hint, plan, parse, str)) == NULL || - (str = skip_closed_parenthesis(str)) == NULL) + if ((str = hint->parse_func(hint, hstate, parse, str)) == NULL) { hint->delete_func(hint); pfree(buf.data); @@ -970,22 +1251,25 @@ parse_hints(PlanHint *plan, Query *parse, const char *str) } /* - * 出来上がったヒント情報を追加。スロットが足りない場合は二倍に拡張する。 + * Add hint information into all_hints array. If we don't have + * enough space, double the array. */ - if (plan->nall_hints == 0) + if (hstate->nall_hints == 0) { - plan->max_all_hints = HINT_ARRAY_DEFAULT_INITSIZE; - plan->all_hints = palloc(sizeof(Hint *) * plan->max_all_hints); + hstate->max_all_hints = HINT_ARRAY_DEFAULT_INITSIZE; + hstate->all_hints = (Hint **) + palloc(sizeof(Hint *) * hstate->max_all_hints); } - else if (plan->nall_hints == plan->max_all_hints) + else if (hstate->nall_hints == hstate->max_all_hints) { - plan->max_all_hints *= 2; - plan->all_hints = repalloc(plan->all_hints, - sizeof(Hint *) * plan->max_all_hints); + hstate->max_all_hints *= 2; + hstate->all_hints = (Hint **) + repalloc(hstate->all_hints, + sizeof(Hint *) * hstate->max_all_hints); } - plan->all_hints[plan->nall_hints] = hint; - plan->nall_hints++; + hstate->all_hints[hstate->nall_hints] = hint; + hstate->nall_hints++; skip_space(str); @@ -994,7 +1278,8 @@ parse_hints(PlanHint *plan, Query *parse, const char *str) if (parser->keyword == NULL) { - parse_ereport(head, ("Unrecognized hint keyword \"%s\".", buf.data)); + hint_ereport(head, + ("Unrecognized hint keyword \"%s\".", buf.data)); pfree(buf.data); return; } @@ -1006,7 +1291,7 @@ parse_hints(PlanHint *plan, Query *parse, const char *str) /* * Do basic parsing of the query head comment. */ -static PlanHint * +static HintState * parse_head_comment(Query *parse) { const char *p; @@ -1014,7 +1299,7 @@ parse_head_comment(Query *parse) char *tail; int len; int i; - PlanHint *plan; + HintState *hstate; /* get client-supplied query string. */ if (stmt_name) @@ -1043,227 +1328,317 @@ parse_head_comment(Query *parse) /* find hint end keyword. */ if ((tail = strstr(p, HINT_END)) == NULL) { - parse_ereport(head, ("Unterminated block comment.")); + hint_ereport(head, ("Unterminated block comment.")); return NULL; } - /* 入れ子にしたブロックコメントはサポートしない */ + /* We don't support nested block comments. */ if ((head = strstr(p, BLOCK_COMMENT_START)) != NULL && head < tail) { - parse_ereport(head, ("Nested block comments are not supported.")); + hint_ereport(head, ("Nested block comments are not supported.")); return NULL; } - /* ヒント句部分を切り出す */ + /* Make a copy of hint. */ len = tail - p; head = palloc(len + 1); memcpy(head, p, len); head[len] = '\0'; p = head; - plan = PlanHintCreate(); - plan->hint_str = head; + hstate = HintStateCreate(); + hstate->hint_str = head; /* parse each hint. */ - parse_hints(plan, parse, p); + parse_hints(hstate, parse, p); - /* When nothing specified a hint, we free PlanHint and returns NULL. */ - if (plan->nall_hints == 0) + /* When nothing specified a hint, we free HintState and returns NULL. */ + if (hstate->nall_hints == 0) { - PlanHintDelete(plan); + HintStateDelete(hstate); return NULL; } - /* パースしたヒントを並び替える */ - qsort(plan->all_hints, plan->nall_hints, sizeof(Hint *), AllHintCmpIsOrder); + /* Sort hints in order of original position. */ + qsort(hstate->all_hints, hstate->nall_hints, sizeof(Hint *), + HintCmpWithPos); - /* 重複したヒントを検索する */ - for (i = 0; i < plan->nall_hints; i++) + /* Count number of hints per hint-type. */ + for (i = 0; i < hstate->nall_hints; i++) { - Hint *hint = plan->all_hints[i]; + Hint *cur_hint = hstate->all_hints[i]; + hstate->num_hints[cur_hint->type]++; + } - plan->num_hints[hint->type]++; + /* + * 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. + * Hints except the last are marked as 'duplicated' to remember the order. + */ + for (i = 0; i < hstate->nall_hints - 1; i++) + { + Hint *cur_hint = hstate->all_hints[i]; + Hint *next_hint = hstate->all_hints[i + 1]; - if (i + 1 >= plan->nall_hints) - break; + /* + * TODO : Leadingヒントの重複チェックは、transform_join_hints関数の実行 + *    中に実施する。 + */ + if (i >= hstate->num_hints[HINT_TYPE_SCAN_METHOD] + + hstate->num_hints[HINT_TYPE_JOIN_METHOD] && + i < hstate->num_hints[HINT_TYPE_SCAN_METHOD] + + hstate->num_hints[HINT_TYPE_JOIN_METHOD] + + hstate->num_hints[HINT_TYPE_LEADING]) + continue; - if (AllHintCmp(plan->all_hints + i, plan->all_hints + i + 1, false) == - 0) + /* + * Note that we need to pass addresses of hint pointers, because + * HintCmp is designed to sort array of Hint* by qsort. + */ + if (HintCmp(&cur_hint, &next_hint) == 0) { - const char *HintTypeName[] = {"scan method", "join method", - "leading", "set"}; - - parse_ereport(plan->all_hints[i]->hint_str, - ("Conflict %s hint.", HintTypeName[hint->type])); - plan->all_hints[i]->state = HINT_STATE_DUPLICATION; + hint_ereport(cur_hint->hint_str, + ("Conflict %s hint.", HintTypeName[cur_hint->type])); + cur_hint->state = HINT_STATE_DUPLICATION; } } - plan->scan_hints = (ScanMethodHint **) plan->all_hints; - plan->join_hints = (JoinMethodHint **) plan->all_hints + - plan->num_hints[HINT_TYPE_SCAN_METHOD]; - plan->leading_hint = (LeadingHint *) plan->all_hints[ - plan->num_hints[HINT_TYPE_SCAN_METHOD] + - plan->num_hints[HINT_TYPE_JOIN_METHOD] + - plan->num_hints[HINT_TYPE_LEADING] - 1]; - plan->set_hints = (SetHint **) plan->all_hints + - plan->num_hints[HINT_TYPE_SCAN_METHOD] + - plan->num_hints[HINT_TYPE_JOIN_METHOD] + - plan->num_hints[HINT_TYPE_LEADING]; - - return plan; + /* + * Make sure that per-type array pointers point proper position in the + * array which consists of all hints. + */ + hstate->scan_hints = (ScanMethodHint **) hstate->all_hints; + hstate->join_hints = (JoinMethodHint **) hstate->all_hints + + hstate->num_hints[HINT_TYPE_SCAN_METHOD]; + hstate->leading_hint = (LeadingHint **) hstate->all_hints + + hstate->num_hints[HINT_TYPE_SCAN_METHOD] + + hstate->num_hints[HINT_TYPE_JOIN_METHOD]; + hstate->set_hints = (SetHint **) hstate->all_hints + + hstate->num_hints[HINT_TYPE_SCAN_METHOD] + + hstate->num_hints[HINT_TYPE_JOIN_METHOD] + + hstate->num_hints[HINT_TYPE_LEADING]; + + return hstate; } /* - * スキャン方式ヒントのカッコ内をパースする + * Parse inside of parentheses of scan-method hints. */ static const char * -ScanMethodHintParse(ScanMethodHint *hint, PlanHint *plan, Query *parse, +ScanMethodHintParse(ScanMethodHint *hint, HintState *hstate, Query *parse, const char *str) { - const char *keyword = hint->base.keyword; + const char *keyword = hint->base.keyword; + HintKeyword hint_keyword = hint->base.hint_keyword; + List *name_list = NIL; + int length; - /* - * スキャン方式のヒントでリレーション名が読み取れない場合はヒント無効 - */ - if ((str = parse_quote_value(str, &hint->relname, "relation name", true)) - == NULL) + if ((str = parse_parentheses(str, &name_list, hint->base.type)) == NULL) return NULL; - skip_space(str); - - /* - * インデックスリストを受け付けるヒントであれば、インデックス参照をパース - * する。 - */ - if (strcmp(keyword, HINT_INDEXSCAN) == 0 || - strcmp(keyword, HINT_BITMAPSCAN) == 0) + /* Parse relation name and index name(s) if given hint accepts. */ + length = list_length(name_list); + if (length > 0) { - while (*str != ')' && *str != '\0') - { - char *indexname; + hint->relname = linitial(name_list); + hint->indexnames = list_delete_first(name_list); - str = parse_quote_value(str, &indexname, "index name", true); - if (str == NULL) - return NULL; - - hint->indexnames = lappend(hint->indexnames, indexname); - skip_space(str); + /* check whether the hint accepts index name(s). */ + if (hint_keyword != HINT_KEYWORD_INDEXSCAN && +#if PG_VERSION_NUM >= 90200 + hint_keyword != HINT_KEYWORD_INDEXONLYSCAN && +#endif + hint_keyword != HINT_KEYWORD_BITMAPSCAN && + length != 1) + { + hint_ereport(str, + ("%s hint accepts only one relation.", + hint->base.keyword)); + hint->base.state = HINT_STATE_ERROR; + return str; } } - - /* - * ヒントごとに決まっている許容スキャン方式をビットマスクとして設定 - */ - if (strcasecmp(keyword, HINT_SEQSCAN) == 0) - hint->enforce_mask = ENABLE_SEQSCAN; - else if (strcasecmp(keyword, HINT_INDEXSCAN) == 0) - hint->enforce_mask = ENABLE_INDEXSCAN; - else if (strcasecmp(keyword, HINT_BITMAPSCAN) == 0) - hint->enforce_mask = ENABLE_BITMAPSCAN; - else if (strcasecmp(keyword, HINT_TIDSCAN) == 0) - hint->enforce_mask = ENABLE_TIDSCAN; - else if (strcasecmp(keyword, HINT_NOSEQSCAN) == 0) - hint->enforce_mask = ENABLE_ALL_SCAN ^ ENABLE_SEQSCAN; - else if (strcasecmp(keyword, HINT_NOINDEXSCAN) == 0) - hint->enforce_mask = ENABLE_ALL_SCAN ^ ENABLE_INDEXSCAN; - else if (strcasecmp(keyword, HINT_NOBITMAPSCAN) == 0) - hint->enforce_mask = ENABLE_ALL_SCAN ^ ENABLE_BITMAPSCAN; - else if (strcasecmp(keyword, HINT_NOTIDSCAN) == 0) - hint->enforce_mask = ENABLE_ALL_SCAN ^ ENABLE_TIDSCAN; else { - parse_ereport(str, ("Unrecognized hint keyword \"%s\".", keyword)); - return NULL; + hint_ereport(str, + ("%s hint requires a relation.", + hint->base.keyword)); + hint->base.state = HINT_STATE_ERROR; + return str; + } + + /* Set a bit for specified hint. */ + switch (hint_keyword) + { + case HINT_KEYWORD_SEQSCAN: + hint->enforce_mask = ENABLE_SEQSCAN; + break; + case HINT_KEYWORD_INDEXSCAN: + hint->enforce_mask = ENABLE_INDEXSCAN; + break; + case HINT_KEYWORD_BITMAPSCAN: + hint->enforce_mask = ENABLE_BITMAPSCAN; + break; + case HINT_KEYWORD_TIDSCAN: + hint->enforce_mask = ENABLE_TIDSCAN; + break; + case HINT_KEYWORD_NOSEQSCAN: + hint->enforce_mask = ENABLE_ALL_SCAN ^ ENABLE_SEQSCAN; + break; + case HINT_KEYWORD_NOINDEXSCAN: + hint->enforce_mask = ENABLE_ALL_SCAN ^ ENABLE_INDEXSCAN; + break; + case HINT_KEYWORD_NOBITMAPSCAN: + hint->enforce_mask = ENABLE_ALL_SCAN ^ ENABLE_BITMAPSCAN; + break; + 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; + 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; + break; } return str; } static const char * -JoinMethodHintParse(JoinMethodHint *hint, PlanHint *plan, Query *parse, +JoinMethodHintParse(JoinMethodHint *hint, HintState *hstate, Query *parse, const char *str) { - char *relname; - const char *keyword = hint->base.keyword; + const char *keyword = hint->base.keyword; + HintKeyword hint_keyword = hint->base.hint_keyword; + List *name_list = NIL; - skip_space(str); + if ((str = parse_parentheses(str, &name_list, hint->base.type)) == NULL) + return NULL; - hint->relnames = palloc(sizeof(char *)); + hint->nrels = list_length(name_list); - while ((str = parse_quote_value(str, &relname, "relation name", true)) - != NULL) + if (hint->nrels > 0) { - hint->nrels++; - hint->relnames = repalloc(hint->relnames, sizeof(char *) * hint->nrels); - hint->relnames[hint->nrels - 1] = relname; + ListCell *l; + int i = 0; - skip_space(str); - if (*str == ')') - break; + /* + * Transform relation names from list to array to sort them with qsort + * after. + */ + hint->relnames = palloc(sizeof(char *) * hint->nrels); + foreach (l, name_list) + { + hint->relnames[i] = lfirst(l); + i++; + } } - if (str == NULL) - return NULL; + list_free(name_list); - /* Join 対象のテーブルは最低でも2つ指定する必要がある */ + /* A join hint requires at least two relations */ if (hint->nrels < 2) { - parse_ereport(str, - ("%s hint requires at least two relations.", - hint->base.keyword)); + hint_ereport(str, + ("%s hint requires at least two relations.", + hint->base.keyword)); hint->base.state = HINT_STATE_ERROR; + return str; } - /* テーブル名順にソートする */ + /* Sort hints in alphabetical order of relation names. */ qsort(hint->relnames, hint->nrels, sizeof(char *), RelnameCmp); - if (strcasecmp(keyword, HINT_NESTLOOP) == 0) - hint->enforce_mask = ENABLE_NESTLOOP; - else if (strcasecmp(keyword, HINT_MERGEJOIN) == 0) - hint->enforce_mask = ENABLE_MERGEJOIN; - else if (strcasecmp(keyword, HINT_HASHJOIN) == 0) - hint->enforce_mask = ENABLE_HASHJOIN; - else if (strcasecmp(keyword, HINT_NONESTLOOP) == 0) - hint->enforce_mask = ENABLE_ALL_JOIN ^ ENABLE_NESTLOOP; - else if (strcasecmp(keyword, HINT_NOMERGEJOIN) == 0) - hint->enforce_mask = ENABLE_ALL_JOIN ^ ENABLE_MERGEJOIN; - else if (strcasecmp(keyword, HINT_NOHASHJOIN) == 0) - hint->enforce_mask = ENABLE_ALL_JOIN ^ ENABLE_HASHJOIN; - else + switch (hint_keyword) { - parse_ereport(str, ("Unrecognized hint keyword \"%s\".", keyword)); - return NULL; + case HINT_KEYWORD_NESTLOOP: + hint->enforce_mask = ENABLE_NESTLOOP; + break; + case HINT_KEYWORD_MERGEJOIN: + hint->enforce_mask = ENABLE_MERGEJOIN; + break; + case HINT_KEYWORD_HASHJOIN: + hint->enforce_mask = ENABLE_HASHJOIN; + break; + case HINT_KEYWORD_NONESTLOOP: + hint->enforce_mask = ENABLE_ALL_JOIN ^ ENABLE_NESTLOOP; + break; + case HINT_KEYWORD_NOMERGEJOIN: + hint->enforce_mask = ENABLE_ALL_JOIN ^ ENABLE_MERGEJOIN; + break; + case HINT_KEYWORD_NOHASHJOIN: + hint->enforce_mask = ENABLE_ALL_JOIN ^ ENABLE_HASHJOIN; + break; + default: + hint_ereport(str, ("Unrecognized hint keyword \"%s\".", keyword)); + return NULL; + break; } return str; } -static const char * -LeadingHintParse(LeadingHint *hint, PlanHint *plan, Query *parse, - const char *str) +static bool +OuterInnerPairCheck(OuterInnerRels *outer_inner) { - skip_space(str); - - while (*str != ')') + ListCell *l; + if (outer_inner->outer_inner_pair == NIL) { - char *relname; - - if ((str = parse_quote_value(str, &relname, "relation name", true)) - == NULL) - return NULL; + if (outer_inner->relation) + return true; + else + return false; + } - hint->relations = lappend(hint->relations, relname); - - skip_space(str); + if (list_length(outer_inner->outer_inner_pair) == 2) + { + foreach(l, outer_inner->outer_inner_pair) + { + if (!OuterInnerPairCheck(lfirst(l))) + return false; + } } + else + return false; + + return true; +} - /* テーブル指定が2つ未満の場合は、Leading ヒントはエラーとする */ - if (list_length(hint->relations) < 2) +static const char * +LeadingHintParse(LeadingHint *hint, HintState *hstate, Query *parse, + const char *str) +{ + List *name_list = NIL; + OuterInnerRels *outer_inner = NULL; + + if ((str = + parse_parentheses_Leading(str, &name_list, &outer_inner)) == NULL) + return NULL; + + hint->relations = name_list; + hint->outer_inner = outer_inner; + + /* A Leading hint requires at least two relations */ + if (list_length(hint->relations) < 2 && hint->outer_inner == NULL) { - parse_ereport(hint->base.hint_str, - ("%s hint requires at least two relations.", - HINT_LEADING)); + hint_ereport(hint->base.hint_str, + ("%s hint requires at least two relations.", + HINT_LEADING)); + hint->base.state = HINT_STATE_ERROR; + } + else if (hint->outer_inner != NULL && + !OuterInnerPairCheck(hint->outer_inner)) + { + hint_ereport(hint->base.hint_str, + ("%s hint requires two sets of relations when parentheses nests.", + HINT_LEADING)); hint->base.state = HINT_STATE_ERROR; } @@ -1271,14 +1646,29 @@ LeadingHintParse(LeadingHint *hint, PlanHint *plan, Query *parse, } static const char * -SetHintParse(SetHint *hint, PlanHint *plan, Query *parse, const char *str) +SetHintParse(SetHint *hint, HintState *hstate, Query *parse, const char *str) { - if ((str = parse_quote_value(str, &hint->name, "parameter name", true)) - == NULL || - (str = parse_quote_value(str, &hint->value, "parameter value", false)) - == NULL) + List *name_list = NIL; + + if ((str = parse_parentheses(str, &name_list, hint->base.type)) == NULL) return NULL; + hint->words = name_list; + + /* We need both name and value to set GUC parameter. */ + if (list_length(name_list) == 2) + { + hint->name = linitial(name_list); + hint->value = lsecond(name_list); + } + else + { + hint_ereport(hint->base.hint_str, + ("%s hint requires name and value of GUC parameter.", + HINT_SET)); + hint->base.state = HINT_STATE_ERROR; + } + return str; } @@ -1296,8 +1686,13 @@ 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(); { @@ -1359,6 +1754,9 @@ 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 @@ -1368,6 +1766,9 @@ 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 @@ -1397,7 +1798,7 @@ pg_hint_plan_ProcessUtility(Node *parsetree, const char *queryString, { Node *node; - if (!pg_hint_plan_enable) + if (!pg_hint_plan_enable_hint) { if (prev_ProcessUtility) (*prev_ProcessUtility) (parsetree, queryString, params, @@ -1413,7 +1814,8 @@ pg_hint_plan_ProcessUtility(Node *parsetree, const char *queryString, if (IsA(node, ExplainStmt)) { /* - * EXPLAIN対象のクエリのパースツリーを取得する + * Draw out parse tree of actual query from Query struct of EXPLAIN + * statement. */ ExplainStmt *stmt; Query *query; @@ -1428,8 +1830,8 @@ pg_hint_plan_ProcessUtility(Node *parsetree, const char *queryString, } /* - * EXECUTEコマンドならば、PREPARE時に指定されたクエリ文字列を取得し、ヒント - * 句の候補として設定する + * 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)) { @@ -1438,7 +1840,28 @@ pg_hint_plan_ProcessUtility(Node *parsetree, const char *queryString, 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(); @@ -1470,41 +1893,38 @@ pg_hint_plan_ProcessUtility(Node *parsetree, const char *queryString, isTopLevel, dest, completionTag); } +/* + * Push a hint into hint stack which is implemented with List struct. Head of + * list is top of stack. + */ static void -push_stack(PlanHint *plan) +push_hint(HintState *hstate) { - struct PlanHintStack *hint_stack; + /* Prepend new hint to the list means pushing to stack. */ + HintStateStack = lcons(hstate, HintStateStack); -/*elog(WARNING,"push");*/ - hint_stack = palloc(sizeof(PlanHintStack)); - hint_stack->plan_hint = plan; - hint_stack->next = head; - head = hint_stack; - -current_hint = head->plan_hint; + /* Pushed hint is the one which should be used hereafter. */ + current_hint = hstate; } +/* Pop a hint from hint stack. Popped hint is automatically discarded. */ static void -pop_stack(void) +pop_hint(void) { - struct PlanHintStack *hint_stack; - -/*elog(WARNING,"pop");*/ - if(!head) - { + /* Hint stack must not be empty. */ + if(HintStateStack == NIL) elog(ERROR, "hint stack is empty"); - return; - } - - hint_stack = head; - head = hint_stack->next; - pfree(hint_stack); - -if(!head) - current_hint = NULL; -else - current_hint = head->plan_hint; + /* + * Take a hint at the head from the list, and free it. Switch current_hint + * to point new head (NULL if the list is empty). + */ + HintStateStack = list_delete_first(HintStateStack); + HintStateDelete(current_hint); + if(HintStateStack == NIL) + current_hint = NULL; + else + current_hint = (HintState *) lfirst(list_head(HintStateStack)); } static PlannedStmt * @@ -1512,18 +1932,31 @@ pg_hint_plan_planner(Query *parse, int cursorOptions, ParamListInfo boundParams) { int save_nestlevel; PlannedStmt *result; - PlanHint *plan; + 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. + */ + if (!pg_hint_plan_enable_hint) + { + current_hint = NULL; + + if (prev_planner) + return (*prev_planner) (parse, cursorOptions, boundParams); + else + return standard_planner(parse, cursorOptions, boundParams); + } - /* 有効なヒントを保存する。 */ - plan = parse_head_comment(parse); + /* Create hint struct from parse tree. */ + hstate = parse_head_comment(parse); /* - * hintが指定されない、または空のhintを指定された場合は通常のparser処理をお - * こなう。 - * 他のフック関数で実行されるhint処理をスキップするために、current_hint 変数をNULL - * に設定しておく。 + * 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 (!pg_hint_plan_enable || plan == NULL) + if (!hstate) { current_hint = NULL; @@ -1533,10 +1966,12 @@ pg_hint_plan_planner(Query *parse, int cursorOptions, ParamListInfo boundParams) return standard_planner(parse, cursorOptions, boundParams); } - /*current_hint = plan;*/ - push_stack(plan); + /* + * Push new hint struct to the hint stack to disable previous hint context. + */ + push_hint(hstate); - /* Set hint で指定されたGUCパラメータを設定する */ + /* Set GUC parameters which are specified with Set hint. */ save_nestlevel = set_config_options(current_hint->set_hints, current_hint->num_hints[HINT_TYPE_SET], current_hint->context); @@ -1549,6 +1984,10 @@ 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) @@ -1556,31 +1995,45 @@ pg_hint_plan_planner(Query *parse, int cursorOptions, ParamListInfo boundParams) if (enable_hashjoin) current_hint->init_join_mask |= ENABLE_HASHJOIN; - if (prev_planner) - result = (*prev_planner) (parse, cursorOptions, boundParams); - else - result = standard_planner(parse, cursorOptions, boundParams); - /* - * Restore the GUC variables we set above. + * Use PG_TRY mechanism to recover GUC parameters and current_hint to the + * state when this planner started when error occurred in planner. */ - AtEOXact_GUC(true, save_nestlevel); + PG_TRY(); + { + if (prev_planner) + result = (*prev_planner) (parse, cursorOptions, boundParams); + else + result = standard_planner(parse, cursorOptions, boundParams); + } + PG_CATCH(); + { + /* + * Rollback changes of GUC parameters, and pop current hint context + * from hint stack to rewind the state. + */ + AtEOXact_GUC(true, save_nestlevel); + pop_hint(); + PG_RE_THROW(); + } + PG_END_TRY(); - /* - * Print hint if debugging. - */ + /* Print hint in debug mode. */ if (pg_hint_plan_debug_print) - PlanHintDump(current_hint); + HintStateDump(current_hint); - PlanHintDelete(current_hint); - /*current_hint = NULL;*/ - pop_stack(); + /* + * Rollback changes of GUC parameters, and pop current hint context from + * hint stack to rewind the state. + */ + AtEOXact_GUC(true, save_nestlevel); + pop_hint(); return result; } /* - * aliasnameと一致するSCANヒントを探す + * Return scan method hint which matches given aliasname. */ static ScanMethodHint * find_scan_hint(PlannerInfo *root, RelOptInfo *rel) @@ -1589,28 +2042,25 @@ find_scan_hint(PlannerInfo *root, RelOptInfo *rel) int i; /* - * RELOPT_BASEREL でなければ、scan method ヒントが適用しない。 - * 子テーブルの場合はRELOPT_OTHER_MEMBER_RELとなるが、サポート対象外とする。 - * また、通常のリレーション以外は、スキャン方式を選択できない。 + * We can't apply scan method hint if the relation is: + * - not a base relation + * - not an ordinary relation (such as join and subquery) */ if (rel->reloptkind != RELOPT_BASEREL || rel->rtekind != RTE_RELATION) return NULL; rte = root->simple_rte_array[rel->relid]; - /* 外部表はスキャン方式が選択できない。 */ + /* We can't force scan method of foreign tables */ if (rte->relkind == RELKIND_FOREIGN_TABLE) return NULL; - /* - * スキャン方式のヒントのリストから、検索対象のリレーションと名称が一致する - * ヒントを検索する。 - */ + /* Find scan method hint, which matches given names, from the list. */ for (i = 0; i < current_hint->num_hints[HINT_TYPE_SCAN_METHOD]; i++) { ScanMethodHint *hint = current_hint->scan_hints[i]; - /* すでに無効となっているヒントは検索対象にしない。 */ + /* We ignore disabled hints. */ if (!hint_state_enabled(hint)) continue; @@ -1622,11 +2072,12 @@ find_scan_hint(PlannerInfo *root, RelOptInfo *rel) } static void -delete_indexes(ScanMethodHint *hint, RelOptInfo *rel) +delete_indexes(ScanMethodHint *hint, RelOptInfo *rel, Oid relationObjectId) { ListCell *cell; ListCell *prev; ListCell *next; + StringInfoData buf; /* * We delete all the IndexOptInfo list and prevent you from being usable by @@ -1653,6 +2104,9 @@ delete_indexes(ScanMethodHint *hint, RelOptInfo *rel) * other than it. */ prev = NULL; + if (pg_hint_plan_debug_print) + initStringInfo(&buf); + for (cell = list_head(rel->indexlist); cell; cell = next) { IndexOptInfo *info = (IndexOptInfo *) lfirst(cell); @@ -1667,6 +2121,12 @@ delete_indexes(ScanMethodHint *hint, RelOptInfo *rel) if (RelnameCmp(&indexname, &lfirst(l)) == 0) { use_index = true; + if (pg_hint_plan_debug_print) + { + appendStringInfoCharMacro(&buf, ' '); + dump_quote_value(&buf, indexname); + } + break; } } @@ -1678,6 +2138,28 @@ delete_indexes(ScanMethodHint *hint, RelOptInfo *rel) pfree(indexname); } + + if (pg_hint_plan_debug_print) + { + char *relname; + StringInfoData rel_buf; + + if (OidIsValid(relationObjectId)) + relname = get_rel_name(relationObjectId); + else + relname = hint->relname; + + initStringInfo(&rel_buf); + dump_quote_value(&rel_buf, relname); + + ereport(LOG, + (errmsg("available indexes for %s(%s):%s", + hint->base.keyword, + rel_buf.data, + buf.data))); + pfree(buf.data); + pfree(rel_buf.data); + } } static void @@ -1689,7 +2171,7 @@ pg_hint_plan_get_relation_info(PlannerInfo *root, Oid relationObjectId, 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) return; @@ -1716,7 +2198,8 @@ pg_hint_plan_get_relation_info(PlannerInfo *root, Oid relationObjectId, appinfo->child_relid == rel->relid) { if (current_hint->parent_hint) - delete_indexes(current_hint->parent_hint, rel); + delete_indexes(current_hint->parent_hint, rel, + relationObjectId); return; } @@ -1727,24 +2210,27 @@ pg_hint_plan_get_relation_info(PlannerInfo *root, Oid relationObjectId, current_hint->parent_hint = NULL; } - /* scan hint が指定されない場合は、GUCパラメータをリセットする。 */ + /* + * 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) { - set_scan_config_options(current_hint->init_scan_mask, current_hint->context); + set_scan_config_options(current_hint->init_scan_mask, + current_hint->context); return; } set_scan_config_options(hint->enforce_mask, current_hint->context); hint->base.state = HINT_STATE_USED; if (inhparent) current_hint->parent_hint = hint; - - delete_indexes(hint, rel); + else + delete_indexes(hint, rel, InvalidOid); } /* - * aliasnameがクエリ中に指定した別名と一致する場合は、そのインデックスを返し、一致 - * する別名がなければ0を返す。 - * aliasnameがクエリ中に複数回指定された場合は、-1を返す。 + * Return index of relation which matches given aliasname, or 0 if not found. + * If same aliasname was used multiple times in a query, return -1. */ static int find_relid_aliasname(PlannerInfo *root, char *aliasname, List *initial_rels, @@ -1762,8 +2248,8 @@ find_relid_aliasname(PlannerInfo *root, char *aliasname, List *initial_rels, Assert(i == root->simple_rel_array[i]->relid); - if (RelnameCmp(&aliasname, &root->simple_rte_array[i]->eref->aliasname) - != 0) + if (RelnameCmp(&aliasname, + &root->simple_rte_array[i]->eref->aliasname) != 0) continue; foreach(l, initial_rels) @@ -1785,9 +2271,9 @@ find_relid_aliasname(PlannerInfo *root, char *aliasname, List *initial_rels, if (found != 0) { - parse_ereport(str, - ("Relation name \"%s\" is ambiguous.", - aliasname)); + hint_ereport(str, + ("Relation name \"%s\" is ambiguous.", + aliasname)); return -1; } @@ -1801,7 +2287,7 @@ find_relid_aliasname(PlannerInfo *root, char *aliasname, List *initial_rels, } /* - * relidビットマスクと一致するヒントを探す + * Return join hint which matches given joinrelids. */ static JoinMethodHint * find_join_hint(Relids joinrelids) @@ -1822,23 +2308,133 @@ find_join_hint(Relids joinrelids) return NULL; } +static List * +OuterInnerList(OuterInnerRels *outer_inner) +{ + List *outer_inner_list = NIL; + ListCell *l; + OuterInnerRels *outer_inner_rels; + + foreach(l, outer_inner->outer_inner_pair) + { + outer_inner_rels = (OuterInnerRels *)(lfirst(l)); + + if (outer_inner_rels->relation != NULL) + outer_inner_list = lappend(outer_inner_list, + outer_inner_rels->relation); + else + outer_inner_list = list_concat(outer_inner_list, + OuterInnerList(outer_inner_rels)); + } + return outer_inner_list; +} + +static Relids +OuterInnerJoinCreate(OuterInnerRels *outer_inner, LeadingHint *leading_hint, + PlannerInfo *root, List *initial_rels, HintState *hstate, int *njoinrels, + int nbaserel) +{ + OuterInnerRels *outer_rels; + OuterInnerRels *inner_rels; + Relids outer_relids; + Relids inner_relids; + Relids join_relids; + JoinMethodHint *hint; + + if (outer_inner->relation != NULL) + { + (*njoinrels)++; + return bms_make_singleton( + find_relid_aliasname(root, outer_inner->relation, + initial_rels, + leading_hint->base.hint_str)); + } + + outer_rels = lfirst(outer_inner->outer_inner_pair->head); + inner_rels = lfirst(outer_inner->outer_inner_pair->tail); + + outer_relids = OuterInnerJoinCreate(outer_rels, + leading_hint, + root, + initial_rels, + hstate, + njoinrels, + nbaserel); + inner_relids = OuterInnerJoinCreate(inner_rels, + leading_hint, + root, + initial_rels, + hstate, + njoinrels, + nbaserel); + + join_relids = bms_add_members(outer_relids, inner_relids); + + if (bms_num_members(join_relids) > nbaserel || *njoinrels > nbaserel) + return join_relids; + + /* + * If we don't have join method hint, create new one for the + * join combination with all join methods are enabled. + */ + hint = find_join_hint(join_relids); + if (hint == NULL) + { + /* + * Here relnames is not set, since Relids bitmap is sufficient to + * control paths of this query afterward. + */ + hint = (JoinMethodHint *) JoinMethodHintCreate( + leading_hint->base.hint_str, + HINT_LEADING, + HINT_KEYWORD_LEADING); + hint->base.state = HINT_STATE_USED; + hint->nrels = bms_num_members(join_relids); + hint->enforce_mask = ENABLE_ALL_JOIN; + hint->joinrelids = bms_copy(join_relids); + hint->inner_nrels = bms_num_members(inner_relids); + hint->inner_joinrelids = bms_copy(inner_relids); + + hstate->join_hint_level[*njoinrels] = + lappend(hstate->join_hint_level[*njoinrels], hint); + } + else + { + hint->inner_nrels = bms_num_members(inner_relids); + hint->inner_joinrelids = bms_copy(inner_relids); + } + + return join_relids; +} + /* - * 結合方式のヒントを使用しやすい構造に変換する。 + * Transform join method hint into handy form. + * + * - create bitmap of relids from alias names, to make it easier to check + * whether a join path matches a join method hint. + * - add join method hints which are necessary to enforce join order + * specified by Leading hint */ static void -transform_join_hints(PlanHint *plan, PlannerInfo *root, int nbaserel, - List *initial_rels, JoinMethodHint **join_method_hints) +transform_join_hints(HintState *hstate, PlannerInfo *root, int nbaserel, + List *initial_rels, JoinMethodHint **join_method_hints, + LeadingHint *lhint) { int i; int relid; - LeadingHint *lhint; Relids joinrelids; int njoinrels; ListCell *l; + char *relname; + bool exist_effective_leading; - for (i = 0; i < plan->num_hints[HINT_TYPE_JOIN_METHOD]; i++) + /* + * Create bitmap of relids from alias names for each join method hint. + * Bitmaps are more handy than strings in join searching. + */ + for (i = 0; i < hstate->num_hints[HINT_TYPE_JOIN_METHOD]; i++) { - JoinMethodHint *hint = plan->join_hints[i]; + JoinMethodHint *hint = hstate->join_hints[i]; int j; if (!hint_state_enabled(hint) || hint->nrels > nbaserel) @@ -1849,7 +2445,7 @@ transform_join_hints(PlanHint *plan, PlannerInfo *root, int nbaserel, relid = 0; for (j = 0; j < hint->nrels; j++) { - char *relname = hint->relnames[j]; + relname = hint->relnames[j]; relid = find_relid_aliasname(root, relname, initial_rels, hint->base.hint_str); @@ -1862,8 +2458,8 @@ transform_join_hints(PlanHint *plan, PlannerInfo *root, int nbaserel, if (bms_is_member(relid, hint->joinrelids)) { - parse_ereport(hint->base.hint_str, - ("Relation name \"%s\" is duplicated.", relname)); + hint_ereport(hint->base.hint_str, + ("Relation name \"%s\" is duplicated.", relname)); hint->base.state = HINT_STATE_ERROR; break; } @@ -1874,95 +2470,206 @@ transform_join_hints(PlanHint *plan, PlannerInfo *root, int nbaserel, if (relid <= 0 || hint->base.state == HINT_STATE_ERROR) continue; - plan->join_hint_level[hint->nrels] = - lappend(plan->join_hint_level[hint->nrels], hint); + hstate->join_hint_level[hint->nrels] = + lappend(hstate->join_hint_level[hint->nrels], hint); } - /* - * 有効なLeading ヒントが指定されている場合は、結合順にあわせてjoin method hint - * のフォーマットに変換する。 - */ - if (plan->num_hints[HINT_TYPE_LEADING] == 0) + /* Do nothing if no Leading hint was supplied. */ + if (hstate->num_hints[HINT_TYPE_LEADING] == 0) return; - lhint = plan->leading_hint; - if (!hint_state_enabled(lhint)) - return; + /* + * Decide to use Leading hint。 + */ + exist_effective_leading = false; + for (i = hstate->num_hints[HINT_TYPE_LEADING] - 1; i >= 0; i--) { + LeadingHint *leading_hint = (LeadingHint *)hstate->leading_hint[i]; - /* Leading hint は、全ての join 方式が有効な hint として登録する */ - joinrelids = NULL; - njoinrels = 0; - foreach(l, lhint->relations) - { - char *relname = (char *)lfirst(l); - JoinMethodHint *hint; + List *relname_list; + Relids relids; - relid = - find_relid_aliasname(root, relname, initial_rels, plan->hint_str); + if (leading_hint->base.state == HINT_STATE_ERROR) + continue; + + if (leading_hint->outer_inner != NULL) + relname_list = OuterInnerList(leading_hint->outer_inner); + else + relname_list = leading_hint->relations; + + relid = 0; + relids = 0; - if (relid == -1) + foreach(l, relname_list) { - bms_free(joinrelids); - return; + relname = (char *)lfirst(l);; + + relid = find_relid_aliasname(root, relname, initial_rels, + leading_hint->base.hint_str); + if (relid == -1) + leading_hint->base.state = HINT_STATE_ERROR; + + if (relid <= 0) + break; + + if (bms_is_member(relid, relids)) + { + hint_ereport(leading_hint->base.hint_str, + ("Relation name \"%s\" is duplicated.", relname)); + leading_hint->base.state = HINT_STATE_ERROR; + break; + } + + relids = bms_add_member(relids, relid); } - if (relid == 0) + if (relid <= 0 || leading_hint->base.state == HINT_STATE_ERROR) continue; - if (bms_is_member(relid, joinrelids)) + if (exist_effective_leading) { - parse_ereport(lhint->base.hint_str, - ("Relation name \"%s\" is duplicated.", relname)); - lhint->base.state = HINT_STATE_ERROR; - bms_free(joinrelids); - return; + hint_ereport(leading_hint->base.hint_str, + ("Conflict %s hint.", HintTypeName[leading_hint->base.type])); + leading_hint->base.state = HINT_STATE_DUPLICATION; + } + else + { + leading_hint->base.state = HINT_STATE_USED; + exist_effective_leading = true; + lhint = leading_hint; } + } + if (exist_effective_leading == false) + return; - joinrelids = bms_add_member(joinrelids, relid); - njoinrels++; + /* + * We need join method hints which fit specified join order in every join + * level. For example, Leading(A B C) virtually requires following join + * method hints, if no join method hint supplied: + * - level 1: none + * - level 2: NestLoop(A B), MergeJoin(A B), HashJoin(A B) + * - level 3: NestLoop(A B C), MergeJoin(A B C), HashJoin(A B C) + * + * If we already have join method hint which fits specified join order in + * that join level, we leave it as-is and don't add new hints. + */ + joinrelids = NULL; + njoinrels = 0; + if (lhint->outer_inner == NULL) + { + foreach(l, lhint->relations) + { + JoinMethodHint *hint; - if (njoinrels < 2) - continue; + relname = (char *)lfirst(l); - hint = find_join_hint(joinrelids); - if (hint == NULL) - { /* - * Here relnames is not set, since Relids bitmap is sufficient to - * control paths of this query afterwards. + * Find relid of the relation which has given name. If we have the + * name given in Leading hint multiple times in the join, nothing to + * do. */ - hint = (JoinMethodHint *) JoinMethodHintCreate(lhint->base.hint_str, - HINT_LEADING); - hint->base.state = HINT_STATE_USED; - hint->nrels = njoinrels; - hint->enforce_mask = ENABLE_ALL_JOIN; - hint->joinrelids = bms_copy(joinrelids); + relid = find_relid_aliasname(root, relname, initial_rels, + hstate->hint_str); + + /* Create bitmap of relids for current join level. */ + joinrelids = bms_add_member(joinrelids, relid); + njoinrels++; + + /* We never have join method hint for single relation. */ + if (njoinrels < 2) + continue; + + /* + * If we don't have join method hint, create new one for the + * join combination with all join methods are enabled. + */ + hint = find_join_hint(joinrelids); + if (hint == NULL) + { + /* + * Here relnames is not set, since Relids bitmap is sufficient + * to control paths of this query afterward. + */ + hint = (JoinMethodHint *) JoinMethodHintCreate( + lhint->base.hint_str, + HINT_LEADING, + HINT_KEYWORD_LEADING); + hint->base.state = HINT_STATE_USED; + hint->nrels = njoinrels; + hint->enforce_mask = ENABLE_ALL_JOIN; + hint->joinrelids = bms_copy(joinrelids); + } + + join_method_hints[njoinrels] = hint; + + if (njoinrels >= nbaserel) + break; } + bms_free(joinrelids); - join_method_hints[njoinrels] = hint; + if (njoinrels < 2) + return; - if (njoinrels >= nbaserel) - break; + /* + * Delete all join hints which have different combination from Leading + * hint. + */ + for (i = 2; i <= njoinrels; i++) + { + list_free(hstate->join_hint_level[i]); + + hstate->join_hint_level[i] = lappend(NIL, join_method_hints[i]); + } } + else + { + joinrelids = OuterInnerJoinCreate(lhint->outer_inner, + lhint, + root, + initial_rels, + hstate, + &njoinrels, + nbaserel); + + /* + * Delete all join hints which have different combination from Leading + * hint. + */ + for (i = 2;i <= nbaserel; i++) + { + 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); + + if (hint->inner_nrels == 0 && + !(bms_intersect(hint->joinrelids, joinrelids) == NULL || + bms_equal(bms_union(hint->joinrelids, joinrelids), + hint->joinrelids))) + { + hstate->join_hint_level[i] = + list_delete_cell(hstate->join_hint_level[i], l, + prev); + } + else + prev = l; + } + } + } bms_free(joinrelids); if (njoinrels < 2) return; - - for (i = 2; i <= njoinrels; i++) - { - /* Leading で指定した組み合わせ以外の join hint を削除する */ - list_free(plan->join_hint_level[i]); - - plan->join_hint_level[i] = lappend(NIL, join_method_hints[i]); } if (hint_state_enabled(lhint)) set_join_config_options(DISABLE_ALL_JOIN, current_hint->context); - - lhint->base.state = HINT_STATE_USED; - } /* @@ -1976,7 +2683,11 @@ 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); @@ -1989,7 +2700,7 @@ set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) } static void -rebuild_scan_path(PlanHint *plan, PlannerInfo *root, int level, +rebuild_scan_path(HintState *hstate, PlannerInfo *root, int level, List *initial_rels) { ListCell *l; @@ -2000,28 +2711,27 @@ rebuild_scan_path(PlanHint *plan, PlannerInfo *root, int level, RangeTblEntry *rte; ScanMethodHint *hint; - /* - * スキャン方式が選択できるリレーションのみ、スキャンパスを再生成 - * する。 - */ + /* Skip relations which we can't choose scan method. */ if (rel->reloptkind != RELOPT_BASEREL || rel->rtekind != RTE_RELATION) continue; rte = root->simple_rte_array[rel->relid]; - /* 外部表はスキャン方式が選択できない。 */ + /* We can't force scan method of foreign tables */ if (rte->relkind == RELKIND_FOREIGN_TABLE) continue; /* - * scan method hint が指定されていなければ、初期値のGUCパラメータでscan - * path を再生成する。 + * Create scan paths with GUC parameters which are at the beginning of + * planner if scan method hint is not specified, otherwise use + * specified hints and mark the hint as used. */ if ((hint = find_scan_hint(root, rel)) == NULL) - set_scan_config_options(plan->init_scan_mask, plan->context); + set_scan_config_options(hstate->init_scan_mask, + hstate->context); else { - set_scan_config_options(hint->enforce_mask, plan->context); + set_scan_config_options(hint->enforce_mask, hstate->context); hint->base.state = HINT_STATE_USED; } @@ -2041,14 +2751,14 @@ rebuild_scan_path(PlanHint *plan, PlannerInfo *root, int level, /* * Restore the GUC variables we set above. */ - set_scan_config_options(plan->init_scan_mask, plan->context); + set_scan_config_options(hstate->init_scan_mask, hstate->context); } /* - * make_join_rel() をラップする関数 + * wrapper of make_join_rel() * - * ヒントにしたがって、enabele_* パラメータを変更した上で、make_join_rel()を - * 呼び出す。 + * call make_join_rel() after changing enable_* parameters according to given + * hints. */ static RelOptInfo * make_join_rel_wrapper(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2) @@ -2065,21 +2775,88 @@ make_join_rel_wrapper(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2) if (!hint) return pg_hint_plan_make_join_rel(root, rel1, rel2); - save_nestlevel = NewGUCNestLevel(); + if (hint->inner_nrels == 0) + { + save_nestlevel = NewGUCNestLevel(); - set_join_config_options(hint->enforce_mask, current_hint->context); + set_join_config_options(hint->enforce_mask, current_hint->context); - rel = pg_hint_plan_make_join_rel(root, rel1, rel2); - hint->base.state = HINT_STATE_USED; + rel = pg_hint_plan_make_join_rel(root, rel1, rel2); + hint->base.state = HINT_STATE_USED; - /* - * Restore the GUC variables we set above. - */ - AtEOXact_GUC(true, save_nestlevel); + /* + * 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; } +/* + * TODO : comment + */ +static void +add_paths_to_joinrel_wrapper(PlannerInfo *root, + RelOptInfo *joinrel, + RelOptInfo *outerrel, + RelOptInfo *innerrel, + JoinType jointype, + SpecialJoinInfo *sjinfo, + List *restrictlist) +{ + ScanMethodHint *scan_hint = NULL; + Relids joinrelids; + JoinMethodHint *join_hint; + int save_nestlevel; + + if ((scan_hint = find_scan_hint(root, innerrel)) != NULL) + { + set_scan_config_options(scan_hint->enforce_mask, current_hint->context); + scan_hint->base.state = HINT_STATE_USED; + } + + joinrelids = bms_union(outerrel->relids, innerrel->relids); + join_hint = find_join_hint(joinrelids); + bms_free(joinrelids); + + if (join_hint && join_hint->inner_nrels != 0) + { + save_nestlevel = NewGUCNestLevel(); + + if (bms_equal(join_hint->inner_joinrelids, innerrel->relids)) + { + + set_join_config_options(join_hint->enforce_mask, + current_hint->context); + + add_paths_to_joinrel(root, joinrel, outerrel, innerrel, jointype, + sjinfo, restrictlist); + join_hint->base.state = HINT_STATE_USED; + } + else + { + set_join_config_options(DISABLE_ALL_JOIN, current_hint->context); + add_paths_to_joinrel(root, joinrel, outerrel, innerrel, jointype, + sjinfo, restrictlist); + } + + /* + * Restore the GUC variables we set above. + */ + AtEOXact_GUC(true, save_nestlevel); + } + else + add_paths_to_joinrel(root, joinrel, outerrel, innerrel, jointype, + sjinfo, restrictlist); + + if (scan_hint != NULL) + set_scan_config_options(current_hint->init_scan_mask, + current_hint->context); +} + static int get_num_baserels(List *initial_rels) { @@ -2097,7 +2874,7 @@ get_num_baserels(List *initial_rels) else { /* other values not expected here */ - elog(ERROR, "Unrecognized reloptkind type: %d", rel->reloptkind); + elog(ERROR, "unrecognized reloptkind type: %d", rel->reloptkind); } } @@ -2108,14 +2885,15 @@ static RelOptInfo * pg_hint_plan_join_search(PlannerInfo *root, int levels_needed, List *initial_rels) { - JoinMethodHint **join_method_hints; - int nbaserel; - RelOptInfo *rel; - int i; + JoinMethodHint **join_method_hints; + LeadingHint *leading_hint = NULL; + int nbaserel; + RelOptInfo *rel; + int i; /* - * pg_hint_planが無効、または有効なヒントが1つも指定されなかった場合は、標準 - * の処理を行う。 + * Use standard planner (or geqo planner) if pg_hint_plan is disabled or no + * valid hint is supplied. */ if (!current_hint) { @@ -2131,9 +2909,8 @@ pg_hint_plan_join_search(PlannerInfo *root, int levels_needed, rebuild_scan_path(current_hint, root, levels_needed, initial_rels); /* - * GEQOを使用する条件を満たした場合は、GEQOを用いた結合方式の検索を行う。 - * このとき、スキャン方式のヒントとSetヒントのみが有効になり、結合方式や結合 - * 順序はヒント句は無効になりGEQOのアルゴリズムで決定される。 + * In the case using GEQO, only scan method hints and Set hints have + * effect. Join method and join order is not controllable by hints. */ if (enable_geqo && levels_needed >= geqo_threshold) return geqo(root, levels_needed, initial_rels); @@ -2143,7 +2920,7 @@ pg_hint_plan_join_search(PlannerInfo *root, int levels_needed, join_method_hints = palloc0(sizeof(JoinMethodHint *) * (nbaserel + 1)); transform_join_hints(current_hint, root, nbaserel, initial_rels, - join_method_hints); + join_method_hints, leading_hint); rel = pg_hint_plan_standard_join_search(root, levels_needed, initial_rels); @@ -2160,8 +2937,10 @@ pg_hint_plan_join_search(PlannerInfo *root, int levels_needed, pfree(join_method_hints); if (current_hint->num_hints[HINT_TYPE_LEADING] > 0 && - hint_state_enabled(current_hint->leading_hint)) - set_join_config_options(current_hint->init_join_mask, current_hint->context); + leading_hint != NULL && + hint_state_enabled(leading_hint)) + set_join_config_options(current_hint->init_join_mask, + current_hint->context); return rel; } @@ -2177,7 +2956,15 @@ 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); @@ -2192,10 +2979,10 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, set_plain_rel_pathlist(root, rel, rte); } else - elog(ERROR, "Unexpected relkind: %c", rte->relkind); + elog(ERROR, "unexpected relkind: %c", rte->relkind); } else - elog(ERROR, "Unexpected rtekind: %d", (int) rel->rtekind); + elog(ERROR, "unexpected rtekind: %d", (int) rel->rtekind); } } @@ -2206,16 +2993,5 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, #undef make_join_rel #define make_join_rel pg_hint_plan_make_join_rel -#define add_paths_to_joinrel(root, joinrel, outerrel, innerrel, jointype, sjinfo, restrictlist) \ -do { \ - ScanMethodHint *hint = NULL; \ - if ((hint = find_scan_hint((root), (innerrel))) != NULL) \ - { \ - set_scan_config_options(hint->enforce_mask, current_hint->context); \ - hint->base.state = HINT_STATE_USED; \ - } \ - add_paths_to_joinrel((root), (joinrel), (outerrel), (innerrel), (jointype), (sjinfo), (restrictlist)); \ - if (hint != NULL) \ - set_scan_config_options(current_hint->init_scan_mask, current_hint->context); \ -} while(0) +#define add_paths_to_joinrel add_paths_to_joinrel_wrapper #include "make_join_rel.c"