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) } }