Compare · 003

Placeholders, inferred names and Params structs: MariaDB and DuckDB

Every file of the MariaDB and DuckDB versions side by side, with the lines that differ marked.

Configuration

sgl.yamldiffers
MariaDB
DuckDB
1version: 1
1version: 1
2
2
3databases:
3databases:
4 - name: ferries
4 - name: ferries
5 dialect: mysql
5 dialect: duckdb
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
MariaDB
DuckDB
1CREATE SEQUENCE sailing_ids;
2
1CREATE TABLE sailings (
3CREATE TABLE sailings (
2 id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
4 id BIGINT PRIMARY KEY DEFAULT nextval('sailing_ids'),
3 origin VARCHAR(100) NOT NULL,
5 origin VARCHAR NOT NULL,
4 destination VARCHAR(100) NOT NULL,
6 destination VARCHAR NOT NULL,
5 departs CHAR(5) NOT NULL,
7 departs VARCHAR NOT NULL,
6 seats_free INT NOT NULL
8 seats_free INTEGER NOT NULL
7);
9);
db/migrations/0001_sailings.down.sqldiffers
MariaDB
DuckDB
1DROP TABLE sailings;
1DROP TABLE sailings;
2DROP SEQUENCE sailing_ids;

Queries

AddSailingdiffers
MariaDB · db/queries/sailings.sql
DuckDB · db/queries/sailings.sql
1-- sgl.query(AddSailing, exec)
1-- sgl.query(AddSailing, exec)
2INSERT INTO sailings (origin, destination, departs, seats_free) VALUES (?, ?, ?, ?);
2INSERT INTO sailings (origin, destination, departs, seats_free) VALUES ($1, $2, $3, $4);
FindSailingsdiffers
MariaDB · db/queries/sailings.sql
DuckDB · 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 = ? AND destination = ?
4WHERE origin = $1 AND destination = $2
5ORDER BY departs;
5ORDER BY departs;
SailingsDepartingdiffers
MariaDB · db/queries/sailings.sql
DuckDB · 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;
BookSeatsdiffers
MariaDB · db/queries/sailings.sql
DuckDB · 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)
4UPDATE sailings
2UPDATE sailings
5SET seats_free = seats_free - ?1
3SET seats_free = seats_free - :seats
6WHERE id = ?2 AND seats_free >= ?1;
4WHERE id = :sailing_id AND seats_free >= :seats;

What sgl generates

AddSailingidentical
MariaDB
DuckDB
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 int32
8}
8}
FindSailingsidentical
MariaDB
DuckDB
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 int32 `json:"seats_free"`
14}
14}
SailingsDepartingidentical
MariaDB
DuckDB
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 int32 `json:"seats_free"`
14}
14}
BookSeatsidentical
MariaDB
DuckDB
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 int32
5 SailingID int64
5 SailingID int64
6}
6}

The program

example.godiffers
MariaDB
DuckDB
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/mysql/003_parameters/internal/store"
9 "seagull.sh/seagull/examples/duckdb/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 ? after the column it is inserted into or
16// - AddSailing and FindSailings name each $N 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 numbers its placeholders: ?1 appears twice but is one Seats field.
20// - BookSeats uses named placeholders; :seats appears twice but is one field. sgl
21//
21// numbers each name in the generated SQL, so the driver binds two values, not three.
22// MySQL itself takes only ?, so the generated SQL has a plain ? for each use, and the
23// method passes Seats once for each.
24//
22//
25// Takes database (*sql.DB) which is a migrated database.
23// Takes database (*sql.DB) which is a migrated database.
26// Takes output (io.Writer) which receives the search results and bookings.
24// Takes output (io.Writer) which receives the search results and bookings.
27//
25//
28// Returns error when a query fails.
26// Returns error when a query fails.
29func run(ctx context.Context, database *sql.DB, output io.Writer) error {
27func run(ctx context.Context, database *sql.DB, output io.Writer) error {
30 queries := store.New(database)
28 queries := store.New(database)
31
29
32 timetable := []store.AddSailingParams{
30 timetable := []store.AddSailingParams{
33 {Origin: "Penzance", Destination: "St Mary's", Departs: "09:15", SeatsFree: 40},
31 {Origin: "Penzance", Destination: "St Mary's", Departs: "09:15", SeatsFree: 40},
34 {Origin: "St Mary's", Destination: "Tresco", Departs: "11:00", SeatsFree: 12},
32 {Origin: "St Mary's", Destination: "Tresco", Departs: "11:00", SeatsFree: 12},
35 {Origin: "Penzance", Destination: "St Mary's", Departs: "14:30", SeatsFree: 4},
33 {Origin: "Penzance", Destination: "St Mary's", Departs: "14:30", SeatsFree: 4},
36 {Origin: "St Mary's", Destination: "Penzance", Departs: "16:30", SeatsFree: 60},
34 {Origin: "St Mary's", Destination: "Penzance", Departs: "16:30", SeatsFree: 60},
37 }
35 }
38 for _, sailing := range timetable {
36 for _, sailing := range timetable {
39 if err := queries.AddSailing(ctx, sailing); err != nil {
37 if err := queries.AddSailing(ctx, sailing); err != nil {
40 return fmt.Errorf("adding the %s sailing: %w", sailing.Departs, err)
38 return fmt.Errorf("adding the %s sailing: %w", sailing.Departs, err)
41 }
39 }
42 }
40 }
43
41
44 route, err := queries.FindSailings(ctx, store.FindSailingsParams{
42 route, err := queries.FindSailings(ctx, store.FindSailingsParams{
45 Origin: "Penzance",
43 Origin: "Penzance",
46 Destination: "St Mary's",
44 Destination: "St Mary's",
47 })
45 })
48 if err != nil {
46 if err != nil {
49 return fmt.Errorf("finding sailings: %w", err)
47 return fmt.Errorf("finding sailings: %w", err)
50 }
48 }
51 fmt.Fprintln(output, "Penzance to St Mary's:")
49 fmt.Fprintln(output, "Penzance to St Mary's:")
52 for _, sailing := range route {
50 for _, sailing := range route {
53 fmt.Fprintf(output, " sailing %d at %s, %d seats free\n", sailing.ID, sailing.Departs, sailing.SeatsFree)
51 fmt.Fprintf(output, " sailing %d at %s, %d seats free\n", sailing.ID, sailing.Departs, sailing.SeatsFree)
54 }
52 }
55
53
56 midday, err := queries.SailingsDeparting(ctx, store.SailingsDepartingParams{
54 midday, err := queries.SailingsDeparting(ctx, store.SailingsDepartingParams{
57 Earliest: "10:00",
55 Earliest: "10:00",
58 Latest: "15:00",
56 Latest: "15:00",
59 })
57 })
60 if err != nil {
58 if err != nil {
61 return fmt.Errorf("finding midday sailings: %w", err)
59 return fmt.Errorf("finding midday sailings: %w", err)
62 }
60 }
63 fmt.Fprintln(output, "Departing between 10:00 and 15:00:")
61 fmt.Fprintln(output, "Departing between 10:00 and 15:00:")
64 for _, sailing := range midday {
62 for _, sailing := range midday {
65 fmt.Fprintf(output, " sailing %d at %s, %s to %s\n", sailing.ID, sailing.Departs, sailing.Origin, sailing.Destination)
63 fmt.Fprintf(output, " sailing %d at %s, %s to %s\n", sailing.ID, sailing.Departs, sailing.Origin, sailing.Destination)
66 }
64 }
67
65
68 // The same booking twice: the first takes 3 of the 4 free seats, so the second changes
66 // The same booking twice: the first takes 3 of the 4 free seats, so the second changes
69 // no row because seats_free >= ?1 is now false.
67 // no row because seats_free >= :seats is now false.
70 booking := store.BookSeatsParams{Seats: 3, SailingID: 3}
68 booking := store.BookSeatsParams{Seats: 3, SailingID: 3}
71 for range 2 {
69 for range 2 {
72 booked, err := queries.BookSeats(ctx, booking)
70 booked, err := queries.BookSeats(ctx, booking)
73 if err != nil {
71 if err != nil {
74 return fmt.Errorf("booking sailing %d: %w", booking.SailingID, err)
72 return fmt.Errorf("booking sailing %d: %w", booking.SailingID, err)
75 }
73 }
76 if booked == 0 {
74 if booked == 0 {
77 fmt.Fprintf(output, "sailing %d has fewer than %d seats free\n", booking.SailingID, booking.Seats)
75 fmt.Fprintf(output, "sailing %d has fewer than %d seats free\n", booking.SailingID, booking.Seats)
78 continue
76 continue
79 }
77 }
80 fmt.Fprintf(output, "booked %d seats on sailing %d\n", booking.Seats, booking.SailingID)
78 fmt.Fprintf(output, "booked %d seats on sailing %d\n", booking.Seats, booking.SailingID)
81 }
79 }
82 return nil
80 return nil
83}
81}