Choosing a command: one, many, exec, execrows, execresult: SQLite and CockroachDB
Every file of the SQLite and CockroachDB versions side by side, with the lines that differ marked.
Configuration
sgl.yamldiffers
SQLite
CockroachDB
1
version: 11
version: 12
2
3
databases:3
databases:4
- name: harbour4
- name: harbour5
dialect: sqlite5
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
SQLite
CockroachDB
1
CREATE TABLE boats (1
CREATE TABLE boats (2
id INTEGER PRIMARY KEY,2
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,3
name TEXT NOT NULL,3
name TEXT NOT NULL,4
berth INTEGER NOT NULL,4
berth BIGINT 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
CockroachDB
1
DROP TABLE boats;1
DROP TABLE boats;Queries
MoorBoatdiffers
SQLite · 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
SQLite · 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
SQLite · 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
SQLite · 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 = 1 WHERE at_sea = 0;2
UPDATE boats SET at_sea = true WHERE NOT at_sea;ScrapBoatdiffers
SQLite · 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
SQLite · 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
SQLite
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 int645
Berth int646
}6
}7
8
type MoorBoatRow struct {9
ID int64 `json:"id"`10
}GetBoatidentical
SQLite
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 int64 `json:"berth"`6
Berth int64 `json:"berth"`7
AtSea bool `json:"at_sea"`7
AtSea bool `json:"at_sea"`8
}8
}ListBoatsidentical
SQLite
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 int64 `json:"berth"`6
Berth int64 `json:"berth"`7
AtSea bool `json:"at_sea"`7
AtSea bool `json:"at_sea"`8
}8
}PutToSeaidentical
SQLite
CockroachDB
1
func (queries *Queries) PutToSea(ctx context.Context) (int64, error)1
func (queries *Queries) PutToSea(ctx context.Context) (int64, error)ScrapBoatidentical
SQLite
CockroachDB
1
func (queries *Queries) ScrapBoat(ctx context.Context, id int64) error1
func (queries *Queries) ScrapBoat(ctx context.Context, id int64) errorMoveBerthonly CockroachDB
SQLite · 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
SQLite
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/sqlite/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 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 without25
// The command has to fit the statement: exec on a SELECT, or one on an INSERT without24
// RETURNING, makes sgl vet report Q009.26
// RETURNING, makes sgl vet report Q009.25
//27
//26
// Takes database (*sql.DB) which is a migrated database.28
// Takes database (*sql.DB) which is a migrated database.27
// Takes output (io.Writer) which receives what each call returned.29
// Takes output (io.Writer) which receives what each call returned.28
//30
//29
// Returns error when a query fails.31
// Returns error when a query fails.30
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 {31
queries := store.New(database)33
queries := store.New(database)32
34
33
arrivals := []store.MoorBoatParams{35
arrivals := []store.MoorBoatParams{34
{Name: "Kittiwake", Berth: 1},36
{Name: "Kittiwake", Berth: 1},35
{Name: "Salty Chip", Berth: 2},37
{Name: "Salty Chip", Berth: 2},36
{Name: "Gull Wing", Berth: 5},38
{Name: "Gull Wing", Berth: 5},37
}39
}38
for _, arrival := range arrivals {40
for _, arrival := range arrivals {39
result, err := queries.MoorBoat(ctx, arrival)41
moored, err := queries.MoorBoat(ctx, arrival)40
if err != nil {42
if err != nil {41
return fmt.Errorf("mooring %s: %w", arrival.Name, err)43
return fmt.Errorf("mooring %s: %w", arrival.Name, err)42
}44
}43
id, err := result.LastInsertId()45
fmt.Fprintf(output, "one moored %s as boat %d\n", arrival.Name, moored.ID)44
if err != nil {45
return fmt.Errorf("reading %s's ID: %w", arrival.Name, err)46
}47
fmt.Fprintf(output, "execresult moored %s as boat %d\n", arrival.Name, id)48
}46
}49
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
50
boat, err := queries.GetBoat(ctx, 2)61
boat, err := queries.GetBoat(ctx, 2)51
if err != nil {62
if err != nil {52
return fmt.Errorf("getting boat 2: %w", err)63
return fmt.Errorf("getting boat 2: %w", err)53
}64
}54
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)55
66
56
if err := queries.ScrapBoat(ctx, 3); err != nil {67
if err := queries.ScrapBoat(ctx, 3); err != nil {57
return fmt.Errorf("scrapping boat 3: %w", err)68
return fmt.Errorf("scrapping boat 3: %w", err)58
}69
}59
fmt.Fprintln(output, "exec scrapped boat 3")70
fmt.Fprintln(output, "exec scrapped boat 3")60
71
61
_, err = queries.GetBoat(ctx, 3)72
_, err = queries.GetBoat(ctx, 3)62
if err == nil {73
if err == nil {63
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")64
}75
}65
if !errors.Is(err, sql.ErrNoRows) {76
if !errors.Is(err, sql.ErrNoRows) {66
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)67
}78
}68
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")69
80
70
// PutToSea only changes boats still in harbour, so a second run changes none.81
// PutToSea only changes boats still in harbour, so a second run changes none.71
for range 2 {82
for range 2 {72
sailed, err := queries.PutToSea(ctx)83
sailed, err := queries.PutToSea(ctx)73
if err != nil {84
if err != nil {74
return fmt.Errorf("putting boats to sea: %w", err)85
return fmt.Errorf("putting boats to sea: %w", err)75
}86
}76
fmt.Fprintf(output, "execrows %d boats put to sea\n", sailed)87
fmt.Fprintf(output, "execrows %d boats put to sea\n", sailed)77
}88
}78
89
79
boats, err := queries.ListBoats(ctx)90
boats, err := queries.ListBoats(ctx)80
if err != nil {91
if err != nil {81
return fmt.Errorf("listing boats: %w", err)92
return fmt.Errorf("listing boats: %w", err)82
}93
}83
for _, boat := range boats {94
for _, boat := range boats {84
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)85
}96
}86
return nil97
return nil87
}98
}