From SQL types to Go types: MariaDB and DuckDB
Every file of the MariaDB and DuckDB versions side by side, with the lines that differ marked.
Configuration
sgl.yamldiffers
MariaDB
DuckDB
1
version: 11
version: 12
2
3
databases:3
databases:4
- name: landings4
- name: harbour5
dialect: mysql5
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_landings.up.sqldiffers
MariaDB
DuckDB
1
CREATE SEQUENCE landing_ids;2
1
CREATE TABLE landings (3
CREATE TABLE landings (2
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,4
id BIGINT PRIMARY KEY DEFAULT nextval('landing_ids'),3
species VARCHAR(50) NOT NULL,5
species VARCHAR NOT NULL,4
boxes SMALLINT NOT NULL,6
quay UTINYINT NOT NULL,5
fish INT NOT NULL,7
boxes SMALLINT NOT NULL,6
weight_g BIGINT NOT NULL,8
fish INTEGER NOT NULL,7
length_cm DOUBLE NOT NULL,9
weight_g BIGINT NOT NULL,8
on_ice BOOLEAN NOT NULL,10
length_cm DOUBLE NOT NULL,9
landed_at DATETIME NOT NULL,11
water_temp REAL NOT NULL,10
tag VARBINARY(16) NOT NULL,12
on_ice BOOLEAN NOT NULL,11
notes TEXT,13
landed_at TIMESTAMP NOT NULL,12
berth TINYINT UNSIGNED NOT NULL,14
landed_on DATE NOT NULL,13
price_per_kg DECIMAL(6, 2) NOT NULL15
catch_id UUID NOT NULL,16
auction_lot HUGEINT NOT NULL,17
tag BLOB NOT NULL,18
notes VARCHAR14
);19
);db/migrations/0001_landings.down.sqldiffers
MariaDB
DuckDB
1
DROP TABLE landings;1
DROP TABLE landings;2
DROP SEQUENCE landing_ids;Queries
RecordLandingdiffers
MariaDB · db/queries/landings.sql
DuckDB · db/queries/landings.sql
1
-- sgl.query(RecordLanding, execresult)1
-- sgl.query(RecordLanding, one)2
-- ?11 as sgl.param(price_per_kg, type: text)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)3
INSERT INTO landings (species, boxes, fish, weight_g, length_cm, on_ice, landed_at, tag, notes, berth, price_per_kg)3
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)4
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);4
RETURNING id;GetLandingdiffers
MariaDB · db/queries/landings.sql
DuckDB · db/queries/landings.sql
1
-- sgl.query(GetLanding, one)1
-- sgl.query(GetLanding, one)2
SELECT id, species, boxes, fish, weight_g, length_cm, on_ice, landed_at, tag, notes, berth, price_per_kg2
SELECT id, species, quay, boxes, fish, weight_g, length_cm, water_temp, on_ice, landed_at, landed_on, catch_id, auction_lot, tag, notes3
FROM landings3
FROM landings4
WHERE id = ?;4
WHERE id = $1;What sgl generates
RecordLandingdiffers
MariaDB
DuckDB
1
func (queries *Queries) RecordLanding(ctx context.Context, params RecordLandingParams) (sql.Result, 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 int166
Boxes int166
Fish int327
Fish int327
WeightG int648
WeightG int648
LengthCm float649
LengthCm float6410
WaterTemp float329
OnIce bool11
OnIce bool10
LandedAt time.Time12
LandedAt time.Time13
LandedOn time.Time14
CatchID uuid.UUID15
AuctionLot *big.Int11
Tag []byte16
Tag []byte12
Notes *string17
Notes *string13
Berth uint814
PricePerKg string15
}18
}19
20
type RecordLandingRow struct {21
ID int64 `json:"id"`22
}GetLandingdiffers
MariaDB
DuckDB
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
Boxes int16 `json:"boxes"`6
Quay uint8 `json:"quay"`7
Fish int32 `json:"fish"`7
Boxes int16 `json:"boxes"`8
WeightG int64 `json:"weight_g"`8
Fish int32 `json:"fish"`9
LengthCm float64 `json:"length_cm"`9
WeightG int64 `json:"weight_g"`10
OnIce bool `json:"on_ice"`10
LengthCm float64 `json:"length_cm"`11
LandedAt time.Time `json:"landed_at"`11
WaterTemp float32 `json:"water_temp"`12
Tag []byte `json:"tag"`12
OnIce bool `json:"on_ice"`13
Notes *string `json:"notes"`13
LandedAt time.Time `json:"landed_at"`14
Berth uint8 `json:"berth"`14
LandedOn time.Time `json:"landed_on"`15
PricePerKg apd.Decimal `json:"price_per_kg"`15
CatchID uuid.UUID `json:"catch_id"`16
AuctionLot *big.Int `json:"auction_lot"`17
Tag []byte `json:"tag"`18
Notes *string `json:"notes"`16
}19
}The program
example.godiffers
MariaDB
DuckDB
1
package main1
package main2
2
3
import (3
import (4
"context"4
"context"5
"database/sql"5
"database/sql"6
"errors"6
"fmt"7
"fmt"7
"io"8
"io"9
"math/big"8
"time"10
"time"9
11
10
"seagull.sh/seagull/examples/mysql/005_type_mapping/internal/store"12
"github.com/google/uuid"13
14
"seagull.sh/seagull/examples/duckdb/005_type_mapping/internal/store"11
)15
)12
16
13
// run records a landing of fish and reads it back, printing the Go type sgl chose for17
// run records a landing of fish and reads it back, printing the Go type sgl chose for14
// each column. Seagull maps the type each column declares in db/migrations: SMALLINT, INT18
// each column. DuckDB declares every column's type exactly, and Seagull maps each one:15
// 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.19
//19
//20
// DECIMAL gives apd.Decimal from github.com/cockroachdb/apd/v3, which holds the exact20
// - SMALLINT, INTEGER and BIGINT give int16, int32 and int64, and the unsigned UTINYINT21
// value. The insert binds the price as text instead, through sgl.param(price_per_kg,21
// gives uint8;22
// type: text), and MySQL converts "4.20" to DECIMAL(6, 2) without rounding.22
// - DOUBLE gives float64, but REAL is DuckDB's 4-byte float, so it gives float32;23
// - TIMESTAMP and DATE give time.Time, and UUID gives uuid.UUID from24
// github.com/google/uuid;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.23
//28
//24
// Takes database (*sql.DB) which is a migrated database.29
// Takes database (*sql.DB) which is a migrated database.25
// Takes output (io.Writer) which receives one line per column.30
// Takes output (io.Writer) which receives one line per column.26
//31
//27
// Returns error when a query fails.32
// Returns error when a query fails.28
func run(ctx context.Context, database *sql.DB, output io.Writer) error {33
func run(ctx context.Context, database *sql.DB, output io.Writer) error {29
queries := store.New(database)34
queries := store.New(database)30
35
31
result, err := queries.RecordLanding(ctx, store.RecordLandingParams{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{32
Species: "mackerel",42
Species: "mackerel",43
Quay: 3,33
Boxes: 12,44
Boxes: 12,34
Fish: 480,45
Fish: 480,35
WeightG: 163_200,46
WeightG: 163_200,36
LengthCm: 31.5,47
LengthCm: 31.5,48
WaterTemp: 11.25,37
OnIce: true,49
OnIce: true,38
LandedAt: time.Date(2026, time.May, 1, 5, 30, 0, 0, time.UTC),50
LandedAt: time.Date(2026, time.May, 1, 5, 30, 0, 0, time.UTC),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,39
Tag: []byte{0x04, 0xa2, 0x19, 0xf3},54
Tag: []byte{0x04, 0xa2, 0x19, 0xf3},40
Notes: new("landed at Newlyn"),55
Notes: new("landed at Newlyn"),41
Berth: 7,42
PricePerKg: "4.20",43
})56
})44
if err != nil {57
if err != nil {45
return fmt.Errorf("recording a landing: %w", err)58
return fmt.Errorf("recording a landing: %w", err)46
}59
}47
id, err := result.LastInsertId()48
if err != nil {49
return fmt.Errorf("reading the landing's id: %w", err)50
}51
60
52
landing, err := queries.GetLanding(ctx, id)61
landing, err := queries.GetLanding(ctx, recorded.ID)53
if err != nil {62
if err != nil {54
return fmt.Errorf("getting landing %d: %w", id, err)63
return fmt.Errorf("getting landing %d: %w", recorded.ID, err)55
}64
}56
fmt.Fprintf(output, "id %-12T %d\n", landing.ID, landing.ID)65
fmt.Fprintf(output, "id %-10T %d\n", landing.ID, landing.ID)57
fmt.Fprintf(output, "species %-12T %s\n", landing.Species, landing.Species)66
fmt.Fprintf(output, "species %-10T %s\n", landing.Species, landing.Species)58
fmt.Fprintf(output, "boxes %-12T %d\n", landing.Boxes, landing.Boxes)67
fmt.Fprintf(output, "quay %-10T %d\n", landing.Quay, landing.Quay)59
fmt.Fprintf(output, "fish %-12T %d\n", landing.Fish, landing.Fish)68
fmt.Fprintf(output, "boxes %-10T %d\n", landing.Boxes, landing.Boxes)60
fmt.Fprintf(output, "weight_g %-12T %d\n", landing.WeightG, landing.WeightG)69
fmt.Fprintf(output, "fish %-10T %d\n", landing.Fish, landing.Fish)61
fmt.Fprintf(output, "length_cm %-12T %.1f\n", landing.LengthCm, landing.LengthCm)70
fmt.Fprintf(output, "weight_g %-10T %d\n", landing.WeightG, landing.WeightG)62
fmt.Fprintf(output, "on_ice %-12T %t\n", landing.OnIce, landing.OnIce)71
fmt.Fprintf(output, "length_cm %-10T %.1f\n", landing.LengthCm, landing.LengthCm)63
fmt.Fprintf(output, "landed_at %-12T %s\n", landing.LandedAt, landing.LandedAt.Format(time.RFC3339))72
fmt.Fprintf(output, "water_temp %-10T %.2f\n", landing.WaterTemp, landing.WaterTemp)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)64
// %T prints []byte as []uint8, since byte is an alias for uint8.78
// %T prints []byte as []uint8, since byte is an alias for uint8.65
fmt.Fprintf(output, "tag %-12T % x\n", landing.Tag, landing.Tag)79
fmt.Fprintf(output, "tag %-10T % x\n", landing.Tag, landing.Tag)66
fmt.Fprintf(output, "notes %-12T %s\n", landing.Notes, *landing.Notes)80
fmt.Fprintf(output, "notes %-10T %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())69
return nil81
return nil70
}82
}