mysql.go 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. // Copyright 2019 Yunion
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package mysql
  15. import (
  16. "fmt"
  17. "reflect"
  18. "strconv"
  19. "strings"
  20. _ "github.com/go-sql-driver/mysql"
  21. "yunion.io/x/pkg/gotypes"
  22. "yunion.io/x/pkg/tristate"
  23. "yunion.io/x/pkg/util/regutils"
  24. "yunion.io/x/sqlchemy"
  25. )
  26. func init() {
  27. sqlchemy.RegisterBackend(&SMySQLBackend{})
  28. }
  29. type SMySQLBackend struct {
  30. sqlchemy.SBaseBackend
  31. }
  32. func (mysql *SMySQLBackend) Name() sqlchemy.DBBackendName {
  33. return sqlchemy.MySQLBackend
  34. }
  35. // CanUpdate returns wether the backend supports update
  36. func (mysql *SMySQLBackend) CanUpdate() bool {
  37. return true
  38. }
  39. // CanInsert returns wether the backend supports Insert
  40. func (mysql *SMySQLBackend) CanInsert() bool {
  41. return true
  42. }
  43. // CanInsertOrUpdate returns weather the backend supports InsertOrUpdate
  44. func (mysql *SMySQLBackend) CanInsertOrUpdate() bool {
  45. return true
  46. }
  47. func (mysql *SMySQLBackend) InsertOrUpdateSQLTemplate() string {
  48. return "INSERT INTO `{{ .Table }}` ({{ .Columns }}) VALUES ({{ .Values }}) ON DUPLICATE KEY UPDATE {{ .SetValues }}"
  49. }
  50. func (mysql *SMySQLBackend) CurrentUTCTimeStampString() string {
  51. return "UTC_TIMESTAMP()"
  52. }
  53. func (mysql *SMySQLBackend) CurrentTimeStampString() string {
  54. return "NOW()"
  55. }
  56. func (mysql *SMySQLBackend) GetCreateSQLs(ts sqlchemy.ITableSpec) []string {
  57. cols := make([]string, 0)
  58. primaries := make([]string, 0)
  59. autoInc := ""
  60. for _, c := range ts.Columns() {
  61. cols = append(cols, c.DefinitionString())
  62. if c.IsPrimary() {
  63. primaries = append(primaries, fmt.Sprintf("`%s`", c.Name()))
  64. if intC, ok := c.(*SIntegerColumn); ok && intC.autoIncrementOffset > 0 {
  65. autoInc = fmt.Sprintf(" AUTO_INCREMENT=%d", intC.autoIncrementOffset)
  66. }
  67. }
  68. }
  69. if len(primaries) > 0 {
  70. cols = append(cols, fmt.Sprintf("PRIMARY KEY (%s)", strings.Join(primaries, ", ")))
  71. }
  72. sqls := []string{
  73. fmt.Sprintf("CREATE TABLE IF NOT EXISTS `%s` (\n%s\n) ENGINE=InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci%s", ts.Name(), strings.Join(cols, ",\n"), autoInc),
  74. }
  75. for _, idx := range ts.Indexes() {
  76. sqls = append(sqls, createIndexSQL(ts, idx))
  77. }
  78. return sqls
  79. }
  80. func (msyql *SMySQLBackend) IsSupportIndexAndContraints() bool {
  81. return true
  82. }
  83. func (mysql *SMySQLBackend) FetchTableColumnSpecs(ts sqlchemy.ITableSpec) ([]sqlchemy.IColumnSpec, error) {
  84. sql := fmt.Sprintf("SHOW FULL COLUMNS IN `%s`", ts.Name())
  85. query := ts.Database().NewRawQuery(sql, "field", "type", "collation", "null", "key", "default", "extra", "privileges", "comment")
  86. infos := make([]sSqlColumnInfo, 0)
  87. err := query.All(&infos)
  88. if err != nil {
  89. return nil, err
  90. }
  91. specs := make([]sqlchemy.IColumnSpec, 0)
  92. for _, info := range infos {
  93. specs = append(specs, info.toColumnSpec())
  94. }
  95. return specs, nil
  96. }
  97. func (mysql *SMySQLBackend) FetchIndexesAndConstraints(ts sqlchemy.ITableSpec) ([]sqlchemy.STableIndex, []sqlchemy.STableConstraint, error) {
  98. sql := fmt.Sprintf("SHOW CREATE TABLE `%s`", ts.Name())
  99. query := ts.Database().NewRawQuery(sql, "table", "create table")
  100. row := query.Row()
  101. var name, defStr string
  102. err := row.Scan(&name, &defStr)
  103. if err != nil {
  104. if isMysqlError(err, mysqlErrorTableNotExist) {
  105. err = sqlchemy.ErrTableNotExists
  106. }
  107. return nil, nil, err
  108. }
  109. indexes := parseIndexes(ts, defStr)
  110. constraints := parseConstraints(defStr)
  111. return indexes, constraints, nil
  112. }
  113. func getTextSqlType(tagmap map[string]string) string {
  114. var width int
  115. var sqltype string
  116. widthStr, _ := tagmap[sqlchemy.TAG_WIDTH]
  117. if len(widthStr) > 0 && regutils.MatchInteger(widthStr) {
  118. width, _ = strconv.Atoi(widthStr)
  119. }
  120. txtLen, _ := tagmap[sqlchemy.TAG_TEXT_LENGTH]
  121. if width == 0 {
  122. switch strings.ToLower(txtLen) {
  123. case "medium":
  124. sqltype = "MEDIUMTEXT"
  125. case "long":
  126. sqltype = "LONGTEXT"
  127. default:
  128. sqltype = "TEXT"
  129. }
  130. } else {
  131. sqltype = "VARCHAR"
  132. }
  133. return sqltype
  134. }
  135. func (mysql *SMySQLBackend) GetColumnSpecByFieldType(table *sqlchemy.STableSpec, fieldType reflect.Type, fieldname string, tagmap map[string]string, isPointer bool) sqlchemy.IColumnSpec {
  136. switch fieldType {
  137. case tristate.TriStateType:
  138. tagmap[sqlchemy.TAG_WIDTH] = "1"
  139. col := NewTristateColumn(table.Name(), fieldname, tagmap, isPointer)
  140. return &col
  141. case gotypes.TimeType:
  142. col := NewDateTimeColumn(fieldname, tagmap, isPointer)
  143. return &col
  144. }
  145. switch fieldType.Kind() {
  146. case reflect.String:
  147. col := NewTextColumn(fieldname, getTextSqlType(tagmap), tagmap, isPointer)
  148. return &col
  149. case reflect.Int, reflect.Int32:
  150. tagmap[sqlchemy.TAG_WIDTH] = intWidthString("INT")
  151. col := NewIntegerColumn(fieldname, "INT", false, tagmap, isPointer)
  152. return &col
  153. case reflect.Int8:
  154. tagmap[sqlchemy.TAG_WIDTH] = intWidthString("TINYINT")
  155. col := NewIntegerColumn(fieldname, "TINYINT", false, tagmap, isPointer)
  156. return &col
  157. case reflect.Int16:
  158. tagmap[sqlchemy.TAG_WIDTH] = intWidthString("SMALLINT")
  159. col := NewIntegerColumn(fieldname, "SMALLINT", false, tagmap, isPointer)
  160. return &col
  161. case reflect.Int64:
  162. tagmap[sqlchemy.TAG_WIDTH] = intWidthString("BIGINT")
  163. col := NewIntegerColumn(fieldname, "BIGINT", false, tagmap, isPointer)
  164. return &col
  165. case reflect.Uint, reflect.Uint32:
  166. tagmap[sqlchemy.TAG_WIDTH] = uintWidthString("INT")
  167. col := NewIntegerColumn(fieldname, "INT", true, tagmap, isPointer)
  168. return &col
  169. case reflect.Uint8:
  170. tagmap[sqlchemy.TAG_WIDTH] = uintWidthString("TINYINT")
  171. col := NewIntegerColumn(fieldname, "TINYINT", true, tagmap, isPointer)
  172. return &col
  173. case reflect.Uint16:
  174. tagmap[sqlchemy.TAG_WIDTH] = uintWidthString("SMALLINT")
  175. col := NewIntegerColumn(fieldname, "SMALLINT", true, tagmap, isPointer)
  176. return &col
  177. case reflect.Uint64:
  178. tagmap[sqlchemy.TAG_WIDTH] = uintWidthString("BIGINT")
  179. col := NewIntegerColumn(fieldname, "BIGINT", true, tagmap, isPointer)
  180. return &col
  181. case reflect.Bool:
  182. tagmap[sqlchemy.TAG_WIDTH] = "1"
  183. col := NewBooleanColumn(fieldname, tagmap, isPointer)
  184. return &col
  185. case reflect.Float32, reflect.Float64:
  186. if _, ok := tagmap[sqlchemy.TAG_WIDTH]; ok {
  187. col := NewDecimalColumn(fieldname, tagmap, isPointer)
  188. return &col
  189. }
  190. colType := "FLOAT"
  191. if fieldType == gotypes.Float64Type {
  192. colType = "DOUBLE"
  193. }
  194. col := NewFloatColumn(fieldname, colType, tagmap, isPointer)
  195. return &col
  196. case reflect.Map, reflect.Slice:
  197. col := NewCompoundColumn(fieldname, getTextSqlType(tagmap), tagmap, isPointer)
  198. return &col
  199. }
  200. if fieldType.Implements(gotypes.ISerializableType) {
  201. col := NewCompoundColumn(fieldname, getTextSqlType(tagmap), tagmap, isPointer)
  202. return &col
  203. }
  204. return nil
  205. }