dameng.go 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  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 dameng
  15. import (
  16. "bytes"
  17. "fmt"
  18. "log"
  19. "reflect"
  20. "strconv"
  21. "strings"
  22. "runtime/debug"
  23. _ "gitee.com/chunanyong/dm"
  24. "yunion.io/x/pkg/errors"
  25. "yunion.io/x/pkg/gotypes"
  26. "yunion.io/x/pkg/tristate"
  27. "yunion.io/x/pkg/util/regutils"
  28. "yunion.io/x/pkg/utils"
  29. "yunion.io/x/sqlchemy"
  30. )
  31. func init() {
  32. sqlchemy.RegisterBackend(&SDamengBackend{})
  33. }
  34. type SDamengBackend struct {
  35. sqlchemy.SBaseBackend
  36. }
  37. func (dameng *SDamengBackend) Name() sqlchemy.DBBackendName {
  38. return sqlchemy.DamengBackend
  39. }
  40. // CanUpdate returns wether the backend supports update
  41. func (dameng *SDamengBackend) CanUpdate() bool {
  42. return true
  43. }
  44. // CanInsert returns wether the backend supports Insert
  45. func (dameng *SDamengBackend) CanInsert() bool {
  46. return true
  47. }
  48. func (dameng *SDamengBackend) CanSupportRowAffected() bool {
  49. return true
  50. }
  51. // CanInsertOrUpdate returns weather the backend supports InsertOrUpdate
  52. func (dameng *SDamengBackend) CanInsertOrUpdate() bool {
  53. return true
  54. }
  55. func (dameng *SDamengBackend) InsertOrUpdateSQLTemplate() string {
  56. // use PrepareInsertOrUpdateSQL instead
  57. return ""
  58. }
  59. func (dameng *SDamengBackend) DropIndexSQLTemplate() string {
  60. return `DROP INDEX "{{ .Index }}"" ON "{{ .Table }}"`
  61. }
  62. func (dameng *SDamengBackend) InsertSQLTemplate() string {
  63. return `INSERT INTO "{{ .Table }}" ({{ .Columns }}) VALUES ({{ .Values }})`
  64. }
  65. func (dameng *SDamengBackend) UpdateSQLTemplate() string {
  66. return `UPDATE "{{ .Table }}" SET {{ .Columns }} WHERE {{ .Conditions }}`
  67. }
  68. func (dameng *SDamengBackend) PrepareInsertOrUpdateSQL(ts sqlchemy.ITableSpec, insertColNames []string, insertFields []string, onPrimaryCols []string, updateSetCols []string, insertValues []interface{}, updateValues []interface{}) (string, []interface{}) {
  69. sqlTemp := `MERGE INTO "{{ .Table }}" T1 USING (SELECT {{ .SelectValues }} FROM DUAL) T2 ON ({{ .OnConditions }}) WHEN NOT MATCHED THEN INSERT({{ .Columns }}) VALUES ({{ .Values }}) WHEN MATCHED THEN UPDATE SET {{ .SetValues }}`
  70. selectValues := make([]string, 0, len(insertColNames))
  71. onConditions := make([]string, 0, len(onPrimaryCols))
  72. colNameMap := make(map[string]struct{})
  73. for i := range insertColNames {
  74. colName := strings.Trim(insertColNames[i], "'\"")
  75. colNameMap[colName] = struct{}{}
  76. selectValues = append(selectValues, fmt.Sprintf("%s AS \"%s\"", insertFields[i], colName))
  77. }
  78. for _, primary := range onPrimaryCols {
  79. colName := strings.Trim(primary, "'\"")
  80. if _, ok := colNameMap[colName]; !ok {
  81. debug.PrintStack()
  82. log.Fatalf("primary colume %s missing from insert columes for table %s", colName, ts.Name())
  83. }
  84. onConditions = append(onConditions, fmt.Sprintf("T1.%s=T2.%s", primary, primary))
  85. }
  86. for i := range updateSetCols {
  87. setCol := updateSetCols[i]
  88. equalPos := strings.Index(setCol, "=")
  89. key := strings.TrimSpace(setCol[0:equalPos])
  90. val := strings.TrimSpace(setCol[equalPos+1:])
  91. tkey := fmt.Sprintf("\"T1\".%s", key)
  92. val = strings.ReplaceAll(val, key, tkey)
  93. updateSetCols[i] = fmt.Sprintf("%s = %s", tkey, val)
  94. }
  95. values := make([]interface{}, 0, len(insertValues)*2+len(updateValues))
  96. values = append(values, insertValues...)
  97. values = append(values, insertValues...)
  98. values = append(values, updateValues...)
  99. sql := sqlchemy.TemplateEval(sqlTemp, struct {
  100. Table string
  101. SelectValues string
  102. OnConditions string
  103. Columns string
  104. Values string
  105. SetValues string
  106. }{
  107. Table: ts.Name(),
  108. SelectValues: strings.Join(selectValues, ", "),
  109. OnConditions: strings.Join(onConditions, " AND "),
  110. Columns: strings.Join(insertColNames, ", "),
  111. Values: strings.Join(insertFields, ", "),
  112. SetValues: strings.Join(updateSetCols, ", "),
  113. })
  114. return sql, values
  115. }
  116. func (dameng *SDamengBackend) GetTableSQL() string {
  117. return `SELECT table_name AS "name" FROM user_tables`
  118. }
  119. func (dameng *SDamengBackend) CurrentUTCTimeStampString() string {
  120. return "GETUTCDATE()"
  121. }
  122. func (dameng *SDamengBackend) CurrentTimeStampString() string {
  123. return "GETDATE()"
  124. }
  125. func (dameng *SDamengBackend) GetCreateSQLs(ts sqlchemy.ITableSpec) []string {
  126. cols := make([]string, 0)
  127. primaries := make([]string, 0)
  128. for _, c := range ts.Columns() {
  129. cols = append(cols, c.DefinitionString())
  130. if c.IsPrimary() {
  131. primaries = append(primaries, fmt.Sprintf(`"%s"`, c.Name()))
  132. }
  133. }
  134. if len(primaries) > 0 {
  135. cols = append(cols, fmt.Sprintf("NOT CLUSTER PRIMARY KEY (%s)", strings.Join(primaries, ", ")))
  136. }
  137. sqls := []string{
  138. fmt.Sprintf(`CREATE TABLE IF NOT EXISTS "%s" (%s);`, ts.Name(), strings.Join(cols, ", ")),
  139. }
  140. for _, idx := range ts.Indexes() {
  141. sqls = append(sqls, createIndexSQL(ts, idx))
  142. }
  143. return sqls
  144. }
  145. func (msyql *SDamengBackend) IsSupportIndexAndContraints() bool {
  146. return true
  147. }
  148. func (dameng *SDamengBackend) FetchTableColumnSpecs(ts sqlchemy.ITableSpec) ([]sqlchemy.IColumnSpec, error) {
  149. infos, err := fetchTableColInfo(ts)
  150. if err != nil {
  151. return nil, errors.Wrap(err, "fetchTableColInfo")
  152. }
  153. specs := make([]sqlchemy.IColumnSpec, 0)
  154. for _, info := range infos {
  155. specs = append(specs, info.toColumnSpec())
  156. }
  157. return specs, nil
  158. }
  159. func (dameng *SDamengBackend) FetchIndexesAndConstraints(ts sqlchemy.ITableSpec) ([]sqlchemy.STableIndex, []sqlchemy.STableConstraint, error) {
  160. indexes, err := fetchTableIndexes(ts)
  161. if err != nil {
  162. return nil, nil, errors.Wrap(err, "fetchTableIndexes")
  163. }
  164. retIdxes := make([]sqlchemy.STableIndex, 0)
  165. for k := range indexes {
  166. if indexes[k].isPrimary {
  167. continue
  168. }
  169. retIdxes = append(retIdxes, sqlchemy.NewTableIndex(ts, indexes[k].indexName, indexes[k].colnames, false))
  170. }
  171. return retIdxes, nil, nil
  172. }
  173. func getTextSqlType(tagmap map[string]string) (string, map[string]string) {
  174. var width int
  175. var sqltype string
  176. tagmap, widthStr, _ := utils.TagPop(tagmap, sqlchemy.TAG_WIDTH)
  177. // widthStr := tagmap[sqlchemy.TAG_WIDTH]
  178. if len(widthStr) > 0 && regutils.MatchInteger(widthStr) {
  179. width, _ = strconv.Atoi(widthStr)
  180. }
  181. if width == 0 || width > 975 {
  182. sqltype = "TEXT"
  183. } else {
  184. tagmap[sqlchemy.TAG_WIDTH] = widthStr
  185. sqltype = "VARCHAR"
  186. }
  187. return sqltype, tagmap
  188. }
  189. func (dameng *SDamengBackend) GetColumnSpecByFieldType(table *sqlchemy.STableSpec, fieldType reflect.Type, fieldname string, tagmap map[string]string, isPointer bool) sqlchemy.IColumnSpec {
  190. switch fieldType {
  191. case tristate.TriStateType:
  192. col := NewTristateColumn(table.Name(), fieldname, tagmap, isPointer)
  193. return &col
  194. case gotypes.TimeType:
  195. col := NewDateTimeColumn(fieldname, tagmap, isPointer)
  196. return &col
  197. }
  198. switch fieldType.Kind() {
  199. case reflect.String:
  200. sqltype, tagmap := getTextSqlType(tagmap)
  201. col := NewTextColumn(fieldname, sqltype, tagmap, isPointer)
  202. return &col
  203. case reflect.Int, reflect.Int32:
  204. col := NewIntegerColumn(fieldname, "INT", tagmap, isPointer)
  205. return &col
  206. case reflect.Int8:
  207. col := NewIntegerColumn(fieldname, "TINYINT", tagmap, isPointer)
  208. return &col
  209. case reflect.Int16:
  210. col := NewIntegerColumn(fieldname, "SMALLINT", tagmap, isPointer)
  211. return &col
  212. case reflect.Int64:
  213. col := NewIntegerColumn(fieldname, "BIGINT", tagmap, isPointer)
  214. return &col
  215. case reflect.Uint, reflect.Uint32:
  216. col := NewIntegerColumn(fieldname, "INT", tagmap, isPointer)
  217. return &col
  218. case reflect.Uint8:
  219. col := NewIntegerColumn(fieldname, "TINYINT", tagmap, isPointer)
  220. return &col
  221. case reflect.Uint16:
  222. col := NewIntegerColumn(fieldname, "SMALLINT", tagmap, isPointer)
  223. return &col
  224. case reflect.Uint64:
  225. col := NewIntegerColumn(fieldname, "BIGINT", tagmap, isPointer)
  226. return &col
  227. case reflect.Bool:
  228. col := NewBooleanColumn(fieldname, tagmap, isPointer)
  229. return &col
  230. case reflect.Float32, reflect.Float64:
  231. if _, ok := tagmap[sqlchemy.TAG_WIDTH]; ok {
  232. col := NewDecimalColumn(fieldname, tagmap, isPointer)
  233. return &col
  234. }
  235. colType := "FLOAT"
  236. if fieldType == gotypes.Float64Type {
  237. colType = "DOUBLE"
  238. }
  239. col := NewFloatColumn(fieldname, colType, tagmap, isPointer)
  240. return &col
  241. case reflect.Map, reflect.Slice:
  242. sqltype, tagmap := getTextSqlType(tagmap)
  243. col := NewCompoundColumn(fieldname, sqltype, tagmap, isPointer)
  244. return &col
  245. }
  246. if fieldType.Implements(gotypes.ISerializableType) {
  247. sqltype, tagmap := getTextSqlType(tagmap)
  248. col := NewCompoundColumn(fieldname, sqltype, tagmap, isPointer)
  249. return &col
  250. }
  251. return nil
  252. }
  253. func (dameng *SDamengBackend) QuoteChar() string {
  254. return "\""
  255. }
  256. func (dameng *SDamengBackend) RegexpWhereClause(cond *sqlchemy.SRegexpConition) string {
  257. var buf bytes.Buffer
  258. buf.WriteString("REGEXP_LIKE(")
  259. buf.WriteString(cond.GetLeft().Reference())
  260. buf.WriteString(", ")
  261. buf.WriteString(sqlchemy.VarConditionWhereClause(cond.GetRight()))
  262. buf.WriteString(")")
  263. return buf.String()
  264. }