On Tue, Dec 22, 2020 at 7:01 PM Bharath Rupireddy <bharath.rupireddyforpostg...@gmail.com> wrote: > Currently, $subject is not allowed. We do plan the mat view query > before every refresh. I propose to show the explain/explain analyze of > the select part of the mat view in case of Refresh Mat View(RMV). It > will be useful for the user to know what exactly is being planned and > executed as part of RMV. Please note that we already have > explain/explain analyze CTAS/Create Mat View(CMV), where we show the > explain/explain analyze of the select part. This proposal will do the > same thing. > > The behaviour can be like this: > EXPLAIN REFRESH MATERIALIZED VIEW mv1; --> will not refresh the mat > view, but shows the select part's plan of mat view. > EXPLAIN ANALYZE REFRESH MATERIALIZED VIEW mv1; --> will refresh the > mat view and shows the select part's plan of mat view. > > Thoughts? If okay, I will post a patch later.
Attaching below patches: 0001 - Rearrange Refresh Mat View Code - Currently, the function ExecRefreshMatView in matview.c is having many lines of code which is not at all good from readability and maintainability perspectives. This patch adds a few functions and moves the code from ExecRefreshMatView to them making the code look better. 0002 - EXPLAIN/EXPLAIN ANALYZE REFRESH MATERIALIZED VIEW support and tests. If this proposal is useful, I have few open points - 1) In the patch I have added a new mat view info parameter to ExplainOneQuery(), do we also need to add it to ExplainOneQuery_hook_type? 2) Do we document (under respective command pages or somewhere else) that we allow explain/explain analyze for a command? Thoughts? With Regards, Bharath Rupireddy. EnterpriseDB: http://www.enterprisedb.com
From e0422b72acbaed27182ad3816cdc921a0f962fe2 Mon Sep 17 00:00:00 2001 From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com> Date: Fri, 25 Dec 2020 14:03:17 +0530 Subject: [PATCH v1] Rearrange Refresh Mat View Code Currently, the function ExecRefreshMatView in matview.c is having many lines of code which is not at all good from readability and maintainability perspectives. This patch adds few functions and moves the code from ExecRefreshMatView to them making the code look better. --- src/backend/commands/matview.c | 452 ++++++++++++++++++++------------- 1 file changed, 273 insertions(+), 179 deletions(-) diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c index cfc63915f3..40cb436d16 100644 --- a/src/backend/commands/matview.c +++ b/src/backend/commands/matview.c @@ -64,7 +64,7 @@ static void transientrel_startup(DestReceiver *self, int operation, TupleDesc ty static bool transientrel_receive(TupleTableSlot *slot, DestReceiver *self); static void transientrel_shutdown(DestReceiver *self); static void transientrel_destroy(DestReceiver *self); -static uint64 refresh_matview_datafill(DestReceiver *dest, Query *query, +static uint64 refresh_matview_datafill(Oid OIDNewHeap, Query *query, const char *queryString); static char *make_temptable_name_n(char *tempname, int n); static void refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner, @@ -73,6 +73,16 @@ static void refresh_by_heap_swap(Oid matviewOid, Oid OIDNewHeap, char relpersist static bool is_usable_unique_index(Relation indexRel); static void OpenMatViewIncrementalMaintenance(void); static void CloseMatViewIncrementalMaintenance(void); +static Query *get_matview_query(RefreshMatViewStmt *stmt, Relation *rel, + Oid *objectId); +static Query *rewrite_refresh_matview_query(Query *dataQuery); +static Oid get_new_heap_oid(RefreshMatViewStmt *stmt, Relation matviewRel, + Oid matviewOid, char *relpersistence); +static void match_matview_with_new_data(RefreshMatViewStmt *stmt, + Relation matviewRel, Oid matviewOid, + Oid OIDNewHeap, Oid relowner, + int save_sec_context, + char relpersistence, uint64 processed); /* * SetMatViewPopulatedState @@ -140,127 +150,20 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString, { Oid matviewOid; Relation matviewRel; - RewriteRule *rule; - List *actions; Query *dataQuery; - Oid tableSpace; - Oid relowner; Oid OIDNewHeap; - DestReceiver *dest; uint64 processed = 0; - bool concurrent; - LOCKMODE lockmode; + Oid relowner; char relpersistence; Oid save_userid; int save_sec_context; int save_nestlevel; ObjectAddress address; - /* Determine strength of lock needed. */ - concurrent = stmt->concurrent; - lockmode = concurrent ? ExclusiveLock : AccessExclusiveLock; - - /* - * Get a lock until end of transaction. - */ - matviewOid = RangeVarGetRelidExtended(stmt->relation, - lockmode, 0, - RangeVarCallbackOwnsTable, NULL); - matviewRel = table_open(matviewOid, NoLock); - - /* Make sure it is a materialized view. */ - if (matviewRel->rd_rel->relkind != RELKIND_MATVIEW) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("\"%s\" is not a materialized view", - RelationGetRelationName(matviewRel)))); - - /* Check that CONCURRENTLY is not specified if not populated. */ - if (concurrent && !RelationIsPopulated(matviewRel)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("CONCURRENTLY cannot be used when the materialized view is not populated"))); - - /* Check that conflicting options have not been specified. */ - if (concurrent && stmt->skipData) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("CONCURRENTLY and WITH NO DATA options cannot be used together"))); - - /* - * Check that everything is correct for a refresh. Problems at this point - * are internal errors, so elog is sufficient. - */ - if (matviewRel->rd_rel->relhasrules == false || - matviewRel->rd_rules->numLocks < 1) - elog(ERROR, - "materialized view \"%s\" is missing rewrite information", - RelationGetRelationName(matviewRel)); - - if (matviewRel->rd_rules->numLocks > 1) - elog(ERROR, - "materialized view \"%s\" has too many rules", - RelationGetRelationName(matviewRel)); - - rule = matviewRel->rd_rules->rules[0]; - if (rule->event != CMD_SELECT || !(rule->isInstead)) - elog(ERROR, - "the rule for materialized view \"%s\" is not a SELECT INSTEAD OF rule", - RelationGetRelationName(matviewRel)); - - actions = rule->actions; - if (list_length(actions) != 1) - elog(ERROR, - "the rule for materialized view \"%s\" is not a single action", - RelationGetRelationName(matviewRel)); - - /* - * Check that there is a unique index with no WHERE clause on one or more - * columns of the materialized view if CONCURRENTLY is specified. - */ - if (concurrent) - { - List *indexoidlist = RelationGetIndexList(matviewRel); - ListCell *indexoidscan; - bool hasUniqueIndex = false; - - foreach(indexoidscan, indexoidlist) - { - Oid indexoid = lfirst_oid(indexoidscan); - Relation indexRel; - - indexRel = index_open(indexoid, AccessShareLock); - hasUniqueIndex = is_usable_unique_index(indexRel); - index_close(indexRel, AccessShareLock); - if (hasUniqueIndex) - break; - } - - list_free(indexoidlist); - - if (!hasUniqueIndex) - ereport(ERROR, - (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), - errmsg("cannot refresh materialized view \"%s\" concurrently", - quote_qualified_identifier(get_namespace_name(RelationGetNamespace(matviewRel)), - RelationGetRelationName(matviewRel))), - errhint("Create a unique index with no WHERE clause on one or more columns of the materialized view."))); - } - - /* - * The stored query was rewritten at the time of the MV definition, but - * has not been scribbled on by the planner. - */ - dataQuery = linitial_node(Query, actions); + /* Get the data generating query. */ + dataQuery = get_matview_query(stmt, &matviewRel, &matviewOid); - /* - * Check for active uses of the relation in the current transaction, such - * as open scans. - * - * NB: We count on this to protect us against problems with refreshing the - * data using TABLE_INSERT_FROZEN. - */ - CheckTableNotInUse(matviewRel, "REFRESH MATERIALIZED VIEW"); + relowner = matviewRel->rd_rel->relowner; /* * Tentatively mark the matview as populated or not (this will roll back @@ -281,27 +184,8 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString, save_sec_context | SECURITY_LOCAL_USERID_CHANGE); save_nestlevel = NewGUCNestLevel(); - /* Concurrent refresh builds new data in temp tablespace, and does diff. */ - if (concurrent) - { - tableSpace = GetDefaultTablespace(RELPERSISTENCE_TEMP, false); - relpersistence = RELPERSISTENCE_TEMP; - } - else - { - tableSpace = matviewRel->rd_rel->reltablespace; - relpersistence = matviewRel->rd_rel->relpersistence; - } - - /* - * Create the transient table that will receive the regenerated data. Lock - * it against access by any other process until commit (by which time it - * will be gone). - */ - OIDNewHeap = make_new_heap(matviewOid, tableSpace, relpersistence, - ExclusiveLock); - LockRelationOid(OIDNewHeap, AccessExclusiveLock); - dest = CreateTransientRelDestReceiver(OIDNewHeap); + OIDNewHeap = get_new_heap_oid(stmt, matviewRel, matviewOid, + &relpersistence); /* * Now lock down security-restricted operations. @@ -311,40 +195,16 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString, /* Generate the data, if wanted. */ if (!stmt->skipData) - processed = refresh_matview_datafill(dest, dataQuery, queryString); - - /* Make the matview match the newly generated data. */ - if (concurrent) { - int old_depth = matview_maintenance_depth; + dataQuery = rewrite_refresh_matview_query(dataQuery); - PG_TRY(); - { - refresh_by_match_merge(matviewOid, OIDNewHeap, relowner, - save_sec_context); - } - PG_CATCH(); - { - matview_maintenance_depth = old_depth; - PG_RE_THROW(); - } - PG_END_TRY(); - Assert(matview_maintenance_depth == old_depth); + processed = refresh_matview_datafill(OIDNewHeap, dataQuery, + queryString); } - else - { - refresh_by_heap_swap(matviewOid, OIDNewHeap, relpersistence); - /* - * Inform stats collector about our activity: basically, we truncated - * the matview and inserted some new data. (The concurrent code path - * above doesn't need to worry about this because the inserts and - * deletes it issues get counted by lower-level code.) - */ - pgstat_count_truncate(matviewRel); - if (!stmt->skipData) - pgstat_count_heap_insert(matviewRel, processed); - } + match_matview_with_new_data(stmt, matviewRel, matviewOid, OIDNewHeap, + relowner, save_sec_context, relpersistence, + processed); table_close(matviewRel, NoLock); @@ -373,30 +233,18 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString, /* * refresh_matview_datafill * - * Execute the given query, sending result rows to "dest" (which will - * insert them into the target matview). + * Create dest receiver and execute the given query, sending result rows to the + * dest receiver which will insert them into the target materialized view. * * Returns number of rows inserted. */ static uint64 -refresh_matview_datafill(DestReceiver *dest, Query *query, - const char *queryString) +refresh_matview_datafill(Oid OIDNewHeap, Query *query, const char *queryString) { - List *rewritten; PlannedStmt *plan; QueryDesc *queryDesc; - Query *copied_query; uint64 processed; - - /* Lock and rewrite, using a copy to preserve the original query. */ - copied_query = copyObject(query); - AcquireRewriteLocks(copied_query, true, false); - rewritten = QueryRewrite(copied_query); - - /* SELECT should never rewrite to more or less than one SELECT query */ - if (list_length(rewritten) != 1) - elog(ERROR, "unexpected rewrite result for REFRESH MATERIALIZED VIEW"); - query = (Query *) linitial(rewritten); + DestReceiver *dest; /* Check for user-requested abort. */ CHECK_FOR_INTERRUPTS(); @@ -413,6 +261,8 @@ refresh_matview_datafill(DestReceiver *dest, Query *query, PushCopiedSnapshot(GetActiveSnapshot()); UpdateActiveSnapshotCommandId(); + dest = CreateTransientRelDestReceiver(OIDNewHeap); + /* Create a QueryDesc, redirecting output to our tuple receiver */ queryDesc = CreateQueryDesc(plan, queryString, GetActiveSnapshot(), InvalidSnapshot, @@ -933,3 +783,247 @@ CloseMatViewIncrementalMaintenance(void) matview_maintenance_depth--; Assert(matview_maintenance_depth >= 0); } + +/* + * get_matview_query + * + * Open the refresh materialized view relation, perform sanity checks and also + * get the associated data generating query from it. + * + * Note that the refresh materialized view relation is opened here, it has to + * be closed in the caller. + */ +static Query * +get_matview_query(RefreshMatViewStmt *stmt, Relation *rel, Oid *objectId) +{ + Oid matviewOid; + Relation matviewRel; + RewriteRule *rule; + List *actions; + Query *dataQuery; + LOCKMODE lockmode; + + /* Determine strength of lock needed. */ + lockmode = stmt->concurrent ? ExclusiveLock : AccessExclusiveLock; + + /* Get a lock until end of transaction. */ + matviewOid = RangeVarGetRelidExtended(stmt->relation, lockmode, 0, + RangeVarCallbackOwnsTable, NULL); + matviewRel = table_open(matviewOid, NoLock); + + /* Make sure it is a materialized view. */ + if (matviewRel->rd_rel->relkind != RELKIND_MATVIEW) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("\"%s\" is not a materialized view", + RelationGetRelationName(matviewRel)))); + + /* Check that CONCURRENTLY is not specified if not populated. */ + if (stmt->concurrent && !RelationIsPopulated(matviewRel)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("CONCURRENTLY cannot be used when the materialized view is not populated"))); + + /* Check that conflicting options have not been specified. */ + if (stmt->concurrent && stmt->skipData) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("CONCURRENTLY and WITH NO DATA options cannot be used together"))); + + /* + * Check that everything is correct for a refresh. Problems at this point + * are internal errors, so elog is sufficient. + */ + if (matviewRel->rd_rel->relhasrules == false || + matviewRel->rd_rules->numLocks < 1) + elog(ERROR, + "materialized view \"%s\" is missing rewrite information", + RelationGetRelationName(matviewRel)); + + if (matviewRel->rd_rules->numLocks > 1) + elog(ERROR, + "materialized view \"%s\" has too many rules", + RelationGetRelationName(matviewRel)); + + rule = matviewRel->rd_rules->rules[0]; + if (rule->event != CMD_SELECT || !(rule->isInstead)) + elog(ERROR, + "the rule for materialized view \"%s\" is not a SELECT INSTEAD OF rule", + RelationGetRelationName(matviewRel)); + + actions = rule->actions; + if (list_length(actions) != 1) + elog(ERROR, + "the rule for materialized view \"%s\" is not a single action", + RelationGetRelationName(matviewRel)); + + /* + * Check that there is a unique index with no WHERE clause on one or more + * columns of the materialized view if CONCURRENTLY is specified. + */ + if (stmt->concurrent) + { + List *indexoidlist = RelationGetIndexList(matviewRel); + ListCell *indexoidscan; + bool hasUniqueIndex = false; + + foreach(indexoidscan, indexoidlist) + { + Oid indexoid = lfirst_oid(indexoidscan); + Relation indexRel; + + indexRel = index_open(indexoid, AccessShareLock); + hasUniqueIndex = is_usable_unique_index(indexRel); + index_close(indexRel, AccessShareLock); + if (hasUniqueIndex) + break; + } + + list_free(indexoidlist); + + if (!hasUniqueIndex) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("cannot refresh materialized view \"%s\" concurrently", + quote_qualified_identifier(get_namespace_name(RelationGetNamespace(matviewRel)), + RelationGetRelationName(matviewRel))), + errhint("Create a unique index with no WHERE clause on one or more columns of the materialized view."))); + } + + /* + * The stored query was rewritten at the time of the MV definition, but + * has not been scribbled on by the planner. + */ + dataQuery = linitial_node(Query, actions); + + /* + * Check for active uses of the relation in the current transaction, such + * as open scans. + * + * NB: We count on this to protect us against problems with refreshing the + * data using TABLE_INSERT_FROZEN. + */ + CheckTableNotInUse(matviewRel, "REFRESH MATERIALIZED VIEW"); + + *rel = matviewRel; + *objectId = matviewOid; + + return dataQuery; +} + +/* + * get_new_heap_oid + * + * Create a new heap and return its oid to which the refresh materialized view + * data is inserted into. + */ +static Oid +get_new_heap_oid(RefreshMatViewStmt *stmt, Relation matviewRel, Oid matviewOid, + char *relpersistence) +{ + Oid OIDNewHeap; + bool concurrent; + Oid tableSpace; + + concurrent = stmt->concurrent; + + /* Concurrent refresh builds new data in temp tablespace, and does diff. */ + if (concurrent) + { + tableSpace = GetDefaultTablespace(RELPERSISTENCE_TEMP, false); + *relpersistence = RELPERSISTENCE_TEMP; + } + else + { + tableSpace = matviewRel->rd_rel->reltablespace; + *relpersistence = matviewRel->rd_rel->relpersistence; + } + + /* + * Create the transient table that will receive the regenerated data. Lock + * it against access by any other process until commit (by which time it + * will be gone). + */ + OIDNewHeap = make_new_heap(matviewOid, tableSpace, *relpersistence, + ExclusiveLock); + LockRelationOid(OIDNewHeap, AccessExclusiveLock); + + return OIDNewHeap; +} + +/* + * match_matview_with_new_data + * + * Arrange the materialized view newly generated data to match the existing + * data i.e merge in case of CONCURRENTLY otherwise perform heap swap and + * truncate the materialized view. + */ +static void +match_matview_with_new_data(RefreshMatViewStmt *stmt, Relation matviewRel, + Oid matviewOid, Oid OIDNewHeap, Oid relowner, + int save_sec_context, char relpersistence, + uint64 processed) +{ + /* Make the materialized view match the newly generated data. */ + if (stmt->concurrent) + { + int old_depth = matview_maintenance_depth; + + PG_TRY(); + { + refresh_by_match_merge(matviewOid, OIDNewHeap, relowner, + save_sec_context); + } + PG_CATCH(); + { + matview_maintenance_depth = old_depth; + PG_RE_THROW(); + } + PG_END_TRY(); + Assert(matview_maintenance_depth == old_depth); + } + else + { + refresh_by_heap_swap(matviewOid, OIDNewHeap, relpersistence); + + /* + * Inform stats collector about our activity: basically, we truncated + * the materialized view and inserted some new data. (The concurrent + * code path above doesn't need to worry about this because the inserts + * and deletes it issues get counted by lower-level code.) + */ + pgstat_count_truncate(matviewRel); + if (!stmt->skipData) + pgstat_count_heap_insert(matviewRel, processed); + } +} + +/* + * rewrite_refresh_matview_query + * + * Rewrite the refresh materialized view data generating query. + * + * Work on the copied query to preserve the original query. Because the + * rewriter and planner tend to scribble on the input, we make a preliminary + * copy of the source querytree. This prevents problems in the case that + * REFRESH MATERIALIZED VIEW is in a portal or plpgsql function and is executed + * repeatedly. (See also the same hack in EXPLAIN and PREPARE.) + */ +static Query * +rewrite_refresh_matview_query(Query *dataQuery) +{ + List *rewritten; + Query *copied_query; + + /* Lock and rewrite, using a copy to preserve the original query. */ + copied_query = copyObject(dataQuery); + AcquireRewriteLocks(copied_query, true, false); + rewritten = QueryRewrite(copied_query); + + /* SELECT should never rewrite to more or less than one SELECT query */ + if (list_length(rewritten) != 1) + elog(ERROR, "unexpected rewrite result for REFRESH MATERIALIZED VIEW"); + dataQuery = (Query *) linitial(rewritten); + + return dataQuery; +} -- 2.25.1
From 514aa9a4b84b4e72c74fc40cd8d9fe7169d75885 Mon Sep 17 00:00:00 2001 From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com> Date: Sat, 26 Dec 2020 10:20:48 +0530 Subject: [PATCH v1] EXPLAIN/EXPLAIN ANALYZE REFRESH MATERIALIZED VIEW Currently, explain/explain analyze refresh materialized view(RMV) is not allowed. We do plan the materialized view query before every refresh. I propose to show the explain/explain analyze of the select part of the materialized view. It will be useful for the user to know what exactly is being planned and executed as part of RMV. Please note that we already have explain/explain analyze CTAS/Create Mat View(CMV), where we show the explain/explain analyze of the select part. This proposal will do the same thing. The behaviour can be like this: EXPLAIN REFRESH MATERIALIZED VIEW mv1; --> will not refresh the mat view, but shows the select part's plan of mat view. EXPLAIN ANALYZE REFRESH MATERIALIZED VIEW mv1; --> will refresh the mat view and shows the select part's plan of mat view. --- src/backend/commands/explain.c | 50 +++++++++++----- src/backend/commands/matview.c | 36 +++++++++--- src/backend/commands/prepare.c | 3 +- src/backend/tcop/utility.c | 3 +- src/include/commands/explain.h | 30 +++++++++- src/include/commands/matview.h | 8 ++- src/test/regress/expected/matview.out | 83 +++++++++++++++++++++++++++ src/test/regress/sql/matview.sql | 27 +++++++++ 8 files changed, 215 insertions(+), 25 deletions(-) diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index 43f9b01e83..0189350416 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -17,6 +17,7 @@ #include "catalog/pg_type.h" #include "commands/createas.h" #include "commands/defrem.h" +#include "commands/matview.h" #include "commands/prepare.h" #include "executor/nodeHash.h" #include "foreign/fdwapi.h" @@ -53,10 +54,6 @@ explain_get_index_name_hook_type explain_get_index_name_hook = NULL; #define X_CLOSE_IMMEDIATE 2 #define X_NOWHITESPACE 4 -static void ExplainOneQuery(Query *query, int cursorOptions, - IntoClause *into, ExplainState *es, - const char *queryString, ParamListInfo params, - QueryEnvironment *queryEnv); static void ExplainPrintJIT(ExplainState *es, int jit_flags, JitInstrumentation *ji); static void report_triggers(ResultRelInfo *rInfo, bool show_relname, @@ -274,7 +271,8 @@ ExplainQuery(ParseState *pstate, ExplainStmt *stmt, { ExplainOneQuery(lfirst_node(Query, l), CURSOR_OPT_PARALLEL_OK, NULL, es, - pstate->p_sourcetext, params, pstate->p_queryEnv); + pstate->p_sourcetext, params, pstate->p_queryEnv, + NULL); /* Separate plans with an appropriate separator */ if (lnext(rewritten, l) != NULL) @@ -357,11 +355,11 @@ ExplainResultDesc(ExplainStmt *stmt) * * "into" is NULL unless we are explaining the contents of a CreateTableAsStmt. */ -static void +void ExplainOneQuery(Query *query, int cursorOptions, IntoClause *into, ExplainState *es, const char *queryString, ParamListInfo params, - QueryEnvironment *queryEnv) + QueryEnvironment *queryEnv, RefreshMatViewInfo *matviewInfo) { /* planner will not cope with utility statements */ if (query->commandType == CMD_UTILITY) @@ -402,7 +400,8 @@ ExplainOneQuery(Query *query, int cursorOptions, /* run it (if needed) and produce output */ ExplainOnePlan(plan, into, es, queryString, params, queryEnv, - &planduration, (es->buffers ? &bufusage : NULL)); + &planduration, (es->buffers ? &bufusage : NULL), + matviewInfo); } } @@ -439,7 +438,7 @@ ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es, Assert(list_length(rewritten) == 1); ExplainOneQuery(linitial_node(Query, rewritten), CURSOR_OPT_PARALLEL_OK, ctas->into, es, - queryString, params, queryEnv); + queryString, params, queryEnv, NULL); } else if (IsA(utilityStmt, DeclareCursorStmt)) { @@ -458,7 +457,8 @@ ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es, Assert(list_length(rewritten) == 1); ExplainOneQuery(linitial_node(Query, rewritten), dcs->options, NULL, es, - queryString, params, queryEnv); + queryString, params, queryEnv, + NULL); } else if (IsA(utilityStmt, ExecuteStmt)) ExplainExecuteQuery((ExecuteStmt *) utilityStmt, into, es, @@ -470,6 +470,16 @@ ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es, else ExplainDummyGroup("Notify", NULL, es); } + else if(IsA(utilityStmt, RefreshMatViewStmt)) + { + RefreshMatViewExplainInfo explainInfo; + + explainInfo.es = es; + explainInfo.queryEnv = queryEnv; + + ExecRefreshMatView((RefreshMatViewStmt *) utilityStmt, + queryString, params, NULL, &explainInfo); + } else { if (es->format == EXPLAIN_FORMAT_TEXT) @@ -496,7 +506,7 @@ void ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es, const char *queryString, ParamListInfo params, QueryEnvironment *queryEnv, const instr_time *planduration, - const BufferUsage *bufusage) + const BufferUsage *bufusage, RefreshMatViewInfo *matviewInfo) { DestReceiver *dest; QueryDesc *queryDesc; @@ -537,6 +547,8 @@ ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es, */ if (into) dest = CreateIntoRelDestReceiver(into); + else if(matviewInfo) + dest = CreateTransientRelDestReceiver(matviewInfo->OIDNewHeap); else dest = None_Receiver; @@ -561,8 +573,12 @@ ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es, { ScanDirection dir; - /* EXPLAIN ANALYZE CREATE TABLE AS WITH NO DATA is weird */ - if (into && into->skipData) + /* + * EXPLAIN ANALYZE CREATE TABLE AS or REFRESH MATERIALIZED VIEW + * WITH NO DATA is weird. + */ + if ((into && into->skipData) || + (matviewInfo && matviewInfo->skipData)) dir = NoMovementScanDirection; else dir = ForwardScanDirection; @@ -570,6 +586,14 @@ ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es, /* run the plan */ ExecutorRun(queryDesc, dir, 0L, true); + /* + * Collect the number of rows inserted in case of REFRESH MATERIALIZED + * VIEW which will be used while merging the newly generated data with + * the existing materialized view data in ExecRefreshMatView. + */ + if (matviewInfo) + matviewInfo->processed = queryDesc->estate->es_processed; + /* run cleanup too */ ExecutorFinish(queryDesc); diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c index 40cb436d16..53af40d4fa 100644 --- a/src/backend/commands/matview.c +++ b/src/backend/commands/matview.c @@ -146,7 +146,8 @@ SetMatViewPopulatedState(Relation relation, bool newstate) */ ObjectAddress ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString, - ParamListInfo params, QueryCompletion *qc) + ParamListInfo params, QueryCompletion *qc, + RefreshMatViewExplainInfo *explainInfo) { Oid matviewOid; Relation matviewRel; @@ -184,8 +185,11 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString, save_sec_context | SECURITY_LOCAL_USERID_CHANGE); save_nestlevel = NewGUCNestLevel(); - OIDNewHeap = get_new_heap_oid(stmt, matviewRel, matviewOid, - &relpersistence); + if (explainInfo && !explainInfo->es->analyze) + OIDNewHeap = InvalidOid; + else + OIDNewHeap = get_new_heap_oid(stmt, matviewRel, matviewOid, + &relpersistence); /* * Now lock down security-restricted operations. @@ -194,17 +198,35 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString, save_sec_context | SECURITY_RESTRICTED_OPERATION); /* Generate the data, if wanted. */ - if (!stmt->skipData) + if (!stmt->skipData && !explainInfo) { dataQuery = rewrite_refresh_matview_query(dataQuery); processed = refresh_matview_datafill(OIDNewHeap, dataQuery, queryString); } + else if (explainInfo) + { + RefreshMatViewInfo matViewInfo; + + matViewInfo.OIDNewHeap = OIDNewHeap; + matViewInfo.skipData = stmt->skipData; + matViewInfo.processed = 0; + + dataQuery = rewrite_refresh_matview_query(dataQuery); + + ExplainOneQuery(dataQuery, + CURSOR_OPT_PARALLEL_OK, NULL, explainInfo->es, + queryString, params, explainInfo->queryEnv, + &matViewInfo); + + processed = matViewInfo.processed; + } - match_matview_with_new_data(stmt, matviewRel, matviewOid, OIDNewHeap, - relowner, save_sec_context, relpersistence, - processed); + if (OidIsValid(OIDNewHeap)) + match_matview_with_new_data(stmt, matviewRel, matviewOid, OIDNewHeap, + relowner, save_sec_context, relpersistence, + processed); table_close(matviewRel, NoLock); diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c index 89087a7be3..07166479e7 100644 --- a/src/backend/commands/prepare.c +++ b/src/backend/commands/prepare.c @@ -672,7 +672,8 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, IntoClause *into, ExplainState *es, if (pstmt->commandType != CMD_UTILITY) ExplainOnePlan(pstmt, into, es, query_string, paramLI, queryEnv, - &planduration, (es->buffers ? &bufusage : NULL)); + &planduration, (es->buffers ? &bufusage : NULL), + NULL); else ExplainOneUtility(pstmt->utilityStmt, into, es, query_string, paramLI, queryEnv); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index a42ead7d69..3ffc6d180f 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1659,7 +1659,8 @@ ProcessUtilitySlow(ParseState *pstate, PG_TRY(); { address = ExecRefreshMatView((RefreshMatViewStmt *) parsetree, - queryString, params, qc); + queryString, params, qc, + NULL); } PG_FINALLY(); { diff --git a/src/include/commands/explain.h b/src/include/commands/explain.h index ba661d32a6..eccc3378b7 100644 --- a/src/include/commands/explain.h +++ b/src/include/commands/explain.h @@ -61,6 +61,27 @@ typedef struct ExplainState ExplainWorkersState *workers_state; /* needed if parallel plan */ } ExplainState; +/* + * Refresh Materialized View information passed across functions for EXPLAIN + * execution. + */ +typedef struct RefreshMatViewInfo +{ + /* Oid of the new heap created. */ + Oid OIDNewHeap; + /* Is WITH NO DATA clause specified? */ + bool skipData; + /* Number of rows inserted. */ + uint64 processed; +} RefreshMatViewInfo; + +/* EXPLAIN information shared to ExecRefreshMatView(). */ +typedef struct RefreshMatViewExplainInfo +{ + ExplainState *es; + QueryEnvironment *queryEnv; +} RefreshMatViewExplainInfo; + /* Hook for plugins to get control in ExplainOneQuery() */ typedef void (*ExplainOneQuery_hook_type) (Query *query, int cursorOptions, @@ -91,7 +112,14 @@ extern void ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es, const char *queryString, ParamListInfo params, QueryEnvironment *queryEnv, const instr_time *planduration, - const BufferUsage *bufusage); + const BufferUsage *bufusage, + RefreshMatViewInfo *matviewInfo); + +extern void ExplainOneQuery(Query *query, int cursorOptions, + IntoClause *into, ExplainState *es, + const char *queryString, ParamListInfo params, + QueryEnvironment *queryEnv, + RefreshMatViewInfo *matviewInfo); extern void ExplainPrintPlan(ExplainState *es, QueryDesc *queryDesc); extern void ExplainPrintTriggers(ExplainState *es, QueryDesc *queryDesc); diff --git a/src/include/commands/matview.h b/src/include/commands/matview.h index 3ea4f5c80b..ad3554cac8 100644 --- a/src/include/commands/matview.h +++ b/src/include/commands/matview.h @@ -15,6 +15,7 @@ #define MATVIEW_H #include "catalog/objectaddress.h" +#include "commands/explain.h" #include "nodes/params.h" #include "nodes/parsenodes.h" #include "tcop/dest.h" @@ -23,8 +24,11 @@ extern void SetMatViewPopulatedState(Relation relation, bool newstate); -extern ObjectAddress ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString, - ParamListInfo params, QueryCompletion *qc); +extern ObjectAddress ExecRefreshMatView(RefreshMatViewStmt *stmt, + const char *queryString, + ParamListInfo params, + QueryCompletion *qc, + RefreshMatViewExplainInfo *explainInfo); extern DestReceiver *CreateTransientRelDestReceiver(Oid oid); diff --git a/src/test/regress/expected/matview.out b/src/test/regress/expected/matview.out index 2c0760404d..0287917be8 100644 --- a/src/test/regress/expected/matview.out +++ b/src/test/regress/expected/matview.out @@ -630,3 +630,86 @@ drop cascades to materialized view matview_schema.mv_withdata2 drop cascades to materialized view matview_schema.mv_nodata1 drop cascades to materialized view matview_schema.mv_nodata2 DROP USER regress_matview_user; +-- test cases for explain/explain analyze refresh materialized view +CREATE TABLE mv_exp_tbl (a) AS SELECT * FROM generate_series(1, 10); +CREATE MATERIALIZED VIEW mv_exp (a) AS + SELECT * FROM mv_exp_tbl WHERE a > 5; +EXPLAIN (COSTS OFF) + REFRESH MATERIALIZED VIEW mv_exp WITH NO DATA; + QUERY PLAN +------------------------ + Seq Scan on mv_exp_tbl + Filter: (a > 5) +(2 rows) + +REFRESH MATERIALIZED VIEW mv_exp WITH NO DATA; +EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF) + REFRESH MATERIALIZED VIEW mv_exp WITH NO DATA; + QUERY PLAN +----------------------------------------- + Seq Scan on mv_exp_tbl (never executed) + Filter: (a > 5) +(2 rows) + +SELECT * FROM mv_exp ORDER BY 1; -- ERROR +ERROR: materialized view "mv_exp" has not been populated +HINT: Use the REFRESH MATERIALIZED VIEW command. +EXPLAIN (COSTS OFF) + REFRESH MATERIALIZED VIEW mv_exp; + QUERY PLAN +------------------------ + Seq Scan on mv_exp_tbl + Filter: (a > 5) +(2 rows) + +REFRESH MATERIALIZED VIEW mv_exp; +EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF) + REFRESH MATERIALIZED VIEW mv_exp; + QUERY PLAN +------------------------------------------------ + Seq Scan on mv_exp_tbl (actual rows=5 loops=1) + Filter: (a > 5) + Rows Removed by Filter: 5 +(3 rows) + +SELECT * FROM mv_exp ORDER BY 1; -- OK + a +---- + 6 + 7 + 8 + 9 + 10 +(5 rows) + +CREATE UNIQUE INDEX ON mv_exp (a); +EXPLAIN (COSTS OFF) + REFRESH MATERIALIZED VIEW CONCURRENTLY mv_exp; + QUERY PLAN +------------------------ + Seq Scan on mv_exp_tbl + Filter: (a > 5) +(2 rows) + +REFRESH MATERIALIZED VIEW CONCURRENTLY mv_exp; +EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF) + REFRESH MATERIALIZED VIEW CONCURRENTLY mv_exp; + QUERY PLAN +------------------------------------------------ + Seq Scan on mv_exp_tbl (actual rows=5 loops=1) + Filter: (a > 5) + Rows Removed by Filter: 5 +(3 rows) + +SELECT * FROM mv_exp ORDER BY 1; -- OK + a +---- + 6 + 7 + 8 + 9 + 10 +(5 rows) + +DROP MATERIALIZED VIEW mv_exp; +DROP TABLE mv_exp_tbl; diff --git a/src/test/regress/sql/matview.sql b/src/test/regress/sql/matview.sql index 70c4954d89..c13a7773aa 100644 --- a/src/test/regress/sql/matview.sql +++ b/src/test/regress/sql/matview.sql @@ -264,3 +264,30 @@ ALTER DEFAULT PRIVILEGES FOR ROLE regress_matview_user DROP SCHEMA matview_schema CASCADE; DROP USER regress_matview_user; + +-- test cases for explain/explain analyze refresh materialized view +CREATE TABLE mv_exp_tbl (a) AS SELECT * FROM generate_series(1, 10); +CREATE MATERIALIZED VIEW mv_exp (a) AS + SELECT * FROM mv_exp_tbl WHERE a > 5; +EXPLAIN (COSTS OFF) + REFRESH MATERIALIZED VIEW mv_exp WITH NO DATA; +REFRESH MATERIALIZED VIEW mv_exp WITH NO DATA; +EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF) + REFRESH MATERIALIZED VIEW mv_exp WITH NO DATA; +SELECT * FROM mv_exp ORDER BY 1; -- ERROR +EXPLAIN (COSTS OFF) + REFRESH MATERIALIZED VIEW mv_exp; +REFRESH MATERIALIZED VIEW mv_exp; +EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF) + REFRESH MATERIALIZED VIEW mv_exp; +SELECT * FROM mv_exp ORDER BY 1; -- OK +CREATE UNIQUE INDEX ON mv_exp (a); +EXPLAIN (COSTS OFF) + REFRESH MATERIALIZED VIEW CONCURRENTLY mv_exp; +REFRESH MATERIALIZED VIEW CONCURRENTLY mv_exp; +EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF) + REFRESH MATERIALIZED VIEW CONCURRENTLY mv_exp; +SELECT * FROM mv_exp ORDER BY 1; -- OK + +DROP MATERIALIZED VIEW mv_exp; +DROP TABLE mv_exp_tbl; -- 2.25.1