2015-01-20 21:15:48 +08:00
package migrator
2015-01-19 17:44:16 +08:00
2017-03-28 20:34:53 +08:00
import (
2022-02-16 00:54:27 +08:00
"database/sql"
2020-11-19 21:47:17 +08:00
"errors"
2020-10-16 15:46:14 +08:00
"fmt"
2017-03-28 20:34:53 +08:00
"strconv"
"strings"
2018-05-10 22:54:21 +08:00
2018-09-27 18:07:43 +08:00
"github.com/VividCortex/mysqlerr"
"github.com/go-sql-driver/mysql"
2025-03-12 22:40:11 +08:00
2025-05-02 23:13:01 +08:00
"github.com/grafana/grafana/pkg/util/xorm"
2017-03-28 20:34:53 +08:00
)
2015-01-19 17:44:16 +08:00
2020-11-11 13:21:08 +08:00
type MySQLDialect struct {
2015-01-19 17:44:16 +08:00
BaseDialect
}
2023-06-15 04:13:36 +08:00
func NewMysqlDialect ( ) Dialect {
2020-11-11 13:21:08 +08:00
d := MySQLDialect { }
2025-04-10 20:42:23 +08:00
d . dialect = & d
d . driverName = MySQL
2015-01-19 17:44:16 +08:00
return & d
}
2020-11-11 13:21:08 +08:00
func ( db * MySQLDialect ) SupportEngine ( ) bool {
2015-01-20 21:44:37 +08:00
return true
}
2020-11-11 13:21:08 +08:00
func ( db * MySQLDialect ) Quote ( name string ) string {
2015-01-19 17:44:16 +08:00
return "`" + name + "`"
}
2020-11-11 13:21:08 +08:00
func ( db * MySQLDialect ) AutoIncrStr ( ) string {
2015-01-19 17:44:16 +08:00
return "AUTO_INCREMENT"
}
2025-03-12 22:40:11 +08:00
func ( db * MySQLDialect ) BooleanValue ( value bool ) interface { } {
if value {
return 1
}
return 0
}
2020-11-11 13:21:08 +08:00
func ( db * MySQLDialect ) BooleanStr ( value bool ) string {
2016-10-25 20:52:20 +08:00
if value {
2016-12-14 11:15:35 +08:00
return "1"
}
return "0"
2016-09-23 14:07:14 +08:00
}
2022-11-02 02:24:32 +08:00
func ( db * MySQLDialect ) BatchSize ( ) int {
return 1000
}
2020-11-11 13:21:08 +08:00
func ( db * MySQLDialect ) SQLType ( c * Column ) string {
2015-01-19 17:44:16 +08:00
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
2023-11-03 22:30:52 +08:00
case DB_Uuid :
res = DB_Char
c . Length = 36
2015-01-19 17:44:16 +08:00
default :
res = c . Type
}
2018-04-28 04:14:36 +08:00
var hasLen1 = ( c . Length > 0 )
var hasLen2 = ( c . Length2 > 0 )
2015-01-19 17:44:16 +08:00
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 ) + ")"
}
2017-03-28 20:34:53 +08:00
switch c . Type {
case DB_Char , DB_Varchar , DB_NVarchar , DB_TinyText , DB_Text , DB_MediumText , DB_LongText :
2022-11-05 05:30:22 +08:00
if c . IsLatin {
res += " CHARACTER SET latin1 COLLATE latin1_bin"
} else {
res += " CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci"
}
2017-03-28 20:34:53 +08:00
}
2015-01-19 17:44:16 +08:00
return res
}
2020-11-11 13:21:08 +08:00
func ( db * MySQLDialect ) UpdateTableSQL ( tableName string , columns [ ] * Column ) string {
2017-03-28 20:34:53 +08:00
var statements = [ ] string { }
statements = append ( statements , "DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci" )
for _ , col := range columns {
statements = append ( statements , "MODIFY " + col . StringNoPk ( db ) )
}
return "ALTER TABLE " + db . Quote ( tableName ) + " " + strings . Join ( statements , ", " ) + ";"
}
2018-05-10 22:54:21 +08:00
2023-08-30 23:46:47 +08:00
func ( db * MySQLDialect ) IndexCheckSQL ( tableName , indexName string ) ( string , [ ] any ) {
args := [ ] any { tableName , indexName }
2018-12-19 04:47:45 +08:00
sql := "SELECT 1 FROM " + db . Quote ( "INFORMATION_SCHEMA" ) + "." + db . Quote ( "STATISTICS" ) + " WHERE " + db . Quote ( "TABLE_SCHEMA" ) + " = DATABASE() AND " + db . Quote ( "TABLE_NAME" ) + "=? AND " + db . Quote ( "INDEX_NAME" ) + "=?"
return sql , args
}
2023-08-30 23:46:47 +08:00
func ( db * MySQLDialect ) ColumnCheckSQL ( tableName , columnName string ) ( string , [ ] any ) {
args := [ ] any { tableName , columnName }
2018-12-19 06:02:08 +08:00
sql := "SELECT 1 FROM " + db . Quote ( "INFORMATION_SCHEMA" ) + "." + db . Quote ( "COLUMNS" ) + " WHERE " + db . Quote ( "TABLE_SCHEMA" ) + " = DATABASE() AND " + db . Quote ( "TABLE_NAME" ) + "=? AND " + db . Quote ( "COLUMN_NAME" ) + "=?"
return sql , args
}
2022-06-03 23:42:08 +08:00
func ( db * MySQLDialect ) RenameColumn ( table Table , column * Column , newName string ) string {
2022-05-23 19:13:55 +08:00
quote := db . dialect . Quote
2022-06-03 23:42:08 +08:00
return fmt . Sprintf (
"ALTER TABLE %s CHANGE %s %s %s" ,
quote ( table . Name ) , quote ( column . Name ) , quote ( newName ) , db . SQLType ( column ) ,
)
2022-05-23 19:13:55 +08:00
}
2023-06-15 04:13:36 +08:00
func ( db * MySQLDialect ) CleanDB ( engine * xorm . Engine ) error {
tables , err := engine . DBMetas ( )
2020-07-10 22:09:21 +08:00
if err != nil {
return err
}
2023-06-15 04:13:36 +08:00
sess := engine . NewSession ( )
2018-05-10 22:54:21 +08:00
defer sess . Close ( )
for _ , table := range tables {
2021-01-07 18:36:13 +08:00
switch table . Name {
default :
if _ , err := sess . Exec ( "set foreign_key_checks = 0" ) ; err != nil {
2022-06-03 15:24:24 +08:00
return fmt . Errorf ( "%v: %w" , "failed to disable foreign key checks" , err )
2021-01-07 18:36:13 +08:00
}
if _ , err := sess . Exec ( "drop table " + table . Name + " ;" ) ; err != nil {
2022-06-07 04:30:31 +08:00
return fmt . Errorf ( "failed to delete table %q: %w" , table . Name , err )
2021-01-07 18:36:13 +08:00
}
if _ , err := sess . Exec ( "set foreign_key_checks = 1" ) ; err != nil {
2022-06-03 15:24:24 +08:00
return fmt . Errorf ( "%v: %w" , "failed to disable foreign key checks" , err )
2021-01-07 18:36:13 +08:00
}
2018-05-10 22:54:21 +08:00
}
}
return nil
}
2018-09-27 18:07:43 +08:00
2020-10-16 15:46:14 +08:00
// TruncateDBTables truncates all the tables.
// A special case is the dashboard_acl table where we keep the default permissions.
2023-06-15 04:13:36 +08:00
func ( db * MySQLDialect ) TruncateDBTables ( engine * xorm . Engine ) error {
2025-03-19 21:16:20 +08:00
tables , err := engine . Dialect ( ) . GetTables ( )
2020-10-16 15:46:14 +08:00
if err != nil {
return err
}
2023-06-15 04:13:36 +08:00
sess := engine . NewSession ( )
2020-10-16 15:46:14 +08:00
defer sess . Close ( )
for _ , table := range tables {
switch table . Name {
2021-08-25 21:11:22 +08:00
case "migration_log" :
continue
2020-10-16 15:46:14 +08:00
case "dashboard_acl" :
// keep default dashboard permissions
if _ , err := sess . Exec ( fmt . Sprintf ( "DELETE FROM %v WHERE dashboard_id != -1 AND org_id != -1;" , db . Quote ( table . Name ) ) ) ; err != nil {
2022-06-07 04:30:31 +08:00
return fmt . Errorf ( "failed to truncate table %q: %w" , table . Name , err )
2020-10-16 15:46:14 +08:00
}
if _ , err := sess . Exec ( fmt . Sprintf ( "ALTER TABLE %v AUTO_INCREMENT = 3;" , db . Quote ( table . Name ) ) ) ; err != nil {
2022-06-07 04:30:31 +08:00
return fmt . Errorf ( "failed to reset table %q: %w" , table . Name , err )
2020-10-16 15:46:14 +08:00
}
default :
if _ , err := sess . Exec ( fmt . Sprintf ( "TRUNCATE TABLE %v;" , db . Quote ( table . Name ) ) ) ; err != nil {
2022-06-07 04:30:31 +08:00
return fmt . Errorf ( "failed to truncate table %q: %w" , table . Name , err )
2020-10-16 15:46:14 +08:00
}
}
}
return nil
}
2020-11-11 13:21:08 +08:00
func ( db * MySQLDialect ) isThisError ( err error , errcode uint16 ) bool {
2020-11-19 21:47:17 +08:00
var driverErr * mysql . MySQLError
if errors . As ( err , & driverErr ) {
2019-06-13 21:36:09 +08:00
if driverErr . Number == errcode {
2018-09-27 18:07:43 +08:00
return true
}
}
return false
}
2019-06-13 21:36:09 +08:00
2020-11-11 13:21:08 +08:00
func ( db * MySQLDialect ) IsUniqueConstraintViolation ( err error ) bool {
2019-06-13 21:36:09 +08:00
return db . isThisError ( err , mysqlerr . ER_DUP_ENTRY )
}
2020-11-11 13:21:08 +08:00
func ( db * MySQLDialect ) ErrorMessage ( err error ) string {
2020-11-19 21:47:17 +08:00
var driverErr * mysql . MySQLError
if errors . As ( err , & driverErr ) {
2020-04-20 21:48:38 +08:00
return driverErr . Message
}
return ""
}
2020-11-11 13:21:08 +08:00
func ( db * MySQLDialect ) IsDeadlock ( err error ) bool {
2019-06-13 21:36:09 +08:00
return db . isThisError ( err , mysqlerr . ER_LOCK_DEADLOCK )
}
2021-01-19 02:57:17 +08:00
2022-07-19 16:42:48 +08:00
// UpsertSQL returns the upsert sql statement for MySQL dialect
2021-01-19 02:57:17 +08:00
func ( db * MySQLDialect ) UpsertSQL ( tableName string , keyCols , updateCols [ ] string ) string {
2022-07-19 16:42:48 +08:00
q , _ := db . UpsertMultipleSQL ( tableName , keyCols , updateCols , 1 )
return q
}
func ( db * MySQLDialect ) UpsertMultipleSQL ( tableName string , keyCols , updateCols [ ] string , count int ) ( string , error ) {
if count < 1 {
return "" , fmt . Errorf ( "upsert statement must have count >= 1. Got %v" , count )
}
2021-01-19 02:57:17 +08:00
columnsStr := strings . Builder { }
colPlaceHoldersStr := strings . Builder { }
setStr := strings . Builder { }
separator := ", "
for i , c := range updateCols {
if i == len ( updateCols ) - 1 {
separator = ""
}
columnsStr . WriteString ( fmt . Sprintf ( "%s%s" , db . Quote ( c ) , separator ) )
colPlaceHoldersStr . WriteString ( fmt . Sprintf ( "?%s" , separator ) )
setStr . WriteString ( fmt . Sprintf ( "%s=VALUES(%s)%s" , db . Quote ( c ) , db . Quote ( c ) , separator ) )
}
2022-07-19 16:42:48 +08:00
valuesStr := strings . Builder { }
separator = ", "
colPlaceHolders := colPlaceHoldersStr . String ( )
for i := 0 ; i < count ; i ++ {
if i == count - 1 {
separator = ""
}
valuesStr . WriteString ( fmt . Sprintf ( "(%s)%s" , colPlaceHolders , separator ) )
}
s := fmt . Sprintf ( ` INSERT INTO %s (%s) VALUES %s ON DUPLICATE KEY UPDATE %s ` ,
2021-01-19 02:57:17 +08:00
tableName ,
columnsStr . String ( ) ,
2022-07-19 16:42:48 +08:00
valuesStr . String ( ) ,
2021-01-19 02:57:17 +08:00
setStr . String ( ) ,
)
2022-07-19 16:42:48 +08:00
return s , nil
2021-01-19 02:57:17 +08:00
}
2022-02-16 00:54:27 +08:00
func ( db * MySQLDialect ) Lock ( cfg LockCfg ) error {
query := "SELECT GET_LOCK(?, ?)"
var success sql . NullBool
// trying to obtain the lock with the specific name
// the lock is exclusive per session and is released explicitly by executing RELEASE_LOCK() or implicitly when the session terminates
// it returns 1 if the lock was obtained successfully,
// 0 if the attempt timed out (for example, because another client has previously locked the name),
// or NULL if an error occurred
// starting from MySQL 5.7 it is even possible for a given session to acquire multiple locks for the same name
// however other sessions cannot acquire a lock with that name until the acquiring session releases all its locks for the name.
2023-06-15 04:13:36 +08:00
_ , err := cfg . Session . SQL ( query , cfg . Key , cfg . Timeout ) . Get ( & success )
2022-02-16 00:54:27 +08:00
if err != nil {
return err
}
if ! success . Valid || ! success . Bool {
return ErrLockDB
}
return nil
}
func ( db * MySQLDialect ) Unlock ( cfg LockCfg ) error {
query := "SELECT RELEASE_LOCK(?)"
var success sql . NullBool
// trying to release the lock with the specific name
// it returns 1 if the lock was released,
// 0 if the lock was not established by this thread (in which case the lock is not released),
// and NULL if the named lock did not exist (it was never obtained by a call to GET_LOCK() or if it has previously been released)
2023-06-15 04:13:36 +08:00
_ , err := cfg . Session . SQL ( query , cfg . Key ) . Get ( & success )
2022-02-16 00:54:27 +08:00
if err != nil {
return err
}
if ! success . Valid || ! success . Bool {
return ErrReleaseLockDB
}
return nil
}
2023-06-15 04:13:36 +08:00
func ( db * MySQLDialect ) GetDBName ( dsn string ) ( string , error ) {
cfg , err := mysql . ParseDSN ( dsn )
2022-02-16 00:54:27 +08:00
if err != nil {
return "" , err
}
2023-06-15 04:13:36 +08:00
return cfg . DBName , nil
2022-02-16 00:54:27 +08:00
}