columninfo.go 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  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 clickhouse
  15. import (
  16. "regexp"
  17. "sort"
  18. "strings"
  19. "yunion.io/x/log"
  20. "yunion.io/x/sqlchemy"
  21. )
  22. // name type default_type default_expression comment codec_expression ttl_expression
  23. type sSqlColumnInfo struct {
  24. Name string `json:"name"`
  25. Type string `json:"type"`
  26. DefaultType string `json:"default_type"`
  27. DefaultExpression string `json:"default_expression"`
  28. Comment string `json:"comment"`
  29. CodecExpression string `json:"codec_expression"`
  30. TtlExpression string `json:"ttl_expression"`
  31. }
  32. func (info *sSqlColumnInfo) isNullable() bool {
  33. if strings.HasPrefix(info.Type, "Nullable(") {
  34. return true
  35. } else {
  36. return false
  37. }
  38. }
  39. func (info *sSqlColumnInfo) getType() string {
  40. if strings.HasPrefix(info.Type, "Nullable(") {
  41. return info.Type[len("Nullable(") : len(info.Type)-1]
  42. } else {
  43. return info.Type
  44. }
  45. }
  46. func (info *sSqlColumnInfo) getDefault() string {
  47. if info.DefaultType == "DEFAULT" {
  48. if strings.HasPrefix(info.DefaultExpression, "CAST(") {
  49. defaultVals := strings.Split(info.DefaultExpression[len("CAST("):len(info.DefaultExpression)-1], ",")
  50. defaultVal := defaultVals[0]
  51. typeStr := info.getType()
  52. if typeStr == "String" || strings.HasPrefix(typeStr, "FixString") {
  53. defaultVal = defaultVal[1 : len(defaultVal)-1]
  54. }
  55. return defaultVal
  56. } else {
  57. return info.DefaultExpression
  58. }
  59. }
  60. return ""
  61. }
  62. func (info *sSqlColumnInfo) getTagmap() map[string]string {
  63. tagmap := make(map[string]string)
  64. if info.isNullable() {
  65. tagmap[sqlchemy.TAG_NULLABLE] = "true"
  66. } else {
  67. tagmap[sqlchemy.TAG_NULLABLE] = "false"
  68. }
  69. defVal := info.getDefault()
  70. if len(defVal) > 0 {
  71. if info.getType() == "String" && defVal[0] == '\'' {
  72. defVal = defVal[1 : len(defVal)-1]
  73. }
  74. tagmap[sqlchemy.TAG_DEFAULT] = defVal
  75. }
  76. sqlType := info.getType()
  77. if strings.HasPrefix(sqlType, "Decimal") {
  78. re := regexp.MustCompile(`Decimal\((\d+),\s*(\d+)\)`)
  79. match := re.FindStringSubmatch(sqlType)
  80. if len(match) == 3 {
  81. tagmap[sqlchemy.TAG_WIDTH], tagmap[sqlchemy.TAG_PRECISION] = match[1], match[2]
  82. }
  83. }
  84. return tagmap
  85. }
  86. func (info *sSqlColumnInfo) toColumnSpec() sqlchemy.IColumnSpec {
  87. sqlType := info.getType()
  88. switch sqlType {
  89. case "String":
  90. c := NewTextColumn(info.Name, sqlType, info.getTagmap(), false)
  91. return &c
  92. case "Int8", "Int16", "Int32", "Int64", "UInt8", "UInt16", "UInt32", "UInt64":
  93. c := NewIntegerColumn(info.Name, sqlType, info.getTagmap(), false)
  94. return &c
  95. case "Float32", "Float64":
  96. c := NewFloatColumn(info.Name, sqlType, info.getTagmap(), false)
  97. return &c
  98. case "DateTime", "DateTime('UTC')":
  99. c := NewDateTimeColumn(info.Name, info.getTagmap(), false)
  100. return &c
  101. default:
  102. if strings.HasPrefix(sqlType, "Decimal") {
  103. c := NewDecimalColumn(info.Name, info.getTagmap(), false)
  104. return &c
  105. } else if strings.HasPrefix(sqlType, "FixString") {
  106. c := NewTextColumn(info.Name, "FixString", info.getTagmap(), false)
  107. return &c
  108. }
  109. log.Errorf("unsupported type %s", info.Type)
  110. }
  111. return nil
  112. }
  113. const (
  114. primaryKeyPrefix = "PRIMARY KEY "
  115. orderByPrefix = "ORDER BY "
  116. partitionByPrefix = "PARTITION BY "
  117. setttingsPrefix = "SETTINGS"
  118. ttlPrefix = "TTL "
  119. paramPattern = `(\w+|\([\w,\s]+\))`
  120. primaryKeyPattern = primaryKeyPrefix + paramPattern
  121. orderByPattern = orderByPrefix + paramPattern
  122. )
  123. var (
  124. primaryKeyRegexp = regexp.MustCompile(primaryKeyPattern)
  125. orderByRegexp = regexp.MustCompile(orderByPattern)
  126. )
  127. func parseKeys(keyStr string) []string {
  128. keyStr = strings.TrimSpace(keyStr)
  129. if keyStr[0] == '(' {
  130. keyStr = keyStr[1 : len(keyStr)-1]
  131. }
  132. ret := make([]string, 0)
  133. for _, key := range strings.Split(keyStr, ",") {
  134. key = strings.TrimSpace(key)
  135. ret = append(ret, key)
  136. }
  137. sort.Strings(ret)
  138. return ret
  139. }
  140. func findSegment(sqlStr string, prefix string) string {
  141. partIdx := strings.Index(sqlStr, prefix)
  142. if partIdx > 0 {
  143. partIdx += len(prefix)
  144. nextIdx := -1
  145. for _, pattern := range []string{partitionByPrefix, primaryKeyPrefix, orderByPrefix, setttingsPrefix, ttlPrefix} {
  146. idx := strings.Index(sqlStr[partIdx:], pattern)
  147. if idx > 0 && (nextIdx < 0 || nextIdx > idx) {
  148. nextIdx = idx
  149. }
  150. }
  151. if nextIdx < 0 {
  152. return strings.TrimSpace(sqlStr[partIdx:])
  153. } else {
  154. return strings.TrimSpace(sqlStr[partIdx:][:nextIdx])
  155. }
  156. }
  157. return ""
  158. }
  159. func trimPartition(partStr string) string {
  160. for {
  161. partStr = strings.TrimSpace(partStr)
  162. if len(partStr) > 0 && partStr[0] == '(' {
  163. partStr = partStr[1 : len(partStr)-1]
  164. } else {
  165. break
  166. }
  167. }
  168. partStr = strings.ReplaceAll(partStr, " ", "")
  169. return partStr
  170. }
  171. func parsePartitions(partStr string) []string {
  172. partStr = trimPartition(partStr)
  173. parts := strings.Split(partStr, ",")
  174. sort.Strings(parts)
  175. return parts
  176. }
  177. func parseCreateTable(sqlStr string) (primaries []string, orderbys []string, partitions []string, ttl string) {
  178. matches := primaryKeyRegexp.FindAllStringSubmatch(sqlStr, -1)
  179. if len(matches) > 0 {
  180. primaries = parseKeys(matches[0][1])
  181. }
  182. matches = orderByRegexp.FindAllStringSubmatch(sqlStr, -1)
  183. if len(matches) > 0 {
  184. orderbys = parseKeys(matches[0][1])
  185. }
  186. partitionStr := findSegment(sqlStr, partitionByPrefix)
  187. partitions = parsePartitions(partitionStr)
  188. ttl = findSegment(sqlStr, ttlPrefix)
  189. return
  190. }