diff --git a/docker/blocks/mssql/build/setup.sql.template b/docker/blocks/mssql/build/setup.sql.template index 1746a18d241..5ff4e194df3 100644 --- a/docker/blocks/mssql/build/setup.sql.template +++ b/docker/blocks/mssql/build/setup.sql.template @@ -1,7 +1,19 @@ CREATE LOGIN %%USER%% WITH PASSWORD = '%%PWD%%' GO -CREATE DATABASE %%DB%%; +CREATE DATABASE %%DB%% +ON +( NAME = %%DB%%, + FILENAME = '/var/opt/mssql/data/%%DB%%.mdf', + SIZE = 500MB, + MAXSIZE = 1000MB, + FILEGROWTH = 100MB ) +LOG ON +( NAME = %%DB%%_log, + FILENAME = '/var/opt/mssql/data/%%DB%%_log.ldf', + SIZE = 500MB, + MAXSIZE = 1000MB, + FILEGROWTH = 100MB ); GO USE %%DB%%; diff --git a/docker/blocks/mssql/docker-compose.yaml b/docker/blocks/mssql/docker-compose.yaml index 538908fec72..a346fb791f7 100644 --- a/docker/blocks/mssql/docker-compose.yaml +++ b/docker/blocks/mssql/docker-compose.yaml @@ -4,7 +4,7 @@ environment: ACCEPT_EULA: Y MSSQL_SA_PASSWORD: Password! - MSSQL_PID: Express + MSSQL_PID: Developer MSSQL_DATABASE: grafana MSSQL_USER: grafana MSSQL_PASSWORD: Password! diff --git a/pkg/services/sqlstore/alert.go b/pkg/services/sqlstore/alert.go index e99367e4d6f..b0ca50eb67d 100644 --- a/pkg/services/sqlstore/alert.go +++ b/pkg/services/sqlstore/alert.go @@ -114,7 +114,7 @@ func HandleAlertsQuery(query *m.GetAlertsQuery) error { builder.Write(" ORDER BY name ASC") if query.Limit != 0 { - builder.Write(" LIMIT ?", query.Limit) + builder.Write(dialect.Limit(query.Limit)) } alerts := make([]*m.AlertListItemDTO, 0) diff --git a/pkg/services/sqlstore/alert_notification_test.go b/pkg/services/sqlstore/alert_notification_test.go index 761114978a8..2dbf9de5ca8 100644 --- a/pkg/services/sqlstore/alert_notification_test.go +++ b/pkg/services/sqlstore/alert_notification_test.go @@ -1,7 +1,6 @@ package sqlstore import ( - "fmt" "testing" "github.com/grafana/grafana/pkg/components/simplejson" @@ -21,7 +20,6 @@ func TestAlertNotificationSQLAccess(t *testing.T) { } err := GetAlertNotifications(cmd) - fmt.Printf("error %v", err) So(err, ShouldBeNil) So(cmd.Result, ShouldBeNil) }) diff --git a/pkg/services/sqlstore/annotation.go b/pkg/services/sqlstore/annotation.go index 1710679cea1..52da7a99516 100644 --- a/pkg/services/sqlstore/annotation.go +++ b/pkg/services/sqlstore/annotation.go @@ -50,7 +50,7 @@ func (r *SqlAnnotationRepo) ensureTagsExist(sess *DBSession, tags []*models.Tag) var existingTag models.Tag // check if it exists - if exists, err := sess.Table("tag").Where("`key`=? AND `value`=?", tag.Key, tag.Value).Get(&existingTag); err != nil { + if exists, err := sess.Table("tag").Where(dialect.Quote("key")+"=? AND "+dialect.Quote("value")+"=?", tag.Key, tag.Value).Get(&existingTag); err != nil { return nil, err } else if exists { tag.Id = existingTag.Id @@ -146,7 +146,7 @@ func (r *SqlAnnotationRepo) Find(query *annotations.ItemQuery) ([]*annotations.I params = append(params, query.OrgId) if query.AnnotationId != 0 { - fmt.Print("annotation query") + // fmt.Print("annotation query") sql.WriteString(` AND annotation.id = ?`) params = append(params, query.AnnotationId) } @@ -193,10 +193,10 @@ func (r *SqlAnnotationRepo) Find(query *annotations.ItemQuery) ([]*annotations.I tags := models.ParseTagPairs(query.Tags) for _, tag := range tags { if tag.Value == "" { - keyValueFilters = append(keyValueFilters, "(tag.key = ?)") + keyValueFilters = append(keyValueFilters, "(tag."+dialect.Quote("key")+" = ?)") params = append(params, tag.Key) } else { - keyValueFilters = append(keyValueFilters, "(tag.key = ? AND tag.value = ?)") + keyValueFilters = append(keyValueFilters, "(tag."+dialect.Quote("key")+" = ? AND tag."+dialect.Quote("value")+" = ?)") params = append(params, tag.Key, tag.Value) } } @@ -219,7 +219,7 @@ func (r *SqlAnnotationRepo) Find(query *annotations.ItemQuery) ([]*annotations.I query.Limit = 100 } - sql.WriteString(fmt.Sprintf(" ORDER BY epoch DESC LIMIT %v", query.Limit)) + sql.WriteString(" ORDER BY epoch DESC" + dialect.Limit(query.Limit)) items := make([]*annotations.ItemDTO, 0) diff --git a/pkg/services/sqlstore/annotation_test.go b/pkg/services/sqlstore/annotation_test.go index 949ed8135ba..01a95c7db7b 100644 --- a/pkg/services/sqlstore/annotation_test.go +++ b/pkg/services/sqlstore/annotation_test.go @@ -10,12 +10,18 @@ import ( ) func TestSavingTags(t *testing.T) { + InitTestDB(t) + Convey("Testing annotation saving/loading", t, func() { - InitTestDB(t) repo := SqlAnnotationRepo{} Convey("Can save tags", func() { + Reset(func() { + _, err := x.Exec("DELETE FROM annotation_tag WHERE 1=1") + So(err, ShouldBeNil) + }) + tagPairs := []*models.Tag{ {Key: "outage"}, {Key: "type", Value: "outage"}, @@ -31,12 +37,19 @@ func TestSavingTags(t *testing.T) { } func TestAnnotations(t *testing.T) { - Convey("Testing annotation saving/loading", t, func() { - InitTestDB(t) + InitTestDB(t) + Convey("Testing annotation saving/loading", t, func() { repo := SqlAnnotationRepo{} Convey("Can save annotation", func() { + Reset(func() { + _, err := x.Exec("DELETE FROM annotation WHERE 1=1") + So(err, ShouldBeNil) + _, err = x.Exec("DELETE FROM annotation_tag WHERE 1=1") + So(err, ShouldBeNil) + }) + annotation := &annotations.Item{ OrgId: 1, UserId: 1, diff --git a/pkg/services/sqlstore/dashboard_snapshot_test.go b/pkg/services/sqlstore/dashboard_snapshot_test.go index 2081cbf6194..d1f70082a38 100644 --- a/pkg/services/sqlstore/dashboard_snapshot_test.go +++ b/pkg/services/sqlstore/dashboard_snapshot_test.go @@ -110,42 +110,39 @@ func TestDashboardSnapshotDBAccess(t *testing.T) { } func TestDeleteExpiredSnapshots(t *testing.T) { - Convey("Testing dashboard snapshots clean up", t, func() { - x := InitTestDB(t) + x := InitTestDB(t) + Convey("Testing dashboard snapshots clean up", t, func() { setting.SnapShotRemoveExpired = true - notExpiredsnapshot := createTestSnapshot(x, "key1", 1000) - createTestSnapshot(x, "key2", -1000) - createTestSnapshot(x, "key3", -1000) + notExpiredsnapshot := createTestSnapshot(x, "key1", 1200) + createTestSnapshot(x, "key2", -1200) + createTestSnapshot(x, "key3", -1200) - Convey("Clean up old dashboard snapshots", func() { - err := DeleteExpiredSnapshots(&m.DeleteExpiredSnapshotsCommand{}) - So(err, ShouldBeNil) + err := DeleteExpiredSnapshots(&m.DeleteExpiredSnapshotsCommand{}) + So(err, ShouldBeNil) - query := m.GetDashboardSnapshotsQuery{ - OrgId: 1, - SignedInUser: &m.SignedInUser{OrgRole: m.ROLE_ADMIN}, - } - err = SearchDashboardSnapshots(&query) - So(err, ShouldBeNil) + query := m.GetDashboardSnapshotsQuery{ + OrgId: 1, + SignedInUser: &m.SignedInUser{OrgRole: m.ROLE_ADMIN}, + } + err = SearchDashboardSnapshots(&query) + So(err, ShouldBeNil) - So(len(query.Result), ShouldEqual, 1) - So(query.Result[0].Key, ShouldEqual, notExpiredsnapshot.Key) - }) + So(len(query.Result), ShouldEqual, 1) + So(query.Result[0].Key, ShouldEqual, notExpiredsnapshot.Key) - Convey("Don't delete anything if there are no expired snapshots", func() { - err := DeleteExpiredSnapshots(&m.DeleteExpiredSnapshotsCommand{}) - So(err, ShouldBeNil) + err = DeleteExpiredSnapshots(&m.DeleteExpiredSnapshotsCommand{}) + So(err, ShouldBeNil) - query := m.GetDashboardSnapshotsQuery{ - OrgId: 1, - SignedInUser: &m.SignedInUser{OrgRole: m.ROLE_ADMIN}, - } - SearchDashboardSnapshots(&query) + query = m.GetDashboardSnapshotsQuery{ + OrgId: 1, + SignedInUser: &m.SignedInUser{OrgRole: m.ROLE_ADMIN}, + } + SearchDashboardSnapshots(&query) - So(len(query.Result), ShouldEqual, 1) - }) + So(len(query.Result), ShouldEqual, 1) + So(query.Result[0].Key, ShouldEqual, notExpiredsnapshot.Key) }) } @@ -164,9 +161,11 @@ func createTestSnapshot(x *xorm.Engine, key string, expires int64) *m.DashboardS So(err, ShouldBeNil) // Set expiry date manually - to be able to create expired snapshots - expireDate := time.Now().Add(time.Second * time.Duration(expires)) - _, err = x.Exec("update dashboard_snapshot set expires = ? where "+dialect.Quote("key")+" = ?", expireDate, key) - So(err, ShouldBeNil) + if expires < 0 { + expireDate := time.Now().Add(time.Second * time.Duration(expires)) + _, err = x.Exec("UPDATE dashboard_snapshot SET expires = ? WHERE id = ?", expireDate, cmd.Result.Id) + So(err, ShouldBeNil) + } return cmd.Result } diff --git a/pkg/services/sqlstore/migrations/annotation_mig.go b/pkg/services/sqlstore/migrations/annotation_mig.go index 7fac0001e5b..d231d3283e2 100644 --- a/pkg/services/sqlstore/migrations/annotation_mig.go +++ b/pkg/services/sqlstore/migrations/annotation_mig.go @@ -86,10 +86,7 @@ func addAnnotationMig(mg *Migrator) { // clear alert text // updateTextFieldSql := "UPDATE annotation SET TEXT = '' WHERE alert_id > 0" - mg.AddMigration("Update alert annotations and set TEXT to empty", new(RawSqlMigration). - Sqlite(updateTextFieldSql). - Postgres(updateTextFieldSql). - Mysql(updateTextFieldSql)) + mg.AddMigration("Update alert annotations and set TEXT to empty", NewRawSqlMigration(updateTextFieldSql)) // // Add a 'created' & 'updated' column @@ -111,8 +108,5 @@ func addAnnotationMig(mg *Migrator) { // Convert epoch saved as seconds to miliseconds // updateEpochSql := "UPDATE annotation SET epoch = (epoch*1000) where epoch < 9999999999" - mg.AddMigration("Convert existing annotations from seconds to milliseconds", new(RawSqlMigration). - Sqlite(updateEpochSql). - Postgres(updateEpochSql). - Mysql(updateEpochSql)) + mg.AddMigration("Convert existing annotations from seconds to milliseconds", NewRawSqlMigration(updateEpochSql)) } diff --git a/pkg/services/sqlstore/migrations/dashboard_acl.go b/pkg/services/sqlstore/migrations/dashboard_acl.go index cc3b813c12f..32e4aec6d4a 100644 --- a/pkg/services/sqlstore/migrations/dashboard_acl.go +++ b/pkg/services/sqlstore/migrations/dashboard_acl.go @@ -45,8 +45,5 @@ INSERT INTO dashboard_acl (-1,-1, 2,'Editor','2017-06-20','2017-06-20') ` - mg.AddMigration("save default acl rules in dashboard_acl table", new(RawSqlMigration). - Sqlite(rawSQL). - Postgres(rawSQL). - Mysql(rawSQL)) + mg.AddMigration("save default acl rules in dashboard_acl table", NewRawSqlMigration(rawSQL)) } diff --git a/pkg/services/sqlstore/migrations/dashboard_mig.go b/pkg/services/sqlstore/migrations/dashboard_mig.go index 296950ee497..170498c4bd9 100644 --- a/pkg/services/sqlstore/migrations/dashboard_mig.go +++ b/pkg/services/sqlstore/migrations/dashboard_mig.go @@ -90,9 +90,7 @@ func addDashboardMigration(mg *Migrator) { mg.AddMigration("drop table dashboard_v1", NewDropTableMigration("dashboard_v1")) // change column type of dashboard.data - mg.AddMigration("alter dashboard.data to mediumtext v1", new(RawSqlMigration). - Sqlite("SELECT 0 WHERE 0;"). - Postgres("SELECT 0;"). + mg.AddMigration("alter dashboard.data to mediumtext v1", NewRawSqlMigration(""). Mysql("ALTER TABLE dashboard MODIFY data MEDIUMTEXT;")) // add column to store updater of a dashboard @@ -157,7 +155,7 @@ func addDashboardMigration(mg *Migrator) { Name: "uid", Type: DB_NVarchar, Length: 40, Nullable: true, })) - mg.AddMigration("Update uid column values in dashboard", new(RawSqlMigration). + mg.AddMigration("Update uid column values in dashboard", NewRawSqlMigration(""). Sqlite("UPDATE dashboard SET uid=printf('%09d',id) WHERE uid IS NULL;"). Postgres("UPDATE dashboard SET uid=lpad('' || id,9,'0') WHERE uid IS NULL;"). Mysql("UPDATE dashboard SET uid=lpad(id,9,'0') WHERE uid IS NULL;")) diff --git a/pkg/services/sqlstore/migrations/dashboard_snapshot_mig.go b/pkg/services/sqlstore/migrations/dashboard_snapshot_mig.go index 1a72bcba929..b880497cd23 100644 --- a/pkg/services/sqlstore/migrations/dashboard_snapshot_mig.go +++ b/pkg/services/sqlstore/migrations/dashboard_snapshot_mig.go @@ -50,9 +50,7 @@ func addDashboardSnapshotMigrations(mg *Migrator) { addTableIndicesMigrations(mg, "v5", snapshotV5) // change column type of dashboard - mg.AddMigration("alter dashboard_snapshot to mediumtext v2", new(RawSqlMigration). - Sqlite("SELECT 0 WHERE 0;"). - Postgres("SELECT 0;"). + mg.AddMigration("alter dashboard_snapshot to mediumtext v2", NewRawSqlMigration(""). Mysql("ALTER TABLE dashboard_snapshot MODIFY dashboard MEDIUMTEXT;")) mg.AddMigration("Update dashboard_snapshot table charset", NewTableCharsetMigration("dashboard_snapshot", []*Column{ diff --git a/pkg/services/sqlstore/migrations/dashboard_version_mig.go b/pkg/services/sqlstore/migrations/dashboard_version_mig.go index 601161a6c89..8a38f885de3 100644 --- a/pkg/services/sqlstore/migrations/dashboard_version_mig.go +++ b/pkg/services/sqlstore/migrations/dashboard_version_mig.go @@ -28,10 +28,7 @@ func addDashboardVersionMigration(mg *Migrator) { // before new dashboards where created with version 0, now they are always inserted with version 1 const setVersionTo1WhereZeroSQL = `UPDATE dashboard SET version = 1 WHERE version = 0` - mg.AddMigration("Set dashboard version to 1 where 0", new(RawSqlMigration). - Sqlite(setVersionTo1WhereZeroSQL). - Postgres(setVersionTo1WhereZeroSQL). - Mysql(setVersionTo1WhereZeroSQL)) + mg.AddMigration("Set dashboard version to 1 where 0", NewRawSqlMigration(setVersionTo1WhereZeroSQL)) const rawSQL = `INSERT INTO dashboard_version ( @@ -54,14 +51,9 @@ SELECT '', dashboard.data FROM dashboard;` - mg.AddMigration("save existing dashboard data in dashboard_version table v1", new(RawSqlMigration). - Sqlite(rawSQL). - Postgres(rawSQL). - Mysql(rawSQL)) + mg.AddMigration("save existing dashboard data in dashboard_version table v1", NewRawSqlMigration(rawSQL)) // change column type of dashboard_version.data - mg.AddMigration("alter dashboard_version.data to mediumtext v1", new(RawSqlMigration). - Sqlite("SELECT 0 WHERE 0;"). - Postgres("SELECT 0;"). + mg.AddMigration("alter dashboard_version.data to mediumtext v1", NewRawSqlMigration(""). Mysql("ALTER TABLE dashboard_version MODIFY data MEDIUMTEXT;")) } diff --git a/pkg/services/sqlstore/migrations/datasource_mig.go b/pkg/services/sqlstore/migrations/datasource_mig.go index 919881adaba..9011429e398 100644 --- a/pkg/services/sqlstore/migrations/datasource_mig.go +++ b/pkg/services/sqlstore/migrations/datasource_mig.go @@ -122,10 +122,7 @@ func addDataSourceMigration(mg *Migrator) { })) const setVersionToOneWhereZero = `UPDATE data_source SET version = 1 WHERE version = 0` - mg.AddMigration("Update initial version to 1", new(RawSqlMigration). - Sqlite(setVersionToOneWhereZero). - Postgres(setVersionToOneWhereZero). - Mysql(setVersionToOneWhereZero)) + mg.AddMigration("Update initial version to 1", NewRawSqlMigration(setVersionToOneWhereZero)) mg.AddMigration("Add read_only data column", NewAddColumnMigration(tableV2, &Column{ Name: "read_only", Type: DB_Bool, Nullable: true, diff --git a/pkg/services/sqlstore/migrations/migrations_test.go b/pkg/services/sqlstore/migrations/migrations_test.go index 53b398124af..c36eee55da9 100644 --- a/pkg/services/sqlstore/migrations/migrations_test.go +++ b/pkg/services/sqlstore/migrations/migrations_test.go @@ -25,7 +25,7 @@ func TestMigrations(t *testing.T) { x, err := xorm.NewEngine(testDB.DriverName, testDB.ConnStr) So(err, ShouldBeNil) - sqlutil.CleanDB(x) + NewDialect(x).CleanDB() _, err = x.SQL(sql).Get(&r) So(err, ShouldNotBeNil) diff --git a/pkg/services/sqlstore/migrations/org_mig.go b/pkg/services/sqlstore/migrations/org_mig.go index cf9b19f6f5b..f74b78cf1b3 100644 --- a/pkg/services/sqlstore/migrations/org_mig.go +++ b/pkg/services/sqlstore/migrations/org_mig.go @@ -85,8 +85,5 @@ func addOrgMigrations(mg *Migrator) { })) const migrateReadOnlyViewersToViewers = `UPDATE org_user SET role = 'Viewer' WHERE role = 'Read Only Editor'` - mg.AddMigration("Migrate all Read Only Viewers to Viewers", new(RawSqlMigration). - Sqlite(migrateReadOnlyViewersToViewers). - Postgres(migrateReadOnlyViewersToViewers). - Mysql(migrateReadOnlyViewersToViewers)) + mg.AddMigration("Migrate all Read Only Viewers to Viewers", NewRawSqlMigration(migrateReadOnlyViewersToViewers)) } diff --git a/pkg/services/sqlstore/migrations/user_auth_mig.go b/pkg/services/sqlstore/migrations/user_auth_mig.go index 953883376c3..2771035b47f 100644 --- a/pkg/services/sqlstore/migrations/user_auth_mig.go +++ b/pkg/services/sqlstore/migrations/user_auth_mig.go @@ -22,8 +22,7 @@ func addUserAuthMigrations(mg *Migrator) { // add indices addTableIndicesMigrations(mg, "v1", userAuthV1) - mg.AddMigration("alter user_auth.auth_id to length 190", new(RawSqlMigration). - Sqlite("SELECT 0 WHERE 0;"). + mg.AddMigration("alter user_auth.auth_id to length 190", NewRawSqlMigration(""). Postgres("ALTER TABLE user_auth ALTER COLUMN auth_id TYPE VARCHAR(190);"). Mysql("ALTER TABLE user_auth MODIFY auth_id VARCHAR(190);")) } diff --git a/pkg/services/sqlstore/migrator/column.go b/pkg/services/sqlstore/migrator/column.go index 11d6a1b8b08..28cef60a94d 100644 --- a/pkg/services/sqlstore/migrator/column.go +++ b/pkg/services/sqlstore/migrator/column.go @@ -15,48 +15,9 @@ type Column struct { } 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 + return d.ColString(col) } 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 " + d.Default(col) + " " - } - - return sql + return d.ColStringNoPk(col) } diff --git a/pkg/services/sqlstore/migrator/dialect.go b/pkg/services/sqlstore/migrator/dialect.go index dadc7248844..427d102b280 100644 --- a/pkg/services/sqlstore/migrator/dialect.go +++ b/pkg/services/sqlstore/migrator/dialect.go @@ -3,11 +3,12 @@ package migrator import ( "fmt" "strings" + + "github.com/go-xorm/xorm" ) type Dialect interface { DriverName() string - QuoteStr() string Quote(string) string AndStr() string AutoIncrStr() string @@ -31,16 +32,29 @@ type Dialect interface { TableCheckSql(tableName string) (string, []interface{}) RenameTable(oldName string, newName string) string UpdateTableSql(tableName string, columns []*Column) string + + ColString(*Column) string + ColStringNoPk(*Column) string + + Limit(limit int64) string + LimitOffset(limit int64, offset int64) string + + PreInsertId(table string, sess *xorm.Session) error + PostInsertId(table string, sess *xorm.Session) error + + CleanDB() error + NoOpSql() string } -func NewDialect(name string) Dialect { +func NewDialect(engine *xorm.Engine) Dialect { + name := engine.DriverName() switch name { case MYSQL: - return NewMysqlDialect() + return NewMysqlDialect(engine) case SQLITE: - return NewSqlite3Dialect() + return NewSqlite3Dialect(engine) case POSTGRES: - return NewPostgresDialect() + return NewPostgresDialect(engine) } panic("Unsupported database type: " + name) @@ -48,6 +62,7 @@ func NewDialect(name string) Dialect { type BaseDialect struct { dialect Dialect + engine *xorm.Engine driverName string } @@ -100,9 +115,12 @@ func (b *BaseDialect) CreateTableSql(table *Table) string { } if len(pkList) > 1 { - sql += "PRIMARY KEY ( " - sql += b.dialect.Quote(strings.Join(pkList, b.dialect.Quote(","))) - sql += " ), " + quotedCols := []string{} + for _, col := range pkList { + quotedCols = append(quotedCols, b.dialect.Quote(col)) + } + + sql += "PRIMARY KEY ( " + strings.Join(quotedCols, ",") + " ), " } sql = sql[:len(sql)-2] + ")" @@ -127,9 +145,12 @@ func (db *BaseDialect) CreateIndexSql(tableName string, index *Index) string { idxName := index.XName(tableName) - return fmt.Sprintf("CREATE%s INDEX %v ON %v (%v);", unique, - quote(idxName), quote(tableName), - quote(strings.Join(index.Cols, quote(",")))) + quotedCols := []string{} + for _, col := range index.Cols { + quotedCols = append(quotedCols, db.dialect.Quote(col)) + } + + return fmt.Sprintf("CREATE%s INDEX %v ON %v (%v);", unique, quote(idxName), quote(tableName), strings.Join(quotedCols, ",")) } func (db *BaseDialect) QuoteColList(cols []string) string { @@ -168,3 +189,74 @@ func (db *BaseDialect) DropIndexSql(tableName string, index *Index) string { func (db *BaseDialect) UpdateTableSql(tableName string, columns []*Column) string { return "-- NOT REQUIRED" } + +func (db *BaseDialect) ColString(col *Column) string { + sql := db.dialect.Quote(col.Name) + " " + + sql += db.dialect.SqlType(col) + " " + + if col.IsPrimaryKey { + sql += "PRIMARY KEY " + if col.IsAutoIncrement { + sql += db.dialect.AutoIncrStr() + " " + } + } + + if db.dialect.ShowCreateNull() { + if col.Nullable { + sql += "NULL " + } else { + sql += "NOT NULL " + } + } + + if col.Default != "" { + sql += "DEFAULT " + db.dialect.Default(col) + " " + } + + return sql +} + +func (db *BaseDialect) ColStringNoPk(col *Column) string { + sql := db.dialect.Quote(col.Name) + " " + + sql += db.dialect.SqlType(col) + " " + + if db.dialect.ShowCreateNull() { + if col.Nullable { + sql += "NULL " + } else { + sql += "NOT NULL " + } + } + + if col.Default != "" { + sql += "DEFAULT " + db.dialect.Default(col) + " " + } + + return sql +} + +func (db *BaseDialect) Limit(limit int64) string { + return fmt.Sprintf(" LIMIT %d", limit) +} + +func (db *BaseDialect) LimitOffset(limit int64, offset int64) string { + return fmt.Sprintf(" LIMIT %d OFFSET %d", limit, offset) +} + +func (db *BaseDialect) PreInsertId(table string, sess *xorm.Session) error { + return nil +} + +func (db *BaseDialect) PostInsertId(table string, sess *xorm.Session) error { + return nil +} + +func (db *BaseDialect) CleanDB() error { + return nil +} + +func (db *BaseDialect) NoOpSql() string { + return "SELECT 0;" +} diff --git a/pkg/services/sqlstore/migrator/migrations.go b/pkg/services/sqlstore/migrator/migrations.go index 4e6402a14e5..fd71cc3d290 100644 --- a/pkg/services/sqlstore/migrator/migrations.go +++ b/pkg/services/sqlstore/migrator/migrations.go @@ -24,37 +24,58 @@ func (m *MigrationBase) GetCondition() MigrationCondition { type RawSqlMigration struct { MigrationBase - sqlite string - mysql string - postgres string + sql map[string]string +} + +func NewRawSqlMigration(sql string) *RawSqlMigration { + m := &RawSqlMigration{} + if sql != "" { + m.Default(sql) + } + return m } func (m *RawSqlMigration) Sql(dialect Dialect) string { - switch dialect.DriverName() { - case MYSQL: - return m.mysql - case SQLITE: - return m.sqlite - case POSTGRES: - return m.postgres + if m.sql != nil { + if val := m.sql[dialect.DriverName()]; val != "" { + return val + } + + if val := m.sql["default"]; val != "" { + return val + } } - panic("db type not supported") + return dialect.NoOpSql() } -func (m *RawSqlMigration) Sqlite(sql string) *RawSqlMigration { - m.sqlite = sql +func (m *RawSqlMigration) Set(dialect string, sql string) *RawSqlMigration { + if m.sql == nil { + m.sql = make(map[string]string) + } + + m.sql[dialect] = sql return m } +func (m *RawSqlMigration) Default(sql string) *RawSqlMigration { + return m.Set("default", sql) +} + +func (m *RawSqlMigration) Sqlite(sql string) *RawSqlMigration { + return m.Set(SQLITE, sql) +} + func (m *RawSqlMigration) Mysql(sql string) *RawSqlMigration { - m.mysql = sql - return m + return m.Set(MYSQL, sql) } func (m *RawSqlMigration) Postgres(sql string) *RawSqlMigration { - m.postgres = sql - return m + return m.Set(POSTGRES, sql) +} + +func (m *RawSqlMigration) Mssql(sql string) *RawSqlMigration { + return m.Set(MSSQL, sql) } type AddColumnMigration struct { diff --git a/pkg/services/sqlstore/migrator/migrator.go b/pkg/services/sqlstore/migrator/migrator.go index cd00cb16712..a55163bd780 100644 --- a/pkg/services/sqlstore/migrator/migrator.go +++ b/pkg/services/sqlstore/migrator/migrator.go @@ -31,7 +31,7 @@ func NewMigrator(engine *xorm.Engine) *Migrator { mg.x = engine mg.Logger = log.New("migrator") mg.migrations = make([]Migration, 0) - mg.dialect = NewDialect(mg.x.DriverName()) + mg.dialect = NewDialect(mg.x) return mg } diff --git a/pkg/services/sqlstore/migrator/mysql_dialect.go b/pkg/services/sqlstore/migrator/mysql_dialect.go index 300224135f0..1ed16871c15 100644 --- a/pkg/services/sqlstore/migrator/mysql_dialect.go +++ b/pkg/services/sqlstore/migrator/mysql_dialect.go @@ -1,17 +1,21 @@ package migrator import ( + "fmt" "strconv" "strings" + + "github.com/go-xorm/xorm" ) type Mysql struct { BaseDialect } -func NewMysqlDialect() *Mysql { +func NewMysqlDialect(engine *xorm.Engine) *Mysql { d := Mysql{} d.BaseDialect.dialect = &d + d.BaseDialect.engine = engine d.BaseDialect.driverName = MYSQL return &d } @@ -24,10 +28,6 @@ func (db *Mysql) Quote(name string) string { return "`" + name + "`" } -func (db *Mysql) QuoteStr() string { - return "`" -} - func (db *Mysql) AutoIncrStr() string { return "AUTO_INCREMENT" } @@ -105,3 +105,23 @@ func (db *Mysql) UpdateTableSql(tableName string, columns []*Column) string { return "ALTER TABLE " + db.Quote(tableName) + " " + strings.Join(statements, ", ") + ";" } + +func (db *Mysql) CleanDB() error { + tables, _ := db.engine.DBMetas() + sess := db.engine.NewSession() + defer sess.Close() + + for _, table := range tables { + if _, err := sess.Exec("set foreign_key_checks = 0"); err != nil { + return fmt.Errorf("failed to disable foreign key checks") + } + if _, err := sess.Exec("drop table " + table.Name + " ;"); err != nil { + return fmt.Errorf("failed to delete table: %v, err: %v", table.Name, err) + } + if _, err := sess.Exec("set foreign_key_checks = 1"); err != nil { + return fmt.Errorf("failed to disable foreign key checks") + } + } + + return nil +} diff --git a/pkg/services/sqlstore/migrator/postgres_dialect.go b/pkg/services/sqlstore/migrator/postgres_dialect.go index e167aa33122..eae9ad3ca3f 100644 --- a/pkg/services/sqlstore/migrator/postgres_dialect.go +++ b/pkg/services/sqlstore/migrator/postgres_dialect.go @@ -4,15 +4,18 @@ import ( "fmt" "strconv" "strings" + + "github.com/go-xorm/xorm" ) type Postgres struct { BaseDialect } -func NewPostgresDialect() *Postgres { +func NewPostgresDialect(engine *xorm.Engine) *Postgres { d := Postgres{} d.BaseDialect.dialect = &d + d.BaseDialect.engine = engine d.BaseDialect.driverName = POSTGRES return &d } @@ -25,10 +28,6 @@ func (db *Postgres) Quote(name string) string { return "\"" + name + "\"" } -func (db *Postgres) QuoteStr() string { - return "\"" -} - func (b *Postgres) LikeStr() string { return "ILIKE" } @@ -117,8 +116,23 @@ func (db *Postgres) UpdateTableSql(tableName string, columns []*Column) string { var statements = []string{} for _, col := range columns { - statements = append(statements, "ALTER "+db.QuoteStr()+col.Name+db.QuoteStr()+" TYPE "+db.SqlType(col)) + statements = append(statements, "ALTER "+db.Quote(col.Name)+" TYPE "+db.SqlType(col)) } return "ALTER TABLE " + db.Quote(tableName) + " " + strings.Join(statements, ", ") + ";" } + +func (db *Postgres) CleanDB() error { + sess := db.engine.NewSession() + defer sess.Close() + + if _, err := sess.Exec("DROP SCHEMA public CASCADE;"); err != nil { + return fmt.Errorf("Failed to drop schema public") + } + + if _, err := sess.Exec("CREATE SCHEMA public;"); err != nil { + return fmt.Errorf("Failed to create schema public") + } + + return nil +} diff --git a/pkg/services/sqlstore/migrator/sqlite_dialect.go b/pkg/services/sqlstore/migrator/sqlite_dialect.go index 1a31cee4f5e..01082b95c88 100644 --- a/pkg/services/sqlstore/migrator/sqlite_dialect.go +++ b/pkg/services/sqlstore/migrator/sqlite_dialect.go @@ -1,14 +1,19 @@ package migrator -import "fmt" +import ( + "fmt" + + "github.com/go-xorm/xorm" +) type Sqlite3 struct { BaseDialect } -func NewSqlite3Dialect() *Sqlite3 { +func NewSqlite3Dialect(engine *xorm.Engine) *Sqlite3 { d := Sqlite3{} d.BaseDialect.dialect = &d + d.BaseDialect.engine = engine d.BaseDialect.driverName = SQLITE return &d } @@ -21,10 +26,6 @@ func (db *Sqlite3) Quote(name string) string { return "`" + name + "`" } -func (db *Sqlite3) QuoteStr() string { - return "`" -} - func (db *Sqlite3) AutoIncrStr() string { return "AUTOINCREMENT" } @@ -77,3 +78,7 @@ func (db *Sqlite3) DropIndexSql(tableName string, index *Index) string { idxName := index.XName(tableName) return fmt.Sprintf("DROP INDEX %v", quote(idxName)) } + +func (db *Sqlite3) CleanDB() error { + return nil +} diff --git a/pkg/services/sqlstore/migrator/types.go b/pkg/services/sqlstore/migrator/types.go index 62ec74e7b9f..26c46889daf 100644 --- a/pkg/services/sqlstore/migrator/types.go +++ b/pkg/services/sqlstore/migrator/types.go @@ -9,6 +9,7 @@ const ( POSTGRES = "postgres" SQLITE = "sqlite3" MYSQL = "mysql" + MSSQL = "mssql" ) type Migration interface { diff --git a/pkg/services/sqlstore/playlist.go b/pkg/services/sqlstore/playlist.go index 7b726880b9e..99519fe762f 100644 --- a/pkg/services/sqlstore/playlist.go +++ b/pkg/services/sqlstore/playlist.go @@ -64,7 +64,7 @@ func UpdatePlaylist(cmd *m.UpdatePlaylistCommand) error { Interval: playlist.Interval, } - _, err := x.ID(cmd.Id).Cols("id", "name", "interval").Update(&playlist) + _, err := x.ID(cmd.Id).Cols("name", "interval").Update(&playlist) if err != nil { return err diff --git a/pkg/services/sqlstore/search_builder.go b/pkg/services/sqlstore/search_builder.go index ddfbfbfc551..7817c4635c9 100644 --- a/pkg/services/sqlstore/search_builder.go +++ b/pkg/services/sqlstore/search_builder.go @@ -92,7 +92,7 @@ func (sb *SearchBuilder) ToSql() (string, []interface{}) { LEFT OUTER JOIN dashboard folder on folder.id = dashboard.folder_id LEFT OUTER JOIN dashboard_tag on dashboard.id = dashboard_tag.dashboard_id`) - sb.sql.WriteString(" ORDER BY dashboard.title ASC LIMIT 5000") + sb.sql.WriteString(" ORDER BY dashboard.title ASC" + dialect.Limit(5000)) return sb.sql.String(), sb.params } @@ -135,12 +135,11 @@ func (sb *SearchBuilder) buildTagQuery() { // this ends the inner select (tag filtered part) sb.sql.WriteString(` GROUP BY dashboard.id HAVING COUNT(dashboard.id) >= ? - LIMIT ?) as ids + ORDER BY dashboard.id` + dialect.Limit(int64(sb.limit)) + `) as ids INNER JOIN dashboard on ids.id = dashboard.id `) sb.params = append(sb.params, len(sb.tags)) - sb.params = append(sb.params, sb.limit) } func (sb *SearchBuilder) buildMainQuery() { @@ -153,8 +152,7 @@ func (sb *SearchBuilder) buildMainQuery() { sb.sql.WriteString(` WHERE `) sb.buildSearchWhereClause() - sb.sql.WriteString(` LIMIT ?) as ids INNER JOIN dashboard on ids.id = dashboard.id `) - sb.params = append(sb.params, sb.limit) + sb.sql.WriteString(` ORDER BY dashboard.title` + dialect.Limit(int64(sb.limit)) + `) as ids INNER JOIN dashboard on ids.id = dashboard.id `) } func (sb *SearchBuilder) buildSearchWhereClause() { diff --git a/pkg/services/sqlstore/search_builder_test.go b/pkg/services/sqlstore/search_builder_test.go index e8b02c445ec..e7ec3eedac6 100644 --- a/pkg/services/sqlstore/search_builder_test.go +++ b/pkg/services/sqlstore/search_builder_test.go @@ -4,13 +4,10 @@ import ( "testing" m "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/services/sqlstore/migrator" . "github.com/smartystreets/goconvey/convey" ) func TestSearchBuilder(t *testing.T) { - dialect = migrator.NewDialect("sqlite3") - Convey("Testing building a search", t, func() { signedInUser := &m.SignedInUser{ OrgId: 1, @@ -23,7 +20,7 @@ func TestSearchBuilder(t *testing.T) { sql, params := sb.IsStarred().WithTitle("test").ToSql() So(sql, ShouldStartWith, "SELECT") So(sql, ShouldContainSubstring, "INNER JOIN dashboard on ids.id = dashboard.id") - So(sql, ShouldEndWith, "ORDER BY dashboard.title ASC LIMIT 5000") + So(sql, ShouldContainSubstring, "ORDER BY dashboard.title ASC") So(len(params), ShouldBeGreaterThan, 0) }) @@ -31,7 +28,7 @@ func TestSearchBuilder(t *testing.T) { sql, params := sb.WithTags([]string{"tag1", "tag2"}).ToSql() So(sql, ShouldStartWith, "SELECT") So(sql, ShouldContainSubstring, "LEFT OUTER JOIN dashboard_tag") - So(sql, ShouldEndWith, "ORDER BY dashboard.title ASC LIMIT 5000") + So(sql, ShouldContainSubstring, "ORDER BY dashboard.title ASC") So(len(params), ShouldBeGreaterThan, 0) }) }) diff --git a/pkg/services/sqlstore/shared.go b/pkg/services/sqlstore/shared.go index 0f4aeb969c6..9a24a513aad 100644 --- a/pkg/services/sqlstore/shared.go +++ b/pkg/services/sqlstore/shared.go @@ -1,6 +1,7 @@ package sqlstore import ( + "reflect" "time" "github.com/go-xorm/xorm" @@ -67,3 +68,23 @@ func inTransactionWithRetry(callback dbTransactionFunc, retry int) error { return nil } + +func (sess *DBSession) InsertId(bean interface{}) (int64, error) { + table := sess.DB().Mapper.Obj2Table(getTypeName(bean)) + + dialect.PreInsertId(table, sess.Session) + + id, err := sess.Session.InsertOne(bean) + + dialect.PostInsertId(table, sess.Session) + + return id, err +} + +func getTypeName(bean interface{}) (res string) { + t := reflect.TypeOf(bean) + for t.Kind() == reflect.Ptr { + t = t.Elem() + } + return t.Name() +} diff --git a/pkg/services/sqlstore/sqlstore.go b/pkg/services/sqlstore/sqlstore.go index cac0c54226c..b1b4b89d716 100644 --- a/pkg/services/sqlstore/sqlstore.go +++ b/pkg/services/sqlstore/sqlstore.go @@ -20,7 +20,6 @@ import ( "github.com/grafana/grafana/pkg/setting" "github.com/go-sql-driver/mysql" - _ "github.com/go-sql-driver/mysql" "github.com/go-xorm/xorm" _ "github.com/lib/pq" _ "github.com/mattn/go-sqlite3" @@ -97,7 +96,7 @@ func NewEngine() *xorm.Engine { func SetEngine(engine *xorm.Engine) (err error) { x = engine - dialect = migrator.NewDialect(x.DriverName()) + dialect = migrator.NewDialect(x) migrator := migrator.NewMigrator(x) migrations.AddMigrations(migrator) @@ -116,7 +115,7 @@ func getEngine() (*xorm.Engine, error) { cnnstr := "" switch DbCfg.Type { - case "mysql": + case migrator.MYSQL: protocol := "tcp" if strings.HasPrefix(DbCfg.Host, "/") { protocol = "unix" @@ -133,7 +132,7 @@ func getEngine() (*xorm.Engine, error) { mysql.RegisterTLSConfig("custom", tlsCert) cnnstr += "&tls=custom" } - case "postgres": + case migrator.POSTGRES: var host, port = "127.0.0.1", "5432" fields := strings.Split(DbCfg.Host, ":") if len(fields) > 0 && len(strings.TrimSpace(fields[0])) > 0 { @@ -153,7 +152,7 @@ func getEngine() (*xorm.Engine, error) { strings.Replace(DbCfg.ClientKeyPath, `'`, `\'`, -1), strings.Replace(DbCfg.CaCertPath, `'`, `\'`, -1), ) - case "sqlite3": + case migrator.SQLITE: if !filepath.IsAbs(DbCfg.Path) { DbCfg.Path = filepath.Join(setting.DataPath, DbCfg.Path) } @@ -230,16 +229,10 @@ func LoadConfig() { DbCfg.Path = sec.Key("path").MustString("data/grafana.db") } -var ( - dbSqlite = "sqlite" - dbMySql = "mysql" - dbPostgres = "postgres" -) - func InitTestDB(t *testing.T) *xorm.Engine { - selectedDb := dbSqlite - // selectedDb := dbMySql - // selectedDb := dbPostgres + selectedDb := migrator.SQLITE + // selectedDb := migrator.MYSQL + // selectedDb := migrator.POSTGRES var x *xorm.Engine var err error @@ -250,9 +243,9 @@ func InitTestDB(t *testing.T) *xorm.Engine { } switch strings.ToLower(selectedDb) { - case dbMySql: + case migrator.MYSQL: x, err = xorm.NewEngine(sqlutil.TestDB_Mysql.DriverName, sqlutil.TestDB_Mysql.ConnStr) - case dbPostgres: + case migrator.POSTGRES: x, err = xorm.NewEngine(sqlutil.TestDB_Postgres.DriverName, sqlutil.TestDB_Postgres.ConnStr) default: x, err = xorm.NewEngine(sqlutil.TestDB_Sqlite3.DriverName, sqlutil.TestDB_Sqlite3.ConnStr) @@ -261,24 +254,29 @@ func InitTestDB(t *testing.T) *xorm.Engine { x.DatabaseTZ = time.UTC x.TZLocation = time.UTC - // x.ShowSQL() - if err != nil { t.Fatalf("Failed to init test database: %v", err) } - sqlutil.CleanDB(x) + dialect = migrator.NewDialect(x) + + err = dialect.CleanDB() + if err != nil { + t.Fatalf("Failed to clean test db %v", err) + } if err := SetEngine(x); err != nil { t.Fatal(err) } + // x.ShowSQL() + return x } func IsTestDbMySql() bool { if db, present := os.LookupEnv("GRAFANA_TEST_DB"); present { - return db == dbMySql + return db == migrator.MYSQL } return false @@ -286,7 +284,7 @@ func IsTestDbMySql() bool { func IsTestDbPostgres() bool { if db, present := os.LookupEnv("GRAFANA_TEST_DB"); present { - return db == dbPostgres + return db == migrator.POSTGRES } return false diff --git a/pkg/services/sqlstore/sqlutil/sqlutil.go b/pkg/services/sqlstore/sqlutil/sqlutil.go index 46306ac8e0d..f73985fb1ba 100644 --- a/pkg/services/sqlstore/sqlutil/sqlutil.go +++ b/pkg/services/sqlstore/sqlutil/sqlutil.go @@ -1,11 +1,5 @@ package sqlutil -import ( - "fmt" - - "github.com/go-xorm/xorm" -) - type TestDB struct { DriverName string ConnStr string @@ -15,34 +9,3 @@ var TestDB_Sqlite3 = TestDB{DriverName: "sqlite3", ConnStr: ":memory:"} var TestDB_Mysql = TestDB{DriverName: "mysql", ConnStr: "grafana:password@tcp(localhost:3306)/grafana_tests?collation=utf8mb4_unicode_ci"} var TestDB_Postgres = TestDB{DriverName: "postgres", ConnStr: "user=grafanatest password=grafanatest host=localhost port=5432 dbname=grafanatest sslmode=disable"} var TestDB_Mssql = TestDB{DriverName: "mssql", ConnStr: "server=localhost;port=1433;database=grafanatest;user id=grafana;password=Password!"} - -func CleanDB(x *xorm.Engine) { - if x.DriverName() == "postgres" { - sess := x.NewSession() - defer sess.Close() - - if _, err := sess.Exec("DROP SCHEMA public CASCADE;"); err != nil { - panic("Failed to drop schema public") - } - - if _, err := sess.Exec("CREATE SCHEMA public;"); err != nil { - panic("Failed to create schema public") - } - } else if x.DriverName() == "mysql" { - 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") - } - } - } -} diff --git a/pkg/services/sqlstore/team.go b/pkg/services/sqlstore/team.go index b3ff4c81e7c..7d53d114235 100644 --- a/pkg/services/sqlstore/team.go +++ b/pkg/services/sqlstore/team.go @@ -161,9 +161,8 @@ func SearchTeams(query *m.SearchTeamsQuery) error { sql.WriteString(` order by team.name asc`) if query.Limit != 0 { - sql.WriteString(` limit ? offset ?`) offset := query.Limit * (query.Page - 1) - params = append(params, query.Limit, offset) + sql.WriteString(dialect.LimitOffset(int64(query.Limit), int64(offset))) } if err := x.Sql(sql.String(), params...).Find(&query.Result.Teams); err != nil { diff --git a/pkg/services/sqlstore/user.go b/pkg/services/sqlstore/user.go index f19019d28a4..e7aa8da837a 100644 --- a/pkg/services/sqlstore/user.go +++ b/pkg/services/sqlstore/user.go @@ -60,8 +60,14 @@ func getOrgIdForNewUser(cmd *m.CreateUserCommand, sess *DBSession) (int64, error org.Created = time.Now() org.Updated = time.Now() - if _, err := sess.Insert(&org); err != nil { - return 0, err + if org.Id != 0 { + if _, err := sess.InsertId(&org); err != nil { + return 0, err + } + } else { + if _, err := sess.InsertOne(&org); err != nil { + return 0, err + } } sess.publishAfterCommit(&events.OrgCreated{