When creating materialized views, use REFRESH to load data.
authorJeff Davis <[email protected]>
Tue, 16 Jul 2024 22:41:29 +0000 (15:41 -0700)
committerJeff Davis <[email protected]>
Tue, 16 Jul 2024 22:41:29 +0000 (15:41 -0700)
Previously, CREATE MATERIALIZED VIEW ... WITH DATA populated the MV
the same way as CREATE TABLE ... AS.

Instead, 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.

Reported-by: Noah Misch
Backpatch-through: 17
Discussion: https://postgr.es/m/20240630222344[email protected]

src/backend/commands/createas.c
src/backend/commands/matview.c
src/include/commands/matview.h
src/test/regress/expected/namespace.out

index 62050f4dc590b8a17326e64405562e91cc4923b9..2c8a93b6e56f86b0c379e3c89eacc2354e7ea3bd 100644 (file)
@@ -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;
index ea05d4b224fa857692a7547efeb212351179ae02..84245b65f7dad8b67629d4e599b0b30f6d0b3166 100644 (file)
@@ -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);
    }
 
index 817b2ba0b6fc49c5287ff609e786b96b8e52b1db..a226b2e68fba5f1de74279ba9a424e0940469ad1 100644 (file)
@@ -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);
 
index 7d36e9cc0c4b2d09678e28e3ea491cbc293c4c42..dbbda72d3951af6f67e4c7d7c355e7e934d9d1d5 100644 (file)
@@ -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