Choosing a command: one, many, exec, execrows, execresult: CockroachDB and TimescaleDB
CockroachDB and TimescaleDB run the same files for this example: the SQL and the Go are identical.
Configuration
sgl.yamlidentical
CockroachDB
TimescaleDB
1
version: 11
version: 12
2
3
databases:3
databases:4
- name: harbour4
- name: harbour5
dialect: postgres5
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/sql11
driver: database/sqlMigrations
db/migrations/0001_boats.up.sqlidentical
CockroachDB
TimescaleDB
1
CREATE TABLE boats (1
CREATE TABLE boats (2
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,2
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,3
name TEXT NOT NULL,3
name TEXT NOT NULL,4
berth BIGINT 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
CockroachDB
TimescaleDB
1
DROP TABLE boats;1
DROP TABLE boats;Queries
MoorBoatidentical
CockroachDB · db/queries/boats.sql
TimescaleDB · db/queries/boats.sql
1
-- sgl.query(MoorBoat, one)1
-- sgl.query(MoorBoat, one)2
INSERT INTO boats (name, berth) VALUES ($1, $2) RETURNING id;2
INSERT INTO boats (name, berth) VALUES ($1, $2) RETURNING id;MoveBerthidentical
CockroachDB · db/queries/boats.sql
TimescaleDB · db/queries/boats.sql
1
-- sgl.query(MoveBerth, execresult)1
-- sgl.query(MoveBerth, execresult)2
UPDATE boats SET berth = $1 WHERE id = $2;2
UPDATE boats SET berth = $1 WHERE id = $2;GetBoatidentical
CockroachDB · db/queries/boats.sql
TimescaleDB · 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 = $1;2
SELECT id, name, berth, at_sea FROM boats WHERE id = $1;ListBoatsidentical
CockroachDB · db/queries/boats.sql
TimescaleDB · 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;PutToSeaidentical
CockroachDB · db/queries/boats.sql
TimescaleDB · db/queries/boats.sql
1
-- sgl.query(PutToSea, execrows)1
-- sgl.query(PutToSea, execrows)2
UPDATE boats SET at_sea = true WHERE NOT at_sea;2
UPDATE boats SET at_sea = true WHERE NOT at_sea;ScrapBoatidentical
CockroachDB · db/queries/boats.sql
TimescaleDB · db/queries/boats.sql
1
-- sgl.query(ScrapBoat, exec)1
-- sgl.query(ScrapBoat, exec)2
DELETE FROM boats WHERE id = $1;2
DELETE FROM boats WHERE id = $1;What sgl generates
MoorBoatidentical
CockroachDB
TimescaleDB
1
func (queries *Queries) MoorBoat(ctx context.Context, params MoorBoatParams) (MoorBoatRow, 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
7
8
type MoorBoatRow struct {8
type MoorBoatRow struct {9
ID int64 `json:"id"`9
ID int64 `json:"id"`10
}10
}MoveBerthidentical
CockroachDB
TimescaleDB
1
func (queries *Queries) MoveBerth(ctx context.Context, params MoveBerthParams) (sql.Result, error)1
func (queries *Queries) MoveBerth(ctx context.Context, params MoveBerthParams) (sql.Result, error)2
2
3
type MoveBerthParams struct {3
type MoveBerthParams struct {4
Berth int644
Berth int645
ID int645
ID int646
}6
}GetBoatidentical
CockroachDB
TimescaleDB
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
CockroachDB
TimescaleDB
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
CockroachDB
TimescaleDB
1
func (queries *Queries) PutToSea(ctx context.Context) (int64, error)1
func (queries *Queries) PutToSea(ctx context.Context) (int64, error)ScrapBoatidentical
CockroachDB
TimescaleDB
1
func (queries *Queries) ScrapBoat(ctx context.Context, id int64) error1
func (queries *Queries) ScrapBoat(ctx context.Context, id int64) errorThe program
example.goidentical
CockroachDB
TimescaleDB
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/postgres/002_commands/internal/store"10
"seagull.sh/seagull/examples/postgres/002_commands/internal/store"11
)11
)12
12
13
// run moors three boats, moves one, looks one up, scraps one, puts the rest to sea and13
// run moors three boats, moves one, looks one up, scraps one, puts the rest to sea and14
// lists the harbour. Each query in db/queries/boats.sql names a command, and the command14
// lists the harbour. Each query in db/queries/boats.sql names a command, and the command15
// decides what its generated method returns:15
// decides what its generated method returns:16
//16
//17
// - one returns a single row, or sql.ErrNoRows when nothing matches; on an INSERT with17
// - one returns a single row, or sql.ErrNoRows when nothing matches; on an INSERT with18
// RETURNING it hands back the new row's ID;18
// RETURNING it hands back the new row's ID;19
// - execresult returns sql.Result. pgx's driver fills in RowsAffected, but its19
// - execresult returns sql.Result. pgx's driver fills in RowsAffected, but its20
// LastInsertId always fails, as PostgreSQL has no last insert ID: use RETURNING;20
// LastInsertId always fails, as PostgreSQL has no last insert ID: use RETURNING;21
// - many returns a slice of rows;21
// - many returns a slice of rows;22
// - execrows returns how many rows the statement changed;22
// - execrows returns how many rows the statement changed;23
// - exec returns only an error.23
// - exec returns only an error.24
//24
//25
// 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 without26
// RETURNING, makes sgl vet report Q009.26
// RETURNING, makes sgl vet report Q009.27
//27
//28
// Takes database (*sql.DB) which is a migrated database.28
// Takes database (*sql.DB) which is a migrated database.29
// Takes output (io.Writer) which receives what each call returned.29
// Takes output (io.Writer) which receives what each call returned.30
//30
//31
// Returns error when a query fails.31
// Returns error when a query fails.32
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 {33
queries := store.New(database)33
queries := store.New(database)34
34
35
arrivals := []store.MoorBoatParams{35
arrivals := []store.MoorBoatParams{36
{Name: "Kittiwake", Berth: 1},36
{Name: "Kittiwake", Berth: 1},37
{Name: "Salty Chip", Berth: 2},37
{Name: "Salty Chip", Berth: 2},38
{Name: "Gull Wing", Berth: 5},38
{Name: "Gull Wing", Berth: 5},39
}39
}40
for _, arrival := range arrivals {40
for _, arrival := range arrivals {41
moored, err := queries.MoorBoat(ctx, arrival)41
moored, err := queries.MoorBoat(ctx, arrival)42
if err != nil {42
if err != nil {43
return fmt.Errorf("mooring %s: %w", arrival.Name, err)43
return fmt.Errorf("mooring %s: %w", arrival.Name, err)44
}44
}45
fmt.Fprintf(output, "one moored %s as boat %d\n", arrival.Name, moored.ID)45
fmt.Fprintf(output, "one moored %s as boat %d\n", arrival.Name, moored.ID)46
}46
}47
47
48
result, err := queries.MoveBerth(ctx, store.MoveBerthParams{Berth: 4, ID: 2})48
result, err := queries.MoveBerth(ctx, store.MoveBerthParams{Berth: 4, ID: 2})49
if err != nil {49
if err != nil {50
return fmt.Errorf("moving boat 2: %w", err)50
return fmt.Errorf("moving boat 2: %w", err)51
}51
}52
moved, err := result.RowsAffected()52
moved, err := result.RowsAffected()53
if err != nil {53
if err != nil {54
return fmt.Errorf("reading the rows moved: %w", err)54
return fmt.Errorf("reading the rows moved: %w", err)55
}55
}56
fmt.Fprintf(output, "execresult moved boat 2 to berth 4, %d row affected\n", moved)56
fmt.Fprintf(output, "execresult moved boat 2 to berth 4, %d row affected\n", moved)57
if _, err := result.LastInsertId(); err != nil {57
if _, err := result.LastInsertId(); err != nil {58
fmt.Fprintf(output, "execresult LastInsertId fails: %v\n", err)58
fmt.Fprintf(output, "execresult LastInsertId fails: %v\n", err)59
}59
}60
60
61
boat, err := queries.GetBoat(ctx, 2)61
boat, err := queries.GetBoat(ctx, 2)62
if err != nil {62
if err != nil {63
return fmt.Errorf("getting boat 2: %w", err)63
return fmt.Errorf("getting boat 2: %w", err)64
}64
}65
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)66
66
67
if err := queries.ScrapBoat(ctx, 3); err != nil {67
if err := queries.ScrapBoat(ctx, 3); err != nil {68
return fmt.Errorf("scrapping boat 3: %w", err)68
return fmt.Errorf("scrapping boat 3: %w", err)69
}69
}70
fmt.Fprintln(output, "exec scrapped boat 3")70
fmt.Fprintln(output, "exec scrapped boat 3")71
71
72
_, err = queries.GetBoat(ctx, 3)72
_, err = queries.GetBoat(ctx, 3)73
if err == nil {73
if err == nil {74
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")75
}75
}76
if !errors.Is(err, sql.ErrNoRows) {76
if !errors.Is(err, sql.ErrNoRows) {77
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)78
}78
}79
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")80
80
81
// 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.82
for range 2 {82
for range 2 {83
sailed, err := queries.PutToSea(ctx)83
sailed, err := queries.PutToSea(ctx)84
if err != nil {84
if err != nil {85
return fmt.Errorf("putting boats to sea: %w", err)85
return fmt.Errorf("putting boats to sea: %w", err)86
}86
}87
fmt.Fprintf(output, "execrows %d boats put to sea\n", sailed)87
fmt.Fprintf(output, "execrows %d boats put to sea\n", sailed)88
}88
}89
89
90
boats, err := queries.ListBoats(ctx)90
boats, err := queries.ListBoats(ctx)91
if err != nil {91
if err != nil {92
return fmt.Errorf("listing boats: %w", err)92
return fmt.Errorf("listing boats: %w", err)93
}93
}94
for _, boat := range boats {94
for _, boat := range boats {95
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)96
}96
}97
return nil97
return nil98
}98
}