Connect ClickHouse Go Client to Tinybird

The official ClickHouse Go client can connect to Tinybird using the ClickHouse® HTTP protocol compatibility. This enables you to query your Tinybird data sources programmatically from Go applications with high performance and type safety.

The ClickHouse® connection to Tinybird is read-only. You can use it to query and analyze data from your Tinybird data sources, but you cannot modify data through this connection.

Prerequisites

  • Go 1.19 or later
  • A Tinybird workspace with data sources
  • A Tinybird Auth Token with read permissions for the workspace data sources

Installation

Add the ClickHouse Go client to your project:

go mod init your-project
go get github.com/ClickHouse/clickhouse-go/v2

Configuration

Create a connection with the following configuration:

package main

import (
    "context"
    "crypto/tls"
    "log"

    "github.com/ClickHouse/clickhouse-go/v2"
    "github.com/ClickHouse/clickhouse-go/v2/lib/driver"
)

func connect() (driver.Conn, error) {
    conn, err := clickhouse.Open(&clickhouse.Options{
        Addr:     []string{"clickhouse.tinybird.co:443"},
        Protocol: clickhouse.HTTP, // Use HTTP protocol
        Auth: clickhouse.Auth{
            Database: "default",
            Username: "<WORKSPACE_NAME>", // Optional, for identification
            Password: "<TOKEN>", // Your Tinybird Auth Token
        },
        TLS: &tls.Config{
            InsecureSkipVerify: false, // Set to true for testing only
        },
        ClientInfo: clickhouse.ClientInfo{
            Products: []struct {
                Name    string
                Version string
            }{
                {Name: "your-app-name", Version: "1.0.0"},
            },
        },
    })
    if err != nil {
        return nil, err
    }

    // Test the connection
    if err := conn.Ping(context.Background()); err != nil {
        return nil, err
    }

    return conn, nil
}

See the list of ClickHouse hosts to find the correct one for your region.

Test the connection

Create a simple query to verify your connection:

func main() {
    conn, err := connect()
    if err != nil {
        log.Fatal(err)
    }
    defer conn.Close()

    ctx := context.Background()

    // Test query
    rows, err := conn.Query(ctx, "SELECT database, name, engine FROM system.tables LIMIT 5")
    if err != nil {
        log.Fatal(err)
    }
    defer rows.Close()

    for rows.Next() {
        var database, name, engine string
        if err := rows.Scan(&database, &name, &engine); err != nil {
            log.Fatal(err)
        }
        fmt.Printf("Database: %s, Table: %s, Engine: %s\n", database, name, engine)
    }
}

Query your data

Once connected, you can query your Tinybird data sources:

// Query a specific data source
func queryDataSource(conn driver.Conn) error {
    ctx := context.Background()

    rows, err := conn.Query(ctx, "SELECT * FROM your_data_source_name LIMIT 10")
    if err != nil {
        return err
    }
    defer rows.Close()

    // Process results
    for rows.Next() {
        var col1 string
        var col2 int64
        // Scan based on your data source schema
        if err := rows.Scan(&col1, &col2); err != nil {
            return err
        }
        fmt.Printf("Col1: %s, Col2: %d\n", col1, col2)
    }

    return rows.Err()
}

// Query with parameters using named parameters
func queryWithParams(conn driver.Conn, startDate string, limit int) error {
    ctx := context.Background()

    query := `
        SELECT timestamp, user_id, event_name
        FROM events
        WHERE timestamp >= @start_date
        LIMIT @limit
    `

    rows, err := conn.Query(ctx, query,
        clickhouse.Named("start_date", startDate),
        clickhouse.Named("limit", limit),
    )
    if err != nil {
        return err
    }
    defer rows.Close()

    // Process results...
    return nil
}

Observability

You can also explore Tinybird's observability data from internal service data sources:

// View recent API endpoint usage and performance
func queryRecentUsage(conn driver.Conn) error {
    ctx := context.Background()

    query := `
        SELECT
            start_datetime,
            pipe_name,
            duration,
            result_rows,
            read_bytes,
            status_code
        FROM tinybird.pipe_stats_rt
        WHERE start_datetime >= now() - INTERVAL 1 DAY
        ORDER BY start_datetime DESC
        LIMIT 10
    `

    rows, err := conn.Query(ctx, query)
    if err != nil {
        return err
    }
    defer rows.Close()

    fmt.Println("Recent API endpoint usage:")
    for rows.Next() {
        var startTime time.Time
        var pipeName string
        var duration float64
        var resultRows, readBytes, statusCode uint64

        if err := rows.Scan(&startTime, &pipeName, &duration, &resultRows, &readBytes, &statusCode); err != nil {
            return err
        }

        fmt.Printf("[%s] %s - %.2fms, %d rows, status %d\n",
            startTime.Format("2006-01-02 15:04:05"), pipeName, duration*1000, resultRows, statusCode)
    }

    return rows.Err()
}

// Analyze endpoint performance metrics
func queryPerformanceMetrics(conn driver.Conn) error {
    ctx := context.Background()

    query := `
        SELECT
            pipe_name,
            count() as request_count,
            avg(duration) as avg_duration_ms,
            avg(result_rows) as avg_result_rows,
            sum(read_bytes) as total_bytes_read
        FROM tinybird.pipe_stats_rt
        WHERE start_datetime >= now() - INTERVAL 1 HOUR
        GROUP BY pipe_name
        ORDER BY request_count DESC
    `

    rows, err := conn.Query(ctx, query)
    if err != nil {
        return err
    }
    defer rows.Close()

    fmt.Println("\nEndpoint performance metrics (last hour):")
    for rows.Next() {
        var pipeName string
        var requestCount uint64
        var avgDuration, avgResultRows float64
        var totalBytesRead uint64

        if err := rows.Scan(&pipeName, &requestCount, &avgDuration, &avgResultRows, &totalBytesRead); err != nil {
            return err
        }

        fmt.Printf("%s: %d requests, %.2fms avg, %.0f avg rows, %d bytes\n",
            pipeName, requestCount, avgDuration*1000, avgResultRows, totalBytesRead)
    }

    return rows.Err()
}

Error handling

Handle ClickHouse-specific errors:

import (
    "github.com/ClickHouse/clickhouse-go/v2"
)

func handleQueryError(err error) {
    if exception, ok := err.(*clickhouse.Exception); ok {
        log.Printf("ClickHouse exception [%d]: %s", exception.Code, exception.Message)
        if exception.StackTrace != "" {
            log.Printf("Stack trace: %s", exception.StackTrace)
        }
    } else {
        log.Printf("Connection error: %v", err)
    }
}

Learn more

Updated