Add grantable MAINTAIN privilege and pg_maintain role.
authorJeff Davis
Wed, 14 Dec 2022 01:33:28 +0000 (17:33 -0800)
committerJeff Davis
Wed, 14 Dec 2022 01:33:28 +0000 (17:33 -0800)
Allows VACUUM, ANALYZE, REINDEX, REFRESH MATERIALIZED VIEW, CLUSTER,
and LOCK TABLE.

Effectively reverts 4441fc704d. Instead of creating separate
privileges for VACUUM, ANALYZE, and other maintenance commands, group
them together under a single MAINTAIN privilege.

Author: Nathan Bossart
Discussion: https://postgr.es/m/20221212210136.GA449764@nathanxps13
Discussion: https://postgr.es/m/45224.1670476523@sss.pgh.pa.us

36 files changed:
doc/src/sgml/ddl.sgml
doc/src/sgml/func.sgml
doc/src/sgml/ref/alter_default_privileges.sgml
doc/src/sgml/ref/analyze.sgml
doc/src/sgml/ref/cluster.sgml
doc/src/sgml/ref/grant.sgml
doc/src/sgml/ref/lock.sgml
doc/src/sgml/ref/refresh_materialized_view.sgml
doc/src/sgml/ref/reindex.sgml
doc/src/sgml/ref/revoke.sgml
doc/src/sgml/ref/vacuum.sgml
doc/src/sgml/user-manag.sgml
src/backend/catalog/aclchk.c
src/backend/commands/analyze.c
src/backend/commands/cluster.c
src/backend/commands/indexcmds.c
src/backend/commands/lockcmds.c
src/backend/commands/matview.c
src/backend/commands/tablecmds.c
src/backend/commands/vacuum.c
src/backend/parser/gram.y
src/backend/utils/adt/acl.c
src/bin/pg_dump/dumputils.c
src/bin/pg_dump/t/002_pg_dump.pl
src/bin/psql/tab-complete.c
src/include/catalog/catversion.h
src/include/catalog/pg_authid.dat
src/include/commands/tablecmds.h
src/include/nodes/parsenodes.h
src/include/utils/acl.h
src/test/regress/expected/dependency.out
src/test/regress/expected/privileges.out
src/test/regress/expected/rowsecurity.out
src/test/regress/expected/vacuum.out
src/test/regress/sql/dependency.sql
src/test/regress/sql/privileges.sql

index 38618de01c528b94f4c49874ceaae0ac509d12c1..6e92bbddd2a3ed9ba8f09013a160fbe84a590408 100644 (file)
@@ -1692,8 +1692,7 @@ ALTER TABLE products RENAME TO items;
    TRUNCATEREFERENCESTRIGGER,
    CREATECONNECTTEMPORARY,
    EXECUTEUSAGESET,
-   ALTER SYSTEMVACUUM, and
-   ANALYZE.
+   ALTER SYSTEM, and MAINTAIN.
    The privileges applicable to a particular
    object vary depending on the object's type (table, function, etc.).
    More detail about the meanings of these privileges appears below.
@@ -1985,19 +1984,13 @@ REVOKE ALL ON accounts FROM PUBLIC;
     
 
    
-    VACUUM
+    MAINTAIN
     
      
-      Allows VACUUM on a relation.
-     
-    
-   
-
-   
-    ANALYZE
-    
-     
-      Allows ANALYZE on a relation.
+      Allows VACUUMANALYZE,
+      CLUSTERREFRESH MATERIALIZED VIEW,
+      REINDEX, and LOCK TABLE on a
+      relation.
      
     
    
@@ -2151,13 +2144,8 @@ REVOKE ALL ON accounts FROM PUBLIC;
       PARAMETER
      
      
-      VACUUM
-      v
-      TABLE
-     
-     
-      ANALYZE
-      z
+      MAINTAIN
+      m
       TABLE
      
      
@@ -2250,7 +2238,7 @@ REVOKE ALL ON accounts FROM PUBLIC;
      
      
       TABLE (and table-like objects)
-      arwdDxtvz
+      arwdDxtm
       none
       \dp
      
@@ -2308,12 +2296,12 @@ GRANT SELECT (col1), UPDATE (col1) ON mytable TO miriam_rw;
    would show:
 
 => \dp mytable
-                                   Access privileges
- Schema |  Name   | Type  |    Access privileges    |   Column privileges   | Policies
---------+---------+-------+-------------------------+-----------------------+----------
- public | mytable | table | miriam=arwdDxtvz/miriam+| col1:                +|
-        |         |       | =r/miriam              +|   miriam_rw=rw/miriam |
-        |         |       | admin=arw/miriam        |                       |
+                                  Access privileges
+ Schema |  Name   | Type  |   Access privileges    |   Column privileges   | Policies
+--------+---------+-------+------------------------+-----------------------+----------
+ public | mytable | table | miriam=arwdDxtm/miriam+| col1:                +|
+        |         |       | =r/miriam             +|   miriam_rw=rw/miriam |
+        |         |       | admin=arw/miriam       |                       |
 (1 row)
 
   
index ad31fdb737c789db08004307eeebd4dbe75a967f..1cd8b11334db3e19a8ac9bad199a066bbb047814 100644 (file)
@@ -22995,8 +22995,7 @@ SELECT has_function_privilege('joeuser', 'myfunc(int, text)', 'execute');
         are SELECTINSERT,
         UPDATEDELETE,
         TRUNCATEREFERENCES,
-        TRIGGERVACUUM and
-        ANALYZE.
+        TRIGGER, and MAINTAIN.
        
       
 
index 0da295daffa5da3f097521305424c8a57f6c099b..a33461fbc2f4372e76e71150d53b298772a98a68 100644 (file)
@@ -28,7 +28,7 @@ ALTER DEFAULT PRIVILEGES
 
 where abbreviated_grant_or_revoke is one of:
 
-GRANT { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER | VACUUM | ANALYZE }
+GRANT { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER | MAINTAIN }
     [, ...] | ALL [ PRIVILEGES ] }
     ON TABLES
     TO { [ GROUP ] role_name | PUBLIC } [, ...] [ WITH GRANT OPTION ]
@@ -51,7 +51,7 @@ GRANT { USAGE | CREATE | ALL [ PRIVILEGES ] }
     TO { [ GROUP ] role_name | PUBLIC } [, ...] [ WITH GRANT OPTION ]
 
 REVOKE [ GRANT OPTION FOR ]
-    { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER | VACUUM | ANALYZE }
+    { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER | MAINTAIN }
     [, ...] | ALL [ PRIVILEGES ] }
     ON TABLES
     FROM { [ GROUP ] role_name | PUBLIC } [, ...]
index 16c0b886fd0d0df0745d6f02e77c83c2c5035cc5..a26834da4f9753d7c88c4ea0ca0916ae606c70f9 100644 (file)
@@ -148,16 +148,15 @@ ANALYZE [ VERBOSE ] [ table_and_columns
   Notes
 
   
-   To analyze a table, one must ordinarily have the ANALYZE
+   To analyze a table, one must ordinarily have the MAINTAIN
    privilege on the table or be the table's owner, a superuser, or a role with
    privileges of the
-   pg_analyze_all_tables
-   role.
-   However, database owners are allowed to
+   pg_maintain
+   role.  However, database owners are allowed to
    analyze all tables in their databases, except shared catalogs.
    (The restriction for shared catalogs means that a true database-wide
    ANALYZE can only be performed by superusers and roles
-   with privileges of pg_analyze_all_tables.)
+   with privileges of pg_maintain.)
    ANALYZE will skip over any tables that the calling user
    does not have permission to analyze.
   
index c37f4236f17f12d2a2bdf351887eaa514cf05424..145101e6a57c429fa7f2811780f9fe67a89dee48 100644 (file)
@@ -69,9 +69,11 @@ CLUSTER [VERBOSE]
   
    CLUSTER without any parameter reclusters all the
    previously-clustered tables in the current database that the calling user
-   owns, or all such tables if called by a superuser.  This
-   form of CLUSTER cannot be executed inside a transaction
-   block.
+   owns or has the MAINTAIN privilege for, or all such tables
+   if called by a superuser or a role with privileges of the
+   pg_maintain
+   role.  This form of CLUSTER cannot be
+   executed inside a transaction block.
   
 
   
index c3c585be7ef58fff374f2c51679a87e1c1c6c1d9..c8ca2b1d6417a030e70930d1de15ebe0fb273888 100644 (file)
@@ -21,7 +21,7 @@ PostgreSQL documentation
 
  
 
-GRANT { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER | VACUUM | ANALYZE }
+GRANT { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER | MAINTAIN }
     [, ...] | ALL [ PRIVILEGES ] }
     ON { [ TABLE ] table_name [, ...]
          | ALL TABLES IN SCHEMA schema_name [, ...] }
@@ -193,8 +193,7 @@ GRANT role_name [, ...] TO 
      USAGE
      SET
      ALTER SYSTEM
-     VACUUM
-     ANALYZE
+     MAINTAIN
      
       
        Specific types of privileges, as defined in .
index 19e71942071a13bc3db7d6a12b5c5b2fdf2eb393..d9c5bf9a1d43eee7a747239d39e3fd365cf43672 100644 (file)
@@ -165,11 +165,17 @@ LOCK [ TABLE ] [ ONLY ] name [ * ]
   Notes
 
    
-    LOCK TABLE ... IN ACCESS SHARE MODE requires SELECT
-    privileges on the target table.  LOCK TABLE ... IN ROW EXCLUSIVE
-    MODE requires INSERTUPDATEDELETE,
-    or TRUNCATE privileges on the target table. All other forms of
-    LOCK require table-level UPDATEDELETE,
+    To lock a table, one must ordinarily have the MAINTAIN
+    privilege on the table or be the table's owner, a superuser, or a role
+    with privileges of the
+    pg_maintain
+    role. LOCK TABLE ... IN ACCESS SHARE MODE is allowed
+    with SELECT privileges on the target
+    table.  LOCK TABLE ... IN ROW EXCLUSIVE MODE is allowed
+    with INSERTUPDATEDELETE,
+    or TRUNCATE privileges on the target table. All other
+    forms of LOCK are allowed with
+    table-level UPDATEDELETE,
     or TRUNCATE privileges.
    
 
index 675d6090f3cd68b17da2ccfa1db851641795b47e..4d79b6ae7f73604054203026d51974c00f6c4fcb 100644 (file)
@@ -32,7 +32,10 @@ REFRESH MATERIALIZED VIEW [ CONCURRENTLY ] name
   
    REFRESH MATERIALIZED VIEW completely replaces the
    contents of a materialized view.  To execute this command you must be the
-   owner of the materialized view.  The old contents are discarded.  If
+   owner of the materialized view, have privileges of the
+   pg_maintain
+   role, or have the MAINTAIN
+   privilege on the materialized view.  The old contents are discarded.  If
    WITH DATA is specified (or defaults) the backing query
    is executed to provide the new data, and the materialized view is left in a
    scannable state.  If WITH NO DATA is specified no new
index fcbda881494a9003d8e1efbba8cc5c06f5bda46c..192513f34e0e0b0cf73e21efc11ef3cb5d8b93c2 100644 (file)
@@ -293,15 +293,20 @@ REINDEX [ ( option [, ...] ) ] { DA
 
   
    Reindexing a single index or table requires being the owner of that
-   index or table.  Reindexing a schema or database requires being the
-   owner of that schema or database.  Note specifically that it's thus
+   index or table, having privileges of the
+   pg_maintain
+   role, or having the MAINTAIN privilege on the
+   table.  Reindexing a schema or database requires being the
+   owner of that schema or database or having privileges of the
+   pg_maintain role.  Note specifically that it's thus
    possible for non-superusers to rebuild indexes of tables owned by
    other users.  However, as a special exception, when
    REINDEX DATABASEREINDEX SCHEMA
    or REINDEX SYSTEM is issued by a non-superuser,
    indexes on shared catalogs will be skipped unless the user owns the
-   catalog (which typically won't be the case).  Of course, superusers
-   can always reindex anything.
+   catalog (which typically won't be the case), has privileges of the
+   pg_maintain role, or has the MAINTAIN
+   privilege on the catalog.  Of course, superusers can always reindex anything.
   
 
   
index e28d192fd306ff281a4fa9452d679671340ffb49..8df492281a1c519754da38197b0a865c8a30c94d 100644 (file)
@@ -22,7 +22,7 @@ PostgreSQL documentation
  
 
 REVOKE [ GRANT OPTION FOR ]
-    { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER | VACUUM | ANALYZE }
+    { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER | MAINTAIN }
     [, ...] | ALL [ PRIVILEGES ] }
     ON { [ TABLE ] table_name [, ...]
          | ALL TABLES IN SCHEMA schema_name [, ...] }
index 9cd880ea34a1d6073493f6ed8263164477184baa..e14ead88267a5b49147cf5577775a0075ce31244 100644 (file)
@@ -356,16 +356,15 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ 
   Notes
 
    
-    To vacuum a table, one must ordinarily have the VACUUM
+    To vacuum a table, one must ordinarily have the MAINTAIN
     privilege on the table or be the table's owner, a superuser, or a role with
     privileges of the
-    pg_vacuum_all_tables
-    role.
-    However, database owners are allowed to
+    pg_maintain
+    role.  However, database owners are allowed to
     vacuum all tables in their databases, except shared catalogs.
     (The restriction for shared catalogs means that a true database-wide
     VACUUM can only be performed by superusers and roles
-    with privileges of pg_vacuum_all_tables.)
+    with privileges of pg_maintain.)
     VACUUM will skip over any tables that the calling user
     does not have permission to vacuum.
    
index 2bff4e47d07f2c5030eb5ae0a560f85035165ea5..77159879c7cfe58d722f796a7c824ba188cbe0df 100644 (file)
@@ -636,16 +636,15 @@ DROP ROLE doomed_role;
        command.
       
       
-       pg_vacuum_all_tables
-       Allow executing the
-       VACUUM command on
-       all tables.
-      
-      
-       pg_analyze_all_tables
-       Allow executing the
-       ANALYZE command on
-       all tables.
+       pg_maintain
+       Allow executing
+       VACUUM,
+       ANALYZE,
+       CLUSTER,
+       REFRESH MATERIALIZED VIEW,
+       REINDEX,
+       and LOCK TABLE on all
+       relations.
       
      
     
index e48fc1647a6779fe2ff7af7dcd0f5277b8c0d84f..b5019059e8cff3662461486659316b4944a5be96 100644 (file)
@@ -2618,10 +2618,8 @@ string_to_privilege(const char *privname)
        return ACL_SET;
    if (strcmp(privname, "alter system") == 0)
        return ACL_ALTER_SYSTEM;
-   if (strcmp(privname, "vacuum") == 0)
-       return ACL_VACUUM;
-   if (strcmp(privname, "analyze") == 0)
-       return ACL_ANALYZE;
+   if (strcmp(privname, "maintain") == 0)
+       return ACL_MAINTAIN;
    if (strcmp(privname, "rule") == 0)
        return 0;               /* ignore old RULE privileges */
    ereport(ERROR,
@@ -2663,10 +2661,8 @@ privilege_to_string(AclMode privilege)
            return "SET";
        case ACL_ALTER_SYSTEM:
            return "ALTER SYSTEM";
-       case ACL_VACUUM:
-           return "VACUUM";
-       case ACL_ANALYZE:
-           return "ANALYZE";
+       case ACL_MAINTAIN:
+           return "MAINTAIN";
        default:
            elog(ERROR, "unrecognized privilege: %d", (int) privilege);
    }
@@ -3401,24 +3397,15 @@ pg_class_aclmask_ext(Oid table_oid, Oid roleid, AclMode mask,
        result |= (mask & (ACL_INSERT | ACL_UPDATE | ACL_DELETE));
 
    /*
-    * Check if ACL_VACUUM is being checked and, if so, and not already set as
+    * Check if ACL_MAINTAIN is being checked and, if so, and not already set as
     * part of the result, then check if the user is a member of the
-    * pg_vacuum_all_tables role, which allows VACUUM on all relations.
+    * pg_maintain role, which allows VACUUM, ANALYZE, CLUSTER, REFRESH
+    * MATERIALIZED VIEW, and REINDEX on all relations.
     */
-   if (mask & ACL_VACUUM &&
-       !(result & ACL_VACUUM) &&
-       has_privs_of_role(roleid, ROLE_PG_VACUUM_ALL_TABLES))
-       result |= ACL_VACUUM;
-
-   /*
-    * Check if ACL_ANALYZE is being checked and, if so, and not already set as
-    * part of the result, then check if the user is a member of the
-    * pg_analyze_all_tables role, which allows ANALYZE on all relations.
-    */
-   if (mask & ACL_ANALYZE &&
-       !(result & ACL_ANALYZE) &&
-       has_privs_of_role(roleid, ROLE_PG_ANALYZE_ALL_TABLES))
-       result |= ACL_ANALYZE;
+   if (mask & ACL_MAINTAIN &&
+       !(result & ACL_MAINTAIN) &&
+       has_privs_of_role(roleid, ROLE_PG_MAINTAIN))
+       result |= ACL_MAINTAIN;
 
    return result;
 }
index 38bccafa0523ab7f2b8b780c3c2e37b567be7193..da1f0f043bd25931c1181605f98d06a6e9564744 100644 (file)
@@ -167,7 +167,7 @@ analyze_rel(Oid relid, RangeVar *relation,
     */
    if (!vacuum_is_permitted_for_relation(RelationGetRelid(onerel),
                                          onerel->rd_rel,
-                                         VACOPT_ANALYZE))
+                                         params->options & VACOPT_ANALYZE))
    {
        relation_close(onerel, ShareUpdateExclusiveLock);
        return;
index 07e091bb87cf50d2f4f7de99322061873c201012..8966b75bd11e2c69bef67a23a0a338f2b84ceff6 100644 (file)
@@ -147,7 +147,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
        tableOid = RangeVarGetRelidExtended(stmt->relation,
                                            AccessExclusiveLock,
                                            0,
-                                           RangeVarCallbackOwnsTable, NULL);
+                                           RangeVarCallbackMaintainsTable,
+                                           NULL);
        rel = table_open(tableOid, NoLock);
 
        /*
@@ -364,8 +365,9 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
     */
    if (recheck)
    {
-       /* Check that the user still owns the relation */
-       if (!object_ownercheck(RelationRelationId, tableOid, save_userid))
+       /* Check that the user still has privileges for the relation */
+       if (!object_ownercheck(RelationRelationId, tableOid, save_userid) &&
+           pg_class_aclcheck(tableOid, save_userid, ACL_MAINTAIN) != ACLCHECK_OK)
        {
            relation_close(OldHeap, AccessExclusiveLock);
            goto out;
@@ -1612,7 +1614,7 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 
 
 /*
- * Get a list of tables that the current user owns and
+ * Get a list of tables that the current user has privileges on and
  * have indisclustered set.  Return the list in a List * of RelToCluster
  * (stored in the specified memory context), each one giving the tableOid
  * and the indexOid on which the table is already clustered.
@@ -1629,8 +1631,8 @@ get_tables_to_cluster(MemoryContext cluster_context)
    List       *rtcs = NIL;
 
    /*
-    * Get all indexes that have indisclustered set and are owned by
-    * appropriate user.
+    * Get all indexes that have indisclustered set and that the current user
+    * has the appropriate privileges for.
     */
    indRelation = table_open(IndexRelationId, AccessShareLock);
    ScanKeyInit(&entry,
@@ -1644,7 +1646,8 @@ get_tables_to_cluster(MemoryContext cluster_context)
 
        index = (Form_pg_index) GETSTRUCT(indexTuple);
 
-       if (!object_ownercheck(RelationRelationId, index->indrelid, GetUserId()))
+       if (!object_ownercheck(RelationRelationId, index->indrelid, GetUserId()) &&
+           pg_class_aclcheck(index->indrelid, GetUserId(), ACL_MAINTAIN) != ACLCHECK_OK)
            continue;
 
        /* Use a permanent memory context for the result list */
@@ -1694,6 +1697,7 @@ get_tables_to_cluster_partitioned(MemoryContext cluster_context, Oid indexOid)
 
        /* Silently skip partitions which the user has no access to. */
        if (!object_ownercheck(RelationRelationId, relid, GetUserId()) &&
+           pg_class_aclcheck(relid, GetUserId(), ACL_MAINTAIN) != ACLCHECK_OK &&
            (!object_ownercheck(DatabaseRelationId, MyDatabaseId, GetUserId()) ||
             IsSharedRelation(relid)))
            continue;
index b5b860c3abf700174e748f4fcd18d23424c25cfd..7dc1aca8fe6b665fdaa1fbce32cacfbafb3b00ec 100644 (file)
@@ -26,6 +26,7 @@
 #include "catalog/index.h"
 #include "catalog/indexing.h"
 #include "catalog/pg_am.h"
+#include "catalog/pg_authid.h"
 #include "catalog/pg_constraint.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2754,6 +2755,7 @@ RangeVarCallbackForReindexIndex(const RangeVar *relation,
    char        relkind;
    struct ReindexIndexCallbackState *state = arg;
    LOCKMODE    table_lockmode;
+   Oid         table_oid;
 
    /*
     * Lock level here should match table lock in reindex_index() for
@@ -2793,14 +2795,16 @@ RangeVarCallbackForReindexIndex(const RangeVar *relation,
                 errmsg("\"%s\" is not an index", relation->relname)));
 
    /* Check permissions */
-   if (!object_ownercheck(RelationRelationId, relId, GetUserId()))
-       aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX, relation->relname);
+   table_oid = IndexGetRelation(relId, true);
+   if (!object_ownercheck(RelationRelationId, relId, GetUserId()) &&
+       OidIsValid(table_oid) &&
+       pg_class_aclcheck(table_oid, GetUserId(), ACL_MAINTAIN) != ACLCHECK_OK)
+       aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
+                      relation->relname);
 
    /* Lock heap before index to avoid deadlock. */
    if (relId != oldRelId)
    {
-       Oid         table_oid = IndexGetRelation(relId, true);
-
        /*
         * If the OID isn't valid, it means the index was concurrently
         * dropped, which is not a problem for us; just return normally.
@@ -2835,7 +2839,7 @@ ReindexTable(RangeVar *relation, ReindexParams *params, bool isTopLevel)
                                       (params->options & REINDEXOPT_CONCURRENTLY) != 0 ?
                                       ShareUpdateExclusiveLock : ShareLock,
                                       0,
-                                      RangeVarCallbackOwnsTable, NULL);
+                                      RangeVarCallbackMaintainsTable, NULL);
 
    if (get_rel_relkind(heapOid) == RELKIND_PARTITIONED_TABLE)
        ReindexPartitions(heapOid, params, isTopLevel);
@@ -2917,7 +2921,8 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
    {
        objectOid = get_namespace_oid(objectName, false);
 
-       if (!object_ownercheck(NamespaceRelationId, objectOid, GetUserId()))
+       if (!object_ownercheck(NamespaceRelationId, objectOid, GetUserId()) &&
+           !has_privs_of_role(GetUserId(), ROLE_PG_MAINTAIN))
            aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SCHEMA,
                           objectName);
    }
@@ -2929,7 +2934,8 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
            ereport(ERROR,
                    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                     errmsg("can only reindex the currently open database")));
-       if (!object_ownercheck(DatabaseRelationId, objectOid, GetUserId()))
+       if (!object_ownercheck(DatabaseRelationId, objectOid, GetUserId()) &&
+           !has_privs_of_role(GetUserId(), ROLE_PG_MAINTAIN))
            aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_DATABASE,
                           get_database_name(objectOid));
    }
@@ -3001,15 +3007,17 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
            continue;
 
        /*
-        * The table can be reindexed if the user is superuser, the table
-        * owner, or the database/schema owner (but in the latter case, only
-        * if it's not a shared relation).  object_ownercheck includes the
-        * superuser case, and depending on objectKind we already know that
-        * the user has permission to run REINDEX on this database or schema
-        * per the permission checks at the beginning of this routine.
+        * The table can be reindexed if the user has been granted MAINTAIN on
+        * the table or the user is a superuser, the table owner, or the
+        * database/schema owner (but in the latter case, only if it's not a
+        * shared relation).  object_ownercheck includes the superuser case,
+        * and depending on objectKind we already know that the user has
+        * permission to run REINDEX on this database or schema per the
+        * permission checks at the beginning of this routine.
         */
        if (classtuple->relisshared &&
-           !object_ownercheck(RelationRelationId, relid, GetUserId()))
+           !object_ownercheck(RelationRelationId, relid, GetUserId()) &&
+           pg_class_aclcheck(relid, GetUserId(), ACL_MAINTAIN) != ACLCHECK_OK)
            continue;
 
        /*
index b0747ce291e613ad22db373cb0765fe83e86f756..e294efc67c15e83f7f6dc84e07b533a1cb2c60c9 100644 (file)
@@ -300,6 +300,9 @@ LockTableAclCheck(Oid reloid, LOCKMODE lockmode, Oid userid)
    else
        aclmask = ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE;
 
+   /* MAINTAIN privilege allows all lock modes */
+   aclmask |= ACL_MAINTAIN;
+
    aclresult = pg_class_aclcheck(reloid, userid, aclmask);
 
    return aclresult;
index 9ac0383459873022407ba723c8169de15b0b8d45..8ba2436a71d0317443c11bfdb1352dcf28629bc8 100644 (file)
@@ -165,7 +165,8 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
     */
    matviewOid = RangeVarGetRelidExtended(stmt->relation,
                                          lockmode, 0,
-                                         RangeVarCallbackOwnsTable, NULL);
+                                         RangeVarCallbackMaintainsTable,
+                                         NULL);
    matviewRel = table_open(matviewOid, NoLock);
    relowner = matviewRel->rd_rel->relowner;
 
index 0b352a5fff6458b25b3b7ad9343aa6821c9ced8d..56dc99571360601bbf1cdf112f5def14b9de7edb 100644 (file)
@@ -16889,13 +16889,13 @@ AtEOSubXact_on_commit_actions(bool isCommit, SubTransactionId mySubid,
  * This is intended as a callback for RangeVarGetRelidExtended().  It allows
  * the relation to be locked only if (1) it's a plain or partitioned table,
  * materialized view, or TOAST table and (2) the current user is the owner (or
- * the superuser).  This meets the permission-checking needs of CLUSTER,
- * REINDEX TABLE, and REFRESH MATERIALIZED VIEW; we expose it here so that it
- * can be used by all.
+ * the superuser) or has been granted MAINTAIN.  This meets the
+ * permission-checking needs of CLUSTER, REINDEX TABLE, and REFRESH
+ * MATERIALIZED VIEW; we expose it here so that it can be used by all.
  */
 void
-RangeVarCallbackOwnsTable(const RangeVar *relation,
-                         Oid relId, Oid oldRelId, void *arg)
+RangeVarCallbackMaintainsTable(const RangeVar *relation,
+                              Oid relId, Oid oldRelId, void *arg)
 {
    char        relkind;
 
@@ -16918,8 +16918,10 @@ RangeVarCallbackOwnsTable(const RangeVar *relation,
                 errmsg("\"%s\" is not a table or materialized view", relation->relname)));
 
    /* Check permissions */
-   if (!object_ownercheck(RelationRelationId, relId, GetUserId()))
-       aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(get_rel_relkind(relId)), relation->relname);
+   if (!object_ownercheck(RelationRelationId, relId, GetUserId()) &&
+       pg_class_aclcheck(relId, GetUserId(), ACL_MAINTAIN) != ACLCHECK_OK)
+       aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_TABLE,
+                      relation->relname);
 }
 
 /*
index a6d5ed1f6b88b2d490728c778637bad63ff6c2f4..293b84bbca89be3d7ff98dbcc06a2133cb36159b 100644 (file)
@@ -557,7 +557,6 @@ vacuum_is_permitted_for_relation(Oid relid, Form_pg_class reltuple,
                                 bits32 options)
 {
    char       *relname;
-   AclMode     mode = 0;
 
    Assert((options & (VACOPT_VACUUM | VACOPT_ANALYZE)) != 0);
 
@@ -567,15 +566,11 @@ vacuum_is_permitted_for_relation(Oid relid, Form_pg_class reltuple,
     *   - the role is a superuser
     *   - the role owns the relation
     *   - the role owns the current database and the relation is not shared
-    *   - the role has been granted privileges to vacuum/analyze the relation
+    *   - the role has been granted the MAINTAIN privilege on the relation
     */
-   if (options & VACOPT_VACUUM)
-       mode |= ACL_VACUUM;
-   if (options & VACOPT_ANALYZE)
-       mode |= ACL_ANALYZE;
    if (object_ownercheck(RelationRelationId, relid, GetUserId()) ||
        (object_ownercheck(DatabaseRelationId, MyDatabaseId, GetUserId()) && !reltuple->relisshared) ||
-       pg_class_aclcheck(relid, GetUserId(), mode) == ACLCHECK_OK)
+       pg_class_aclcheck(relid, GetUserId(), ACL_MAINTAIN) == ACLCHECK_OK)
        return true;
 
    relname = NameStr(reltuple->relname);
@@ -1800,9 +1795,7 @@ vac_truncate_clog(TransactionId frozenXID,
  *     be stale.
  *
  *     Returns true if it's okay to proceed with a requested ANALYZE
- *     operation on this table.  Note that if vacuuming fails because the user
- *     does not have the required privileges, this function returns true since
- *     the user might have been granted privileges to ANALYZE the relation.
+ *     operation on this table.
  *
  *     Doing one heap at a time incurs extra overhead, since we need to
  *     check that the heap exists again just before we vacuum it.  The
@@ -1902,12 +1895,12 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
     */
    if (!vacuum_is_permitted_for_relation(RelationGetRelid(rel),
                                          rel->rd_rel,
-                                         VACOPT_VACUUM))
+                                         params->options & VACOPT_VACUUM))
    {
        relation_close(rel, lmode);
        PopActiveSnapshot();
        CommitTransactionCommand();
-       return true;    /* user might have the ANALYZE privilege */
+       return false;
    }
 
    /*
index adc3f8ced3b68f3a0c364e96a88eb1e642c1225c..63b4baaed9053a24054d92b7804c649996073b64 100644 (file)
@@ -7502,13 +7502,6 @@ privilege:   SELECT opt_column_list
                n->cols = NIL;
                $$ = n;
            }
-       | analyze_keyword
-           {
-               AccessPriv *n = makeNode(AccessPriv);
-               n->priv_name = pstrdup("analyze");
-               n->cols = NIL;
-               $$ = n;
-           }
        | ColId opt_column_list
            {
                AccessPriv *n = makeNode(AccessPriv);
index ed1b6a41cfb13f0b71157966ad23da9c17d2ecf4..bba953cd6e0b42899228ec535fac8122d017910b 100644 (file)
@@ -321,11 +321,8 @@ aclparse(const char *s, AclItem *aip)
            case ACL_ALTER_SYSTEM_CHR:
                read = ACL_ALTER_SYSTEM;
                break;
-           case ACL_VACUUM_CHR:
-               read = ACL_VACUUM;
-               break;
-           case ACL_ANALYZE_CHR:
-               read = ACL_ANALYZE;
+           case ACL_MAINTAIN_CHR:
+               read = ACL_MAINTAIN;
                break;
            case 'R':           /* ignore old RULE privileges */
                read = 0;
@@ -1601,8 +1598,7 @@ makeaclitem(PG_FUNCTION_ARGS)
        {"CONNECT", ACL_CONNECT},
        {"SET", ACL_SET},
        {"ALTER SYSTEM", ACL_ALTER_SYSTEM},
-       {"VACUUM", ACL_VACUUM},
-       {"ANALYZE", ACL_ANALYZE},
+       {"MAINTAIN", ACL_MAINTAIN},
        {"RULE", 0},            /* ignore old RULE privileges */
        {NULL, 0}
    };
@@ -1711,10 +1707,8 @@ convert_aclright_to_string(int aclright)
            return "SET";
        case ACL_ALTER_SYSTEM:
            return "ALTER SYSTEM";
-       case ACL_VACUUM:
-           return "VACUUM";
-       case ACL_ANALYZE:
-           return "ANALYZE";
+       case ACL_MAINTAIN:
+           return "MAINTAIN";
        default:
            elog(ERROR, "unrecognized aclright: %d", aclright);
            return NULL;
@@ -2024,10 +2018,8 @@ convert_table_priv_string(text *priv_type_text)
        {"REFERENCES WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_REFERENCES)},
        {"TRIGGER", ACL_TRIGGER},
        {"TRIGGER WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_TRIGGER)},
-       {"VACUUM", ACL_VACUUM},
-       {"VACUUM WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_VACUUM)},
-       {"ANALYZE", ACL_ANALYZE},
-       {"ANALYZE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_ANALYZE)},
+       {"MAINTAIN", ACL_MAINTAIN},
+       {"MAINTAIN WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_MAINTAIN)},
        {"RULE", 0},            /* ignore old RULE privileges */
        {"RULE WITH GRANT OPTION", 0},
        {NULL, 0}
index 0e20c66bee537b7a984503e0550904727d0ca935..f45cc6cb73f94efbc74fe52ec60b516a00337c5b 100644 (file)
@@ -463,8 +463,7 @@ do { \
                CONVERT_PRIV('d', "DELETE");
                CONVERT_PRIV('t', "TRIGGER");
                CONVERT_PRIV('D', "TRUNCATE");
-               CONVERT_PRIV('v', "VACUUM");
-               CONVERT_PRIV('z', "ANALYZE");
+               CONVERT_PRIV('m', "MAINTAIN");
            }
        }
 
index 4732ee2e4a88eb90729ab7b5837848490adedd44..1c7fc728c2a39a8dadf0fa00a2f66d8b85c7fbb8 100644 (file)
@@ -618,7 +618,7 @@ my %tests = (
            \QREVOKE ALL ON TABLES FROM regress_dump_test_role;\E\n
            \QALTER DEFAULT PRIVILEGES \E
            \QFOR ROLE regress_dump_test_role \E
-           \QGRANT INSERT,REFERENCES,DELETE,TRIGGER,TRUNCATE,VACUUM,ANALYZE,UPDATE ON TABLES TO regress_dump_test_role;\E
+           \QGRANT INSERT,REFERENCES,DELETE,TRIGGER,TRUNCATE,MAINTAIN,UPDATE ON TABLES TO regress_dump_test_role;\E
            /xm,
        like => { %full_runs, section_post_data => 1, },
        unlike => { no_privs => 1, },
index dd7d02161972c65e22b770556d11cc10f773837e..2a3921937c377a67f7db94bba9a6171602ac5ef1 100644 (file)
@@ -1147,7 +1147,7 @@ static const SchemaQuery Query_for_trigger_of_table = {
 #define Privilege_options_of_grant_and_revoke \
 "SELECT", "INSERT", "UPDATE", "DELETE", "TRUNCATE", "REFERENCES", "TRIGGER", \
 "CREATE", "CONNECT", "TEMPORARY", "EXECUTE", "USAGE", "SET", "ALTER SYSTEM", \
-"VACUUM", "ANALYZE", "ALL"
+"MAINTAIN", "ALL"
 
 /*
  * These object types were introduced later than our support cutoff of
@@ -3782,8 +3782,7 @@ psql_completion(const char *text, int start, int end)
        if (HeadMatches("ALTER", "DEFAULT", "PRIVILEGES"))
            COMPLETE_WITH("SELECT", "INSERT", "UPDATE",
                          "DELETE", "TRUNCATE", "REFERENCES", "TRIGGER",
-                         "CREATE", "EXECUTE", "USAGE", "VACUUM", "ANALYZE",
-                         "ALL");
+                         "CREATE", "EXECUTE", "USAGE", "MAINTAIN", "ALL");
        else if (TailMatches("GRANT"))
            COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_roles,
                                     Privilege_options_of_grant_and_revoke);
index f52ed8f929d572a552d84ef91850febfe1e708c1..5cb12437a67e658d075c6a75d884082b509c7fc9 100644 (file)
@@ -57,6 +57,6 @@
  */
 
 /*                         yyyymmddN */
-#define CATALOG_VERSION_NO 202212121
+#define CATALOG_VERSION_NO 202212131
 
 #endif
index 2574e2906de91f7736a567ad6a21871ec0fda978..11d62e82df9abdbfdc51918ba611d6d798f6f718 100644 (file)
   rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
   rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
   rolpassword => '_null_', rolvaliduntil => '_null_' },
-{ oid => '4549', oid_symbol => 'ROLE_PG_VACUUM_ALL_TABLES',
-  rolname => 'pg_vacuum_all_tables', rolsuper => 'f', rolinherit => 't',
-  rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
-  rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
-  rolpassword => '_null_', rolvaliduntil => '_null_' },
-{ oid => '4550', oid_symbol => 'ROLE_PG_ANALYZE_ALL_TABLES',
-  rolname => 'pg_analyze_all_tables', rolsuper => 'f', rolinherit => 't',
+{ oid => '4549', oid_symbol => 'ROLE_PG_MAINTAIN',
+  rolname => 'pg_maintain', rolsuper => 'f', rolinherit => 't',
   rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
   rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
   rolpassword => '_null_', rolvaliduntil => '_null_' },
index 03f14d6be1e73d5685213cde34c9c978db17fc6b..07eac9a26ca4f5ffbc2fcf8a712b5e6b5b9cafd2 100644 (file)
@@ -95,8 +95,9 @@ extern void AtEOSubXact_on_commit_actions(bool isCommit,
                                          SubTransactionId mySubid,
                                          SubTransactionId parentSubid);
 
-extern void RangeVarCallbackOwnsTable(const RangeVar *relation,
-                                     Oid relId, Oid oldRelId, void *arg);
+extern void RangeVarCallbackMaintainsTable(const RangeVar *relation,
+                                          Oid relId, Oid oldRelId,
+                                          void *arg);
 
 extern void RangeVarCallbackOwnsRelation(const RangeVar *relation,
                                         Oid relId, Oid oldRelId, void *arg);
index bebb9620b270b76484bc0bffa9546c150aab6646..34bc640ff29e7aa24669922d244d0be6af239fd3 100644 (file)
@@ -95,9 +95,8 @@ typedef uint64 AclMode;           /* a bitmask of privilege bits */
 #define ACL_CONNECT        (1<<11) /* for databases */
 #define ACL_SET            (1<<12) /* for configuration parameters */
 #define ACL_ALTER_SYSTEM (1<<13)   /* for configuration parameters */
-#define ACL_VACUUM     (1<<14) /* for relations */
-#define ACL_ANALYZE        (1<<15) /* for relations */
-#define N_ACL_RIGHTS   16      /* 1 plus the last 1<
+#define ACL_MAINTAIN       (1<<14) /* for relations */
+#define N_ACL_RIGHTS   15      /* 1 plus the last 1<
 #define ACL_NO_RIGHTS  0
 /* Currently, SELECT ... FOR [KEY] UPDATE/SHARE requires UPDATE privileges */
 #define ACL_SELECT_FOR_UPDATE  ACL_UPDATE
index e566ff0c730729a5023590784bf65c7428571e11..69eb437376d909690ede1c47ae9139253d913241 100644 (file)
@@ -148,17 +148,16 @@ typedef struct ArrayType Acl;
 #define ACL_CONNECT_CHR            'c'
 #define ACL_SET_CHR                's'
 #define ACL_ALTER_SYSTEM_CHR   'A'
-#define ACL_VACUUM_CHR         'v'
-#define ACL_ANALYZE_CHR            'z'
+#define ACL_MAINTAIN_CHR       'm'
 
 /* string holding all privilege code chars, in order by bitmask position */
-#define ACL_ALL_RIGHTS_STR "arwdDxtXUCTcsAvz"
+#define ACL_ALL_RIGHTS_STR "arwdDxtXUCTcsAm"
 
 /*
  * Bitmasks defining "all rights" for each supported object type
  */
 #define ACL_ALL_RIGHTS_COLUMN      (ACL_INSERT|ACL_SELECT|ACL_UPDATE|ACL_REFERENCES)
-#define ACL_ALL_RIGHTS_RELATION        (ACL_INSERT|ACL_SELECT|ACL_UPDATE|ACL_DELETE|ACL_TRUNCATE|ACL_REFERENCES|ACL_TRIGGER|ACL_VACUUM|ACL_ANALYZE)
+#define ACL_ALL_RIGHTS_RELATION        (ACL_INSERT|ACL_SELECT|ACL_UPDATE|ACL_DELETE|ACL_TRUNCATE|ACL_REFERENCES|ACL_TRIGGER|ACL_MAINTAIN)
 #define ACL_ALL_RIGHTS_SEQUENCE        (ACL_USAGE|ACL_SELECT|ACL_UPDATE)
 #define ACL_ALL_RIGHTS_DATABASE        (ACL_CREATE|ACL_CREATE_TEMP|ACL_CONNECT)
 #define ACL_ALL_RIGHTS_FDW         (ACL_USAGE)
index 81d8376509bcb6cacdb2ca13bda279a764c67ce9..520035f6a0e877e6fd1f3df0d14898735e962d06 100644 (file)
@@ -19,7 +19,7 @@ DETAIL:  privileges for table deptest
 REVOKE SELECT ON deptest FROM GROUP regress_dep_group;
 DROP GROUP regress_dep_group;
 -- can't drop the user if we revoke the privileges partially
-REVOKE SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, VACUUM, ANALYZE ON deptest FROM regress_dep_user;
+REVOKE SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, MAINTAIN ON deptest FROM regress_dep_user;
 DROP USER regress_dep_user;
 ERROR:  role "regress_dep_user" cannot be dropped because some objects depend on it
 DETAIL:  privileges for table deptest
@@ -63,21 +63,21 @@ CREATE TABLE deptest (a serial primary key, b text);
 GRANT ALL ON deptest1 TO regress_dep_user2;
 RESET SESSION AUTHORIZATION;
 \z deptest1
-                                                 Access privileges
- Schema |   Name   | Type  |                   Access privileges                    | Column privileges | Policies 
---------+----------+-------+--------------------------------------------------------+-------------------+----------
- public | deptest1 | table | regress_dep_user0=arwdDxtvz/regress_dep_user0         +|                   | 
-        |          |       | regress_dep_user1=a*r*w*d*D*x*t*v*z*/regress_dep_user0+|                   | 
-        |          |       | regress_dep_user2=arwdDxtvz/regress_dep_user1          |                   | 
+                                                Access privileges
+ Schema |   Name   | Type  |                  Access privileges                   | Column privileges | Policies 
+--------+----------+-------+------------------------------------------------------+-------------------+----------
+ public | deptest1 | table | regress_dep_user0=arwdDxtm/regress_dep_user0        +|                   | 
+        |          |       | regress_dep_user1=a*r*w*d*D*x*t*m*/regress_dep_user0+|                   | 
+        |          |       | regress_dep_user2=arwdDxtm/regress_dep_user1         |                   | 
 (1 row)
 
 DROP OWNED BY regress_dep_user1;
 -- all grants revoked
 \z deptest1
                                             Access privileges
- Schema |   Name   | Type  |               Access privileges               | Column privileges | Policies 
---------+----------+-------+-----------------------------------------------+-------------------+----------
- public | deptest1 | table | regress_dep_user0=arwdDxtvz/regress_dep_user0 |                   | 
+ Schema |   Name   | Type  |              Access privileges               | Column privileges | Policies 
+--------+----------+-------+----------------------------------------------+-------------------+----------
+ public | deptest1 | table | regress_dep_user0=arwdDxtm/regress_dep_user0 |                   | 
 (1 row)
 
 -- table was dropped
index 7933314fd30447966465fdb1a10c0a95c40c6853..169b364b22fc461b73f8a48305f3e701e0c44e12 100644 (file)
@@ -2570,39 +2570,39 @@ grant select on dep_priv_test to regress_priv_user4 with grant option;
 set session role regress_priv_user4;
 grant select on dep_priv_test to regress_priv_user5;
 \dp dep_priv_test
-                                                Access privileges
- Schema |     Name      | Type  |                Access privileges                | Column privileges | Policies 
---------+---------------+-------+-------------------------------------------------+-------------------+----------
- public | dep_priv_test | table | regress_priv_user1=arwdDxtvz/regress_priv_user1+|                   | 
-        |               |       | regress_priv_user2=r*/regress_priv_user1       +|                   | 
-        |               |       | regress_priv_user3=r*/regress_priv_user1       +|                   | 
-        |               |       | regress_priv_user4=r*/regress_priv_user2       +|                   | 
-        |               |       | regress_priv_user4=r*/regress_priv_user3       +|                   | 
-        |               |       | regress_priv_user5=r/regress_priv_user4         |                   | 
+                                               Access privileges
+ Schema |     Name      | Type  |               Access privileges                | Column privileges | Policies 
+--------+---------------+-------+------------------------------------------------+-------------------+----------
+ public | dep_priv_test | table | regress_priv_user1=arwdDxtm/regress_priv_user1+|                   | 
+        |               |       | regress_priv_user2=r*/regress_priv_user1      +|                   | 
+        |               |       | regress_priv_user3=r*/regress_priv_user1      +|                   | 
+        |               |       | regress_priv_user4=r*/regress_priv_user2      +|                   | 
+        |               |       | regress_priv_user4=r*/regress_priv_user3      +|                   | 
+        |               |       | regress_priv_user5=r/regress_priv_user4        |                   | 
 (1 row)
 
 set session role regress_priv_user2;
 revoke select on dep_priv_test from regress_priv_user4 cascade;
 \dp dep_priv_test
-                                                Access privileges
- Schema |     Name      | Type  |                Access privileges                | Column privileges | Policies 
---------+---------------+-------+-------------------------------------------------+-------------------+----------
- public | dep_priv_test | table | regress_priv_user1=arwdDxtvz/regress_priv_user1+|                   | 
-        |               |       | regress_priv_user2=r*/regress_priv_user1       +|                   | 
-        |               |       | regress_priv_user3=r*/regress_priv_user1       +|                   | 
-        |               |       | regress_priv_user4=r*/regress_priv_user3       +|                   | 
-        |               |       | regress_priv_user5=r/regress_priv_user4         |                   | 
+                                               Access privileges
+ Schema |     Name      | Type  |               Access privileges                | Column privileges | Policies 
+--------+---------------+-------+------------------------------------------------+-------------------+----------
+ public | dep_priv_test | table | regress_priv_user1=arwdDxtm/regress_priv_user1+|                   | 
+        |               |       | regress_priv_user2=r*/regress_priv_user1      +|                   | 
+        |               |       | regress_priv_user3=r*/regress_priv_user1      +|                   | 
+        |               |       | regress_priv_user4=r*/regress_priv_user3      +|                   | 
+        |               |       | regress_priv_user5=r/regress_priv_user4        |                   | 
 (1 row)
 
 set session role regress_priv_user3;
 revoke select on dep_priv_test from regress_priv_user4 cascade;
 \dp dep_priv_test
-                                                Access privileges
- Schema |     Name      | Type  |                Access privileges                | Column privileges | Policies 
---------+---------------+-------+-------------------------------------------------+-------------------+----------
- public | dep_priv_test | table | regress_priv_user1=arwdDxtvz/regress_priv_user1+|                   | 
-        |               |       | regress_priv_user2=r*/regress_priv_user1       +|                   | 
-        |               |       | regress_priv_user3=r*/regress_priv_user1        |                   | 
+                                               Access privileges
+ Schema |     Name      | Type  |               Access privileges                | Column privileges | Policies 
+--------+---------------+-------+------------------------------------------------+-------------------+----------
+ public | dep_priv_test | table | regress_priv_user1=arwdDxtm/regress_priv_user1+|                   | 
+        |               |       | regress_priv_user2=r*/regress_priv_user1      +|                   | 
+        |               |       | regress_priv_user3=r*/regress_priv_user1       |                   | 
 (1 row)
 
 set session role regress_priv_user1;
@@ -2849,68 +2849,79 @@ DROP SCHEMA regress_roleoption;
 DROP ROLE regress_roleoption_protagonist;
 DROP ROLE regress_roleoption_donor;
 DROP ROLE regress_roleoption_recipient;
--- VACUUM and ANALYZE
-CREATE ROLE regress_no_priv;
-CREATE ROLE regress_only_vacuum;
-CREATE ROLE regress_only_analyze;
-CREATE ROLE regress_both;
-CREATE ROLE regress_only_vacuum_all IN ROLE pg_vacuum_all_tables;
-CREATE ROLE regress_only_analyze_all IN ROLE pg_analyze_all_tables;
-CREATE ROLE regress_both_all IN ROLE pg_vacuum_all_tables, pg_analyze_all_tables;
-CREATE TABLE vacanalyze_test (a INT);
-GRANT VACUUM ON vacanalyze_test TO regress_only_vacuum, regress_both;
-GRANT ANALYZE ON vacanalyze_test TO regress_only_analyze, regress_both;
-SET ROLE regress_no_priv;
-VACUUM vacanalyze_test;
-WARNING:  permission denied to vacuum "vacanalyze_test", skipping it
-ANALYZE vacanalyze_test;
-WARNING:  permission denied to analyze "vacanalyze_test", skipping it
-VACUUM (ANALYZE) vacanalyze_test;
-WARNING:  permission denied to vacuum "vacanalyze_test", skipping it
-RESET ROLE;
-SET ROLE regress_only_vacuum;
-VACUUM vacanalyze_test;
-ANALYZE vacanalyze_test;
-WARNING:  permission denied to analyze "vacanalyze_test", skipping it
-VACUUM (ANALYZE) vacanalyze_test;
-WARNING:  permission denied to analyze "vacanalyze_test", skipping it
-RESET ROLE;
-SET ROLE regress_only_analyze;
-VACUUM vacanalyze_test;
-WARNING:  permission denied to vacuum "vacanalyze_test", skipping it
-ANALYZE vacanalyze_test;
-VACUUM (ANALYZE) vacanalyze_test;
-WARNING:  permission denied to vacuum "vacanalyze_test", skipping it
-RESET ROLE;
-SET ROLE regress_both;
-VACUUM vacanalyze_test;
-ANALYZE vacanalyze_test;
-VACUUM (ANALYZE) vacanalyze_test;
-RESET ROLE;
-SET ROLE regress_only_vacuum_all;
-VACUUM vacanalyze_test;
-ANALYZE vacanalyze_test;
-WARNING:  permission denied to analyze "vacanalyze_test", skipping it
-VACUUM (ANALYZE) vacanalyze_test;
-WARNING:  permission denied to analyze "vacanalyze_test", skipping it
+-- MAINTAIN
+CREATE ROLE regress_no_maintain;
+CREATE ROLE regress_maintain;
+CREATE ROLE regress_maintain_all IN ROLE pg_maintain;
+CREATE TABLE maintain_test (a INT);
+CREATE INDEX ON maintain_test (a);
+GRANT MAINTAIN ON maintain_test TO regress_maintain;
+CREATE MATERIALIZED VIEW refresh_test AS SELECT 1;
+GRANT MAINTAIN ON refresh_test TO regress_maintain;
+CREATE SCHEMA reindex_test;
+-- negative tests; should fail
+SET ROLE regress_no_maintain;
+VACUUM maintain_test;
+WARNING:  permission denied to vacuum "maintain_test", skipping it
+ANALYZE maintain_test;
+WARNING:  permission denied to analyze "maintain_test", skipping it
+VACUUM (ANALYZE) maintain_test;
+WARNING:  permission denied to vacuum "maintain_test", skipping it
+CLUSTER maintain_test USING maintain_test_a_idx;
+ERROR:  must be owner of table maintain_test
+REFRESH MATERIALIZED VIEW refresh_test;
+ERROR:  must be owner of table refresh_test
+REINDEX TABLE maintain_test;
+ERROR:  must be owner of table maintain_test
+REINDEX INDEX maintain_test_a_idx;
+ERROR:  must be owner of index maintain_test_a_idx
+REINDEX SCHEMA reindex_test;
+ERROR:  must be owner of schema reindex_test
+BEGIN;
+LOCK TABLE maintain_test IN ACCESS SHARE MODE;
+ERROR:  permission denied for table maintain_test
+COMMIT;
+BEGIN;
+LOCK TABLE maintain_test IN ACCESS EXCLUSIVE MODE;
+ERROR:  permission denied for table maintain_test
+COMMIT;
 RESET ROLE;
-SET ROLE regress_only_analyze_all;
-VACUUM vacanalyze_test;
-WARNING:  permission denied to vacuum "vacanalyze_test", skipping it
-ANALYZE vacanalyze_test;
-VACUUM (ANALYZE) vacanalyze_test;
-WARNING:  permission denied to vacuum "vacanalyze_test", skipping it
+SET ROLE regress_maintain;
+VACUUM maintain_test;
+ANALYZE maintain_test;
+VACUUM (ANALYZE) maintain_test;
+CLUSTER maintain_test USING maintain_test_a_idx;
+REFRESH MATERIALIZED VIEW refresh_test;
+REINDEX TABLE maintain_test;
+REINDEX INDEX maintain_test_a_idx;
+REINDEX SCHEMA reindex_test;
+ERROR:  must be owner of schema reindex_test
+BEGIN;
+LOCK TABLE maintain_test IN ACCESS SHARE MODE;
+COMMIT;
+BEGIN;
+LOCK TABLE maintain_test IN ACCESS EXCLUSIVE MODE;
+COMMIT;
 RESET ROLE;
-SET ROLE regress_both_all;
-VACUUM vacanalyze_test;
-ANALYZE vacanalyze_test;
-VACUUM (ANALYZE) vacanalyze_test;
+SET ROLE regress_maintain_all;
+VACUUM maintain_test;
+ANALYZE maintain_test;
+VACUUM (ANALYZE) maintain_test;
+CLUSTER maintain_test USING maintain_test_a_idx;
+REFRESH MATERIALIZED VIEW refresh_test;
+REINDEX TABLE maintain_test;
+REINDEX INDEX maintain_test_a_idx;
+REINDEX SCHEMA reindex_test;
+BEGIN;
+LOCK TABLE maintain_test IN ACCESS SHARE MODE;
+COMMIT;
+BEGIN;
+LOCK TABLE maintain_test IN ACCESS EXCLUSIVE MODE;
+COMMIT;
 RESET ROLE;
-DROP TABLE vacanalyze_test;
-DROP ROLE regress_no_priv;
-DROP ROLE regress_only_vacuum;
-DROP ROLE regress_only_analyze;
-DROP ROLE regress_both;
-DROP ROLE regress_only_vacuum_all;
-DROP ROLE regress_only_analyze_all;
-DROP ROLE regress_both_all;
+DROP TABLE maintain_test;
+DROP MATERIALIZED VIEW refresh_test;
+DROP SCHEMA reindex_test;
+DROP ROLE regress_no_maintain;
+DROP ROLE regress_maintain;
+DROP ROLE regress_maintain_all;
index 31509a0a6f8721c622ba90a44b19af5d74e1820f..a415ad168c54bc5b695c085244f2ba7afeed29c7 100644 (file)
@@ -94,22 +94,22 @@ CREATE POLICY p1r ON document AS RESTRICTIVE TO regress_rls_dave
     USING (cid <> 44);
 \dp
                                                                    Access privileges
-       Schema       |   Name   | Type  |               Access privileges               | Column privileges |                  Policies                  
---------------------+----------+-------+-----------------------------------------------+-------------------+--------------------------------------------
- regress_rls_schema | category | table | regress_rls_alice=arwdDxtvz/regress_rls_alice+|                   | 
-                    |          |       | =arwdDxtvz/regress_rls_alice                  |                   | 
- regress_rls_schema | document | table | regress_rls_alice=arwdDxtvz/regress_rls_alice+|                   | p1:                                       +
-                    |          |       | =arwdDxtvz/regress_rls_alice                  |                   |   (u): (dlevel <= ( SELECT uaccount.seclv +
-                    |          |       |                                               |                   |    FROM uaccount                          +
-                    |          |       |                                               |                   |   WHERE (uaccount.pguser = CURRENT_USER)))+
-                    |          |       |                                               |                   | p2r (RESTRICTIVE):                        +
-                    |          |       |                                               |                   |   (u): ((cid <> 44) AND (cid < 50))       +
-                    |          |       |                                               |                   |   to: regress_rls_dave                    +
-                    |          |       |                                               |                   | p1r (RESTRICTIVE):                        +
-                    |          |       |                                               |                   |   (u): (cid <> 44)                        +
-                    |          |       |                                               |                   |   to: regress_rls_dave
- regress_rls_schema | uaccount | table | regress_rls_alice=arwdDxtvz/regress_rls_alice+|                   | 
-                    |          |       | =r/regress_rls_alice                          |                   | 
+       Schema       |   Name   | Type  |              Access privileges               | Column privileges |                  Policies                  
+--------------------+----------+-------+----------------------------------------------+-------------------+--------------------------------------------
+ regress_rls_schema | category | table | regress_rls_alice=arwdDxtm/regress_rls_alice+|                   | 
+                    |          |       | =arwdDxtm/regress_rls_alice                  |                   | 
+ regress_rls_schema | document | table | regress_rls_alice=arwdDxtm/regress_rls_alice+|                   | p1:                                       +
+                    |          |       | =arwdDxtm/regress_rls_alice                  |                   |   (u): (dlevel <= ( SELECT uaccount.seclv +
+                    |          |       |                                              |                   |    FROM uaccount                          +
+                    |          |       |                                              |                   |   WHERE (uaccount.pguser = CURRENT_USER)))+
+                    |          |       |                                              |                   | p2r (RESTRICTIVE):                        +
+                    |          |       |                                              |                   |   (u): ((cid <> 44) AND (cid < 50))       +
+                    |          |       |                                              |                   |   to: regress_rls_dave                    +
+                    |          |       |                                              |                   | p1r (RESTRICTIVE):                        +
+                    |          |       |                                              |                   |   (u): (cid <> 44)                        +
+                    |          |       |                                              |                   |   to: regress_rls_dave
+ regress_rls_schema | uaccount | table | regress_rls_alice=arwdDxtm/regress_rls_alice+|                   | 
+                    |          |       | =r/regress_rls_alice                         |                   | 
 (3 rows)
 
 \d document
index e0fb21b36e537330fe8f802eac981579f424dc7f..0035d158b7bf913a2617b211a0a5b3dd05e6bcab 100644 (file)
@@ -336,9 +336,7 @@ WARNING:  permission denied to analyze "vacowned_part2", skipping it
 VACUUM (ANALYZE) vacowned_parted;
 WARNING:  permission denied to vacuum "vacowned_parted", skipping it
 WARNING:  permission denied to vacuum "vacowned_part1", skipping it
-WARNING:  permission denied to analyze "vacowned_part1", skipping it
 WARNING:  permission denied to vacuum "vacowned_part2", skipping it
-WARNING:  permission denied to analyze "vacowned_part2", skipping it
 VACUUM (ANALYZE) vacowned_part1;
 WARNING:  permission denied to vacuum "vacowned_part1", skipping it
 VACUUM (ANALYZE) vacowned_part2;
@@ -360,7 +358,6 @@ ANALYZE vacowned_part2;
 WARNING:  permission denied to analyze "vacowned_part2", skipping it
 VACUUM (ANALYZE) vacowned_parted;
 WARNING:  permission denied to vacuum "vacowned_part2", skipping it
-WARNING:  permission denied to analyze "vacowned_part2", skipping it
 VACUUM (ANALYZE) vacowned_part1;
 VACUUM (ANALYZE) vacowned_part2;
 WARNING:  permission denied to vacuum "vacowned_part2", skipping it
@@ -383,7 +380,6 @@ WARNING:  permission denied to analyze "vacowned_part2", skipping it
 VACUUM (ANALYZE) vacowned_parted;
 WARNING:  permission denied to vacuum "vacowned_parted", skipping it
 WARNING:  permission denied to vacuum "vacowned_part2", skipping it
-WARNING:  permission denied to analyze "vacowned_part2", skipping it
 VACUUM (ANALYZE) vacowned_part1;
 VACUUM (ANALYZE) vacowned_part2;
 WARNING:  permission denied to vacuum "vacowned_part2", skipping it
@@ -408,9 +404,7 @@ ANALYZE vacowned_part2;
 WARNING:  permission denied to analyze "vacowned_part2", skipping it
 VACUUM (ANALYZE) vacowned_parted;
 WARNING:  permission denied to vacuum "vacowned_part1", skipping it
-WARNING:  permission denied to analyze "vacowned_part1", skipping it
 WARNING:  permission denied to vacuum "vacowned_part2", skipping it
-WARNING:  permission denied to analyze "vacowned_part2", skipping it
 VACUUM (ANALYZE) vacowned_part1;
 WARNING:  permission denied to vacuum "vacowned_part1", skipping it
 VACUUM (ANALYZE) vacowned_part2;
index 99b905a938a7e65ed95d6fb4c264b101f2773a63..8d74ed7122c249494d677f3acbc8ca97892d6517 100644 (file)
@@ -21,7 +21,7 @@ REVOKE SELECT ON deptest FROM GROUP regress_dep_group;
 DROP GROUP regress_dep_group;
 
 -- can't drop the user if we revoke the privileges partially
-REVOKE SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, VACUUM, ANALYZE ON deptest FROM regress_dep_user;
+REVOKE SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, MAINTAIN ON deptest FROM regress_dep_user;
 DROP USER regress_dep_user;
 
 -- now we are OK to drop him
index 1bcaaba4ebacf9891c352cebbfa4e3f1702a1b44..b2db1c6dd56b84302dccf9377a7763a9fed603e9 100644 (file)
@@ -1853,66 +1853,73 @@ DROP ROLE regress_roleoption_protagonist;
 DROP ROLE regress_roleoption_donor;
 DROP ROLE regress_roleoption_recipient;
 
--- VACUUM and ANALYZE
-CREATE ROLE regress_no_priv;
-CREATE ROLE regress_only_vacuum;
-CREATE ROLE regress_only_analyze;
-CREATE ROLE regress_both;
-CREATE ROLE regress_only_vacuum_all IN ROLE pg_vacuum_all_tables;
-CREATE ROLE regress_only_analyze_all IN ROLE pg_analyze_all_tables;
-CREATE ROLE regress_both_all IN ROLE pg_vacuum_all_tables, pg_analyze_all_tables;
-
-CREATE TABLE vacanalyze_test (a INT);
-GRANT VACUUM ON vacanalyze_test TO regress_only_vacuum, regress_both;
-GRANT ANALYZE ON vacanalyze_test TO regress_only_analyze, regress_both;
-
-SET ROLE regress_no_priv;
-VACUUM vacanalyze_test;
-ANALYZE vacanalyze_test;
-VACUUM (ANALYZE) vacanalyze_test;
-RESET ROLE;
-
-SET ROLE regress_only_vacuum;
-VACUUM vacanalyze_test;
-ANALYZE vacanalyze_test;
-VACUUM (ANALYZE) vacanalyze_test;
-RESET ROLE;
-
-SET ROLE regress_only_analyze;
-VACUUM vacanalyze_test;
-ANALYZE vacanalyze_test;
-VACUUM (ANALYZE) vacanalyze_test;
-RESET ROLE;
-
-SET ROLE regress_both;
-VACUUM vacanalyze_test;
-ANALYZE vacanalyze_test;
-VACUUM (ANALYZE) vacanalyze_test;
-RESET ROLE;
-
-SET ROLE regress_only_vacuum_all;
-VACUUM vacanalyze_test;
-ANALYZE vacanalyze_test;
-VACUUM (ANALYZE) vacanalyze_test;
+-- MAINTAIN
+CREATE ROLE regress_no_maintain;
+CREATE ROLE regress_maintain;
+CREATE ROLE regress_maintain_all IN ROLE pg_maintain;
+
+CREATE TABLE maintain_test (a INT);
+CREATE INDEX ON maintain_test (a);
+GRANT MAINTAIN ON maintain_test TO regress_maintain;
+CREATE MATERIALIZED VIEW refresh_test AS SELECT 1;
+GRANT MAINTAIN ON refresh_test TO regress_maintain;
+CREATE SCHEMA reindex_test;
+
+-- negative tests; should fail
+SET ROLE regress_no_maintain;
+VACUUM maintain_test;
+ANALYZE maintain_test;
+VACUUM (ANALYZE) maintain_test;
+CLUSTER maintain_test USING maintain_test_a_idx;
+REFRESH MATERIALIZED VIEW refresh_test;
+REINDEX TABLE maintain_test;
+REINDEX INDEX maintain_test_a_idx;
+REINDEX SCHEMA reindex_test;
+BEGIN;
+LOCK TABLE maintain_test IN ACCESS SHARE MODE;
+COMMIT;
+BEGIN;
+LOCK TABLE maintain_test IN ACCESS EXCLUSIVE MODE;
+COMMIT;
 RESET ROLE;
 
-SET ROLE regress_only_analyze_all;
-VACUUM vacanalyze_test;
-ANALYZE vacanalyze_test;
-VACUUM (ANALYZE) vacanalyze_test;
+SET ROLE regress_maintain;
+VACUUM maintain_test;
+ANALYZE maintain_test;
+VACUUM (ANALYZE) maintain_test;
+CLUSTER maintain_test USING maintain_test_a_idx;
+REFRESH MATERIALIZED VIEW refresh_test;
+REINDEX TABLE maintain_test;
+REINDEX INDEX maintain_test_a_idx;
+REINDEX SCHEMA reindex_test;
+BEGIN;
+LOCK TABLE maintain_test IN ACCESS SHARE MODE;
+COMMIT;
+BEGIN;
+LOCK TABLE maintain_test IN ACCESS EXCLUSIVE MODE;
+COMMIT;
 RESET ROLE;
 
-SET ROLE regress_both_all;
-VACUUM vacanalyze_test;
-ANALYZE vacanalyze_test;
-VACUUM (ANALYZE) vacanalyze_test;
+SET ROLE regress_maintain_all;
+VACUUM maintain_test;
+ANALYZE maintain_test;
+VACUUM (ANALYZE) maintain_test;
+CLUSTER maintain_test USING maintain_test_a_idx;
+REFRESH MATERIALIZED VIEW refresh_test;
+REINDEX TABLE maintain_test;
+REINDEX INDEX maintain_test_a_idx;
+REINDEX SCHEMA reindex_test;
+BEGIN;
+LOCK TABLE maintain_test IN ACCESS SHARE MODE;
+COMMIT;
+BEGIN;
+LOCK TABLE maintain_test IN ACCESS EXCLUSIVE MODE;
+COMMIT;
 RESET ROLE;
 
-DROP TABLE vacanalyze_test;
-DROP ROLE regress_no_priv;
-DROP ROLE regress_only_vacuum;
-DROP ROLE regress_only_analyze;
-DROP ROLE regress_both;
-DROP ROLE regress_only_vacuum_all;
-DROP ROLE regress_only_analyze_all;
-DROP ROLE regress_both_all;
+DROP TABLE maintain_test;
+DROP MATERIALIZED VIEW refresh_test;
+DROP SCHEMA reindex_test;
+DROP ROLE regress_no_maintain;
+DROP ROLE regress_maintain;
+DROP ROLE regress_maintain_all;