Enums as generated Go types, and your own types with go_type: DuckDB and MySQL
Every file of the DuckDB and MySQL versions side by side, with the lines that differ marked. 3 parameter or column types differ in Go.
Configuration
sgl.yamldiffers
DuckDB
MySQL
1
version: 11
version: 12
2
3
databases:3
databases:4
- name: menu4
- name: menu5
dialect: duckdb5
dialect: mysql6
migrations: db/migrations6
migrations: db/migrations7
queries: db/queries7
queries: db/queries8
go:8
go:9
package: store9
package: store10
out: internal/store10
out: internal/store11
types:11
types:12
overrides:12
overrides:13
- sql: item_status13
- column: menu_items.status14
go: seagull.sh/seagull/examples/duckdb/007_enums/internal/menu.Status14
go: seagull.sh/seagull/examples/mysql/007_enums/internal/menu.StatusMigrations
db/migrations/0001_menu.up.sqldiffers
DuckDB
MySQL
1
CREATE TYPE section AS ENUM ('fish', 'sides', 'puddings');2
3
CREATE TYPE item_status AS ENUM ('available', 'sold_out');4
5
CREATE SEQUENCE menu_item_ids;6
7
CREATE TABLE menu_items (1
CREATE TABLE menu_items (8
id BIGINT PRIMARY KEY DEFAULT nextval('menu_item_ids'),2
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,9
name VARCHAR NOT NULL UNIQUE,3
name VARCHAR(50) NOT NULL UNIQUE,10
section section NOT NULL,4
section ENUM('fish', 'sides', 'puddings') NOT NULL,11
status item_status NOT NULL DEFAULT 'available'5
status ENUM('available', 'sold_out') NOT NULL DEFAULT 'available'12
);6
);db/migrations/0001_menu.down.sqldiffers
DuckDB
MySQL
1
DROP TABLE menu_items;1
DROP TABLE menu_items;2
DROP SEQUENCE menu_item_ids;3
DROP TYPE item_status;4
DROP TYPE section;Queries
AddItemdiffers
DuckDB · db/queries/menu.sql
MySQL · db/queries/menu.sql
1
-- sgl.query(AddItem, exec)1
-- sgl.query(AddItem, exec)2
INSERT INTO menu_items (name, section) VALUES ($1, $2);2
INSERT INTO menu_items (name, section) VALUES (?, ?);SetStatusdiffers
DuckDB · db/queries/menu.sql
MySQL · db/queries/menu.sql
1
-- sgl.query(SetStatus, execrows)1
-- sgl.query(SetStatus, execrows)2
UPDATE menu_items SET status = $1 WHERE name = $2;2
UPDATE menu_items SET status = ? WHERE name = ?;ListMenuidentical
DuckDB · db/queries/menu.sql
MySQL · db/queries/menu.sql
1
-- sgl.query(ListMenu, many)1
-- sgl.query(ListMenu, many)2
SELECT name, section, status FROM menu_items ORDER BY section, name;2
SELECT name, section, status FROM menu_items ORDER BY section, name;ItemsInSectiondiffers
DuckDB · db/queries/menu.sql
MySQL · db/queries/menu.sql
1
-- sgl.query(ItemsInSection, many)1
-- sgl.query(ItemsInSection, many)2
-- sgl.column(section, go_type: "seagull.sh/seagull/examples/duckdb/007_enums/internal/menu.Section")2
-- sgl.column(section, go_type: "seagull.sh/seagull/examples/mysql/007_enums/internal/menu.Section")3
SELECT name, section, status FROM menu_items WHERE section = $1 ORDER BY name;3
SELECT name, section, status FROM menu_items WHERE section = ? ORDER BY name;What sgl generates
AddItemdiffers
DuckDB
MySQL
1
func (queries *Queries) AddItem(ctx context.Context, params AddItemParams) error1
func (queries *Queries) AddItem(ctx context.Context, params AddItemParams) error2
2
3
type AddItemParams struct {3
type AddItemParams struct {4
Name string4
Name string5
Section Section5
Section MenuItemsSection6
}6
}7
7
8
type Section string8
type MenuItemsSection stringSetStatusidentical
DuckDB
MySQL
1
func (queries *Queries) SetStatus(ctx context.Context, params SetStatusParams) (int64, error)1
func (queries *Queries) SetStatus(ctx context.Context, params SetStatusParams) (int64, error)2
2
3
type SetStatusParams struct {3
type SetStatusParams struct {4
Status menu.Status4
Status menu.Status5
Name string5
Name string6
}6
}ListMenudiffers
DuckDB
MySQL
1
func (queries *Queries) ListMenu(ctx context.Context) ([]ListMenuRow, error)1
func (queries *Queries) ListMenu(ctx context.Context) ([]ListMenuRow, error)2
2
3
type ListMenuRow struct {3
type ListMenuRow struct {4
Name string `json:"name"`4
Name string `json:"name"`5
Section Section `json:"section"`5
Section MenuItemsSection `json:"section"`6
Status menu.Status `json:"status"`6
Status menu.Status `json:"status"`7
}7
}8
8
9
type Section string9
type MenuItemsSection stringItemsInSectiondiffers
DuckDB
MySQL
1
func (queries *Queries) ItemsInSection(ctx context.Context, section Section) ([]ItemsInSectionRow, error)1
func (queries *Queries) ItemsInSection(ctx context.Context, section MenuItemsSection) ([]ItemsInSectionRow, error)2
2
3
type Section string3
type MenuItemsSection string4
4
5
type ItemsInSectionRow struct {5
type ItemsInSectionRow struct {6
Name string `json:"name"`6
Name string `json:"name"`7
Section menu.Section `json:"section"`7
Section menu.Section `json:"section"`8
Status menu.Status `json:"status"`8
Status menu.Status `json:"status"`9
}9
}The program
example.godiffers
DuckDB
MySQL
1
package main1
package main2
2
3
import (3
import (4
"context"4
"context"5
"database/sql"5
"database/sql"6
"errors"6
"errors"7
"fmt"7
"fmt"8
"io"8
"io"9
9
10
"seagull.sh/seagull/examples/duckdb/007_enums/internal/menu"10
"seagull.sh/seagull/examples/mysql/007_enums/internal/menu"11
"seagull.sh/seagull/examples/duckdb/007_enums/internal/store"11
"seagull.sh/seagull/examples/mysql/007_enums/internal/store"12
)12
)13
13
14
// run fills a chip shop menu whose section and status columns are DuckDB enums, marks an14
// run fills a chip shop menu whose section and status columns are MySQL ENUMs, marks an15
// item sold out and reads the menu back. Each enum becomes a named Go type in the15
// item sold out and reads the menu back. Each ENUM becomes a named Go type in the16
// generated package unless an override names a type of your own:16
// generated package unless an override names a type of your own:17
//17
//18
// - section has no override, so models.go declares store.Section, with a constant for18
// - section has no override, so models.go declares store.MenuItemsSection, named after19
// each value, such as store.SectionFish, and a Valid method; ListMenuRow.Section,19
// the table and column that hold the ENUM, with a constant for each value, such as20
// AddItemParams.Section and ItemsInSection's parameter are all a store.Section;20
// store.MenuItemsSectionFish, and a Valid method; ListMenuRow.Section,21
// - sgl.yaml's types.overrides names the enum type item_status, so every column of that21
// AddItemParams.Section and ItemsInSection's parameter are all a22
// enum gives menu.Status, as in ListMenuRow.Status, and so does every parameter22
// store.MenuItemsSection;23
// compared with or assigned to one, as in SetStatusParams.Status; the generated23
// - sgl.yaml's types.overrides names the column menu_items.status, so every query that24
// package then declares no type for item_status;24
// returns it as it is gives menu.Status, as in ListMenuRow.Status, and so does every25
// parameter compared with or assigned to it, as in SetStatusParams.Status; models.go26
// uses it too, and the generated package declares no type for that ENUM;25
// - sgl.column(section, go_type: ...) in ItemsInSection gives that one query's returned27
// - sgl.column(section, go_type: ...) in ItemsInSection gives that one query's returned26
// Section the type menu.Section; the parameter it compares with stays a28
// Section the type menu.Section; the parameter it compares with stays a27
// store.Section.29
// store.MenuItemsSection.28
//30
//29
// An enum sorts in the order its values are declared, not alphabetically, so ORDER BY31
// An ENUM sorts in the order its values are declared, not alphabetically, so ORDER BY30
// section lists fish, then sides, then puddings. Valid reports whether a value is one the32
// section lists fish, then sides, then puddings. Valid reports whether a value is one the31
// enum declares, so the shop can check a section before it binds it. DuckDB checks each33
// ENUM declares, so the shop can check a section before it binds it. In strict mode, the32
// value against the enum as it is stored as well, so a section the enum does not list is34
// default since MySQL 5.7, a value the ENUM does not list is refused as it is stored as33
// refused; DuckDB keeps an enum as a small integer, and its error names that type.35
// well; MySQL keeps an ENUM as the index of its value, and its error says the value was36
// truncated.34
//37
//35
// Takes database (*sql.DB) which is a migrated database.38
// Takes database (*sql.DB) which is a migrated database.36
// Takes output (io.Writer) which receives the menu, the fish section and the refusal.39
// Takes output (io.Writer) which receives the menu, the fish section and the refusal.37
//40
//38
// Returns error when a query fails other than as the example expects.41
// Returns error when a query fails other than as the example expects.39
func run(ctx context.Context, database *sql.DB, output io.Writer) error {42
func run(ctx context.Context, database *sql.DB, output io.Writer) error {40
queries := store.New(database)43
queries := store.New(database)41
44
42
items := []store.AddItemParams{45
items := []store.AddItemParams{43
{Name: "cod", Section: store.SectionFish},46
{Name: "cod", Section: store.MenuItemsSectionFish},44
{Name: "haddock", Section: store.SectionFish},47
{Name: "haddock", Section: store.MenuItemsSectionFish},45
{Name: "chips", Section: store.SectionSides},48
{Name: "chips", Section: store.MenuItemsSectionSides},46
{Name: "mushy peas", Section: store.SectionSides},49
{Name: "mushy peas", Section: store.MenuItemsSectionSides},47
{Name: "sticky toffee pudding", Section: store.SectionPuddings},50
{Name: "sticky toffee pudding", Section: store.MenuItemsSectionPuddings},48
}51
}49
for _, item := range items {52
for _, item := range items {50
if err := queries.AddItem(ctx, item); err != nil {53
if err := queries.AddItem(ctx, item); err != nil {51
return fmt.Errorf("adding %s: %w", item.Name, err)54
return fmt.Errorf("adding %s: %w", item.Name, err)52
}55
}53
}56
}54
if _, err := queries.SetStatus(ctx, store.SetStatusParams{Status: menu.SoldOut, Name: "haddock"}); err != nil {57
if _, err := queries.SetStatus(ctx, store.SetStatusParams{Status: menu.SoldOut, Name: "haddock"}); err != nil {55
return fmt.Errorf("selling out of haddock: %w", err)58
return fmt.Errorf("selling out of haddock: %w", err)56
}59
}57
60
58
listed, err := queries.ListMenu(ctx)61
listed, err := queries.ListMenu(ctx)59
if err != nil {62
if err != nil {60
return fmt.Errorf("listing the menu: %w", err)63
return fmt.Errorf("listing the menu: %w", err)61
}64
}62
var menuRow store.ListMenuRow65
var menuRow store.ListMenuRow63
fmt.Fprintf(output, "ListMenu: section %T, status %T\n", menuRow.Section, menuRow.Status)66
fmt.Fprintf(output, "ListMenu: section %T, status %T\n", menuRow.Section, menuRow.Status)64
for _, item := range listed {67
for _, item := range listed {65
fmt.Fprintf(output, " %-9s %s%s\n", item.Section, item.Name, soldOut(item.Status))68
fmt.Fprintf(output, " %-9s %s%s\n", item.Section, item.Name, soldOut(item.Status))66
}69
}67
70
68
fish, err := queries.ItemsInSection(ctx, store.SectionFish)71
fish, err := queries.ItemsInSection(ctx, store.MenuItemsSectionFish)69
if err != nil {72
if err != nil {70
return fmt.Errorf("listing the fish: %w", err)73
return fmt.Errorf("listing the fish: %w", err)71
}74
}72
var sectionRow store.ItemsInSectionRow75
var sectionRow store.ItemsInSectionRow73
fmt.Fprintf(output, "ItemsInSection: section %T, status %T\n", sectionRow.Section, sectionRow.Status)76
fmt.Fprintf(output, "ItemsInSection: section %T, status %T\n", sectionRow.Section, sectionRow.Status)74
for _, item := range fish {77
for _, item := range fish {75
fmt.Fprintf(output, " %-9s %s%s\n", item.Section, item.Name, soldOut(item.Status))78
fmt.Fprintf(output, " %-9s %s%s\n", item.Section, item.Name, soldOut(item.Status))76
}79
}77
80
78
drinks := store.Section("drinks")81
drinks := store.MenuItemsSection("drinks")79
fmt.Fprintf(output, "section drinks is valid: %t\n", drinks.Valid())82
fmt.Fprintf(output, "section drinks is valid: %t\n", drinks.Valid())80
err = queries.AddItem(ctx, store.AddItemParams{Name: "lemonade", Section: drinks})83
err = queries.AddItem(ctx, store.AddItemParams{Name: "lemonade", Section: drinks})81
if err == nil {84
if err == nil {82
return errors.New("added an item to a section the enum does not list")85
return errors.New("added an item to a section the enum does not list")83
}86
}84
fmt.Fprintf(output, "refused section drinks: %v\n", err)87
fmt.Fprintf(output, "refused section drinks: %v\n", err)85
return nil88
return nil86
}89
}87
90
88
// soldOut returns a note for an item that has sold out, comparing against the typed91
// soldOut returns a note for an item that has sold out, comparing against the typed89
// constant rather than a string.92
// constant rather than a string.90
//93
//91
// Takes status (menu.Status) which is the item's status.94
// Takes status (menu.Status) which is the item's status.92
//95
//93
// Returns string which is the note, or empty when the item is available.96
// Returns string which is the note, or empty when the item is available.94
func soldOut(status menu.Status) string {97
func soldOut(status menu.Status) string {95
if status == menu.SoldOut {98
if status == menu.SoldOut {96
return " (sold out)"99
return " (sold out)"97
}100
}98
return ""101
return ""99
}102
}Go type differences
The same SQL types map to different Go types, because each database reports them differently.
| Query | Field | DuckDB | MySQL |
|---|---|---|---|
| AddItem | parameter section | Section | MenuItemsSection |
| ListMenu | column section | Section | MenuItemsSection |
| ItemsInSection | parameter section | Section | MenuItemsSection |