# REVOKE reference

> **For AI agents:** the complete documentation index is at [llms.txt](https://questdb.com/docs/llms.txt). Every page is available as markdown by appending `.md` to its URL, or by sending an `Accept: text/markdown` request header.

REVOKE takes permissions on the database, tables, columns or resource groups away from a user, group or service account. Applies to RBAC in QuestDB Enterprise.

<EnterpriseNote>
  RBAC provides fine-grained database permissions management.
</EnterpriseNote>

`REVOKE` - revoke permission from user, group or service account.

For full documentation of the Access Control List and Role-based Access Control,
see the [RBAC operations](/docs/security/rbac) page.

---

## Syntax

```questdb-sql title="Database scope"
REVOKE permission [, permission ...] FROM entityName;
```

```questdb-sql title="Table or column scope"
REVOKE permission [, permission ...]
    ON { ALL TABLES
       | tableName [(columns)] [, tableName [(columns)] ...] }
    FROM entityName;
```

Where `columns` is either an explicit list of columns, or `*` for all of the
table's current columns with an optional `EXCLUDE` list:

```questdb-sql title="Column specification"
{ columnName [, columnName ...]
| * [EXCLUDE (columnName [, columnName ...])] }
```

The `USE RESOURCE GROUP` permission on a single resource group is revoked with:

```questdb-sql title="Resource group scope"
REVOKE USE ON RESOURCE GROUP groupName FROM entityName;
```

## Description

- `REVOKE [permissions] FROM entity` - revoke database level permissions from an
  entity
- `REVOKE [permissions] ON ALL TABLES FROM entity` - revoke table/column level
  permissions on database level from an entity
- `REVOKE [permissions] ON [table] FROM entity` - revoke table/column level
  permissions on table level from an entity
- `REVOKE [permissions] ON [table(columns)] FROM entity` - revoke column level
  permissions on column level from an entity
- `REVOKE [permissions] ON [table(*)] FROM entity` - revoke column level
  permissions on all of the table's current columns, with an optional `EXCLUDE`
  list
- `REVOKE USE ON RESOURCE GROUP [group] FROM entity` - revoke the use of one
  resource group from an entity

### Revoke database level permissions

```questdb-sql
REVOKE CREATE TABLE FROM john;
```

### Revoke table level permissions for entire database

```questdb-sql
REVOKE ADD INDEX, REINDEX ON ALL TABLES FROM john;
```

### Revoke table level permissions on specific tables

```questdb-sql
REVOKE ADD INDEX, REINDEX ON orders FROM john;
```

### Revoke column level permissions for entire database

```questdb-sql
REVOKE SELECT ON ALL TABLES FROM john;
```

### Revoke column level permissions on specific tables

```questdb-sql
REVOKE SELECT ON orders, trades FROM john;
```

### Revoke column level permissions on specific columns

```questdb-sql
REVOKE SELECT ON orders(id, name) FROM john;
```

### Revoke from all columns of a table

Use the `*` wildcard to revoke a column level permission from every column the
table currently has:

```questdb-sql
REVOKE SELECT ON trades(*) FROM john;
```

As with [`GRANT`](/docs/query/sql/acl/grant/#grant-on-all-columns-of-a-table),
the `*` is expanded to the table's columns at the time the statement runs, and
the target table must exist.

Add an `EXCLUDE` list to revoke from all columns except the ones you name. For
example, to take away `SELECT` on every column of `trades` except `symbol` and
`ts`:

```questdb-sql
REVOKE SELECT ON trades(* EXCLUDE (symbol, ts)) FROM john;
```

`EXCLUDE` can only be used together with `*`. Every excluded column must exist,
and the list can neither be empty nor cover every column of the table. When the
permission was originally granted at table level, revoking it from a subset of
columns this way triggers
[permission level readjustment](#permission-level-readjustment).

### Revoke use of resource groups

Revoke the use of one resource group, granted with
[`GRANT USE ON RESOURCE GROUP`](/docs/query/sql/acl/grant/#grant-use-of-resource-groups):

```questdb-sql
REVOKE USE ON RESOURCE GROUP alerts FROM monitoring;
```

Like the grant, this form takes the
[`RESOURCE GROUP ADMIN`](/docs/security/rbac/#permissions) permission, and the
group must exist.

It removes only the grant on that group. An entity that also holds the
database-level `USE RESOURCE GROUP`, directly or through an ACL group, can still
use the group afterwards, because
[permission level readjustment](#permission-level-readjustment) does not apply
to resource groups: revoking one group never splits the database-level grant
into grants on the others. To take every group away, revoke the database-level
permission.

`REVOKE USE RESOURCE GROUP FROM monitoring` revokes the database-level
permission, and with it every grant the entity holds on single groups.
`REVOKE ALL FROM monitoring` and dropping the entity clear them as well, and
`DROP RESOURCE GROUP` removes every grant on the dropped group.

The permission is checked at the start of every managed query, so a revoke
reaches sessions that already chose the group with
[`SET RESOURCE GROUP`](/docs/query/sql/acl/set-resource-group/): their next such
query fails with
`Access denied for <principal> [USE RESOURCE GROUP on alerts]` until they choose
another group or run `RESET RESOURCE GROUP`. A query already running finishes in
its group.

### Implicit permissions

If the target table has implicit timestamp permissions, then revoking `SELECT`
or `UPDATE` permission on all other table columns also revokes it on the
designated timestamp column:

```questdb-sql
CREATE TABLE products(id INT, name STRING, ts TIMESTAMP) TIMESTAMP(ts);
GRANT SELECT ON products(id) TO john;
GRANT SELECT, UPDATE ON products(name) TO john;
```

| permission | table_name | column_name | grant_option | origin |
| ---------- | ---------- | ----------- | ------------ | ------ |
| UPDATE     | products   | name        | f            | G      |
| UPDATE     | products   | ts          | f            | I      |
| SELECT     | products   | id          | f            | G      |
| SELECT     | products   | name        | f            | G      |
| SELECT     | products   | ts          | f            | I      |

Revoking a permission from all columns revokes the implicitly granted permission
from the designated timestamp column:

```questdb-sql
REVOKE UPDATE ON products(name) FROM john;
```

| permission | table_name | column_name | grant_option | origin |
| ---------- | ---------- | ----------- | ------------ | ------ |
| SELECT     | products   | id          | f            | G      |
| SELECT     | products   | name        | f            | G      |
| SELECT     | products   | ts          | f            | I      |

However, if there is even a single column which still has the permission, then
the implicit permission is kept:

```questdb-sql
REVOKE SELECT ON products(id) FROM john;
```

| permission | table_name | column_name | grant_option | origin |
| ---------- | ---------- | ----------- | ------------ | ------ |
| SELECT     | products   | name        | f            | G      |
| SELECT     | products   | ts          | f            | I      |

### Permission level readjustment

If the user has a database- or table-level permission, then revoking it on a
lower level triggers
[permission level re-adjustment](/docs/security/rbac/#permission-level-re-adjustment).
Permission is switched to lower level and `materialized`:

- database level permission is pushed to table level, so e.g. SELECT will not
  apply to any new tables
- table level permission is pushed to column level, so e.g. SELECT will not
  apply to any new table columns

For example, assume we have the following tables: `orders`, `trades` and
`products`, and revoking a permission from a table which was granted on database
level previously.

```questdb-sql
GRANT SELECT ON ALL TABLES TO john;
REVOKE SELECT ON trades FROM john;
```

Database level permission is replaced with table level on all existing tables,
except the one being revoked.

| permission | table_name | column_name | grant_option | origin |
| ---------- | ---------- | ----------- | ------------ | ------ |
| SELECT     | orders     |             | f            | G      |
| SELECT     | products   |             | f            | G      |

As a consequence permission, which was granted for all tables previously, will
not apply to any newly-created tables:

```questdb-sql
CREATE TABLE new_tab( id INT );
```

| permission | table_name | column_name | grant_option | origin |
| ---------- | ---------- | ----------- | ------------ | ------ |
| SELECT     | orders     |             | f            | G      |
| SELECT     | products   |             | f            | G      |

Permission level re-adjustment can also happen from the table level to the
column level. For example, the following column level revoke replaces the table
level permission on the products table with column level permissions:

```questdb-sql
REVOKE SELECT on products(id) FROM john;
```

| permission | table_name | column_name | grant_option | origin |
| ---------- | ---------- | ----------- | ------------ | ------ |
| SELECT     | orders     |             | f            | G      |
| SELECT     | products   | name        | f            | G      |

### Revoke permissions inherited from group

Permissions of groups are applied after user permissions, thus it is not
possible to revoke them directly from the user.

```questdb-sql
CREATE group admins;
GRANT SELECT on products to admins;
ADD USER john to admins;
REVOKE SELECT on products from john;
```

| permission | table_name | column_name | grant_option | origin |
| ---------- | ---------- | ----------- | ------------ | ------ |
| SELECT     | products   |             | f            | G      |

To do so, either:

- the user has to be removed from the group where the permission is inherited
  from
- or the permission has to be revoked from the group

```questdb-sql
REVOKE SELECT on products FROM admins;
-- or
REMOVE USER john FROM admins;
```

### Revoke on a missing or hidden table

`REVOKE` on a table that does not exist fails with
`table does not exist [table=tableName]`, so a misspelled name is reported
instead of revoking nothing. A column that does not exist fails with
`column does not exist [table=tableName, column=columnName]`. A table the
revoking principal [cannot see](/docs/security/rbac/#object-visibility) fails
like a missing one, before its columns are checked.
