Skip to content

postgres: Drop() also drops custom types - #1426

Open
xhon-pelushi wants to merge 2 commits into
golang-migrate:masterfrom
xhon-pelushi:fix/626-postgres-drop-custom-types
Open

postgres: Drop() also drops custom types#1426
xhon-pelushi wants to merge 2 commits into
golang-migrate:masterfrom
xhon-pelushi:fix/626-postgres-drop-custom-types

Conversation

@xhon-pelushi

Copy link
Copy Markdown

Fixes #626.

Problem

`Drop()` deletes every table in the current schema but leaves custom types (enums, domains, standalone composite types) behind. If a migration creates one of those types, dropping and re-running fails because the type already exists — even though every table is gone.

Root cause

Confirmed empirically against a real local Postgres 16 instance, not just by reading the code:

```
CREATE TYPE mood AS ENUM ('sad','ok','happy');
CREATE TABLE widgets (id serial primary key, m mood);
DROP TABLE widgets CASCADE;
\dT+ -- mood is still there
```

`DROP TABLE ... CASCADE` cascades to things that depend on the table (views, FKs), not to types the table's columns merely reference — the dependency runs the other way. So a type used as a column type always survives a table drop.

Fix

After dropping tables, the same pattern for types: query, then drop one by one, `IF EXISTS ... CASCADE`.

The query mirrors the existing table query's schema-scoping (`current_schema()`) and adds two exclusions, verified against real catalog data:

  • Table row types: every table gets an implicit composite type; joining to `pg_class` and requiring `relkind = 'c'` (or no backing relation at all, `typrelid = 0`) excludes those while still catching real standalone composite types created via `CREATE TYPE ... AS (...)`.
  • Array types: every type gets an implicit array type (e.g. `_mood` alongside `mood`); the `NOT EXISTS (... el.typarray = t.oid)` check excludes those.

Tested against enums, domains, and standalone composite types together in the same schema, plus a second schema and a table with columns of all three custom types, to confirm the query returns exactly the 3 real user types and nothing else — not the table's row type, not any array type, not the other schema's type.

Testing

  • Ran the exact scenario from the issue end-to-end through the real `postgres.WithInstance` driver (not just the raw SQL) against a local Postgres 16: create the enum, create a table using it, `Drop()`, then re-run the exact `CREATE TYPE` from the issue. Before the fix this reproduces `type "mood" already exists`; after the fix it succeeds.
  • Added `testDropWithCustomTypes` to `postgres_test.go`, following the existing `dktesting`/`dktest` pattern used by the rest of this file (registered in the top-level `Test` alongside the others) — creates an enum, a domain, and a standalone composite type, uses all three as column types in a table, calls `Drop()`, then asserts all three types can be re-created without error.
  • `go build ./...` and `go vet ./...` clean on `database/postgres`.

I don't have Docker in this environment, so I could not run the `dktest`-based container suite that `testDropWithCustomTypes` is written for — the direct-driver run above against a real (non-containerized) Postgres 16 is what actually verifies the fix, and the added test is written to match how this repo's CI will run it.

Drop() dropped every table in the current schema but left custom
types (enums, domains, standalone composite types) behind. A
migration that creates one of those types could not be re-run on a
database that had just been dropped, since the type it tries to
create already exists.

Query pg_type for types owned by the current schema, excluding the
row type Postgres implicitly creates for every table (relkind != 'c'
in the join to pg_class) and the array type Postgres implicitly
creates for every type (the typarray back-reference check), then drop
what's left the same way tables are dropped: one by one, IF EXISTS,
CASCADE.

Fixes golang-migrate#626
@coveralls

coveralls commented Aug 18, 2026

Copy link
Copy Markdown

Coverage Status

coverage: 54.497% (+0.09%) from 54.412% — xhon-pelushi:fix/626-postgres-drop-custom-types into golang-migrate:master

… Drop()

A second Claude Code session independently verifying this PR found two real
regressions in the type-selection query added in the previous commit:

- Any type an extension installs into the current schema (citext, hstore,
  postgis, ...) was selected and DROP TYPE fails on it -- only dropping the
  extension can remove it. Drop() would return that error and abort with
  the tables already gone.

- A range type's auto-generated multirange type has an internal dependency
  on its range type. If the multirange sorts before the range type in the
  (unordered) query result, its DROP fails immediately for the same reason,
  again aborting with tables already dropped. Reordering wouldn't fully fix
  this either -- it would just make the failure order-dependent.

Exclude anything with an extension ('e') or internal ('i') pg_depend entry.
Verified against a real Postgres 16 instance: citext extension + enum +
range type (which also exercises its implicit multirange) + a table, in
combination. Before this commit, Drop() failed outright. After, it succeeds
and drops everything droppable, leaving only the extension-owned type,
which is correct since removing it means dropping the extension.

Adds testDropWithExtensionAndRangeTypes alongside the existing
testDropWithCustomTypes.
@xhon-pelushi

Copy link
Copy Markdown
Author

Pushed a fix for two real regressions caught in independent review: any extension-owned type (citext, postgis, ...) in the current schema made Drop() fail where it used to succeed, and a range type's auto-generated multirange type has an internal dependency that a plain DROP TYPE can't satisfy regardless of statement order — same failure mode, worse because it could abort with tables already dropped.

Excluded both via a pg_depend check (deptype IN ('e', 'i')). Verified against a real Postgres 16 instance with citext + enum + range type + table in one database — Drop() now succeeds and correctly leaves only the extension-owned type behind. Added testDropWithExtensionAndRangeTypes alongside the existing test.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Postgres Drop() implementation incomplete

2 participants