OSDN Git Service

Allow hints to be placed anywhere in query
[pghintplan/pg_hint_plan.git] / pg_hint_plan.c
index 7223123..0852f56 100644 (file)
@@ -237,6 +237,16 @@ static unsigned int msgqno = 0;
 static char qnostr[32];
 static const char *current_hint_str = NULL;
 
+/*
+ * We can utilize in-core generated jumble state in post_parse_analyze_hook.
+ * On the other hand there's a case where we're forced to get hints in
+ * planner_hook, where we don't have a jumble state.  If we a query had not a
+ * hint, we need to try to retrieve hints twice or more for one query, which is
+ * the quite common case.  To avoid such case, this variables is set true when
+ * we *try* hint retrieval.
+ */
+static bool current_hint_retrieved = false;
+
 /* common data for all hints. */
 struct Hint
 {
@@ -394,6 +404,9 @@ typedef struct HintParser
        HintKeyword                     hint_keyword;
 } HintParser;
 
+static bool enable_hint_table_check(bool *newval, void **extra, GucSource source);
+static void assign_enable_hint_table(bool newval, void *extra);
+
 /* Module callbacks */
 void           _PG_init(void);
 void           _PG_fini(void);
@@ -401,6 +414,8 @@ void                _PG_fini(void);
 static void push_hint(HintState *hstate);
 static void pop_hint(void);
 
+static void pg_hint_plan_post_parse_analyze(ParseState *pstate, Query *query,
+                                                                                       JumbleState *jstate);
 static PlannedStmt *pg_hint_plan_planner(Query *parse, const char *query_string,
                                                                                 int cursorOptions,
                                                                                 ParamListInfo boundParams);
@@ -515,6 +530,7 @@ static int  pg_hint_plan_parse_message_level = INFO;
 static int     pg_hint_plan_debug_message_level = LOG;
 /* Default is off, to keep backward compatibility. */
 static bool    pg_hint_plan_enable_hint_table = false;
+static bool    pg_hint_plan_hints_anywhere = false;
 
 static int plpgsql_recurse_level = 0;          /* PLpgSQL recursion level            */
 static int recurse_level = 0;          /* recursion level incl. direct SPI calls */
@@ -522,6 +538,8 @@ static int hint_inhibit_level = 0;                  /* Inhibit hinting if this is above 0 */
                                                                                        /* (This could not be above 1)        */
 static int max_hint_nworkers = -1;             /* Maximum nworkers of Workers hints */
 
+static bool    hint_table_deactivated = false;
+
 static const struct config_enum_entry parse_messages_level_options[] = {
        {"debug", DEBUG2, true},
        {"debug5", DEBUG5, false},
@@ -558,6 +576,7 @@ static const struct config_enum_entry parse_debug_level_options[] = {
 };
 
 /* Saved hook values in case of unload */
+static post_parse_analyze_hook_type prev_post_parse_analyze_hook = NULL;
 static planner_hook_type prev_planner = NULL;
 static join_search_hook_type prev_join_search = NULL;
 static set_rel_pathlist_hook_type prev_set_rel_pathlist = NULL;
@@ -679,11 +698,26 @@ _PG_init(void)
                                                         false,
                                                         PGC_USERSET,
                                                         0,
+                                                        enable_hint_table_check,
+                                                        assign_enable_hint_table,
+                                                        NULL);
+
+       DefineCustomBoolVariable("pg_hint_plan.hints_anywhere",
+                                                        "Read hints from anywhere in a query.",
+                                                        "This option lets pg_hint_plan ignore syntax so be cautious for false reads.",
+                                                        &pg_hint_plan_hints_anywhere,
+                                                        false,
+                                                        PGC_USERSET,
+                                                        0,
                                                         NULL,
                                                         NULL,
                                                         NULL);
 
+       EmitWarningsOnPlaceholders("pg_hint_plan");
+
        /* Install hooks. */
+       prev_post_parse_analyze_hook = post_parse_analyze_hook;
+       post_parse_analyze_hook = pg_hint_plan_post_parse_analyze;
        prev_planner = planner_hook;
        planner_hook = pg_hint_plan_planner;
        prev_join_search = join_search_hook;
@@ -717,6 +751,31 @@ _PG_fini(void)
        *var_ptr = NULL;
 }
 
+static bool
+enable_hint_table_check(bool *newval, void **extra, GucSource source)
+{
+       if (*newval)
+       {
+               EnableQueryId();
+
+               if (!IsQueryIdEnabled())
+               {
+                       GUC_check_errmsg("table hint is not activated because queryid is not available");
+                       GUC_check_errhint("Set compute_query_id to on or auto to use hint table.");
+                       return false;
+               }
+       }
+
+       return true;
+}
+
+static void
+assign_enable_hint_table(bool newval, void *extra)
+{
+       if (!newval)
+               hint_table_deactivated = false;
+}
+
 /*
  * create and delete functions the hint object
  */
@@ -1834,33 +1893,35 @@ get_hints_from_comment(const char *p)
        hint_head = strstr(p, HINT_START);
        if (hint_head == NULL)
                return NULL;
-       for (;p < hint_head; p++)
+       if (!pg_hint_plan_hints_anywhere)
        {
-               /*
-                * Allow these characters precedes hint comment:
-                *   - digits
-                *   - alphabets which are in ASCII range
-                *   - space, tabs and new-lines
-                *   - underscores, for identifier
-                *   - commas, for SELECT clause, EXPLAIN and PREPARE
-                *   - parentheses, for EXPLAIN and PREPARE
-                *
-                * Note that we don't use isalpha() nor isalnum() in ctype.h here to
-                * avoid behavior which depends on locale setting.
-                */
-               if (!(*p >= '0' && *p <= '9') &&
-                       !(*p >= 'A' && *p <= 'Z') &&
-                       !(*p >= 'a' && *p <= 'z') &&
-                       !isspace(*p) &&
-                       *p != '_' &&
-                       *p != ',' &&
-                       *p != '(' && *p != ')')
-                       return NULL;
+               for (;p < hint_head; p++)
+               {
+                       /*
+                        * Allow these characters precedes hint comment:
+                        *   - digits
+                        *   - alphabets which are in ASCII range
+                        *   - space, tabs and new-lines
+                        *   - underscores, for identifier
+                        *   - commas, for SELECT clause, EXPLAIN and PREPARE
+                        *   - parentheses, for EXPLAIN and PREPARE
+                        *
+                        * Note that we don't use isalpha() nor isalnum() in ctype.h here to
+                        * avoid behavior which depends on locale setting.
+                        */
+                       if (!(*p >= '0' && *p <= '9') &&
+                               !(*p >= 'A' && *p <= 'Z') &&
+                               !(*p >= 'a' && *p <= 'z') &&
+                               !isspace(*p) &&
+                               *p != '_' &&
+                               *p != ',' &&
+                               *p != '(' && *p != ')')
+                               return NULL;
+               }
        }
 
-       len = strlen(HINT_START);
-       head = (char *) p;
-       p += len;
+       head = (char *)hint_head;
+       p = head + strlen(HINT_START);
        skip_space(p);
 
        /* find hint end keyword. */
@@ -2399,7 +2460,7 @@ ParallelHintParse(ParallelHint *hint, HintState *hstate, Query *parse,
                                                  hint->base.keyword));
                else if (nworkers < 0)
                        hint_ereport(hint->nworkers_str,
-                                                ("number of workers must be positive: %s",
+                                                ("number of workers must be greater than zero: %s",
                                                  hint->base.keyword));
                else if (nworkers > max_worker_processes)
                        hint_ereport(hint->nworkers_str,
@@ -2743,14 +2804,28 @@ pop_hint(void)
  * Retrieve and store hint string from given query or from the hint table.
  */
 static void
-get_current_hint_string(Query *query, const char *query_str)
+get_current_hint_string(Query *query, const char *query_str,
+                                               JumbleState *jstate)
 {
        MemoryContext   oldcontext;
 
-       /* do nothing while scanning hint table */
-       if (hint_inhibit_level > 0)
+       /* We shouldn't get here for internal queries. */
+       Assert (hint_inhibit_level == 0);
+
+       /* We shouldn't get here if hint is disabled. */
+       Assert (pg_hint_plan_enable_hint);
+
+       /* Do not anything if we have already tried to get hints for this query. */
+       if (current_hint_retrieved)
+               return;
+
+       /* No way to retrieve hints from empty string. */
+       if (!query_str)
                return;
 
+       /* Don't parse the current query hereafter */
+       current_hint_retrieved = true;
+
        /* Make sure trashing old hint string */
        if (current_hint_str)
        {
@@ -2758,10 +2833,6 @@ get_current_hint_string(Query *query, const char *query_str)
                current_hint_str = NULL;
        }
 
-       /* Return if nothing to do. */
-       if (!pg_hint_plan_enable_hint || !query_str)
-               return;
-
        /* increment the query number */
        qnostr[0] = 0;
        if (debug_level > 1)
@@ -2771,11 +2842,36 @@ get_current_hint_string(Query *query, const char *query_str)
        /* search the hint table for a hint if requested */
        if (pg_hint_plan_enable_hint_table)
        {
-               JumbleState        *jstate;
-               int                             query_len;
-               char               *normalized_query;
+               int                     query_len;
+               char       *normalized_query;
+
+               if (!IsQueryIdEnabled())
+               {
+                       /*
+                        * compute_query_id was turned off while enable_hint_table is
+                        * on. Do not go ahead and complain once until it is turned on
+                        * again.
+                        */
+                       if (!hint_table_deactivated)
+                               ereport(WARNING,
+                                               (errmsg ("hint table feature is deactivated because queryid is not available"),
+                                                errhint("Set compute_query_id to \"auto\" or \"on\" to use hint table.")));
+
+                       hint_table_deactivated = true;
+                       return;
+               }
+
+               if (hint_table_deactivated)
+               {
+                       ereport(LOG, (errmsg ("hint table feature is reactivated")));
+                       hint_table_deactivated = false;
+               }
 
-               jstate = JumbleQuery(query, query_str);
+               if (!jstate)
+                       jstate = JumbleQuery(query, query_str);
+
+               if (!jstate)
+                       return;
 
                /*
                 * Normalize the query string by replacing constants with '?'
@@ -2839,7 +2935,7 @@ get_current_hint_string(Query *query, const char *query_str)
 
        if (debug_level > 1)
        {
-               if (debug_level == 1 && query_str && debug_query_string &&
+               if (debug_level == 2 && query_str && debug_query_string &&
                        strcmp(query_str, debug_query_string))
                        ereport(pg_hint_plan_debug_message_level,
                                        (errmsg("hints in comment=\"%s\"",
@@ -2859,6 +2955,32 @@ get_current_hint_string(Query *query, const char *query_str)
 }
 
 /*
+ * Retrieve hint string from the current query.
+ */
+static void
+pg_hint_plan_post_parse_analyze(ParseState *pstate, Query *query,
+                                                               JumbleState *jstate)
+{
+       if (prev_post_parse_analyze_hook)
+               prev_post_parse_analyze_hook(pstate, query, jstate);
+
+       if (!pg_hint_plan_enable_hint || hint_inhibit_level > 0)
+               return;
+
+       /* always retrieve hint from the top-level query string */
+       if (plpgsql_recurse_level == 0)
+               current_hint_retrieved = false;
+
+       /*
+        * Jumble state is required when hint table is used.  This is the only
+        * chance to have one already generated in-core.  If it's not the case, no
+        * use to do the work now and pg_hint_plan_planner() will do the all work.
+        */
+       if (jstate)
+               get_current_hint_string(query, pstate->p_sourcetext, jstate);
+}
+
+/*
  * Read and set up hint information
  */
 static PlannedStmt *
@@ -2888,16 +3010,30 @@ pg_hint_plan_planner(Query *parse, const char *query_string, int cursorOptions,
                goto standard_planner_proc;
        }
 
-       /* always retrieve hint from the top-level query string */
-       if (plpgsql_recurse_level == 0 && current_hint_str)
+       /*
+        * SQL commands invoked in plpgsql functions may also have hints. In that
+        * case override the upper level hint by the new hint.
+        */
+       if (plpgsql_recurse_level > 0)
        {
-               pfree((void *)current_hint_str);
+               const char       *tmp_hint_str = current_hint_str;
+
+               /* don't let get_current_hint_string free this string */
                current_hint_str = NULL;
-       }
 
-       get_current_hint_string(parse, query_string);
+               current_hint_retrieved = false;
 
-       /* No hint, go the normal way */
+               get_current_hint_string(parse, query_string, NULL);
+
+               if (current_hint_str == NULL)
+                       current_hint_str = tmp_hint_str;
+               else if (tmp_hint_str != NULL)
+                       pfree((void *)tmp_hint_str);
+       }
+       else
+               get_current_hint_string(parse, query_string, NULL);
+
+       /* No hints, go the normal way */
        if (!current_hint_str)
                goto standard_planner_proc;
 
@@ -2985,7 +3121,8 @@ pg_hint_plan_planner(Query *parse, const char *query_string, int cursorOptions,
        {
                /*
                 * Rollback changes of GUC parameters, and pop current hint context
-                * from hint stack to rewind the state.
+                * from hint stack to rewind the state. current_hint_str will be freed
+                * by context deletion.
                 */
                current_hint_str = prev_hint_str;
                recurse_level--;
@@ -2998,12 +3135,13 @@ pg_hint_plan_planner(Query *parse, const char *query_string, int cursorOptions,
 
        /*
         * current_hint_str is useless after planning of the top-level query.
+        * There's a case where the caller has multiple queries. This causes hint
+        * parsing multiple times for the same string but we don't have a simple
+        * and reliable way to distinguish that case from the case where of
+        * separate queries.
         */
-       if (recurse_level < 1 && current_hint_str)
-       {
-               pfree((void *)current_hint_str);
-               current_hint_str = NULL;
-       }
+       if (recurse_level < 1)
+               current_hint_retrieved = false;
 
        /* Print hint in debug mode. */
        if (debug_level == 1)
@@ -3446,7 +3584,7 @@ restrict_indexes(PlannerInfo *root, ScanMethodHint *hint, RelOptInfo *rel,
                pfree(indexname);
        }
 
-       if (debug_level == 1)
+       if (debug_level > 0)
        {
                StringInfoData  rel_buf;
                char *disprelname = "";