From SQL types to Go types: DuckDB and MariaDB
Every file of the DuckDB and MariaDB versions side by side, with the lines that differ marked.
Configuration
sgl.yamldiffers
DuckDB
MariaDB
1
version: 11
version: 12
2
3
databases:3
databases:4
- name: harbour4
- name: landings5
dialect: duckdb5
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/storeMigrations
db/migrations/0001_landings.up.sqldiffers
DuckDB
MariaDB
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 BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,5
species VARCHAR NOT NULL,3
species VARCHAR(50) NOT NULL,6
quay UTINYINT NOT NULL,4
boxes SMALLINT NOT NULL,7
boxes SMALLINT NOT NULL,5
fish INT NOT NULL,8
fish INTEGER NOT NULL,6
weight_g BIGINT NOT NULL,9
weight_g BIGINT NOT NULL,7
length_cm DOUBLE 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 VARBINARY(16) NOT NULL,13
landed_at TIMESTAMP NOT NULL,11
notes TEXT,14
landed_on DATE NOT NULL,12
berth TINYINT UNSIGNED NOT NULL,15
catch_id UUID NOT NULL,13
price_per_kg DECIMAL(6, 2) NOT NULL16
auction_lot HUGEINT NOT NULL,17
tag BLOB NOT NULL,18
notes VARCHAR19
);14
);db/migrations/0001_landings.down.sqldiffers
DuckDB
MariaDB
1
DROP TABLE landings;1
DROP TABLE landings;2
DROP SEQUENCE landing_ids;Queries
RecordLandingdiffers
DuckDB · db/queries/landings.sql
MariaDB · db/queries/landings.sql
1
-- sgl.query(RecordLanding, one)1
-- sgl.query(RecordLanding, execresult)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
-- ?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
DuckDB · db/queries/landings.sql
MariaDB · 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, notes, berth, price_per_kg3
FROM landings3
FROM landings4
WHERE id = $1;4
WHERE id = ?;What sgl generates
RecordLandingdiffers
DuckDB
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
Species string4
Species string5
Quay uint86
Boxes int165
Boxes int167
Fish int326
Fish int328
WeightG int647
WeightG int649
LengthCm float648
LengthCm float6410
WaterTemp float3211
OnIce bool9
OnIce bool12
LandedAt time.Time10
LandedAt time.Time13
LandedOn time.Time14
CatchID uuid.UUID15
AuctionLot *big.Int16
Tag []byte11
Tag []byte17
Notes *string12
Notes *string18
}13
Berth uint819
14
PricePerKg string20
type RecordLandingRow struct {21
ID int64 `json:"id"`22
}15
}GetLandingdiffers
DuckDB
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
Species string `json:"species"`5
Species string `json:"species"`6
Quay uint8 `json:"quay"`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 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"`14
Berth uint8 `json:"berth"`15
CatchID uuid.UUID `json:"catch_id"`15
PricePerKg apd.Decimal `json:"price_per_kg"`16
AuctionLot *big.Int `json:"auction_lot"`17
Tag []byte `json:"tag"`18
Notes *string `json:"notes"`19
}16
}The program
example.godiffers
DuckDB
MariaDB
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/mysql/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. 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.19
//19
//20
// - SMALLINT, INTEGER and BIGINT give int16, int32 and int64, and the unsigned UTINYINT20
// DECIMAL gives apd.Decimal from github.com/cockroachdb/apd/v3, which holds the exact21
// gives uint8;21
// value. The insert binds the price as text instead, through sgl.param(price_per_kg,22
// - DOUBLE gives float64, but REAL is DuckDB's 4-byte float, so it gives float32;22
// type: text), and MySQL converts "4.20" to DECIMAL(6, 2) without rounding.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.28
//23
//29
// Takes database (*sql.DB) which is a migrated database.24
// Takes database (*sql.DB) which is a migrated database.30
// Takes output (io.Writer) which receives one line per column.25
// Takes output (io.Writer) which receives one line per column.31
//26
//32
// Returns error when a query fails.27
// Returns error when a query fails.33
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 {34
queries := store.New(database)29
queries := store.New(database)35
30
36
// The auction lot number has 20 digits, too many for int64 or uint64.31
result, err := queries.RecordLanding(ctx, store.RecordLandingParams{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{42
Species: "mackerel",32
Species: "mackerel",43
Quay: 3,44
Boxes: 12,33
Boxes: 12,45
Fish: 480,34
Fish: 480,46
WeightG: 163_200,35
WeightG: 163_200,47
LengthCm: 31.5,36
LengthCm: 31.5,48
WaterTemp: 11.25,49
OnIce: true,37
OnIce: true,50
LandedAt: time.Date(2026, time.May, 1, 5, 30, 0, 0, time.UTC),38
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,54
Tag: []byte{0x04, 0xa2, 0x19, 0xf3},39
Tag: []byte{0x04, 0xa2, 0x19, 0xf3},55
Notes: new("landed at Newlyn"),40
Notes: new("landed at Newlyn"),41
Berth: 7,42
PricePerKg: "4.20",56
})43
})57
if err != nil {44
if err != nil {58
return fmt.Errorf("recording a landing: %w", err)45
return fmt.Errorf("recording a landing: %w", err)59
}46
}47
id, err := result.LastInsertId()48
if err != nil {49
return fmt.Errorf("reading the landing's id: %w", err)50
}60
51
61
landing, err := queries.GetLanding(ctx, recorded.ID)52
landing, err := queries.GetLanding(ctx, id)62
if err != nil {53
if err != nil {63
return fmt.Errorf("getting landing %d: %w", recorded.ID, err)54
return fmt.Errorf("getting landing %d: %w", id, err)64
}55
}65
fmt.Fprintf(output, "id %-10T %d\n", landing.ID, landing.ID)56
fmt.Fprintf(output, "id %-12T %d\n", landing.ID, landing.ID)66
fmt.Fprintf(output, "species %-10T %s\n", landing.Species, landing.Species)57
fmt.Fprintf(output, "species %-12T %s\n", landing.Species, landing.Species)67
fmt.Fprintf(output, "quay %-10T %d\n", landing.Quay, landing.Quay)58
fmt.Fprintf(output, "boxes %-12T %d\n", landing.Boxes, landing.Boxes)68
fmt.Fprintf(output, "boxes %-10T %d\n", landing.Boxes, landing.Boxes)59
fmt.Fprintf(output, "fish %-12T %d\n", landing.Fish, landing.Fish)69
fmt.Fprintf(output, "fish %-10T %d\n", landing.Fish, landing.Fish)60
fmt.Fprintf(output, "weight_g %-12T %d\n", landing.WeightG, landing.WeightG)70
fmt.Fprintf(output, "weight_g %-10T %d\n", landing.WeightG, landing.WeightG)61
fmt.Fprintf(output, "length_cm %-12T %.1f\n", landing.LengthCm, landing.LengthCm)71
fmt.Fprintf(output, "length_cm %-10T %.1f\n", landing.LengthCm, landing.LengthCm)62
fmt.Fprintf(output, "on_ice %-12T %t\n", landing.OnIce, landing.OnIce)72
fmt.Fprintf(output, "water_temp %-10T %.2f\n", landing.WaterTemp, landing.WaterTemp)63
fmt.Fprintf(output, "landed_at %-12T %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.64
// %T prints []byte as []uint8, since byte is an alias for uint8.79
fmt.Fprintf(output, "tag %-10T % x\n", landing.Tag, landing.Tag)65
fmt.Fprintf(output, "tag %-12T % x\n", landing.Tag, landing.Tag)80
fmt.Fprintf(output, "notes %-10T %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())81
return nil69
return nil82
}70
}