278 lines
7.1 KiB
C
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);
|
|
}
|