Provide per-table permissions for vacuum and analyze.

Currently a table can only be vacuumed or analyzed by its owner or
a superuser. This can now be extended to any user by means of an
appropriate GRANT.

Nathan Bossart

Reviewed by: Bharath Rupireddy, Kyotaro Horiguchi, Stephen Frost, Robert
Haas, Mark Dilger, Tom Lane, Corey Huinker, David G. Johnston, Michael
Paquier.

Discussion: https://postgr.es/m/20220722203735.GB3996698@nathanxps13
This commit is contained in:
Andrew Dunstan 2022-11-28 10:08:42 -05:00
parent 1f059a4408
commit b5d6382496
24 changed files with 274 additions and 113 deletions

View File

@ -1691,8 +1691,9 @@ ALTER TABLE products RENAME TO items;
<literal>INSERT</literal>, <literal>UPDATE</literal>, <literal>DELETE</literal>, <literal>INSERT</literal>, <literal>UPDATE</literal>, <literal>DELETE</literal>,
<literal>TRUNCATE</literal>, <literal>REFERENCES</literal>, <literal>TRIGGER</literal>, <literal>TRUNCATE</literal>, <literal>REFERENCES</literal>, <literal>TRIGGER</literal>,
<literal>CREATE</literal>, <literal>CONNECT</literal>, <literal>TEMPORARY</literal>, <literal>CREATE</literal>, <literal>CONNECT</literal>, <literal>TEMPORARY</literal>,
<literal>EXECUTE</literal>, <literal>USAGE</literal>, <literal>SET</literal> <literal>EXECUTE</literal>, <literal>USAGE</literal>, <literal>SET</literal>,
and <literal>ALTER SYSTEM</literal>. <literal>ALTER SYSTEM</literal>, <literal>VACUUM</literal>, and
<literal>ANALYZE</literal>.
The privileges applicable to a particular The privileges applicable to a particular
object vary depending on the object's type (table, function, etc.). object vary depending on the object's type (table, function, etc.).
More detail about the meanings of these privileges appears below. More detail about the meanings of these privileges appears below.
@ -1982,7 +1983,25 @@ REVOKE ALL ON accounts FROM PUBLIC;
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
</variablelist>
<varlistentry>
<term><literal>VACUUM</literal></term>
<listitem>
<para>
Allows <command>VACUUM</command> on a relation.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>ANALYZE</literal></term>
<listitem>
<para>
Allows <command>ANALYZE</command> on a relation.
</para>
</listitem>
</varlistentry>
</variablelist>
The privileges required by other commands are listed on the The privileges required by other commands are listed on the
reference page of the respective command. reference page of the respective command.
@ -2131,6 +2150,16 @@ REVOKE ALL ON accounts FROM PUBLIC;
<entry><literal>A</literal></entry> <entry><literal>A</literal></entry>
<entry><literal>PARAMETER</literal></entry> <entry><literal>PARAMETER</literal></entry>
</row> </row>
<row>
<entry><literal>VACUUM</literal></entry>
<entry><literal>v</literal></entry>
<entry><literal>TABLE</literal></entry>
</row>
<row>
<entry><literal>ANALYZE</literal></entry>
<entry><literal>z</literal></entry>
<entry><literal>TABLE</literal></entry>
</row>
</tbody> </tbody>
</tgroup> </tgroup>
</table> </table>
@ -2221,7 +2250,7 @@ REVOKE ALL ON accounts FROM PUBLIC;
</row> </row>
<row> <row>
<entry><literal>TABLE</literal> (and table-like objects)</entry> <entry><literal>TABLE</literal> (and table-like objects)</entry>
<entry><literal>arwdDxt</literal></entry> <entry><literal>arwdDxtvz</literal></entry>
<entry>none</entry> <entry>none</entry>
<entry><literal>\dp</literal></entry> <entry><literal>\dp</literal></entry>
</row> </row>
@ -2279,12 +2308,12 @@ GRANT SELECT (col1), UPDATE (col1) ON mytable TO miriam_rw;
would show: would show:
<programlisting> <programlisting>
=&gt; \dp mytable =&gt; \dp mytable
Access privileges Access privileges
Schema | Name | Type | Access privileges | Column privileges | Policies Schema | Name | Type | Access privileges | Column privileges | Policies
--------+---------+-------+-----------------------+-----------------------+---------- --------+---------+-------+-------------------------+-----------------------+----------
public | mytable | table | miriam=arwdDxt/miriam+| col1: +| public | mytable | table | miriam=arwdDxtvz/miriam+| col1: +|
| | | =r/miriam +| miriam_rw=rw/miriam | | | | =r/miriam +| miriam_rw=rw/miriam |
| | | admin=arw/miriam | | | | | admin=arw/miriam | |
(1 row) (1 row)
</programlisting> </programlisting>
</para> </para>

View File

@ -22978,7 +22978,8 @@ SELECT has_function_privilege('joeuser', 'myfunc(int, text)', 'execute');
are <literal>SELECT</literal>, <literal>INSERT</literal>, are <literal>SELECT</literal>, <literal>INSERT</literal>,
<literal>UPDATE</literal>, <literal>DELETE</literal>, <literal>UPDATE</literal>, <literal>DELETE</literal>,
<literal>TRUNCATE</literal>, <literal>REFERENCES</literal>, <literal>TRUNCATE</literal>, <literal>REFERENCES</literal>,
and <literal>TRIGGER</literal>. <literal>TRIGGER</literal>, <literal>VACUUM</literal> and
<literal>ANALYZE</literal>.
</para></entry> </para></entry>
</row> </row>

View File

@ -28,7 +28,7 @@ ALTER DEFAULT PRIVILEGES
<phrase>where <replaceable class="parameter">abbreviated_grant_or_revoke</replaceable> is one of:</phrase> <phrase>where <replaceable class="parameter">abbreviated_grant_or_revoke</replaceable> is one of:</phrase>
GRANT { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER } GRANT { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER | VACUUM | ANALYZE }
[, ...] | ALL [ PRIVILEGES ] } [, ...] | ALL [ PRIVILEGES ] }
ON TABLES ON TABLES
TO { [ GROUP ] <replaceable class="parameter">role_name</replaceable> | PUBLIC } [, ...] [ WITH GRANT OPTION ] TO { [ GROUP ] <replaceable class="parameter">role_name</replaceable> | PUBLIC } [, ...] [ WITH GRANT OPTION ]
@ -51,7 +51,7 @@ GRANT { USAGE | CREATE | ALL [ PRIVILEGES ] }
TO { [ GROUP ] <replaceable class="parameter">role_name</replaceable> | PUBLIC } [, ...] [ WITH GRANT OPTION ] TO { [ GROUP ] <replaceable class="parameter">role_name</replaceable> | PUBLIC } [, ...] [ WITH GRANT OPTION ]
REVOKE [ GRANT OPTION FOR ] REVOKE [ GRANT OPTION FOR ]
{ { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER } { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER | VACUUM | ANALYZE }
[, ...] | ALL [ PRIVILEGES ] } [, ...] | ALL [ PRIVILEGES ] }
ON TABLES ON TABLES
FROM { [ GROUP ] <replaceable class="parameter">role_name</replaceable> | PUBLIC } [, ...] FROM { [ GROUP ] <replaceable class="parameter">role_name</replaceable> | PUBLIC } [, ...]

View File

@ -149,7 +149,8 @@ ANALYZE [ VERBOSE ] [ <replaceable class="parameter">table_and_columns</replacea
<para> <para>
To analyze a table, one must ordinarily be the table's owner or a To analyze a table, one must ordinarily be the table's owner or a
superuser. However, database owners are allowed to superuser or have the <literal>ANALYZE</literal> privilege on the table.
However, database owners are allowed to
analyze all tables in their databases, except shared catalogs. analyze all tables in their databases, except shared catalogs.
(The restriction for shared catalogs means that a true database-wide (The restriction for shared catalogs means that a true database-wide
<command>ANALYZE</command> can only be performed by a superuser.) <command>ANALYZE</command> can only be performed by a superuser.)

View File

@ -21,7 +21,7 @@ PostgreSQL documentation
<refsynopsisdiv> <refsynopsisdiv>
<synopsis> <synopsis>
GRANT { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER } GRANT { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER | VACUUM | ANALYZE }
[, ...] | ALL [ PRIVILEGES ] } [, ...] | ALL [ PRIVILEGES ] }
ON { [ TABLE ] <replaceable class="parameter">table_name</replaceable> [, ...] ON { [ TABLE ] <replaceable class="parameter">table_name</replaceable> [, ...]
| ALL TABLES IN SCHEMA <replaceable class="parameter">schema_name</replaceable> [, ...] } | ALL TABLES IN SCHEMA <replaceable class="parameter">schema_name</replaceable> [, ...] }
@ -193,6 +193,8 @@ GRANT <replaceable class="parameter">role_name</replaceable> [, ...] TO <replace
<term><literal>USAGE</literal></term> <term><literal>USAGE</literal></term>
<term><literal>SET</literal></term> <term><literal>SET</literal></term>
<term><literal>ALTER SYSTEM</literal></term> <term><literal>ALTER SYSTEM</literal></term>
<term><literal>VACUUM</literal></term>
<term><literal>ANALYZE</literal></term>
<listitem> <listitem>
<para> <para>
Specific types of privileges, as defined in <xref linkend="ddl-priv"/>. Specific types of privileges, as defined in <xref linkend="ddl-priv"/>.

View File

@ -22,7 +22,7 @@ PostgreSQL documentation
<refsynopsisdiv> <refsynopsisdiv>
<synopsis> <synopsis>
REVOKE [ GRANT OPTION FOR ] REVOKE [ GRANT OPTION FOR ]
{ { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER } { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER | VACUUM | ANALYZE }
[, ...] | ALL [ PRIVILEGES ] } [, ...] | ALL [ PRIVILEGES ] }
ON { [ TABLE ] <replaceable class="parameter">table_name</replaceable> [, ...] ON { [ TABLE ] <replaceable class="parameter">table_name</replaceable> [, ...]
| ALL TABLES IN SCHEMA <replaceable>schema_name</replaceable> [, ...] } | ALL TABLES IN SCHEMA <replaceable>schema_name</replaceable> [, ...] }

View File

@ -357,7 +357,8 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
<para> <para>
To vacuum a table, one must ordinarily be the table's owner or a To vacuum a table, one must ordinarily be the table's owner or a
superuser. However, database owners are allowed to superuser or have the <literal>VACUUM</literal> privilege on the table.
However, database owners are allowed to
vacuum all tables in their databases, except shared catalogs. vacuum all tables in their databases, except shared catalogs.
(The restriction for shared catalogs means that a true database-wide (The restriction for shared catalogs means that a true database-wide
<command>VACUUM</command> can only be performed by a superuser.) <command>VACUUM</command> can only be performed by a superuser.)

View File

@ -3420,6 +3420,10 @@ string_to_privilege(const char *privname)
return ACL_SET; return ACL_SET;
if (strcmp(privname, "alter system") == 0) if (strcmp(privname, "alter system") == 0)
return ACL_ALTER_SYSTEM; return ACL_ALTER_SYSTEM;
if (strcmp(privname, "vacuum") == 0)
return ACL_VACUUM;
if (strcmp(privname, "analyze") == 0)
return ACL_ANALYZE;
if (strcmp(privname, "rule") == 0) if (strcmp(privname, "rule") == 0)
return 0; /* ignore old RULE privileges */ return 0; /* ignore old RULE privileges */
ereport(ERROR, ereport(ERROR,
@ -3461,6 +3465,10 @@ privilege_to_string(AclMode privilege)
return "SET"; return "SET";
case ACL_ALTER_SYSTEM: case ACL_ALTER_SYSTEM:
return "ALTER SYSTEM"; return "ALTER SYSTEM";
case ACL_VACUUM:
return "VACUUM";
case ACL_ANALYZE:
return "ANALYZE";
default: default:
elog(ERROR, "unrecognized privilege: %d", (int) privilege); elog(ERROR, "unrecognized privilege: %d", (int) privilege);
} }

View File

@ -159,16 +159,15 @@ analyze_rel(Oid relid, RangeVar *relation,
return; return;
/* /*
* Check if relation needs to be skipped based on ownership. This check * Check if relation needs to be skipped based on privileges. This check
* happens also when building the relation list to analyze for a manual * happens also when building the relation list to analyze for a manual
* operation, and needs to be done additionally here as ANALYZE could * operation, and needs to be done additionally here as ANALYZE could
* happen across multiple transactions where relation ownership could have * happen across multiple transactions where privileges could have changed
* changed in-between. Make sure to generate only logs for ANALYZE in * in-between. Make sure to generate only logs for ANALYZE in this case.
* this case.
*/ */
if (!vacuum_is_relation_owner(RelationGetRelid(onerel), if (!vacuum_is_permitted_for_relation(RelationGetRelid(onerel),
onerel->rd_rel, onerel->rd_rel,
params->options & VACOPT_ANALYZE)) VACOPT_ANALYZE))
{ {
relation_close(onerel, ShareUpdateExclusiveLock); relation_close(onerel, ShareUpdateExclusiveLock);
return; return;

View File

@ -547,32 +547,35 @@ vacuum(List *relations, VacuumParams *params,
} }
/* /*
* Check if a given relation can be safely vacuumed or analyzed. If the * Check if the current user has privileges to vacuum or analyze the relation.
* user is not the relation owner, issue a WARNING log message and return * If not, issue a WARNING log message and return false to let the caller
* false to let the caller decide what to do with this relation. This * decide what to do with this relation. This routine is used to decide if a
* routine is used to decide if a relation can be processed for VACUUM or * relation can be processed for VACUUM or ANALYZE.
* ANALYZE.
*/ */
bool bool
vacuum_is_relation_owner(Oid relid, Form_pg_class reltuple, bits32 options) vacuum_is_permitted_for_relation(Oid relid, Form_pg_class reltuple,
bits32 options)
{ {
char *relname; char *relname;
AclMode mode = 0;
Assert((options & (VACOPT_VACUUM | VACOPT_ANALYZE)) != 0); Assert((options & (VACOPT_VACUUM | VACOPT_ANALYZE)) != 0);
/* /*
* Check permissions. * A role has privileges to vacuum or analyze the relation if any of the
* * following are true:
* We allow the user to vacuum or analyze a table if he is superuser, the * - the role is a superuser
* table owner, or the database owner (but in the latter case, only if * - the role owns the relation
* it's not a shared relation). object_ownercheck includes the * - the role owns the current database and the relation is not shared
* superuser case. * - the role has been granted privileges to vacuum/analyze the relation
*
* Note we choose to treat permissions failure as a WARNING and keep
* trying to vacuum or analyze the rest of the DB --- is this appropriate?
*/ */
if (options & VACOPT_VACUUM)
mode |= ACL_VACUUM;
if (options & VACOPT_ANALYZE)
mode |= ACL_ANALYZE;
if (object_ownercheck(RelationRelationId, relid, GetUserId()) || if (object_ownercheck(RelationRelationId, relid, GetUserId()) ||
(object_ownercheck(DatabaseRelationId, MyDatabaseId, GetUserId()) && !reltuple->relisshared)) (object_ownercheck(DatabaseRelationId, MyDatabaseId, GetUserId()) && !reltuple->relisshared) ||
pg_class_aclcheck(relid, GetUserId(), mode) == ACLCHECK_OK)
return true; return true;
relname = NameStr(reltuple->relname); relname = NameStr(reltuple->relname);
@ -787,10 +790,10 @@ expand_vacuum_rel(VacuumRelation *vrel, int options)
classForm = (Form_pg_class) GETSTRUCT(tuple); classForm = (Form_pg_class) GETSTRUCT(tuple);
/* /*
* Make a returnable VacuumRelation for this rel if user is a proper * Make a returnable VacuumRelation for this rel if the user has the
* owner. * required privileges.
*/ */
if (vacuum_is_relation_owner(relid, classForm, options)) if (vacuum_is_permitted_for_relation(relid, classForm, options))
{ {
oldcontext = MemoryContextSwitchTo(vac_context); oldcontext = MemoryContextSwitchTo(vac_context);
vacrels = lappend(vacrels, makeVacuumRelation(vrel->relation, vacrels = lappend(vacrels, makeVacuumRelation(vrel->relation,
@ -877,7 +880,7 @@ get_all_vacuum_rels(int options)
Oid relid = classForm->oid; Oid relid = classForm->oid;
/* check permissions of relation */ /* check permissions of relation */
if (!vacuum_is_relation_owner(relid, classForm, options)) if (!vacuum_is_permitted_for_relation(relid, classForm, options))
continue; continue;
/* /*
@ -1797,7 +1800,9 @@ vac_truncate_clog(TransactionId frozenXID,
* be stale. * be stale.
* *
* Returns true if it's okay to proceed with a requested ANALYZE * Returns true if it's okay to proceed with a requested ANALYZE
* operation on this table. * 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.
* *
* Doing one heap at a time incurs extra overhead, since we need to * 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 * check that the heap exists again just before we vacuum it. The
@ -1889,21 +1894,20 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
} }
/* /*
* Check if relation needs to be skipped based on ownership. This check * Check if relation needs to be skipped based on privileges. This check
* happens also when building the relation list to vacuum for a manual * happens also when building the relation list to vacuum for a manual
* operation, and needs to be done additionally here as VACUUM could * operation, and needs to be done additionally here as VACUUM could
* happen across multiple transactions where relation ownership could have * happen across multiple transactions where privileges could have changed
* changed in-between. Make sure to only generate logs for VACUUM in this * in-between. Make sure to only generate logs for VACUUM in this case.
* case.
*/ */
if (!vacuum_is_relation_owner(RelationGetRelid(rel), if (!vacuum_is_permitted_for_relation(RelationGetRelid(rel),
rel->rd_rel, rel->rd_rel,
params->options & VACOPT_VACUUM)) VACOPT_VACUUM))
{ {
relation_close(rel, lmode); relation_close(rel, lmode);
PopActiveSnapshot(); PopActiveSnapshot();
CommitTransactionCommand(); CommitTransactionCommand();
return false; return true; /* user might have the ANALYZE privilege */
} }
/* /*

View File

@ -7482,6 +7482,13 @@ privilege: SELECT opt_column_list
n->cols = NIL; n->cols = NIL;
$$ = n; $$ = n;
} }
| analyze_keyword
{
AccessPriv *n = makeNode(AccessPriv);
n->priv_name = pstrdup("analyze");
n->cols = NIL;
$$ = n;
}
| ColId opt_column_list | ColId opt_column_list
{ {
AccessPriv *n = makeNode(AccessPriv); AccessPriv *n = makeNode(AccessPriv);

View File

@ -321,6 +321,12 @@ aclparse(const char *s, AclItem *aip)
case ACL_ALTER_SYSTEM_CHR: case ACL_ALTER_SYSTEM_CHR:
read = ACL_ALTER_SYSTEM; read = ACL_ALTER_SYSTEM;
break; break;
case ACL_VACUUM_CHR:
read = ACL_VACUUM;
break;
case ACL_ANALYZE_CHR:
read = ACL_ANALYZE;
break;
case 'R': /* ignore old RULE privileges */ case 'R': /* ignore old RULE privileges */
read = 0; read = 0;
break; break;
@ -1595,6 +1601,8 @@ makeaclitem(PG_FUNCTION_ARGS)
{"CONNECT", ACL_CONNECT}, {"CONNECT", ACL_CONNECT},
{"SET", ACL_SET}, {"SET", ACL_SET},
{"ALTER SYSTEM", ACL_ALTER_SYSTEM}, {"ALTER SYSTEM", ACL_ALTER_SYSTEM},
{"VACUUM", ACL_VACUUM},
{"ANALYZE", ACL_ANALYZE},
{"RULE", 0}, /* ignore old RULE privileges */ {"RULE", 0}, /* ignore old RULE privileges */
{NULL, 0} {NULL, 0}
}; };
@ -1703,6 +1711,10 @@ convert_aclright_to_string(int aclright)
return "SET"; return "SET";
case ACL_ALTER_SYSTEM: case ACL_ALTER_SYSTEM:
return "ALTER SYSTEM"; return "ALTER SYSTEM";
case ACL_VACUUM:
return "VACUUM";
case ACL_ANALYZE:
return "ANALYZE";
default: default:
elog(ERROR, "unrecognized aclright: %d", aclright); elog(ERROR, "unrecognized aclright: %d", aclright);
return NULL; return NULL;
@ -2012,6 +2024,10 @@ convert_table_priv_string(text *priv_type_text)
{"REFERENCES WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_REFERENCES)}, {"REFERENCES WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_REFERENCES)},
{"TRIGGER", ACL_TRIGGER}, {"TRIGGER", ACL_TRIGGER},
{"TRIGGER WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(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)},
{"RULE", 0}, /* ignore old RULE privileges */ {"RULE", 0}, /* ignore old RULE privileges */
{"RULE WITH GRANT OPTION", 0}, {"RULE WITH GRANT OPTION", 0},
{NULL, 0} {NULL, 0}

View File

@ -457,6 +457,8 @@ do { \
CONVERT_PRIV('d', "DELETE"); CONVERT_PRIV('d', "DELETE");
CONVERT_PRIV('t', "TRIGGER"); CONVERT_PRIV('t', "TRIGGER");
CONVERT_PRIV('D', "TRUNCATE"); CONVERT_PRIV('D', "TRUNCATE");
CONVERT_PRIV('v', "VACUUM");
CONVERT_PRIV('z', "ANALYZE");
} }
} }

View File

@ -566,7 +566,7 @@ my %tests = (
\QREVOKE ALL ON TABLES FROM regress_dump_test_role;\E\n \QREVOKE ALL ON TABLES FROM regress_dump_test_role;\E\n
\QALTER DEFAULT PRIVILEGES \E \QALTER DEFAULT PRIVILEGES \E
\QFOR ROLE regress_dump_test_role \E \QFOR ROLE regress_dump_test_role \E
\QGRANT INSERT,REFERENCES,DELETE,TRIGGER,TRUNCATE,UPDATE ON TABLES TO regress_dump_test_role;\E \QGRANT INSERT,REFERENCES,DELETE,TRIGGER,TRUNCATE,VACUUM,ANALYZE,UPDATE ON TABLES TO regress_dump_test_role;\E
/xm, /xm,
like => { %full_runs, section_post_data => 1, }, like => { %full_runs, section_post_data => 1, },
unlike => { no_privs => 1, }, unlike => { no_privs => 1, },

View File

@ -1147,7 +1147,7 @@ static const SchemaQuery Query_for_trigger_of_table = {
#define Privilege_options_of_grant_and_revoke \ #define Privilege_options_of_grant_and_revoke \
"SELECT", "INSERT", "UPDATE", "DELETE", "TRUNCATE", "REFERENCES", "TRIGGER", \ "SELECT", "INSERT", "UPDATE", "DELETE", "TRUNCATE", "REFERENCES", "TRIGGER", \
"CREATE", "CONNECT", "TEMPORARY", "EXECUTE", "USAGE", "SET", "ALTER SYSTEM", \ "CREATE", "CONNECT", "TEMPORARY", "EXECUTE", "USAGE", "SET", "ALTER SYSTEM", \
"ALL" "VACUUM", "ANALYZE", "ALL"
/* /*
* These object types were introduced later than our support cutoff of * These object types were introduced later than our support cutoff of
@ -3782,7 +3782,8 @@ psql_completion(const char *text, int start, int end)
if (HeadMatches("ALTER", "DEFAULT", "PRIVILEGES")) if (HeadMatches("ALTER", "DEFAULT", "PRIVILEGES"))
COMPLETE_WITH("SELECT", "INSERT", "UPDATE", COMPLETE_WITH("SELECT", "INSERT", "UPDATE",
"DELETE", "TRUNCATE", "REFERENCES", "TRIGGER", "DELETE", "TRUNCATE", "REFERENCES", "TRIGGER",
"CREATE", "EXECUTE", "USAGE", "ALL"); "CREATE", "EXECUTE", "USAGE", "VACUUM", "ANALYZE",
"ALL");
else if (TailMatches("GRANT")) else if (TailMatches("GRANT"))
COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_roles, COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_roles,
Privilege_options_of_grant_and_revoke); Privilege_options_of_grant_and_revoke);

View File

@ -295,8 +295,8 @@ extern bool vacuum_xid_failsafe_check(TransactionId relfrozenxid,
MultiXactId relminmxid); MultiXactId relminmxid);
extern void vac_update_datfrozenxid(void); extern void vac_update_datfrozenxid(void);
extern void vacuum_delay_point(void); extern void vacuum_delay_point(void);
extern bool vacuum_is_relation_owner(Oid relid, Form_pg_class reltuple, extern bool vacuum_is_permitted_for_relation(Oid relid, Form_pg_class reltuple,
bits32 options); bits32 options);
extern Relation vacuum_open_relation(Oid relid, RangeVar *relation, extern Relation vacuum_open_relation(Oid relid, RangeVar *relation,
bits32 options, bool verbose, bits32 options, bool verbose,
LOCKMODE lmode); LOCKMODE lmode);

View File

@ -95,7 +95,9 @@ typedef uint64 AclMode; /* a bitmask of privilege bits */
#define ACL_CONNECT (1<<11) /* for databases */ #define ACL_CONNECT (1<<11) /* for databases */
#define ACL_SET (1<<12) /* for configuration parameters */ #define ACL_SET (1<<12) /* for configuration parameters */
#define ACL_ALTER_SYSTEM (1<<13) /* for configuration parameters */ #define ACL_ALTER_SYSTEM (1<<13) /* for configuration parameters */
#define N_ACL_RIGHTS 14 /* 1 plus the last 1<<x */ #define ACL_VACUUM (1<<14) /* for relations */
#define ACL_ANALYZE (1<<15) /* for relations */
#define N_ACL_RIGHTS 16 /* 1 plus the last 1<<x */
#define ACL_NO_RIGHTS 0 #define ACL_NO_RIGHTS 0
/* Currently, SELECT ... FOR [KEY] UPDATE/SHARE requires UPDATE privileges */ /* Currently, SELECT ... FOR [KEY] UPDATE/SHARE requires UPDATE privileges */
#define ACL_SELECT_FOR_UPDATE ACL_UPDATE #define ACL_SELECT_FOR_UPDATE ACL_UPDATE

View File

@ -148,15 +148,17 @@ typedef struct ArrayType Acl;
#define ACL_CONNECT_CHR 'c' #define ACL_CONNECT_CHR 'c'
#define ACL_SET_CHR 's' #define ACL_SET_CHR 's'
#define ACL_ALTER_SYSTEM_CHR 'A' #define ACL_ALTER_SYSTEM_CHR 'A'
#define ACL_VACUUM_CHR 'v'
#define ACL_ANALYZE_CHR 'z'
/* string holding all privilege code chars, in order by bitmask position */ /* string holding all privilege code chars, in order by bitmask position */
#define ACL_ALL_RIGHTS_STR "arwdDxtXUCTcsA" #define ACL_ALL_RIGHTS_STR "arwdDxtXUCTcsAvz"
/* /*
* Bitmasks defining "all rights" for each supported object type * 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_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) #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_SEQUENCE (ACL_USAGE|ACL_SELECT|ACL_UPDATE) #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_DATABASE (ACL_CREATE|ACL_CREATE_TEMP|ACL_CONNECT)
#define ACL_ALL_RIGHTS_FDW (ACL_USAGE) #define ACL_ALL_RIGHTS_FDW (ACL_USAGE)

View File

@ -19,7 +19,7 @@ DETAIL: privileges for table deptest
REVOKE SELECT ON deptest FROM GROUP regress_dep_group; REVOKE SELECT ON deptest FROM GROUP regress_dep_group;
DROP GROUP regress_dep_group; DROP GROUP regress_dep_group;
-- can't drop the user if we revoke the privileges partially -- can't drop the user if we revoke the privileges partially
REVOKE SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES ON deptest FROM regress_dep_user; REVOKE SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, VACUUM, ANALYZE ON deptest FROM regress_dep_user;
DROP USER regress_dep_user; DROP USER regress_dep_user;
ERROR: role "regress_dep_user" cannot be dropped because some objects depend on it ERROR: role "regress_dep_user" cannot be dropped because some objects depend on it
DETAIL: privileges for table deptest 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; GRANT ALL ON deptest1 TO regress_dep_user2;
RESET SESSION AUTHORIZATION; RESET SESSION AUTHORIZATION;
\z deptest1 \z deptest1
Access privileges Access privileges
Schema | Name | Type | Access privileges | Column privileges | Policies Schema | Name | Type | Access privileges | Column privileges | Policies
--------+----------+-------+----------------------------------------------------+-------------------+---------- --------+----------+-------+--------------------------------------------------------+-------------------+----------
public | deptest1 | table | regress_dep_user0=arwdDxt/regress_dep_user0 +| | public | deptest1 | table | regress_dep_user0=arwdDxtvz/regress_dep_user0 +| |
| | | regress_dep_user1=a*r*w*d*D*x*t*/regress_dep_user0+| | | | | regress_dep_user1=a*r*w*d*D*x*t*v*z*/regress_dep_user0+| |
| | | regress_dep_user2=arwdDxt/regress_dep_user1 | | | | | regress_dep_user2=arwdDxtvz/regress_dep_user1 | |
(1 row) (1 row)
DROP OWNED BY regress_dep_user1; DROP OWNED BY regress_dep_user1;
-- all grants revoked -- all grants revoked
\z deptest1 \z deptest1
Access privileges Access privileges
Schema | Name | Type | Access privileges | Column privileges | Policies Schema | Name | Type | Access privileges | Column privileges | Policies
--------+----------+-------+---------------------------------------------+-------------------+---------- --------+----------+-------+-----------------------------------------------+-------------------+----------
public | deptest1 | table | regress_dep_user0=arwdDxt/regress_dep_user0 | | public | deptest1 | table | regress_dep_user0=arwdDxtvz/regress_dep_user0 | |
(1 row) (1 row)
-- table was dropped -- table was dropped

View File

@ -2570,39 +2570,39 @@ grant select on dep_priv_test to regress_priv_user4 with grant option;
set session role regress_priv_user4; set session role regress_priv_user4;
grant select on dep_priv_test to regress_priv_user5; grant select on dep_priv_test to regress_priv_user5;
\dp dep_priv_test \dp dep_priv_test
Access privileges Access privileges
Schema | Name | Type | Access privileges | Column privileges | Policies Schema | Name | Type | Access privileges | Column privileges | Policies
--------+---------------+-------+-----------------------------------------------+-------------------+---------- --------+---------------+-------+-------------------------------------------------+-------------------+----------
public | dep_priv_test | table | regress_priv_user1=arwdDxt/regress_priv_user1+| | public | dep_priv_test | table | regress_priv_user1=arwdDxtvz/regress_priv_user1+| |
| | | regress_priv_user2=r*/regress_priv_user1 +| | | | | regress_priv_user2=r*/regress_priv_user1 +| |
| | | regress_priv_user3=r*/regress_priv_user1 +| | | | | regress_priv_user3=r*/regress_priv_user1 +| |
| | | regress_priv_user4=r*/regress_priv_user2 +| | | | | regress_priv_user4=r*/regress_priv_user2 +| |
| | | regress_priv_user4=r*/regress_priv_user3 +| | | | | regress_priv_user4=r*/regress_priv_user3 +| |
| | | regress_priv_user5=r/regress_priv_user4 | | | | | regress_priv_user5=r/regress_priv_user4 | |
(1 row) (1 row)
set session role regress_priv_user2; set session role regress_priv_user2;
revoke select on dep_priv_test from regress_priv_user4 cascade; revoke select on dep_priv_test from regress_priv_user4 cascade;
\dp dep_priv_test \dp dep_priv_test
Access privileges Access privileges
Schema | Name | Type | Access privileges | Column privileges | Policies Schema | Name | Type | Access privileges | Column privileges | Policies
--------+---------------+-------+-----------------------------------------------+-------------------+---------- --------+---------------+-------+-------------------------------------------------+-------------------+----------
public | dep_priv_test | table | regress_priv_user1=arwdDxt/regress_priv_user1+| | public | dep_priv_test | table | regress_priv_user1=arwdDxtvz/regress_priv_user1+| |
| | | regress_priv_user2=r*/regress_priv_user1 +| | | | | regress_priv_user2=r*/regress_priv_user1 +| |
| | | regress_priv_user3=r*/regress_priv_user1 +| | | | | regress_priv_user3=r*/regress_priv_user1 +| |
| | | regress_priv_user4=r*/regress_priv_user3 +| | | | | regress_priv_user4=r*/regress_priv_user3 +| |
| | | regress_priv_user5=r/regress_priv_user4 | | | | | regress_priv_user5=r/regress_priv_user4 | |
(1 row) (1 row)
set session role regress_priv_user3; set session role regress_priv_user3;
revoke select on dep_priv_test from regress_priv_user4 cascade; revoke select on dep_priv_test from regress_priv_user4 cascade;
\dp dep_priv_test \dp dep_priv_test
Access privileges Access privileges
Schema | Name | Type | Access privileges | Column privileges | Policies Schema | Name | Type | Access privileges | Column privileges | Policies
--------+---------------+-------+-----------------------------------------------+-------------------+---------- --------+---------------+-------+-------------------------------------------------+-------------------+----------
public | dep_priv_test | table | regress_priv_user1=arwdDxt/regress_priv_user1+| | public | dep_priv_test | table | regress_priv_user1=arwdDxtvz/regress_priv_user1+| |
| | | regress_priv_user2=r*/regress_priv_user1 +| | | | | regress_priv_user2=r*/regress_priv_user1 +| |
| | | regress_priv_user3=r*/regress_priv_user1 | | | | | regress_priv_user3=r*/regress_priv_user1 | |
(1 row) (1 row)
set session role regress_priv_user1; set session role regress_priv_user1;
@ -2849,3 +2849,43 @@ DROP SCHEMA regress_roleoption;
DROP ROLE regress_roleoption_protagonist; DROP ROLE regress_roleoption_protagonist;
DROP ROLE regress_roleoption_donor; DROP ROLE regress_roleoption_donor;
DROP ROLE regress_roleoption_recipient; 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 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;
DROP TABLE vacanalyze_test;
DROP ROLE regress_no_priv;
DROP ROLE regress_only_vacuum;
DROP ROLE regress_only_analyze;
DROP ROLE regress_both;

View File

@ -93,23 +93,23 @@ CREATE POLICY p2r ON document AS RESTRICTIVE TO regress_rls_dave
CREATE POLICY p1r ON document AS RESTRICTIVE TO regress_rls_dave CREATE POLICY p1r ON document AS RESTRICTIVE TO regress_rls_dave
USING (cid <> 44); USING (cid <> 44);
\dp \dp
Access privileges Access privileges
Schema | Name | Type | Access privileges | Column privileges | Policies Schema | Name | Type | Access privileges | Column privileges | Policies
--------------------+----------+-------+---------------------------------------------+-------------------+-------------------------------------------- --------------------+----------+-------+-----------------------------------------------+-------------------+--------------------------------------------
regress_rls_schema | category | table | regress_rls_alice=arwdDxt/regress_rls_alice+| | regress_rls_schema | category | table | regress_rls_alice=arwdDxtvz/regress_rls_alice+| |
| | | =arwdDxt/regress_rls_alice | | | | | =arwdDxtvz/regress_rls_alice | |
regress_rls_schema | document | table | regress_rls_alice=arwdDxt/regress_rls_alice+| | p1: + regress_rls_schema | document | table | regress_rls_alice=arwdDxtvz/regress_rls_alice+| | p1: +
| | | =arwdDxt/regress_rls_alice | | (u): (dlevel <= ( SELECT uaccount.seclv + | | | =arwdDxtvz/regress_rls_alice | | (u): (dlevel <= ( SELECT uaccount.seclv +
| | | | | FROM uaccount + | | | | | FROM uaccount +
| | | | | WHERE (uaccount.pguser = CURRENT_USER)))+ | | | | | WHERE (uaccount.pguser = CURRENT_USER)))+
| | | | | p2r (RESTRICTIVE): + | | | | | p2r (RESTRICTIVE): +
| | | | | (u): ((cid <> 44) AND (cid < 50)) + | | | | | (u): ((cid <> 44) AND (cid < 50)) +
| | | | | to: regress_rls_dave + | | | | | to: regress_rls_dave +
| | | | | p1r (RESTRICTIVE): + | | | | | p1r (RESTRICTIVE): +
| | | | | (u): (cid <> 44) + | | | | | (u): (cid <> 44) +
| | | | | to: regress_rls_dave | | | | | to: regress_rls_dave
regress_rls_schema | uaccount | table | regress_rls_alice=arwdDxt/regress_rls_alice+| | regress_rls_schema | uaccount | table | regress_rls_alice=arwdDxtvz/regress_rls_alice+| |
| | | =r/regress_rls_alice | | | | | =r/regress_rls_alice | |
(3 rows) (3 rows)
\d document \d document

View File

@ -336,7 +336,9 @@ WARNING: permission denied to analyze "vacowned_part2", skipping it
VACUUM (ANALYZE) vacowned_parted; VACUUM (ANALYZE) vacowned_parted;
WARNING: permission denied to vacuum "vacowned_parted", skipping it WARNING: permission denied to vacuum "vacowned_parted", skipping it
WARNING: permission denied to vacuum "vacowned_part1", 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 vacuum "vacowned_part2", skipping it
WARNING: permission denied to analyze "vacowned_part2", skipping it
VACUUM (ANALYZE) vacowned_part1; VACUUM (ANALYZE) vacowned_part1;
WARNING: permission denied to vacuum "vacowned_part1", skipping it WARNING: permission denied to vacuum "vacowned_part1", skipping it
VACUUM (ANALYZE) vacowned_part2; VACUUM (ANALYZE) vacowned_part2;
@ -358,6 +360,7 @@ ANALYZE vacowned_part2;
WARNING: permission denied to analyze "vacowned_part2", skipping it WARNING: permission denied to analyze "vacowned_part2", skipping it
VACUUM (ANALYZE) vacowned_parted; VACUUM (ANALYZE) vacowned_parted;
WARNING: permission denied to vacuum "vacowned_part2", 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_part1;
VACUUM (ANALYZE) vacowned_part2; VACUUM (ANALYZE) vacowned_part2;
WARNING: permission denied to vacuum "vacowned_part2", skipping it WARNING: permission denied to vacuum "vacowned_part2", skipping it
@ -380,6 +383,7 @@ WARNING: permission denied to analyze "vacowned_part2", skipping it
VACUUM (ANALYZE) vacowned_parted; VACUUM (ANALYZE) vacowned_parted;
WARNING: permission denied to vacuum "vacowned_parted", skipping it WARNING: permission denied to vacuum "vacowned_parted", skipping it
WARNING: permission denied to vacuum "vacowned_part2", 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_part1;
VACUUM (ANALYZE) vacowned_part2; VACUUM (ANALYZE) vacowned_part2;
WARNING: permission denied to vacuum "vacowned_part2", skipping it WARNING: permission denied to vacuum "vacowned_part2", skipping it
@ -404,7 +408,9 @@ ANALYZE vacowned_part2;
WARNING: permission denied to analyze "vacowned_part2", skipping it WARNING: permission denied to analyze "vacowned_part2", skipping it
VACUUM (ANALYZE) vacowned_parted; VACUUM (ANALYZE) vacowned_parted;
WARNING: permission denied to vacuum "vacowned_part1", 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 vacuum "vacowned_part2", skipping it
WARNING: permission denied to analyze "vacowned_part2", skipping it
VACUUM (ANALYZE) vacowned_part1; VACUUM (ANALYZE) vacowned_part1;
WARNING: permission denied to vacuum "vacowned_part1", skipping it WARNING: permission denied to vacuum "vacowned_part1", skipping it
VACUUM (ANALYZE) vacowned_part2; VACUUM (ANALYZE) vacowned_part2;

View File

@ -21,7 +21,7 @@ REVOKE SELECT ON deptest FROM GROUP regress_dep_group;
DROP GROUP regress_dep_group; DROP GROUP regress_dep_group;
-- can't drop the user if we revoke the privileges partially -- can't drop the user if we revoke the privileges partially
REVOKE SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES ON deptest FROM regress_dep_user; REVOKE SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, VACUUM, ANALYZE ON deptest FROM regress_dep_user;
DROP USER regress_dep_user; DROP USER regress_dep_user;
-- now we are OK to drop him -- now we are OK to drop him

View File

@ -1852,3 +1852,43 @@ DROP SCHEMA regress_roleoption;
DROP ROLE regress_roleoption_protagonist; DROP ROLE regress_roleoption_protagonist;
DROP ROLE regress_roleoption_donor; DROP ROLE regress_roleoption_donor;
DROP ROLE regress_roleoption_recipient; 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 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;
DROP TABLE vacanalyze_test;
DROP ROLE regress_no_priv;
DROP ROLE regress_only_vacuum;
DROP ROLE regress_only_analyze;
DROP ROLE regress_both;