clickhouse

package
v0.2.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 13, 2026 License: MIT Imports: 10 Imported by: 0

Documentation

Overview

Package clickhouse provides a ClickHouse dialect for drops.

The shape mirrors the drops/pg package — typed columns via Col[T], table declarations, fluent query builders, and a DB wrapper with the same Hook / Ping / Close / InTx surface — but the SQL it emits is ClickHouse-flavoured:

  • "?" positional placeholders (matches the clickhouse-go database/sql driver)
  • Engine-bound tables (CREATE TABLE … ENGINE = MergeTree() ORDER BY …)
  • Native CH types: Array(T), Nullable(T), LowCardinality(T), Decimal(P,S), Map(K,V), Tuple(...), Enum8/16, DateTime[64], fixed-width integers (UInt8/UInt64/Int8/Int64/…), UUID
  • Aggregates such as uniq, uniqExact, quantile, anyAgg

Driver-agnostic: like drops/pg, the package imports no concrete driver. The bundled drops/stdlib adapter works against any database/ sql-compatible ClickHouse driver — clickhouse-go's stdlib bridge is the standard choice:

import (
    _ "github.com/ClickHouse/clickhouse-go/v2"
    "github.com/bernardoforcillo/drops/clickhouse"
    "github.com/bernardoforcillo/drops/stdlib"
)

sqlDB, _ := sql.Open("clickhouse", "clickhouse://localhost:9000/default")
db := clickhouse.New(stdlib.New(sqlDB))

Templates (Timestamps, SoftDelete, Audit, UUIDPrimaryKey) provide reusable column groups — see template.go for the function-style pattern and mixin.go for the richer Mixin interface. ClickHouse has no foreign keys, so Audit emits plain scalar columns mirroring the target's type. Lifecycle hooks are limited to OnInsert (no builder-side UPDATE/DELETE); default filters on SelectBuilder honour Unscoped() for opt-out.

Entity[T] (see entity.go) binds a Go struct to a Table and exposes Create / CreateMany / Query — the narrow subset of CRUD that maps to ClickHouse's Insert + Select builders. Entity.Validate registers per-row validators that run before Create / CreateMany; on CreateMany the first failing row aborts the whole batch.

What this package does NOT try to mirror from drops/pg:

  • per-row UPDATE/DELETE (ClickHouse mutations are asynchronous and fundamentally different; use ALTER TABLE … UPDATE/DELETE via raw SQL when you need them)
  • ON CONFLICT (handled by engine choice, e.g. ReplacingMergeTree)
  • Foreign keys / referential integrity (ClickHouse has none)
  • Schema introspection and Push (planned)

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	ErrNoRows               = errors.New("drops/clickhouse: no rows in result set")
	ErrNoRowsToInsert       = errors.New("drops/clickhouse: INSERT with no rows")
	ErrReturningUnsupported = errors.New("drops/clickhouse: RETURNING is not supported by ClickHouse")
)

Sentinel errors for assertable failure modes.

View Source
var ErrEngineRequired = errors.New("drops/clickhouse: table has no Engine set; call .Engine(clickhouse.MergeTree()) before CreateTable")

ErrEngineRequired is returned by CreateTable when the target table doesn't yet have an Engine set. ClickHouse refuses CREATE TABLE without an ENGINE clause, so we fail fast and clearly.

View Source
var ErrInvalidIdentifier = errors.New("drops/clickhouse: invalid SQL identifier")

ErrInvalidIdentifier is returned when a table / database / column name fails validation. The decorating wrappers use fmt.Errorf with %w so errors.Is(err, ErrInvalidIdentifier) succeeds.

View Source
var Placeholder = drops.WithPlaceholder(func(int) string { return "?" })

Placeholder is the placeholder strategy used by the ClickHouse dialect — bare positional question marks, matching the clickhouse-go database/sql driver.

Functions

func Abs

func Abs(e any) drops.Expression

func And

func And(preds ...drops.Expression) drops.Expression

func AnyAgg

func AnyAgg(e drops.Expression) drops.Expression

AnyAgg returns an arbitrary value from the group (CH's `any`). Named AnyAgg to avoid colliding with Go's any keyword visually.

func AnyHeavy

func AnyHeavy(e drops.Expression) drops.Expression

func AnyLast

func AnyLast(e drops.Expression) drops.Expression

AnyLast / AnyHeavy variants.

func ArgMax

func ArgMax(value, by drops.Expression) drops.Expression

Argument-aware aggregates: argMax / argMin.

func ArgMin

func ArgMin(value, by drops.Expression) drops.Expression

func As

func As(e drops.Expression, alias string) drops.Expression

As renames an expression (for SELECT projections).

func Avg

func Between

func Between(left, low, high any) drops.Expression

func Coalesce

func Coalesce(args ...any) drops.Expression

func Count

func CountAll

func CountAll() drops.Expression

func CreateDatabase

func CreateDatabase(name string) drops.Expression

func CreateDatabaseIfNotExists

func CreateDatabaseIfNotExists(name string) drops.Expression

func CreateTable

func CreateTable(t *Table) drops.Expression

CreateTable returns a CREATE TABLE statement for t. It panics-via- expression: rendering builds a SQL fragment that ends up emitting a clearly-marked error string if the engine is missing, so a caller who forgets gets a loud failure at exec time rather than silent bad DDL. Use CreateTableErr if you want the engine check at build time.

Example

ExampleCreateTable shows the rendered DDL for a MergeTree table.

package main

import (
	"fmt"

	"github.com/bernardoforcillo/drops/clickhouse"
)

// Schema fixtures used by the godoc examples — picked from a small
// analytics-style table so the rendered SQL is recognisable.
var exEvents = clickhouse.NewTable("events")

func main() {
	sql, _ := clickhouse.ToSQL(clickhouse.CreateTableIfNotExists(exEvents))
	// trim for godoc readability — only the first two clauses
	end := 0
	for n := 0; n < 2; n++ {
		end = nthIndex(sql, "\n", end+1)
		if end < 0 {
			break
		}
	}
	fmt.Println(sql[:end])
}

// nthIndex returns the index of the nth occurrence of sep in s
// starting from start, or -1.
func nthIndex(s, sep string, start int) int {
	for i := start; i < len(s); i++ {
		if s[i] == sep[0] {
			return i
		}
	}
	return -1
}
Output:
CREATE TABLE IF NOT EXISTS "events" (
	"id" UUID,

func CreateTableErr

func CreateTableErr(t *Table) (drops.Expression, error)

CreateTableErr returns the DDL or ErrEngineRequired. Use it in migration tooling that wants a definite error rather than SQL that references a sentinel string.

func CreateTableIfNotExists

func CreateTableIfNotExists(t *Table) drops.Expression

CreateTableIfNotExists is the IF NOT EXISTS variant.

func DateDiff

func DateDiff(unit string, a, b any) drops.Expression

func DropDatabase

func DropDatabase(name string) drops.Expression

func DropDatabaseIfExists

func DropDatabaseIfExists(name string) drops.Expression

func DropTable

func DropTable(t *Table) drops.Expression

func DropTableIfExists

func DropTableIfExists(t *Table) drops.Expression

func Eq

func Eq(left, right any) drops.Expression

func Func

func Func(name string, args ...any) drops.Expression

Func is the escape hatch for any function not covered by helpers.

func GroupArray

func GroupArray(e drops.Expression) drops.Expression

GroupArray returns an array of all values in the group.

func GroupUniqArray

func GroupUniqArray(e drops.Expression) drops.Expression

GroupUniqArray is the deduplicated variant.

func Gt

func Gt(left, right any) drops.Expression

func Gte

func Gte(left, right any) drops.Expression

func ILike

func ILike(left, pattern any) drops.Expression

func IfNull

func IfNull(e, fallback any) drops.Expression

func In

func In(left any, values ...any) drops.Expression

func IsNotNull

func IsNotNull(e any) drops.Expression

func IsNull

func IsNull(e any) drops.Expression

func Length

func Length(e any) drops.Expression

func Like

func Like(left, pattern any) drops.Expression

func LoggerHook

func LoggerHook(log LoggerFunc, opts ...LoggerOptions) drops.Hook

LoggerHook re-exports drops.LoggerHook. See its documentation for behaviour and tuning options.

func Lower

func Lt

func Lt(left, right any) drops.Expression

func Lte

func Lte(left, right any) drops.Expression

func Max

func Min

func Ne

func Ne(left, right any) drops.Expression

func Not

func NotIn

func NotIn(left any, values ...any) drops.Expression

func Now

func Now() drops.Expression

func OptimizeTable

func OptimizeTable(t *Table, final bool) drops.Expression

OptimizeTable triggers a merge round; final=true emits FINAL so all data is collapsed into a single part (expensive on large tables).

func Or

func Or(preds ...drops.Expression) drops.Expression

func Quantile

func Quantile(level float64, e drops.Expression) drops.Expression

Quantile is the approximate quantile aggregate; level is in [0, 1].

clickhouse.Quantile(0.95, Latency)  // 95th percentile

func QuantileExact

func QuantileExact(level float64, e drops.Expression) drops.Expression

QuantileExact uses the exact algorithm; QuantileTiming is optimised for non-negative integers (latency in ms).

func QuantileTiming

func QuantileTiming(level float64, e drops.Expression) drops.Expression

func Round

func Round(e any) drops.Expression

func Sum

func ToDate

func ToDate(e any) drops.Expression

func ToDateTime

func ToDateTime(e any) drops.Expression

func ToSQL

func ToSQL(e drops.Expression) (string, []any)

ToSQL renders an Expression with the ClickHouse placeholder style. Use it when you need to inspect generated SQL outside of the builders (logging, snapshotting, tests).

func ToStartOfDay

func ToStartOfDay(e any) drops.Expression

func ToStartOfHour

func ToStartOfHour(e any) drops.Expression

func ToStartOfMinute

func ToStartOfMinute(e any) drops.Expression

func ToStartOfMonth

func ToStartOfMonth(e any) drops.Expression

func ToYYYYMM

func ToYYYYMM(e any) drops.Expression

func ToYYYYMMDD

func ToYYYYMMDD(e any) drops.Expression

func TruncateTable

func TruncateTable(t *Table) drops.Expression

func TruncateTableIfExists

func TruncateTableIfExists(t *Table) drops.Expression

func Uniq

Uniq is an approximate-distinct count (HyperLogLog).

func UniqExact

func UniqExact(e drops.Expression) drops.Expression

UniqExact is the exact distinct count.

func UniqHLL12

func UniqHLL12(e drops.Expression) drops.Expression

UniqHLL12 is the configurable HLL approximation.

func Upper

Types

type AuditCols added in v0.2.0

type AuditCols[T any] struct {
	CreatedBy *Col[T]
	UpdatedBy *Col[T]
}

AuditCols holds the typed handles created by Audit. The type parameter mirrors the type of the supplied identity column.

func Audit added in v0.2.0

func Audit[T any](t *Table, target *Col[T]) AuditCols[T]

Audit appends "createdBy" and "updatedBy" columns to t, mirroring target's SQL type. ClickHouse has no foreign-key enforcement, so the columns are plain scalars; the typed handles let queries still compare them against target safely.

type AuditMixin added in v0.2.0

type AuditMixin[T any] struct {
	Target *Col[T]
	Cols   AuditCols[T]
}

AuditMixin registers "createdBy" and "updatedBy" columns of the same SQL type as Target. ClickHouse has no foreign-key enforcement, so the columns are plain scalars.

func (*AuditMixin[T]) Apply added in v0.2.0

func (m *AuditMixin[T]) Apply(t *Table)

Apply implements Mixin.

type Col

type Col[T any] struct {
	*Column
}

Col is the typed handle for a column whose Go value type is T.

func Add

func Add[T any](t *Table, c *Col[T]) *Col[T]

Add registers c with t and returns it. Type inference keeps the *Col[T] handle typed.

var Events = clickhouse.NewTable("events")
var (
    EventID = clickhouse.Add(Events, clickhouse.UUID("id"))
    EventTS = clickhouse.Add(Events, clickhouse.DateTime("ts", "UTC"))
)
Example

ExampleAdd shows the canonical table+columns declaration pattern.

package main

import (
	"fmt"

	"github.com/bernardoforcillo/drops/clickhouse"
)

func main() {
	logs := clickhouse.NewTable("logs")
	id := clickhouse.Add(logs, clickhouse.UInt64("id"))
	msg := clickhouse.Add(logs, clickhouse.String("message").LowCardinality())
	fmt.Printf("%s, %s\n", id.Name(), msg.Name())
}
Output:
id, message

func Bool

func Bool(name string) *Col[bool]

func Custom

func Custom[T any](name, typeSQL string) *Col[T]

Custom creates a column with an arbitrary type literal — useful for IPv4/IPv6, AggregateFunction(...), or vendor types not covered here.

func Date

func Date(name string) *Col[time.Time]

func Date32

func Date32(name string) *Col[time.Time]

func DateTime

func DateTime(name, tz string) *Col[time.Time]

DateTime renders DateTime, or DateTime('TZ') when tz is non-empty.

func DateTime64

func DateTime64(name string, precision int, tz string) *Col[time.Time]

DateTime64 renders DateTime64(precision[, 'TZ']). precision is the sub-second precision (0..9).

func Decimal

func Decimal(name string, precision, scale int) *Col[string]

Decimal(precision, scale) — represented as string to preserve the full arbitrary-precision value.

func FixedString

func FixedString(name string, n int) *Col[string]

FixedString returns a FixedString(n) column. n must be > 0.

func Float32

func Float32(name string) *Col[float32]

func Float64

func Float64(name string) *Col[float64]

func Int8

func Int8(name string) *Col[int8]

func Int16

func Int16(name string) *Col[int16]

func Int32

func Int32(name string) *Col[int32]

func Int64

func Int64(name string) *Col[int64]

func JSON

func JSON(name string) *Col[json.RawMessage]

func String

func String(name string) *Col[string]

func UInt8

func UInt8(name string) *Col[uint8]

func UInt16

func UInt16(name string) *Col[uint16]

func UInt32

func UInt32(name string) *Col[uint32]

func UInt64

func UInt64(name string) *Col[uint64]

func UUID

func UUID(name string) *Col[string]

func (*Col[T]) Between

func (c *Col[T]) Between(lo, hi T) drops.Expression

func (*Col[T]) Codec

func (c *Col[T]) Codec(spec string) *Col[T]

Codec sets the CODEC(...) clause — e.g. Codec("ZSTD(3)"), Codec("Delta, LZ4"). Pass the inner text without parentheses.

func (*Col[T]) Comment

func (c *Col[T]) Comment(text string) *Col[T]

Comment attaches a free-form comment that ends up in the CREATE TABLE column definition.

func (*Col[T]) Default

func (c *Col[T]) Default(sqlExpr string) *Col[T]

Default sets a raw SQL default expression (e.g. "0", "now()", "'pending'", "uuidv4()"). DEFAULT expressions cannot be parameterised.

func (*Col[T]) Eq

func (c *Col[T]) Eq(v T) drops.Expression

func (*Col[T]) EqCol

func (c *Col[T]) EqCol(o *Col[T]) drops.Expression

func (*Col[T]) Expr

func (c *Col[T]) Expr(e drops.Expression) ColumnValue

Expr binds an arbitrary SQL expression to the column in an INSERT.

func (*Col[T]) Gt

func (c *Col[T]) Gt(v T) drops.Expression

func (*Col[T]) GtCol

func (c *Col[T]) GtCol(o *Col[T]) drops.Expression

func (*Col[T]) Gte

func (c *Col[T]) Gte(v T) drops.Expression

func (*Col[T]) GteCol

func (c *Col[T]) GteCol(o *Col[T]) drops.Expression

func (*Col[T]) ILike

func (c *Col[T]) ILike(pattern string) drops.Expression

func (*Col[T]) In

func (c *Col[T]) In(values ...T) drops.Expression

func (*Col[T]) IsNotNull

func (c *Col[T]) IsNotNull() drops.Expression

func (*Col[T]) IsNull

func (c *Col[T]) IsNull() drops.Expression

func (*Col[T]) Like

func (c *Col[T]) Like(pattern string) drops.Expression

func (*Col[T]) LowCardinality

func (c *Col[T]) LowCardinality() *Col[T]

LowCardinality wraps the column type in LowCardinality(...).

func (*Col[T]) Lt

func (c *Col[T]) Lt(v T) drops.Expression

func (*Col[T]) LtCol

func (c *Col[T]) LtCol(o *Col[T]) drops.Expression

func (*Col[T]) Lte

func (c *Col[T]) Lte(v T) drops.Expression

func (*Col[T]) LteCol

func (c *Col[T]) LteCol(o *Col[T]) drops.Expression

func (*Col[T]) Ne

func (c *Col[T]) Ne(v T) drops.Expression

func (*Col[T]) NeCol

func (c *Col[T]) NeCol(o *Col[T]) drops.Expression

func (*Col[T]) NotIn

func (c *Col[T]) NotIn(values ...T) drops.Expression

func (*Col[T]) Nullable

func (c *Col[T]) Nullable() *Col[T]

Nullable wraps the column type in Nullable(...).

func (*Col[T]) TTL

func (c *Col[T]) TTL(expr string) *Col[T]

TTL sets a per-column TTL expression.

func (*Col[T]) Val

func (c *Col[T]) Val(v T) ColumnValue

Val binds a typed value as the column's payload in an INSERT row.

type ColRef

type ColRef interface {
	drops.Expression
	// contains filtered or unexported methods
}

ColRef is implemented by *Column and *Col[T]. Use it where the value type doesn't matter (engine ORDER BY / PARTITION BY, index columns, SELECT projections).

type Column

type Column struct {
	// contains filtered or unexported fields
}

Column is the type-erased AST node for a column reference. Most user code holds a *Col[T] (returned by every type constructor) which embeds *Column and adds type-safe builder + operator methods.

func (*Column) As

func (c *Column) As(alias string) drops.Expression

As / Asc / Desc helpers.

func (*Column) Asc

func (c *Column) Asc() drops.Expression

func (*Column) Codec

func (c *Column) Codec() string

Codec returns the CODEC(...) clause, or empty.

func (*Column) Comment

func (c *Column) Comment() string

Comment returns the column comment, or empty.

func (*Column) DefaultSQL

func (c *Column) DefaultSQL() string

DefaultSQL returns the raw DEFAULT expression.

func (*Column) Desc

func (c *Column) Desc() drops.Expression

func (*Column) HasDefault

func (c *Column) HasDefault() bool

HasDefault reports whether a DEFAULT clause was set.

func (*Column) IsNullable

func (c *Column) IsNullable() bool

IsNullable reports whether the column was wrapped in Nullable().

func (*Column) Name

func (c *Column) Name() string

Name returns the column's unqualified identifier.

func (*Column) TTL

func (c *Column) TTL() string

TTL returns the per-column TTL expression, or empty.

func (*Column) Table

func (c *Column) Table() *Table

Table returns the table this column was registered with, or nil before registration.

func (*Column) Type

func (c *Column) Type() ColumnType

Type returns the column's SQL type.

func (*Column) WriteSQL

func (c *Column) WriteSQL(b *drops.Builder)

WriteSQL writes a qualified reference to the column.

type ColumnType

type ColumnType interface {
	TypeSQL() string
}

ColumnType describes the SQL type literal of a ClickHouse column — the bit that goes right after the column name in DDL.

func TypeArray

func TypeArray(inner ColumnType) ColumnType

TypeArray wraps an inner type as Array(T).

func TypeEnum8

func TypeEnum8(values map[string]int8) ColumnType

TypeEnum8 / TypeEnum16 render the labelled enum types.

TypeEnum8(map[string]int8{"a": 1, "b": 2})

The output order is sorted by value so the generated DDL is stable across runs.

func TypeEnum16

func TypeEnum16(values map[string]int16) ColumnType

func TypeLowCardinality

func TypeLowCardinality(inner ColumnType) ColumnType

TypeLowCardinality wraps an inner type as LowCardinality(T).

func TypeMap

func TypeMap(key, value ColumnType) ColumnType

TypeMap renders Map(K, V).

func TypeNullable

func TypeNullable(inner ColumnType) ColumnType

TypeNullable wraps an inner type as Nullable(T).

func TypeTuple

func TypeTuple(members ...ColumnType) ColumnType

TypeTuple renders Tuple(T1, T2, ...).

type ColumnValue

type ColumnValue interface {
	// contains filtered or unexported methods
}

ColumnValue pairs a target column with the value or expression to bind for it in an INSERT row. Construct one with (*Col[T]).Val or (*Col[T]).Expr.

type DB

type DB struct {
	// contains filtered or unexported fields
}

DB is the entry point for issuing ClickHouse queries through a drops.Driver. The same Hook / Ping / Close / InTx contract as drops/pg's DB, but every emitted statement uses "?" placeholders.

Safe for concurrent use by multiple goroutines provided the underlying Driver is. Builders returned by Select / Insert are not — create one per query.

func New

func New(drv drops.Driver) *DB

New wraps a drops.Driver as a DB.

func (*DB) Begin

func (db *DB) Begin(ctx context.Context) (*DB, drops.Tx, error)

Begin opens a transaction. Note that ClickHouse only supports transactions in the MergeTree family from recent versions and the guarantees are weaker than PostgreSQL — use sparingly.

func (*DB) Close

func (db *DB) Close() error

Close releases the underlying driver if it implements io.Closer.

func (*DB) Driver

func (db *DB) Driver() drops.Driver

Driver returns the underlying driver.

func (*DB) Exec

func (db *DB) Exec(ctx context.Context, sql string, args ...any) (drops.Result, error)

Exec runs a raw SQL statement (with "?" placeholders).

func (*DB) ExecExpr

func (db *DB) ExecExpr(ctx context.Context, e drops.Expression) (drops.Result, error)

ExecExpr renders e to SQL and runs it. Convenience for DDL helpers.

func (*DB) Hook

func (db *DB) Hook() drops.Hook

Hook returns the currently attached hook, or nil.

func (*DB) InTx

func (db *DB) InTx(ctx context.Context, fn func(*DB) error) (err error)

InTx runs fn inside a transaction. Rollback uses a detached context with a short timeout so a cancelled caller-ctx doesn't poison cleanup. Hook events fire for begin/commit/rollback.

func (*DB) Insert

func (db *DB) Insert(t *Table) *InsertBuilder

Insert begins an INSERT INTO <t>.

func (*DB) Ping

func (db *DB) Ping(ctx context.Context) error

Ping verifies the connection with SELECT 1.

func (*DB) Query

func (db *DB) Query(ctx context.Context, sql string, args ...any) (drops.Rows, error)

Query runs a raw SQL query.

func (*DB) Select

func (db *DB) Select(cols ...drops.Expression) *SelectBuilder

Select begins a SELECT.

Example

ExampleDB_Select shows a quantile + grouped analytics query.

package main

import (
	"fmt"

	"github.com/bernardoforcillo/drops/clickhouse"
)

// Schema fixtures used by the godoc examples — picked from a small
// analytics-style table so the rendered SQL is recognisable.
var (
	exEvents = clickhouse.NewTable("events")

	exEventTS = clickhouse.Add(exEvents, clickhouse.DateTime("ts", "UTC"))

	exEventDur = clickhouse.Add(exEvents, clickhouse.Float64("duration_ms"))
)

func main() {
	db := clickhouse.New(nil)
	sql, _ := db.Select(
		clickhouse.As(clickhouse.ToStartOfDay(exEventTS), "day"),
		clickhouse.As(clickhouse.QuantileTiming(0.95, exEventDur), "p95"),
		clickhouse.As(clickhouse.CountAll(), "hits"),
	).
		From(exEvents).
		GroupBy(clickhouse.ToStartOfDay(exEventTS)).
		OrderBy(exEventTS.Asc()).
		ToSQL()
	fmt.Println(sql)
}
Output:
SELECT toStartOfDay("events"."ts") AS "day", quantileTiming(?)("events"."duration_ms") AS "p95", count() AS "hits" FROM "events" GROUP BY toStartOfDay("events"."ts") ORDER BY "events"."ts" ASC

func (*DB) WithHook

func (db *DB) WithHook(hook drops.Hook) *DB

WithHook returns a shallow copy with hook installed; nil removes.

Example

ExampleDB_WithHook shows attaching the dialect-neutral LoggerHook to a ClickHouse DB.

package main

import (
	"context"
	"fmt"

	"github.com/bernardoforcillo/drops"
	"github.com/bernardoforcillo/drops/clickhouse"
)

// Schema fixtures used by the godoc examples — picked from a small
// analytics-style table so the rendered SQL is recognisable.
var (
	exEvents = clickhouse.NewTable("events")

	exEventTS  = clickhouse.Add(exEvents, clickhouse.DateTime("ts", "UTC"))
	exEventUID = clickhouse.Add(exEvents, clickhouse.UInt64("userId"))
)

func init() {
	exEvents.
		Engine(clickhouse.MergeTree()).
		OrderBy(exEventTS, exEventUID).
		PartitionBy(clickhouse.ToYYYYMM(exEventTS))
}

func main() {
	db := clickhouse.New(exampleNoopDriver{}).WithHook(
		drops.LoggerHook(func(format string, args ...any) {
			fmt.Printf("logged: "+format+"\n", args...)
		}),
	)
	_, _ = db.Exec(context.Background(), "SELECT 1")
	// Sample output omitted — duration is non-deterministic. The hook
	// produces one line per call with kind, status, elapsed and SQL.
}

// exampleNoopDriver lets the godoc examples render through DB without
// pulling in a real ClickHouse driver. Production code never embeds
// a no-op.
type exampleNoopDriver struct{}

func (exampleNoopDriver) Exec(context.Context, string, ...any) (drops.Result, error) {
	return exampleNoopResult{}, nil
}
func (exampleNoopDriver) Query(context.Context, string, ...any) (drops.Rows, error) {
	return nil, fmt.Errorf("not implemented")
}
func (exampleNoopDriver) Begin(context.Context) (drops.Tx, error) {
	return nil, fmt.Errorf("not implemented")
}

type exampleNoopResult struct{}

func (exampleNoopResult) RowsAffected() (int64, error) { return 0, nil }

type Engine

type Engine interface {
	WriteEngine(b *drops.Builder)
}

Engine is the table-engine specification. ClickHouse requires every table to declare one; Engine values render as the text right after the ENGINE = keyword inside CREATE TABLE.

Common engines are provided as constructor helpers below; for anything else (Distributed, Kafka, MaterializedView source engines, AggregatingMergeTree with explicit parameters) use Raw.

func AggregatingMergeTree

func AggregatingMergeTree() Engine

AggregatingMergeTree is the empty-constructor form.

func CollapsingMergeTree

func CollapsingMergeTree(signCol string) Engine

CollapsingMergeTree(sign_column).

func Log

func Log() Engine

func Memory

func Memory() Engine

func MergeTree

func MergeTree() Engine

MergeTree returns the MergeTree() engine. ORDER BY / PARTITION BY / settings are configured on the table itself (Table.OrderBy etc.).

func Null

func Null() Engine

func Raw

func Raw(text string) Engine

Raw builds an engine spec from a literal string. Use for engines the typed constructors don't cover.

clickhouse.Raw("Distributed(cluster, db, table, rand())")

func ReplacingMergeTree

func ReplacingMergeTree(versionCol string) Engine

ReplacingMergeTree(version_column) — version is optional; pass an empty string for the default form ReplacingMergeTree().

func ReplicatedMergeTree

func ReplicatedMergeTree(zkPath, replica string) Engine

ReplicatedMergeTree(zk_path, replica). The path/replica strings are passed verbatim — typically use macros: '/clickhouse/tables/{shard}/foo'.

func StripeLog

func StripeLog() Engine

func SummingMergeTree

func SummingMergeTree(cols ...string) Engine

SummingMergeTree(columns...) — optional list of columns to sum.

func TinyLog

func TinyLog() Engine

func VersionedCollapsingMergeTree

func VersionedCollapsingMergeTree(signCol, versionCol string) Engine

VersionedCollapsingMergeTree(sign_column, version_column).

type Entity added in v0.2.0

type Entity[T any] struct {
	// contains filtered or unexported fields
}

Entity binds a Go struct T to a Table and precomputes the column ↔ field mapping used by the CRUD shortcuts. ClickHouse exposes only Insert and Select builders (mutations are async ALTERs and not first-class), so the Entity surface is intentionally narrow:

  • Create / CreateMany insert one or many rows from the typed struct. There is no RETURNING in ClickHouse, so the caller is responsible for filling every column they care about.
  • Query returns a typed builder for SELECT chains that scan into T directly.

Declare an Entity once at package level:

type Event struct {
    ID     string    `drop:"id"`
    UserID uint64    `drop:"userId"`
    Kind   string    `drop:"kind"`
    Ts     time.Time `drop:"ts"`
}

var (
    Events    = clickhouse.NewTable("events").Engine(clickhouse.MergeTree())
    EventID   = clickhouse.Add(Events, clickhouse.UUID("id"))
    EventEnt  = clickhouse.NewEntity[Event](Events)
)

func NewEntity added in v0.2.0

func NewEntity[T any](t *Table) *Entity[T]

NewEntity validates that T is a struct and builds the column ↔ field index map. It panics on a non-struct type because schema declarations are typically loaded at process startup.

Field matching mirrors the row scanner: `drop:"colname"` tag wins, otherwise field name and snake_case form are tried.

func (*Entity[T]) Create added in v0.2.0

func (e *Entity[T]) Create(db *DB, ctx context.Context, r *T) (drops.Result, error)

Create inserts a single row from r. ClickHouse has no RETURNING, so r is not refreshed — any DEFAULT-driven values (timestamps, UUIDs) stay zero on the Go side.

func (*Entity[T]) CreateMany added in v0.2.0

func (e *Entity[T]) CreateMany(db *DB, ctx context.Context, rs []T) (drops.Result, error)

CreateMany inserts a batch of rows. This is the typical analytics pattern; for very large batches drop down to the native columnar protocol via clickhouse-go's Prepare/Exec loop. Validators run against every row before any SQL is issued — the first failure aborts the whole batch.

func (*Entity[T]) Query added in v0.2.0

func (e *Entity[T]) Query(db *DB) *EntityQuery[T]

Query returns a typed query builder that scans into T.

func (*Entity[T]) Table added in v0.2.0

func (e *Entity[T]) Table() *Table

Table returns the table the entity is bound to.

func (*Entity[T]) Validate added in v0.2.0

func (e *Entity[T]) Validate(v Validator[T]) *Entity[T]

Validate registers a validator that runs before Create / CreateMany. Validators are invoked in registration order; the first to return a non-nil error aborts the operation.

type EntityQuery added in v0.2.0

type EntityQuery[T any] struct {
	// contains filtered or unexported fields
}

EntityQuery is the typed counterpart of SelectBuilder — same shape, but its executors return ([]T, error) and (T, error) directly.

func (*EntityQuery[T]) All added in v0.2.0

func (q *EntityQuery[T]) All(ctx context.Context) ([]T, error)

All executes the query and returns the matching rows as a typed slice.

func (*EntityQuery[T]) Limit added in v0.2.0

func (q *EntityQuery[T]) Limit(n int64) *EntityQuery[T]

Limit sets the LIMIT.

func (*EntityQuery[T]) Offset added in v0.2.0

func (q *EntityQuery[T]) Offset(n int64) *EntityQuery[T]

Offset sets the OFFSET.

func (*EntityQuery[T]) One added in v0.2.0

func (q *EntityQuery[T]) One(ctx context.Context) (T, error)

One executes the query and returns the first matching row. Returns ErrNoRows if the query produces no rows.

func (*EntityQuery[T]) OrderBy added in v0.2.0

func (q *EntityQuery[T]) OrderBy(exprs ...drops.Expression) *EntityQuery[T]

OrderBy appends ORDER BY expressions.

func (*EntityQuery[T]) Prewhere added in v0.2.0

func (q *EntityQuery[T]) Prewhere(preds ...drops.Expression) *EntityQuery[T]

Prewhere appends PREWHERE predicates.

func (*EntityQuery[T]) Unscoped added in v0.2.0

func (q *EntityQuery[T]) Unscoped() *EntityQuery[T]

Unscoped opts out of the table's DefaultFilter predicates.

func (*EntityQuery[T]) Where added in v0.2.0

func (q *EntityQuery[T]) Where(preds ...drops.Expression) *EntityQuery[T]

Where appends predicates joined by AND.

type InsertBuilder

type InsertBuilder struct {
	// contains filtered or unexported fields
}

InsertBuilder composes an INSERT INTO …(cols) VALUES (…), (…), … statement. ClickHouse-optimal bulk loads use the native columnar protocol via clickhouse-go's Prepare/Exec loop; for that path drop down to the driver directly. This builder is the convenient form for small batches and one-off rows.

func (*InsertBuilder) Columns

func (i *InsertBuilder) Columns(cols ...ColRef) *InsertBuilder

Columns explicitly fixes the column list (and order) before any Row call. Useful when the first row in your batch omits columns you want present in the SQL.

func (*InsertBuilder) Exec

func (i *InsertBuilder) Exec(ctx context.Context) (drops.Result, error)

Exec runs the INSERT.

func (*InsertBuilder) Row

func (i *InsertBuilder) Row(values ...ColumnValue) *InsertBuilder

Row appends a single row. The first Row fixes the column list.

func (*InsertBuilder) Rows

func (i *InsertBuilder) Rows(rows ...[]ColumnValue) *InsertBuilder

Rows appends multiple rows in one call.

func (*InsertBuilder) ToSQL

func (i *InsertBuilder) ToSQL() (string, []any)

ToSQL renders the statement.

func (*InsertBuilder) WriteSQL

func (i *InsertBuilder) WriteSQL(b *drops.Builder)

WriteSQL renders the INSERT statement.

type InsertHook added in v0.2.0

type InsertHook interface {
	BeforeInsert(ctx *InsertHookCtx)
}

InsertHook is invoked once per INSERT statement, before rendering.

type InsertHookCtx added in v0.2.0

type InsertHookCtx struct {
	// contains filtered or unexported fields
}

InsertHookCtx exposes which columns the caller already bound and lets the hook append additional bindings that apply to every row.

func (*InsertHookCtx) Has added in v0.2.0

func (c *InsertHookCtx) Has(col *Column) bool

Has reports whether col is already bound on the INSERT.

func (*InsertHookCtx) Set added in v0.2.0

func (c *InsertHookCtx) Set(v ColumnValue)

Set binds a typed ColumnValue (e.g. the result of (*Col[T]).Val(v)).

func (*InsertHookCtx) SetExpr added in v0.2.0

func (c *InsertHookCtx) SetExpr(col *Column, expr drops.Expression)

SetExpr binds expr to col across every row, unless col is already bound — typical for DB-evaluated defaults such as drops.Raw("now()").

type InsertHookFunc added in v0.2.0

type InsertHookFunc func(*InsertHookCtx)

InsertHookFunc adapts a plain function to the InsertHook interface.

func (InsertHookFunc) BeforeInsert added in v0.2.0

func (f InsertHookFunc) BeforeInsert(ctx *InsertHookCtx)

BeforeInsert implements InsertHook.

type LoggerFunc

type LoggerFunc = drops.LoggerFunc

LoggerFunc / LoggerOptions / LoggerHook re-export the dialect-neutral helpers from the root drops package. A single Hook function works against pg.DB, clickhouse.DB and qdrant.Client unchanged.

type LoggerOptions

type LoggerOptions = drops.LoggerOptions

LoggerFunc / LoggerOptions / LoggerHook re-export the dialect-neutral helpers from the root drops package. A single Hook function works against pg.DB, clickhouse.DB and qdrant.Client unchanged.

type Mixin added in v0.2.0

type Mixin interface {
	Apply(*Table)
}

Mixin is the richer companion of the plain template functions in template.go. ClickHouse exposes only InsertHook and SelectBuilder default filters — UPDATE/DELETE happen via async ALTERs and are not modelled by builders — so the rich mixins here are more limited than in drops/pg.

clickhouse.ApplyMixins(Events,
    clickhouse.UUIDPrimaryKeyMixin{},
    clickhouse.TimestampsMixin{},  // adds createdAt, updatedAt
    clickhouse.SoftDeleteMixin{},  // adds deletedAt + default scope
)

type MixinFunc added in v0.2.0

type MixinFunc func(*Table)

MixinFunc adapts a plain function to the Mixin interface.

func (MixinFunc) Apply added in v0.2.0

func (f MixinFunc) Apply(t *Table)

Apply implements Mixin.

type SelectBuilder

type SelectBuilder struct {
	// contains filtered or unexported fields
}

SelectBuilder composes a ClickHouse SELECT. It mirrors the drops/pg surface (Where, OrderBy, GroupBy, Limit, joins) plus CH-specific clauses (PREWHERE, FINAL, SAMPLE, SETTINGS).

func (*SelectBuilder) All

func (s *SelectBuilder) All(ctx context.Context, dest any) error

All scans every row into dest.

func (*SelectBuilder) AllJoin

func (s *SelectBuilder) AllJoin(t *Table, on drops.Expression) *SelectBuilder

func (*SelectBuilder) AnyJoin

func (s *SelectBuilder) AnyJoin(t *Table, on drops.Expression) *SelectBuilder

func (*SelectBuilder) AsofJoin

func (s *SelectBuilder) AsofJoin(t *Table, on drops.Expression) *SelectBuilder

func (*SelectBuilder) Count

func (s *SelectBuilder) Count(ctx context.Context) (int64, error)

Count wraps the current SELECT in `SELECT count() FROM (... )`.

func (*SelectBuilder) Distinct

func (s *SelectBuilder) Distinct() *SelectBuilder

Distinct toggles SELECT DISTINCT.

func (*SelectBuilder) Final

func (s *SelectBuilder) Final() *SelectBuilder

Final appends FINAL after the table, forcing CH to merge parts at read time (handy with ReplacingMergeTree / CollapsingMergeTree when you accept the cost).

func (*SelectBuilder) From

func (s *SelectBuilder) From(t *Table) *SelectBuilder

From sets the FROM table. Required before execution.

func (*SelectBuilder) FullJoin

func (s *SelectBuilder) FullJoin(t *Table, on drops.Expression) *SelectBuilder

func (*SelectBuilder) GroupBy

func (s *SelectBuilder) GroupBy(exprs ...drops.Expression) *SelectBuilder

GroupBy / Having / OrderBy / Limit / Offset.

func (*SelectBuilder) Having

func (s *SelectBuilder) Having(preds ...drops.Expression) *SelectBuilder

func (*SelectBuilder) Join

Join / LeftJoin / RightJoin / FullJoin / AnyJoin / AllJoin / AsofJoin.

func (*SelectBuilder) LeftJoin

func (s *SelectBuilder) LeftJoin(t *Table, on drops.Expression) *SelectBuilder

func (*SelectBuilder) Limit

func (s *SelectBuilder) Limit(n int64) *SelectBuilder

func (*SelectBuilder) Offset

func (s *SelectBuilder) Offset(n int64) *SelectBuilder

func (*SelectBuilder) One

func (s *SelectBuilder) One(ctx context.Context, dest any) error

One scans the first row into dest. Returns ErrNoRows if empty.

func (*SelectBuilder) OrderBy

func (s *SelectBuilder) OrderBy(exprs ...drops.Expression) *SelectBuilder

func (*SelectBuilder) Prewhere

func (s *SelectBuilder) Prewhere(preds ...drops.Expression) *SelectBuilder

Prewhere adds a PREWHERE predicate — evaluated before the main WHERE, with the right primary-key columns it can dramatically cut scanned data on MergeTree tables.

func (*SelectBuilder) RightJoin

func (s *SelectBuilder) RightJoin(t *Table, on drops.Expression) *SelectBuilder

func (*SelectBuilder) Rows

func (s *SelectBuilder) Rows(ctx context.Context) (drops.Rows, error)

Rows runs the SELECT and returns the raw cursor.

func (*SelectBuilder) SampleBy

func (s *SelectBuilder) SampleBy(e any) *SelectBuilder

SampleBy adds a SAMPLE clause (e.g. SampleBy(0.1) for 10%).

func (*SelectBuilder) Setting

func (s *SelectBuilder) Setting(key, value string) *SelectBuilder

Setting appends a "key = value" pair to the SETTINGS clause.

func (*SelectBuilder) ToSQL

func (s *SelectBuilder) ToSQL() (string, []any)

ToSQL renders the statement using the ClickHouse placeholder style.

func (*SelectBuilder) Unscoped added in v0.2.0

func (s *SelectBuilder) Unscoped() *SelectBuilder

Unscoped opts out of the FROM table's DefaultFilter predicates for this SELECT.

func (*SelectBuilder) Where

func (s *SelectBuilder) Where(preds ...drops.Expression) *SelectBuilder

Where appends predicates joined by AND.

func (*SelectBuilder) WriteSQL

func (s *SelectBuilder) WriteSQL(b *drops.Builder)

WriteSQL renders the SELECT.

type SoftDeleteCols added in v0.2.0

type SoftDeleteCols struct {
	DeletedAt *Col[time.Time]
}

SoftDeleteCols holds the typed handle created by SoftDelete.

func SoftDelete added in v0.2.0

func SoftDelete(t *Table) SoftDeleteCols

SoftDelete appends a Nullable(DateTime) "deletedAt" column to t. A row is treated as live while deletedAt IS NULL.

type SoftDeleteMixin added in v0.2.0

type SoftDeleteMixin struct {
	Cols SoftDeleteCols
}

SoftDeleteMixin registers a Nullable(DateTime) "deletedAt" column and a default filter that excludes already-deleted rows from SELECTs. ClickHouse has no UPDATE/DELETE builder, so the "soft delete the row" operation is left to the caller as a raw ALTER TABLE … UPDATE statement.

func (*SoftDeleteMixin) Apply added in v0.2.0

func (m *SoftDeleteMixin) Apply(t *Table)

Apply implements Mixin.

type Table

type Table struct {
	// contains filtered or unexported fields
}

Table represents a ClickHouse table. Beyond columns, it carries the engine spec plus the optional clauses every CREATE TABLE may stipulate: ORDER BY, PARTITION BY, PRIMARY KEY, SAMPLE BY, TTL, and the SETTINGS bag.

func ApplyMixins added in v0.2.0

func ApplyMixins(t *Table, mixins ...Mixin) *Table

ApplyMixins runs each mixin against t in order and returns t.

func NewDatabaseTable

func NewDatabaseTable(database, name string) *Table

NewDatabaseTable scopes the table to an explicit database.

func NewTable

func NewTable(name string) *Table

NewTable creates a table in the default database. The name is validated and a bad identifier panics at startup (see ErrInvalidIdentifier).

func (*Table) Alias

func (t *Table) Alias() string

func (*Table) As

func (t *Table) As(alias string) *Table

As returns a shallow copy bound to alias.

func (*Table) Col

func (t *Table) Col(name string) *Column

Col looks up a column by name.

func (*Table) Columns

func (t *Table) Columns() []*Column

Columns returns the columns in declaration order.

func (*Table) Database

func (t *Table) Database() string

func (*Table) DefaultFilter added in v0.2.0

func (t *Table) DefaultFilter(e drops.Expression) *Table

DefaultFilter appends a predicate applied to every Select against the table, unless the builder is marked Unscoped().

func (*Table) Engine

func (t *Table) Engine(e Engine) *Table

Engine sets the table's engine. Required before CREATE TABLE.

func (*Table) Name

func (t *Table) Name() string

Name / Database / Alias accessors.

func (*Table) OnInsert added in v0.2.0

func (t *Table) OnInsert(h InsertHook) *Table

OnInsert registers a hook invoked by InsertBuilder.WriteSQL.

func (*Table) OrderBy

func (t *Table) OrderBy(cols ...ColRef) *Table

OrderBy sets the ORDER BY columns (MergeTree family).

func (*Table) PartitionBy

func (t *Table) PartitionBy(exprs ...drops.Expression) *Table

PartitionBy sets the PARTITION BY expression(s).

func (*Table) PrimaryKey

func (t *Table) PrimaryKey(cols ...ColRef) *Table

PrimaryKey sets an explicit PRIMARY KEY (defaults to ORDER BY when omitted on MergeTree-family engines).

func (*Table) SampleBy

func (t *Table) SampleBy(e drops.Expression) *Table

SampleBy sets the SAMPLE BY expression.

func (*Table) Setting

func (t *Table) Setting(key, value string) *Table

Setting appends a "key = value" pair to the SETTINGS clause.

func (*Table) TTL

func (t *Table) TTL(expr string) *Table

TTL sets the table-wide TTL expression (raw SQL).

func (*Table) WriteSQL

func (t *Table) WriteSQL(b *drops.Builder)

WriteSQL writes the FROM/JOIN form so a *Table satisfies drops.Expression and can appear anywhere a SQL fragment is expected.

type TimestampsCols added in v0.2.0

type TimestampsCols struct {
	CreatedAt *Col[time.Time]
	UpdatedAt *Col[time.Time]
}

TimestampsCols holds the typed handles created by Timestamps.

func Timestamps added in v0.2.0

func Timestamps(t *Table) TimestampsCols

Timestamps appends "createdAt" and "updatedAt" DateTime columns defaulting to now() to t.

type TimestampsMixin added in v0.2.0

type TimestampsMixin struct {
	Cols TimestampsCols
}

TimestampsMixin registers "createdAt" and "updatedAt" DateTime columns. ClickHouse has no UPDATE builder so there is no UpdateHook equivalent; both columns get DEFAULT now() so INSERT without an explicit value picks up the server clock.

func (*TimestampsMixin) Apply added in v0.2.0

func (m *TimestampsMixin) Apply(t *Table)

Apply implements Mixin.

type UUIDPrimaryKeyCols added in v0.2.0

type UUIDPrimaryKeyCols struct {
	ID *Col[string]
}

UUIDPrimaryKeyCols holds the typed handle created by UUIDPrimaryKey.

func UUIDPrimaryKey added in v0.2.0

func UUIDPrimaryKey(t *Table) UUIDPrimaryKeyCols

UUIDPrimaryKey appends an "id" UUID column defaulting to generateUUIDv4(). ClickHouse has no primary-key constraint; pair the column with the table's ORDER BY (or PRIMARY KEY clause on MergeTree-family engines) to make it the row identifier.

type UUIDPrimaryKeyMixin added in v0.2.0

type UUIDPrimaryKeyMixin struct {
	Cols UUIDPrimaryKeyCols
}

UUIDPrimaryKeyMixin registers an "id" UUID column defaulting to generateUUIDv4(). Pair the column with the table's ORDER BY (or PRIMARY KEY clause on MergeTree-family engines) to make it the row identifier.

func (*UUIDPrimaryKeyMixin) Apply added in v0.2.0

func (m *UUIDPrimaryKeyMixin) Apply(t *Table)

Apply implements Mixin.

type Validator added in v0.2.0

type Validator[T any] func(*T) error

Validator is called before Create / CreateMany with a pointer to each candidate row. Returning a non-nil error aborts the operation before any SQL is issued.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL