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