Skip to main content

camus-dump

camus-dump is a utility for a logical backup of CamusDB. It connects to a database. It reads the metadata of the schema, and the rows. It then writes SQL. You can replay that SQL later, through camus-cli, or through another client of SQL for CamusDB.

Use the utility for one of three purposes: a backup in SQL that you can inspect, an export of one table, or a consistent logical snapshot of a database.

The dump is consistent across the tables, by default. It fixes one instant at its start. It then reads every table as of that instant. Read Point in time before you turn that behavior off.

Not for large databases​

camus-dump is the wrong tool for a database of tens of gigabytes, and of hundreds of gigabytes. Use Backup And Restore at that scale.

Every row travels through the layer of the SQL. It comes back out as text. Both ends therefore grow with the count of the rows. They do not grow with the bytes on the disk.

The dump runs slower than the disk that it reads. The file of the SQL is usually larger than the data of its source.

The restore is the harder half. A replay of a dump parses every statement, checks every constraint again, and builds every index from the start. That work takes hours, and it can take days. A physical restore is a copy of some files, plus a replay of the WAL.

A dump can need a long night for its creation. It can then need much longer for its restore. That shape is exactly wrong for a path of a recovery.

Use camus-dump for two purposes. The output must be readable SQL that you can carry to another system. The data set must also be small enough for a quick replay.

Use a physical backup when your purpose is the return of the data.

Install​

Install the global tool:

dotnet tool install --global CamusDB.Dump

The executable name is:

camus-dump

Basic usage​

Dump a database to standard output:

camus-dump --endpoint http://localhost:5096 --database factory

Dump one table, group 100 rows per INSERT, and write to a file:

camus-dump \
--endpoint http://localhost:5096 \
--database factory \
--table orders \
--batch 100 \
--output orders.sql

Dump the database as it was five minutes ago, replayable onto an existing schema:

camus-dump \
--endpoint http://localhost:5096 \
--database factory \
--as-of -5m \
--if-not-exists \
--output factory.sql

Dump every database on the server, one file each:

camus-dump \
--endpoint http://localhost:5096 \
--all-databases \
--output-directory backup/

By default, camus-dump uses the protocol of gRPC, and the address http://localhost:5096. That port is the default port of gRPC for a client of CamusDB.

Use a --protocol rest, with the endpoint of REST, when you want REST with JSON instead.

Connection options​

OptionDescription
-c, --connection-sourceFull connection string. Other connection options fill in keys that the connection string does not already set.
-e, --endpointServer endpoint or comma-separated endpoint pool. Defaults to http://localhost:5096.
-d, --databaseDatabase to dump. Defaults to test.
-A, --all-databasesDump every database on the server.
-X, --exclude-databaseWith --all-databases, skip these databases. Accepts comma-separated names or repeated options.
--protocolgrpc by default, or rest. The endpoint port must match the selected protocol. Pointing REST at the gRPC port fails with an HTTP/1.x request was sent to an HTTP/2 only endpoint.
--timeoutPer-statement timeout in seconds. Defaults to 10.

Example with a connection string:

camus-dump \
--connection-source "Endpoint=http://localhost:5096;Database=factory;Protocol=grpc"

Authentication​

The authentication of CamusDB is off by default. Against a server with authentication, pass a credential, or a bearer token:

CAMUSDB_PASSWORD=app-secret \
camus-dump -e https://camus.internal:5096 -d factory -u app

Prompt for the password:

camus-dump -e https://camus.internal:5096 -d factory -u app --ask-password

Use a token obtained elsewhere:

camus-dump -e https://camus.internal:5096 -d factory --access-token "camus_..."
OptionDescription
-u, --userUser to authenticate as.
-p, --passwordUser password. Prefer CAMUSDB_PASSWORD or --ask-password so the password does not appear in the process list.
-W, --ask-passwordPrompt for the password on the terminal.
--access-tokenBearer token obtained elsewhere, used instead of logging in.
--token-lifetimeSeconds to reuse a minted token when the server reports no expiry. Defaults to 600.

The utility exchanges the password one time, for a bearer token with a short life. A statement then uses the token. It does not use the password.

Over gRPC, the authentication uses the service CamusAuth. That service runs on the same channel as the queries of the dump.

The dump reads the schema, and the data of a table. While authentication is enabled, the user needs the privileges of the relevant SHOW operations, and of the relevant SELECT operations.

Use an https:// address for a deployment with authentication, outside loopback. While TLS is necessary, CamusDB rejects a plaintext request with a credential from outside loopback.

Choosing what to dump​

OptionDescription
-t, --tableDump only these tables. Accepts comma-separated names or repeated options.
-x, --exclude-tableSkip these tables. Accepts comma-separated names or repeated options.
-w, --whereDump only rows matching this condition.
--as-ofRead every table as of this point in time.
--no-as-ofRead the latest committed data instead.
--no-create-tableDo not emit CREATE TABLE.
--no-dataDo not emit INSERT.
--no-indexesDo not emit secondary index DDL.

Example:

camus-dump -e http://localhost:5096 -d factory \
--table orders \
--where 'status = "open"' \
--output open-orders.sql

Dumping every database​

--all-databases asks the server for its databases, with a SHOW DATABASES. It then dumps each database in turn. It skips every database that an --exclude-database names:

# Every database, as one stream of sections
camus-dump -A -o server.sql

# Every database except two, one file per database
camus-dump -A -X scratch,tempdb --output-directory backup/

The utility reads every database as of the same instant. It fixes the point in time one time, before the first statement leaves.

--single-transaction is the exception. A transaction belongs to a connection, and each database receives its own connection. That option therefore makes each database internally consistent. It does not bind the databases to one common snapshot.

Each section opens with a CREATE DATABASE IF NOT EXISTS, and with a USE. That happens with a --create-database, and without one. One file therefore restores every database, in turn.

A USE is not SQL of the server. The parser of CamusDB rejects it. A client reads that statement, and it points the statements after it at that database. camus-cli therefore takes the whole file.

Another client may not read a USE. Dump with an --output-directory for such a client. The utility then writes one <database>.sql for each database. Each file replays on its own, and a -d names its target.

The other options apply to each database. A -t and an -x match the names of the tables of every database. A -w filters the rows of every table that it names.

A condition can reference a column that only some tables hold. That condition then fails on the other tables.

Point in time​

By default, the dump does not read the latest data. It fixes an instant at its start. That instant is one second behind the wall clock, and it therefore stays clear of the clock of the server. The dump then reads every table with the AS OF SYSTEM TIME clause of CamusDB.

That default exists for a reason. Without it, a problem appears. A user writes a row between the scan of the first table and the scan of the last one. That row arrives in the dump. The rows that it references, in a table that the dump wrote already, do not. The dump then restores a state that the database never held.

The utility records the instant in the header of the dump. You can therefore repeat a dump exactly:

-- Point in time: 2026-07-29 19:15:35.277+00:00
-- Rows read with AS OF SYSTEM TIME '2026-07-29 19:15:35.277+00:00'; table definitions and indexes are current.

An --as-of selects a different instant. It takes any form that the server accepts:

# Five minutes ago
camus-dump -d factory --as-of -5m

# An absolute UTC instant, for example the one a previous dump recorded
camus-dump -d factory --as-of "2026-07-29 19:15:35.277+00:00"

# Unix epoch milliseconds
camus-dump -d factory --as-of 1721420000000

An offset takes ms, s, m, h, and d. It must be negative.

The utility resolves a relative offset to an absolute instant one time, before the first statement leaves. It does not pass the offset through. The server would otherwise evaluate that offset again for each table. The tables would then share no snapshot.

Four points are worth your attention:

  • Rows only. SHOW CREATE TABLE and SHOW INDEXES have no time-travel form, so the schema in the dump is the current one. A table created after the chosen instant appears with its definition and no rows.
  • Retention bounds how far back you can look. An instant older than the history the storage layer still keeps reads as empty rather than as an error.
  • --single-transaction replaces it. CamusDB rejects AS OF SYSTEM TIME inside an explicit transaction, which is already pinned to one snapshot. Passing --single-transaction turns the default off; passing it together with --as-of is an error.
  • --no-as-of reads the latest committed data, with no consistency guarantee across tables.

Shaping the output​

OptionDescription
-b, --batchRows per INSERT statement. Defaults to 1.
-o, --outputWrite to a file instead of standard output.
--output-directoryWrite one <database>.sql file per database into this directory, creating it if missing. Cannot be combined with -o.
--defer-indexesEmit each table's CREATE INDEX statements after its data.
--add-drop-tableEmit DROP TABLE IF EXISTS before each CREATE TABLE.
--if-not-existsEmit CREATE TABLE IF NOT EXISTS, useful when replaying onto an existing schema.
--create-databaseEmit CREATE DATABASE IF NOT EXISTS for the dumped database, followed by USE. Implied by --all-databases.
--single-transactionRead every table from one lock-free Serializable snapshot instead of a fixed past instant.
--strictFail instead of emitting NULL for values that have no exact SQL literal.
--no-headerOmit the leading comment header.

--single-transaction is an alternative to the default read at a point in time. It is not an addition to that read. Both forms give a snapshot that is consistent across the tables.

Use the option when you want the dump pinned to the present moment. The default pins the dump to a fixed instant of the past.

Use --batch 100 as a starting point for a restore. One row per INSERT is usually much slower, while very large batches can hit the transaction mutation limit or the transport message-size limit on wide tables.

--defer-indexes loads the table data before building secondary indexes. It can help for a table with one secondary index, but it is not automatically faster for a table with several indexes because each deferred index build scans the table. Keep the target out of service until deferred indexes are built.

Dump contents​

The dump can include these parts. Your selected options decide:

  • optional header comments, including the point in time the rows were read at
  • CREATE DATABASE IF NOT EXISTS followed by USE
  • DROP TABLE IF EXISTS
  • CREATE TABLE statements
  • INSERT INTO statements
  • secondary CREATE INDEX IF NOT EXISTS statements

Typical shape:

CREATE DATABASE IF NOT EXISTS factory;
USE factory;

CREATE TABLE IF NOT EXISTS `orders` (
`id` OID NOT NULL DEFAULT (gen_id()),
`name` STRING(20) NULL,
PRIMARY KEY (`id`)
);

INSERT INTO `orders` (`id`, `name`)
VALUES
(STR_ID('507f1f77bcf86cd799439011'), 'first order'),
(STR_ID('507f1f77bcf86cd799439012'), 'second order');

Literal encoding​

camus-dump writes a value as a literal of CamusSQL. That literal parses back to the same stored value:

TypeDump form
OIDSTR_ID('...')
STRINGPlain '...' when possible, or E'...' for control characters
INT64Integer literal
FLOAT64, FLOAT32Numeric literal
BOOLtrue or false
BYTESX'...' hex bytes literal
DATEQuoted yyyy-MM-dd string
DATETIMEQuoted ISO-8601 datetime string
UUIDQuoted canonical UUID string
ARRAYARRAY[...]
NULLNULL

A string returns unchanged, even with five difficult characters inside it: a backslash, a backslash at its end, both kinds of quotation mark, a new line, and a NUL. See Data Types for the rules of a literal.

The dump writes an index two times: inline, inside the CREATE TABLE, and as a separate CREATE INDEX IF NOT EXISTS statement. Index definitions include their per-column sort direction and comment when the source server reports them.

That repetition keeps the definitions of the indexes available with a --no-create-table. It also lets a --defer-indexes build the indexes after the load of the rows.

Lossy values​

Most stored values have an exact form as a literal of SQL. camus-dump reports a value that it cannot restore exactly.

Non-finite floats have no CamusSQL literal:

  • NaN
  • +Infinity
  • -Infinity

By default, the dump writes a NULL for such a value. It adds a comment of a warning to the dump. It prints a count to the standard error at its end. Use a --strict for a failure instead.

A literal of a DATETIME carries a precision of a millisecond. The tool can need a truncation of a dumped datetime to that precision. It reports that truncation as well. A --strict turns it into a failure.

Restore a dump​

Restore the data. Send the generated SQL to camus-cli:

camus-cli -c "Endpoint=http://localhost:5096;Protocol=grpc"

Then run:

source ./factory.sql

The dump can hold no --create-database. Create the target database first. Then select it:

CREATE DATABASE IF NOT EXISTS factory;
use factory;
source ./factory.sql

See camus-cli for the use of the interactive shell.

When to use it​

Use camus-dump for these purposes:

  • a logical SQL backup
  • a table-level export
  • a readable dump for review or source control
  • a consistent snapshot across tables
  • a restore path through ordinary CamusSQL

Every purpose above assumes a database small enough for a replay in a reasonable time.

Use Backup And Restore instead in two cases: your goal is a disaster recovery, and the database holds tens of gigabytes or more.

Such a backup is physical, and it covers a whole node. It restores to a chosen point in time. It also returns far faster than a replay of a dump, statement by statement. See Not for large databases.

For an interactive replay, and for a restore by hand, use camus-cli.