Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions documentation/changelog.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ This page tracks significant updates to the QuestDB documentation.

### New

- [Audited views](/docs/security/audited-views/) - QuestDB Enterprise views created `WITH AUDIT` record every read in `sys.view_audit`, with the principal, the time, and the resolved values of the view's `AUDITED` parameters, covering the `params` JSON format, what counts as a read, audited views read through other audited views, the `AUDIT VIEW` permission, replication, and limitations, plus the [`view.audit.*` settings](/docs/configuration/audited-views/)
- [Declared value lists](/docs/query/sql/declare/#value-lists) - `DECLARE @symbols := ('BTC-USDT', 'ETH-USDT')` names the values of an `IN` filter once, including lists of bind variables and list parameters in views
- [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
Expand Down
35 changes: 35 additions & 0 deletions documentation/concepts/views.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,23 @@ CREATE VIEW mixed_params AS (
DECLARE @limit := 50 SELECT * FROM mixed_params
```

### List parameters

A parameter can hold a list of values for an `IN` filter. A caller can override
it with a list of any length:

```questdb-sql
CREATE VIEW trades_for AS (
DECLARE OVERRIDABLE @symbols := ('BTC-USDT', 'ETH-USDT')
SELECT timestamp, symbol, price FROM trades WHERE symbol IN @symbols
)

-- A list of one needs the trailing comma
DECLARE @symbols := ('SOL-USDT',) SELECT * FROM trades_for
```

See [value lists](/docs/query/sql/declare/#value-lists) for the rules.

## View hierarchies

Views can reference other views, tables, and materialized views:
Expand Down Expand Up @@ -442,6 +459,23 @@ GRANT SELECT ON desk_a_trades TO desk_a_users;
For more details on permissions, see
[Role-Based Access Control (RBAC)](/docs/security/rbac/).

### Audited views (Enterprise)

A view created `WITH AUDIT` records every read of it in the `sys.view_audit`
table: who read it, when, and the values its `AUDITED` parameters resolved to,
including a caller's overrides and bind variables. Use it to keep an audit
trail of access to sensitive data:

```questdb-sql
CREATE VIEW trades_by_symbol AS (
DECLARE OVERRIDABLE AUDITED @symbols := ('BTC-USDT', 'ETH-USDT')
SELECT timestamp, symbol, price, amount FROM trades WHERE symbol IN @symbols
) WITH AUDIT;
```

See [Audited views](/docs/security/audited-views/) for what a read records, the
audit table, permissions, and limitations.

## Performance considerations

### Views don't cache results
Expand Down Expand Up @@ -496,3 +530,4 @@ EXPLAIN SELECT * FROM my_view WHERE symbol = 'AAPL'
- [Materialized Views](/docs/concepts/materialized-views/): Incrementally maintained `SAMPLE BY` aggregates
- [Live views](/docs/concepts/live-views/): Incrementally maintained row-per-input window-function results
- [DECLARE](/docs/query/sql/declare/): Parameter declaration for views
- [Audited views](/docs/security/audited-views/): Record every read of a view (Enterprise)
51 changes: 51 additions & 0 deletions documentation/configuration/audited-views.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
---
title: Audited views
description: Configuration settings for audited views in QuestDB Enterprise.
---

:::note

Audited views are [Enterprise](/enterprise/) only.

:::

An audited view records each read of it in the `sys.view_audit` table. These
settings control the in-memory queue that carries rows from the reading query
to the background job that writes them, and the storage policy the table is
created with.

For details, see [Audited views](/docs/security/audited-views/).

## view.audit.queue.capacity

- **Default**: `4096`
- **Reloadable**: no

Number of audit rows the queue holds between the queries that read audited
views and the job that writes them to `sys.view_audit`. The value is rounded up
to a power of two, and the queue is allocated on the heap at startup.

Recording never makes a read wait. When the queue is full, the read still runs,
its row is dropped, and the server logs `view audit queue is full, dropping
rows`. Raise the capacity if that message appears during bursts of audited
reads. Auditing is lossy by design: see
[Delivery](/docs/security/audited-views/#delivery) for every case in which a
read goes unrecorded.

## view.audit.storage.policy

- **Default**: `TO PARQUET 1d`
- **Reloadable**: no

[Storage policy](/docs/concepts/storage-policy/) that `sys.view_audit` is
created with. The default converts each daily partition to Parquet one day
after the partition ends. An audit trail is append-only and read cold, and
every partition converts once whatever the threshold, so a longer one keeps
native files around without saving any work.

The setting applies only when the server creates the table at startup. It does
not change the policy of a table that already exists: use
[`ALTER TABLE SET STORAGE POLICY`](/docs/query/sql/alter-table-set-storage-policy/)
for that. Set the property to an empty value to create the table with no
storage policy. If the server rejects the policy, it logs the error and creates
the table without one.
1 change: 1 addition & 0 deletions documentation/configuration/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,7 @@ http.net.connection.sndbuf=2m

| Section | Description                                                                                  | Enterprise only |
|---------|-------------|:----------:|
| [Audited views](/docs/configuration/audited-views/) | Audit trail of view reads | ✓ |
| [Cairo engine](/docs/configuration/cairo-engine/) | SQL engine settings | |
| [Cold storage](/docs/configuration/cold-storage/) | Historical partitions on object storage | ✓ |
| [COPY settings](/docs/configuration/copy-settings/) | CSV import and Parquet export | |
Expand Down
1 change: 1 addition & 0 deletions documentation/query/sql/alter-view.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ ALTER VIEW trades_filtered AS (
- Dependent views may become invalid if the altered view's output changes
- Use `CREATE OR REPLACE VIEW` as an alternative if you want to create the view
when it doesn't exist
- An [audited view](/docs/security/audited-views/) stays audited (Enterprise)

### Definer permissions transfer (Enterprise)

Expand Down
26 changes: 26 additions & 0 deletions documentation/query/sql/create-view.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ documentation.

```questdb-sql
CREATE [ OR REPLACE ] VIEW [ IF NOT EXISTS ] view_name AS ( query )
[ WITH AUDIT ] [ OWNED BY owner_name ]
```

## Parameters
Expand All @@ -24,6 +25,10 @@ CREATE [ OR REPLACE ] VIEW [ IF NOT EXISTS ] view_name AS ( query )
| `OR REPLACE` | Replaces existing view or creates new one |
| `view_name` | Name of the view (case-insensitive, Unicode supported) |
| `query` | SELECT statement defining the view |
| `WITH AUDIT` | Enterprise only. Records every read of the view. See [WITH AUDIT](#with-audit-enterprise) |
| `OWNED BY` | Enterprise only. Assigns the view's owner. See [OWNED BY](#owned-by-enterprise) |

`WITH AUDIT` and `OWNED BY` may appear in either order.

## Examples

Expand Down Expand Up @@ -227,10 +232,31 @@ CREATE VIEW trades_summary AS (
OWNED BY 'analysts';
```

## WITH AUDIT (Enterprise)

`WITH AUDIT` makes the view an [audited view](/docs/security/audited-views/):
every read of it records a row in `sys.view_audit`, with the principal, the
time, and the resolved values of the variables the view declares `AUDITED`.

```questdb-sql title="Create an audited view"
CREATE VIEW trades_by_symbol AS (
DECLARE OVERRIDABLE AUDITED @symbols := ('BTC-USDT', 'ETH-USDT')
SELECT timestamp, symbol, price, amount
FROM trades
WHERE symbol IN @symbols
) WITH AUDIT;
```

Creating a view `WITH AUDIT` requires the `AUDIT VIEW` permission in addition
to `CREATE VIEW`. The view stays audited through `ALTER VIEW` and
`CREATE OR REPLACE VIEW`, which do not accept `WITH AUDIT` for an existing
view. To audit an existing view, drop it and create it again.

## See also

- [Views concept](/docs/concepts/views/)
- [ALTER VIEW](/docs/query/sql/alter-view/)
- [DROP VIEW](/docs/query/sql/drop-view/)
- [COMPILE VIEW](/docs/query/sql/compile-view/)
- [DECLARE](/docs/query/sql/declare/)
- [Audited views](/docs/security/audited-views/)
90 changes: 75 additions & 15 deletions documentation/query/sql/declare.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,32 @@ DECLARE @variable := expression [, @variable := expression ...]
SELECT ...
```

```questdb-sql title="Inside a view definition (with optional OVERRIDABLE)"
DECLARE [OVERRIDABLE] @variable := expression
[, [OVERRIDABLE] @variable := expression ...]
```questdb-sql title="Inside a view definition (with optional OVERRIDABLE and AUDITED)"
DECLARE [OVERRIDABLE] [AUDITED] @variable := expression
[, [OVERRIDABLE] [AUDITED] @variable := expression ...]
[WITH ...]
SELECT ...
```

```questdb-sql title="Value list, for the right-hand side of IN"
DECLARE @variable := ( value [, value ...] [,] )
```

The `OVERRIDABLE` keyword only takes effect inside a
[view definition](/docs/query/sql/create-view/#declare-with-overridable). It
marks a variable as a parameter that the caller of the view can override at
query time. Variables without `OVERRIDABLE` use the value set in the view and
cannot be changed by the caller.

The `AUDITED` keyword only takes effect inside the definition of an
[audited view](/docs/security/audited-views/), a QuestDB Enterprise feature. It
marks a variable whose resolved value each read of the view records in the
audit trail. It is independent of `OVERRIDABLE`, and the two may appear in
either order. Elsewhere it is accepted and has no effect.

A [value list](#value-lists) declares the set of values an `IN` filter tests
against.

## Mechanics

The `DECLARE` keyword comes before the `SELECT` clause in your query:
Expand Down Expand Up @@ -190,6 +203,65 @@ FROM second;
| 10 | 9 |


### Value lists

A parenthesised, comma-separated list declares the values of an `IN` filter
once, so that a query or a view can name the set instead of spelling it out:

```questdb-sql title="Declare the values of an IN filter"
DECLARE @symbols := ('BTC-USDT', 'ETH-USDT')
SELECT timestamp, symbol, price, amount
FROM trades
WHERE symbol IN @symbols AND timestamp IN '$now-1h..$now';
```

The list is expanded into the `IN` when the query is parsed, so the query above
is exactly `symbol IN ('BTC-USDT', 'ETH-USDT')`. Each member keeps its own type,
and every form of `IN` works as it does with a written-out list, including
`NOT IN` and interval scans on the designated timestamp.

- `IN @symbols` and `IN (@symbols)` are equivalent.
- A list mixes with literals: `symbol IN ('SOL-USDT', @symbols)`.
- One list variable can be assigned to another: `@majors := @symbols`.
- A list of one needs a trailing comma, `('BTC-USDT',)`. Without it,
`('BTC-USDT')` is a parenthesised value. A trailing comma is also accepted
after the last member of a longer list.
- A bracketed sub-query, `(SELECT ...)`, is a sub-query and not a list.

Members can be bind variables, which lets one prepared statement filter on a
different set of values each time:

```questdb-sql title="A list of bind variables"
DECLARE @symbols := ($1, $2)
SELECT timestamp, symbol, price FROM trades WHERE symbol IN @symbols;
```

In a [view](/docs/concepts/views/#parameterized-views), an `OVERRIDABLE` list
can be overridden with a list of a different length:

```questdb-sql title="A list parameter in a view"
CREATE VIEW trades_for AS (
DECLARE OVERRIDABLE @symbols := ('BTC-USDT', 'ETH-USDT')
SELECT timestamp, symbol, price FROM trades WHERE symbol IN @symbols
);

DECLARE @symbols := ('SOL-USDT',) SELECT * FROM trades_for;
```

A list has no value of its own, so it can only be used on the right-hand side
of `IN`. These fail:

| Query | Error |
| ------------------------------------------------------ | --------------------------------------------------------- |
| `SELECT @symbols`, `WHERE symbol = @symbols` | `declared list can only be used on the right-hand side of IN` |
| `OVER (PARTITION BY @symbols)` | `declared list can only be used on the right-hand side of IN` |
| `@all := (@symbols, 'SOL-USDT')` | `declared list can only be used on the right-hand side of IN` |
| `@x := ('BTC-USDT', ('ETH-USDT', 'SOL-USDT'))` | `nested lists are not supported` |
| `@x := ()` | `value expected in list` |

To combine a list with more values, write both in the `IN`:
`symbol IN (@symbols, 'SOL-USDT')`.

### Bind variables

`DECLARE` syntax will work with prepared statements over PG Wire, so long as the client library
Expand Down Expand Up @@ -241,18 +313,6 @@ how many places you need to update the constant.

However, not all expressions are supported. The following are explicitly disallowed:

#### Bracket lists

```questdb-sql title="bracket lists are not allowed"
DECLARE
@symbols := ('BTC-USDT', 'ETH-USDT')
SELECT timestamp, price, symbol
FROM trades
WHERE symbol IN @symbols;

-- error: unexpected bind expression - bracket lists not supported
```

#### SQL statement fragments

```questdb-sql title="sql fragments are not allowed"
Expand Down
4 changes: 4 additions & 0 deletions documentation/query/sql/drop-view.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ GRANT DROP VIEW ON view1, view2 TO username;
When a user creates a view, they are automatically granted all permissions
including `DROP VIEW` on that view.

Dropping an [audited view](/docs/security/audited-views/) also requires the
database-level `AUDIT VIEW` permission, because dropping is the only way a view
stops being audited.

## See also

- [Views concept](/docs/concepts/views/)
Expand Down
Loading
Loading