OSDN Git Service

ParseScanMethodをPostgreSQL9.1に対応した。
[pghintplan/pg_hint_plan.git] / pg_hint_plan.c
index 502c7da..b398018 100644 (file)
@@ -31,27 +31,27 @@ PG_MODULE_MAGIC;
 #define HINT_END       "*/"
 
 /* hint keywords */
-#define HINT_SEQSCAN           "SeqScan"
-#define HINT_INDEXSCAN         "IndexScan"
-#define HINT_BITMAPSCAN                "BitmapScan"
-#define HINT_TIDSCAN           "TidScan"
-#define HINT_NOSEQSCAN         "NoSeqScan"
-#define HINT_NOINDEXSCAN       "NoIndexScan"
-#define HINT_NOBITMAPSCAN      "NoBitmapScan"
-#define HINT_NOTIDSCAN         "NoTidScan"
-#define HINT_NESTLOOP          "NestLoop"
-#define HINT_MERGEJOIN         "MergeJoin"
-#define HINT_HASHJOIN          "HashJoin"
-#define HINT_NONESTLOOP                "NoNestLoop"
-#define HINT_NOMERGEJOIN       "NoMergeJoin"
-#define HINT_NOHASHJOIN                "NoHashJoin"
-#define HINT_LEADING           "Leading"
-#define HINT_SET                       "Set"
-
+#define HINT_SEQSCAN                   "SeqScan"
+#define HINT_INDEXSCAN                 "IndexScan"
+#define HINT_BITMAPSCAN                        "BitmapScan"
+#define HINT_TIDSCAN                   "TidScan"
+#define HINT_NOSEQSCAN                 "NoSeqScan"
+#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"
+#define HINT_INDEXONLYSCAN             "IndexonlyScan"
+#define HINT_NOINDEXONLYSCAN   "NoIndexonlyScan"
 #endif
+#define HINT_NESTLOOP                  "NestLoop"
+#define HINT_MERGEJOIN                 "MergeJoin"
+#define HINT_HASHJOIN                  "HashJoin"
+#define HINT_NONESTLOOP                        "NoNestLoop"
+#define HINT_NOMERGEJOIN               "NoMergeJoin"
+#define HINT_NOHASHJOIN                        "NoHashJoin"
+#define HINT_LEADING                   "Leading"
+#define HINT_SET                               "Set"
+
 
 #define HINT_ARRAY_DEFAULT_INITSIZE 8
 
@@ -105,29 +105,39 @@ typedef struct SetHint
        char   *value;
 } SetHint;
 
+/*
+ * Describes a context of hint processing.
+ */
 typedef struct PlanHint
 {
-       char       *hint_str;
+       char       *hint_str;           /* original hint string */
 
-       int                     nscan_hints;
-       int                     max_scan_hints;
-       ScanHint  **scan_hints;
+       /* for scan method hints */
+       int                     nscan_hints;    /* # of valid scan hints */
+       int                     max_scan_hints; /* # of slots for scan hints */
+       ScanHint  **scan_hints;         /* parsed scan hints */
 
-       int                     njoin_hints;
-       int                     max_join_hints;
-       JoinHint  **join_hints;
+       /* for join method hints */
+       int                     njoin_hints;    /* # of valid join hints */
+       int                     max_join_hints; /* # of slots for join hints */
+       JoinHint  **join_hints;         /* parsed join hints */
 
-       int                     nlevel;
+       int                     nlevel;                 /* # of relations to be joined */
        List      **join_hint_level;
 
-       List       *leading;
+       /* for Leading hints */
+       List       *leading;            /* relation names specified in Leading hint */
 
-       GucContext      context;
-       List       *set_hints;
+       /* for Set hints */
+       GucContext      context;                /* which GUC parameters can we set? */
+       List       *set_hints;          /* parsed Set hints */
 } PlanHint;
 
 typedef const char *(*HintParserFunction) (PlanHint *plan, Query *parse, char *keyword, const char *str);
 
+/*
+ * Describes a hint parser module which is bound with particular hint keyword.
+ */
 typedef struct HintParser
 {
        char   *keyword;
@@ -147,7 +157,6 @@ static RelOptInfo *pg_hint_plan_join_search(PlannerInfo *root, int levels_needed
                                                                  List *initial_rels);
 
 static const char *ParseScanMethod(PlanHint *plan, Query *parse, char *keyword, const char *str);
-static const char *ParseIndexScanMethod(PlanHint *plan, Query *parse, char *keyword, const char *str);
 static const char *ParseJoinMethod(PlanHint *plan, Query *parse, char *keyword, const char *str);
 static const char *ParseLeading(PlanHint *plan, Query *parse, char *keyword, const char *str);
 static const char *ParseSet(PlanHint *plan, Query *parse, char *keyword, const char *str);
@@ -197,16 +206,16 @@ static PlanHint *global = NULL;
 
 static const HintParser parsers[] = {
        {HINT_SEQSCAN, true, ParseScanMethod},
-       {HINT_INDEXSCAN, true, ParseIndexScanMethod},
-       {HINT_BITMAPSCAN, true, ParseIndexScanMethod},
+       {HINT_INDEXSCAN, true, ParseScanMethod},
+       {HINT_BITMAPSCAN, true, ParseScanMethod},
        {HINT_TIDSCAN, true, ParseScanMethod},
        {HINT_NOSEQSCAN, true, ParseScanMethod},
        {HINT_NOINDEXSCAN, true, ParseScanMethod},
        {HINT_NOBITMAPSCAN, true, ParseScanMethod},
        {HINT_NOTIDSCAN, true, ParseScanMethod},
 #if PG_VERSION_NUM >= 90200
-       {HINT_INDEXONLYSCAN, true, ParseIndexScanMethod},
-       {HINT_NOINDEXONLYSCAN, true, ParseIndexScanMethod},
+       {HINT_INDEXONLYSCAN, true, ParseScanMethod},
+       {HINT_NOINDEXONLYSCAN, true, ParseScanMethod},
 #endif
        {HINT_NESTLOOP, true, ParseJoinMethod},
        {HINT_MERGEJOIN, true, ParseJoinMethod},
@@ -271,6 +280,7 @@ _PG_init(void)
 
 /*
  * Module unload callback
+ * XXX never called
  */
 void
 _PG_fini(void)
@@ -435,7 +445,7 @@ PlanHintIsempty(PlanHint *hint)
        return false;
 }
 
-// TODO オブジェクト名のクォート処理を追加
+/* TODO オブジェクト名のクォート処理を追加 */
 static void
 PlanHintDump(PlanHint *hint)
 {
@@ -570,7 +580,7 @@ ScanHintCmp(const void *a, const void *b, bool order)
        const ScanHint     *hintb = *((const ScanHint **) b);
        int                                     result;
 
-       if ((result = strcmp(hinta->relname, hintb->relname)) != 0)
+       if ((result = RelnameCmp(&hinta->relname, &hintb->relname)) != 0)
                return result;
 
        /* ヒント句で指定した順を返す */
@@ -598,7 +608,7 @@ JoinHintCmp(const void *a, const void *b, bool order)
                for (i = 0; i < hinta->nrels; i++)
                {
                        int     result;
-                       if ((result = strcmp(hinta->relnames[i], hintb->relnames[i])) != 0)
+                       if ((result = RelnameCmp(&hinta->relnames[i], &hintb->relnames[i])) != 0)
                                return result;
                }
 
@@ -755,12 +765,20 @@ skip_closed_parenthesis(const char *str)
        return str;
 }
 
+/*
+ * 二重引用符で囲まれているかもしれないトークンを読み取り word 引数に palloc
+ * で確保したバッファに格納してそのポインタを返す。
+ *
+ * 正常にパースできた場合は残りの文字列の先頭位置を、異常があった場合は NULL を
+ * 返す。
+ */
 static const char *
 parse_quote_value(const char *str, char **word, char *value_type)
 {
        StringInfoData  buf;
        bool                    in_quote;
 
+       /* 先頭のスペースは読み飛ばす。 */
        skip_space(str);
 
        initStringInfo(&buf);
@@ -770,25 +788,13 @@ parse_quote_value(const char *str, char **word, char *value_type)
                in_quote = true;
        }
        else
-       {
-               /*
-                * 1文字目以降の制限の適用
-                */
-               if (!isalpha(*str) && *str != '_')
-               {
-                       pfree(buf.data);
-                       parse_ereport(str, ("Need for %s to be quoted.", value_type));
-                       return NULL;
-               }
-
                in_quote = false;
-               appendStringInfoCharMacro(&buf, *str++);
-       }
 
        while (true)
        {
                if (in_quote)
                {
+                       /* 二重引用符が閉じられていない場合はパース中断 */
                        if (*str == '\0')
                        {
                                pfree(buf.data);
@@ -797,8 +803,11 @@ parse_quote_value(const char *str, char **word, char *value_type)
                        }
 
                        /*
-                        * エスケープ対象をスキップする。
-                        * TODO エスケープ対象の仕様にあわせた処理を行う。
+                        * エスケープ対象のダブルクウォートをスキップする。
+                        * もしブロックコメントの開始文字列や終了文字列もオブジェクト名とし
+                        * て使用したい場合は、/ と * もエスケープ対象とすることで使用できる
+                        * が、処理対象としていない。もしテーブル名にこれらの文字が含まれる
+                        * 場合は、エイリアスを指定する必要がある。
                         */
                        if(*str == '"')
                        {
@@ -808,13 +817,8 @@ parse_quote_value(const char *str, char **word, char *value_type)
                        }
                }
                else
-               {
-                       /*
-                        * 2文字目以降の制限の適用
-                        */
-                       if (!isalnum(*str) && *str != '_' && *str != '$')
+                       if (isspace(*str) || *str == ')' || *str == '\0')
                                break;
-               }
 
                appendStringInfoCharMacro(&buf, *str++);
        }
@@ -934,7 +938,6 @@ parse_head_comment(Query *parse)
        if (p == NULL)
                return NULL;
 
-
        /* extract query head comment. */
        len = strlen(HINT_START);
        skip_space(p);
@@ -945,8 +948,10 @@ parse_head_comment(Query *parse)
        skip_space(p);
 
        if ((tail = strstr(p, HINT_END)) == NULL)
-               elog(ERROR, "unterminated /* comment at or near \"%s\"",
-                        debug_query_string);
+       {
+               parse_ereport(debug_query_string, ("unterminated /* comment"));
+               return NULL;
+       }
 
        /* 入れ子にしたブロックコメントはサポートしない */
        if ((head = strstr(p, HINT_START)) != NULL && head < tail)
@@ -1003,20 +1008,65 @@ parse_head_comment(Query *parse)
        return plan;
 }
 
+/*
+ * スキャン方式ヒントのカッコ内をパースする
+ */
 static const char *
-parse_scan_method(PlanHint *plan, Query *parse, char *keyword, const char *str)
+ParseScanMethod(PlanHint *plan, Query *parse, char *keyword, const char *str)
 {
        ScanHint   *hint;
 
        hint = ScanHintCreate();
        hint->opt_str = str;
 
+       /*
+        * スキャン方式のヒントでリレーション名が読み取れない場合はヒント無効
+        */
        if ((str = parse_quote_value(str, &hint->relname, "ralation name")) == NULL)
        {
                ScanHintDelete(hint);
                return NULL;
        }
 
+       /*
+        * インデックスリストを受け付けるヒントであれば、インデックス参照をパース
+        * する。
+        */
+       if (strcmp(keyword, HINT_INDEXSCAN) == 0 ||
+#if PG_VERSION_NUM >= 90200
+               strcmp(keyword, HINT_INDEXONLYSCAN) == 0 ||
+#endif
+               strcmp(keyword, HINT_BITMAPSCAN) == 0)
+       {
+               skip_space(str);
+               while (*str != ')' && *str != '\0')
+               {
+                       char       *indexname;
+
+                       str = parse_quote_value(str, &indexname, "index name");
+                       if (str == NULL)
+                       {
+                               ScanHintDelete(hint);
+                               return NULL;
+                       }
+
+                       hint->indexnames = lappend(hint->indexnames, indexname);
+                       skip_space(str);
+               }
+       }
+
+       /* カッコが閉じていなければヒント無効。 */
+       skip_space(str);
+       if (*str != ')')
+       {
+               parse_ereport(str, ("Closed parenthesis is necessary."));
+               ScanHintDelete(hint);
+               return NULL;
+       }
+
+       /*
+        * ヒントごとに決まっている許容スキャン方式をビットマスクとして設定
+        */
        if (strcasecmp(keyword, HINT_SEQSCAN) == 0)
                hint->enforce_mask = ENABLE_SEQSCAN;
        else if (strcasecmp(keyword, HINT_INDEXSCAN) == 0)
@@ -1035,22 +1085,25 @@ parse_scan_method(PlanHint *plan, Query *parse, char *keyword, const char *str)
                hint->enforce_mask = ENABLE_ALL_SCAN ^ ENABLE_TIDSCAN;
        else
        {
-               elog(ERROR, "unrecognized hint keyword \"%s\"", keyword);
+               ScanHintDelete(hint);
+               parse_ereport(str, ("unrecognized hint keyword \"%s\"", keyword));
                return NULL;
        }
 
+       /*
+        * 出来上がったヒント情報を追加。スロットが足りない場合は二倍に拡張する。
+        */
        if (plan->nscan_hints == 0)
        {
                plan->max_scan_hints = HINT_ARRAY_DEFAULT_INITSIZE;
-               plan->scan_hints = palloc(sizeof(JoinHint *) * plan->max_scan_hints);
+               plan->scan_hints = palloc(sizeof(ScanHint *) * plan->max_scan_hints);
        }
        else if (plan->nscan_hints == plan->max_scan_hints)
        {
                plan->max_scan_hints *= 2;
                plan->scan_hints = repalloc(plan->scan_hints,
-                                                               sizeof(JoinHint *) * plan->max_scan_hints);
+                                                               sizeof(ScanHint *) * plan->max_scan_hints);
        }
-
        plan->scan_hints[plan->nscan_hints] = hint;
        plan->nscan_hints++;
 
@@ -1058,60 +1111,6 @@ parse_scan_method(PlanHint *plan, Query *parse, char *keyword, const char *str)
 }
 
 static const char *
-ParseScanMethod(PlanHint *plan, Query *parse, char *keyword, const char *str)
-{
-       ScanHint   *hint;
-
-       if ((str = parse_scan_method(plan, parse, keyword, str)) == NULL)
-               return NULL;
-
-       hint = plan->scan_hints[plan->nscan_hints - 1];
-
-       skip_space(str);
-       if (*str != ')')
-       {
-               parse_ereport(str, ("Closed parenthesis is necessary."));
-               plan->nscan_hints--;
-               ScanHintDelete(hint);
-               return NULL;
-       }
-
-       return str;
-}
-
-static const char *
-ParseIndexScanMethod(PlanHint *plan, Query *parse, char *keyword, const char *str)
-{
-       char       *indexname;
-       ScanHint   *hint;
-
-       if ((str = parse_scan_method(plan, parse, keyword, str)) == NULL)
-               return NULL;
-
-       hint = plan->scan_hints[plan->nscan_hints - 1];
-
-       /* インデックス参照をパースする。 */
-       while (true)
-       {
-               // TODO 直前のオブジェクト名がクウォート処理されていた場合の処理を実装
-               skip_space(str);
-               if (*str == ')')
-                       break;
-
-               if ((str = parse_quote_value(str, &indexname, "index name")) == NULL)
-               {
-                       plan->nscan_hints--;
-                       ScanHintDelete(hint);
-                       return NULL;
-               }
-
-               hint->indexnames = lappend(hint->indexnames, indexname);
-       }
-
-       return str;
-}
-
-static const char *
 ParseJoinMethod(PlanHint *plan, Query *parse, char *keyword, const char *str)
 {
        char       *relname;
@@ -1164,7 +1163,11 @@ ParseJoinMethod(PlanHint *plan, Query *parse, char *keyword, const char *str)
        else if (strcasecmp(keyword, HINT_NOHASHJOIN) == 0)
                hint->enforce_mask = ENABLE_ALL_JOIN ^ ENABLE_HASHJOIN;
        else
-               elog(ERROR, "unrecognized hint keyword \"%s\"", keyword);
+       {
+               JoinHintDelete(hint);
+               parse_ereport(str, ("unrecognized hint keyword \"%s\"", keyword));
+               return NULL;
+       }
 
        if (plan->njoin_hints == 0)
        {
@@ -1315,7 +1318,7 @@ pg_hint_plan_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
                {
                        ScanHint           *hint = global->scan_hints[i];
 
-                       if (strcmp(hint->relname, rte->eref->aliasname) != 0)
+                       if (RelnameCmp(&rte->eref->aliasname, &hint->relname) != 0)
                                parse_ereport(hint->opt_str, ("Relation \"%s\" does not exist.", hint->relname));
 
                        set_scan_config_options(hint->enforce_mask, global->context);
@@ -1335,7 +1338,9 @@ pg_hint_plan_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
        if (pg_hint_plan_debug_print)
        {
                PlanHintDump(global);
-               //elog_node_display(INFO, "rtable", parse->rtable, true);
+#ifdef NOT_USED
+               elog_node_display(INFO, "rtable", parse->rtable, true);
+#endif
        }
 
        PlanHintDelete(global);
@@ -1356,7 +1361,7 @@ find_scan_hint(RangeTblEntry *rte)
        {
                ScanHint   *hint = global->scan_hints[i];
 
-               if (strcmp(rte->eref->aliasname, hint->relname) == 0)
+               if (RelnameCmp(&rte->eref->aliasname, &hint->relname) == 0)
                        return hint;
        }
 
@@ -1411,7 +1416,7 @@ pg_hint_plan_get_relation_info(PlannerInfo *root, Oid relationObjectId,
 
                foreach(l, hint->indexnames)
                {
-                       if (strcmp(indexname, lfirst(l)) == 0)
+                       if (RelnameCmp(&indexname, &lfirst(l)) == 0)
                        {
                                use_index = true;
                                break;
@@ -1430,7 +1435,7 @@ pg_hint_plan_get_relation_info(PlannerInfo *root, Oid relationObjectId,
 static Index
 scan_relid_aliasname(PlannerInfo *root, char *aliasname, bool check_ambiguous, const char *str)
 {
-       // TODO refnameRangeTblEntry を参考
+       /* TODO refnameRangeTblEntry を参考 */
        int             i;
        Index   find = 0;
 
@@ -1441,7 +1446,8 @@ scan_relid_aliasname(PlannerInfo *root, char *aliasname, bool check_ambiguous, c
 
                Assert(i == root->simple_rel_array[i]->relid);
 
-               if (strcmp(aliasname, root->simple_rte_array[i]->eref->aliasname) != 0)
+               if (RelnameCmp(&aliasname, &root->simple_rte_array[i]->eref->aliasname)
+                               != 0)
                        continue;
 
                if (!check_ambiguous)
@@ -1552,6 +1558,10 @@ rebuild_join_hints(PlanHint *plan, PlannerInfo *root, int level, List *initial_r
                        plan->join_hint_level[njoinrels] = lappend(NIL, hint);
                else
                {
+                       /*
+                        * Here relnames is not set, since Relids bitmap is sufficient to
+                        * control paths of this query afterwards.
+                        */
                        hint = JoinHintCreate();
                        hint->nrels = njoinrels;
                        hint->enforce_mask = ENABLE_ALL_JOIN;
@@ -1598,7 +1608,7 @@ rebuild_scan_path(PlanHint *plan, PlannerInfo *root, int level, List *initial_re
                        RangeTblEntry  *rte = root->simple_rte_array[rel->relid];
 
                        if (rel->reloptkind != RELOPT_BASEREL ||
-                               strcmp(hint->relname, rte->eref->aliasname) != 0)
+                               RelnameCmp(&hint->relname, &rte->eref->aliasname) != 0)
                                continue;
 
                        if (save_nestlevel != 0)
@@ -1610,7 +1620,7 @@ rebuild_scan_path(PlanHint *plan, PlannerInfo *root, int level, List *initial_re
                         */
                        set_scan_config_options(hint->enforce_mask, plan->context);
 
-                       rel->pathlist = NIL;    // TODO 解放
+                       rel->pathlist = NIL;    /* TODO 解放 */
                        set_plain_rel_pathlist(root, rel, rte);
 
                        break;
@@ -1663,7 +1673,10 @@ pg_hint_plan_make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2
 static RelOptInfo *
 pg_hint_plan_join_search(PlannerInfo *root, int levels_needed, List *initial_rels)
 {
-       /* 有効なヒントが指定されなかった場合は本来の処理を実行する。 */
+       /*
+        * pg_hint_planが無効、または有効なヒントが1つも指定されなかった場合は、標準
+        * の処理を行う。
+        */
        if (!global)
        {
                if (prev_join_search)
@@ -1677,6 +1690,14 @@ pg_hint_plan_join_search(PlannerInfo *root, int levels_needed, List *initial_rel
        rebuild_join_hints(global, root, levels_needed, initial_rels);
        rebuild_scan_path(global, root, levels_needed, initial_rels);
 
+       /*
+        * GEQOを使用する条件を満たした場合は、GEQOを用いた結合方式の検索を行う。
+        * このとき、スキャン方式のヒントとSetヒントのみが有効になり、結合方式や結合
+        * 順序はヒント句は無効になりGEQOのアルゴリズムで決定される。
+        */
+       if (enable_geqo && levels_needed >= geqo_threshold)
+               return geqo(root, levels_needed, initial_rels);
+
        return standard_join_search_org(root, levels_needed, initial_rels);
 }