130 lines
3.1 KiB
Go
130 lines
3.1 KiB
Go
/*
|
|
Copyright 2016 GitHub Inc.
|
|
See https://github.com/github/gh-ost/blob/master/LICENSE
|
|
*/
|
|
|
|
package sql
|
|
|
|
import (
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
var (
|
|
sanitizeQuotesRegexp = regexp.MustCompile("('[^']*')")
|
|
renameColumnRegexp = regexp.MustCompile(`(?i)\bchange\s+(column\s+|)([\S]+)\s+([\S]+)\s+`)
|
|
dropColumnRegexp = regexp.MustCompile(`(?i)\bdrop\s+(column\s+|)([\S]+)$`)
|
|
renameTableRegexp = regexp.MustCompile(`(?i)\brename\s+(to|as)\s+`)
|
|
)
|
|
|
|
type Parser struct {
|
|
columnRenameMap map[string]string
|
|
droppedColumns map[string]bool
|
|
isRenameTable bool
|
|
}
|
|
|
|
func NewParser() *Parser {
|
|
return &Parser{
|
|
columnRenameMap: make(map[string]string),
|
|
droppedColumns: make(map[string]bool),
|
|
}
|
|
}
|
|
|
|
func (this *Parser) tokenizeAlterStatement(alterStatement string) (tokens []string, err error) {
|
|
terminatingQuote := rune(0)
|
|
f := func(c rune) bool {
|
|
switch {
|
|
case c == terminatingQuote:
|
|
terminatingQuote = rune(0)
|
|
return false
|
|
case terminatingQuote != rune(0):
|
|
return false
|
|
case c == '\'':
|
|
terminatingQuote = c
|
|
return false
|
|
case c == '(':
|
|
terminatingQuote = ')'
|
|
return false
|
|
default:
|
|
return c == ','
|
|
}
|
|
}
|
|
|
|
tokens = strings.FieldsFunc(alterStatement, f)
|
|
for i := range tokens {
|
|
tokens[i] = strings.TrimSpace(tokens[i])
|
|
}
|
|
return tokens, nil
|
|
}
|
|
|
|
func (this *Parser) sanitizeQuotesFromAlterStatement(alterStatement string) (strippedStatement string) {
|
|
strippedStatement = alterStatement
|
|
strippedStatement = sanitizeQuotesRegexp.ReplaceAllString(strippedStatement, "''")
|
|
return strippedStatement
|
|
}
|
|
|
|
func (this *Parser) parseAlterToken(alterToken string) (err error) {
|
|
{
|
|
// rename
|
|
allStringSubmatch := renameColumnRegexp.FindAllStringSubmatch(alterToken, -1)
|
|
for _, submatch := range allStringSubmatch {
|
|
if unquoted, err := strconv.Unquote(submatch[2]); err == nil {
|
|
submatch[2] = unquoted
|
|
}
|
|
if unquoted, err := strconv.Unquote(submatch[3]); err == nil {
|
|
submatch[3] = unquoted
|
|
}
|
|
this.columnRenameMap[submatch[2]] = submatch[3]
|
|
}
|
|
}
|
|
{
|
|
// drop
|
|
allStringSubmatch := dropColumnRegexp.FindAllStringSubmatch(alterToken, -1)
|
|
for _, submatch := range allStringSubmatch {
|
|
if unquoted, err := strconv.Unquote(submatch[2]); err == nil {
|
|
submatch[2] = unquoted
|
|
}
|
|
this.droppedColumns[submatch[2]] = true
|
|
}
|
|
}
|
|
{
|
|
// rename table
|
|
if renameTableRegexp.MatchString(alterToken) {
|
|
this.isRenameTable = true
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (this *Parser) ParseAlterStatement(alterStatement string) (err error) {
|
|
alterTokens, _ := this.tokenizeAlterStatement(alterStatement)
|
|
for _, alterToken := range alterTokens {
|
|
alterToken = this.sanitizeQuotesFromAlterStatement(alterToken)
|
|
this.parseAlterToken(alterToken)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (this *Parser) GetNonTrivialRenames() map[string]string {
|
|
result := make(map[string]string)
|
|
for column, renamed := range this.columnRenameMap {
|
|
if column != renamed {
|
|
result[column] = renamed
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func (this *Parser) HasNonTrivialRenames() bool {
|
|
return len(this.GetNonTrivialRenames()) > 0
|
|
}
|
|
|
|
func (this *Parser) DroppedColumnsMap() map[string]bool {
|
|
return this.droppedColumns
|
|
}
|
|
|
|
func (this *Parser) IsRenameTable() bool {
|
|
return this.isRenameTable
|
|
}
|