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  }