github.com/dhui/migrate@v3.4.0+incompatible/database/clickhouse/clickhouse.go (about)

     1  package clickhouse
     2  
     3  import (
     4  	"database/sql"
     5  	"fmt"
     6  	"io"
     7  	"io/ioutil"
     8  	"net/url"
     9  	"time"
    10  
    11  	"github.com/golang-migrate/migrate"
    12  	"github.com/golang-migrate/migrate/database"
    13  )
    14  
    15  var DefaultMigrationsTable = "schema_migrations"
    16  
    17  var ErrNilConfig = fmt.Errorf("no config")
    18  
    19  type Config struct {
    20  	DatabaseName    string
    21  	MigrationsTable string
    22  }
    23  
    24  func init() {
    25  	database.Register("clickhouse", &ClickHouse{})
    26  }
    27  
    28  func WithInstance(conn *sql.DB, config *Config) (database.Driver, error) {
    29  	if config == nil {
    30  		return nil, ErrNilConfig
    31  	}
    32  
    33  	if err := conn.Ping(); err != nil {
    34  		return nil, err
    35  	}
    36  
    37  	ch := &ClickHouse{
    38  		conn:   conn,
    39  		config: config,
    40  	}
    41  
    42  	if err := ch.init(); err != nil {
    43  		return nil, err
    44  	}
    45  
    46  	return ch, nil
    47  }
    48  
    49  type ClickHouse struct {
    50  	conn   *sql.DB
    51  	config *Config
    52  }
    53  
    54  func (ch *ClickHouse) Open(dsn string) (database.Driver, error) {
    55  	purl, err := url.Parse(dsn)
    56  	if err != nil {
    57  		return nil, err
    58  	}
    59  	q := migrate.FilterCustomQuery(purl)
    60  	q.Scheme = "tcp"
    61  	conn, err := sql.Open("clickhouse", q.String())
    62  	if err != nil {
    63  		return nil, err
    64  	}
    65  
    66  	ch = &ClickHouse{
    67  		conn: conn,
    68  		config: &Config{
    69  			MigrationsTable: purl.Query().Get("x-migrations-table"),
    70  			DatabaseName:    purl.Query().Get("database"),
    71  		},
    72  	}
    73  
    74  	if err := ch.init(); err != nil {
    75  		return nil, err
    76  	}
    77  
    78  	return ch, nil
    79  }
    80  
    81  func (ch *ClickHouse) init() error {
    82  	if len(ch.config.DatabaseName) == 0 {
    83  		if err := ch.conn.QueryRow("SELECT currentDatabase()").Scan(&ch.config.DatabaseName); err != nil {
    84  			return err
    85  		}
    86  	}
    87  
    88  	if len(ch.config.MigrationsTable) == 0 {
    89  		ch.config.MigrationsTable = DefaultMigrationsTable
    90  	}
    91  
    92  	return ch.ensureVersionTable()
    93  }
    94  
    95  func (ch *ClickHouse) Run(r io.Reader) error {
    96  	migration, err := ioutil.ReadAll(r)
    97  	if err != nil {
    98  		return err
    99  	}
   100  	if _, err := ch.conn.Exec(string(migration)); err != nil {
   101  		return database.Error{OrigErr: err, Err: "migration failed", Query: migration}
   102  	}
   103  
   104  	return nil
   105  }
   106  func (ch *ClickHouse) Version() (int, bool, error) {
   107  	var (
   108  		version int
   109  		dirty   uint8
   110  		query   = "SELECT version, dirty FROM `" + ch.config.MigrationsTable + "` ORDER BY sequence DESC LIMIT 1"
   111  	)
   112  	if err := ch.conn.QueryRow(query).Scan(&version, &dirty); err != nil {
   113  		if err == sql.ErrNoRows {
   114  			return database.NilVersion, false, nil
   115  		}
   116  		return 0, false, &database.Error{OrigErr: err, Query: []byte(query)}
   117  	}
   118  	return version, dirty == 1, nil
   119  }
   120  
   121  func (ch *ClickHouse) SetVersion(version int, dirty bool) error {
   122  	var (
   123  		bool = func(v bool) uint8 {
   124  			if v {
   125  				return 1
   126  			}
   127  			return 0
   128  		}
   129  		tx, err = ch.conn.Begin()
   130  	)
   131  	if err != nil {
   132  		return err
   133  	}
   134  
   135  	query := "INSERT INTO " + ch.config.MigrationsTable + " (version, dirty, sequence) VALUES (?, ?, ?)"
   136  	if _, err := tx.Exec(query, version, bool(dirty), time.Now().UnixNano()); err != nil {
   137  		return &database.Error{OrigErr: err, Query: []byte(query)}
   138  	}
   139  
   140  	return tx.Commit()
   141  }
   142  
   143  func (ch *ClickHouse) ensureVersionTable() error {
   144  	var (
   145  		table string
   146  		query = "SHOW TABLES FROM " + ch.config.DatabaseName + " LIKE '" + ch.config.MigrationsTable + "'"
   147  	)
   148  	// check if migration table exists
   149  	if err := ch.conn.QueryRow(query).Scan(&table); err != nil {
   150  		if err != sql.ErrNoRows {
   151  			return &database.Error{OrigErr: err, Query: []byte(query)}
   152  		}
   153  	} else {
   154  		return nil
   155  	}
   156  	// if not, create the empty migration table
   157  	query = `
   158  		CREATE TABLE ` + ch.config.MigrationsTable + ` (
   159  			version    UInt32, 
   160  			dirty      UInt8,
   161  			sequence   UInt64
   162  		) Engine=TinyLog
   163  	`
   164  	if _, err := ch.conn.Exec(query); err != nil {
   165  		return &database.Error{OrigErr: err, Query: []byte(query)}
   166  	}
   167  	return nil
   168  }
   169  
   170  func (ch *ClickHouse) Drop() error {
   171  	var (
   172  		query       = "SHOW TABLES FROM " + ch.config.DatabaseName
   173  		tables, err = ch.conn.Query(query)
   174  	)
   175  	if err != nil {
   176  		return &database.Error{OrigErr: err, Query: []byte(query)}
   177  	}
   178  	defer tables.Close()
   179  	for tables.Next() {
   180  		var table string
   181  		if err := tables.Scan(&table); err != nil {
   182  			return err
   183  		}
   184  
   185  		query = "DROP TABLE IF EXISTS " + ch.config.DatabaseName + "." + table
   186  
   187  		if _, err := ch.conn.Exec(query); err != nil {
   188  			return &database.Error{OrigErr: err, Query: []byte(query)}
   189  		}
   190  	}
   191  	return ch.ensureVersionTable()
   192  }
   193  
   194  func (ch *ClickHouse) Lock() error   { return nil }
   195  func (ch *ClickHouse) Unlock() error { return nil }
   196  func (ch *ClickHouse) Close() error  { return ch.conn.Close() }