From SQL types to Go types: SQLite and MySQL
Every file of the SQLite and MySQL versions side by side, with the lines that differ marked. 4 parameter or column types differ in Go.
Configuration
sgl.yamldiffers
SQLite
MySQL
1
version: 11
version: 12
2
3
databases:3
databases:4
- name: landings4
- name: landings5
dialect: sqlite5
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
SQLite
MySQL
1
CREATE TABLE landings (1
CREATE TABLE landings (2
id INTEGER PRIMARY KEY,2
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,3
species TEXT NOT NULL,3
species VARCHAR(50) NOT NULL,4
boxes SMALLINT NOT NULL,4
boxes SMALLINT NOT NULL,5
fish INTEGER NOT NULL,5
fish INT NOT NULL,6
weight_g BIGINT NOT NULL,6
weight_g BIGINT NOT NULL,7
length_cm REAL NOT NULL,7
length_cm DOUBLE 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 BLOB NOT NULL,10
tag VARBINARY(16) NOT NULL,11
notes TEXT11
notes TEXT,12
berth TINYINT UNSIGNED NOT NULL,13
price_per_kg DECIMAL(6, 2) NOT NULL12
);14
);db/migrations/0001_landings.down.sqlidentical
SQLite
MySQL
1
DROP TABLE landings;1
DROP TABLE landings;Queries
RecordLandingdiffers
SQLite · db/queries/landings.sql
MySQL · db/queries/landings.sql
1
-- sgl.query(RecordLanding, one)1
-- sgl.query(RecordLanding, execresult)2
INSERT INTO landings (species, boxes, fish, weight_g, length_cm, 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
RETURNING id;4
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);GetLandingdiffers
SQLite · db/queries/landings.sql
MySQL · 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, 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 = ?;4
WHERE id = ?;What sgl generates
RecordLandingdiffers
SQLite
MySQL
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
Boxes int645
Boxes int166
Fish int646
Fish int327
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
}13
Berth uint814
14
PricePerKg string15
type RecordLandingRow struct {16
ID int64 `json:"id"`17
}15
}GetLandingdiffers
SQLite
MySQL
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 int64 `json:"boxes"`6
Boxes int16 `json:"boxes"`7
Fish int64 `json:"fish"`7
Fish int32 `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"`14
}16
}The program
example.godiffers
SQLite
MySQL
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/sqlite/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. SQLite stores every value as one of five storage classes, so Seagull maps14
// each column. Seagull maps the type each column declares in db/migrations: SMALLINT, INT15
// the type each column declares in db/migrations. SMALLINT, INTEGER and BIGINT all have15
// and BIGINT give int16, int32 and int64, and an UNSIGNED integer gives the unsigned Go16
// INTEGER affinity, which SQLite stores in up to eight bytes whatever width the column16
// type of the same width, so TINYINT UNSIGNED is uint8. DOUBLE gives float64, BOOLEAN17
// declares, so each gives int64, as the INTEGER PRIMARY KEY rowid does. REAL gives17
// (MySQL's TINYINT(1)) gives bool, DATETIME gives time.Time when the DSN sets18
// float64, and DATETIME gives time.Time, which the driver parses back from the text it18
// parseTime=true, and VARBINARY gives []byte.19
// stored. SQLite has no exact decimal type, so money is best kept as INTEGER pence; a20
// DECIMAL column maps to apd.Decimal from github.com/cockroachdb/apd/v3.21
//19
//20
// DECIMAL gives apd.Decimal from github.com/cockroachdb/apd/v3, which holds the exact21
// 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
//22
// Takes database (*sql.DB) which is a migrated database.24
// Takes database (*sql.DB) which is a migrated database.23
// Takes output (io.Writer) which receives one line per column.25
// Takes output (io.Writer) which receives one line per column.24
//26
//25
// Returns error when a query fails.27
// Returns error when a query fails.26
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 {27
queries := store.New(database)29
queries := store.New(database)28
30
29
recorded, err := queries.RecordLanding(ctx, store.RecordLandingParams{31
result, err := queries.RecordLanding(ctx, store.RecordLandingParams{30
Species: "mackerel",32
Species: "mackerel",31
Boxes: 12,33
Boxes: 12,32
Fish: 480,34
Fish: 480,33
WeightG: 163_200,35
WeightG: 163_200,34
LengthCm: 31.5,36
LengthCm: 31.5,35
OnIce: true,37
OnIce: true,36
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),37
Tag: []byte{0x04, 0xa2, 0x19, 0xf3},39
Tag: []byte{0x04, 0xa2, 0x19, 0xf3},38
Notes: new("landed at Newlyn"),40
Notes: new("landed at Newlyn"),41
Berth: 7,42
PricePerKg: "4.20",39
})43
})40
if err != nil {44
if err != nil {41
return fmt.Errorf("recording a landing: %w", err)45
return fmt.Errorf("recording a landing: %w", err)42
}46
}47
id, err := result.LastInsertId()48
if err != nil {49
return fmt.Errorf("reading the landing's id: %w", err)50
}43
51
44
landing, err := queries.GetLanding(ctx, recorded.ID)52
landing, err := queries.GetLanding(ctx, id)45
if err != nil {53
if err != nil {46
return fmt.Errorf("getting landing %d: %w", recorded.ID, err)54
return fmt.Errorf("getting landing %d: %w", id, err)47
}55
}48
fmt.Fprintf(output, "id %-10T %d\n", landing.ID, landing.ID)56
fmt.Fprintf(output, "id %-12T %d\n", landing.ID, landing.ID)49
fmt.Fprintf(output, "species %-10T %s\n", landing.Species, landing.Species)57
fmt.Fprintf(output, "species %-12T %s\n", landing.Species, landing.Species)50
fmt.Fprintf(output, "boxes %-10T %d\n", landing.Boxes, landing.Boxes)58
fmt.Fprintf(output, "boxes %-12T %d\n", landing.Boxes, landing.Boxes)51
fmt.Fprintf(output, "fish %-10T %d\n", landing.Fish, landing.Fish)59
fmt.Fprintf(output, "fish %-12T %d\n", landing.Fish, landing.Fish)52
fmt.Fprintf(output, "weight_g %-10T %d\n", landing.WeightG, landing.WeightG)60
fmt.Fprintf(output, "weight_g %-12T %d\n", landing.WeightG, landing.WeightG)53
fmt.Fprintf(output, "length_cm %-10T %.1f\n", landing.LengthCm, landing.LengthCm)61
fmt.Fprintf(output, "length_cm %-12T %.1f\n", landing.LengthCm, landing.LengthCm)54
fmt.Fprintf(output, "on_ice %-10T %t\n", landing.OnIce, landing.OnIce)62
fmt.Fprintf(output, "on_ice %-12T %t\n", landing.OnIce, landing.OnIce)55
fmt.Fprintf(output, "landed_at %-10T %s\n", landing.LandedAt, landing.LandedAt.Format(time.RFC3339))63
fmt.Fprintf(output, "landed_at %-12T %s\n", landing.LandedAt, landing.LandedAt.Format(time.RFC3339))56
// %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.57
fmt.Fprintf(output, "tag %-10T % x\n", landing.Tag, landing.Tag)65
fmt.Fprintf(output, "tag %-12T % x\n", landing.Tag, landing.Tag)58
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())59
return nil69
return nil60
}70
}Go type differences
The same SQL types map to different Go types, because each database reports them differently.
| Query | Field | SQLite | MySQL |
|---|---|---|---|
| RecordLanding | parameter boxes | int64 | int16 |
| RecordLanding | parameter fish | int64 | int32 |
| GetLanding | column boxes | int64 | int16 |
| GetLanding | column fish | int64 | int32 |