DocsGuides
PowDB sandboxes
Pull a branch into a local PowDB copy for agents and offline work.
PowDB is an embedded database written in Rust. It
runs inside your process: no server, no network, no connection pool. That makes it a
good fit for one job: giving an AI agent a real, disposable copy of a database to read
and write, then throwing it away with rm -rf.
bata powdb pull makes that copy. It reads a BataDB branch's schema and data and writes
one local PowQL script (PowQL is PowDB's query language). You load the script into a
fresh PowDB data directory, and your agent works against it offline.
A pulled copy is not BataDB. Branching, point-in-time restore, the pooled and direct Postgres endpoints, query insights and metering do not apply to it. A pull is a one-time export you run locally. PowDB is not Postgres-compatible beyond the mapping on this page.
When to use it
- Agent sandboxes. Give an agent a real dataset it can change freely. Nothing it does reaches your BataDB branch.
- Offline reproduction. Pull a branch once and iterate without a network round trip per query.
- Throwaway experiments. Try a destructive change against real-shaped data, then delete the directory.
If you want a managed, networked database with branching, point-in-time restore and
insights, use a regular BataDB project (bata create) and connect over TCP or HTTP.
See Connecting.
Pull a branch
# Pull the primary branch of the linked or default project.
bata powdb pull --out ./sandbox.powql
# Or name the project and branch (no link needed):
bata powdb pull --project <project-id> --branch main --out ./sandbox.powql
# Cap the rows exported per table while you iterate:
bata powdb pull --project <project-id> --limit 1000 --out ./sandbox.powql| Flag | Meaning |
|---|---|
--project <id> | Source project (default: the linked or default project) |
--branch <name-or-id> | Source branch (default: the checked-out branch, else the primary) |
--out <path> | Where to write the script (default: ./<project-id>-<branch>.powql). - writes to stdout. |
--limit <n> | Export at most n rows per table |
--json | Print a machine-readable summary (below) |
pull works over the API: it reads the schema, then selects each table's rows. An idle
branch wakes for you, with no direct connection needed. While the branch's compute
wakes, pull exits 6 (COMPUTE_STARTING). Retry in a few seconds.
The key needs the sql scope. A pull reads every row over the API, so it counts toward
the source project's data transfer.
Load it into PowDB
Load the script into a fresh local data directory, then query the copy in-process:
powdb-cli --data-dir ./sandbox --exec-file ./sandbox.powql
# Then query the local copy, with no network:
powdb-cli --data-dir ./sandbox --exec 'users filter .email = "ada@example.com" { .id, .name }'Install PowDB with cargo install powdb-cli, or download a prebuilt binary from the
PowDB releases. The script needs PowDB
0.10.0 or later. An export with json or jsonb columns needs 0.12.0 or later.
The script's # Target: header line says which minimum applies.
--exec-file reads the whole file. The load_command that pull prints uses
--exec "$(cat <file>)" instead, which also works but hits your shell's argument-length
limit on large exports.
JSON for agents
bata powdb pull --project <project-id> --jsonThis prints a summary: one entry per table with its PowDB name, the number of columns
kept, the rows exported and the compat notes for that table. It also gives
warnings_total, load_risk and the load_command to run:
{
"project_id": "<project-id>",
"branch": { "id": "<branch-id>", "name": "main" },
"engine": "powdb",
"stage": "A",
"out": "/abs/path/<project-id>-main.powql",
"tables": [
{
"postgres": "public.users",
"powdb": "users",
"columns": 6,
"rows": 2,
"warnings": [
"column created_at: stored as PowDB datetime (epoch seconds); timezone + sub-second precision not preserved",
"column created_at: expression default dropped (now())"
]
}
],
"row_count": 2,
"warnings_total": 7,
"load_risk": false,
"load_command": "powdb-cli --data-dir ./sandbox --exec \"$(cat ./<project-id>-main.powql)\""
}With --out - and --json together, the script itself is in the payload under
artifact, and out is null.
What carries over, and what doesn't
PowDB's types and constraints are narrower than Postgres's. pull reports each lossy or
dropped column, constraint and index: in the human summary, in the --json payload, and
as # WARN: lines just above the affected type in the script. The notes travel with
the file, so you can read what was lost straight from the .powql.
Type mapping
| Postgres type | PowDB (PowQL) | Fidelity |
|---|---|---|
smallint, integer, bigint, serial, bigserial | int (serial becomes auto) | Faithful |
real, double precision | float | Faithful |
numeric, decimal | float | Lossy: a 64-bit float, not an exact decimal |
text, varchar, char, citext | str | Faithful |
boolean | bool | Faithful |
timestamp, timestamptz, date | datetime (epoch seconds) | Lossy: time zone and sub-second precision are dropped |
time, interval | str | Carried as text |
uuid | str | Carried as text |
bytea | str | Carried as hex text |
json, jsonb | json (PowDB 0.12+) | Near-faithful: stored in a canonical form, so object keys are re-sorted and duplicate keys keep the last value (as jsonb does). -> path queries work locally. |
Arrays, enums, ranges, geometry, network types, tsvector, composite types | None | Dropped: the column is left out |
Constraints and indexes
| Postgres feature | In the PowDB copy |
|---|---|
Single-column PRIMARY KEY | required unique (a serial primary key becomes unique auto) |
NOT NULL | required |
Single-column UNIQUE | unique |
Composite PRIMARY KEY or UNIQUE | Not enforced: PowDB uniqueness is per column |
FOREIGN KEY | Dropped |
CHECK | Dropped |
A literal column DEFAULT (int, float, string, bool) | default <literal> |
An expression DEFAULT (now(), gen_random_uuid() and so on) | Dropped |
| Secondary index on one plain btree column | alter <type> add index .<column> after the type block |
| Any other index (multi-column, expression, partial, non-btree) | Not carried: warned per index |
A table with no column PowDB can represent is skipped, with a warning.
load_risk
load_risk is true when some exported string contains a ; or a newline, and the
script header then carries a LOAD CAVEAT note. PowDB 0.10 and later split statements
with awareness of string literals, so those rows load intact with the commands above.
Hosted PowDB projects
BataDB can also host PowDB as a second engine. A hosted PowDB project is one PowDB server that BataDB runs for you. It is a different engine, not a different Postgres: Postgres tools, drivers and ORMs do not connect to it.
bata projects create notes --engine powdb
bata powdb query 'type Note { required title: str }' --project <project-id>
bata powdb query 'insert Note { title := "hello" }' --project <project-id>
bata powdb query 'Note' --project <project-id>| Command | What it does |
|---|---|
bata powdb query "<powql>" | Run one PowQL statement |
bata powdb exec "<script>" or --file <path> | Run a ;-separated script (below) |
bata powdb status | Show whether the server is running or parked |
bata powdb park | Stop the server. The next query wakes it. |
bata powdb wake | Start a parked server |
bata powdb upgrade --to <x.y.z> --yes | Move the project to another PowDB server version |
Each command takes --project <id> (or a project id as its argument), and falls back to
the linked or default project. Add --json for machine-readable output.
An upgrade parks the server, backs up its data, switches versions, wakes it and checks it, and rolls back if that check fails. A release that changes PowDB's storage format cannot be undone once new data is written, so the CLI asks you to confirm.
Run a script: bata powdb exec
exec runs a whole ;-separated PowQL script. Splitting understands strings and #
comments. The statements are pipelined down one server connection, so a long script
costs about one round trip, not one per statement. Use it to seed, bulk-load or migrate.
bata powdb exec 'insert Note { title := "a" }; insert Note { title := "b" }; count(Note)'
bata powdb exec --file seed.powql # from a file
bata powdb pull --out - | bata powdb exec --file - # load a pulled branch straight in
bata powdb exec --file seed.powql --transactional # all or nothing
bata powdb exec --file load.powql --continue-on-error --jsonPick at most one mode:
- Default (fail fast). Stops sending statements at the first failure and reports
its index. Because statements are pipelined, ones already sent still run. Use
--transactionalif that matters. --transactional. The server commits only if every statement succeeds. Any failure rolls the whole script back (rolled_back: true, nothing saved). The script may not contain its ownbegin,commitorrollback.--continue-on-error. Runs every statement and returns one outcome per statement. Exits non-zero if any failed.
Over HTTP, it is POST /v1/powdb/<project-id>/exec with
{ "script": "...", "transactional": true } or { "script": "...", "continue_on_error": true }.
A script can be up to 1,000,000 characters.
What hosted PowDB does not have yet
- No direct connection. You reach it through the API:
bata powdb query, orPOST /v1/powdb/<project-id>/query. - One shape. One server per project, with no size choice and no always-on tier.
- No branching, point-in-time restore or query insights on this engine.
pg_versiondoes not apply. - Not metered yet. Hosted PowDB usage is not metered or billed yet. A PowDB project still counts toward your plan's project limit, and, like any project, it needs a card on file to start.
Exit codes
bata powdb follows the CLI's exit codes: 4 for a missing or refused key, 5 for a
bad flag or a project or branch that does not exist, 6 for a compute that is still
starting (retry), and 1 for anything else, such as a failed statement. See
Exit codes.