Enums as generated Go types, and your own types with go_type: DuckDB and CockroachDB
Every file of the DuckDB and CockroachDB versions side by side, with the lines that differ marked. 1 parameter or column types differ in Go.
Configuration
sgl.yamldiffers
DuckDB
CockroachDB
1
version: 11
version: 12
2
3
databases:3
databases:4
- name: menu4
- name: harbour5
dialect: duckdb5
dialect: postgres6
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
driver: database/sql12
overrides:13
- sql: item_status14
go: seagull.sh/seagull/examples/duckdb/007_enums/internal/menu.StatusMigrations
db/migrations/0001_menu.up.sqldiffers
DuckDB
CockroachDB · db/migrations/0001_boats.up.sql
1
CREATE TYPE section AS ENUM ('fish', 'sides', 'puddings');1
CREATE TYPE boat_status AS ENUM ('moored', 'at_sea', 'dry_dock');2
3
CREATE TYPE item_status AS ENUM ('available', 'sold_out');4
5
CREATE SEQUENCE menu_item_ids;6
2
7
CREATE TABLE menu_items (3
CREATE TABLE boats (8
id BIGINT PRIMARY KEY DEFAULT nextval('menu_item_ids'),4
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,9
name VARCHAR NOT NULL UNIQUE,5
name TEXT NOT NULL,10
section section NOT NULL,6
status boat_status NOT NULL DEFAULT 'moored'11
status item_status NOT NULL DEFAULT 'available'12
);7
);db/migrations/0001_menu.down.sqldiffers
DuckDB
CockroachDB · db/migrations/0001_boats.down.sql
1
DROP TABLE menu_items;1
DROP TABLE boats;2
DROP SEQUENCE menu_item_ids;2
DROP TYPE boat_status;3
DROP TYPE item_status;4
DROP TYPE section;Queries
AddItemonly DuckDB
DuckDB · db/queries/menu.sql
CockroachDB · not in this version
1
-- sgl.query(AddItem, exec)2
INSERT INTO menu_items (name, section) VALUES ($1, $2);SetStatusdiffers
DuckDB · db/queries/menu.sql
CockroachDB · db/queries/boats.sql
1
-- sgl.query(SetStatus, execrows)1
-- sgl.query(SetStatus, execrows)2
UPDATE menu_items SET status = $1 WHERE name = $2;2
UPDATE boats SET status = $1 WHERE name = $2;ListMenuonly DuckDB
DuckDB · db/queries/menu.sql
CockroachDB · not in this version
1
-- sgl.query(ListMenu, many)2
SELECT name, section, status FROM menu_items ORDER BY section, name;ItemsInSectiononly DuckDB
DuckDB · db/queries/menu.sql
CockroachDB · not in this version
1
-- sgl.query(ItemsInSection, many)2
-- sgl.column(section, go_type: "seagull.sh/seagull/examples/duckdb/007_enums/internal/menu.Section")3
SELECT name, section, status FROM menu_items WHERE section = $1 ORDER BY name;AddBoatonly CockroachDB
DuckDB · not in this version
CockroachDB · db/queries/boats.sql
1
-- sgl.query(AddBoat, exec)2
INSERT INTO boats (name, status) VALUES ($1, $2);ListBoatsonly CockroachDB
DuckDB · not in this version
CockroachDB · db/queries/boats.sql
1
-- sgl.query(ListBoats, many)2
SELECT name, status FROM boats ORDER BY id;BoatsWithStatusonly CockroachDB
DuckDB · not in this version
CockroachDB · db/queries/boats.sql
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;What sgl generates
AddItemonly DuckDB
DuckDB
CockroachDB · not in this version
1
func (queries *Queries) AddItem(ctx context.Context, params AddItemParams) error2
3
type AddItemParams struct {4
Name string5
Section Section6
}7
8
type Section stringSetStatusdiffers
DuckDB
CockroachDB
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 BoatStatus5
Name string5
Name string6
}6
}7
8
type BoatStatus stringListMenuonly DuckDB
DuckDB
CockroachDB · not in this version
1
func (queries *Queries) ListMenu(ctx context.Context) ([]ListMenuRow, error)2
3
type ListMenuRow struct {4
Name string `json:"name"`5
Section Section `json:"section"`6
Status menu.Status `json:"status"`7
}8
9
type Section stringItemsInSectiononly DuckDB
DuckDB
CockroachDB · not in this version
1
func (queries *Queries) ItemsInSection(ctx context.Context, section Section) ([]ItemsInSectionRow, error)2
3
type Section string4
5
type ItemsInSectionRow struct {6
Name string `json:"name"`7
Section menu.Section `json:"section"`8
Status menu.Status `json:"status"`9
}AddBoatonly CockroachDB
DuckDB · not in this version
CockroachDB
1
func (queries *Queries) AddBoat(ctx context.Context, params AddBoatParams) error2
3
type AddBoatParams struct {4
Name string5
Status BoatStatus6
}7
8
type BoatStatus stringListBoatsonly CockroachDB
DuckDB · not in this version
CockroachDB
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 CockroachDB
DuckDB · not in this version
CockroachDB
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
}The program
example.godiffers
DuckDB
CockroachDB
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/postgres/007_enums/internal/store"11
"seagull.sh/seagull/examples/duckdb/007_enums/internal/store"12
)11
)13
12
14
// run fills a chip shop menu whose section and status columns are DuckDB enums, marks an13
// run gives the harbour's boats a status from the boat_status enum and lists them twice.15
// item sold out and reads the menu back. Each enum becomes a named Go type in the14
// The enum becomes a named Go type in the generated package: models.go declares16
// generated package unless an override names a type of your own:15
// store.BoatStatus, named after the enum type, with a constant for each label, such as17
//16
// store.BoatStatusMoored, and a Valid method. Every column and parameter of the enum18
// - section has no override, so models.go declares store.Section, with a constant for17
// takes it, in models.go, in Params structs and in Row structs, as ListBoats shows. Valid19
// each value, such as store.SectionFish, and a Valid method; ListMenuRow.Section,18
// reports whether a value is one of the labels, and the server still enforces them, so a20
// AddItemParams.Section and ItemsInSection's parameter are all a store.Section;19
// status outside the enum is refused when the statement runs.21
// - sgl.yaml's types.overrides names the enum type item_status, so every column of that22
// enum gives menu.Status, as in ListMenuRow.Status, and so does every parameter23
// compared with or assigned to one, as in SetStatusParams.Status; the generated24
// package then declares no type for item_status;25
// - sgl.column(section, go_type: ...) in ItemsInSection gives that one query's returned26
// Section the type menu.Section; the parameter it compares with stays a27
// store.Section.28
//20
//29
// An enum sorts in the order its values are declared, not alphabetically, so ORDER BY21
// For a type of your own, here harbour.Status with a method that describes each label,30
// section lists fish, then sides, then puddings. Valid reports whether a value is one the22
// name it with a go_type override. BoatsWithStatus does that for one query with31
// enum declares, so the shop can check a section before it binds it. DuckDB checks each23
// sgl.column(status, go_type: ...); a types.overrides entry in sgl.yaml, naming the enum32
// value against the enum as it is stored as well, so a section the enum does not list is24
// type boat_status or the column boats.status, would do it for every query, and for the33
// refused; DuckDB keeps an enum as a small integer, and its error names that type.25
// parameters bound to the column too. A query's sgl.column override changes only that26
// query's returned column, so AddBoat, SetStatus and BoatsWithStatus's own parameter27
// still take the generated store.BoatStatus.34
//28
//35
// Takes database (*sql.DB) which is a migrated database.29
// Takes database (*sql.DB) which is a migrated database.36
// Takes output (io.Writer) which receives the menu, the fish section and the refusal.30
// Takes output (io.Writer) which receives both listings.37
//31
//38
// Returns error when a query fails other than as the example expects.32
// Returns error when a query fails or a status outside the enum is accepted.39
func run(ctx context.Context, database *sql.DB, output io.Writer) error {33
func run(ctx context.Context, database *sql.DB, output io.Writer) error {40
queries := store.New(database)34
queries := store.New(database)41
35
42
items := []store.AddItemParams{36
boats := []store.AddBoatParams{43
{Name: "cod", Section: store.SectionFish},37
{Name: "Kittiwake", Status: store.BoatStatusMoored},44
{Name: "haddock", Section: store.SectionFish},38
{Name: "Salty Chip", Status: store.BoatStatusAtSea},45
{Name: "chips", Section: store.SectionSides},39
{Name: "Gull Wing", Status: store.BoatStatusDryDock},46
{Name: "mushy peas", Section: store.SectionSides},40
{Name: "Driftwood", Status: store.BoatStatusMoored},47
{Name: "sticky toffee pudding", Section: store.SectionPuddings},48
}41
}49
for _, item := range items {42
for _, boat := range boats {50
if err := queries.AddItem(ctx, item); err != nil {43
if err := queries.AddBoat(ctx, boat); err != nil {51
return fmt.Errorf("adding %s: %w", item.Name, err)44
return fmt.Errorf("adding %s: %w", boat.Name, err)52
}45
}53
}46
}54
if _, err := queries.SetStatus(ctx, store.SetStatusParams{Status: menu.SoldOut, Name: "haddock"}); err != nil {55
return fmt.Errorf("selling out of haddock: %w", err)56
}57
47
58
listed, err := queries.ListMenu(ctx)48
sunk := store.BoatStatus("sunk")59
if err != nil {49
if err := queries.AddBoat(ctx, store.AddBoatParams{Name: "Wreck", Status: sunk}); err == nil {60
return fmt.Errorf("listing the menu: %w", err)50
return errors.New("adding a boat with status sunk: got no error, want the enum to refuse it")61
}51
}62
var menuRow store.ListMenuRow52
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())63
fmt.Fprintf(output, "ListMenu: section %T, status %T\n", menuRow.Section, menuRow.Status)53
64
for _, item := range listed {54
if _, err := queries.SetStatus(ctx, store.SetStatusParams{Status: store.BoatStatusAtSea, Name: "Kittiwake"}); err != nil {65
fmt.Fprintf(output, " %-9s %s%s\n", item.Section, item.Name, soldOut(item.Status))55
return fmt.Errorf("sending Kittiwake to sea: %w", err)66
}56
}67
57
68
fish, err := queries.ItemsInSection(ctx, store.SectionFish)58
listed, err := queries.ListBoats(ctx)69
if err != nil {59
if err != nil {70
return fmt.Errorf("listing the fish: %w", err)60
return fmt.Errorf("listing boats: %w", err)71
}61
}72
var sectionRow store.ItemsInSectionRow62
fmt.Fprintln(output, "\nListBoats, where status is the generated store.BoatStatus:")73
fmt.Fprintf(output, "ItemsInSection: section %T, status %T\n", sectionRow.Section, sectionRow.Status)63
for _, boat := range listed {74
for _, item := range fish {64
fmt.Fprintf(output, " %-10s %-8s %T\n", boat.Name, boat.Status, boat.Status)75
fmt.Fprintf(output, " %-9s %s%s\n", item.Section, item.Name, soldOut(item.Status))76
}65
}77
66
78
drinks := store.Section("drinks")67
atSea, err := queries.BoatsWithStatus(ctx, store.BoatStatusAtSea)79
fmt.Fprintf(output, "section drinks is valid: %t\n", drinks.Valid())68
if err != nil {80
err = queries.AddItem(ctx, store.AddItemParams{Name: "lemonade", Section: drinks})69
return fmt.Errorf("listing boats at sea: %w", err)81
if err == nil {82
return errors.New("added an item to a section the enum does not list")83
}70
}84
fmt.Fprintf(output, "refused section drinks: %v\n", err)71
fmt.Fprintln(output, "\nBoatsWithStatus, where a go_type override makes status a harbour.Status:")85
return nil72
for _, boat := range atSea {86
}73
fmt.Fprintf(output, " %-10s %-14s %T\n", boat.Name, boat.Status.Describe(), boat.Status)87
88
// soldOut returns a note for an item that has sold out, comparing against the typed89
// constant rather than a string.90
//91
// Takes status (menu.Status) which is the item's status.92
//93
// Returns string which is the note, or empty when the item is available.94
func soldOut(status menu.Status) string {95
if status == menu.SoldOut {96
return " (sold out)"97
}74
}98
return ""75
return nil99
}76
}Go type differences
The same SQL types map to different Go types, because each database reports them differently.
| Query | Field | DuckDB | CockroachDB |
|---|---|---|---|
| SetStatus | parameter status | menu.Status | BoatStatus |