From d6f32e7b77ce5adbf47fa5a01c3f938e272439bf Mon Sep 17 00:00:00 2001 From: Adam Guo Date: Wed, 26 Mar 2025 17:07:26 +0000 Subject: [PATCH 01/13] Add querystring column to pg_ivm_immv. --- Makefile | 2 +- createas.c | 1 + pg_ivm--1.10--1.11.sql | 1 + pg_ivm.control | 2 +- pg_ivm.h | 3 ++- 5 files changed, 6 insertions(+), 3 deletions(-) create mode 100644 pg_ivm--1.10--1.11.sql diff --git a/Makefile b/Makefile index 9f770f6..e225eaa 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,7 @@ DATA = pg_ivm--1.0.sql \ pg_ivm--1.3--1.4.sql pg_ivm--1.4--1.5.sql pg_ivm--1.5--1.6.sql \ pg_ivm--1.6--1.7.sql pg_ivm--1.7--1.8.sql pg_ivm--1.8--1.9.sql \ pg_ivm--1.9--1.10.sql \ - pg_ivm--1.10.sql + pg_ivm--1.10.sql pg_ivm--1.10--1.11.sql REGRESS = pg_ivm create_immv refresh_immv diff --git a/createas.c b/createas.c index b41fd1d..8c6ce4d 100644 --- a/createas.c +++ b/createas.c @@ -1719,6 +1719,7 @@ StoreImmvQuery(Oid viewOid, Query *viewQuery) memset(values, 0, sizeof(values)); memset(isNulls, false, sizeof(isNulls)); + isNulls[Anum_pg_ivm_immv_querystring - 1] = true; values[Anum_pg_ivm_immv_immvrelid -1 ] = ObjectIdGetDatum(viewOid); values[Anum_pg_ivm_immv_ispopulated -1 ] = BoolGetDatum(false); diff --git a/pg_ivm--1.10--1.11.sql b/pg_ivm--1.10--1.11.sql new file mode 100644 index 0000000..b093d22 --- /dev/null +++ b/pg_ivm--1.10--1.11.sql @@ -0,0 +1 @@ +ALTER TABLE pgivm.pg_ivm_immv ADD COLUMN querystring text; diff --git a/pg_ivm.control b/pg_ivm.control index 27dc5af..fcfe90d 100644 --- a/pg_ivm.control +++ b/pg_ivm.control @@ -1,6 +1,6 @@ # incremental view maintenance extension_ comment = 'incremental view maintenance on PostgreSQL' -default_version = '1.10' +default_version = '1.11' module_pathname = '$libdir/pg_ivm' relocatable = false schema = pg_catalog diff --git a/pg_ivm.h b/pg_ivm.h index 88d7003..ae79e45 100644 --- a/pg_ivm.h +++ b/pg_ivm.h @@ -20,12 +20,13 @@ #include "tcop/dest.h" #include "utils/queryenvironment.h" -#define Natts_pg_ivm_immv 4 +#define Natts_pg_ivm_immv 5 #define Anum_pg_ivm_immv_immvrelid 1 #define Anum_pg_ivm_immv_viewdef 2 #define Anum_pg_ivm_immv_ispopulated 3 #define Anum_pg_ivm_immv_lastivmupdate 4 +#define Anum_pg_ivm_immv_querystring 5 /* pg_ivm.c */ From 7a36cc501ad4b7b0114fe847e4cf1d3dede4aaea Mon Sep 17 00:00:00 2001 From: Adam Guo Date: Wed, 26 Mar 2025 17:20:36 +0000 Subject: [PATCH 02/13] Add event trigger to update querystring. --- pg_ivm--1.10--1.11.sql | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/pg_ivm--1.10--1.11.sql b/pg_ivm--1.10--1.11.sql index b093d22..6819be4 100644 --- a/pg_ivm--1.10--1.11.sql +++ b/pg_ivm--1.10--1.11.sql @@ -1 +1,23 @@ ALTER TABLE pgivm.pg_ivm_immv ADD COLUMN querystring text; + +CREATE FUNCTION pgivm.refresh_query_strings() +RETURNS event_trigger LANGUAGE plpgsql AS +$$ +DECLARE + old_search_path text; +BEGIN + -- Empty search path so that get_immv_def returns a fully-qualified query. + SELECT setting INTO old_search_path FROM pg_catalog.pg_settings + WHERE name = 'search_path'; + SET search_path = ''; + + UPDATE pgivm.pg_ivm_immv SET querystring = pgivm.get_immv_def(immvrelid); + + -- Reset search path to the original value. + EXECUTE format('SET search_path = %s', old_search_path); +END +$$; + +CREATE EVENT TRIGGER refresh_query_strings +ON ddl_command_end +EXECUTE FUNCTION pgivm.refresh_query_strings(); From 3524de96acdb554af3faf4619206a293abf8c659 Mon Sep 17 00:00:00 2001 From: Adam Guo Date: Wed, 26 Mar 2025 18:32:09 +0000 Subject: [PATCH 03/13] Store querystring on create. --- createas.c | 10 +++++++++- pg_ivm--1.10--1.11.sql | 4 ++-- pg_ivm.c | 9 +++++++++ pg_ivm.h | 5 +++++ ruleutils.c | 7 +++++++ 5 files changed, 32 insertions(+), 3 deletions(-) diff --git a/createas.c b/createas.c index 8c6ce4d..026cce3 100644 --- a/createas.c +++ b/createas.c @@ -41,6 +41,7 @@ #include "rewrite/rewriteManip.h" #include "utils/builtins.h" #include "utils/fmgroids.h" +#include "utils/guc.h" #include "utils/lsyscache.h" #include "utils/regproc.h" #include "utils/rel.h" @@ -1710,20 +1711,27 @@ static void StoreImmvQuery(Oid viewOid, Query *viewQuery) { char *querytree = nodeToString((Node *) viewQuery); + char *querystring; Datum values[Natts_pg_ivm_immv]; bool isNulls[Natts_pg_ivm_immv]; + Relation matviewRel; Relation pgIvmImmv; TupleDesc tupleDescriptor; HeapTuple heapTuple; ObjectAddress address; + RestrictSearchPath(); + matviewRel = table_open(viewOid, AccessShareLock); + querystring = pg_ivm_get_viewdef_internal(viewQuery, matviewRel, true); + table_close(matviewRel, NoLock); + memset(values, 0, sizeof(values)); memset(isNulls, false, sizeof(isNulls)); - isNulls[Anum_pg_ivm_immv_querystring - 1] = true; values[Anum_pg_ivm_immv_immvrelid -1 ] = ObjectIdGetDatum(viewOid); values[Anum_pg_ivm_immv_ispopulated -1 ] = BoolGetDatum(false); values[Anum_pg_ivm_immv_viewdef -1 ] = CStringGetTextDatum(querytree); + values[Anum_pg_ivm_immv_querystring - 1] = CStringGetTextDatum(querystring); pgIvmImmv = table_open(PgIvmImmvRelationId(), RowExclusiveLock); diff --git a/pg_ivm--1.10--1.11.sql b/pg_ivm--1.10--1.11.sql index 6819be4..745f3dc 100644 --- a/pg_ivm--1.10--1.11.sql +++ b/pg_ivm--1.10--1.11.sql @@ -1,7 +1,7 @@ -ALTER TABLE pgivm.pg_ivm_immv ADD COLUMN querystring text; +ALTER TABLE pgivm.pg_ivm_immv ADD COLUMN querystring text NOT NULL; CREATE FUNCTION pgivm.refresh_query_strings() -RETURNS event_trigger LANGUAGE plpgsql AS +RETURNS event_trigger LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE old_search_path text; diff --git a/pg_ivm.c b/pg_ivm.c index 19d0e0e..fe5c9bb 100644 --- a/pg_ivm.c +++ b/pg_ivm.c @@ -458,3 +458,12 @@ PgIvmFuncName(char *name) { return list_make2(makeString("pgivm"), makeString(name)); } + +#if defined(PG_VERSION_NUM) && (PG_VERSION_NUM < 170000) +void +RestrictSearchPath(void) +{ + set_config_option("search_path", "pg_catalog, pg_temp", PGC_USERSET, + PGC_S_SESSION, GUC_ACTION_SAVE, true, 0, false); +} +#endif diff --git a/pg_ivm.h b/pg_ivm.h index ae79e45..e334141 100644 --- a/pg_ivm.h +++ b/pg_ivm.h @@ -65,8 +65,13 @@ extern bool isIvmName(const char *s); /* ruleutils.c */ extern char *pg_ivm_get_viewdef(Relation immvrel, bool pretty); +extern char *pg_ivm_get_viewdef_internal(Query *query, Relation immvrel, bool pretty); /* subselect.c */ extern void inline_cte(PlannerInfo *root, CommonTableExpr *cte); +#if defined(PG_VERSION_NUM) && (PG_VERSION_NUM < 170000) +extern void RestrictSearchPath(void); +#endif + #endif diff --git a/ruleutils.c b/ruleutils.c index dfe1fe4..2c7e080 100644 --- a/ruleutils.c +++ b/ruleutils.c @@ -43,6 +43,13 @@ char * pg_ivm_get_viewdef(Relation immvrel, bool pretty) { Query *query = get_immv_query(immvrel); + + return pg_ivm_get_viewdef_internal(query, immvrel, pretty); +} + +char * +pg_ivm_get_viewdef_internal(Query *query, Relation immvrel, bool pretty) +{ TupleDesc resultDesc = RelationGetDescr(immvrel); #if defined(PG_VERSION_NUM) && (PG_VERSION_NUM >= 150000) From 0a5bd500e6fa3efcc265fa2976d4a963375a0b65 Mon Sep 17 00:00:00 2001 From: Adam Guo Date: Wed, 26 Mar 2025 21:42:06 +0000 Subject: [PATCH 04/13] refresh_immv: reparse the query string --- matview.c | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- pg_ivm.c | 33 ++++++++++++++++++----- pg_ivm.h | 2 ++ 3 files changed, 108 insertions(+), 7 deletions(-) diff --git a/matview.c b/matview.c index 66709b5..aa95b58 100644 --- a/matview.c +++ b/matview.c @@ -233,6 +233,7 @@ static void clean_up_IVM_hash_entry(MV_TriggerHashEntry *entry, bool is_abort, static void setLastUpdateXid(Oid immv_oid, FullTransactionId xid); static FullTransactionId getLastUpdateXid(Oid immv_oid); +static Query *recreate_immv_query(Relation matviewRel); /* SQL callable functions */ PG_FUNCTION_INFO_V1(IVM_immediate_before); @@ -388,7 +389,7 @@ RefreshImmvByOid(Oid matviewOid, bool is_create, bool skipData, systable_endscan(scan); table_close(pgIvmImmv, NoLock); - viewQuery = get_immv_query(matviewRel); + viewQuery = recreate_immv_query(matviewRel); /* For IMMV, we need to rewrite matview query */ if (!skipData) @@ -713,6 +714,83 @@ get_immv_query(Relation matviewRel) return query; } +/* + * recreate_immv_query + * + * Parse the querystring for this IMMV. Update the viewdef column in + * pg_ivm_immv and return the Query tree. + */ +static Query * +recreate_immv_query(Relation matviewRel) +{ + Relation pgIvmImmv = table_open(PgIvmImmvRelationId(), AccessShareLock); + TupleDesc tupdesc = RelationGetDescr(pgIvmImmv); + SysScanDesc scan; + ScanKeyData key; + HeapTuple tup; + bool isnull; + Datum datum; + Query *query = NULL; + IntoClause *into; + ParseState *pstate = NULL; + CreateTableAsStmt *stmt; + const char *querystring; + const char *relname; + const char *querytree; + + Datum values[Natts_pg_ivm_immv]; + bool nulls[Natts_pg_ivm_immv]; + bool replaces[Natts_pg_ivm_immv]; + HeapTuple newtup = NULL; + + ScanKeyInit(&key, + Anum_pg_ivm_immv_immvrelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(RelationGetRelid(matviewRel))); + scan = systable_beginscan(pgIvmImmv, PgIvmImmvPrimaryKeyIndexId(), + true, NULL, 1, &key); + + tup = systable_getnext(scan); + + if (!HeapTupleIsValid(tup)) + { + systable_endscan(scan); + table_close(pgIvmImmv, NoLock); + return NULL; + } + + datum = heap_getattr(tup, Anum_pg_ivm_immv_querystring, tupdesc, &isnull); + Assert(!isnull); + querystring = TextDatumGetCString(datum); + + relname = psprintf("%s.%s", + get_namespace_name(get_rel_namespace(matviewRel->rd_id)), + get_rel_name(matviewRel->rd_id)); + + parse_immv_query(relname, querystring, &query, &pstate); + stmt = castNode(CreateTableAsStmt, query->utilityStmt); + into = stmt->into; + + query = castNode(Query, stmt->query); + query = rewriteQueryForIMMV(query, into->colNames); + querytree = nodeToString((Node *) query); + + memset(values, 0, sizeof(values)); + memset(nulls, 0, sizeof(nulls)); + memset(replaces, 0, sizeof(replaces)); + values[Anum_pg_ivm_immv_viewdef - 1] = CStringGetTextDatum(querytree); + replaces[Anum_pg_ivm_immv_viewdef - 1] = true; + + newtup = heap_modify_tuple(tup, tupdesc, values, nulls, replaces); + CatalogTupleUpdate(pgIvmImmv, &newtup->t_self, newtup); + heap_freetuple(newtup); + + systable_endscan(scan); + table_close(pgIvmImmv, NoLock); + + return query; +} + static Tuplestorestate * tuplestore_copy(Tuplestorestate *tuplestore, Relation rel) { diff --git a/pg_ivm.c b/pg_ivm.c index fe5c9bb..a005e07 100644 --- a/pg_ivm.c +++ b/pg_ivm.c @@ -177,17 +177,39 @@ create_immv(PG_FUNCTION_ARGS) text *t_sql = PG_GETARG_TEXT_PP(1); char *relname = text_to_cstring(t_relname); char *sql = text_to_cstring(t_sql); + + Query *query = NULL; + QueryCompletion qc; + ParseState *pstate = NULL; + + parse_immv_query(relname, sql, &query, &pstate); + + ExecCreateImmv(pstate, (CreateTableAsStmt *) query->utilityStmt, &qc); + + PG_RETURN_INT64(qc.nprocessed); +} + +/* + * parse_immv_query + * + * Parse an IMMV definition query and return the Query tree and ParseState using + * the supplied pointers. + */ +void +parse_immv_query(const char *relname, const char *sql, Query **query_ret, + ParseState **pstate_ret) +{ List *parsetree_list; RawStmt *parsetree; - Query *query; - QueryCompletion qc; List *names = NIL; List *colNames = NIL; - ParseState *pstate = make_parsestate(NULL); CreateTableAsStmt *ctas; StringInfoData command_buf; + Query *query; + ParseState *pstate; + pstate = make_parsestate(NULL); parseNameAndColumns(relname, &names, &colNames); initStringInfo(&command_buf); @@ -230,9 +252,8 @@ create_immv(PG_FUNCTION_ARGS) query = transformStmt(pstate, (Node *)ctas); Assert(query->commandType == CMD_UTILITY && IsA(query->utilityStmt, CreateTableAsStmt)); - ExecCreateImmv(pstate, (CreateTableAsStmt *) query->utilityStmt, &qc); - - PG_RETURN_INT64(qc.nprocessed); + *query_ret = query; + *pstate_ret = pstate; } /* diff --git a/pg_ivm.h b/pg_ivm.h index e334141..04c83f0 100644 --- a/pg_ivm.h +++ b/pg_ivm.h @@ -35,6 +35,8 @@ extern Oid PgIvmImmvRelationId(void); extern Oid PgIvmImmvPrimaryKeyIndexId(void); extern bool isImmv(Oid immv_oid); extern List *PgIvmFuncName(char *name); +extern void parse_immv_query(const char *relname, const char *sql, + Query **query_ret, ParseState **pstate_ret); /* createas.c */ From 7bc34dfa908556c87b62e2ccb9af2315c5bc6710 Mon Sep 17 00:00:00 2001 From: Adam Guo Date: Wed, 26 Mar 2025 22:44:45 +0000 Subject: [PATCH 05/13] Add recreate_all_immvs function. Recreate change prevent triggers during refresh_immv in addition to base table triggers. --- matview.c | 6 +++++- pg_ivm--1.10--1.11.sql | 23 ++++++++++++++++++++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/matview.c b/matview.c index aa95b58..8854124 100644 --- a/matview.c +++ b/matview.c @@ -444,7 +444,8 @@ RefreshImmvByOid(Oid matviewOid, bool is_create, bool skipData, tgform = (Form_pg_trigger) GETSTRUCT(tgtup); /* If trigger is created by IMMV, delete it. */ - if (strncmp(NameStr(tgform->tgname), "IVM_trigger_", 12) == 0) + if (strncmp(NameStr(tgform->tgname), "IVM_trigger_", 12) == 0 || + strncmp(NameStr(tgform->tgname), "IVM_prevent_", 12) == 0) { obj.classId = foundDep->classid; obj.objectId = foundDep->objid; @@ -474,7 +475,10 @@ RefreshImmvByOid(Oid matviewOid, bool is_create, bool skipData, * is created. */ if (!skipData && !oldPopulated) + { CreateIvmTriggersOnBaseTables(dataQuery, matviewOid); + CreateChangePreventTrigger(matviewOid); + } /* * Create the transient table that will receive the regenerated data. Lock diff --git a/pg_ivm--1.10--1.11.sql b/pg_ivm--1.10--1.11.sql index 745f3dc..4f99101 100644 --- a/pg_ivm--1.10--1.11.sql +++ b/pg_ivm--1.10--1.11.sql @@ -1,5 +1,24 @@ ALTER TABLE pgivm.pg_ivm_immv ADD COLUMN querystring text NOT NULL; +CREATE FUNCTION pgivm.recreate_all_immvs() RETURNS VOID LANGUAGE PLPGSQL AS +$$ +BEGIN + PERFORM pgivm.refresh_immv(n.nspname || '.' || c.relname, false) + FROM pgivm.pg_ivm_immv as ivm + JOIN pg_catalog.pg_class as c + ON c.oid = ivm.immvrelid + JOIN pg_catalog.pg_namespace as n + ON c.relnamespace = n.oid; + + PERFORM pgivm.refresh_immv(n.nspname || '.' || c.relname, true) + FROM pgivm.pg_ivm_immv as ivm + JOIN pg_catalog.pg_class as c + ON c.oid = ivm.immvrelid + JOIN pg_catalog.pg_namespace as n + ON c.relnamespace = n.oid; +END +$$; + CREATE FUNCTION pgivm.refresh_query_strings() RETURNS event_trigger LANGUAGE plpgsql SECURITY DEFINER AS $$ @@ -14,7 +33,9 @@ BEGIN UPDATE pgivm.pg_ivm_immv SET querystring = pgivm.get_immv_def(immvrelid); -- Reset search path to the original value. - EXECUTE format('SET search_path = %s', old_search_path); + IF old_search_path != '' AND old_search_path != '""' THEN + EXECUTE format('SET search_path = %s', old_search_path); + END IF; END $$; From 5b95cfddf4cd2711ca98d117e45fbc47bb7e4835 Mon Sep 17 00:00:00 2001 From: Adam Guo Date: Thu, 27 Mar 2025 14:14:05 +0000 Subject: [PATCH 06/13] Formatting, naming, comments --- matview.c | 54 +++++++++++++++++++++++++++++++++++------------------- 1 file changed, 35 insertions(+), 19 deletions(-) diff --git a/matview.c b/matview.c index 8854124..e6db540 100644 --- a/matview.c +++ b/matview.c @@ -233,7 +233,7 @@ static void clean_up_IVM_hash_entry(MV_TriggerHashEntry *entry, bool is_abort, static void setLastUpdateXid(Oid immv_oid, FullTransactionId xid); static FullTransactionId getLastUpdateXid(Oid immv_oid); -static Query *recreate_immv_query(Relation matviewRel); +static Query *update_immv_viewdef(Relation matviewRel); /* SQL callable functions */ PG_FUNCTION_INFO_V1(IVM_immediate_before); @@ -389,7 +389,18 @@ RefreshImmvByOid(Oid matviewOid, bool is_create, bool skipData, systable_endscan(scan); table_close(pgIvmImmv, NoLock); - viewQuery = recreate_immv_query(matviewRel); + /* + * Recreate the Query tree from the query string to account for changing + * base table OIDs (e.g. after dump/restore) or changing format of the Query + * node (after pg_upgrade). + * + * No need to create the Query tree a second time if we are creating a new + * IMMV. + */ + if (is_create) + viewQuery = get_immv_query(matviewRel); + else + viewQuery = update_immv_viewdef(matviewRel); /* For IMMV, we need to rewrite matview query */ if (!skipData) @@ -719,34 +730,36 @@ get_immv_query(Relation matviewRel) } /* - * recreate_immv_query + * update_immv_viewdef * - * Parse the querystring for this IMMV. Update the viewdef column in - * pg_ivm_immv and return the Query tree. + * Read the query string for this IMMV from pg_ivm_immv. Parse the query string + * and update the viewdef column with the new Query tree. Return the Query + * tree. */ static Query * -recreate_immv_query(Relation matviewRel) +update_immv_viewdef(Relation matviewRel) { - Relation pgIvmImmv = table_open(PgIvmImmvRelationId(), AccessShareLock); - TupleDesc tupdesc = RelationGetDescr(pgIvmImmv); - SysScanDesc scan; - ScanKeyData key; - HeapTuple tup; - bool isnull; + CreateTableAsStmt *stmt; Datum datum; - Query *query = NULL; + Datum values[Natts_pg_ivm_immv]; + HeapTuple newtup = NULL; + HeapTuple tup; IntoClause *into; ParseState *pstate = NULL; - CreateTableAsStmt *stmt; - const char *querystring; - const char *relname; - const char *querytree; + Query *query = NULL; + Relation pgIvmImmv = table_open(PgIvmImmvRelationId(), AccessShareLock); + ScanKeyData key; + SysScanDesc scan; + TupleDesc tupdesc = RelationGetDescr(pgIvmImmv); - Datum values[Natts_pg_ivm_immv]; + bool isnull; bool nulls[Natts_pg_ivm_immv]; bool replaces[Natts_pg_ivm_immv]; - HeapTuple newtup = NULL; + const char *querystring; + const char *querytree; + const char *relname; + /* Scan pg_ivm_immv for the given IMMV entry. */ ScanKeyInit(&key, Anum_pg_ivm_immv_immvrelid, BTEqualStrategyNumber, F_OIDEQ, @@ -763,10 +776,12 @@ recreate_immv_query(Relation matviewRel) return NULL; } + /* Read the query string column. */ datum = heap_getattr(tup, Anum_pg_ivm_immv_querystring, tupdesc, &isnull); Assert(!isnull); querystring = TextDatumGetCString(datum); + /* Parse the query string using the same logic as create_immv. */ relname = psprintf("%s.%s", get_namespace_name(get_rel_namespace(matviewRel->rd_id)), get_rel_name(matviewRel->rd_id)); @@ -779,6 +794,7 @@ recreate_immv_query(Relation matviewRel) query = rewriteQueryForIMMV(query, into->colNames); querytree = nodeToString((Node *) query); + /* Update the pg_ivm_immv tuple with the new query tree. */ memset(values, 0, sizeof(values)); memset(nulls, 0, sizeof(nulls)); memset(replaces, 0, sizeof(replaces)); From eefb16763e4a446978c6437e24bc38e19798effb Mon Sep 17 00:00:00 2001 From: Adam Guo Date: Thu, 27 Mar 2025 14:46:07 +0000 Subject: [PATCH 07/13] Add warning to event trigger --- pg_ivm--1.10--1.11.sql | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/pg_ivm--1.10--1.11.sql b/pg_ivm--1.10--1.11.sql index 4f99101..bff5179 100644 --- a/pg_ivm--1.10--1.11.sql +++ b/pg_ivm--1.10--1.11.sql @@ -25,17 +25,25 @@ $$ DECLARE old_search_path text; BEGIN - -- Empty search path so that get_immv_def returns a fully-qualified query. - SELECT setting INTO old_search_path FROM pg_catalog.pg_settings - WHERE name = 'search_path'; - SET search_path = ''; + -- Only need to refresh query strings if an object is renamed. + -- As a rough heuristic, check if this is an ALTER command. + IF tg_tag LIKE 'ALTER %' THEN + -- Empty search path so that get_immv_def returns a fully-qualified query. + SELECT setting INTO old_search_path FROM pg_catalog.pg_settings + WHERE name = 'search_path'; + SET search_path = ''; - UPDATE pgivm.pg_ivm_immv SET querystring = pgivm.get_immv_def(immvrelid); + UPDATE pgivm.pg_ivm_immv SET querystring = pgivm.get_immv_def(immvrelid); - -- Reset search path to the original value. - IF old_search_path != '' AND old_search_path != '""' THEN - EXECUTE format('SET search_path = %s', old_search_path); + -- Reset search path to the original value. + IF old_search_path != '' AND old_search_path != '""' THEN + EXECUTE format('SET search_path = %s', old_search_path); + END IF; END IF; +EXCEPTION + WHEN internal_error THEN + RAISE WARNING 'pg_ivm could not refresh the pg_ivm_immv query strings.' + USING HINT = 'Please recreate your IMMVs using pgivm.recreate_all_immvs().'; END $$; From 1901c99f2169670aac8a82d88c176e7db6ec8172 Mon Sep 17 00:00:00 2001 From: Adam Guo Date: Thu, 27 Mar 2025 18:15:22 +0000 Subject: [PATCH 08/13] Rollback GUC change in createas.c --- createas.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/createas.c b/createas.c index 026cce3..338650d 100644 --- a/createas.c +++ b/createas.c @@ -1712,6 +1712,7 @@ StoreImmvQuery(Oid viewOid, Query *viewQuery) { char *querytree = nodeToString((Node *) viewQuery); char *querystring; + int save_nestlevel; Datum values[Natts_pg_ivm_immv]; bool isNulls[Natts_pg_ivm_immv]; Relation matviewRel; @@ -1720,10 +1721,17 @@ StoreImmvQuery(Oid viewOid, Query *viewQuery) HeapTuple heapTuple; ObjectAddress address; + /* + * Restrict search_path so that pg_ivm_get_viewdef_internal returns a + * fully-qualified query. + */ + save_nestlevel = NewGUCNestLevel(); RestrictSearchPath(); matviewRel = table_open(viewOid, AccessShareLock); querystring = pg_ivm_get_viewdef_internal(viewQuery, matviewRel, true); table_close(matviewRel, NoLock); + /* Roll back the search_path change. */ + AtEOXact_GUC(false, save_nestlevel); memset(values, 0, sizeof(values)); memset(isNulls, false, sizeof(isNulls)); From 38777f260aaf1eb8142a2f8f5db1b11accdbd4f7 Mon Sep 17 00:00:00 2001 From: Adam Guo Date: Tue, 20 May 2025 19:16:55 +0000 Subject: [PATCH 09/13] Event trigger --- Makefile | 3 +- event_trigger.c | 237 +++++++++++++++++++++++++++++++++++++++++ pg_ivm--1.10--1.11.sql | 36 ++----- pg_ivm.h | 5 + 4 files changed, 253 insertions(+), 28 deletions(-) create mode 100644 event_trigger.c diff --git a/Makefile b/Makefile index e225eaa..8ff11d3 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,8 @@ OBJS = \ matview.o \ pg_ivm.o \ ruleutils.o \ - subselect.o + subselect.o \ + event_trigger.o PGFILEDESC = "pg_ivm - incremental view maintenance on PostgreSQL" EXTENSION = pg_ivm diff --git a/event_trigger.c b/event_trigger.c new file mode 100644 index 0000000..d23b4ac --- /dev/null +++ b/event_trigger.c @@ -0,0 +1,237 @@ +#include "postgres.h" +#include "pg_ivm.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 + +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); + +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(); +} + +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(); +} + +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); +} + +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); +} + +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; +} + +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); + + /* Store entry for this IMMV. */ + hash_query(matview_oid, nodeToString(query)); + } + + systable_endscan(scan); + table_close(pg_ivm_immv_rel, NoLock); +} + +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); + query = (Query *) ((CreateTableAsStmt *) query->utilityStmt)->into->viewQuery; + + 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); +} diff --git a/pg_ivm--1.10--1.11.sql b/pg_ivm--1.10--1.11.sql index bff5179..c5aeaca 100644 --- a/pg_ivm--1.10--1.11.sql +++ b/pg_ivm--1.10--1.11.sql @@ -19,34 +19,16 @@ BEGIN END $$; -CREATE FUNCTION pgivm.refresh_query_strings() -RETURNS event_trigger LANGUAGE plpgsql SECURITY DEFINER AS -$$ -DECLARE - old_search_path text; -BEGIN - -- Only need to refresh query strings if an object is renamed. - -- As a rough heuristic, check if this is an ALTER command. - IF tg_tag LIKE 'ALTER %' THEN - -- Empty search path so that get_immv_def returns a fully-qualified query. - SELECT setting INTO old_search_path FROM pg_catalog.pg_settings - WHERE name = 'search_path'; - SET search_path = ''; +CREATE FUNCTION pgivm.save_query_strings() RETURNS event_trigger +AS 'MODULE_PATHNAME', 'save_query_strings' LANGUAGE C; - UPDATE pgivm.pg_ivm_immv SET querystring = pgivm.get_immv_def(immvrelid); +CREATE FUNCTION pgivm.restore_query_strings() RETURNS event_trigger +AS 'MODULE_PATHNAME', 'restore_query_strings' LANGUAGE C; - -- Reset search path to the original value. - IF old_search_path != '' AND old_search_path != '""' THEN - EXECUTE format('SET search_path = %s', old_search_path); - END IF; - END IF; -EXCEPTION - WHEN internal_error THEN - RAISE WARNING 'pg_ivm could not refresh the pg_ivm_immv query strings.' - USING HINT = 'Please recreate your IMMVs using pgivm.recreate_all_immvs().'; -END -$$; +CREATE EVENT TRIGGER save_query_strings +ON ddl_command_start +EXECUTE FUNCTION pgivm.save_query_strings(); -CREATE EVENT TRIGGER refresh_query_strings +CREATE EVENT TRIGGER restore_query_strings ON ddl_command_end -EXECUTE FUNCTION pgivm.refresh_query_strings(); +EXECUTE FUNCTION pgivm.restore_query_strings(); diff --git a/pg_ivm.h b/pg_ivm.h index 04c83f0..c8b29c6 100644 --- a/pg_ivm.h +++ b/pg_ivm.h @@ -72,6 +72,11 @@ extern char *pg_ivm_get_viewdef_internal(Query *query, Relation immvrel, bool pr /* subselect.c */ extern void inline_cte(PlannerInfo *root, CommonTableExpr *cte); +/* event_trigger.c */ + +extern Datum save_query_string(PG_FUNCTION_ARGS); +extern Datum restore_query_string(PG_FUNCTION_ARGS); + #if defined(PG_VERSION_NUM) && (PG_VERSION_NUM < 170000) extern void RestrictSearchPath(void); #endif From 5a31d7a9ad1901ec7edab66cbbd52ea0e7057357 Mon Sep 17 00:00:00 2001 From: Adam Guo Date: Mon, 19 May 2025 16:22:52 +0000 Subject: [PATCH 10/13] Add UUID column to pg_ivm_immv. --- createas.c | 89 +++++++++++++++++++++++------------------- matview.c | 29 +++++++++----- pg_ivm--1.10--1.11.sql | 2 + pg_ivm.c | 86 ++++++++++++++++++++++++++++++++++++++++ pg_ivm.h | 17 ++++++-- 5 files changed, 169 insertions(+), 54 deletions(-) diff --git a/createas.c b/createas.c index 338650d..10a09c0 100644 --- a/createas.c +++ b/createas.c @@ -62,8 +62,8 @@ typedef struct } DR_intorel; /* utility functions for IMMV definition creation */ -static ObjectAddress create_immv_internal(List *attrList, IntoClause *into); -static ObjectAddress create_immv_nodata(List *tlist, IntoClause *into); +static ImmvAddress create_immv_internal(List *attrList, IntoClause *into); +static ImmvAddress create_immv_nodata(List *tlist, IntoClause *into); typedef struct { @@ -74,15 +74,15 @@ typedef struct int sublevels_up; /* (current) nesting depth */ } check_ivm_restriction_context; -static void CreateIvmTriggersOnBaseTablesRecurse(Query *qry, Node *node, Oid matviewOid, +static void CreateIvmTriggersOnBaseTablesRecurse(Query *qry, Node *node, ImmvAddress immv_addr, Relids *relids, bool ex_lock); -static void CreateIvmTrigger(Oid relOid, Oid viewOid, int16 type, int16 timing, bool ex_lock); +static void CreateIvmTrigger(Oid relOid, ImmvAddress immv_addr, int16 type, int16 timing, bool ex_lock); static void check_ivm_restriction(Node *node); static bool check_ivm_restriction_walker(Node *node, check_ivm_restriction_context *context); static Bitmapset *get_primary_key_attnos_from_query(Query *query, List **constraintList); static bool check_aggregate_supports_ivm(Oid aggfnoid); -static void StoreImmvQuery(Oid viewOid, Query *viewQuery); +static void StoreImmvQuery(ImmvAddress immv_addr, Query *viewQuery); #if defined(PG_VERSION_NUM) && (PG_VERSION_NUM < 140000) static bool CreateTableAsRelExists(CreateTableAsStmt *ctas); @@ -96,14 +96,15 @@ static bool CreateTableAsRelExists(CreateTableAsStmt *ctas); * * This imitates PostgreSQL's create_ctas_internal(). */ -static ObjectAddress +static ImmvAddress create_immv_internal(List *attrList, IntoClause *into) { CreateStmt *create = makeNode(CreateStmt); char relkind; Datum toast_options; static char *validnsps[] = HEAP_RELOPT_NAMESPACES; - ObjectAddress intoRelationAddr; + ImmvAddress immv_addr; + pg_uuid_t *immv_uuid; /* This code supports both CREATE TABLE AS and CREATE MATERIALIZED VIEW */ /* relkind of IMMV must be RELKIND_RELATION */ @@ -128,7 +129,12 @@ create_immv_internal(List *attrList, IntoClause *into) * Create the relation. (This will error out if there's an existing view, * so we don't need more code to complain if "replace" is false.) */ - intoRelationAddr = DefineRelation(create, relkind, InvalidOid, NULL, NULL); + immv_addr.address = DefineRelation(create, relkind, InvalidOid, NULL, NULL); + /* Generate the IMMV UUID. */ + // TODO: check for hash collision + immv_uuid = DatumGetUUIDP(DirectFunctionCall1(gen_random_uuid, (Datum) NULL)); + memcpy(&immv_addr.immv_uuid, immv_uuid, sizeof(*immv_uuid)); + pfree(immv_uuid); /* * If necessary, create a TOAST table for the target table. Note that @@ -146,13 +152,13 @@ create_immv_internal(List *attrList, IntoClause *into) (void) heap_reloptions(RELKIND_TOASTVALUE, toast_options, true); - NewRelationCreateToastTable(intoRelationAddr.objectId, toast_options); + NewRelationCreateToastTable(immv_addr.address.objectId, toast_options); /* Create the "view" part of an IMMV. */ - StoreImmvQuery(intoRelationAddr.objectId, (Query *) into->viewQuery); + StoreImmvQuery(immv_addr, (Query *) into->viewQuery); CommandCounterIncrement(); - return intoRelationAddr; + return immv_addr; } /* @@ -163,7 +169,7 @@ create_immv_internal(List *attrList, IntoClause *into) * * This imitates PostgreSQL's create_ctas_nodata(). */ -static ObjectAddress +static ImmvAddress create_immv_nodata(List *tlist, IntoClause *into) { List *attrList; @@ -240,7 +246,7 @@ ExecCreateImmv(ParseState *pstate, CreateTableAsStmt *stmt, Query *query = castNode(Query, stmt->query); IntoClause *into = stmt->into; bool do_refresh = false; - ObjectAddress address; + ImmvAddress immv_addr; /* Check if the relation exists or not */ if (CreateTableAsRelExists(stmt)) @@ -275,7 +281,7 @@ ExecCreateImmv(ParseState *pstate, CreateTableAsStmt *stmt, * similar to CREATE VIEW. This avoids dump/restore problems stemming * from running the planner before all dependencies are set up. */ - address = create_immv_nodata(query->targetList, into); + immv_addr = create_immv_nodata(query->targetList, into); /* * For materialized views, reuse the REFRESH logic, which locks down @@ -286,18 +292,18 @@ ExecCreateImmv(ParseState *pstate, CreateTableAsStmt *stmt, { Relation matviewRel; - RefreshImmvByOid(address.objectId, true, false, pstate->p_sourcetext, qc); + RefreshImmvByOid(immv_addr, true, false, pstate->p_sourcetext, qc); if (qc) qc->commandTag = CMDTAG_SELECT; - matviewRel = table_open(address.objectId, NoLock); + matviewRel = table_open(immv_addr.address.objectId, NoLock); /* Create an index on incremental maintainable materialized view, if possible */ CreateIndexOnIMMV(query, matviewRel); /* Create triggers to prevent IMMV from being changed */ - CreateChangePreventTrigger(address.objectId); + CreateChangePreventTrigger(immv_addr.address.objectId); table_close(matviewRel, NoLock); @@ -309,7 +315,7 @@ ExecCreateImmv(ParseState *pstate, CreateTableAsStmt *stmt, "or execute refresh_immv to make sure the view is consistent."))); } - return address; + return immv_addr.address; } /* @@ -548,7 +554,7 @@ makeIvmAggColumn(ParseState *pstate, Aggref *aggref, char *resname, AttrNumber * * CreateIvmTriggersOnBaseTables -- create IVM triggers on all base tables */ void -CreateIvmTriggersOnBaseTables(Query *qry, Oid matviewOid) +CreateIvmTriggersOnBaseTables(Query *qry, ImmvAddress immv_addr) { Relids relids = NULL; bool ex_lock = false; @@ -582,13 +588,13 @@ CreateIvmTriggersOnBaseTables(Query *qry, Oid matviewOid) qry->distinctClause || (qry->hasAggs && qry->groupClause)) ex_lock = true; - CreateIvmTriggersOnBaseTablesRecurse(qry, (Node *)qry, matviewOid, &relids, ex_lock); + CreateIvmTriggersOnBaseTablesRecurse(qry, (Node *)qry, immv_addr, &relids, ex_lock); bms_free(relids); } static void -CreateIvmTriggersOnBaseTablesRecurse(Query *qry, Node *node, Oid matviewOid, +CreateIvmTriggersOnBaseTablesRecurse(Query *qry, Node *node, ImmvAddress immv_addr, Relids *relids, bool ex_lock) { if (node == NULL) @@ -604,12 +610,12 @@ CreateIvmTriggersOnBaseTablesRecurse(Query *qry, Node *node, Oid matviewOid, Query *query = (Query *) node; ListCell *lc; - CreateIvmTriggersOnBaseTablesRecurse(qry, (Node *) query->jointree, matviewOid, relids, ex_lock); + CreateIvmTriggersOnBaseTablesRecurse(qry, (Node *) query->jointree, immv_addr, relids, ex_lock); foreach(lc, query->cteList) { CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc); Assert(IsA(cte->ctequery, Query)); - CreateIvmTriggersOnBaseTablesRecurse((Query *) cte->ctequery, cte->ctequery, matviewOid, relids, ex_lock); + CreateIvmTriggersOnBaseTablesRecurse((Query *) cte->ctequery, cte->ctequery, immv_addr, relids, ex_lock); } } break; @@ -621,14 +627,14 @@ CreateIvmTriggersOnBaseTablesRecurse(Query *qry, Node *node, Oid matviewOid, if (rte->rtekind == RTE_RELATION && !bms_is_member(rte->relid, *relids)) { - CreateIvmTrigger(rte->relid, matviewOid, TRIGGER_TYPE_INSERT, TRIGGER_TYPE_BEFORE, ex_lock); - CreateIvmTrigger(rte->relid, matviewOid, TRIGGER_TYPE_DELETE, TRIGGER_TYPE_BEFORE, ex_lock); - CreateIvmTrigger(rte->relid, matviewOid, TRIGGER_TYPE_UPDATE, TRIGGER_TYPE_BEFORE, ex_lock); - CreateIvmTrigger(rte->relid, matviewOid, TRIGGER_TYPE_TRUNCATE, TRIGGER_TYPE_BEFORE, true); - CreateIvmTrigger(rte->relid, matviewOid, TRIGGER_TYPE_INSERT, TRIGGER_TYPE_AFTER, ex_lock); - CreateIvmTrigger(rte->relid, matviewOid, TRIGGER_TYPE_DELETE, TRIGGER_TYPE_AFTER, ex_lock); - CreateIvmTrigger(rte->relid, matviewOid, TRIGGER_TYPE_UPDATE, TRIGGER_TYPE_AFTER, ex_lock); - CreateIvmTrigger(rte->relid, matviewOid, TRIGGER_TYPE_TRUNCATE, TRIGGER_TYPE_AFTER, true); + CreateIvmTrigger(rte->relid, immv_addr, TRIGGER_TYPE_INSERT, TRIGGER_TYPE_BEFORE, ex_lock); + CreateIvmTrigger(rte->relid, immv_addr, TRIGGER_TYPE_DELETE, TRIGGER_TYPE_BEFORE, ex_lock); + CreateIvmTrigger(rte->relid, immv_addr, TRIGGER_TYPE_UPDATE, TRIGGER_TYPE_BEFORE, ex_lock); + CreateIvmTrigger(rte->relid, immv_addr, TRIGGER_TYPE_TRUNCATE, TRIGGER_TYPE_BEFORE, true); + CreateIvmTrigger(rte->relid, immv_addr, TRIGGER_TYPE_INSERT, TRIGGER_TYPE_AFTER, ex_lock); + CreateIvmTrigger(rte->relid, immv_addr, TRIGGER_TYPE_DELETE, TRIGGER_TYPE_AFTER, ex_lock); + CreateIvmTrigger(rte->relid, immv_addr, TRIGGER_TYPE_UPDATE, TRIGGER_TYPE_AFTER, ex_lock); + CreateIvmTrigger(rte->relid, immv_addr, TRIGGER_TYPE_TRUNCATE, TRIGGER_TYPE_AFTER, true); *relids = bms_add_member(*relids, rte->relid); } @@ -636,7 +642,7 @@ CreateIvmTriggersOnBaseTablesRecurse(Query *qry, Node *node, Oid matviewOid, { Query *subquery = rte->subquery; Assert(rte->subquery != NULL); - CreateIvmTriggersOnBaseTablesRecurse(subquery, (Node *) subquery, matviewOid, relids, ex_lock); + CreateIvmTriggersOnBaseTablesRecurse(subquery, (Node *) subquery, immv_addr, relids, ex_lock); } } break; @@ -647,7 +653,7 @@ CreateIvmTriggersOnBaseTablesRecurse(Query *qry, Node *node, Oid matviewOid, ListCell *l; foreach(l, f->fromlist) - CreateIvmTriggersOnBaseTablesRecurse(qry, lfirst(l), matviewOid, relids, ex_lock); + CreateIvmTriggersOnBaseTablesRecurse(qry, lfirst(l), immv_addr, relids, ex_lock); } break; @@ -655,8 +661,8 @@ CreateIvmTriggersOnBaseTablesRecurse(Query *qry, Node *node, Oid matviewOid, { JoinExpr *j = (JoinExpr *) node; - CreateIvmTriggersOnBaseTablesRecurse(qry, j->larg, matviewOid, relids, ex_lock); - CreateIvmTriggersOnBaseTablesRecurse(qry, j->rarg, matviewOid, relids, ex_lock); + CreateIvmTriggersOnBaseTablesRecurse(qry, j->larg, immv_addr, relids, ex_lock); + CreateIvmTriggersOnBaseTablesRecurse(qry, j->rarg, immv_addr, relids, ex_lock); } break; @@ -669,7 +675,7 @@ CreateIvmTriggersOnBaseTablesRecurse(Query *qry, Node *node, Oid matviewOid, * CreateIvmTrigger -- create IVM trigger on a base table */ static void -CreateIvmTrigger(Oid relOid, Oid viewOid, int16 type, int16 timing, bool ex_lock) +CreateIvmTrigger(Oid relOid, ImmvAddress immv_addr, int16 type, int16 timing, bool ex_lock) { ObjectAddress refaddr; ObjectAddress address; @@ -679,7 +685,7 @@ CreateIvmTrigger(Oid relOid, Oid viewOid, int16 type, int16 timing, bool ex_lock Assert(timing == TRIGGER_TYPE_BEFORE || timing == TRIGGER_TYPE_AFTER); refaddr.classId = RelationRelationId; - refaddr.objectId = viewOid; + refaddr.objectId = immv_addr.address.objectId; refaddr.objectSubId = 0; ivm_trigger = makeNode(CreateTrigStmt); @@ -753,7 +759,7 @@ CreateIvmTrigger(Oid relOid, Oid viewOid, int16 type, int16 timing, bool ex_lock ivm_trigger->initdeferred = false; ivm_trigger->constrrel = NULL; ivm_trigger->args = list_make2( - makeString(DatumGetPointer(DirectFunctionCall1(oidout, ObjectIdGetDatum(viewOid)))), + makeString(DatumGetPointer(UUIDPGetDatum(&immv_addr.immv_uuid))), makeString(DatumGetPointer(DirectFunctionCall1(boolout, BoolGetDatum(ex_lock)))) ); @@ -1708,7 +1714,7 @@ get_primary_key_attnos_from_query(Query *query, List **constraintList) * Store the query for the IMMV to pg_ivm_immv */ static void -StoreImmvQuery(Oid viewOid, Query *viewQuery) +StoreImmvQuery(ImmvAddress immv_addr, Query *viewQuery) { char *querytree = nodeToString((Node *) viewQuery); char *querystring; @@ -1736,10 +1742,11 @@ StoreImmvQuery(Oid viewOid, Query *viewQuery) memset(values, 0, sizeof(values)); memset(isNulls, false, sizeof(isNulls)); - values[Anum_pg_ivm_immv_immvrelid -1 ] = ObjectIdGetDatum(viewOid); + values[Anum_pg_ivm_immv_immvrelid -1 ] = ObjectIdGetDatum(immv_addr.address.objectId); values[Anum_pg_ivm_immv_ispopulated -1 ] = BoolGetDatum(false); values[Anum_pg_ivm_immv_viewdef -1 ] = CStringGetTextDatum(querytree); values[Anum_pg_ivm_immv_querystring - 1] = CStringGetTextDatum(querystring); + values[Anum_pg_ivm_immv_immvuuid -1 ] = UUIDPGetDatum(&immv_addr.immv_uuid); pgIvmImmv = table_open(PgIvmImmvRelationId(), RowExclusiveLock); @@ -1749,7 +1756,7 @@ StoreImmvQuery(Oid viewOid, Query *viewQuery) CatalogTupleInsert(pgIvmImmv, heapTuple); address.classId = RelationRelationId; - address.objectId = viewOid; + address.objectId = immv_addr.address.objectId; address.objectSubId = 0; recordDependencyOnExpr(&address, (Node *) viewQuery, NIL, diff --git a/matview.c b/matview.c index e6db540..b42001c 100644 --- a/matview.c +++ b/matview.c @@ -50,6 +50,7 @@ #include "utils/rel.h" #include "utils/snapmgr.h" #include "utils/typcache.h" +#include "utils/uuid.h" #include "utils/xid8.h" #include "pg_ivm.h" @@ -251,6 +252,7 @@ ExecRefreshImmv(const RangeVar *relation, bool skipData, { Oid matviewOid; LOCKMODE lockmode; + ImmvAddress immv_addr; /* Determine strength of lock needed. */ //concurrent = stmt->concurrent; @@ -272,7 +274,12 @@ ExecRefreshImmv(const RangeVar *relation, bool skipData, NULL); #endif - return RefreshImmvByOid(matviewOid, false, skipData, queryString, qc); + immv_addr.address.classId = RelationRelationId; + immv_addr.address.objectId = matviewOid; + immv_addr.address.objectSubId = 0; + + // TODO: get uuid + return RefreshImmvByOid(immv_addr, false, skipData, queryString, qc); } /* @@ -283,7 +290,7 @@ ExecRefreshImmv(const RangeVar *relation, bool skipData, * This imitates PostgreSQL's RefreshMatViewByOid(). */ ObjectAddress -RefreshImmvByOid(Oid matviewOid, bool is_create, bool skipData, +RefreshImmvByOid(ImmvAddress immv_addr, bool is_create, bool skipData, const char *queryString, QueryCompletion *qc) { Relation matviewRel; @@ -300,6 +307,7 @@ RefreshImmvByOid(Oid matviewOid, bool is_create, bool skipData, int save_nestlevel; ObjectAddress address; bool oldPopulated; + Oid matviewOid = immv_addr.address.objectId; Relation pgIvmImmv; TupleDesc tupdesc; @@ -486,10 +494,7 @@ RefreshImmvByOid(Oid matviewOid, bool is_create, bool skipData, * is created. */ if (!skipData && !oldPopulated) - { - CreateIvmTriggersOnBaseTables(dataQuery, matviewOid); - CreateChangePreventTrigger(matviewOid); - } + CreateIvmTriggersOnBaseTables(dataQuery, immv_addr); /* * Create the transient table that will receive the regenerated data. Lock @@ -844,15 +849,17 @@ Datum IVM_immediate_before(PG_FUNCTION_ARGS) { TriggerData *trigdata = (TriggerData *) fcinfo->context; - char *matviewOid_text = trigdata->tg_trigger->tgargs[0]; + char *immv_uuid_text = trigdata->tg_trigger->tgargs[0]; char *ex_lock_text = trigdata->tg_trigger->tgargs[1]; + pg_uuid_t *immv_uuid; Oid matviewOid; MV_TriggerHashEntry *entry; bool found; bool ex_lock; - matviewOid = DatumGetObjectId(DirectFunctionCall1(oidin, CStringGetDatum(matviewOid_text))); + immv_uuid = DatumGetUUIDP(CStringGetDatum(immv_uuid_text)); ex_lock = DatumGetBool(DirectFunctionCall1(boolin, CStringGetDatum(ex_lock_text))); + matviewOid = GetImmvRelid(immv_uuid); /* If the view has more than one tables, we have to use an exclusive lock. */ if (ex_lock) @@ -967,10 +974,11 @@ IVM_immediate_maintenance(PG_FUNCTION_ARGS) Oid matviewOid; Query *query; Query *rewritten = NULL; - char *matviewOid_text = trigdata->tg_trigger->tgargs[0]; + char *immv_uuid_text = trigdata->tg_trigger->tgargs[0]; Relation matviewRel; int old_depth = immv_maintenance_depth; SubTransactionId subxid; + pg_uuid_t *immv_uuid; Oid relowner; Tuplestorestate *old_tuplestore = NULL; @@ -998,7 +1006,8 @@ IVM_immediate_maintenance(PG_FUNCTION_ARGS) rel = trigdata->tg_relation; relid = rel->rd_id; - matviewOid = DatumGetObjectId(DirectFunctionCall1(oidin, CStringGetDatum(matviewOid_text))); + immv_uuid = DatumGetUUIDP(CStringGetDatum(immv_uuid_text)); + matviewOid = GetImmvRelid(immv_uuid); /* * On the first call initialize the hashtable diff --git a/pg_ivm--1.10--1.11.sql b/pg_ivm--1.10--1.11.sql index c5aeaca..1e718de 100644 --- a/pg_ivm--1.10--1.11.sql +++ b/pg_ivm--1.10--1.11.sql @@ -1,4 +1,6 @@ ALTER TABLE pgivm.pg_ivm_immv ADD COLUMN querystring text NOT NULL; +ALTER TABLE pgivm.pg_ivm_immv ADD COLUMN immvuuid uuid NOT NULL; +ALTER TABLE pgivm.pg_ivm_immv ADD CONSTRAINT pg_ivm_immv_uuid UNIQUE (immvuuid); CREATE FUNCTION pgivm.recreate_all_immvs() RETURNS VOID LANGUAGE PLPGSQL AS $$ diff --git a/pg_ivm.c b/pg_ivm.c index a005e07..6b8e1d4 100644 --- a/pg_ivm.c +++ b/pg_ivm.c @@ -32,6 +32,7 @@ #include "utils/lsyscache.h" #include "utils/regproc.h" #include "utils/rel.h" +#include "utils/uuid.h" #include "utils/varlena.h" #include "pg_ivm.h" @@ -365,6 +366,17 @@ PgIvmImmvPrimaryKeyIndexId(void) AccessShareLock, true); } +/* + * Get relid of pg_ivm_immv's UUID unique key. + */ +Oid +PgIvmImmvUuidIndexId(void) +{ + return RangeVarGetRelid( + makeRangeVar("pgivm", "pg_ivm_immv_uuid", -1), + AccessShareLock, true); +} + /* * Return the SELECT part of an IMMV */ @@ -488,3 +500,77 @@ RestrictSearchPath(void) PGC_S_SESSION, GUC_ACTION_SAVE, true, 0, false); } #endif + +/* + * GetImmvOid + * + * Look up the immvrelid of an IMMV from its immv_uuid. + */ +Oid +GetImmvRelid(pg_uuid_t *immv_uuid) +{ + Datum datum; + HeapTuple tup; + Relation pgIvmImmv = table_open(PgIvmImmvRelationId(), AccessShareLock); + ScanKeyData key; + SysScanDesc scan; + TupleDesc tupdesc = RelationGetDescr(pgIvmImmv); + bool isnull; + + ScanKeyInit(&key, Anum_pg_ivm_immv_immvuuid, BTEqualStrategyNumber, + F_UUID_EQ, UUIDPGetDatum(immv_uuid)); + scan = systable_beginscan(pgIvmImmv, PgIvmImmvUuidIndexId(), true, NULL, 1, + &key); + tup = systable_getnext(scan); + + if (!HeapTupleIsValid(tup)) + { + systable_endscan(scan); + table_close(pgIvmImmv, NoLock); + return InvalidOid; + } + + datum = heap_getattr(tup, Anum_pg_ivm_immv_immvrelid, tupdesc, &isnull); + Assert(!isnull); + + systable_endscan(scan); + table_close(pgIvmImmv, NoLock); + return DatumGetObjectId(datum); +} + +/* + * GetImmvUuid + * + * Look up the immv_uuid of an IMMV from its immvrelid. + */ +pg_uuid_t * +GetImmvUuid(Oid immvrelid) +{ + Datum datum; + HeapTuple tup; + Relation pgIvmImmv = table_open(PgIvmImmvRelationId(), AccessShareLock); + ScanKeyData key; + SysScanDesc scan; + TupleDesc tupdesc = RelationGetDescr(pgIvmImmv); + bool isnull; + + ScanKeyInit(&key, Anum_pg_ivm_immv_immvrelid, BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(immvrelid)); + scan = systable_beginscan(pgIvmImmv, PgIvmImmvPrimaryKeyIndexId(), + true, NULL, 1, &key); + tup = systable_getnext(scan); + + if (!HeapTupleIsValid(tup)) + { + systable_endscan(scan); + table_close(pgIvmImmv, NoLock); + return NULL; + } + + datum = heap_getattr(tup, Anum_pg_ivm_immv_immvuuid, tupdesc, &isnull); + Assert(!isnull); + + systable_endscan(scan); + table_close(pgIvmImmv, NoLock); + return DatumGetUUIDP(datum); +} diff --git a/pg_ivm.h b/pg_ivm.h index c8b29c6..d8f80f9 100644 --- a/pg_ivm.h +++ b/pg_ivm.h @@ -19,30 +19,41 @@ #include "parser/parse_node.h" #include "tcop/dest.h" #include "utils/queryenvironment.h" +#include "utils/uuid.h" -#define Natts_pg_ivm_immv 5 +#define Natts_pg_ivm_immv 6 #define Anum_pg_ivm_immv_immvrelid 1 #define Anum_pg_ivm_immv_viewdef 2 #define Anum_pg_ivm_immv_ispopulated 3 #define Anum_pg_ivm_immv_lastivmupdate 4 #define Anum_pg_ivm_immv_querystring 5 +#define Anum_pg_ivm_immv_immvuuid 6 + +typedef struct ImmvAddress +{ + ObjectAddress address; + pg_uuid_t immv_uuid; +} ImmvAddress; /* pg_ivm.c */ extern void CreateChangePreventTrigger(Oid matviewOid); extern Oid PgIvmImmvRelationId(void); extern Oid PgIvmImmvPrimaryKeyIndexId(void); +extern Oid PgIvmImmvUuidIndexId(void); extern bool isImmv(Oid immv_oid); extern List *PgIvmFuncName(char *name); extern void parse_immv_query(const char *relname, const char *sql, Query **query_ret, ParseState **pstate_ret); +extern Oid GetImmvRelid(pg_uuid_t *immv_uuid); +extern pg_uuid_t *GetImmvUuid(Oid immvrelid); /* createas.c */ extern ObjectAddress ExecCreateImmv(ParseState *pstate, CreateTableAsStmt *stmt, QueryCompletion *qc); -extern void CreateIvmTriggersOnBaseTables(Query *qry, Oid matviewOid); +extern void CreateIvmTriggersOnBaseTables(Query *qry, ImmvAddress immv_addr); extern void CreateIndexOnIMMV(Query *query, Relation matviewRel); extern Query *rewriteQueryForIMMV(Query *query, List *colNames); extern void makeIvmAggColumn(ParseState *pstate, Aggref *aggref, char *resname, AttrNumber *next_resno, List **aggs); @@ -52,7 +63,7 @@ extern void makeIvmAggColumn(ParseState *pstate, Aggref *aggref, char *resname, extern Query *get_immv_query(Relation matviewRel); extern ObjectAddress ExecRefreshImmv(const RangeVar *relation, bool skipData, const char *queryString, QueryCompletion *qc); -extern ObjectAddress RefreshImmvByOid(Oid matviewOid, bool is_create, bool skipData, +extern ObjectAddress RefreshImmvByOid(ImmvAddress immv_addr, bool is_create, bool skipData, const char *queryString, QueryCompletion *qc); extern bool ImmvIncrementalMaintenanceIsEnabled(void); extern Datum IVM_immediate_before(PG_FUNCTION_ARGS); From 0be0a58bc902b0733e04387824b0ee1d58fbf492 Mon Sep 17 00:00:00 2001 From: Adam Guo Date: Tue, 20 May 2025 23:46:39 +0000 Subject: [PATCH 11/13] External triggers --- createas.c | 8 +++++--- event_trigger.c | 1 + matview.c | 4 ++-- pg_ivm.c | 5 +++-- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/createas.c b/createas.c index 10a09c0..3385255 100644 --- a/createas.c +++ b/createas.c @@ -747,6 +747,8 @@ CreateIvmTrigger(Oid relOid, ImmvAddress immv_addr, int16 type, int16 timing, bo if (type == TRIGGER_TYPE_DELETE || type == TRIGGER_TYPE_UPDATE) ex_lock = true; + ivm_trigger->trigname = psprintf("%s_%d_%d", ivm_trigger->trigname, relOid, + immv_addr.address.objectId); ivm_trigger->funcname = (timing == TRIGGER_TYPE_BEFORE ? PgIvmFuncName("IVM_immediate_before") : PgIvmFuncName("IVM_immediate_maintenance")); @@ -759,12 +761,12 @@ CreateIvmTrigger(Oid relOid, ImmvAddress immv_addr, int16 type, int16 timing, bo ivm_trigger->initdeferred = false; ivm_trigger->constrrel = NULL; ivm_trigger->args = list_make2( - makeString(DatumGetPointer(UUIDPGetDatum(&immv_addr.immv_uuid))), + makeString(DatumGetPointer(DirectFunctionCall1(uuid_out, UUIDPGetDatum(&immv_addr.immv_uuid)))), makeString(DatumGetPointer(DirectFunctionCall1(boolout, BoolGetDatum(ex_lock)))) ); address = CreateTrigger(ivm_trigger, NULL, relOid, InvalidOid, InvalidOid, - InvalidOid, InvalidOid, InvalidOid, NULL, true, false); + InvalidOid, InvalidOid, InvalidOid, NULL, false, false); recordDependencyOn(&address, &refaddr, DEPENDENCY_AUTO); @@ -1733,7 +1735,7 @@ StoreImmvQuery(ImmvAddress immv_addr, Query *viewQuery) */ save_nestlevel = NewGUCNestLevel(); RestrictSearchPath(); - matviewRel = table_open(viewOid, AccessShareLock); + matviewRel = table_open(immv_addr.address.objectId, AccessShareLock); querystring = pg_ivm_get_viewdef_internal(viewQuery, matviewRel, true); table_close(matviewRel, NoLock); /* Roll back the search_path change. */ diff --git a/event_trigger.c b/event_trigger.c index d23b4ac..77685f8 100644 --- a/event_trigger.c +++ b/event_trigger.c @@ -1,6 +1,7 @@ #include "postgres.h" #include "pg_ivm.h" +#include "access/genam.h" #include "access/table.h" #include "catalog/indexing.h" #include "commands/event_trigger.h" diff --git a/matview.c b/matview.c index b42001c..cfce832 100644 --- a/matview.c +++ b/matview.c @@ -857,7 +857,7 @@ IVM_immediate_before(PG_FUNCTION_ARGS) bool found; bool ex_lock; - immv_uuid = DatumGetUUIDP(CStringGetDatum(immv_uuid_text)); + immv_uuid = DatumGetUUIDP(DirectFunctionCall1(uuid_in, (CStringGetDatum(immv_uuid_text)))); ex_lock = DatumGetBool(DirectFunctionCall1(boolin, CStringGetDatum(ex_lock_text))); matviewOid = GetImmvRelid(immv_uuid); @@ -1006,7 +1006,7 @@ IVM_immediate_maintenance(PG_FUNCTION_ARGS) rel = trigdata->tg_relation; relid = rel->rd_id; - immv_uuid = DatumGetUUIDP(CStringGetDatum(immv_uuid_text)); + immv_uuid = DatumGetUUIDP(DirectFunctionCall1(uuid_in, (CStringGetDatum(immv_uuid_text)))); matviewOid = GetImmvRelid(immv_uuid); /* diff --git a/pg_ivm.c b/pg_ivm.c index 6b8e1d4..88c5841 100644 --- a/pg_ivm.c +++ b/pg_ivm.c @@ -320,7 +320,6 @@ CreateChangePreventTrigger(Oid matviewOid) ivm_trigger->row = false; ivm_trigger->timing = TRIGGER_TYPE_BEFORE; - ivm_trigger->trigname = "IVM_prevent_immv_change"; ivm_trigger->funcname = PgIvmFuncName("IVM_prevent_immv_change"); ivm_trigger->columns = NIL; ivm_trigger->transitionRels = NIL; @@ -334,8 +333,10 @@ CreateChangePreventTrigger(Oid matviewOid) for (i = 0; i < 4; i++) { ivm_trigger->events = types[i]; + ivm_trigger->trigname = psprintf("IVM_prevent_immv_change_%d_%d", + matviewOid, i + 1); address = CreateTrigger(ivm_trigger, NULL, matviewOid, InvalidOid, InvalidOid, - InvalidOid, InvalidOid, InvalidOid, NULL, true, false); + InvalidOid, InvalidOid, InvalidOid, NULL, false, false); recordDependencyOn(&address, &refaddr, DEPENDENCY_AUTO); } From c13a33d847ddbae0febc8cdfa40d292247495e7f Mon Sep 17 00:00:00 2001 From: Adam Guo Date: Wed, 21 May 2025 00:10:15 +0000 Subject: [PATCH 12/13] sql scripts --- pg_ivm--1.10--1.11.sql | 11 ++++-- pg_ivm--1.11.sql | 78 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 pg_ivm--1.11.sql diff --git a/pg_ivm--1.10--1.11.sql b/pg_ivm--1.10--1.11.sql index 1e718de..35aba6d 100644 --- a/pg_ivm--1.10--1.11.sql +++ b/pg_ivm--1.10--1.11.sql @@ -1,6 +1,13 @@ -ALTER TABLE pgivm.pg_ivm_immv ADD COLUMN querystring text NOT NULL; -ALTER TABLE pgivm.pg_ivm_immv ADD COLUMN immvuuid uuid NOT NULL; +ALTER TABLE pgivm.pg_ivm_immv ADD COLUMN querystring text; +ALTER TABLE pgivm.pg_ivm_immv ADD COLUMN immvuuid uuid; + +UPDATE pgivm.pg_ivm_immv SET querystring = pgivm.get_immv_def(immvrelid); +UPDATE pgivm.pg_ivm_immv SET immvuuid = gen_random_uuid(); + ALTER TABLE pgivm.pg_ivm_immv ADD CONSTRAINT pg_ivm_immv_uuid UNIQUE (immvuuid); +ALTER TABLE pgivm.pg_ivm_immv ALTER COLUMN querystring SET NOT NULL; +ALTER TABLE pgivm.pg_ivm_immv ALTER COLUMN immvuuid SET NOT NULL; +ALTER TABLE pgivm.pg_ivm_immv DROP COLUMN viewdef; CREATE FUNCTION pgivm.recreate_all_immvs() RETURNS VOID LANGUAGE PLPGSQL AS $$ diff --git a/pg_ivm--1.11.sql b/pg_ivm--1.11.sql new file mode 100644 index 0000000..8181acc --- /dev/null +++ b/pg_ivm--1.11.sql @@ -0,0 +1,78 @@ +CREATE SCHEMA pgivm; + +-- catalog + +CREATE TABLE pgivm.pg_ivm_immv( + immvrelid regclass NOT NULL, + immvuuid uuid NOT NULL, + querystring text NOT NULL, + ispopulated bool NOT NULL, + lastivmupdate xid8, + + CONSTRAINT pg_ivm_immv_pkey PRIMARY KEY (immvrelid), + CONSTRAINT pg_ivm_immv_uuid UNIQUE (immvuuid) +); + +SELECT pg_catalog.pg_extension_config_dump('pgivm.pg_ivm_immv', ''); + +-- functions + +CREATE FUNCTION pgivm.create_immv(text, text) +RETURNS bigint +STRICT +AS 'MODULE_PATHNAME', 'create_immv' +LANGUAGE C; + +CREATE FUNCTION pgivm.refresh_immv(text, bool) +RETURNS bigint +STRICT +AS 'MODULE_PATHNAME', 'refresh_immv' +LANGUAGE C; + +CREATE FUNCTION pgivm.get_immv_def(IN immvrelid regclass) +RETURNS text +STRICT +AS 'MODULE_PATHNAME', 'get_immv_def' +LANGUAGE C; + +CREATE FUNCTION pgivm.ivm_visible_in_prestate(oid, tid, oid) +RETURNS bool +STABLE +AS 'MODULE_PATHNAME', 'ivm_visible_in_prestate' +LANGUAGE C; + +-- trigger functions + +CREATE FUNCTION pgivm."IVM_immediate_before"() +RETURNS trigger +AS 'MODULE_PATHNAME', 'IVM_immediate_before' +LANGUAGE C; + +CREATE FUNCTION pgivm."IVM_immediate_maintenance"() +RETURNS trigger +AS 'MODULE_PATHNAME', 'IVM_immediate_maintenance' +LANGUAGE C; + +CREATE FUNCTION pgivm."IVM_prevent_immv_change"() +RETURNS trigger +AS 'MODULE_PATHNAME', 'IVM_prevent_immv_change' +LANGUAGE C; + +GRANT SELECT ON TABLE pgivm.pg_ivm_immv TO PUBLIC; +GRANT USAGE ON SCHEMA pgivm TO PUBLIC; + +-- event triggers + +CREATE FUNCTION pgivm.save_query_strings() RETURNS event_trigger +AS 'MODULE_PATHNAME', 'save_query_strings' LANGUAGE C; + +CREATE FUNCTION pgivm.restore_query_strings() RETURNS event_trigger +AS 'MODULE_PATHNAME', 'restore_query_strings' LANGUAGE C; + +CREATE EVENT TRIGGER save_query_strings +ON ddl_command_start +EXECUTE FUNCTION pgivm.save_query_strings(); + +CREATE EVENT TRIGGER restore_query_strings +ON ddl_command_end +EXECUTE FUNCTION pgivm.restore_query_strings(); From f219050b3362dc02470811a872ed695778e067ec Mon Sep 17 00:00:00 2001 From: Adam Guo Date: Wed, 21 May 2025 01:34:52 +0000 Subject: [PATCH 13/13] Clean up --- Makefile | 2 +- createas.c | 12 +++--- event_trigger.c | 42 +++++++++++++++++- matview.c | 97 +++--------------------------------------- pg_ivm--1.10--1.11.sql | 43 ------------------- pg_ivm.h | 16 ++++--- 6 files changed, 64 insertions(+), 148 deletions(-) delete mode 100644 pg_ivm--1.10--1.11.sql diff --git a/Makefile b/Makefile index 8ff11d3..47aba28 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,7 @@ DATA = pg_ivm--1.0.sql \ pg_ivm--1.3--1.4.sql pg_ivm--1.4--1.5.sql pg_ivm--1.5--1.6.sql \ pg_ivm--1.6--1.7.sql pg_ivm--1.7--1.8.sql pg_ivm--1.8--1.9.sql \ pg_ivm--1.9--1.10.sql \ - pg_ivm--1.10.sql pg_ivm--1.10--1.11.sql + pg_ivm--1.10.sql pg_ivm--1.11.sql REGRESS = pg_ivm create_immv refresh_immv diff --git a/createas.c b/createas.c index 3385255..6526369 100644 --- a/createas.c +++ b/createas.c @@ -130,8 +130,10 @@ create_immv_internal(List *attrList, IntoClause *into) * so we don't need more code to complain if "replace" is false.) */ immv_addr.address = DefineRelation(create, relkind, InvalidOid, NULL, NULL); - /* Generate the IMMV UUID. */ - // TODO: check for hash collision + /* + * Generate the IMMV UUID. + * TODO: check for hash collision + */ immv_uuid = DatumGetUUIDP(DirectFunctionCall1(gen_random_uuid, (Datum) NULL)); memcpy(&immv_addr.immv_uuid, immv_uuid, sizeof(*immv_uuid)); pfree(immv_uuid); @@ -1718,7 +1720,6 @@ get_primary_key_attnos_from_query(Query *query, List **constraintList) static void StoreImmvQuery(ImmvAddress immv_addr, Query *viewQuery) { - char *querytree = nodeToString((Node *) viewQuery); char *querystring; int save_nestlevel; Datum values[Natts_pg_ivm_immv]; @@ -1745,10 +1746,9 @@ StoreImmvQuery(ImmvAddress immv_addr, Query *viewQuery) memset(isNulls, false, sizeof(isNulls)); values[Anum_pg_ivm_immv_immvrelid -1 ] = ObjectIdGetDatum(immv_addr.address.objectId); - values[Anum_pg_ivm_immv_ispopulated -1 ] = BoolGetDatum(false); - values[Anum_pg_ivm_immv_viewdef -1 ] = CStringGetTextDatum(querytree); - values[Anum_pg_ivm_immv_querystring - 1] = CStringGetTextDatum(querystring); values[Anum_pg_ivm_immv_immvuuid -1 ] = UUIDPGetDatum(&immv_addr.immv_uuid); + values[Anum_pg_ivm_immv_querystring - 1] = CStringGetTextDatum(querystring); + values[Anum_pg_ivm_immv_ispopulated -1 ] = BoolGetDatum(false); pgIvmImmv = table_open(PgIvmImmvRelationId(), RowExclusiveLock); diff --git a/event_trigger.c b/event_trigger.c index 77685f8..c20e92e 100644 --- a/event_trigger.c +++ b/event_trigger.c @@ -14,6 +14,22 @@ #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; @@ -35,6 +51,9 @@ 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) { @@ -55,6 +74,9 @@ save_query_strings(PG_FUNCTION_ARGS) PG_RETURN_NULL(); } +/* + * Post-DDL event trigger function. + */ Datum restore_query_strings(PG_FUNCTION_ARGS) { @@ -73,6 +95,9 @@ restore_query_strings(PG_FUNCTION_ARGS) PG_RETURN_NULL(); } +/* + * Initialize the hash table. + */ static void initialize_query_cache(void) { @@ -85,6 +110,9 @@ initialize_query_cache(void) &ctl, HASH_ELEM | HASH_BLOBS); } +/* + * Store a query node in the hash table. + */ static void hash_query(Oid immv_relid, char *query) { @@ -103,6 +131,9 @@ hash_query(Oid immv_relid, char *query) MemoryContextSwitchTo(old_cxt); } +/* + * Retrieve a query node from the hash table. + */ static char * retrieve_query(Oid immv_relid) { @@ -125,6 +156,10 @@ retrieve_query(Oid immv_relid) 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) { @@ -162,6 +197,7 @@ save_query_strings_internal(void) /* 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)); @@ -171,6 +207,11 @@ save_query_strings_internal(void) 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) { @@ -211,7 +252,6 @@ restore_query_strings_internal(void) serialized_query = retrieve_query(matview_oid); query = stringToNode(serialized_query); - query = (Query *) ((CreateTableAsStmt *) query->utilityStmt)->into->viewQuery; matview_rel = table_open(matview_oid, AccessShareLock); new_query_string = pg_ivm_get_viewdef_internal(query, matview_rel, true); diff --git a/matview.c b/matview.c index cfce832..8b591e6 100644 --- a/matview.c +++ b/matview.c @@ -234,8 +234,6 @@ static void clean_up_IVM_hash_entry(MV_TriggerHashEntry *entry, bool is_abort, static void setLastUpdateXid(Oid immv_oid, FullTransactionId xid); static FullTransactionId getLastUpdateXid(Oid immv_oid); -static Query *update_immv_viewdef(Relation matviewRel); - /* SQL callable functions */ PG_FUNCTION_INFO_V1(IVM_immediate_before); PG_FUNCTION_INFO_V1(IVM_immediate_maintenance); @@ -397,18 +395,7 @@ RefreshImmvByOid(ImmvAddress immv_addr, bool is_create, bool skipData, systable_endscan(scan); table_close(pgIvmImmv, NoLock); - /* - * Recreate the Query tree from the query string to account for changing - * base table OIDs (e.g. after dump/restore) or changing format of the Query - * node (after pg_upgrade). - * - * No need to create the Query tree a second time if we are creating a new - * IMMV. - */ - if (is_create) - viewQuery = get_immv_query(matviewRel); - else - viewQuery = update_immv_viewdef(matviewRel); + viewQuery = get_immv_query(matviewRel); /* For IMMV, we need to rewrite matview query */ if (!skipData) @@ -707,6 +694,9 @@ get_immv_query(Relation matviewRel) bool isnull; Datum datum; Query *query; + char *querystring; + char *relname; + ParseState *parse_state; ScanKeyInit(&key, Anum_pg_ivm_immv_immvrelid, @@ -724,91 +714,16 @@ get_immv_query(Relation matviewRel) return NULL; } - datum = heap_getattr(tup, Anum_pg_ivm_immv_viewdef, tupdesc, &isnull); - Assert(!isnull); - query = (Query *) stringToNode(TextDatumGetCString(datum)); - - systable_endscan(scan); - table_close(pgIvmImmv, NoLock); - - return query; -} - -/* - * update_immv_viewdef - * - * Read the query string for this IMMV from pg_ivm_immv. Parse the query string - * and update the viewdef column with the new Query tree. Return the Query - * tree. - */ -static Query * -update_immv_viewdef(Relation matviewRel) -{ - CreateTableAsStmt *stmt; - Datum datum; - Datum values[Natts_pg_ivm_immv]; - HeapTuple newtup = NULL; - HeapTuple tup; - IntoClause *into; - ParseState *pstate = NULL; - Query *query = NULL; - Relation pgIvmImmv = table_open(PgIvmImmvRelationId(), AccessShareLock); - ScanKeyData key; - SysScanDesc scan; - TupleDesc tupdesc = RelationGetDescr(pgIvmImmv); - - bool isnull; - bool nulls[Natts_pg_ivm_immv]; - bool replaces[Natts_pg_ivm_immv]; - const char *querystring; - const char *querytree; - const char *relname; - - /* Scan pg_ivm_immv for the given IMMV entry. */ - ScanKeyInit(&key, - Anum_pg_ivm_immv_immvrelid, - BTEqualStrategyNumber, F_OIDEQ, - ObjectIdGetDatum(RelationGetRelid(matviewRel))); - scan = systable_beginscan(pgIvmImmv, PgIvmImmvPrimaryKeyIndexId(), - true, NULL, 1, &key); - - tup = systable_getnext(scan); - - if (!HeapTupleIsValid(tup)) - { - systable_endscan(scan); - table_close(pgIvmImmv, NoLock); - return NULL; - } - - /* Read the query string column. */ datum = heap_getattr(tup, Anum_pg_ivm_immv_querystring, tupdesc, &isnull); Assert(!isnull); querystring = TextDatumGetCString(datum); - /* Parse the query string using the same logic as create_immv. */ relname = psprintf("%s.%s", get_namespace_name(get_rel_namespace(matviewRel->rd_id)), get_rel_name(matviewRel->rd_id)); - parse_immv_query(relname, querystring, &query, &pstate); - stmt = castNode(CreateTableAsStmt, query->utilityStmt); - into = stmt->into; - - query = castNode(Query, stmt->query); - query = rewriteQueryForIMMV(query, into->colNames); - querytree = nodeToString((Node *) query); - - /* Update the pg_ivm_immv tuple with the new query tree. */ - memset(values, 0, sizeof(values)); - memset(nulls, 0, sizeof(nulls)); - memset(replaces, 0, sizeof(replaces)); - values[Anum_pg_ivm_immv_viewdef - 1] = CStringGetTextDatum(querytree); - replaces[Anum_pg_ivm_immv_viewdef - 1] = true; - - newtup = heap_modify_tuple(tup, tupdesc, values, nulls, replaces); - CatalogTupleUpdate(pgIvmImmv, &newtup->t_self, newtup); - heap_freetuple(newtup); + parse_immv_query(relname, querystring, &query, &parse_state); + query = (Query *) ((CreateTableAsStmt *) query->utilityStmt)->into->viewQuery; systable_endscan(scan); table_close(pgIvmImmv, NoLock); diff --git a/pg_ivm--1.10--1.11.sql b/pg_ivm--1.10--1.11.sql deleted file mode 100644 index 35aba6d..0000000 --- a/pg_ivm--1.10--1.11.sql +++ /dev/null @@ -1,43 +0,0 @@ -ALTER TABLE pgivm.pg_ivm_immv ADD COLUMN querystring text; -ALTER TABLE pgivm.pg_ivm_immv ADD COLUMN immvuuid uuid; - -UPDATE pgivm.pg_ivm_immv SET querystring = pgivm.get_immv_def(immvrelid); -UPDATE pgivm.pg_ivm_immv SET immvuuid = gen_random_uuid(); - -ALTER TABLE pgivm.pg_ivm_immv ADD CONSTRAINT pg_ivm_immv_uuid UNIQUE (immvuuid); -ALTER TABLE pgivm.pg_ivm_immv ALTER COLUMN querystring SET NOT NULL; -ALTER TABLE pgivm.pg_ivm_immv ALTER COLUMN immvuuid SET NOT NULL; -ALTER TABLE pgivm.pg_ivm_immv DROP COLUMN viewdef; - -CREATE FUNCTION pgivm.recreate_all_immvs() RETURNS VOID LANGUAGE PLPGSQL AS -$$ -BEGIN - PERFORM pgivm.refresh_immv(n.nspname || '.' || c.relname, false) - FROM pgivm.pg_ivm_immv as ivm - JOIN pg_catalog.pg_class as c - ON c.oid = ivm.immvrelid - JOIN pg_catalog.pg_namespace as n - ON c.relnamespace = n.oid; - - PERFORM pgivm.refresh_immv(n.nspname || '.' || c.relname, true) - FROM pgivm.pg_ivm_immv as ivm - JOIN pg_catalog.pg_class as c - ON c.oid = ivm.immvrelid - JOIN pg_catalog.pg_namespace as n - ON c.relnamespace = n.oid; -END -$$; - -CREATE FUNCTION pgivm.save_query_strings() RETURNS event_trigger -AS 'MODULE_PATHNAME', 'save_query_strings' LANGUAGE C; - -CREATE FUNCTION pgivm.restore_query_strings() RETURNS event_trigger -AS 'MODULE_PATHNAME', 'restore_query_strings' LANGUAGE C; - -CREATE EVENT TRIGGER save_query_strings -ON ddl_command_start -EXECUTE FUNCTION pgivm.save_query_strings(); - -CREATE EVENT TRIGGER restore_query_strings -ON ddl_command_end -EXECUTE FUNCTION pgivm.restore_query_strings(); diff --git a/pg_ivm.h b/pg_ivm.h index d8f80f9..b03d92f 100644 --- a/pg_ivm.h +++ b/pg_ivm.h @@ -21,15 +21,19 @@ #include "utils/queryenvironment.h" #include "utils/uuid.h" -#define Natts_pg_ivm_immv 6 +#define Natts_pg_ivm_immv 5 #define Anum_pg_ivm_immv_immvrelid 1 -#define Anum_pg_ivm_immv_viewdef 2 -#define Anum_pg_ivm_immv_ispopulated 3 -#define Anum_pg_ivm_immv_lastivmupdate 4 -#define Anum_pg_ivm_immv_querystring 5 -#define Anum_pg_ivm_immv_immvuuid 6 +#define Anum_pg_ivm_immv_immvuuid 2 +#define Anum_pg_ivm_immv_querystring 3 +#define Anum_pg_ivm_immv_ispopulated 4 +#define Anum_pg_ivm_immv_lastivmupdate 5 +/* + * This struct uniquely identifies in IMMV. It consists of an ObjectAddress + * (OID of the IMMV table and its schema) as well as a pg_uuid_t (which is + * stored in the pg_ivm_immv table). + */ typedef struct ImmvAddress { ObjectAddress address;