---
title: "Database (Live Tables) in the SDK"
canonical: https://trickest.com/docs/developer-tools/sdk/database
description: "client.database; list tables, TQL query, queryAll, schema, row CRUD, and detect tables from runs."
---

# Database (Live Tables) in the SDK

Examples use a server-side `client` from
[Client setup](/docs/developer-tools/sdk/client-setup), and resource IDs supplied
by your application.

Use **`client.database`** to query and manage **Live Tables**, structured datasets
written by workflows and solutions.

<Info>
  **TQL in the UI:**
  [Querying](/docs/using-the-app/database-management/querying). **Concepts:**
  [Solutions & Database](/docs/key-concepts/solutions-database). **CLI:**
  [Database](/docs/developer-tools/cli/database).
</Info>

`tableId` is the database table UUID, not a
solution ID or dataset ID. Discover tables by workflow instead of hardcoding
an ID that may change when a table is promoted again.

## Table operations

| Method                                                   | Description                                                      |
| -------------------------------------------------------- | ---------------------------------------------------------------- |
| `list(options?)`                                         | All tables                                                       |
| `listPage(options?)`                                     | One page                                                         |
| `listByWorkflow(workflowId, options?)`                   | Tables for a workflow                                            |
| `get(id, options?)`                                      | Table metadata                                                   |
| `getSchema(id, options?)`                                | `Column[]`                                                       |
| `resolveByName(name, workflowId?, options?)`             | Lookup by name → `Table \| null`                                 |
| `detect(workflowId, workflowName?, options?)`            | Scan run outputs for JSONL tables                                |
| `preview(id, limit?, options?)`                          | First N rows                                                     |
| `delete(id, options?)` / `drop(tableId, options?)`       | Remove table                                                     |
| `createLive(tableId, { workflowId, spaceId }, options?)` | Promote a detected table; inspect the result and backfill status |

## TQL query

TQL uses filters of the form `column op value`, joined with `AND` or `OR`.
It does not accept SQL statements. Supported comparison operators:

```text
=  !=  >  <  ~  !~
```

```ts
const result = await client.database.query(
  tableId,
  'severity = "critical" AND port > 443',
  {
    select: "host,port,severity",
    orderBy: "-_timestamp",
    limit: 100,
    offset: 0,
  },
);
// { columns, rows, row_count, total_count?, … }
```

| Guard          | Behavior                                                                            |
| -------------- | ----------------------------------------------------------------------------------- |
| `limit`        | Must be **1-500** per request or SDK throws `ValidationError`                       |
| SQL-shaped TQL | Rejects the returned promise before sending a request; use TQL filters + `queryAll` |

### Count rows

Use `limit: 1` and read **`row_count`**, which the SDK normalizes from the
server's `total_count` when available. This is the matching-row count;
`rows.length` is only the number of rows returned in this page.

```ts
const count = await client.database.query(tableId, 'severity = "critical"', {
  limit: 1,
});
console.log(count.row_count);
```

### Read all rows with `queryAll`

```ts
for await (const row of client.database.queryAll<{ host: string }>(
  tableId,
  'severity = "high"',
  { pageSize: 500, maxRows: 50_000 },
)) {
  console.log(row.host);
}
```

The iterator advances by the number of rows received. It stops on an empty or
short page, the reported count, or `maxRows`. It is pagination, not a snapshot: a
changing table can shift rows between requests. The generic row type is a
TypeScript assumption, not runtime validation.

## Row overlay CRUD

| Method                               | Description       |
| ------------------------------------ | ----------------- |
| `listRows(tableId, …)`               | List overlay rows |
| `getRow(tableId, rowId, …)`          | One row           |
| `insertRow(tableId, data, …)`        | Insert            |
| `updateRow(tableId, rowId, data, …)` | Update            |
| `deleteRow(tableId, rowId, …)`       | Delete            |

Each operation returns an envelope: `{ rows }` for listing and `{ row }` for
single-row operations. A `DatasetUserRow` has an `id`, a `payload` object with
your columns, and audit/deletion fields. `deleteRow` soft-deletes the row.

The current platform can reject reporting queries with HTTP 409 while local
overlay rows exist. Do not assume these writes immediately merge into the
reporting backend queried by `query()`.

**Types:** `Table`, `Column`, `QueryResult`, `DatasetUserRow`, `DetectionResult`

`resolveByName(name, workflowId)` prefers an exact match, then a name suffix.
If the workflow has no match, it searches the vault. Use the scoped list below
when a same-named table from another workflow would be the wrong input.

## End-to-end after a run

```ts
const tables = await client.database.listByWorkflow(workflowId);
const table = tables.find((candidate) => candidate.name === "http_probe");
if (!table) throw new Error("Table not found in this workflow");

const page = await client.database.query(table.id, "status_code = 200", {
  limit: 100,
});
for (const row of page.rows) console.log(row);
```

## Related

<CardGroup cols={2}>
  <Card
    title="Runs"
    icon="play"
    href="/docs/developer-tools/sdk/runs-schedules"
  >
    Runs populate Live Tables.
  </Card>
  <Card title="Solutions" icon="gem" href="/docs/developer-tools/sdk/solutions">
    Solution datasets back tables.
  </Card>
</CardGroup>

For an example of workflows feeding evidence into tables, see [npm and PyPI package scanning at scale](/blog/how-we-scan-thousands-of-packages).

---
_Markdown source of https://trickest.com/docs/developer-tools/sdk/database._
