clickhouse

package
v0.3.13 Latest Latest
Warning

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

Go to latest
Published: Jul 2, 2026 License: MIT Imports: 13 Imported by: 0

Documentation

Overview

Package clickhouse is TinyRaven's ClickHouse adapter. One Client drives both transports ClickHouse exposes (ADR 0013): the HTTP interface (8123) for read-only queries + liveness, and the native protocol (TCP 9000) for the batched insert hot path. It implements model.CHQuerier, model.Pinger and model.CHInserter.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type CHError

type CHError struct {
	Code int
	Msg  string
}

CHError carries a ClickHouse exception code (X-ClickHouse-Exception-Code) so the API layer can map it to a Tinybird-compatible status (ADR 0012).

func (*CHError) Error

func (e *CHError) Error() string

type Client

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

Client talks to one ClickHouse instance/database over both transports.

func New

func New(cfg Config) (*Client, error)

New builds a Client. The native conn is opened lazily by the driver (no dial here), so New only fails on bad options; readiness is probed via Ping.

func (*Client) Backfill

func (c *Client) Backfill(ctx context.Context, dst, src string, cols []string) error

Backfill copies cols from src into dst (INSERT INTO dst (cols) SELECT cols FROM src) — the second step of a breaking migration, populating the shadow table from the live one over the columns that survive the change.

ponytail: cols must be the type-compatible overlap; the caller (deploy) drops type-changed and removed columns, which then take their CH defaults in the new schema. A genuinely incompatible backfill would error here at the CH layer.

func (*Client) Close

func (c *Client) Close() error

Close releases the native connection pool.

func (*Client) CreateDatabase

func (c *Client) CreateDatabase(ctx context.Context, name string) error

CreateDatabase issues CREATE DATABASE IF NOT EXISTS. It runs against the client's currently-scoped database, so the orchestrator calls it on a client pointed at an existing DB, then WithDatabase to target the new one (ADR 0007).

ponytail: name is backticked but not otherwise sanitized — branch names with '-' or '/' (e.g. tr_feature-x) require the quoting. Mapping a git branch to a legal CH database name is the orchestrator's job.

func (*Client) CreateMaterializedView

func (c *Client) CreateMaterializedView(ctx context.Context, m *model.Materialization) error

CreateMaterializedView creates an incremental MV that writes into an existing target table (CREATE MATERIALIZED VIEW ... TO <target> AS <select>). The target table must already exist; deploy ensures it first (ADR 0010). Idempotent via IF NOT EXISTS — re-running deploy never errors on an existing MV.

ponytail: backfill of pre-existing source rows (ADR 0010) is deploy's concern, not this builder's; this only wires the forward MV.

func (*Client) CreateShadowTable

func (c *Client) CreateShadowTable(ctx context.Context, ds *model.Datasource, shadowName string) error

CreateShadowTable creates shadowName with ds's NEW schema/engine — the first step of a breaking migration. Only the main table is created (not the quarantine sibling): the swap rewrites event data, while quarantine schema is fixed and untouched (ADR 0007).

func (*Client) DropTable

func (c *Client) DropTable(ctx context.Context, name string) error

DropTable issues DROP TABLE IF EXISTS — the final cleanup after EXCHANGE TABLES drops the now-shadow (old) table.

func (*Client) EnsureReadonlyUser

func (c *Client) EnsureReadonlyUser(ctx context.Context, name, password string) error

EnsureReadonlyUser provisions the dedicated read-only ClickHouse identity that the HTTP read path (/v0/sql, pipe queries) authenticates as (ADR 0011). It runs as the read-write/admin user (via exec). Idempotent: re-running converges to the same state and keeps the password in sync with config.

It issues two statements (the HTTP interface runs one statement per request, so they cannot be combined):

CREATE USER OR REPLACE `<name>` IDENTIFIED BY '<password>' SETTINGS readonly = 2
GRANT SELECT ON *.* TO `<name>`

readonly=2 (not the stock `readonly` profile, which is readonly=1) is the deliberate choice from ADR 0011: it refuses writes, DDL and settings *injection* while still letting the query layer forward per-query settings (resource caps, query cache) as URL args — readonly=1 would reject those. A user under readonly=2 also cannot lower its own readonly value, so the guard can't be turned off from a query. GRANT SELECT gives the SELECT privilege the fresh user otherwise lacks under RBAC; readonly=2 is an orthogonal layer that still blocks writes even if broader privileges were ever granted (defence in depth).

ponytail: resource caps (max_result_rows / max_execution_time; ADR 0025/0011) are NOT baked into this user. They stay per-query, forwarded through Query's settings map by the pipe/sql layer, so caps can vary per endpoint without reprovisioning the user. Bake them onto the user here (SETTINGS ... = N) only if a hard, query-independent ceiling is ever wanted.

func (*Client) EnsureTable

func (c *Client) EnsureTable(ctx context.Context, ds *model.Datasource) error

EnsureTable creates the datasource's table and (for MergeTree datasources) its quarantine sibling if they don't exist. This is the MVP bootstrap DDL for local dev — full schema diffing and safe migrations are tr deploy's job (Phase 2). The target database is assumed to exist (provisioned by the container/operator).

Connector engines (Kafka/S3/PostgreSQL; ADR 0019) get NO quarantine sibling: quarantine is the landing zone for rows the Gatherer rejects on the HTTP events path (ADR 0018), and connector tables are pulled by ClickHouse, never ingested through the Gatherer, so a quarantine table would misrepresent the data path.

func (*Client) ExchangeTables

func (c *Client) ExchangeTables(ctx context.Context, a, b string) error

ExchangeTables atomically swaps two tables (EXCHANGE TABLES a AND b). It is the final, atomic step of a breaking migration: swap the rebuilt shadow into the live name with no window of missing data (ADR 0007).

func (*Client) Insert

func (c *Client) Insert(ctx context.Context, ds *model.Datasource, rows []map[string]any) error

Insert sends rows as one native batch into <db>.<ds.Name> (ADR 0013, the perf path). Values are appended in ds.Schema column order, coerced from their JSON-decoded Go types to what the native driver expects.

func (*Client) InsertSelect

func (c *Client) InsertSelect(ctx context.Context, sql string, params map[string]string) error

InsertSelect runs an INSERT INTO ... SELECT over HTTP as the read-write user, binding CH query parameters (param_<name>) — the copy-pipe write path (model.CHWriter). It is exec with params: the read-only identity (readonly=2; ADR 0011) would refuse the write, so this always authenticates as user/password. The body is discarded (an INSERT returns none worth keeping).

func (*Client) Ping

func (c *Client) Ping(ctx context.Context) error

Ping issues SELECT 1 over HTTP for the readiness probe (ADR 0024).

func (*Client) Query

func (c *Client) Query(ctx context.Context, sql string, params, settings map[string]string) ([]byte, error)

Query runs sql over the HTTP interface and returns the response body verbatim (ADR 0013). It is the read path — /v0/sql and pipe queries — so it authenticates as the read-only identity when one is configured (readonly=2; ADR 0011), guaranteeing ClickHouse refuses writes/DDL no matter what SQL arrives. params (already prefixed param_<name> by the caller) and settings are forwarded as URL query args; the target db comes from cfg.

func (*Client) WithDatabase

func (c *Client) WithDatabase(db string) *Client

WithDatabase returns a shallow copy of c that targets db, sharing the same HTTP client and native connection. deploy uses it to point DDL at a per-branch workspace database (tr_<branch>; ADR 0007) after CreateDatabase, without reopening connections. Only the HTTP Query/DDL path is re-scoped (all deploy needs); the shared native conn keeps its original Auth.Database, so Insert still targets the original database.

ponytail: Close on either the original or a copy closes the shared native pool. Treat the original as the owner and don't Close copies.

type Config

type Config struct {
	HTTPURL    string // ClickHouse HTTP, e.g. "http://localhost:8123"
	NativeAddr string // native TCP, e.g. "localhost:9000"; empty disables Insert
	Database   string // target database
	User       string
	Password   string

	// ROUser/ROPassword are the dedicated read-only identity (readonly=2;
	// ADR 0011). When set, the HTTP read path (Query) authenticates as this user
	// so ClickHouse structurally refuses writes, DDL and settings injection from
	// /v0/sql and pipe queries. When empty, the read path falls back to
	// User/Password — backward-compatible, no behaviour change. Writes (native
	// Insert) and DDL always use User/Password regardless.
	ROUser     string
	ROPassword string
}

Config is the connection info for both transports. Populated by the orchestrator from internal/config.

Jump to

Keyboard shortcuts

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