#include "catalog/pg_class.h"
#endif
+#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"
+
#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif
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_stmt_beg(PLpgSQL_execstate *estate,
+ PLpgSQL_stmt *stmt);
+static void pg_hint_plan_plpgsql_stmt_end(PLpgSQL_execstate *estate,
+ PLpgSQL_stmt *stmt);
+
/* GUC variables */
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},
};
/*
+ * 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;
get_relation_info_hook = pg_hint_plan_get_relation_info;
prev_join_search = join_search_hook;
join_search_hook = pg_hint_plan_join_search;
+
+ /* setup PL/pgSQL plugin hook */
+ var_ptr = (PLpgSQL_plugin **) find_rendezvous_variable("PLpgSQL_plugin");
+ *var_ptr = &plugin_funcs;
}
/*
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
pfree(buf.data);
}
+
+/*
+ * Get hints from table by client-supplied query string and application name.
+ */
+static const char *
+get_hints_from_table(const char *client_query, const char *client_application)
+{
+ const char *search_query =
+ "SELECT hints "
+ " FROM hint_plan.hints "
+ " WHERE norm_query_string = $1 "
+ " AND ( application_name = $2 "
+ " OR application_name = '' ) "
+ " ORDER BY application_name DESC";
+ static SPIPlanPtr plan = NULL;
+ char *hints = NULL;
+ Oid argtypes[2] = { TEXTOID, TEXTOID };
+ Datum values[2];
+ bool nulls[2] = { false, false };
+ text *qry;
+ text *app;
+
+ PG_TRY();
+ {
+ ++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;
+ }
+ PG_CATCH();
+ {
+ --nested_level;
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+
+ return hints;
+}
+
/*
* Get client-supplied query string.
*/
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 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;
+ int query_len;
int save_nestlevel;
PlannedStmt *result;
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();
+
/*
- * Create hint struct from client-supplied query string.
+ * Create hintstate from hint specified for the query, if any.
+ *
+ * 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 = get_query_string();
- hints = get_hints_from_comment(query);
+ 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);
/*
return result;
- standard_planner_proc:
- current_hint = NULL;
- if (prev_planner)
- return (*prev_planner) (parse, cursorOptions, boundParams);
- else
- return standard_planner(parse, cursorOptions, boundParams);
+standard_planner_proc:
+ current_hint = NULL;
+ if (prev_planner)
+ return (*prev_planner) (parse, cursorOptions, boundParams);
+ else
+ return standard_planner(parse, cursorOptions, boundParams);
}
/*
}
}
- /* 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);
}
}
+/*
+ * 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;
+ 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)
+ plpgsql_query_string = NULL;
+}
+
#define standard_join_search pg_hint_plan_standard_join_search
#define join_search_one_level pg_hint_plan_join_search_one_level
#define make_join_rel make_join_rel_wrapper
#define make_join_rel pg_hint_plan_make_join_rel
#define add_paths_to_joinrel add_paths_to_joinrel_wrapper
#include "make_join_rel.c"
+
+#include "pg_stat_statements.c"