Placeholders, inferred names and Params structs: MariaDB and CockroachDB
Every file of the MariaDB and CockroachDB versions side by side, with the lines that differ marked. 4 parameter or column types differ in Go.
Configuration
sgl.yamldiffers
MariaDB
CockroachDB
1
version: 11
version: 12
2
3
databases:3
databases:4
- name: ferries4
- name: ferries5
dialect: mysql5
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_sailings.up.sqldiffers
MariaDB
CockroachDB
1
CREATE TABLE sailings (1
CREATE TABLE sailings (2
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,2
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,3
origin VARCHAR(100) NOT NULL,3
origin TEXT NOT NULL,4
destination VARCHAR(100) NOT NULL,4
destination TEXT NOT NULL,5
departs CHAR(5) NOT NULL,5
departs TEXT NOT NULL,6
seats_free INT NOT NULL6
seats_free BIGINT NOT NULL7
);7
);db/migrations/0001_sailings.down.sqlidentical
MariaDB
CockroachDB
1
DROP TABLE sailings;1
DROP TABLE sailings;Queries
AddSailingdiffers
MariaDB · db/queries/sailings.sql
CockroachDB · db/queries/sailings.sql
1
-- sgl.query(AddSailing, exec)1
-- sgl.query(AddSailing, exec)2
INSERT INTO sailings (origin, destination, departs, seats_free) VALUES (?, ?, ?, ?);2
INSERT INTO sailings (origin, destination, departs, seats_free) VALUES ($1, $2, $3, $4);FindSailingsdiffers
MariaDB · db/queries/sailings.sql
CockroachDB · db/queries/sailings.sql
1
-- sgl.query(FindSailings, many)1
-- sgl.query(FindSailings, many)2
SELECT id, origin, destination, departs, seats_free2
SELECT id, origin, destination, departs, seats_free3
FROM sailings3
FROM sailings4
WHERE origin = ? AND destination = ?4
WHERE origin = $1 AND destination = $25
ORDER BY departs;5
ORDER BY departs;SailingsDepartingdiffers
MariaDB · db/queries/sailings.sql
CockroachDB · db/queries/sailings.sql
1
-- sgl.query(SailingsDeparting, many)1
-- sgl.query(SailingsDeparting, many)2
-- ?1 as sgl.param(earliest)2
-- $1 as sgl.param(earliest)3
-- ?2 as sgl.param(latest)3
-- $2 as sgl.param(latest)4
SELECT id, origin, destination, departs, seats_free4
SELECT id, origin, destination, departs, seats_free5
FROM sailings5
FROM sailings6
WHERE departs >= ?1 AND departs < ?26
WHERE departs >= $1 AND departs < $27
ORDER BY departs;7
ORDER BY departs;BookSeatsdiffers
MariaDB · db/queries/sailings.sql
CockroachDB · db/queries/sailings.sql
1
-- sgl.query(BookSeats, execrows)1
-- sgl.query(BookSeats, execrows)2
-- ?1 as sgl.param(seats)3
-- ?2 as sgl.param(sailing_id)4
UPDATE sailings2
UPDATE sailings5
SET seats_free = seats_free - ?13
SET seats_free = seats_free - :seats6
WHERE id = ?2 AND seats_free >= ?1;4
WHERE id = :sailing_id AND seats_free >= :seats;What sgl generates
AddSailingdiffers
MariaDB
CockroachDB
1
func (queries *Queries) AddSailing(ctx context.Context, params AddSailingParams) error1
func (queries *Queries) AddSailing(ctx context.Context, params AddSailingParams) error2
2
3
type AddSailingParams struct {3
type AddSailingParams struct {4
Origin string4
Origin string5
Destination string5
Destination string6
Departs string6
Departs string7
SeatsFree int327
SeatsFree int648
}8
}FindSailingsdiffers
MariaDB
CockroachDB
1
func (queries *Queries) FindSailings(ctx context.Context, params FindSailingsParams) ([]FindSailingsRow, error)1
func (queries *Queries) FindSailings(ctx context.Context, params FindSailingsParams) ([]FindSailingsRow, error)2
2
3
type FindSailingsParams struct {3
type FindSailingsParams struct {4
Origin string4
Origin string5
Destination string5
Destination string6
}6
}7
7
8
type FindSailingsRow struct {8
type FindSailingsRow struct {9
ID int64 `json:"id"`9
ID int64 `json:"id"`10
Origin string `json:"origin"`10
Origin string `json:"origin"`11
Destination string `json:"destination"`11
Destination string `json:"destination"`12
Departs string `json:"departs"`12
Departs string `json:"departs"`13
SeatsFree int32 `json:"seats_free"`13
SeatsFree int64 `json:"seats_free"`14
}14
}SailingsDepartingdiffers
MariaDB
CockroachDB
1
func (queries *Queries) SailingsDeparting(ctx context.Context, params SailingsDepartingParams) ([]SailingsDepartingRow, error)1
func (queries *Queries) SailingsDeparting(ctx context.Context, params SailingsDepartingParams) ([]SailingsDepartingRow, error)2
2
3
type SailingsDepartingParams struct {3
type SailingsDepartingParams struct {4
Earliest string4
Earliest string5
Latest string5
Latest string6
}6
}7
7
8
type SailingsDepartingRow struct {8
type SailingsDepartingRow struct {9
ID int64 `json:"id"`9
ID int64 `json:"id"`10
Origin string `json:"origin"`10
Origin string `json:"origin"`11
Destination string `json:"destination"`11
Destination string `json:"destination"`12
Departs string `json:"departs"`12
Departs string `json:"departs"`13
SeatsFree int32 `json:"seats_free"`13
SeatsFree int64 `json:"seats_free"`14
}14
}BookSeatsdiffers
MariaDB
CockroachDB
1
func (queries *Queries) BookSeats(ctx context.Context, params BookSeatsParams) (int64, error)1
func (queries *Queries) BookSeats(ctx context.Context, params BookSeatsParams) (int64, error)2
2
3
type BookSeatsParams struct {3
type BookSeatsParams struct {4
Seats int324
Seats int645
SailingID int645
SailingID int646
}6
}The program
example.godiffers
MariaDB
CockroachDB
1
package main1
package main2
2
3
import (3
import (4
"context"4
"context"5
"database/sql"5
"database/sql"6
"fmt"6
"fmt"7
"io"7
"io"8
8
9
"seagull.sh/seagull/examples/mysql/003_parameters/internal/store"9
"seagull.sh/seagull/examples/postgres/003_parameters/internal/store"10
)10
)11
11
12
// run fills a ferry timetable, searches it two ways and books seats. Every query has two12
// run fills a ferry timetable, searches it two ways and books seats. Every query has two13
// or more parameters, so each method takes a Params struct, and the struct's field names13
// or more parameters, so each method takes a Params struct, and the struct's field names14
// come from the SQL:14
// come from the SQL:15
//15
//16
// - AddSailing and FindSailings name each ? after the column it is inserted into or16
// - AddSailing and FindSailings name each $N after the column it is inserted into or17
// compared with: Origin, Destination, Departs, SeatsFree;17
// compared with: Origin, Destination, Departs, SeatsFree;18
// - SailingsDeparting compares departs twice, which would give Departs and Departs2, so18
// - SailingsDeparting compares departs twice, which would give Departs and Departs2, so19
// its binding lines (?1 as sgl.param(earliest)) choose better names;19
// its binding lines ($1 as sgl.param(earliest)) choose better names;20
// - BookSeats numbers its placeholders: ?1 appears twice but is one Seats field.20
// - BookSeats uses named placeholders; :seats appears twice but is one field. The21
//21
// generated SQL numbers them, so :seats becomes $1 in both places, as PostgreSQL22
// MySQL itself takes only ?, so the generated SQL has a plain ? for each use, and the22
// binds a reused number once.23
// method passes Seats once for each.24
//23
//25
// Takes database (*sql.DB) which is a migrated database.24
// Takes database (*sql.DB) which is a migrated database.26
// Takes output (io.Writer) which receives the search results and bookings.25
// Takes output (io.Writer) which receives the search results and bookings.27
//26
//28
// Returns error when a query fails.27
// Returns error when a query fails.29
func run(ctx context.Context, database *sql.DB, output io.Writer) error {28
func run(ctx context.Context, database *sql.DB, output io.Writer) error {30
queries := store.New(database)29
queries := store.New(database)31
30
32
timetable := []store.AddSailingParams{31
timetable := []store.AddSailingParams{33
{Origin: "Penzance", Destination: "St Mary's", Departs: "09:15", SeatsFree: 40},32
{Origin: "Penzance", Destination: "St Mary's", Departs: "09:15", SeatsFree: 40},34
{Origin: "St Mary's", Destination: "Tresco", Departs: "11:00", SeatsFree: 12},33
{Origin: "St Mary's", Destination: "Tresco", Departs: "11:00", SeatsFree: 12},35
{Origin: "Penzance", Destination: "St Mary's", Departs: "14:30", SeatsFree: 4},34
{Origin: "Penzance", Destination: "St Mary's", Departs: "14:30", SeatsFree: 4},36
{Origin: "St Mary's", Destination: "Penzance", Departs: "16:30", SeatsFree: 60},35
{Origin: "St Mary's", Destination: "Penzance", Departs: "16:30", SeatsFree: 60},37
}36
}38
for _, sailing := range timetable {37
for _, sailing := range timetable {39
if err := queries.AddSailing(ctx, sailing); err != nil {38
if err := queries.AddSailing(ctx, sailing); err != nil {40
return fmt.Errorf("adding the %s sailing: %w", sailing.Departs, err)39
return fmt.Errorf("adding the %s sailing: %w", sailing.Departs, err)41
}40
}42
}41
}43
42
44
route, err := queries.FindSailings(ctx, store.FindSailingsParams{43
route, err := queries.FindSailings(ctx, store.FindSailingsParams{45
Origin: "Penzance",44
Origin: "Penzance",46
Destination: "St Mary's",45
Destination: "St Mary's",47
})46
})48
if err != nil {47
if err != nil {49
return fmt.Errorf("finding sailings: %w", err)48
return fmt.Errorf("finding sailings: %w", err)50
}49
}51
fmt.Fprintln(output, "Penzance to St Mary's:")50
fmt.Fprintln(output, "Penzance to St Mary's:")52
for _, sailing := range route {51
for _, sailing := range route {53
fmt.Fprintf(output, " sailing %d at %s, %d seats free\n", sailing.ID, sailing.Departs, sailing.SeatsFree)52
fmt.Fprintf(output, " sailing %d at %s, %d seats free\n", sailing.ID, sailing.Departs, sailing.SeatsFree)54
}53
}55
54
56
midday, err := queries.SailingsDeparting(ctx, store.SailingsDepartingParams{55
midday, err := queries.SailingsDeparting(ctx, store.SailingsDepartingParams{57
Earliest: "10:00",56
Earliest: "10:00",58
Latest: "15:00",57
Latest: "15:00",59
})58
})60
if err != nil {59
if err != nil {61
return fmt.Errorf("finding midday sailings: %w", err)60
return fmt.Errorf("finding midday sailings: %w", err)62
}61
}63
fmt.Fprintln(output, "Departing between 10:00 and 15:00:")62
fmt.Fprintln(output, "Departing between 10:00 and 15:00:")64
for _, sailing := range midday {63
for _, sailing := range midday {65
fmt.Fprintf(output, " sailing %d at %s, %s to %s\n", sailing.ID, sailing.Departs, sailing.Origin, sailing.Destination)64
fmt.Fprintf(output, " sailing %d at %s, %s to %s\n", sailing.ID, sailing.Departs, sailing.Origin, sailing.Destination)66
}65
}67
66
68
// The same booking twice: the first takes 3 of the 4 free seats, so the second changes67
// The same booking twice: the first takes 3 of the 4 free seats, so the second changes69
// no row because seats_free >= ?1 is now false.68
// no row because seats_free >= :seats is now false.70
booking := store.BookSeatsParams{Seats: 3, SailingID: 3}69
booking := store.BookSeatsParams{Seats: 3, SailingID: 3}71
for range 2 {70
for range 2 {72
booked, err := queries.BookSeats(ctx, booking)71
booked, err := queries.BookSeats(ctx, booking)73
if err != nil {72
if err != nil {74
return fmt.Errorf("booking sailing %d: %w", booking.SailingID, err)73
return fmt.Errorf("booking sailing %d: %w", booking.SailingID, err)75
}74
}76
if booked == 0 {75
if booked == 0 {77
fmt.Fprintf(output, "sailing %d has fewer than %d seats free\n", booking.SailingID, booking.Seats)76
fmt.Fprintf(output, "sailing %d has fewer than %d seats free\n", booking.SailingID, booking.Seats)78
continue77
continue79
}78
}80
fmt.Fprintf(output, "booked %d seats on sailing %d\n", booking.Seats, booking.SailingID)79
fmt.Fprintf(output, "booked %d seats on sailing %d\n", booking.Seats, booking.SailingID)81
}80
}82
return nil81
return nil83
}82
}Go type differences
The same SQL types map to different Go types, because each database reports them differently.
| Query | Field | MariaDB | CockroachDB |
|---|---|---|---|
| AddSailing | parameter seats_free | int32 | int64 |
| FindSailings | column seats_free | int32 | int64 |
| SailingsDeparting | column seats_free | int32 | int64 |
| BookSeats | parameter seats | int32 | int64 |