mirror of https://github.com/writeas/writefreely
This runs database migrations and obviates the need for manually running changes. Ref T509pull/68/head
parent
6c2bd8031a
commit
47b2155f92
@ -0,0 +1,66 @@ |
||||
/* |
||||
* Copyright © 2019 A Bunch Tell LLC. |
||||
* |
||||
* This file is part of WriteFreely. |
||||
* |
||||
* WriteFreely is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License, included |
||||
* in the LICENSE file in this source code package. |
||||
*/ |
||||
|
||||
package migrations |
||||
|
||||
import ( |
||||
"fmt" |
||||
) |
||||
|
||||
// TODO: use now() from writefreely pkg
|
||||
func (db *datastore) now() string { |
||||
if db.driverName == driverSQLite { |
||||
return "strftime('%Y-%m-%d %H:%M:%S','now')" |
||||
} |
||||
return "NOW()" |
||||
} |
||||
|
||||
func (db *datastore) typeInt() string { |
||||
if db.driverName == driverSQLite { |
||||
return "INTEGER" |
||||
} |
||||
return "INT" |
||||
} |
||||
|
||||
func (db *datastore) typeSmallInt() string { |
||||
if db.driverName == driverSQLite { |
||||
return "INTEGER" |
||||
} |
||||
return "SMALLINT" |
||||
} |
||||
|
||||
func (db *datastore) typeText() string { |
||||
return "TEXT" |
||||
} |
||||
|
||||
func (db *datastore) typeChar(l int) string { |
||||
if db.driverName == driverSQLite { |
||||
return "TEXT" |
||||
} |
||||
return fmt.Sprintf("CHAR(%d)", l) |
||||
} |
||||
|
||||
func (db *datastore) typeBool() string { |
||||
if db.driverName == driverSQLite { |
||||
return "INTEGER" |
||||
} |
||||
return "TINYINT(1)" |
||||
} |
||||
|
||||
func (db *datastore) typeDateTime() string { |
||||
return "DATETIME" |
||||
} |
||||
|
||||
func (db *datastore) engine() string { |
||||
if db.driverName == driverSQLite { |
||||
return "" |
||||
} |
||||
return " ENGINE = InnoDB" |
||||
} |
@ -0,0 +1,116 @@ |
||||
/* |
||||
* Copyright © 2019 A Bunch Tell LLC. |
||||
* |
||||
* This file is part of WriteFreely. |
||||
* |
||||
* WriteFreely is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License, included |
||||
* in the LICENSE file in this source code package. |
||||
*/ |
||||
|
||||
// Package migrations contains database migrations for WriteFreely
|
||||
package migrations |
||||
|
||||
import ( |
||||
"database/sql" |
||||
"github.com/writeas/web-core/log" |
||||
) |
||||
|
||||
// TODO: refactor to use the datastore struct from writefreely pkg
|
||||
type datastore struct { |
||||
*sql.DB |
||||
driverName string |
||||
} |
||||
|
||||
func NewDatastore(db *sql.DB, dn string) *datastore { |
||||
return &datastore{db, dn} |
||||
} |
||||
|
||||
// TODO: use these consts from writefreely pkg
|
||||
const ( |
||||
driverMySQL = "mysql" |
||||
driverSQLite = "sqlite3" |
||||
) |
||||
|
||||
type Migration interface { |
||||
Description() string |
||||
Migrate(db *datastore) error |
||||
} |
||||
|
||||
type migration struct { |
||||
description string |
||||
migrate func(db *datastore) error |
||||
} |
||||
|
||||
func New(d string, fn func(db *datastore) error) Migration { |
||||
return &migration{d, fn} |
||||
} |
||||
|
||||
func (m *migration) Description() string { |
||||
return m.description |
||||
} |
||||
|
||||
func (m *migration) Migrate(db *datastore) error { |
||||
return m.migrate(db) |
||||
} |
||||
|
||||
var migrations = []Migration{} |
||||
|
||||
func Migrate(db *datastore) error { |
||||
var version int |
||||
var err error |
||||
if db.tableExists("appmigrations") { |
||||
err = db.QueryRow("SELECT MAX(version) FROM appmigrations").Scan(&version) |
||||
} else { |
||||
log.Info("Initializing appmigrations table...") |
||||
version = 0 |
||||
_, err = db.Exec(`CREATE TABLE appmigrations ( |
||||
version ` + db.typeInt() + ` NOT NULL, |
||||
migrated ` + db.typeDateTime() + ` NOT NULL, |
||||
result ` + db.typeText() + ` NOT NULL |
||||
) ` + db.engine() + `;`) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
} |
||||
|
||||
if len(migrations[version:]) > 0 { |
||||
for i, m := range migrations[version:] { |
||||
curVer := version + i + 1 |
||||
log.Info("Migrating to V%d: %s", curVer, m.Description()) |
||||
err = m.Migrate(db) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
// Update migrations table
|
||||
_, err = db.Exec("INSERT INTO appmigrations (version, migrated, result) VALUES (?, "+db.now()+", ?)", curVer, "") |
||||
if err != nil { |
||||
return err |
||||
} |
||||
} |
||||
} else { |
||||
log.Info("Database up-to-date. No migrations to run.") |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
func (db *datastore) tableExists(t string) bool { |
||||
var dummy string |
||||
var err error |
||||
if db.driverName == driverSQLite { |
||||
err = db.QueryRow("SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?", t).Scan(&dummy) |
||||
} else { |
||||
err = db.QueryRow("SHOW TABLES LIKE ?", t).Scan(&dummy) |
||||
} |
||||
switch { |
||||
case err == sql.ErrNoRows: |
||||
return false |
||||
case err != nil: |
||||
log.Error("Couldn't SHOW TABLES: %v", err) |
||||
return false |
||||
} |
||||
|
||||
return true |
||||
} |
Loading…
Reference in new issue