Enums as generated Go types, and your own types with go_type: PostgreSQL and CockroachDB
PostgreSQL and CockroachDB run the same files for this example: the SQL and the Go are identical.
Configuration
sgl.yamlidentical
PostgreSQL
CockroachDB
1
version: 11
version: 12
2
3
databases:3
databases:4
- name: harbour4
- name: harbour5
dialect: postgres5
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
driver: database/sql11
driver: database/sqlMigrations
db/migrations/0001_boats.up.sqlidentical
PostgreSQL
CockroachDB
1
CREATE TYPE boat_status AS ENUM ('moored', 'at_sea', 'dry_dock');1
CREATE TYPE boat_status AS ENUM ('moored', 'at_sea', 'dry_dock');2
2
3
CREATE TABLE boats (3
CREATE TABLE boats (4
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,4
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,5
name TEXT NOT NULL,5
name TEXT NOT NULL,6
status boat_status NOT NULL DEFAULT 'moored'6
status boat_status NOT NULL DEFAULT 'moored'7
);7
);db/migrations/0001_boats.down.sqlidentical
PostgreSQL
CockroachDB
1
DROP TABLE boats;1
DROP TABLE boats;2
DROP TYPE boat_status;2
DROP TYPE boat_status;Queries
AddBoatidentical
PostgreSQL · db/queries/boats.sql
CockroachDB · db/queries/boats.sql
1
-- sgl.query(AddBoat, exec)1
-- sgl.query(AddBoat, exec)2
INSERT INTO boats (name, status) VALUES ($1, $2);2
INSERT INTO boats (name, status) VALUES ($1, $2);SetStatusidentical
PostgreSQL · db/queries/boats.sql
CockroachDB · db/queries/boats.sql
1
-- sgl.query(SetStatus, execrows)1
-- sgl.query(SetStatus, execrows)2
UPDATE boats SET status = $1 WHERE name = $2;2
UPDATE boats SET status = $1 WHERE name = $2;ListBoatsidentical
PostgreSQL · db/queries/boats.sql
CockroachDB · db/queries/boats.sql
1
-- sgl.query(ListBoats, many)1
-- sgl.query(ListBoats, many)2
SELECT name, status FROM boats ORDER BY id;2
SELECT name, status FROM boats ORDER BY id;BoatsWithStatusidentical
PostgreSQL · db/queries/boats.sql
CockroachDB · db/queries/boats.sql
1
-- sgl.query(BoatsWithStatus, many)1
-- sgl.query(BoatsWithStatus, many)2
-- sgl.column(status, go_type: "seagull.sh/seagull/examples/postgres/007_enums/internal/harbour.Status")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;3
SELECT name, status FROM boats WHERE status = $1 ORDER BY id;What sgl generates
AddBoatidentical
PostgreSQL
CockroachDB
1
func (queries *Queries) AddBoat(ctx context.Context, params AddBoatParams) error1
func (queries *Queries) AddBoat(ctx context.Context, params AddBoatParams) error2
2
3
type AddBoatParams struct {3
type AddBoatParams struct {4
Name string4
Name string5
Status BoatStatus5
Status BoatStatus6
}6
}7
7
8
type BoatStatus string8
type BoatStatus stringSetStatusidentical
PostgreSQL
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 BoatStatus4
Status BoatStatus5
Name string5
Name string6
}6
}7
7
8
type BoatStatus string8
type BoatStatus stringListBoatsidentical
PostgreSQL
CockroachDB
1
func (queries *Queries) ListBoats(ctx context.Context) ([]ListBoatsRow, error)1
func (queries *Queries) ListBoats(ctx context.Context) ([]ListBoatsRow, error)2
2
3
type ListBoatsRow struct {3
type ListBoatsRow struct {4
Name string `json:"name"`4
Name string `json:"name"`5
Status BoatStatus `json:"status"`5
Status BoatStatus `json:"status"`6
}6
}7
7
8
type BoatStatus string8
type BoatStatus stringBoatsWithStatusidentical
PostgreSQL
CockroachDB
1
func (queries *Queries) BoatsWithStatus(ctx context.Context, status BoatStatus) ([]BoatsWithStatusRow, error)1
func (queries *Queries) BoatsWithStatus(ctx context.Context, status BoatStatus) ([]BoatsWithStatusRow, error)2
2
3
type BoatStatus string3
type BoatStatus string4
4
5
type BoatsWithStatusRow struct {5
type BoatsWithStatusRow struct {6
Name string `json:"name"`6
Name string `json:"name"`7
Status harbour.Status `json:"status"`7
Status harbour.Status `json:"status"`8
}8
}The program
example.goidentical
PostgreSQL
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/postgres/007_enums/internal/store"10
"seagull.sh/seagull/examples/postgres/007_enums/internal/store"11
)11
)12
12
13
// run gives the harbour's boats a status from the boat_status enum and lists them twice.13
// run gives the harbour's boats a status from the boat_status enum and lists them twice.14
// The enum becomes a named Go type in the generated package: models.go declares14
// The enum becomes a named Go type in the generated package: models.go declares15
// store.BoatStatus, named after the enum type, with a constant for each label, such as15
// store.BoatStatus, named after the enum type, with a constant for each label, such as16
// store.BoatStatusMoored, and a Valid method. Every column and parameter of the enum16
// 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. Valid17
// 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 a18
// 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.19
// status outside the enum is refused when the statement runs.20
//20
//21
// For a type of your own, here harbour.Status with a method that describes each label,21
// For a type of your own, here harbour.Status with a method that describes each label,22
// name it with a go_type override. BoatsWithStatus does that for one query with22
// name it with a go_type override. BoatsWithStatus does that for one query with23
// sgl.column(status, go_type: ...); a types.overrides entry in sgl.yaml, naming the enum23
// sgl.column(status, go_type: ...); a types.overrides entry in sgl.yaml, naming the enum24
// type boat_status or the column boats.status, would do it for every query, and for the24
// type boat_status or the column boats.status, would do it for every query, and for the25
// parameters bound to the column too. A query's sgl.column override changes only that25
// 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 parameter26
// query's returned column, so AddBoat, SetStatus and BoatsWithStatus's own parameter27
// still take the generated store.BoatStatus.27
// still take the generated store.BoatStatus.28
//28
//29
// Takes database (*sql.DB) which is a migrated database.29
// Takes database (*sql.DB) which is a migrated database.30
// Takes output (io.Writer) which receives both listings.30
// Takes output (io.Writer) which receives both listings.31
//31
//32
// Returns error when a query fails or a status outside the enum is accepted.32
// Returns error when a query fails or a status outside the enum is accepted.33
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 {34
queries := store.New(database)34
queries := store.New(database)35
35
36
boats := []store.AddBoatParams{36
boats := []store.AddBoatParams{37
{Name: "Kittiwake", Status: store.BoatStatusMoored},37
{Name: "Kittiwake", Status: store.BoatStatusMoored},38
{Name: "Salty Chip", Status: store.BoatStatusAtSea},38
{Name: "Salty Chip", Status: store.BoatStatusAtSea},39
{Name: "Gull Wing", Status: store.BoatStatusDryDock},39
{Name: "Gull Wing", Status: store.BoatStatusDryDock},40
{Name: "Driftwood", Status: store.BoatStatusMoored},40
{Name: "Driftwood", Status: store.BoatStatusMoored},41
}41
}42
for _, boat := range boats {42
for _, boat := range boats {43
if err := queries.AddBoat(ctx, boat); err != nil {43
if err := queries.AddBoat(ctx, boat); err != nil {44
return fmt.Errorf("adding %s: %w", boat.Name, err)44
return fmt.Errorf("adding %s: %w", boat.Name, err)45
}45
}46
}46
}47
47
48
sunk := store.BoatStatus("sunk")48
sunk := store.BoatStatus("sunk")49
if err := queries.AddBoat(ctx, store.AddBoatParams{Name: "Wreck", Status: sunk}); err == nil {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")50
return errors.New("adding a boat with status sunk: got no error, want the enum to refuse it")51
}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())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
53
54
if _, err := queries.SetStatus(ctx, store.SetStatusParams{Status: store.BoatStatusAtSea, Name: "Kittiwake"}); err != nil {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)55
return fmt.Errorf("sending Kittiwake to sea: %w", err)56
}56
}57
57
58
listed, err := queries.ListBoats(ctx)58
listed, err := queries.ListBoats(ctx)59
if err != nil {59
if err != nil {60
return fmt.Errorf("listing boats: %w", err)60
return fmt.Errorf("listing boats: %w", err)61
}61
}62
fmt.Fprintln(output, "\nListBoats, where status is the generated store.BoatStatus:")62
fmt.Fprintln(output, "\nListBoats, where status is the generated store.BoatStatus:")63
for _, boat := range listed {63
for _, boat := range listed {64
fmt.Fprintf(output, " %-10s %-8s %T\n", boat.Name, boat.Status, boat.Status)64
fmt.Fprintf(output, " %-10s %-8s %T\n", boat.Name, boat.Status, boat.Status)65
}65
}66
66
67
atSea, err := queries.BoatsWithStatus(ctx, store.BoatStatusAtSea)67
atSea, err := queries.BoatsWithStatus(ctx, store.BoatStatusAtSea)68
if err != nil {68
if err != nil {69
return fmt.Errorf("listing boats at sea: %w", err)69
return fmt.Errorf("listing boats at sea: %w", err)70
}70
}71
fmt.Fprintln(output, "\nBoatsWithStatus, where a go_type override makes status a harbour.Status:")71
fmt.Fprintln(output, "\nBoatsWithStatus, where a go_type override makes status a harbour.Status:")72
for _, boat := range atSea {72
for _, boat := range atSea {73
fmt.Fprintf(output, " %-10s %-14s %T\n", boat.Name, boat.Status.Describe(), boat.Status)73
fmt.Fprintf(output, " %-10s %-14s %T\n", boat.Name, boat.Status.Describe(), boat.Status)74
}74
}75
return nil75
return nil76
}76
}