From SQL types to Go types: 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: harbour4
- name: landings5
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_landings.up.sqldiffers
DuckDB
SQLite
1
CREATE SEQUENCE landing_ids;2
3
CREATE TABLE landings (1
CREATE TABLE landings (4
id BIGINT PRIMARY KEY DEFAULT nextval('landing_ids'),2
id INTEGER PRIMARY KEY,5
species VARCHAR NOT NULL,3
species TEXT NOT NULL,6
quay UTINYINT NOT NULL,4
boxes SMALLINT NOT NULL,7
boxes SMALLINT NOT NULL,5
fish INTEGER NOT NULL,8
fish INTEGER NOT NULL,6
weight_g BIGINT NOT NULL,9
weight_g BIGINT NOT NULL,7
length_cm REAL NOT NULL,10
length_cm DOUBLE NOT NULL,8
on_ice BOOLEAN NOT NULL,11
water_temp REAL NOT NULL,9
landed_at DATETIME NOT NULL,12
on_ice BOOLEAN NOT NULL,10
tag BLOB NOT NULL,13
landed_at TIMESTAMP NOT NULL,11
notes TEXT14
landed_on DATE NOT NULL,15
catch_id UUID NOT NULL,16
auction_lot HUGEINT NOT NULL,17
tag BLOB NOT NULL,18
notes VARCHAR19
);12
);db/migrations/0001_landings.down.sqldiffers
DuckDB
SQLite
1
DROP TABLE landings;1
DROP TABLE landings;2
DROP SEQUENCE landing_ids;Queries
RecordLandingdiffers
DuckDB · db/queries/landings.sql
SQLite · db/queries/landings.sql
1
-- sgl.query(RecordLanding, one)1
-- sgl.query(RecordLanding, one)2
INSERT INTO landings (species, quay, boxes, fish, weight_g, length_cm, water_temp, on_ice, landed_at, landed_on, catch_id, auction_lot, tag, notes)2
INSERT INTO landings (species, boxes, fish, weight_g, length_cm, on_ice, landed_at, tag, notes)3
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)3
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)4
RETURNING id;4
RETURNING id;GetLandingdiffers
DuckDB · db/queries/landings.sql
SQLite · db/queries/landings.sql
1
-- sgl.query(GetLanding, one)1
-- sgl.query(GetLanding, one)2
SELECT id, species, quay, boxes, fish, weight_g, length_cm, water_temp, on_ice, landed_at, landed_on, catch_id, auction_lot, tag, notes2
SELECT id, species, boxes, fish, weight_g, length_cm, on_ice, landed_at, tag, notes3
FROM landings3
FROM landings4
WHERE id = $1;4
WHERE id = ?;What sgl generates
RecordLandingdiffers
DuckDB
SQLite
1
func (queries *Queries) RecordLanding(ctx context.Context, params RecordLandingParams) (RecordLandingRow, error)1
func (queries *Queries) RecordLanding(ctx context.Context, params RecordLandingParams) (RecordLandingRow, error)2
2
3
type RecordLandingParams struct {3
type RecordLandingParams struct {4
Species string4
Species string5
Quay uint85
Boxes int646
Boxes int166
Fish int647
Fish int327
WeightG int648
WeightG int648
LengthCm float649
LengthCm float649
OnIce bool10
WaterTemp float3210
LandedAt time.Time11
OnIce bool11
Tag []byte12
LandedAt time.Time12
Notes *string13
LandedOn time.Time14
CatchID uuid.UUID15
AuctionLot *big.Int16
Tag []byte17
Notes *string18
}13
}19
14
20
type RecordLandingRow struct {15
type RecordLandingRow struct {21
ID int64 `json:"id"`16
ID int64 `json:"id"`22
}17
}GetLandingdiffers
DuckDB
SQLite
1
func (queries *Queries) GetLanding(ctx context.Context, id int64) (GetLandingRow, error)1
func (queries *Queries) GetLanding(ctx context.Context, id int64) (GetLandingRow, error)2
2
3
type GetLandingRow struct {3
type GetLandingRow struct {4
ID int64 `json:"id"`4
ID int64 `json:"id"`5
Species string `json:"species"`5
Species string `json:"species"`6
Quay uint8 `json:"quay"`6
Boxes int64 `json:"boxes"`7
Boxes int16 `json:"boxes"`7
Fish int64 `json:"fish"`8
Fish int32 `json:"fish"`8
WeightG int64 `json:"weight_g"`9
WeightG int64 `json:"weight_g"`9
LengthCm float64 `json:"length_cm"`10
LengthCm float64 `json:"length_cm"`10
OnIce bool `json:"on_ice"`11
WaterTemp float32 `json:"water_temp"`11
LandedAt time.Time `json:"landed_at"`12
OnIce bool `json:"on_ice"`12
Tag []byte `json:"tag"`13
LandedAt time.Time `json:"landed_at"`13
Notes *string `json:"notes"`14
LandedOn time.Time `json:"landed_on"`15
CatchID uuid.UUID `json:"catch_id"`16
AuctionLot *big.Int `json:"auction_lot"`17
Tag []byte `json:"tag"`18
Notes *string `json:"notes"`19
}14
}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
"errors"7
"fmt"6
"fmt"8
"io"7
"io"9
"math/big"10
"time"8
"time"11
9
12
"github.com/google/uuid"10
"seagull.sh/seagull/examples/sqlite/005_type_mapping/internal/store"13
14
"seagull.sh/seagull/examples/duckdb/005_type_mapping/internal/store"15
)11
)16
12
17
// run records a landing of fish and reads it back, printing the Go type sgl chose for13
// run records a landing of fish and reads it back, printing the Go type sgl chose for18
// each column. DuckDB declares every column's type exactly, and Seagull maps each one:14
// each column. SQLite stores every value as one of five storage classes, so Seagull maps19
//15
// the type each column declares in db/migrations. SMALLINT, INTEGER and BIGINT all have20
// - SMALLINT, INTEGER and BIGINT give int16, int32 and int64, and the unsigned UTINYINT16
// INTEGER affinity, which SQLite stores in up to eight bytes whatever width the column21
// gives uint8;17
// declares, so each gives int64, as the INTEGER PRIMARY KEY rowid does. REAL gives22
// - DOUBLE gives float64, but REAL is DuckDB's 4-byte float, so it gives float32;18
// float64, and DATETIME gives time.Time, which the driver parses back from the text it23
// - TIMESTAMP and DATE give time.Time, and UUID gives uuid.UUID from19
// stored. SQLite has no exact decimal type, so money is best kept as INTEGER pence; a24
// github.com/google/uuid;20
// DECIMAL column maps to apd.Decimal from github.com/cockroachdb/apd/v3.25
// - HUGEINT is a 128-bit integer with no Go equivalent, so it gives *big.Int, the type26
// duckdb-go returns, even when the column is NOT NULL;27
// - BLOB gives []byte, and a nullable VARCHAR gives *string.28
//21
//29
// Takes database (*sql.DB) which is a migrated database.22
// Takes database (*sql.DB) which is a migrated database.30
// Takes output (io.Writer) which receives one line per column.23
// Takes output (io.Writer) which receives one line per column.31
//24
//32
// Returns error when a query fails.25
// Returns error when a query fails.33
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 {34
queries := store.New(database)27
queries := store.New(database)35
28
36
// The auction lot number has 20 digits, too many for int64 or uint64.37
lot, ok := new(big.Int).SetString("20260501000000000001", 10)38
if !ok {39
return errors.New("parsing the auction lot number")40
}41
recorded, err := queries.RecordLanding(ctx, store.RecordLandingParams{29
recorded, err := queries.RecordLanding(ctx, store.RecordLandingParams{42
Species: "mackerel",30
Species: "mackerel",43
Quay: 3,31
Boxes: 12,44
Boxes: 12,32
Fish: 480,45
Fish: 480,33
WeightG: 163_200,46
WeightG: 163_200,34
LengthCm: 31.5,47
LengthCm: 31.5,35
OnIce: true,48
WaterTemp: 11.25,36
LandedAt: time.Date(2026, time.May, 1, 5, 30, 0, 0, time.UTC),49
OnIce: true,37
Tag: []byte{0x04, 0xa2, 0x19, 0xf3},50
LandedAt: time.Date(2026, time.May, 1, 5, 30, 0, 0, time.UTC),38
Notes: new("landed at Newlyn"),51
LandedOn: time.Date(2026, time.May, 1, 0, 0, 0, 0, time.UTC),52
CatchID: uuid.MustParse("6f1c2a5e-4d3b-4c2a-9e8f-1a2b3c4d5e6f"),53
AuctionLot: lot,54
Tag: []byte{0x04, 0xa2, 0x19, 0xf3},55
Notes: new("landed at Newlyn"),56
})39
})57
if err != nil {40
if err != nil {58
return fmt.Errorf("recording a landing: %w", err)41
return fmt.Errorf("recording a landing: %w", err)59
}42
}60
43
61
landing, err := queries.GetLanding(ctx, recorded.ID)44
landing, err := queries.GetLanding(ctx, recorded.ID)62
if err != nil {45
if err != nil {63
return fmt.Errorf("getting landing %d: %w", recorded.ID, err)46
return fmt.Errorf("getting landing %d: %w", recorded.ID, err)64
}47
}65
fmt.Fprintf(output, "id %-10T %d\n", landing.ID, landing.ID)48
fmt.Fprintf(output, "id %-10T %d\n", landing.ID, landing.ID)66
fmt.Fprintf(output, "species %-10T %s\n", landing.Species, landing.Species)49
fmt.Fprintf(output, "species %-10T %s\n", landing.Species, landing.Species)67
fmt.Fprintf(output, "quay %-10T %d\n", landing.Quay, landing.Quay)50
fmt.Fprintf(output, "boxes %-10T %d\n", landing.Boxes, landing.Boxes)68
fmt.Fprintf(output, "boxes %-10T %d\n", landing.Boxes, landing.Boxes)51
fmt.Fprintf(output, "fish %-10T %d\n", landing.Fish, landing.Fish)69
fmt.Fprintf(output, "fish %-10T %d\n", landing.Fish, landing.Fish)52
fmt.Fprintf(output, "weight_g %-10T %d\n", landing.WeightG, landing.WeightG)70
fmt.Fprintf(output, "weight_g %-10T %d\n", landing.WeightG, landing.WeightG)53
fmt.Fprintf(output, "length_cm %-10T %.1f\n", landing.LengthCm, landing.LengthCm)71
fmt.Fprintf(output, "length_cm %-10T %.1f\n", landing.LengthCm, landing.LengthCm)54
fmt.Fprintf(output, "on_ice %-10T %t\n", landing.OnIce, landing.OnIce)72
fmt.Fprintf(output, "water_temp %-10T %.2f\n", landing.WaterTemp, landing.WaterTemp)55
fmt.Fprintf(output, "landed_at %-10T %s\n", landing.LandedAt, landing.LandedAt.Format(time.RFC3339))73
fmt.Fprintf(output, "on_ice %-10T %t\n", landing.OnIce, landing.OnIce)74
fmt.Fprintf(output, "landed_at %-10T %s\n", landing.LandedAt, landing.LandedAt.Format(time.RFC3339))75
fmt.Fprintf(output, "landed_on %-10T %s\n", landing.LandedOn, landing.LandedOn.Format(time.DateOnly))76
fmt.Fprintf(output, "catch_id %-10T %s\n", landing.CatchID, landing.CatchID)77
fmt.Fprintf(output, "auction_lot %-10T %s\n", landing.AuctionLot, landing.AuctionLot)78
// %T prints []byte as []uint8, since byte is an alias for uint8.56
// %T prints []byte as []uint8, since byte is an alias for uint8.79
fmt.Fprintf(output, "tag %-10T % x\n", landing.Tag, landing.Tag)57
fmt.Fprintf(output, "tag %-10T % x\n", landing.Tag, landing.Tag)80
fmt.Fprintf(output, "notes %-10T %s\n", landing.Notes, *landing.Notes)58
fmt.Fprintf(output, "notes %-10T %s\n", landing.Notes, *landing.Notes)81
return nil59
return nil82
}60
}Go type differences
The same SQL types map to different Go types, because each database reports them differently.
| Query | Field | DuckDB | SQLite |
|---|---|---|---|
| RecordLanding | parameter boxes | int16 | int64 |
| RecordLanding | parameter fish | int32 | int64 |
| GetLanding | column boxes | int16 | int64 |
| GetLanding | column fish | int32 | int64 |