From cf89f48f2f2cf5029f57a7b56cc0287b44aa4e39 Mon Sep 17 00:00:00 2001 From: Yugo Nagata Date: Wed, 31 May 2023 19:08:51 +0900 Subject: [PATCH v33 07/11] Add DISTINCT support for IVM When IMMV is created with DISTINCT, multiplicity of tuples is counted and stored in "__ivm_count__" column, which is a hidden column of IMMV. The value in __ivm_count__ is updated when IMMV is maintained incrementally. A tuple in IMMV can be removed if and only if the count becomes zero. --- src/backend/commands/createas.c | 141 ++++++++++++++++++++------ src/backend/commands/indexcmds.c | 40 ++++++++ src/backend/commands/matview.c | 148 ++++++++++++++++++++++++++-- src/backend/commands/tablecmds.c | 9 ++ src/backend/nodes/outfuncs.c | 1 + src/backend/nodes/readfuncs.c | 1 + src/backend/parser/parse_relation.c | 18 +++- src/backend/rewrite/rewriteDefine.c | 3 +- src/include/commands/createas.h | 2 + src/include/nodes/parsenodes.h | 2 + 10 files changed, 320 insertions(+), 45 deletions(-) diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c index a424abbd32..7138dd59ce 100644 --- a/src/backend/commands/createas.c +++ b/src/backend/commands/createas.c @@ -50,6 +50,7 @@ #include "parser/parser.h" #include "parser/parsetree.h" #include "parser/parse_clause.h" +#include "parser/parse_func.h" #include "rewrite/rewriteHandler.h" #include "tcop/tcopprot.h" #include "utils/builtins.h" @@ -305,6 +306,9 @@ ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *stmt, errhint("functions must be marked IMMUTABLE"))); check_ivm_restriction((Node *) query); + + /* For IMMV, we need to rewrite matview query */ + query = rewriteQueryForIMMV(query, into->colNames); } if (into->skipData) @@ -409,6 +413,49 @@ ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *stmt, return address; } +/* + * rewriteQueryForIMMV -- rewrite view definition query for IMMV + * + * count(*) is added for counting distinct tuples in views. + */ +Query * +rewriteQueryForIMMV(Query *query, List *colNames) +{ + Query *rewritten; + + Node *node; + ParseState *pstate = make_parsestate(NULL); + FuncCall *fn; + + rewritten = copyObject(query); + pstate->p_expr_kind = EXPR_KIND_SELECT_TARGET; + + /* + * Convert DISTINCT to GROUP BY and add count(*) for counting distinct + * tuples in views. + */ + if (rewritten->distinctClause) + { + TargetEntry *tle; + + rewritten->groupClause = transformDistinctClause(NULL, &rewritten->targetList, rewritten->sortClause, false); + + fn = makeFuncCall(SystemFuncName("count"), NIL, COERCE_EXPLICIT_CALL, -1); + fn->agg_star = true; + + node = ParseFuncOrColumn(pstate, fn->funcname, NIL, NULL, fn, false, -1); + + tle = makeTargetEntry((Expr *) node, + list_length(rewritten->targetList) + 1, + pstrdup("__ivm_count__"), + false); + rewritten->targetList = lappend(rewritten->targetList, tle); + rewritten->hasAggs = true; + } + + return rewritten; +} + /* * GetIntoRelEFlags --- compute executor flags needed for CREATE TABLE AS * @@ -532,7 +579,8 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo) ColumnDef *col; char *colname; - if (lc) + /* Don't override hidden columns added for IVM */ + if (lc && !isIvmName(NameStr(attribute->attname))) { colname = strVal(lfirst(lc)); lc = lnext(into->colNames, lc); @@ -936,10 +984,6 @@ check_ivm_restriction_walker(Node *node, void *context) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("LIMIT/OFFSET clause is not supported on incrementally maintainable materialized view"))); - if (qry->distinctClause) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("DISTINCT is not supported on incrementally maintainable materialized view"))); if (qry->hasDistinctOn) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), @@ -1086,12 +1130,18 @@ CreateIndexOnIMMV(Query *query, Relation matviewRel) char idxname[NAMEDATALEN]; List *indexoidlist = RelationGetIndexList(matviewRel); ListCell *indexoidscan; - Bitmapset *key_attnos; snprintf(idxname, sizeof(idxname), "%s_index", RelationGetRelationName(matviewRel)); index = makeNode(IndexStmt); + /* + * We consider null values not distinct to make sure that views with DISTINCT + * or GROUP BY don't contain multiple NULL rows when NULL is inserted to + * a base table concurrently. + */ + index->nulls_not_distinct = true; + index->unique = true; index->primary = false; index->isconstraint = false; @@ -1118,41 +1168,68 @@ CreateIndexOnIMMV(Query *query, Relation matviewRel) index->concurrent = false; index->if_not_exists = false; - /* create index on the base tables' primary key columns */ - key_attnos = get_primary_key_attnos_from_query(query, &constraintList); - if (key_attnos) + if (query->distinctClause) { + /* create unique constraint on all columns */ foreach(lc, query->targetList) { TargetEntry *tle = (TargetEntry *) lfirst(lc); Form_pg_attribute attr = TupleDescAttr(matviewRel->rd_att, tle->resno - 1); - - if (bms_is_member(tle->resno - FirstLowInvalidHeapAttributeNumber, key_attnos)) - { - IndexElem *iparam; - - iparam = makeNode(IndexElem); - iparam->name = pstrdup(NameStr(attr->attname)); - iparam->expr = NULL; - iparam->indexcolname = NULL; - iparam->collation = NIL; - iparam->opclass = NIL; - iparam->opclassopts = NIL; - iparam->ordering = SORTBY_DEFAULT; - iparam->nulls_ordering = SORTBY_NULLS_DEFAULT; - index->indexParams = lappend(index->indexParams, iparam); - } + IndexElem *iparam; + + iparam = makeNode(IndexElem); + iparam->name = pstrdup(NameStr(attr->attname)); + iparam->expr = NULL; + iparam->indexcolname = NULL; + iparam->collation = NIL; + iparam->opclass = NIL; + iparam->opclassopts = NIL; + iparam->ordering = SORTBY_DEFAULT; + iparam->nulls_ordering = SORTBY_NULLS_DEFAULT; + index->indexParams = lappend(index->indexParams, iparam); } } else { - /* create no index, just notice that an appropriate index is necessary for efficient IVM */ - ereport(NOTICE, - (errmsg("could not create an index on materialized view \"%s\" automatically", - RelationGetRelationName(matviewRel)), - errdetail("This target list does not have all the primary key columns. "), - errhint("Create an index on the materialized view for efficient incremental maintenance."))); - return; + Bitmapset *key_attnos; + + /* create index on the base tables' primary key columns */ + key_attnos = get_primary_key_attnos_from_query(query, &constraintList); + if (key_attnos) + { + foreach(lc, query->targetList) + { + TargetEntry *tle = (TargetEntry *) lfirst(lc); + Form_pg_attribute attr = TupleDescAttr(matviewRel->rd_att, tle->resno - 1); + + if (bms_is_member(tle->resno - FirstLowInvalidHeapAttributeNumber, key_attnos)) + { + IndexElem *iparam; + + iparam = makeNode(IndexElem); + iparam->name = pstrdup(NameStr(attr->attname)); + iparam->expr = NULL; + iparam->indexcolname = NULL; + iparam->collation = NIL; + iparam->opclass = NIL; + iparam->opclassopts = NIL; + iparam->ordering = SORTBY_DEFAULT; + iparam->nulls_ordering = SORTBY_NULLS_DEFAULT; + index->indexParams = lappend(index->indexParams, iparam); + } + } + } + else + { + /* create no index, just notice that an appropriate index is necessary for efficient IVM */ + ereport(NOTICE, + (errmsg("could not create an index on materialized view \"%s\" automatically", + RelationGetRelationName(matviewRel)), + errdetail("This target list does not have all the primary key columns, " + "or this view does not contain DISTINCT clause."), + errhint("Create an index on the materialized view for efficient incremental maintenance."))); + return; + } } /* If we have a compatible index, we don't need to create another. */ diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c index 2caab88aa5..462c15f9c8 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -40,6 +40,7 @@ #include "commands/dbcommands.h" #include "commands/defrem.h" #include "commands/event_trigger.h" +#include "commands/matview.h" #include "commands/progress.h" #include "commands/tablecmds.h" #include "commands/tablespace.h" @@ -1120,6 +1121,45 @@ DefineIndex(Oid tableId, safe_index = indexInfo->ii_Expressions == NIL && indexInfo->ii_Predicate == NIL; + /* + * We disallow unique indexes on IVM columns of IMMVs. + */ + if (RelationIsIVM(rel) && stmt->unique) + { + for (int i = 0; i < indexInfo->ii_NumIndexKeyAttrs; i++) + { + AttrNumber attno = indexInfo->ii_IndexAttrNumbers[i]; + if (attno > 0) + { + char *name = NameStr(TupleDescAttr(rel->rd_att, attno - 1)->attname); + if (name && isIvmName(name)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("unique index creation on IVM columns is not supported"))); + } + } + + if (indexInfo->ii_Expressions) + { + Bitmapset *indexattrs = NULL; + int varno = -1; + + pull_varattnos((Node *) indexInfo->ii_Expressions, 1, &indexattrs); + + while ((varno = bms_next_member(indexattrs, varno)) >= 0) + { + int attno = varno + FirstLowInvalidHeapAttributeNumber; + char *name = NameStr(TupleDescAttr(rel->rd_att, attno - 1)->attname); + if (name && isIvmName(name)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("unique index creation on IVM columns is not supported"))); + } + + } + } + + /* * Report index creation if appropriate (delay this till after most of the * error checks) diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c index 1061c37b2c..f2e8aa02a3 100644 --- a/src/backend/commands/matview.c +++ b/src/backend/commands/matview.c @@ -148,11 +148,15 @@ static Query *rewrite_query_for_postupdate_state(Query *query, MV_TriggerTable * static void apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *new_tuplestores, TupleDesc tupdesc_old, TupleDesc tupdesc_new, - Query *query); + Query *query, bool use_count, char *count_colname); static void apply_old_delta(const char *matviewname, const char *deltaname_old, List *keys); +static void apply_old_delta_with_count(const char *matviewname, const char *deltaname_old, + List *keys, const char *count_colname); static void apply_new_delta(const char *matviewname, const char *deltaname_new, StringInfo target_list); +static void apply_new_delta_with_count(const char *matviewname, const char* deltaname_new, + List *keys, StringInfo target_list, const char* count_colname); static char *get_matching_condition_string(List *keys); static void generate_equal(StringInfo querybuf, Oid opttype, const char *leftop, const char *rightop); @@ -267,6 +271,7 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString, Oid matviewOid; Relation matviewRel; Query *dataQuery; + Query *viewQuery; Oid tableSpace; Oid relowner; Oid OIDNewHeap; @@ -329,8 +334,13 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString, "CONCURRENTLY", "WITH NO DATA"))); - dataQuery = get_matview_query(matviewRel); + viewQuery = get_matview_query(matviewRel); + /* For IMMV, we need to rewrite matview query */ + if (!stmt->skipData && RelationIsIVM(matviewRel)) + dataQuery = rewriteQueryForIMMV(viewQuery,NIL); + else + dataQuery = viewQuery; /* * Check that there is a unique index with no WHERE clause on one or more @@ -510,8 +520,8 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString, if (!stmt->skipData && RelationIsIVM(matviewRel) && !oldPopulated) { - CreateIndexOnIMMV(dataQuery, matviewRel); - CreateIvmTriggersOnBaseTables(dataQuery, matviewOid); + CreateIndexOnIMMV(viewQuery, matviewRel); + CreateIvmTriggersOnBaseTables(viewQuery, matviewOid); } table_close(matviewRel, NoLock); @@ -1533,6 +1543,13 @@ IVM_immediate_maintenance(PG_FUNCTION_ARGS) int rte_index = lfirst_int(lc2); TupleDesc tupdesc_old; TupleDesc tupdesc_new; + bool use_count = false; + char *count_colname = NULL; + + count_colname = pstrdup("__ivm_count__"); + + if (query->distinctClause) + use_count = true; /* calculate delta tables */ calc_delta(table, rte_index, rewritten, dest_old, dest_new, @@ -1545,7 +1562,8 @@ IVM_immediate_maintenance(PG_FUNCTION_ARGS) { /* apply the delta tables to the materialized view */ apply_delta(matviewOid, old_tuplestore, new_tuplestore, - tupdesc_old, tupdesc_new, query); + tupdesc_old, tupdesc_new, query, use_count, + count_colname); } PG_CATCH(); { @@ -2018,7 +2036,7 @@ rewrite_query_for_postupdate_state(Query *query, MV_TriggerTable *table, int rte static void apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *new_tuplestores, TupleDesc tupdesc_old, TupleDesc tupdesc_new, - Query *query) + Query *query, bool use_count, char *count_colname) { StringInfoData querybuf; StringInfoData target_list_buf; @@ -2094,7 +2112,12 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *n if (rc != SPI_OK_REL_REGISTER) elog(ERROR, "SPI_register failed"); - apply_old_delta(matviewname, OLD_DELTA_ENRNAME, keys); + if (use_count) + /* apply old delta and get rows to be recalculated */ + apply_old_delta_with_count(matviewname, OLD_DELTA_ENRNAME, + keys, count_colname); + else + apply_old_delta(matviewname, OLD_DELTA_ENRNAME, keys); } /* For tuple insertion */ @@ -2116,7 +2139,11 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *n elog(ERROR, "SPI_register failed"); /* apply new delta */ - apply_new_delta(matviewname, NEW_DELTA_ENRNAME, &target_list_buf); + if (use_count) + apply_new_delta_with_count(matviewname, NEW_DELTA_ENRNAME, + keys, &target_list_buf, count_colname); + else + apply_new_delta(matviewname, NEW_DELTA_ENRNAME, &target_list_buf); } /* We're done maintaining the materialized view. */ @@ -2129,6 +2156,51 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *n elog(ERROR, "SPI_finish failed"); } +/* + * apply_old_delta_with_count + * + * Execute a query for applying a delta table given by deltname_old + * which contains tuples to be deleted from to a materialized view given by + * matviewname. This is used when counting is required, that is, the view + * has aggregate or distinct. + */ +static void +apply_old_delta_with_count(const char *matviewname, const char *deltaname_old, + List *keys, const char *count_colname) +{ + StringInfoData querybuf; + char *match_cond; + + /* build WHERE condition for searching tuples to be deleted */ + match_cond = get_matching_condition_string(keys); + + /* Search for matching tuples from the view and update or delete if found. */ + initStringInfo(&querybuf); + appendStringInfo(&querybuf, + "WITH t AS (" /* collecting tid of target tuples in the view */ + "SELECT diff.%s, " /* count column */ + "(diff.%s OPERATOR(pg_catalog.=) mv.%s) AS for_dlt, " + "mv.ctid " + "FROM %s AS mv, %s AS diff " + "WHERE %s" /* tuple matching condition */ + "), updt AS (" /* update a tuple if this is not to be deleted */ + "UPDATE %s AS mv SET %s = mv.%s OPERATOR(pg_catalog.-) t.%s " + "FROM t WHERE mv.ctid OPERATOR(pg_catalog.=) t.ctid AND NOT for_dlt " + ")" + /* delete a tuple if this is to be deleted */ + "DELETE FROM %s AS mv USING t " + "WHERE mv.ctid OPERATOR(pg_catalog.=) t.ctid AND for_dlt", + count_colname, + count_colname, count_colname, + matviewname, deltaname_old, + match_cond, + matviewname, count_colname, count_colname, count_colname, + matviewname); + + if (SPI_exec(querybuf.data, 0) != SPI_OK_DELETE) + elog(ERROR, "SPI_exec failed: %s", querybuf.data); +} + /* * apply_old_delta * @@ -2178,6 +2250,66 @@ apply_old_delta(const char *matviewname, const char *deltaname_old, elog(ERROR, "SPI_exec failed: %s", querybuf.data); } +/* + * apply_new_delta_with_count + * + * Execute a query for applying a delta table given by deltname_new + * which contains tuples to be inserted into a materialized view given by + * matviewname. This is used when counting is required, that is, the view + * has aggregate or distinct. Also, when a table in EXISTS sub queries + * is modified. + */ +static void +apply_new_delta_with_count(const char *matviewname, const char* deltaname_new, + List *keys, StringInfo target_list, const char* count_colname) +{ + StringInfoData querybuf; + StringInfoData returning_keys; + ListCell *lc; + char *match_cond = ""; + + /* build WHERE condition for searching tuples to be updated */ + match_cond = get_matching_condition_string(keys); + + /* build string of keys list */ + initStringInfo(&returning_keys); + if (keys) + { + foreach (lc, keys) + { + Form_pg_attribute attr = (Form_pg_attribute) lfirst(lc); + char *resname = NameStr(attr->attname); + appendStringInfo(&returning_keys, "%s", quote_qualified_identifier("mv", resname)); + if (lnext(keys, lc)) + appendStringInfo(&returning_keys, ", "); + } + } + else + appendStringInfo(&returning_keys, "NULL"); + + /* Search for matching tuples from the view and update if found or insert if not. */ + initStringInfo(&querybuf); + appendStringInfo(&querybuf, + "WITH updt AS (" /* update a tuple if this exists in the view */ + "UPDATE %s AS mv SET %s = mv.%s OPERATOR(pg_catalog.+) diff.%s " + "FROM %s AS diff " + "WHERE %s " /* tuple matching condition */ + "RETURNING %s" /* returning keys of updated tuples */ + ") INSERT INTO %s (%s) " /* insert a new tuple if this doesn't exist */ + "SELECT %s FROM %s AS diff " + "WHERE NOT EXISTS (SELECT 1 FROM updt AS mv WHERE %s);", + matviewname, count_colname, count_colname, count_colname, + deltaname_new, + match_cond, + returning_keys.data, + matviewname, target_list->data, + target_list->data, deltaname_new, + match_cond); + + if (SPI_exec(querybuf.data, 0) != SPI_OK_INSERT) + elog(ERROR, "SPI_exec failed: %s", querybuf.data); +} + /* * apply_new_delta * diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 9e9dc5c2c1..ddb0732542 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -57,6 +57,7 @@ #include "commands/cluster.h" #include "commands/comment.h" #include "commands/defrem.h" +#include "commands/matview.h" #include "commands/event_trigger.h" #include "commands/sequence.h" #include "commands/tablecmds.h" @@ -3696,6 +3697,14 @@ renameatt_internal(Oid myrelid, targetrelation = relation_open(myrelid, AccessExclusiveLock); renameatt_check(myrelid, RelationGetForm(targetrelation), recursing); + /* + * Don't rename IVM columns. + */ + if (RelationIsIVM(targetrelation) && isIvmName(oldattname)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("IVM column can not be renamed"))); + /* * if the 'recurse' flag is set then we are supposed to rename this * attribute in all classes that inherit from 'relname' (as well as in diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 3337b77ae6..c191f70a6f 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -510,6 +510,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node) WRITE_INT_FIELD(rellockmode); WRITE_UINT_FIELD(perminfoindex); WRITE_NODE_FIELD(tablesample); + WRITE_BOOL_FIELD(relisivm); break; case RTE_SUBQUERY: WRITE_NODE_FIELD(subquery); diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index c4d01a441a..ffcab8cda2 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -361,6 +361,7 @@ _readRangeTblEntry(void) READ_INT_FIELD(rellockmode); READ_UINT_FIELD(perminfoindex); READ_NODE_FIELD(tablesample); + READ_BOOL_FIELD(relisivm); break; case RTE_SUBQUERY: READ_NODE_FIELD(subquery); diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c index 2f64eaf0e3..a39358f125 100644 --- a/src/backend/parser/parse_relation.c +++ b/src/backend/parser/parse_relation.c @@ -36,6 +36,7 @@ #include "utils/rel.h" #include "utils/syscache.h" #include "utils/varlena.h" +#include "commands/matview.h" /* @@ -97,7 +98,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref, int count, int offset, int rtindex, int sublevels_up, int location, bool include_dropped, - List **colnames, List **colvars); + List **colnames, List **colvars, bool is_ivm); static int specialAttNum(const char *attname); static bool rte_visible_if_lateral(ParseState *pstate, RangeTblEntry *rte); static bool rte_visible_if_qualified(ParseState *pstate, RangeTblEntry *rte); @@ -1503,6 +1504,7 @@ addRangeTableEntry(ParseState *pstate, rte->inh = inh; rte->relkind = rel->rd_rel->relkind; rte->rellockmode = lockmode; + rte->relisivm = rel->rd_rel->relisivm; /* * Build the list of effective column names using user-supplied aliases @@ -1588,6 +1590,7 @@ addRangeTableEntryForRelation(ParseState *pstate, rte->inh = inh; rte->relkind = rel->rd_rel->relkind; rte->rellockmode = lockmode; + rte->relisivm = rel->rd_rel->relisivm; /* * Build the list of effective column names using user-supplied aliases @@ -2757,7 +2760,7 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up, expandTupleDesc(tupdesc, rte->eref, rtfunc->funccolcount, atts_done, rtindex, sublevels_up, location, - include_dropped, colnames, colvars); + include_dropped, colnames, colvars, false); } else if (functypclass == TYPEFUNC_SCALAR) { @@ -3025,7 +3028,7 @@ expandRelation(Oid relid, Alias *eref, int rtindex, int sublevels_up, expandTupleDesc(rel->rd_att, eref, rel->rd_att->natts, 0, rtindex, sublevels_up, location, include_dropped, - colnames, colvars); + colnames, colvars, RelationIsIVM(rel)); relation_close(rel, AccessShareLock); } @@ -3042,7 +3045,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref, int count, int offset, int rtindex, int sublevels_up, int location, bool include_dropped, - List **colnames, List **colvars) + List **colnames, List **colvars, bool is_ivm) { ListCell *aliascell; int varattno; @@ -3055,6 +3058,9 @@ expandTupleDesc(TupleDesc tupdesc, Alias *eref, int count, int offset, { Form_pg_attribute attr = TupleDescAttr(tupdesc, varattno); + if (is_ivm && isIvmName(NameStr(attr->attname)) && !MatViewIncrementalMaintenanceIsEnabled()) + continue; + if (attr->attisdropped) { if (include_dropped) @@ -3217,6 +3223,10 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem, Var *varnode = (Var *) lfirst(var); TargetEntry *te; + /* if transform * into columnlist with IMMV, remove IVM columns */ + if (rte->relisivm && isIvmName(label) && !MatViewIncrementalMaintenanceIsEnabled()) + continue; + te = makeTargetEntry((Expr *) varnode, (AttrNumber) pstate->p_next_resno++, label, diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c index 6cc9a8d8bf..5d22dbcfcf 100644 --- a/src/backend/rewrite/rewriteDefine.c +++ b/src/backend/rewrite/rewriteDefine.c @@ -614,7 +614,8 @@ checkRuleResultList(List *targetList, TupleDesc resultDesc, bool isSelect, attr->atttypmod)))); } - if (i != resultDesc->natts) + /* No check for materialized views since this could have special columns for IVM */ + if ((!isSelect || requireColumnNameMatch) && i != resultDesc->natts) ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), isSelect ? diff --git a/src/include/commands/createas.h b/src/include/commands/createas.h index 396ad1bb4c..6b47e66bfd 100644 --- a/src/include/commands/createas.h +++ b/src/include/commands/createas.h @@ -29,6 +29,8 @@ extern ObjectAddress ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *st extern void CreateIvmTriggersOnBaseTables(Query *qry, Oid matviewOid); extern void CreateIndexOnIMMV(Query *query, Relation matviewRel); +extern Query *rewriteQueryForIMMV(Query *query, List *colNames); + extern int GetIntoRelEFlags(IntoClause *intoClause); extern DestReceiver *CreateIntoRelDestReceiver(IntoClause *intoClause); diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 85a62b538e..1366946bb4 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -1106,6 +1106,8 @@ typedef struct RangeTblEntry Index perminfoindex pg_node_attr(query_jumble_ignore); /* sampling info, or NULL */ struct TableSampleClause *tablesample; + /* incrementally maintainable materialized view? */ + bool relisivm; /* * Fields valid for a subquery RTE (else NULL): -- 2.25.1