github.com/matcornic/migrate@v3.3.2-0.20180717234201-feea45c20506+incompatible/database/ql/ql.go (about) 1 package ql 2 3 import ( 4 "database/sql" 5 "fmt" 6 "io" 7 "io/ioutil" 8 "strings" 9 10 nurl "net/url" 11 12 _ "github.com/cznic/ql/driver" 13 "github.com/golang-migrate/migrate" 14 "github.com/golang-migrate/migrate/database" 15 ) 16 17 func init() { 18 database.Register("ql", &Ql{}) 19 } 20 21 var DefaultMigrationsTable = "schema_migrations" 22 var ( 23 ErrDatabaseDirty = fmt.Errorf("database is dirty") 24 ErrNilConfig = fmt.Errorf("no config") 25 ErrNoDatabaseName = fmt.Errorf("no database name") 26 ErrAppendPEM = fmt.Errorf("failed to append PEM") 27 ) 28 29 type Config struct { 30 MigrationsTable string 31 DatabaseName string 32 } 33 34 type Ql struct { 35 db *sql.DB 36 isLocked bool 37 38 config *Config 39 } 40 41 func WithInstance(instance *sql.DB, config *Config) (database.Driver, error) { 42 if config == nil { 43 return nil, ErrNilConfig 44 } 45 46 if err := instance.Ping(); err != nil { 47 return nil, err 48 } 49 if len(config.MigrationsTable) == 0 { 50 config.MigrationsTable = DefaultMigrationsTable 51 } 52 53 mx := &Ql{ 54 db: instance, 55 config: config, 56 } 57 if err := mx.ensureVersionTable(); err != nil { 58 return nil, err 59 } 60 return mx, nil 61 } 62 func (m *Ql) ensureVersionTable() error { 63 tx, err := m.db.Begin() 64 if err != nil { 65 return err 66 } 67 if _, err := tx.Exec(fmt.Sprintf(` 68 CREATE TABLE IF NOT EXISTS %s (version uint64,dirty bool); 69 CREATE UNIQUE INDEX IF NOT EXISTS version_unique ON %s (version); 70 `, m.config.MigrationsTable, m.config.MigrationsTable)); err != nil { 71 if err := tx.Rollback(); err != nil { 72 return err 73 } 74 return err 75 } 76 if err := tx.Commit(); err != nil { 77 return err 78 } 79 return nil 80 } 81 82 func (m *Ql) Open(url string) (database.Driver, error) { 83 purl, err := nurl.Parse(url) 84 if err != nil { 85 return nil, err 86 } 87 dbfile := strings.Replace(migrate.FilterCustomQuery(purl).String(), "ql://", "", 1) 88 db, err := sql.Open("ql", dbfile) 89 if err != nil { 90 return nil, err 91 } 92 migrationsTable := purl.Query().Get("x-migrations-table") 93 if len(migrationsTable) == 0 { 94 migrationsTable = DefaultMigrationsTable 95 } 96 mx, err := WithInstance(db, &Config{ 97 DatabaseName: purl.Path, 98 MigrationsTable: migrationsTable, 99 }) 100 if err != nil { 101 return nil, err 102 } 103 return mx, nil 104 } 105 func (m *Ql) Close() error { 106 return m.db.Close() 107 } 108 func (m *Ql) Drop() error { 109 query := `SELECT Name FROM __Table` 110 tables, err := m.db.Query(query) 111 if err != nil { 112 return &database.Error{OrigErr: err, Query: []byte(query)} 113 } 114 defer tables.Close() 115 tableNames := make([]string, 0) 116 for tables.Next() { 117 var tableName string 118 if err := tables.Scan(&tableName); err != nil { 119 return err 120 } 121 if len(tableName) > 0 { 122 if strings.HasPrefix(tableName, "__") == false { 123 tableNames = append(tableNames, tableName) 124 } 125 } 126 } 127 if len(tableNames) > 0 { 128 for _, t := range tableNames { 129 query := "DROP TABLE " + t 130 err = m.executeQuery(query) 131 if err != nil { 132 return &database.Error{OrigErr: err, Query: []byte(query)} 133 } 134 } 135 if err := m.ensureVersionTable(); err != nil { 136 return err 137 } 138 } 139 140 return nil 141 } 142 func (m *Ql) Lock() error { 143 if m.isLocked { 144 return database.ErrLocked 145 } 146 m.isLocked = true 147 return nil 148 } 149 func (m *Ql) Unlock() error { 150 if !m.isLocked { 151 return nil 152 } 153 m.isLocked = false 154 return nil 155 } 156 func (m *Ql) Run(migration io.Reader) error { 157 migr, err := ioutil.ReadAll(migration) 158 if err != nil { 159 return err 160 } 161 query := string(migr[:]) 162 163 return m.executeQuery(query) 164 } 165 func (m *Ql) executeQuery(query string) error { 166 tx, err := m.db.Begin() 167 if err != nil { 168 return &database.Error{OrigErr: err, Err: "transaction start failed"} 169 } 170 if _, err := tx.Exec(query); err != nil { 171 tx.Rollback() 172 return &database.Error{OrigErr: err, Query: []byte(query)} 173 } 174 if err := tx.Commit(); err != nil { 175 return &database.Error{OrigErr: err, Err: "transaction commit failed"} 176 } 177 return nil 178 } 179 func (m *Ql) SetVersion(version int, dirty bool) error { 180 tx, err := m.db.Begin() 181 if err != nil { 182 return &database.Error{OrigErr: err, Err: "transaction start failed"} 183 } 184 185 query := "TRUNCATE TABLE " + m.config.MigrationsTable 186 if _, err := tx.Exec(query); err != nil { 187 return &database.Error{OrigErr: err, Query: []byte(query)} 188 } 189 190 if version >= 0 { 191 query := fmt.Sprintf(`INSERT INTO %s (version, dirty) VALUES (%d, %t)`, m.config.MigrationsTable, version, dirty) 192 if _, err := tx.Exec(query); err != nil { 193 tx.Rollback() 194 return &database.Error{OrigErr: err, Query: []byte(query)} 195 } 196 } 197 198 if err := tx.Commit(); err != nil { 199 return &database.Error{OrigErr: err, Err: "transaction commit failed"} 200 } 201 202 return nil 203 } 204 205 func (m *Ql) Version() (version int, dirty bool, err error) { 206 query := "SELECT version, dirty FROM " + m.config.MigrationsTable + " LIMIT 1" 207 err = m.db.QueryRow(query).Scan(&version, &dirty) 208 if err != nil { 209 return database.NilVersion, false, nil 210 } 211 return version, dirty, nil 212 }