From acad473930e1b0c67ab2ffffd77636db77c9de7d Mon Sep 17 00:00:00 2001 From: glasstiger Date: Wed, 10 Jun 2026 16:31:12 +0100 Subject: [PATCH] Document EXCLUDE column option for GRANT/REVOKE Document the column wildcard with optional EXCLUDE list for column-level GRANT/REVOKE: tableName(* [EXCLUDE (col, ...)]). Covers the snapshot semantics (current columns only, unlike a table-level grant), the EXCLUDE rules, and the table-must-exist requirement. Updates the GRANT and REVOKE references and adds a discoverability note to the RBAC page. Co-Authored-By: Claude Opus 4.8 (1M context) --- documentation/query/sql/acl/grant.md | 81 ++++++++++++++++++++++++++- documentation/query/sql/acl/revoke.md | 41 +++++++++++++- documentation/security/rbac.md | 12 ++++ 3 files changed, 130 insertions(+), 4 deletions(-) diff --git a/documentation/query/sql/acl/grant.md b/documentation/query/sql/acl/grant.md index 2762deab5..3a2e17b45 100644 --- a/documentation/query/sql/acl/grant.md +++ b/documentation/query/sql/acl/grant.md @@ -30,13 +30,20 @@ GRANT permission [, permission ...] TO entityName ```questdb-sql title="Table or column scope" GRANT permission [, permission ...] ON { ALL TABLES - | tableName [(columnName [, columnName ...])] - [, tableName [(columnName [, columnName ...])] ...] } + | tableName [(columns)] [, tableName [(columns)] ...] } TO entityName [WITH GRANT OPTION] [WITH VERIFICATION]; ``` +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 ...])] } +``` + ## Description - `GRANT [permissions] TO entity` - grant database level permissions on database @@ -47,6 +54,8 @@ GRANT permission [, permission ...] permissions on table level to an entity - `GRANT [permissions] ON [table(columns)] TO entity` - grant column level permissions on column level to an entity +- `GRANT [permissions] ON [table(*)] TO entity` - grant column level permissions + on all of the table's current columns, with an optional `EXCLUDE` list ### Grant database level permissions @@ -116,6 +125,74 @@ GRANT SELECT ON orders(id, name), trades(id, quantity) TO john; | SELECT | orders | id | f | G | | SELECT | orders | name | f | G | +### Grant on all columns of a table + +Use the `*` wildcard to grant a column level permission on every column the table +currently has, without naming each one: + +```questdb-sql +CREATE TABLE trades ( + symbol SYMBOL, price DOUBLE, quantity DOUBLE, + counterparty SYMBOL, ts TIMESTAMP +) TIMESTAMP(ts); +GRANT SELECT ON trades(*) TO john; +``` + +| permission | table_name | column_name | grant_option | origin | +| ---------- | ---------- | ------------ | ------------ | ------ | +| SELECT | trades | symbol | f | G | +| SELECT | trades | price | f | G | +| SELECT | trades | quantity | f | G | +| SELECT | trades | counterparty | f | G | +| SELECT | trades | ts | f | G | + +The `*` is expanded to the table's columns at the time the statement runs, so the +result is a set of individual column level grants. + +:::note + +`GRANT SELECT ON trades(*)` is not the same as `GRANT SELECT ON trades`. The +wildcard grants only on the columns that exist at that moment and does not extend +to columns added later. A table level grant (without parentheses) covers all +current and future columns. Use the table level form when new columns should be +included automatically. + +::: + +#### Exclude columns from the wildcard + +Add an `EXCLUDE` list to grant on all columns except the ones you name. This is +useful when you want to expose most of a table but keep a few sensitive columns +private: + +```questdb-sql +GRANT SELECT ON trades(* EXCLUDE (counterparty)) TO john; +``` + +| permission | table_name | column_name | grant_option | origin | +| ---------- | ---------- | ----------- | ------------ | ------ | +| SELECT | trades | symbol | f | G | +| SELECT | trades | price | f | G | +| SELECT | trades | quantity | f | G | +| SELECT | trades | ts | f | G | + +`EXCLUDE` can only be used together with `*`. The following rules apply: + +- Every excluded column must exist, otherwise the statement fails. This catches + typos that would otherwise silently grant a column you meant to keep private. +- The `EXCLUDE` list cannot be empty and cannot cover every column of the table. +- The target table must exist. Unlike other grants, a wildcard grant cannot be + issued ahead of table creation. + +:::note + +Excluding the designated timestamp column does not necessarily hide it. If the +entity still holds `SELECT` or `UPDATE` on another column of the same table, the +timestamp keeps that permission as an +[implicit permission](#implicit-permissions) shown with origin `I`. + +::: + ### Grant option If the `WITH GRANT OPTION` clause is present, then the target entity is allowed diff --git a/documentation/query/sql/acl/revoke.md b/documentation/query/sql/acl/revoke.md index 5cd656f09..c302b6014 100644 --- a/documentation/query/sql/acl/revoke.md +++ b/documentation/query/sql/acl/revoke.md @@ -28,11 +28,18 @@ REVOKE permission [, permission ...] FROM entityName; ```questdb-sql title="Table or column scope" REVOKE permission [, permission ...] ON { ALL TABLES - | tableName [(columnName [, columnName ...])] - [, tableName [(columnName [, columnName ...])] ...] } + | 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 ...])] } +``` + ## Description - `REVOKE [permissions] FROM entity` - revoke database level permissions from an @@ -43,6 +50,9 @@ REVOKE permission [, permission ...] 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 database level permissions @@ -80,6 +90,33 @@ REVOKE SELECT ON orders, trades FROM john; 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). + ### Implicit permissions If the target table has implicit timestamp permissions, then revoking `SELECT` diff --git a/documentation/security/rbac.md b/documentation/security/rbac.md index b82e3eff7..9bb9897a0 100644 --- a/documentation/security/rbac.md +++ b/documentation/security/rbac.md @@ -88,6 +88,18 @@ Restrict users to see only certain columns: GRANT SELECT ON trades(ts, price) TO analyst; ``` +To grant on every column except a few, use the `*` wildcard with an `EXCLUDE` +list: + +```questdb-sql +-- User can see all columns except trader_id +GRANT SELECT ON trades(* EXCLUDE (trader_id)) TO analyst; +``` + +The wildcard covers the columns that exist when the statement runs, not columns +added later. See +[GRANT](/docs/query/sql/acl/grant/#grant-on-all-columns-of-a-table) for details. + ### Row-level access with views For row-level security, create a [view](/docs/concepts/views/) that filters rows,