Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions go/base/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ type MigrationContext struct {
UniqueKey *sql.UniqueKey
SharedColumns *sql.ColumnList
ColumnRenameMap map[string]string
DroppedColumnsMap map[string]bool
MappedSharedColumns *sql.ColumnList
MigrationRangeMinValues *sql.ColumnValues
MigrationRangeMaxValues *sql.ColumnValues
Expand Down
7 changes: 7 additions & 0 deletions go/logic/inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -662,7 +662,14 @@ func (this *Inspector) getSharedColumns(originalColumns, ghostColumns *sql.Colum
}
sharedColumnNames := []string{}
for _, originalColumn := range originalColumns.Names() {
isSharedColumn := false
if columnsInGhost[originalColumn] || columnsInGhost[columnRenameMap[originalColumn]] {
isSharedColumn = true
}
if this.migrationContext.DroppedColumnsMap[originalColumn] {
isSharedColumn = false
}
if isSharedColumn {
sharedColumnNames = append(sharedColumnNames, originalColumn)
}
}
Expand Down
1 change: 1 addition & 0 deletions go/logic/migrator.go
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ func (this *Migrator) validateStatement() (err error) {
}
log.Infof("Alter statement has column(s) renamed. gh-ost finds the following renames: %v; --approve-renamed-columns is given and so migration proceeds.", this.parser.GetNonTrivialRenames())
}
this.migrationContext.DroppedColumnsMap = this.parser.DroppedColumnsMap()
return nil
}

Expand Down
34 changes: 29 additions & 5 deletions go/sql/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,18 @@ import (
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]+)$`)
)

type Parser struct {
columnRenameMap map[string]string
droppedColumns map[string]bool
}

func NewParser() *Parser {
return &Parser{
columnRenameMap: make(map[string]string),
droppedColumns: make(map[string]bool),
}
}

Expand Down Expand Up @@ -59,10 +62,9 @@ func (this *Parser) sanitizeQuotesFromAlterStatement(alterStatement string) (str
return strippedStatement
}

func (this *Parser) ParseAlterStatement(alterStatement string) (err error) {
alterTokens, _ := this.tokenizeAlterStatement(alterStatement)
for _, alterToken := range alterTokens {
alterToken = this.sanitizeQuotesFromAlterStatement(alterToken)
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 {
Expand All @@ -71,10 +73,28 @@ func (this *Parser) ParseAlterStatement(alterStatement string) (err error) {
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
}
}
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
}

Expand All @@ -91,3 +111,7 @@ func (this *Parser) GetNonTrivialRenames() map[string]string {
func (this *Parser) HasNonTrivialRenames() bool {
return len(this.GetNonTrivialRenames()) > 0
}

func (this *Parser) DroppedColumnsMap() map[string]bool {
return this.droppedColumns
}
39 changes: 39 additions & 0 deletions go/sql/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,3 +120,42 @@ func TestSanitizeQuotesFromAlterStatement(t *testing.T) {
test.S(t).ExpectEquals(strippedStatement, "change column i int ''")
}
}

func TestParseAlterStatementDroppedColumns(t *testing.T) {

{
parser := NewParser()
statement := "drop column b"
err := parser.ParseAlterStatement(statement)
test.S(t).ExpectNil(err)
test.S(t).ExpectEquals(len(parser.droppedColumns), 1)
test.S(t).ExpectTrue(parser.droppedColumns["b"])
}
{
parser := NewParser()
statement := "drop column b, drop key c_idx, drop column `d`"
err := parser.ParseAlterStatement(statement)
test.S(t).ExpectNil(err)
test.S(t).ExpectEquals(len(parser.droppedColumns), 2)
test.S(t).ExpectTrue(parser.droppedColumns["b"])
test.S(t).ExpectTrue(parser.droppedColumns["d"])
}
{
parser := NewParser()
statement := "drop column b, drop key c_idx, drop column `d`, drop `e`, drop primary key, drop foreign key fk_1"
err := parser.ParseAlterStatement(statement)
test.S(t).ExpectNil(err)
test.S(t).ExpectEquals(len(parser.droppedColumns), 3)
test.S(t).ExpectTrue(parser.droppedColumns["b"])
test.S(t).ExpectTrue(parser.droppedColumns["d"])
test.S(t).ExpectTrue(parser.droppedColumns["e"])
}
{
parser := NewParser()
statement := "drop column b, drop bad statement, add column i int"
err := parser.ParseAlterStatement(statement)
test.S(t).ExpectNil(err)
test.S(t).ExpectEquals(len(parser.droppedColumns), 1)
test.S(t).ExpectTrue(parser.droppedColumns["b"])
}
}
30 changes: 30 additions & 0 deletions localtests/drop-null-add-not-null/create.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
drop table if exists gh_ost_test;
create table gh_ost_test (
id int auto_increment,
c1 int null,
c2 int not null,
primary key (id)
) auto_increment=1;

insert into gh_ost_test values (null, null, 17);
insert into gh_ost_test values (null, null, 19);

drop event if exists gh_ost_test;
delimiter ;;
create event gh_ost_test
on schedule every 1 second
starts current_timestamp
ends current_timestamp + interval 60 second
on completion not preserve
enable
do
begin
insert ignore into gh_ost_test values (101, 11, 23);
insert ignore into gh_ost_test values (102, 13, 23);
insert into gh_ost_test values (null, 17, 23);
insert into gh_ost_test values (null, null, 29);
set @last_insert_id := last_insert_id();
-- update gh_ost_test set c2=c2+@last_insert_id where id=@last_insert_id order by id desc limit 1;
delete from gh_ost_test where id=1;
delete from gh_ost_test where c1=13; -- id=2
end ;;
1 change: 1 addition & 0 deletions localtests/drop-null-add-not-null/extra_args
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
--alter="drop column c1, add column c1 int not null default 47"
1 change: 1 addition & 0 deletions localtests/drop-null-add-not-null/ghost_columns
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
c2
1 change: 1 addition & 0 deletions localtests/drop-null-add-not-null/orig_columns
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
c2