ddlmod.go 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. package sqlite
  2. import (
  3. "database/sql"
  4. "errors"
  5. "fmt"
  6. "regexp"
  7. "strconv"
  8. "strings"
  9. "gorm.io/gorm/migrator"
  10. )
  11. var (
  12. sqliteSeparator = "`|\"|'|\t"
  13. uniqueRegexp = regexp.MustCompile(fmt.Sprintf(`^CONSTRAINT [%v]?[\w-]+[%v]? UNIQUE (.*)$`, sqliteSeparator, sqliteSeparator))
  14. indexRegexp = regexp.MustCompile(fmt.Sprintf(`(?is)CREATE(?: UNIQUE)? INDEX [%v]?[\w\d-]+[%v]?(?s:.*?)ON (.*)$`, sqliteSeparator, sqliteSeparator))
  15. tableRegexp = regexp.MustCompile(fmt.Sprintf(`(?is)(CREATE TABLE [%v]?[\w\d-]+[%v]?)(?:\s*\((.*)\))?`, sqliteSeparator, sqliteSeparator))
  16. separatorRegexp = regexp.MustCompile(fmt.Sprintf("[%v]", sqliteSeparator))
  17. columnRegexp = regexp.MustCompile(fmt.Sprintf(`^[%v]?([\w\d]+)[%v]?\s+([\w\(\)\d]+)(.*)$`, sqliteSeparator, sqliteSeparator))
  18. defaultValueRegexp = regexp.MustCompile(`(?i) DEFAULT \(?(.+)?\)?( |COLLATE|GENERATED|$)`)
  19. regRealDataType = regexp.MustCompile(`[^\d](\d+)[^\d]?`)
  20. )
  21. type ddl struct {
  22. head string
  23. fields []string
  24. columns []migrator.ColumnType
  25. }
  26. func parseDDL(strs ...string) (*ddl, error) {
  27. var result ddl
  28. for _, str := range strs {
  29. if sections := tableRegexp.FindStringSubmatch(str); len(sections) > 0 {
  30. var (
  31. ddlBody = sections[2]
  32. ddlBodyRunes = []rune(ddlBody)
  33. bracketLevel int
  34. quote rune
  35. buf string
  36. )
  37. ddlBodyRunesLen := len(ddlBodyRunes)
  38. result.head = sections[1]
  39. for idx := 0; idx < ddlBodyRunesLen; idx++ {
  40. var (
  41. next rune = 0
  42. c = ddlBodyRunes[idx]
  43. )
  44. if idx+1 < ddlBodyRunesLen {
  45. next = ddlBodyRunes[idx+1]
  46. }
  47. if sc := string(c); separatorRegexp.MatchString(sc) {
  48. if c == next {
  49. buf += sc // Skip escaped quote
  50. idx++
  51. } else if quote > 0 {
  52. quote = 0
  53. } else {
  54. quote = c
  55. }
  56. } else if quote == 0 {
  57. if c == '(' {
  58. bracketLevel++
  59. } else if c == ')' {
  60. bracketLevel--
  61. } else if bracketLevel == 0 {
  62. if c == ',' {
  63. result.fields = append(result.fields, strings.TrimSpace(buf))
  64. buf = ""
  65. continue
  66. }
  67. }
  68. }
  69. if bracketLevel < 0 {
  70. return nil, errors.New("invalid DDL, unbalanced brackets")
  71. }
  72. buf += string(c)
  73. }
  74. if bracketLevel != 0 {
  75. return nil, errors.New("invalid DDL, unbalanced brackets")
  76. }
  77. if buf != "" {
  78. result.fields = append(result.fields, strings.TrimSpace(buf))
  79. }
  80. for _, f := range result.fields {
  81. fUpper := strings.ToUpper(f)
  82. if strings.HasPrefix(fUpper, "CHECK") {
  83. continue
  84. }
  85. if strings.HasPrefix(fUpper, "CONSTRAINT") {
  86. matches := uniqueRegexp.FindStringSubmatch(f)
  87. if len(matches) > 0 {
  88. cols, err := parseAllColumns(matches[1])
  89. if err == nil && len(cols) == 1 {
  90. for idx, column := range result.columns {
  91. if column.NameValue.String == cols[0] {
  92. column.UniqueValue = sql.NullBool{Bool: true, Valid: true}
  93. result.columns[idx] = column
  94. break
  95. }
  96. }
  97. }
  98. }
  99. continue
  100. }
  101. if strings.HasPrefix(fUpper, "PRIMARY KEY") {
  102. cols, err := parseAllColumns(f)
  103. if err == nil {
  104. for _, name := range cols {
  105. for idx, column := range result.columns {
  106. if column.NameValue.String == name {
  107. column.PrimaryKeyValue = sql.NullBool{Bool: true, Valid: true}
  108. result.columns[idx] = column
  109. break
  110. }
  111. }
  112. }
  113. }
  114. } else if matches := columnRegexp.FindStringSubmatch(f); len(matches) > 0 {
  115. columnType := migrator.ColumnType{
  116. NameValue: sql.NullString{String: matches[1], Valid: true},
  117. DataTypeValue: sql.NullString{String: matches[2], Valid: true},
  118. ColumnTypeValue: sql.NullString{String: matches[2], Valid: true},
  119. PrimaryKeyValue: sql.NullBool{Valid: true},
  120. UniqueValue: sql.NullBool{Valid: true},
  121. NullableValue: sql.NullBool{Bool: true, Valid: true},
  122. DefaultValueValue: sql.NullString{Valid: false},
  123. }
  124. matchUpper := strings.ToUpper(matches[3])
  125. if strings.Contains(matchUpper, " NOT NULL") {
  126. columnType.NullableValue = sql.NullBool{Bool: false, Valid: true}
  127. } else if strings.Contains(matchUpper, " NULL") {
  128. columnType.NullableValue = sql.NullBool{Bool: true, Valid: true}
  129. }
  130. if strings.Contains(matchUpper, " UNIQUE") {
  131. columnType.UniqueValue = sql.NullBool{Bool: true, Valid: true}
  132. }
  133. if strings.Contains(matchUpper, " PRIMARY") {
  134. columnType.PrimaryKeyValue = sql.NullBool{Bool: true, Valid: true}
  135. }
  136. if defaultMatches := defaultValueRegexp.FindStringSubmatch(matches[3]); len(defaultMatches) > 1 {
  137. if strings.ToLower(defaultMatches[1]) != "null" {
  138. columnType.DefaultValueValue = sql.NullString{String: strings.Trim(defaultMatches[1], `"`), Valid: true}
  139. }
  140. }
  141. // data type length
  142. matches := regRealDataType.FindAllStringSubmatch(columnType.DataTypeValue.String, -1)
  143. if len(matches) == 1 && len(matches[0]) == 2 {
  144. size, _ := strconv.Atoi(matches[0][1])
  145. columnType.LengthValue = sql.NullInt64{Valid: true, Int64: int64(size)}
  146. columnType.DataTypeValue.String = strings.TrimSuffix(columnType.DataTypeValue.String, matches[0][0])
  147. }
  148. result.columns = append(result.columns, columnType)
  149. }
  150. }
  151. } else if matches := indexRegexp.FindStringSubmatch(str); len(matches) > 0 {
  152. // don't report Unique by UniqueIndex
  153. } else {
  154. return nil, errors.New("invalid DDL")
  155. }
  156. }
  157. return &result, nil
  158. }
  159. func (d *ddl) clone() *ddl {
  160. copied := new(ddl)
  161. *copied = *d
  162. copied.fields = make([]string, len(d.fields))
  163. copy(copied.fields, d.fields)
  164. copied.columns = make([]migrator.ColumnType, len(d.columns))
  165. copy(copied.columns, d.columns)
  166. return copied
  167. }
  168. func (d *ddl) compile() string {
  169. if len(d.fields) == 0 {
  170. return d.head
  171. }
  172. return fmt.Sprintf("%s (%s)", d.head, strings.Join(d.fields, ","))
  173. }
  174. func (d *ddl) renameTable(dst, src string) error {
  175. tableReg, err := regexp.Compile("\\s*('|`|\")?\\b" + regexp.QuoteMeta(src) + "\\b('|`|\")?\\s*")
  176. if err != nil {
  177. return err
  178. }
  179. replaced := tableReg.ReplaceAllString(d.head, fmt.Sprintf(" `%s` ", dst))
  180. if replaced == d.head {
  181. return fmt.Errorf("failed to look up tablename `%s` from DDL head '%s'", src, d.head)
  182. }
  183. d.head = replaced
  184. return nil
  185. }
  186. func (d *ddl) addConstraint(name string, sql string) {
  187. reg := regexp.MustCompile("^CONSTRAINT [\"`]?" + regexp.QuoteMeta(name) + "[\"` ]")
  188. for i := 0; i < len(d.fields); i++ {
  189. if reg.MatchString(d.fields[i]) {
  190. d.fields[i] = sql
  191. return
  192. }
  193. }
  194. d.fields = append(d.fields, sql)
  195. }
  196. func (d *ddl) removeConstraint(name string) bool {
  197. reg := regexp.MustCompile("^CONSTRAINT [\"`]?" + regexp.QuoteMeta(name) + "[\"` ]")
  198. for i := 0; i < len(d.fields); i++ {
  199. if reg.MatchString(d.fields[i]) {
  200. d.fields = append(d.fields[:i], d.fields[i+1:]...)
  201. return true
  202. }
  203. }
  204. return false
  205. }
  206. func (d *ddl) hasConstraint(name string) bool {
  207. reg := regexp.MustCompile("^CONSTRAINT [\"`]?" + regexp.QuoteMeta(name) + "[\"` ]")
  208. for _, f := range d.fields {
  209. if reg.MatchString(f) {
  210. return true
  211. }
  212. }
  213. return false
  214. }
  215. func (d *ddl) getColumns() []string {
  216. res := []string{}
  217. for _, f := range d.fields {
  218. fUpper := strings.ToUpper(f)
  219. if strings.HasPrefix(fUpper, "PRIMARY KEY") ||
  220. strings.HasPrefix(fUpper, "CHECK") ||
  221. strings.HasPrefix(fUpper, "CONSTRAINT") ||
  222. strings.Contains(fUpper, "GENERATED ALWAYS AS") {
  223. continue
  224. }
  225. reg := regexp.MustCompile("^[\"`']?([\\w\\d]+)[\"`']?")
  226. match := reg.FindStringSubmatch(f)
  227. if match != nil {
  228. res = append(res, "`"+match[1]+"`")
  229. }
  230. }
  231. return res
  232. }
  233. func (d *ddl) removeColumn(name string) bool {
  234. reg := regexp.MustCompile("^(`|'|\"| )" + regexp.QuoteMeta(name) + "(`|'|\"| ) .*?$")
  235. for i := 0; i < len(d.fields); i++ {
  236. if reg.MatchString(d.fields[i]) {
  237. d.fields = append(d.fields[:i], d.fields[i+1:]...)
  238. return true
  239. }
  240. }
  241. return false
  242. }