From SQL types to Go types: TimescaleDB and MariaDB
Every file of the TimescaleDB and MariaDB versions side by side, with the lines that differ marked. 2 parameter or column types differ in Go.
Configuration
sgl.yamldiffers
TimescaleDB
MariaDB
1
version: 11
version: 12
2
3
databases:3
databases:4
- name: landings4
- name: landings5
dialect: postgres5
dialect: mysql6
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
MariaDB
1
CREATE TABLE landings (1
CREATE TABLE landings (2
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,2
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,3
catch_id UUID NOT NULL,3
species VARCHAR(50) NOT NULL,4
species TEXT NOT NULL,4
boxes SMALLINT NOT NULL,5
boxes SMALLINT NOT NULL,5
fish INT NOT NULL,6
fish INTEGER NOT NULL,6
weight_g BIGINT NOT NULL,7
weight_g BIGINT NOT NULL,7
length_cm DOUBLE 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 VARBINARY(16) NOT NULL,11
on_ice BOOLEAN NOT NULL,11
notes TEXT,12
landed_on DATE NOT NULL,12
berth TINYINT UNSIGNED NOT NULL,13
landed_at TIMESTAMPTZ NOT NULL,13
price_per_kg DECIMAL(6, 2) NOT NULL14
harbour_ip INET NOT NULL,15
tag BYTEA NOT NULL,16
notes TEXT17
);14
);db/migrations/0001_landings.down.sqlidentical
TimescaleDB
MariaDB
1
DROP TABLE landings;1
DROP TABLE landings;Queries
RecordLandingdiffers
TimescaleDB · db/queries/landings.sql
MariaDB · db/queries/landings.sql
1
-- sgl.query(RecordLanding, one)1
-- sgl.query(RecordLanding, execresult)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
-- ?11 as sgl.param(price_per_kg, type: text)3
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)3
INSERT INTO landings (species, boxes, fish, weight_g, length_cm, on_ice, landed_at, tag, notes, berth, price_per_kg)4
RETURNING id;4
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);GetLandingdiffers
TimescaleDB · db/queries/landings.sql
MariaDB · 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, notes, berth, price_per_kg3
FROM landings3
FROM landings4
WHERE id = $1;4
WHERE id = ?;What sgl generates
RecordLandingdiffers
TimescaleDB
MariaDB
1
func (queries *Queries) RecordLanding(ctx context.Context, params RecordLandingParams) (RecordLandingRow, error)1
func (queries *Queries) RecordLanding(ctx context.Context, params RecordLandingParams) (sql.Result, error)2
2
3
type RecordLandingParams struct {3
type RecordLandingParams struct {4
CatchID uuid.UUID4
Species string5
Species string5
Boxes int166
Boxes int166
Fish int327
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.Time13
Berth uint814
LandedAt time.Time14
PricePerKg string15
HarbourIP any16
Tag []byte17
Notes *string18
}19
20
type RecordLandingRow struct {21
ID int64 `json:"id"`22
}15
}GetLandingdiffers
TimescaleDB
MariaDB
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 int16 `json:"boxes"`7
Boxes int16 `json:"boxes"`7
Fish int32 `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"`14
Berth uint8 `json:"berth"`15
LandedAt time.Time `json:"landed_at"`15
PricePerKg apd.Decimal `json:"price_per_kg"`16
HarbourIP any `json:"harbour_ip"`17
Tag []byte `json:"tag"`18
Notes *string `json:"notes"`19
}16
}The program
example.godiffers
TimescaleDB
MariaDB
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/mysql/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. Seagull maps the type each column declares in db/migrations: SMALLINT, INT15
// and BIGINT give int16, int32 and int64, and an UNSIGNED integer gives the unsigned Go16
// type of the same width, so TINYINT UNSIGNED is uint8. DOUBLE gives float64, BOOLEAN17
// (MySQL's TINYINT(1)) gives bool, DATETIME gives time.Time when the DSN sets18
// parseTime=true, and VARBINARY gives []byte.18
//19
//19
// - SMALLINT, INTEGER and BIGINT give int16, int32 and int64;20
// DECIMAL gives apd.Decimal from github.com/cockroachdb/apd/v3, which holds the exact20
// - REAL is single precision and gives float32, while DOUBLE PRECISION gives float64;21
// value. The insert binds the price as text instead, through sgl.param(price_per_kg,21
// - NUMERIC gives apd.Decimal from github.com/cockroachdb/apd/v3, which keeps money22
// type: text), and MySQL converts "4.20" to DECIMAL(6, 2) without rounding.22
// exact, and UUID gives uuid.UUID from github.com/google/uuid;23
// - DATE and TIMESTAMPTZ give time.Time, and BYTEA gives []byte;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
//23
//28
// Takes database (*sql.DB) which is a migrated database.24
// Takes database (*sql.DB) which is a migrated database.29
// Takes output (io.Writer) which receives one line per column.25
// Takes output (io.Writer) which receives one line per column.30
//26
//31
// Returns error when a query fails.27
// Returns error when a query fails.32
func run(ctx context.Context, database *sql.DB, output io.Writer) error {28
func run(ctx context.Context, database *sql.DB, output io.Writer) error {33
queries := store.New(database)29
queries := store.New(database)34
30
35
recorded, err := queries.RecordLanding(ctx, store.RecordLandingParams{31
result, err := queries.RecordLanding(ctx, store.RecordLandingParams{36
CatchID: uuid.MustParse("6f1c0b8e-4b2a-4c55-9d7e-2a3f5e8c1d40"),32
Species: "mackerel",37
Species: "mackerel",33
Boxes: 12,38
Boxes: 12,34
Fish: 480,39
Fish: 480,35
WeightG: 163_200,40
WeightG: 163_200,36
LengthCm: 31.5,41
LengthCm: 31.5,37
OnIce: true,42
SeaTemp: 12.25,38
LandedAt: time.Date(2026, time.May, 1, 5, 30, 0, 0, time.UTC),43
Price: *apd.New(41_280, -2),39
Tag: []byte{0x04, 0xa2, 0x19, 0xf3},44
OnIce: true,40
Notes: new("landed at Newlyn"),45
LandedOn: time.Date(2026, time.May, 1, 0, 0, 0, 0, time.UTC),41
Berth: 7,46
LandedAt: time.Date(2026, time.May, 1, 5, 30, 0, 0, time.UTC),42
PricePerKg: "4.20",47
HarbourIP: "192.0.2.17",48
Tag: []byte{0x04, 0xa2, 0x19, 0xf3},49
Notes: new("landed at Newlyn"),50
})43
})51
if err != nil {44
if err != nil {52
return fmt.Errorf("recording a landing: %w", err)45
return fmt.Errorf("recording a landing: %w", err)53
}46
}47
id, err := result.LastInsertId()48
if err != nil {49
return fmt.Errorf("reading the landing's id: %w", err)50
}54
51
55
landing, err := queries.GetLanding(ctx, recorded.ID)52
landing, err := queries.GetLanding(ctx, id)56
if err != nil {53
if err != nil {57
return fmt.Errorf("getting landing %d: %w", recorded.ID, err)54
return fmt.Errorf("getting landing %d: %w", id, err)58
}55
}59
fmt.Fprintf(output, "id %-12T %d\n", landing.ID, landing.ID)56
fmt.Fprintf(output, "id %-12T %d\n", landing.ID, landing.ID)60
fmt.Fprintf(output, "catch_id %-12T %s\n", landing.CatchID, landing.CatchID)57
fmt.Fprintf(output, "species %-12T %s\n", landing.Species, landing.Species)61
fmt.Fprintf(output, "species %-12T %s\n", landing.Species, landing.Species)58
fmt.Fprintf(output, "boxes %-12T %d\n", landing.Boxes, landing.Boxes)62
fmt.Fprintf(output, "boxes %-12T %d\n", landing.Boxes, landing.Boxes)59
fmt.Fprintf(output, "fish %-12T %d\n", landing.Fish, landing.Fish)63
fmt.Fprintf(output, "fish %-12T %d\n", landing.Fish, landing.Fish)60
fmt.Fprintf(output, "weight_g %-12T %d\n", landing.WeightG, landing.WeightG)64
fmt.Fprintf(output, "weight_g %-12T %d\n", landing.WeightG, landing.WeightG)61
fmt.Fprintf(output, "length_cm %-12T %.1f\n", landing.LengthCm, landing.LengthCm)65
fmt.Fprintf(output, "length_cm %-12T %.1f\n", landing.LengthCm, landing.LengthCm)62
fmt.Fprintf(output, "on_ice %-12T %t\n", landing.OnIce, landing.OnIce)66
fmt.Fprintf(output, "sea_temp %-12T %.2f\n", landing.SeaTemp, landing.SeaTemp)63
fmt.Fprintf(output, "landed_at %-12T %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.64
// %T prints []byte as []uint8, since byte is an alias for uint8.74
fmt.Fprintf(output, "tag %-12T % x\n", landing.Tag, landing.Tag)65
fmt.Fprintf(output, "tag %-12T % x\n", landing.Tag, landing.Tag)75
fmt.Fprintf(output, "notes %-12T %s\n", landing.Notes, *landing.Notes)66
fmt.Fprintf(output, "notes %-12T %s\n", landing.Notes, *landing.Notes)67
fmt.Fprintf(output, "berth %-12T %d\n", landing.Berth, landing.Berth)68
fmt.Fprintf(output, "price_per_kg %-12T %s\n", landing.PricePerKg, landing.PricePerKg.String())76
return nil69
return nil77
}70
}Go type differences
The same SQL types map to different Go types, because each database reports them differently.
| Query | Field | TimescaleDB | MariaDB |
|---|---|---|---|
| RecordLanding | parameter length_cm | float32 | float64 |
| GetLanding | column length_cm | float32 | float64 |