mirror of
https://github.com/HypoPG/hypopg
synced 2026-05-24 09:38:21 +00:00
380 lines
8.4 KiB
C
380 lines
8.4 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* hypopg.c: Implementation of hypothetical indexes for PostgreSQL
|
|
*
|
|
* Some functions are imported from PostgreSQL source code, theses are present
|
|
* in hypopg_import.* files.
|
|
*
|
|
* This program is open source, licensed under the PostgreSQL license.
|
|
* For license terms, see the LICENSE file.
|
|
*
|
|
* Copyright (C) 2015-2018: Julien Rouhaud
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
|
|
|
|
#include "postgres.h"
|
|
#include "fmgr.h"
|
|
|
|
#include "include/hypopg.h"
|
|
#include "include/hypopg_import.h"
|
|
#include "include/hypopg_index.h"
|
|
#include "include/hypopg_table.h"
|
|
|
|
PG_MODULE_MAGIC;
|
|
|
|
/*--- Macros ---*/
|
|
#define HYPO_ENABLED() (isExplain && hypo_is_enabled)
|
|
|
|
/*--- Variables exported ---*/
|
|
|
|
bool isExplain;
|
|
bool hypo_is_enabled;
|
|
MemoryContext HypoMemoryContext;
|
|
|
|
/*--- Functions --- */
|
|
|
|
void _PG_init(void);
|
|
void _PG_fini(void);
|
|
|
|
Datum hypopg_reset(PG_FUNCTION_ARGS);
|
|
|
|
PG_FUNCTION_INFO_V1(hypopg_reset);
|
|
|
|
static void
|
|
hypo_utility_hook(
|
|
#if PG_VERSION_NUM >= 100000
|
|
PlannedStmt *pstmt,
|
|
#else
|
|
Node *parsetree,
|
|
#endif
|
|
const char *queryString,
|
|
#if PG_VERSION_NUM >= 90300
|
|
ProcessUtilityContext context,
|
|
#endif
|
|
ParamListInfo params,
|
|
#if PG_VERSION_NUM >= 100000
|
|
QueryEnvironment *queryEnv,
|
|
#endif
|
|
#if PG_VERSION_NUM < 90300
|
|
bool isTopLevel,
|
|
#endif
|
|
DestReceiver *dest,
|
|
char *completionTag);
|
|
static ProcessUtility_hook_type prev_utility_hook = NULL;
|
|
|
|
static void hypo_executorEnd_hook(QueryDesc *queryDesc);
|
|
static ExecutorEnd_hook_type prev_ExecutorEnd_hook = NULL;
|
|
|
|
|
|
static void hypo_get_relation_info_hook(PlannerInfo *root,
|
|
Oid relationObjectId,
|
|
bool inhparent,
|
|
RelOptInfo *rel);
|
|
static get_relation_info_hook_type prev_get_relation_info_hook = NULL;
|
|
|
|
static void hypo_set_rel_pathlist_hook(PlannerInfo *root,
|
|
RelOptInfo *rel,
|
|
Index rti,
|
|
RangeTblEntry *rte);
|
|
static set_rel_pathlist_hook_type prev_set_rel_pathlist_hook = NULL;
|
|
|
|
static bool hypo_query_walker(Node *node);
|
|
|
|
void
|
|
_PG_init(void)
|
|
{
|
|
/* Install hooks */
|
|
prev_utility_hook = ProcessUtility_hook;
|
|
ProcessUtility_hook = hypo_utility_hook;
|
|
|
|
prev_ExecutorEnd_hook = ExecutorEnd_hook;
|
|
ExecutorEnd_hook = hypo_executorEnd_hook;
|
|
|
|
prev_get_relation_info_hook = get_relation_info_hook;
|
|
get_relation_info_hook = hypo_get_relation_info_hook;
|
|
|
|
prev_explain_get_index_name_hook = explain_get_index_name_hook;
|
|
explain_get_index_name_hook = hypo_explain_get_index_name_hook;
|
|
|
|
prev_set_rel_pathlist_hook = set_rel_pathlist_hook;
|
|
set_rel_pathlist_hook = hypo_set_rel_pathlist_hook;
|
|
|
|
isExplain = false;
|
|
hypoIndexes = NIL;
|
|
|
|
HypoMemoryContext = AllocSetContextCreate(TopMemoryContext,
|
|
"HypoPG context",
|
|
#if PG_VERSION_NUM >= 90600
|
|
ALLOCSET_DEFAULT_SIZES
|
|
#else
|
|
ALLOCSET_DEFAULT_MINSIZE,
|
|
ALLOCSET_DEFAULT_INITSIZE,
|
|
ALLOCSET_DEFAULT_MAXSIZE
|
|
#endif
|
|
);
|
|
|
|
DefineCustomBoolVariable("hypopg.enabled",
|
|
"Enable / Disable hypopg",
|
|
NULL,
|
|
&hypo_is_enabled,
|
|
true,
|
|
PGC_USERSET,
|
|
0,
|
|
NULL,
|
|
NULL,
|
|
NULL);
|
|
|
|
}
|
|
|
|
void
|
|
_PG_fini(void)
|
|
{
|
|
/* uninstall hooks */
|
|
ProcessUtility_hook = prev_utility_hook;
|
|
ExecutorEnd_hook = prev_ExecutorEnd_hook;
|
|
get_relation_info_hook = prev_get_relation_info_hook;
|
|
explain_get_index_name_hook = prev_explain_get_index_name_hook;
|
|
set_rel_pathlist_hook = prev_set_rel_pathlist_hook;
|
|
}
|
|
|
|
/*---------------------------------
|
|
* Wrapper around GetNewRelFileNode
|
|
* Return a new OID for an hypothetical index.
|
|
*/
|
|
Oid
|
|
hypo_getNewOid(Oid relid)
|
|
{
|
|
Relation pg_class;
|
|
Relation relation;
|
|
Oid newoid;
|
|
Oid reltablespace;
|
|
char relpersistence;
|
|
|
|
/* Open the relation on which we want a new OID */
|
|
relation = heap_open(relid, AccessShareLock);
|
|
|
|
reltablespace = relation->rd_rel->reltablespace;
|
|
relpersistence = relation->rd_rel->relpersistence;
|
|
|
|
/* Close the relation and release the lock now */
|
|
heap_close(relation, AccessShareLock);
|
|
|
|
/* Open pg_class to aks a new OID */
|
|
pg_class = heap_open(RelationRelationId, RowExclusiveLock);
|
|
|
|
/* ask for a new relfilenode */
|
|
newoid = GetNewRelFileNode(reltablespace, pg_class, relpersistence);
|
|
|
|
/* Close pg_class and release the lock now */
|
|
heap_close(pg_class, RowExclusiveLock);
|
|
|
|
return newoid;
|
|
}
|
|
|
|
/* This function setup the "isExplain" flag for next hooks.
|
|
* If this flag is setup, we can add hypothetical indexes.
|
|
*/
|
|
void
|
|
hypo_utility_hook(
|
|
#if PG_VERSION_NUM >= 100000
|
|
PlannedStmt *pstmt,
|
|
#else
|
|
Node *parsetree,
|
|
#endif
|
|
const char *queryString,
|
|
#if PG_VERSION_NUM >= 90300
|
|
ProcessUtilityContext context,
|
|
#endif
|
|
ParamListInfo params,
|
|
#if PG_VERSION_NUM >= 100000
|
|
QueryEnvironment *queryEnv,
|
|
#endif
|
|
#if PG_VERSION_NUM < 90300
|
|
bool isTopLevel,
|
|
#endif
|
|
DestReceiver *dest,
|
|
char *completionTag)
|
|
{
|
|
isExplain = query_or_expression_tree_walker(
|
|
#if PG_VERSION_NUM >= 100000
|
|
(Node *) pstmt,
|
|
#else
|
|
parsetree,
|
|
#endif
|
|
hypo_query_walker,
|
|
NULL, 0);
|
|
|
|
if (prev_utility_hook)
|
|
prev_utility_hook(
|
|
#if PG_VERSION_NUM >= 100000
|
|
pstmt,
|
|
#else
|
|
parsetree,
|
|
#endif
|
|
queryString,
|
|
#if PG_VERSION_NUM >= 90300
|
|
context,
|
|
#endif
|
|
params,
|
|
#if PG_VERSION_NUM >= 100000
|
|
queryEnv,
|
|
#endif
|
|
#if PG_VERSION_NUM < 90300
|
|
isTopLevel,
|
|
#endif
|
|
dest, completionTag);
|
|
else
|
|
standard_ProcessUtility(
|
|
#if PG_VERSION_NUM >= 100000
|
|
pstmt,
|
|
#else
|
|
parsetree,
|
|
#endif
|
|
queryString,
|
|
#if PG_VERSION_NUM >= 90300
|
|
context,
|
|
#endif
|
|
params,
|
|
#if PG_VERSION_NUM >= 100000
|
|
queryEnv,
|
|
#endif
|
|
#if PG_VERSION_NUM < 90300
|
|
isTopLevel,
|
|
#endif
|
|
dest, completionTag);
|
|
|
|
}
|
|
|
|
/* Detect if the current utility command is compatible with hypothetical indexes
|
|
* i.e. an EXPLAIN, no ANALYZE
|
|
*/
|
|
static bool
|
|
hypo_query_walker(Node *parsetree)
|
|
{
|
|
if (parsetree == NULL)
|
|
return false;
|
|
|
|
#if PG_VERSION_NUM >= 100000
|
|
parsetree = ((PlannedStmt *) parsetree)->utilityStmt;
|
|
if (parsetree == NULL)
|
|
return false;
|
|
#endif
|
|
switch (nodeTag(parsetree))
|
|
{
|
|
case T_ExplainStmt:
|
|
{
|
|
ListCell *lc;
|
|
|
|
foreach(lc, ((ExplainStmt *) parsetree)->options)
|
|
{
|
|
DefElem *opt = (DefElem *) lfirst(lc);
|
|
|
|
if (strcmp(opt->defname, "analyze") == 0)
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/* Reset the isExplain flag after each query */
|
|
static void
|
|
hypo_executorEnd_hook(QueryDesc *queryDesc)
|
|
{
|
|
isExplain = false;
|
|
|
|
if (prev_ExecutorEnd_hook)
|
|
prev_ExecutorEnd_hook(queryDesc);
|
|
else
|
|
standard_ExecutorEnd(queryDesc);
|
|
}
|
|
|
|
/*
|
|
* This function will execute the "hypo_injectHypotheticalIndex" for every
|
|
* hypothetical index found for each relation if the isExplain flag is setup.
|
|
*/
|
|
static void
|
|
hypo_get_relation_info_hook(PlannerInfo *root,
|
|
Oid relationObjectId,
|
|
bool inhparent,
|
|
RelOptInfo *rel)
|
|
{
|
|
if (HYPO_ENABLED())
|
|
{
|
|
Relation relation;
|
|
|
|
/* Open the current relation */
|
|
relation = heap_open(relationObjectId, AccessShareLock);
|
|
|
|
if (relation->rd_rel->relkind == RELKIND_RELATION
|
|
#if PG_VERSION_NUM >= 90300
|
|
|| relation->rd_rel->relkind == RELKIND_MATVIEW
|
|
#endif
|
|
)
|
|
{
|
|
ListCell *lc;
|
|
|
|
foreach(lc, hypoIndexes)
|
|
{
|
|
hypoIndex *entry = (hypoIndex *) lfirst(lc);
|
|
|
|
if (entry->relid == relationObjectId)
|
|
{
|
|
/*
|
|
* hypothetical index found, add it to the relation's
|
|
* indextlist
|
|
*/
|
|
hypo_injectHypotheticalIndex(root, relationObjectId,
|
|
inhparent, rel, relation, entry);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Close the relation release the lock now */
|
|
heap_close(relation, AccessShareLock);
|
|
|
|
if(hypo_table_oid_is_hypothetical(relationObjectId))
|
|
/*
|
|
* this relation is table we want to partition hypothetical,
|
|
* inject hypothetical partitioning
|
|
*/
|
|
hypo_injectHypotheticalPartitioning(root, relationObjectId, rel);
|
|
|
|
}
|
|
if (prev_get_relation_info_hook)
|
|
prev_get_relation_info_hook(root, relationObjectId, inhparent, rel);
|
|
}
|
|
|
|
/*
|
|
* if this child relation is excluded by constraints, call set_dummy_rel_pathlist
|
|
*/
|
|
static void
|
|
hypo_set_rel_pathlist_hook(PlannerInfo *root,
|
|
RelOptInfo *rel,
|
|
Index rti,
|
|
RangeTblEntry *rte)
|
|
{
|
|
if(HYPO_ENABLED() && hypo_table_oid_is_hypothetical(rte->relid)
|
|
&& rte->relkind == 'r')
|
|
hypo_setPartitionPathlist(root,rel,rti,rte);
|
|
|
|
if (prev_set_rel_pathlist_hook)
|
|
prev_set_rel_pathlist_hook(root, rel, rti, rte);
|
|
}
|
|
|
|
|
|
/*
|
|
* Reset statistics.
|
|
*/
|
|
Datum
|
|
hypopg_reset(PG_FUNCTION_ARGS)
|
|
{
|
|
hypo_index_reset();
|
|
PG_RETURN_VOID();
|
|
}
|