Validated ORDER BY: CockroachDB and ClickHouse
Every file of the CockroachDB and ClickHouse versions side by side, with the lines that differ marked. 5 parameter or column types differ in Go.
Configuration
sgl.yamldiffers
CockroachDB
ClickHouse
1
version: 11
version: 12
2
3
databases:3
databases:4
- name: regatta4
- name: regatta5
dialect: postgres5
dialect: clickhouse6
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/store11
driver: database/sqlMigrations
db/migrations/0001_entries.up.sqldiffers
CockroachDB
ClickHouse
1
CREATE TABLE entries (1
CREATE TABLE entries (2
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,2
id UInt64,3
boat TEXT NOT NULL,3
boat String,4
crew BIGINT NOT NULL,4
crew UInt8,5
finish_seconds BIGINT NOT NULL5
finish_seconds UInt326
);6
) ENGINE = MergeTree() ORDER BY id;db/migrations/0001_entries.down.sqlidentical
CockroachDB
ClickHouse
1
DROP TABLE entries;1
DROP TABLE entries;Queries
EnterBoatdiffers
CockroachDB · db/queries/entries.sql
ClickHouse · db/queries/entries.sql
1
-- sgl.query(EnterBoat, exec)1
-- sgl.query(EnterBoat, exec)2
INSERT INTO entries (boat, crew, finish_seconds) VALUES ($1, $2, $3);2
INSERT INTO entries (id, boat, crew, finish_seconds)3
VALUES ({id:UInt64}, {boat:String}, {crew:UInt8}, {finish_seconds:UInt32});ListEntriesidentical
CockroachDB · 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])3
SELECT id, boat, crew, finish_seconds FROM entries3
SELECT id, boat, crew, finish_seconds FROM entriesWhat sgl generates
EnterBoatdiffers
CockroachDB
ClickHouse
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 uint644
Boat string5
Boat string5
Crew int646
Crew uint86
FinishSeconds int647
FinishSeconds uint327
}8
}ListEntriesdiffers
CockroachDB
ClickHouse
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 int64 `json:"id"`9
ID uint64 `json:"id"`10
Boat string `json:"boat"`10
Boat string `json:"boat"`11
Crew int64 `json:"crew"`11
Crew uint8 `json:"crew"`12
FinishSeconds int64 `json:"finish_seconds"`12
FinishSeconds uint32 `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
CockroachDB
ClickHouse
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/postgres/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, 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 and the rows come back in whatever order17
// text from a request, adds nothing to the SQL. A sortable column the query does not18
// the server reads them, here the order they were entered. A sortable column the query18
// select makes sgl vet report Q011.19
// does not select makes sgl vet report Q011.20
//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
//21
// Takes database (*sql.DB) which is a migrated database.23
// Takes database (*sql.DB) which is a migrated database.22
// Takes output (io.Writer) which receives each listing.24
// Takes output (io.Writer) which receives each listing.23
//25
//24
// Returns error when a query fails.26
// Returns error when a query fails.25
func run(ctx context.Context, database *sql.DB, output io.Writer) error {27
func run(ctx context.Context, database *sql.DB, output io.Writer) error {26
queries := store.New(database)28
queries := store.New(database)27
29
28
entries := []store.EnterBoatParams{30
entries := []store.EnterBoatParams{29
{Boat: "Kittiwake", Crew: 4, FinishSeconds: 3725},31
{ID: 1, Boat: "Kittiwake", Crew: 4, FinishSeconds: 3725},30
{Boat: "Salty Chip", Crew: 3, FinishSeconds: 3410},32
{ID: 2, Boat: "Salty Chip", Crew: 3, FinishSeconds: 3410},31
{Boat: "Gull Wing", Crew: 5, FinishSeconds: 3990},33
{ID: 3, Boat: "Gull Wing", Crew: 5, FinishSeconds: 3990},32
{Boat: "Driftwood", Crew: 2, FinishSeconds: 3588},34
{ID: 4, Boat: "Driftwood", Crew: 2, FinishSeconds: 3588},33
}35
}34
for _, entry := range entries {36
for _, entry := range entries {35
if err := queries.EnterBoat(ctx, entry); err != nil {37
if err := queries.EnterBoat(ctx, entry); err != nil {36
return fmt.Errorf("entering %s: %w", entry.Boat, err)38
return fmt.Errorf("entering %s: %w", entry.Boat, err)37
}39
}38
}40
}39
41
40
orders := []struct {42
orders := []struct {41
label string43
label string42
parameters store.ListEntriesParams44
parameters store.ListEntriesParams43
}{45
}{44
{label: "results", parameters: store.ListEntriesParams{OrderBy: store.ListEntriesOrderByFinishSeconds, OrderByDirection: store.OrderAsc}},46
{label: "results", parameters: store.ListEntriesParams{OrderBy: store.ListEntriesOrderByFinishSeconds, OrderByDirection: store.OrderAsc}},45
{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}},46
{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}},47
{label: "raw text, ignored", parameters: store.ListEntriesParams{OrderBy: "boat; DROP TABLE entries", OrderByDirection: store.OrderAsc}},48
}49
}49
for _, order := range orders {50
for _, order := range orders {50
listed, err := queries.ListEntries(ctx, order.parameters)51
listed, err := queries.ListEntries(ctx, order.parameters)51
if err != nil {52
if err != nil {52
return fmt.Errorf("listing entries %s: %w", order.label, err)53
return fmt.Errorf("listing entries %s: %w", order.label, err)53
}54
}54
fmt.Fprintf(output, "%s:\n", order.label)55
fmt.Fprintf(output, "%s:\n", order.label)55
for _, entry := range listed {56
for _, entry := range listed {56
finish := time.Duration(entry.FinishSeconds) * time.Second57
finish := time.Duration(entry.FinishSeconds) * time.Second57
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)58
}59
}59
}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))60
return nil67
return nil61
}68
}Go type differences
The same SQL types map to different Go types, because each database reports them differently.
| Query | Field | CockroachDB | ClickHouse |
|---|---|---|---|
| EnterBoat | parameter crew | int64 | uint8 |
| EnterBoat | parameter finish_seconds | int64 | uint32 |
| ListEntries | column id | int64 | uint64 |
| ListEntries | column crew | int64 | uint8 |
| ListEntries | column finish_seconds | int64 | uint32 |