mirror of https://github.com/grafana/grafana.git
				
				
				
			Worked on database agnostic table creation for db migrations
This commit is contained in:
		
							parent
							
								
									7d70ffe201
								
							
						
					
					
						commit
						d8e5be5782
					
				|  | @ -4,4 +4,8 @@ app_mode = development | |||
| router_logging = false | ||||
| static_root_path = grafana/src | ||||
| 
 | ||||
| [log] | ||||
| level = Trace | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -12,19 +12,17 @@ var ( | |||
| 
 | ||||
| // Directly mapped to db schema, Do not change field names lighly
 | ||||
| type Account struct { | ||||
| 	Id              int64 | ||||
| 	Login           string `xorm:"UNIQUE NOT NULL"` | ||||
| 	Email           string `xorm:"UNIQUE NOT NULL"` | ||||
| 	Name            string | ||||
| 	FullName        string | ||||
| 	Password        string | ||||
| 	IsAdmin         bool | ||||
| 	Salt            string `xorm:"VARCHAR(10)"` | ||||
| 	Company         string | ||||
| 	NextDashboardId int | ||||
| 	UsingAccountId  int64 | ||||
| 	Created         time.Time | ||||
| 	Updated         time.Time | ||||
| 	Id             int64 | ||||
| 	Login          string `xorm:"UNIQUE NOT NULL"` | ||||
| 	Email          string `xorm:"UNIQUE NOT NULL"` | ||||
| 	Name           string | ||||
| 	Password       string | ||||
| 	IsAdmin        bool | ||||
| 	Salt           string `xorm:"VARCHAR(10)"` | ||||
| 	Company        string | ||||
| 	UsingAccountId int64 | ||||
| 	Created        time.Time | ||||
| 	Updated        time.Time | ||||
| } | ||||
| 
 | ||||
| // ---------------------
 | ||||
|  |  | |||
|  | @ -1,28 +1,5 @@ | |||
| package migrations | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	POSTGRES = "postgres" | ||||
| 	SQLITE   = "sqlite3" | ||||
| 	MYSQL    = "mysql" | ||||
| ) | ||||
| 
 | ||||
| type Migration interface { | ||||
| 	Sql(dialect Dialect) string | ||||
| 	Id() string | ||||
| 	SetId(string) | ||||
| } | ||||
| 
 | ||||
| type ColumnType string | ||||
| 
 | ||||
| const ( | ||||
| 	DB_TYPE_STRING ColumnType = "String" | ||||
| ) | ||||
| 
 | ||||
| type MigrationBase struct { | ||||
| 	id string | ||||
| } | ||||
|  | @ -65,10 +42,8 @@ func (m *RawSqlMigration) Mysql(sql string) *RawSqlMigration { | |||
| 
 | ||||
| type AddColumnMigration struct { | ||||
| 	MigrationBase | ||||
| 	tableName  string | ||||
| 	columnName string | ||||
| 	columnType ColumnType | ||||
| 	length     int | ||||
| 	tableName string | ||||
| 	column    *Column | ||||
| } | ||||
| 
 | ||||
| func (m *AddColumnMigration) Table(tableName string) *AddColumnMigration { | ||||
|  | @ -76,35 +51,23 @@ func (m *AddColumnMigration) Table(tableName string) *AddColumnMigration { | |||
| 	return m | ||||
| } | ||||
| 
 | ||||
| func (m *AddColumnMigration) Length(length int) *AddColumnMigration { | ||||
| 	m.length = length | ||||
| 	return m | ||||
| } | ||||
| 
 | ||||
| func (m *AddColumnMigration) Column(columnName string) *AddColumnMigration { | ||||
| 	m.columnName = columnName | ||||
| 	return m | ||||
| } | ||||
| 
 | ||||
| func (m *AddColumnMigration) Type(columnType ColumnType) *AddColumnMigration { | ||||
| 	m.columnType = columnType | ||||
| func (m *AddColumnMigration) Column(col *Column) *AddColumnMigration { | ||||
| 	m.column = col | ||||
| 	return m | ||||
| } | ||||
| 
 | ||||
| func (m *AddColumnMigration) Sql(dialect Dialect) string { | ||||
| 	return fmt.Sprintf("ALTER TABLE %s ADD COLUMN %s %s", m.tableName, m.columnName, dialect.ToDBTypeSql(m.columnType, m.length)) | ||||
| 	return dialect.AddColumnSql(m.tableName, m.column) | ||||
| } | ||||
| 
 | ||||
| type AddIndexMigration struct { | ||||
| 	MigrationBase | ||||
| 	tableName string | ||||
| 	columns   string | ||||
| 	indexName string | ||||
| 	unique    string | ||||
| 	index     Index | ||||
| } | ||||
| 
 | ||||
| func (m *AddIndexMigration) Name(name string) *AddIndexMigration { | ||||
| 	m.indexName = name | ||||
| 	m.index.Name = name | ||||
| 	return m | ||||
| } | ||||
| 
 | ||||
|  | @ -114,15 +77,47 @@ func (m *AddIndexMigration) Table(tableName string) *AddIndexMigration { | |||
| } | ||||
| 
 | ||||
| func (m *AddIndexMigration) Unique() *AddIndexMigration { | ||||
| 	m.unique = "UNIQUE" | ||||
| 	m.index.Type = UniqueIndex | ||||
| 	return m | ||||
| } | ||||
| 
 | ||||
| func (m *AddIndexMigration) Columns(columns ...string) *AddIndexMigration { | ||||
| 	m.columns = strings.Join(columns, ",") | ||||
| 	m.index.Cols = columns | ||||
| 	return m | ||||
| } | ||||
| 
 | ||||
| func (m *AddIndexMigration) Sql(dialect Dialect) string { | ||||
| 	return fmt.Sprintf("CREATE %s INDEX %s ON %s(%s)", m.unique, m.indexName, m.tableName, m.columns) | ||||
| 	return dialect.CreateIndexSql(m.tableName, &m.index) | ||||
| } | ||||
| 
 | ||||
| type AddTableMigration struct { | ||||
| 	MigrationBase | ||||
| 	table Table | ||||
| } | ||||
| 
 | ||||
| func (m *AddTableMigration) Sql(d Dialect) string { | ||||
| 	return d.CreateTableSql(&m.table) | ||||
| } | ||||
| 
 | ||||
| func (m *AddTableMigration) Name(name string) *AddTableMigration { | ||||
| 	m.table.Name = name | ||||
| 	return m | ||||
| } | ||||
| 
 | ||||
| func (m *AddTableMigration) WithColumns(columns ...*Column) *AddTableMigration { | ||||
| 	for _, col := range columns { | ||||
| 		m.table.Columns = append(m.table.Columns, col) | ||||
| 		if col.IsPrimaryKey { | ||||
| 			m.table.PrimaryKeys = append(m.table.PrimaryKeys, col.Name) | ||||
| 		} | ||||
| 	} | ||||
| 	return m | ||||
| } | ||||
| 
 | ||||
| func (m *AddTableMigration) WithColumn(col *Column) *AddTableMigration { | ||||
| 	m.table.Columns = append(m.table.Columns, col) | ||||
| 	if col.IsPrimaryKey { | ||||
| 		m.table.PrimaryKeys = append(m.table.PrimaryKeys, col.Name) | ||||
| 	} | ||||
| 	return m | ||||
| } | ||||
|  |  | |||
|  | @ -0,0 +1,62 @@ | |||
| package migrations | ||||
| 
 | ||||
| // Notice
 | ||||
| // code based on parts from from https://github.com/go-xorm/core/blob/3e0fa232ab5c90996406c0cd7ae86ad0e5ecf85f/column.go
 | ||||
| 
 | ||||
| type Column struct { | ||||
| 	Name            string | ||||
| 	Type            string | ||||
| 	Length          int | ||||
| 	Length2         int | ||||
| 	Nullable        bool | ||||
| 	IsPrimaryKey    bool | ||||
| 	IsAutoIncrement bool | ||||
| 	Default         string | ||||
| } | ||||
| 
 | ||||
| func (col *Column) String(d Dialect) string { | ||||
| 	sql := d.QuoteStr() + col.Name + d.QuoteStr() + " " | ||||
| 
 | ||||
| 	sql += d.SqlType(col) + " " | ||||
| 
 | ||||
| 	if col.IsPrimaryKey { | ||||
| 		sql += "PRIMARY KEY " | ||||
| 		if col.IsAutoIncrement { | ||||
| 			sql += d.AutoIncrStr() + " " | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if d.ShowCreateNull() { | ||||
| 		if col.Nullable { | ||||
| 			sql += "NULL " | ||||
| 		} else { | ||||
| 			sql += "NOT NULL " | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if col.Default != "" { | ||||
| 		sql += "DEFAULT " + col.Default + " " | ||||
| 	} | ||||
| 
 | ||||
| 	return sql | ||||
| } | ||||
| 
 | ||||
| func (col *Column) StringNoPk(d Dialect) string { | ||||
| 	sql := d.QuoteStr() + col.Name + d.QuoteStr() + " " | ||||
| 
 | ||||
| 	sql += d.SqlType(col) + " " | ||||
| 
 | ||||
| 	if d.ShowCreateNull() { | ||||
| 		if col.Nullable { | ||||
| 			sql += "NULL " | ||||
| 		} else { | ||||
| 			sql += "NOT NULL " | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if col.Default != "" { | ||||
| 		sql += "DEFAULT " + col.Default + " " | ||||
| 	} | ||||
| 
 | ||||
| 	return sql | ||||
| } | ||||
|  | @ -1,52 +1,97 @@ | |||
| package migrations | ||||
| 
 | ||||
| import "fmt" | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
| ) | ||||
| 
 | ||||
| type Dialect interface { | ||||
| 	DriverName() string | ||||
| 	ToDBTypeSql(columnType ColumnType, length int) string | ||||
| 	QuoteStr() string | ||||
| 	Quote(string) string | ||||
| 	AndStr() string | ||||
| 	AutoIncrStr() string | ||||
| 	OrStr() string | ||||
| 	EqStr() string | ||||
| 	ShowCreateNull() bool | ||||
| 	SqlType(col *Column) string | ||||
| 
 | ||||
| 	CreateIndexSql(tableName string, index *Index) string | ||||
| 	CreateTableSql(table *Table) string | ||||
| 	AddColumnSql(tableName string, Col *Column) string | ||||
| 
 | ||||
| 	TableCheckSql(tableName string) (string, []interface{}) | ||||
| } | ||||
| 
 | ||||
| type Sqlite3 struct { | ||||
| type BaseDialect struct { | ||||
| 	dialect    Dialect | ||||
| 	driverName string | ||||
| } | ||||
| 
 | ||||
| type Mysql struct { | ||||
| func (d *BaseDialect) DriverName() string { | ||||
| 	return d.driverName | ||||
| } | ||||
| 
 | ||||
| func (db *Sqlite3) DriverName() string { | ||||
| 	return SQLITE | ||||
| func (b *BaseDialect) ShowCreateNull() bool { | ||||
| 	return true | ||||
| } | ||||
| 
 | ||||
| func (db *Mysql) DriverName() string { | ||||
| 	return MYSQL | ||||
| func (b *BaseDialect) AndStr() string { | ||||
| 	return "AND" | ||||
| } | ||||
| 
 | ||||
| func (db *Sqlite3) ToDBTypeSql(columnType ColumnType, length int) string { | ||||
| 	switch columnType { | ||||
| 	case DB_TYPE_STRING: | ||||
| 		return "TEXT" | ||||
| func (b *BaseDialect) OrStr() string { | ||||
| 	return "OR" | ||||
| } | ||||
| 
 | ||||
| func (b *BaseDialect) EqStr() string { | ||||
| 	return "=" | ||||
| } | ||||
| 
 | ||||
| func (b *BaseDialect) CreateTableSql(table *Table) string { | ||||
| 	var sql string | ||||
| 	sql = "CREATE TABLE IF NOT EXISTS " | ||||
| 	sql += b.dialect.Quote(table.Name) + " (\n" | ||||
| 
 | ||||
| 	pkList := table.PrimaryKeys | ||||
| 
 | ||||
| 	for _, col := range table.Columns { | ||||
| 		if col.IsPrimaryKey && len(pkList) == 1 { | ||||
| 			sql += col.String(b.dialect) | ||||
| 		} else { | ||||
| 			sql += col.StringNoPk(b.dialect) | ||||
| 		} | ||||
| 		sql = strings.TrimSpace(sql) | ||||
| 		sql += "\n, " | ||||
| 	} | ||||
| 
 | ||||
| 	panic("Unsupported db type") | ||||
| } | ||||
| 
 | ||||
| func (db *Mysql) ToDBTypeSql(columnType ColumnType, length int) string { | ||||
| 	switch columnType { | ||||
| 	case DB_TYPE_STRING: | ||||
| 		return fmt.Sprintf("NVARCHAR(%d)", length) | ||||
| 	if len(pkList) > 1 { | ||||
| 		sql += "PRIMARY KEY ( " | ||||
| 		sql += b.dialect.Quote(strings.Join(pkList, b.dialect.Quote(","))) | ||||
| 		sql += " ), " | ||||
| 	} | ||||
| 
 | ||||
| 	panic("Unsupported db type") | ||||
| 	sql = sql[:len(sql)-2] + ")" | ||||
| 	sql += ";" | ||||
| 	return sql | ||||
| } | ||||
| 
 | ||||
| func (db *Sqlite3) TableCheckSql(tableName string) (string, []interface{}) { | ||||
| 	args := []interface{}{tableName} | ||||
| 	return "SELECT name FROM sqlite_master WHERE type='table' and name = ?", args | ||||
| func (db *BaseDialect) AddColumnSql(tableName string, col *Column) string { | ||||
| 	return fmt.Sprintf("alter table %s ADD COLUMN %s", tableName, col.StringNoPk(db.dialect)) | ||||
| } | ||||
| 
 | ||||
| func (db *Mysql) TableCheckSql(tableName string) (string, []interface{}) { | ||||
| 	args := []interface{}{"grafana", tableName} | ||||
| 	sql := "SELECT `TABLE_NAME` from `INFORMATION_SCHEMA`.`TABLES` WHERE `TABLE_SCHEMA`=? and `TABLE_NAME`=?" | ||||
| 	return sql, args | ||||
| func (db *BaseDialect) CreateIndexSql(tableName string, index *Index) string { | ||||
| 	quote := db.dialect.Quote | ||||
| 	var unique string | ||||
| 	var idxName string | ||||
| 	if index.Type == UniqueIndex { | ||||
| 		unique = " UNIQUE" | ||||
| 		idxName = fmt.Sprintf("UQE_%v_%v", tableName, index.Name) | ||||
| 	} else { | ||||
| 		idxName = fmt.Sprintf("IDX_%v_%v", tableName, index.Name) | ||||
| 	} | ||||
| 
 | ||||
| 	return fmt.Sprintf("CREATE%s INDEX %v ON %v (%v);", unique, | ||||
| 		quote(idxName), quote(tableName), | ||||
| 		quote(strings.Join(index.Cols, quote(",")))) | ||||
| } | ||||
|  |  | |||
|  | @ -2,61 +2,40 @@ package migrations | |||
| 
 | ||||
| import "time" | ||||
| 
 | ||||
| // Id              int64
 | ||||
| // Login           string `xorm:"UNIQUE NOT NULL"`
 | ||||
| // Email           string `xorm:"UNIQUE NOT NULL"`
 | ||||
| // Name            string
 | ||||
| // FullName        string
 | ||||
| // Password        string
 | ||||
| // IsAdmin         bool
 | ||||
| // Salt            string `xorm:"VARCHAR(10)"`
 | ||||
| // Company         string
 | ||||
| // NextDashboardId int
 | ||||
| // UsingAccountId  int64
 | ||||
| // Created         time.Time
 | ||||
| // Updated         time.Time
 | ||||
| 
 | ||||
| func AddMigrations(mg *Migrator) { | ||||
| 
 | ||||
| 	// TABLE Account
 | ||||
| 	// -------------------------------
 | ||||
| 	mg.AddMigration("create account table", new(RawSqlMigration). | ||||
| 		Sqlite(` | ||||
| 		  CREATE TABLE account ( | ||||
| 		  	id                INTEGER PRIMARY KEY AUTOINCREMENT, | ||||
| 		  	login             TEXT NOT NULL, | ||||
| 		  	email             TEXT NOT NULL, | ||||
| 		  	name						  TEXT NULL, | ||||
| 		  	password		 		  TEXT NULL, | ||||
| 		  	salt							TEXT NULL, | ||||
| 		  	company						TEXT NULL, | ||||
| 		  	using_account_id  INTEGER NULL, | ||||
| 		  	is_admin					INTEGER NOT NULL, | ||||
| 		  	created           INTEGER NOT NULL, | ||||
| 		  	updated           INTEGER NOT NULL | ||||
| 			) | ||||
| 		`). | ||||
| 		Mysql(` | ||||
| 		  CREATE TABLE account ( | ||||
| 		  	id								BIGINT NOT NULL AUTO_INCREMENT, PRIMARY KEY (id), | ||||
| 		  	login							VARCHAR(255) NOT NULL, | ||||
| 		  	email							VARCHAR(255) NOT NULL, | ||||
| 		  	name							VARCHAR(255) NULL, | ||||
| 		    password					VARCHAR(50) NULL, | ||||
| 				salt    					VARCHAR(50) NULL, | ||||
| 				company      			VARCHAR(255) NULL, | ||||
| 				using_account_id	BIGINT NULL, | ||||
| 				is_admin	        BOOL NOT NULL, | ||||
| 				created	          DATETIME NOT NULL, | ||||
| 				update	          DATETIME NOT NULL | ||||
| 		  ) | ||||
| 		`)) | ||||
| 	// ------------------------------
 | ||||
| 	mg.AddMigration("add index UIX_account.login", new(AddIndexMigration). | ||||
| 	//-------  migration_log table -------------------
 | ||||
| 	mg.AddMigration("create migration_log table", new(AddTableMigration). | ||||
| 		Name("migration_log").WithColumns( | ||||
| 		&Column{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true}, | ||||
| 		&Column{Name: "migration_id", Type: DB_NVarchar, Length: 255}, | ||||
| 		&Column{Name: "sql", Type: DB_Text}, | ||||
| 		&Column{Name: "success", Type: DB_Bool}, | ||||
| 		&Column{Name: "error", Type: DB_Text}, | ||||
| 		&Column{Name: "timestamp", Type: DB_DateTime}, | ||||
| 	)) | ||||
| 
 | ||||
| 	//-------  account table -------------------
 | ||||
| 	mg.AddMigration("create account table", new(AddTableMigration). | ||||
| 		Name("account").WithColumns( | ||||
| 		&Column{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true}, | ||||
| 		&Column{Name: "login", Type: DB_NVarchar, Length: 255}, | ||||
| 		&Column{Name: "email", Type: DB_NVarchar, Length: 255}, | ||||
| 		&Column{Name: "name", Type: DB_NVarchar, Length: 255}, | ||||
| 		&Column{Name: "password", Type: DB_NVarchar, Length: 50}, | ||||
| 		&Column{Name: "salt", Type: DB_NVarchar, Length: 50}, | ||||
| 		&Column{Name: "company", Type: DB_NVarchar, Length: 255}, | ||||
| 		&Column{Name: "using_account_id", Type: DB_BigInt}, | ||||
| 		&Column{Name: "is_admin", Type: DB_Bool}, | ||||
| 		&Column{Name: "created", Type: DB_DateTime}, | ||||
| 		&Column{Name: "updated", Type: DB_DateTime}, | ||||
| 	)) | ||||
| 
 | ||||
| 	//-------  account table indexes ------------------
 | ||||
| 	mg.AddMigration("add unique index UIX_account.login", new(AddIndexMigration). | ||||
| 		Name("UIX_account_login").Table("account").Columns("login")) | ||||
| 	// ------------------------------
 | ||||
| 	// mg.AddMigration("add column", new(AddColumnMigration).
 | ||||
| 	// 	Table("account").Column("name").Type(DB_TYPE_STRING).Length(255))
 | ||||
| 	mg.AddMigration("add unique index UIX_account.email", new(AddIndexMigration). | ||||
| 		Name("UIX_account_email").Table("account").Columns("email")) | ||||
| } | ||||
| 
 | ||||
| type MigrationLog struct { | ||||
|  |  | |||
|  | @ -35,7 +35,7 @@ func TestMigrations(t *testing.T) { | |||
| 	log.NewLogger(0, "console", `{"level": 0}`) | ||||
| 
 | ||||
| 	testDBs := [][]string{ | ||||
| 		//[]string{"mysql", "grafana:password@tcp(localhost:3306)/grafana_tests?charset=utf8"},
 | ||||
| 		[]string{"mysql", "grafana:password@tcp(localhost:3306)/grafana_tests?charset=utf8"}, | ||||
| 		[]string{"sqlite3", ":memory:"}, | ||||
| 	} | ||||
| 
 | ||||
|  |  | |||
|  | @ -23,9 +23,9 @@ func NewMigrator(engine *xorm.Engine) *Migrator { | |||
| 
 | ||||
| 	switch mg.x.DriverName() { | ||||
| 	case MYSQL: | ||||
| 		mg.dialect = new(Mysql) | ||||
| 		mg.dialect = NewMysqlDialect() | ||||
| 	case SQLITE: | ||||
| 		mg.dialect = new(Sqlite3) | ||||
| 		mg.dialect = NewSqlite3Dialect() | ||||
| 	} | ||||
| 
 | ||||
| 	return mg | ||||
|  | @ -37,20 +37,18 @@ func (mg *Migrator) AddMigration(id string, m Migration) { | |||
| } | ||||
| 
 | ||||
| func (mg *Migrator) GetMigrationLog() (map[string]MigrationLog, error) { | ||||
| 	logMap := make(map[string]MigrationLog) | ||||
| 	logItems := make([]MigrationLog, 0) | ||||
| 
 | ||||
| 	exists, err := mg.x.IsTableExist(new(MigrationLog)) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	if !exists { | ||||
| 		if err := mg.x.CreateTables(new(MigrationLog)); err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		return nil, nil | ||||
| 		return logMap, nil | ||||
| 	} | ||||
| 
 | ||||
| 	logMap := make(map[string]MigrationLog) | ||||
| 	logItems := make([]MigrationLog, 0) | ||||
| 	if err = mg.x.Find(&logItems); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | @ -66,7 +64,7 @@ func (mg *Migrator) GetMigrationLog() (map[string]MigrationLog, error) { | |||
| } | ||||
| 
 | ||||
| func (mg *Migrator) Start() error { | ||||
| 	log.Info("Migrator::Start DB migration") | ||||
| 	log.Info("Migrator::Starting DB migration") | ||||
| 
 | ||||
| 	logMap, err := mg.GetMigrationLog() | ||||
| 	if err != nil { | ||||
|  | @ -76,13 +74,15 @@ func (mg *Migrator) Start() error { | |||
| 	for _, m := range mg.migrations { | ||||
| 		_, exists := logMap[m.Id()] | ||||
| 		if exists { | ||||
| 			log.Info("Migrator:: Skipping migration: %v, Already executed", m.Id()) | ||||
| 			log.Debug("Migrator:: Skipping migration: %v, Already executed", m.Id()) | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		sql := m.Sql(mg.dialect) | ||||
| 
 | ||||
| 		record := MigrationLog{ | ||||
| 			MigrationId: m.Id(), | ||||
| 			Sql:         m.Sql(mg.dialect), | ||||
| 			Sql:         sql, | ||||
| 			Timestamp:   time.Now(), | ||||
| 		} | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,78 +0,0 @@ | |||
| package migrations | ||||
| 
 | ||||
| import ( | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"github.com/go-xorm/xorm" | ||||
| 
 | ||||
| 	. "github.com/smartystreets/goconvey/convey" | ||||
| ) | ||||
| 
 | ||||
| // func cleanDB(x *xorm.Engine) {
 | ||||
| // 	tables, _ := x.DBMetas()
 | ||||
| // 	sess := x.NewSession()
 | ||||
| // 	defer sess.Close()
 | ||||
| //
 | ||||
| // 	for _, table := range tables {
 | ||||
| // 		if _, err := sess.Exec("SET FOREIGN_KEY_CHECKS = 0"); err != nil {
 | ||||
| // 			panic("Failed to disable foreign key checks")
 | ||||
| // 		}
 | ||||
| // 		if _, err := sess.Exec("DROP TABLE " + table.Name); err != nil {
 | ||||
| // 			panic(fmt.Sprintf("Failed to delete table: %v, err: %v", table.Name, err))
 | ||||
| // 		}
 | ||||
| // 		if _, err := sess.Exec("SET FOREIGN_KEY_CHECKS = 1"); err != nil {
 | ||||
| // 			panic("Failed to disable foreign key checks")
 | ||||
| // 		}
 | ||||
| // 	}
 | ||||
| // }
 | ||||
| //
 | ||||
| // var indexTypes = []string{"Unknown", "", "UNIQUE"}
 | ||||
| //
 | ||||
| 
 | ||||
| func TestMigrator(t *testing.T) { | ||||
| 
 | ||||
| 	Convey("Migrator", t, func() { | ||||
| 		x, err := xorm.NewEngine(SQLITE, ":memory:") | ||||
| 		So(err, ShouldBeNil) | ||||
| 
 | ||||
| 		mg := NewMigrator(x) | ||||
| 
 | ||||
| 		Convey("Given one migration", func() { | ||||
| 			mg.AddMigration("test migration", new(RawSqlMigration). | ||||
| 				Sqlite(` | ||||
| 			    CREATE TABLE account ( | ||||
| 		       	id INTEGER PRIMARY KEY AUTOINCREMENT | ||||
| 				  )`). | ||||
| 				Mysql(` | ||||
| 			   	CREATE TABLE account ( | ||||
| 						id BIGINT NOT NULL AUTO_INCREMENT, PRIMARY KEY (id) | ||||
| 					)`)) | ||||
| 
 | ||||
| 			err := mg.Start() | ||||
| 			So(err, ShouldBeNil) | ||||
| 
 | ||||
| 			log, err := mg.GetMigrationLog() | ||||
| 			So(err, ShouldBeNil) | ||||
| 			So(len(log), ShouldEqual, 1) | ||||
| 		}) | ||||
| 
 | ||||
| 		// So(err, ShouldBeNil)
 | ||||
| 		//
 | ||||
| 		// So(len(tables), ShouldEqual, 2)
 | ||||
| 		// fmt.Printf("\nDB Schema after migration: table count: %v\n", len(tables))
 | ||||
| 		//
 | ||||
| 		// for _, table := range tables {
 | ||||
| 		// 	fmt.Printf("\nTable: %v \n", table.Name)
 | ||||
| 		// 	for _, column := range table.Columns() {
 | ||||
| 		// 		fmt.Printf("\t %v \n", column.String(x.Dialect()))
 | ||||
| 		// 	}
 | ||||
| 		//
 | ||||
| 		// 	if len(table.Indexes) > 0 {
 | ||||
| 		// 		fmt.Printf("\n\tIndexes:\n")
 | ||||
| 		// 		for _, index := range table.Indexes {
 | ||||
| 		// 			fmt.Printf("\t %v (%v) %v \n", index.Name, strings.Join(index.Cols, ","), indexTypes[index.Type])
 | ||||
| 		// 		}
 | ||||
| 		// 	}
 | ||||
| 		// }
 | ||||
| 	}) | ||||
| } | ||||
|  | @ -0,0 +1,87 @@ | |||
| package migrations | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"strconv" | ||||
| ) | ||||
| 
 | ||||
| type Mysql struct { | ||||
| 	BaseDialect | ||||
| } | ||||
| 
 | ||||
| func NewMysqlDialect() *Mysql { | ||||
| 	d := Mysql{} | ||||
| 	d.BaseDialect.dialect = &d | ||||
| 	d.BaseDialect.driverName = MYSQL | ||||
| 	return &d | ||||
| } | ||||
| 
 | ||||
| func (db *Mysql) Quote(name string) string { | ||||
| 	return "`" + name + "`" | ||||
| } | ||||
| 
 | ||||
| func (db *Mysql) QuoteStr() string { | ||||
| 	return "`" | ||||
| } | ||||
| 
 | ||||
| func (db *Mysql) AutoIncrStr() string { | ||||
| 	return "AUTO_INCREMENT" | ||||
| } | ||||
| 
 | ||||
| func (db *Mysql) SqlType(c *Column) string { | ||||
| 	var res string | ||||
| 	switch c.Type { | ||||
| 	case DB_Bool: | ||||
| 		res = DB_TinyInt | ||||
| 		c.Length = 1 | ||||
| 	case DB_Serial: | ||||
| 		c.IsAutoIncrement = true | ||||
| 		c.IsPrimaryKey = true | ||||
| 		c.Nullable = false | ||||
| 		res = DB_Int | ||||
| 	case DB_BigSerial: | ||||
| 		c.IsAutoIncrement = true | ||||
| 		c.IsPrimaryKey = true | ||||
| 		c.Nullable = false | ||||
| 		res = DB_BigInt | ||||
| 	case DB_Bytea: | ||||
| 		res = DB_Blob | ||||
| 	case DB_TimeStampz: | ||||
| 		res = DB_Char | ||||
| 		c.Length = 64 | ||||
| 	case DB_NVarchar: | ||||
| 		res = DB_Varchar | ||||
| 	default: | ||||
| 		res = c.Type | ||||
| 	} | ||||
| 
 | ||||
| 	var hasLen1 bool = (c.Length > 0) | ||||
| 	var hasLen2 bool = (c.Length2 > 0) | ||||
| 
 | ||||
| 	if res == DB_BigInt && !hasLen1 && !hasLen2 { | ||||
| 		c.Length = 20 | ||||
| 		hasLen1 = true | ||||
| 	} | ||||
| 
 | ||||
| 	if hasLen2 { | ||||
| 		res += "(" + strconv.Itoa(c.Length) + "," + strconv.Itoa(c.Length2) + ")" | ||||
| 	} else if hasLen1 { | ||||
| 		res += "(" + strconv.Itoa(c.Length) + ")" | ||||
| 	} | ||||
| 	return res | ||||
| } | ||||
| 
 | ||||
| func (db *Mysql) ToDBTypeSql(columnType ColumnType, length int) string { | ||||
| 	switch columnType { | ||||
| 	case DB_TYPE_STRING: | ||||
| 		return fmt.Sprintf("NVARCHAR(%d)", length) | ||||
| 	} | ||||
| 
 | ||||
| 	panic("Unsupported db type") | ||||
| } | ||||
| 
 | ||||
| func (db *Mysql) TableCheckSql(tableName string) (string, []interface{}) { | ||||
| 	args := []interface{}{"grafana", tableName} | ||||
| 	sql := "SELECT `TABLE_NAME` from `INFORMATION_SCHEMA`.`TABLES` WHERE `TABLE_SCHEMA`=? and `TABLE_NAME`=?" | ||||
| 	return sql, args | ||||
| } | ||||
|  | @ -0,0 +1,57 @@ | |||
| package migrations | ||||
| 
 | ||||
| import "github.com/go-xorm/core" | ||||
| 
 | ||||
| type Sqlite3 struct { | ||||
| 	BaseDialect | ||||
| } | ||||
| 
 | ||||
| func NewSqlite3Dialect() *Sqlite3 { | ||||
| 	d := Sqlite3{} | ||||
| 	d.BaseDialect.dialect = &d | ||||
| 	d.BaseDialect.driverName = SQLITE | ||||
| 	return &d | ||||
| } | ||||
| 
 | ||||
| func (db *Sqlite3) Quote(name string) string { | ||||
| 	return "`" + name + "`" | ||||
| } | ||||
| 
 | ||||
| func (db *Sqlite3) QuoteStr() string { | ||||
| 	return "`" | ||||
| } | ||||
| 
 | ||||
| func (db *Sqlite3) AutoIncrStr() string { | ||||
| 	return "AUTOINCREMENT" | ||||
| } | ||||
| 
 | ||||
| func (db *Sqlite3) SqlType(c *Column) string { | ||||
| 	switch c.Type { | ||||
| 	case DB_Date, DB_DateTime, DB_TimeStamp, DB_Time: | ||||
| 		return DB_DateTime | ||||
| 	case DB_TimeStampz: | ||||
| 		return DB_Text | ||||
| 	case DB_Char, DB_Varchar, DB_NVarchar, DB_TinyText, DB_Text, DB_MediumText, DB_LongText: | ||||
| 		return core.Text | ||||
| 	case DB_Bit, DB_TinyInt, DB_SmallInt, DB_MediumInt, DB_Int, DB_Integer, DB_BigInt, DB_Bool: | ||||
| 		return DB_Integer | ||||
| 	case DB_Float, DB_Double, DB_Real: | ||||
| 		return DB_Real | ||||
| 	case DB_Decimal, DB_Numeric: | ||||
| 		return DB_Numeric | ||||
| 	case DB_TinyBlob, DB_Blob, DB_MediumBlob, DB_LongBlob, DB_Bytea, DB_Binary, DB_VarBinary: | ||||
| 		return DB_Blob | ||||
| 	case DB_Serial, DB_BigSerial: | ||||
| 		c.IsPrimaryKey = true | ||||
| 		c.IsAutoIncrement = true | ||||
| 		c.Nullable = false | ||||
| 		return core.Integer | ||||
| 	default: | ||||
| 		return c.Type | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (db *Sqlite3) TableCheckSql(tableName string) (string, []interface{}) { | ||||
| 	args := []interface{}{tableName} | ||||
| 	return "SELECT name FROM sqlite_master WHERE type='table' and name = ?", args | ||||
| } | ||||
|  | @ -0,0 +1,86 @@ | |||
| package migrations | ||||
| 
 | ||||
| const ( | ||||
| 	POSTGRES = "postgres" | ||||
| 	SQLITE   = "sqlite3" | ||||
| 	MYSQL    = "mysql" | ||||
| ) | ||||
| 
 | ||||
| type Migration interface { | ||||
| 	Sql(dialect Dialect) string | ||||
| 	Id() string | ||||
| 	SetId(string) | ||||
| } | ||||
| 
 | ||||
| type SQLType string | ||||
| 
 | ||||
| type ColumnType string | ||||
| 
 | ||||
| const ( | ||||
| 	DB_TYPE_STRING ColumnType = "String" | ||||
| ) | ||||
| 
 | ||||
| type Table struct { | ||||
| 	Name        string | ||||
| 	Columns     []*Column | ||||
| 	PrimaryKeys []string | ||||
| } | ||||
| 
 | ||||
| const ( | ||||
| 	IndexType = iota + 1 | ||||
| 	UniqueIndex | ||||
| ) | ||||
| 
 | ||||
| type Index struct { | ||||
| 	Name string | ||||
| 	Type int | ||||
| 	Cols []string | ||||
| } | ||||
| 
 | ||||
| var ( | ||||
| 	DB_Bit       = "BIT" | ||||
| 	DB_TinyInt   = "TINYINT" | ||||
| 	DB_SmallInt  = "SMALLINT" | ||||
| 	DB_MediumInt = "MEDIUMINT" | ||||
| 	DB_Int       = "INT" | ||||
| 	DB_Integer   = "INTEGER" | ||||
| 	DB_BigInt    = "BIGINT" | ||||
| 
 | ||||
| 	DB_Enum = "ENUM" | ||||
| 	DB_Set  = "SET" | ||||
| 
 | ||||
| 	DB_Char       = "CHAR" | ||||
| 	DB_Varchar    = "VARCHAR" | ||||
| 	DB_NVarchar   = "NVARCHAR" | ||||
| 	DB_TinyText   = "TINYTEXT" | ||||
| 	DB_Text       = "TEXT" | ||||
| 	DB_MediumText = "MEDIUMTEXT" | ||||
| 	DB_LongText   = "LONGTEXT" | ||||
| 	DB_Uuid       = "UUID" | ||||
| 
 | ||||
| 	DB_Date       = "DATE" | ||||
| 	DB_DateTime   = "DATETIME" | ||||
| 	DB_Time       = "TIME" | ||||
| 	DB_TimeStamp  = "TIMESTAMP" | ||||
| 	DB_TimeStampz = "TIMESTAMPZ" | ||||
| 
 | ||||
| 	DB_Decimal = "DECIMAL" | ||||
| 	DB_Numeric = "NUMERIC" | ||||
| 
 | ||||
| 	DB_Real   = "REAL" | ||||
| 	DB_Float  = "FLOAT" | ||||
| 	DB_Double = "DOUBLE" | ||||
| 
 | ||||
| 	DB_Binary     = "BINARY" | ||||
| 	DB_VarBinary  = "VARBINARY" | ||||
| 	DB_TinyBlob   = "TINYBLOB" | ||||
| 	DB_Blob       = "BLOB" | ||||
| 	DB_MediumBlob = "MEDIUMBLOB" | ||||
| 	DB_LongBlob   = "LONGBLOB" | ||||
| 	DB_Bytea      = "BYTEA" | ||||
| 
 | ||||
| 	DB_Bool = "BOOL" | ||||
| 
 | ||||
| 	DB_Serial    = "SERIAL" | ||||
| 	DB_BigSerial = "BIGSERIAL" | ||||
| ) | ||||
|  | @ -9,6 +9,7 @@ import ( | |||
| 	"github.com/torkelo/grafana-pro/pkg/bus" | ||||
| 	"github.com/torkelo/grafana-pro/pkg/log" | ||||
| 	m "github.com/torkelo/grafana-pro/pkg/models" | ||||
| 	"github.com/torkelo/grafana-pro/pkg/services/sqlstore/migrations" | ||||
| 	"github.com/torkelo/grafana-pro/pkg/setting" | ||||
| 	"github.com/torkelo/grafana-pro/pkg/util" | ||||
| 
 | ||||
|  | @ -34,7 +35,7 @@ var ( | |||
| func init() { | ||||
| 	tables = make([]interface{}, 0) | ||||
| 
 | ||||
| 	tables = append(tables, new(m.Account), new(m.Dashboard), | ||||
| 	tables = append(tables, new(m.Dashboard), | ||||
| 		new(m.Collaborator), new(m.DataSource), new(DashboardTag), | ||||
| 		new(m.Token)) | ||||
| } | ||||
|  | @ -77,6 +78,13 @@ func NewEngine() { | |||
| func SetEngine(engine *xorm.Engine, enableLog bool) (err error) { | ||||
| 	x = engine | ||||
| 
 | ||||
| 	migrator := migrations.NewMigrator(x) | ||||
| 	migrations.AddMigrations(migrator) | ||||
| 
 | ||||
| 	if err := migrator.Start(); err != nil { | ||||
| 		return fmt.Errorf("Sqlstore::Migration failed err: %v\n", err) | ||||
| 	} | ||||
| 
 | ||||
| 	if err := x.Sync2(tables...); err != nil { | ||||
| 		return fmt.Errorf("sync database struct error: %v\n", err) | ||||
| 	} | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue