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 | router_logging = false | ||||||
| static_root_path = grafana/src | static_root_path = grafana/src | ||||||
| 
 | 
 | ||||||
|  | [log] | ||||||
|  | level = Trace | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -12,19 +12,17 @@ var ( | ||||||
| 
 | 
 | ||||||
| // Directly mapped to db schema, Do not change field names lighly
 | // Directly mapped to db schema, Do not change field names lighly
 | ||||||
| type Account struct { | type Account struct { | ||||||
| 	Id              int64 | 	Id             int64 | ||||||
| 	Login           string `xorm:"UNIQUE NOT NULL"` | 	Login          string `xorm:"UNIQUE NOT NULL"` | ||||||
| 	Email           string `xorm:"UNIQUE NOT NULL"` | 	Email          string `xorm:"UNIQUE NOT NULL"` | ||||||
| 	Name            string | 	Name           string | ||||||
| 	FullName        string | 	Password       string | ||||||
| 	Password        string | 	IsAdmin        bool | ||||||
| 	IsAdmin         bool | 	Salt           string `xorm:"VARCHAR(10)"` | ||||||
| 	Salt            string `xorm:"VARCHAR(10)"` | 	Company        string | ||||||
| 	Company         string | 	UsingAccountId int64 | ||||||
| 	NextDashboardId int | 	Created        time.Time | ||||||
| 	UsingAccountId  int64 | 	Updated        time.Time | ||||||
| 	Created         time.Time |  | ||||||
| 	Updated         time.Time |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // ---------------------
 | // ---------------------
 | ||||||
|  |  | ||||||
|  | @ -1,28 +1,5 @@ | ||||||
| package migrations | 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 { | type MigrationBase struct { | ||||||
| 	id string | 	id string | ||||||
| } | } | ||||||
|  | @ -65,10 +42,8 @@ func (m *RawSqlMigration) Mysql(sql string) *RawSqlMigration { | ||||||
| 
 | 
 | ||||||
| type AddColumnMigration struct { | type AddColumnMigration struct { | ||||||
| 	MigrationBase | 	MigrationBase | ||||||
| 	tableName  string | 	tableName string | ||||||
| 	columnName string | 	column    *Column | ||||||
| 	columnType ColumnType |  | ||||||
| 	length     int |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (m *AddColumnMigration) Table(tableName string) *AddColumnMigration { | func (m *AddColumnMigration) Table(tableName string) *AddColumnMigration { | ||||||
|  | @ -76,35 +51,23 @@ func (m *AddColumnMigration) Table(tableName string) *AddColumnMigration { | ||||||
| 	return m | 	return m | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (m *AddColumnMigration) Length(length int) *AddColumnMigration { | func (m *AddColumnMigration) Column(col *Column) *AddColumnMigration { | ||||||
| 	m.length = length | 	m.column = col | ||||||
| 	return m |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (m *AddColumnMigration) Column(columnName string) *AddColumnMigration { |  | ||||||
| 	m.columnName = columnName |  | ||||||
| 	return m |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (m *AddColumnMigration) Type(columnType ColumnType) *AddColumnMigration { |  | ||||||
| 	m.columnType = columnType |  | ||||||
| 	return m | 	return m | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (m *AddColumnMigration) Sql(dialect Dialect) string { | 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 { | type AddIndexMigration struct { | ||||||
| 	MigrationBase | 	MigrationBase | ||||||
| 	tableName string | 	tableName string | ||||||
| 	columns   string | 	index     Index | ||||||
| 	indexName string |  | ||||||
| 	unique    string |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (m *AddIndexMigration) Name(name string) *AddIndexMigration { | func (m *AddIndexMigration) Name(name string) *AddIndexMigration { | ||||||
| 	m.indexName = name | 	m.index.Name = name | ||||||
| 	return m | 	return m | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -114,15 +77,47 @@ func (m *AddIndexMigration) Table(tableName string) *AddIndexMigration { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (m *AddIndexMigration) Unique() *AddIndexMigration { | func (m *AddIndexMigration) Unique() *AddIndexMigration { | ||||||
| 	m.unique = "UNIQUE" | 	m.index.Type = UniqueIndex | ||||||
| 	return m | 	return m | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (m *AddIndexMigration) Columns(columns ...string) *AddIndexMigration { | func (m *AddIndexMigration) Columns(columns ...string) *AddIndexMigration { | ||||||
| 	m.columns = strings.Join(columns, ",") | 	m.index.Cols = columns | ||||||
| 	return m | 	return m | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (m *AddIndexMigration) Sql(dialect Dialect) string { | 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 | package migrations | ||||||
| 
 | 
 | ||||||
| import "fmt" | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
| 
 | 
 | ||||||
| type Dialect interface { | type Dialect interface { | ||||||
| 	DriverName() string | 	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{}) | 	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 { | func (b *BaseDialect) ShowCreateNull() bool { | ||||||
| 	return SQLITE | 	return true | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (db *Mysql) DriverName() string { | func (b *BaseDialect) AndStr() string { | ||||||
| 	return MYSQL | 	return "AND" | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (db *Sqlite3) ToDBTypeSql(columnType ColumnType, length int) string { | func (b *BaseDialect) OrStr() string { | ||||||
| 	switch columnType { | 	return "OR" | ||||||
| 	case DB_TYPE_STRING: | } | ||||||
| 		return "TEXT" | 
 | ||||||
|  | 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") | 	if len(pkList) > 1 { | ||||||
| } | 		sql += "PRIMARY KEY ( " | ||||||
| 
 | 		sql += b.dialect.Quote(strings.Join(pkList, b.dialect.Quote(","))) | ||||||
| func (db *Mysql) ToDBTypeSql(columnType ColumnType, length int) string { | 		sql += " ), " | ||||||
| 	switch columnType { |  | ||||||
| 	case DB_TYPE_STRING: |  | ||||||
| 		return fmt.Sprintf("NVARCHAR(%d)", length) |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	panic("Unsupported db type") | 	sql = sql[:len(sql)-2] + ")" | ||||||
|  | 	sql += ";" | ||||||
|  | 	return sql | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (db *Sqlite3) TableCheckSql(tableName string) (string, []interface{}) { | func (db *BaseDialect) AddColumnSql(tableName string, col *Column) string { | ||||||
| 	args := []interface{}{tableName} | 	return fmt.Sprintf("alter table %s ADD COLUMN %s", tableName, col.StringNoPk(db.dialect)) | ||||||
| 	return "SELECT name FROM sqlite_master WHERE type='table' and name = ?", args |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (db *Mysql) TableCheckSql(tableName string) (string, []interface{}) { | func (db *BaseDialect) CreateIndexSql(tableName string, index *Index) string { | ||||||
| 	args := []interface{}{"grafana", tableName} | 	quote := db.dialect.Quote | ||||||
| 	sql := "SELECT `TABLE_NAME` from `INFORMATION_SCHEMA`.`TABLES` WHERE `TABLE_SCHEMA`=? and `TABLE_NAME`=?" | 	var unique string | ||||||
| 	return sql, args | 	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" | 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) { | func AddMigrations(mg *Migrator) { | ||||||
| 
 | 
 | ||||||
| 	// TABLE Account
 | 	//-------  migration_log table -------------------
 | ||||||
| 	// -------------------------------
 | 	mg.AddMigration("create migration_log table", new(AddTableMigration). | ||||||
| 	mg.AddMigration("create account table", new(RawSqlMigration). | 		Name("migration_log").WithColumns( | ||||||
| 		Sqlite(` | 		&Column{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true}, | ||||||
| 		  CREATE TABLE account ( | 		&Column{Name: "migration_id", Type: DB_NVarchar, Length: 255}, | ||||||
| 		  	id                INTEGER PRIMARY KEY AUTOINCREMENT, | 		&Column{Name: "sql", Type: DB_Text}, | ||||||
| 		  	login             TEXT NOT NULL, | 		&Column{Name: "success", Type: DB_Bool}, | ||||||
| 		  	email             TEXT NOT NULL, | 		&Column{Name: "error", Type: DB_Text}, | ||||||
| 		  	name						  TEXT NULL, | 		&Column{Name: "timestamp", Type: DB_DateTime}, | ||||||
| 		  	password		 		  TEXT NULL, | 	)) | ||||||
| 		  	salt							TEXT NULL, | 
 | ||||||
| 		  	company						TEXT NULL, | 	//-------  account table -------------------
 | ||||||
| 		  	using_account_id  INTEGER NULL, | 	mg.AddMigration("create account table", new(AddTableMigration). | ||||||
| 		  	is_admin					INTEGER NOT NULL, | 		Name("account").WithColumns( | ||||||
| 		  	created           INTEGER NOT NULL, | 		&Column{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true}, | ||||||
| 		  	updated           INTEGER NOT NULL | 		&Column{Name: "login", Type: DB_NVarchar, Length: 255}, | ||||||
| 			) | 		&Column{Name: "email", Type: DB_NVarchar, Length: 255}, | ||||||
| 		`). | 		&Column{Name: "name", Type: DB_NVarchar, Length: 255}, | ||||||
| 		Mysql(` | 		&Column{Name: "password", Type: DB_NVarchar, Length: 50}, | ||||||
| 		  CREATE TABLE account ( | 		&Column{Name: "salt", Type: DB_NVarchar, Length: 50}, | ||||||
| 		  	id								BIGINT NOT NULL AUTO_INCREMENT, PRIMARY KEY (id), | 		&Column{Name: "company", Type: DB_NVarchar, Length: 255}, | ||||||
| 		  	login							VARCHAR(255) NOT NULL, | 		&Column{Name: "using_account_id", Type: DB_BigInt}, | ||||||
| 		  	email							VARCHAR(255) NOT NULL, | 		&Column{Name: "is_admin", Type: DB_Bool}, | ||||||
| 		  	name							VARCHAR(255) NULL, | 		&Column{Name: "created", Type: DB_DateTime}, | ||||||
| 		    password					VARCHAR(50) NULL, | 		&Column{Name: "updated", Type: DB_DateTime}, | ||||||
| 				salt    					VARCHAR(50) NULL, | 	)) | ||||||
| 				company      			VARCHAR(255) NULL, | 
 | ||||||
| 				using_account_id	BIGINT NULL, | 	//-------  account table indexes ------------------
 | ||||||
| 				is_admin	        BOOL NOT NULL, | 	mg.AddMigration("add unique index UIX_account.login", new(AddIndexMigration). | ||||||
| 				created	          DATETIME NOT NULL, |  | ||||||
| 				update	          DATETIME NOT NULL |  | ||||||
| 		  ) |  | ||||||
| 		`)) |  | ||||||
| 	// ------------------------------
 |  | ||||||
| 	mg.AddMigration("add index UIX_account.login", new(AddIndexMigration). |  | ||||||
| 		Name("UIX_account_login").Table("account").Columns("login")) | 		Name("UIX_account_login").Table("account").Columns("login")) | ||||||
| 	// ------------------------------
 | 	mg.AddMigration("add unique index UIX_account.email", new(AddIndexMigration). | ||||||
| 	// mg.AddMigration("add column", new(AddColumnMigration).
 | 		Name("UIX_account_email").Table("account").Columns("email")) | ||||||
| 	// 	Table("account").Column("name").Type(DB_TYPE_STRING).Length(255))
 |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type MigrationLog struct { | type MigrationLog struct { | ||||||
|  |  | ||||||
|  | @ -35,7 +35,7 @@ func TestMigrations(t *testing.T) { | ||||||
| 	log.NewLogger(0, "console", `{"level": 0}`) | 	log.NewLogger(0, "console", `{"level": 0}`) | ||||||
| 
 | 
 | ||||||
| 	testDBs := [][]string{ | 	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:"}, | 		[]string{"sqlite3", ":memory:"}, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -23,9 +23,9 @@ func NewMigrator(engine *xorm.Engine) *Migrator { | ||||||
| 
 | 
 | ||||||
| 	switch mg.x.DriverName() { | 	switch mg.x.DriverName() { | ||||||
| 	case MYSQL: | 	case MYSQL: | ||||||
| 		mg.dialect = new(Mysql) | 		mg.dialect = NewMysqlDialect() | ||||||
| 	case SQLITE: | 	case SQLITE: | ||||||
| 		mg.dialect = new(Sqlite3) | 		mg.dialect = NewSqlite3Dialect() | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return mg | 	return mg | ||||||
|  | @ -37,20 +37,18 @@ func (mg *Migrator) AddMigration(id string, m Migration) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (mg *Migrator) GetMigrationLog() (map[string]MigrationLog, error) { | func (mg *Migrator) GetMigrationLog() (map[string]MigrationLog, error) { | ||||||
|  | 	logMap := make(map[string]MigrationLog) | ||||||
|  | 	logItems := make([]MigrationLog, 0) | ||||||
|  | 
 | ||||||
| 	exists, err := mg.x.IsTableExist(new(MigrationLog)) | 	exists, err := mg.x.IsTableExist(new(MigrationLog)) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if !exists { | 	if !exists { | ||||||
| 		if err := mg.x.CreateTables(new(MigrationLog)); err != nil { | 		return logMap, nil | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 		return nil, nil |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	logMap := make(map[string]MigrationLog) |  | ||||||
| 	logItems := make([]MigrationLog, 0) |  | ||||||
| 	if err = mg.x.Find(&logItems); err != nil { | 	if err = mg.x.Find(&logItems); err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  | @ -66,7 +64,7 @@ func (mg *Migrator) GetMigrationLog() (map[string]MigrationLog, error) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (mg *Migrator) Start() error { | func (mg *Migrator) Start() error { | ||||||
| 	log.Info("Migrator::Start DB migration") | 	log.Info("Migrator::Starting DB migration") | ||||||
| 
 | 
 | ||||||
| 	logMap, err := mg.GetMigrationLog() | 	logMap, err := mg.GetMigrationLog() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | @ -76,13 +74,15 @@ func (mg *Migrator) Start() error { | ||||||
| 	for _, m := range mg.migrations { | 	for _, m := range mg.migrations { | ||||||
| 		_, exists := logMap[m.Id()] | 		_, exists := logMap[m.Id()] | ||||||
| 		if exists { | 		if exists { | ||||||
| 			log.Info("Migrator:: Skipping migration: %v, Already executed", m.Id()) | 			log.Debug("Migrator:: Skipping migration: %v, Already executed", m.Id()) | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | 		sql := m.Sql(mg.dialect) | ||||||
|  | 
 | ||||||
| 		record := MigrationLog{ | 		record := MigrationLog{ | ||||||
| 			MigrationId: m.Id(), | 			MigrationId: m.Id(), | ||||||
| 			Sql:         m.Sql(mg.dialect), | 			Sql:         sql, | ||||||
| 			Timestamp:   time.Now(), | 			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/bus" | ||||||
| 	"github.com/torkelo/grafana-pro/pkg/log" | 	"github.com/torkelo/grafana-pro/pkg/log" | ||||||
| 	m "github.com/torkelo/grafana-pro/pkg/models" | 	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/setting" | ||||||
| 	"github.com/torkelo/grafana-pro/pkg/util" | 	"github.com/torkelo/grafana-pro/pkg/util" | ||||||
| 
 | 
 | ||||||
|  | @ -34,7 +35,7 @@ var ( | ||||||
| func init() { | func init() { | ||||||
| 	tables = make([]interface{}, 0) | 	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.Collaborator), new(m.DataSource), new(DashboardTag), | ||||||
| 		new(m.Token)) | 		new(m.Token)) | ||||||
| } | } | ||||||
|  | @ -77,6 +78,13 @@ func NewEngine() { | ||||||
| func SetEngine(engine *xorm.Engine, enableLog bool) (err error) { | func SetEngine(engine *xorm.Engine, enableLog bool) (err error) { | ||||||
| 	x = engine | 	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 { | 	if err := x.Sync2(tables...); err != nil { | ||||||
| 		return fmt.Errorf("sync database struct error: %v\n", err) | 		return fmt.Errorf("sync database struct error: %v\n", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue