From c010fcb279198955009f4990d28be28376c9015d Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Fri, 13 Oct 2023 12:37:25 -0700 Subject: [PATCH 1/5] wip --- .../enum_alter_change/mysql/go/models.go | 60 ------- .../enum_alter_change/mysql/go/query.sql.go | 37 ----- .../enum_alter_change/mysql/query.sql | 2 - .../enum_alter_change/mysql/schema.sql | 9 -- .../enum_alter_change/mysql/sqlc.json | 13 -- .../testdata/enum_alter_modify/mysql/go/db.go | 31 ---- .../enum_alter_modify/mysql/go/models.go | 60 ------- .../enum_alter_modify/mysql/go/query.sql.go | 37 ----- .../enum_alter_modify/mysql/query.sql | 2 - .../enum_alter_modify/mysql/schema.sql | 9 -- .../enum_alter_modify/mysql/sqlc.json | 13 -- .../endtoend/testdata/enum_column/issue.md | 3 + .../mysql/go/db.go | 2 +- .../testdata/enum_column/mysql/go/models.go | 147 ++++++++++++++++++ .../enum_column/mysql/go/query.sql.go | 71 +++++++++ .../testdata/enum_column/mysql/query.sql | 5 + .../testdata/enum_column/mysql/schema.sql | 39 +++++ .../testdata/enum_column/mysql/sqlc.yaml | 9 ++ internal/sql/catalog/catalog.go | 2 +- internal/sql/catalog/table.go | 12 +- internal/sql/catalog/types.go | 16 +- 21 files changed, 286 insertions(+), 293 deletions(-) delete mode 100644 internal/endtoend/testdata/enum_alter_change/mysql/go/models.go delete mode 100644 internal/endtoend/testdata/enum_alter_change/mysql/go/query.sql.go delete mode 100644 internal/endtoend/testdata/enum_alter_change/mysql/query.sql delete mode 100644 internal/endtoend/testdata/enum_alter_change/mysql/schema.sql delete mode 100644 internal/endtoend/testdata/enum_alter_change/mysql/sqlc.json delete mode 100644 internal/endtoend/testdata/enum_alter_modify/mysql/go/db.go delete mode 100644 internal/endtoend/testdata/enum_alter_modify/mysql/go/models.go delete mode 100644 internal/endtoend/testdata/enum_alter_modify/mysql/go/query.sql.go delete mode 100644 internal/endtoend/testdata/enum_alter_modify/mysql/query.sql delete mode 100644 internal/endtoend/testdata/enum_alter_modify/mysql/schema.sql delete mode 100644 internal/endtoend/testdata/enum_alter_modify/mysql/sqlc.json create mode 100644 internal/endtoend/testdata/enum_column/issue.md rename internal/endtoend/testdata/{enum_alter_change => enum_column}/mysql/go/db.go (97%) create mode 100644 internal/endtoend/testdata/enum_column/mysql/go/models.go create mode 100644 internal/endtoend/testdata/enum_column/mysql/go/query.sql.go create mode 100644 internal/endtoend/testdata/enum_column/mysql/query.sql create mode 100644 internal/endtoend/testdata/enum_column/mysql/schema.sql create mode 100644 internal/endtoend/testdata/enum_column/mysql/sqlc.yaml 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 3af0810506..0000000000 --- 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 ccac241cd6..0000000000 --- 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 0b16d94be6..0000000000 --- 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 67c89a0eff..0000000000 --- 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 feb988c2be..0000000000 --- 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 57406b68e8..0000000000 --- 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 3af0810506..0000000000 --- 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 ccac241cd6..0000000000 --- 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 0b16d94be6..0000000000 --- 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 d199af185f..0000000000 --- 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 feb988c2be..0000000000 --- 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 0000000000..e618d6c240 --- /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 57406b68e8..a457fb76b2 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 0000000000..c6824f7a0f --- /dev/null +++ b/internal/endtoend/testdata/enum_column/mysql/go/models.go @@ -0,0 +1,147 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.22.0 + +package querytest + +import ( + "database/sql/driver" + "fmt" +) + +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 AuthorsRenamed string + +const ( + AuthorsRenamedOk AuthorsRenamed = "ok" +) + +func (e *AuthorsRenamed) Scan(src interface{}) error { + switch s := src.(type) { + case []byte: + *e = AuthorsRenamed(s) + case string: + *e = AuthorsRenamed(s) + default: + return fmt.Errorf("unsupported scan type for AuthorsRenamed: %T", src) + } + return nil +} + +type NullAuthorsRenamed struct { + AuthorsRenamed AuthorsRenamed + Valid bool // Valid is true if AuthorsRenamed is not NULL +} + +// Scan implements the Scanner interface. +func (ns *NullAuthorsRenamed) Scan(value interface{}) error { + if value == nil { + ns.AuthorsRenamed, ns.Valid = "", false + return nil + } + ns.Valid = true + return ns.AuthorsRenamed.Scan(value) +} + +// Value implements the driver Valuer interface. +func (ns NullAuthorsRenamed) Value() (driver.Value, error) { + if !ns.Valid { + return nil, nil + } + return string(ns.AuthorsRenamed), nil +} + +type RenamedFoo string + +const ( + RenamedFooOk RenamedFoo = "ok" +) + +func (e *RenamedFoo) Scan(src interface{}) error { + switch s := src.(type) { + case []byte: + *e = RenamedFoo(s) + case string: + *e = RenamedFoo(s) + default: + return fmt.Errorf("unsupported scan type for RenamedFoo: %T", src) + } + return nil +} + +type NullRenamedFoo struct { + RenamedFoo RenamedFoo + Valid bool // Valid is true if RenamedFoo is not NULL +} + +// Scan implements the Scanner interface. +func (ns *NullRenamedFoo) Scan(value interface{}) error { + if value == nil { + ns.RenamedFoo, ns.Valid = "", false + return nil + } + ns.Valid = true + return ns.RenamedFoo.Scan(value) +} + +// Value implements the driver Valuer interface. +func (ns NullRenamedFoo) Value() (driver.Value, error) { + if !ns.Valid { + return nil, nil + } + return string(ns.RenamedFoo), nil +} + +type Author struct { + ID int64 + Foo AuthorsFoo + Bar AuthorsRenamed + Added string + AddItem string + RemoveItem string +} + +type Book struct { + ID int64 + Foo RenamedFoo +} 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 0000000000..fc24144638 --- /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 0000000000..893743b53a --- /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 0000000000..82771c1951 --- /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 0000000000..b843ef55e3 --- /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/sql/catalog/catalog.go b/internal/sql/catalog/catalog.go index 9a4a29e880..278ea8797d 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 1c9bacf994..760588eb3f 100644 --- a/internal/sql/catalog/table.go +++ b/internal/sql/catalog/table.go @@ -51,7 +51,7 @@ func (table *Table) addColumn(c *Catalog, cmd *ast.AlterTableCmd) error { } } - tc, err := c.defToColumn(table.Rel, cmd.Def) + tc, err := c.defineColumn(table.Rel, cmd.Def) if err != nil { return err } @@ -118,6 +118,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. @@ -302,7 +304,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 +323,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 +339,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 } diff --git a/internal/sql/catalog/types.go b/internal/sql/catalog/types.go index 2bb1033b3a..464472bcf2 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, From ebbca33ea588a74a07bc8c4b4a068292cf81fe79 Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Fri, 13 Oct 2023 12:50:14 -0700 Subject: [PATCH 2/5] fix(myql): Add, drop, rename, and change enums --- .../testdata/enum_column/mysql/go/models.go | 190 +++++++++++++++--- internal/sql/catalog/table.go | 66 +++++- 2 files changed, 213 insertions(+), 43 deletions(-) diff --git a/internal/endtoend/testdata/enum_column/mysql/go/models.go b/internal/endtoend/testdata/enum_column/mysql/go/models.go index c6824f7a0f..84d959387a 100644 --- a/internal/endtoend/testdata/enum_column/mysql/go/models.go +++ b/internal/endtoend/testdata/enum_column/mysql/go/models.go @@ -9,6 +9,130 @@ import ( "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 ( @@ -50,98 +174,98 @@ func (ns NullAuthorsFoo) Value() (driver.Value, error) { return string(ns.AuthorsFoo), nil } -type AuthorsRenamed string +type AuthorsRemoveItem string const ( - AuthorsRenamedOk AuthorsRenamed = "ok" + AuthorsRemoveItemOk AuthorsRemoveItem = "ok" ) -func (e *AuthorsRenamed) Scan(src interface{}) error { +func (e *AuthorsRemoveItem) Scan(src interface{}) error { switch s := src.(type) { case []byte: - *e = AuthorsRenamed(s) + *e = AuthorsRemoveItem(s) case string: - *e = AuthorsRenamed(s) + *e = AuthorsRemoveItem(s) default: - return fmt.Errorf("unsupported scan type for AuthorsRenamed: %T", src) + return fmt.Errorf("unsupported scan type for AuthorsRemoveItem: %T", src) } return nil } -type NullAuthorsRenamed struct { - AuthorsRenamed AuthorsRenamed - Valid bool // Valid is true if AuthorsRenamed is not NULL +type NullAuthorsRemoveItem struct { + AuthorsRemoveItem AuthorsRemoveItem + Valid bool // Valid is true if AuthorsRemoveItem is not NULL } // Scan implements the Scanner interface. -func (ns *NullAuthorsRenamed) Scan(value interface{}) error { +func (ns *NullAuthorsRemoveItem) Scan(value interface{}) error { if value == nil { - ns.AuthorsRenamed, ns.Valid = "", false + ns.AuthorsRemoveItem, ns.Valid = "", false return nil } ns.Valid = true - return ns.AuthorsRenamed.Scan(value) + return ns.AuthorsRemoveItem.Scan(value) } // Value implements the driver Valuer interface. -func (ns NullAuthorsRenamed) Value() (driver.Value, error) { +func (ns NullAuthorsRemoveItem) Value() (driver.Value, error) { if !ns.Valid { return nil, nil } - return string(ns.AuthorsRenamed), nil + return string(ns.AuthorsRemoveItem), nil } -type RenamedFoo string +type BooksFoo string const ( - RenamedFooOk RenamedFoo = "ok" + BooksFooOk BooksFoo = "ok" ) -func (e *RenamedFoo) Scan(src interface{}) error { +func (e *BooksFoo) Scan(src interface{}) error { switch s := src.(type) { case []byte: - *e = RenamedFoo(s) + *e = BooksFoo(s) case string: - *e = RenamedFoo(s) + *e = BooksFoo(s) default: - return fmt.Errorf("unsupported scan type for RenamedFoo: %T", src) + return fmt.Errorf("unsupported scan type for BooksFoo: %T", src) } return nil } -type NullRenamedFoo struct { - RenamedFoo RenamedFoo - Valid bool // Valid is true if RenamedFoo is not NULL +type NullBooksFoo struct { + BooksFoo BooksFoo + Valid bool // Valid is true if BooksFoo is not NULL } // Scan implements the Scanner interface. -func (ns *NullRenamedFoo) Scan(value interface{}) error { +func (ns *NullBooksFoo) Scan(value interface{}) error { if value == nil { - ns.RenamedFoo, ns.Valid = "", false + ns.BooksFoo, ns.Valid = "", false return nil } ns.Valid = true - return ns.RenamedFoo.Scan(value) + return ns.BooksFoo.Scan(value) } // Value implements the driver Valuer interface. -func (ns NullRenamedFoo) Value() (driver.Value, error) { +func (ns NullBooksFoo) Value() (driver.Value, error) { if !ns.Valid { return nil, nil } - return string(ns.RenamedFoo), nil + return string(ns.BooksFoo), nil } type Author struct { ID int64 Foo AuthorsFoo - Bar AuthorsRenamed - Added string - AddItem string - RemoveItem string + Bar AuthorsBar + Added AuthorsAdded + AddItem AuthorsAddItem + RemoveItem AuthorsRemoveItem } type Book struct { ID int64 - Foo RenamedFoo + Foo BooksFoo } diff --git a/internal/sql/catalog/table.go b/internal/sql/catalog/table.go index 760588eb3f..bfa2da027e 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.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 } @@ -186,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: @@ -194,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: @@ -303,7 +313,6 @@ func (c *Catalog) createTable(stmt *ast.CreateTableStmt) error { } continue } - tc, err := c.defineColumn(stmt.Name, col) if err != nil { return err @@ -361,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 @@ -391,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 } @@ -405,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 } From c2505c265cc3b143fa1a5219efe5b0fe1243418f Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Fri, 13 Oct 2023 14:01:51 -0700 Subject: [PATCH 3/5] Change examples --- .../kotlin/com/example/ondeck/mysql/Models.kt | 6 ++-- .../com/example/ondeck/mysql/Queries.kt | 2 +- .../com/example/ondeck/mysql/QueriesImpl.kt | 6 ++-- examples/ondeck/mysql/models.go | 32 +++++++++---------- examples/ondeck/mysql/venue.sql.go | 2 +- internal/engine/sqlite/catalog_test.go | 2 +- 6 files changed, 25 insertions(+), 25 deletions(-) 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 4cb2a95793..a034c3600f 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 5e97478f49..7cacad0d10 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 c3cba30364..172967cdec 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 0ca972f6a0..12e87cd90c 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 55d37ad785..13b5e28d0b 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/engine/sqlite/catalog_test.go b/internal/engine/sqlite/catalog_test.go index 3c3028fcc4..bf6dcd8316 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) } From 6efd59d6a54fe00abce82a6e51c7bb166f4d24b6 Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Fri, 13 Oct 2023 14:05:24 -0700 Subject: [PATCH 4/5] Fix Kotlin code --- .../test/kotlin/com/example/ondeck/mysql/QueriesImplTest.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 e946157bab..27fac23535 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( From 7b8250d560b570d08327faed03e5cbd1b76225e1 Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Fri, 13 Oct 2023 15:47:18 -0700 Subject: [PATCH 5/5] Fix db_test.go --- examples/ondeck/mysql/db_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/ondeck/mysql/db_test.go b/examples/ondeck/mysql/db_test.go index b4b343f9dc..a6ddecc18b 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 {