From SQL types to Go types: MySQL and SQLite
Every file of the MySQL and SQLite versions side by side, with the lines that differ marked. 4 parameter or column types differ in Go.
Configuration
sgl.yamldiffers
MySQL
SQLite
1
version: 11
version: 12
2
3
databases:3
databases:4
- name: landings4
- name: landings5
dialect: mysql5
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/storeMigrations
db/migrations/0001_landings.up.sqldiffers
MySQL
SQLite
1
CREATE TABLE landings (1
CREATE TABLE landings (2
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,2
id INTEGER PRIMARY KEY,3
species VARCHAR(50) NOT NULL,3
species TEXT NOT NULL,4
boxes SMALLINT NOT NULL,4
boxes SMALLINT NOT NULL,5
fish INT NOT NULL,5
fish INTEGER NOT NULL,6
weight_g BIGINT NOT NULL,6
weight_g BIGINT NOT NULL,7
length_cm DOUBLE NOT NULL,7
length_cm REAL NOT NULL,8
on_ice BOOLEAN NOT NULL,8
on_ice BOOLEAN NOT NULL,9
landed_at DATETIME NOT NULL,9
landed_at DATETIME NOT NULL,10
tag VARBINARY(16) NOT NULL,10
tag BLOB NOT NULL,11
notes TEXT,11
notes TEXT12
berth TINYINT UNSIGNED NOT NULL,13
price_per_kg DECIMAL(6, 2) NOT NULL14
);12
);db/migrations/0001_landings.down.sqlidentical
MySQL
SQLite
1
DROP TABLE landings;1
DROP TABLE landings;Queries
RecordLandingdiffers
MySQL · db/queries/landings.sql
SQLite · 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, boxes, fish, weight_g, length_cm, on_ice, landed_at, 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 (?, ?, ?, ?, ?, ?, ?, ?, ?)4
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);4
RETURNING id;GetLandingdiffers
MySQL · db/queries/landings.sql
SQLite · 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, boxes, fish, weight_g, length_cm, on_ice, landed_at, tag, notes3
FROM landings3
FROM landings4
WHERE id = ?;4
WHERE id = ?;What sgl generates
RecordLandingdiffers
MySQL
SQLite
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
Boxes int165
Boxes int646
Fish int326
Fish int647
WeightG int647
WeightG int648
LengthCm float648
LengthCm float649
OnIce bool9
OnIce bool10
LandedAt time.Time10
LandedAt time.Time11
Tag []byte11
Tag []byte12
Notes *string12
Notes *string13
Berth uint814
PricePerKg string15
}13
}14
15
type RecordLandingRow struct {16
ID int64 `json:"id"`17
}GetLandingdiffers
MySQL
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
Species string `json:"species"`5
Species string `json:"species"`6
Boxes int16 `json:"boxes"`6
Boxes int64 `json:"boxes"`7
Fish int32 `json:"fish"`7
Fish int64 `json:"fish"`8
WeightG int64 `json:"weight_g"`8
WeightG int64 `json:"weight_g"`9
LengthCm float64 `json:"length_cm"`9
LengthCm float64 `json:"length_cm"`10
OnIce bool `json:"on_ice"`10
OnIce bool `json:"on_ice"`11
LandedAt time.Time `json:"landed_at"`11
LandedAt time.Time `json:"landed_at"`12
Tag []byte `json:"tag"`12
Tag []byte `json:"tag"`13
Notes *string `json:"notes"`13
Notes *string `json:"notes"`14
Berth uint8 `json:"berth"`15
PricePerKg apd.Decimal `json:"price_per_kg"`16
}14
}The program
example.godiffers
MySQL
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
"seagull.sh/seagull/examples/mysql/005_type_mapping/internal/store"10
"seagull.sh/seagull/examples/sqlite/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. Seagull maps the type each column declares in db/migrations: SMALLINT, INT14
// each column. SQLite stores every value as one of five storage classes, so Seagull maps15
// and BIGINT give int16, int32 and int64, and an UNSIGNED integer gives the unsigned Go15
// the type each column declares in db/migrations. SMALLINT, INTEGER and BIGINT all have16
// type of the same width, so TINYINT UNSIGNED is uint8. DOUBLE gives float64, BOOLEAN16
// INTEGER affinity, which SQLite stores in up to eight bytes whatever width the column17
// (MySQL's TINYINT(1)) gives bool, DATETIME gives time.Time when the DSN sets17
// declares, so each gives int64, as the INTEGER PRIMARY KEY rowid does. REAL gives18
// parseTime=true, and VARBINARY gives []byte.18
// float64, and DATETIME gives time.Time, which the driver parses back from the text it19
//19
// stored. SQLite has no exact decimal type, so money is best kept as INTEGER pence; a20
// DECIMAL gives apd.Decimal from github.com/cockroachdb/apd/v3, which holds the exact20
// DECIMAL column maps to apd.Decimal from github.com/cockroachdb/apd/v3.21
// value. The insert binds the price as text instead, through sgl.param(price_per_kg,22
// type: text), and MySQL converts "4.20" to DECIMAL(6, 2) without rounding.23
//21
//24
// Takes database (*sql.DB) which is a migrated database.22
// Takes database (*sql.DB) which is a migrated database.25
// Takes output (io.Writer) which receives one line per column.23
// Takes output (io.Writer) which receives one line per column.26
//24
//27
// Returns error when a query fails.25
// Returns error when a query fails.28
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 {29
queries := store.New(database)27
queries := store.New(database)30
28
31
result, err := queries.RecordLanding(ctx, store.RecordLandingParams{29
recorded, err := queries.RecordLanding(ctx, store.RecordLandingParams{32
Species: "mackerel",30
Species: "mackerel",33
Boxes: 12,31
Boxes: 12,34
Fish: 480,32
Fish: 480,35
WeightG: 163_200,33
WeightG: 163_200,36
LengthCm: 31.5,34
LengthCm: 31.5,37
OnIce: true,35
OnIce: true,38
LandedAt: time.Date(2026, time.May, 1, 5, 30, 0, 0, time.UTC),36
LandedAt: time.Date(2026, time.May, 1, 5, 30, 0, 0, time.UTC),39
Tag: []byte{0x04, 0xa2, 0x19, 0xf3},37
Tag: []byte{0x04, 0xa2, 0x19, 0xf3},40
Notes: new("landed at Newlyn"),38
Notes: new("landed at Newlyn"),41
Berth: 7,42
PricePerKg: "4.20",43
})39
})44
if err != nil {40
if err != nil {45
return fmt.Errorf("recording a landing: %w", err)41
return fmt.Errorf("recording a landing: %w", err)46
}42
}47
id, err := result.LastInsertId()48
if err != nil {49
return fmt.Errorf("reading the landing's id: %w", err)50
}51
43
52
landing, err := queries.GetLanding(ctx, id)44
landing, err := queries.GetLanding(ctx, recorded.ID)53
if err != nil {45
if err != nil {54
return fmt.Errorf("getting landing %d: %w", id, err)46
return fmt.Errorf("getting landing %d: %w", recorded.ID, err)55
}47
}56
fmt.Fprintf(output, "id %-12T %d\n", landing.ID, landing.ID)48
fmt.Fprintf(output, "id %-10T %d\n", landing.ID, landing.ID)57
fmt.Fprintf(output, "species %-12T %s\n", landing.Species, landing.Species)49
fmt.Fprintf(output, "species %-10T %s\n", landing.Species, landing.Species)58
fmt.Fprintf(output, "boxes %-12T %d\n", landing.Boxes, landing.Boxes)50
fmt.Fprintf(output, "boxes %-10T %d\n", landing.Boxes, landing.Boxes)59
fmt.Fprintf(output, "fish %-12T %d\n", landing.Fish, landing.Fish)51
fmt.Fprintf(output, "fish %-10T %d\n", landing.Fish, landing.Fish)60
fmt.Fprintf(output, "weight_g %-12T %d\n", landing.WeightG, landing.WeightG)52
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)53
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)54
fmt.Fprintf(output, "on_ice %-10T %t\n", landing.OnIce, landing.OnIce)63
fmt.Fprintf(output, "landed_at %-12T %s\n", landing.LandedAt, landing.LandedAt.Format(time.RFC3339))55
fmt.Fprintf(output, "landed_at %-10T %s\n", landing.LandedAt, landing.LandedAt.Format(time.RFC3339))64
// %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.65
fmt.Fprintf(output, "tag %-12T % x\n", landing.Tag, landing.Tag)57
fmt.Fprintf(output, "tag %-10T % x\n", landing.Tag, landing.Tag)66
fmt.Fprintf(output, "notes %-12T %s\n", landing.Notes, *landing.Notes)58
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 nil59
return nil70
}60
}Go type differences
The same SQL types map to different Go types, because each database reports them differently.
| Query | Field | MySQL | SQLite |
|---|---|---|---|
| RecordLanding | parameter boxes | int16 | int64 |
| RecordLanding | parameter fish | int32 | int64 |
| GetLanding | column boxes | int16 | int64 |
| GetLanding | column fish | int32 | int64 |