/*-------------------------------------------------------------------------
*
* pg_hint_plan.c
- * do instructions or hints to the planner using C-style block comments
- * of the SQL.
+ * hinting on how to execute a query for PostgreSQL
*
- * Copyright (c) 2012-2014, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
+ * Copyright (c) 2012-2017, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
*
*-------------------------------------------------------------------------
*/
+#include <string.h>
+
#include "postgres.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_index.h"
#include "utils/memutils.h"
#include "utils/rel.h"
#include "utils/syscache.h"
+#include "utils/resowner.h"
#include "catalog/pg_class.h"
#include "executor/spi.h"
#include "catalog/pg_type.h"
-/*
- * We have our own header file "plpgsql-9.1", which is necessary to support
- * hints for queries in PL/pgSQL blocks, in pg_hint_plan source package,
- * because PostgreSQL 9.1 doesn't provide the header file as a part of
- * installation. This header file is a copy of src/pl/plpgsql/src/plpgsql.h in
- * PostgreSQL 9.1.9 source tree,
- *
- * On the other hand, 9.2 installation provides that header file for external
- * modules, so we include the header in ordinary place.
- */
#include "plpgsql.h"
/* partially copied from pg_stat_statements */
#define HINT_INDEXONLYSCAN "IndexOnlyScan"
#define HINT_INDEXONLYSCANREGEXP "IndexOnlyScanRegexp"
#define HINT_NOINDEXONLYSCAN "NoIndexOnlyScan"
+#define HINT_PARALLEL "Parallel"
+
#define HINT_NESTLOOP "NestLoop"
#define HINT_MERGEJOIN "MergeJoin"
#define HINT_HASHJOIN "HashJoin"
HINT_KEYWORD_INDEXONLYSCAN,
HINT_KEYWORD_INDEXONLYSCANREGEXP,
HINT_KEYWORD_NOINDEXONLYSCAN,
+
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_ROWS,
+ HINT_KEYWORD_PARALLEL,
+
HINT_KEYWORD_UNRECOGNIZED
} HintKeyword;
+#define SCAN_HINT_ACCEPTS_INDEX_NAMES(kw) \
+ (kw == HINT_KEYWORD_INDEXSCAN || \
+ kw == HINT_KEYWORD_INDEXSCANREGEXP || \
+ kw == HINT_KEYWORD_INDEXONLYSCAN || \
+ kw == HINT_KEYWORD_INDEXONLYSCANREGEXP || \
+ kw == HINT_KEYWORD_BITMAPSCAN || \
+ kw == HINT_KEYWORD_BITMAPSCANREGEXP)
+
+
typedef struct Hint Hint;
typedef struct HintState HintState;
Query *parse, const char *str);
/* hint types */
-#define NUM_HINT_TYPE 5
+#define NUM_HINT_TYPE 6
typedef enum HintType
{
HINT_TYPE_SCAN_METHOD,
HINT_TYPE_LEADING,
HINT_TYPE_SET,
HINT_TYPE_ROWS,
+ HINT_TYPE_PARALLEL
} HintType;
static const char *HintTypeName[] = {
"leading",
"set",
"rows",
+ "parallel"
};
/* hint status */
double rows;
} RowsHint;
+/* parallel hints */
+typedef struct ParallelHint
+{
+ Hint base;
+ char *relname;
+ char *nworkers_str; /* original string of nworkers */
+ int nworkers; /* num of workers specified by Worker */
+ bool force_parallel; /* force parallel scan */
+} ParallelHint;
+
/*
* Describes a context of hint processing.
*/
/* for scan method hints */
ScanMethodHint **scan_hints; /* parsed scan hints */
- int init_scan_mask; /* initial value scan parameter */
- Index parent_relid; /* inherit parent table relid */
- ScanMethodHint *parent_hint; /* inherit parent table scan hint */
- List *parent_index_infos; /* information of inherit parent table's
- * index */
- /* for join method hints */
+ /* Initial values of parameters */
+ int init_scan_mask; /* enable_* mask */
+ int init_nworkers; /* max_parallel_workers_per_gather */
+ int init_min_para_size; /* min_parallel_relation_size*/
+ int init_paratup_cost; /* parallel_tuple_cost */
+ int init_parasetup_cost;/* parallel_setup_cost */
+
+ 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 */
+ List *parent_index_infos; /* list of parent table's index */
+
JoinMethodHint **join_hints; /* parsed join hints */
int init_join_mask; /* initial value join parameter */
List **join_hint_level;
-
- /* for Leading hint */
LeadingHint **leading_hint; /* parsed Leading hints */
-
- /* for Set hints */
SetHint **set_hints; /* parsed Set hints */
GucContext context; /* which GUC parameters can we set? */
-
- /* for Rows hints */
RowsHint **rows_hints; /* parsed Rows hints */
+ ParallelHint **parallel_hints; /* parsed Parallel hints */
};
/*
DestReceiver *dest, char *completionTag);
static PlannedStmt *pg_hint_plan_planner(Query *parse, int cursorOptions,
ParamListInfo boundParams);
-static void pg_hint_plan_get_relation_info(PlannerInfo *root,
- Oid relationObjectId,
- bool inhparent, RelOptInfo *rel);
static RelOptInfo *pg_hint_plan_join_search(PlannerInfo *root,
int levels_needed,
List *initial_rels);
+/* Scan method hint callbacks */
static Hint *ScanMethodHintCreate(const char *hint_str, const char *keyword,
HintKeyword hint_keyword);
static void ScanMethodHintDelete(ScanMethodHint *hint);
static int ScanMethodHintCmp(const ScanMethodHint *a, const ScanMethodHint *b);
static const char *ScanMethodHintParse(ScanMethodHint *hint, HintState *hstate,
Query *parse, const char *str);
+
+/* Join method hint callbacks */
static Hint *JoinMethodHintCreate(const char *hint_str, const char *keyword,
HintKeyword hint_keyword);
static void JoinMethodHintDelete(JoinMethodHint *hint);
static int JoinMethodHintCmp(const JoinMethodHint *a, const JoinMethodHint *b);
static const char *JoinMethodHintParse(JoinMethodHint *hint, HintState *hstate,
Query *parse, const char *str);
+
+/* Leading hint callbacks */
static Hint *LeadingHintCreate(const char *hint_str, const char *keyword,
HintKeyword hint_keyword);
static void LeadingHintDelete(LeadingHint *hint);
static int LeadingHintCmp(const LeadingHint *a, const LeadingHint *b);
static const char *LeadingHintParse(LeadingHint *hint, HintState *hstate,
Query *parse, const char *str);
+
+/* Set hint callbacks */
static Hint *SetHintCreate(const char *hint_str, const char *keyword,
HintKeyword hint_keyword);
static void SetHintDelete(SetHint *hint);
static int SetHintCmp(const SetHint *a, const SetHint *b);
static const char *SetHintParse(SetHint *hint, HintState *hstate, Query *parse,
const char *str);
+
+/* Rows hint callbacks */
static Hint *RowsHintCreate(const char *hint_str, const char *keyword,
HintKeyword hint_keyword);
static void RowsHintDelete(RowsHint *hint);
static int RowsHintCmp(const RowsHint *a, const RowsHint *b);
static const char *RowsHintParse(RowsHint *hint, HintState *hstate,
Query *parse, const char *str);
-static Hint *LeadingHintCreate(const char *hint_str, const char *keyword,
- HintKeyword hint_keyword);
+
+/* Parallel hint callbacks */
+static Hint *ParallelHintCreate(const char *hint_str, const char *keyword,
+ HintKeyword hint_keyword);
+static void ParallelHintDelete(ParallelHint *hint);
+static void ParallelHintDesc(ParallelHint *hint, StringInfo buf, bool nolf);
+static int ParallelHintCmp(const ParallelHint *a, const ParallelHint *b);
+static const char *ParallelHintParse(ParallelHint *hint, HintState *hstate,
+ Query *parse, const char *str);
static void quote_value(StringInfo buf, const char *value);
int levels_needed,
List *initial_rels);
void pg_hint_plan_join_search_one_level(PlannerInfo *root, int level);
+void pg_hint_plan_set_rel_pathlist(PlannerInfo * root, RelOptInfo *rel,
+ Index rti, RangeTblEntry *rte);
+static void create_plain_partial_paths(PlannerInfo *root,
+ RelOptInfo *rel);
+static int compute_parallel_worker(RelOptInfo *rel, BlockNumber pages);
+
static void make_rels_by_clause_joins(PlannerInfo *root, RelOptInfo *old_rel,
ListCell *other_rels);
static void make_rels_by_clauseless_joins(PlannerInfo *root,
RelOptInfo *old_rel,
ListCell *other_rels);
static bool has_join_restriction(PlannerInfo *root, RelOptInfo *rel);
+static void set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
+ RangeTblEntry *rte);
static void set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
Index rti, RangeTblEntry *rte);
static void generate_mergeappend_paths(PlannerInfo *root, RelOptInfo *rel,
PLpgSQL_stmt *stmt);
static void pg_hint_plan_plpgsql_stmt_end(PLpgSQL_execstate *estate,
PLpgSQL_stmt *stmt);
+static void plpgsql_query_erase_callback(ResourceReleasePhase phase,
+ bool isCommit,
+ bool isTopLevel,
+ void *arg);
+static int set_config_option_noerror(const char *name, const char *value,
+ GucContext context, GucSource source,
+ GucAction action, bool changeVal, int elevel);
+static void setup_scan_method_enforcement(ScanMethodHint *scanhint,
+ HintState *state);
+static int set_config_int32_option(const char *name, int32 value,
+ GucContext context);
/* GUC variables */
static bool pg_hint_plan_enable_hint = true;
static int pg_hint_plan_message_level = INFO;
/* Default is off, to keep backward compatibility. */
static bool pg_hint_plan_enable_hint_table = false;
-static bool hidestmt = false;
+
+/* Internal static variables. */
+static bool hidestmt = false; /* Allow or inhibit STATEMENT: output */
+
+static int plpgsql_recurse_level = 0; /* PLpgSQL recursion level */
+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 const struct config_enum_entry parse_messages_level_options[] = {
{"debug", DEBUG2, true},
/* Saved hook values in case of unload */
static ProcessUtility_hook_type prev_ProcessUtility = NULL;
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 set_rel_pathlist_hook_type prev_set_rel_pathlist = NULL;
/* Hold reference to currently active hint */
-static HintState *current_hint = NULL;
+static HintState *current_hint_state = 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.
+ * context stack, so current_hint_state always points the first element of this
+ * list.
*/
static List *HintStateStack = NIL;
{HINT_INDEXONLYSCANREGEXP, ScanMethodHintCreate,
HINT_KEYWORD_INDEXONLYSCANREGEXP},
{HINT_NOINDEXONLYSCAN, ScanMethodHintCreate, HINT_KEYWORD_NOINDEXONLYSCAN},
+
{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},
{HINT_ROWS, RowsHintCreate, HINT_KEYWORD_ROWS},
+ {HINT_PARALLEL, ParallelHintCreate, HINT_KEYWORD_PARALLEL},
+
{NULL, NULL, HINT_KEYWORD_UNRECOGNIZED}
};
-/*
- * PL/pgSQL plugin for retrieving string representation of each query during
- * function execution.
- */
-static const char *plpgsql_query_string = NULL;
-static enum PLpgSQL_stmt_types plpgsql_query_string_src;
-
PLpgSQL_plugin plugin_funcs = {
NULL,
NULL,
NULL,
};
-/* Current nesting depth of SPI calls, used to prevent recursive calls */
-static int nested_level = 0;
-
/*
* Module load callbacks
*/
NULL);
DefineCustomBoolVariable("pg_hint_plan.enable_hint_table",
- "Force planner to not get hint by using table lookups.",
+ "Let pg_hint_plan look up the hint table.",
NULL,
&pg_hint_plan_enable_hint_table,
false,
ProcessUtility_hook = pg_hint_plan_ProcessUtility;
prev_planner = planner_hook;
planner_hook = pg_hint_plan_planner;
- prev_get_relation_info = get_relation_info_hook;
- get_relation_info_hook = pg_hint_plan_get_relation_info;
prev_join_search = join_search_hook;
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;
/* setup PL/pgSQL plugin hook */
var_ptr = (PLpgSQL_plugin **) find_rendezvous_variable("PLpgSQL_plugin");
*var_ptr = &plugin_funcs;
+
+ RegisterResourceReleaseCallback(plpgsql_query_erase_callback, NULL);
}
/*
/* Uninstall hooks. */
ProcessUtility_hook = prev_ProcessUtility;
planner_hook = prev_planner;
- get_relation_info_hook = prev_get_relation_info;
join_search_hook = prev_join_search;
+ set_rel_pathlist_hook = prev_set_rel_pathlist;
/* uninstall PL/pgSQL plugin hook */
var_ptr = (PLpgSQL_plugin **) find_rendezvous_variable("PLpgSQL_plugin");
pfree(hint);
}
+static Hint *
+ParallelHintCreate(const char *hint_str, const char *keyword,
+ HintKeyword hint_keyword)
+{
+ ParallelHint *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_PARALLEL;
+ hint->base.state = HINT_STATE_NOTUSED;
+ hint->base.delete_func = (HintDeleteFunction) ParallelHintDelete;
+ hint->base.desc_func = (HintDescFunction) ParallelHintDesc;
+ hint->base.cmp_func = (HintCmpFunction) ParallelHintCmp;
+ hint->base.parse_func = (HintParseFunction) ParallelHintParse;
+ hint->relname = NULL;
+ hint->nworkers = 0;
+ hint->nworkers_str = "0";
+
+ return (Hint *) hint;
+}
+
+static void
+ParallelHintDelete(ParallelHint *hint)
+{
+ if (!hint)
+ return;
+
+ if (hint->relname)
+ pfree(hint->relname);
+ pfree(hint);
+}
+
+
static HintState *
HintStateCreate(void)
{
memset(hstate->num_hints, 0, sizeof(hstate->num_hints));
hstate->scan_hints = NULL;
hstate->init_scan_mask = 0;
+ hstate->init_nworkers = 0;
+ hstate->init_min_para_size = 0;
+ hstate->init_paratup_cost = 0;
+ hstate->init_parasetup_cost = 0;
hstate->parent_relid = 0;
- hstate->parent_hint = NULL;
+ hstate->parent_scan_hint = NULL;
+ hstate->parent_parallel_hint = NULL;
hstate->parent_index_infos = NIL;
hstate->join_hints = NULL;
hstate->init_join_mask = 0;
hstate->context = superuser() ? PGC_SUSET : PGC_USERSET;
hstate->set_hints = NULL;
hstate->rows_hints = NULL;
+ hstate->parallel_hints = NULL;
return hstate;
}
appendStringInfoChar(buf, '\n');
}
+static void
+ParallelHintDesc(ParallelHint *hint, StringInfo buf, bool nolf)
+{
+ appendStringInfo(buf, "%s(", hint->base.keyword);
+ if (hint->relname != NULL)
+ {
+ quote_value(buf, hint->relname);
+
+ /* number of workers */
+ appendStringInfoCharMacro(buf, ' ');
+ quote_value(buf, hint->nworkers_str);
+ /* application mode of num of workers */
+ appendStringInfoCharMacro(buf, ' ');
+ appendStringInfoString(buf,
+ (hint->force_parallel ? "hard" : "soft"));
+ }
+ appendStringInfoString(buf, ")");
+ if (!nolf)
+ appendStringInfoChar(buf, '\n');
+}
+
/*
* Append string which represents all hints in a given state to buf, with
* preceding title with them.
if (!hstate)
{
elog(pg_hint_plan_message_level,
- "pg_hint_plan%s: HintStateDump:\nno hint", qnostr);
+ "pg_hint_plan%s: HintStateDump: no hint", qnostr);
return;
}
}
static int
+ParallelHintCmp(const ParallelHint *a, const ParallelHint *b)
+{
+ return RelnameCmp(&a->relname, &b->relname);
+}
+
+static int
HintCmp(const void *a, const void *b)
{
const Hint *hinta = *((const Hint **) a);
PG_TRY();
{
- ++nested_level;
+ hint_inhibit_level++;
SPI_connect();
SPI_finish();
- --nested_level;
+ hint_inhibit_level--;
}
PG_CATCH();
{
- --nested_level;
+ hint_inhibit_level--;
PG_RE_THROW();
}
PG_END_TRY();
{
const char *p;
- if (stmt_name)
+ if (plpgsql_recurse_level > 0)
+ {
+ /*
+ * This is quite ugly but this is the only point I could find where
+ * we can get the query string.
+ */
+ p = (char*)error_context_stack->arg;
+ }
+ else if (stmt_name)
{
PreparedStatement *entry;
entry = FetchPreparedStatement(stmt_name, true);
p = entry->plansource->query_string;
}
- else if (plpgsql_query_string)
- p = plpgsql_query_string;
else
p = debug_query_string;
if (hints == NULL)
return NULL;
+ /* -1 means that no Parallel hint is specified. */
+ max_hint_nworkers = -1;
+
p = hints;
hstate = HintStateCreate();
hstate->hint_str = (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->num_hints[HINT_TYPE_ROWS]);
return hstate;
}
/* Parse relation name and index name(s) if given hint accepts. */
length = list_length(name_list);
- if (length > 0)
+
+ /* at least twp parameters required */
+ if (length < 1)
{
- hint->relname = linitial(name_list);
- hint->indexnames = list_delete_first(name_list);
-
- /* check whether the hint accepts index name(s). */
- if (length != 1 &&
- hint_keyword != HINT_KEYWORD_INDEXSCAN &&
- hint_keyword != HINT_KEYWORD_INDEXSCANREGEXP &&
- hint_keyword != HINT_KEYWORD_INDEXONLYSCAN &&
- hint_keyword != HINT_KEYWORD_INDEXONLYSCANREGEXP &&
- hint_keyword != HINT_KEYWORD_BITMAPSCAN &&
- hint_keyword != HINT_KEYWORD_BITMAPSCANREGEXP)
- {
- hint_ereport(str,
- ("%s hint accepts only one relation.",
- hint->base.keyword));
- hint->base.state = HINT_STATE_ERROR;
- return str;
- }
+ hint_ereport(str,
+ ("%s hint requires a relation.", hint->base.keyword));
+ hint->base.state = HINT_STATE_ERROR;
+ return str;
}
- else
+
+ hint->relname = linitial(name_list);
+ hint->indexnames = list_delete_first(name_list);
+
+ /* check whether the hint accepts index name(s) */
+ if (length > 1 && !SCAN_HINT_ACCEPTS_INDEX_NAMES(hint_keyword))
{
hint_ereport(str,
- ("%s hint requires a relation.",
+ ("%s hint accepts only one relation.",
hint->base.keyword));
hint->base.state = HINT_STATE_ERROR;
return str;
return str;
}
+static const char *
+ParallelHintParse(ParallelHint *hint, HintState *hstate, Query *parse,
+ const char *str)
+{
+ HintKeyword hint_keyword = hint->base.hint_keyword;
+ List *name_list = NIL;
+ int length;
+ char *end_ptr;
+ int nworkers;
+ bool force_parallel = false;
+
+ if ((str = parse_parentheses(str, &name_list, hint_keyword)) == NULL)
+ return NULL;
+
+ /* Parse relation name and index name(s) if given hint accepts. */
+ length = list_length(name_list);
+
+ if (length < 2 || length > 3)
+ {
+ hint_ereport(str,
+ ("Wrong number of arguments for %s hint.",
+ hint->base.keyword));
+ hint->base.state = HINT_STATE_ERROR;
+ return str;
+ }
+
+ hint->relname = linitial(name_list);
+
+ /* The second parameter is number of workers */
+ hint->nworkers_str = list_nth(name_list, 1);
+ nworkers = strtod(hint->nworkers_str, &end_ptr);
+ if (*end_ptr || nworkers < 0)
+ {
+ hint_ereport(hint->nworkers_str,
+ ("number of workers must be a positive integer: %s",
+ hint->base.keyword));
+ hint->base.state = HINT_STATE_ERROR;
+ return str;
+ }
+
+ hint->nworkers = nworkers;
+
+ if (nworkers > max_hint_nworkers)
+ max_hint_nworkers = nworkers;
+
+ /* optional third parameter is specified */
+ if (length == 3)
+ {
+ const char *modeparam = (const char *)list_nth(name_list, 2);
+ if (strcasecmp(modeparam, "hard") == 0)
+ force_parallel = true;
+ else if (strcasecmp(modeparam, "soft") != 0)
+ {
+ hint_ereport(str,
+ ("The mode of Worker hint must be soft or hard."));
+ hint->base.state = HINT_STATE_ERROR;
+ return str;
+ }
+ }
+
+ hint->force_parallel = force_parallel;
+
+ return str;
+}
+
/*
* set GUC parameter functions
*/
static int
-set_config_option_wrapper(const char *name, const char *value,
+get_current_scan_mask()
+{
+ int mask = 0;
+
+ if (enable_seqscan)
+ mask |= ENABLE_SEQSCAN;
+ if (enable_indexscan)
+ mask |= ENABLE_INDEXSCAN;
+ if (enable_bitmapscan)
+ mask |= ENABLE_BITMAPSCAN;
+ if (enable_tidscan)
+ mask |= ENABLE_TIDSCAN;
+ if (enable_indexonlyscan)
+ mask |= ENABLE_INDEXONLYSCAN;
+
+ return mask;
+}
+
+static int
+get_current_join_mask()
+{
+ int mask = 0;
+
+ if (enable_nestloop)
+ mask |= ENABLE_NESTLOOP;
+ if (enable_mergejoin)
+ mask |= ENABLE_MERGEJOIN;
+ if (enable_hashjoin)
+ mask |= ENABLE_HASHJOIN;
+
+ return mask;
+}
+
+/*
+ * Sets GUC prameters without throwing exception. Reutrns false if something
+ * wrong.
+ */
+static int
+set_config_option_noerror(const char *name, const char *value,
GucContext context, GucSource source,
GucAction action, bool changeVal, int elevel)
{
PG_TRY();
{
result = set_config_option(name, value, context, source,
- action, changeVal, 0);
+ action, changeVal, 0, false);
}
PG_CATCH();
{
return result;
}
+/*
+ * Sets GUC parameter of int32 type without throwing exceptions. Returns false
+ * if something wrong.
+ */
static int
-set_config_options(SetHint **options, int noptions, GucContext context)
+set_config_int32_option(const char *name, int32 value, GucContext context)
{
- int i;
- int save_nestlevel;
+ char buf[16]; /* enough for int32 */
- save_nestlevel = NewGUCNestLevel();
+ if (snprintf(buf, 16, "%d", value) < 0)
+ {
+ ereport(pg_hint_plan_message_level,
+ (errmsg ("Cannot set integer value: %d: %s",
+ max_hint_nworkers, strerror(errno))));
+ return false;
+ }
+
+ return
+ set_config_option_noerror(name, buf, context,
+ PGC_S_SESSION, GUC_ACTION_SAVE, true,
+ pg_hint_plan_message_level);
+}
+
+/* setup scan method enforcement according to given options */
+static void
+setup_guc_enforcement(SetHint **options, int noptions, GucContext context)
+{
+ int i;
for (i = 0; i < noptions; i++)
{
if (!hint_state_enabled(hint))
continue;
- result = set_config_option_wrapper(hint->name, hint->value, context,
+ result = set_config_option_noerror(hint->name, hint->value, context,
PGC_S_SESSION, GUC_ACTION_SAVE, true,
pg_hint_plan_message_level);
if (result != 0)
hint->base.state = HINT_STATE_ERROR;
}
- return save_nestlevel;
+ return;
+}
+
+/*
+ * Setup parallel execution environment.
+ *
+ * If hint is not NULL, set up using it, elsewise reset to initial environment.
+ */
+static void
+setup_parallel_plan_enforcement(ParallelHint *hint, HintState *state)
+{
+ if (hint)
+ {
+ hint->base.state = HINT_STATE_USED;
+ set_config_int32_option("max_parallel_workers_per_gather",
+ hint->nworkers, state->context);
+ }
+ else
+ set_config_int32_option("max_parallel_workers_per_gather",
+ state->init_nworkers, state->context);
+
+ /* force means that enforce parallel as far as possible */
+ if (hint && hint->force_parallel)
+ {
+ set_config_int32_option("parallel_tuple_cost", 0, state->context);
+ set_config_int32_option("parallel_setup_cost", 0, state->context);
+ set_config_int32_option("min_parallel_relation_size", 0,
+ state->context);
+ }
+ else
+ {
+ set_config_int32_option("parallel_tuple_cost",
+ state->init_paratup_cost, state->context);
+ set_config_int32_option("parallel_setup_cost",
+ state->init_parasetup_cost, state->context);
+ set_config_int32_option("min_parallel_relation_size",
+ state->init_min_para_size, state->context);
+ }
}
#define SET_CONFIG_OPTION(name, type_bits) \
- set_config_option_wrapper((name), \
+ set_config_option_noerror((name), \
(mask & (type_bits)) ? "true" : "false", \
context, PGC_S_SESSION, GUC_ACTION_SAVE, true, ERROR)
+
+/*
+ * Setup GUC environment to enforce scan methods. If scanhint is NULL, reset
+ * GUCs to the saved state in state.
+ */
static void
-set_scan_config_options(unsigned char enforce_mask, GucContext context)
+setup_scan_method_enforcement(ScanMethodHint *scanhint, HintState *state)
{
+ unsigned char enforce_mask = state->init_scan_mask;
+ GucContext context = state->context;
unsigned char mask;
+ if (scanhint)
+ {
+ enforce_mask = scanhint->enforce_mask;
+ scanhint->base.state = HINT_STATE_USED;
+ }
+
if (enforce_mask == ENABLE_SEQSCAN || enforce_mask == ENABLE_INDEXSCAN ||
enforce_mask == ENABLE_BITMAPSCAN || enforce_mask == ENABLE_TIDSCAN
|| enforce_mask == (ENABLE_INDEXSCAN | ENABLE_INDEXONLYSCAN)
)
mask = enforce_mask;
else
- mask = enforce_mask & current_hint->init_scan_mask;
+ mask = enforce_mask & current_hint_state->init_scan_mask;
SET_CONFIG_OPTION("enable_seqscan", ENABLE_SEQSCAN);
SET_CONFIG_OPTION("enable_indexscan", ENABLE_INDEXSCAN);
enforce_mask == ENABLE_HASHJOIN)
mask = enforce_mask;
else
- mask = enforce_mask & current_hint->init_join_mask;
+ mask = enforce_mask & current_hint_state->init_join_mask;
SET_CONFIG_OPTION("enable_nestloop", ENABLE_NESTLOOP);
SET_CONFIG_OPTION("enable_mergejoin", ENABLE_MERGEJOIN);
* Use standard planner if pg_hint_plan is disabled or current nesting
* depth is nesting depth of SPI calls.
*/
- if (!pg_hint_plan_enable_hint || nested_level > 0)
+ if (!pg_hint_plan_enable_hint || hint_inhibit_level > 0)
{
if (debug_level > 1)
ereport(pg_hint_plan_message_level,
HintStateStack = lcons(hstate, HintStateStack);
/* Pushed hint is the one which should be used hereafter. */
- current_hint = hstate;
+ current_hint_state = hstate;
}
/* Pop a hint from hint stack. Popped hint is automatically discarded. */
elog(ERROR, "hint stack is empty");
/*
- * 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).
+ * Take a hint at the head from the list, and free it. Switch
+ * current_hint_state to point new head (NULL if the list is empty).
*/
HintStateStack = list_delete_first(HintStateStack);
- HintStateDelete(current_hint);
+ HintStateDelete(current_hint_state);
if(HintStateStack == NIL)
- current_hint = NULL;
+ current_hint_state = NULL;
else
- current_hint = (HintState *) lfirst(list_head(HintStateStack));
+ current_hint_state = (HintState *) lfirst(list_head(HintStateStack));
}
static PlannedStmt *
/*
* Use standard planner if pg_hint_plan is disabled or current nesting
* depth is nesting depth of SPI calls. Other hook functions try to change
- * plan with current_hint if any, so set it to NULL.
+ * plan with current_hint_state if any, so set it to NULL.
*/
- if (!pg_hint_plan_enable_hint || nested_level > 0)
+ if (!pg_hint_plan_enable_hint || hint_inhibit_level > 0)
{
if (debug_level > 1)
elog(pg_hint_plan_message_level,
"pg_hint_plan%s: planner: enable_hint=%d,"
- " nested_level=%d",
- qnostr, pg_hint_plan_enable_hint, nested_level);
+ " hint_inhibit_level=%d",
+ qnostr, pg_hint_plan_enable_hint, hint_inhibit_level);
hidestmt = true;
goto standard_planner_proc;
/*
* 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.
+ * functions try to change plan with current_hint_state if any, so set it
+ * to NULL.
*/
if (!hstate)
goto standard_planner_proc;
*/
push_hint(hstate);
- /* 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);
+ /* Set scan enforcement here. */
+ save_nestlevel = NewGUCNestLevel();
- if (enable_seqscan)
- current_hint->init_scan_mask |= ENABLE_SEQSCAN;
- if (enable_indexscan)
- current_hint->init_scan_mask |= ENABLE_INDEXSCAN;
- if (enable_bitmapscan)
- current_hint->init_scan_mask |= ENABLE_BITMAPSCAN;
- if (enable_tidscan)
- current_hint->init_scan_mask |= ENABLE_TIDSCAN;
- if (enable_indexonlyscan)
- current_hint->init_scan_mask |= ENABLE_INDEXONLYSCAN;
- if (enable_nestloop)
- current_hint->init_join_mask |= ENABLE_NESTLOOP;
- if (enable_mergejoin)
- current_hint->init_join_mask |= ENABLE_MERGEJOIN;
- if (enable_hashjoin)
- current_hint->init_join_mask |= ENABLE_HASHJOIN;
+ /* Apply Set hints, then save it as the initial state */
+ setup_guc_enforcement(current_hint_state->set_hints,
+ current_hint_state->num_hints[HINT_TYPE_SET],
+ current_hint_state->context);
+
+ current_hint_state->init_scan_mask = get_current_scan_mask();
+ current_hint_state->init_join_mask = get_current_join_mask();
+ current_hint_state->init_min_para_size = min_parallel_relation_size;
+ current_hint_state->init_paratup_cost = parallel_tuple_cost;
+ current_hint_state->init_parasetup_cost = parallel_setup_cost;
+
+ /*
+ * max_parallel_workers_per_gather should be non-zero here if Workers hint
+ * is specified.
+ */
+ if (max_hint_nworkers > 0 && max_parallel_workers_per_gather < 1)
+ set_config_int32_option("max_parallel_workers_per_gather",
+ 1, current_hint_state->context);
+ current_hint_state->init_nworkers = max_parallel_workers_per_gather;
if (debug_level > 1)
{
}
/*
- * Use PG_TRY mechanism to recover GUC parameters and current_hint to the
- * state when this planner started when error occurred in planner.
+ * Use PG_TRY mechanism to recover GUC parameters and current_hint_state to
+ * the state when this planner started when error occurred in planner.
*/
PG_TRY();
{
/* Print hint in debug mode. */
if (debug_level == 1)
- HintStateDump(current_hint);
+ HintStateDump(current_hint_state);
else if (debug_level > 1)
- HintStateDump2(current_hint);
+ HintStateDump2(current_hint_state);
/*
* Rollback changes of GUC parameters, and pop current hint context from
qnostr, msgstr)));
hidestmt = true;
}
- current_hint = NULL;
+ current_hint_state = NULL;
if (prev_planner)
return (*prev_planner) (parse, cursorOptions, boundParams);
else
}
/*
- * Return scan method hint which matches given aliasname.
+ * Find scan method hint to be applied to the given relation
*/
static ScanMethodHint *
find_scan_hint(PlannerInfo *root, Index relid, RelOptInfo *rel)
int i;
/*
- * We can't apply scan method hint if the relation is:
+ * We don't apply scan method hint if the relation is:
* - not a base relation
* - not an ordinary relation (such as join and subquery)
*/
- if (rel && (rel->reloptkind != RELOPT_BASEREL || rel->rtekind != RTE_RELATION))
+ if (rel && (rel->reloptkind != RELOPT_BASEREL ||
+ rel->rtekind != RTE_RELATION))
+ return NULL;
+
+ rte = root->simple_rte_array[relid];
+
+ /* We don'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_state->num_hints[HINT_TYPE_SCAN_METHOD]; i++)
+ {
+ ScanMethodHint *hint = current_hint_state->scan_hints[i];
+
+ /* We ignore disabled hints. */
+ if (!hint_state_enabled(hint))
+ continue;
+
+ if (RelnameCmp(&rte->eref->aliasname, &hint->relname) == 0)
+ return hint;
+ }
+
+ return NULL;
+}
+
+static ParallelHint *
+find_parallel_hint(PlannerInfo *root, Index relid, RelOptInfo *rel)
+{
+ RangeTblEntry *rte;
+ int i;
+
+ /*
+ * We don't apply scan method hint if the relation is:
+ * - not a base relation and inheritance children
+ * - not an ordinary relation (such as join and subquery)
+ */
+ if (!rel || rel->rtekind != RTE_RELATION ||
+ (rel->reloptkind != RELOPT_BASEREL &&
+ rel->reloptkind != RELOPT_OTHER_MEMBER_REL))
return NULL;
rte = root->simple_rte_array[relid];
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++)
+ for (i = 0; i < current_hint_state->num_hints[HINT_TYPE_PARALLEL]; i++)
{
- ScanMethodHint *hint = current_hint->scan_hints[i];
+ ParallelHint *hint = current_hint_state->parallel_hints[i];
/* We ignore disabled hints. */
if (!hint_state_enabled(hint))
return DatumGetBool(result);
}
+
+/* Remove indexes instructed not to use by hint. */
static void
-delete_indexes(ScanMethodHint *hint, RelOptInfo *rel, Oid relationObjectId)
+restrict_indexes(PlannerInfo *root, ScanMethodHint *hint, RelOptInfo *rel,
+ bool using_parent_hint)
{
ListCell *cell;
ListCell *prev;
ListCell *next;
StringInfoData buf;
+ RangeTblEntry *rte = root->simple_rte_array[rel->relid];
+ Oid relationObjectId = rte->relid;
/*
* We delete all the IndexOptInfo list and prevent you from being usable by
}
/*
- * to make the index a candidate when definition of this index is
- * matched with the index's definition of current_hint.
+ * Apply index restriction of parent hint to children. Since index
+ * inheritance is not explicitly described we should search for an
+ * children's index with the same definition to that of the parent.
*/
- if (OidIsValid(relationObjectId) && !use_index)
+ if (using_parent_hint && !use_index)
{
- foreach(l, current_hint->parent_index_infos)
+ foreach(l, current_hint_state->parent_index_infos)
{
int i;
HeapTuple ht_idx;
ParentIndexInfo *p_info = (ParentIndexInfo *)lfirst(l);
- /* check to match the parameter of unique */
- if (p_info->indisunique != info->unique)
- continue;
-
- /* check to match the parameter of index's method */
- if (p_info->method != info->relam)
- continue;
-
- /* to check to match the indexkey's configuration */
- if ((list_length(p_info->column_names)) !=
- info->ncolumns)
+ /*
+ * we check the 'same' index by comparing uniqueness, access
+ * method and index key columns.
+ */
+ if (p_info->indisunique != info->unique ||
+ p_info->method != info->relam ||
+ list_length(p_info->column_names) != info->ncolumns)
continue;
- /* check to match the indexkey's configuration */
+ /* Check if index key columns match */
for (i = 0; i < info->ncolumns; i++)
{
char *c_attname = NULL;
char *p_attname = NULL;
- p_attname =
- list_nth(p_info->column_names, i);
+ p_attname = list_nth(p_info->column_names, i);
- /* both are expressions */
+ /*
+ * if both of the key of the same position are expressions,
+ * ignore them for now and check later.
+ */
if (info->indexkeys[i] == 0 && !p_attname)
continue;
- /* one's column is expression, the other is not */
+ /* deny if one is expression while another is not */
if (info->indexkeys[i] == 0 || !p_attname)
break;
c_attname = get_attname(relationObjectId,
info->indexkeys[i]);
- if (strcmp(p_attname, c_attname) != 0)
- break;
-
- if (p_info->indcollation[i] != info->indexcollations[i])
- break;
-
- if (p_info->opclass[i] != info->opcintype[i])
- break;
-
- if (((p_info->indoption[i] & INDOPTION_DESC) != 0) !=
- info->reverse_sort[i])
+ /* deny if any of column attributes don't match */
+ if (strcmp(p_attname, c_attname) != 0 ||
+ p_info->indcollation[i] != info->indexcollations[i] ||
+ p_info->opclass[i] != info->opcintype[i]||
+ ((p_info->indoption[i] & INDOPTION_DESC) != 0)
+ != info->reverse_sort[i] ||
+ ((p_info->indoption[i] & INDOPTION_NULLS_FIRST) != 0)
+ != info->nulls_first[i])
break;
-
- if (((p_info->indoption[i] & INDOPTION_NULLS_FIRST) != 0) !=
- info->nulls_first[i])
- break;
-
}
+ /* deny this if any difference found */
if (i != info->ncolumns)
continue;
+ /* check on key expressions */
if ((p_info->expression_str && (info->indexprs != NIL)) ||
(p_info->indpred_str && (info->indpred != NIL)))
{
- /*
- * Fetch the pg_index tuple by the Oid of the index
- */
+ /* fetch the index of this child */
ht_idx = SearchSysCache1(INDEXRELID,
ObjectIdGetDatum(info->indexoid));
- /* check to match the expression's parameter of index */
+ /* check expressions if both expressions are available */
if (p_info->expression_str &&
!heap_attisnull(ht_idx, Anum_pg_index_indexprs))
{
ObjectIdGetDatum(
relationObjectId));
+ /* deny if expressions don't match */
if (strcmp(p_info->expression_str,
text_to_cstring(DatumGetTextP(result))) != 0)
{
/* Clean up */
ReleaseSysCache(ht_idx);
-
continue;
}
}
- /* Check to match the predicate's parameter of index */
+ /* compare index predicates */
if (p_info->indpred_str &&
!heap_attisnull(ht_idx, Anum_pg_index_indpred))
{
bool isnull;
Datum result;
- /*
- * to change the predicate's parameter of child's
- * index to strings
- */
predDatum = SysCacheGetAttr(INDEXRELID, ht_idx,
Anum_pg_index_indpred,
&isnull);
{
/* Clean up */
ReleaseSysCache(ht_idx);
-
continue;
}
}
if (debug_level == 1)
{
- char *relname;
StringInfoData rel_buf;
+ char *disprelname = "";
- if (OidIsValid(relationObjectId))
- relname = get_rel_name(relationObjectId);
+ /*
+ * If this hint targetted the parent, use the real name of this
+ * child. Otherwise use hint specification.
+ */
+ if (using_parent_hint)
+ disprelname = get_rel_name(rte->relid);
else
- relname = hint->relname;
+ disprelname = hint->relname;
+
initStringInfo(&rel_buf);
- quote_value(&rel_buf, relname);
+ quote_value(&rel_buf, disprelname);
ereport(LOG,
(errmsg("available indexes for %s(%s):%s",
return p_info;
}
+/*
+ * cancel hint enforcement
+ */
static void
-pg_hint_plan_get_relation_info(PlannerInfo *root, Oid relationObjectId,
- bool inhparent, RelOptInfo *rel)
+reset_hint_enforcement()
{
- ScanMethodHint *hint = NULL;
- ListCell *l;
- Index new_parent_relid = 0;
-
- if (prev_get_relation_info)
- (*prev_get_relation_info) (root, relationObjectId, inhparent, rel);
+ setup_scan_method_enforcement(NULL, current_hint_state);
+ setup_parallel_plan_enforcement(NULL, current_hint_state);
+}
- /*
- * Do nothing if we don't have a valid hint in this context or current
- * nesting depth is at SPI calls.
- */
- if (!current_hint || nested_level > 0)
- {
- if (debug_level > 1)
- ereport(pg_hint_plan_message_level,
- (errhidestmt(true),
- errmsg ("pg_hint_plan%s: get_relation_info"
- " no hint to apply: relation=%u(%s), inhparent=%d,"
- " current_hint=%p, nested_level=%d",
- qnostr, relationObjectId,
- get_rel_name(relationObjectId),
- inhparent, current_hint, nested_level)));
- return;
- }
+/*
+ * Set planner guc parameters according to corresponding scan hints.
+ */
+static bool
+setup_hint_enforcement(PlannerInfo *root, RelOptInfo *rel)
+{
+ Index new_parent_relid = 0;
+ ListCell *l;
+ ScanMethodHint *shint = NULL;
+ ParallelHint *phint = NULL;
+ bool inhparent = root->simple_rte_array[rel->relid]->inh;
+ Oid relationObjectId = root->simple_rte_array[rel->relid]->relid;
/*
* We could register the parent relation of the following children here
- * when inhparent == true but inheritnce planner doesn't request
- * information for inheritance parents. We also cannot distinguish the
- * caller so we should always find the parents without this function being
- * called for them.
+ * when inhparent == true but inheritnce planner doesn't call this function
+ * for parents. Since we cannot distinguish who called this function we
+ * cannot do other than always seeking the parent regardless of who called
+ * this function.
*/
if (inhparent)
{
if (debug_level > 1)
ereport(pg_hint_plan_message_level,
(errhidestmt(true),
- errmsg ("pg_hint_plan%s: get_relation_info"
+ errmsg ("pg_hint_plan%s: setup_hint_enforcement"
" skipping inh parent: relation=%u(%s), inhparent=%d,"
- " current_hint=%p, nested_level=%d",
+ " current_hint_state=%p, hint_inhibit_level=%d",
qnostr, relationObjectId,
get_rel_name(relationObjectId),
- inhparent, current_hint, nested_level)));
- return;
+ inhparent, current_hint_state, hint_inhibit_level)));
+ return false;
}
- /* Find the parent for this relation */
+ /* Find the parent for this relation other than the registered parent */
foreach (l, root->append_rel_list)
{
AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l);
if (appinfo->child_relid == rel->relid)
{
- if (current_hint->parent_relid != appinfo->parent_relid)
+ if (current_hint_state->parent_relid != appinfo->parent_relid)
new_parent_relid = appinfo->parent_relid;
break;
}
if (!l)
{
- /* This relation doesn't have a parent. Cancel current_hint. */
- current_hint->parent_relid = 0;
- current_hint->parent_hint = NULL;
+ /* This relation doesn't have a parent. Cancel current_hint_state. */
+ current_hint_state->parent_relid = 0;
+ current_hint_state->parent_scan_hint = NULL;
+ current_hint_state->parent_parallel_hint = NULL;
}
if (new_parent_relid > 0)
{
/*
- * Here we found a parent relation different from the remembered one.
- * Remember it, apply the scan mask of it and then resolve the index
- * restriction in order to be used by its children.
+ * Here we found a new parent for the current relation. Scan continues
+ * hint to other childrens of this parent so remember it * to avoid
+ * hinthintredundant setup cost.
*/
- int scanmask = current_hint->init_scan_mask;
- ScanMethodHint *parent_hint;
-
- current_hint->parent_relid = new_parent_relid;
+ current_hint_state->parent_relid = new_parent_relid;
+ /* Find hints for the parent */
+ current_hint_state->parent_scan_hint =
+ find_scan_hint(root, current_hint_state->parent_relid, NULL);
+
+ current_hint_state->parent_parallel_hint =
+ find_parallel_hint(root, current_hint_state->parent_relid, NULL);
+
/*
- * Get and apply the hint for the new parent relation. It should be an
- * ordinary relation so calling find_scan_hint with rel == NULL is
- * safe.
+ * If hint is found for the parent, apply it for this child instead
+ * of its own.
*/
- current_hint->parent_hint = parent_hint =
- find_scan_hint(root, current_hint->parent_relid, NULL);
-
- if (parent_hint)
+ if (current_hint_state->parent_scan_hint)
{
- scanmask = current_hint->parent_hint->enforce_mask;
- parent_hint->base.state = HINT_STATE_USED;
+ ScanMethodHint * pshint = current_hint_state->parent_scan_hint;
- /* Resolve index name mask (if any) using the parent. */
- if (parent_hint->indexnames)
+ pshint->base.state = HINT_STATE_USED;
+
+ /* Apply index mask in the same manner to the parent. */
+ if (pshint->indexnames)
{
Oid parentrel_oid;
Relation parent_rel;
parentrel_oid =
- root->simple_rte_array[current_hint->parent_relid]->relid;
+ root->simple_rte_array[current_hint_state->parent_relid]->relid;
parent_rel = heap_open(parentrel_oid, NoLock);
/* Search the parent relation for indexes match the hint spec */
ListCell *lc;
ParentIndexInfo *parent_index_info;
- foreach(lc, parent_hint->indexnames)
+ foreach(lc, pshint->indexnames)
{
if (RelnameCmp(&indexname, &lfirst(lc)) == 0)
break;
parent_index_info =
get_parent_index_info(indexoid, parentrel_oid);
- current_hint->parent_index_infos =
- lappend(current_hint->parent_index_infos, parent_index_info);
+ current_hint_state->parent_index_infos =
+ lappend(current_hint_state->parent_index_infos,
+ parent_index_info);
}
heap_close(parent_rel, NoLock);
}
}
-
- set_scan_config_options(scanmask, current_hint->context);
}
- if (current_hint->parent_hint != 0)
- {
- delete_indexes(current_hint->parent_hint, rel,
- relationObjectId);
-
- /* Scan fixation status is the same to the parent. */
- if (debug_level > 1)
- ereport(pg_hint_plan_message_level,
- (errhidestmt(true),
- errmsg("pg_hint_plan%s: get_relation_info:"
- " index deletion by parent hint: "
- "relation=%u(%s), inhparent=%d, current_hint=%p,"
- " nested_level=%d",
- qnostr, relationObjectId,
- get_rel_name(relationObjectId),
- inhparent, current_hint, nested_level)));
- return;
- }
+ if (current_hint_state->parent_scan_hint)
+ shint = current_hint_state->parent_scan_hint;
+ else
+ shint = find_scan_hint(root, rel->relid, rel);
- /* This table doesn't have a parent hint. Apply its own hint if any. */
- if ((hint = find_scan_hint(root, rel->relid, rel)) != NULL)
+ if (shint)
{
- set_scan_config_options(hint->enforce_mask, current_hint->context);
- hint->base.state = HINT_STATE_USED;
+ bool using_parent_hint =
+ (shint == current_hint_state->parent_scan_hint);
+
+ /* Setup scan enforcement environment */
+ setup_scan_method_enforcement(shint, current_hint_state);
- delete_indexes(hint, rel, InvalidOid);
+ /* restrict unwanted inexes */
+ restrict_indexes(root, shint, rel, using_parent_hint);
if (debug_level > 1)
+ {
+ char *additional_message = "";
+
+ if (shint == current_hint_state->parent_scan_hint)
+ additional_message = " by parent hint";
+
ereport(pg_hint_plan_message_level,
(errhidestmt(true),
- errmsg ("pg_hint_plan%s: get_relation_info"
- " index deletion:"
- " relation=%u(%s), inhparent=%d, current_hint=%p,"
- " nested_level=%d, scanmask=0x%x",
- qnostr, relationObjectId,
+ errmsg ("pg_hint_plan%s: setup_hint_enforcement"
+ " index deletion%s:"
+ " relation=%u(%s), inhparent=%d, "
+ "current_hint_state=%p,"
+ " hint_inhibit_level=%d, scanmask=0x%x",
+ qnostr, additional_message,
+ relationObjectId,
get_rel_name(relationObjectId),
- inhparent, current_hint, nested_level,
- hint->enforce_mask)));
+ inhparent, current_hint_state,
+ hint_inhibit_level,
+ shint->enforce_mask)));
+ }
}
+
+ /* Do the same for parallel plan enforcement */
+ if (current_hint_state->parent_parallel_hint)
+ phint = current_hint_state->parent_parallel_hint;
else
+ phint = find_parallel_hint(root, rel->relid, rel);
+
+ setup_parallel_plan_enforcement(phint, current_hint_state);
+
+ /* Nothing to apply. Reset the scan mask to intial state */
+ if (!shint && ! phint)
{
if (debug_level > 1)
ereport(pg_hint_plan_message_level,
errmsg ("pg_hint_plan%s: get_relation_info"
" no hint applied:"
" relation=%u(%s), inhparent=%d, current_hint=%p,"
- " nested_level=%d, scanmask=0x%x",
+ " hint_inhibit_level=%d, scanmask=0x%x",
qnostr, relationObjectId,
get_rel_name(relationObjectId),
- inhparent, current_hint, nested_level,
- current_hint->init_scan_mask)));
- set_scan_config_options(current_hint->init_scan_mask,
- current_hint->context);
+ inhparent, current_hint_state, hint_inhibit_level,
+ current_hint_state->init_scan_mask)));
+
+ setup_scan_method_enforcement(NULL, current_hint_state);
+
+ return false;
}
- return;
+
+ return true;
}
/*
List *join_hint;
ListCell *l;
- join_hint = current_hint->join_hint_level[bms_num_members(joinrelids)];
+ join_hint = current_hint_state->join_hint_level[bms_num_members(joinrelids)];
foreach(l, join_hint)
{
if (hint_state_enabled(lhint))
{
- set_join_config_options(DISABLE_ALL_JOIN, current_hint->context);
+ set_join_config_options(DISABLE_ALL_JOIN, current_hint_state->context);
return true;
}
return false;
}
/*
- * set_plain_rel_pathlist
- * Build access paths for a plain relation (no subquery, no inheritance)
- *
- * This function was copied and edited from set_plain_rel_pathlist() in
- * src/backend/optimizer/path/allpaths.c
- */
-static void
-set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
-{
- /* Consider sequential scan */
- add_path(rel, create_seqscan_path(root, rel, NULL));
-
- /* Consider index scans */
- create_index_paths(root, rel);
-
- /* Consider TID scans */
- create_tidscan_paths(root, rel);
-
- /* Now find the cheapest of the paths for this rel */
- set_cheapest(rel);
-}
-
-static void
-rebuild_scan_path(HintState *hstate, PlannerInfo *root, int level,
- List *initial_rels)
-{
- ListCell *l;
-
- foreach(l, initial_rels)
- {
- RelOptInfo *rel = (RelOptInfo *) lfirst(l);
- 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;
-
- /*
- * 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->relid, rel)) == NULL)
- set_scan_config_options(hstate->init_scan_mask,
- hstate->context);
- else
- {
- set_scan_config_options(hint->enforce_mask, hstate->context);
- hint->base.state = HINT_STATE_USED;
- }
-
- list_free_deep(rel->pathlist);
- rel->pathlist = NIL;
- if (rte->inh)
- {
- /* It's an "append relation", process accordingly */
- set_append_rel_pathlist(root, rel, rel->relid, rte);
- }
- else
- {
- set_plain_rel_pathlist(root, rel, rte);
- }
- }
-
- /*
- * Restore the GUC variables we set above.
- */
- set_scan_config_options(hstate->init_scan_mask, hstate->context);
-}
-
-/*
* wrapper of make_join_rel()
*
* call make_join_rel() after changing enable_* parameters according to given
{
save_nestlevel = NewGUCNestLevel();
- set_join_config_options(hint->enforce_mask, current_hint->context);
+ set_join_config_options(hint->enforce_mask,
+ current_hint_state->context);
rel = pg_hint_plan_make_join_rel(root, rel1, rel2);
hint->base.state = HINT_STATE_USED;
int save_nestlevel;
if ((scan_hint = find_scan_hint(root, innerrel->relid, innerrel)) != NULL)
- {
- set_scan_config_options(scan_hint->enforce_mask, current_hint->context);
- scan_hint->base.state = HINT_STATE_USED;
- }
+ setup_scan_method_enforcement(scan_hint, current_hint_state);
joinrelids = bms_union(outerrel->relids, innerrel->relids);
join_hint = find_join_hint(joinrelids);
{
set_join_config_options(join_hint->enforce_mask,
- current_hint->context);
+ current_hint_state->context);
add_paths_to_joinrel(root, joinrel, outerrel, innerrel, jointype,
sjinfo, restrictlist);
}
else
{
- set_join_config_options(DISABLE_ALL_JOIN, current_hint->context);
+ set_join_config_options(DISABLE_ALL_JOIN,
+ current_hint_state->context);
add_paths_to_joinrel(root, joinrel, outerrel, innerrel, jointype,
sjinfo, restrictlist);
}
add_paths_to_joinrel(root, joinrel, outerrel, innerrel, jointype,
sjinfo, restrictlist);
+ /* Reset the environment if changed */
if (scan_hint != NULL)
- set_scan_config_options(current_hint->init_scan_mask,
- current_hint->context);
+ setup_scan_method_enforcement(NULL, current_hint_state);
}
static int
* valid hint is supplied or current nesting depth is nesting depth of SPI
* calls.
*/
- if (!current_hint || nested_level > 0)
+ if (!current_hint_state || hint_inhibit_level > 0)
{
if (prev_join_search)
return (*prev_join_search) (root, levels_needed, initial_rels);
return standard_join_search(root, levels_needed, initial_rels);
}
- /* We apply scan method hint rebuild scan path. */
- rebuild_scan_path(current_hint, root, levels_needed, initial_rels);
-
/*
* In the case using GEQO, only scan method hints and Set hints have
* effect. Join method and join order is not controllable by hints.
return geqo(root, levels_needed, initial_rels);
nbaserel = get_num_baserels(initial_rels);
- current_hint->join_hint_level = palloc0(sizeof(List *) * (nbaserel + 1));
+ current_hint_state->join_hint_level =
+ palloc0(sizeof(List *) * (nbaserel + 1));
join_method_hints = palloc0(sizeof(JoinMethodHint *) * (nbaserel + 1));
- leading_hint_enable = transform_join_hints(current_hint, root, nbaserel,
+ leading_hint_enable = transform_join_hints(current_hint_state,
+ root, nbaserel,
initial_rels, join_method_hints);
rel = pg_hint_plan_standard_join_search(root, levels_needed, initial_rels);
for (i = 2; i <= nbaserel; i++)
{
- list_free(current_hint->join_hint_level[i]);
+ list_free(current_hint_state->join_hint_level[i]);
/* free Leading hint only */
if (join_method_hints[i] != NULL &&
join_method_hints[i]->enforce_mask == ENABLE_ALL_JOIN)
JoinMethodHintDelete(join_method_hints[i]);
}
- pfree(current_hint->join_hint_level);
+ pfree(current_hint_state->join_hint_level);
pfree(join_method_hints);
if (leading_hint_enable)
- set_join_config_options(current_hint->init_join_mask,
- current_hint->context);
+ set_join_config_options(current_hint_state->init_join_mask,
+ current_hint_state->context);
return rel;
}
/*
+ * Force number of wokers if instructed by hint
+ */
+void
+pg_hint_plan_set_rel_pathlist(PlannerInfo * root, RelOptInfo *rel,
+ Index rti, RangeTblEntry *rte)
+{
+ ParallelHint *phint;
+ ListCell *l;
+
+ /* call the previous hook */
+ if (prev_set_rel_pathlist)
+ prev_set_rel_pathlist(root, rel, rti, rte);
+
+ /* Nothing to do when hint has not been parsed yet */
+ if (current_hint_state == NULL)
+ return;
+
+ /* Don't touch dummy rel */
+ if (IS_DUMMY_REL(rel))
+ return;
+
+ /* We cannot handle if this requires an outer */
+ if (rel->lateral_relids)
+ return;
+
+ if (!setup_hint_enforcement(root, rel))
+ {
+ /*
+ * No enforcement requested, but we might have to generate gather
+ * path on this relation
+ */
+
+ /* If no need of a gather path, just return */
+ if (rel->reloptkind != RELOPT_BASEREL || max_hint_nworkers < 1 ||
+ rel->partial_pathlist == NIL)
+ return;
+
+ /* Lower the priorities of existing paths, then add a new path */
+ foreach (l, rel->pathlist)
+ {
+ Path *path = (Path *) lfirst(l);
+
+ if (path->startup_cost < disable_cost)
+ {
+ path->startup_cost += disable_cost;
+ path->total_cost += disable_cost;
+ }
+ }
+
+ generate_gather_paths(root, rel);
+ return;
+ }
+
+ /* Here, we regenerate paths with the current hint restriction */
+
+ /* Remove prviously paths except dummy rels */
+ list_free_deep(rel->pathlist);
+ rel->pathlist = NIL;
+
+ /* Rebuild access paths */
+ set_plain_rel_pathlist(root, rel, rte);
+
+ /*
+ * create_plain_partial_paths creates partial paths with reasonably
+ * estimated number of workers. Force the requested number of workers if
+ * hard mode.
+ */
+ phint = find_parallel_hint(root, rel->relid, rel);
+
+ if (phint)
+ {
+ /* if inhibiting parallel, remove existing partial paths */
+ if (phint->nworkers == 0 && rel->partial_pathlist)
+ {
+ list_free_deep(rel->partial_pathlist);
+ rel->partial_pathlist = NIL;
+ }
+
+ /* enforce number of workers if requested */
+ if (rel->partial_pathlist && phint->force_parallel)
+ {
+ foreach (l, rel->partial_pathlist)
+ {
+ Path *ppath = (Path *) lfirst(l);
+
+ ppath->parallel_workers = phint->nworkers;
+ }
+ }
+
+ /* Generate gather paths for base rels */
+ if (rel->reloptkind == RELOPT_BASEREL)
+ generate_gather_paths(root, rel);
+ }
+
+ reset_hint_enforcement();
+}
+
+/*
* set_rel_pathlist
* Build access paths for a base relation
*
* This function was copied and edited from set_rel_pathlist() in
- * src/backend/optimizer/path/allpaths.c
+ * src/backend/optimizer/path/allpaths.c in order not to copy other static
+ * functions not required here.
*/
static void
set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
{
if (rte->relkind == RELKIND_RELATION)
{
+ if(rte->tablesample != NULL)
+ elog(ERROR, "sampled relation is not supported");
+
/* Plain relation */
set_plain_rel_pathlist(root, rel, rte);
}
else
elog(ERROR, "unexpected rtekind: %d", (int) rel->rtekind);
}
+
+ /*
+ * Allow a plugin to editorialize on the set of Paths for this base
+ * relation. It could add new paths (such as CustomPaths) by calling
+ * add_path(), or delete or modify paths added by the core code.
+ */
+ if (set_rel_pathlist_hook)
+ (*set_rel_pathlist_hook) (root, rel, rti, rte);
+
+ /* Now find the cheapest of the paths for this rel */
+ set_cheapest(rel);
}
/*
static void
pg_hint_plan_plpgsql_stmt_beg(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt)
{
- PLpgSQL_expr *expr = NULL;
-
- switch ((enum PLpgSQL_stmt_types) stmt->cmd_type)
- {
- case PLPGSQL_STMT_FORS:
- expr = ((PLpgSQL_stmt_fors *) stmt)->query;
- break;
- case PLPGSQL_STMT_FORC:
- expr = ((PLpgSQL_var *) (estate->datums[((PLpgSQL_stmt_forc *)stmt)->curvar]))->cursor_explicit_expr;
- break;
- case PLPGSQL_STMT_RETURN_QUERY:
- if (((PLpgSQL_stmt_return_query *) stmt)->query != NULL)
- expr = ((PLpgSQL_stmt_return_query *) stmt)->query;
- else
- expr = ((PLpgSQL_stmt_return_query *) stmt)->dynquery;
- break;
- case PLPGSQL_STMT_EXECSQL:
- expr = ((PLpgSQL_stmt_execsql *) stmt)->sqlstmt;
- break;
- case PLPGSQL_STMT_DYNEXECUTE:
- expr = ((PLpgSQL_stmt_dynexecute *) stmt)->query;
- break;
- case PLPGSQL_STMT_DYNFORS:
- expr = ((PLpgSQL_stmt_dynfors *) stmt)->query;
- break;
- case PLPGSQL_STMT_OPEN:
- if (((PLpgSQL_stmt_open *) stmt)->query != NULL)
- expr = ((PLpgSQL_stmt_open *) stmt)->query;
- else if (((PLpgSQL_stmt_open *) stmt)->dynquery != NULL)
- expr = ((PLpgSQL_stmt_open *) stmt)->dynquery;
- else
- expr = ((PLpgSQL_var *) (estate->datums[((PLpgSQL_stmt_open *)stmt)->curvar]))->cursor_explicit_expr;
- break;
- default:
- break;
- }
-
- if (expr)
- {
- plpgsql_query_string = expr->query;
- plpgsql_query_string_src = (enum PLpgSQL_stmt_types) stmt->cmd_type;
- }
+ plpgsql_recurse_level++;
}
/*
static void
pg_hint_plan_plpgsql_stmt_end(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt)
{
- if (plpgsql_query_string &&
- plpgsql_query_string_src == stmt->cmd_type)
- plpgsql_query_string = NULL;
+ plpgsql_recurse_level--;
+}
+
+void plpgsql_query_erase_callback(ResourceReleasePhase phase,
+ bool isCommit,
+ bool isTopLevel,
+ void *arg)
+{
+ if (phase != RESOURCE_RELEASE_AFTER_LOCKS)
+ return;
+ /* Cancel plpgsql nest level*/
+ plpgsql_recurse_level = 0;
}
#define standard_join_search pg_hint_plan_standard_join_search