Your first query: TimescaleDB and ClickHouse
Every file of the TimescaleDB and ClickHouse versions side by side, with the lines that differ marked. 3 parameter or column types differ in Go.
Configuration
sgl.yamldiffers
TimescaleDB
ClickHouse
1
version: 11
version: 12
2
3
databases:3
databases:4
- name: tasks4
- name: tasks5
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/sql12
doc_comments: true13
migrate:14
dsn: ${env:DATABASE_URL}Migrations
db/migrations/0001_init.up.sqldiffers
TimescaleDB
ClickHouse
1
CREATE TABLE tasks (1
CREATE TABLE tasks (2
id BIGSERIAL PRIMARY KEY,2
id UInt64,3
title TEXT NOT NULL,3
title String,4
completed BOOLEAN NOT NULL DEFAULT false,4
completed UInt8 DEFAULT 0,5
created_at TIMESTAMPTZ NOT NULL DEFAULT now()5
created_at DateTime DEFAULT now()6
);6
) ENGINE = MergeTree() ORDER BY id;7
8
COMMENT ON TABLE tasks IS 'Jobs to do around the harbour.';9
COMMENT ON COLUMN tasks.completed IS 'Whether the job is done.';db/migrations/0001_init.down.sqlidentical
TimescaleDB
ClickHouse
1
DROP TABLE tasks;1
DROP TABLE tasks;Queries
CreateTaskdiffers
TimescaleDB · db/queries/tasks.sql
ClickHouse · db/queries/tasks.sql
1
-- CreateTask adds a task and returns it with the id and creation time the database1
-- sgl.query(CreateTask, exec)2
-- gave it.2
INSERT INTO tasks (id, title) VALUES ({id:UInt64}, {title:String});3
-- sgl.query(CreateTask, one)4
INSERT INTO tasks (title) VALUES ($1) RETURNING id, title, completed, created_at;ListTasksdiffers
TimescaleDB · db/queries/tasks.sql
ClickHouse · db/queries/tasks.sql
1
-- ListTasks returns every task, oldest first.2
-- sgl.query(ListTasks, many)1
-- sgl.query(ListTasks, many)3
SELECT id, title, completed, created_at FROM tasks ORDER BY id;2
SELECT id, title, completed, created_at FROM tasks ORDER BY id;CompleteTaskdiffers
TimescaleDB · db/queries/tasks.sql
ClickHouse · db/queries/tasks.sql
1
-- CompleteTask marks a task done and returns how many tasks it changed.1
-- sgl.query(CompleteTask, asyncexec)2
-- sgl.query(CompleteTask, execrows)2
ALTER TABLE tasks UPDATE completed = 1 WHERE id = {id:UInt64};3
UPDATE tasks SET completed = true WHERE id = $1;GetTaskonly ClickHouse
TimescaleDB · not in this version
ClickHouse · db/queries/tasks.sql
1
-- sgl.query(GetTask, one)2
SELECT id, title, completed, created_at FROM tasks WHERE id = {id:UInt64};What sgl generates
CreateTaskdiffers
TimescaleDB
ClickHouse
1
func (queries *Queries) CreateTask(ctx context.Context, title string) (CreateTaskRow, error)1
func (queries *Queries) CreateTask(ctx context.Context, params CreateTaskParams) error2
2
3
type CreateTaskRow struct {3
type CreateTaskParams struct {4
ID int64 `json:"id"`4
ID uint645
Title string `json:"title"`5
Title string6
Completed bool `json:"completed"`7
CreatedAt time.Time `json:"created_at"`8
}6
}ListTasksdiffers
TimescaleDB
ClickHouse
1
func (queries *Queries) ListTasks(ctx context.Context) ([]ListTasksRow, error)1
func (queries *Queries) ListTasks(ctx context.Context) ([]ListTasksRow, error)2
2
3
type ListTasksRow struct {3
type ListTasksRow struct {4
ID int64 `json:"id"`4
ID uint64 `json:"id"`5
Title string `json:"title"`5
Title string `json:"title"`6
Completed bool `json:"completed"`6
Completed uint8 `json:"completed"`7
CreatedAt time.Time `json:"created_at"`7
CreatedAt time.Time `json:"created_at"`8
}8
}CompleteTaskdiffers
TimescaleDB
ClickHouse
1
func (queries *Queries) CompleteTask(ctx context.Context, id int64) (int64, error)1
func (queries *Queries) CompleteTask(ctx context.Context, id uint64) errorGetTaskonly ClickHouse
TimescaleDB · not in this version
ClickHouse
1
func (queries *Queries) GetTask(ctx context.Context, id uint64) (GetTaskRow, error)2
3
type GetTaskRow struct {4
ID uint64 `json:"id"`5
Title string `json:"title"`6
Completed uint8 `json:"completed"`7
CreatedAt time.Time `json:"created_at"`8
}The program
example.godiffers
TimescaleDB
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
8
9
"seagull.sh/seagull/examples/postgres/001_first_query/internal/store"9
"seagull.sh/seagull/examples/clickhouse/001_first_query/internal/store"10
)10
)11
11
12
// run adds two tasks, completes the first and lists them all, through the methods sgl12
// run adds two tasks, completes the first and lists them all, through the methods sgl13
// generated from db/queries/tasks.sql. The id column is BIGSERIAL, so ID is int64, and13
// generated from db/queries/tasks.sql.14
// created_at is TIMESTAMPTZ, so CreatedAt is a time.Time; the example leaves it out of15
// the output, as it changes on every run.16
//14
//17
// sgl.yaml sets go.doc_comments, so the comment above each query in tasks.sql becomes its15
// ClickHouse has no auto-increment and no RETURNING, so the caller chooses each task's id18
// method's doc comment, and the migration's COMMENT ON TABLE and COMMENT ON COLUMN become16
// and reads a task back with GetTask. CompleteTask uses ALTER TABLE ... UPDATE, which19
// the doc comments of the Tasks model and its Completed field: open internal/store to see17
// queues a mutation that the server applies in the background, so the query is declared20
// them, or hover over queries.CreateTask in an editor.18
// asyncexec and returns only an error (concept 36 explains why). The connection sets19
// mutations_sync=2, which makes the server finish the mutation before it replies, so the20
// list below already shows the task completed.21
//21
//22
// Takes database (*sql.DB) which is a migrated database, opened with pgx's database/sql22
// Takes database (*sql.DB) which is a migrated database.23
// driver.24
// Takes output (io.Writer) which receives the task list.23
// Takes output (io.Writer) which receives the task list.25
//24
//26
// Returns error when a query fails.25
// Returns error when a query fails.27
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 {28
queries := store.New(database)27
queries := store.New(database)29
28
30
feed, err := queries.CreateTask(ctx, "Feed the seagulls")29
tasks := []store.CreateTaskParams{31
if err != nil {30
{ID: 1, Title: "Feed the seagulls"},32
return fmt.Errorf("creating a task: %w", err)31
{ID: 2, Title: "Count the chips"},33
}32
}34
if _, err := queries.CreateTask(ctx, "Count the chips"); err != nil {33
for _, task := range tasks {35
return fmt.Errorf("creating a task: %w", err)34
if err := queries.CreateTask(ctx, task); err != nil {35
return fmt.Errorf("creating task %d: %w", task.ID, err)36
}36
}37
}37
38
38
completed, err := queries.CompleteTask(ctx, feed.ID)39
feed, err := queries.GetTask(ctx, 1)39
if err != nil {40
if err != nil {41
return fmt.Errorf("getting task 1: %w", err)42
}43
fmt.Fprintf(output, "created task %d, %s\n", feed.ID, feed.Title)44
45
if err := queries.CompleteTask(ctx, feed.ID); err != nil {40
return fmt.Errorf("completing task %d: %w", feed.ID, err)46
return fmt.Errorf("completing task %d: %w", feed.ID, err)41
}47
}42
fmt.Fprintf(output, "completed %d task\n", completed)48
fmt.Fprintf(output, "completed task %d\n", feed.ID)43
49
44
tasks, err := queries.ListTasks(ctx)50
listed, err := queries.ListTasks(ctx)45
if err != nil {51
if err != nil {46
return fmt.Errorf("listing tasks: %w", err)52
return fmt.Errorf("listing tasks: %w", err)47
}53
}48
for _, task := range tasks {54
for _, task := range listed {49
fmt.Fprintf(output, "%d\t%-18s completed=%t\n", task.ID, task.Title, task.Completed)55
fmt.Fprintf(output, "%d\t%-18s completed=%d\n", task.ID, task.Title, task.Completed)50
}56
}51
return nil57
return nil52
}58
}Go type differences
The same SQL types map to different Go types, because each database reports them differently.
| Query | Field | TimescaleDB | ClickHouse |
|---|---|---|---|
| ListTasks | column id | int64 | uint64 |
| ListTasks | column completed | bool | uint8 |
| CompleteTask | parameter id | int64 | uint64 |