Repository: sqlc-dev/sqlc
Pull request #2853 (2023-10-13 19:51:42)

fix(mysql): Add, drop, rename and change enum values
Extends the work done in #2680 to support adding and dropping columns and tables.

Fixes https://github.com/sqlc-dev/sqlc/issues/2475


Patch:
diff --git a/examples/kotlin/src/main/kotlin/com/example/ondeck/mysql/Models.kt b/examples/kotlin/src/main/kotlin/com/example/ondeck/mysql/Models.kt
index 4cb2a9579..a034c3600 100644
--- a/examples/kotlin/src/main/kotlin/com/example/ondeck/mysql/Models.kt
+++ b/examples/kotlin/src/main/kotlin/com/example/ondeck/mysql/Models.kt
@@ -7,12 +7,12 @@ package com.example.ondeck.mysql
 import java.sql.Timestamp
 import java.time.Instant
 
-enum class VenuesStatus(val value: String) {
+enum class VenueStatus(val value: String) {
   OPEN("open"),
   CLOSED("closed");
 
   companion object {
-    private val map = VenuesStatus.values().associateBy(VenuesStatus::value)
+    private val map = VenueStatus.values().associateBy(VenueStatus::value)
     fun lookup(value: String) = map[value]
   }
 }
@@ -26,7 +26,7 @@ data class City (
 data class Venue (
   val id: Long,
   // Venues can be either open or closed
-  val status: VenuesStatus,
+  val status: VenueStatus,
   val statuses: String?,
   // This value appears in public URLs
   val slug: String,
diff --git a/examples/kotlin/src/main/kotlin/com/example/ondeck/mysql/Queries.kt b/examples/kotlin/src/main/kotlin/com/example/ondeck/mysql/Queries.kt
index 5e97478f4..7cacad0d1 100644
--- a/examples/kotlin/src/main/kotlin/com/example/ondeck/mysql/Queries.kt
+++ b/examples/kotlin/src/main/kotlin/com/example/ondeck/mysql/Queries.kt
@@ -20,7 +20,7 @@ interface Queries {
       name: String,
       city: String,
       spotifyPlaylist: String,
-      status: VenuesStatus,
+      status: VenueStatus,
       statuses: String?,
       tags: String?): Long
   
diff --git a/examples/kotlin/src/main/kotlin/com/example/ondeck/mysql/QueriesImpl.kt b/examples/kotlin/src/main/kotlin/com/example/ondeck/mysql/QueriesImpl.kt
index c3cba3036..172967cde 100644
--- a/examples/kotlin/src/main/kotlin/com/example/ondeck/mysql/QueriesImpl.kt
+++ b/examples/kotlin/src/main/kotlin/com/example/ondeck/mysql/QueriesImpl.kt
@@ -116,7 +116,7 @@ class QueriesImpl(private val conn: Connection) : Queries {
       name: String,
       city: String,
       spotifyPlaylist: String,
-      status: VenuesStatus,
+      status: VenueStatus,
       statuses: String?,
       tags: String?): Long {
     return conn.prepareStatement(createVenue, Statement.RETURN_GENERATED_KEYS).use { stmt ->
@@ -180,7 +180,7 @@ class QueriesImpl(private val conn: Connection) : Queries {
       }
       val ret = Venue(
                 results.getLong(1),
-                VenuesStatus.lookup(results.getString(2))!!,
+                VenueStatus.lookup(results.getString(2))!!,
                 results.getString(3),
                 results.getString(4),
                 results.getString(5),
@@ -223,7 +223,7 @@ class QueriesImpl(private val conn: Connection) : Queries {
       while (results.next()) {
           ret.add(Venue(
                 results.getLong(1),
-                VenuesStatus.lookup(results.getString(2))!!,
+                VenueStatus.lookup(results.getString(2))!!,
                 results.getString(3),
                 results.getString(4),
                 results.getString(5),
diff --git a/examples/ondeck/mysql/models.go b/examples/ondeck/mysql/models.go
index 0ca972f6a..12e87cd90 100644
--- a/examples/ondeck/mysql/models.go
+++ b/examples/ondeck/mysql/models.go
@@ -11,46 +11,46 @@ import (
 	"time"
 )
 
-type VenuesStatus string
+type VenueStatus string
 
 const (
-	VenuesStatusOpen   VenuesStatus = "open"
-	VenuesStatusClosed VenuesStatus = "closed"
+	VenueStatusOpen   VenueStatus = "open"
+	VenueStatusClosed VenueStatus = "closed"
 )
 
-func (e *VenuesStatus) Scan(src interface{}) error {
+func (e *VenueStatus) Scan(src interface{}) error {
 	switch s := src.(type) {
 	case []byte:
-		*e = VenuesStatus(s)
+		*e = VenueStatus(s)
 	case string:
-		*e = VenuesStatus(s)
+		*e = VenueStatus(s)
 	default:
-		return fmt.Errorf("unsupported scan type for VenuesStatus: %T", src)
+		return fmt.Errorf("unsupported scan type for VenueStatus: %T", src)
 	}
 	return nil
 }
 
-type NullVenuesStatus struct {
-	VenuesStatus VenuesStatus `json:"venues_status"`
-	Valid        bool         `json:"valid"` // Valid is true if VenuesStatus is not NULL
+type NullVenueStatus struct {
+	VenueStatus VenueStatus `json:"venue_status"`
+	Valid       bool        `json:"valid"` // Valid is true if VenueStatus is not NULL
 }
 
 // Scan implements the Scanner interface.
-func (ns *NullVenuesStatus) Scan(value interface{}) error {
+func (ns *NullVenueStatus) Scan(value interface{}) error {
 	if value == nil {
-		ns.VenuesStatus, ns.Valid = "", false
+		ns.VenueStatus, ns.Valid = "", false
 		return nil
 	}
 	ns.Valid = true
-	return ns.VenuesStatus.Scan(value)
+	return ns.VenueStatus.Scan(value)
 }
 
 // Value implements the driver Valuer interface.
-func (ns NullVenuesStatus) Value() (driver.Value, error) {
+func (ns NullVenueStatus) Value() (driver.Value, error) {
 	if !ns.Valid {
 		return nil, nil
 	}
-	return string(ns.VenuesStatus), nil
+	return string(ns.VenueStatus), nil
 }
 
 type City struct {
@@ -62,7 +62,7 @@ type City struct {
 type Venue struct {
 	ID uint64 `json:"id"`
 	// Venues can be either open or closed
-	Status   VenuesStatus   `json:"status"`
+	Status   VenueStatus    `json:"status"`
 	Statuses sql.NullString `json:"statuses"`
 	// This value appears in public URLs
 	Slug            string         `json:"slug"`
diff --git a/examples/ondeck/mysql/venue.sql.go b/examples/ondeck/mysql/venue.sql.go
index 55d37ad78..13b5e28d0 100644
--- a/examples/ondeck/mysql/venue.sql.go
+++ b/examples/ondeck/mysql/venue.sql.go
@@ -37,7 +37,7 @@ type CreateVenueParams struct {
 	Name            string         `json:"name"`
 	City            string         `json:"city"`
 	SpotifyPlaylist string         `json:"spotify_playlist"`
-	Status          VenuesStatus   `json:"status"`
+	Status          VenueStatus    `json:"status"`
 	Statuses        sql.NullString `json:"statuses"`
 	Tags            sql.NullString `json:"tags"`
 }
diff --git a/internal/sql/catalog/catalog.go b/internal/sql/catalog/catalog.go
index 9a4a29e88..278ea8797 100644
--- a/internal/sql/catalog/catalog.go
+++ b/internal/sql/catalog/catalog.go
@@ -83,7 +83,7 @@ func (c *Catalog) Update(stmt ast.Statement, colGen columnGenerator) error {
 		err = c.createCompositeType(n)
 
 	case *ast.CreateEnumStmt:
-		err = c.createEnum(n, false)
+		err = c.createEnum(n)
 
 	case *ast.CreateExtensionStmt:
 		err = c.createExtension(n)
diff --git a/internal/sql/catalog/table.go b/internal/sql/catalog/table.go
index 1c9bacf99..bfa2da027 100644
--- a/internal/sql/catalog/table.go
+++ b/internal/sql/catalog/table.go
@@ -41,7 +41,7 @@ func (table *Table) isExistColumn(cmd *ast.AlterTableCmd) (int, error) {
 	return -1, nil
 }
 
-func (table *Table) addColumn(c *Catalog, cmd *ast.AlterTableCmd) error {
+func (c *Catalog) addColumn(table *Table, cmd *ast.AlterTableCmd) error {
 	for _, c := range table.Columns {
 		if c.Name == cmd.Def.Colname {
 			if !cmd.MissingOk {
@@ -50,12 +50,10 @@ func (table *Table) addColumn(c *Catalog, cmd *ast.AlterTableCmd) error {
 			return nil
 		}
 	}
-
-	tc, err := c.defToColumn(table.Rel, cmd.Def)
+	tc, err := c.defineColumn(table.Rel, cmd.Def)
 	if err != nil {
 		return err
 	}
-
 	table.Columns = append(table.Columns, tc)
 	return nil
 }
@@ -73,14 +71,26 @@ func (table *Table) alterColumnType(cmd *ast.AlterTableCmd) error {
 	return nil
 }
 
-func (table *Table) dropColumn(cmd *ast.AlterTableCmd) error {
+func (c *Catalog) dropColumn(table *Table, cmd *ast.AlterTableCmd) error {
 	index, err := table.isExistColumn(cmd)
 	if err != nil {
 		return err
 	}
-	if index >= 0 {
-		table.Columns = append(table.Columns[:index], table.Columns[index+1:]...)
+	if index < 0 {
+		return nil
+	}
+	col := table.Columns[index]
+	if col.linkedType {
+		drop := &ast.DropTypeStmt{
+			Types: []*ast.TypeName{
+				&col.Type,
+			},
+		}
+		if err := c.dropType(drop); err != nil {
+			return err
+		}
 	}
+	table.Columns = append(table.Columns[:index], table.Columns[index+1:]...)
 	return nil
 }
 
@@ -118,6 +128,8 @@ type Column struct {
 	ArrayDims  int
 	Comment    string
 	Length     *int
+
+	linkedType bool
 }
 
 // An interface is used to resolve a circular import between the catalog and compiler packages.
@@ -184,7 +196,7 @@ func (c *Catalog) alterTable(stmt *ast.AlterTableStmt) error {
 		case *ast.AlterTableCmd:
 			switch cmd.Subtype {
 			case ast.AT_AddColumn:
-				if err := table.addColumn(c, cmd); err != nil {
+				if err := c.addColumn(table, cmd); err != nil {
 					return err
 				}
 			case ast.AT_AlterColumnType:
@@ -192,7 +204,7 @@ func (c *Catalog) alterTable(stmt *ast.AlterTableStmt) error {
 					return err
 				}
 			case ast.AT_DropColumn:
-				if err := table.dropColumn(cmd); err != nil {
+				if err := c.dropColumn(table, cmd); err != nil {
 					return err
 				}
 			case ast.AT_DropNotNull:
@@ -301,8 +313,7 @@ func (c *Catalog) createTable(stmt *ast.CreateTableStmt) error {
 				}
 				continue
 			}
-
-			tc, err := c.defToColumn(stmt.Name, col)
+			tc, err := c.defineColumn(stmt.Name, col)
 			if err != nil {
 				return err
 			}
@@ -321,7 +332,7 @@ func (c *Catalog) createTable(stmt *ast.CreateTableStmt) error {
 	return nil
 }
 
-func (c *Catalog) defToColumn(table *ast.TableName, col *ast.ColumnDef) (*Column, error) {
+func (c *Catalog) defineColumn(table *ast.TableName, col *ast.ColumnDef) (*Column, error) {
 	tc := &Column{
 		Name:       col.Colname,
 		Type:       *col.TypeName,
@@ -337,12 +348,12 @@ func (c *Catalog) defToColumn(table *ast.TableName, col *ast.ColumnDef) (*Column
 			Name: fmt.Sprintf("%s_%s", table.Name, col.Colname),
 		}
 		s := &ast.CreateEnumStmt{TypeName: &typeName, Vals: col.Vals}
-		if err := c.createEnum(s, true); err != nil {
+		if err := c.createEnum(s); err != nil {
 			return nil, err
 		}
 		tc.Type = typeName
+		tc.linkedType = true
 	}
-
 	return tc, nil
 }
 
@@ -359,13 +370,24 @@ func (c *Catalog) dropTable(stmt *ast.DropTableStmt) error {
 			return err
 		}
 
-		_, idx, err := schema.getTable(name)
+		tbl, idx, err := schema.getTable(name)
 		if errors.Is(err, sqlerr.NotFound) && stmt.IfExists {
 			continue
 		} else if err != nil {
 			return err
 		}
 
+		drop := &ast.DropTypeStmt{}
+		for _, col := range tbl.Columns {
+			if !col.linkedType {
+				continue
+			}
+			drop.Types = append(drop.Types, &col.Type)
+		}
+		if err := c.dropType(drop); err != nil {
+			return err
+		}
+
 		schema.Tables = append(schema.Tables[:idx], schema.Tables[idx+1:]...)
 	}
 	return nil
@@ -389,6 +411,18 @@ func (c *Catalog) renameColumn(stmt *ast.RenameColumnStmt) error {
 		return sqlerr.ColumnNotFound(tbl.Rel.Name, stmt.Col.Name)
 	}
 	tbl.Columns[idx].Name = *stmt.NewName
+
+	if tbl.Columns[idx].linkedType {
+		name := fmt.Sprintf("%s_%s", tbl.Rel.Name, *stmt.NewName)
+		rename := &ast.RenameTypeStmt{
+			Type:    &tbl.Columns[idx].Type,
+			NewName: &name,
+		}
+		if err := c.renameType(rename); err != nil {
+			return err
+		}
+	}
+
 	return nil
 }
 
@@ -403,6 +437,20 @@ func (c *Catalog) renameTable(stmt *ast.RenameTableStmt) error {
 	if stmt.NewName != nil {
 		tbl.Rel.Name = *stmt.NewName
 	}
+
+	for idx := range tbl.Columns {
+		if tbl.Columns[idx].linkedType {
+			name := fmt.Sprintf("%s_%s", *stmt.NewName, tbl.Columns[idx].Name)
+			rename := &ast.RenameTypeStmt{
+				Type:    &tbl.Columns[idx].Type,
+				NewName: &name,
+			}
+			if err := c.renameType(rename); err != nil {
+				return err
+			}
+		}
+	}
+
 	return nil
 }
 
diff --git a/internal/sql/catalog/types.go b/internal/sql/catalog/types.go
index 2bb1033b3..464472bcf 100644
--- a/internal/sql/catalog/types.go
+++ b/internal/sql/catalog/types.go
@@ -62,7 +62,7 @@ func sameType(a, b *ast.TypeName) bool {
 	return true
 }
 
-func (c *Catalog) createEnum(stmt *ast.CreateEnumStmt, overwrite bool) error {
+func (c *Catalog) createEnum(stmt *ast.CreateEnumStmt) error {
 	ns := stmt.TypeName.Schema
 	if ns == "" {
 		ns = c.DefaultSchema
@@ -81,18 +81,8 @@ func (c *Catalog) createEnum(stmt *ast.CreateEnumStmt, overwrite bool) error {
 	if _, _, err := schema.getTable(tbl); err == nil {
 		return sqlerr.RelationExists(tbl.Name)
 	}
-	if typ, _, err := schema.getType(stmt.TypeName); err == nil {
-		if !overwrite {
-			return sqlerr.TypeExists(tbl.Name)
-		}
-
-		enum, ok := typ.(*Enum)
-		if !ok {
-			return fmt.Errorf("type is not an enum: %s", stmt.TypeName.Name)
-		}
-		enum.Vals = stringSlice(stmt.Vals)
-
-		return nil
+	if _, _, err := schema.getType(stmt.TypeName); err == nil {
+		return sqlerr.TypeExists(tbl.Name)
 	}
 	schema.Types = append(schema.Types, &Enum{
 		Name: stmt.TypeName.Name,


Test patch:
diff --git a/examples/kotlin/src/test/kotlin/com/example/ondeck/mysql/QueriesImplTest.kt b/examples/kotlin/src/test/kotlin/com/example/ondeck/mysql/QueriesImplTest.kt
index e946157ba..27fac2353 100644
--- a/examples/kotlin/src/test/kotlin/com/example/ondeck/mysql/QueriesImplTest.kt
+++ b/examples/kotlin/src/test/kotlin/com/example/ondeck/mysql/QueriesImplTest.kt
@@ -25,8 +25,8 @@ class QueriesImplTest {
             name = "The Fillmore",
             city = city.slug,
             spotifyPlaylist = "spotify=uri",
-            status = VenuesStatus.OPEN,
-            statuses = listOf(VenuesStatus.OPEN, VenuesStatus.CLOSED).joinToString(","),
+            status = VenueStatus.OPEN,
+            statuses = listOf(VenueStatus.OPEN, VenueStatus.CLOSED).joinToString(","),
             tags = listOf("rock", "punk").joinToString(",")
         )
         val venue = q.getVenue(
diff --git a/examples/ondeck/mysql/db_test.go b/examples/ondeck/mysql/db_test.go
index b4b343f9d..a6ddecc18 100644
--- a/examples/ondeck/mysql/db_test.go
+++ b/examples/ondeck/mysql/db_test.go
@@ -45,8 +45,8 @@ func runOnDeckQueries(t *testing.T, q *Queries) {
 		Name:            "The Fillmore",
 		City:            city.Slug,
 		SpotifyPlaylist: "spotify:uri",
-		Status:          VenuesStatusOpen,
-		Statuses:        join(string(VenuesStatusOpen), string(VenuesStatusClosed)),
+		Status:          VenueStatusOpen,
+		Statuses:        join(string(VenueStatusOpen), string(VenueStatusClosed)),
 		Tags:            join("rock", "punk"),
 	})
 	if err != nil {
diff --git a/internal/endtoend/testdata/enum_alter_change/mysql/go/models.go b/internal/endtoend/testdata/enum_alter_change/mysql/go/models.go
deleted file mode 100644
index 3af081050..000000000
--- a/internal/endtoend/testdata/enum_alter_change/mysql/go/models.go
+++ /dev/null
@@ -1,60 +0,0 @@
-// Code generated by sqlc. DO NOT EDIT.
-// versions:
-//   sqlc v1.20.0
-
-package querytest
-
-import (
-	"database/sql/driver"
-	"fmt"
-)
-
-type AuthorsStatus string
-
-const (
-	AuthorsStatusInit       AuthorsStatus = "init"
-	AuthorsStatusDone       AuthorsStatus = "done"
-	AuthorsStatusCanceled   AuthorsStatus = "canceled"
-	AuthorsStatusProcessing AuthorsStatus = "processing"
-	AuthorsStatusWaiting    AuthorsStatus = "waiting"
-)
-
-func (e *AuthorsStatus) Scan(src interface{}) error {
-	switch s := src.(type) {
-	case []byte:
-		*e = AuthorsStatus(s)
-	case string:
-		*e = AuthorsStatus(s)
-	default:
-		return fmt.Errorf("unsupported scan type for AuthorsStatus: %T", src)
-	}
-	return nil
-}
-
-type NullAuthorsStatus struct {
-	AuthorsStatus AuthorsStatus
-	Valid         bool // Valid is true if AuthorsStatus is not NULL
-}
-
-// Scan implements the Scanner interface.
-func (ns *NullAuthorsStatus) Scan(value interface{}) error {
-	if value == nil {
-		ns.AuthorsStatus, ns.Valid = "", false
-		return nil
-	}
-	ns.Valid = true
-	return ns.AuthorsStatus.Scan(value)
-}
-
-// Value implements the driver Valuer interface.
-func (ns NullAuthorsStatus) Value() (driver.Value, error) {
-	if !ns.Valid {
-		return nil, nil
-	}
-	return string(ns.AuthorsStatus), nil
-}
-
-type Author struct {
-	ID     int64
-	Status AuthorsStatus
-}
diff --git a/internal/endtoend/testdata/enum_alter_change/mysql/go/query.sql.go b/internal/endtoend/testdata/enum_alter_change/mysql/go/query.sql.go
deleted file mode 100644
index ccac241cd..000000000
--- a/internal/endtoend/testdata/enum_alter_change/mysql/go/query.sql.go
+++ /dev/null
@@ -1,37 +0,0 @@
-// Code generated by sqlc. DO NOT EDIT.
-// versions:
-//   sqlc v1.20.0
-// source: query.sql
-
-package querytest
-
-import (
-	"context"
-)
-
-const listAuthors = `-- name: ListAuthors :many
-select id, status from authors
-`
-
-func (q *Queries) ListAuthors(ctx context.Context) ([]Author, error) {
-	rows, err := q.db.QueryContext(ctx, listAuthors)
-	if err != nil {
-		return nil, err
-	}
-	defer rows.Close()
-	var items []Author
-	for rows.Next() {
-		var i Author
-		if err := rows.Scan(&i.ID, &i.Status); err != nil {
-			return nil, err
-		}
-		items = append(items, i)
-	}
-	if err := rows.Close(); err != nil {
-		return nil, err
-	}
-	if err := rows.Err(); err != nil {
-		return nil, err
-	}
-	return items, nil
-}
diff --git a/internal/endtoend/testdata/enum_alter_change/mysql/query.sql b/internal/endtoend/testdata/enum_alter_change/mysql/query.sql
deleted file mode 100644
index 0b16d94be..000000000
--- a/internal/endtoend/testdata/enum_alter_change/mysql/query.sql
+++ /dev/null
@@ -1,2 +0,0 @@
--- name: ListAuthors :many
-select * from authors;
diff --git a/internal/endtoend/testdata/enum_alter_change/mysql/schema.sql b/internal/endtoend/testdata/enum_alter_change/mysql/schema.sql
deleted file mode 100644
index 67c89a0ef..000000000
--- a/internal/endtoend/testdata/enum_alter_change/mysql/schema.sql
+++ /dev/null
@@ -1,9 +0,0 @@
--- similar to issue https://github.com/sqlc-dev/sqlc/issues/1503
-
-CREATE TABLE authors (
-  id   bigint primary key,
-  status enum("ok", "init") default "init" not null
-);
-
--- remove this alter to see the change in models.go
-ALTER TABLE authors CHANGE status status enum('init', 'done', 'canceled', 'processing', 'waiting') default "init" not null;
\ No newline at end of file
diff --git a/internal/endtoend/testdata/enum_alter_change/mysql/sqlc.json b/internal/endtoend/testdata/enum_alter_change/mysql/sqlc.json
deleted file mode 100644
index feb988c2b..000000000
--- a/internal/endtoend/testdata/enum_alter_change/mysql/sqlc.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
-  "version": "1",
-  "packages": [
-    {
-      "name": "querytest",
-      "path": "go",
-      "schema": "schema.sql",
-      "queries": "query.sql",
-      "engine": "mysql",
-      "omit_unused_structs": true
-    }
-  ]
-}
diff --git a/internal/endtoend/testdata/enum_alter_modify/mysql/go/db.go b/internal/endtoend/testdata/enum_alter_modify/mysql/go/db.go
deleted file mode 100644
index 57406b68e..000000000
--- a/internal/endtoend/testdata/enum_alter_modify/mysql/go/db.go
+++ /dev/null
@@ -1,31 +0,0 @@
-// Code generated by sqlc. DO NOT EDIT.
-// versions:
-//   sqlc v1.20.0
-
-package querytest
-
-import (
-	"context"
-	"database/sql"
-)
-
-type DBTX interface {
-	ExecContext(context.Context, string, ...interface{}) (sql.Result, error)
-	PrepareContext(context.Context, string) (*sql.Stmt, error)
-	QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error)
-	QueryRowContext(context.Context, string, ...interface{}) *sql.Row
-}
-
-func New(db DBTX) *Queries {
-	return &Queries{db: db}
-}
-
-type Queries struct {
-	db DBTX
-}
-
-func (q *Queries) WithTx(tx *sql.Tx) *Queries {
-	return &Queries{
-		db: tx,
-	}
-}
diff --git a/internal/endtoend/testdata/enum_alter_modify/mysql/go/models.go b/internal/endtoend/testdata/enum_alter_modify/mysql/go/models.go
deleted file mode 100644
index 3af081050..000000000
--- a/internal/endtoend/testdata/enum_alter_modify/mysql/go/models.go
+++ /dev/null
@@ -1,60 +0,0 @@
-// Code generated by sqlc. DO NOT EDIT.
-// versions:
-//   sqlc v1.20.0
-
-package querytest
-
-import (
-	"database/sql/driver"
-	"fmt"
-)
-
-type AuthorsStatus string
-
-const (
-	AuthorsStatusInit       AuthorsStatus = "init"
-	AuthorsStatusDone       AuthorsStatus = "done"
-	AuthorsStatusCanceled   AuthorsStatus = "canceled"
-	AuthorsStatusProcessing AuthorsStatus = "processing"
-	AuthorsStatusWaiting    AuthorsStatus = "waiting"
-)
-
-func (e *AuthorsStatus) Scan(src interface{}) error {
-	switch s := src.(type) {
-	case []byte:
-		*e = AuthorsStatus(s)
-	case string:
-		*e = AuthorsStatus(s)
-	default:
-		return fmt.Errorf("unsupported scan type for AuthorsStatus: %T", src)
-	}
-	return nil
-}
-
-type NullAuthorsStatus struct {
-	AuthorsStatus AuthorsStatus
-	Valid         bool // Valid is true if AuthorsStatus is not NULL
-}
-
-// Scan implements the Scanner interface.
-func (ns *NullAuthorsStatus) Scan(value interface{}) error {
-	if value == nil {
-		ns.AuthorsStatus, ns.Valid = "", false
-		return nil
-	}
-	ns.Valid = true
-	return ns.AuthorsStatus.Scan(value)
-}
-
-// Value implements the driver Valuer interface.
-func (ns NullAuthorsStatus) Value() (driver.Value, error) {
-	if !ns.Valid {
-		return nil, nil
-	}
-	return string(ns.AuthorsStatus), nil
-}
-
-type Author struct {
-	ID     int64
-	Status AuthorsStatus
-}
diff --git a/internal/endtoend/testdata/enum_alter_modify/mysql/go/query.sql.go b/internal/endtoend/testdata/enum_alter_modify/mysql/go/query.sql.go
deleted file mode 100644
index ccac241cd..000000000
--- a/internal/endtoend/testdata/enum_alter_modify/mysql/go/query.sql.go
+++ /dev/null
@@ -1,37 +0,0 @@
-// Code generated by sqlc. DO NOT EDIT.
-// versions:
-//   sqlc v1.20.0
-// source: query.sql
-
-package querytest
-
-import (
-	"context"
-)
-
-const listAuthors = `-- name: ListAuthors :many
-select id, status from authors
-`
-
-func (q *Queries) ListAuthors(ctx context.Context) ([]Author, error) {
-	rows, err := q.db.QueryContext(ctx, listAuthors)
-	if err != nil {
-		return nil, err
-	}
-	defer rows.Close()
-	var items []Author
-	for rows.Next() {
-		var i Author
-		if err := rows.Scan(&i.ID, &i.Status); err != nil {
-			return nil, err
-		}
-		items = append(items, i)
-	}
-	if err := rows.Close(); err != nil {
-		return nil, err
-	}
-	if err := rows.Err(); err != nil {
-		return nil, err
-	}
-	return items, nil
-}
diff --git a/internal/endtoend/testdata/enum_alter_modify/mysql/query.sql b/internal/endtoend/testdata/enum_alter_modify/mysql/query.sql
deleted file mode 100644
index 0b16d94be..000000000
--- a/internal/endtoend/testdata/enum_alter_modify/mysql/query.sql
+++ /dev/null
@@ -1,2 +0,0 @@
--- name: ListAuthors :many
-select * from authors;
diff --git a/internal/endtoend/testdata/enum_alter_modify/mysql/schema.sql b/internal/endtoend/testdata/enum_alter_modify/mysql/schema.sql
deleted file mode 100644
index d199af185..000000000
--- a/internal/endtoend/testdata/enum_alter_modify/mysql/schema.sql
+++ /dev/null
@@ -1,9 +0,0 @@
--- similar to issue https://github.com/sqlc-dev/sqlc/issues/1503
-
-CREATE TABLE authors (
-  id   bigint primary key,
-  status enum("ok", "init") default "init" not null
-);
-
--- remove this alter to see the change in models.go
-ALTER TABLE authors MODIFY status enum('init', 'done', 'canceled', 'processing', 'waiting') default "init" not null;
\ No newline at end of file
diff --git a/internal/endtoend/testdata/enum_alter_modify/mysql/sqlc.json b/internal/endtoend/testdata/enum_alter_modify/mysql/sqlc.json
deleted file mode 100644
index feb988c2b..000000000
--- a/internal/endtoend/testdata/enum_alter_modify/mysql/sqlc.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
-  "version": "1",
-  "packages": [
-    {
-      "name": "querytest",
-      "path": "go",
-      "schema": "schema.sql",
-      "queries": "query.sql",
-      "engine": "mysql",
-      "omit_unused_structs": true
-    }
-  ]
-}
diff --git a/internal/endtoend/testdata/enum_column/issue.md b/internal/endtoend/testdata/enum_column/issue.md
new file mode 100644
index 000000000..e618d6c24
--- /dev/null
+++ b/internal/endtoend/testdata/enum_column/issue.md
@@ -0,0 +1,3 @@
+https://github.com/sqlc-dev/sqlc/issues/1503
+https://github.com/sqlc-dev/sqlc/issues/2475
+
diff --git a/internal/endtoend/testdata/enum_alter_change/mysql/go/db.go b/internal/endtoend/testdata/enum_column/mysql/go/db.go
similarity index 97%
rename from internal/endtoend/testdata/enum_alter_change/mysql/go/db.go
rename to internal/endtoend/testdata/enum_column/mysql/go/db.go
index 57406b68e..a457fb76b 100644
--- a/internal/endtoend/testdata/enum_alter_change/mysql/go/db.go
+++ b/internal/endtoend/testdata/enum_column/mysql/go/db.go
@@ -1,6 +1,6 @@
 // Code generated by sqlc. DO NOT EDIT.
 // versions:
-//   sqlc v1.20.0
+//   sqlc v1.22.0
 
 package querytest
 
diff --git a/internal/endtoend/testdata/enum_column/mysql/go/models.go b/internal/endtoend/testdata/enum_column/mysql/go/models.go
new file mode 100644
index 000000000..84d959387
--- /dev/null
+++ b/internal/endtoend/testdata/enum_column/mysql/go/models.go
@@ -0,0 +1,271 @@
+// Code generated by sqlc. DO NOT EDIT.
+// versions:
+//   sqlc v1.22.0
+
+package querytest
+
+import (
+	"database/sql/driver"
+	"fmt"
+)
+
+type AuthorsAddItem string
+
+const (
+	AuthorsAddItemOk    AuthorsAddItem = "ok"
+	AuthorsAddItemAdded AuthorsAddItem = "added"
+)
+
+func (e *AuthorsAddItem) Scan(src interface{}) error {
+	switch s := src.(type) {
+	case []byte:
+		*e = AuthorsAddItem(s)
+	case string:
+		*e = AuthorsAddItem(s)
+	default:
+		return fmt.Errorf("unsupported scan type for AuthorsAddItem: %T", src)
+	}
+	return nil
+}
+
+type NullAuthorsAddItem struct {
+	AuthorsAddItem AuthorsAddItem
+	Valid          bool // Valid is true if AuthorsAddItem is not NULL
+}
+
+// Scan implements the Scanner interface.
+func (ns *NullAuthorsAddItem) Scan(value interface{}) error {
+	if value == nil {
+		ns.AuthorsAddItem, ns.Valid = "", false
+		return nil
+	}
+	ns.Valid = true
+	return ns.AuthorsAddItem.Scan(value)
+}
+
+// Value implements the driver Valuer interface.
+func (ns NullAuthorsAddItem) Value() (driver.Value, error) {
+	if !ns.Valid {
+		return nil, nil
+	}
+	return string(ns.AuthorsAddItem), nil
+}
+
+type AuthorsAdded string
+
+const (
+	AuthorsAddedOk AuthorsAdded = "ok"
+)
+
+func (e *AuthorsAdded) Scan(src interface{}) error {
+	switch s := src.(type) {
+	case []byte:
+		*e = AuthorsAdded(s)
+	case string:
+		*e = AuthorsAdded(s)
+	default:
+		return fmt.Errorf("unsupported scan type for AuthorsAdded: %T", src)
+	}
+	return nil
+}
+
+type NullAuthorsAdded struct {
+	AuthorsAdded AuthorsAdded
+	Valid        bool // Valid is true if AuthorsAdded is not NULL
+}
+
+// Scan implements the Scanner interface.
+func (ns *NullAuthorsAdded) Scan(value interface{}) error {
+	if value == nil {
+		ns.AuthorsAdded, ns.Valid = "", false
+		return nil
+	}
+	ns.Valid = true
+	return ns.AuthorsAdded.Scan(value)
+}
+
+// Value implements the driver Valuer interface.
+func (ns NullAuthorsAdded) Value() (driver.Value, error) {
+	if !ns.Valid {
+		return nil, nil
+	}
+	return string(ns.AuthorsAdded), nil
+}
+
+type AuthorsBar string
+
+const (
+	AuthorsBarOk AuthorsBar = "ok"
+)
+
+func (e *AuthorsBar) Scan(src interface{}) error {
+	switch s := src.(type) {
+	case []byte:
+		*e = AuthorsBar(s)
+	case string:
+		*e = AuthorsBar(s)
+	default:
+		return fmt.Errorf("unsupported scan type for AuthorsBar: %T", src)
+	}
+	return nil
+}
+
+type NullAuthorsBar struct {
+	AuthorsBar AuthorsBar
+	Valid      bool // Valid is true if AuthorsBar is not NULL
+}
+
+// Scan implements the Scanner interface.
+func (ns *NullAuthorsBar) Scan(value interface{}) error {
+	if value == nil {
+		ns.AuthorsBar, ns.Valid = "", false
+		return nil
+	}
+	ns.Valid = true
+	return ns.AuthorsBar.Scan(value)
+}
+
+// Value implements the driver Valuer interface.
+func (ns NullAuthorsBar) Value() (driver.Value, error) {
+	if !ns.Valid {
+		return nil, nil
+	}
+	return string(ns.AuthorsBar), nil
+}
+
+type AuthorsFoo string
+
+const (
+	AuthorsFooOk AuthorsFoo = "ok"
+)
+
+func (e *AuthorsFoo) Scan(src interface{}) error {
+	switch s := src.(type) {
+	case []byte:
+		*e = AuthorsFoo(s)
+	case string:
+		*e = AuthorsFoo(s)
+	default:
+		return fmt.Errorf("unsupported scan type for AuthorsFoo: %T", src)
+	}
+	return nil
+}
+
+type NullAuthorsFoo struct {
+	AuthorsFoo AuthorsFoo
+	Valid      bool // Valid is true if AuthorsFoo is not NULL
+}
+
+// Scan implements the Scanner interface.
+func (ns *NullAuthorsFoo) Scan(value interface{}) error {
+	if value == nil {
+		ns.AuthorsFoo, ns.Valid = "", false
+		return nil
+	}
+	ns.Valid = true
+	return ns.AuthorsFoo.Scan(value)
+}
+
+// Value implements the driver Valuer interface.
+func (ns NullAuthorsFoo) Value() (driver.Value, error) {
+	if !ns.Valid {
+		return nil, nil
+	}
+	return string(ns.AuthorsFoo), nil
+}
+
+type AuthorsRemoveItem string
+
+const (
+	AuthorsRemoveItemOk AuthorsRemoveItem = "ok"
+)
+
+func (e *AuthorsRemoveItem) Scan(src interface{}) error {
+	switch s := src.(type) {
+	case []byte:
+		*e = AuthorsRemoveItem(s)
+	case string:
+		*e = AuthorsRemoveItem(s)
+	default:
+		return fmt.Errorf("unsupported scan type for AuthorsRemoveItem: %T", src)
+	}
+	return nil
+}
+
+type NullAuthorsRemoveItem struct {
+	AuthorsRemoveItem AuthorsRemoveItem
+	Valid             bool // Valid is true if AuthorsRemoveItem is not NULL
+}
+
+// Scan implements the Scanner interface.
+func (ns *NullAuthorsRemoveItem) Scan(value interface{}) error {
+	if value == nil {
+		ns.AuthorsRemoveItem, ns.Valid = "", false
+		return nil
+	}
+	ns.Valid = true
+	return ns.AuthorsRemoveItem.Scan(value)
+}
+
+// Value implements the driver Valuer interface.
+func (ns NullAuthorsRemoveItem) Value() (driver.Value, error) {
+	if !ns.Valid {
+		return nil, nil
+	}
+	return string(ns.AuthorsRemoveItem), nil
+}
+
+type BooksFoo string
+
+const (
+	BooksFooOk BooksFoo = "ok"
+)
+
+func (e *BooksFoo) Scan(src interface{}) error {
+	switch s := src.(type) {
+	case []byte:
+		*e = BooksFoo(s)
+	case string:
+		*e = BooksFoo(s)
+	default:
+		return fmt.Errorf("unsupported scan type for BooksFoo: %T", src)
+	}
+	return nil
+}
+
+type NullBooksFoo struct {
+	BooksFoo BooksFoo
+	Valid    bool // Valid is true if BooksFoo is not NULL
+}
+
+// Scan implements the Scanner interface.
+func (ns *NullBooksFoo) Scan(value interface{}) error {
+	if value == nil {
+		ns.BooksFoo, ns.Valid = "", false
+		return nil
+	}
+	ns.Valid = true
+	return ns.BooksFoo.Scan(value)
+}
+
+// Value implements the driver Valuer interface.
+func (ns NullBooksFoo) Value() (driver.Value, error) {
+	if !ns.Valid {
+		return nil, nil
+	}
+	return string(ns.BooksFoo), nil
+}
+
+type Author struct {
+	ID         int64
+	Foo        AuthorsFoo
+	Bar        AuthorsBar
+	Added      AuthorsAdded
+	AddItem    AuthorsAddItem
+	RemoveItem AuthorsRemoveItem
+}
+
+type Book struct {
+	ID  int64
+	Foo BooksFoo
+}
diff --git a/internal/endtoend/testdata/enum_column/mysql/go/query.sql.go b/internal/endtoend/testdata/enum_column/mysql/go/query.sql.go
new file mode 100644
index 000000000..fc2414463
--- /dev/null
+++ b/internal/endtoend/testdata/enum_column/mysql/go/query.sql.go
@@ -0,0 +1,71 @@
+// Code generated by sqlc. DO NOT EDIT.
+// versions:
+//   sqlc v1.22.0
+// source: query.sql
+
+package querytest
+
+import (
+	"context"
+)
+
+const listAuthors = `-- name: ListAuthors :many
+SELECT id, foo, bar, added, add_item, remove_item FROM authors
+`
+
+func (q *Queries) ListAuthors(ctx context.Context) ([]Author, error) {
+	rows, err := q.db.QueryContext(ctx, listAuthors)
+	if err != nil {
+		return nil, err
+	}
+	defer rows.Close()
+	var items []Author
+	for rows.Next() {
+		var i Author
+		if err := rows.Scan(
+			&i.ID,
+			&i.Foo,
+			&i.Bar,
+			&i.Added,
+			&i.AddItem,
+			&i.RemoveItem,
+		); err != nil {
+			return nil, err
+		}
+		items = append(items, i)
+	}
+	if err := rows.Close(); err != nil {
+		return nil, err
+	}
+	if err := rows.Err(); err != nil {
+		return nil, err
+	}
+	return items, nil
+}
+
+const listBooks = `-- name: ListBooks :many
+SELECT id, foo FROM books
+`
+
+func (q *Queries) ListBooks(ctx context.Context) ([]Book, error) {
+	rows, err := q.db.QueryContext(ctx, listBooks)
+	if err != nil {
+		return nil, err
+	}
+	defer rows.Close()
+	var items []Book
+	for rows.Next() {
+		var i Book
+		if err := rows.Scan(&i.ID, &i.Foo); err != nil {
+			return nil, err
+		}
+		items = append(items, i)
+	}
+	if err := rows.Close(); err != nil {
+		return nil, err
+	}
+	if err := rows.Err(); err != nil {
+		return nil, err
+	}
+	return items, nil
+}
diff --git a/internal/endtoend/testdata/enum_column/mysql/query.sql b/internal/endtoend/testdata/enum_column/mysql/query.sql
new file mode 100644
index 000000000..893743b53
--- /dev/null
+++ b/internal/endtoend/testdata/enum_column/mysql/query.sql
@@ -0,0 +1,5 @@
+/* name: ListAuthors :many */
+SELECT * FROM authors;
+
+/* name: ListBooks :many */
+SELECT * FROM books;
diff --git a/internal/endtoend/testdata/enum_column/mysql/schema.sql b/internal/endtoend/testdata/enum_column/mysql/schema.sql
new file mode 100644
index 000000000..82771c195
--- /dev/null
+++ b/internal/endtoend/testdata/enum_column/mysql/schema.sql
@@ -0,0 +1,39 @@
+CREATE TABLE authors (
+  id          BIGINT PRIMARY KEY,
+  foo         ENUM("ok") DEFAULT "ok" NOT NULL,
+  renamed     ENUM("ok") DEFAULT "ok" NOT NULL,
+  removed     ENUM("ok") DEFAULT "ok" NOT NULL,
+  add_item    ENUM("ok") DEFAULT "ok" NOT NULL,
+  remove_item ENUM("ok", "removed") DEFAULT "ok" NOT NULL
+);
+
+CREATE TABLE renamed (
+  id       BIGINT PRIMARY KEY,
+  foo      ENUM("ok") DEFAULT "ok" NOT NULL
+);
+
+CREATE TABLE removed (
+  id       BIGINT PRIMARY KEY,
+  foo      ENUM("ok") DEFAULT "ok" NOT NULL
+);
+
+/* Rename column */
+ALTER TABLE authors RENAME COLUMN renamed TO bar;
+
+/* Drop column */
+ALTER TABLE authors DROP COLUMN removed;
+
+/* Add column */
+ALTER TABLE authors ADD COLUMN added ENUM("ok") DEFAULT "ok" NOT NULL;
+
+/* Add enum values */
+ALTER TABLE authors MODIFY add_item ENUM("ok", "added") DEFAULT "ok" NOT NULL;
+
+/* Remove enum values */
+ALTER TABLE authors MODIFY remove_item ENUM("ok") DEFAULT "ok" NOT NULL;
+
+/* Drop table */
+DROP TABLE removed;
+
+/* Rename table */
+ALTER TABLE renamed RENAME TO books;
diff --git a/internal/endtoend/testdata/enum_column/mysql/sqlc.yaml b/internal/endtoend/testdata/enum_column/mysql/sqlc.yaml
new file mode 100644
index 000000000..b843ef55e
--- /dev/null
+++ b/internal/endtoend/testdata/enum_column/mysql/sqlc.yaml
@@ -0,0 +1,9 @@
+version: "2"
+sql:
+  - engine: "mysql"
+    schema: "schema.sql"
+    queries: "query.sql"
+    gen:
+      go:
+        package: "querytest"
+        out: "go"
diff --git a/internal/engine/sqlite/catalog_test.go b/internal/engine/sqlite/catalog_test.go
index 3c3028fcc..bf6dcd831 100644
--- a/internal/engine/sqlite/catalog_test.go
+++ b/internal/engine/sqlite/catalog_test.go
@@ -260,7 +260,7 @@ func TestUpdate(t *testing.T) {
 				}
 			}
 
-			if diff := cmp.Diff(e, c, cmpopts.EquateEmpty()); diff != "" {
+			if diff := cmp.Diff(e, c, cmpopts.EquateEmpty(), cmpopts.IgnoreUnexported(catalog.Column{})); diff != "" {
 				t.Log(test.stmt)
 				t.Errorf("catalog mismatch:\n%s", diff)
 			}