diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c index 62050f4dc5..2c8a93b6e5 100644 --- a/src/backend/commands/createas.c +++ b/src/backend/commands/createas.c @@ -225,10 +225,8 @@ ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *stmt, Query *query = castNode(Query, stmt->query); IntoClause *into = stmt->into; bool is_matview = (into->viewQuery != NULL); + bool do_refresh = false; DestReceiver *dest; - Oid save_userid = InvalidOid; - int save_sec_context = 0; - int save_nestlevel = 0; ObjectAddress address; List *rewritten; PlannedStmt *plan; @@ -263,18 +261,13 @@ ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *stmt, Assert(query->commandType == CMD_SELECT); /* - * For materialized views, lock down security-restricted operations and - * arrange to make GUC variable changes local to this command. This is - * not necessary for security, but this keeps the behavior similar to - * REFRESH MATERIALIZED VIEW. Otherwise, one could create a materialized - * view not possible to refresh. + * For materialized views, always skip data during table creation, and use + * REFRESH instead (see below). */ if (is_matview) { - GetUserIdAndSecContext(&save_userid, &save_sec_context); - SetUserIdAndSecContext(save_userid, - save_sec_context | SECURITY_RESTRICTED_OPERATION); - save_nestlevel = NewGUCNestLevel(); + do_refresh = !into->skipData; + into->skipData = true; } if (into->skipData) @@ -346,13 +339,18 @@ ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *stmt, PopActiveSnapshot(); } - if (is_matview) + /* + * For materialized views, reuse the REFRESH logic, which locks down + * security-restricted operations and restricts the search_path. This + * reduces the chance that a subsequent refresh will fail. + */ + if (do_refresh) { - /* Roll back any GUC changes */ - AtEOXact_GUC(false, save_nestlevel); + RefreshMatViewByOid(address.objectId, false, false, + pstate->p_sourcetext, NULL, qc); - /* Restore userid and security context */ - SetUserIdAndSecContext(save_userid, save_sec_context); + if (qc) + qc->commandTag = CMDTAG_SELECT; } return address; diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c index ea05d4b224..84245b65f7 100644 --- a/src/backend/commands/matview.c +++ b/src/backend/commands/matview.c @@ -112,15 +112,44 @@ SetMatViewPopulatedState(Relation relation, bool newstate) /* * ExecRefreshMatView -- execute a REFRESH MATERIALIZED VIEW command * + * If WITH NO DATA was specified, this is effectively like a TRUNCATE; + * otherwise it is like a TRUNCATE followed by an INSERT using the SELECT + * statement associated with the materialized view. The statement node's + * skipData field shows whether the clause was used. + */ +ObjectAddress +ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString, + ParamListInfo params, QueryCompletion *qc) +{ + Oid matviewOid; + 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, + RangeVarCallbackMaintainsTable, + NULL); + + return RefreshMatViewByOid(matviewOid, stmt->skipData, stmt->concurrent, + queryString, params, qc); +} + +/* + * RefreshMatViewByOid -- refresh materialized view by OID + * * This refreshes the materialized view by creating a new table and swapping * the relfilenumbers of the new table and the old materialized view, so the OID * of the original materialized view is preserved. Thus we do not lose GRANT * nor references to this materialized view. * - * If WITH NO DATA was specified, this is effectively like a TRUNCATE; - * otherwise it is like a TRUNCATE followed by an INSERT using the SELECT - * statement associated with the materialized view. The statement node's - * skipData field shows whether the clause was used. + * If skipData is true, this is effectively like a TRUNCATE; otherwise it is + * like a TRUNCATE followed by an INSERT using the SELECT statement associated + * with the materialized view. * * Indexes are rebuilt too, via REINDEX. Since we are effectively bulk-loading * the new heap, it's better to create the indexes afterwards than to fill them @@ -130,10 +159,10 @@ SetMatViewPopulatedState(Relation relation, bool newstate) * reflect the result set of the materialized view's query. */ ObjectAddress -ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString, - ParamListInfo params, QueryCompletion *qc) +RefreshMatViewByOid(Oid matviewOid, bool skipData, bool concurrent, + const char *queryString, ParamListInfo params, + QueryCompletion *qc) { - Oid matviewOid; Relation matviewRel; RewriteRule *rule; List *actions; @@ -143,25 +172,12 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString, Oid OIDNewHeap; DestReceiver *dest; uint64 processed = 0; - bool concurrent; - LOCKMODE lockmode; 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, - RangeVarCallbackMaintainsTable, - NULL); matviewRel = table_open(matviewOid, NoLock); relowner = matviewRel->rd_rel->relowner; @@ -190,7 +206,7 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString, errmsg("CONCURRENTLY cannot be used when the materialized view is not populated"))); /* Check that conflicting options have not been specified. */ - if (concurrent && stmt->skipData) + if (concurrent && skipData) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("%s and %s options cannot be used together", @@ -275,7 +291,7 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString, * Tentatively mark the matview as populated or not (this will roll back * if we fail later). */ - SetMatViewPopulatedState(matviewRel, !stmt->skipData); + SetMatViewPopulatedState(matviewRel, !skipData); /* Concurrent refresh builds new data in temp tablespace, and does diff. */ if (concurrent) @@ -301,7 +317,7 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString, dest = CreateTransientRelDestReceiver(OIDNewHeap); /* Generate the data, if wanted. */ - if (!stmt->skipData) + if (!skipData) processed = refresh_matview_datafill(dest, dataQuery, queryString); /* Make the matview match the newly generated data. */ @@ -333,7 +349,7 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString, * inserts and deletes it issues get counted by lower-level code.) */ pgstat_count_truncate(matviewRel); - if (!stmt->skipData) + if (!skipData) pgstat_count_heap_insert(matviewRel, processed); } diff --git a/src/include/commands/matview.h b/src/include/commands/matview.h index 817b2ba0b6..a226b2e68f 100644 --- a/src/include/commands/matview.h +++ b/src/include/commands/matview.h @@ -25,6 +25,9 @@ extern void SetMatViewPopulatedState(Relation relation, bool newstate); extern ObjectAddress ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString, ParamListInfo params, QueryCompletion *qc); +extern ObjectAddress RefreshMatViewByOid(Oid matviewOid, bool skipData, bool concurrent, + const char *queryString, ParamListInfo params, + QueryCompletion *qc); extern DestReceiver *CreateTransientRelDestReceiver(Oid transientoid); diff --git a/src/test/regress/expected/namespace.out b/src/test/regress/expected/namespace.out index 7d36e9cc0c..dbbda72d39 100644 --- a/src/test/regress/expected/namespace.out +++ b/src/test/regress/expected/namespace.out @@ -129,8 +129,8 @@ $$; CREATE TABLE test_maint(i INT); INSERT INTO test_maint VALUES (1), (2); CREATE MATERIALIZED VIEW test_maint_mv AS SELECT fn(i) FROM test_maint; -NOTICE: current search_path: test_maint_search_path -NOTICE: current search_path: test_maint_search_path +NOTICE: current search_path: pg_catalog, pg_temp +NOTICE: current search_path: pg_catalog, pg_temp -- the following commands should see search_path as pg_catalog, pg_temp CREATE INDEX test_maint_idx ON test_maint_search_path.test_maint (fn(i)); NOTICE: current search_path: pg_catalog, pg_temp