diff --git a/documentation/concepts/live-views.md b/documentation/concepts/live-views.md new file mode 100644 index 000000000..5d49e26e4 --- /dev/null +++ b/documentation/concepts/live-views.md @@ -0,0 +1,416 @@ +--- +title: Live views +sidebar_label: Live views +description: + Live views incrementally maintain window-function results over a base table so + that running totals, moving averages, and rankings can be read like a regular + table without recomputing on every query. +--- + +A live view is a QuestDB table that stores the incrementally maintained result +of a window-function query over a single base table. As new rows arrive in the +base table, the window functions run once per new row and the output is appended +to the view. Querying the live view then scans precomputed rows instead of +reprocessing the base table on every read. + +Live views target workloads where the same window aggregate is read frequently +against high-rate ingestion: rolling VWAP, cumulative volume, running ranks, or +day-over-day comparisons that would otherwise recompute a window over millions +of rows on each query. + +:::note + +Live views are a new feature. The supported SQL surface is deliberately narrow +in this first version. See [Limitations](#limitations) for the shapes that are +rejected at creation time. + +::: + +## Live views vs materialized views + +Both feature types pre-compute a query and refresh it incrementally, but they +serve different query shapes: + +| Aspect | Live view | [Materialized view](/docs/concepts/materialized-views/) | +| ------ | --------- | --------------------- | +| Query shape | Window functions (`OVER`) | `SAMPLE BY` / time-based `GROUP BY` | +| Output cardinality | One row per base row | One row per time bucket | +| Typical use | Running totals, moving averages, rankings | OHLC bars, downsampled summaries | +| Base tables | A single WAL-backed table | One or more tables (JOINs allowed) | +| Freshness / durability | `FLUSH EVERY`, `IN MEMORY` | `REFRESH` strategy | + +Use a materialized view when you want to aggregate rows into time buckets. Use a +live view when you want to keep a row-per-input result of a window computation. + +## Quick example + +Given a `trades` table of incoming trades: + +```questdb-sql title="Base table" +CREATE TABLE trades ( + symbol SYMBOL, + side SYMBOL, + price DOUBLE, + amount DOUBLE, + timestamp TIMESTAMP +) TIMESTAMP(timestamp) PARTITION BY DAY WAL; +``` + +Create a live view that keeps a 300-row moving average of price per symbol: + +```questdb-sql title="Live view with a moving average" +CREATE LIVE VIEW trades_ma +FLUSH EVERY 1s +IN MEMORY 5s +START FROM NOW +AS +SELECT + timestamp, + symbol, + price, + avg(price) OVER ( + PARTITION BY symbol + ORDER BY timestamp + ROWS 300 PRECEDING + ) AS moving_avg +FROM trades; +``` + +Query it like any table: + +```questdb-sql title="Query the live view" +SELECT * FROM trades_ma; +``` + +The view updates incrementally as new rows arrive in `trades`. Each new trade +produces one output row carrying its moving average. A direct `SELECT` of the +full output rows sees data as soon as it is refreshed. Filtering a read to a +timestamp interval (for example `WHERE timestamp IN '$today'`) is served from the +disk tier and can trail by up to one `FLUSH EVERY` interval; see +[Freshness](#freshness). + +## How live views work + +A live view is its own WAL-backed table maintained by a background refresh +worker. The worker reads new committed rows from the base table and runs the +view's window functions over them, appending the output. Two independent +cadences govern how that output becomes visible and durable: + +- **Refresh** runs continuously. As the worker computes output rows, it appends + them to an in-memory tier. This is what keeps the view fresh. +- **Flush** runs on the `FLUSH EVERY` cadence. It persists the in-memory rows to + the live view's own WAL-backed disk tier and advances a durability checkpoint. + +Reads combine both tiers. Recent rows are served from the in-memory tier and the +older prefix from disk, so a query sees the freshest computed rows without +waiting for a flush. + +```questdb-sql title="Show the live view definition" +SHOW CREATE LIVE VIEW trades_ma; +``` + +### Freshness + +Because refresh publishes to the in-memory tier ahead of flush, a direct +`SELECT` that reads the full output rows sees data as soon as it is refreshed. +This is independent of `FLUSH EVERY`, which is a durability and +write-amplification control, not a freshness control. + +Some read shapes are served from the disk tier only and therefore trail by up to +one `FLUSH EVERY` interval: + +- Reads that project or aggregate the view's columns rather than reading full + output rows +- Reads filtered to a timestamp interval +- A live view used as the right-hand side of an [`ASOF JOIN`](/docs/query/sql/asof-join/) + +Keep `FLUSH EVERY` small (for example `1s`) so this lag stays negligible. + +:::tip + +A live view falling behind sustained ingestion stays correct but grows stale. +There is no automatic throttle. See [Monitoring](#monitoring) for how to +interpret lag and detect a view that cannot keep up. + +::: + +## Supported window functions + +Live views maintain window functions whose result can be computed incrementally +in a single forward pass and whose state can be checkpointed: + +- **Anchored ranking**: `row_number`, `rank`, `dense_rank` +- **Bounded or anchored aggregates**: `sum`, `avg`, `count`, `min`, `max`, + `ksum`, `first_value`, `last_value`, `nth_value` +- **Offset**: `lag` +- **Statistics**: `variance`, `stddev`, covariance, correlation, EMA, and VWEMA + +Stateful window functions must have a `PARTITION BY` clause. Both bounded `ROWS` +and bounded `RANGE` frames are supported. Ranking functions must use an anchored +window because an out-of-order row would otherwise change every later rank. + +String, `VARCHAR`, `BINARY`, `ARRAY`, and `SYMBOL` columns can appear as +pass-through output columns and as `count` arguments, but there are no string- +or array-valued window functions. + +The following shapes cannot be maintained by an append-only incremental refresh +and are rejected at creation time: + +- Multi-pass or look-ahead functions: `percent_rank`, `cume_dist`, `ntile`, + `lead` +- Stateful window functions without `PARTITION BY` +- Unanchored ranking functions +- Frames that start at `UNBOUNDED PRECEDING`, except stateless `last_value` + shapes + +## Anchored windows + +An anchored window resets its cumulative aggregate on a boundary, which is +useful for running totals that restart each day or on a period boundary. Declare +it in a named window with either `ANCHOR DAILY 'HH:MM' ['timezone']` or an +`ANCHOR EXPRESSION` clause: + +```questdb-sql title="Cumulative daily volume per symbol" +CREATE LIVE VIEW trades_daily_volume +FLUSH EVERY 1s +START FROM NOW +AS +SELECT + timestamp, + symbol, + sum(amount) OVER w AS cumulative_volume +FROM trades +WINDOW w AS ( + PARTITION BY symbol + ORDER BY timestamp + ANCHOR DAILY '00:00' +); +``` + +`ANCHOR DAILY` resets at the specified wall-clock time. Without a time zone, the +time is interpreted in UTC; add an IANA time zone such as `'America/New_York'` +when the boundary follows local civil time. + +An anchored window must be a named window, must partition by base-table columns, +must order by the designated timestamp ascending, and cannot use a bounded +frame. A live view supports at most one anchored window. An anchor expression +must be deterministic and return `TIMESTAMP`, `LONG`, or `INT`. + +## Start boundary and historical data + +Every live view has a mandatory `START FROM` clause that defines an event-time +lower bound for its rows: + +- `START FROM NOW` resolves to the creation time. +- `START FROM BEGINNING` includes all base-table history. +- `START FROM 'timestamp'` includes rows at or after the specified timestamp. + +The boundary is inclusive and is evaluated against the base table's designated +timestamp, not commit time. If qualifying rows already exist when the view is +created, QuestDB seeds them before switching to continuous refresh. + +```questdb-sql title="Include all existing history" +CREATE LIVE VIEW trades_ma +FLUSH EVERY 1s +START FROM BEGINNING +AS +SELECT + timestamp, + symbol, + avg(price) OVER (PARTITION BY symbol ORDER BY timestamp ROWS 300 PRECEDING) + AS moving_avg +FROM trades; +``` + +The seed sweep is resumable: it checkpoints its progress and continues after a +restart. `START FROM NOW` can still seed existing future-dated rows whose +designated timestamps are at or above the resolved creation-time boundary. + +## Base table lifecycle + +A live view is tied to a single WAL-backed base table and tracks the exact set of +base columns its query references: + +- Changes to columns the view does not reference pass through transparently and + the view keeps refreshing. +- Dropping, renaming, or changing the type of a referenced column invalidates the + view. +- Renaming or dropping the base table invalidates the view. +- `DROP PARTITION`, `TRUNCATE`, and base TTL eviction freeze the already-emitted + rows and the view continues forward from where it was. + +An invalidated view keeps serving its existing data and reports the reason in +[`live_views()`](/docs/query/functions/meta/#live_views). It stops refreshing. +Invalidation is permanent: reversing the schema change does not automatically +revalidate the view, and `ALTER LIVE VIEW ... RESUME WAL` only recovers a +suspended WAL writer. + +To recover, inspect `invalidation_reason`, repair the base-table schema, and +save the definition before dropping the view: + +```questdb-sql +SHOW CREATE LIVE VIEW trades_ma; +``` + +Then drop and recreate the live view. Use `START FROM BEGINNING` or an explicit +timestamp if it must include history that is still present in the base table. +Dropping the invalid view removes its materialized rows, including rows whose +source history is no longer retained by the base table. + +Live views over [deduplicated](/docs/concepts/deduplication/) base tables are +supported. A keep-last `UPSERT` replacement at an earlier timestamp is reflected +in the view. A view over a deduplicated base is one `FLUSH EVERY` cycle behind +rather than sub-cycle fresh, because its refresh is coupled to base apply. + +## Monitoring + +The [`live_views()`](/docs/query/functions/meta/#live_views) function exposes +the state, refresh lag, in-memory footprint, and seed progress of every live +view: + +```questdb-sql title="List all live views" +SELECT view_name, base_table_name, view_status, lag_seqtxn, lag_micros +FROM live_views(); +``` + +`lag_seqtxn` is the number of committed base-table WAL transactions beyond the +view's durable processed watermark. It counts transactions, not rows: one +transaction may contain one row or millions. A value of `0` means that the +durable tier is caught up. A temporary non-zero value is expected between +`FLUSH EVERY` cycles, especially when ingestion produces many small commits. + +There is no universal acceptable non-zero value. Sample the metric over time and +compare it with the view's normal flush-cycle baseline. A bounded sawtooth that +returns to zero around flushes is normal. A value that stays elevated for +multiple flush intervals or keeps increasing indicates that the view cannot keep +up. + +`lag_micros` reports the elapsed time since the last successful flush. It is a +flush-activity indicator, not the timestamp difference between base and view +rows, and can continue growing while an idle view has `lag_seqtxn = 0`. + +When lag grows persistently, check `view_status` and `writer_stall_micros` for a +failed or blocked flush. Also verify CPU and I/O capacity, reduce the number or +cost of maintained views, or increase the shared +[`mat.view.refresh.worker.count`](/docs/configuration/materialized-views/) +setting. + +For out-of-order repair cost, compare `o3_replay_scan_rows`, +`o3_resume_replay_rows`, and `o3_boundary_replay_rows`. The first counts base +rows scanned; the other two split rows emitted by the resume-from-checkpoint and +rebuild paths. `checkpoint_repair_plan` reports whether the view has a finite +`range`, `rows`, or `anchor` repair plan. The last disposition and denial +columns show which path actually ran and why a local rebuild was not selected. +These counters reset on restart. + +The `checkpoint_timeline_*` columns describe the persistent timeline used for +restart and out-of-order repair. In particular, +`checkpoint_timeline_sharing_ratio` shows how effectively checkpoint state is +shared, while `checkpoint_gc_lag_generations` and +`checkpoint_obsolete_segment_bytes` can expose delayed cleanup. See +[`live_views()`](/docs/query/functions/meta/#live_views) for the complete +catalog. + +Live views also appear in [`tables()`](/docs/query/functions/meta/#tables) with +`table_type = 'L'`, and are recognized by `SHOW CREATE LIVE VIEW`, `EXPLAIN`, +`pg_class`, and `information_schema.tables`. + +## Limitations + +Live views have a deliberately narrow surface in this first version. Statements +outside it are rejected at creation time with a specific error: + +| Base object | Derived object | Supported | +| ----------------- | ----------------- | ------------------------------------- | +| Live view | Regular view | Yes | +| Live view | Materialized view | No | +| Live view | Live view | No | +| Regular view | Live view | No; a regular view is not a WAL table | +| Materialized view | Live view | Yes | + +A full rebuild of a materialized view invalidates a live view that uses it as +its base. Recreate the live view after the rebuild, following the recovery steps +in [Base table lifecycle](#base-table-lifecycle). + +- **Single base table only.** No JOINs, subqueries, or CTEs in the view query. +- **Explicit output columns only.** `SELECT *` and other wildcard projections + are rejected because the output schema is fixed when the view is created. +- **No pre-aggregation.** `SAMPLE BY` and `GROUP BY` are not allowed between the + base table and the window functions. A view like "5-minute candles with a + rolling VWAP" must pre-aggregate upstream. +- **No designated-timestamp filter.** A `WHERE` clause may filter other columns, + but cannot filter the base table's designated timestamp yet. +- **Deterministic queries only.** Non-deterministic functions such as `now()`, + `sysdate()`, `systimestamp()`, and `rnd_*()` are rejected in the projection, + the `WHERE` filter, and window-function arguments. +- **No TTL on the view.** Live-view disk growth is unbounded in this version. + Size retention on the base table instead. + +## Tradeoffs + +- **Storage grows with output.** The computed rows are stored on the live view's + disk tier in addition to the base table's rows. For wide projections or long + retention the view's footprint can exceed the base table. +- **No admission control.** A view that cannot keep up with ingestion stays + correct but stale, with no automatic throttle or drop. +- **Per-partition state for partitioned windows grows with distinct partition + cardinality.** A base table with high-cardinality partition keys (UUIDs, + session ids) holds state per key. Use + [`cairo.live.view.refresh.memory.limit.bytes`](/docs/configuration/live-views/#cairoliveviewrefreshmemorylimitbytes) + to bound the per-view refresh footprint. The `in_mem_bytes` column in + [`live_views()`](/docs/query/functions/meta/#live_views) separately reports + the peak-sticky capacity of the recent-row tier. + +## Enterprise features + +QuestDB Enterprise adds access control, replication, and backup support for live +views. + +### Permissions + +Two dedicated permissions govern live-view DDL, modelled on the materialized-view +permissions: + +- `CREATE LIVE VIEW` is a database-level permission. +- `DROP LIVE VIEW` is checked against the target view. + +Querying a live view uses the standard table-level `SELECT` permission, since a +live view is a regular table token. See +[Role-based access control](/docs/security/rbac/) for the full permission model. + +### Replication + +Live-view definitions replicate, but their derived rows do not. Every node +refreshes and flushes its own node-local live-view table: + +- The primary refreshes directly from the base table's WAL. +- A read-only replica refreshes from its locally applied copy of the base table. +- Live-view WAL is not uploaded or transferred between nodes. + +A role switch continues the local refresh state; it does not reconstruct or +transfer the former primary's live-view rows. Replica freshness therefore also +depends on base-table replication and apply lag. + +### Backup and restore + +A live view is captured by the object-store backup like a materialized view: its +table data rides the standard table path and its definition sidecars are carried +in the backup manifest. After restore, unflushed rows are re-derived from the +base table. + +## Related documentation + +- **SQL commands** + - [`CREATE LIVE VIEW`](/docs/query/sql/create-live-view/): Create a live view + - [`DROP LIVE VIEW`](/docs/query/sql/drop-live-view/): Remove a live view + +- **Related concepts** + - [Materialized views](/docs/concepts/materialized-views/): Incrementally + maintained `SAMPLE BY` aggregates + - [Views](/docs/concepts/views/): Virtual tables computed at query time + - [Window functions](/docs/query/functions/window-functions/overview/): The `OVER` functions a + live view maintains + +- **Configuration** + - [Live views configs](/docs/configuration/live-views/): Server configuration + options for live views diff --git a/documentation/concepts/materialized-views.md b/documentation/concepts/materialized-views.md index a0722c58b..cf7ed4ac2 100644 --- a/documentation/concepts/materialized-views.md +++ b/documentation/concepts/materialized-views.md @@ -97,6 +97,10 @@ Materialized views are ideal for: - **Historical summaries**: Data that doesn't need real-time accuracy - **OHLC calculations**: Candlestick charts, time-bucketed analytics +Use a [live view](/docs/concepts/live-views/) instead when you need to +incrementally maintain a row-per-input window computation, such as a moving +average, running total, or ranking. + Use regular [views](/docs/concepts/views/) instead when: - Query execution cost is acceptable for your workload @@ -571,6 +575,8 @@ the replica's view was not fully up-to-date. - **Related Concepts** - [Views](/docs/concepts/views/): Virtual tables that compute results at query time + - [Live views](/docs/concepts/live-views/): Incrementally maintained + row-per-input window-function results - **SQL Commands** diff --git a/documentation/concepts/views.md b/documentation/concepts/views.md index 0995c637c..fdb136ae0 100644 --- a/documentation/concepts/views.md +++ b/documentation/concepts/views.md @@ -313,6 +313,7 @@ SELECT table_name, table_type FROM tables() | `T` | Regular table | | `V` | View | | `M` | Materialized view | +| `L` | [Live view](/docs/concepts/live-views/) | ## Views vs materialized views diff --git a/documentation/configuration/live-views.md b/documentation/configuration/live-views.md new file mode 100644 index 000000000..204de6751 --- /dev/null +++ b/documentation/configuration/live-views.md @@ -0,0 +1,162 @@ +--- +title: Live views +description: Configuration settings for live views in QuestDB. +--- + +These settings control live view SQL support and the background refresh job that +maintains live views incrementally. For a conceptual overview, see +[Live views](/docs/concepts/live-views/). + +Live view refresh shares the materialized view refresh worker pool, so the +worker-pool settings under +[Materialized views](/docs/configuration/materialized-views/) +(`mat.view.refresh.worker.count`, `.affinity`, `.haltOnError`) also govern live +view refresh. There are no dedicated live-view worker-pool properties. + +## cairo.live.view.checkpoint.compaction.interval + +- **Default**: `0` (disabled) +- **Reloadable**: no + +Number of checkpoint seals between physical compaction attempts. Compaction +repacks live state pages from sparse data segments so obsolete segments can be +reclaimed. A value of `0` disables compaction. + +## cairo.live.view.checkpoint.max.duration.micros + +- **Default**: `300000000` (5 minutes) +- **Reloadable**: no + +Maximum wall-clock interval, in microseconds, between head-checkpoint writes. +This caps restart and out-of-order replay work for low-rate views that do not +reach the row-count trigger. + +## cairo.live.view.checkpoint.repair.replay.max.rows + +- **Default**: `1000000` +- **Reloadable**: no + +Maximum base rows a localized out-of-order repair replays in one refresh turn. A +larger repair pauses at a timestamp-group boundary and continues in a later turn +without publishing partial output. The refresh-turn duration limit also applies. +A value of `0` or less disables this row budget. + +## cairo.live.view.checkpoint.repair.scan.max.keys + +- **Default**: `100000` +- **Reloadable**: no + +Maximum partition keys inspected while planning a localized `ROWS` repair. If +planning crosses the limit, QuestDB uses an unlocalized repair instead. A value +of `0` or less disables this key budget. + +## cairo.live.view.checkpoint.repair.scan.max.rows + +- **Default**: `1000000` +- **Reloadable**: no + +Maximum base rows scanned while discovering the bounds of a localized `ROWS` +repair. This includes rows later discarded by the view's `WHERE` filter. If the +limit is crossed, QuestDB uses the conservative unlocalized bound. A value of +`0` or less disables this scan budget. + +## cairo.live.view.checkpoint.rows + +- **Default**: `1000000` +- **Reloadable**: no + +Number of newly flushed rows after which the refresh worker writes a head +checkpoint. Smaller values shorten restart replay at the cost of more checkpoint +writes. + +## cairo.live.view.enabled + +- **Default**: `true` +- **Reloadable**: no + +Enables or disables SQL support and the refresh job for live views. When +disabled, `CREATE LIVE VIEW` fails with `live views are disabled`. + +## cairo.live.view.flush.retry.max + +- **Default**: `5` +- **Reloadable**: no + +Maximum number of consecutive flush attempts before a view is marked invalid. A +flush persists the in-memory rows to the view's disk tier. + +## cairo.live.view.flush.retry.max.duration.micros + +- **Default**: `60000000` (60 seconds) +- **Reloadable**: no + +Maximum total time, in microseconds, spent retrying a stalled flush before the +view is marked invalid. + +## cairo.live.view.in.memory.buffer.growth.bytes + +- **Default**: `16777216` (16 MiB) +- **Reloadable**: no + +Fast-path growth budget for the in-memory tier. Once the published buffer's +footprint reaches this size, refresh falls back to a swap that evicts expired +rows and may shrink the buffer instead of continuing to append in place. Raise +it for `IN MEMORY` windows expected to exceed the default. A value of `0` or +less forces this compaction path on every publish. Accepts a size suffix such as +`16M`. + +## cairo.live.view.in.memory.buffer.initial.bytes + +- **Default**: `65536` (64 KiB) +- **Reloadable**: no + +Initial size of a live view's in-memory tier buffer. Accepts a size suffix such +as `64K`. + +## cairo.live.view.in.memory.max + +- **Default**: `3600000000` (60 minutes) +- **Reloadable**: no + +Upper bound on the `IN MEMORY` retention window. A `CREATE LIVE VIEW` whose +`IN MEMORY` (or defaulted `FLUSH EVERY`) exceeds this value is rejected. + +## cairo.live.view.partition.compact.threshold + +- **Default**: `100000` +- **Reloadable**: no + +Anchor-map tombstone threshold that triggers compaction. Compaction also runs +when tombstones exceed half of the anchor map, regardless of this absolute +threshold. + +## cairo.live.view.refresh.memory.limit.bytes + +- **Default**: `0` (unlimited) +- **Reloadable**: yes + +Per-live-view limit on the peak memory used during a refresh cycle. It includes +persistent window state, the `IN MEMORY` recent-row tier, staging memory, and +transient buffers such as Parquet row-group decode buffers. Exceeding the limit +invalidates the view immediately; recovery requires dropping and recreating it. + +Size the limit above the refresh workload's allocation floor. In particular, a +bounded `ROWS` frame allocates at least one `cairo.sql.window.store.page.size` +page (1 MiB by default), and a Parquet-backed refresh may decode a complete row +group. + +## cairo.live.view.refresh.turn.max.commits + +- **Default**: `64` +- **Reloadable**: no + +Maximum number of base-table commits a refresh worker processes in a single turn +before yielding to other views. + +## cairo.live.view.refresh.turn.max.duration.micros + +- **Default**: `50000` (50 milliseconds) +- **Reloadable**: no + +Maximum wall-clock time, in microseconds, a refresh worker spends on one view +per turn before yielding. diff --git a/documentation/configuration/overview.md b/documentation/configuration/overview.md index 5d2aa018a..f9b9d0f93 100644 --- a/documentation/configuration/overview.md +++ b/documentation/configuration/overview.md @@ -531,6 +531,7 @@ http.net.connection.sndbuf=2m | [HTTP server](/docs/configuration/http-server/) | Web Console and REST API | | | [IAM](/docs/configuration/iam/) | Identity and Access Management | ✓ | | [Ingestion (ILP/HTTP)](/docs/configuration/ingestion/) | InfluxDB Line Protocol settings | | +| [Live views](/docs/configuration/live-views/) | Live view refresh settings | | | [Logging & Metrics](/docs/configuration/logging-metrics/) | Log levels and metrics | | | [Materialized views](/docs/configuration/materialized-views/) | Materialized view refresh settings | | | [Minimal HTTP server](/docs/configuration/http-min-server/) | Health check and metrics endpoint | | diff --git a/documentation/operations/backup.md b/documentation/operations/backup.md index 5af15cacc..e21d5b3f7 100644 --- a/documentation/operations/backup.md +++ b/documentation/operations/backup.md @@ -362,7 +362,7 @@ primary/replica backups below). - **Database-wide only**: Backup captures the entire database. You cannot exclude tables or backup selected tables individually. Every backup includes - all user tables, materialized views, and metadata. + all user tables, materialized views, live views, and metadata. - **One backup at a time**: Only one backup can run at any given time. Starting a new backup while one is running will return an error. - **Primary and replica backups are separate**: Each QuestDB instance has its diff --git a/documentation/query/functions/meta.md b/documentation/query/functions/meta.md index ada4004dd..edd667c5c 100644 --- a/documentation/query/functions/meta.md +++ b/documentation/query/functions/meta.md @@ -134,6 +134,110 @@ If you want to re-read metadata for all user tables, simply use an asterisk: SELECT hydrate_table_metadata('*'); ``` +## live_views + +`live_views()` returns the list of all [live views](/docs/concepts/live-views/) +in the database, along with their status, refresh lag, in-memory footprint, and +seed progress. + +**Arguments:** + +- `live_views()` does not require arguments. + +**Return value:** + +Returns a `table` with the following columns: + +| Column | Type | Description | +| ---------------------------------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------- | +| `view_name` | STRING | Live view name | +| `view_table_dir_name` | STRING | View directory name on disk | +| `base_table_name` | STRING | Base table name | +| `view_sql` | STRING | Query used to maintain the view | +| `view_status` | STRING | Lifecycle status: `creating`, `active`, `seeding`, `invalid`, `dropping`, `version_unsupported`, or `state_unreadable` | +| `invalidation_reason` | STRING | Message explaining why the view was marked invalid | +| `flush_every_interval` | LONG | `FLUSH EVERY` interval value | +| `flush_every_interval_unit` | STRING | `FLUSH EVERY` unit: `MILLISECOND`, `SECOND`, `MINUTE`, `HOUR`, or `DAY` | +| `in_memory_interval` | LONG | `IN MEMORY` interval value | +| `in_memory_interval_unit` | STRING | `IN MEMORY` unit: `MILLISECOND`, `SECOND`, `MINUTE`, `HOUR`, or `DAY` | +| `in_mem_bytes` | LONG | Native capacity of the in-memory tier; a peak-sticky high-water mark | +| `in_mem_rows` | LONG | Live row count in the published in-memory tier | +| `o3_rejected_count` | LONG | Late out-of-order rows rejected below the view lower bound; resets on restart | +| `below_lower_bound_count` | LONG | In-order rows dropped below the view lower bound; resets on restart | +| `lag_seqtxn` | LONG | Committed base WAL transactions beyond `last_processed_seqtxn` | +| `lag_micros` | LONG | Microseconds since the last successful flush | +| `last_processed_seqtxn` | LONG | Last base transaction processed by the refresh worker | +| `applied_watermark` | LONG | Last base transaction durably applied to the view's disk tier | +| `lv_consumed_seqtxn` | LONG | Base WAL purge floor held by this view | +| `view_lower_bound_timestamp` | TIMESTAMP | Resolved `START FROM` boundary; `NULL` for `BEGINNING` | +| `writer_stall_micros` | LONG | Current uninterrupted flush-writer stall duration, or `0` | +| `seed_target_seqtxn` | LONG | Target base transaction during initial seeding; otherwise `NULL` | +| `o3_resume_replay_rows` | LONG | Rows emitted by repairs resumed from a checkpoint; resets on restart | +| `o3_boundary_replay_rows` | LONG | Rows emitted by rebuild repairs; resets on restart | +| `o3_replay_scan_rows` | LONG | Base rows scanned by both repair paths; resets on restart | +| `checkpoint_timeline_generation` | LONG | Current published checkpoint-timeline generation | +| `checkpoint_timeline_entries` | LONG | Checkpoint roots in the current generation | +| `checkpoint_timeline_normalized_base_seqtxn` | LONG | Base transaction through which the timeline is normalized | +| `checkpoint_timeline_logical_bytes` | LONG | Bytes the roots would use as independent complete state images | +| `checkpoint_timeline_physical_bytes` | LONG | Bytes physically stored for the current timeline generation | +| `checkpoint_timeline_shared_bytes` | LONG | Logical bytes avoided through state sharing | +| `checkpoint_timeline_sharing_ratio` | DOUBLE | Shared bytes divided by logical bytes | +| `checkpoint_timeline_row_position_delta_bytes` | LONG | Bytes used by the row-position delta index | +| `checkpoint_data_segment_count` | LONG | Data segments found by the latest checkpoint purge sweep | +| `checkpoint_obsolete_segment_bytes` | LONG | Obsolete segment bytes found by the latest purge sweep | +| `checkpoint_oldest_pinned_generation` | LONG | Oldest checkpoint generation still retained | +| `checkpoint_gc_lag_generations` | LONG | Generations between the current and oldest retained generation | +| `checkpoint_last_write_micros` | LONG | Duration of the latest checkpoint write | +| `checkpoint_last_restore_micros` | LONG | Duration of the latest checkpoint restore | +| `checkpoint_last_write_new_bytes` | LONG | New bytes written by the latest checkpoint publication | +| `checkpoint_last_lookup_depth` | LONG | Metadata-tree depth of the latest checkpoint lookup | +| `checkpoint_repair_in_progress` | BOOLEAN | Whether a localized repair is suspended across refresh turns | +| `checkpoint_repair_correction_timestamp` | TIMESTAMP | Earliest timestamp whose output may have changed in the active repair | +| `checkpoint_repair_low_timestamp` | TIMESTAMP | Inclusive base timestamp from which the active repair scans | +| `checkpoint_repair_high_timestamp` | TIMESTAMP | Repair convergence boundary; `NULL` when it runs to end of data | +| `checkpoint_repair_roots_versioned` | LONG | Checkpoint roots versioned by repairs; resets on restart | +| `checkpoint_repair_new_bytes` | LONG | Bytes written by repairs; resets on restart | +| `checkpoint_repair_resumes` | LONG | Times a repair resumed in a later refresh turn; resets on restart | +| `checkpoint_repair_failures` | LONG | Repair failures; resets on restart | +| `checkpoint_repair_plan` | STRING | Available localized plan: `range`, `rows`, `anchor`, a `+` combination, `none`, or `NULL` before compilation | +| `checkpoint_repair_last_disposition` | STRING | Last executor used: `localized rebuild`, `boundary rebuild`, or `resume from anchor` | +| `checkpoint_repair_last_denial` | STRING | Reason the last repair did not use a localized rebuild; otherwise `NULL` | + +The `in_mem_bytes` and `in_mem_rows` columns are complementary. `in_mem_bytes` +is the peak-sticky arena footprint that does not shrink after a burst, while +`in_mem_rows` is the live row count that drops as rows age out of the +`IN MEMORY` window. Together they distinguish a view actively buffering rows +from one holding capacity retained from a past burst. + +`lag_seqtxn` counts transactions, not rows. A value of `0` means the durable +tier is caught up; a temporary non-zero value is expected between `FLUSH EVERY` +cycles. Alert on a value that remains above its normal flush-cycle baseline or +keeps increasing across samples rather than on a universal fixed threshold. +`lag_micros` measures flush activity and may grow while an idle view has +`lag_seqtxn = 0`. + +The checkpoint columns describe the current persistent timeline, its storage and +lookup cost, and out-of-order repair activity. Timeline fields are `NULL` before +the first generation is published. The data-segment and obsolete-byte fields +reflect the latest purge sweep and remain `NULL` until a sweep has run. +`checkpoint_repair_plan` describes the query's available repair bounds, while +the disposition and denial columns report what the most recent repair actually +did. + +For operational guidance, see +[Monitoring live views](/docs/concepts/live-views/#monitoring). + +**Examples:** + +```questdb-sql title="List all live views" +SELECT view_name, base_table_name, view_status, lag_seqtxn, lag_micros +FROM live_views(); +``` + +| view_name | base_table_name | view_status | lag_seqtxn | lag_micros | +| --------- | --------------- | ----------- | ---------- | ---------- | +| trades_ma | trades | active | 0 | 0 | + ## materialized_views `materialized_views()` returns the list of all materialized views in the @@ -618,7 +722,7 @@ Returns a `table` with the following columns: | Column | Type | Description | |--------|------|-------------| | `table_suspended` | BOOLEAN | Whether a WAL table is suspended (`false` for non-WAL tables) | -| `table_type` | CHAR | Table type: `T` (table), `M` (materialized view), `V` (view) | +| `table_type` | CHAR | Table type: `T` (table), `M` (materialized view), `V` (view), `L` (live view) | | `table_row_count` | LONG | Approximate row count at last tracked write | | `table_min_timestamp` | TIMESTAMP | Minimum timestamp of data in the table (updated on WAL merge) | | `table_max_timestamp` | TIMESTAMP | Maximum timestamp of data in the table (updated on WAL merge) | diff --git a/documentation/query/sql/create-live-view.md b/documentation/query/sql/create-live-view.md new file mode 100644 index 000000000..fe7247cc7 --- /dev/null +++ b/documentation/query/sql/create-live-view.md @@ -0,0 +1,337 @@ +--- +title: CREATE LIVE VIEW +sidebar_label: CREATE LIVE VIEW +description: + Documentation for the CREATE LIVE VIEW SQL keyword in QuestDB. +--- + +Creates a live view that incrementally maintains the result of a window-function +query over a single base table and can be queried like a regular table. For a +conceptual overview, see [Live views](/docs/concepts/live-views/). + +## Syntax + +```questdb-sql title="CREATE LIVE VIEW" +CREATE LIVE VIEW [ IF NOT EXISTS ] viewName +FLUSH EVERY duration +[ IN MEMORY duration ] +[ PARTITION BY ( YEAR | MONTH | WEEK | DAY | HOUR ) ] +START FROM ( NOW | BEGINNING | 'timestamp' ) +AS [ ( ] query [ ) ] +[ OWNED BY ownerName ] +``` + +Where: + +- `duration`: a single token with a unit of `ms`, `s`, `m`, `h`, or `d`, for + example `100ms`, `5s`, or `30m`. +- `query`: a `SELECT` over one WAL-backed base table whose projection contains + [window functions](/docs/query/functions/window-functions/overview/). + +`FLUSH EVERY` is required and must come first. `START FROM` is also required and +may appear in any order with the optional `IN MEMORY` and `PARTITION BY` +clauses. These clauses all precede `AS`; the optional `OWNED BY` clause follows +the query. + +## Parameters + +| Parameter | Description | +| --------- | ----------- | +| `viewName` | Name for the live view | +| `IF NOT EXISTS` | Create only if a view with this name does not already exist | +| `FLUSH EVERY` | How often computed rows are persisted to disk. Required | +| `IN MEMORY` | Window of recent rows kept in RAM for fresh reads. Defaults to `FLUSH EVERY` | +| `PARTITION BY` | Partitioning unit for the view's disk tier. Defaults to the base table's scheme | +| `START FROM` | Inclusive event-time boundary: `NOW`, `BEGINNING`, or a timestamp literal. Required | +| `query` | A window-function `SELECT` over a single WAL-backed base table | +| `OWNED BY` | Assign ownership (Enterprise) | + +## Clauses + +### FLUSH EVERY + +`FLUSH EVERY` sets how often the view's computed rows are persisted from the +in-memory tier to the view's own WAL-backed disk tier. It controls durability and +write amplification, not read freshness: a direct `SELECT` reads the freshest +computed rows regardless of the flush cadence. + +A smaller interval persists more often, shortening crash recovery at the cost of +more write volume. A larger interval reduces write volume but lengthens recovery +and increases the staleness of the read shapes that are served from disk only +(see [Freshness](/docs/concepts/live-views/#freshness)). + +The minimum is `100ms`. The maximum is +[`cairo.live.view.in.memory.max`](/docs/configuration/live-views/#cairoliveviewinmemorymax) +(60 minutes by default), because `IN MEMORY` defaults to `FLUSH EVERY`. + +```questdb-sql +CREATE LIVE VIEW trades_ma +FLUSH EVERY 1s +START FROM NOW +AS +SELECT timestamp, symbol, + avg(price) OVER (PARTITION BY symbol ORDER BY timestamp ROWS 300 PRECEDING) + AS moving_avg +FROM trades; +``` + +### IN MEMORY + +`IN MEMORY` sets how long a window of recent output rows is retained in RAM to +serve fast, fresh reads. Reads of recent data are served from the in-memory tier +and older data from disk. It defaults to `FLUSH EVERY`. + +`IN MEMORY` must be at least `FLUSH EVERY` and at most +[`cairo.live.view.in.memory.max`](/docs/configuration/live-views/#cairoliveviewinmemorymax). + +```questdb-sql +CREATE LIVE VIEW trades_ma +FLUSH EVERY 1s +IN MEMORY 5s +START FROM NOW +AS +SELECT timestamp, symbol, + avg(price) OVER (PARTITION BY symbol ORDER BY timestamp ROWS 300 PRECEDING) + AS moving_avg +FROM trades; +``` + +### PARTITION BY + +`PARTITION BY` sets the partitioning of the view's disk tier. If omitted, the +view inherits the base table's partitioning scheme. + +```questdb-sql +CREATE LIVE VIEW trades_ma +FLUSH EVERY 1s +PARTITION BY HOUR +START FROM NOW +AS +SELECT timestamp, symbol, + avg(price) OVER (PARTITION BY symbol ORDER BY timestamp ROWS 300 PRECEDING) + AS moving_avg +FROM trades; +``` + +### START FROM + +`START FROM` defines the inclusive event-time boundary for rows in the live +view. It is mandatory and accepts: + +- `NOW`: resolve the engine clock once when the view is created. +- `BEGINNING`: include all base-table history. +- A quoted timestamp literal: include rows whose designated timestamp is equal + to or later than that value. + +The boundary applies to the base table's designated timestamp, not to commit +time. QuestDB performs a resumable initial seed for qualifying rows already +present at creation, then continues refreshing from new base commits. + +```questdb-sql +CREATE LIVE VIEW trades_ma +FLUSH EVERY 1s +START FROM BEGINNING +AS +SELECT timestamp, symbol, + avg(price) OVER (PARTITION BY symbol ORDER BY timestamp ROWS 300 PRECEDING) + AS moving_avg +FROM trades; +``` + +```questdb-sql title="Start from an explicit timestamp" +CREATE LIVE VIEW trades_ma_from_april +FLUSH EVERY 1s +START FROM '2026-04-01T00:00:00.000000Z' +AS +SELECT timestamp, symbol, + avg(price) OVER (PARTITION BY symbol ORDER BY timestamp ROWS 300 PRECEDING) + AS moving_avg +FROM trades; +``` + +## Anchored windows + +An anchored window resets its functions on a boundary. Declare it in a named +`WINDOW` with one of these forms: + +```questdb-sql +ANCHOR DAILY 'HH:MM' [ 'timezone' ] +ANCHOR EXPRESSION expression +``` + +`ANCHOR DAILY` requires a quoted 24-hour time. An optional IANA time zone makes +the reset follow local civil time; without one, the boundary is in UTC. + +```questdb-sql title="Cumulative daily volume, reset each day" +CREATE LIVE VIEW trades_daily_volume +FLUSH EVERY 1s +START FROM NOW +AS +SELECT timestamp, symbol, + sum(amount) OVER w AS cumulative_volume +FROM trades +WINDOW w AS ( + PARTITION BY symbol + ORDER BY timestamp + ANCHOR DAILY '00:00' +); +``` + +For example, `ANCHOR DAILY '09:30' 'America/New_York'` resets at the New York +market open and follows daylight-saving transitions. + +```questdb-sql title="Anchor on an arbitrary expression" +CREATE LIVE VIEW trades_hourly_volume +FLUSH EVERY 1s +START FROM NOW +AS +SELECT timestamp, symbol, + sum(amount) OVER w AS bucket_volume +FROM trades +WINDOW w AS ( + PARTITION BY symbol + ORDER BY timestamp + ANCHOR EXPRESSION timestamp_floor('1h', timestamp) +); +``` + +An anchored window must: + +- Be a named window; `ANCHOR` is not supported in an inline `OVER (...)`. +- Use `PARTITION BY` with base-table columns directly. +- `ORDER BY` the designated timestamp ascending. +- Use the default unbounded frame; `ANCHOR` cannot be combined with a bounded + `ROWS` or `RANGE` frame. + +A live view supports at most one anchored window. An `ANCHOR EXPRESSION` must be +deterministic, non-constant, and return `TIMESTAMP`, `LONG`, or `INT`. + +## Query constraints + +The view query is validated at creation time and must: + +- Read a single WAL-backed base table that has a designated timestamp. No JOINs, + subqueries, or CTEs. +- Contain [window functions](/docs/query/functions/window-functions/overview/) + that can be maintained incrementally (see + [supported functions](/docs/concepts/live-views/#supported-window-functions)). +- Give every stateful window function a `PARTITION BY` clause. +- Use a bounded `ROWS` or `RANGE` frame, or a named anchored window. Ranking + functions (`row_number`, `rank`, and `dense_rank`) must be anchored. Frames + starting at `UNBOUNDED PRECEDING` are rejected except for stateless + `last_value` shapes. +- Not use `SAMPLE BY`, `GROUP BY`, a top-level `ORDER BY`, or `LIMIT` in the + view query. The `ORDER BY` inside a window's `OVER (...)` is required and + allowed. +- List output columns explicitly; wildcard projections such as `SELECT *` are + not allowed. +- Not filter on the base table's designated timestamp. Other deterministic + `WHERE` predicates are supported. +- Not use non-deterministic functions such as `now()`, `sysdate()`, + `systimestamp()`, or `rnd_*()`. +- Not read another live view. + +## Complete example + +```questdb-sql title="Base table" +CREATE TABLE trades ( + symbol SYMBOL, + side SYMBOL, + price DOUBLE, + amount DOUBLE, + timestamp TIMESTAMP +) TIMESTAMP(timestamp) PARTITION BY DAY WAL; +``` + +```questdb-sql title="Fully specified live view" +CREATE LIVE VIEW IF NOT EXISTS trades_ma +FLUSH EVERY 1s +IN MEMORY 5s +PARTITION BY HOUR +START FROM BEGINNING +AS +SELECT + timestamp, + symbol, + price, + avg(price) OVER ( + PARTITION BY symbol + ORDER BY timestamp + ROWS 300 PRECEDING + ) AS moving_avg +FROM trades; +``` + +This creates a view that: + +- Persists computed rows to disk every second (`FLUSH EVERY 1s`) +- Keeps 5 seconds of recent rows in RAM for fresh reads (`IN MEMORY 5s`) +- Partitions its disk tier by hour (`PARTITION BY HOUR`) +- Includes all existing history in `trades` (`START FROM BEGINNING`) +- Keeps a 300-row moving average of price per symbol + +## Metadata + +Query view metadata with [`live_views()`](/docs/query/functions/meta/#live_views): + +```questdb-sql +SELECT view_name, base_table_name, view_status, lag_seqtxn +FROM live_views(); +``` + +## Permissions (Enterprise) + +Creating a live view requires the database-level `CREATE LIVE VIEW` permission +and `SELECT` on the base table: + +```questdb-sql title="Grant permission to create live views" +GRANT CREATE LIVE VIEW TO user1; +``` + +```questdb-sql title="Grant SELECT on the base table" +GRANT SELECT ON trades TO user1; +``` + +When you create a live view you automatically receive all permissions on it, +including `DROP LIVE VIEW`, with the `GRANT` option. + +### OWNED BY clause + +Assign ownership to a user, group, or service account: + +```questdb-sql +CREATE GROUP analysts; +CREATE LIVE VIEW trades_ma +FLUSH EVERY 1s +START FROM NOW +AS +SELECT timestamp, symbol, + avg(price) OVER (PARTITION BY symbol ORDER BY timestamp ROWS 300 PRECEDING) + AS moving_avg +FROM trades +OWNED BY analysts; +``` + +## Errors + +| Error | Cause | +| ------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------- | +| `live views are disabled` | Live-view support is turned off (`cairo.live.view.enabled=false`) | +| `live view already exists` | A live view of this name exists and `IF NOT EXISTS` was not specified | +| `table or view with the requested name already exists` | The name is taken by a table, view, or materialized view | +| `live view FLUSH EVERY must be at least 100ms` | The `FLUSH EVERY` interval is below the minimum | +| `live view select must be a simple scan of a single WAL base table; joins, subqueries, GROUP BY, ORDER BY and LIMIT are not supported yet` | The view query is not a simple scan of one base table | +| `base table must be a WAL table` | The base object is a non-WAL table or a regular view | +| `live views are not allowed as base tables in V1` | The base object is another live view | +| `live view base table must have a designated timestamp` | The base table has no designated timestamp | +| `wildcard column select is not allowed in live view queries` | The top-level projection contains `*` | +| `live view unbounded window must have an ANCHOR clause` | A stateful partitioned window uses the default unbounded frame without an anchor | +| `non-deterministic function cannot be used in live view` | The query uses `now()`, `rnd_*()`, or a similar non-deterministic function | +| `permission denied` | Missing required permission (Enterprise) | + +## See also + +- [Live views concept](/docs/concepts/live-views/) +- [DROP LIVE VIEW](/docs/query/sql/drop-live-view/) +- [Window functions](/docs/query/functions/window-functions/overview/) +- [live_views()](/docs/query/functions/meta/#live_views) diff --git a/documentation/query/sql/drop-live-view.md b/documentation/query/sql/drop-live-view.md new file mode 100644 index 000000000..dd684e04b --- /dev/null +++ b/documentation/query/sql/drop-live-view.md @@ -0,0 +1,72 @@ +--- +title: DROP LIVE VIEW +sidebar_label: DROP LIVE VIEW +description: + Documentation for the DROP LIVE VIEW SQL keyword in QuestDB. +--- + +Permanently deletes a live view and all of its data. For a conceptual overview, +see [Live views](/docs/concepts/live-views/). + +## Syntax + +```questdb-sql title="DROP LIVE VIEW" +DROP LIVE VIEW [ IF EXISTS ] viewName +``` + +## Parameters + +| Parameter | Description | +| --------- | ----------- | +| `viewName` | Name of the live view to drop | +| `IF EXISTS` | Suppress the error if the view does not exist | + +## Examples + +```questdb-sql title="Drop a live view" +DROP LIVE VIEW trades_ma; +``` + +```questdb-sql title="Drop only if it exists (no error if missing)" +DROP LIVE VIEW IF EXISTS trades_ma; +``` + +## Behavior + +| Aspect | Description | +| ------ | ----------- | +| Permanence | Deletion is permanent and not recoverable | +| Space reclamation | Disk space is reclaimed asynchronously | +| Active queries | Existing read queries may delay space reclamation | +| Permissions | On Enterprise, the view's access-control grants are removed with it | + +:::warning + +This operation cannot be undone. The view and all of its precomputed data are +permanently deleted. + +::: + +## Permissions (Enterprise) + +Dropping a live view requires the `DROP LIVE VIEW` permission on the specific +view: + +```questdb-sql title="Grant drop permission" +GRANT DROP LIVE VIEW ON trades_ma TO user1; +``` + +The view creator automatically receives this permission with the `GRANT` option. + +## Errors + +| Error | Cause | +| ----- | ----- | +| `live view name expected` | The name refers to a table or view that is not a live view | +| `live view does not exist` | The view does not exist and `IF EXISTS` was not specified | +| `permission denied` | Missing `DROP LIVE VIEW` permission (Enterprise) | + +## See also + +- [Live views concept](/docs/concepts/live-views/) +- [CREATE LIVE VIEW](/docs/query/sql/create-live-view/) diff --git a/documentation/query/sql/show.md b/documentation/query/sql/show.md index 0d19bae2b..086419fef 100644 --- a/documentation/query/sql/show.md +++ b/documentation/query/sql/show.md @@ -18,6 +18,7 @@ SHOW { TABLES | PARTITIONS FROM tableName | CREATE TABLE tableName | CREATE VIEW viewName + | CREATE LIVE VIEW viewName | USER [userName] | USERS | GROUPS [userName] @@ -36,6 +37,8 @@ SHOW { TABLES - `SHOW PARTITIONS` returns the partition information for the selected table. - `SHOW CREATE TABLE` returns a DDL query that allows you to recreate the table. - `SHOW CREATE VIEW` returns a DDL query that allows you to recreate a view. +- `SHOW CREATE LIVE VIEW` returns a DDL query that allows you to recreate a live + view. - `SHOW USER` shows user secret (enterprise-only) - `SHOW GROUPS` shows all groups the user belongs or all groups in the system (enterprise-only) @@ -201,6 +204,21 @@ SHOW CREATE VIEW my_view; This returns the `CREATE VIEW` statement that would recreate the view, including any `DECLARE` parameters if the view is parameterized. +### SHOW CREATE LIVE VIEW + +```questdb-sql title="retrieving live view ddl" +SHOW CREATE LIVE VIEW trades_ma; +``` + +| ddl | +| --- | +| CREATE LIVE VIEW 'trades_ma' FLUSH EVERY 1s IN MEMORY 5s PARTITION BY DAY START FROM NOW AS (
SELECT timestamp, symbol, avg(price) OVER (PARTITION BY symbol ORDER BY timestamp ROWS 300 PRECEDING) AS moving_avg FROM trades
); | + +This returns the `CREATE LIVE VIEW` statement that would recreate the +[live view](/docs/concepts/live-views/), including its `FLUSH EVERY`, +`IN MEMORY`, `PARTITION BY`, and `START FROM` clauses. On QuestDB Enterprise the +output also carries an `OWNED BY` clause identifying the view's owner. + ### SHOW PARTITIONS ```questdb-sql diff --git a/documentation/security/rbac.md b/documentation/security/rbac.md index 9bb9897a0..d92a6dd2e 100644 --- a/documentation/security/rbac.md +++ b/documentation/security/rbac.md @@ -580,6 +580,7 @@ SELECT * FROM all_permissions(); | CANCEL ANY COPY | Database | Cancel COPY operations | | CREATE TABLE | Database | Create tables | | CREATE MATERIALIZED VIEW | Database | Create materialized views | +| CREATE LIVE VIEW | Database | Create live views | | DEDUP ENABLE | Database | Table | Enable deduplication | | DEDUP DISABLE | Database | Table | Disable deduplication | | DETACH PARTITION | Database | Table | Detach partitions | @@ -589,6 +590,7 @@ SELECT * FROM all_permissions(); | DROP PARTITION | Database | Table | Drop partitions | | DROP TABLE | Database | Table | Drop tables | | DROP MATERIALIZED VIEW | Database | Table | Drop materialized views | +| DROP LIVE VIEW | Database | Table | Drop live views | | ENABLE STORAGE POLICY | Database | Table | Enable storage policies | | INSERT | Database | Table | Insert data | | REFRESH MATERIALIZED VIEW | Database | Table | Refresh materialized views | diff --git a/documentation/sidebars.js b/documentation/sidebars.js index f4734f2e9..e6bc0bfde 100644 --- a/documentation/sidebars.js +++ b/documentation/sidebars.js @@ -334,6 +334,7 @@ module.exports = { id: "query/sql/acl/create-group", type: "doc", }, + "query/sql/create-live-view", "query/sql/create-mat-view", { id: "query/sql/acl/create-service-account", @@ -355,6 +356,7 @@ module.exports = { id: "query/sql/acl/drop-group", type: "doc", }, + "query/sql/drop-live-view", "query/sql/drop-mat-view", { id: "query/sql/acl/drop-service-account", @@ -533,6 +535,11 @@ module.exports = { type: "doc", label: "Materialized Views", }, + { + id: "concepts/live-views", + type: "doc", + label: "Live Views", + }, "concepts/deduplication", "concepts/ttl", "concepts/storage-policy", @@ -589,6 +596,7 @@ module.exports = { "configuration/http-server", "configuration/iam", "configuration/ingestion", + "configuration/live-views", "configuration/logging-metrics", "configuration/materialized-views", "configuration/http-min-server",