diff --git a/pkg/services/sqlstore/migrations/builder.go b/pkg/services/sqlstore/migrations/builder.go new file mode 100644 index 00000000000..7fa9412f314 --- /dev/null +++ b/pkg/services/sqlstore/migrations/builder.go @@ -0,0 +1,31 @@ +package migrations + +type migration struct { + desc string + sqlite string + verifyTable string +} + +type migrationBuilder struct { + migration *migration +} + +func (b *migrationBuilder) sqlite(sql string) *migrationBuilder { + b.migration.sqlite = sql + return b +} + +func (b *migrationBuilder) verifyTable(name string) *migrationBuilder { + b.migration.verifyTable = name + return b +} + +func (b *migrationBuilder) add() *migrationBuilder { + migrationList = append(migrationList, b.migration) + return b +} + +func (b *migrationBuilder) desc(desc string) *migrationBuilder { + b.migration = &migration{desc: desc} + return b +} diff --git a/pkg/services/sqlstore/migrations/engine.go b/pkg/services/sqlstore/migrations/engine.go new file mode 100644 index 00000000000..aeb75ad6c0e --- /dev/null +++ b/pkg/services/sqlstore/migrations/engine.go @@ -0,0 +1,95 @@ +package migrations + +import ( + "errors" + "fmt" + + "github.com/go-xorm/xorm" + "github.com/torkelo/grafana-pro/pkg/services/sqlstore/sqlsyntax" +) + +var x *xorm.Engine +var dialect sqlsyntax.Dialect + +func getSchemaVersion() (int, error) { + exists, err := x.IsTableExist(new(SchemaVersion)) + if err != nil { + return 0, err + } + + if !exists { + if err := x.CreateTables(new(SchemaVersion)); err != nil { + return 0, err + } + return 0, nil + } + + v := SchemaVersion{} + _, err = x.Table("schema_version").Limit(1, 0).Desc("version").Get(&v) + return v.Version, err +} + +func StartMigration(engine *xorm.Engine) error { + x = engine + dialect = new(sqlsyntax.Sqlite3) + + _, err := getSchemaVersion() + if err != nil { + return err + } + + for _, m := range migrationList { + if err := execMigration(m); err != nil { + return err + } + } + + return nil +} + +func execMigration(m *migration) error { + err := inTransaction(func(sess *xorm.Session) error { + _, err := sess.Exec(m.sqlite) + if err != nil { + return err + } + return nil + }) + if err != nil { + return err + } + // verify + if m.verifyTable != "" { + sqlStr, args := dialect.TableCheckSql(m.verifyTable) + results, err := x.Query(sqlStr, args...) + if err != nil || len(results) == 0 { + return errors.New(fmt.Sprintf("Verify failed: table %v does not exist", m.verifyTable)) + } + } + + return nil +} + +type dbTransactionFunc func(sess *xorm.Session) error + +func inTransaction(callback dbTransactionFunc) error { + var err error + + sess := x.NewSession() + defer sess.Close() + + if err = sess.Begin(); err != nil { + return err + } + + err = callback(sess) + + if err != nil { + sess.Rollback() + return err + } else if err = sess.Commit(); err != nil { + return err + } + + return nil +} diff --git a/pkg/services/sqlstore/migrations/migrations.go b/pkg/services/sqlstore/migrations/migrations.go new file mode 100644 index 00000000000..71964f079ee --- /dev/null +++ b/pkg/services/sqlstore/migrations/migrations.go @@ -0,0 +1,26 @@ +package migrations + +var migrationList []*migration + +func init() { + new(migrationBuilder). + desc("Create account table"). + sqlite(` + CREATE TABLE account ( + id INTEGER PRIMARY KEY + ) + `). + verifyTable("account") +} + +type SchemaVersion struct { + Version int +} + +type SchemaLog struct { + Id int64 + Version int64 + Desc string + Info string + Error bool +} diff --git a/pkg/services/sqlstore/migrations/migrations_test.go b/pkg/services/sqlstore/migrations/migrations_test.go new file mode 100644 index 00000000000..c670f856592 --- /dev/null +++ b/pkg/services/sqlstore/migrations/migrations_test.go @@ -0,0 +1,22 @@ +package migrations + +// import ( +// "testing" +// +// "github.com/go-xorm/xorm" +// +// . "github.com/smartystreets/goconvey/convey" +// ) +// +// func TestMigrationsSqlite(t *testing.T) { +// +// Convey("Initial SQLite3 migration", t, func() { +// x, err := xorm.NewEngine("sqlite3", ":memory:") +// StartMigration(x) +// +// tables, err := x.DBMetas() +// So(err, ShouldBeNil) +// +// So(len(tables), ShouldEqual, 1) +// }) +// } diff --git a/pkg/services/sqlstore/sqlstore.go b/pkg/services/sqlstore/sqlstore.go index 9fed4855c7a..eae66e1975b 100644 --- a/pkg/services/sqlstore/sqlstore.go +++ b/pkg/services/sqlstore/sqlstore.go @@ -31,12 +31,6 @@ var ( UseSQLite3 bool ) -type DashboardTag struct { - Id int64 - DashboardId int64 - Term string -} - func init() { tables = make([]interface{}, 0) diff --git a/pkg/services/sqlstore/sqlsyntax/dialect.go b/pkg/services/sqlstore/sqlsyntax/dialect.go new file mode 100644 index 00000000000..526ce2decaf --- /dev/null +++ b/pkg/services/sqlstore/sqlsyntax/dialect.go @@ -0,0 +1,18 @@ +package sqlsyntax + +type Dialect interface { + DBType() string + TableCheckSql(tableName string) (string, []interface{}) +} + +type Sqlite3 struct { +} + +func (db *Sqlite3) DBType() string { + return "sqlite3" +} + +func (db *Sqlite3) TableCheckSql(tableName string) (string, []interface{}) { + args := []interface{}{tableName} + return "SELECT name FROM sqlite_master WHERE type='table' and name = ?", args +} diff --git a/pkg/services/sqlstore/tables.go b/pkg/services/sqlstore/tables.go new file mode 100644 index 00000000000..f7dc0b5e0ec --- /dev/null +++ b/pkg/services/sqlstore/tables.go @@ -0,0 +1,9 @@ +package sqlstore + +// extra tables not required by the core/outside model + +type DashboardTag struct { + Id int64 + DashboardId int64 + Term string +}