Files
akvorado/configure/clickhouse/migrations-driver.go
2022-04-01 20:21:53 +02:00

165 lines
3.8 KiB
Go

package clickhouse
import (
"database/sql"
"fmt"
"io"
"io/ioutil"
"time"
_ "github.com/ClickHouse/clickhouse-go/v2" // clickhouse driver for database/sql
"github.com/golang-migrate/migrate/v4/database"
"github.com/hashicorp/go-multierror"
"go.uber.org/atomic"
)
// Code below is a trimmed version of
// https://github.com/golang-migrate/migrate/blob/master/database/clickhouse/clickhouse.go
// The goal is to make it work with v2 driver. I have dropped
// clustering and hard-coded the database name.
func init() {
database.Register("clickhouse", &ClickHouse{})
}
// ClickHouse struct represents the migrate ClickHouse driver
type ClickHouse struct {
conn *sql.DB
isLocked atomic.Bool
}
// Open opens the connection to ClickHouse using the provided DSN
func (ch *ClickHouse) Open(dsn string) (database.Driver, error) {
conn, err := sql.Open("clickhouse", dsn)
if err != nil {
return nil, err
}
ch = &ClickHouse{conn: conn}
if err = ch.Lock(); err != nil {
return nil, err
}
defer func() {
if e := ch.Unlock(); e != nil {
err = multierror.Append(err, e)
}
}()
var (
table string
query = "SHOW TABLES LIKE 'schema_migrations'"
)
// Check if migration table exists
if err := ch.conn.QueryRow(query).Scan(&table); err != nil {
if err != sql.ErrNoRows {
return nil, &database.Error{
OrigErr: err,
Err: "cannot get list of tables",
Query: []byte(query),
}
}
} else {
return ch, nil
}
// If not, create
query = `
CREATE TABLE schema_migrations (
version Int64,
dirty UInt8,
sequence UInt64
) Engine=TinyLog`
if _, err := ch.conn.Exec(query); err != nil {
return nil, &database.Error{
OrigErr: err,
Err: "cannot create schema migrations table",
Query: []byte(query),
}
}
return ch, nil
}
// Run executes the provided migration
func (ch *ClickHouse) Run(r io.Reader) error {
migration, err := ioutil.ReadAll(r)
if err != nil {
return fmt.Errorf("unable to read migration: %w", err)
}
if _, err := ch.conn.Exec(string(migration)); err != nil {
return database.Error{OrigErr: err, Err: "migration failed", Query: migration}
}
return nil
}
// Version returns the current version.
func (ch *ClickHouse) Version() (int, bool, error) {
var (
version int
dirty uint8
)
query := "SELECT version, dirty FROM `schema_migrations` ORDER BY sequence DESC LIMIT 1"
if err := ch.conn.QueryRow(query).Scan(&version, &dirty); err != nil {
if err == sql.ErrNoRows {
return database.NilVersion, false, nil
}
return 0, false, &database.Error{OrigErr: err, Err: "cannot get last version", Query: []byte(query)}
}
return version, dirty == 1, nil
}
// SetVersion updates the current version.
func (ch *ClickHouse) SetVersion(version int, dirty bool) error {
bool := func(v bool) uint8 {
if v {
return 1
}
return 0
}
tx, err := ch.conn.Begin()
if err != nil {
return err
}
query := "INSERT INTO `schema_migrations` (version, dirty, sequence) VALUES"
batch, err := tx.Prepare(query)
if err != nil {
return database.Error{OrigErr: err, Err: "cannot update schema migrations table", Query: []byte(query)}
}
if _, err := batch.Exec(int64(version), bool(dirty), uint64(time.Now().UnixNano())); err != nil {
return &database.Error{OrigErr: err, Query: []byte(query)}
}
return tx.Commit()
}
// Drop should have removed the table. Not used.
func (ch *ClickHouse) Drop() (err error) {
panic("unused")
}
// Lock locks the ClickHouse database.
func (ch *ClickHouse) Lock() error {
if !ch.isLocked.CAS(false, true) {
return database.ErrLocked
}
return nil
}
// Unlock unlocks the ClickHouse database.
func (ch *ClickHouse) Unlock() error {
if !ch.isLocked.CAS(true, false) {
return database.ErrNotLocked
}
return nil
}
// Close close the connection to ClickHouse.
func (ch *ClickHouse) Close() error {
return ch.conn.Close()
}