2022-12-26 17:45:21 +08:00
// Copyright 2015 The Xorm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package xorm
import (
"errors"
"fmt"
"regexp"
"strconv"
"strings"
"time"
2025-04-16 14:58:50 +08:00
"github.com/grafana/grafana/pkg/util/xorm/core"
2022-12-26 17:45:21 +08:00
)
var (
mysqlReservedWords = map [ string ] bool {
"ADD" : true ,
"ALL" : true ,
"ALTER" : true ,
"ANALYZE" : true ,
"AND" : true ,
"AS" : true ,
"ASC" : true ,
"ASENSITIVE" : true ,
"BEFORE" : true ,
"BETWEEN" : true ,
"BIGINT" : true ,
"BINARY" : true ,
"BLOB" : true ,
"BOTH" : true ,
"BY" : true ,
"CALL" : true ,
"CASCADE" : true ,
"CASE" : true ,
"CHANGE" : true ,
"CHAR" : true ,
"CHARACTER" : true ,
"CHECK" : true ,
"COLLATE" : true ,
"COLUMN" : true ,
"CONDITION" : true ,
"CONNECTION" : true ,
"CONSTRAINT" : true ,
"CONTINUE" : true ,
"CONVERT" : true ,
"CREATE" : true ,
"CROSS" : true ,
"CURRENT_DATE" : true ,
"CURRENT_TIME" : true ,
"CURRENT_TIMESTAMP" : true ,
"CURRENT_USER" : true ,
"CURSOR" : true ,
"DATABASE" : true ,
"DATABASES" : true ,
"DAY_HOUR" : true ,
"DAY_MICROSECOND" : true ,
"DAY_MINUTE" : true ,
"DAY_SECOND" : true ,
"DEC" : true ,
"DECIMAL" : true ,
"DECLARE" : true ,
"DEFAULT" : true ,
"DELAYED" : true ,
"DELETE" : true ,
"DESC" : true ,
"DESCRIBE" : true ,
"DETERMINISTIC" : true ,
"DISTINCT" : true ,
"DISTINCTROW" : true ,
"DIV" : true ,
"DOUBLE" : true ,
"DROP" : true ,
"DUAL" : true ,
"EACH" : true ,
"ELSE" : true ,
"ELSEIF" : true ,
"ENCLOSED" : true ,
"ESCAPED" : true ,
"EXISTS" : true ,
"EXIT" : true ,
"EXPLAIN" : true ,
"FALSE" : true ,
"FETCH" : true ,
"FLOAT" : true ,
"FLOAT4" : true ,
"FLOAT8" : true ,
"FOR" : true ,
"FORCE" : true ,
"FOREIGN" : true ,
"FROM" : true ,
"FULLTEXT" : true ,
"GOTO" : true ,
"GRANT" : true ,
"GROUP" : true ,
"HAVING" : true ,
"HIGH_PRIORITY" : true ,
"HOUR_MICROSECOND" : true ,
"HOUR_MINUTE" : true ,
"HOUR_SECOND" : true ,
"IF" : true ,
"IGNORE" : true ,
"IN" : true , "INDEX" : true ,
"INFILE" : true , "INNER" : true , "INOUT" : true ,
"INSENSITIVE" : true , "INSERT" : true , "INT" : true ,
"INT1" : true , "INT2" : true , "INT3" : true ,
"INT4" : true , "INT8" : true , "INTEGER" : true ,
"INTERVAL" : true , "INTO" : true , "IS" : true ,
"ITERATE" : true , "JOIN" : true , "KEY" : true ,
"KEYS" : true , "KILL" : true , "LABEL" : true ,
"LEADING" : true , "LEAVE" : true , "LEFT" : true ,
"LIKE" : true , "LIMIT" : true , "LINEAR" : true ,
"LINES" : true , "LOAD" : true , "LOCALTIME" : true ,
"LOCALTIMESTAMP" : true , "LOCK" : true , "LONG" : true ,
"LONGBLOB" : true , "LONGTEXT" : true , "LOOP" : true ,
"LOW_PRIORITY" : true , "MATCH" : true , "MEDIUMBLOB" : true ,
"MEDIUMINT" : true , "MEDIUMTEXT" : true , "MIDDLEINT" : true ,
"MINUTE_MICROSECOND" : true , "MINUTE_SECOND" : true , "MOD" : true ,
"MODIFIES" : true , "NATURAL" : true , "NOT" : true ,
"NO_WRITE_TO_BINLOG" : true , "NULL" : true , "NUMERIC" : true ,
"ON OPTIMIZE" : true , "OPTION" : true ,
"OPTIONALLY" : true , "OR" : true , "ORDER" : true ,
"OUT" : true , "OUTER" : true , "OUTFILE" : true ,
"PRECISION" : true , "PRIMARY" : true , "PROCEDURE" : true ,
"PURGE" : true , "RAID0" : true , "RANGE" : true ,
"READ" : true , "READS" : true , "REAL" : true ,
"REFERENCES" : true , "REGEXP" : true , "RELEASE" : true ,
"RENAME" : true , "REPEAT" : true , "REPLACE" : true ,
"REQUIRE" : true , "RESTRICT" : true , "RETURN" : true ,
"REVOKE" : true , "RIGHT" : true , "RLIKE" : true ,
"SCHEMA" : true , "SCHEMAS" : true , "SECOND_MICROSECOND" : true ,
"SELECT" : true , "SENSITIVE" : true , "SEPARATOR" : true ,
"SET" : true , "SHOW" : true , "SMALLINT" : true ,
"SPATIAL" : true , "SPECIFIC" : true , "SQL" : true ,
"SQLEXCEPTION" : true , "SQLSTATE" : true , "SQLWARNING" : true ,
"SQL_BIG_RESULT" : true , "SQL_CALC_FOUND_ROWS" : true , "SQL_SMALL_RESULT" : true ,
"SSL" : true , "STARTING" : true , "STRAIGHT_JOIN" : true ,
"TABLE" : true , "TERMINATED" : true , "THEN" : true ,
"TINYBLOB" : true , "TINYINT" : true , "TINYTEXT" : true ,
"TO" : true , "TRAILING" : true , "TRIGGER" : true ,
"TRUE" : true , "UNDO" : true , "UNION" : true ,
"UNIQUE" : true , "UNLOCK" : true , "UNSIGNED" : true ,
"UPDATE" : true , "USAGE" : true , "USE" : true ,
"USING" : true , "UTC_DATE" : true , "UTC_TIME" : true ,
"UTC_TIMESTAMP" : true , "VALUES" : true , "VARBINARY" : true ,
"VARCHAR" : true ,
"VARCHARACTER" : true ,
"VARYING" : true ,
"WHEN" : true ,
"WHERE" : true ,
"WHILE" : true ,
"WITH" : true ,
"WRITE" : true ,
"X509" : true ,
"XOR" : true ,
"YEAR_MONTH" : true ,
"ZEROFILL" : true ,
}
)
type mysql struct {
core . Base
2023-01-24 19:57:33 +08:00
rowFormat string
2022-12-26 17:45:21 +08:00
}
func ( db * mysql ) Init ( d * core . DB , uri * core . Uri , drivername , dataSourceName string ) error {
return db . Base . Init ( d , db , uri , drivername , dataSourceName )
}
func ( db * mysql ) SetParams ( params map [ string ] string ) {
rowFormat , ok := params [ "rowFormat" ]
if ok {
var t = strings . ToUpper ( rowFormat )
switch t {
case "COMPACT" :
fallthrough
case "REDUNDANT" :
fallthrough
case "DYNAMIC" :
fallthrough
case "COMPRESSED" :
db . rowFormat = t
default :
break
}
}
}
func ( db * mysql ) SqlType ( c * core . Column ) string {
var res string
switch t := c . SQLType . Name ; t {
case core . Bool :
res = core . TinyInt
c . Length = 1
case core . Serial :
c . IsAutoIncrement = true
c . IsPrimaryKey = true
c . Nullable = false
res = core . Int
case core . BigSerial :
c . IsAutoIncrement = true
c . IsPrimaryKey = true
c . Nullable = false
res = core . BigInt
case core . Bytea :
res = core . Blob
case core . TimeStampz :
res = core . Char
c . Length = 64
case core . Enum : // mysql enum
res = core . Enum
res += "("
opts := ""
for v := range c . EnumOptions {
opts += fmt . Sprintf ( ",'%v'" , v )
}
res += strings . TrimLeft ( opts , "," )
res += ")"
case core . Set : // mysql set
res = core . Set
res += "("
opts := ""
for v := range c . SetOptions {
opts += fmt . Sprintf ( ",'%v'" , v )
}
res += strings . TrimLeft ( opts , "," )
res += ")"
case core . NVarchar :
res = core . Varchar
case core . Uuid :
res = core . Varchar
c . Length = 40
case core . Json :
res = core . Text
default :
res = t
}
hasLen1 := ( c . Length > 0 )
hasLen2 := ( c . Length2 > 0 )
if res == core . 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 ) SupportInsertMany ( ) bool {
return true
}
func ( db * mysql ) IsReserved ( name string ) bool {
_ , ok := mysqlReservedWords [ name ]
return ok
}
func ( db * mysql ) Quote ( name string ) string {
return "`" + name + "`"
}
func ( db * mysql ) SupportEngine ( ) bool {
return true
}
func ( db * mysql ) AutoIncrStr ( ) string {
return "AUTO_INCREMENT"
}
func ( db * mysql ) SupportCharset ( ) bool {
return true
}
func ( db * mysql ) IndexOnTable ( ) bool {
return true
}
2023-08-30 23:46:47 +08:00
func ( db * mysql ) IndexCheckSql ( tableName , idxName string ) ( string , [ ] any ) {
args := [ ] any { db . DbName , tableName , idxName }
2022-12-26 17:45:21 +08:00
sql := "SELECT `INDEX_NAME` FROM `INFORMATION_SCHEMA`.`STATISTICS`"
sql += " WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ? AND `INDEX_NAME`=?"
return sql , args
}
2023-08-30 23:46:47 +08:00
/ * func ( db * mysql ) ColumnCheckSql ( tableName , colName string ) ( string , [ ] any ) {
args := [ ] any { db . DbName , tableName , colName }
2022-12-26 17:45:21 +08:00
sql := "SELECT `COLUMN_NAME` FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ? AND `COLUMN_NAME` = ?"
return sql , args
} * /
2023-08-30 23:46:47 +08:00
func ( db * mysql ) TableCheckSql ( tableName string ) ( string , [ ] any ) {
args := [ ] any { db . DbName , tableName }
2022-12-26 17:45:21 +08:00
sql := "SELECT `TABLE_NAME` from `INFORMATION_SCHEMA`.`TABLES` WHERE `TABLE_SCHEMA`=? and `TABLE_NAME`=?"
return sql , args
}
func ( db * mysql ) GetColumns ( tableName string ) ( [ ] string , map [ string ] * core . Column , error ) {
2023-08-30 23:46:47 +08:00
args := [ ] any { db . DbName , tableName }
2022-12-26 17:45:21 +08:00
s := "SELECT `COLUMN_NAME`, `IS_NULLABLE`, `COLUMN_DEFAULT`, `COLUMN_TYPE`," +
2025-03-06 23:11:20 +08:00
" `COLUMN_KEY`, `EXTRA`,`COLUMN_COMMENT` FROM `INFORMATION_SCHEMA`.`COLUMNS`" +
" WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ? ORDER BY ORDINAL_POSITION ASC"
2022-12-26 17:45:21 +08:00
db . LogSQL ( s , args )
rows , err := db . DB ( ) . Query ( s , args ... )
if err != nil {
return nil , nil , err
}
defer rows . Close ( )
cols := make ( map [ string ] * core . Column )
colSeq := make ( [ ] string , 0 )
for rows . Next ( ) {
col := new ( core . Column )
col . Indexes = make ( map [ string ] int )
var columnName , isNullable , colType , colKey , extra , comment string
var colDefault * string
err = rows . Scan ( & columnName , & isNullable , & colDefault , & colType , & colKey , & extra , & comment )
if err != nil {
return nil , nil , err
}
col . Name = strings . Trim ( columnName , "` " )
col . Comment = comment
2023-01-24 19:57:33 +08:00
if isNullable == "YES" {
2022-12-26 17:45:21 +08:00
col . Nullable = true
}
if colDefault != nil {
col . Default = * colDefault
col . DefaultIsEmpty = false
} else {
col . DefaultIsEmpty = true
}
cts := strings . Split ( colType , "(" )
colName := cts [ 0 ]
colType = strings . ToUpper ( colName )
var len1 , len2 int
if len ( cts ) == 2 {
idx := strings . Index ( cts [ 1 ] , ")" )
if colType == core . Enum && cts [ 1 ] [ 0 ] == '\'' { // enum
options := strings . Split ( cts [ 1 ] [ 0 : idx ] , "," )
col . EnumOptions = make ( map [ string ] int )
for k , v := range options {
v = strings . TrimSpace ( v )
v = strings . Trim ( v , "'" )
col . EnumOptions [ v ] = k
}
} else if colType == core . Set && cts [ 1 ] [ 0 ] == '\'' {
options := strings . Split ( cts [ 1 ] [ 0 : idx ] , "," )
col . SetOptions = make ( map [ string ] int )
for k , v := range options {
v = strings . TrimSpace ( v )
v = strings . Trim ( v , "'" )
col . SetOptions [ v ] = k
}
} else {
lens := strings . Split ( cts [ 1 ] [ 0 : idx ] , "," )
len1 , err = strconv . Atoi ( strings . TrimSpace ( lens [ 0 ] ) )
if err != nil {
return nil , nil , err
}
if len ( lens ) == 2 {
len2 , err = strconv . Atoi ( lens [ 1 ] )
if err != nil {
return nil , nil , err
}
}
}
}
if colType == "FLOAT UNSIGNED" {
colType = "FLOAT"
}
if colType == "DOUBLE UNSIGNED" {
colType = "DOUBLE"
}
col . Length = len1
col . Length2 = len2
if _ , ok := core . SqlTypes [ colType ] ; ok {
col . SQLType = core . SQLType { Name : colType , DefaultLength : len1 , DefaultLength2 : len2 }
} else {
2023-01-24 19:57:33 +08:00
return nil , nil , fmt . Errorf ( "unknown colType %v" , colType )
2022-12-26 17:45:21 +08:00
}
if colKey == "PRI" {
col . IsPrimaryKey = true
}
if extra == "auto_increment" {
col . IsAutoIncrement = true
}
if ! col . DefaultIsEmpty {
if col . SQLType . IsText ( ) {
col . Default = "'" + col . Default + "'"
} else if col . SQLType . IsTime ( ) && col . Default != "CURRENT_TIMESTAMP" {
col . Default = "'" + col . Default + "'"
}
}
cols [ col . Name ] = col
colSeq = append ( colSeq , col . Name )
}
return colSeq , cols , nil
}
func ( db * mysql ) GetTables ( ) ( [ ] * core . Table , error ) {
2023-08-30 23:46:47 +08:00
args := [ ] any { db . DbName }
2022-12-26 17:45:21 +08:00
s := "SELECT `TABLE_NAME`, `ENGINE`, `TABLE_ROWS`, `AUTO_INCREMENT`, `TABLE_COMMENT` from " +
"`INFORMATION_SCHEMA`.`TABLES` WHERE `TABLE_SCHEMA`=? AND (`ENGINE`='MyISAM' OR `ENGINE` = 'InnoDB' OR `ENGINE` = 'TokuDB')"
db . LogSQL ( s , args )
rows , err := db . DB ( ) . Query ( s , args ... )
if err != nil {
return nil , err
}
defer rows . Close ( )
tables := make ( [ ] * core . Table , 0 )
for rows . Next ( ) {
table := core . NewEmptyTable ( )
var name , engine , tableRows , comment string
var autoIncr * string
err = rows . Scan ( & name , & engine , & tableRows , & autoIncr , & comment )
if err != nil {
return nil , err
}
table . Name = name
table . Comment = comment
table . StoreEngine = engine
tables = append ( tables , table )
}
return tables , nil
}
func ( db * mysql ) GetIndexes ( tableName string ) ( map [ string ] * core . Index , error ) {
2023-08-30 23:46:47 +08:00
args := [ ] any { db . DbName , tableName }
2025-03-06 23:11:20 +08:00
s := "SELECT `INDEX_NAME`, `NON_UNIQUE`, `COLUMN_NAME` FROM `INFORMATION_SCHEMA`.`STATISTICS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ? ORDER BY `INDEX_NAME`, `SEQ_IN_INDEX`"
2022-12-26 17:45:21 +08:00
db . LogSQL ( s , args )
rows , err := db . DB ( ) . Query ( s , args ... )
if err != nil {
return nil , err
}
defer rows . Close ( )
indexes := make ( map [ string ] * core . Index , 0 )
for rows . Next ( ) {
var indexType int
var indexName , colName , nonUnique string
err = rows . Scan ( & indexName , & nonUnique , & colName )
if err != nil {
return nil , err
}
if indexName == "PRIMARY" {
continue
}
2023-01-24 19:57:33 +08:00
if nonUnique == "YES" || nonUnique == "1" {
2022-12-26 17:45:21 +08:00
indexType = core . IndexType
} else {
indexType = core . UniqueType
}
colName = strings . Trim ( colName , "` " )
var isRegular bool
if strings . HasPrefix ( indexName , "IDX_" + tableName ) || strings . HasPrefix ( indexName , "UQE_" + tableName ) {
indexName = indexName [ 5 + len ( tableName ) : ]
isRegular = true
}
var index * core . Index
var ok bool
if index , ok = indexes [ indexName ] ; ! ok {
index = new ( core . Index )
index . IsRegular = isRegular
index . Type = indexType
index . Name = indexName
indexes [ indexName ] = index
}
index . AddColumn ( colName )
}
return indexes , nil
}
func ( db * mysql ) CreateTableSql ( table * core . Table , tableName , storeEngine , charset string ) string {
var sql string
sql = "CREATE TABLE IF NOT EXISTS "
if tableName == "" {
tableName = table . Name
}
sql += db . Quote ( tableName )
sql += " ("
if len ( table . ColumnsSeq ( ) ) > 0 {
pkList := table . PrimaryKeys
for _ , colName := range table . ColumnsSeq ( ) {
col := table . GetColumn ( colName )
if col . IsPrimaryKey && len ( pkList ) == 1 {
sql += col . String ( db )
} else {
sql += col . StringNoPk ( db )
}
sql = strings . TrimSpace ( sql )
if len ( col . Comment ) > 0 {
sql += " COMMENT '" + col . Comment + "'"
}
sql += ", "
}
if len ( pkList ) > 1 {
sql += "PRIMARY KEY ( "
sql += db . Quote ( strings . Join ( pkList , db . Quote ( "," ) ) )
sql += " ), "
}
sql = sql [ : len ( sql ) - 2 ]
}
sql += ")"
if storeEngine != "" {
sql += " ENGINE=" + storeEngine
}
if len ( charset ) == 0 {
charset = db . URI ( ) . Charset
}
if len ( charset ) != 0 {
sql += " DEFAULT CHARSET " + charset
}
if db . rowFormat != "" {
sql += " ROW_FORMAT=" + db . rowFormat
}
return sql
}
func ( db * mysql ) Filters ( ) [ ] core . Filter {
return [ ] core . Filter { & core . IdFilter { } }
}
type mymysqlDriver struct {
}
func ( p * mymysqlDriver ) Parse ( driverName , dataSourceName string ) ( * core . Uri , error ) {
db := & core . Uri { DbType : core . MYSQL }
pd := strings . SplitN ( dataSourceName , "*" , 2 )
if len ( pd ) == 2 {
// Parse protocol part of URI
p := strings . SplitN ( pd [ 0 ] , ":" , 2 )
if len ( p ) != 2 {
2023-01-24 19:57:33 +08:00
return nil , errors . New ( "wrong protocol part of URI" )
2022-12-26 17:45:21 +08:00
}
db . Proto = p [ 0 ]
options := strings . Split ( p [ 1 ] , "," )
db . Raddr = options [ 0 ]
for _ , o := range options [ 1 : ] {
kv := strings . SplitN ( o , "=" , 2 )
var k , v string
if len ( kv ) == 2 {
k , v = kv [ 0 ] , kv [ 1 ]
} else {
k , v = o , "true"
}
switch k {
case "laddr" :
db . Laddr = v
case "timeout" :
to , err := time . ParseDuration ( v )
if err != nil {
return nil , err
}
db . Timeout = to
default :
return nil , errors . New ( "Unknown option: " + k )
}
}
// Remove protocol part
pd = pd [ 1 : ]
}
// Parse database part of URI
dup := strings . SplitN ( pd [ 0 ] , "/" , 3 )
if len ( dup ) != 3 {
2023-01-24 19:57:33 +08:00
return nil , errors . New ( "wrong database part of URI" )
2022-12-26 17:45:21 +08:00
}
db . DbName = dup [ 0 ]
db . User = dup [ 1 ]
db . Passwd = dup [ 2 ]
return db , nil
}
type mysqlDriver struct {
}
func ( p * mysqlDriver ) Parse ( driverName , dataSourceName string ) ( * core . Uri , error ) {
dsnPattern := regexp . MustCompile (
` ^(?:(?P<user>.*?)(?::(?P<passwd>.*))?@)? ` + // [user[:password]@]
` (?:(?P<net>[^\(]*)(?:\((?P<addr>[^\)]*)\))?)? ` + // [net[(addr)]]
` \/(?P<dbname>.*?) ` + // /dbname
` (?:\?(?P<params>[^\?]*))?$ ` ) // [?param1=value1¶mN=valueN]
matches := dsnPattern . FindStringSubmatch ( dataSourceName )
names := dsnPattern . SubexpNames ( )
uri := & core . Uri { DbType : core . MYSQL }
for i , match := range matches {
switch names [ i ] {
case "dbname" :
uri . DbName = match
case "params" :
if len ( match ) > 0 {
kvs := strings . Split ( match , "&" )
for _ , kv := range kvs {
splits := strings . Split ( kv , "=" )
if len ( splits ) == 2 {
switch splits [ 0 ] {
case "charset" :
uri . Charset = splits [ 1 ]
}
}
}
}
}
}
return uri , nil
}