Skip to content

fix: Postgres Drop() also removes custom enum types#1391

Open
Yanhu007 wants to merge 1 commit into
golang-migrate:masterfrom
Yanhu007:fix/postgres-drop-all-objects
Open

fix: Postgres Drop() also removes custom enum types#1391
Yanhu007 wants to merge 1 commit into
golang-migrate:masterfrom
Yanhu007:fix/postgres-drop-all-objects

Conversation

@Yanhu007

Copy link
Copy Markdown

Fixes #626

Problem

Drop() only drops tables but leaves custom types (enums, etc.) behind. When a migration creates a custom type:

CREATE TYPE mytype AS ENUM('a', 'b');

Running Drop() followed by Up() fails:

pq: type "mytype" already exists

Fix

After dropping tables, query pg_type for enum types (typtype = 'e') in the current schema and drop them with CASCADE. This follows the same pattern used for table dropping (iterate + DROP IF EXISTS).

Previous PR #477 was closed without merge. This implementation uses a simpler approach querying pg_type directly.

Drop() only drops tables, leaving custom types (enums) behind.
When a migration creates a custom type, Drop() followed by Up()
fails with 'type already exists'.

Add type dropping after table dropping by querying pg_type for
enum types in the current schema.

Fixes golang-migrate#626
Copilot AI review requested due to automatic review settings April 13, 2026 09:37

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates the Postgres driver’s Drop() implementation to fully clean a schema by removing custom enum types in addition to dropping tables, fixing the “type already exists” failure when re-running migrations after Drop().

Changes:

  • Query pg_type/pg_namespace for enum types in the current schema (typtype = 'e').
  • Iterate found enum types and DROP TYPE IF EXISTS ... CASCADE after tables are dropped.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +464 to +471
query = `DROP TYPE IF EXISTS ` + pq.QuoteIdentifier(typeName) + ` CASCADE`
if _, err := p.conn.ExecContext(context.Background(), query); err != nil {
return &database.Error{OrigErr: err, Query: []byte(query)}
}
}
if err := types.Err(); err != nil {
return &database.Error{OrigErr: err, Query: []byte(query)}
}

Copilot AI Apr 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

types.Err() is wrapped using the mutable query variable, but by the time this runs query may contain the last DROP TYPE ... statement rather than the original SELECT ... FROM pg_type .... This makes the reported query misleading and complicates debugging. Consider keeping the SELECT in a separate variable (e.g., typesQuery) and using that for Query: in errors coming from iteration/scan/Err(), while using a separate dropQuery for the DDL.

Copilot uses AI. Check for mistakes.
Comment on lines +459 to +463
for types.Next() {
var typeName string
if err := types.Scan(&typeName); err != nil {
return err
}

Copilot AI Apr 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On types.Scan(&typeName) failure, this returns the raw error without attaching the SQL that was executing. Elsewhere in this method, query errors are wrapped with database.Error{..., Query: ...}; doing the same here (using the SELECT query, not the last DROP) would improve error context consistency.

Copilot uses AI. Check for mistakes.
Comment on lines +459 to +472
for types.Next() {
var typeName string
if err := types.Scan(&typeName); err != nil {
return err
}
query = `DROP TYPE IF EXISTS ` + pq.QuoteIdentifier(typeName) + ` CASCADE`
if _, err := p.conn.ExecContext(context.Background(), query); err != nil {
return &database.Error{OrigErr: err, Query: []byte(query)}
}
}
if err := types.Err(); err != nil {
return &database.Error{OrigErr: err, Query: []byte(query)}
}

Copilot AI Apr 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new behavior (dropping enum types during Drop()) isn't covered by the existing Postgres driver tests; database/testing.TestDrop only checks that Drop() returns nil. Please add an integration test that creates a custom enum type in the current schema, calls Drop(), and asserts the type no longer exists (or that re-creating the type succeeds) to prevent regressions like #626.

Suggested change
for types.Next() {
var typeName string
if err := types.Scan(&typeName); err != nil {
return err
}
query = `DROP TYPE IF EXISTS ` + pq.QuoteIdentifier(typeName) + ` CASCADE`
if _, err := p.conn.ExecContext(context.Background(), query); err != nil {
return &database.Error{OrigErr: err, Query: []byte(query)}
}
}
if err := types.Err(); err != nil {
return &database.Error{OrigErr: err, Query: []byte(query)}
}
typeNames := make([]string, 0)
for types.Next() {
var typeName string
if err := types.Scan(&typeName); err != nil {
return err
}
if len(typeName) > 0 {
typeNames = append(typeNames, typeName)
}
}
if err := types.Err(); err != nil {
return &database.Error{OrigErr: err, Query: []byte(query)}
}
if len(typeNames) > 0 {
for _, t := range typeNames {
query = `DROP TYPE IF EXISTS ` + pq.QuoteIdentifier(t) + ` CASCADE`
if _, err := p.conn.ExecContext(context.Background(), query); err != nil {
return &database.Error{OrigErr: err, Query: []byte(query)}
}
}
}

Copilot uses AI. Check for mistakes.
@coveralls

Copy link
Copy Markdown

Coverage Status

coverage: 54.377% (-0.06%) from 54.432% — Yanhu007:fix/postgres-drop-all-objects into golang-migrate:master

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

3 participants