diff --git a/documentation/changelog.mdx b/documentation/changelog.mdx
index 833739bcc..77f65b6d5 100644
--- a/documentation/changelog.mdx
+++ b/documentation/changelog.mdx
@@ -16,6 +16,7 @@ This page tracks significant updates to the QuestDB documentation.
### New
+- [Access control functions](/docs/query/functions/access-control/) - QuestDB Enterprise's four ACL table functions: `all_permissions()` lists available permissions and their grant levels, `permissions()` inspects one principal, and `active_permissions()` and `active_grants()` search effective permissions and direct grants across principals
- [Memory limits](/docs/configuration/cairo-engine/#memory-limits) - New section covering the per-query, materialized view refresh, WAL apply, and live view refresh memory limits, what counts toward them, and what happens on a breach, plus the previously undocumented [`cairo.mat.view.max.refresh.retries`](/docs/configuration/materialized-views/#cairomatviewmaxrefreshretries), [`cairo.mat.view.refresh.busy.retry.limit`](/docs/configuration/materialized-views/#cairomatviewrefreshbusyretrylimit), [`cairo.mat.view.refresh.busy.retry.timeout`](/docs/configuration/materialized-views/#cairomatviewrefreshbusyretrytimeout), [`cairo.write.back.off.timeout.on.mem.pressure`](/docs/configuration/cairo-engine/#cairowritebackofftimeoutonmempressure), [`ram.usage.limit.bytes`](/docs/configuration/cairo-engine/#ramusagelimitbytes), and [`ram.usage.limit.percent`](/docs/configuration/cairo-engine/#ramusagelimitpercent) keys
- [RBAC memory limits](/docs/security/rbac/#memory-limits) - Per-user, per-group, and per-service-account query memory limits in QuestDB Enterprise: `SET MEMORY LIMIT` on `ALTER USER`, `ALTER GROUP`, and `ALTER SERVICE ACCOUNT`, how limits resolve, the `SET MEMORY LIMIT` permission, and the upgrade migration
- [ALTER GROUP](/docs/query/sql/acl/alter-group/) - New reference page covering `SET MEMORY LIMIT` and external alias mapping
diff --git a/documentation/getting-started/enterprise-quick-start.md b/documentation/getting-started/enterprise-quick-start.md
index 86dbd3e2e..a0d8d2ee7 100644
--- a/documentation/getting-started/enterprise-quick-start.md
+++ b/documentation/getting-started/enterprise-quick-start.md
@@ -188,7 +188,11 @@ GRANT ALL ON table2 TO user2 WITH GRANT OPTION;
Permission grants can be specific and fine-tuned.
-List the full list of applied permissions with `all_permissions()`.
+Check what a user can do with `SHOW PERMISSIONS user1;`, or audit grants across
+all users, groups, and service accounts with
+[`active_grants()`](/docs/query/functions/access-control/#active_grants).
+[`all_permissions()`](/docs/query/functions/access-control/#all_permissions)
+lists the permissions available to grant.
- For the full role-based access control docs, including group management, see
the [RBAC operations guide](/docs/security/rbac/).
diff --git a/documentation/query/functions/access-control.md b/documentation/query/functions/access-control.md
new file mode 100644
index 000000000..5229087b0
--- /dev/null
+++ b/documentation/query/functions/access-control.md
@@ -0,0 +1,351 @@
+---
+title: Access control functions
+sidebar_label: Access control
+description: >-
+ Audit QuestDB Enterprise RBAC in SQL with active_grants(),
+ active_permissions(), permissions(), and all_permissions(): list grants and
+ find who can read a table.
+---
+
+import { EnterpriseNote } from "@site/src/components/EnterpriseNote"
+
+
+ Access control functions are available in QuestDB Enterprise.
+
+
+QuestDB Enterprise provides four SQL table functions for inspecting
+[role-based access control](/docs/security/rbac/): `all_permissions()` lists
+available permission names and scopes; `permissions()` shows one principal's
+access; `active_permissions()` and `active_grants()` let you search effective
+permissions or direct grants across all persisted internal users, groups, and
+service accounts. Use them to find who can access a table or who has been
+granted a particular permission.
+
+## Syntax
+
+```questdb-sql title="Available permission names and levels"
+all_permissions()
+```
+
+```questdb-sql title="Current or named entity's permissions"
+permissions([entityName])
+```
+
+```questdb-sql title="Effective permissions across entities"
+active_permissions()
+```
+
+```questdb-sql title="Direct grants across entities"
+active_grants()
+```
+
+All four are table functions used in the `FROM` clause. Their results can be
+filtered, joined, and ordered with SQL.
+
+- `entityName` (optional, string literal): existing user, group, or service
+ account to inspect. Omit it to inspect the current entity.
+
+## all_permissions
+
+`all_permissions()` takes no arguments and returns the permission names
+supported by the server and the levels where they can be granted. It lists
+permissions, **not** principals or their grants. Its columns are `permission`
+(STRING) and `level` (STRING). `level` is `Database`, `Database|Table`, or
+`Database|Table|Column` according to the permission's allowed scopes.
+
+For example, check where `SELECT` can be granted:
+
+```questdb-sql title="Where SELECT can be granted"
+SELECT permission, level
+FROM all_permissions()
+WHERE permission = 'SELECT';
+```
+
+See the [permissions reference](/docs/security/rbac/#permissions) for the
+available permissions and their uses.
+
+## permissions
+
+`permissions()` returns the effective permissions of one user, group, or
+service account. Without an argument, it returns permissions for the current
+principal. Pass an existing user, group, or service account name as a string to
+inspect that entity instead. It returns the same result as
+[`SHOW PERMISSIONS`](/docs/query/sql/show/#show-permissions),
+but can be composed with `WHERE`, `ORDER BY`, and other SQL clauses:
+
+```questdb-sql title="SELECT permissions of one user"
+SELECT permission, table_name, column_name, grant_option, origin
+FROM permissions('analyst')
+WHERE permission = 'SELECT';
+```
+
+| Column | Type | Description |
+| -------------- | ------- | ----------------------------------------------------------------------- |
+| `permission` | STRING | Permission name |
+| `table_name` | STRING | Table scope, or `NULL` for database-level permissions |
+| `column_name` | STRING | Column scope, or `NULL` for table- and database-level permissions |
+| `grant_option` | BOOLEAN | Whether the principal can grant this permission at this scope to others |
+| `origin` | STRING | `G` for granted access, `I` for implicit designated-timestamp access |
+
+`G` includes both direct and inherited permissions; it does not distinguish
+between them. You can inspect your own permissions without `USER DETAILS`.
+Inspecting another entity generally requires `USER DETAILS`; users can also
+inspect their own groups and service accounts they can assume. `permissions()`
+does not show all entities in a single result. It cannot be used in a
+materialized or live view.
+
+
+
+## active_permissions
+
+`active_permissions()` returns the effective permissions of every persisted
+user, group, and service account, including permissions users inherit from
+their groups. Use it to find who can access a table or column. It takes no
+arguments and returns these columns:
+
+| Column | Type | Description |
+| -------------- | ------- | ------------------------------------------------------------------------------ |
+| `entity_name` | STRING | Name of the user, group, or service account |
+| `entity_type` | STRING | `User`, `Group`, or `Service Account` |
+| `permission` | STRING | Permission name, such as `SELECT` or `CREATE TABLE` |
+| `table_name` | STRING | Table name for a table or column scope; `NULL` for a database-level permission |
+| `column_name` | STRING | Column name for a column scope; `NULL` for a table or database scope |
+| `grant_option` | BOOLEAN | Whether the entity can grant this permission at this scope to others |
+
+Each row is one scope at which the entity holds a permission:
+
+- `table_name` is `NULL`: database-wide. A database-level `SELECT` applies to
+ all tables, including tables created later.
+- `table_name` is set and `column_name` is `NULL`: the whole table.
+- Both are set: that column only.
+
+An entity can have rows for the same permission at more than one scope. For
+example, a table-wide `SELECT` without the grant option can sit next to column
+rows that carry the grant option for specific columns. Access is limited to
+specific columns only when the entity has no row for that permission on the
+whole table or database. When filtering for access to a particular table,
+include both its table name and `NULL`.
+
+Each user's rows combine its direct permissions with permissions inherited
+from its groups. Groups and service accounts have their own rows. The result
+also includes implicit access to a table's designated timestamp column when a
+principal has `SELECT` or `UPDATE` on another column. For a principal with
+`DATABASE ADMIN`, it expands the effective database permissions.
+
+## active_grants
+
+`active_grants()` returns the permissions granted directly to each persisted
+user, group, and service account. Use it to audit who was granted what. It
+takes no arguments and returns the same columns and scope rows as
+[`active_permissions()`](#active_permissions).
+
+Unlike `active_permissions()`, it does not repeat a group's grants under its
+members or include implicit designated timestamp permissions. Results reflect the **current, normalized ACL scopes**,
+not the original `GRANT` statements: for example, revoking access to a single
+column can turn a table-wide grant into column-level rows. To inspect one
+principal instead, use [`permissions()`](#permissions) or
+[`SHOW PERMISSIONS`](/docs/query/sql/show/#show-permissions).
+
+:::note
+
+`active_permissions()` and `active_grants()` both require `LIST USERS` and
+`USER DETAILS` permissions. The built-in
+admin can call them without explicit grants unless it has assumed a service
+account, in which case the assumed account needs both permissions. They return
+an empty result if ACL is disabled. They do not list external SSO/OIDC
+identities, which get their access through the groups they are mapped to, or
+the built-in admin, which has no persisted ACL entry. A disabled user can still
+appear with its retained permissions, so a row does not necessarily mean the
+entity can connect. Check the `enabled` column of
+[`SHOW USERS`](/docs/query/sql/show/#show-users) or
+[`SHOW SERVICE ACCOUNTS`](/docs/query/sql/show/#show-service-accounts), and the
+[endpoint permissions](/docs/security/rbac/#endpoint-permissions) such as
+`PGWIRE` or `HTTP`. Neither function can be used in a materialized or live view.
+
+:::
+
+## Examples
+
+The examples use the following entities and grants on the `trades` table, whose
+designated timestamp column is `timestamp`. The result tables show the output
+for this setup.
+
+```questdb-sql title="Example setup"
+CREATE GROUP trading_team;
+CREATE USER analyst WITH PASSWORD 'pwd';
+CREATE USER risk_manager WITH PASSWORD 'pwd';
+CREATE SERVICE ACCOUNT report_svc;
+ADD USER analyst TO trading_team;
+
+GRANT CREATE TABLE TO trading_team;
+GRANT SELECT ON trades(symbol, price) TO trading_team WITH GRANT OPTION;
+GRANT SELECT ON trades TO analyst;
+GRANT SELECT ON ALL TABLES TO risk_manager;
+GRANT SELECT ON trades(symbol, price) TO report_svc;
+```
+
+### Compare effective permissions with direct grants
+
+`active_permissions()` shows everything `analyst` can do, including what it
+inherits from `trading_team`:
+
+```questdb-sql title="Effective permissions of one user"
+SELECT permission, table_name, column_name, grant_option
+FROM active_permissions()
+WHERE entity_name = 'analyst'
+ORDER BY permission, table_name, column_name;
+```
+
+| permission | table_name | column_name | grant_option |
+| ------------ | ---------- | ----------- | ------------ |
+| CREATE TABLE | NULL | NULL | false |
+| SELECT | trades | NULL | false |
+| SELECT | trades | price | true |
+| SELECT | trades | symbol | true |
+
+`analyst` can read the whole `trades` table through its own table-wide grant.
+The `price` and `symbol` rows come from the group and carry its grant option,
+so `analyst` can grant `SELECT` on those two columns, but not on the whole
+table. `CREATE TABLE` is also inherited from the group.
+
+`active_grants()` shows only what was granted to `analyst` directly:
+
+```questdb-sql title="Direct grants of one user"
+SELECT permission, table_name, column_name, grant_option
+FROM active_grants()
+WHERE entity_name = 'analyst';
+```
+
+| permission | table_name | column_name | grant_option |
+| ---------- | ---------- | ----------- | ------------ |
+| SELECT | trades | NULL | false |
+
+### Find who can read a table
+
+List every entity with effective `SELECT` permission on `trades`, including
+database-wide access:
+
+```questdb-sql title="Entities that can read trades"
+SELECT entity_name, entity_type, table_name, column_name
+FROM active_permissions()
+WHERE permission = 'SELECT'
+ AND (table_name = 'trades' OR table_name IS NULL)
+ORDER BY entity_type, entity_name, table_name, column_name;
+```
+
+| entity_name | entity_type | table_name | column_name |
+| ------------ | --------------- | ---------- | ----------- |
+| trading_team | Group | trades | price |
+| trading_team | Group | trades | symbol |
+| trading_team | Group | trades | timestamp |
+| report_svc | Service Account | trades | price |
+| report_svc | Service Account | trades | symbol |
+| report_svc | Service Account | trades | timestamp |
+| analyst | User | trades | NULL |
+| analyst | User | trades | price |
+| analyst | User | trades | symbol |
+| risk_manager | User | NULL | NULL |
+
+- `risk_manager` can read every table, including `trades`.
+- `analyst` can read the whole table. Its column rows record the grant option
+ shown in the previous example.
+- `trading_team` and `report_svc` can read `symbol` and `price`, plus the
+ designated timestamp column, which comes with column-level `SELECT`.
+- Keep group rows when auditing: a group cannot log in, but its members can,
+ including external SSO/OIDC users mapped to it, who are not listed
+ individually.
+
+To check access to a single column, also accept table-wide rows, and use
+`DISTINCT` to get one row per entity:
+
+```questdb-sql title="Entities that can read trades.price"
+SELECT DISTINCT entity_name, entity_type
+FROM active_permissions()
+WHERE permission = 'SELECT'
+ AND (table_name = 'trades' OR table_name IS NULL)
+ AND (column_name = 'price' OR column_name IS NULL)
+ORDER BY entity_type, entity_name;
+```
+
+| entity_name | entity_type |
+| ------------ | --------------- |
+| trading_team | Group |
+| report_svc | Service Account |
+| analyst | User |
+| risk_manager | User |
+
+These queries do not show every way to reach the data:
+
+- **Assumed service accounts.** A user who can assume a service account gets
+ its permissions after
+ [`ASSUME SERVICE ACCOUNT`](/docs/security/rbac/#service-account-assumption),
+ but those permissions appear only under the service account's name. Use
+ [`SHOW SERVICE ACCOUNTS userName`](/docs/query/sql/show/#show-service-accounts)
+ to list the accounts a user or group can assume.
+- **Views.** `SELECT` on a view over `trades` lets the grantee read the view's
+ rows without any grant on `trades`. See
+ [row-level access with views](/docs/security/rbac/#row-level-access-with-views).
+
+### Find direct recipients of a permission
+
+Find who was directly granted `CREATE TABLE`. Members who inherit a group's
+permission are not repeated, so `analyst` does not appear. Use
+`active_permissions()` to see effective access.
+
+```questdb-sql title="Direct recipients of CREATE TABLE"
+SELECT entity_name, entity_type, grant_option
+FROM active_grants()
+WHERE permission = 'CREATE TABLE'
+ORDER BY entity_type, entity_name;
+```
+
+| entity_name | entity_type | grant_option |
+| ------------ | ----------- | ------------ |
+| trading_team | Group | false |
+
+For a table-scoped permission such as `SELECT`, also filter by scope, including
+database-wide grants:
+
+```questdb-sql title="Direct SELECT grants on trades"
+SELECT entity_name, entity_type, table_name, column_name, grant_option
+FROM active_grants()
+WHERE permission = 'SELECT'
+ AND (table_name = 'trades' OR table_name IS NULL)
+ORDER BY entity_type, entity_name, table_name, column_name;
+```
+
+| entity_name | entity_type | table_name | column_name | grant_option |
+| ------------ | --------------- | ---------- | ----------- | ------------ |
+| trading_team | Group | trades | price | true |
+| trading_team | Group | trades | symbol | true |
+| report_svc | Service Account | trades | price | false |
+| report_svc | Service Account | trades | symbol | false |
+| analyst | User | trades | NULL | false |
+| risk_manager | User | NULL | NULL | false |
+
+Unlike `active_permissions()`, this does not include the implicit designated
+timestamp rows.
+
+### Find who can delegate SELECT on a table
+
+Filter effective permissions by `grant_option` to find entities that can grant
+`SELECT` on all or part of `trades` to others:
+
+```questdb-sql title="Entities that can grant SELECT on trades"
+SELECT entity_name, entity_type, table_name, column_name
+FROM active_permissions()
+WHERE permission = 'SELECT'
+ AND grant_option
+ AND (table_name = 'trades' OR table_name IS NULL)
+ORDER BY entity_type, entity_name, table_name, column_name;
+```
+
+| entity_name | entity_type | table_name | column_name |
+| ------------ | ----------- | ---------- | ----------- |
+| trading_team | Group | trades | price |
+| trading_team | Group | trades | symbol |
+| analyst | User | trades | price |
+| analyst | User | trades | symbol |
+
+Both can grant `SELECT` on `price` and `symbol` only. No one in this setup can
+grant `SELECT` on the whole table.
diff --git a/documentation/query/sql/acl/grant.md b/documentation/query/sql/acl/grant.md
index 3a2e17b45..6c06d9971 100644
--- a/documentation/query/sql/acl/grant.md
+++ b/documentation/query/sql/acl/grant.md
@@ -15,7 +15,11 @@ import { EnterpriseNote } from "@site/src/components/EnterpriseNote"
`GRANT` - grants permissions to a 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.
+see the [RBAC operations](/docs/security/rbac) page. To inspect the resulting ACL
+state, use [`permissions()`](/docs/query/functions/access-control/#permissions)
+for one entity or
+[`active_grants()`](/docs/query/functions/access-control/#active_grants) across
+all users, groups, and service accounts.
---
@@ -240,7 +244,8 @@ Therefore when a table has a designated timestamp, granting `SELECT` or `UPDATE`
permissions on any column will automatically extend those permissions to the
timestamp column. These are known as
[implicit permissions](/docs/security/rbac/#implicit-permissions), and they're
-indicated by an `I` in the `origin` column of the `SHOW PERMISSIONS` output.
+indicated by an `I` in the `origin` column of the
+[`SHOW PERMISSIONS`](/docs/query/sql/show/#show-permissions) output.
For example, if you grant `UPDATE` permission on the `id` column of the
`products` table, the timestamp column also receives `UPDATE` permission:
@@ -258,7 +263,8 @@ GRANT UPDATE ON products(id) TO john;
### Optimization
When granting permissions on the table or column level, sometimes it might seem
-like there is no effect when cross-checking with the `SHOW permissions` command.
+like there is no effect when cross-checking with the
+[`SHOW PERMISSIONS`](/docs/query/sql/show/#show-permissions) command.
If QuestDB detects that the permission is already granted on a higher level, it
optimizes and removes any child permissions. Doing so keeps the access list
model simple and permission checks faster.
@@ -297,7 +303,10 @@ GRANT UPDATE ON countries(id) TO john;
GRANT UPDATE ON countries(description) TO john;
```
-Such permissions do not show on `SHOW PERMISSIONS` output.
+Such permissions do not show in
+[`SHOW PERMISSIONS`](/docs/query/sql/show/#show-permissions) output, but
+[`active_grants()`](/docs/query/functions/access-control/#active_grants) lists
+them.
| permission | table_name | column_name | grant_option | origin |
| ---------- | ---------- | ----------- | ------------ | ------ |
diff --git a/documentation/query/sql/acl/revoke.md b/documentation/query/sql/acl/revoke.md
index c302b6014..eb40dd72a 100644
--- a/documentation/query/sql/acl/revoke.md
+++ b/documentation/query/sql/acl/revoke.md
@@ -15,7 +15,11 @@ import { EnterpriseNote } from "@site/src/components/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.
+see the [RBAC operations](/docs/security/rbac) page. After revoking a permission,
+inspect current direct grants with
+[`active_grants()`](/docs/query/functions/access-control/#active_grants)
+or effective access with
+[`active_permissions()`](/docs/query/functions/access-control/#active_permissions).
---
diff --git a/documentation/query/sql/show.md b/documentation/query/sql/show.md
index 29256373f..2bd765be1 100644
--- a/documentation/query/sql/show.md
+++ b/documentation/query/sql/show.md
@@ -448,57 +448,62 @@ full column list, including `hasParquetGenerated`, `isParquet`,
:::
-### SHOW PERMISSIONS FOR CURRENT USER
+
+
-_Enterprise only._
+### SHOW PERMISSIONS
+
+_Enterprise only._ `SHOW PERMISSIONS` displays the effective permissions of one
+user, group, or service account. Omitting the name shows the current principal;
+providing a name shows that entity's permissions.
+
+#### Current principal
```questdb-sql
SHOW PERMISSIONS;
```
-| permission | table_name | column_name | grant_option | origin |
-| ---------- | ---------- | ----------- | ------------ | ------ |
-| SELECT | | | t | G |
-
-### SHOW PERMISSIONS user
-
-_Enterprise only._
+#### Named user, group, or service account
```questdb-sql
-SHOW PERMISSIONS admin;
+SHOW PERMISSIONS analyst; -- user
+SHOW PERMISSIONS trading_team; -- group
+SHOW PERMISSIONS ingest_app; -- service account
```
-| permission | table_name | column_name | grant_option | origin |
-| ---------- | ---------- | ----------- | ------------ | ------ |
-| SELECT | | | t | G |
-| INSERT | orders | | f | G |
-| UPDATE | order_itme | quantity | f | G |
-
-### SHOW PERMISSIONS
+The result has these columns:
-_Enterprise only._
+| Column | Description |
+| -------------- | -------------------------------------------------------------------- |
+| `permission` | Permission name |
+| `table_name` | Table name, or `NULL` for a database-level permission |
+| `column_name` | Column name, or `NULL` for a table- or database-level permission |
+| `grant_option` | Boolean: whether the entity can grant this permission at this scope |
+| `origin` | `G` for granted access, `I` for implicit designated-timestamp access |
-#### For a group
+For an existing `trades` table, grant `analyst` table-wide access and inspect
+its permissions:
-```questdb-sql
-SHOW PERMISSIONS admin_group;
+```questdb-sql title="Inspect a table-wide grant"
+GRANT SELECT ON trades TO analyst;
+SHOW PERMISSIONS analyst;
```
| permission | table_name | column_name | grant_option | origin |
| ---------- | ---------- | ----------- | ------------ | ------ |
-| INSERT | orders | | f | G |
+| SELECT | trades | | false | G |
-#### For a service account
+`G` includes direct and inherited group permissions; it does not distinguish
+between them. You can view your own permissions without `USER DETAILS`. Viewing
+another entity generally requires `USER DETAILS`, but users can also view their
+own groups and service accounts they can assume.
-```questdb-sql
-SHOW PERMISSIONS ilp_ingestion;
-```
-
-| permission | table_name | column_name | grant_option | origin |
-| ---------- | ---------- | ----------- | ------------ | ------ |
-| SELECT | | | t | G |
-| INSERT | | | f | G |
-| UPDATE | | | f | G |
+To filter the result for one entity with SQL, use
+[`permissions()`](/docs/query/functions/access-control/#permissions). To search
+across all users, groups, and service accounts, use
+[`active_permissions()`](/docs/query/functions/access-control/#active_permissions) and
+[`active_grants()`](/docs/query/functions/access-control/#active_grants).
+The latter two require `LIST USERS` and `USER DETAILS` permissions.
### SHOW SERVER_VERSION
@@ -638,6 +643,9 @@ these columns by position rather than by name must account for it. See
The following functions allow querying tables and views with filters and using
the results as part of a function:
+- [`permissions()`](/docs/query/functions/access-control/#permissions)
+- [`active_grants()`](/docs/query/functions/access-control/#active_grants)
+- [`active_permissions()`](/docs/query/functions/access-control/#active_permissions)
- [table_columns()](/docs/query/functions/meta/#table_columns)
- [tables()](/docs/query/functions/meta/#tables)
- [table_partitions()](/docs/query/functions/meta/#table_partitions)
diff --git a/documentation/security/rbac.md b/documentation/security/rbac.md
index 62dd03447..9aa3d8fe3 100644
--- a/documentation/security/rbac.md
+++ b/documentation/security/rbac.md
@@ -571,7 +571,13 @@ SHOW PERMISSIONS username; -- Show permissions for user
```
`SHOW USERS`, `SHOW GROUPS`, and `SHOW SERVICE ACCOUNTS` also report each
-entity's [memory limit](#memory-limits).
+entity's [memory limit](#memory-limits). To filter the permissions of one
+principal, use [`permissions()`](/docs/query/functions/access-control/#permissions)
+instead of `SHOW PERMISSIONS`. To search across all principals, use
+[`active_permissions()`](/docs/query/functions/access-control/#active_permissions) or
+[`active_grants()`](/docs/query/functions/access-control/#active_grants).
+These show effective access (including group inheritance) or direct grants,
+respectively, and require both `LIST USERS` and `USER DETAILS`.
Example output from `SHOW USER`:
@@ -585,9 +591,12 @@ REST Token true
:::note
-Viewing other users' information requires `LIST USERS` (to list all) or
-`USER DETAILS` (to see details) permissions. Users can always view their own
-information without these permissions.
+Listing all users, groups, or service accounts requires `LIST USERS`. Viewing
+another entity's details requires `USER DETAILS`. Because `active_permissions()`
+and `active_grants()` perform both operations, they require both permissions.
+Users can view their own information without either permission. With
+`SHOW PERMISSIONS` or `permissions()`, they can also inspect their own groups
+and service accounts they can assume without `USER DETAILS`.
:::
@@ -798,7 +807,8 @@ again at startup.
## Permissions reference {#permissions}
-Use `all_permissions()` to see all available permissions:
+Use [`all_permissions()`](/docs/query/functions/access-control/#all_permissions)
+to see all available permissions and where they can be granted:
```questdb-sql
SELECT * FROM all_permissions();
@@ -899,6 +909,7 @@ replication role, is an ordinary grantable permission.
## SQL commands reference
+- [Access control functions](/docs/query/functions/access-control/)
- [ADD USER](/docs/query/sql/acl/add-user/)
- [ALTER GROUP](/docs/query/sql/acl/alter-group/)
- [ALTER SERVICE ACCOUNT](/docs/query/sql/acl/alter-service-account/)
@@ -921,4 +932,4 @@ replication role, is an ordinary grantable permission.
- [SHOW GROUPS](/docs/query/sql/show/#show-groups)
- [SHOW SERVICE ACCOUNT](/docs/query/sql/show/#show-service-account)
- [SHOW SERVICE ACCOUNTS](/docs/query/sql/show/#show-service-accounts)
-- [SHOW PERMISSIONS](/docs/query/sql/show/#show-permissions-for-current-user)
+- [SHOW PERMISSIONS](/docs/query/sql/show/#show-permissions)
diff --git a/documentation/sidebars.js b/documentation/sidebars.js
index e82481098..78eddaa19 100644
--- a/documentation/sidebars.js
+++ b/documentation/sidebars.js
@@ -491,6 +491,7 @@ module.exports = {
type: "category",
label: "Functions",
items: [
+ "query/functions/access-control",
"query/functions/aggregation",
"query/functions/array",
"query/functions/binary",