parser.go 9.7 KB

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