Skip to content

CLI Reference

plan

Preview what apply and load would do: every table, column, type mapping, write strategy, route and cursor. Changes nothing.

sqlload plan [SELECTOR ...] [OPTIONS]

sqlload plan prints what apply and load would do: tables to create or alter, each column’s type mapping, write strategy, route, batches and cursor. It reads the sources’ and destinations’ catalogs and changes nothing. apply and load print the same plan before they run. See Projects for how the three fit together.

In a Kafka exporter configuration, plan shows each topic’s table and the objects it needs, and refuses the options below.

A selector is a source name, a destination table name, or a source’s schema.table. Give selectors positionally or with --select, and leave tables out with --exclude. With none, every table is selected. See Selecting tables for examples.

A source’s problems, such as a refused setting or a server it can’t reach, fail only a command that selects something from it. A source that can’t list its tables also fails a command that selects a table by name, since that table could be one of its own.

plan, apply and load take the same options.

Option Meaning
SELECTOR ..., -s, --select <SELECTOR>... Sources or tables to select: a source name, a destination table name, or a source’s schema.table. All when none is given.
--exclude <SELECTOR>... Sources or tables to leave out. Wins over --select.
--start-cursor <VALUE> Read from this cursor value instead of the destination’s maximum. Typed from each table’s cursor: an integer, an RFC 3339 timestamp or a date.
--end-cursor <VALUE> Read only cursor values below this one.
--reload Rebuild the selected tables from the whole source, with the current columns and types. Asks first, unless --yes. Refuses postgres_cdc tables, which --resnapshot copies again; see Reloading.
--resnapshot postgres_cdc sources: drop and recreate the replication slot, then copy every table again. Asks first, unless --yes. See Postgres changes.
--follow load of one postgres_cdc source only: keep streaming after catching up, writing a batch every batch_interval, until --max-duration passes or Ctrl-C or SIGTERM. See Keep streaming.
--max-duration <DURATION> With --follow: stop after this long, such as 10m, once the changes read so far are written.
--yes Rebuild with --reload or --resnapshot without asking. Needed when not on a terminal.
--allow-type-change-rebuild Allow table rebuilds that type_change_rebuild = "require_confirmation" would block.
--concurrency <N> The most tables loading at once. Default: the project’s concurrency, else 4.
--json Print one JSON document on standard output. For plan, the plan. For apply and load (from 0.27.0), the result: see Results as JSON.
$ sqlload plan
Plan ready 2 selected, 2 objects to create, 2 to load
to DuckDB local
Objects (2) sqlload apply
├── raw.app__customers create table
│ ├── customer_id int -> BIGINT
│ ├── email text -> VARCHAR
│ ├── country text -> VARCHAR
│ ├── created_at timestamp_tz -> TIMESTAMP WITH TIME ZONE (stored as a UTC instant; the offset is not kept)
│ └── + _sqlload_load_id, _loaded_at
└── raw.app__orders create table
├── order_id int -> BIGINT
├── customer_id int -> BIGINT
├── status text -> VARCHAR
├── total decimal(12,2) -> DECIMAL(12,2)
├── updated_at timestamp_tz -> TIMESTAMP WITH TIME ZONE (stored as a UTC instant; the offset is not kept)
└── + _sqlload_load_id, _loaded_at
Loads (2) sqlload load
├── raw.app__customers merge (unique_key: customer_id ← {primary_key})
│ ├── source postgres app public.customers
│ └── route read as Arrow -> DuckDB takes Arrow
└── raw.app__orders merge (unique_key: order_id ← {primary_key})
├── source postgres app public.orders
├── route read as Arrow -> DuckDB takes Arrow
└── reads updated_at: first run, everything
✓ Plan complete 2 selected, 2 objects to create, 2 to load

Tables are named as they land, schema.table, and sorted by that name. A table read by cursor says where its read starts and when it commits:

$ sqlload plan
Plan ready 2 selected, 2 to load
to DuckDB local
Loads (2) sqlload load
├── raw.app__customers merge (unique_key: customer_id ← {primary_key})
│ ├── source postgres app public.customers
│ └── route read as Arrow -> DuckDB takes Arrow
└── raw.app__orders merge (unique_key: order_id ← {primary_key})
├── source postgres app public.orders
├── route read as Arrow -> DuckDB takes Arrow
└── reads updated_at >= 2026-11-01 16:40:00+00 (warehouse maximum)
✓ Plan complete 2 selected, 2 to load

reads gives the cursor’s bound (>= when the bound row is read again, > when it isn’t) and where it comes from: the warehouse maximum, minus any lookback, --start-cursor, or a logged state. commits shows when the read doesn’t commit the usual way.

A plan of several tables names each destination on a to line; when there are several, each route names its destination too. It leaves out why, and the batch and schema lines most of its tables share. Select one table to see every line, with times to the millisecond:

$ sqlload plan app__orders
Plan ready 1 selected, 1 to load
Loads (1) sqlload load
└── raw.app__orders merge (unique_key: order_id ← {primary_key})
├── source postgres app public.orders
├── route read as Arrow -> DuckDB takes Arrow
├── key order_id, the primary key the source reports (unique_key = "{primary_key}", sqlload.toml:21)
├── reads updated_at >= 2026-11-01 16:40:00.000+00 (warehouse maximum)
├── columns 5
└── commits about every 1 GB, in updated_at order (an index leads with it, NULLs first), between distinct values
✓ Plan complete 1 selected, 1 to load

On a terminal, plan shows Planning checking sources, 1 of 3 done while it reads catalogs, and clears it when the plan prints.

Section Lists
Objects Tables apply creates or alters: each column as its portable type -> the warehouse type, then in brackets what that type loses, if anything (from 0.29.0; for an array, the array’s own loss, then its elements’ after elements:), added and widened columns, with the same note from 0.31.0, excluded and masked columns.
Loads Each table load loads, with its write strategy, and lines for source, key, reads, commits, files, schema, route, why, batch and note. A plan of one table also shows columns (how many, and under naming = "dlt" the names it changes) and checks, and key names the sqlload.toml line that sets unique_key.
Skipped Tables left out on purpose, with the reason: a files table with no new file (up to date: 0 new of 3 files in drop/orders/ (csv)), or one left out by skip_tables_with_columns or new_tables = "skip". load reads nothing for them.
Errors Tables that can’t load as configured, with a short headline, the reason and the fix. A source that can’t be read is one item, with what it holds up under blocks.
Warnings Tables that load, but slower or with less than you might expect.

A table under Errors is left out; the others still run, and the command exits 1.