Your first query: DuckDB and ClickHouse
Every file of the DuckDB and ClickHouse versions side by side, with the lines that differ marked. 3 parameter or column types differ in Go.
Configuration
sgl.yamldiffers
DuckDB
ClickHouse
1
version: 11
version: 12
2
3
databases:3
databases:4
- name: tasks4
- name: tasks5
dialect: duckdb5
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/storeMigrations
db/migrations/0001_init.up.sqldiffers
DuckDB
ClickHouse
1
CREATE SEQUENCE task_ids;2
3
CREATE TABLE tasks (1
CREATE TABLE tasks (4
id BIGINT PRIMARY KEY DEFAULT nextval('task_ids'),2
id UInt64,5
title VARCHAR NOT NULL,3
title String,6
completed BOOLEAN NOT NULL DEFAULT false,4
completed UInt8 DEFAULT 0,7
created_at TIMESTAMP NOT NULL DEFAULT current_timestamp5
created_at DateTime DEFAULT now()8
);6
) ENGINE = MergeTree() ORDER BY id;db/migrations/0001_init.down.sqldiffers
DuckDB
ClickHouse
1
DROP TABLE tasks;1
DROP TABLE tasks;2
DROP SEQUENCE task_ids;Queries
CreateTaskdiffers
DuckDB · db/queries/tasks.sql
ClickHouse · db/queries/tasks.sql
1
-- sgl.query(CreateTask, one)1
-- sgl.query(CreateTask, exec)2
INSERT INTO tasks (title) VALUES ($1) RETURNING id, title, completed, created_at;2
INSERT INTO tasks (id, title) VALUES ({id:UInt64}, {title:String});ListTasksidentical
DuckDB · db/queries/tasks.sql
ClickHouse · db/queries/tasks.sql
1
-- sgl.query(ListTasks, many)1
-- sgl.query(ListTasks, many)2
SELECT id, title, completed, created_at FROM tasks ORDER BY id;2
SELECT id, title, completed, created_at FROM tasks ORDER BY id;CompleteTaskdiffers
DuckDB · db/queries/tasks.sql
ClickHouse · db/queries/tasks.sql
1
-- sgl.query(CompleteTask, execrows)1
-- sgl.query(CompleteTask, asyncexec)2
UPDATE tasks SET completed = true WHERE id = $1;2
ALTER TABLE tasks UPDATE completed = 1 WHERE id = {id:UInt64};GetTaskonly ClickHouse
DuckDB · 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
DuckDB
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
DuckDB
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
DuckDB
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
DuckDB · 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
DuckDB
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/duckdb/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.13
// generated from db/queries/tasks.sql.14
//14
//15
// DuckDB has no auto-incrementing INTEGER PRIMARY KEY, so the migration draws each id15
// ClickHouse has no auto-increment and no RETURNING, so the caller chooses each task's id16
// from a sequence with DEFAULT nextval('task_ids'), and RETURNING hands it back.16
// and reads a task back with GetTask. CompleteTask uses ALTER TABLE ... UPDATE, which17
// queues a mutation that the server applies in the background, so the query is declared18
// 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.17
//21
//18
// Takes database (*sql.DB) which is a migrated database.22
// Takes database (*sql.DB) which is a migrated database.19
// Takes output (io.Writer) which receives the task list.23
// Takes output (io.Writer) which receives the task list.20
//24
//21
// Returns error when a query fails.25
// Returns error when a query fails.22
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 {23
queries := store.New(database)27
queries := store.New(database)24
28
25
feed, err := queries.CreateTask(ctx, "Feed the seagulls")29
tasks := []store.CreateTaskParams{26
if err != nil {30
{ID: 1, Title: "Feed the seagulls"},27
return fmt.Errorf("creating a task: %w", err)31
{ID: 2, Title: "Count the chips"},28
}32
}29
if _, err := queries.CreateTask(ctx, "Count the chips"); err != nil {33
for _, task := range tasks {30
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
}31
}37
}32
38
33
completed, err := queries.CompleteTask(ctx, feed.ID)39
feed, err := queries.GetTask(ctx, 1)34
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 {35
return fmt.Errorf("completing task %d: %w", feed.ID, err)46
return fmt.Errorf("completing task %d: %w", feed.ID, err)36
}47
}37
fmt.Fprintf(output, "completed %d task\n", completed)48
fmt.Fprintf(output, "completed task %d\n", feed.ID)38
49
39
tasks, err := queries.ListTasks(ctx)50
listed, err := queries.ListTasks(ctx)40
if err != nil {51
if err != nil {41
return fmt.Errorf("listing tasks: %w", err)52
return fmt.Errorf("listing tasks: %w", err)42
}53
}43
for _, task := range tasks {54
for _, task := range listed {44
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)45
}56
}46
return nil57
return nil47
}58
}Go type differences
The same SQL types map to different Go types, because each database reports them differently.
| Query | Field | DuckDB | ClickHouse |
|---|---|---|---|
| ListTasks | column id | int64 | uint64 |
| ListTasks | column completed | bool | uint8 |
| CompleteTask | parameter id | int64 | uint64 |