OSDN Git Service

Ignore non-existent prepared statement in get_query_string.
[pghintplan/pg_hint_plan.git] / pg_hint_plan.c
index e050672..e3c03ff 100644 (file)
@@ -3,7 +3,7 @@
  * pg_hint_plan.c
  *               hinting on how to execute a query for PostgreSQL
  *
- * Copyright (c) 2012-2018, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
+ * Copyright (c) 2012-2020, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
  *
  *-------------------------------------------------------------------------
  */
@@ -359,8 +359,8 @@ struct HintState
        int                             init_min_para_tablescan_size;
        /* min_parallel_index_scan_size*/
        int                             init_min_para_indexscan_size;
-       int                             init_paratup_cost;      /* parallel_tuple_cost */
-       int                             init_parasetup_cost;/* parallel_setup_cost */
+       double                  init_paratup_cost;      /* parallel_tuple_cost */
+       double                  init_parasetup_cost;/* parallel_setup_cost */
 
        PlannerInfo        *current_root;               /* PlannerInfo for the followings */
        Index                   parent_relid;           /* inherit parent of table relid */
@@ -502,6 +502,8 @@ static void setup_scan_method_enforcement(ScanMethodHint *scanhint,
                                                                                  HintState *state);
 static int set_config_int32_option(const char *name, int32 value,
                                                                        GucContext context);
+static int set_config_double_option(const char *name, double value,
+                                                                       GucContext context);
 
 /* GUC variables */
 static bool    pg_hint_plan_enable_hint = true;
@@ -512,6 +514,7 @@ static int  pg_hint_plan_debug_message_level = LOG;
 static bool    pg_hint_plan_enable_hint_table = false;
 
 static int plpgsql_recurse_level = 0;          /* PLpgSQL recursion level            */
+static int recurse_level = 0;          /* recursion level incl. direct SPI calls */
 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 */
@@ -929,7 +932,7 @@ ParallelHintCreate(const char *hint_str, const char *keyword,
 {
        ParallelHint *hint;
 
-       hint = palloc(sizeof(ScanMethodHint));
+       hint = palloc(sizeof(ParallelHint));
        hint->base.hint_str = hint_str;
        hint->base.keyword = keyword;
        hint->base.hint_keyword = hint_keyword;
@@ -1175,7 +1178,8 @@ RowsHintDesc(RowsHint *hint, StringInfo buf, bool nolf)
                        quote_value(buf, hint->relnames[i]);
                }
        }
-       appendStringInfo(buf, " %s", hint->rows_str);
+       if (hint->rows_str != NULL)
+               appendStringInfo(buf, " %s", hint->rows_str);
        appendStringInfoString(buf, ")");
        if (!nolf)
                appendStringInfoChar(buf, '\n');
@@ -1815,7 +1819,7 @@ get_query_string(ParseState *pstate, Query *query, Query **jumblequery)
         * case of DESCRIBE message handling or EXECUTE command. We may still see a
         * candidate top-level query in pstate in the case.
         */
-       if (!p && pstate)
+       if (pstate && pstate->p_sourcetext)
                p = pstate->p_sourcetext;
 
        /* We don't see a query string, return NULL */
@@ -1881,14 +1885,30 @@ get_query_string(ParseState *pstate, Query *query, Query **jumblequery)
                        ExecuteStmt *stmt = (ExecuteStmt *)target_query;
                        PreparedStatement  *entry;
 
-                       entry = FetchPreparedStatement(stmt->name, true);
-                       p = entry->plansource->query_string;
-                       target_query = (Query *) linitial (entry->plansource->query_list);
+                       /*
+                        * Silently ignore nonexistent prepared statements. This may happen
+                        * for EXECUTE within a function definition.  Otherwise the
+                        * execution will fail anyway.
+                        */
+                       entry = FetchPreparedStatement(stmt->name, false);
+
+                       if (entry && entry->plansource->is_valid)
+                       {
+                               p = entry->plansource->query_string;
+                               target_query = (Query *) linitial (entry->plansource->query_list);
+                       }
+                       else
+                       {
+                               /* igonre the hint for EXECUTE if invalidated */
+                               p = NULL;
+                               target_query = NULL;
+                       }
                }
                        
                /* JumbleQuery accespts only a non-utility Query */
-               if (!IsA(target_query, Query) ||
-                       target_query->utilityStmt != NULL)
+               if (target_query &&
+                       (!IsA(target_query, Query) ||
+                        target_query->utilityStmt != NULL))
                        target_query = NULL;
 
                if (jumblequery)
@@ -2059,7 +2079,7 @@ create_hintstate(Query *parse, const char *hints)
                hstate->num_hints[HINT_TYPE_LEADING]);
        hstate->rows_hints = (RowsHint **) (hstate->set_hints +
                hstate->num_hints[HINT_TYPE_SET]);
-       hstate->parallel_hints = (ParallelHint **) (hstate->set_hints +
+       hstate->parallel_hints = (ParallelHint **) (hstate->rows_hints +
                hstate->num_hints[HINT_TYPE_ROWS]);
 
        return hstate;
@@ -2353,6 +2373,8 @@ RowsHintParse(RowsHint *hint, HintState *hstate, Query *parse,
        List               *name_list = NIL;
        char               *rows_str;
        char               *end_ptr;
+       ListCell   *l;
+       int                     i = 0;
 
        if ((str = parse_parentheses(str, &name_list, hint_keyword)) == NULL)
                return NULL;
@@ -2360,23 +2382,28 @@ RowsHintParse(RowsHint *hint, HintState *hstate, Query *parse,
        /* Last element must be rows specification */
        hint->nrels = list_length(name_list) - 1;
 
-       if (hint->nrels > 0)
+       if (hint->nrels < 1)
        {
-               ListCell   *l;
-               int                     i = 0;
+               hint_ereport(str,
+                                        ("%s hint needs at least one relation followed by one correction term.",
+                                         hint->base.keyword));
+               hint->base.state = HINT_STATE_ERROR;
 
-               /*
-                * Transform relation names from list to array to sort them with qsort
-                * after.
-                */
-               hint->relnames = palloc(sizeof(char *) * hint->nrels);
-               foreach (l, name_list)
-               {
-                       if (hint->nrels <= i)
-                               break;
-                       hint->relnames[i] = lfirst(l);
-                       i++;
-               }
+               return str;
+       }
+       
+
+       /*
+        * Transform relation names from list to array to sort them with qsort
+        * after.
+        */
+       hint->relnames = palloc(sizeof(char *) * hint->nrels);
+       foreach (l, name_list)
+       {
+               if (hint->nrels <= i)
+                       break;
+               hint->relnames[i] = lfirst(l);
+               i++;
        }
 
        /* Retieve rows estimation */
@@ -2610,6 +2637,23 @@ set_config_int32_option(const char *name, int32 value, GucContext context)
                                                                  pg_hint_plan_parse_message_level);
 }
 
+/*
+ * Sets GUC parameter of double type without throwing exceptions. Returns false
+ * if something wrong.
+ */
+static int
+set_config_double_option(const char *name, double value, GucContext context)
+{
+       char *buf = float8out_internal(value);
+       int       result;
+
+       result = set_config_option_noerror(name, buf, context,
+                                                                          PGC_S_SESSION, GUC_ACTION_SAVE, true,
+                                                                          pg_hint_plan_parse_message_level);
+       pfree(buf);
+       return result;
+}
+
 /* setup scan method enforcement according to given options */
 static void
 setup_guc_enforcement(SetHint **options, int noptions, GucContext context)
@@ -2657,8 +2701,8 @@ setup_parallel_plan_enforcement(ParallelHint *hint, HintState *state)
        /* force means that enforce parallel as far as possible */
        if (hint && hint->force_parallel && hint->nworkers > 0)
        {
-               set_config_int32_option("parallel_tuple_cost", 0, state->context);
-               set_config_int32_option("parallel_setup_cost", 0, state->context);
+               set_config_double_option("parallel_tuple_cost", 0.0, state->context);
+               set_config_double_option("parallel_setup_cost", 0.0, state->context);
                set_config_int32_option("min_parallel_table_scan_size", 0,
                                                                state->context);
                set_config_int32_option("min_parallel_index_scan_size", 0,
@@ -2666,9 +2710,9 @@ setup_parallel_plan_enforcement(ParallelHint *hint, HintState *state)
        }
        else
        {
-               set_config_int32_option("parallel_tuple_cost",
+               set_config_double_option("parallel_tuple_cost",
                                                                state->init_paratup_cost, state->context);
-               set_config_int32_option("parallel_setup_cost",
+               set_config_double_option("parallel_setup_cost",
                                                                state->init_parasetup_cost, state->context);
                set_config_int32_option("min_parallel_table_scan_size",
                                                                state->init_min_para_tablescan_size,
@@ -2731,6 +2775,25 @@ set_join_config_options(unsigned char enforce_mask, GucContext context)
        SET_CONFIG_OPTION("enable_nestloop", ENABLE_NESTLOOP);
        SET_CONFIG_OPTION("enable_mergejoin", ENABLE_MERGEJOIN);
        SET_CONFIG_OPTION("enable_hashjoin", ENABLE_HASHJOIN);
+
+       /*
+        * Hash join may be rejected for the reason of estimated memory usage. Try
+        * getting rid of that limitation. This change on work_mem is reverted just
+        * after searching join path so no suginificant side-effects are expected.
+        */
+       if (enforce_mask == ENABLE_HASHJOIN)
+       {
+               char                    buf[32];
+
+               /* See final_cost_hashjoin(). */
+               if (work_mem < MAX_KILOBYTES)
+               {
+                       snprintf(buf, sizeof(buf), UINT64_FORMAT, (uint64)MAX_KILOBYTES);
+                       set_config_option_noerror("work_mem", buf,
+                                                                         context, PGC_S_SESSION, GUC_ACTION_SAVE,
+                                                                         true, ERROR);
+               }
+       }
 }
 
 /*
@@ -2852,9 +2915,7 @@ get_current_hint_string(ParseState *pstate, Query *query)
                         */
                        query_len = strlen(query_str) + 1;
                        normalized_query =
-                               generate_normalized_query(&jstate, query_str,
-                                                                                 query->stmt_location,
-                                                                                 &query_len,
+                               generate_normalized_query(&jstate, query_str, 0, &query_len,
                                                                                  GetDatabaseEncoding());
 
                        /*
@@ -2915,6 +2976,14 @@ get_current_hint_string(ParseState *pstate, Query *query)
                current_hint_str = get_hints_from_comment(query_str);
                MemoryContextSwitchTo(oldcontext);
        }
+       else
+       {
+               /*
+                * Failed to get query. We would be in fetching invalidated
+                * plancache. Try the next chance.
+                */
+               current_hint_retrieved = false;
+       }
 
        if (debug_level > 1)
        {
@@ -2967,6 +3036,9 @@ pg_hint_plan_ProcessUtility(PlannedStmt *pstmt, const char *queryString,
        if (prev_ProcessUtility_hook)
                prev_ProcessUtility_hook(pstmt, queryString, context, params, queryEnv,
                                                                 dest, completionTag);
+       else
+               standard_ProcessUtility(pstmt, queryString, context, params, queryEnv,
+                                                                dest, completionTag);
 
        if (plpgsql_recurse_level == 0)
                current_hint_retrieved = false;
@@ -2981,6 +3053,7 @@ pg_hint_plan_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
        int                             save_nestlevel;
        PlannedStmt        *result;
        HintState          *hstate;
+       const char         *prev_hint_str = NULL;
 
        /*
         * Use standard planner if pg_hint_plan is disabled or current nesting 
@@ -3005,13 +3078,11 @@ pg_hint_plan_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
         * Support for nested plpgsql functions. This is quite ugly but this is the
         * only point I could find where I can get the query string.
         */
-       if (plpgsql_recurse_level > 0)
+       if (plpgsql_recurse_level > 0 &&
+               error_context_stack && error_context_stack->arg)
        {
                MemoryContext oldcontext;
 
-               if (current_hint_str)
-                       pfree((void *)current_hint_str);
-
                oldcontext = MemoryContextSwitchTo(TopMemoryContext);
                current_hint_str =
                        get_hints_from_comment((char *)error_context_stack->arg);
@@ -3076,6 +3147,15 @@ pg_hint_plan_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
        }
 
        /*
+        * The planner call below may replace current_hint_str. Store and restore
+        * it so that the subsequent planning in the upper level doesn't get
+        * confused.
+        */
+       recurse_level++;
+       prev_hint_str = current_hint_str;
+       current_hint_str = NULL;
+       
+       /*
         * Use PG_TRY mechanism to recover GUC parameters and current_hint_state to
         * the state when this planner started when error occurred in planner.
         */
@@ -3085,6 +3165,9 @@ pg_hint_plan_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
                        result = (*prev_planner) (parse, cursorOptions, boundParams);
                else
                        result = standard_planner(parse, cursorOptions, boundParams);
+
+               current_hint_str = prev_hint_str;
+               recurse_level--;
        }
        PG_CATCH();
        {
@@ -3092,6 +3175,8 @@ pg_hint_plan_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
                 * Rollback changes of GUC parameters, and pop current hint context
                 * from hint stack to rewind the state.
                 */
+               current_hint_str = prev_hint_str;
+               recurse_level--;
                AtEOXact_GUC(true, save_nestlevel);
                pop_hint();
                PG_RE_THROW();
@@ -3102,7 +3187,7 @@ pg_hint_plan_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
        /*
         * current_hint_str is useless after planning of the top-level query.
         */
-       if (plpgsql_recurse_level < 1 && current_hint_str)
+       if (recurse_level < 1 && current_hint_str)
        {
                pfree((void *)current_hint_str);
                current_hint_str = NULL;
@@ -3135,9 +3220,15 @@ standard_planner_proc:
        }
        current_hint_state = NULL;
        if (prev_planner)
-               return (*prev_planner) (parse, cursorOptions, boundParams);
+               result =  (*prev_planner) (parse, cursorOptions, boundParams);
        else
-               return standard_planner(parse, cursorOptions, boundParams);
+               result = standard_planner(parse, cursorOptions, boundParams);
+
+       /* The upper-level planner still needs the current hint state */
+       if (HintStateStack != NIL)
+               current_hint_state = (HintState *) lfirst(list_head(HintStateStack));
+
+       return result;
 }
 
 /*
@@ -4634,7 +4725,7 @@ pg_hint_plan_set_rel_pathlist(PlannerInfo * root, RelOptInfo *rel,
                /*
                 * When hint is specified on non-parent relations, discard existing
                 * paths and regenerate based on the hint considered. Otherwise we
-                * already have hinted childx paths then just adjust the number of
+                * already have hinted child paths then just adjust the number of
                 * planned number of workers.
                 */
                if (root->simple_rte_array[rel->relid]->inh)
@@ -4717,7 +4808,8 @@ pg_hint_plan_set_rel_pathlist(PlannerInfo * root, RelOptInfo *rel,
                                }
 
                                /* Generate gather paths */
-                               if (rel->reloptkind == RELOPT_BASEREL)
+                               if (rel->reloptkind == RELOPT_BASEREL &&
+                                       bms_membership(root->all_baserels) != BMS_SINGLETON)
                                        generate_gather_paths(root, rel, false);
                        }
                }