pg_ivm/event_trigger.c
2025-05-21 02:00:04 +00:00

278 lines
7.1 KiB
C

#include "postgres.h"
#include "pg_ivm.h"
#include "access/genam.h"
#include "access/table.h"
#include "catalog/indexing.h"
#include "commands/event_trigger.h"
#include "utils/builtins.h"
#include "utils/guc.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/rel.h"
#include "utils/snapmgr.h"
#define IMMV_INIT_QUERYHASHSIZE 16
/*
* This file defines the DDL event triggers that maintain pgivm.pg_ivm_immv.
*
* Every time an ALTER RENAME statement is executed, the save_query_strings
* pre-event trigger will parse each IMMV's query string and store the Query
* nodes in a hash table. Then, the restore_query_strings post-event trigger
* will take each Query node and re-create the query string.
*
* This allows the querystring column to remain up to date when any objects are
* renamed.
*
* TODO: This uses a brute force approach to checking if re-parse is necessary.
* Even if an unrelated object is renamed, these event triggers still fire. Can
* figure out a more precise heuristic.
*/
typedef struct HashEntry
{
Oid immv_relid;
char *query;
} HashEntry;
/* Stores the Query nodes of each IMMV view query as they were pre-DDL. */
static HTAB *immv_query_cache;
/* Active snapshot at the time of the pre-DDL trigger. */
static Snapshot active_snapshot;
static char *retrieve_query(Oid immv_relid);
static void hash_query(Oid immv_relid, char *query);
static void initialize_query_cache(void);
static void restore_query_strings_internal(void);
static void save_query_strings_internal(void);
PG_FUNCTION_INFO_V1(save_query_strings);
PG_FUNCTION_INFO_V1(restore_query_strings);
/*
* Pre-DDL event trigger function.
*/
Datum
save_query_strings(PG_FUNCTION_ARGS)
{
EventTriggerData *trigdata;
if (!CALLED_AS_EVENT_TRIGGER(fcinfo))
ereport(ERROR, errmsg("not fired by event trigger manager"));
trigdata = (EventTriggerData *) fcinfo->context;
if (!IsA(trigdata->parsetree, RenameStmt))
PG_RETURN_NULL();
initialize_query_cache();
active_snapshot = GetActiveSnapshot();
save_query_strings_internal();
PG_RETURN_NULL();
}
/*
* Post-DDL event trigger function.
*/
Datum
restore_query_strings(PG_FUNCTION_ARGS)
{
EventTriggerData *trigdata;
if (!CALLED_AS_EVENT_TRIGGER(fcinfo))
ereport(ERROR, errmsg("not fired by event trigger manager"));
trigdata = (EventTriggerData *) fcinfo->context;
if (!IsA(trigdata->parsetree, RenameStmt))
PG_RETURN_NULL();
restore_query_strings_internal();
PG_RETURN_NULL();
}
/*
* Initialize the hash table.
*/
static void
initialize_query_cache(void)
{
HASHCTL ctl;
memset(&ctl, 0, sizeof(ctl));
ctl.keysize = sizeof(Oid);
ctl.entrysize = sizeof(HashEntry);
immv_query_cache = hash_create("IMMV query cache", IMMV_INIT_QUERYHASHSIZE,
&ctl, HASH_ELEM | HASH_BLOBS);
}
/*
* Store a query node in the hash table.
*/
static void
hash_query(Oid immv_relid, char *query)
{
HashEntry *entry;
MemoryContext old_cxt;
bool found;
entry = (HashEntry *) hash_search(immv_query_cache,
&immv_relid,
HASH_ENTER,
&found);
Assert(!found);
old_cxt = MemoryContextSwitchTo(TopTransactionContext);
entry->query = pstrdup(query);
MemoryContextSwitchTo(old_cxt);
}
/*
* Retrieve a query node from the hash table.
*/
static char *
retrieve_query(Oid immv_relid)
{
HashEntry *entry;
MemoryContext old_cxt;
bool found;
char *query;
entry = (HashEntry *) hash_search(immv_query_cache,
&immv_relid,
HASH_FIND,
&found);
Assert(found);
old_cxt = MemoryContextSwitchTo(TopTransactionContext);
query = pstrdup(entry->query);
pfree(entry->query);
MemoryContextSwitchTo(old_cxt);
return query;
}
/*
* Iterate through the pg_ivm_immv table and parse the querystring of each
* entry. Store the query nodes in the hash table.
*/
static void
save_query_strings_internal(void)
{
HeapTuple tup;
Relation pg_ivm_immv_rel;
SysScanDesc scan;
TupleDesc tupdesc;
pg_ivm_immv_rel = table_open(PgIvmImmvRelationId(), RowExclusiveLock);
scan = systable_beginscan(pg_ivm_immv_rel, PgIvmImmvPrimaryKeyIndexId(),
true, active_snapshot, 0, NULL);
tupdesc = RelationGetDescr(pg_ivm_immv_rel);
while ((tup = systable_getnext(scan)) != NULL)
{
Oid matview_oid;
ParseState *parse_state;
Query *query;
bool isnull;
char *matview_relname;
char *query_string;
query_string = TextDatumGetCString(heap_getattr(tup,
Anum_pg_ivm_immv_querystring,
tupdesc, &isnull));
Assert(!isnull);
matview_oid = DatumGetObjectId(heap_getattr(tup, Anum_pg_ivm_immv_immvrelid,
tupdesc, &isnull));
Assert(!isnull);
matview_relname = psprintf("%s.%s",
get_namespace_name(get_rel_namespace(matview_oid)),
get_rel_name(matview_oid));
/* Parse the existing IMMV query pre-DDL. Add it to the list. */
parse_immv_query(matview_relname, query_string, &query, &parse_state);
query = (Query *) ((CreateTableAsStmt *) query->utilityStmt)->into->viewQuery;
/* Store entry for this IMMV. */
hash_query(matview_oid, nodeToString(query));
}
systable_endscan(scan);
table_close(pg_ivm_immv_rel, NoLock);
}
/*
* Iterate through the pg_ivm_immv table. For each entry, get its Query node
* from the hash table and reconstruct the query string. Update the row entry
* with the reconstructed query string.
*/
static void
restore_query_strings_internal(void)
{
HeapTuple tup;
Relation pg_ivm_immv_rel;
SysScanDesc scan;
TupleDesc tupdesc;
int save_nestlevel;
pg_ivm_immv_rel = table_open(PgIvmImmvRelationId(), RowExclusiveLock);
scan = systable_beginscan(pg_ivm_immv_rel, PgIvmImmvPrimaryKeyIndexId(),
true, active_snapshot, 0, NULL);
tupdesc = RelationGetDescr(pg_ivm_immv_rel);
/*
* Restrict search_path so that pg_ivm_get_viewdef_internal returns a
* fully-qualified query.
*/
save_nestlevel = NewGUCNestLevel();
RestrictSearchPath();
while ((tup = systable_getnext(scan)) != NULL)
{
Datum values[Natts_pg_ivm_immv];
HeapTuple newtup;
Oid matview_oid;
Query *query;
Relation matview_rel;
bool isnull;
bool nulls[Natts_pg_ivm_immv];
bool replaces[Natts_pg_ivm_immv];
char *new_query_string;
char *serialized_query;
matview_oid = DatumGetObjectId(heap_getattr(tup, Anum_pg_ivm_immv_immvrelid,
tupdesc, &isnull));
Assert(!isnull);
serialized_query = retrieve_query(matview_oid);
query = stringToNode(serialized_query);
matview_rel = table_open(matview_oid, AccessShareLock);
new_query_string = pg_ivm_get_viewdef_internal(query, matview_rel, true);
table_close(matview_rel, NoLock);
memset(values, 0, sizeof(values));
values[Anum_pg_ivm_immv_querystring - 1] = CStringGetTextDatum(new_query_string);
memset(nulls, false, sizeof(nulls));
memset(replaces, false, sizeof(nulls));
replaces[Anum_pg_ivm_immv_querystring - 1] = true;
newtup = heap_modify_tuple(tup, tupdesc, values, nulls, replaces);
CatalogTupleUpdate(pg_ivm_immv_rel, &newtup->t_self, newtup);
heap_freetuple(newtup);
}
systable_endscan(scan);
table_close(pg_ivm_immv_rel, NoLock);
hash_destroy(immv_query_cache);
/* Roll back the search_path change. */
AtEOXact_GUC(false, save_nestlevel);
}