From SQL types to Go types: ClickHouse and MariaDB
Every file of the ClickHouse and MariaDB versions side by side, with the lines that differ marked. 10 parameter or column types differ in Go.
Configuration
sgl.yamldiffers
ClickHouse
MariaDB
1
version: 11
version: 12
2
3
databases:3
databases:4
- name: landings4
- name: landings5
dialect: clickhouse5
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
ClickHouse
MariaDB
1
CREATE TABLE landings (1
CREATE TABLE landings (2
id UInt64,2
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,3
catch_id UUID,3
species VARCHAR(50) NOT NULL,4
species String,4
boxes SMALLINT NOT NULL,5
boxes UInt16,5
fish INT NOT NULL,6
fish UInt32,6
weight_g BIGINT NOT NULL,7
weight_g Int64,7
length_cm DOUBLE NOT NULL,8
length_cm Float32,8
on_ice BOOLEAN NOT NULL,9
price Decimal(9, 2),9
landed_at DATETIME NOT NULL,10
on_ice Bool,10
tag VARBINARY(16) NOT NULL,11
landed_at DateTime('UTC'),11
notes TEXT,12
tag FixedString(4),12
berth TINYINT UNSIGNED NOT NULL,13
notes Nullable(String)13
price_per_kg DECIMAL(6, 2) NOT NULL14
) ENGINE = MergeTree() ORDER BY id;14
);db/migrations/0001_landings.down.sqlidentical
ClickHouse
MariaDB
1
DROP TABLE landings;1
DROP TABLE landings;Queries
RecordLandingdiffers
ClickHouse · db/queries/landings.sql
MariaDB · db/queries/landings.sql
1
-- sgl.query(RecordLanding, exec)1
-- sgl.query(RecordLanding, execresult)2
INSERT INTO landings (id, catch_id, species, boxes, fish, weight_g, length_cm, price, on_ice, landed_at, tag, notes)2
-- ?11 as sgl.param(price_per_kg, type: text)3
VALUES (3
INSERT INTO landings (species, boxes, fish, weight_g, length_cm, on_ice, landed_at, tag, notes, berth, price_per_kg)4
{id:UInt64},4
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);5
toUUID({catch_id:String}),6
{species:String},7
{boxes:UInt16},8
{fish:UInt32},9
{weight_g:Int64},10
{length_cm:Float32},11
toDecimal32({price:String}, 2),12
{on_ice:Bool},13
{landed_at:DateTime('UTC')},14
{tag:FixedString(4)},15
{notes:Nullable(String)}16
);GetLandingdiffers
ClickHouse · 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, price, on_ice, landed_at, 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 = {id:UInt64};4
WHERE id = ?;What sgl generates
RecordLandingdiffers
ClickHouse
MariaDB
1
func (queries *Queries) RecordLanding(ctx context.Context, params RecordLandingParams) error1
func (queries *Queries) RecordLanding(ctx context.Context, params RecordLandingParams) (sql.Result, error)2
2
3
type RecordLandingParams struct {3
type RecordLandingParams struct {4
ID uint644
Species string5
CatchID string5
Boxes int166
Species string6
Fish int327
Boxes uint167
WeightG int648
Fish uint328
LengthCm float649
WeightG int649
OnIce bool10
LengthCm float3210
LandedAt time.Time11
Price string11
Tag []byte12
OnIce bool12
Notes *string13
LandedAt time.Time13
Berth uint814
Tag string14
PricePerKg string15
Notes *string16
}15
}GetLandingdiffers
ClickHouse
MariaDB
1
func (queries *Queries) GetLanding(ctx context.Context, id uint64) (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 uint64 `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 uint16 `json:"boxes"`7
Fish int32 `json:"fish"`8
Fish uint32 `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
Price decimal.Decimal `json:"price"`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
Tag string `json:"tag"`14
Berth uint8 `json:"berth"`15
Notes *string `json:"notes"`15
PricePerKg apd.Decimal `json:"price_per_kg"`16
}16
}The program
example.godiffers
ClickHouse
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
"seagull.sh/seagull/examples/clickhouse/005_type_mapping/internal/store"10
"seagull.sh/seagull/examples/mysql/005_type_mapping/internal/store"11
)11
)12
12
13
// 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 for14
// each column. ClickHouse types are exact about width and sign, and Seagull follows them:14
// each column. Seagull maps the type each column declares in db/migrations: SMALLINT, INT15
// UInt16, UInt32 and UInt64 give uint16, uint32 and uint64, Int64 gives int64, Float3215
// and BIGINT give int16, int32 and int64, and an UNSIGNED integer gives the unsigned Go16
// gives float32, and Bool gives bool. DateTime('UTC') gives time.Time; the generated code16
// type of the same width, so TINYINT UNSIGNED is uint8. DOUBLE gives float64, BOOLEAN17
// sends a time as Unix seconds, so the server keeps its moment whatever the zone, and the17
// (MySQL's TINYINT(1)) gives bool, DATETIME gives time.Time when the DSN sets18
// column's time zone makes it read back in UTC. FixedString(4) and String both give18
// parseTime=true, and VARBINARY gives []byte.19
// string, which in ClickHouse holds any bytes, and Nullable(String) gives *string.20
//19
//21
// UUID gives uuid.UUID from github.com/google/uuid and Decimal(9, 2) gives20
// DECIMAL gives apd.Decimal from github.com/cockroachdb/apd/v3, which holds the exact22
// decimal.Decimal from github.com/shopspring/decimal, the types clickhouse-go scans them21
// value. The insert binds the price as text instead, through sgl.param(price_per_kg,23
// into. A {price:Decimal(9, 2)} placeholder would make the parameter a decimal.Decimal22
// type: text), and MySQL converts "4.20" to DECIMAL(6, 2) without rounding.24
// too; this example passes the price and catch id as text and converts them in SQL with25
// toDecimal32 and toUUID, so it needs nothing beyond the standard library.26
//23
//27
// Takes database (*sql.DB) which is a migrated database.24
// Takes database (*sql.DB) which is a migrated database.28
// Takes output (io.Writer) which receives one line per column.25
// Takes output (io.Writer) which receives one line per column.29
//26
//30
// Returns error when a query fails.27
// Returns error when a query fails.31
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 {32
queries := store.New(database)29
queries := store.New(database)33
30
34
if err := queries.RecordLanding(ctx, store.RecordLandingParams{31
result, err := queries.RecordLanding(ctx, store.RecordLandingParams{35
ID: 1,32
Species: "mackerel",36
CatchID: "5f0c6c7e-8d0e-4b77-9a51-2c4a6f9e3b10",33
Boxes: 12,37
Species: "mackerel",34
Fish: 480,38
Boxes: 12,35
WeightG: 163_200,39
Fish: 480,36
LengthCm: 31.5,40
WeightG: 163_200,37
OnIce: true,41
LengthCm: 31.5,38
LandedAt: time.Date(2026, time.May, 1, 5, 30, 0, 0, time.UTC),42
Price: "412.50",39
Tag: []byte{0x04, 0xa2, 0x19, 0xf3},43
OnIce: true,40
Notes: new("landed at Newlyn"),44
LandedAt: time.Date(2026, time.May, 1, 5, 30, 0, 0, time.UTC),41
Berth: 7,45
Tag: string([]byte{0x04, 0xa2, 0x19, 0xf3}),42
PricePerKg: "4.20",46
Notes: new("landed at Newlyn"),43
})47
}); err != nil {44
if err != nil {48
return fmt.Errorf("recording a landing: %w", err)45
return fmt.Errorf("recording a landing: %w", err)49
}46
}47
id, err := result.LastInsertId()48
if err != nil {49
return fmt.Errorf("reading the landing's id: %w", err)50
}50
51
51
landing, err := queries.GetLanding(ctx, 1)52
landing, err := queries.GetLanding(ctx, id)52
if err != nil {53
if err != nil {53
return fmt.Errorf("getting landing 1: %w", err)54
return fmt.Errorf("getting landing %d: %w", id, err)54
}55
}55
fmt.Fprintf(output, "id %-16T %d\n", landing.ID, landing.ID)56
fmt.Fprintf(output, "id %-12T %d\n", landing.ID, landing.ID)56
fmt.Fprintf(output, "catch_id %-16T %s\n", landing.CatchID, landing.CatchID)57
fmt.Fprintf(output, "species %-12T %s\n", landing.Species, landing.Species)57
fmt.Fprintf(output, "species %-16T %s\n", landing.Species, landing.Species)58
fmt.Fprintf(output, "boxes %-12T %d\n", landing.Boxes, landing.Boxes)58
fmt.Fprintf(output, "boxes %-16T %d\n", landing.Boxes, landing.Boxes)59
fmt.Fprintf(output, "fish %-12T %d\n", landing.Fish, landing.Fish)59
fmt.Fprintf(output, "fish %-16T %d\n", landing.Fish, landing.Fish)60
fmt.Fprintf(output, "weight_g %-12T %d\n", landing.WeightG, landing.WeightG)60
fmt.Fprintf(output, "weight_g %-16T %d\n", landing.WeightG, landing.WeightG)61
fmt.Fprintf(output, "length_cm %-12T %.1f\n", landing.LengthCm, landing.LengthCm)61
fmt.Fprintf(output, "length_cm %-16T %.1f\n", landing.LengthCm, landing.LengthCm)62
fmt.Fprintf(output, "on_ice %-12T %t\n", landing.OnIce, landing.OnIce)62
fmt.Fprintf(output, "price %-16T %s\n", landing.Price, landing.Price.StringFixed(2))63
fmt.Fprintf(output, "landed_at %-12T %s\n", landing.LandedAt, landing.LandedAt.Format(time.RFC3339))63
fmt.Fprintf(output, "on_ice %-16T %t\n", landing.OnIce, landing.OnIce)64
// %T prints []byte as []uint8, since byte is an alias for uint8.64
fmt.Fprintf(output, "landed_at %-16T %s\n", landing.LandedAt, landing.LandedAt.Format(time.RFC3339))65
fmt.Fprintf(output, "tag %-12T % x\n", landing.Tag, landing.Tag)65
fmt.Fprintf(output, "tag %-16T % x\n", landing.Tag, landing.Tag)66
fmt.Fprintf(output, "notes %-12T %s\n", landing.Notes, *landing.Notes)66
fmt.Fprintf(output, "notes %-16T %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())67
return nil69
return nil68
}70
}Go type differences
The same SQL types map to different Go types, because each database reports them differently.
| Query | Field | ClickHouse | MariaDB |
|---|---|---|---|
| RecordLanding | parameter boxes | uint16 | int16 |
| RecordLanding | parameter fish | uint32 | int32 |
| RecordLanding | parameter length_cm | float32 | float64 |
| RecordLanding | parameter tag | string | []byte |
| GetLanding | parameter id | uint64 | int64 |
| GetLanding | column id | uint64 | int64 |
| GetLanding | column boxes | uint16 | int16 |
| GetLanding | column fish | uint32 | int32 |
| GetLanding | column length_cm | float32 | float64 |
| GetLanding | column tag | string | []byte |