Missing rows without errors: CockroachDB and SQLite
Every file of the CockroachDB and SQLite versions side by side, with the lines that differ marked.
Configuration
sgl.yamldiffers
CockroachDB
SQLite
1
version: 11
version: 12
2
3
databases:3
databases:4
- name: ringing4
- name: ringing5
dialect: postgres5
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/store11
driver: database/sql12
migrate:13
dsn: ${env:DATABASE_URL}Migrations
db/migrations/0001_gulls.up.sqldiffers
CockroachDB
SQLite
1
CREATE TABLE gulls (1
CREATE TABLE gulls (2
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,2
id INTEGER PRIMARY KEY,3
ring TEXT NOT NULL UNIQUE,3
ring TEXT NOT NULL UNIQUE,4
species TEXT NOT NULL4
species TEXT NOT NULL5
);5
);db/migrations/0001_gulls.down.sqlidentical
CockroachDB
SQLite
1
DROP TABLE gulls;1
DROP TABLE gulls;Queries
RingGulldiffers
CockroachDB · db/queries/gulls.sql
SQLite · 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 ($1, $2)2
INSERT INTO gulls (ring, species) VALUES (?, ?)3
ON CONFLICT (ring) DO NOTHING3
ON CONFLICT (ring) DO NOTHING4
RETURNING id, ring;4
RETURNING id, ring;FindGulldiffers
CockroachDB · db/queries/gulls.sql
SQLite · 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 = $1;2
SELECT id, ring, species FROM gulls WHERE ring = ?;What sgl generates
RingGullidentical
CockroachDB
SQLite
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
CockroachDB
SQLite
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
CockroachDB
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/postgres/018_optional_one/internal/store"9
"seagull.sh/seagull/examples/sqlite/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
// PostgreSQL takes the next id from the identity column's sequence before it checks for22
// the conflict, and a skipped insert does not hand it back. The black-headed gull that is23
// turned away and then ringed GB1003 becomes gull 4, not gull 3.24
//25
// Takes database (*sql.DB) which is a migrated database.21
// Takes database (*sql.DB) which is a migrated database.26
// Takes output (io.Writer) which receives each ringing and lookup.22
// Takes output (io.Writer) which receives each ringing and lookup.27
//23
//28
// Returns error when a query fails.24
// Returns error when a query fails.29
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 {30
queries := store.New(database)26
queries := store.New(database)31
27
32
attempts := []store.RingGullParams{28
attempts := []store.RingGullParams{33
{Ring: "GB1001", Species: "herring gull"},29
{Ring: "GB1001", Species: "herring gull"},34
{Ring: "GB1002", Species: "kittiwake"},30
{Ring: "GB1002", Species: "kittiwake"},35
{Ring: "GB1001", Species: "black-headed gull"},31
{Ring: "GB1001", Species: "black-headed gull"},36
{Ring: "GB1003", Species: "black-headed gull"},37
}32
}38
for _, attempt := range attempts {33
for _, attempt := range attempts {39
ringed, ok, err := queries.RingGull(ctx, attempt)34
ringed, ok, err := queries.RingGull(ctx, attempt)40
if err != nil {35
if err != nil {41
return fmt.Errorf("ringing %s: %w", attempt.Ring, err)36
return fmt.Errorf("ringing %s: %w", attempt.Ring, err)42
}37
}43
if !ok {38
if !ok {44
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)45
continue40
continue46
}41
}47
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)48
}43
}49
44
50
for _, ring := range []string{"GB1002", "GB9999"} {45
for _, ring := range []string{"GB1002", "GB9999"} {51
gull, ok, err := queries.FindGull(ctx, ring)46
gull, ok, err := queries.FindGull(ctx, ring)52
if err != nil {47
if err != nil {53
return fmt.Errorf("finding %s: %w", ring, err)48
return fmt.Errorf("finding %s: %w", ring, err)54
}49
}55
if !ok {50
if !ok {56
fmt.Fprintf(output, "no gull wears ring %s\n", ring)51
fmt.Fprintf(output, "no gull wears ring %s\n", ring)57
continue52
continue58
}53
}59
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)60
}55
}61
return nil56
return nil62
}57
}