From SQL types to Go types: TimescaleDB and SQLite
Every file of the TimescaleDB and SQLite versions side by side, with the lines that differ marked. 6 parameter or column types differ in Go.
Configuration
sgl.yamldiffers
TimescaleDB
SQLite
1
version: 11
version: 12
2
3
databases:3
databases:4
- name: landings4
- name: landings5
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/sqlMigrations
db/migrations/0001_landings.up.sqldiffers
TimescaleDB
SQLite
1
CREATE TABLE landings (1
CREATE TABLE landings (2
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,2
id INTEGER PRIMARY KEY,3
catch_id UUID NOT NULL,3
species TEXT NOT NULL,4
species TEXT NOT NULL,4
boxes SMALLINT NOT NULL,5
boxes SMALLINT NOT NULL,5
fish INTEGER NOT NULL,6
fish INTEGER NOT NULL,6
weight_g BIGINT NOT NULL,7
weight_g BIGINT NOT NULL,7
length_cm REAL NOT NULL,8
length_cm REAL NOT NULL,8
on_ice BOOLEAN NOT NULL,9
sea_temp DOUBLE PRECISION NOT NULL,9
landed_at DATETIME NOT NULL,10
price NUMERIC(8, 2) NOT NULL,10
tag BLOB NOT NULL,11
on_ice BOOLEAN NOT NULL,12
landed_on DATE NOT NULL,13
landed_at TIMESTAMPTZ NOT NULL,14
harbour_ip INET NOT NULL,15
tag BYTEA NOT NULL,16
notes TEXT11
notes TEXT17
);12
);db/migrations/0001_landings.down.sqlidentical
TimescaleDB
SQLite
1
DROP TABLE landings;1
DROP TABLE landings;Queries
RecordLandingdiffers
TimescaleDB · db/queries/landings.sql
SQLite · db/queries/landings.sql
1
-- sgl.query(RecordLanding, one)1
-- sgl.query(RecordLanding, one)2
INSERT INTO landings (catch_id, species, boxes, fish, weight_g, length_cm, sea_temp, price, on_ice, landed_on, landed_at, harbour_ip, 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
TimescaleDB · db/queries/landings.sql
SQLite · db/queries/landings.sql
1
-- sgl.query(GetLanding, one)1
-- sgl.query(GetLanding, one)2
SELECT id, catch_id, species, boxes, fish, weight_g, length_cm, sea_temp, price, on_ice, landed_on, landed_at, harbour_ip, 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
TimescaleDB
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
CatchID uuid.UUID4
Species string5
Species string5
Boxes int646
Boxes int166
Fish int647
Fish int327
WeightG int648
WeightG int648
LengthCm float649
LengthCm float329
OnIce bool10
SeaTemp float6410
LandedAt time.Time11
Price apd.Decimal11
Tag []byte12
OnIce bool12
Notes *string13
LandedOn time.Time14
LandedAt time.Time15
HarbourIP any16
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
TimescaleDB
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
CatchID uuid.UUID `json:"catch_id"`5
Species string `json:"species"`6
Species string `json:"species"`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 float32 `json:"length_cm"`10
OnIce bool `json:"on_ice"`11
SeaTemp float64 `json:"sea_temp"`11
LandedAt time.Time `json:"landed_at"`12
Price apd.Decimal `json:"price"`12
Tag []byte `json:"tag"`13
OnIce bool `json:"on_ice"`13
Notes *string `json:"notes"`14
LandedOn time.Time `json:"landed_on"`15
LandedAt time.Time `json:"landed_at"`16
HarbourIP any `json:"harbour_ip"`17
Tag []byte `json:"tag"`18
Notes *string `json:"notes"`19
}14
}The program
example.godiffers
TimescaleDB
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
"time"8
"time"9
9
10
"github.com/cockroachdb/apd/v3"10
"seagull.sh/seagull/examples/sqlite/005_type_mapping/internal/store"11
"github.com/google/uuid"12
13
"seagull.sh/seagull/examples/postgres/005_type_mapping/internal/store"14
)11
)15
12
16
// 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 for17
// each column of db/migrations. PostgreSQL's types are exact, so each maps by name:14
// each column. SQLite stores every value as one of five storage classes, so Seagull maps18
//15
// the type each column declares in db/migrations. SMALLINT, INTEGER and BIGINT all have19
// - SMALLINT, INTEGER and BIGINT give int16, int32 and int64;16
// INTEGER affinity, which SQLite stores in up to eight bytes whatever width the column20
// - REAL is single precision and gives float32, while DOUBLE PRECISION gives float64;17
// declares, so each gives int64, as the INTEGER PRIMARY KEY rowid does. REAL gives21
// - NUMERIC gives apd.Decimal from github.com/cockroachdb/apd/v3, which keeps money18
// float64, and DATETIME gives time.Time, which the driver parses back from the text it22
// exact, and UUID gives uuid.UUID from github.com/google/uuid;19
// stored. SQLite has no exact decimal type, so money is best kept as INTEGER pence; a23
// - DATE and TIMESTAMPTZ give time.Time, and BYTEA gives []byte;20
// DECIMAL column maps to apd.Decimal from github.com/cockroachdb/apd/v3.24
// - a type Seagull has no mapping for, such as INET, gives any, and the driver decides25
// what it holds: pgx's database/sql driver hands back the address as a string. Give26
// such a column a go_type override to pin it down.27
//21
//28
// Takes database (*sql.DB) which is a migrated database.22
// Takes database (*sql.DB) which is a migrated database.29
// Takes output (io.Writer) which receives one line per column.23
// Takes output (io.Writer) which receives one line per column.30
//24
//31
// Returns error when a query fails.25
// Returns error when a query fails.32
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 {33
queries := store.New(database)27
queries := store.New(database)34
28
35
recorded, err := queries.RecordLanding(ctx, store.RecordLandingParams{29
recorded, err := queries.RecordLanding(ctx, store.RecordLandingParams{36
CatchID: uuid.MustParse("6f1c0b8e-4b2a-4c55-9d7e-2a3f5e8c1d40"),30
Species: "mackerel",37
Species: "mackerel",31
Boxes: 12,38
Boxes: 12,32
Fish: 480,39
Fish: 480,33
WeightG: 163_200,40
WeightG: 163_200,34
LengthCm: 31.5,41
LengthCm: 31.5,35
OnIce: true,42
SeaTemp: 12.25,36
LandedAt: time.Date(2026, time.May, 1, 5, 30, 0, 0, time.UTC),43
Price: *apd.New(41_280, -2),37
Tag: []byte{0x04, 0xa2, 0x19, 0xf3},44
OnIce: true,38
Notes: new("landed at Newlyn"),45
LandedOn: time.Date(2026, time.May, 1, 0, 0, 0, 0, time.UTC),46
LandedAt: time.Date(2026, time.May, 1, 5, 30, 0, 0, time.UTC),47
HarbourIP: "192.0.2.17",48
Tag: []byte{0x04, 0xa2, 0x19, 0xf3},49
Notes: new("landed at Newlyn"),50
})39
})51
if err != nil {40
if err != nil {52
return fmt.Errorf("recording a landing: %w", err)41
return fmt.Errorf("recording a landing: %w", err)53
}42
}54
43
55
landing, err := queries.GetLanding(ctx, recorded.ID)44
landing, err := queries.GetLanding(ctx, recorded.ID)56
if err != nil {45
if err != nil {57
return fmt.Errorf("getting landing %d: %w", recorded.ID, err)46
return fmt.Errorf("getting landing %d: %w", recorded.ID, err)58
}47
}59
fmt.Fprintf(output, "id %-12T %d\n", landing.ID, landing.ID)48
fmt.Fprintf(output, "id %-10T %d\n", landing.ID, landing.ID)60
fmt.Fprintf(output, "catch_id %-12T %s\n", landing.CatchID, landing.CatchID)49
fmt.Fprintf(output, "species %-10T %s\n", landing.Species, landing.Species)61
fmt.Fprintf(output, "species %-12T %s\n", landing.Species, landing.Species)50
fmt.Fprintf(output, "boxes %-10T %d\n", landing.Boxes, landing.Boxes)62
fmt.Fprintf(output, "boxes %-12T %d\n", landing.Boxes, landing.Boxes)51
fmt.Fprintf(output, "fish %-10T %d\n", landing.Fish, landing.Fish)63
fmt.Fprintf(output, "fish %-12T %d\n", landing.Fish, landing.Fish)52
fmt.Fprintf(output, "weight_g %-10T %d\n", landing.WeightG, landing.WeightG)64
fmt.Fprintf(output, "weight_g %-12T %d\n", landing.WeightG, landing.WeightG)53
fmt.Fprintf(output, "length_cm %-10T %.1f\n", landing.LengthCm, landing.LengthCm)65
fmt.Fprintf(output, "length_cm %-12T %.1f\n", landing.LengthCm, landing.LengthCm)54
fmt.Fprintf(output, "on_ice %-10T %t\n", landing.OnIce, landing.OnIce)66
fmt.Fprintf(output, "sea_temp %-12T %.2f\n", landing.SeaTemp, landing.SeaTemp)55
fmt.Fprintf(output, "landed_at %-10T %s\n", landing.LandedAt, landing.LandedAt.Format(time.RFC3339))67
fmt.Fprintf(output, "price %-12T %s\n", landing.Price, landing.Price.String())68
fmt.Fprintf(output, "on_ice %-12T %t\n", landing.OnIce, landing.OnIce)69
fmt.Fprintf(output, "landed_on %-12T %s\n", landing.LandedOn, landing.LandedOn.Format(time.DateOnly))70
// TIMESTAMPTZ arrives in the connection's time zone, so print it in UTC.71
fmt.Fprintf(output, "landed_at %-12T %s\n", landing.LandedAt, landing.LandedAt.UTC().Format(time.RFC3339))72
fmt.Fprintf(output, "harbour_ip %-12T %v\n", landing.HarbourIP, landing.HarbourIP)73
// %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.74
fmt.Fprintf(output, "tag %-12T % x\n", landing.Tag, landing.Tag)57
fmt.Fprintf(output, "tag %-10T % x\n", landing.Tag, landing.Tag)75
fmt.Fprintf(output, "notes %-12T %s\n", landing.Notes, *landing.Notes)58
fmt.Fprintf(output, "notes %-10T %s\n", landing.Notes, *landing.Notes)76
return nil59
return nil77
}60
}Go type differences
The same SQL types map to different Go types, because each database reports them differently.
| Query | Field | TimescaleDB | SQLite |
|---|---|---|---|
| RecordLanding | parameter boxes | int16 | int64 |
| RecordLanding | parameter fish | int32 | int64 |
| RecordLanding | parameter length_cm | float32 | float64 |
| GetLanding | column boxes | int16 | int64 |
| GetLanding | column fish | int32 | int64 |
| GetLanding | column length_cm | float32 | float64 |