database
The database worker connects to PostgreSQL, MySQL, and SQLite through a
managed per-database connection pool. Every callable surface lives under
the database::* namespace. The driver is chosen from each database URL
scheme (sqlite:, postgres://, postgresql://, mysql://).
Runtime settings live in the configuration worker under id database;
pools hot-reload when the value changes. SQLite is the recommended starting
point. Placeholder syntax: ? for SQLite and MySQL, $1/$2/… for Postgres.
When to Use
- You need to read rows from a configured database (
database::query).
- You need to insert, update, delete, or run DDL and read affected-row
counts or autoincrement ids (
database::execute).
- Several statements must commit or roll back together as one unit
(
database::transaction, database::executeBatch, or the interactive
transaction surface).
- The same parameterized SQL will run many times and you want to skip
per-call parse/plan cost (
database::prepareStatement +
database::runStatement).
- You need read-your-writes across round-trips with logic between steps
(
database::beginTransaction … commitTransaction / rollbackTransaction).
Boundaries
- Not a migration tool, ORM, or schema designer — pass raw SQL only.
- Not a general pub/sub bus.
database::row-changed reports only what THIS
worker wrote, on commit — not change data capture; a write from psql or
another worker is invisible to it.
database::query is read-oriented; use database::execute for writes.
Running a SELECT through execute discards rows.
- Prepared handles pin a pool connection until TTL expiry — not transactions.
Batch
database::transaction / database::executeBatch need every
statement up front; use the interactive surface when code must branch
between steps.
- MySQL ignores the
returning option on execute (warn-once). SQLite
degrades read_committed / repeatable_read isolation to serializable.
- For filesystem or shell operations, use the
shell worker instead.
Functions
database::query — run read-only SQL and return rows, row count, and
column metadata.
database::execute — run write SQL (INSERT/UPDATE/DELETE/DDL) and
return affected rows, optional last insert id, and optional RETURNING rows.
database::executeBatch — convenience form of transaction: statements
may be bare SQL strings or {sql, params} objects (prefer params for
dynamic values). Same atomic semantics, envelope, and failed_index
reporting as transaction.
database::prepareStatement — parse and plan SQL once; return a handle
that pins a pool connection until TTL expiry.
database::runStatement — re-execute a prepared handle with new bind
params; response shape matches query.
database::transaction — run an ordered batch of statements atomically;
rolls back on first failure and reports failed_index.
database::beginTransaction — open an interactive transaction and
return an id plus expiry deadline.
database::transactionQuery — read SQL inside an open interactive
transaction; same envelope as query.
database::transactionExecute — write SQL inside an open interactive
transaction; same envelope as execute. Rejects bare transaction-control
SQL — finalize via commitTransaction or rollbackTransaction.
database::commitTransaction — commit and finalize an interactive
transaction.
database::rollbackTransaction — roll back and finalize an interactive
transaction.
database::listDatabases — every configured database with its driver,
credential-redacted URL, pool settings and TLS mode. Config only; use
database::health for live state.
Interactive transactions auto-roll back when timeout_ms elapses (default
30 s, max 5 min). Prepared handles default to a 1 h TTL (max 24 h) with no
explicit release call — let them expire or stop using them when done.
Reading the schema
One shape across all three drivers — prefer these over hand-writing
sqlite_master / information_schema / PRAGMA.
database::listTables — tables and views, with kind and (postgres) schema.
database::describeTable — columns with type, nullability, default, primary
key and a structured foreign_key of { schema, table, column }; plus
indexes and a planner row estimate.
database::describeSchema — the same for every table in one pass. Use this
rather than looping describeTable.
database::schemaDiagram — positioned nodes, routed foreign-key edges, hub
degree and isolated tables. For reasoning about a schema's shape, not
only for drawing it.
Reading data
database::browseTable — paged, sorted, filtered reads with no SQL. Filters
are { column, op, value } and total honours them. Follow a foreign key
with an equality filter at page_size: 1.
database::explain — the plan as a tree with costs and warnings. analyze
runs the statement, so it defaults to false and is refused for anything
that is not a single read.
database::columnStats — planner statistics by default (approximate,
labelled source: planner); exact: true scans. To profile rows you already
hold, use the fp worker on a browseTable result instead.
Operations and reuse
database::health — pool occupancy, active queries, table sizes, locks,
cache ratio. Each section is available, unsupported or denied, so a
driver gap is never mistaken for an empty result.
database::terminateQuery — end a session, or cancel its statement with
cancel_only. Takes an id from database::health.
database::saveQuery, database::listSavedQueries,
database::deleteSavedQuery — named queries per database, kept in the
state worker.
database::history — recent queries, newest first. Best effort, not an audit
log; bind database::row-changed for that.
Reacting to writes
Register a database::row-changed trigger to be told when this worker commits
a change, instead of polling:
{ "trigger_type": "database::row-changed", "config": { "db": "primary", "table": "orders", "ops": ["insert"] } }
The event is { db, table, op, affected_rows, returning?, at }. It fires on
commit — an interactive transaction's writes are announced by
commitTransaction, and a rollback announces nothing. table is null when the
statement's table cannot be read off the SQL (a CTE-wrapped write), and
runStatement does not fire because it has no affected-row count to report.
Delivery is best-effort: it is not durable with the commit and has no replay or
exactly-once guarantee.
1---2name: database3description: Run SQL against PostgreSQL, MySQL, or SQLite from the iii engine — reads, writes, transactions, and prepared statements over managed connection pools.4---56# database78The database worker connects to PostgreSQL, MySQL, and SQLite through a9managed per-database connection pool. Every callable surface lives under10the `database::*` namespace. The driver is chosen from each database URL11scheme (`sqlite:`, `postgres://`, `postgresql://`, `mysql://`).1213Runtime settings live in the `configuration` worker under id `database`;14pools hot-reload when the value changes. SQLite is the recommended starting15point. Placeholder syntax: `?` for SQLite and MySQL, `$1`/`$2`/… for Postgres.1617## When to Use1819- You need to read rows from a configured database (`database::query`).20- You need to insert, update, delete, or run DDL and read affected-row21 counts or autoincrement ids (`database::execute`).22- Several statements must commit or roll back together as one unit23 (`database::transaction`, `database::executeBatch`, or the interactive24 transaction surface).25- The same parameterized SQL will run many times and you want to skip26 per-call parse/plan cost (`database::prepareStatement` +27 `database::runStatement`).28- You need read-your-writes across round-trips with logic between steps29 (`database::beginTransaction` … `commitTransaction` / `rollbackTransaction`).3031## Boundaries3233- Not a migration tool, ORM, or schema designer — pass raw SQL only.34- Not a general pub/sub bus. `database::row-changed` reports only what THIS35 worker wrote, on commit — not change data capture; a write from psql or36 another worker is invisible to it.37- `database::query` is read-oriented; use `database::execute` for writes.38 Running a SELECT through `execute` discards rows.39- Prepared handles pin a pool connection until TTL expiry — not transactions.40 Batch `database::transaction` / `database::executeBatch` need every41 statement up front; use the interactive surface when code must branch42 between steps.43- MySQL ignores the `returning` option on `execute` (warn-once). SQLite44 degrades `read_committed` / `repeatable_read` isolation to serializable.45- For filesystem or shell operations, use the `shell` worker instead.4647## Functions4849- `database::query` — run read-only SQL and return rows, row count, and50 column metadata.51- `database::execute` — run write SQL (INSERT/UPDATE/DELETE/DDL) and52 return affected rows, optional last insert id, and optional RETURNING rows.53- `database::executeBatch` — convenience form of `transaction`: statements54 may be bare SQL strings or `{sql, params}` objects (prefer `params` for55 dynamic values). Same atomic semantics, envelope, and `failed_index`56 reporting as `transaction`.57- `database::prepareStatement` — parse and plan SQL once; return a handle58 that pins a pool connection until TTL expiry.59- `database::runStatement` — re-execute a prepared handle with new bind60 params; response shape matches `query`.61- `database::transaction` — run an ordered batch of statements atomically;62 rolls back on first failure and reports `failed_index`.63- `database::beginTransaction` — open an interactive transaction and64 return an id plus expiry deadline.65- `database::transactionQuery` — read SQL inside an open interactive66 transaction; same envelope as `query`.67- `database::transactionExecute` — write SQL inside an open interactive68 transaction; same envelope as `execute`. Rejects bare transaction-control69 SQL — finalize via `commitTransaction` or `rollbackTransaction`.70- `database::commitTransaction` — commit and finalize an interactive71 transaction.72- `database::rollbackTransaction` — roll back and finalize an interactive73 transaction.74- `database::listDatabases` — every configured database with its driver,75 credential-redacted URL, pool settings and TLS mode. Config only; use76 `database::health` for live state.7778Interactive transactions auto-roll back when `timeout_ms` elapses (default7930 s, max 5 min). Prepared handles default to a 1 h TTL (max 24 h) with no80explicit release call — let them expire or stop using them when done.8182### Reading the schema8384One shape across all three drivers — prefer these over hand-writing85`sqlite_master` / `information_schema` / `PRAGMA`.8687- `database::listTables` — tables and views, with kind and (postgres) schema.88- `database::describeTable` — columns with type, nullability, default, primary89 key and a structured `foreign_key` of `{ schema, table, column }`; plus90 indexes and a planner row estimate.91- `database::describeSchema` — the same for every table in one pass. Use this92 rather than looping `describeTable`.93- `database::schemaDiagram` — positioned nodes, routed foreign-key edges, hub94 `degree` and `isolated` tables. For reasoning about a schema's shape, not95 only for drawing it.9697### Reading data9899- `database::browseTable` — paged, sorted, filtered reads with no SQL. Filters100 are `{ column, op, value }` and `total` honours them. Follow a foreign key101 with an equality filter at `page_size: 1`.102- `database::explain` — the plan as a tree with costs and warnings. `analyze`103 **runs** the statement, so it defaults to false and is refused for anything104 that is not a single read.105- `database::columnStats` — planner statistics by default (approximate,106 labelled `source: planner`); `exact: true` scans. To profile rows you already107 hold, use the `fp` worker on a `browseTable` result instead.108109### Operations and reuse110111- `database::health` — pool occupancy, active queries, table sizes, locks,112 cache ratio. Each section is `available`, `unsupported` or `denied`, so a113 driver gap is never mistaken for an empty result.114- `database::terminateQuery` — end a session, or cancel its statement with115 `cancel_only`. Takes an id from `database::health`.116- `database::saveQuery`, `database::listSavedQueries`,117 `database::deleteSavedQuery` — named queries per database, kept in the118 `state` worker.119- `database::history` — recent queries, newest first. Best effort, not an audit120 log; bind `database::row-changed` for that.121122## Reacting to writes123124Register a `database::row-changed` trigger to be told when this worker commits125a change, instead of polling:126127```json128{ "trigger_type": "database::row-changed", "config": { "db": "primary", "table": "orders", "ops": ["insert"] } }129```130131The event is `{ db, table, op, affected_rows, returning?, at }`. It fires on132commit — an interactive transaction's writes are announced by133`commitTransaction`, and a rollback announces nothing. `table` is null when the134statement's table cannot be read off the SQL (a CTE-wrapped write), and135`runStatement` does not fire because it has no affected-row count to report.136Delivery is best-effort: it is not durable with the commit and has no replay or137exactly-once guarantee.