Choosing a command: one, many, exec, execrows, execresult: MySQL and CockroachDB
Every file of the MySQL and CockroachDB versions side by side, with the lines that differ marked. 3 parameter or column types differ in Go.
Configuration
sgl.yamldiffers
MySQL
CockroachDB
1
version: 11
version: 12
2
3
databases:3
databases:4
- name: harbour4
- name: harbour5
dialect: mysql5
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/sqlMigrations
db/migrations/0001_boats.up.sqldiffers
MySQL
CockroachDB
1
CREATE TABLE boats (1
CREATE TABLE boats (2
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,2
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,3
name VARCHAR(100) NOT NULL,3
name TEXT NOT NULL,4
berth INT NOT NULL,4
berth BIGINT NOT NULL,5
at_sea BOOLEAN NOT NULL DEFAULT FALSE5
at_sea BOOLEAN NOT NULL DEFAULT false6
);6
);db/migrations/0001_boats.down.sqlidentical
MySQL
CockroachDB
1
DROP TABLE boats;1
DROP TABLE boats;Queries
MoorBoatdiffers
MySQL · db/queries/boats.sql
CockroachDB · db/queries/boats.sql
1
-- sgl.query(MoorBoat, execresult)1
-- sgl.query(MoorBoat, one)2
INSERT INTO boats (name, berth) VALUES (?, ?);2
INSERT INTO boats (name, berth) VALUES ($1, $2) RETURNING id;GetBoatdiffers
MySQL · db/queries/boats.sql
CockroachDB · db/queries/boats.sql
1
-- sgl.query(GetBoat, one)1
-- sgl.query(GetBoat, one)2
SELECT id, name, berth, at_sea FROM boats WHERE id = ?;2
SELECT id, name, berth, at_sea FROM boats WHERE id = $1;ListBoatsidentical
MySQL · db/queries/boats.sql
CockroachDB · db/queries/boats.sql
1
-- sgl.query(ListBoats, many)1
-- sgl.query(ListBoats, many)2
SELECT id, name, berth, at_sea FROM boats ORDER BY berth;2
SELECT id, name, berth, at_sea FROM boats ORDER BY berth;PutToSeadiffers
MySQL · db/queries/boats.sql
CockroachDB · db/queries/boats.sql
1
-- sgl.query(PutToSea, execrows)1
-- sgl.query(PutToSea, execrows)2
UPDATE boats SET at_sea = TRUE WHERE at_sea = FALSE;2
UPDATE boats SET at_sea = true WHERE NOT at_sea;ScrapBoatdiffers
MySQL · db/queries/boats.sql
CockroachDB · db/queries/boats.sql
1
-- sgl.query(ScrapBoat, exec)1
-- sgl.query(ScrapBoat, exec)2
DELETE FROM boats WHERE id = ?;2
DELETE FROM boats WHERE id = $1;MoveBerthonly CockroachDB
MySQL · not in this version
CockroachDB · db/queries/boats.sql
1
-- sgl.query(MoveBerth, execresult)2
UPDATE boats SET berth = $1 WHERE id = $2;What sgl generates
MoorBoatdiffers
MySQL
CockroachDB
1
func (queries *Queries) MoorBoat(ctx context.Context, params MoorBoatParams) (sql.Result, error)1
func (queries *Queries) MoorBoat(ctx context.Context, params MoorBoatParams) (MoorBoatRow, error)2
2
3
type MoorBoatParams struct {3
type MoorBoatParams struct {4
Name string4
Name string5
Berth int325
Berth int646
}6
}7
8
type MoorBoatRow struct {9
ID int64 `json:"id"`10
}GetBoatdiffers
MySQL
CockroachDB
1
func (queries *Queries) GetBoat(ctx context.Context, id int64) (GetBoatRow, error)1
func (queries *Queries) GetBoat(ctx context.Context, id int64) (GetBoatRow, error)2
2
3
type GetBoatRow struct {3
type GetBoatRow struct {4
ID int64 `json:"id"`4
ID int64 `json:"id"`5
Name string `json:"name"`5
Name string `json:"name"`6
Berth int32 `json:"berth"`6
Berth int64 `json:"berth"`7
AtSea bool `json:"at_sea"`7
AtSea bool `json:"at_sea"`8
}8
}ListBoatsdiffers
MySQL
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
ID int64 `json:"id"`4
ID int64 `json:"id"`5
Name string `json:"name"`5
Name string `json:"name"`6
Berth int32 `json:"berth"`6
Berth int64 `json:"berth"`7
AtSea bool `json:"at_sea"`7
AtSea bool `json:"at_sea"`8
}8
}PutToSeaidentical
MySQL
CockroachDB
1
func (queries *Queries) PutToSea(ctx context.Context) (int64, error)1
func (queries *Queries) PutToSea(ctx context.Context) (int64, error)ScrapBoatidentical
MySQL
CockroachDB
1
func (queries *Queries) ScrapBoat(ctx context.Context, id int64) error1
func (queries *Queries) ScrapBoat(ctx context.Context, id int64) errorMoveBerthonly CockroachDB
MySQL · not in this version
CockroachDB
1
func (queries *Queries) MoveBerth(ctx context.Context, params MoveBerthParams) (sql.Result, error)2
3
type MoveBerthParams struct {4
Berth int645
ID int646
}The program
example.godiffers
MySQL
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/mysql/002_commands/internal/store"10
"seagull.sh/seagull/examples/postgres/002_commands/internal/store"11
)11
)12
12
13
// run moors three boats, looks one up, scraps one, puts the rest to sea and lists the13
// run moors three boats, moves one, looks one up, scraps one, puts the rest to sea and14
// harbour. Each query in db/queries/boats.sql names a command, and the command decides14
// lists the harbour. Each query in db/queries/boats.sql names a command, and the command15
// what its generated method returns:15
// decides what its generated method returns:16
//16
//17
// - execresult returns sql.Result, which carries the new row's AUTO_INCREMENT id;17
// - one returns a single row, or sql.ErrNoRows when nothing matches; on an INSERT with18
// - one returns a single row, or sql.ErrNoRows when nothing matches;18
// RETURNING it hands back the new row's ID;19
// - execresult returns sql.Result. pgx's driver fills in RowsAffected, but its20
// LastInsertId always fails, as PostgreSQL has no last insert ID: use RETURNING;19
// - many returns a slice of rows;21
// - many returns a slice of rows;20
// - execrows returns how many rows the statement changed;22
// - execrows returns how many rows the statement changed;21
// - exec returns only an error.23
// - exec returns only an error.22
//24
//23
// The command has to fit the statement: exec on a SELECT, or one on an INSERT, makes sgl25
// The command has to fit the statement: exec on a SELECT, or one on an INSERT without24
// vet report Q009. MySQL has no RETURNING to turn a write into a one query, and sgl vet26
// RETURNING, makes sgl vet report Q009.25
// refuses one with Q052, so execresult is how an INSERT hands back its id.26
//27
//27
// Takes database (*sql.DB) which is a migrated database.28
// Takes database (*sql.DB) which is a migrated database.28
// Takes output (io.Writer) which receives what each call returned.29
// Takes output (io.Writer) which receives what each call returned.29
//30
//30
// Returns error when a query fails.31
// Returns error when a query fails.31
func run(ctx context.Context, database *sql.DB, output io.Writer) error {32
func run(ctx context.Context, database *sql.DB, output io.Writer) error {32
queries := store.New(database)33
queries := store.New(database)33
34
34
arrivals := []store.MoorBoatParams{35
arrivals := []store.MoorBoatParams{35
{Name: "Kittiwake", Berth: 1},36
{Name: "Kittiwake", Berth: 1},36
{Name: "Salty Chip", Berth: 2},37
{Name: "Salty Chip", Berth: 2},37
{Name: "Gull Wing", Berth: 5},38
{Name: "Gull Wing", Berth: 5},38
}39
}39
for _, arrival := range arrivals {40
for _, arrival := range arrivals {40
result, err := queries.MoorBoat(ctx, arrival)41
moored, err := queries.MoorBoat(ctx, arrival)41
if err != nil {42
if err != nil {42
return fmt.Errorf("mooring %s: %w", arrival.Name, err)43
return fmt.Errorf("mooring %s: %w", arrival.Name, err)43
}44
}44
id, err := result.LastInsertId()45
fmt.Fprintf(output, "one moored %s as boat %d\n", arrival.Name, moored.ID)45
if err != nil {46
return fmt.Errorf("reading %s's ID: %w", arrival.Name, err)47
}48
fmt.Fprintf(output, "execresult moored %s as boat %d\n", arrival.Name, id)49
}46
}50
47
48
result, err := queries.MoveBerth(ctx, store.MoveBerthParams{Berth: 4, ID: 2})49
if err != nil {50
return fmt.Errorf("moving boat 2: %w", err)51
}52
moved, err := result.RowsAffected()53
if err != nil {54
return fmt.Errorf("reading the rows moved: %w", err)55
}56
fmt.Fprintf(output, "execresult moved boat 2 to berth 4, %d row affected\n", moved)57
if _, err := result.LastInsertId(); err != nil {58
fmt.Fprintf(output, "execresult LastInsertId fails: %v\n", err)59
}60
51
boat, err := queries.GetBoat(ctx, 2)61
boat, err := queries.GetBoat(ctx, 2)52
if err != nil {62
if err != nil {53
return fmt.Errorf("getting boat 2: %w", err)63
return fmt.Errorf("getting boat 2: %w", err)54
}64
}55
fmt.Fprintf(output, "one boat 2 is %s, at berth %d\n", boat.Name, boat.Berth)65
fmt.Fprintf(output, "one boat 2 is %s, at berth %d\n", boat.Name, boat.Berth)56
66
57
if err := queries.ScrapBoat(ctx, 3); err != nil {67
if err := queries.ScrapBoat(ctx, 3); err != nil {58
return fmt.Errorf("scrapping boat 3: %w", err)68
return fmt.Errorf("scrapping boat 3: %w", err)59
}69
}60
fmt.Fprintln(output, "exec scrapped boat 3")70
fmt.Fprintln(output, "exec scrapped boat 3")61
71
62
_, err = queries.GetBoat(ctx, 3)72
_, err = queries.GetBoat(ctx, 3)63
if err == nil {73
if err == nil {64
return errors.New("getting scrapped boat 3: got a boat, want sql.ErrNoRows")74
return errors.New("getting scrapped boat 3: got a boat, want sql.ErrNoRows")65
}75
}66
if !errors.Is(err, sql.ErrNoRows) {76
if !errors.Is(err, sql.ErrNoRows) {67
return fmt.Errorf("getting scrapped boat 3: got %w, want sql.ErrNoRows", err)77
return fmt.Errorf("getting scrapped boat 3: got %w, want sql.ErrNoRows", err)68
}78
}69
fmt.Fprintln(output, "one boat 3 is gone, so GetBoat returns sql.ErrNoRows")79
fmt.Fprintln(output, "one boat 3 is gone, so GetBoat returns sql.ErrNoRows")70
80
71
// PutToSea only changes boats still in harbour, so a second run changes none. MySQL81
// PutToSea only changes boats still in harbour, so a second run changes none.72
// counts the rows a statement changed, not the rows its WHERE matched, unless the DSN73
// sets clientFoundRows=true.74
for range 2 {82
for range 2 {75
sailed, err := queries.PutToSea(ctx)83
sailed, err := queries.PutToSea(ctx)76
if err != nil {84
if err != nil {77
return fmt.Errorf("putting boats to sea: %w", err)85
return fmt.Errorf("putting boats to sea: %w", err)78
}86
}79
fmt.Fprintf(output, "execrows %d boats put to sea\n", sailed)87
fmt.Fprintf(output, "execrows %d boats put to sea\n", sailed)80
}88
}81
89
82
boats, err := queries.ListBoats(ctx)90
boats, err := queries.ListBoats(ctx)83
if err != nil {91
if err != nil {84
return fmt.Errorf("listing boats: %w", err)92
return fmt.Errorf("listing boats: %w", err)85
}93
}86
for _, boat := range boats {94
for _, boat := range boats {87
fmt.Fprintf(output, "many berth %d %-10s at sea: %t\n", boat.Berth, boat.Name, boat.AtSea)95
fmt.Fprintf(output, "many berth %d %-10s at sea: %t\n", boat.Berth, boat.Name, boat.AtSea)88
}96
}89
return nil97
return nil90
}98
}Go type differences
The same SQL types map to different Go types, because each database reports them differently.
| Query | Field | MySQL | CockroachDB |
|---|---|---|---|
| MoorBoat | parameter berth | int32 | int64 |
| GetBoat | column berth | int32 | int64 |
| ListBoats | column berth | int32 | int64 |