parser.go 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375
  1. package parser
  2. import (
  3. "fmt"
  4. "path/filepath"
  5. "sort"
  6. "strings"
  7. "github.com/zeromicro/ddl-parser/parser"
  8. "github.com/zeromicro/go-zero/core/collection"
  9. "github.com/zeromicro/go-zero/tools/goctl/model/sql/converter"
  10. "github.com/zeromicro/go-zero/tools/goctl/model/sql/model"
  11. "github.com/zeromicro/go-zero/tools/goctl/model/sql/util"
  12. "github.com/zeromicro/go-zero/tools/goctl/util/console"
  13. "github.com/zeromicro/go-zero/tools/goctl/util/stringx"
  14. )
  15. const timeImport = "time.Time"
  16. type (
  17. // Table describes a mysql table
  18. Table struct {
  19. Name stringx.String
  20. Db stringx.String
  21. PrimaryKey Primary
  22. UniqueIndex map[string][]*Field
  23. Fields []*Field
  24. ContainsPQ bool
  25. }
  26. // Primary describes a primary key
  27. Primary struct {
  28. Field
  29. AutoIncrement bool
  30. }
  31. // Field describes a table field
  32. Field struct {
  33. NameOriginal string
  34. Name stringx.String
  35. DataType string
  36. Comment string
  37. SeqInIndex int
  38. OrdinalPosition int
  39. ContainsPQ bool
  40. }
  41. // KeyType types alias of int
  42. KeyType int
  43. )
  44. func parseNameOriginal(ts []*parser.Table) (nameOriginals [][]string) {
  45. var columns []string
  46. for _, t := range ts {
  47. columns = []string{}
  48. for _, c := range t.Columns {
  49. columns = append(columns, c.Name)
  50. }
  51. nameOriginals = append(nameOriginals, columns)
  52. }
  53. return
  54. }
  55. // Parse parses ddl into golang structure
  56. func Parse(filename, database string, strict bool) ([]*Table, error) {
  57. p := parser.NewParser()
  58. tables, err := p.From(filename)
  59. if err != nil {
  60. return nil, err
  61. }
  62. nameOriginals := parseNameOriginal(tables)
  63. indexNameGen := func(column ...string) string {
  64. return strings.Join(column, "_")
  65. }
  66. prefix := filepath.Base(filename)
  67. var list []*Table
  68. for indexTable, e := range tables {
  69. var (
  70. primaryColumn string
  71. primaryColumnSet = collection.NewSet()
  72. uniqueKeyMap = make(map[string][]string)
  73. // Unused local variable
  74. // normalKeyMap = make(map[string][]string)
  75. columns = e.Columns
  76. )
  77. for _, column := range columns {
  78. if column.Constraint != nil {
  79. if column.Constraint.Primary {
  80. primaryColumnSet.AddStr(column.Name)
  81. }
  82. if column.Constraint.Unique {
  83. indexName := indexNameGen(column.Name, "unique")
  84. uniqueKeyMap[indexName] = []string{column.Name}
  85. }
  86. if column.Constraint.Key {
  87. indexName := indexNameGen(column.Name, "idx")
  88. uniqueKeyMap[indexName] = []string{column.Name}
  89. }
  90. }
  91. }
  92. for _, e := range e.Constraints {
  93. if len(e.ColumnPrimaryKey) > 1 {
  94. return nil, fmt.Errorf("%s: unexpected join primary key", prefix)
  95. }
  96. if len(e.ColumnPrimaryKey) == 1 {
  97. primaryColumn = e.ColumnPrimaryKey[0]
  98. primaryColumnSet.AddStr(e.ColumnPrimaryKey[0])
  99. }
  100. if len(e.ColumnUniqueKey) > 0 {
  101. list := append([]string(nil), e.ColumnUniqueKey...)
  102. list = append(list, "unique")
  103. indexName := indexNameGen(list...)
  104. uniqueKeyMap[indexName] = e.ColumnUniqueKey
  105. }
  106. }
  107. if primaryColumnSet.Count() > 1 {
  108. return nil, fmt.Errorf("%s: unexpected join primary key", prefix)
  109. }
  110. delete(uniqueKeyMap, indexNameGen(primaryColumn, "idx"))
  111. delete(uniqueKeyMap, indexNameGen(primaryColumn, "unique"))
  112. primaryKey, fieldM, err := convertColumns(columns, primaryColumn, strict)
  113. if err != nil {
  114. return nil, err
  115. }
  116. var fields []*Field
  117. // sort
  118. for indexColumn, c := range columns {
  119. field, ok := fieldM[c.Name]
  120. if ok {
  121. field.NameOriginal = nameOriginals[indexTable][indexColumn]
  122. fields = append(fields, field)
  123. }
  124. }
  125. uniqueIndex := make(map[string][]*Field)
  126. for indexName, each := range uniqueKeyMap {
  127. for _, columnName := range each {
  128. // Prevent a crash if there is a unique key constraint with a nil field.
  129. if fieldM[columnName] == nil {
  130. return nil, fmt.Errorf("table %s: unique key with error column name[%s]", e.Name, columnName)
  131. }
  132. uniqueIndex[indexName] = append(uniqueIndex[indexName], fieldM[columnName])
  133. }
  134. }
  135. checkDuplicateUniqueIndex(uniqueIndex, e.Name)
  136. list = append(list, &Table{
  137. Name: stringx.From(e.Name),
  138. Db: stringx.From(database),
  139. PrimaryKey: primaryKey,
  140. UniqueIndex: uniqueIndex,
  141. Fields: fields,
  142. })
  143. }
  144. return list, nil
  145. }
  146. func checkDuplicateUniqueIndex(uniqueIndex map[string][]*Field, tableName string) {
  147. log := console.NewColorConsole()
  148. uniqueSet := collection.NewSet()
  149. for k, i := range uniqueIndex {
  150. var list []string
  151. for _, e := range i {
  152. list = append(list, e.Name.Source())
  153. }
  154. joinRet := strings.Join(list, ",")
  155. if uniqueSet.Contains(joinRet) {
  156. log.Warning("[checkDuplicateUniqueIndex]: table %s: duplicate unique index %s", tableName, joinRet)
  157. delete(uniqueIndex, k)
  158. continue
  159. }
  160. uniqueSet.AddStr(joinRet)
  161. }
  162. }
  163. func convertColumns(columns []*parser.Column, primaryColumn string, strict bool) (Primary, map[string]*Field, error) {
  164. var (
  165. primaryKey Primary
  166. fieldM = make(map[string]*Field)
  167. log = console.NewColorConsole()
  168. )
  169. for _, column := range columns {
  170. if column == nil {
  171. continue
  172. }
  173. var (
  174. comment string
  175. isDefaultNull bool
  176. )
  177. if column.Constraint != nil {
  178. comment = column.Constraint.Comment
  179. isDefaultNull = !column.Constraint.NotNull
  180. if !column.Constraint.NotNull && column.Constraint.HasDefaultValue {
  181. isDefaultNull = false
  182. }
  183. if column.Name == primaryColumn {
  184. isDefaultNull = false
  185. }
  186. }
  187. dataType, err := converter.ConvertDataType(column.DataType.Type(), isDefaultNull, column.DataType.Unsigned(), strict)
  188. if err != nil {
  189. return Primary{}, nil, err
  190. }
  191. if column.Constraint != nil {
  192. if column.Name == primaryColumn {
  193. if !column.Constraint.AutoIncrement && dataType == "int64" {
  194. log.Warning("[convertColumns]: The primary key %q is recommended to add constraint `AUTO_INCREMENT`", column.Name)
  195. }
  196. } else if column.Constraint.NotNull && !column.Constraint.HasDefaultValue {
  197. log.Warning("[convertColumns]: The column %q is recommended to add constraint `DEFAULT`", column.Name)
  198. }
  199. }
  200. var field Field
  201. field.Name = stringx.From(column.Name)
  202. field.DataType = dataType
  203. field.Comment = util.TrimNewLine(comment)
  204. if field.Name.Source() == primaryColumn {
  205. primaryKey = Primary{
  206. Field: field,
  207. }
  208. if column.Constraint != nil {
  209. primaryKey.AutoIncrement = column.Constraint.AutoIncrement
  210. }
  211. }
  212. fieldM[field.Name.Source()] = &field
  213. }
  214. return primaryKey, fieldM, nil
  215. }
  216. // ContainsTime returns true if contains golang type time.Time
  217. func (t *Table) ContainsTime() bool {
  218. for _, item := range t.Fields {
  219. if item.DataType == timeImport {
  220. return true
  221. }
  222. }
  223. return false
  224. }
  225. // ConvertDataType converts mysql data type into golang data type
  226. func ConvertDataType(table *model.Table, strict bool) (*Table, error) {
  227. isPrimaryDefaultNull := table.PrimaryKey.ColumnDefault == nil && table.PrimaryKey.IsNullAble == "YES"
  228. isPrimaryUnsigned := strings.Contains(table.PrimaryKey.DbColumn.ColumnType, "unsigned")
  229. primaryDataType, containsPQ, err := converter.ConvertStringDataType(table.PrimaryKey.DataType, isPrimaryDefaultNull, isPrimaryUnsigned, strict)
  230. if err != nil {
  231. return nil, err
  232. }
  233. var reply Table
  234. reply.ContainsPQ = containsPQ
  235. reply.UniqueIndex = map[string][]*Field{}
  236. reply.Name = stringx.From(table.Table)
  237. reply.Db = stringx.From(table.Db)
  238. seqInIndex := 0
  239. if table.PrimaryKey.Index != nil {
  240. seqInIndex = table.PrimaryKey.Index.SeqInIndex
  241. }
  242. reply.PrimaryKey = Primary{
  243. Field: Field{
  244. Name: stringx.From(table.PrimaryKey.Name),
  245. DataType: primaryDataType,
  246. Comment: table.PrimaryKey.Comment,
  247. SeqInIndex: seqInIndex,
  248. OrdinalPosition: table.PrimaryKey.OrdinalPosition,
  249. },
  250. AutoIncrement: strings.Contains(table.PrimaryKey.Extra, "auto_increment"),
  251. }
  252. fieldM, err := getTableFields(table, strict)
  253. if err != nil {
  254. return nil, err
  255. }
  256. for _, each := range fieldM {
  257. if each.ContainsPQ {
  258. reply.ContainsPQ = true
  259. }
  260. reply.Fields = append(reply.Fields, each)
  261. }
  262. sort.Slice(reply.Fields, func(i, j int) bool {
  263. return reply.Fields[i].OrdinalPosition < reply.Fields[j].OrdinalPosition
  264. })
  265. uniqueIndexSet := collection.NewSet()
  266. log := console.NewColorConsole()
  267. for indexName, each := range table.UniqueIndex {
  268. sort.Slice(each, func(i, j int) bool {
  269. if each[i].Index != nil {
  270. return each[i].Index.SeqInIndex < each[j].Index.SeqInIndex
  271. }
  272. return false
  273. })
  274. if len(each) == 1 {
  275. one := each[0]
  276. if one.Name == table.PrimaryKey.Name {
  277. log.Warning("[ConvertDataType]: table %q, duplicate unique index with primary key: %q", table.Table, one.Name)
  278. continue
  279. }
  280. }
  281. var list []*Field
  282. var uniqueJoin []string
  283. for _, c := range each {
  284. list = append(list, fieldM[c.Name])
  285. uniqueJoin = append(uniqueJoin, c.Name)
  286. }
  287. uniqueKey := strings.Join(uniqueJoin, ",")
  288. if uniqueIndexSet.Contains(uniqueKey) {
  289. log.Warning("[ConvertDataType]: table %q, duplicate unique index %q", table.Table, uniqueKey)
  290. continue
  291. }
  292. uniqueIndexSet.AddStr(uniqueKey)
  293. reply.UniqueIndex[indexName] = list
  294. }
  295. return &reply, nil
  296. }
  297. func getTableFields(table *model.Table, strict bool) (map[string]*Field, error) {
  298. fieldM := make(map[string]*Field)
  299. for _, each := range table.Columns {
  300. isDefaultNull := each.ColumnDefault == nil && each.IsNullAble == "YES"
  301. isPrimaryUnsigned := strings.Contains(each.ColumnType, "unsigned")
  302. dt, containsPQ, err := converter.ConvertStringDataType(each.DataType, isDefaultNull, isPrimaryUnsigned, strict)
  303. if err != nil {
  304. return nil, err
  305. }
  306. columnSeqInIndex := 0
  307. if each.Index != nil {
  308. columnSeqInIndex = each.Index.SeqInIndex
  309. }
  310. field := &Field{
  311. NameOriginal: each.Name,
  312. Name: stringx.From(each.Name),
  313. DataType: dt,
  314. Comment: each.Comment,
  315. SeqInIndex: columnSeqInIndex,
  316. OrdinalPosition: each.OrdinalPosition,
  317. ContainsPQ: containsPQ,
  318. }
  319. fieldM[each.Name] = field
  320. }
  321. return fieldM, nil
  322. }