Validated ORDER BY: ClickHouse and MariaDB
Every file of the ClickHouse and MariaDB versions side by side, with the lines that differ marked. 5 parameter or column types differ in Go.
Configuration
sgl.yamldiffers
ClickHouse
MariaDB
1
version: 11
version: 12
2
3
databases:3
databases:4
- name: regatta4
- name: regatta5
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_entries.up.sqldiffers
ClickHouse
MariaDB
1
CREATE TABLE entries (1
CREATE TABLE entries (2
id UInt64,2
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,3
boat String,3
boat VARCHAR(50) NOT NULL,4
crew UInt8,4
crew INT NOT NULL,5
finish_seconds UInt325
finish_seconds INT NOT NULL6
) ENGINE = MergeTree() ORDER BY id;6
);db/migrations/0001_entries.down.sqlidentical
ClickHouse
MariaDB
1
DROP TABLE entries;1
DROP TABLE entries;Queries
EnterBoatdiffers
ClickHouse · db/queries/entries.sql
MariaDB · db/queries/entries.sql
1
-- sgl.query(EnterBoat, exec)1
-- sgl.query(EnterBoat, exec)2
INSERT INTO entries (id, boat, crew, finish_seconds)2
INSERT INTO entries (boat, crew, finish_seconds) VALUES (?, ?, ?);3
VALUES ({id:UInt64}, {boat:String}, {crew:UInt8}, {finish_seconds:UInt32});ListEntriesidentical
ClickHouse · db/queries/entries.sql
MariaDB · db/queries/entries.sql
1
-- sgl.query(ListEntries, many)1
-- sgl.query(ListEntries, many)2
-- sgl.sortable(order_by, [boat, crew, finish_seconds])2
-- sgl.sortable(order_by, [boat, crew, finish_seconds])3
SELECT id, boat, crew, finish_seconds FROM entries3
SELECT id, boat, crew, finish_seconds FROM entriesWhat sgl generates
EnterBoatdiffers
ClickHouse
MariaDB
1
func (queries *Queries) EnterBoat(ctx context.Context, params EnterBoatParams) error1
func (queries *Queries) EnterBoat(ctx context.Context, params EnterBoatParams) error2
2
3
type EnterBoatParams struct {3
type EnterBoatParams struct {4
ID uint645
Boat string4
Boat string6
Crew uint85
Crew int327
FinishSeconds uint326
FinishSeconds int328
}7
}ListEntriesdiffers
ClickHouse
MariaDB
1
func (queries *Queries) ListEntries(ctx context.Context, params ListEntriesParams) ([]ListEntriesRow, error)1
func (queries *Queries) ListEntries(ctx context.Context, params ListEntriesParams) ([]ListEntriesRow, error)2
2
3
type ListEntriesParams struct {3
type ListEntriesParams struct {4
OrderBy ListEntriesOrderBy4
OrderBy ListEntriesOrderBy5
OrderByDirection OrderDirection5
OrderByDirection OrderDirection6
}6
}7
7
8
type ListEntriesRow struct {8
type ListEntriesRow struct {9
ID uint64 `json:"id"`9
ID int64 `json:"id"`10
Boat string `json:"boat"`10
Boat string `json:"boat"`11
Crew uint8 `json:"crew"`11
Crew int32 `json:"crew"`12
FinishSeconds uint32 `json:"finish_seconds"`12
FinishSeconds int32 `json:"finish_seconds"`13
}13
}14
14
15
type ListEntriesOrderBy string15
type ListEntriesOrderBy string16
16
17
type OrderDirection string17
type OrderDirection stringThe 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/012_sortable/internal/store"10
"seagull.sh/seagull/examples/mysql/012_sortable/internal/store"11
)11
)12
12
13
// run lists a regatta's entries in four orders. sgl.sortable(order_by, [boat, crew,13
// run lists a regatta's entries in four orders. sgl.sortable(order_by, [boat, crew,14
// finish_seconds]) has no placeholder: it gives ListEntriesParams an OrderBy field, typed14
// finish_seconds]) has no placeholder: it gives ListEntriesParams an OrderBy field, typed15
// with one constant per allowed column, and an OrderByDirection. The method appends ORDER15
// with one constant per allowed column, and an OrderByDirection. The method appends ORDER16
// BY only when OrderBy is one of the allowed columns, so a value that is not, such as raw16
// BY only when OrderBy is one of the allowed columns, so a value that is not, such as raw17
// text from a request, adds nothing to the SQL. A sortable column the query does not17
// text from a request, adds nothing to the SQL and the rows come back unsorted. A18
// select makes sgl vet report Q011.18
// sortable column the query does not select makes sgl vet report Q011.19
//20
// Without ORDER BY, ClickHouse returns rows in whatever order its parallel reads finish,21
// so the last listing prints only how many entries came back.22
//19
//23
// Takes database (*sql.DB) which is a migrated database.20
// Takes database (*sql.DB) which is a migrated database.24
// Takes output (io.Writer) which receives each listing.21
// Takes output (io.Writer) which receives each listing.25
//22
//26
// Returns error when a query fails.23
// Returns error when a query fails.27
func run(ctx context.Context, database *sql.DB, output io.Writer) error {24
func run(ctx context.Context, database *sql.DB, output io.Writer) error {28
queries := store.New(database)25
queries := store.New(database)29
26
30
entries := []store.EnterBoatParams{27
entries := []store.EnterBoatParams{31
{ID: 1, Boat: "Kittiwake", Crew: 4, FinishSeconds: 3725},28
{Boat: "Kittiwake", Crew: 4, FinishSeconds: 3725},32
{ID: 2, Boat: "Salty Chip", Crew: 3, FinishSeconds: 3410},29
{Boat: "Salty Chip", Crew: 3, FinishSeconds: 3410},33
{ID: 3, Boat: "Gull Wing", Crew: 5, FinishSeconds: 3990},30
{Boat: "Gull Wing", Crew: 5, FinishSeconds: 3990},34
{ID: 4, Boat: "Driftwood", Crew: 2, FinishSeconds: 3588},31
{Boat: "Driftwood", Crew: 2, FinishSeconds: 3588},35
}32
}36
for _, entry := range entries {33
for _, entry := range entries {37
if err := queries.EnterBoat(ctx, entry); err != nil {34
if err := queries.EnterBoat(ctx, entry); err != nil {38
return fmt.Errorf("entering %s: %w", entry.Boat, err)35
return fmt.Errorf("entering %s: %w", entry.Boat, err)39
}36
}40
}37
}41
38
42
orders := []struct {39
orders := []struct {43
label string40
label string44
parameters store.ListEntriesParams41
parameters store.ListEntriesParams45
}{42
}{46
{label: "results", parameters: store.ListEntriesParams{OrderBy: store.ListEntriesOrderByFinishSeconds, OrderByDirection: store.OrderAsc}},43
{label: "results", parameters: store.ListEntriesParams{OrderBy: store.ListEntriesOrderByFinishSeconds, OrderByDirection: store.OrderAsc}},47
{label: "by boat", parameters: store.ListEntriesParams{OrderBy: store.ListEntriesOrderByBoat, OrderByDirection: store.OrderAsc}},44
{label: "by boat", parameters: store.ListEntriesParams{OrderBy: store.ListEntriesOrderByBoat, OrderByDirection: store.OrderAsc}},48
{label: "largest crew first", parameters: store.ListEntriesParams{OrderBy: store.ListEntriesOrderByCrew, OrderByDirection: store.OrderDesc}},45
{label: "largest crew first", parameters: store.ListEntriesParams{OrderBy: store.ListEntriesOrderByCrew, OrderByDirection: store.OrderDesc}},46
{label: "raw text, ignored", parameters: store.ListEntriesParams{OrderBy: "boat; DROP TABLE entries", OrderByDirection: store.OrderAsc}},49
}47
}50
for _, order := range orders {48
for _, order := range orders {51
listed, err := queries.ListEntries(ctx, order.parameters)49
listed, err := queries.ListEntries(ctx, order.parameters)52
if err != nil {50
if err != nil {53
return fmt.Errorf("listing entries %s: %w", order.label, err)51
return fmt.Errorf("listing entries %s: %w", order.label, err)54
}52
}55
fmt.Fprintf(output, "%s:\n", order.label)53
fmt.Fprintf(output, "%s:\n", order.label)56
for _, entry := range listed {54
for _, entry := range listed {57
finish := time.Duration(entry.FinishSeconds) * time.Second55
finish := time.Duration(entry.FinishSeconds) * time.Second58
fmt.Fprintf(output, " %-10s crew %d %s\n", entry.Boat, entry.Crew, finish)56
fmt.Fprintf(output, " %-10s crew %d %s\n", entry.Boat, entry.Crew, finish)59
}57
}60
}58
}61
62
unsorted, err := queries.ListEntries(ctx, store.ListEntriesParams{OrderBy: "boat; DROP TABLE entries", OrderByDirection: store.OrderAsc})63
if err != nil {64
return fmt.Errorf("listing entries with raw text: %w", err)65
}66
fmt.Fprintf(output, "raw text, ignored: %d entries, in no set order\n", len(unsorted))67
return nil59
return nil68
}60
}Go type differences
The same SQL types map to different Go types, because each database reports them differently.
| Query | Field | ClickHouse | MariaDB |
|---|---|---|---|
| EnterBoat | parameter crew | uint8 | int32 |
| EnterBoat | parameter finish_seconds | uint32 | int32 |
| ListEntries | column id | uint64 | int64 |
| ListEntries | column crew | uint8 | int32 |
| ListEntries | column finish_seconds | uint32 | int32 |