GORM ClickHouse Driver

repository·master·Indexed 43 days ago

https://github.com/go-gorm/clickhouse

A GORM driver providing ORM capabilities for ClickHouse databases. It supports standard GORM operations like AutoMigrate, Create, and Find, while implementing ClickHouse-specific features such as ALTER TABLE syntax for updates and deletes, specialized table engines (e.g., Distributed), and data indexes (minmax, bloomfilter). The driver includes a Migrator for schema changes, support for ON CLUSTER clauses, and custom data type mapping between Go and ClickHouse.

Tokens
4.7K
Snippets
12
Records
18
Agent score
54%

What's inside GORM ClickHouse

  1. Quick Start with GORM ClickHouse

    master

    To connect to ClickHouse using GORM, use clickhouse.Open(dsn) with a valid DSN string. You can perform standard GORM operations like AutoMigrate, Create, and Find.

    This driver also supports setting ClickHouse-specific table options and cluster options during migration using db.Set with the following keys:

    • gorm:table_options: Used to specify the ClickHouse engine (e.g., ENGINE=Distributed(...)).
    • gorm:table_cluster_options: Used to specify cluster-specific settings (e.g., on cluster default).
    package main
    
    import (
    	"gorm.io/driver/clickhouse"
    	"gorm.io/gorm"
    )
    
    type User struct {
    	Name string
    	Age  int
    }
    
    func main() {
    	dsn := "clickhouse://gorm:gorm@localhost:9942/gorm?dial_timeout=10s&read_timeout=20s"
    	db, err := gorm.Open(clickhouse.Open(dsn), &gorm.Config{})
    	if err != nil {
    		panic("failed to connect database")
    	}
    
    	// Auto Migrate
    	db.AutoMigrate(&User{})
    
    	// Set table options (e.g. Distributed engine)
    	db.Set("gorm:table_options", "ENGINE=Distributed(cluster, default, hits)").AutoMigrate(&User{})
    
    	// Set table cluster options
    	db.Set("gorm:table_cluster_options", "on cluster default").AutoMigrate(&User{})
    
    	// Insert
    	db.Create(&User{Name: "Angeliz", Age: 18})
    
    	// Select
    	db.Find(&User{}, "name = ?", "Angeliz")
    
    	// Batch Insert
    	users := []User{
    		{Age: 12, Name: "Bruce Lee"},
    		{Age: 13, Name: "Feynman"},
    		{Age: 14, Name: "Angeliz"},
    	}
    	db.Create(&users)
    }
  2. How ClickHouse handles UPDATE and DELETE operations

    master

    Because ClickHouse uses ALTER TABLE ... UPDATE and ALTER TABLE ... DELETE syntax rather than standard SQL UPDATE and DELETE, this driver implements custom ClauseBuilders.

    When you call .Delete() or .Update() in GORM, the driver automatically transforms the statement to use the ALTER TABLE syntax and ensures the WHERE conditions are correctly applied to the table.

    // The driver internally transforms standard GORM calls:
    // db.Delete(&User{}, "id = ?", 1)
    // into:
    // ALTER TABLE users DELETE WHERE id = 1
  3. Create tables with ClickHouse-specific options

    master

    When using CreateTable, the driver automatically incorporates ClickHouse features based on GORM struct tags:

    • Default Values: Uses DEFAULT clause.
    • Comments: Uses COMMENT tag.
    • TTL: Uses TTL tag.
    • Compression: Uses CODEC tag (e.g., gorm:"CODEC:LZ4,ZSTD").
    • Cluster Support: If gorm:table_options is set in the DB session, it extracts and applies the ON CLUSTER clause.
    • Indexes: Supports ClickHouse data indexes (e.g., minmax, bloomfilter) via the type option in the gorm:"index" tag.
    // Example of a model with ClickHouse specific tags
    type User struct {
        ID        uint64    `gorm:"primaryKey"` 
        Email     string    `gorm:"COMMENT:User email, CODEC:LZ4"` 
        CreatedAt time.Time `gorm:"TTL:INTERVAL 30 DAY"` 
        Data      []byte    `gorm:"CODEC:ZSTD(1)"` 
    }
  4. Run ClickHouse via Docker Compose for local development

    master

    You can use the provided docker-compose.yml to spin up a local ClickHouse server instance for testing the GORM ClickHouse driver. This configuration sets up a server with the following default credentials and port mappings:

    • Database: gorm
    • User: gorm
    • Password: gorm

    Port Mappings:

    • 9941 -> 8123 (HTTP interface)
    • 9942 -> 9000 (Native protocol interface)
    • 9943 -> 9009 (Inter-server communication)
    version: "3"
    services:
        server:
          image: yandex/clickhouse-server:latest
          environment:
            - CLICKHOUSE_DB=gorm
            - CLICKHOUSE_USER=gorm
            - CLICKHOUSE_PASSWORD=gorm
          ports:
          - 9941:8123
          - 9942:9000
          - 9943:9009
  5. Initialize GORM with an existing sql.DB connection

    master

    If you need fine-grained control over the underlying ClickHouse connection (e.g., setting TLS, compression, or specific driver settings), you can initialize a standard clickhouse-go connection and pass it to clickhouse.New using the Conn field in the configuration.

    package main
    
    import (
      std_ck "github.com/ClickHouse/clickhouse-go/v2"
      "gorm.io/driver/clickhouse"
      "gorm.io/gorm"
      "crypto/tls"
      "time"
    )
    
    func main() {
      sqlDB, err := std_ck.OpenDB(&std_ck.Options{
        Addr: []string{"127.0.0.1:9999"},
        Auth: std_ck.Auth{
          Database: "default",
          Username: "default",
          Password: "",
        },
        TLS: &tls.Config{
          InsecureSkipVerify: true,
        },
        Settings: std_ck.Settings{
          "max_execution_time": 60,
        },
        DialTimeout: 5 * time.Second,
        Compression: &std_ck.Compression{
          std_ck.CompressionLZ4,
        },
        Debug: true,
      })
      if err != nil {
        panic(err)
      }
    
      db, err := gorm.Open(clickhouse.New(clickhouse.Config{
        Conn: sqlDB, // initialize with existing database conn
      }), &gorm.Config{})
      if err != nil {
        panic(err)
      }
    }
  6. Configure clickhouse.Config options

    master

    When using clickhouse.New(config), you can provide several configuration options to tune the driver behavior and compatibility with specific ClickHouse versions.

    Note: Refer to the official clickhouse-go documentation for DSN format and connection string details.

    package main
    
    import (
      "gorm.io/driver/clickhouse"
      "gorm.io/gorm"
    )
    
    var dsn = "clickhouse://username:password@host1:9000,host2:9000/database?dial_timeout=200ms&max_execution_time=60"
    
    func main() {
      db, err := gorm.Open(clickhouse.New(clickhouse.Config{
        DSN: dsn,
        // Conn: conn,                       // initialize with existing database conn
        DisableDatetimePrecision: true,       // disable datetime64 precision, not supported before clickhouse 20.4
        DontSupportRenameColumn: true,       // rename column not supported before clickhouse 20.4
        DontSupportEmptyDefaultValue: false,  // do not consider empty strings as valid default values
        SkipInitializeWithVersion: false,     // smart configure based on used version
        DefaultGranularity: 3,                // 1 granule = 8192 rows
        DefaultCompression: "LZ4",           // default compression algorithm. LZ4 is lossless
        DefaultIndexType: "minmax",         // index stores extremes of the expression
        DefaultTableEngineOpts: "ENGINE=MergeTree() ORDER BY tuple()",
      }), &gorm.Config{})
      if err != nil {
        panic(err)
      }
    }
  7. Configure the ClickHouse Dialector

    master

    The Config struct allows you to fine-tune how GORM interacts with ClickHouse. Key configuration options include:

    • DSN: The connection string.
    • Conn: An existing gorm.ConnPool to use instead of opening a new connection.
    • DisableDatetimePrecision: If true, prevents adding precision to DateTime64 types.
    • DontSupportRenameColumn: Automatically set to true if the ClickHouse version is < 20.4.
    • DontSupportColumnPrecision: Automatically set to true if the ClickHouse version is < 21.11.
    • DefaultGranularity: Default is 3 (1 granule = 8192 rows).
    • DefaultCompression: Default is LZ4.
    • DefaultIndexType: Default is minmax.
    • DefaultTableEngineOpts: Default is ENGINE=MergeTree() ORDER BY tuple().
    • InformationSchemaTablesTableTypeString: Automatically set to true if ClickHouse version is >= 23.9.
    type Config struct {
    	DriverName                   string
    	DSN                          string
    	Conn                         gorm.ConnPool
    	DisableDatetimePrecision     bool
    	DontSupportRenameColumn      bool
    	DontSupportColumnPrecision   bool
    	DontSupportEmptyDefaultValue bool
    	SkipInitializeWithVersion    bool
    	DefaultGranularity           int    // 1 granule = 8192 rows
    	DefaultCompression           string // default compression algorithm. LZ4 is lossless
    	DefaultIndexType             string // index stores extremes of the expression
    	DefaultTableEngineOpts       string
    
    InformationSchemaTablesTableTypeString bool // information_schema.tables.table_type is String
    }
  8. Manage columns in ClickHouse

    master

    The Migrator provides methods to modify table columns. Note that RenameColumn is only supported on ClickHouse versions 20.4 and above.

    • AddColumn(model, fieldName): Adds a new column.
    • DropColumn(model, fieldName): Removes a column.
    • AlterColumn(model, fieldName): Modifies an existing column's type or definition.
    • RenameColumn(model, oldName, newName): Renames a column (requires ClickHouse >= 20.4).
    • HasColumn(model, fieldName): Checks if a column exists in the system schema.
  9. Manage indexes in ClickHouse

    master

    ClickHouse uses data indexes rather than traditional B-Tree indexes. The driver supports creating and dropping these via the Migrator.

    • Supported Index Types: minmax, set(n), ngrambf_v1(n, size, hash, seed), and bloomfilter().
    • Granularity: You can specify granularity via the gorm:"index" tag (e.g., gorm:"index,type:minmax,granularity:3").
    • Limitations: RenameIndex is currently unsupported. UNIQUE, FULLTEXT, and SPATIAL index types are not supported in ClickHouse.

    Methods:

    • CreateIndex(model, indexName)
    • DropIndex(model, indexName)
    • HasIndex(model, indexName)
  10. Use the ClickHouse Migrator for schema changes

    master
    The Migrator struct implements the GORM migrator.Migrator interface specifically for ClickHouse. It allows you to perform schema migrations such as creating tables, adding/dropping columns, and managing indexes. It handles ClickHouse-specific syntax like ON CLUSTER clauses and specialized column attributes (TTL, CODEC, COMMENT).
  11. Retrieve column type information

    master

    The ColumnTypes method returns detailed information about the columns of a table by querying the system.columns table. It includes metadata such as:

    • NameValue (Column name)
    • DataTypeValue (Data type)
    • DefaultValueValue (Default expression)
    • CommentValue (Column comment)
    • PrimaryKeyValue (Whether it is part of the primary key)
    • DecimalSizeValue / ScaleValue (For decimal types)
    • DatetimePrecision (For datetime types)
  12. Modify update statements to target local tables using UpdateLocalTable

    master

    In ClickHouse, updates often need to be performed on the local part of a distributed table rather than the distributed table itself. You can use the UpdateLocalTable struct to intercept GORM update statements and modify the target table name using a prefix, a suffix, or a complete replacement.

    To use this, call ModifyStatement on your GORM statement. This is typically done within a custom callback or by accessing the statement directly if you are extending the driver's behavior.

    Supported modification modes:

    • Suffix: Appends a string to the end of the table name.
    • Prefix: Prepends a string to the beginning of the table name.
    • Table: Replaces the table name entirely.
    // Example of how UpdateLocalTable might be used to target a local table
    // Note: This requires access to the gorm.Statement object
    db.Statement.Settings.Store("gorm:clickhouse:update_local_table_name", clickhouse.UpdateLocalTable{
        Suffix: "_local",
    })