Compare · 003

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
1version: 1
1version: 1
2
2
3databases:
3databases:
4 - name: ferries
4 - name: ferries
5 dialect: duckdb
5 dialect: sqlite
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_sailings.up.sqldiffers
DuckDB
SQLite
1CREATE SEQUENCE sailing_ids;
2
3CREATE TABLE sailings (
1CREATE 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 NULL
6 seats_free INTEGER NOT NULL
9);
7);
db/migrations/0001_sailings.down.sqldiffers
DuckDB
SQLite
1DROP TABLE sailings;
1DROP TABLE sailings;
2DROP 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)
2INSERT INTO sailings (origin, destination, departs, seats_free) VALUES ($1, $2, $3, $4);
2INSERT 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)
2SELECT id, origin, destination, departs, seats_free
2SELECT id, origin, destination, departs, seats_free
3FROM sailings
3FROM sailings
4WHERE origin = $1 AND destination = $2
4WHERE origin = ? AND destination = ?
5ORDER BY departs;
5ORDER 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)
4SELECT id, origin, destination, departs, seats_free
4SELECT id, origin, destination, departs, seats_free
5FROM sailings
5FROM sailings
6WHERE departs >= $1 AND departs < $2
6WHERE departs >= ?1 AND departs < ?2
7ORDER BY departs;
7ORDER BY departs;
BookSeatsidentical
DuckDB · db/queries/sailings.sql
SQLite · db/queries/sailings.sql
1-- sgl.query(BookSeats, execrows)
1-- sgl.query(BookSeats, execrows)
2UPDATE sailings
2UPDATE sailings
3SET seats_free = seats_free - :seats
3SET seats_free = seats_free - :seats
4WHERE id = :sailing_id AND seats_free >= :seats;
4WHERE id = :sailing_id AND seats_free >= :seats;

What sgl generates

AddSailingdiffers
DuckDB
SQLite
1func (queries *Queries) AddSailing(ctx context.Context, params AddSailingParams) error
1func (queries *Queries) AddSailing(ctx context.Context, params AddSailingParams) error
2
2
3type AddSailingParams struct {
3type AddSailingParams struct {
4 Origin string
4 Origin string
5 Destination string
5 Destination string
6 Departs string
6 Departs string
7 SeatsFree int32
7 SeatsFree int64
8}
8}
FindSailingsdiffers
DuckDB
SQLite
1func (queries *Queries) FindSailings(ctx context.Context, params FindSailingsParams) ([]FindSailingsRow, error)
1func (queries *Queries) FindSailings(ctx context.Context, params FindSailingsParams) ([]FindSailingsRow, error)
2
2
3type FindSailingsParams struct {
3type FindSailingsParams struct {
4 Origin string
4 Origin string
5 Destination string
5 Destination string
6}
6}
7
7
8type FindSailingsRow struct {
8type 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
1func (queries *Queries) SailingsDeparting(ctx context.Context, params SailingsDepartingParams) ([]SailingsDepartingRow, error)
1func (queries *Queries) SailingsDeparting(ctx context.Context, params SailingsDepartingParams) ([]SailingsDepartingRow, error)
2
2
3type SailingsDepartingParams struct {
3type SailingsDepartingParams struct {
4 Earliest string
4 Earliest string
5 Latest string
5 Latest string
6}
6}
7
7
8type SailingsDepartingRow struct {
8type 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
1func (queries *Queries) BookSeats(ctx context.Context, params BookSeatsParams) (int64, error)
1func (queries *Queries) BookSeats(ctx context.Context, params BookSeatsParams) (int64, error)
2
2
3type BookSeatsParams struct {
3type BookSeatsParams struct {
4 Seats int32
4 Seats int64
5 SailingID int64
5 SailingID int64
6}
6}

The program

example.godiffers
DuckDB
SQLite
1package main
1package main
2
2
3import (
3import (
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 two
12// run fills a ferry timetable, searches it two ways and books seats. Every query has two
13// or more parameters, so each method takes a Params struct, and the struct's field names
13// or more parameters, so each method takes a Params struct, and the struct's field names
14// 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 or
16// - AddSailing and FindSailings name each ? after the column it is inserted into or
17// compared with: Origin, Destination, Departs, SeatsFree;
17// compared with: Origin, Destination, Departs, SeatsFree;
18// - SailingsDeparting compares departs twice, which would give Departs and Departs2, so
18// - SailingsDeparting compares departs twice, which would give Departs and Departs2, so
19// 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. sgl
20// - 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.
27func run(ctx context.Context, database *sql.DB, output io.Writer) error {
26func 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 changes
65 // The same booking twice: the first takes 3 of the 4 free seats, so the second changes
67 // 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 continue
75 continue
77 }
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 nil
79 return nil
81}
80}

Go type differences

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

QueryFieldDuckDBSQLite
AddSailingparameter seats_freeint32int64
FindSailingscolumn seats_freeint32int64
SailingsDepartingcolumn seats_freeint32int64
BookSeatsparameter seatsint32int64