--- /dev/null
+LOAD 'pg_hint_plan';
+ALTER SYSTEM SET session_preload_libraries TO 'pg_hint_plan';
+SET pg_hint_plan.enable_hint TO on;
+SET pg_hint_plan.debug_print TO on;
+SET client_min_messages TO LOG;
+SET search_path TO public;
+SET max_parallel_workers_per_gather TO 0;
+SET enable_indexscan to false;
+SET enable_bitmapscan to false;
+SET parallel_setup_cost to 0;
+SET parallel_tuple_cost to 0;
+SET min_parallel_relation_size to 0;
+SET max_parallel_workers_per_gather to 0;
+CREATE TABLE s1.tl (a int);
+INSERT INTO s1.tl (SELECT a FROM generate_series(0, 100000) a);
+EXPLAIN (COSTS false) SELECT * FROM s1.t1;
+ QUERY PLAN
+----------------
+ Seq Scan on t1
+(1 row)
+
+/*+Parallel(t1 10)*/
+EXPLAIN (COSTS false) SELECT * FROM s1.t1;
+LOG: pg_hint_plan:
+used hint:
+not used hint:
+Parallel(t1 10 soft)
+duplication hint:
+error hint:
+
+ QUERY PLAN
+-------------------------------
+ Gather
+ Workers Planned: 2
+ -> Parallel Seq Scan on t1
+(3 rows)
+
+/*+Parallel(t1 10 soft)*/
+EXPLAIN (COSTS false) SELECT * FROM s1.t1;
+LOG: pg_hint_plan:
+used hint:
+not used hint:
+Parallel(t1 10 soft)
+duplication hint:
+error hint:
+
+ QUERY PLAN
+-------------------------------
+ Gather
+ Workers Planned: 2
+ -> Parallel Seq Scan on t1
+(3 rows)
+
+/*+Parallel(t1 10 hard)*/
+EXPLAIN (COSTS false) SELECT * FROM s1.t1;
+LOG: pg_hint_plan:
+used hint:
+not used hint:
+Parallel(t1 10 hard)
+duplication hint:
+error hint:
+
+ QUERY PLAN
+-------------------------------
+ Gather
+ Workers Planned: 10
+ -> Parallel Seq Scan on t1
+(3 rows)
+
+-- Inheritnce tables
+/*+Parallel(p1 10 soft)*/
+EXPLAIN (COSTS false) SELECT * FROM p1;
+LOG: pg_hint_plan:
+used hint:
+not used hint:
+Parallel(p1 10 soft)
+duplication hint:
+error hint:
+
+ QUERY PLAN
+-------------------------------------------
+ Gather
+ Workers Planned: 1
+ -> Append
+ -> Parallel Seq Scan on p1
+ -> Parallel Seq Scan on p1_c1
+ -> Parallel Seq Scan on p1_c2
+ -> Parallel Seq Scan on p1_c3
+ -> Parallel Seq Scan on p1_c4
+ -> Parallel Seq Scan on p1_c1_c1
+ -> Parallel Seq Scan on p1_c1_c2
+ -> Parallel Seq Scan on p1_c3_c1
+ -> Parallel Seq Scan on p1_c3_c2
+(12 rows)
+
+/*+Parallel(p1 10 hard)*/
+EXPLAIN (COSTS false) SELECT * FROM p1;
+LOG: pg_hint_plan:
+used hint:
+not used hint:
+Parallel(p1 10 hard)
+duplication hint:
+error hint:
+
+ QUERY PLAN
+-------------------------------------------
+ Gather
+ Workers Planned: 10
+ -> Append
+ -> Parallel Seq Scan on p1
+ -> Parallel Seq Scan on p1_c1
+ -> Parallel Seq Scan on p1_c2
+ -> Parallel Seq Scan on p1_c3
+ -> Parallel Seq Scan on p1_c4
+ -> Parallel Seq Scan on p1_c1_c1
+ -> Parallel Seq Scan on p1_c1_c2
+ -> Parallel Seq Scan on p1_c3_c1
+ -> Parallel Seq Scan on p1_c3_c2
+(12 rows)
+
+-- Joins
+EXPLAIN (COSTS false) SELECT * FROM p1_c1 join p2_c1 on p1_c1.id = p2_c1.id;
+ QUERY PLAN
+----------------------------------------
+ Hash Join
+ Hash Cond: (p1_c1.id = p2_c1.id)
+ -> Append
+ -> Seq Scan on p1_c1
+ -> Seq Scan on p1_c1_c1
+ -> Seq Scan on p1_c1_c2
+ -> Hash
+ -> Append
+ -> Seq Scan on p2_c1
+ -> Seq Scan on p2_c1_c1
+ -> Seq Scan on p2_c1_c2
+(11 rows)
+
+/*+Parallel(p1_c1 10 hard)*/
+EXPLAIN (COSTS false) SELECT * FROM p1_c1 join p2_c1 on p1_c1.id = p2_c1.id;
+LOG: pg_hint_plan:
+used hint:
+not used hint:
+Parallel(p1_c1 10 hard)
+duplication hint:
+error hint:
+
+ QUERY PLAN
+-------------------------------------------------
+ Gather
+ Workers Planned: 10
+ -> Hash Join
+ Hash Cond: (p1_c1.id = p2_c1.id)
+ -> Append
+ -> Parallel Seq Scan on p1_c1
+ -> Parallel Seq Scan on p1_c1_c1
+ -> Parallel Seq Scan on p1_c1_c2
+ -> Hash
+ -> Append
+ -> Seq Scan on p2_c1
+ -> Seq Scan on p2_c1_c1
+ -> Seq Scan on p2_c1_c2
+(13 rows)
+
+/*+Parallel(p2_c1 10 hard)*/
+EXPLAIN (COSTS false) SELECT * FROM p1_c1 join p2_c1 on p1_c1.id = p2_c1.id;
+LOG: pg_hint_plan:
+used hint:
+not used hint:
+Parallel(p2_c1 10 hard)
+duplication hint:
+error hint:
+
+ QUERY PLAN
+-------------------------------------------------
+ Gather
+ Workers Planned: 10
+ -> Hash Join
+ Hash Cond: (p2_c1.id = p1_c1.id)
+ -> Append
+ -> Parallel Seq Scan on p2_c1
+ -> Parallel Seq Scan on p2_c1_c1
+ -> Parallel Seq Scan on p2_c1_c2
+ -> Hash
+ -> Append
+ -> Seq Scan on p1_c1
+ -> Seq Scan on p1_c1_c1
+ -> Seq Scan on p1_c1_c2
+(13 rows)
+
+/*+Parallel(p1_c1 10 hard) Parallel(p2_c1 10 hard)*/
+EXPLAIN (COSTS false) SELECT * FROM p1_c1 join p2_c1 on p1_c1.id = p2_c1.id;
+LOG: pg_hint_plan:
+used hint:
+not used hint:
+Parallel(p1_c1 10 hard)
+Parallel(p2_c1 10 hard)
+duplication hint:
+error hint:
+
+ QUERY PLAN
+-------------------------------------------------------
+ Hash Join
+ Hash Cond: (p1_c1.id = p2_c1.id)
+ -> Gather
+ Workers Planned: 10
+ -> Append
+ -> Parallel Seq Scan on p1_c1
+ -> Parallel Seq Scan on p1_c1_c1
+ -> Parallel Seq Scan on p1_c1_c2
+ -> Hash
+ -> Gather
+ Workers Planned: 10
+ -> Append
+ -> Parallel Seq Scan on p2_c1
+ -> Parallel Seq Scan on p2_c1_c1
+ -> Parallel Seq Scan on p2_c1_c2
+(15 rows)
+
+-- Joins on inheritance tables
+/*+Parallel(p1 10)*/
+EXPLAIN (COSTS false) SELECT * FROM p1 join p2 on p1.id = p2.id;
+LOG: pg_hint_plan:
+used hint:
+not used hint:
+Parallel(p1 10 soft)
+duplication hint:
+error hint:
+
+ QUERY PLAN
+-------------------------------------------------
+ Gather
+ Workers Planned: 1
+ -> Hash Join
+ Hash Cond: (p1.id = p2.id)
+ -> Append
+ -> Parallel Seq Scan on p1
+ -> Parallel Seq Scan on p1_c1
+ -> Parallel Seq Scan on p1_c2
+ -> Parallel Seq Scan on p1_c3
+ -> Parallel Seq Scan on p1_c4
+ -> Parallel Seq Scan on p1_c1_c1
+ -> Parallel Seq Scan on p1_c1_c2
+ -> Parallel Seq Scan on p1_c3_c1
+ -> Parallel Seq Scan on p1_c3_c2
+ -> Hash
+ -> Append
+ -> Seq Scan on p2
+ -> Seq Scan on p2_c1
+ -> Seq Scan on p2_c2
+ -> Seq Scan on p2_c3
+ -> Seq Scan on p2_c4
+ -> Seq Scan on p2_c1_c1
+ -> Seq Scan on p2_c1_c2
+ -> Seq Scan on p2_c3_c1
+ -> Seq Scan on p2_c3_c2
+(25 rows)
+
+/*+Parallel(p2 10 hard)*/
+EXPLAIN (COSTS false) SELECT * FROM p1 join p2 on p1.id = p2.id;
+LOG: pg_hint_plan:
+used hint:
+not used hint:
+Parallel(p2 10 hard)
+duplication hint:
+error hint:
+
+ QUERY PLAN
+-------------------------------------------------
+ Gather
+ Workers Planned: 10
+ -> Hash Join
+ Hash Cond: (p2.id = p1.id)
+ -> Append
+ -> Parallel Seq Scan on p2
+ -> Parallel Seq Scan on p2_c1
+ -> Parallel Seq Scan on p2_c2
+ -> Parallel Seq Scan on p2_c3
+ -> Parallel Seq Scan on p2_c4
+ -> Parallel Seq Scan on p2_c1_c1
+ -> Parallel Seq Scan on p2_c1_c2
+ -> Parallel Seq Scan on p2_c3_c1
+ -> Parallel Seq Scan on p2_c3_c2
+ -> Hash
+ -> Append
+ -> Seq Scan on p1
+ -> Seq Scan on p1_c1
+ -> Seq Scan on p1_c2
+ -> Seq Scan on p1_c3
+ -> Seq Scan on p1_c4
+ -> Seq Scan on p1_c1_c1
+ -> Seq Scan on p1_c1_c2
+ -> Seq Scan on p1_c3_c1
+ -> Seq Scan on p1_c3_c2
+(25 rows)
+
+/*+Parallel(p2 10 hard) Parallel(p1 5 hard) */
+EXPLAIN (COSTS false) SELECT * FROM p1 join p2 on p1.id = p2.id;
+LOG: pg_hint_plan:
+used hint:
+not used hint:
+Parallel(p1 5 hard)
+Parallel(p2 10 hard)
+duplication hint:
+error hint:
+
+ QUERY PLAN
+-------------------------------------------------------
+ Hash Join
+ Hash Cond: (p1.id = p2.id)
+ -> Gather
+ Workers Planned: 5
+ -> Append
+ -> Parallel Seq Scan on p1
+ -> Parallel Seq Scan on p1_c1
+ -> Parallel Seq Scan on p1_c2
+ -> Parallel Seq Scan on p1_c3
+ -> Parallel Seq Scan on p1_c4
+ -> Parallel Seq Scan on p1_c1_c1
+ -> Parallel Seq Scan on p1_c1_c2
+ -> Parallel Seq Scan on p1_c3_c1
+ -> Parallel Seq Scan on p1_c3_c2
+ -> Hash
+ -> Gather
+ Workers Planned: 10
+ -> Append
+ -> Parallel Seq Scan on p2
+ -> Parallel Seq Scan on p2_c1
+ -> Parallel Seq Scan on p2_c2
+ -> Parallel Seq Scan on p2_c3
+ -> Parallel Seq Scan on p2_c4
+ -> Parallel Seq Scan on p2_c1_c1
+ -> Parallel Seq Scan on p2_c1_c2
+ -> Parallel Seq Scan on p2_c3_c1
+ -> Parallel Seq Scan on p2_c3_c2
+(27 rows)
+
+-- Negative hint
+SET max_parallel_workers_per_gather to 5;
+EXPLAIN (COSTS false) SELECT * FROM p1;
+ QUERY PLAN
+-------------------------------------------
+ Gather
+ Workers Planned: 1
+ -> Append
+ -> Parallel Seq Scan on p1
+ -> Parallel Seq Scan on p1_c1
+ -> Parallel Seq Scan on p1_c2
+ -> Parallel Seq Scan on p1_c3
+ -> Parallel Seq Scan on p1_c4
+ -> Parallel Seq Scan on p1_c1_c1
+ -> Parallel Seq Scan on p1_c1_c2
+ -> Parallel Seq Scan on p1_c3_c1
+ -> Parallel Seq Scan on p1_c3_c2
+(12 rows)
+
+/*+Parallel(p1 0 hard)*/
+EXPLAIN (COSTS false) SELECT * FROM p1;
+LOG: pg_hint_plan:
+used hint:
+not used hint:
+Parallel(p1 0 hard)
+duplication hint:
+error hint:
+
+ QUERY PLAN
+----------------------------
+ Append
+ -> Seq Scan on p1
+ -> Seq Scan on p1_c1
+ -> Seq Scan on p1_c2
+ -> Seq Scan on p1_c3
+ -> Seq Scan on p1_c4
+ -> Seq Scan on p1_c1_c1
+ -> Seq Scan on p1_c1_c2
+ -> Seq Scan on p1_c3_c1
+ -> Seq Scan on p1_c3_c2
+(10 rows)
+
+ALTER SYSTEM SET session_preload_libraries TO DEFAULT;
+SELECT pg_reload_conf();
+ pg_reload_conf
+----------------
+ t
+(1 row)
+
*
*-------------------------------------------------------------------------
*/
+#include <string.h>
+
#include "postgres.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_index.h"
#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_JOIN_METHOD,
HINT_TYPE_LEADING,
HINT_TYPE_SET,
- HINT_TYPE_ROWS
+ HINT_TYPE_ROWS,
+ HINT_TYPE_PARALLEL
} HintType;
static const char *HintTypeName[] = {
"join method",
"leading",
"set",
- "rows"
+ "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 of scan parameter */
+
+ /* 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_hint; /* inherit parent of table scan hint */
List *parent_index_infos; /* list of parent table's index */
- /* for join method hints */
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 */
};
/*
static const char *RowsHintParse(RowsHint *hint, HintState *hstate,
Query *parse, const char *str);
+/* 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);
static const char *parse_quoted_value(const char *str, char **word,
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,
bool isCommit,
bool isTopLevel,
void *arg);
+static int set_config_option_wrapper(const char *name, const char *value,
+ GucContext context, GucSource source,
+ GucAction action, bool changeVal, int elevel);
+static void set_scan_config_options(unsigned char enforce_mask,
+ GucContext context);
+static void set_config_int32_option(const char *name, int32 value,
+ GucContext context);
/* GUC variables */
static bool pg_hint_plan_enable_hint = true;
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},
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_state = NULL;
{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}
};
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,
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");
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_index_infos = NIL;
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.
}
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);
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
+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;
+}
+
+static int
set_config_option_wrapper(const char *name, const char *value,
GucContext context, GucSource source,
GucAction action, bool changeVal, int elevel)
return save_nestlevel;
}
+static void
+set_config_int32_option(const char *name, int32 value, GucContext context)
+{
+ char buf[16]; /* enough for int32 */
+
+ 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;
+ }
+
+ set_config_option_wrapper(name, buf, context,
+ PGC_S_SESSION, GUC_ACTION_SAVE, true,
+ pg_hint_plan_message_level);
+}
+
+
+/*
+ * Setup parallel execution environment. If init == true, it is set to the
+ * initial values in state, elsewise set to values in hint.
+ */
+static void
+setup_parallel_scan(ParallelHint *hint, bool init, HintState *state)
+{
+ /* !init requires hint */
+ Assert(init|| hint);
+
+ if (init)
+ set_config_int32_option("max_parallel_workers_per_gather",
+ state->init_nworkers, state->context);
+ else
+ set_config_int32_option("max_parallel_workers_per_gather",
+ hint->nworkers, state->context);
+
+ /* force means that enforce parallel as far as possible */
+ if (!init && 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), \
(mask & (type_bits)) ? "true" : "false", \
current_hint_state->context);
/* save current status to current_hint_state */
- if (enable_seqscan)
- current_hint_state->init_scan_mask |= ENABLE_SEQSCAN;
- if (enable_indexscan)
- current_hint_state->init_scan_mask |= ENABLE_INDEXSCAN;
- if (enable_bitmapscan)
- current_hint_state->init_scan_mask |= ENABLE_BITMAPSCAN;
- if (enable_tidscan)
- current_hint_state->init_scan_mask |= ENABLE_TIDSCAN;
- if (enable_indexonlyscan)
- current_hint_state->init_scan_mask |= ENABLE_INDEXONLYSCAN;
- if (enable_nestloop)
- current_hint_state->init_join_mask |= ENABLE_NESTLOOP;
- if (enable_mergejoin)
- current_hint_state->init_join_mask |= ENABLE_MERGEJOIN;
- if (enable_hashjoin)
- current_hint_state->init_join_mask |= ENABLE_HASHJOIN;
+ 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)
+ {
+ current_hint_state->init_nworkers = 0;
+ set_config_int32_option("max_parallel_workers_per_gather",
+ 1, current_hint_state->context);
+ }
if (debug_level > 1)
{
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 can't force scan method of foreign tables */
+ /* We don't force scan method of foreign tables */
if (rte->relkind == RELKIND_FOREIGN_TABLE)
return NULL;
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->reloptkind != RELOPT_BASEREL &&
+ rel->reloptkind != RELOPT_OTHER_MEMBER_REL) ||
+ rel->rtekind != RTE_RELATION))
+ return NULL;
+
+ rte = root->simple_rte_array[relid];
+
+ /* We can'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_PARALLEL]; i++)
+ {
+ ParallelHint *hint = current_hint_state->parallel_hints[i];
+
+ /* We ignore disabled hints. */
+ if (!hint_state_enabled(hint))
+ continue;
+
+ if (RelnameCmp(&rte->eref->aliasname, &hint->relname) == 0)
+ return hint;
+ }
+
+ return NULL;
+}
+
/*
* regexeq
*
}
static void
-pg_hint_plan_get_relation_info(PlannerInfo *root, Oid relationObjectId,
- bool inhparent, RelOptInfo *rel)
+process_scanmethod_hints(PlannerInfo *root, Oid relationObjectId,
+ bool inhparent, RelOptInfo *rel)
{
- ScanMethodHint *hint = NULL;
- ListCell *l;
Index new_parent_relid = 0;
-
- if (prev_get_relation_info)
- (*prev_get_relation_info) (root, relationObjectId, inhparent, rel);
-
- /*
- * Do nothing if we don't have a valid hint in this context or current
- * nesting depth is at SPI calls.
- */
- if (!current_hint_state || hint_inhibit_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, hint_inhibit_level=%d",
- qnostr, relationObjectId,
- get_rel_name(relationObjectId),
- inhparent, current_hint_state,
- hint_inhibit_level)));
- return;
- }
+ ListCell *l;
+ ScanMethodHint *scanhint = NULL;
/*
* We could register the parent relation of the following children here
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_state->init_scan_mask;
- ScanMethodHint *parent_hint;
+ bool use_parent_env = false;
+ ScanMethodHint *parent_hint = NULL;
current_hint_state->parent_relid = new_parent_relid;
- /*
- * 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.
- */
+ /* Check if the parent has a hint */
current_hint_state->parent_hint = parent_hint =
find_scan_hint(root, current_hint_state->parent_relid, NULL);
if (parent_hint)
{
- scanmask = current_hint_state->parent_hint->enforce_mask;
+ /* If hint is found for the parent, apply its hint instead. */
+ use_parent_env = true;
parent_hint->base.state = HINT_STATE_USED;
/* Resolve index name mask (if any) using the parent. */
heap_close(parent_rel, NoLock);
}
}
-
- set_scan_config_options(scanmask, current_hint_state->context);
+
+ if (use_parent_env)
+ set_scan_config_options(parent_hint->enforce_mask,
+ current_hint_state->context);
+ else
+ set_scan_config_options(current_hint_state->init_scan_mask,
+ current_hint_state->context);
}
+ /* Process index restriction hint inheritance */
if (current_hint_state->parent_hint != 0)
{
delete_indexes(current_hint_state->parent_hint, 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 ((scanhint = find_scan_hint(root, rel->relid, rel)) != NULL)
{
- set_scan_config_options(hint->enforce_mask,
+ set_scan_config_options(scanhint->enforce_mask,
current_hint_state->context);
- hint->base.state = HINT_STATE_USED;
+ scanhint->base.state = HINT_STATE_USED;
- delete_indexes(hint, rel, InvalidOid);
+ delete_indexes(scanhint, rel, InvalidOid);
if (debug_level > 1)
ereport(pg_hint_plan_message_level,
qnostr, relationObjectId,
get_rel_name(relationObjectId),
inhparent, current_hint_state, hint_inhibit_level,
- hint->enforce_mask)));
+ scanhint->enforce_mask)));
+ return;
}
- else
+
+ /* Nothing to apply. Reset the scan mask to intial state */
+ if (debug_level > 1)
+ ereport(pg_hint_plan_message_level,
+ (errhidestmt (true),
+ errmsg ("pg_hint_plan%s: get_relation_info"
+ " no hint applied:"
+ " relation=%u(%s), inhparent=%d, current_hint=%p,"
+ " hint_inhibit_level=%d, scanmask=0x%x",
+ qnostr, relationObjectId,
+ get_rel_name(relationObjectId),
+ inhparent, current_hint_state, hint_inhibit_level,
+ current_hint_state->init_scan_mask)));
+ set_scan_config_options(current_hint_state->init_scan_mask,
+ current_hint_state->context);
+}
+
+static void
+pg_hint_plan_get_relation_info(PlannerInfo *root, Oid relationObjectId,
+ bool inhparent, RelOptInfo *rel)
+{
+ ParallelHint *parallelhint = NULL;
+
+ if (prev_get_relation_info)
+ (*prev_get_relation_info) (root, relationObjectId, inhparent, rel);
+
+ /*
+ * Do nothing if we don't have a valid hint in this context or current
+ * nesting depth is at SPI calls.
+ */
+ if (!current_hint_state || hint_inhibit_level > 0)
{
if (debug_level > 1)
ereport(pg_hint_plan_message_level,
(errhidestmt (true),
errmsg ("pg_hint_plan%s: get_relation_info"
- " no hint applied:"
- " relation=%u(%s), inhparent=%d, current_hint=%p,"
- " hint_inhibit_level=%d, scanmask=0x%x",
+ " no hint to apply: relation=%u(%s), inhparent=%d,"
+ " current_hint_state=%p, hint_inhibit_level=%d",
qnostr, relationObjectId,
get_rel_name(relationObjectId),
- inhparent, current_hint_state, hint_inhibit_level,
- current_hint_state->init_scan_mask)));
- set_scan_config_options(current_hint_state->init_scan_mask,
- current_hint_state->context);
+ inhparent, current_hint_state, hint_inhibit_level)));
+ return;
+ }
+
+ process_scanmethod_hints(root, relationObjectId, inhparent, rel);
+
+ /*
+ * Parallel hint doesn't inherit, process separately from other types of
+ * hint
+ */
+ if ((parallelhint = find_parallel_hint(root, rel->relid, rel)) != NULL)
+ {
+ /* Set parallel environment according to the hint */
+ setup_parallel_scan(parallelhint, false, current_hint_state);
+ }
+ else
+ {
+ /* Reset parallel environment */
+ setup_parallel_scan(NULL, true, current_hint_state);
}
return;
}
/* Consider sequential scan */
add_path(rel, create_seqscan_path(root, rel, required_outer, 0));
+ /* If appropriate, consider parallel sequential scan */
+ if (rel->consider_parallel && required_outer == NULL)
+ {
+ ParallelHint *phint = find_parallel_hint(root, rel->relid, rel);
+
+ /* Consider parallel paths only if not inhibited by hint */
+ if (!phint || phint->nworkers > 0)
+ create_plain_partial_paths(root, rel);
+
+ /*
+ * Overwirte parallel_workers if requested. partial_pathlist seems to
+ * have up to one path but looping over all possible paths don't harm.
+ */
+ if (phint && phint->nworkers > 0 && phint->force_parallel)
+ {
+ ListCell *l;
+ foreach (l, rel->partial_pathlist)
+ {
+ Path *ppath = (Path *) lfirst(l);
+
+ Assert(ppath->parallel_workers > 0);
+ ppath->parallel_workers = phint->nworkers;
+ }
+ }
+ }
+
/* Consider index scans */
create_index_paths(root, rel);
{
RelOptInfo *rel = (RelOptInfo *) lfirst(l);
RangeTblEntry *rte;
- ScanMethodHint *hint;
+ ScanMethodHint *scanhint;
+ ParallelHint *parallelhint;
/* Skip relations which we can't choose scan method. */
if (rel->reloptkind != RELOPT_BASEREL || rel->rtekind != RTE_RELATION)
* 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)
+ if ((scanhint = 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;
+ set_scan_config_options(scanhint->enforce_mask, hstate->context);
+ scanhint->base.state = HINT_STATE_USED;
}
+ if ((parallelhint = find_parallel_hint(root, rel->relid, rel)) != NULL)
+ {
+ /* Set parallel environment according to the hint */
+ setup_parallel_scan(parallelhint, false, current_hint_state);
+ }
+ else
+ {
+ /* Reset parallel environment */
+ setup_parallel_scan(NULL, true, current_hint_state);
+ }
+
+ /* remove existing partial paths from this baserel */
+ list_free_deep(rel->partial_pathlist);
+ rel->partial_pathlist = NIL;
+
+ /* remove existing paths from this baserel */
list_free_deep(rel->pathlist);
rel->pathlist = NIL;
+
if (rte->inh)
{
+ ListCell *l;
+
+ /* remove partial paths from all chlidren */
+ foreach (l, root->append_rel_list)
+ {
+ AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l);
+ RelOptInfo *childrel;
+
+ if (appinfo->parent_relid != rel->relid)
+ continue;
+
+ childrel = root->simple_rel_array[appinfo->child_relid];
+ list_free_deep(childrel->partial_pathlist);
+ childrel->partial_pathlist = NIL;
+ }
/* It's an "append relation", process accordingly */
set_append_rel_pathlist(root, rel, rel->relid, rte);
}
set_plain_rel_pathlist(root, rel, rte);
}
+ /*
+ * If this is a baserel, consider gathering any partial paths we may
+ * hinthave created for it.
+ */
+ if (rel->reloptkind == RELOPT_BASEREL)
+ generate_gather_paths(root, rel);
+
+ /* Now find the cheapest of the paths for this rel */
set_cheapest(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;
+ List *oldpathlist;
+ ListCell *l;
+ bool regenerate = false;
+
+ /* Hint has not been parsed yet, or this is not a parallel'able relation */
+ if (current_hint_state == NULL || rel->partial_pathlist == NIL)
+ return;
+
+ phint = find_parallel_hint(root, rel->relid, rel);
+
+ if (phint == NULL || !phint->force_parallel)
+ return;
+
+ /*
+ * This relation contains gather paths previously created and they prevent
+ * adding new gahter path with same cost. Remove them.
+ */
+ oldpathlist = rel->pathlist;
+ rel->pathlist = NIL;
+ foreach (l, oldpathlist)
+ {
+ Path *path = (Path *) lfirst(l);
+
+ if (IsA(path, GatherPath) &&
+ path->parallel_workers != phint->nworkers)
+ {
+ pfree(path);
+ regenerate = true;
+ }
+ else
+ rel->pathlist = lappend(rel->pathlist, path);
+ }
+ list_free(oldpathlist);
+
+ if (regenerate)
+ {
+ foreach (l, rel->partial_pathlist)
+ {
+ Path *ppath = (Path *) lfirst(l);
+
+ if (phint && phint->nworkers > 0 && phint->force_parallel)
+ {
+ Assert(ppath->parallel_workers > 0);
+ ppath->parallel_workers = phint->nworkers;
+ }
+ }
+
+ generate_gather_paths(root, rel);
+ }
+}
+
+/*
* set_rel_pathlist
* Build access paths for a base relation
*