Index parent_relid; /* inherit parent table relid */
Oid parent_rel_oid; /* inherit parent table relid */
ScanMethodHint *parent_hint; /* inherit parent table scan hint */
- List *parent_index_infos; /* infomation of inherit parent table's
+ List *parent_index_infos; /* information of inherit parent table's
* index */
/* for join method hints */
static bool pg_hint_plan_enable_hint = true;
static bool pg_hint_plan_debug_print = false;
static int pg_hint_plan_parse_messages = INFO;
+/* Default is off, to keep backward compatibility. */
+static bool pg_hint_plan_enable_hint_table = false;
static const struct config_enum_entry parse_messages_level_options[] = {
{"debug", DEBUG2, true},
NULL,
};
+/* Current nesting depth of SPI calls, used to prevent recursive calls */
+static int nested_level = 0;
+
/*
* Module load callbacks
*/
NULL,
NULL);
+ DefineCustomBoolVariable("pg_hint_plan.enable_hint_table",
+ "Force planner to not get hint by using table lookups.",
+ NULL,
+ &pg_hint_plan_enable_hint_table,
+ false,
+ PGC_USERSET,
+ 0,
+ NULL,
+ NULL,
+ NULL);
+
/* Install hooks. */
prev_ProcessUtility = ProcessUtility_hook;
ProcessUtility_hook = pg_hint_plan_ProcessUtility;
}
/*
- * Append string which repserents all hints in a given state to buf, with
+ * Append string which represents all hints in a given state to buf, with
* preceding title with them.
*/
static void
" OR application_name = '' ) "
" ORDER BY application_name DESC";
static SPIPlanPtr plan = NULL;
- int ret;
char *hints = NULL;
Oid argtypes[2] = { TEXTOID, TEXTOID };
Datum values[2];
text *qry;
text *app;
- ret = SPI_connect();
- if (ret != SPI_OK_CONNECT)
- elog(ERROR, "pg_hint_plan: SPI_connect => %d", ret);
-
- if (plan == NULL)
+ PG_TRY();
{
- SPIPlanPtr p;
- p = SPI_prepare(search_query, 2, argtypes);
- if (p == NULL)
- elog(ERROR, "pg_hint_plan: SPI_prepare => %d", SPI_result);
- plan = SPI_saveplan(p);
- SPI_freeplan(p);
+ ++nested_level;
+
+ SPI_connect();
+
+ if (plan == NULL)
+ {
+ SPIPlanPtr p;
+ p = SPI_prepare(search_query, 2, argtypes);
+ plan = SPI_saveplan(p);
+ SPI_freeplan(p);
+ }
+
+ qry = cstring_to_text(client_query);
+ app = cstring_to_text(client_application);
+ values[0] = PointerGetDatum(qry);
+ values[1] = PointerGetDatum(app);
+
+ SPI_execute_plan(plan, values, nulls, true, 1);
+
+ if (SPI_processed > 0)
+ {
+ char *buf;
+
+ hints = SPI_getvalue(SPI_tuptable->vals[0],
+ SPI_tuptable->tupdesc, 1);
+ /*
+ * Here we use SPI_palloc to ensure that hints string is valid even
+ * after SPI_finish call. We can't use simple palloc because it
+ * allocates memory in SPI's context and that context is deleted in
+ * SPI_finish.
+ */
+ buf = SPI_palloc(strlen(hints) + 1);
+ strcpy(buf, hints);
+ hints = buf;
+ }
+
+ SPI_finish();
+
+ --nested_level;
}
-
- qry = cstring_to_text(client_query);
- app = cstring_to_text(client_application);
- values[0] = PointerGetDatum(qry);
- values[1] = PointerGetDatum(app);
-
- pg_hint_plan_enable_hint = false;
- ret = SPI_execute_plan(plan, values, nulls, true, 1);
- pg_hint_plan_enable_hint = true;
- if (ret != SPI_OK_SELECT)
- elog(ERROR, "pg_hint_plan: SPI_execute_plan => %d", ret);
-
- if (SPI_processed > 0)
+ PG_CATCH();
{
- char *buf;
-
- hints = SPI_getvalue(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1);
- /*
- * Here we use SPI_palloc to ensure that hints string is valid even
- * after SPI_finish call. We can't use simple palloc because it
- * allocates memory in SPI's context and that context is deleted in
- * SPI_finish.
- */
- buf = SPI_palloc(strlen(hints) + 1);
- strcpy(buf, hints);
- hints = buf;
+ --nested_level;
+ PG_RE_THROW();
}
-
- SPI_finish();
+ PG_END_TRY();
return hints;
}
/*
* If an object (or a set of objects) has multiple hints of same hint-type,
- * only the last hint is valid and others are igonred in planning.
+ * only the last hint is valid and others are ignored in planning.
* Hints except the last are marked as 'duplicated' to remember the order.
*/
for (i = 0; i < hstate->nall_hints - 1; i++)
{
Node *node;
- if (!pg_hint_plan_enable_hint)
+ /*
+ * 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 (prev_ProcessUtility)
(*prev_ProcessUtility) (parsetree, queryString, params,
static PlannedStmt *
pg_hint_plan_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
{
- const char *hints;
+ const char *hints = NULL;
const char *query;
char *norm_query;
pgssJumbleState jstate;
HintState *hstate;
/*
- * Use standard planner if pg_hint_plan is disabled. Other hook functions
- * try to change plan with current_hint if any, so set it to NULL.
+ * 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.
*/
- if (!pg_hint_plan_enable_hint)
+ if (!pg_hint_plan_enable_hint || nested_level > 0)
goto standard_planner_proc;
/* Create hint struct from client-supplied query string. */
query = get_query_string();
/*
- * Search hint information which is stored for the query and the
- * application. Query string is normalized before using in condition
- * in order to allow fuzzy matching.
+ * Create hintstate from hint specified for the query, if any.
*
- * XXX: normalizing code is copied from pg_stat_statements.c, so be careful
- * when supporting PostgreSQL's version up.
- */
- jstate.jumble = (unsigned char *) palloc(JUMBLE_SIZE);
- jstate.jumble_len = 0;
- jstate.clocations_buf_size = 32;
- jstate.clocations = (pgssLocationLen *)
- palloc(jstate.clocations_buf_size * sizeof(pgssLocationLen));
- jstate.clocations_count = 0;
- JumbleQuery(&jstate, parse);
- /*
- * generate_normalized_query() copies exact given query_len bytes, so we
- * add 1 byte for null-termination here. As comments on
- * generate_normalized_query says, generate_normalized_query doesn't take
- * care of null-terminate, but additional 1 byte ensures that '\0' byte in
- * the source buffer to be copied into norm_query.
+ * First we lookup hint in pg_hint.hints table by normalized query string,
+ * unless pg_hint_plan.enable_hint_table is OFF.
+ * This parameter provides option to avoid overhead of table lookup during
+ * planning.
+ *
+ * If no hint was found, then we try to get hint from special query comment.
*/
- query_len = strlen(query) + 1;
- norm_query = generate_normalized_query(&jstate,
- query,
- &query_len,
- GetDatabaseEncoding());
- hints = get_hints_from_table(norm_query, application_name);
- elog(DEBUG1,
- "pg_hint_plan: get_hints_from_table [%s][%s]=>[%s]",
- norm_query, application_name,
- hints ? hints : "(none)");
+ if (pg_hint_plan_enable_hint_table)
+ {
+ /*
+ * Search hint information which is stored for the query and the
+ * application. Query string is normalized before using in condition
+ * in order to allow fuzzy matching.
+ *
+ * XXX: normalizing code is copied from pg_stat_statements.c, so be
+ * careful when supporting PostgreSQL's version up.
+ */
+ jstate.jumble = (unsigned char *) palloc(JUMBLE_SIZE);
+ jstate.jumble_len = 0;
+ jstate.clocations_buf_size = 32;
+ jstate.clocations = (pgssLocationLen *)
+ palloc(jstate.clocations_buf_size * sizeof(pgssLocationLen));
+ jstate.clocations_count = 0;
+ JumbleQuery(&jstate, parse);
+ /*
+ * generate_normalized_query() copies exact given query_len bytes, so we
+ * add 1 byte for null-termination here. As comments on
+ * generate_normalized_query says, generate_normalized_query doesn't
+ * take care of null-terminate, but additional 1 byte ensures that '\0'
+ * byte in the source buffer to be copied into norm_query.
+ */
+ query_len = strlen(query) + 1;
+ norm_query = generate_normalized_query(&jstate,
+ query,
+ &query_len,
+ GetDatabaseEncoding());
+ hints = get_hints_from_table(norm_query, application_name);
+ elog(DEBUG1,
+ "pg_hint_plan: get_hints_from_table [%s][%s]=>[%s]",
+ norm_query, application_name, hints ? hints : "(none)");
+ }
if (hints == NULL)
hints = get_hints_from_comment(query);
hstate = create_hintstate(parse, hints);
}
}
- /* Check to match the predicate's paraameter of index */
+ /* Check to match the predicate's parameter of index */
if (p_info->indpred_str &&
!heap_attisnull(ht_idx, Anum_pg_index_indpred))
{
Datum result;
/*
- * to change the predicate's parabeter of child's
+ * to change the predicate's parameter of child's
* index to strings
*/
predDatum = SysCacheGetAttr(INDEXRELID, ht_idx,
}
/*
- * to check to match the expression's paraameter of index with child indexes
+ * to check to match the expression's parameter of index with child indexes
*/
p_info->expression_str = NULL;
if(!heap_attisnull(indexRelation->rd_indextuple, Anum_pg_index_indexprs))
}
/*
- * to check to match the predicate's paraameter of index with child indexes
+ * to check to match the predicate's parameter of index with child indexes
*/
p_info->indpred_str = NULL;
if(!heap_attisnull(indexRelation->rd_indextuple, Anum_pg_index_indpred))
if (prev_get_relation_info)
(*prev_get_relation_info) (root, relationObjectId, inhparent, rel);
- /* Do nothing if we don't have valid hint in this context. */
- if (!current_hint)
+ /*
+ * Do nothing if we don't have valid hint in this context or current
+ * nesting depth is nesting depth of SPI calls.
+ */
+ if (!current_hint || nested_level > 0)
return;
if (inhparent)
/*
* Use standard planner (or geqo planner) if pg_hint_plan is disabled or no
- * valid hint is supplied.
+ * valid hint is supplied or current nesting depth is nesting depth of SPI
+ * calls.
*/
- if (!current_hint)
+ if (!current_hint || nested_level > 0)
{
if (prev_join_search)
return (*prev_join_search) (root, levels_needed, initial_rels);
* plpgsql_query_string to use it in planner hook. It's safe to use one global
* variable for the purpose, because its content is only necessary until
* planner hook is called for the query, so recursive PL/pgSQL function calls
- * don't harm this mechanismk.
+ * don't harm this mechanism.
*/
static void
pg_hint_plan_plpgsql_stmt_beg(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt)