Enums as generated Go types, and your own types with go_type: PostgreSQL and MySQL
Every file of the PostgreSQL and MySQL versions side by side, with the lines that differ marked. 1 parameter or column types differ in Go.
Configuration
sgl.yamldiffers
PostgreSQL
MySQL
1
version: 11
version: 12
2
3
databases:3
databases:4
- name: harbour4
- name: menu5
dialect: postgres5
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
driver: database/sql11
types:12
overrides:13
- column: menu_items.status14
go: seagull.sh/seagull/examples/mysql/007_enums/internal/menu.StatusMigrations
db/migrations/0001_boats.up.sqldiffers
PostgreSQL
MySQL · db/migrations/0001_menu.up.sql
1
CREATE TYPE boat_status AS ENUM ('moored', 'at_sea', 'dry_dock');1
CREATE TABLE menu_items (2
2
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,3
CREATE TABLE boats (3
name VARCHAR(50) NOT NULL UNIQUE,4
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,4
section ENUM('fish', 'sides', 'puddings') NOT NULL,5
name TEXT NOT NULL,5
status ENUM('available', 'sold_out') NOT NULL DEFAULT 'available'6
status boat_status NOT NULL DEFAULT 'moored'7
);6
);db/migrations/0001_boats.down.sqldiffers
PostgreSQL
MySQL · db/migrations/0001_menu.down.sql
1
DROP TABLE boats;1
DROP TABLE menu_items;2
DROP TYPE boat_status;Queries
AddBoatonly PostgreSQL
PostgreSQL · db/queries/boats.sql
MySQL · not in this version
1
-- sgl.query(AddBoat, exec)2
INSERT INTO boats (name, status) VALUES ($1, $2);SetStatusdiffers
PostgreSQL · db/queries/boats.sql
MySQL · db/queries/menu.sql
1
-- sgl.query(SetStatus, execrows)1
-- sgl.query(SetStatus, execrows)2
UPDATE boats SET status = $1 WHERE name = $2;2
UPDATE menu_items SET status = ? WHERE name = ?;ListBoatsonly PostgreSQL
PostgreSQL · db/queries/boats.sql
MySQL · not in this version
1
-- sgl.query(ListBoats, many)2
SELECT name, status FROM boats ORDER BY id;BoatsWithStatusonly PostgreSQL
PostgreSQL · db/queries/boats.sql
MySQL · not in this version
1
-- sgl.query(BoatsWithStatus, many)2
-- sgl.column(status, go_type: "seagull.sh/seagull/examples/postgres/007_enums/internal/harbour.Status")3
SELECT name, status FROM boats WHERE status = $1 ORDER BY id;AddItemonly MySQL
PostgreSQL · not in this version
MySQL · db/queries/menu.sql
1
-- sgl.query(AddItem, exec)2
INSERT INTO menu_items (name, section) VALUES (?, ?);ListMenuonly MySQL
PostgreSQL · not in this version
MySQL · db/queries/menu.sql
1
-- sgl.query(ListMenu, many)2
SELECT name, section, status FROM menu_items ORDER BY section, name;ItemsInSectiononly MySQL
PostgreSQL · not in this version
MySQL · db/queries/menu.sql
1
-- sgl.query(ItemsInSection, many)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 = ? ORDER BY name;What sgl generates
AddBoatonly PostgreSQL
PostgreSQL
MySQL · not in this version
1
func (queries *Queries) AddBoat(ctx context.Context, params AddBoatParams) error2
3
type AddBoatParams struct {4
Name string5
Status BoatStatus6
}7
8
type BoatStatus stringSetStatusdiffers
PostgreSQL
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 BoatStatus4
Status menu.Status5
Name string5
Name string6
}6
}7
8
type BoatStatus stringListBoatsonly PostgreSQL
PostgreSQL
MySQL · not in this version
1
func (queries *Queries) ListBoats(ctx context.Context) ([]ListBoatsRow, error)2
3
type ListBoatsRow struct {4
Name string `json:"name"`5
Status BoatStatus `json:"status"`6
}7
8
type BoatStatus stringBoatsWithStatusonly PostgreSQL
PostgreSQL
MySQL · not in this version
1
func (queries *Queries) BoatsWithStatus(ctx context.Context, status BoatStatus) ([]BoatsWithStatusRow, error)2
3
type BoatStatus string4
5
type BoatsWithStatusRow struct {6
Name string `json:"name"`7
Status harbour.Status `json:"status"`8
}AddItemonly MySQL
PostgreSQL · not in this version
MySQL
1
func (queries *Queries) AddItem(ctx context.Context, params AddItemParams) error2
3
type AddItemParams struct {4
Name string5
Section MenuItemsSection6
}7
8
type MenuItemsSection stringListMenuonly MySQL
PostgreSQL · not in this version
MySQL
1
func (queries *Queries) ListMenu(ctx context.Context) ([]ListMenuRow, error)2
3
type ListMenuRow struct {4
Name string `json:"name"`5
Section MenuItemsSection `json:"section"`6
Status menu.Status `json:"status"`7
}8
9
type MenuItemsSection stringItemsInSectiononly MySQL
PostgreSQL · not in this version
MySQL
1
func (queries *Queries) ItemsInSection(ctx context.Context, section MenuItemsSection) ([]ItemsInSectionRow, error)2
3
type MenuItemsSection string4
5
type ItemsInSectionRow struct {6
Name string `json:"name"`7
Section menu.Section `json:"section"`8
Status menu.Status `json:"status"`9
}The program
example.godiffers
PostgreSQL
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/postgres/007_enums/internal/store"10
"seagull.sh/seagull/examples/mysql/007_enums/internal/menu"11
"seagull.sh/seagull/examples/mysql/007_enums/internal/store"11
)12
)12
13
13
// run gives the harbour's boats a status from the boat_status enum and lists them twice.14
// run fills a chip shop menu whose section and status columns are MySQL ENUMs, marks an14
// The enum becomes a named Go type in the generated package: models.go declares15
// item sold out and reads the menu back. Each ENUM becomes a named Go type in the15
// store.BoatStatus, named after the enum type, with a constant for each label, such as16
// generated package unless an override names a type of your own:16
// store.BoatStatusMoored, and a Valid method. Every column and parameter of the enum17
// takes it, in models.go, in Params structs and in Row structs, as ListBoats shows. Valid18
// reports whether a value is one of the labels, and the server still enforces them, so a19
// status outside the enum is refused when the statement runs.20
//17
//21
// For a type of your own, here harbour.Status with a method that describes each label,18
// - section has no override, so models.go declares store.MenuItemsSection, named after22
// name it with a go_type override. BoatsWithStatus does that for one query with19
// the table and column that hold the ENUM, with a constant for each value, such as23
// sgl.column(status, go_type: ...); a types.overrides entry in sgl.yaml, naming the enum20
// store.MenuItemsSectionFish, and a Valid method; ListMenuRow.Section,24
// type boat_status or the column boats.status, would do it for every query, and for the21
// AddItemParams.Section and ItemsInSection's parameter are all a25
// parameters bound to the column too. A query's sgl.column override changes only that22
// store.MenuItemsSection;26
// query's returned column, so AddBoat, SetStatus and BoatsWithStatus's own parameter23
// - sgl.yaml's types.overrides names the column menu_items.status, so every query that27
// still take the generated store.BoatStatus.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;27
// - sgl.column(section, go_type: ...) in ItemsInSection gives that one query's returned28
// Section the type menu.Section; the parameter it compares with stays a29
// store.MenuItemsSection.28
//30
//31
// An ENUM sorts in the order its values are declared, not alphabetically, so ORDER BY32
// section lists fish, then sides, then puddings. Valid reports whether a value is one the33
// ENUM declares, so the shop can check a section before it binds it. In strict mode, the34
// default since MySQL 5.7, a value the ENUM does not list is refused as it is stored as35
// well; MySQL keeps an ENUM as the index of its value, and its error says the value was36
// truncated.37
//29
// Takes database (*sql.DB) which is a migrated database.38
// Takes database (*sql.DB) which is a migrated database.30
// Takes output (io.Writer) which receives both listings.39
// Takes output (io.Writer) which receives the menu, the fish section and the refusal.31
//40
//32
// Returns error when a query fails or a status outside the enum is accepted.41
// Returns error when a query fails other than as the example expects.33
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 {34
queries := store.New(database)43
queries := store.New(database)35
44
36
boats := []store.AddBoatParams{45
items := []store.AddItemParams{37
{Name: "Kittiwake", Status: store.BoatStatusMoored},46
{Name: "cod", Section: store.MenuItemsSectionFish},38
{Name: "Salty Chip", Status: store.BoatStatusAtSea},47
{Name: "haddock", Section: store.MenuItemsSectionFish},39
{Name: "Gull Wing", Status: store.BoatStatusDryDock},48
{Name: "chips", Section: store.MenuItemsSectionSides},40
{Name: "Driftwood", Status: store.BoatStatusMoored},49
{Name: "mushy peas", Section: store.MenuItemsSectionSides},50
{Name: "sticky toffee pudding", Section: store.MenuItemsSectionPuddings},41
}51
}42
for _, boat := range boats {52
for _, item := range items {43
if err := queries.AddBoat(ctx, boat); err != nil {53
if err := queries.AddItem(ctx, item); err != nil {44
return fmt.Errorf("adding %s: %w", boat.Name, err)54
return fmt.Errorf("adding %s: %w", item.Name, err)45
}55
}46
}56
}47
57
if _, err := queries.SetStatus(ctx, store.SetStatusParams{Status: menu.SoldOut, Name: "haddock"}); err != nil {48
sunk := store.BoatStatus("sunk")58
return fmt.Errorf("selling out of haddock: %w", err)49
if err := queries.AddBoat(ctx, store.AddBoatParams{Name: "Wreck", Status: sunk}); err == nil {50
return errors.New("adding a boat with status sunk: got no error, want the enum to refuse it")51
}52
fmt.Fprintf(output, "status %q: Valid() is %t, and the server refuses it, as it is not a boat_status label\n", sunk, sunk.Valid())53
54
if _, err := queries.SetStatus(ctx, store.SetStatusParams{Status: store.BoatStatusAtSea, Name: "Kittiwake"}); err != nil {55
return fmt.Errorf("sending Kittiwake to sea: %w", err)56
}59
}57
60
58
listed, err := queries.ListBoats(ctx)61
listed, err := queries.ListMenu(ctx)59
if err != nil {62
if err != nil {60
return fmt.Errorf("listing boats: %w", err)63
return fmt.Errorf("listing the menu: %w", err)61
}64
}62
fmt.Fprintln(output, "\nListBoats, where status is the generated store.BoatStatus:")65
var menuRow store.ListMenuRow63
for _, boat := range listed {66
fmt.Fprintf(output, "ListMenu: section %T, status %T\n", menuRow.Section, menuRow.Status)64
fmt.Fprintf(output, " %-10s %-8s %T\n", boat.Name, boat.Status, boat.Status)67
for _, item := range listed {68
fmt.Fprintf(output, " %-9s %s%s\n", item.Section, item.Name, soldOut(item.Status))65
}69
}66
70
67
atSea, err := queries.BoatsWithStatus(ctx, store.BoatStatusAtSea)71
fish, err := queries.ItemsInSection(ctx, store.MenuItemsSectionFish)68
if err != nil {72
if err != nil {69
return fmt.Errorf("listing boats at sea: %w", err)73
return fmt.Errorf("listing the fish: %w", err)70
}74
}71
fmt.Fprintln(output, "\nBoatsWithStatus, where a go_type override makes status a harbour.Status:")75
var sectionRow store.ItemsInSectionRow72
for _, boat := range atSea {76
fmt.Fprintf(output, "ItemsInSection: section %T, status %T\n", sectionRow.Section, sectionRow.Status)73
fmt.Fprintf(output, " %-10s %-14s %T\n", boat.Name, boat.Status.Describe(), boat.Status)77
for _, item := range fish {78
fmt.Fprintf(output, " %-9s %s%s\n", item.Section, item.Name, soldOut(item.Status))74
}79
}80
81
drinks := store.MenuItemsSection("drinks")82
fmt.Fprintf(output, "section drinks is valid: %t\n", drinks.Valid())83
err = queries.AddItem(ctx, store.AddItemParams{Name: "lemonade", Section: drinks})84
if err == nil {85
return errors.New("added an item to a section the enum does not list")86
}87
fmt.Fprintf(output, "refused section drinks: %v\n", err)75
return nil88
return nil76
}89
}90
91
// soldOut returns a note for an item that has sold out, comparing against the typed92
// constant rather than a string.93
//94
// Takes status (menu.Status) which is the item's status.95
//96
// Returns string which is the note, or empty when the item is available.97
func soldOut(status menu.Status) string {98
if status == menu.SoldOut {99
return " (sold out)"100
}101
return ""102
}Go type differences
The same SQL types map to different Go types, because each database reports them differently.
| Query | Field | PostgreSQL | MySQL |
|---|---|---|---|
| SetStatus | parameter status | BoatStatus | menu.Status |