mirror of
https://github.com/akvorado/akvorado.git
synced 2025-12-11 22:14:02 +01:00
165 lines
3.8 KiB
Go
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()
|
|
}
|