Golang ClickHouse

Installation

go get github.com/uptrace/go-clickhouse@latest

Connecting to ClickHouse

To connect to ClickHouse:

import "github.com/uptrace/go-clickhouse/ch"

db := ch.Connect(
	// clickhouse://<user>:<password>@<host>:<port>/<database>?sslmode=disable
	ch.WithDSN("clickhouse://localhost:9000/default?sslmode=disable"),
)

To see the executed queries in the console, install chdebug query hook:

import "github.com/uptrace/go-clickhouse/chdebug"

db.AddQueryHook(chdebug.NewQueryHook(
	chdebug.WithVerbose(true),
	chdebug.FromEnv("CHDEBUG"),
))

Now you are ready to execute queries using database/sql API:

res, err := db.ExecContext(ctx, "SELECT 1")

var num int
err := db.QueryRowContext(ctx, "SELECT 1").Scan(&num)

Or using the query builder:

res, err := db.NewSelect().ColumnExpr("1").Exec(ctx)

var num int
err := db.NewSelect().ColumnExpr("1").Scan(ctx, &num)

Options

You can specify the following options in a DSN (connection string):

  • ?sslmode=verify-full - enable TLS.
  • ?sslmode=disable - disables TLS.
  • ?dial_timeout=5s - timeout for establishing new connections.
  • ?read_timeout=5s - timeout for socket reads.
  • ?write_timeout=5s - timeout for socket writes.
  • ?timeout=5s - sets all three timeouts described above.

go-clickhouse treats all unknown options as ClickHouse query settings, for example, ?prefer_column_name_to_alias=1 enables the corresponding ClickHouse settingopen in new window.

In addition to DSN, you can also use ch.Optionopen in new window to configure the client:

db := ch.Connect(
	ch.WithAddr("localhost:9000"),
	ch.WithTLSConfig(&tls.Config{InsecureSkipVerify: true}),
	ch.WithUser("test"),
	ch.WithPassword("test"),
	ch.WithDatabase("test"),
	ch.WithTimeout(5 * time.Second),
	ch.WithDialTimeout(5 * time.Second),
	ch.WithReadTimeout(5 * time.Second),
	ch.WithWriteTimeout(5 * time.Second),
	ch.WithQuerySettings(map[string]interface{}{
		"prefer_column_name_to_alias": 1,
	}),
)

Or use a DSN and options together:

db := ch.Connect(
	ch.WithDSN("clickhouse://default:@localhost:9000/uptrace?sslmode=verify-full"),
	ch.WithTLSConfig(tlsConfig),
)

Server timezone

You can avoid a lot of confusion by configuring ClickHouse to use UTC timezoneopen in new window by changing config.xml:

<?xml version="1.0" ?>
<clickhouse>
  <timezone>UTC</timezone>
</clickhouse>

Models

go-clickhouse uses struct-based models to build queries and scan results. A typical model looks like this:

type Span struct {
	ch.CHModel `ch:"partition:toYYYYMM(time)"`

	ID   uint64
	Text string    `ch:",lc"` // low cardinality column
	Time time.Time `ch:",pk"` // ClickHouse primary key to be used in order by
}

Having a model, you can create and drop tables:

// Create spans table.
res, err := db.NewCreateTable().Model((*Span)(nil)).Exec(ctx)

// Drop spans table.
res, err := db.NewDropTable().Model((*Span)(nil)).Exec(ctx)

// Drop table if they exist and create new tables.
err := db.ResetModel(ctx, (*Model1)(nil), (*Model2)(nil))

Insert rows:

// Insert a single span.
span := &Span{Name: "admin"}
res, err := db.NewInsert().Model(span).Exec(ctx)

// Insert multiple spans (bulk-insert).
spans := []Span{span1, span2}
res, err := db.NewInsert().Model(&spans).Exec(ctx)

And select rows scanning the results:

// Select a span by an id.
span := new(Span)
err := db.NewSelect().Model(span).Where("id = ?", 1).Scan(ctx)

// Select first 10 spans.
var spans []Span
err := db.NewSelect().Model(&spans).OrderExpr("id ASC").Limit(10).Scan(ctx)

Scanning query results

When it comes to scanning query results, go-clickhouse is very flexible and allows scanning into structs:

span := new(Span)
err := db.NewSelect().Model(span).Limit(1).Scan(ctx)

Into scalars:

var id int64
var name string
err := db.NewSelect().Model((*Span)(nil)).Column("id", "name").Limit(1).Scan(ctx, &id, &name)

Into a map[string]interface{}:

var m map[string]interface{}
err := db.NewSelect().Model((*Span)(nil)).Limit(1).Scan(ctx, &m)

And into slices of the types above:

var spans []Span
err := db.NewSelect().Model(&spans).Limit(1).Scan(ctx)

var ids []int64
var names []string
err := db.NewSelect().Model((*Span)(nil)).Column("id", "name").Limit(1).Scan(ctx, &ids, &names)

var ms []map[string]interface{}
err := db.NewSelect().Model((*Span)(nil)).Scan(ctx, &ms)

What's next

By now, you should have basic understanding of the API. Next, learn how to define models and write queries.