Missing rows without errors: SQLite and DuckDB
Every file of the SQLite and DuckDB versions side by side, with the lines that differ marked.
Configuration
sgl.yamldiffers
SQLite
DuckDB
1
version: 11
version: 12
2
3
databases:3
databases:4
- name: ringing4
- name: colony5
dialect: sqlite5
dialect: duckdb6
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_gulls.up.sqldiffers
SQLite
DuckDB
1
CREATE SEQUENCE gull_ids;2
1
CREATE TABLE gulls (3
CREATE TABLE gulls (2
id INTEGER PRIMARY KEY,4
id BIGINT PRIMARY KEY DEFAULT nextval('gull_ids'),3
ring TEXT NOT NULL UNIQUE,5
ring VARCHAR NOT NULL UNIQUE,4
species TEXT NOT NULL6
species VARCHAR NOT NULL5
);7
);db/migrations/0001_gulls.down.sqldiffers
SQLite
DuckDB
1
DROP TABLE gulls;1
DROP TABLE gulls;2
DROP SEQUENCE gull_ids;Queries
RingGulldiffers
SQLite · db/queries/gulls.sql
DuckDB · db/queries/gulls.sql
1
-- sgl.query(RingGull, one, optional: true)1
-- sgl.query(RingGull, one, optional: true)2
INSERT INTO gulls (ring, species) VALUES (?, ?)2
INSERT INTO gulls (ring, species) VALUES ($1, $2)3
ON CONFLICT (ring) DO NOTHING3
ON CONFLICT (ring) DO NOTHING4
RETURNING id, ring;4
RETURNING id, ring;FindGulldiffers
SQLite · db/queries/gulls.sql
DuckDB · db/queries/gulls.sql
1
-- sgl.query(FindGull, one, optional: true)1
-- sgl.query(FindGull, one, optional: true)2
SELECT id, ring, species FROM gulls WHERE ring = ?;2
SELECT id, ring, species FROM gulls WHERE ring = $1;What sgl generates
RingGullidentical
SQLite
DuckDB
1
func (queries *Queries) RingGull(ctx context.Context, params RingGullParams) (RingGullRow, bool, error)1
func (queries *Queries) RingGull(ctx context.Context, params RingGullParams) (RingGullRow, bool, error)2
2
3
type RingGullParams struct {3
type RingGullParams struct {4
Ring string4
Ring string5
Species string5
Species string6
}6
}7
7
8
type RingGullRow struct {8
type RingGullRow struct {9
ID int64 `json:"id"`9
ID int64 `json:"id"`10
Ring string `json:"ring"`10
Ring string `json:"ring"`11
}11
}FindGullidentical
SQLite
DuckDB
1
func (queries *Queries) FindGull(ctx context.Context, ring string) (FindGullRow, bool, error)1
func (queries *Queries) FindGull(ctx context.Context, ring string) (FindGullRow, bool, error)2
2
3
type FindGullRow struct {3
type FindGullRow struct {4
ID int64 `json:"id"`4
ID int64 `json:"id"`5
Ring string `json:"ring"`5
Ring string `json:"ring"`6
Species string `json:"species"`6
Species string `json:"species"`7
}7
}The program
example.godiffers
SQLite
DuckDB
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/sqlite/018_optional_one/internal/store"9
"seagull.sh/seagull/examples/duckdb/018_optional_one/internal/store"10
)10
)11
11
12
// run rings gulls and looks them up. Both queries are one queries with optional: true, so12
// run rings gulls and looks them up. Both queries are one queries with optional: true, so13
// they return (row, ok, error): a missing row gives ok false and a nil error instead of13
// they return (row, ok, error): a missing row gives ok false and a nil error instead of14
// sql.ErrNoRows, and error is left for real failures.14
// sql.ErrNoRows, and error is left for real failures.15
//15
//16
// RingGull inserts with ON CONFLICT (ring) DO NOTHING RETURNING. A ring that is already16
// RingGull inserts with ON CONFLICT (ring) DO NOTHING RETURNING. A ring that is already17
// taken skips the insert, so RETURNING has no row and ok is false. Without optional: true17
// taken skips the insert, so RETURNING has no row and ok is false. Without optional: true18
// sgl vet warns about this (Q046), as the call would fail with sql.ErrNoRows. optional18
// sgl vet warns about this (Q046), as the call would fail with sql.ErrNoRows. optional19
// applies only to a one query; on any other command sgl vet refuses it (Q047).19
// applies only to a one query; on any other command sgl vet refuses it (Q047).20
//20
//21
// Takes database (*sql.DB) which is a migrated database.21
// Takes database (*sql.DB) which is a migrated database.22
// Takes output (io.Writer) which receives each ringing and lookup.22
// Takes output (io.Writer) which receives each ringing and lookup.23
//23
//24
// Returns error when a query fails.24
// Returns error when a query fails.25
func run(ctx context.Context, database *sql.DB, output io.Writer) error {25
func run(ctx context.Context, database *sql.DB, output io.Writer) error {26
queries := store.New(database)26
queries := store.New(database)27
27
28
attempts := []store.RingGullParams{28
attempts := []store.RingGullParams{29
{Ring: "GB1001", Species: "herring gull"},29
{Ring: "GB1001", Species: "herring gull"},30
{Ring: "GB1002", Species: "kittiwake"},30
{Ring: "GB1002", Species: "kittiwake"},31
{Ring: "GB1001", Species: "black-headed gull"},31
{Ring: "GB1001", Species: "black-headed gull"},32
}32
}33
for _, attempt := range attempts {33
for _, attempt := range attempts {34
ringed, ok, err := queries.RingGull(ctx, attempt)34
ringed, ok, err := queries.RingGull(ctx, attempt)35
if err != nil {35
if err != nil {36
return fmt.Errorf("ringing %s: %w", attempt.Ring, err)36
return fmt.Errorf("ringing %s: %w", attempt.Ring, err)37
}37
}38
if !ok {38
if !ok {39
fmt.Fprintf(output, "ring %s is already taken, so the %s was not ringed\n", attempt.Ring, attempt.Species)39
fmt.Fprintf(output, "ring %s is already taken, so the %s was not ringed\n", attempt.Ring, attempt.Species)40
continue40
continue41
}41
}42
fmt.Fprintf(output, "ringed a %s as %s, gull %d\n", attempt.Species, ringed.Ring, ringed.ID)42
fmt.Fprintf(output, "ringed a %s as %s, gull %d\n", attempt.Species, ringed.Ring, ringed.ID)43
}43
}44
44
45
for _, ring := range []string{"GB1002", "GB9999"} {45
for _, ring := range []string{"GB1002", "GB9999"} {46
gull, ok, err := queries.FindGull(ctx, ring)46
gull, ok, err := queries.FindGull(ctx, ring)47
if err != nil {47
if err != nil {48
return fmt.Errorf("finding %s: %w", ring, err)48
return fmt.Errorf("finding %s: %w", ring, err)49
}49
}50
if !ok {50
if !ok {51
fmt.Fprintf(output, "no gull wears ring %s\n", ring)51
fmt.Fprintf(output, "no gull wears ring %s\n", ring)52
continue52
continue53
}53
}54
fmt.Fprintf(output, "ring %s is on gull %d, a %s\n", ring, gull.ID, gull.Species)54
fmt.Fprintf(output, "ring %s is on gull %d, a %s\n", ring, gull.ID, gull.Species)55
}55
}56
return nil56
return nil57
}57
}