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