Choosing a command: one, many, exec, execrows, execresult: SQLite and MariaDB
Every file of the SQLite and MariaDB versions side by side, with the lines that differ marked. 3 parameter or column types differ in Go.
Configuration
sgl.yamldiffers
SQLite
MariaDB
1
version: 11
version: 12
2
3
databases:3
databases:4
- name: harbour4
- name: harbour5
dialect: sqlite5
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/storeMigrations
db/migrations/0001_boats.up.sqldiffers
SQLite
MariaDB
1
CREATE TABLE boats (1
CREATE TABLE boats (2
id INTEGER PRIMARY KEY,2
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,3
name TEXT NOT NULL,3
name VARCHAR(100) NOT NULL,4
berth INTEGER NOT NULL,4
berth INT NOT NULL,5
at_sea BOOLEAN NOT NULL DEFAULT 05
at_sea BOOLEAN NOT NULL DEFAULT FALSE6
);6
);db/migrations/0001_boats.down.sqlidentical
SQLite
MariaDB
1
DROP TABLE boats;1
DROP TABLE boats;Queries
MoorBoatidentical
SQLite · db/queries/boats.sql
MariaDB · 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
SQLite · db/queries/boats.sql
MariaDB · 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
SQLite · db/queries/boats.sql
MariaDB · 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
SQLite · db/queries/boats.sql
MariaDB · db/queries/boats.sql
1
-- sgl.query(PutToSea, execrows)1
-- sgl.query(PutToSea, execrows)2
UPDATE boats SET at_sea = 1 WHERE at_sea = 0;2
UPDATE boats SET at_sea = TRUE WHERE at_sea = FALSE;ScrapBoatidentical
SQLite · db/queries/boats.sql
MariaDB · 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
SQLite
MariaDB
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 int645
Berth int326
}6
}GetBoatdiffers
SQLite
MariaDB
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 int64 `json:"berth"`6
Berth int32 `json:"berth"`7
AtSea bool `json:"at_sea"`7
AtSea bool `json:"at_sea"`8
}8
}ListBoatsdiffers
SQLite
MariaDB
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 int64 `json:"berth"`6
Berth int32 `json:"berth"`7
AtSea bool `json:"at_sea"`7
AtSea bool `json:"at_sea"`8
}8
}PutToSeaidentical
SQLite
MariaDB
1
func (queries *Queries) PutToSea(ctx context.Context) (int64, error)1
func (queries *Queries) PutToSea(ctx context.Context) (int64, error)ScrapBoatidentical
SQLite
MariaDB
1
func (queries *Queries) ScrapBoat(ctx context.Context, id int64) error1
func (queries *Queries) ScrapBoat(ctx context.Context, id int64) errorThe program
example.godiffers
SQLite
MariaDB
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/sqlite/002_commands/internal/store"10
"seagull.sh/seagull/examples/mysql/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 ID;17
// - execresult returns sql.Result, which carries the new row's AUTO_INCREMENT 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 without23
// The command has to fit the statement: exec on a SELECT, or one on an INSERT, makes sgl24
// RETURNING, makes sgl vet report Q009.24
// vet report Q009. MySQL has no RETURNING to turn a write into a one query, and sgl vet25
// refuses one with Q052, so execresult is how an INSERT hands back its id.25
//26
//26
// Takes database (*sql.DB) which is a migrated database.27
// Takes database (*sql.DB) which is a migrated database.27
// Takes output (io.Writer) which receives what each call returned.28
// Takes output (io.Writer) which receives what each call returned.28
//29
//29
// Returns error when a query fails.30
// Returns error when a query fails.30
func run(ctx context.Context, database *sql.DB, output io.Writer) error {31
func run(ctx context.Context, database *sql.DB, output io.Writer) error {31
queries := store.New(database)32
queries := store.New(database)32
33
33
arrivals := []store.MoorBoatParams{34
arrivals := []store.MoorBoatParams{34
{Name: "Kittiwake", Berth: 1},35
{Name: "Kittiwake", Berth: 1},35
{Name: "Salty Chip", Berth: 2},36
{Name: "Salty Chip", Berth: 2},36
{Name: "Gull Wing", Berth: 5},37
{Name: "Gull Wing", Berth: 5},37
}38
}38
for _, arrival := range arrivals {39
for _, arrival := range arrivals {39
result, err := queries.MoorBoat(ctx, arrival)40
result, err := queries.MoorBoat(ctx, arrival)40
if err != nil {41
if err != nil {41
return fmt.Errorf("mooring %s: %w", arrival.Name, err)42
return fmt.Errorf("mooring %s: %w", arrival.Name, err)42
}43
}43
id, err := result.LastInsertId()44
id, err := result.LastInsertId()44
if err != nil {45
if err != nil {45
return fmt.Errorf("reading %s's ID: %w", arrival.Name, err)46
return fmt.Errorf("reading %s's ID: %w", arrival.Name, err)46
}47
}47
fmt.Fprintf(output, "execresult moored %s as boat %d\n", arrival.Name, id)48
fmt.Fprintf(output, "execresult moored %s as boat %d\n", arrival.Name, id)48
}49
}49
50
50
boat, err := queries.GetBoat(ctx, 2)51
boat, err := queries.GetBoat(ctx, 2)51
if err != nil {52
if err != nil {52
return fmt.Errorf("getting boat 2: %w", err)53
return fmt.Errorf("getting boat 2: %w", err)53
}54
}54
fmt.Fprintf(output, "one boat 2 is %s, at berth %d\n", boat.Name, boat.Berth)55
fmt.Fprintf(output, "one boat 2 is %s, at berth %d\n", boat.Name, boat.Berth)55
56
56
if err := queries.ScrapBoat(ctx, 3); err != nil {57
if err := queries.ScrapBoat(ctx, 3); err != nil {57
return fmt.Errorf("scrapping boat 3: %w", err)58
return fmt.Errorf("scrapping boat 3: %w", err)58
}59
}59
fmt.Fprintln(output, "exec scrapped boat 3")60
fmt.Fprintln(output, "exec scrapped boat 3")60
61
61
_, err = queries.GetBoat(ctx, 3)62
_, err = queries.GetBoat(ctx, 3)62
if err == nil {63
if err == nil {63
return errors.New("getting scrapped boat 3: got a boat, want sql.ErrNoRows")64
return errors.New("getting scrapped boat 3: got a boat, want sql.ErrNoRows")64
}65
}65
if !errors.Is(err, sql.ErrNoRows) {66
if !errors.Is(err, sql.ErrNoRows) {66
return fmt.Errorf("getting scrapped boat 3: got %w, want sql.ErrNoRows", err)67
return fmt.Errorf("getting scrapped boat 3: got %w, want sql.ErrNoRows", err)67
}68
}68
fmt.Fprintln(output, "one boat 3 is gone, so GetBoat returns sql.ErrNoRows")69
fmt.Fprintln(output, "one boat 3 is gone, so GetBoat returns sql.ErrNoRows")69
70
70
// PutToSea only changes boats still in harbour, so a second run changes none.71
// PutToSea only changes boats still in harbour, so a second run changes none. MySQL72
// counts the rows a statement changed, not the rows its WHERE matched, unless the DSN73
// sets clientFoundRows=true.71
for range 2 {74
for range 2 {72
sailed, err := queries.PutToSea(ctx)75
sailed, err := queries.PutToSea(ctx)73
if err != nil {76
if err != nil {74
return fmt.Errorf("putting boats to sea: %w", err)77
return fmt.Errorf("putting boats to sea: %w", err)75
}78
}76
fmt.Fprintf(output, "execrows %d boats put to sea\n", sailed)79
fmt.Fprintf(output, "execrows %d boats put to sea\n", sailed)77
}80
}78
81
79
boats, err := queries.ListBoats(ctx)82
boats, err := queries.ListBoats(ctx)80
if err != nil {83
if err != nil {81
return fmt.Errorf("listing boats: %w", err)84
return fmt.Errorf("listing boats: %w", err)82
}85
}83
for _, boat := range boats {86
for _, boat := range boats {84
fmt.Fprintf(output, "many berth %d %-10s at sea: %t\n", boat.Berth, boat.Name, boat.AtSea)87
fmt.Fprintf(output, "many berth %d %-10s at sea: %t\n", boat.Berth, boat.Name, boat.AtSea)85
}88
}86
return nil89
return nil87
}90
}Go type differences
The same SQL types map to different Go types, because each database reports them differently.
| Query | Field | SQLite | MariaDB |
|---|---|---|---|
| MoorBoat | parameter berth | int64 | int32 |
| GetBoat | column berth | int64 | int32 |
| ListBoats | column berth | int64 | int32 |