Skip to main content
supaschema fails closed when intent cannot be proven from the SQL parse tree. Hints are the explicit, reviewable way to state supported source intent. Use hints for four cases:
  • a destructive change that a human already reviewed;
  • a rename that would otherwise look like drop plus create;
  • roles allowed by the free grant least-privilege rule;
  • table columns required by the free RLS policy-column rule.
Do not commit broad destructive approval. Use exact object keys, not "*", in shared config.

Destructive changes

A blocked plan looks like this:
Recovery:
  1. Review the rendered -- BLOCKED section.
  2. Confirm the drop is intended.
  3. Add the exact object key.
"*" approves all destructive changes for one run; prefer exact keys in committed config. Dropped grants render as REVOKE, and dropped default privileges render as the reverse ALTER DEFAULT PRIVILEGES ... REVOKE, but both still require the destructive hint first.

Grant and RLS safety

hints.allowedGrantees declares the roles that grant/default-privilege diagnostics accept:
hints.requiredPolicyColumns declares table columns that must appear in the effective RLS predicate for each configured table:
For SELECT and DELETE, the required-column rule reads USING. For INSERT, it reads WITH CHECK. For UPDATE and ALL, required-column matching uses WITH CHECK when present and otherwise reads PostgreSQL’s USING fallback, while the RLS safety pack still warns when an update-capable policy omits an explicit WITH CHECK.

Column drops and type changes

When only columns change, the hinted plan renders data-preserving column ALTERs instead of replacing the table (SUPA_PLAN_COLUMN_ALTER_HINT_REQUIRED):
  • removed columns render ALTER TABLE ... DROP COLUMN IF EXISTS,
  • type changes render ALTER COLUMN ... TYPE <type> USING <column>::<type>,
  • identity, generated-expression, NOT NULL, and DEFAULT changes render without a destructive hint.
Type changes also emit SUPA_PLAN_COLUMN_TYPE_USING_REVIEW and add an inline SQL comment because the rendered USING <column>::<type> expression is only a default identity cast. Replace it when PostgreSQL needs a custom conversion. Identity changes emit SUPA_PLAN_COLUMN_IDENTITY_REVIEW because PostgreSQL identity actions affect future generated values. Generated-expression changes emit SUPA_PLAN_COLUMN_GENERATED_REVIEW because stored generated values can be rewritten and statistics may need refresh. Table-constraint changes render as separate guarded constraint operations. A hinted table replace drops and recreates the table — it is not data-preserving; prefer the column lane or hand-author the migration. If a routine, view, policy, or trigger depends on a column being dropped or type-changed, the dependent object must be dropped or replaced before the table alter. If the source still references the changed column, the planner blocks with SUPA_PLAN_COLUMN_DEPENDENT_REWRITE_REQUIRED.

Routine dependencies

supaschema extracts routine dependencies from SQL-standard function bodies, SQL string bodies, and common static PL/pgSQL statements such as SELECT, PERFORM, DML, RETURN QUERY, RETURN (SELECT ...), cursor queries, and FOR ... IN query loops. Dynamic SQL (EXECUTE), partially parsed PL/pgSQL, and unsupported routine languages fail closed when the same plan changes relations or types. Supaschema no longer accepts config-supplied routine dependency hints for this path because they are not structural proof. Rewrite the routine so dependencies are statically extractable, include the routine rewrite in the same plan, split the relation/type change, or use a reviewed explicit migration.

Incompatible view and routine replacements

PostgreSQL rejects CREATE OR REPLACE when a view drops/renames/reorders output columns, or when supaschema cannot prove the output columns (SUPA_PLAN_VIEW_REPLACE_INCOMPATIBLE). A routine replacement also blocks when the return type or OUT parameters change (SUPA_PLAN_ROUTINE_RETURN_TYPE_CHANGED). Hinting the object key renders a guarded DROP ... IF EXISTS followed by the new definition. Dependent objects (views over views) must be hinted and recreated in the same plan.

Renames

Without a hint, a rename diffs as drop+create (destructive, blocked). Declare the rename instead:
The rendered SQL is a guarded DO block: it raises if both names exist, renames if only the old name exists, and is a no-op if the rename already happened. Rename hints:
  • must keep the same object kind (SUPA_PLAN_RENAME_KIND_MISMATCH),
  • must stay in the same schema (SUPA_PLAN_RENAME_SET_SCHEMA_UNSUPPORTED),
  • support schemas, tables, sequences, indexes, functions, procedures, views, and materialized views (SUPA_PLAN_RENAME_UNSUPPORTED otherwise),
  • always carry a SUPA_PLAN_RENAME_VERIFY_REQUIRED warning — run supaschema verify.

Object keys

Keys follow kind:schema.name, with (signature) for functions/procedures and :table for table-scoped objects:
supaschema plan prints every operation key; copy keys from there rather than constructing them by hand.

Enum reordering and removal

Enum value removal/reordering has no in-place ALTER in PostgreSQL. The hinted plan replaces the type (drop+create), which fails while columns depend on it. The safe hand-authored recipe is:
Validate it with supaschema check and supaschema verify like any hand-authored migration.

Changes that require source intent

Data backfills have no broad hint lane because the package must not invent row values, predicates, or conversion expressions. Put that intent in an explicit reviewed migration or another checked source-intent artifact, then validate it with supaschema check and supaschema verify. When one table change drops columns and adds columns in the same transition, supaschema treats it as a possible storage rewrite. If the configured migration corpus has no reviewed DML or DO intent, planning blocks with SUPA_PLAN_DATA_TRANSITION_REQUIRED even when the destructive table key is hinted. CASCADE drops have no hint lane by design.
Last modified on July 6, 2026