# Meta functions

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

Meta functions for inspecting tables, WAL status, running queries with their memory usage and limits, configuration, and server metadata.

These functions provide instance-level information and table, column and
partition information including metadata. They are particularly useful for
learning useful information about your instance, including:

- [Designated timestamp](/docs/concepts/designated-timestamp/) columns
- [Attached, detached, or attachable](/docs/query/sql/alter-table-attach-partition/)
  partitions
- Partition storage size on disk
- Running SQL commands

:::note

In QuestDB Enterprise, the catalogue functions on this page, such as
`tables()`, `views()`, and `wal_tables()`, list only the objects the caller
[can see](/docs/security/rbac/#object-visibility). The functions that take a
table name, such as `table_columns()`, fail with `table does not exist` for a
table hidden from the caller.

:::

## build

**Arguments:**

- `build()` does not require arguments.

**Return value:**

Returns a string with the current QuestDB version and hash.

**Examples:**

```questdb-sql
SELECT build();
```

| build                                                                                          |
| ---------------------------------------------------------------------------------------------- |
| Build Information: QuestDB 9.0.0, JDK 25, Commit Hash 460b817b0a3705c5633619a8ef9efb5163f1569c |

## current_data_id

Returns the data ID: a UUID that identifies this database, generated on first
start and stored in the database root. Replication and restore use it to tell
one database apart from another, so a backup cannot be restored over an
unrelated instance by accident.

**Arguments:**

- `current_data_id()` does not require arguments.

**Return value:**

Returns a `uuid`.

**Examples:**

```questdb-sql
SELECT current_data_id();
```

| current_data_id                      |
| ------------------------------------ |
| 75f5a084-7a53-5d7f-941a-c2b4c4c6f127 |

## current_database

Returns the name of the database the session is connected to.

**Arguments:**

- `current_database()` does not require arguments.

**Return value:**

Returns a `string`.

**Examples:**

```questdb-sql
SELECT current_database();
```

| current_database |
| ---------------- |
| qdb              |

## current_resource_group

:::note

Resource groups and the `current_resource_group()` function are available in
**QuestDB Enterprise** only.

:::

`current_resource_group()` returns the name of the
[resource group](/docs/concepts/resource-groups/) the calling query was admitted
to. Run it from a client session to confirm which group that session's queries
land in, which is the quickest way to check that a principal mapping took
effect. Any authenticated session can call it, with no permission of its own,
unlike [`resource_groups()`](#resource_groups) which requires
[`RESOURCE GROUP ADMIN`](/docs/security/rbac/#permissions). An ordinary user can
therefore check their own group but not read anyone else's policy.

**Arguments:**

- `current_resource_group()` does not require arguments.

**Return value:**

Returns a string naming the group. A principal with no mapping of its own
resolves to `DEFAULT`, so on an instance where nobody has been mapped yet every
session sees `DEFAULT`:

```questdb-sql title="Check which group the calling session's queries run in"
SELECT current_resource_group();
```

| current_resource_group |
| ---------------------- |
| DEFAULT                |

It returns `null` only when the query was never admitted to a group at all: when
`resource.groups.enabled` is `false`, or on a replica that has not yet received
the group catalog. A group name and `null` therefore mean
different things: `DEFAULT` says the feature placed the query in the built-in
group, while `null` says the feature was not in force for that query.

The group is resolved when the query starts and stays fixed for its lifetime,
including across the pages of a suspended cursor. Changing a mapping affects the
next query, never one already running.

**Confirming a mapping end to end:**

```questdb-sql title="Map a user, then confirm from that user's own session"
ALTER USER analyst SET RESOURCE GROUP reporting;
```

Reconnect as `analyst` and run:

```questdb-sql title="Confirm the mapping took effect"
SELECT current_resource_group();
```

| current_resource_group |
| ---------------------- |
| reporting              |

A principal with `SQL ENGINE ADMIN` can check the same thing without
reconnecting, because [`query_activity()`](#query_activity) carries a
`resource_group` column for every running query.

## current_schema

Returns the name of the current schema.

**Arguments:**

- `current_schema()` does not require arguments.

**Return value:**

Returns a `string`.

**Examples:**

```questdb-sql
SELECT current_schema();
```

| current_schema |
| -------------- |
| public         |

## current_user

Returns the user that authenticated on the current connection, whichever
protocol it arrived on. [`session_user()`](#session_user) is a compatibility
alias for it, and the two are interchangeable in QuestDB.

**Arguments:**

- `current_user()` does not require arguments.

**Return value:**

Returns a `string`.

**Examples:**

```questdb-sql
SELECT current_user();
```

| current_user |
| ------------ |
| admin        |

## flush_query_cache

`flush_query_cache' invalidates cached query execution plans.

**Arguments:**

- `flush_query_cache()` does not require arguments.

**Return value:**

Returns `boolean`. `true` if successful, `false` if unsuccessful.

**Examples:**

```questdb-sql title="Flush cached query execution plans"
SELECT flush_query_cache();
```

## functions

**Arguments:**

- `functions()` does not require arguments.

**Return value:**

Returns all available database functions.

**Examples:**

```questdb-sql
functions();
```

| name | signature | signature_translated  | runtime_constant | type     |
| ---- | --------- | --------------------- | ---------------- | -------- |
| or   | or(TT)    | or(boolean, boolean)  | FALSE            | STANDARD |
| and  | and(TT)   | and(boolean, boolean) | FALSE            | STANDARD |
| not  | not(T)    | not(boolean)          | FALSE            | STANDARD |

## hydrate_table_metadata

`hydrate_table_metadata' re-reads table metadata from disk to update the static
metadata cache.

:::warning

This function should only be used when directed by QuestDB support. Misuse could
cause corruption of the metadata cache, requiring the database to be restarted.

:::

In QuestDB Enterprise, the function requires the `SYSTEM ADMIN` permission.

**Arguments:**

A variable list of strings, corresponding to table names.

Alternatively, a single asterisk, '\*', representing all tables.

**Return value:**

Returns `boolean`. `true` if successful, `false` if unsuccessful.

**Examples:**

Simply pass table names as arguments to the function.

```
SELECT hydrate_table_metadata('trades', 'trips');
```

| hydrate_table_metadata |
| ---------------------- |
| true                   |

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`. A suspended WAL on the view still shows `active`; check [`wal_tables()`](#wal_tables) |
| `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      | Base transactions applied to the base table beyond `last_processed_seqtxn`                                             |
| `lag_micros`                                   | LONG      | Microseconds since the last successful flush; `NULL` until the first flush after creation or restart                   |
| `last_processed_seqtxn`                        | LONG      | Last base transaction whose output was flushed to the view; the in-memory tier can be ahead of it                      |
| `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      | How long the in-memory tier has been unable to publish because readers pin both buffer slots, 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`                                               |
| `checkpoint_seal_failures`                     | LONG      | Failed checkpoint seals; growth means restart-recovery state is going stale; resets on restart                         |

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 view's
durable tier has caught up with every transaction applied to the base table; a
temporary non-zero value is expected between `FLUSH EVERY` cycles. Base commits
that are not yet applied to the base table are not counted, so when the base
table itself lags or is suspended, check
[`wal_tables()`](#wal_tables) as well. 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
database.

**Arguments:**

- `materialized_views()` does not require arguments.

**Return value:**

Returns a `table` including the following information:

- `view_name` - materialized view name
- `refresh_type` - refresh strategy type
- `base_table_name` - base table name
- `last_refresh_start_timestamp` - last time when an incremental refresh for the
  view was started
- `last_refresh_finish_timestamp` - last time when an incremental refresh for
  the view finished
- `view_sql` - query used to populate view data
- `view_table_dir_name` - view directory name
- `invalidation_reason` - message explaining why the view was marked as invalid
- `view_status` - view status: 'valid', 'refreshing', or 'invalid'
- `refresh_base_table_txn` - the last base table transaction used to refresh the
  materialized view
- `base_table_txn` - the last committed transaction in the base table
- `refresh_limit_value` - how many units back in time the refresh limit goes
- `refresh_limit_unit` - how long each unit is
- `timer_start` - start date for the scheduled refresh timer
- `timer_interval_value` - how many interval units between each refresh
- `timer_interval_unit` - how long each unit is

**Examples:**

```questdb-sql title="List all materialized views"
materialized_views();
```

| view_name        | refresh_type | base_table_name | last_refresh_start_timestamp | last_refresh_finish_timestamp | view_sql                                                                                                                                                     | view_table_dir_name | invalidation_reason | view_status | refresh_base_table_txn | base_table_txn | refresh_limit_value | refresh_limit_unit | timer_start | timer_interval_value | timer_interval_unit |
| ---------------- | ------------ | --------------- | ---------------------------- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------- | ------------------- | ----------- | ---------------------- | -------------- | ------------------- | ------------------ | ----------- | -------------------- | ------------------- |
| trades_OHLC_15m  | immediate    | trades          | 2025-05-30T16:40:37.562421Z  | 2025-05-30T16:40:37.568800Z   | SELECT timestamp, symbol, first(price) AS open, max(price) as high, min(price) as low, last(price) AS close, sum(amount) AS volume FROM trades SAMPLE BY 15m | trades_OHLC_15m~27  | null                | valid       | 55141609               | 55141609       | 0                   | null               | null        | 0                    | null                |
| trades_latest_1d | immediate    | trades          | 2025-05-30T16:40:37.554274Z  | 2025-05-30T16:40:37.562049Z   | SELECT timestamp, symbol, side, last(price) AS price, last(amount) AS amount, last(timestamp) as latest FROM trades SAMPLE BY 1d                             | trades_latest_1d~28 | null                | valid       | 55141609               | 55141609       | 0                   | null               | null        | 0                    | null                |

## memory_metrics

**Arguments:**

- `memory_metrics()` does not require arguments.

**Return value:**

Returns granular memory metrics. `RSS` is the process resident set size and
`TOTAL_USED` the sum of all tracked allocations. The `NATIVE_*` rows are the
tracked allocations that count toward
[`ram.usage.limit.bytes`](/docs/configuration/cairo-engine/#ramusagelimitbytes)
and the per-workload
[memory limits](/docs/configuration/cairo-engine/#memory-limits); the `MMAP_*`
rows do not.

**Examples:**

```questdb-sql
memory_metrics();
```

| memory_tag     | bytes     |
| -------------- | --------- |
| TOTAL_USED     | 142624730 |
| RSS            | 328609792 |
| MMAP_DEFAULT   | 196728    |
| NATIVE_DEFAULT | 256       |
| MMAP_O3        | 0         |
| NATIVE_O3      | 96        |

## node_role

:::note

Replication and the `node_role()` function are available in **QuestDB
Enterprise** only, since version 4.0.0.

:::

`node_role()` returns the [replication](/docs/high-availability/overview/) role
the instance currently holds, so a session can tell whether it is connected to
the primary or to a replica. Any authenticated session can call it; unlike
`SWITCH STATUS`, it needs no permission.

**Arguments:**

- `node_role()` does not require arguments.

**Return value:**

Returns a string: `PRIMARY`, `REPLICA`, or `UNKNOWN` while an in-place role
switch has been aborted part-way, see
[Failover and role switch](/docs/high-availability/failover/#refusals-and-the-torn-state).
On an instance without replication it returns the role derived from
`replication.role`, which is `PRIMARY` for a standalone instance.

The value is read once per statement execution, so a cached plan or a prepared
statement observes a role change on its next execution.

**Examples:**

```questdb-sql
SELECT node_role();
```

| node_role |
| --------- |
| PRIMARY   |

:::warning

`node_role()` cannot be used in a materialized view or a live view. Avoid it in
`UPDATE` on a WAL table as well: the statement is re-executed on every node of a
replicated cluster and each node evaluates its own role, so the primary and its
replicas would write different values. Tagging rows on `INSERT` is safe, because
inserted rows replicate as data.

:::

## query_activity

**Arguments:**

- `query_activity()` does not require arguments.

**Return value:**

Returns metadata on running SQL queries, with the following columns:

- query_id - identifier of the query that can be used with
  [cancel query](/docs/query/sql/cancel-query) command or
  [cancelQuery()](/docs/query/sql/cancel-query) function
- worker_id - identifier of worker thread that initiated query processing. Note
  that many multithreaded queries also run on other workers
- worker_pool - name of worker pool used to execute the query
- username - name of user executing the query
- query_start - timestamp of when query started
- state_change - timestamp of latest query state change, such as a cancellation
- state - state of running query, can be `active` or `cancelled`
- is_wal - `true` when the SQL is being applied by the WAL apply job, such as an
  `UPDATE` on a WAL table. Such queries cannot be cancelled
- query - text of sql query
- memory_used - native memory currently allocated by the query, in bytes, as
  tracked by the
  [per-query memory limit](/docs/configuration/cairo-engine/#memory-limits)
- memory_limit - effective native memory limit for the query, in bytes, or
  `null` when the query runs unlimited. On QuestDB Enterprise it starts from the
  principal's [memory limit](/docs/security/rbac/#memory-limits) when one is
  set, otherwise the workload limit, and is then capped by the
  [resource group](/docs/concepts/resource-groups/) budget and the process
  budget. Unlike the `memory_limit` column of `SHOW USERS`, it includes the
  workload limit and the group budget, so a principal with no limit of its own
  still reports a value here when its group sets one
- resource_group - the [resource group](/docs/concepts/resource-groups/) the
  query was admitted to on QuestDB Enterprise. A query whose principal has no
  mapping reports `DEFAULT`; `null` means the query was never admitted to a
  group at all, which happens when resource groups are disabled and when a
  replica has not yet received the group catalog

`memory_used` is a live gauge with no peak value, and it is reported even when
`memory_limit` is `null`. Both memory columns are `null` for SQL that runs under
a background workload's tracker, such as the `SELECT` a materialized view
refresh runs or an `UPDATE` applied by the WAL apply job, because that SQL
charges the workload's budget instead of acquiring its own.

**Examples:**

```questdb-sql
SELECT query_id, username, state, memory_used, memory_limit, query
FROM query_activity();
```

| query_id | username | state  | memory_used | memory_limit | query                                                                                    |
| -------- | -------- | ------ | ----------- | ------------ | ---------------------------------------------------------------------------------------- |
| 62179    | john     | active | 262144      | 536870912    | SELECT query_id, username, state, memory_used, memory_limit, query FROM query_activity() |
| 57777    | john     | active | 8388608     | 536870912    | SELECT symbol, approx_percentile(price, 0.5, 2) FROM trades                              |

To inspect query memory and resource group assignment in QuestDB Enterprise:

```questdb-sql title="See which group each running query was admitted to"
SELECT query_id, username, resource_group, memory_used, memory_limit
FROM query_activity();
```

| query_id | username | resource_group | memory_used | memory_limit |
| -------- | -------- | -------------- | ----------- | ------------ |
| 47       | analyst  | reporting      | 0           | 2147483648   |

`analyst` has no memory limit of its own here, so the 2 GiB ceiling is the one
the `reporting` group sets. Joining this against
[`resource_groups()`](#resource_groups) shows how much of a group's budget its
running queries are actually holding.

## reader_pool

**Arguments:**

- `reader_pool()` does not require arguments.

**Return value:**

Returns information about the current state of the reader pool in QuestDB. The
reader pool is a cache of table readers that are kept open to speed up
subsequent reads from the same table. The returned information includes the
table name, the ID of the thread that currently owns the reader, the timestamp
of the last time the reader was accessed, and the current transaction ID with
which the reader is associated.

In QuestDB Enterprise, `reader_pool()` requires the `SYSTEM ADMIN` permission,
because the pool lists tables whether or not the caller
[can see](/docs/security/rbac/#object-visibility) them.

**Examples:**

```questdb-sql
SELECT * FROM reader_pool();
```

| table_name | owner_thread_id | last_access_timestamp       | current_txn |
| ---------- | --------------- | --------------------------- | ----------- |
| sensors    | null            | 2023-12-01T19:28:14.311703Z | 1           |

## reload_config

`reload_config' reloads server configuration file's contents (`server.conf`)
without server restart. The list of reloadable settings can be found
[here](/docs/configuration/overview/#reloadable-settings).

**Arguments:**

- `reload_config()` does not require arguments.

**Return value:**

Returns `boolean`. `true` if any configuration properties were reloaded, `false`
if none were reloaded.

**Examples:**

Edit `server.conf` and run `reload_config`:

```questdb-sql title="Reload server configuration"
SELECT reload_config();
```

## resource_groups

:::note

Resource groups and the `resource_groups()` function are available in **QuestDB
Enterprise** only.

:::

`resource_groups()` returns one row per
[resource group](/docs/concepts/resource-groups/) that currently exists,
including the built-in `DEFAULT`, pairing each group's policy with its live
counters. It is the main way to see what a group is configured to do and what it
is doing right now. Groups stay visible when `resource.groups.enabled` is
`false`; only their counters sit at zero.

Calling it requires the database-level
[`RESOURCE GROUP ADMIN`](/docs/security/rbac/#permissions) permission, the same
permission that gates creating, altering and mapping groups. A principal without
it gets `Access denied for <user> [RESOURCE GROUP ADMIN]`.

**Arguments:**

- `resource_groups()` does not require arguments.

**Return value:**

Returns a table with these columns:

| Column                     | Type      | Description                                                                                  |
| -------------------------- | --------- | -------------------------------------------------------------------------------------------- |
| `name`                     | `VARCHAR` | Group name                                                                                   |
| `memory_limit_bytes`       | `LONG`    | Effective memory ceiling in bytes, after the process budget caps it; `0` means no group ceiling |
| `max_active_queries`       | `INT`     | Concurrent admission limit as configured; `null` when the group sets none                     |
| `max_queued_queries`       | `INT`     | Queue capacity as configured; `null` when the group sets none, `0` to disable queueing        |
| `queue_timeout_millis`     | `LONG`    | Effective admission timeout in milliseconds, falling back to the instance default             |
| `cpu_weight`               | `INT`     | Effective relative scheduling weight; `100` when the group sets none                          |
| `active_queries`           | `LONG`    | Queries currently holding admission slots                                                    |
| `queued_queries`           | `LONG`    | Queries waiting for admission                                                                |
| `oldest_queue_wait_millis` | `LONG`    | Age of the oldest admission waiter in milliseconds; `0` when none                            |
| `memory_used_bytes`        | `LONG`    | Published tracked native query memory in bytes                                               |
| `cpu_nanos_total`          | `LONG`    | CPU nanoseconds measured by managed scheduling                                               |
| `cpu_wait_nanos_total`     | `LONG`    | Cumulative time queries spent waiting for CPU, in nanoseconds                                |
| `admission_rejections`     | `LONG`    | Cumulative queue-full rejections                                                             |
| `admission_timeouts`       | `LONG`    | Cumulative admission timeouts                                                                |

The policy columns do not all report the same thing. `max_active_queries` and
`max_queued_queries` show what the group itself sets, and are `null` when it sets
nothing. `memory_limit_bytes`, `queue_timeout_millis` and `cpu_weight` show the
effective value after instance defaults and the process budget have been applied,
so they are never `null`.

**Inspecting group policy:**

Given a group created like this:

```questdb-sql title="Create a group with an explicit policy"
CREATE RESOURCE GROUP reporting WITH (
    cpu_weight = 50,
    max_active_queries = 4,
    max_queued_queries = 32,
    memory_limit = '2G'
);
```

```questdb-sql title="Compare each group's policy against its live admission state"
SELECT name, cpu_weight, max_active_queries, max_queued_queries,
       memory_limit_bytes, active_queries, queued_queries
FROM resource_groups()
ORDER BY name;
```

| name      | cpu_weight | max_active_queries | max_queued_queries | memory_limit_bytes | active_queries | queued_queries |
| --------- | ---------- | ------------------ | ------------------ | ------------------ | -------------- | -------------- |
| DEFAULT   | 100        | null               | null               | 0                  | 1              | 0              |
| reporting | 50         | 4                  | 32                 | 2147483648         | 0              | 0              |

`DEFAULT` sets no admission limits of its own, so both columns are `null`, while
its `cpu_weight` of 100 is the effective default rather than something anyone
configured. Its `active_queries` counts the introspection query itself, which is
why the group you are querying from is never idle in its own output.

**Finding groups under admission pressure:**

```questdb-sql title="List groups where queries are waiting or being turned away"
SELECT name, active_queries, queued_queries, oldest_queue_wait_millis,
       admission_rejections, admission_timeouts
FROM resource_groups()
WHERE queued_queries > 0
   OR admission_rejections > 0
   OR admission_timeouts > 0;
```

A high `queued_queries` with a rising `oldest_queue_wait_millis` means work is
being held at the admission gate rather than by a busy machine, which is the
signal that distinguishes a concurrency limit from a slow query.

**Constraints and edge cases:**

- Counters describe the current runtime. They reset when the instance restarts,
  and recreating a group under the same name starts it from zero.
- `cpu_nanos_total` counts only CPU that managed scheduling actually measured, so
  it is a floor rather than a full account of query CPU. While `DEFAULT` is the
  only group, scheduling is disengaged and nothing is sampled, so the column sits
  at `0` however busy the instance is. It also undercounts once scheduling is
  engaged: a query already running when a second group was created stays outside
  scheduling until it next suspends or finishes, and a scheduler that has
  degraded after an internal fault stops sampling from then on, which
  `questdb_resource_groups_cpu_scheduler_degraded` reports as `1`.
- `memory_used_bytes` can lag real usage, because workers accumulate allocation
  deltas locally and publish them to the shared counter in batches.
- A dropped group disappears from this table immediately, while its running and
  queued queries finish under the settings it had.

The same values are published as
[Prometheus metrics](/docs/operations/logging-metrics/#resource-group-metrics),
where a group with no memory ceiling also reports `0`.

No group ceiling does not mean unlimited memory. A principal-specific limit, the
instance default `cairo.query.memory.limit.bytes` and the process budget
`resource.groups.process.memory.limit.bytes` all still apply.

## session_user

Compatibility alias for [`current_user()`](#current_user). The two are
interchangeable in QuestDB.

**Arguments:**

- `session_user()` does not require arguments.

**Return value:**

Returns a `string`.

**Examples:**

```questdb-sql
SELECT session_user();
```

| session_user |
| ------------ |
| admin        |

## sleep

Pauses the query for the given number of seconds, then returns the timestamp at
which it resumed. Intended for testing and demonstration, for example to hold a
query open while inspecting [`query_activity()`](#query_activity) from another
session.

`sleep()` does not hold a worker thread while it waits, so many concurrent calls
can be parked at once without exhausting the shared worker pool.

**Arguments:**

- `seconds` is the number of seconds to pause for.

**Return value:**

Returns a `timestamp`: the moment the call resumed.

**Examples:**

```questdb-sql title="Pause for one second"
SELECT * FROM sleep(1);
```

| sleep                       |
| --------------------------- |
| 2026-08-28T11:52:41.614549Z |

## storage_policies

:::note

Storage policies — and the `storage_policies` view — are available in **QuestDB
Enterprise** only.

:::

`storage_policies` is a system view that lists the
[storage policies](/docs/concepts/storage-policy/) currently attached to
tables. Every principal can see the view, but it lists only the policies of the
tables the caller [can see](/docs/security/rbac/#object-visibility). Unlike
`tables()`, it also lists the system tables the caller can see, such as
`sys.view_audit`. Query it like any other table:

```questdb-sql
SELECT * FROM storage_policies;
```

:::note

Querying the view also requires `SELECT` on the `status` column of the
`sys.sp_entries` system table, which `SELECT ON ALL TABLES` and
`DATABASE ADMIN` include. Without it, the query fails with
`Access denied for <principal> [SELECT on sys.sp_entries(status)]`. Grant that
column alone:

```questdb-sql
GRANT SELECT ON 'sys.sp_entries'(status) TO analyst;
```

Do not grant `SELECT` on all of `sys.sp_entries` or `sys.sp_links`. They hold
the directory name of every table with a policy, so a principal that can read
them finds tables otherwise hidden from it.

:::

**Columns:**

| Column           | Type        | Description                                                                                                                                                               |
| ---------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `table_dir_name` | _STRING_    | Directory name of the table the policy is attached to. Matches the `directoryName` column in [`tables()`](#tables).                                                       |
| `to_parquet`     | _STRING_    | TTL for the `TO PARQUET` stage (e.g. `72h`, `1m`). `0h` when the stage is not configured.                                                                                 |
| `to_remote`      | _STRING_    | TTL for the `TO REMOTE` stage, which uploads the partition to object storage. `0h` when the stage is not configured.                                                      |
| `drop_local`     | _STRING_    | TTL for the `DROP LOCAL` stage. `0h` when the stage is not configured.                                                                                                    |
| `drop_remote`    | _STRING_    | TTL for the `DROP REMOTE` stage, which removes the partition and reclaims its remote objects. `0h` when the stage is not configured.                                      |
| `status`         | _CHAR_      | Policy status. `A` = active (the policy is being enforced), `D` = disabled (via [`ALTER TABLE DISABLE STORAGE POLICY`](/docs/query/sql/alter-table-set-storage-policy/)). |
| `last_updated`   | _TIMESTAMP_ | Timestamp of the most recent change to the policy definition (not the last time partitions were processed).                                                               |

**Notes on TTL formatting:**

- TTL values are rendered in two units: `h` for hours and `m` for **months**.
  Hour-, day-, and week-based durations are stored as hours (e.g. `3 DAYS` →
  `72h`, `1 WEEK` → `168h`). Month- and year-based durations are stored as
  months (e.g. `1 MONTH` → `1m`, `1 YEAR` → `12m`). Despite the visual collision
  with "minute", `m` in this view is **months**; QuestDB's duration shorthand
  has no unit for minutes.
- An unset stage renders as `0h`, not blank.

**Example:**

```questdb-sql title="List all storage policies"
SELECT * FROM storage_policies;
```

| table_dir_name | to_parquet | to_remote | drop_local | drop_remote | status | last_updated                |
| -------------- | ---------- | --------- | ---------- | ----------- | ------ | --------------------------- |
| trades~12      | 168h       | 336h      | 1m         | 84m         | A      | 2025-01-15T10:30:00.000000Z |
| metrics~18     | 168h       | 0h        | 0h         | 0h          | D      | 2025-01-14T09:15:42.000000Z |

The first row is a full four-stage policy and is currently enforced: convert to
Parquet at 7 days, upload to object storage at 14 days, evict locally at 1
month, and drop remotely at 7 years. The second row has only the `TO PARQUET`
stage set and has been temporarily disabled. Every unset stage renders as `0h`.

## table_cold_partitions

:::note

[Cold storage](/docs/concepts/cold-storage/) and the `table_cold_partitions()`
function are available in **QuestDB Enterprise** only.

:::

`table_cold_partitions('tableName')` returns one row per partition in the
table's remote manifest, with the state of its object in the store. Use it to
follow a partition through upload and sealing, and to find partitions that are
not progressing.

**Arguments:**

- `tableName` (`string`): the name of an existing table.

The caller needs a permission on the table, though not necessarily `SELECT`,
because the function returns partition metadata rather than data. For a table
the caller [cannot see](/docs/security/rbac/#object-visibility), it fails with
`table does not exist [table=tableName]`.

**Columns:**

| Column           | Type        | Description                                                                                     |
| ---------------- | ----------- | ----------------------------------------------------------------------------------------------- |
| `timestamp`      | _TIMESTAMP_ | Lower bound of the partition.                                                                   |
| `state`          | _STRING_    | `pending`, `live`, `sealed`, or `deleting`. See below.                                          |
| `seq_txn`        | _LONG_      | WAL transaction version the uploaded object was built from. `NULL` until the object is durable. |
| `size`           | _LONG_      | Size of the uploaded object in bytes. `NULL` until the object is durable.                       |
| `last_modified`  | _TIMESTAMP_ | When the object was last written. `NULL` until the object is durable.                           |
| `deleting_since` | _TIMESTAMP_ | When the garbage-collection grace timer started. `NULL` unless the state is `deleting`.         |
| `partition_path` | _STRING_    | Object key of the partition's `data.parquet`.                                                   |

**Partition states:**

| State      | Meaning                                                                                                 |
| ---------- | ------------------------------------------------------------------------------------------------------- |
| `pending`  | Upload work is registered, but the object is not yet durable. The object-fact columns are `NULL`.       |
| `live`     | The uploaded snapshot is durable. The partition is still served locally and can still accept late data. |
| `sealed`   | The partition is read-only and approved for remote service.                                             |
| `deleting` | The table no longer references the partition. Its objects are reclaimed once the grace period elapses.  |

**Example:**

```questdb-sql title="Remote state of every partition"
SELECT * FROM table_cold_partitions('trades');
```

| timestamp                   | state    | seq_txn | size    | last_modified               | deleting_since              | partition_path                                  |
| --------------------------- | -------- | ------- | ------- | --------------------------- | --------------------------- | ----------------------------------------------- |
| 2026-02-08T00:00:00.000000Z | deleting | 152300  | 5318144 | 2026-02-15T01:30:11.482000Z | 2026-03-11T08:32:50.000000Z | trades~5/year=2026/month=02/day=08/data.parquet |
| 2026-02-09T00:00:00.000000Z | sealed   | 153118  | 5290992 | 2026-02-16T01:30:09.044000Z | null                        | trades~5/year=2026/month=02/day=09/data.parquet |
| 2026-02-10T00:00:00.000000Z | live     | 198404  | 5275600 | 2026-02-17T01:30:14.210000Z | null                        | trades~5/year=2026/month=02/day=10/data.parquet |
| 2026-02-11T00:00:00.000000Z | pending  | null    | null    | null                        | null                        | trades~5/year=2026/month=02/day=11/data.parquet |

```questdb-sql title="Partitions that are not yet durable"
SELECT timestamp, state, partition_path
FROM table_cold_partitions('trades')
WHERE state = 'pending';
```

**Notes:**

- The cold storage manager answers from its own in-memory view. A refresher
  answers from its mirrored copy, which it updates when the catalog generation
  changes, so the two can differ briefly.
- While an instance is transitioning between the manager and refresher roles,
  the function returns zero rows rather than blocking. Check the live role with
  [`SWITCH COLD STORAGE STATUS`](/docs/query/sql/switch-cold-storage-role/).
- The function reflects the remote manifest, not local partition state. Use
  [`SHOW PARTITIONS`](/docs/query/sql/show/#show-partitions) or
  [`table_partitions()`](#table_partitions) to see whether a partition is
  actually being served remotely.

## table_columns

`table_columns('tableName')` returns the schema of a table or a materialized
view.

**Arguments:**

- `tableName` is the name of an existing table or materialized view as a string.

**Return value:**

Returns a `table` with the following columns:

- `column` - name of the available columns in the table
- `type` - type of the column
- `indexed` - if indexing is applied to this column
- `indexBlockCapacity` - how many row IDs to store in a single storage block on
  disk (bitmap indexes only)
- `symbolCached` - whether this `symbol` column is cached
- `symbolCapacity` - how many distinct values this column of `symbol` type is
  expected to have
- `symbolTableSize` - current number of distinct values stored in this `symbol`
  column's table
- `designated` - if this is set as the designated timestamp column for this
  table
- `upsertKey` - if this column is a part of UPSERT KEYS list for table
  [deduplication](/docs/concepts/deduplication)
- `indexType` - the [index type](/docs/concepts/deep-dive/indexes/) (`POSTING`,
  `POSTING DELTA`, `POSTING EF`, `BITMAP`, or empty)
- `indexInclude` - comma-separated names of columns included in a
  [posting index's](/docs/concepts/deep-dive/posting-index/) covering sidecar

For more details on the meaning and use of these values, see the
[CREATE TABLE](/docs/query/sql/create-table/) documentation.

**Examples:**

```questdb-sql title="Get all columns in a table"
table_columns('my_table');
```

| column | type      | indexed | indexBlockCapacity | symbolCached | symbolCapacity | symbolTableSize | designated | upsertKey | indexType | indexInclude |
| ------ | --------- | ------- | ------------------ | ------------ | -------------- | --------------- | ---------- | --------- | --------- | ------------ |
| symb   | SYMBOL    | true    | 1048576            | false        | 256            | 0               | false      | false     | BITMAP    |              |
| price  | DOUBLE    | false   | 0                  | false        | 0              | 0               | false      | false     |           |              |
| ts     | TIMESTAMP | false   | 0                  | false        | 0              | 0               | true       | false     |           |              |
| s      | VARCHAR   | false   | 0                  | false        | 0              | 0               | false      | false     |           |              |

```questdb-sql title="Get designated timestamp column"
SELECT "column", type, designated FROM table_columns('my_table') WHERE designated = true;
```

| column | type      | designated |
| ------ | --------- | ---------- |
| ts     | TIMESTAMP | true       |

```questdb-sql title="Get the count of column types"
SELECT type, count() FROM table_columns('my_table');
```

| type      | count |
| --------- | ----- |
| SYMBOL    | 1     |
| DOUBLE    | 1     |
| TIMESTAMP | 1     |
| VARCHAR   | 1     |

## table_partitions

`table_partitions('tableName')` returns information for the partitions of a
table or a materialized view with the option to filter the partitions.

**Arguments:**

- `tableName` is the name of an existing table or materialized view as a string.

**Return value:**

Returns a table with the following columns:

- `index` - _INTEGER_, index of the partition (_NaN_ when the partition is not
  attached)
- `partitionBy` - _STRING_, one of _NONE_, _HOUR_, _DAY_, _WEEK_, _MONTH_ and
  _YEAR_
- `name` - _STRING_, name of the partition, e.g. `2023-03-14`,
  `2023-03-14.detached`, `2023-03-14.attachable`
- `minTimestamp` - _LONG_, min timestamp of the partition (_NaN_ when the table
  is not partitioned)
- `maxTimestamp` - _LONG_, max timestamp of the partition (_NaN_ when the table
  is not partitioned)
- `numRows` - _LONG_, number of rows in the partition
- `diskSize` - _LONG_, size of the partition in bytes
- `diskSizeHuman` - _STRING_, size of the partition meant for humans to read
  (same output as function
  [size_pretty](/docs/query/functions/numeric/#size_pretty))
- `readOnly` - _BOOLEAN_, true if the partition is
  [attached via soft link](/docs/query/sql/alter-table-attach-partition/#symbolic-links)
- `active` - _BOOLEAN_, true if the partition is the last partition, and whether
  we are writing to it (at least one record)
- `attached` - _BOOLEAN_, true if the partition is
  [attached](/docs/query/sql/alter-table-attach-partition/)
- `detached` - _BOOLEAN_, true if the partition is
  [detached](/docs/query/sql/alter-table-detach-partition/) (`name` of the
  partition will contain the `.detached` extension)
- `attachable` - _BOOLEAN_, true if the partition is detached and can be
  attached (`name` of the partition will contain the `.attachable` extension)
- `hasParquetGenerated` - _BOOLEAN_, true if a Parquet copy of the partition has
  been generated. Set by either
  [manual Parquet conversion](/docs/concepts/parquet/#in-place-conversion)
  (`ALTER TABLE ... CONVERT PARTITION TO PARQUET`) or by a
  [storage policy](/docs/concepts/storage-policy/)'s `TO PARQUET` stage
  (Enterprise)
- `isParquet` - _BOOLEAN_, true if the partition is stored in Parquet format:
  the native files have been removed and reads are served from the Parquet file.
  Set the same way as `hasParquetGenerated`: either manually or by a storage
  policy's `TO PARQUET` stage
- `parquetFileSize` - _LONG_, size in bytes of the partition's `data.parquet`
  file when `hasParquetGenerated` or `isParquet` is true; `-1` otherwise
- `seqTxn` - _LONG_, WAL transaction version the partition was last written at
- `isRemotelyServed` - _BOOLEAN_, true if the partition's data lives in object
  storage and is fetched with range reads. Only local Parquet metadata and
  symbol indexes remain on disk. A remotely served partition is always
  read-only. See [cold storage](/docs/concepts/cold-storage/) (Enterprise)

- `deadRows` - _LONG_, physical rows no longer referenced by live pieces;
  zero for a standard native partition
- `lastWriteTimestamp` - _TIMESTAMP_, wall-clock time of the last merge-append
  layout update, including compaction, in microseconds regardless of the
  designated timestamp's resolution; null when unavailable
- `pieceCount` - _INT_, number of pieces sharing the partition's column files;
  one for a standard native partition

These columns also appear in `SHOW PARTITIONS` and are null when unavailable,
such as for detached partitions. See
[monitoring merge-append partitions](/docs/concepts/merge-append-partitions/#monitor-partitions)
for an example. Attached Parquet partitions report `deadRows=0`,
`pieceCount=1` and a null `lastWriteTimestamp`; these columns do not measure
Parquet dead row-group space.

**Examples:**

```questdb-sql title="Create table my_table"
CREATE TABLE my_table AS (
    SELECT
        rnd_symbol('EURO', 'USD', 'OTHER') symbol,
        rnd_double() * 50.0 price,
        rnd_double() * 20.0 amount,
        to_timestamp('2023-01-01', 'yyyy-MM-dd') + x * 6 * 3600 * 100000L timestamp
    FROM long_sequence(700)
), INDEX(symbol capacity 32) TIMESTAMP(timestamp) PARTITION BY WEEK;
```

```questdb-sql title="Get all partitions from my_table"
table_partitions('my_table');
```

Selected columns are shown below; the three new trailing columns are omitted.

| index | partitionBy | name     | minTimestamp          | maxTimestamp          | numRows | diskSize | diskSizeHuman | readOnly | active | attached | detached | attachable | hasParquetGenerated | isParquet | parquetFileSize | seqTxn | isRemotelyServed |
| ----- | ----------- | -------- | --------------------- | --------------------- | ------- | -------- | ------------- | -------- | ------ | -------- | -------- | ---------- | ------------------- | --------- | --------------- | ------ | ---------------- |
| 0     | WEEK        | 2022-W52 | 2023-01-01 00:36:00.0 | 2023-01-01 23:24:00.0 | 39      | 98304    | 96.0 KiB      | false    | false  | true     | false    | false      | false               | false     | -1              | 12     | false            |
| 1     | WEEK        | 2023-W01 | 2023-01-02 00:00:00.0 | 2023-01-08 23:24:00.0 | 280     | 98304    | 96.0 KiB      | false    | false  | true     | false    | false      | false               | false     | -1              | 34     | false            |
| 2     | WEEK        | 2023-W02 | 2023-01-09 00:00:00.0 | 2023-01-15 23:24:00.0 | 280     | 98304    | 96.0 KiB      | false    | false  | true     | false    | false      | false               | false     | -1              | 56     | false            |
| 3     | WEEK        | 2023-W03 | 2023-01-16 00:00:00.0 | 2023-01-18 12:00:00.0 | 101     | 83902464 | 80.0 MiB      | false    | true   | true     | false    | false      | false               | false     | -1              | 78     | false            |

```questdb-sql title="Get size of a table in disk"
SELECT size_pretty(sum(diskSize)) FROM table_partitions('my_table');
```

| size_pretty |
| ----------- |
| 80.3 MB     |

```questdb-sql title="Get active partition of a table"
SELECT * FROM table_partitions('my_table') WHERE active = true;
```

Selected columns are shown below; the three new trailing columns are omitted.

| index | partitionBy | name     | minTimestamp          | maxTimestamp          | numRows | diskSize | diskSizeHuman | readOnly | active | attached | detached | attachable | hasParquetGenerated | isParquet | parquetFileSize | seqTxn | isRemotelyServed |
| ----- | ----------- | -------- | --------------------- | --------------------- | ------- | -------- | ------------- | -------- | ------ | -------- | -------- | ---------- | ------------------- | --------- | --------------- | ------ | ---------------- |
| 3     | WEEK        | 2023-W03 | 2023-01-16 00:00:00.0 | 2023-01-18 12:00:00.0 | 101     | 83902464 | 80.0 MiB      | false    | true   | true     | false    | false      | false               | false     | -1              | 78     | false            |

## table_storage

`table_storage()` - Returns information about the storage and structure of all
user tables and materialized views in the database.

Provides detailed storage information about all user tables and materialized
views within QuestDB. It returns one row per table, including information about
partitioning, row counts, and disk usage.

- The `table_storage()` function excludes system tables; it only lists
  user-created tables.
- The `diskSize` value represents the total size of all files associated with
  the table on disk, including data, index, and metadata files.
- The `partitionBy` column indicates the partitioning strategy used for the
  table. It can be `NONE` if the table is not partitioned.

**Return values:**

The function returns the following columns:

- `tableName` (`string`): The name of the table or materialized view.
- `walEnabled` (`boolean`): Indicates whether Write-Ahead Logging (WAL) is
  enabled for the table.
- `partitionBy` (`string`): The partitioning type of the table (e.g., NONE, DAY,
  MONTH, YEAR, etc.).
- `partitionCount` (`long`): The number of partitions the table has.
- `rowCount` (`long`): The total number of rows in the table.
- `diskSize` (`long`): The total disk space used by the table, in bytes.

**Examples:**

Retrieve storage information for all tables.

```questdb-sql title="Checking our demo tables" demo
SELECT * FROM table_storage();
```

- The query retrieves storage details for all tables in the database.
- The `diskSize` column shows the total disk space used by each table in bytes.

| tableName      | walEnabled | partitionBy | partitionCount | rowCount   | diskSize     |
| -------------- | ---------- | ----------- | -------------- | ---------- | ------------ |
| trips          | true       | MONTH       | 126            | 1634599313 | 261536158948 |
| AAPL_orderbook | true       | HOUR        | 16             | 3024878    | 2149403527   |
| weather        | false      | NONE        | 1              | 137627     | 9972598      |
| trades         | true       | DAY         | 954            | 1000848308 | 32764798760  |
| ethblocks_json | true       | DAY         | 3328           | 20688364   | 28311960478  |

<hr />

Filter tables with WAL enabled.

```questdb-sql title="WAL only tables" demo
SELECT tableName, rowCount, diskSize
FROM table_storage()
WHERE walEnabled = true;
```

| tableName      | rowCount   | diskSize     |
| -------------- | ---------- | ------------ |
| trips          | 1634599313 | 261536158948 |
| AAPL_orderbook | 3024878    | 2149403527   |
| trades         | 1000850255 | 32764804264  |
| ethblocks_json | 20688364   | 28311960478  |

<hr />

Show tables partitioned by `HOUR`.

```questdb-sql title="Show tables partitioned by hour" demo
SELECT tableName, partitionCount, rowCount
FROM table_storage()
WHERE partitionBy = 'HOUR';
```

## tables

`tables()` returns metadata and real-time statistics for all tables in the
database, including write activity, WAL status, and performance metrics.

**Arguments:**

- `tables()` does not require arguments.

**Return value:**

Returns a `table` with the following columns:

### Basic table information

| Column                | Type    | Description                                                        |
| --------------------- | ------- | ------------------------------------------------------------------ |
| `id`                  | INT     | Internal table ID                                                  |
| `table_name`          | STRING  | Table name                                                         |
| `designatedTimestamp` | STRING  | Name of the designated timestamp column, or `null`                 |
| `partitionBy`         | STRING  | Partition strategy: `NONE`, `HOUR`, `DAY`, `WEEK`, `MONTH`, `YEAR` |
| `walEnabled`          | BOOLEAN | Whether WAL (Write-Ahead Log) is enabled                           |
| `dedup`               | BOOLEAN | Whether deduplication is enabled                                   |
| `ttlValue`            | INT     | TTL (Time-To-Live) value                                           |
| `ttlUnit`             | STRING  | TTL unit: `HOUR`, `DAY`, `WEEK`, `MONTH`, `YEAR`                   |
| `matView`             | BOOLEAN | Whether this is a materialized view                                |
| `directoryName`       | STRING  | Directory name on disk (includes ` (->)` suffix for symlinks)      |
| `maxUncommittedRows`  | INT     | Table's `maxUncommittedRows` setting                               |
| `o3MaxLag`            | LONG    | Table's `o3MaxLag` setting in microseconds                         |

:::note

`(->)` means the table was created using the
[IN VOLUME](/docs/query/sql/create-table/#table-target-volume) clause.

:::

### Table metrics (table\_\* prefix)

| 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), `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)                             |
| `table_last_write_timestamp`  | TIMESTAMP | Approximate timestamp of last TableWriter commit                                          |
| `table_txn`                   | LONG      | TableWriter transaction number at last tracked write                                      |
| `table_memory_pressure_level` | INT       | Memory pressure: `0` (none), `1` (reduced parallelism), `2` (backoff). `null` for non-WAL |
| `table_write_amp_count`       | LONG      | Total write amplification samples recorded                                                |
| `table_write_amp_p50`         | DOUBLE    | Median write amplification ratio                                                          |
| `table_write_amp_p90`         | DOUBLE    | 90th percentile ratio                                                                     |
| `table_write_amp_p99`         | DOUBLE    | 99th percentile ratio                                                                     |
| `table_write_amp_max`         | DOUBLE    | Maximum ratio                                                                             |
| `table_merge_rate_count`      | LONG      | Total merge throughput samples recorded                                                   |
| `table_merge_rate_p50`        | LONG      | Median throughput in rows/second                                                          |
| `table_merge_rate_p90`        | LONG      | Throughput that 90% of jobs **exceeded** (slowest 10%)                                    |
| `table_merge_rate_p99`        | LONG      | Throughput that 99% of jobs **exceeded** (slowest 1%)                                     |
| `table_merge_rate_max`        | LONG      | Maximum throughput in rows/second                                                         |

Write amplification measures O3 (out-of-order) merge overhead as
`physicalRowsWritten / logicalRows`. A ratio of `1.0` means no amplification.
Higher values indicate O3 merge overhead.

:::note

Merge rate P99 shows the _lowest_ throughput (worst performance), not the
highest.

:::

### WAL metrics (wal\_\* prefix)

| Column                            | Type      | Description                                                   |
| --------------------------------- | --------- | ------------------------------------------------------------- |
| `wal_pending_row_count`           | LONG      | Rows written to WAL but not yet applied to table              |
| `wal_dedup_row_count_since_start` | LONG      | Cumulative rows removed by deduplication (since server start) |
| `wal_txn`                         | LONG      | WAL sequencer transaction number (WAL tables only)            |
| `wal_max_timestamp`               | TIMESTAMP | Max data timestamp from last WAL commit (WAL tables only)     |
| `wal_tx_count`                    | LONG      | Total WAL transactions recorded (since server start)          |
| `wal_tx_size_p50`                 | LONG      | Median transaction size in rows                               |
| `wal_tx_size_p90`                 | LONG      | 90th percentile transaction size in rows                      |
| `wal_tx_size_p99`                 | LONG      | 99th percentile transaction size in rows                      |
| `wal_tx_size_max`                 | LONG      | Maximum transaction size in rows                              |

### Replica metrics (replica\_\* prefix)

These columns are populated on **replicas only** via replication download
tracking:

| Column                   | Type    | Description                                                              |
| ------------------------ | ------- | ------------------------------------------------------------------------ |
| `replica_batch_count`    | LONG    | Total download batches recorded                                          |
| `replica_batch_size_p50` | LONG    | Median batch size in rows                                                |
| `replica_batch_size_p90` | LONG    | 90th percentile batch size                                               |
| `replica_batch_size_p99` | LONG    | 99th percentile batch size                                               |
| `replica_batch_size_max` | LONG    | Maximum batch size                                                       |
| `replica_more_pending`   | BOOLEAN | `true` if the last download batch was limited and more data is available |

On primary instances, these columns will be `0` or `false`.

### Data precision and limitations

These values are approximations, not precise real-time metrics:

- **Null when not tracked**: Values are `null` for tables not written to since
  server start, or evicted from the tracker
- **Writer stats updated on pool return**: `table_row_count`,
  `table_last_write_timestamp`, `table_txn` are captured when TableWriter
  returns to the pool, not on every commit. A writer held for a long time won't
  update these columns until released.
- **WAL stats updated in real-time**:
  - On WAL commit: `wal_pending_row_count` (incremented), `wal_txn`,
    `wal_max_timestamp`, `wal_tx_size_*` histogram
  - On WAL apply: `wal_pending_row_count` (decremented),
    `wal_dedup_row_count_since_start`, `table_min_timestamp`,
    `table_max_timestamp`, `table_write_amp_*`, `table_merge_rate_*`
- **LRU eviction**: Tracker maintains bounded memory (default 1000 tables).
  Least recently written tables are evicted when capacity is exceeded
- **Startup hydration**: Values are hydrated from table metadata (`TxReader`) on
  startup, but diverge as writes occur

**Non-WAL tables**: `wal_txn`, `wal_max_timestamp`, `wal_pending_row_count`,
`wal_dedup_row_count_since_start`, `table_min_timestamp`, `table_max_timestamp`,
`table_memory_pressure_level`, and histogram columns are `null` or `0`.

**WAL tables**: All columns populated when tracked. `wal_max_timestamp` reflects
the max data timestamp from the WAL transaction, not wall-clock time.
`table_min_timestamp` and `table_max_timestamp` reflect the actual data range in
the table after WAL merge.

### Configuration

| Property                              | Default | Description                      |
| ------------------------------------- | ------- | -------------------------------- |
| `cairo.recent.write.tracker.capacity` | 1000    | Maximum number of tables tracked |

Tables exceeding this capacity are evicted based on least recent write activity.

**Examples:**

```questdb-sql title="List all tables"
tables();
```

| id  | table_name  | designatedTimestamp | partitionBy | walEnabled | directoryName    | dedup | ttlValue | ttlUnit | matView |
| --- | ----------- | ------------------- | ----------- | ---------- | ---------------- | ----- | -------- | ------- | ------- |
| 1   | my_table    | ts                  | DAY         | false      | my_table         | false | 0        | HOUR    | false   |
| 2   | device_data | null                | NONE        | false      | device_data      | false | 0        | HOUR    | false   |
| 3   | short_lived | null                | HOUR        | false      | short_lived (->) | false | 1        | HOUR    | false   |

```questdb-sql title="All tables with a daily partitioning strategy"
tables() WHERE partitionBy = 'DAY';
```

| id  | table_name | designatedTimestamp | partitionBy | walEnabled | directoryName | dedup | ttlValue | ttlUnit | matView |
| --- | ---------- | ------------------- | ----------- | ---------- | ------------- | ----- | -------- | ------- | ------- |
| 1   | my_table   | ts                  | DAY         | true       | my_table      | false | 0        | HOUR    | false   |

```questdb-sql title="Recently written tables"
SELECT table_name, table_row_count, table_last_write_timestamp
FROM tables()
WHERE table_last_write_timestamp IS NOT NULL
ORDER BY table_last_write_timestamp DESC
LIMIT 10;
```

```questdb-sql title="Tables with most recent data"
SELECT table_name, table_min_timestamp, table_max_timestamp
FROM tables()
WHERE table_max_timestamp IS NOT NULL
ORDER BY table_max_timestamp DESC
LIMIT 10;
```

```questdb-sql title="WAL apply lag"
SELECT
    table_name,
    wal_txn - table_txn AS pending_txns,
    wal_pending_row_count
FROM tables()
WHERE walEnabled
  AND wal_txn IS NOT NULL
  AND wal_txn > table_txn
ORDER BY pending_txns DESC;
```

```questdb-sql title="Suspended tables"
SELECT table_name, table_suspended, table_memory_pressure_level
FROM tables()
WHERE walEnabled AND table_suspended;
```

```questdb-sql title="Memory pressure"
SELECT table_name, table_memory_pressure_level
FROM tables()
WHERE table_memory_pressure_level > 0
ORDER BY table_memory_pressure_level DESC;
```

```questdb-sql title="Deduplication activity"
SELECT table_name, wal_dedup_row_count_since_start, wal_tx_count,
       wal_dedup_row_count_since_start * 100.0 / NULLIF(wal_tx_count, 0) AS dedup_ratio
FROM tables()
WHERE wal_dedup_row_count_since_start > 0
ORDER BY wal_dedup_row_count_since_start DESC;
```

```questdb-sql title="Large transactions"
SELECT table_name, wal_tx_count, wal_tx_size_p50, wal_tx_size_p99, wal_tx_size_max
FROM tables()
WHERE walEnabled AND wal_tx_count > 0
ORDER BY wal_tx_size_p99 DESC;
```

```questdb-sql title="High variance (spiky workloads)"
SELECT table_name, wal_tx_size_p50, wal_tx_size_max,
       wal_tx_size_max / NULLIF(wal_tx_size_p50, 0) AS spike_ratio
FROM tables()
WHERE walEnabled AND wal_tx_size_max > wal_tx_size_p50 * 10;
```

```questdb-sql title="High write amplification (O3 overhead)"
SELECT table_name,
       table_write_amp_count,
       table_write_amp_p50,
       table_write_amp_p99,
       table_write_amp_max
FROM tables()
WHERE walEnabled
  AND table_write_amp_p50 > 2.0
ORDER BY table_write_amp_p99 DESC;
```

```questdb-sql title="Slow merge performance"
SELECT table_name,
       table_merge_rate_p99 AS slowest_throughput,
       table_merge_rate_p50 AS median_throughput,
       table_merge_rate_max AS best_throughput
FROM tables()
WHERE walEnabled
  AND table_merge_rate_count > 0
ORDER BY table_merge_rate_p99 ASC;
```

```questdb-sql title="Replica download statistics"
SELECT table_name, replica_batch_count, replica_batch_size_p50,
       replica_batch_size_p90, replica_batch_size_max, replica_more_pending
FROM tables()
WHERE replica_batch_count > 0
ORDER BY replica_batch_count DESC;
```

```questdb-sql title="Table configuration overview"
SELECT table_name,
       partitionBy,
       walEnabled,
       dedup,
       maxUncommittedRows,
       o3MaxLag / 1000000 AS o3MaxLagSeconds
FROM tables()
WHERE walEnabled
ORDER BY table_name;
```

```questdb-sql title="Health dashboard query"
SELECT
    table_name,
    table_row_count,
    wal_pending_row_count,
    CASE
        WHEN table_suspended THEN 'SUSPENDED'
        WHEN table_memory_pressure_level = 2 THEN 'BACKOFF'
        WHEN table_memory_pressure_level = 1 THEN 'PRESSURE'
        ELSE 'OK'
    END AS status,
    wal_txn - table_txn AS lag_txns,
    table_write_amp_p50 AS write_amp,
    table_merge_rate_p99 AS slowest_merge
FROM tables()
WHERE walEnabled
ORDER BY
    table_suspended DESC,
    table_memory_pressure_level DESC,
    wal_pending_row_count DESC;
```

## version/pg_catalog.version

`version()` or `pg_catalog.version()` returns the supported version of the
PostgreSQL Wire Protocol.

**Arguments:**

- `version()` or `pg_catalog.version()` does not require arguments.

**Return value:**

Returns `string`.

**Examples:**

```questdb-sql
SELECT version();

--The above equals to:

SELECT pg_catalog.version();
```

| version                                                             |
| ------------------------------------------------------------------- |
| PostgreSQL 12.3, compiled by Visual C++ build 1914, 64-bit, QuestDB |

## views

`views()` returns the list of all views in the database.

**Arguments:**

- `views()` does not require arguments.

**Return value:**

Returns a `table` including the following information:

- `view_name` - view name
- `view_sql` - query used to define the view (without CREATE VIEW wrapper)
- `view_table_dir_name` - internal directory name
- `invalidation_reason` - message explaining why the view was marked as invalid,
  empty if valid
- `view_status` - view status: `valid` or `invalid`
- `view_status_update_time` - timestamp of last status change

`views()` does not report whether a view is
[audited](/docs/security/audited-views/). Use
[`SHOW CREATE VIEW`](/docs/query/sql/show/#show-create-view) for that.

**Examples:**

```questdb-sql title="List all views"
views();
```

| view_name      | view_sql                                             | view_table_dir_name | invalidation_reason | view_status | view_status_update_time     |
| -------------- | ---------------------------------------------------- | ------------------- | ------------------- | ----------- | --------------------------- |
| hourly_summary | SELECT ts, symbol, sum(qty) FROM trades SAMPLE BY 1h | hourly_summary~1    |                     | valid       | 2025-05-30T10:15:00.000000Z |
| price_view     | SELECT symbol, last(price) FROM trades SAMPLE BY 1d  | price_view~2        |                     | valid       | 2025-05-30T10:20:00.000000Z |

```questdb-sql title="Find invalid views"
SELECT view_name, invalidation_reason
FROM views()
WHERE view_status = 'invalid';
```

```questdb-sql title="List views ordered by name"
SELECT * FROM views() ORDER BY view_name;
```

## wait_wal_table

Blocks until the WAL writer for a table has applied its transactions up to a
target sequencer transaction, then returns `true`.

Writes to a [WAL table](/docs/concepts/write-ahead-log/) are committed to the
sequencer first and applied to the table afterwards, so a read issued
immediately after a write may not see it yet. `wait_wal_table()` closes that
window: call it between the write and the read when the read depends on the
write having landed.

The call does not hold a worker thread while it waits, so the number of
concurrent waiters is not bounded by the shared worker pool.

**Arguments:**

- `tableName` (`string`): name of the table to wait for. Must be a constant, not
  a column reference. On a non-WAL table the call returns `true` immediately.
- `seqTxn` (optional, `long`): the sequencer transaction to wait for. When
  omitted, the call captures the table's current `seqTxn` when it starts and
  waits for that, which is what you want after your own write.

**Return value:**

Returns `boolean`. `true` once the writer has caught up.

Throws if the table is dropped while the call is waiting, and if the table
becomes [suspended](/docs/query/sql/alter-table-resume-wal/), since a suspended
table would otherwise never catch up.

**Examples:**

```questdb-sql title="Wait for a table's pending writes to be applied"
SELECT wait_wal_table('trades');
```

| wait_wal_table |
| -------------- |
| true           |

Wait for one specific transaction. `seqTxn` values are visible in
[`wal_tables()`](#wal_tables), where `sequencerTxn` is the last committed
transaction and `writerTxn` the last applied one:

```questdb-sql title="Wait for a specific transaction"
SELECT wait_wal_table('trades', 42);
```

## wal_tables

:::note

For monitoring and observability, use [`tables()`](#tables) instead. `tables()`
provides the same status information plus additional metrics (pending rows,
memory pressure, deduplication stats, throughput histograms), and is fully
in-memory. `wal_tables()` reads from disk and is less suitable for frequent
polling, but it is the only function that reports the `errorTag` and
`errorMessage` of a suspended table.

:::

`wal_tables()` returns the WAL status for all
[WAL tables](/docs/concepts/write-ahead-log/), materialized views, and live
views in the database. In QuestDB Enterprise, it lists the objects the caller
[can see](/docs/security/rbac/#object-visibility), and also the protected
access control tables to principals holding `DATABASE ADMIN` or
`SYSTEM ADMIN`, so that they can recover them.

**Arguments:**

- `wal_tables()` does not require arguments.

**Return value:**

Returns a `table` including the following information:

- `name` - table, materialized view, or live view name
- `suspended` - suspended status flag
- `writerTxn` - the last committed transaction in TableWriter (equivalent to
  `table_txn` in `tables()`)
- `bufferedTxnSize` - the number of transactions that are kept invisible when
  writing to the table; these transactions will be eventually moved to the table
  data and become visible for readers (equivalent to `wal_txn - table_txn`)
- `sequencerTxn` - the last committed transaction in the sequencer (equivalent
  to `wal_txn` in `tables()`)
- `errorTag` - short classification of the error that suspended the table, such
  as `OUT OF MEMORY` when a WAL apply batch breached its
  [memory limit](/docs/configuration/cairo-engine/#memory-limits), or empty when
  the table is not suspended
- `errorMessage` - full text of the error that suspended the table, or empty
  when the table is not suspended
- `memoryPressure` - memory pressure level of the table writer: `0` for none,
  `1` when parallelism is reduced, `2` when the writer backs off between
  attempts (equivalent to `table_memory_pressure_level` in `tables()`)

**Examples:**

```questdb-sql title="List all tables"
wal_tables();
```

| name        | suspended | writerTxn | bufferedTxnSize | sequencerTxn | errorTag      | errorMessage                                                                                                                | memoryPressure |
| ----------- | --------- | --------- | --------------- | ------------ | ------------- | --------------------------------------------------------------------------------------------------------------------------- | -------------- |
| sensor_wal  | false     | 2         | 1               | 4            |               |                                                                                                                             | 0              |
| weather_wal | false     | 3         | 0               | 3            |               |                                                                                                                             | 0              |
| test_wal    | true      | 7         | 1               | 9            | OUT OF MEMORY | query memory limit exceeded [workload=WAL_APPLY, queryId=12, limit=1073741824, used=1073217536, size=1048576, memoryTag=27] | 2              |

## writer_pool

**Arguments:**

- `writer_pool()` does not require arguments.

**Return value:**

Returns information about the current state of the writer pool in QuestDB. The
writer pool is a cache of table writers that are kept open to speed up
subsequent writes to the same table. The returned information includes the table
name, the ID of the thread that currently owns the writer, the timestamp of the
last time the writer was accessed, and the reason for the ownership.

In QuestDB Enterprise, `writer_pool()` requires the `SYSTEM ADMIN` permission,
because the pool lists tables whether or not the caller
[can see](/docs/security/rbac/#object-visibility) them.

**Examples:**

```questdb-sql
SELECT * FROM writer_pool();
```

| table_name                    | owner_thread_id | last_access_timestamp       | ownership_reason |
| ----------------------------- | --------------- | --------------------------- | ---------------- |
| sys.column_versions_purge_log | 1               | 2023-12-01T18:50:03.412468Z | QuestDB system   |
| telemetry_config              | 1               | 2023-12-01T18:50:03.470604Z | telemetryConfig  |
| telemetry                     | 1               | 2023-12-01T18:50:03.464501Z | telemetry        |
| sys.telemetry_wal             | 1               | 2023-12-01T18:50:03.467924Z | telemetry        |
| example_table                 | null            | 2023-12-01T20:33:33.270984Z | null             |
