Skip to content

Commit

Permalink
ddl: Fix generated column can refer only to generated columns defined…
Browse files Browse the repository at this point in the history
… prior to it. (#11549)
  • Loading branch information
AilinKid authored and bb7133 committed Aug 8, 2019
1 parent 5fff4e9 commit 110b073
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 5 deletions.
24 changes: 24 additions & 0 deletions ddl/db_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,30 @@ func (s *testIntegrationSuite2) TestNullGeneratedColumn(c *C) {
tk.MustExec("drop table t")
}

func (s *testIntegrationSuite2) TestDependedGeneratedColumnPrior2GeneratedColumn(c *C) {
tk := testkit.NewTestKit(c, s.store)
tk.MustExec("use test")
tk.MustExec("drop table if exists t")
tk.MustExec("CREATE TABLE `t` (" +
"`a` int(11) DEFAULT NULL," +
"`b` int(11) GENERATED ALWAYS AS (`a` + 1) VIRTUAL," +
"`c` int(11) GENERATED ALWAYS AS (`b` + 1) VIRTUAL" +
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin")
// should check unknown column first, then the prior ones.
sql := "alter table t add column d int as (c + f + 1) first"
assertErrorCode(c, tk, sql, mysql.ErrBadField)

// depended generated column should be prior to generated column self
sql = "alter table t add column d int as (c+1) first"
assertErrorCode(c, tk, sql, mysql.ErrGeneratedColumnNonPrior)

// correct case
tk.MustExec("alter table t add column d int as (c+1) after c")

// check position nil case
tk.MustExec("alter table t add column(e int as (c+1))")
}

func (s *testIntegrationSuite3) TestChangingCharsetToUtf8(c *C) {
tk := testkit.NewTestKit(c, s.store)

Expand Down
16 changes: 12 additions & 4 deletions ddl/ddl_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -2093,9 +2093,8 @@ func (d *ddl) AddColumn(ctx sessionctx.Context, ti ast.Ident, spec *ast.AlterTab
}

// If new column is a generated column, do validation.
// NOTE: Because now we can only append columns to table,
// we don't need check whether the column refers other
// generated columns occurring later in table.
// NOTE: we do check whether the column refers other generated
// columns occurring later in a table, but we don't handle the col offset.
for _, option := range specNewColumn.Options {
if option.Tp == ast.ColumnOptionGenerated {
if err := checkIllegalFn4GeneratedColumn(specNewColumn.Name.Name.L, option.Expr); err != nil {
Expand All @@ -2110,8 +2109,17 @@ func (d *ddl) AddColumn(ctx sessionctx.Context, ti ast.Ident, spec *ast.AlterTab
if err = checkAutoIncrementRef(specNewColumn.Name.Name.L, dependColNames, t.Meta()); err != nil {
return errors.Trace(err)
}
duplicateColNames := make(map[string]struct{}, len(dependColNames))
for k := range dependColNames {
duplicateColNames[k] = struct{}{}
}
cols := t.Cols()

if err = checkDependedColExist(dependColNames, cols); err != nil {
return errors.Trace(err)
}

if err = checkDependedColExist(dependColNames, t.Cols()); err != nil {
if err = verifyColumnGenerationSingle(duplicateColNames, cols, spec.Position); err != nil {
return errors.Trace(err)
}
}
Expand Down
48 changes: 47 additions & 1 deletion ddl/generated_column.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,26 @@ func verifyColumnGeneration(colName2Generation map[string]columnGenerationInDDL,
return nil
}

// verifyColumnGenerationSingle is for ADD GENERATED COLUMN, we just need verify one column itself.
func verifyColumnGenerationSingle(dependColNames map[string]struct{}, cols []*table.Column, position *ast.ColumnPosition) error {
// Since the added column does not exist yet, we should derive it's offset from ColumnPosition.
pos, err := findPositionRelativeColumn(cols, position)
if err != nil {
return errors.Trace(err)
}
// should check unknown column first, then the prior ones.
for _, col := range cols {
if _, ok := dependColNames[col.Name.L]; ok {
if col.IsGenerated() && col.Offset >= pos {
// Generated column can refer only to generated columns defined prior to it.
return errGeneratedColumnNonPrior.GenWithStackByArgs()
}
}
}
return nil
}

// checkDependedColExist ensure all depended columns exist.
//
// NOTE: this will MODIFY parameter `dependCols`.
func checkDependedColExist(dependCols map[string]struct{}, cols []*table.Column) error {
for _, col := range cols {
Expand All @@ -65,6 +83,34 @@ func checkDependedColExist(dependCols map[string]struct{}, cols []*table.Column)
return nil
}

// findPositionRelativeColumn returns a pos relative to added generated column position.
func findPositionRelativeColumn(cols []*table.Column, pos *ast.ColumnPosition) (int, error) {
position := len(cols)
// Get the column position, default is cols's length means appending.
// For "alter table ... add column(...)", the position will be nil.
// For "alter table ... add column ... ", the position will be default one.
if pos == nil {
return position, nil
}
if pos.Tp == ast.ColumnPositionFirst {
position = 0
} else if pos.Tp == ast.ColumnPositionAfter {
var col *table.Column
for _, c := range cols {
if c.Name.L == pos.RelativeColumn.Name.L {
col = c
break
}
}
if col == nil {
return -1, ErrBadField.GenWithStackByArgs(pos.RelativeColumn, "generated column function")
}
// Inserted position is after the mentioned column.
position = col.Offset + 1
}
return position, nil
}

// findDependedColumnNames returns a set of string, which indicates
// the names of the columns that are depended by colDef.
func findDependedColumnNames(colDef *ast.ColumnDef) (generated bool, colsMap map[string]struct{}) {
Expand Down

0 comments on commit 110b073

Please sign in to comment.