Skip to content

Commit

Permalink
shcemadiff: support INSTANT DDL for changing column visibility (#…
Browse files Browse the repository at this point in the history
…16503)

Signed-off-by: Shlomi Noach <[email protected]>
  • Loading branch information
shlomi-noach authored Jul 30, 2024
1 parent 3d104d0 commit 4377b88
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 53 deletions.
3 changes: 3 additions & 0 deletions go/mysql/capabilities/capability.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ const (
InstantAddDropColumnFlavorCapability // Adding/dropping column in any position/ordinal.
InstantChangeColumnDefaultFlavorCapability //
InstantExpandEnumCapability //
InstantChangeColumnVisibilityCapability //
MySQLUpgradeInServerFlavorCapability //
DynamicRedoLogCapacityFlavorCapability // supported in MySQL 8.0.30 and above: https://dev.mysql.com/doc/relnotes/mysql/8.0/en/news-8-0-30.html
DisableRedoLogFlavorCapability // supported in MySQL 8.0.21 and above: https://dev.mysql.com/doc/relnotes/mysql/8.0/en/news-8-0-21.html
Expand Down Expand Up @@ -106,6 +107,8 @@ func MySQLVersionHasCapability(serverVersion string, capability FlavorCapability
return atLeast(8, 0, 21)
case FastDropTableFlavorCapability:
return atLeast(8, 0, 23)
case InstantChangeColumnVisibilityCapability:
return atLeast(8, 0, 23)
case InstantAddDropColumnFlavorCapability:
return atLeast(8, 0, 29)
case DynamicRedoLogCapacityFlavorCapability:
Expand Down
126 changes: 73 additions & 53 deletions go/vt/schemadiff/capability.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,13 @@ func alterOptionCapableOfInstantDDL(alterOption sqlparser.AlterOption, createTab
}
return true, col.Type.Options.Storage
}
colStringStrippedDown := func(col *sqlparser.ColumnDefinition, stripDefault bool, stripEnum bool) string {
colStringStrippedDown := func(col *sqlparser.ColumnDefinition, stripEnum bool) string {
strippedCol := sqlparser.Clone(col)
if stripDefault {
strippedCol.Type.Options.Default = nil
strippedCol.Type.Options.DefaultLiteral = false
}
// strip `default`
strippedCol.Type.Options.Default = nil
strippedCol.Type.Options.DefaultLiteral = false
// strip `visibility`
strippedCol.Type.Options.Invisible = nil
if stripEnum {
strippedCol.Type.EnumValues = nil
}
Expand All @@ -95,15 +96,53 @@ func alterOptionCapableOfInstantDDL(alterOption sqlparser.AlterOption, createTab
}
return true
}
changeModifyColumnCapableOfInstantDDL := func(col *sqlparser.ColumnDefinition, newCol *sqlparser.ColumnDefinition) (bool, error) {
// Check if only diff is change of default.
// We temporarily remove the DEFAULT expression (if any) from both
// table and ALTER statement, and compare the columns: if they're otherwise equal,
// then the only change can be an addition/change/removal of DEFAULT, which
// is instant-table.
tableColDefinition := colStringStrippedDown(col, false)
newColDefinition := colStringStrippedDown(newCol, false)
if tableColDefinition == newColDefinition {
return capableOf(capabilities.InstantChangeColumnDefaultFlavorCapability)
}
// Check if:
// 1. this an ENUM/SET
// 2. and the change is to append values to the end of the list
// 3. and the number of added values does not increase the storage size for the enum/set
// 4. while still not caring about a change in the default value
if len(col.Type.EnumValues) > 0 && len(newCol.Type.EnumValues) > 0 {
// both are enum or set
if !hasPrefix(newCol.Type.EnumValues, col.Type.EnumValues) {
return false, nil
}
// we know the new column definition is identical to, or extends, the old definition.
// Now validate storage:
if strings.EqualFold(col.Type.Type, "enum") {
if len(col.Type.EnumValues) <= 255 && len(newCol.Type.EnumValues) > 255 {
// this increases the SET storage size (1 byte for up to 8 values, 2 bytes beyond)
return false, nil
}
}
if strings.EqualFold(col.Type.Type, "set") {
if (len(col.Type.EnumValues)+7)/8 != (len(newCol.Type.EnumValues)+7)/8 {
// this increases the SET storage size (1 byte for up to 8 values, 2 bytes for 8-15, etc.)
return false, nil
}
}
// Now don't care about change of default:
tableColDefinition := colStringStrippedDown(col, true)
newColDefinition := colStringStrippedDown(newCol, true)
if tableColDefinition == newColDefinition {
return capableOf(capabilities.InstantExpandEnumCapability)
}
}
return false, nil
}

// Up to 8.0.26 we could only ADD COLUMN as last column
switch opt := alterOption.(type) {
case *sqlparser.ChangeColumn:
// We do not support INSTANT for renaming a column (ALTER TABLE ...CHANGE) because:
// 1. We discourage column rename
// 2. We do not produce CHANGE statements in declarative diff
// 3. The success of the operation depends on whether the column is referenced by a foreign key
// in another table. Which is a bit too much to compute here.
return false, nil
case *sqlparser.AddColumns:
if tableHasFulltextIndex {
// not supported if the table has a FULLTEXT index
Expand Down Expand Up @@ -157,49 +196,30 @@ func alterOptionCapableOfInstantDDL(alterOption sqlparser.AlterOption, createTab
return capableOf(capabilities.InstantAddDropVirtualColumnFlavorCapability)
}
return capableOf(capabilities.InstantAddDropColumnFlavorCapability)
case *sqlparser.ChangeColumn:
// We do not support INSTANT for renaming a column (ALTER TABLE ...CHANGE) because:
// 1. We discourage column rename
// 2. We do not produce CHANGE statements in declarative diff
// 3. The success of the operation depends on whether the column is referenced by a foreign key
// in another table. Which is a bit too much to compute here.
if opt.OldColumn.Name.String() != opt.NewColDefinition.Name.String() {
return false, nil
}
if col := findColumn(opt.OldColumn.Name.String()); col != nil {
return changeModifyColumnCapableOfInstantDDL(col, opt.NewColDefinition)
}
return false, nil
case *sqlparser.ModifyColumn:
if col := findColumn(opt.NewColDefinition.Name.String()); col != nil {
// Check if only diff is change of default.
// We temporarily remove the DEFAULT expression (if any) from both
// table and ALTER statement, and compare the columns: if they're otherwise equal,
// then the only change can be an addition/change/removal of DEFAULT, which
// is instant-table.
tableColDefinition := colStringStrippedDown(col, true, false)
newColDefinition := colStringStrippedDown(opt.NewColDefinition, true, false)
if tableColDefinition == newColDefinition {
return capableOf(capabilities.InstantChangeColumnDefaultFlavorCapability)
}
// Check if:
// 1. this an ENUM/SET
// 2. and the change is to append values to the end of the list
// 3. and the number of added values does not increase the storage size for the enum/set
// 4. while still not caring about a change in the default value
if len(col.Type.EnumValues) > 0 && len(opt.NewColDefinition.Type.EnumValues) > 0 {
// both are enum or set
if !hasPrefix(opt.NewColDefinition.Type.EnumValues, col.Type.EnumValues) {
return false, nil
}
// we know the new column definition is identical to, or extends, the old definition.
// Now validate storage:
if strings.EqualFold(col.Type.Type, "enum") {
if len(col.Type.EnumValues) <= 255 && len(opt.NewColDefinition.Type.EnumValues) > 255 {
// this increases the SET storage size (1 byte for up to 8 values, 2 bytes beyond)
return false, nil
}
}
if strings.EqualFold(col.Type.Type, "set") {
if (len(col.Type.EnumValues)+7)/8 != (len(opt.NewColDefinition.Type.EnumValues)+7)/8 {
// this increases the SET storage size (1 byte for up to 8 values, 2 bytes for 8-15, etc.)
return false, nil
}
}
// Now don't care about change of default:
tableColDefinition := colStringStrippedDown(col, true, true)
newColDefinition := colStringStrippedDown(opt.NewColDefinition, true, true)
if tableColDefinition == newColDefinition {
return capableOf(capabilities.InstantExpandEnumCapability)
}
}
return changeModifyColumnCapableOfInstantDDL(col, opt.NewColDefinition)
}
return false, nil
case *sqlparser.AlterColumn:
if opt.DropDefault || opt.DefaultLiteral || opt.DefaultVal != nil {
return capableOf(capabilities.InstantChangeColumnDefaultFlavorCapability)
}
if opt.Invisible != nil {
return capableOf(capabilities.InstantChangeColumnVisibilityCapability)
}
return false, nil
default:
Expand Down
31 changes: 31 additions & 0 deletions go/vt/schemadiff/capability_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ func TestAlterTableCapableOfInstantDDL(t *testing.T) {
capabilities.InstantAddDropVirtualColumnFlavorCapability,
capabilities.InstantAddDropColumnFlavorCapability,
capabilities.InstantChangeColumnDefaultFlavorCapability,
capabilities.InstantChangeColumnVisibilityCapability,
capabilities.InstantExpandEnumCapability:
return true, nil
}
Expand Down Expand Up @@ -272,6 +273,36 @@ func TestAlterTableCapableOfInstantDDL(t *testing.T) {
alter: "alter table t modify column c1 set('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i')",
expectCapableOfInstantDDL: false,
},
{
name: "make a column invisible",
create: "create table t1 (id int, i1 int)",
alter: "alter table t1 modify column i1 int invisible",
expectCapableOfInstantDDL: true,
},
{
name: "make a column visible",
create: "create table t1 (id int, i1 int)",
alter: "alter table t1 change column i1 i1 int visible",
expectCapableOfInstantDDL: true,
},
{
name: "make a column visible with rename",
create: "create table t1 (id int, i1 int)",
alter: "alter table t1 change column i1 i2 int visible",
expectCapableOfInstantDDL: false,
},
{
name: "make a column invisible via SET",
create: "create table t1 (id int, i1 int)",
alter: "alter table t1 alter column i1 set invisible",
expectCapableOfInstantDDL: true,
},
{
name: "drop column default",
create: "create table t1 (id int, i1 int)",
alter: "alter table t1 alter column i1 drop default",
expectCapableOfInstantDDL: true,
},
}
for _, tcase := range tcases {
t.Run(tcase.name, func(t *testing.T) {
Expand Down

0 comments on commit 4377b88

Please sign in to comment.