Placeholders, inferred names and Params structs: DuckDB and SQLite
Every file of the DuckDB and SQLite versions side by side, with the lines that differ marked. 4 parameter or column types differ in Go.
Configuration
sgl.yamldiffers
DuckDB
SQLite
1
version: 11
version: 12
2
3
databases:3
databases:4
- name: ferries4
- name: ferries5
dialect: duckdb5
dialect: sqlite6
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_sailings.up.sqldiffers
DuckDB
SQLite
1
CREATE SEQUENCE sailing_ids;2
3
CREATE TABLE sailings (1
CREATE TABLE sailings (4
id BIGINT PRIMARY KEY DEFAULT nextval('sailing_ids'),2
id INTEGER PRIMARY KEY,5
origin VARCHAR NOT NULL,3
origin TEXT NOT NULL,6
destination VARCHAR NOT NULL,4
destination TEXT NOT NULL,7
departs VARCHAR NOT NULL,5
departs TEXT NOT NULL,8
seats_free INTEGER NOT NULL6
seats_free INTEGER NOT NULL9
);7
);db/migrations/0001_sailings.down.sqldiffers
DuckDB
SQLite
1
DROP TABLE sailings;1
DROP TABLE sailings;2
DROP SEQUENCE sailing_ids;Queries
AddSailingdiffers
DuckDB · db/queries/sailings.sql
SQLite · db/queries/sailings.sql
1
-- sgl.query(AddSailing, exec)1
-- sgl.query(AddSailing, exec)2
INSERT INTO sailings (origin, destination, departs, seats_free) VALUES ($1, $2, $3, $4);2
INSERT INTO sailings (origin, destination, departs, seats_free) VALUES (?, ?, ?, ?);FindSailingsdiffers
DuckDB · db/queries/sailings.sql
SQLite · 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 = $1 AND destination = $24
WHERE origin = ? AND destination = ?5
ORDER BY departs;5
ORDER BY departs;SailingsDepartingdiffers
DuckDB · db/queries/sailings.sql
SQLite · 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;BookSeatsidentical
DuckDB · db/queries/sailings.sql
SQLite · db/queries/sailings.sql
1
-- sgl.query(BookSeats, execrows)1
-- sgl.query(BookSeats, execrows)2
UPDATE sailings2
UPDATE sailings3
SET seats_free = seats_free - :seats3
SET seats_free = seats_free - :seats4
WHERE id = :sailing_id AND seats_free >= :seats;4
WHERE id = :sailing_id AND seats_free >= :seats;What sgl generates
AddSailingdiffers
DuckDB
SQLite
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
DuckDB
SQLite
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
DuckDB
SQLite
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
DuckDB
SQLite
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
DuckDB
SQLite
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/duckdb/003_parameters/internal/store"9
"seagull.sh/seagull/examples/sqlite/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 $N after the column it is inserted into or16
// - AddSailing and FindSailings name each ? 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 uses named placeholders; :seats appears twice but is one field. sgl20
// - BookSeats uses named placeholders; :seats appears twice but is one field.21
// numbers each name in the generated SQL, so the driver binds two values, not three.22
//21
//23
// Takes database (*sql.DB) which is a migrated database.22
// Takes database (*sql.DB) which is a migrated database.24
// Takes output (io.Writer) which receives the search results and bookings.23
// Takes output (io.Writer) which receives the search results and bookings.25
//24
//26
// Returns error when a query fails.25
// Returns error when a query fails.27
func run(ctx context.Context, database *sql.DB, output io.Writer) error {26
func run(ctx context.Context, database *sql.DB, output io.Writer) error {28
queries := store.New(database)27
queries := store.New(database)29
28
30
timetable := []store.AddSailingParams{29
timetable := []store.AddSailingParams{31
{Origin: "Penzance", Destination: "St Mary's", Departs: "09:15", SeatsFree: 40},30
{Origin: "Penzance", Destination: "St Mary's", Departs: "09:15", SeatsFree: 40},32
{Origin: "St Mary's", Destination: "Tresco", Departs: "11:00", SeatsFree: 12},31
{Origin: "St Mary's", Destination: "Tresco", Departs: "11:00", SeatsFree: 12},33
{Origin: "Penzance", Destination: "St Mary's", Departs: "14:30", SeatsFree: 4},32
{Origin: "Penzance", Destination: "St Mary's", Departs: "14:30", SeatsFree: 4},34
{Origin: "St Mary's", Destination: "Penzance", Departs: "16:30", SeatsFree: 60},33
{Origin: "St Mary's", Destination: "Penzance", Departs: "16:30", SeatsFree: 60},35
}34
}36
for _, sailing := range timetable {35
for _, sailing := range timetable {37
if err := queries.AddSailing(ctx, sailing); err != nil {36
if err := queries.AddSailing(ctx, sailing); err != nil {38
return fmt.Errorf("adding the %s sailing: %w", sailing.Departs, err)37
return fmt.Errorf("adding the %s sailing: %w", sailing.Departs, err)39
}38
}40
}39
}41
40
42
route, err := queries.FindSailings(ctx, store.FindSailingsParams{41
route, err := queries.FindSailings(ctx, store.FindSailingsParams{43
Origin: "Penzance",42
Origin: "Penzance",44
Destination: "St Mary's",43
Destination: "St Mary's",45
})44
})46
if err != nil {45
if err != nil {47
return fmt.Errorf("finding sailings: %w", err)46
return fmt.Errorf("finding sailings: %w", err)48
}47
}49
fmt.Fprintln(output, "Penzance to St Mary's:")48
fmt.Fprintln(output, "Penzance to St Mary's:")50
for _, sailing := range route {49
for _, sailing := range route {51
fmt.Fprintf(output, " sailing %d at %s, %d seats free\n", sailing.ID, sailing.Departs, sailing.SeatsFree)50
fmt.Fprintf(output, " sailing %d at %s, %d seats free\n", sailing.ID, sailing.Departs, sailing.SeatsFree)52
}51
}53
52
54
midday, err := queries.SailingsDeparting(ctx, store.SailingsDepartingParams{53
midday, err := queries.SailingsDeparting(ctx, store.SailingsDepartingParams{55
Earliest: "10:00",54
Earliest: "10:00",56
Latest: "15:00",55
Latest: "15:00",57
})56
})58
if err != nil {57
if err != nil {59
return fmt.Errorf("finding midday sailings: %w", err)58
return fmt.Errorf("finding midday sailings: %w", err)60
}59
}61
fmt.Fprintln(output, "Departing between 10:00 and 15:00:")60
fmt.Fprintln(output, "Departing between 10:00 and 15:00:")62
for _, sailing := range midday {61
for _, sailing := range midday {63
fmt.Fprintf(output, " sailing %d at %s, %s to %s\n", sailing.ID, sailing.Departs, sailing.Origin, sailing.Destination)62
fmt.Fprintf(output, " sailing %d at %s, %s to %s\n", sailing.ID, sailing.Departs, sailing.Origin, sailing.Destination)64
}63
}65
64
66
// The same booking twice: the first takes 3 of the 4 free seats, so the second changes65
// The same booking twice: the first takes 3 of the 4 free seats, so the second changes67
// no row because seats_free >= :seats is now false.66
// no row because seats_free >= :seats is now false.68
booking := store.BookSeatsParams{Seats: 3, SailingID: 3}67
booking := store.BookSeatsParams{Seats: 3, SailingID: 3}69
for range 2 {68
for range 2 {70
booked, err := queries.BookSeats(ctx, booking)69
booked, err := queries.BookSeats(ctx, booking)71
if err != nil {70
if err != nil {72
return fmt.Errorf("booking sailing %d: %w", booking.SailingID, err)71
return fmt.Errorf("booking sailing %d: %w", booking.SailingID, err)73
}72
}74
if booked == 0 {73
if booked == 0 {75
fmt.Fprintf(output, "sailing %d has fewer than %d seats free\n", booking.SailingID, booking.Seats)74
fmt.Fprintf(output, "sailing %d has fewer than %d seats free\n", booking.SailingID, booking.Seats)76
continue75
continue77
}76
}78
fmt.Fprintf(output, "booked %d seats on sailing %d\n", booking.Seats, booking.SailingID)77
fmt.Fprintf(output, "booked %d seats on sailing %d\n", booking.Seats, booking.SailingID)79
}78
}80
return nil79
return nil81
}80
}Go type differences
The same SQL types map to different Go types, because each database reports them differently.
| Query | Field | DuckDB | SQLite |
|---|---|---|---|
| AddSailing | parameter seats_free | int32 | int64 |
| FindSailings | column seats_free | int32 | int64 |
| SailingsDeparting | column seats_free | int32 | int64 |
| BookSeats | parameter seats | int32 | int64 |