Choosing a command: one, many, exec, execrows, execresult: MySQL and SQLite
Every file of the MySQL and SQLite versions side by side, with the lines that differ marked. 3 parameter or column types differ in Go.
Configuration
sgl.yamldiffers
MySQL
SQLite
1
version: 11
version: 12
2
3
databases:3
databases:4
- name: harbour4
- name: harbour5
dialect: mysql5
dialect: sqlite6
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/storeMigrations
db/migrations/0001_boats.up.sqldiffers
MySQL
SQLite
1
CREATE TABLE boats (1
CREATE TABLE boats (2
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,2
id INTEGER PRIMARY KEY,3
name VARCHAR(100) NOT NULL,3
name TEXT NOT NULL,4
berth INT NOT NULL,4
berth INTEGER NOT NULL,5
at_sea BOOLEAN NOT NULL DEFAULT FALSE5
at_sea BOOLEAN NOT NULL DEFAULT 06
);6
);db/migrations/0001_boats.down.sqlidentical
MySQL
SQLite
1
DROP TABLE boats;1
DROP TABLE boats;Queries
MoorBoatidentical
MySQL · db/queries/boats.sql
SQLite · db/queries/boats.sql
1
-- sgl.query(MoorBoat, execresult)1
-- sgl.query(MoorBoat, execresult)2
INSERT INTO boats (name, berth) VALUES (?, ?);2
INSERT INTO boats (name, berth) VALUES (?, ?);GetBoatidentical
MySQL · db/queries/boats.sql
SQLite · 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 = ?;ListBoatsidentical
MySQL · db/queries/boats.sql
SQLite · 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
SQLite · 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 = 1 WHERE at_sea = 0;ScrapBoatidentical
MySQL · db/queries/boats.sql
SQLite · 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 = ?;What sgl generates
MoorBoatdiffers
MySQL
SQLite
1
func (queries *Queries) MoorBoat(ctx context.Context, params MoorBoatParams) (sql.Result, error)1
func (queries *Queries) MoorBoat(ctx context.Context, params MoorBoatParams) (sql.Result, error)2
2
3
type MoorBoatParams struct {3
type MoorBoatParams struct {4
Name string4
Name string5
Berth int325
Berth int646
}6
}GetBoatdiffers
MySQL
SQLite
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
SQLite
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
SQLite
1
func (queries *Queries) PutToSea(ctx context.Context) (int64, error)1
func (queries *Queries) PutToSea(ctx context.Context) (int64, error)ScrapBoatidentical
MySQL
SQLite
1
func (queries *Queries) ScrapBoat(ctx context.Context, id int64) error1
func (queries *Queries) ScrapBoat(ctx context.Context, id int64) errorThe program
example.godiffers
MySQL
SQLite
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/sqlite/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, looks one up, scraps one, puts the rest to sea and lists the14
// harbour. Each query in db/queries/boats.sql names a command, and the command decides14
// harbour. Each query in db/queries/boats.sql names a command, and the command decides15
// what its generated method returns:15
// what its generated method returns:16
//16
//17
// - execresult returns sql.Result, which carries the new row's AUTO_INCREMENT id;17
// - execresult returns sql.Result, which carries the new row's ID;18
// - one returns a single row, or sql.ErrNoRows when nothing matches;18
// - one returns a single row, or sql.ErrNoRows when nothing matches;19
// - many returns a slice of rows;19
// - many returns a slice of rows;20
// - execrows returns how many rows the statement changed;20
// - execrows returns how many rows the statement changed;21
// - exec returns only an error.21
// - exec returns only an error.22
//22
//23
// The command has to fit the statement: exec on a SELECT, or one on an INSERT, makes sgl23
// 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 vet24
// RETURNING, makes sgl vet report Q009.25
// refuses one with Q052, so execresult is how an INSERT hands back its id.26
//25
//27
// Takes database (*sql.DB) which is a migrated database.26
// Takes database (*sql.DB) which is a migrated database.28
// Takes output (io.Writer) which receives what each call returned.27
// Takes output (io.Writer) which receives what each call returned.29
//28
//30
// Returns error when a query fails.29
// Returns error when a query fails.31
func run(ctx context.Context, database *sql.DB, output io.Writer) error {30
func run(ctx context.Context, database *sql.DB, output io.Writer) error {32
queries := store.New(database)31
queries := store.New(database)33
32
34
arrivals := []store.MoorBoatParams{33
arrivals := []store.MoorBoatParams{35
{Name: "Kittiwake", Berth: 1},34
{Name: "Kittiwake", Berth: 1},36
{Name: "Salty Chip", Berth: 2},35
{Name: "Salty Chip", Berth: 2},37
{Name: "Gull Wing", Berth: 5},36
{Name: "Gull Wing", Berth: 5},38
}37
}39
for _, arrival := range arrivals {38
for _, arrival := range arrivals {40
result, err := queries.MoorBoat(ctx, arrival)39
result, err := queries.MoorBoat(ctx, arrival)41
if err != nil {40
if err != nil {42
return fmt.Errorf("mooring %s: %w", arrival.Name, err)41
return fmt.Errorf("mooring %s: %w", arrival.Name, err)43
}42
}44
id, err := result.LastInsertId()43
id, err := result.LastInsertId()45
if err != nil {44
if err != nil {46
return fmt.Errorf("reading %s's ID: %w", arrival.Name, err)45
return fmt.Errorf("reading %s's ID: %w", arrival.Name, err)47
}46
}48
fmt.Fprintf(output, "execresult moored %s as boat %d\n", arrival.Name, id)47
fmt.Fprintf(output, "execresult moored %s as boat %d\n", arrival.Name, id)49
}48
}50
49
51
boat, err := queries.GetBoat(ctx, 2)50
boat, err := queries.GetBoat(ctx, 2)52
if err != nil {51
if err != nil {53
return fmt.Errorf("getting boat 2: %w", err)52
return fmt.Errorf("getting boat 2: %w", err)54
}53
}55
fmt.Fprintf(output, "one boat 2 is %s, at berth %d\n", boat.Name, boat.Berth)54
fmt.Fprintf(output, "one boat 2 is %s, at berth %d\n", boat.Name, boat.Berth)56
55
57
if err := queries.ScrapBoat(ctx, 3); err != nil {56
if err := queries.ScrapBoat(ctx, 3); err != nil {58
return fmt.Errorf("scrapping boat 3: %w", err)57
return fmt.Errorf("scrapping boat 3: %w", err)59
}58
}60
fmt.Fprintln(output, "exec scrapped boat 3")59
fmt.Fprintln(output, "exec scrapped boat 3")61
60
62
_, err = queries.GetBoat(ctx, 3)61
_, err = queries.GetBoat(ctx, 3)63
if err == nil {62
if err == nil {64
return errors.New("getting scrapped boat 3: got a boat, want sql.ErrNoRows")63
return errors.New("getting scrapped boat 3: got a boat, want sql.ErrNoRows")65
}64
}66
if !errors.Is(err, sql.ErrNoRows) {65
if !errors.Is(err, sql.ErrNoRows) {67
return fmt.Errorf("getting scrapped boat 3: got %w, want sql.ErrNoRows", err)66
return fmt.Errorf("getting scrapped boat 3: got %w, want sql.ErrNoRows", err)68
}67
}69
fmt.Fprintln(output, "one boat 3 is gone, so GetBoat returns sql.ErrNoRows")68
fmt.Fprintln(output, "one boat 3 is gone, so GetBoat returns sql.ErrNoRows")70
69
71
// PutToSea only changes boats still in harbour, so a second run changes none. MySQL70
// 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 {71
for range 2 {75
sailed, err := queries.PutToSea(ctx)72
sailed, err := queries.PutToSea(ctx)76
if err != nil {73
if err != nil {77
return fmt.Errorf("putting boats to sea: %w", err)74
return fmt.Errorf("putting boats to sea: %w", err)78
}75
}79
fmt.Fprintf(output, "execrows %d boats put to sea\n", sailed)76
fmt.Fprintf(output, "execrows %d boats put to sea\n", sailed)80
}77
}81
78
82
boats, err := queries.ListBoats(ctx)79
boats, err := queries.ListBoats(ctx)83
if err != nil {80
if err != nil {84
return fmt.Errorf("listing boats: %w", err)81
return fmt.Errorf("listing boats: %w", err)85
}82
}86
for _, boat := range boats {83
for _, boat := range boats {87
fmt.Fprintf(output, "many berth %d %-10s at sea: %t\n", boat.Berth, boat.Name, boat.AtSea)84
fmt.Fprintf(output, "many berth %d %-10s at sea: %t\n", boat.Berth, boat.Name, boat.AtSea)88
}85
}89
return nil86
return nil90
}87
}Go type differences
The same SQL types map to different Go types, because each database reports them differently.
| Query | Field | MySQL | SQLite |
|---|---|---|---|
| MoorBoat | parameter berth | int32 | int64 |
| GetBoat | column berth | int32 | int64 |
| ListBoats | column berth | int32 | int64 |