OSDN Git Service

Call standrad_ProcessUtility when no hook is set
[pghintplan/pg_hint_plan.git] / pg_hint_plan.c
index c139c06..4a6de90 100644 (file)
@@ -222,6 +222,14 @@ static unsigned int msgqno = 0;
 static char qnostr[32];
 static const char *current_hint_str = NULL;
 
+/*
+ * However we usually take a hint stirng in post_parse_analyze_hook, we still
+ * need to do so in planner_hook when client starts query execution from the
+ * bind message on a prepared query. This variable prevent duplicate and
+ * sometimes harmful hint string retrieval.
+ */
+static bool current_hint_retrieved = false;
+
 /* common data for all hints. */
 struct Hint
 {
@@ -348,6 +356,7 @@ struct HintState
        int                             init_paratup_cost;      /* parallel_tuple_cost */
        int                             init_parasetup_cost;/* parallel_setup_cost */
 
+       PlannerInfo        *current_root;               /* PlannerInfo for the followings */
        Index                   parent_relid;           /* inherit parent of table relid */
        ScanMethodHint *parent_scan_hint;       /* scan hint for the parent */
        ParallelHint   *parent_parallel_hint; /* parallel hint for the parent */
@@ -381,6 +390,10 @@ static void push_hint(HintState *hstate);
 static void pop_hint(void);
 
 static void pg_hint_plan_post_parse_analyze(ParseState *pstate, Query *query);
+static void pg_hint_plan_ProcessUtility(Node *parsetree,
+                                       const char *queryString,
+                                       ProcessUtilityContext context, ParamListInfo params,
+                                       DestReceiver *dest, char *completionTag);
 static PlannedStmt *pg_hint_plan_planner(Query *parse, int cursorOptions,
                                                                                 ParamListInfo boundParams);
 static RelOptInfo *pg_hint_plan_join_search(PlannerInfo *root,
@@ -544,6 +557,7 @@ 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;
+static ProcessUtility_hook_type prev_ProcessUtility_hook = NULL;
 
 /* Hold reference to currently active hint */
 static HintState *current_hint_state = NULL;
@@ -555,12 +569,6 @@ static HintState *current_hint_state = NULL;
  */
 static List *HintStateStack = NIL;
 
-/*
- * Holds statement name during executing EXECUTE command.  NULL for other
- * statements.
- */
-static char       *stmt_name = NULL;
-
 static const HintParser parsers[] = {
        {HINT_SEQSCAN, ScanMethodHintCreate, HINT_KEYWORD_SEQSCAN},
        {HINT_INDEXSCAN, ScanMethodHintCreate, HINT_KEYWORD_INDEXSCAN},
@@ -679,6 +687,8 @@ _PG_init(void)
        join_search_hook = pg_hint_plan_join_search;
        prev_set_rel_pathlist = set_rel_pathlist_hook;
        set_rel_pathlist_hook = pg_hint_plan_set_rel_pathlist;
+       prev_ProcessUtility_hook = ProcessUtility_hook;
+       ProcessUtility_hook = pg_hint_plan_ProcessUtility;
 
        /* setup PL/pgSQL plugin hook */
        var_ptr = (PLpgSQL_plugin **) find_rendezvous_variable("PLpgSQL_plugin");
@@ -701,6 +711,7 @@ _PG_fini(void)
        planner_hook = prev_planner;
        join_search_hook = prev_join_search;
        set_rel_pathlist_hook = prev_set_rel_pathlist;
+       ProcessUtility_hook = prev_ProcessUtility_hook;
 
        /* uninstall PL/pgSQL plugin hook */
        var_ptr = (PLpgSQL_plugin **) find_rendezvous_variable("PLpgSQL_plugin");
@@ -965,6 +976,7 @@ HintStateCreate(void)
        hstate->init_min_para_size = 0;
        hstate->init_paratup_cost = 0;
        hstate->init_parasetup_cost = 0;
+       hstate->current_root = NULL;
        hstate->parent_relid = 0;
        hstate->parent_scan_hint = NULL;
        hstate->parent_parallel_hint = NULL;
@@ -1800,17 +1812,15 @@ get_query_string(ParseState *pstate, Query *query, Query **jumblequery)
        /*
         * If debug_query_string is set, it is the top level statement. But in some
         * cases we reach here with debug_query_string set NULL for example in the
-        * case of DESCRIBE message handling. We may still see a candidate
-        * top-level query in pstate in the case.
+        * case of DESCRIBE message handling or EXECUTE command. We may still see a
+        * candidate top-level query in pstate in the case.
         */
-       if (!p)
-       {
-               /* We don't see a query string, return NULL */
-               if (!pstate->p_sourcetext)
-                       return NULL;
-
+       if (!p && pstate)
                p = pstate->p_sourcetext;
-       }
+
+       /* We don't see a query string, return NULL */
+       if (!p)
+               return NULL;
 
        if (jumblequery != NULL)
                *jumblequery = query;
@@ -1894,9 +1904,12 @@ get_query_string(ParseState *pstate, Query *query, Query **jumblequery)
                if (jumblequery)
                        *jumblequery = target_query;
        }
-
-       /* Return NULL if the pstate is not identical to the top-level query */
-       else if (strcmp(pstate->p_sourcetext, p) != 0)
+       /*
+        * Return NULL if pstate is not of top-level query.  We don't need this
+        * when jumble info is not requested or cannot do this when pstate is NULL.
+        */
+       else if (!jumblequery && pstate && pstate->p_sourcetext != p &&
+                        strcmp(pstate->p_sourcetext, p) != 0)
                p = NULL;
 
        return p;
@@ -2759,24 +2772,25 @@ pop_hint(void)
 }
 
 /*
- * Retrieve and store a hint string from given query or from the hint table.
- * If we are using the hint table, the query string is needed to be normalized.
- * However, ParseState, which is not available in planner_hook, is required to
- * check if the query tree (Query) is surely corresponding to the target query.
+ * Retrieve and store hint string from given query or from the hint table.
  */
 static void
-pg_hint_plan_post_parse_analyze(ParseState *pstate, Query *query)
+get_current_hint_string(ParseState *pstate, Query *query)
 {
        const char *query_str;
        MemoryContext   oldcontext;
 
-       if (prev_post_parse_analyze_hook)
-               prev_post_parse_analyze_hook(pstate, query);
-
        /* do nothing under hint table search */
        if (hint_inhibit_level > 0)
                return;
 
+       /* We alredy have one, don't parse it again. */
+       if (current_hint_retrieved)
+               return;
+
+       /* Don't parse the current query hereafter */
+       current_hint_retrieved = true;
+
        if (!pg_hint_plan_enable_hint)
        {
                if (current_hint_str)
@@ -2817,8 +2831,8 @@ pg_hint_plan_post_parse_analyze(ParseState *pstate, Query *query)
                if (jumblequery)
                {
                        /*
-                        * XXX: normalizing code is copied from pg_stat_statements.c, so be
-                        * careful to PostgreSQL's version up.
+                        * XXX: normalization code is copied from pg_stat_statements.c.
+                        * Make sure to keep up-to-date with it.
                         */
                        jstate.jumble = (unsigned char *) palloc(JUMBLE_SIZE);
                        jstate.jumble_len = 0;
@@ -2894,8 +2908,8 @@ pg_hint_plan_post_parse_analyze(ParseState *pstate, Query *query)
        {
                /*
                 * get hints from the comment. However we may have the same query
-                * string with the previous call, but just retrieving hints is expected
-                * to be faster than checking for identicalness before retrieval.
+                * string with the previous call, but the extra comparison seems no
+                * use..
                 */
                if (current_hint_str)
                        pfree((void *)current_hint_str);
@@ -2907,8 +2921,8 @@ pg_hint_plan_post_parse_analyze(ParseState *pstate, Query *query)
 
        if (debug_level > 1)
        {
-               if (debug_level == 1 &&
-                       (stmt_name || strcmp(query_str, debug_query_string)))
+               if (debug_level == 1 && query_str && debug_query_string &&
+                       strcmp(query_str, debug_query_string))
                        ereport(pg_hint_plan_debug_message_level,
                                        (errmsg("hints in comment=\"%s\"",
                                                        current_hint_str ? current_hint_str : "(none)"),
@@ -2916,9 +2930,10 @@ pg_hint_plan_post_parse_analyze(ParseState *pstate, Query *query)
                                         errhidecontext(msgqno != qno)));
                else
                        ereport(pg_hint_plan_debug_message_level,
-                                       (errmsg("hints in comment=\"%s\", stmt=\"%s\", query=\"%s\", debug_query_string=\"%s\"",
+                                       (errmsg("hints in comment=\"%s\", query=\"%s\", debug_query_string=\"%s\"",
                                                        current_hint_str ? current_hint_str : "(none)",
-                                                       stmt_name, query_str, debug_query_string),
+                                                       query_str ? query_str : "(none)",
+                                                       debug_query_string ? debug_query_string : "(none)"),
                                         errhidestmt(msgqno != qno),
                                         errhidecontext(msgqno != qno)));
                msgqno = qno;
@@ -2926,6 +2941,43 @@ pg_hint_plan_post_parse_analyze(ParseState *pstate, Query *query)
 }
 
 /*
+ * Retrieve hint string from the current query.
+ */
+static void
+pg_hint_plan_post_parse_analyze(ParseState *pstate, Query *query)
+{
+       if (prev_post_parse_analyze_hook)
+               prev_post_parse_analyze_hook(pstate, query);
+
+       /* always retrieve hint from the top-level query string */
+       if (plpgsql_recurse_level == 0)
+               current_hint_retrieved = false;
+
+       get_current_hint_string(pstate, query);
+}
+
+/*
+ * We need to reset current_hint_retrieved flag always when a command execution
+ * is finished. This is true even for a pure utility command that doesn't
+ * involve planning phase.
+ */
+static void
+pg_hint_plan_ProcessUtility(Node *parsetree, const char *queryString,
+                                       ProcessUtilityContext context, ParamListInfo params,
+                                       DestReceiver *dest, char *completionTag)
+{
+       if (prev_ProcessUtility_hook)
+               prev_ProcessUtility_hook(parsetree, queryString, context, params,
+                                                                dest, completionTag);
+       else
+               standard_ProcessUtility(parsetree, queryString, context, params,
+                                                                dest, completionTag);
+
+       if (plpgsql_recurse_level == 0)
+               current_hint_retrieved = false;
+}
+
+/*
  * Read and set up hint information
  */
 static PlannedStmt *
@@ -2971,6 +3023,14 @@ pg_hint_plan_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
                MemoryContextSwitchTo(oldcontext);
        }
 
+       /*
+        * Query execution in extended protocol can be started without the analyze
+        * phase. In the case retrieve hint string here.
+        */
+       if (!current_hint_str)
+               get_current_hint_string(NULL, parse);
+
+       /* No hint, go the normal way */
        if (!current_hint_str)
                goto standard_planner_proc;
 
@@ -3040,6 +3100,17 @@ pg_hint_plan_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
        }
        PG_END_TRY();
 
+
+       /*
+        * current_hint_str is useless after planning of the top-level query.
+        */
+       if (plpgsql_recurse_level < 1 && current_hint_str)
+       {
+               pfree((void *)current_hint_str);
+               current_hint_str = NULL;
+               current_hint_retrieved = false;
+       }
+
        /* Print hint in debug mode. */
        if (debug_level == 1)
                HintStateDump(current_hint_state);
@@ -3627,6 +3698,10 @@ setup_hint_enforcement(PlannerInfo *root, RelOptInfo *rel,
                return 0;
        }
 
+       /* Forget about the parent of another subquery */
+       if (root != current_hint_state->current_root)
+               current_hint_state->parent_relid = 0;
+
        /* Find the parent for this relation other than the registered parent */
        foreach (l, root->append_rel_list)
        {
@@ -3635,7 +3710,10 @@ setup_hint_enforcement(PlannerInfo *root, RelOptInfo *rel,
                if (appinfo->child_relid == rel->relid)
                {
                        if (current_hint_state->parent_relid != appinfo->parent_relid)
+                       {
                                new_parent_relid = appinfo->parent_relid;
+                               current_hint_state->current_root = root;
+                       }
                        break;
                }
        }