Commands¶
Usage: pista <command> [flags]
Flags:
-h, --help Show context-sensitive help.
-c, --conn-string="postgres://postgres@localhost/postgres"
PostgreSQL connection string. See
https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING
($PISTA_CONN_STR)
-d, --dbname=STRING PostgreSQL database name. Overrides the dbname in
--conn-string ($PISTA_DBNAME).
--password=STRING PostgreSQL password ($PISTA_PASSWORD).
-n, --schemas=public,... Schemas to inspect and modify ($PISTA_SCHEMAS).
-m, --schema-map=KEY=VALUE;...
Schema name mapping (e.g. -m old=new).
--search-path=public search_path for the database connection.
The catalog reports an object reachable through
it without its schema, so this decides how dump
writes that object. Pass an empty value to qualify
everything ($PISTA_SEARCH_PATH).
-C, --config=FILE Load options from a YAML file ($PISTA_CONFIG).
--version
--[no-]pager Force paging via $PISTA_PAGER even when stdout is
not a TTY. PISTA_PAGER must be set.
Commands:
apply <files> ... [flags]
Apply schema changes to the database.
plan <files> ... [flags]
Print the schema diff SQL without applying it.
dump [flags]
Dump the current database schema as SQL.
Run "pista <command> --help" for more information on a command.
Note
By default, pistachio targets the public schema.
See Working with multiple schemas for details.
pista plan --help
Usage: pista plan <files> ... [flags]
Print the schema diff SQL without applying it.
Arguments:
<files> ... Path to the desired schema SQL file(s).
Flags:
-h, --help Show context-sensitive help.
-c, --conn-string="postgres://postgres@localhost/postgres"
PostgreSQL connection string. See
https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING
($PISTA_CONN_STR)
-d, --dbname=STRING PostgreSQL database name. Overrides the dbname
in --conn-string ($PISTA_DBNAME).
--password=STRING PostgreSQL password ($PISTA_PASSWORD).
-n, --schemas=public,... Schemas to inspect and modify ($PISTA_SCHEMAS).
-m, --schema-map=KEY=VALUE;...
Schema name mapping (e.g. -m old=new).
--search-path=public search_path for the database connection.
The catalog reports an object reachable through
it without its schema, so this decides how
dump writes that object. Pass an empty value to
qualify everything ($PISTA_SEARCH_PATH).
-C, --config=FILE Load options from a YAML file ($PISTA_CONFIG).
--version
--[no-]pager Force paging via $PISTA_PAGER even when stdout
is not a TTY. PISTA_PAGER must be set.
-I, --include=INCLUDE,... Include only
tables/views/enums/domains/composite
types/sequences/routines matching the pattern
(wildcard: *, ?; /re/ for a regular expression)
($PISTA_INCLUDE).
-E, --exclude=EXCLUDE,... Exclude tables/views/enums/domains/composite
types/sequences/routines matching the pattern
(wildcard: *, ?; /re/ for a regular expression)
($PISTA_EXCLUDE).
--enable=ENABLE,... Enable only specified object types (can be
repeated) ($PISTA_ENABLE).
--disable=DISABLE,... Disable specified object types (can be repeated)
($PISTA_DISABLE).
--manage-routine Manage functions and procedures. Off by default;
--allow-drop routine still gates dropping them
($PISTA_MANAGE_ROUTINE).
--manage-storage-param Manage a table's storage parameters, the WITH
(...) clause. Off by default; without it the
clause is ignored on both sides and dump does
not write it ($PISTA_MANAGE_STORAGE_PARAM).
--skip-partition-child Manage a partitioned table without its
partitions. For a schema whose partitions
another tool creates. An INHERITS child is
unaffected ($PISTA_SKIP_PARTITION_CHILD).
--allow-drop=ALLOW-DROP,...
Allow dropping these object types (repeatable;
'all' allows everything) ($PISTA_ALLOW_DROP).
--pre-sql=STRING SQL to prepend to the plan output
($PISTA_PRE_SQL).
--pre-sql-file=STRING Path to a SQL file to prepend to the plan output
($PISTA_PRE_SQL_FILE).
--concurrently-pre-sql=STRING
SQL to run before CONCURRENTLY index DDL (e.g.
SET lock_timeout). Emitted only when
the diff contains CONCURRENTLY index DDL
($PISTA_CONCURRENTLY_PRE_SQL).
--concurrently-pre-sql-file=STRING
Path to a SQL file to run before CONCURRENTLY
index DDL ($PISTA_CONCURRENTLY_PRE_SQL_FILE).
--disable-index-concurrently
Ignore CONCURRENTLY opt-ins (directive and
inline) and emit plain CREATE/DROP INDEX
($PISTA_DISABLE_INDEX_CONCURRENTLY).
--force-index-concurrently
Force CONCURRENTLY on every
CREATE/DROP INDEX, including pure drops
($PISTA_FORCE_INDEX_CONCURRENTLY).
--bulk-alter Combine consecutive ALTER TABLE actions on the
same table into a single statement. FK changes,
RENAME, VALIDATE CONSTRAINT, RLS toggles, and
skipped DROPs stay separate ($PISTA_BULK_ALTER).
--assume-validated Treat every table constraint, domain constraint,
and foreign key as validated: ignore NOT
VALID and never emit VALIDATE CONSTRAINT
($PISTA_ASSUME_VALIDATED).
--no-read-only Open the database connection read-write.
By default plan uses a read-only connection
($PISTA_NO_READ_ONLY).
--check Exit with code 2 when the plan contains
executable changes ($PISTA_CHECK).
pista apply --help
Usage: pista apply <files> ... [flags]
Apply schema changes to the database.
Arguments:
<files> ... Path to the desired schema SQL file(s).
Flags:
-h, --help Show context-sensitive help.
-c, --conn-string="postgres://postgres@localhost/postgres"
PostgreSQL connection string. See
https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING
($PISTA_CONN_STR)
-d, --dbname=STRING PostgreSQL database name. Overrides the
dbname in --conn-string ($PISTA_DBNAME).
--password=STRING PostgreSQL password ($PISTA_PASSWORD).
-n, --schemas=public,... Schemas to inspect and modify
($PISTA_SCHEMAS).
-m, --schema-map=KEY=VALUE;...
Schema name mapping (e.g. -m old=new).
--search-path=public search_path for the database connection. The
catalog reports an object reachable through
it without its schema, so this decides how
dump writes that object. Pass an empty value
to qualify everything ($PISTA_SEARCH_PATH).
-C, --config=FILE Load options from a YAML file
($PISTA_CONFIG).
--version
--[no-]pager Force paging via $PISTA_PAGER even when
stdout is not a TTY. PISTA_PAGER must be set.
-I, --include=INCLUDE,... Include only
tables/views/enums/domains/composite
types/sequences/routines matching the
pattern (wildcard: *, ?; /re/ for a regular
expression) ($PISTA_INCLUDE).
-E, --exclude=EXCLUDE,... Exclude tables/views/enums/domains/composite
types/sequences/routines matching the
pattern (wildcard: *, ?; /re/ for a regular
expression) ($PISTA_EXCLUDE).
--enable=ENABLE,... Enable only specified object types (can be
repeated) ($PISTA_ENABLE).
--disable=DISABLE,... Disable specified object types (can be
repeated) ($PISTA_DISABLE).
--manage-routine Manage functions and procedures. Off by
default; --allow-drop routine still gates
dropping them ($PISTA_MANAGE_ROUTINE).
--manage-storage-param Manage a table's storage parameters, the WITH
(...) clause. Off by default; without it the
clause is ignored on both sides and dump does
not write it ($PISTA_MANAGE_STORAGE_PARAM).
--skip-partition-child Manage a partitioned table without its
partitions. For a schema whose partitions
another tool creates. An INHERITS child is
unaffected ($PISTA_SKIP_PARTITION_CHILD).
--allow-drop=ALLOW-DROP,...
Allow dropping these object types
(repeatable; 'all' allows everything)
($PISTA_ALLOW_DROP).
--pre-sql=STRING SQL to execute before applying changes
($PISTA_PRE_SQL).
--pre-sql-file=STRING Path to a SQL file to execute before applying
changes ($PISTA_PRE_SQL_FILE).
--concurrently-pre-sql=STRING
SQL to execute before CONCURRENTLY
index DDL (e.g. SET lock_timeout).
Runs outside any transaction, only when
the diff contains CONCURRENTLY index DDL
($PISTA_CONCURRENTLY_PRE_SQL).
--concurrently-pre-sql-file=STRING
Path to a SQL file to execute
before CONCURRENTLY index DDL
($PISTA_CONCURRENTLY_PRE_SQL_FILE).
--with-tx Execute pre-SQL and schema changes in a
transaction ($PISTA_WITH_TX).
--try-tx Execute pre-SQL and schema changes in
a transaction when possible. A diff
containing CONCURRENTLY index DDL runs
without a transaction instead of failing
($PISTA_TRY_TX).
--disable-index-concurrently
Ignore CONCURRENTLY opt-ins (directive and
inline) and emit plain CREATE/DROP INDEX
($PISTA_DISABLE_INDEX_CONCURRENTLY).
--force-index-concurrently
Force CONCURRENTLY on every CREATE/DROP
INDEX, including pure drops.
Cannot be combined with --with-tx
($PISTA_FORCE_INDEX_CONCURRENTLY).
--bulk-alter Combine consecutive ALTER TABLE actions on
the same table into a single statement.
FK changes, RENAME, VALIDATE CONSTRAINT,
RLS toggles, and skipped DROPs stay separate
($PISTA_BULK_ALTER).
--assume-validated Treat every table constraint, domain
constraint, and foreign key as validated:
ignore NOT VALID and never emit VALIDATE
CONSTRAINT ($PISTA_ASSUME_VALIDATED).
--exclusive Make apply runs on the same database
mutually exclusive: fail immediately
when another exclusive apply is running
($PISTA_EXCLUSIVE).
--exclusive-wait=DURATION Like --exclusive, but wait up to
the given duration (0 waits without
limit) for the other apply to finish
($PISTA_EXCLUSIVE_WAIT).
pista dump --help
Usage: pista dump [flags]
Dump the current database schema as SQL.
Flags:
-h, --help Show context-sensitive help.
-c, --conn-string="postgres://postgres@localhost/postgres"
PostgreSQL connection string. See
https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING
($PISTA_CONN_STR)
-d, --dbname=STRING PostgreSQL database name. Overrides the dbname
in --conn-string ($PISTA_DBNAME).
--password=STRING PostgreSQL password ($PISTA_PASSWORD).
-n, --schemas=public,... Schemas to inspect and modify ($PISTA_SCHEMAS).
-m, --schema-map=KEY=VALUE;...
Schema name mapping (e.g. -m old=new).
--search-path=public search_path for the database connection.
The catalog reports an object reachable through
it without its schema, so this decides how
dump writes that object. Pass an empty value to
qualify everything ($PISTA_SEARCH_PATH).
-C, --config=FILE Load options from a YAML file ($PISTA_CONFIG).
--version
--[no-]pager Force paging via $PISTA_PAGER even when stdout
is not a TTY. PISTA_PAGER must be set.
-I, --include=INCLUDE,... Include only
tables/views/enums/domains/composite
types/sequences/routines matching the pattern
(wildcard: *, ?; /re/ for a regular expression)
($PISTA_INCLUDE).
-E, --exclude=EXCLUDE,... Exclude tables/views/enums/domains/composite
types/sequences/routines matching the pattern
(wildcard: *, ?; /re/ for a regular expression)
($PISTA_EXCLUDE).
--enable=ENABLE,... Enable only specified object types (can be
repeated) ($PISTA_ENABLE).
--disable=DISABLE,... Disable specified object types (can be repeated)
($PISTA_DISABLE).
--manage-routine Manage functions and procedures. Off by default;
--allow-drop routine still gates dropping them
($PISTA_MANAGE_ROUTINE).
--manage-storage-param Manage a table's storage parameters, the WITH
(...) clause. Off by default; without it the
clause is ignored on both sides and dump does
not write it ($PISTA_MANAGE_STORAGE_PARAM).
--skip-partition-child Manage a partitioned table without its
partitions. For a schema whose partitions
another tool creates. An INHERITS child is
unaffected ($PISTA_SKIP_PARTITION_CHILD).
--split=STRING Output each table/view/enum/domain/composite
type/sequence as a separate file in the
specified directory.
--omit-schema Omit schema name from the dump output.
--sort-by-deps Order the dump output by object dependency
instead of by name. Errors when the dependency
graph has a cycle. Cannot be used with --split.
--no-read-only Open the database connection read-write.
By default dump uses a read-only connection
($PISTA_NO_READ_ONLY).
plan¶
Compare schema file(s) against the current database and print the SQL needed to reconcile them.
pista plan schema.sql
# Multiple files
pista plan tables.sql views.sql
# Include pre-SQL in the output
pista plan schema.sql --pre-sql "SET statement_timeout = '5s';"
pista plan schema.sql --pre-sql-file pre.sql
--pre-sql / --pre-sql-file are also available as $PISTA_PRE_SQL / $PISTA_PRE_SQL_FILE.
Use --check to detect schema drift from the exit code. The exit code is 2 if the plan contains executable changes, 0 if not, and 1 on error. The output does not change. Suppressed drops alone exit 0 because they generate no executable DDL. Also available as $PISTA_CHECK.
pista plan --check schema.sql
echo $? # 0: no changes, 2: changes, 1: error
plan and dump open a read-only connection, so they cannot write to the database. Pass --no-read-only (env $PISTA_NO_READ_ONLY) to use a read-write connection.
Every connection sets search_path to public, so a server-side ALTER ROLE ... SET search_path does not reach it. --search-path (env $PISTA_SEARCH_PATH) sets another value. PostgreSQL's own default, "$user", public, is not used: it would read the objects of a schema named after the connecting role without their schema, and the role that runs migrations is often not the role the application connects as.
The catalog reports an object reachable through search_path without its schema. dump writes the object as the catalog reports it, and plan compares that form against the desired schema, so a desired schema that qualifies an object the catalog reports bare differs on every run. Under --search-path= the objects pistachio manages keep their schema. Under --search-path=myschema the objects in myschema lose theirs, and under the default so do those in public.
Note
apply sets search_path to the target schemas plus public so unqualified type and object references resolve. plan output does not include that SET search_path, so piping pista plan -n <schema> into psql for a non-public schema may fail on an unqualified reference. Qualify the reference or run pista apply.
apply¶
Apply the diff to the database.
pista apply schema.sql
# Multiple files
pista apply tables.sql views.sql
Use --pre-sql or --pre-sql-file to run SQL before applying changes (mutually exclusive). Also available as $PISTA_PRE_SQL / $PISTA_PRE_SQL_FILE. Use --with-tx to wrap the apply in a transaction (also available as $PISTA_WITH_TX).
# Inline SQL
pista apply schema.sql --pre-sql "SET statement_timeout = '5s';" --with-tx
# From file
pista apply schema.sql --pre-sql-file pre.sql --with-tx
Use --try-tx to wrap the apply in a transaction only when possible. It works like --with-tx, except that a diff containing CREATE/DROP INDEX CONCURRENTLY runs without a transaction instead of failing. The two flags are mutually exclusive. Also available as $PISTA_TRY_TX.
pista apply --try-tx schema.sql
The skipped transaction is recorded in the output:
-- Transaction skipped: plan contains CONCURRENTLY index DDL
CREATE INDEX CONCURRENTLY idx_users_name ON public.users USING btree (name);
An apply that skips the transaction is not all-or-nothing. A failure leaves the statements that already ran in place, and re-running pista apply applies the rest. A failed CREATE INDEX CONCURRENTLY also leaves an invalid index that must be dropped by hand.
To apply CONCURRENTLY to individual indexes, either write CREATE INDEX CONCURRENTLY directly or use the -- pista:concurrently directive before the CREATE INDEX statement. Both are treated equivalently:
-- pista:concurrently
CREATE INDEX idx_users_name ON public.users USING btree (name);
-- Equivalent: inline CONCURRENTLY
CREATE INDEX CONCURRENTLY idx_users_email ON public.users USING btree (email);
-- This index will NOT use CONCURRENTLY
CREATE INDEX idx_users_id ON public.users USING btree (id);
Use --concurrently-pre-sql (or --concurrently-pre-sql-file) to run SQL (typically SET lock_timeout = '...') before any CONCURRENTLY index DDL. The SQL is emitted only when the plan contains CREATE/DROP INDEX CONCURRENTLY. Because SET is session-scoped and CONCURRENTLY runs outside a transaction, the value carries over to every subsequent CONCURRENTLY statement in the same apply. Also available as $PISTA_CONCURRENTLY_PRE_SQL / $PISTA_CONCURRENTLY_PRE_SQL_FILE.
pista apply schema.sql --concurrently-pre-sql "SET lock_timeout = '5s';"
Use --disable-index-concurrently to ignore all CONCURRENTLY opt-ins (both inline and directive) and emit plain CREATE INDEX / DROP INDEX instead. This lets you keep the directives in your schema files while running a one-off plan/apply inside a transaction. Also available as $PISTA_DISABLE_INDEX_CONCURRENTLY.
pista plan --disable-index-concurrently schema.sql
pista apply --disable-index-concurrently --with-tx schema.sql
Use --force-index-concurrently to apply CONCURRENTLY to every CREATE INDEX and DROP INDEX the diff emits, regardless of per-index directives. This also covers pure drops (indexes removed from the desired schema), which the directive cannot reach. Conflicts with --disable-index-concurrently and --with-tx. Also available as $PISTA_FORCE_INDEX_CONCURRENTLY.
pista plan --force-index-concurrently schema.sql
pista apply --force-index-concurrently schema.sql
Note
When the generated diff includes CREATE INDEX CONCURRENTLY or DROP INDEX CONCURRENTLY, --with-tx cannot be used because CONCURRENTLY operations cannot run inside a transaction. If there are no index changes, --with-tx is allowed even when an index is opted into CONCURRENTLY. To run apply inside a transaction in spite of the opt-in, combine --with-tx with --disable-index-concurrently. To keep the opt-in and run without a transaction instead, use --try-tx.
Use --bulk-alter to combine consecutive ALTER TABLE actions on the same table into a single statement with comma-separated actions. This reduces metadata-lock churn and lets PostgreSQL plan the changes together. Foreign keys, RENAME, VALIDATE CONSTRAINT, RLS toggles, and skipped DROPs are kept as separate statements. Also available as $PISTA_BULK_ALTER.
pista plan --bulk-alter schema.sql
pista apply --bulk-alter schema.sql
ALTER TABLE public.users
ADD COLUMN email text,
ALTER COLUMN name SET NOT NULL,
DROP COLUMN legacy,
ADD CONSTRAINT users_id_pos CHECK (id > 0);
To merge ALTER TABLE actions for individual tables only, put the -- pista:bulk-alter directive before the CREATE TABLE statement. Other tables keep one statement per action. --bulk-alter merges every table regardless of the directive.
-- pista:bulk-alter
CREATE TABLE public.users (
id integer NOT NULL,
CONSTRAINT users_pkey PRIMARY KEY (id)
);
Use --assume-validated to treat every table constraint, domain constraint, and foreign key as validated. Pistachio ignores NOT VALID in the desired schema and never emits NOT VALID or VALIDATE CONSTRAINT. Use it when NOT VALID is a one-off migration step you do not want in the desired state. Also available as $PISTA_ASSUME_VALIDATED.
pista plan --assume-validated schema.sql
pista apply --assume-validated schema.sql
Use --exclusive to make apply runs on the same database mutually exclusive: while another exclusive apply is running, the command fails at once. --exclusive-wait waits for the other apply instead, up to the given duration (0 waits without limit). The two flags conflict. Also available as $PISTA_EXCLUSIVE / $PISTA_EXCLUSIVE_WAIT. See Preventing concurrent applies.
pista apply schema.sql --exclusive
pista apply schema.sql --exclusive-wait=5m
By default, plan and apply do not drop tables, views, enums, domains, composite types, columns, constraints, foreign keys, or indexes. Use --allow-drop to enable dropping specific object types (all, table, view, enum, domain, composite_type, sequence, routine, column, constraint, foreign_key, index, policy, trigger). Also available as $PISTA_ALLOW_DROP. constraint covers CHECK / UNIQUE / PRIMARY KEY / EXCLUSION; foreign keys are governed by foreign_key separately. composite_type also gates DROP ATTRIBUTE on a composite type. routine covers functions and procedures, and also gates the drop half of a recreate.
# Allow all drops
pista plan --allow-drop all schema.sql
# Allow only column and table drops
pista apply --allow-drop column,table schema.sql
Suppressed drops are emitted as commented-out DDL prefixed with -- skipped:. The plan still reports -- No changes when the only diff would be a suppressed drop, since no executable DDL is generated:
-- Plan for schema public (1 table, 0 views, 0 enums, 0 domains, 0 composite types, 0 sequences)
-- skipped: DROP TABLE public.legacy_users;
-- No changes
Note
Only pure removals of constraints, foreign keys, and indexes (those absent from the desired schema) are governed by --allow-drop=constraint / --allow-drop=foreign_key / --allow-drop=index. Definition changes still execute regardless of --allow-drop: constraints and foreign keys as DROP + ADD, and indexes as DROP + CREATE, because PostgreSQL has no ALTER CONSTRAINT and no general ALTER INDEX form for definition changes.
Foreign-key drops emitted because the owning table is being dropped follow the table-drop policy (not foreign_key): if the table drop is suppressed, the FK drop is suppressed too and surfaces as -- skipped: alongside the table.
dump¶
Dump the current database schema as SQL. Output can be used directly as a schema file.
pista dump