Compare · 002

Choosing a command: one, many, exec, execrows, execresult: SQLite and MySQL

Every file of the SQLite and MySQL versions side by side, with the lines that differ marked. 3 parameter or column types differ in Go.

Configuration

sgl.yamldiffers
SQLite
MySQL
1version: 1
1version: 1
2
2
3databases:
3databases:
4 - name: harbour
4 - name: harbour
5 dialect: sqlite
5 dialect: mysql
6 migrations: db/migrations
6 migrations: db/migrations
7 queries: db/queries
7 queries: db/queries
8 go:
8 go:
9 package: store
9 package: store
10 out: internal/store
10 out: internal/store

Migrations

db/migrations/0001_boats.up.sqldiffers
SQLite
MySQL
1CREATE TABLE boats (
1CREATE 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 0
5 at_sea BOOLEAN NOT NULL DEFAULT FALSE
6);
6);
db/migrations/0001_boats.down.sqlidentical
SQLite
MySQL
1DROP TABLE boats;
1DROP TABLE boats;

Queries

MoorBoatidentical
SQLite · db/queries/boats.sql
MySQL · db/queries/boats.sql
1-- sgl.query(MoorBoat, execresult)
1-- sgl.query(MoorBoat, execresult)
2INSERT INTO boats (name, berth) VALUES (?, ?);
2INSERT INTO boats (name, berth) VALUES (?, ?);
GetBoatidentical
SQLite · db/queries/boats.sql
MySQL · db/queries/boats.sql
1-- sgl.query(GetBoat, one)
1-- sgl.query(GetBoat, one)
2SELECT id, name, berth, at_sea FROM boats WHERE id = ?;
2SELECT id, name, berth, at_sea FROM boats WHERE id = ?;
ListBoatsidentical
SQLite · db/queries/boats.sql
MySQL · db/queries/boats.sql
1-- sgl.query(ListBoats, many)
1-- sgl.query(ListBoats, many)
2SELECT id, name, berth, at_sea FROM boats ORDER BY berth;
2SELECT id, name, berth, at_sea FROM boats ORDER BY berth;
PutToSeadiffers
SQLite · db/queries/boats.sql
MySQL · db/queries/boats.sql
1-- sgl.query(PutToSea, execrows)
1-- sgl.query(PutToSea, execrows)
2UPDATE boats SET at_sea = 1 WHERE at_sea = 0;
2UPDATE boats SET at_sea = TRUE WHERE at_sea = FALSE;
ScrapBoatidentical
SQLite · db/queries/boats.sql
MySQL · db/queries/boats.sql
1-- sgl.query(ScrapBoat, exec)
1-- sgl.query(ScrapBoat, exec)
2DELETE FROM boats WHERE id = ?;
2DELETE FROM boats WHERE id = ?;

What sgl generates

MoorBoatdiffers
SQLite
MySQL
1func (queries *Queries) MoorBoat(ctx context.Context, params MoorBoatParams) (sql.Result, error)
1func (queries *Queries) MoorBoat(ctx context.Context, params MoorBoatParams) (sql.Result, error)
2
2
3type MoorBoatParams struct {
3type MoorBoatParams struct {
4 Name string
4 Name string
5 Berth int64
5 Berth int32
6}
6}
GetBoatdiffers
SQLite
MySQL
1func (queries *Queries) GetBoat(ctx context.Context, id int64) (GetBoatRow, error)
1func (queries *Queries) GetBoat(ctx context.Context, id int64) (GetBoatRow, error)
2
2
3type GetBoatRow struct {
3type 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
MySQL
1func (queries *Queries) ListBoats(ctx context.Context) ([]ListBoatsRow, error)
1func (queries *Queries) ListBoats(ctx context.Context) ([]ListBoatsRow, error)
2
2
3type ListBoatsRow struct {
3type 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
MySQL
1func (queries *Queries) PutToSea(ctx context.Context) (int64, error)
1func (queries *Queries) PutToSea(ctx context.Context) (int64, error)
ScrapBoatidentical
SQLite
MySQL
1func (queries *Queries) ScrapBoat(ctx context.Context, id int64) error
1func (queries *Queries) ScrapBoat(ctx context.Context, id int64) error

The program

example.godiffers
SQLite
MySQL
1package main
1package main
2
2
3import (
3import (
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 the
13// run moors three boats, looks one up, scraps one, puts the rest to sea and lists the
14// harbour. Each query in db/queries/boats.sql names a command, and the command decides
14// harbour. Each query in db/queries/boats.sql names a command, and the command decides
15// 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 without
23// The command has to fit the statement: exec on a SELECT, or one on an INSERT, makes sgl
24// RETURNING, makes sgl vet report Q009.
24// vet report Q009. MySQL has no RETURNING to turn a write into a one query, and sgl vet
25// 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.
30func run(ctx context.Context, database *sql.DB, output io.Writer) error {
31func 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. MySQL
72 // counts the rows a statement changed, not the rows its WHERE matched, unless the DSN
73 // 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 nil
89 return nil
87}
90}

Go type differences

The same SQL types map to different Go types, because each database reports them differently.

QueryFieldSQLiteMySQL
MoorBoatparameter berthint64int32
GetBoatcolumn berthint64int32
ListBoatscolumn berthint64int32