package repo import ( "context" "fmt" "github.com/ClickHouse/clickhouse-go/v2" "github.com/ClickHouse/clickhouse-go/v2/lib/driver" ) // NewClickHouse opens a native-protocol ClickHouse connection. The returned // driver.Conn is safe for concurrent use. Caller owns Close(). func NewClickHouse(ctx context.Context, addr, db, user, password string) (driver.Conn, error) { conn, err := clickhouse.Open(&clickhouse.Options{ Addr: []string{addr}, Auth: clickhouse.Auth{ Database: db, Username: user, Password: password, }, Settings: clickhouse.Settings{ "readonly": 0, // analytics queries; per-user read-only enforced for /query/sql separately }, }) if err != nil { return nil, fmt.Errorf("open clickhouse: %w", err) } if err := conn.Ping(ctx); err != nil { _ = conn.Close() return nil, fmt.Errorf("ping clickhouse: %w", err) } return conn, nil } // NewClickHouseReadOnly opens a ClickHouse connection using a SELECT-only // account. Used to back the /query/sql sandbox: DDL/DML are rejected at the DB // level even if the app-level keyword guard is bypassed. func NewClickHouseReadOnly(ctx context.Context, addr, db, user, password string) (driver.Conn, error) { conn, err := clickhouse.Open(&clickhouse.Options{ Addr: []string{addr}, Auth: clickhouse.Auth{ Database: db, Username: user, Password: password, }, Settings: clickhouse.Settings{ "readonly": 2, // belt-and-braces: server-side enforce read-only }, }) if err != nil { return nil, fmt.Errorf("open clickhouse (ro): %w", err) } if err := conn.Ping(ctx); err != nil { _ = conn.Close() return nil, fmt.Errorf("ping clickhouse (ro): %w", err) } return conn, nil }