Skip to content
BataDB

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

Terminal
# 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
FlagMeaning
--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
--jsonPrint 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:

Terminal
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

Terminal
bata powdb pull --project <project-id> --json

This 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:

JSON
{
  "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 typePowDB (PowQL)Fidelity
smallint, integer, bigint, serial, bigserialint (serial becomes auto)Faithful
real, double precisionfloatFaithful
numeric, decimalfloatLossy: a 64-bit float, not an exact decimal
text, varchar, char, citextstrFaithful
booleanboolFaithful
timestamp, timestamptz, datedatetime (epoch seconds)Lossy: time zone and sub-second precision are dropped
time, intervalstrCarried as text
uuidstrCarried as text
byteastrCarried as hex text
json, jsonbjson (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 typesNoneDropped: the column is left out

Constraints and indexes

Postgres featureIn the PowDB copy
Single-column PRIMARY KEYrequired unique (a serial primary key becomes unique auto)
NOT NULLrequired
Single-column UNIQUEunique
Composite PRIMARY KEY or UNIQUENot enforced: PowDB uniqueness is per column
FOREIGN KEYDropped
CHECKDropped
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 columnalter <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.

Terminal
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>
CommandWhat it does
bata powdb query "<powql>"Run one PowQL statement
bata powdb exec "<script>" or --file <path>Run a ;-separated script (below)
bata powdb statusShow whether the server is running or parked
bata powdb parkStop the server. The next query wakes it.
bata powdb wakeStart a parked server
bata powdb upgrade --to <x.y.z> --yesMove 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.

Terminal
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 --json

Pick 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 --transactional if 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 own begin, commit or rollback.
  • --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, or POST /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_version does 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.