Compare · 012

Validated ORDER BY: MariaDB and ClickHouse

Every file of the MariaDB and ClickHouse versions side by side, with the lines that differ marked. 5 parameter or column types differ in Go.

Configuration

sgl.yamldiffers
MariaDB
ClickHouse
1version: 1
1version: 1
2
2
3databases:
3databases:
4 - name: regatta
4 - name: regatta
5 dialect: mysql
5 dialect: clickhouse
6 migrations: db/migrations
6 migrations: db/migrations
7 queries: db/queries
7 queries: db/queries
8 go:
8 go:
9 package: store
9 package: store
10 out: internal/store
10 out: internal/store

Migrations

db/migrations/0001_entries.up.sqldiffers
MariaDB
ClickHouse
1CREATE TABLE entries (
1CREATE TABLE entries (
2 id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
2 id UInt64,
3 boat VARCHAR(50) NOT NULL,
3 boat String,
4 crew INT NOT NULL,
4 crew UInt8,
5 finish_seconds INT NOT NULL
5 finish_seconds UInt32
6);
6) ENGINE = MergeTree() ORDER BY id;
db/migrations/0001_entries.down.sqlidentical
MariaDB
ClickHouse
1DROP TABLE entries;
1DROP TABLE entries;

Queries

EnterBoatdiffers
MariaDB · db/queries/entries.sql
ClickHouse · db/queries/entries.sql
1-- sgl.query(EnterBoat, exec)
1-- sgl.query(EnterBoat, exec)
2INSERT INTO entries (boat, crew, finish_seconds) VALUES (?, ?, ?);
2INSERT INTO entries (id, boat, crew, finish_seconds)
3VALUES ({id:UInt64}, {boat:String}, {crew:UInt8}, {finish_seconds:UInt32});
ListEntriesidentical
MariaDB · db/queries/entries.sql
ClickHouse · 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])
3SELECT id, boat, crew, finish_seconds FROM entries
3SELECT id, boat, crew, finish_seconds FROM entries

What sgl generates

EnterBoatdiffers
MariaDB
ClickHouse
1func (queries *Queries) EnterBoat(ctx context.Context, params EnterBoatParams) error
1func (queries *Queries) EnterBoat(ctx context.Context, params EnterBoatParams) error
2
2
3type EnterBoatParams struct {
3type EnterBoatParams struct {
4 ID uint64
4 Boat string
5 Boat string
5 Crew int32
6 Crew uint8
6 FinishSeconds int32
7 FinishSeconds uint32
7}
8}
ListEntriesdiffers
MariaDB
ClickHouse
1func (queries *Queries) ListEntries(ctx context.Context, params ListEntriesParams) ([]ListEntriesRow, error)
1func (queries *Queries) ListEntries(ctx context.Context, params ListEntriesParams) ([]ListEntriesRow, error)
2
2
3type ListEntriesParams struct {
3type ListEntriesParams struct {
4 OrderBy ListEntriesOrderBy
4 OrderBy ListEntriesOrderBy
5 OrderByDirection OrderDirection
5 OrderByDirection OrderDirection
6}
6}
7
7
8type ListEntriesRow struct {
8type ListEntriesRow struct {
9 ID int64 `json:"id"`
9 ID uint64 `json:"id"`
10 Boat string `json:"boat"`
10 Boat string `json:"boat"`
11 Crew int32 `json:"crew"`
11 Crew uint8 `json:"crew"`
12 FinishSeconds int32 `json:"finish_seconds"`
12 FinishSeconds uint32 `json:"finish_seconds"`
13}
13}
14
14
15type ListEntriesOrderBy string
15type ListEntriesOrderBy string
16
16
17type OrderDirection string
17type OrderDirection string

The program

example.godiffers
MariaDB
ClickHouse
1package main
1package main
2
2
3import (
3import (
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/012_sortable/internal/store"
10 "seagull.sh/seagull/examples/clickhouse/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, typed
14// finish_seconds]) has no placeholder: it gives ListEntriesParams an OrderBy field, typed
15// with one constant per allowed column, and an OrderByDirection. The method appends ORDER
15// with one constant per allowed column, and an OrderByDirection. The method appends ORDER
16// BY only when OrderBy is one of the allowed columns, so a value that is not, such as raw
16// BY only when OrderBy is one of the allowed columns, so a value that is not, such as raw
17// text from a request, adds nothing to the SQL and the rows come back unsorted. A
17// text from a request, adds nothing to the SQL. A sortable column the query does not
18// sortable column the query does not select makes sgl vet report Q011.
18// select makes sgl vet report Q011.
19//
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//
20// Takes database (*sql.DB) which is a migrated database.
23// Takes database (*sql.DB) which is a migrated database.
21// Takes output (io.Writer) which receives each listing.
24// Takes output (io.Writer) which receives each listing.
22//
25//
23// Returns error when a query fails.
26// Returns error when a query fails.
24func run(ctx context.Context, database *sql.DB, output io.Writer) error {
27func run(ctx context.Context, database *sql.DB, output io.Writer) error {
25 queries := store.New(database)
28 queries := store.New(database)
26
29
27 entries := []store.EnterBoatParams{
30 entries := []store.EnterBoatParams{
28 {Boat: "Kittiwake", Crew: 4, FinishSeconds: 3725},
31 {ID: 1, Boat: "Kittiwake", Crew: 4, FinishSeconds: 3725},
29 {Boat: "Salty Chip", Crew: 3, FinishSeconds: 3410},
32 {ID: 2, Boat: "Salty Chip", Crew: 3, FinishSeconds: 3410},
30 {Boat: "Gull Wing", Crew: 5, FinishSeconds: 3990},
33 {ID: 3, Boat: "Gull Wing", Crew: 5, FinishSeconds: 3990},
31 {Boat: "Driftwood", Crew: 2, FinishSeconds: 3588},
34 {ID: 4, Boat: "Driftwood", Crew: 2, FinishSeconds: 3588},
32 }
35 }
33 for _, entry := range entries {
36 for _, entry := range entries {
34 if err := queries.EnterBoat(ctx, entry); err != nil {
37 if err := queries.EnterBoat(ctx, entry); err != nil {
35 return fmt.Errorf("entering %s: %w", entry.Boat, err)
38 return fmt.Errorf("entering %s: %w", entry.Boat, err)
36 }
39 }
37 }
40 }
38
41
39 orders := []struct {
42 orders := []struct {
40 label string
43 label string
41 parameters store.ListEntriesParams
44 parameters store.ListEntriesParams
42 }{
45 }{
43 {label: "results", parameters: store.ListEntriesParams{OrderBy: store.ListEntriesOrderByFinishSeconds, OrderByDirection: store.OrderAsc}},
46 {label: "results", parameters: store.ListEntriesParams{OrderBy: store.ListEntriesOrderByFinishSeconds, OrderByDirection: store.OrderAsc}},
44 {label: "by boat", parameters: store.ListEntriesParams{OrderBy: store.ListEntriesOrderByBoat, OrderByDirection: store.OrderAsc}},
47 {label: "by boat", parameters: store.ListEntriesParams{OrderBy: store.ListEntriesOrderByBoat, OrderByDirection: store.OrderAsc}},
45 {label: "largest crew first", parameters: store.ListEntriesParams{OrderBy: store.ListEntriesOrderByCrew, OrderByDirection: store.OrderDesc}},
48 {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}},
47 }
49 }
48 for _, order := range orders {
50 for _, order := range orders {
49 listed, err := queries.ListEntries(ctx, order.parameters)
51 listed, err := queries.ListEntries(ctx, order.parameters)
50 if err != nil {
52 if err != nil {
51 return fmt.Errorf("listing entries %s: %w", order.label, err)
53 return fmt.Errorf("listing entries %s: %w", order.label, err)
52 }
54 }
53 fmt.Fprintf(output, "%s:\n", order.label)
55 fmt.Fprintf(output, "%s:\n", order.label)
54 for _, entry := range listed {
56 for _, entry := range listed {
55 finish := time.Duration(entry.FinishSeconds) * time.Second
57 finish := time.Duration(entry.FinishSeconds) * time.Second
56 fmt.Fprintf(output, " %-10s crew %d %s\n", entry.Boat, entry.Crew, finish)
58 fmt.Fprintf(output, " %-10s crew %d %s\n", entry.Boat, entry.Crew, finish)
57 }
59 }
58 }
60 }
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))
59 return nil
67 return nil
60}
68}

Go type differences

The same SQL types map to different Go types, because each database reports them differently.

QueryFieldMariaDBClickHouse
EnterBoatparameter crewint32uint8
EnterBoatparameter finish_secondsint32uint32
ListEntriescolumn idint64uint64
ListEntriescolumn crewint32uint8
ListEntriescolumn finish_secondsint32uint32