#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.
+ */
+#if PG_VERSION_NUM >= 90200
#include "plpgsql.h"
+#else
+#include "plpgsql-9.1.h"
+#endif
/* partially copied from pg_stat_statements */
#include "normalize_query.h"
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 */
RelOptInfo *pg_hint_plan_make_join_rel(PlannerInfo *root, RelOptInfo *rel1,
RelOptInfo *rel2);
-static void pg_hint_plan_plpgsql_func_setup(PLpgSQL_execstate *estate,
- PLpgSQL_stmt *stmt);
static void pg_hint_plan_plpgsql_stmt_beg(PLpgSQL_execstate *estate,
PLpgSQL_stmt *stmt);
static void pg_hint_plan_plpgsql_stmt_end(PLpgSQL_execstate *estate,
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, NULL, HINT_KEYWORD_UNRECOGNIZED}
};
-const char *hint_query_string = NULL;
-PLpgSQL_plugin plugin_funcs = { };
+/*
+ * PL/pgSQL plugin for retrieving string representation of each query during
+ * function execution.
+ */
+const char *plpgsql_query_string = NULL;
+PLpgSQL_plugin plugin_funcs = {
+ NULL,
+ NULL,
+ NULL,
+ pg_hint_plan_plpgsql_stmt_beg,
+ pg_hint_plan_plpgsql_stmt_end,
+ NULL,
+ NULL,
+};
+
+/* Current nesting depth of SPI calls, used to prevent recursive calls */
+static int nested_level = 0;
+
/*
* Module load callbacks
*/
void
_PG_init(void)
{
+ PLpgSQL_plugin **var_ptr;
+
/* Define custom GUC variables. */
DefineCustomBoolVariable("pg_hint_plan.enable_hint",
"Force planner to use plans specified in the hint comment preceding to the query.",
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;
prev_join_search = join_search_hook;
join_search_hook = pg_hint_plan_join_search;
- /* PL/pgSQL plugin hook */
- PLpgSQL_plugin **var_ptr = (PLpgSQL_plugin **) find_rendezvous_variable("PLpgSQL_plugin");
+ /* setup PL/pgSQL plugin hook */
+ var_ptr = (PLpgSQL_plugin **) find_rendezvous_variable("PLpgSQL_plugin");
*var_ptr = &plugin_funcs;
- (&plugin_funcs)->func_setup = (void *)pg_hint_plan_plpgsql_func_setup;
}
/*
void
_PG_fini(void)
{
+ PLpgSQL_plugin **var_ptr;
+
/* Uninstall hooks. */
ProcessUtility_hook = prev_ProcessUtility;
planner_hook = prev_planner;
get_relation_info_hook = prev_get_relation_info;
join_search_hook = prev_join_search;
+
+ /* uninstall PL/pgSQL plugin hook */
+ var_ptr = (PLpgSQL_plugin **) find_rendezvous_variable("PLpgSQL_plugin");
+ *var_ptr = NULL;
}
/*
}
/*
- * 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);
- /*
- * SPI_connectで新しく作成されたメモリコンテキスト内で、pallocを
- * 使用してメモリを確保してもSPI_finishで解放されてしまう。
- * それを防ぐために、SPI_pallocを使って上位エクゼキュータコンテ
- * キスト内にメモリを確保し、そこにヒント用テーブルから取得した
- * ヒントを保存している。
- */
- buf = SPI_palloc(strlen(hints) + 1);
- strcpy(buf, hints);
- hints = buf;
+ --nested_level;
+ PG_RE_THROW();
}
-
- SPI_finish();
+ PG_END_TRY();
return hints;
}
entry = FetchPreparedStatement(stmt_name, true);
p = entry->plansource->query_string;
}
- else if (hint_query_string)
- p = hint_query_string;
+ else if (plpgsql_query_string)
+ p = plpgsql_query_string;
else
p = debug_query_string;
/*
* 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);
}
}
-static void
-pg_hint_plan_plpgsql_func_setup(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt)
-{
- (&plugin_funcs)->stmt_beg = pg_hint_plan_plpgsql_stmt_beg;
- (&plugin_funcs)->stmt_end = pg_hint_plan_plpgsql_stmt_end;
-}
-
+/*
+ * stmt_beg callback is called when each query in PL/pgSQL function is about
+ * to be executed. At that timing, we save query string in the global variable
+ * 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 mechanism.
+ */
static void
pg_hint_plan_plpgsql_stmt_beg(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt)
{
if ((enum PLpgSQL_stmt_types) stmt->cmd_type == PLPGSQL_STMT_EXECSQL)
{
PLpgSQL_expr *expr = ((PLpgSQL_stmt_execsql *) stmt)->sqlstmt;
- hint_query_string = expr->query;
+ plpgsql_query_string = expr->query;
}
}
+/*
+ * stmt_end callback is called then each query in PL/pgSQL function has
+ * finished. At that timing, we clear plpgsql_query_string to tell planner
+ * hook that next call is not for a query written in PL/pgSQL block.
+ */
static void
pg_hint_plan_plpgsql_stmt_end(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt)
{
if ((enum PLpgSQL_stmt_types) stmt->cmd_type == PLPGSQL_STMT_EXECSQL)
- hint_query_string = NULL;
+ plpgsql_query_string = NULL;
}
#define standard_join_search pg_hint_plan_standard_join_search