Add support for PostgreSQL 16 (#69) (#70)

Build errors/warnings against PostgreSQL 16 are fixed.
Also, adapted to the change of codes, including:

- Get rid of the "new" and "old" entries in a view's rangetable.
(Although, removed codes were dead codes because pg_ivm doesn't
 have any rules in pg_rewrite.)

- Rework query relation permission checking

- Require empty Bitmapsets to be represented as NULL

- Fix some compiler warnings
This commit is contained in:
Yugo Nagata 2023-09-11 15:23:51 +09:00 committed by GitHub
parent 6f99049848
commit 71f9d268b0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 83 additions and 72 deletions

View file

@ -2,7 +2,7 @@
The `pg_ivm` module provides Incremental View Maintenance (IVM) feature for PostgreSQL. The `pg_ivm` module provides Incremental View Maintenance (IVM) feature for PostgreSQL.
The extension is compatible with PostgreSQL 13, 14, and 15. The extension is compatible with PostgreSQL 13, 14, 15, and 16.
## Description ## Description

View file

@ -78,7 +78,7 @@ static void CreateIvmTriggersOnBaseTablesRecurse(Query *qry, Node *node, Oid mat
static void CreateIvmTrigger(Oid relOid, Oid viewOid, int16 type, int16 timing, bool ex_lock); static void CreateIvmTrigger(Oid relOid, Oid viewOid, int16 type, int16 timing, bool ex_lock);
static void check_ivm_restriction(Node *node); static void check_ivm_restriction(Node *node);
static bool check_ivm_restriction_walker(Node *node, check_ivm_restriction_context *context); 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, bool is_create); static Bitmapset *get_primary_key_attnos_from_query(Query *query, List **constraintList);
static bool check_aggregate_supports_ivm(Oid aggfnoid); static bool check_aggregate_supports_ivm(Oid aggfnoid);
static void StoreImmvQuery(Oid viewOid, bool ispopulated, Query *viewQuery); static void StoreImmvQuery(Oid viewOid, bool ispopulated, Query *viewQuery);
@ -262,14 +262,14 @@ ExecCreateImmv(ParseState *pstate, CreateTableAsStmt *stmt,
if (!into->skipData) if (!into->skipData)
{ {
/* Create an index on incremental maintainable materialized view, if possible */ /* Create an index on incremental maintainable materialized view, if possible */
CreateIndexOnIMMV(viewQuery, matviewRel, true); CreateIndexOnIMMV(viewQuery, matviewRel);
/* /*
* Create triggers on incremental maintainable materialized view * Create triggers on incremental maintainable materialized view
* This argument should use 'query'. This needs to use a rewritten query, * This argument should use 'query'. This needs to use a rewritten query,
* because a sublink in jointree is not supported by this function. * because a sublink in jointree is not supported by this function.
*/ */
CreateIvmTriggersOnBaseTables(query, matviewOid, true); CreateIvmTriggersOnBaseTables(query, matviewOid);
/* Create triggers to prevent IMMV from beeing changed */ /* Create triggers to prevent IMMV from beeing changed */
CreateChangePreventTrigger(matviewOid); CreateChangePreventTrigger(matviewOid);
@ -297,7 +297,6 @@ rewriteQueryForIMMV(Query *query, List *colNames)
{ {
Query *rewritten; Query *rewritten;
TargetEntry *tle;
Node *node; Node *node;
ParseState *pstate = make_parsestate(NULL); ParseState *pstate = make_parsestate(NULL);
FuncCall *fn; FuncCall *fn;
@ -348,7 +347,7 @@ rewriteQueryForIMMV(Query *query, List *colNames)
if (countCol != NULL) if (countCol != NULL)
{ {
tle = makeTargetEntry((Expr *) countCol, TargetEntry *tle = makeTargetEntry((Expr *) countCol,
list_length(rewritten->targetList) + 1, list_length(rewritten->targetList) + 1,
pstrdup(columnName), pstrdup(columnName),
false); false);
@ -399,6 +398,8 @@ rewriteQueryForIMMV(Query *query, List *colNames)
/* Add count(*) for counting distinct tuples in views */ /* Add count(*) for counting distinct tuples in views */
if (rewritten->distinctClause || rewritten->hasAggs) if (rewritten->distinctClause || rewritten->hasAggs)
{ {
TargetEntry *tle;
#if defined(PG_VERSION_NUM) && (PG_VERSION_NUM >= 140000) #if defined(PG_VERSION_NUM) && (PG_VERSION_NUM >= 140000)
fn = makeFuncCall(SystemFuncName("count"), NIL, COERCE_EXPLICIT_CALL, -1); fn = makeFuncCall(SystemFuncName("count"), NIL, COERCE_EXPLICIT_CALL, -1);
#else #else
@ -516,22 +517,14 @@ makeIvmAggColumn(ParseState *pstate, Aggref *aggref, char *resname, AttrNumber *
* CreateIvmTriggersOnBaseTables -- create IVM triggers on all base tables * CreateIvmTriggersOnBaseTables -- create IVM triggers on all base tables
*/ */
void void
CreateIvmTriggersOnBaseTables(Query *qry, Oid matviewOid, bool is_create) CreateIvmTriggersOnBaseTables(Query *qry, Oid matviewOid)
{ {
Relids relids = NULL; Relids relids = NULL;
bool ex_lock = false; bool ex_lock = false;
Index first_rtindex = is_create ? 1 : PRS2_NEW_VARNO + 1;
RangeTblEntry *rte; RangeTblEntry *rte;
/*
* is_create must be true in pg_ivm because the view definition doesn't
* contain NEW/OLD RTE.
* XXX: This argument should be removed?
*/
Assert(is_create);
/* Immediately return if we don't have any base tables. */ /* Immediately return if we don't have any base tables. */
if (list_length(qry->rtable) < first_rtindex) if (list_length(qry->rtable) < 1)
return; return;
/* /*
@ -553,10 +546,9 @@ CreateIvmTriggersOnBaseTables(Query *qry, Oid matviewOid, bool is_create)
* target list are not nullable. * target list are not nullable.
*/ */
rte = list_nth(qry->rtable, first_rtindex - 1); rte = list_nth(qry->rtable, 0);
if (list_length(qry->rtable) > first_rtindex || if (list_length(qry->rtable) > 1 || rte->rtekind != RTE_RELATION ||
rte->rtekind != RTE_RELATION || qry->distinctClause || qry->distinctClause || (qry->hasAggs && qry->groupClause))
(qry->hasAggs && qry->groupClause))
ex_lock = true; ex_lock = true;
CreateIvmTriggersOnBaseTablesRecurse(qry, (Node *)qry, matviewOid, &relids, ex_lock); CreateIvmTriggersOnBaseTablesRecurse(qry, (Node *)qry, matviewOid, &relids, ex_lock);
@ -927,8 +919,6 @@ check_ivm_restriction_walker(Node *node, check_ivm_restriction_context *context)
*/ */
if (context->exists_qual_vars != NIL && context->sublevels_up == 0) if (context->exists_qual_vars != NIL && context->sublevels_up == 0)
{ {
ListCell *lc;
foreach (lc, context->exists_qual_vars) foreach (lc, context->exists_qual_vars)
{ {
Var *var = (Var *) lfirst(lc); Var *var = (Var *) lfirst(lc);
@ -1284,7 +1274,7 @@ check_aggregate_supports_ivm(Oid aggfnoid)
* is created on these attritubes. In other cases, no index is created. * is created on these attritubes. In other cases, no index is created.
*/ */
void void
CreateIndexOnIMMV(Query *query, Relation matviewRel, bool is_create) CreateIndexOnIMMV(Query *query, Relation matviewRel)
{ {
ListCell *lc; ListCell *lc;
IndexStmt *index; IndexStmt *index;
@ -1294,14 +1284,6 @@ CreateIndexOnIMMV(Query *query, Relation matviewRel, bool is_create)
List *indexoidlist = RelationGetIndexList(matviewRel); List *indexoidlist = RelationGetIndexList(matviewRel);
ListCell *indexoidscan; ListCell *indexoidscan;
/*
* is_create must be true in pg_ivm because the view definition doesn't
* contain NEW/OLD RTE.
* XXX: This argument should be removed?
*/
Assert(is_create);
/* /*
* For aggregate without GROUP BY, we do not need to create an index * For aggregate without GROUP BY, we do not need to create an index
* because the view has only one row. * because the view has only one row.
@ -1341,9 +1323,14 @@ CreateIndexOnIMMV(Query *query, Relation matviewRel, bool is_create)
index->excludeOpNames = NIL; index->excludeOpNames = NIL;
index->idxcomment = NULL; index->idxcomment = NULL;
index->indexOid = InvalidOid; index->indexOid = InvalidOid;
#if defined(PG_VERSION_NUM) && (PG_VERSION_NUM >= 160000)
index->oldNumber = InvalidRelFileNumber;
index->oldFirstRelfilelocatorSubid = InvalidSubTransactionId;
#else
index->oldNode = InvalidOid; index->oldNode = InvalidOid;
index->oldCreateSubid = InvalidSubTransactionId;
index->oldFirstRelfilenodeSubid = InvalidSubTransactionId; index->oldFirstRelfilenodeSubid = InvalidSubTransactionId;
#endif
index->oldCreateSubid = InvalidSubTransactionId;
index->transformed = true; index->transformed = true;
index->concurrent = false; index->concurrent = false;
index->if_not_exists = false; index->if_not_exists = false;
@ -1396,7 +1383,7 @@ CreateIndexOnIMMV(Query *query, Relation matviewRel, bool is_create)
Bitmapset *key_attnos; Bitmapset *key_attnos;
/* create index on the base tables' primary key columns */ /* create index on the base tables' primary key columns */
key_attnos = get_primary_key_attnos_from_query(query, &constraintList, is_create); key_attnos = get_primary_key_attnos_from_query(query, &constraintList);
if (key_attnos) if (key_attnos)
{ {
foreach(lc, query->targetList) foreach(lc, query->targetList)
@ -1460,6 +1447,9 @@ CreateIndexOnIMMV(Query *query, Relation matviewRel, bool is_create)
InvalidOid, InvalidOid,
InvalidOid, InvalidOid,
InvalidOid, InvalidOid,
#if defined(PG_VERSION_NUM) && (PG_VERSION_NUM >= 160000)
-1,
#endif
false, true, false, false, true); false, true, false, false, true);
ereport(NOTICE, ereport(NOTICE,
@ -1500,7 +1490,7 @@ CreateIndexOnIMMV(Query *query, Relation matviewRel, bool is_create)
* constraintList is set to a list of the OIDs of the pkey constraints. * constraintList is set to a list of the OIDs of the pkey constraints.
*/ */
static Bitmapset * static Bitmapset *
get_primary_key_attnos_from_query(Query *query, List **constraintList, bool is_create) get_primary_key_attnos_from_query(Query *query, List **constraintList)
{ {
List *key_attnos_list = NIL; List *key_attnos_list = NIL;
ListCell *lc; ListCell *lc;
@ -1527,21 +1517,16 @@ get_primary_key_attnos_from_query(Query *query, List **constraintList, bool is_c
* Collect primary key attributes from all tables used in query. The key attributes * Collect primary key attributes from all tables used in query. The key attributes
* sets for each table are stored in key_attnos_list in order by RTE index. * sets for each table are stored in key_attnos_list in order by RTE index.
*/ */
i = 1;
foreach(lc, query->rtable) foreach(lc, query->rtable)
{ {
RangeTblEntry *r = (RangeTblEntry*) lfirst(lc); RangeTblEntry *r = (RangeTblEntry*) lfirst(lc);
Bitmapset *key_attnos; Bitmapset *key_attnos;
bool has_pkey = true; bool has_pkey = true;
Index first_rtindex = is_create ? 1 : PRS2_NEW_VARNO + 1;
/* skip NEW/OLD entries */
if (i >= first_rtindex)
{
/* for subqueries, scan recursively */ /* for subqueries, scan recursively */
if (r->rtekind == RTE_SUBQUERY) if (r->rtekind == RTE_SUBQUERY)
{ {
key_attnos = get_primary_key_attnos_from_query(r->subquery, constraintList, true); key_attnos = get_primary_key_attnos_from_query(r->subquery, constraintList);
has_pkey = (key_attnos != NULL); has_pkey = (key_attnos != NULL);
} }
/* for tables, call get_primary_key_attnos */ /* for tables, call get_primary_key_attnos */
@ -1555,9 +1540,6 @@ get_primary_key_attnos_from_query(Query *query, List **constraintList, bool is_c
/* for other RTEs, store NULL into key_attnos_list */ /* for other RTEs, store NULL into key_attnos_list */
else else
key_attnos = NULL; key_attnos = NULL;
}
else
key_attnos = NULL;
/* /*
* If any table or subquery has no primary key or its pkey constraint is deferrable, * If any table or subquery has no primary key or its pkey constraint is deferrable,
@ -1567,14 +1549,17 @@ get_primary_key_attnos_from_query(Query *query, List **constraintList, bool is_c
return NULL; return NULL;
key_attnos_list = lappend(key_attnos_list, key_attnos); key_attnos_list = lappend(key_attnos_list, key_attnos);
i++;
} }
/* Collect key attributes appearing in the target list */ /* Collect key attributes appearing in the target list */
i = 1; i = 1;
foreach(lc, query->targetList) foreach(lc, query->targetList)
{ {
#if defined(PG_VERSION_NUM) && (PG_VERSION_NUM >= 160000)
TargetEntry *tle = (TargetEntry *) flatten_join_alias_vars(NULL, query, lfirst(lc));
#else
TargetEntry *tle = (TargetEntry *) flatten_join_alias_vars(query, lfirst(lc)); TargetEntry *tle = (TargetEntry *) flatten_join_alias_vars(query, lfirst(lc));
#endif
if (IsA(tle->expr, Var)) if (IsA(tle->expr, Var))
{ {
@ -1588,7 +1573,12 @@ get_primary_key_attnos_from_query(Query *query, List **constraintList, bool is_c
* Remove found key attributes from key_attnos_list, and add this * Remove found key attributes from key_attnos_list, and add this
* to the result list. * to the result list.
*/ */
bms_del_member(key_attnos, var->varattno - FirstLowInvalidHeapAttributeNumber); key_attnos = bms_del_member(key_attnos, var->varattno - FirstLowInvalidHeapAttributeNumber);
if (bms_is_empty(key_attnos))
{
key_attnos_list = list_delete_nth_cell(key_attnos_list, var->varno - 1);
key_attnos_list = list_insert_nth(key_attnos_list, var->varno - 1, NULL);
}
keys = bms_add_member(keys, i - FirstLowInvalidHeapAttributeNumber); keys = bms_add_member(keys, i - FirstLowInvalidHeapAttributeNumber);
} }
} }
@ -1596,7 +1586,11 @@ get_primary_key_attnos_from_query(Query *query, List **constraintList, bool is_c
} }
/* Collect RTE indexes of relations appearing in the FROM clause */ /* Collect RTE indexes of relations appearing in the FROM clause */
#if defined(PG_VERSION_NUM) && (PG_VERSION_NUM >= 160000)
rels_in_from = get_relids_in_jointree((Node *) query->jointree, false, false);
#else
rels_in_from = get_relids_in_jointree((Node *) query->jointree, false); rels_in_from = get_relids_in_jointree((Node *) query->jointree, false);
#endif
/* /*
* Check if all key attributes of relations in FROM are appearing in the target * Check if all key attributes of relations in FROM are appearing in the target

View file

@ -354,9 +354,6 @@ ExecRefreshImmv(const RangeVar *relation, bool skipData,
{ {
Relation tgRel; Relation tgRel;
Relation depRel; Relation depRel;
ScanKeyData key;
SysScanDesc scan;
HeapTuple tup;
ObjectAddresses *immv_triggers; ObjectAddresses *immv_triggers;
immv_triggers = new_object_addresses(); immv_triggers = new_object_addresses();
@ -450,7 +447,7 @@ ExecRefreshImmv(const RangeVar *relation, bool skipData,
pgstat_count_heap_insert(matviewRel, processed); pgstat_count_heap_insert(matviewRel, processed);
if (!skipData && !oldPopulated) if (!skipData && !oldPopulated)
CreateIvmTriggersOnBaseTables(viewQuery, matviewOid, true); CreateIvmTriggersOnBaseTables(viewQuery, matviewOid);
table_close(matviewRel, NoLock); table_close(matviewRel, NoLock);
@ -905,8 +902,13 @@ IVM_immediate_maintenance(PG_FUNCTION_ARGS)
if (!(query->hasAggs && query->groupClause == NIL)) if (!(query->hasAggs && query->groupClause == NIL))
{ {
OpenImmvIncrementalMaintenance(); OpenImmvIncrementalMaintenance();
#if defined(PG_VERSION_NUM) && (PG_VERSION_NUM >= 160000)
ExecuteTruncateGuts(list_make1(matviewRel), list_make1_oid(matviewOid),
NIL, DROP_RESTRICT, false, false);
#else
ExecuteTruncateGuts(list_make1(matviewRel), list_make1_oid(matviewOid), ExecuteTruncateGuts(list_make1(matviewRel), list_make1_oid(matviewOid),
NIL, DROP_RESTRICT, false); NIL, DROP_RESTRICT, false);
#endif
CloseImmvIncrementalMaintenance(); CloseImmvIncrementalMaintenance();
} }
else else
@ -1041,7 +1043,6 @@ IVM_immediate_maintenance(PG_FUNCTION_ARGS)
foreach(lc2, table->rte_paths) foreach(lc2, table->rte_paths)
{ {
List *rte_path = lfirst(lc2); List *rte_path = lfirst(lc2);
int i;
Query *querytree = rewritten; Query *querytree = rewritten;
RangeTblEntry *rte; RangeTblEntry *rte;
TupleDesc tupdesc_old; TupleDesc tupdesc_old;
@ -1359,7 +1360,7 @@ get_prestate_rte(RangeTblEntry *rte, MV_TriggerTable *table,
{ {
StringInfoData str; StringInfoData str;
RawStmt *raw; RawStmt *raw;
Query *sub; Query *subquery;
Relation rel; Relation rel;
ParseState *pstate; ParseState *pstate;
char *relname; char *relname;
@ -1371,7 +1372,7 @@ get_prestate_rte(RangeTblEntry *rte, MV_TriggerTable *table,
/* /*
* We can use NoLock here since AcquireRewriteLocks should * We can use NoLock here since AcquireRewriteLocks should
* have locked the rel already. * have locked the relation already.
*/ */
rel = table_open(table->table_id, NoLock); rel = table_open(table->table_id, NoLock);
relname = quote_qualified_identifier( relname = quote_qualified_identifier(
@ -1379,12 +1380,19 @@ get_prestate_rte(RangeTblEntry *rte, MV_TriggerTable *table,
RelationGetRelationName(rel)); RelationGetRelationName(rel));
table_close(rel, NoLock); table_close(rel, NoLock);
/*
* Filtering inserted row using the snapshot taken before the table
* is modified. ctid is required for maintaining outer join views.
*/
initStringInfo(&str); initStringInfo(&str);
appendStringInfo(&str, appendStringInfo(&str,
"SELECT t.* FROM %s t" "SELECT t.* FROM %s t"
" WHERE pg_catalog.ivm_visible_in_prestate(t.tableoid, t.ctid ,%d::pg_catalog.oid)", " WHERE pg_catalog.ivm_visible_in_prestate(t.tableoid, t.ctid ,%d::pg_catalog.oid)",
relname, matviewid); relname, matviewid);
/*
* Append deleted rows contained in old transition tables.
*/
for (i = 0; i < list_length(table->old_tuplestores); i++) for (i = 0; i < list_length(table->old_tuplestores); i++)
{ {
appendStringInfo(&str, " UNION ALL "); appendStringInfo(&str, " UNION ALL ");
@ -1392,20 +1400,21 @@ get_prestate_rte(RangeTblEntry *rte, MV_TriggerTable *table,
make_delta_enr_name("old", table->table_id, i)); make_delta_enr_name("old", table->table_id, i));
} }
/* Get a subquery representing pre-state of the table */
#if defined(PG_VERSION_NUM) && (PG_VERSION_NUM >= 140000) #if defined(PG_VERSION_NUM) && (PG_VERSION_NUM >= 140000)
raw = (RawStmt*)linitial(raw_parser(str.data, RAW_PARSE_DEFAULT)); raw = (RawStmt*)linitial(raw_parser(str.data, RAW_PARSE_DEFAULT));
#else #else
raw = (RawStmt*)linitial(raw_parser(str.data)); raw = (RawStmt*)linitial(raw_parser(str.data));
#endif #endif
sub = transformStmt(pstate, raw->stmt); subquery = transformStmt(pstate, raw->stmt);
/* save the original RTE */ /* save the original RTE */
table->original_rte = copyObject(rte); table->original_rte = copyObject(rte);
rte->rtekind = RTE_SUBQUERY; rte->rtekind = RTE_SUBQUERY;
rte->subquery = sub; rte->subquery = subquery;
rte->security_barrier = false; rte->security_barrier = false;
/* Clear fields that should not be set in a subquery RTE */ /* Clear fields that should not be set in a subquery RTE */
rte->relid = InvalidOid; rte->relid = InvalidOid;
rte->relkind = 0; rte->relkind = 0;
@ -1413,12 +1422,16 @@ get_prestate_rte(RangeTblEntry *rte, MV_TriggerTable *table,
rte->tablesample = NULL; rte->tablesample = NULL;
rte->inh = false; /* must not be set for a subquery */ rte->inh = false; /* must not be set for a subquery */
#if defined(PG_VERSION_NUM) && (PG_VERSION_NUM >= 160000)
rte->perminfoindex = 0; /* no permission checking for this RTE */
#else
rte->requiredPerms = 0; /* no permission check on subquery itself */ rte->requiredPerms = 0; /* no permission check on subquery itself */
rte->checkAsUser = InvalidOid; rte->checkAsUser = InvalidOid;
rte->selectedCols = NULL; rte->selectedCols = NULL;
rte->insertedCols = NULL; rte->insertedCols = NULL;
rte->updatedCols = NULL; rte->updatedCols = NULL;
rte->extraUpdatedCols = NULL; rte->extraUpdatedCols = NULL;
#endif
return rte; return rte;
} }

View file

@ -122,7 +122,11 @@ parseNameAndColumns(const char *string, List **names, List **colNames)
/* Separate the name and parse it into a list */ /* Separate the name and parse it into a list */
*ptr++ = '\0'; *ptr++ = '\0';
#if defined(PG_VERSION_NUM) && (PG_VERSION_NUM >= 160000)
*names = stringToQualifiedNameList(rawname, NULL);
#else
*names = stringToQualifiedNameList(rawname); *names = stringToQualifiedNameList(rawname);
#endif
if (!has_colnames) if (!has_colnames)
goto end; goto end;

View file

@ -38,8 +38,8 @@ extern bool isImmv(Oid immv_oid);
extern ObjectAddress ExecCreateImmv(ParseState *pstate, CreateTableAsStmt *stmt, extern ObjectAddress ExecCreateImmv(ParseState *pstate, CreateTableAsStmt *stmt,
ParamListInfo params, QueryEnvironment *queryEnv, ParamListInfo params, QueryEnvironment *queryEnv,
QueryCompletion *qc); QueryCompletion *qc);
extern void CreateIvmTriggersOnBaseTables(Query *qry, Oid matviewOid, bool is_create); extern void CreateIvmTriggersOnBaseTables(Query *qry, Oid matviewOid);
extern void CreateIndexOnIMMV(Query *query, Relation matviewRel, bool is_create); extern void CreateIndexOnIMMV(Query *query, Relation matviewRel);
extern Query *rewriteQueryForIMMV(Query *query, List *colNames); extern Query *rewriteQueryForIMMV(Query *query, List *colNames);
extern void makeIvmAggColumn(ParseState *pstate, Aggref *aggref, char *resname, AttrNumber *next_resno, List **aggs); extern void makeIvmAggColumn(ParseState *pstate, Aggref *aggref, char *resname, AttrNumber *next_resno, List **aggs);