diff --git a/ast/ddl.go b/ast/ddl.go index 48b855bda16f1..7d332b7cd91cf 100644 --- a/ast/ddl.go +++ b/ast/ddl.go @@ -604,6 +604,7 @@ const ( AlterTableDropIndex AlterTableDropForeignKey AlterTableModifyColumn + AlterTableChangeColumn // TODO: Add more actions ) @@ -612,13 +613,13 @@ const ( type AlterTableSpec struct { node - Tp AlterTableType - Name string - Constraint *Constraint - Options []*TableOption - Column *ColumnDef - DropColumn *ColumnName - Position *ColumnPosition + Tp AlterTableType + Name string + Constraint *Constraint + Options []*TableOption + NewColumn *ColumnDef + OldColumnName *ColumnName + Position *ColumnPosition } // Accept implements Node Accept interface. @@ -635,19 +636,19 @@ func (n *AlterTableSpec) Accept(v Visitor) (Node, bool) { } n.Constraint = node.(*Constraint) } - if n.Column != nil { - node, ok := n.Column.Accept(v) + if n.NewColumn != nil { + node, ok := n.NewColumn.Accept(v) if !ok { return n, false } - n.Column = node.(*ColumnDef) + n.NewColumn = node.(*ColumnDef) } - if n.DropColumn != nil { - node, ok := n.DropColumn.Accept(v) + if n.OldColumnName != nil { + node, ok := n.OldColumnName.Accept(v) if !ok { return n, false } - n.DropColumn = node.(*ColumnName) + n.OldColumnName = node.(*ColumnName) } if n.Position != nil { node, ok := n.Position.Accept(v) diff --git a/ddl/column.go b/ddl/column.go index 2c300d8e1b6e2..bf0b6a59da5ea 100644 --- a/ddl/column.go +++ b/ddl/column.go @@ -425,12 +425,14 @@ func (d *ddl) onModifyColumn(t *meta.Meta, job *model.Job) error { return errors.Trace(err) } newCol := &model.ColumnInfo{} - err = job.DecodeArgs(newCol) + oldColName := &model.CIStr{} + err = job.DecodeArgs(newCol, oldColName) if err != nil { job.State = model.JobCancelled return errors.Trace(err) } - oldCol := findCol(tblInfo.Columns, newCol.Name.L) + + oldCol := findCol(tblInfo.Columns, oldColName.L) if oldCol == nil || oldCol.State != model.StatePublic { job.State = model.JobCancelled return infoschema.ErrColumnNotExists.GenByArgs(newCol.Name, tblInfo.Name) @@ -441,6 +443,7 @@ func (d *ddl) onModifyColumn(t *meta.Meta, job *model.Job) error { job.State = model.JobCancelled return errors.Trace(err) } + ver, err := updateSchemaVersion(t, job) if err != nil { return errors.Trace(err) diff --git a/ddl/column_test.go b/ddl/column_test.go index 686dda4d68b5e..6a89b1e46e680 100644 --- a/ddl/column_test.go +++ b/ddl/column_test.go @@ -895,7 +895,7 @@ func (s *testColumnSuite) colDefStrToFieldType(c *C, str string) *types.FieldTyp sqlA := "alter table t modify column a " + str stmt, err := parser.New().ParseOneStmt(sqlA, "", "") c.Assert(err, IsNil) - colDef := stmt.(*ast.AlterTableStmt).Specs[0].Column + colDef := stmt.(*ast.AlterTableStmt).Specs[0].NewColumn col, _, err := columnDefToCol(nil, 0, colDef) c.Assert(err, IsNil) return &col.FieldType diff --git a/ddl/ddl.go b/ddl/ddl.go index f99cd7f4240bb..1847a8dcec4e3 100644 --- a/ddl/ddl.go +++ b/ddl/ddl.go @@ -64,6 +64,8 @@ var ( errTooLongKey = terror.ClassDDL.New(codeTooLongKey, fmt.Sprintf("Specified key was too long; max key length is %d bytes", maxPrefixLength)) errKeyColumnDoesNotExits = terror.ClassDDL.New(codeKeyColumnDoesNotExits, "this key column doesn't exist in table") errDupKeyName = terror.ClassDDL.New(codeDupKeyName, "duplicate key name") + errWrongDBName = terror.ClassDDL.New(codeWrongDBName, "Incorrect database name '%s'") + errWrongTableName = terror.ClassDDL.New(codeWrongTableName, "Incorrect table name '%s'") // ErrInvalidDBState returns for invalid database state. ErrInvalidDBState = terror.ClassDDL.New(codeInvalidDBState, "invalid database state") @@ -941,7 +943,7 @@ func (d *ddl) AlterTable(ctx context.Context, ident ast.Ident, specs []*ast.Alte case ast.AlterTableAddColumn: err = d.AddColumn(ctx, ident, spec) case ast.AlterTableDropColumn: - err = d.DropColumn(ctx, ident, spec.DropColumn.Name) + err = d.DropColumn(ctx, ident, spec.OldColumnName.Name) case ast.AlterTableDropIndex: err = d.DropIndex(ctx, ident, model.NewCIStr(spec.Name)) case ast.AlterTableAddConstraint: @@ -960,6 +962,8 @@ func (d *ddl) AlterTable(ctx context.Context, ident ast.Ident, specs []*ast.Alte err = d.DropForeignKey(ctx, ident, model.NewCIStr(spec.Name)) case ast.AlterTableModifyColumn: err = d.ModifyColumn(ctx, ident, spec) + case ast.AlterTableChangeColumn: + err = d.ChangeColumn(ctx, ident, spec) default: // Nothing to do now. } @@ -986,7 +990,7 @@ func checkColumnConstraint(constraints []*ast.ColumnOption) error { // AddColumn will add a new column to the table. func (d *ddl) AddColumn(ctx context.Context, ti ast.Ident, spec *ast.AlterTableSpec) error { // Check whether the added column constraints are supported. - err := checkColumnConstraint(spec.Column.Options) + err := checkColumnConstraint(spec.NewColumn.Options) if err != nil { return errors.Trace(err) } @@ -1003,7 +1007,7 @@ func (d *ddl) AddColumn(ctx context.Context, ti ast.Ident, spec *ast.AlterTableS } // Check whether added column has existed. - colName := spec.Column.Name.Name.O + colName := spec.NewColumn.Name.Name.O col := table.FindCol(t.Cols(), colName) if col != nil { return infoschema.ErrColumnExists.GenByArgs(colName) @@ -1016,7 +1020,7 @@ func (d *ddl) AddColumn(ctx context.Context, ti ast.Ident, spec *ast.AlterTableS // Ingore table constraints now, maybe return error later. // We use length(t.Cols()) as the default offset firstly, later we will change the // column's offset later. - col, _, err = d.buildColumnAndConstraint(ctx, len(t.Cols()), spec.Column) + col, _, err = d.buildColumnAndConstraint(ctx, len(t.Cols()), spec.NewColumn) if err != nil { return errors.Trace(err) } @@ -1103,40 +1107,80 @@ func (d *ddl) modifiable(origin *types.FieldType, to *types.FieldType) bool { } } -// ModifyColumn does modification on an existing column, currently we only support limited kind of changes -// that do not need to change or check data on the table. -func (d *ddl) ModifyColumn(ctx context.Context, ident ast.Ident, spec *ast.AlterTableSpec) error { +func (d *ddl) getModifiableColumnJob(ctx context.Context, ident ast.Ident, originalColName model.CIStr, + spec *ast.AlterTableSpec) (*model.Job, error) { is := d.infoHandle.Get() schema, ok := is.SchemaByName(ident.Schema) if !ok { - return errors.Trace(infoschema.ErrDatabaseNotExists) + return nil, errors.Trace(infoschema.ErrDatabaseNotExists) } t, err := is.TableByName(ident.Schema, ident.Name) if err != nil { - return errors.Trace(infoschema.ErrTableNotExists) + return nil, errors.Trace(infoschema.ErrTableNotExists) } - colName := spec.Column.Name.Name - col := table.FindCol(t.Cols(), colName.L) + + col := table.FindCol(t.Cols(), originalColName.L) if col == nil { - return infoschema.ErrColumnNotExists.GenByArgs(colName, ident.Name) + return nil, infoschema.ErrColumnNotExists.GenByArgs(originalColName, ident.Name) } - if spec.Constraint != nil || spec.Position.Tp != ast.ColumnPositionNone || - len(spec.Column.Options) != 0 || spec.Column.Tp == nil { + if spec.Constraint != nil || (spec.Position != nil && spec.Position.Tp != ast.ColumnPositionNone) || + len(spec.NewColumn.Options) != 0 || spec.NewColumn.Tp == nil { // Make sure the column definition is simple field type. - return errUnsupportedModifyColumn + return nil, errUnsupportedModifyColumn } - d.setCharsetCollationFlenDecimal(spec.Column.Tp) - if !d.modifiable(&col.FieldType, spec.Column.Tp) { - return errUnsupportedModifyColumn + d.setCharsetCollationFlenDecimal(spec.NewColumn.Tp) + if !d.modifiable(&col.FieldType, spec.NewColumn.Tp) { + return nil, errUnsupportedModifyColumn } + newCol := *col - newCol.FieldType = *spec.Column.Tp + newCol.FieldType = *spec.NewColumn.Tp + newCol.Name = spec.NewColumn.Name.Name job := &model.Job{ SchemaID: schema.ID, TableID: t.Meta().ID, Type: model.ActionModifyColumn, - Args: []interface{}{&newCol}, + Args: []interface{}{&newCol, originalColName}, + } + return job, nil +} + +// ChangeColumn renames an existing column and modifies the column's definition, +// currently we only support limited kind of changes +// that do not need to change or check data on the table. +func (d *ddl) ChangeColumn(ctx context.Context, ident ast.Ident, spec *ast.AlterTableSpec) error { + if len(spec.NewColumn.Name.Schema.O) != 0 && ident.Schema.L != spec.NewColumn.Name.Schema.L { + return errWrongDBName.GenByArgs(spec.NewColumn.Name.Schema.O) + } + if len(spec.OldColumnName.Schema.O) != 0 && ident.Schema.L != spec.OldColumnName.Schema.L { + return errWrongDBName.GenByArgs(spec.OldColumnName.Schema.O) + } + if len(spec.NewColumn.Name.Table.O) != 0 && ident.Name.L != spec.NewColumn.Name.Table.L { + return errWrongTableName.GenByArgs(spec.NewColumn.Name.Table.O) + } + if len(spec.OldColumnName.Table.O) != 0 && ident.Name.L != spec.OldColumnName.Table.L { + return errWrongTableName.GenByArgs(spec.OldColumnName.Table.O) + } + + job, err := d.getModifiableColumnJob(ctx, ident, spec.OldColumnName.Name, spec) + if err != nil { + return errors.Trace(err) + } + + err = d.doDDLJob(ctx, job) + err = d.callHookOnChanged(err) + return errors.Trace(err) +} + +// ModifyColumn does modification on an existing column, currently we only support limited kind of changes +// that do not need to change or check data on the table. +func (d *ddl) ModifyColumn(ctx context.Context, ident ast.Ident, spec *ast.AlterTableSpec) error { + originalColName := spec.NewColumn.Name.Name + job, err := d.getModifiableColumnJob(ctx, ident, originalColName, spec) + if err != nil { + return errors.Trace(err) } + err = d.doDDLJob(ctx, job) err = d.callHookOnChanged(err) return errors.Trace(err) @@ -1458,6 +1502,8 @@ const ( codeIncorrectPrefixKey = 1089 codeCantRemoveAllFields = 1090 codeCantDropFieldOrKey = 1091 + codeWrongDBName = 1102 + codeWrongTableName = 1103 codeBlobKeyWithoutLength = 1170 codeInvalidOnUpdate = 1294 ) @@ -1474,6 +1520,8 @@ func init() { codeTooLongKey: mysql.ErrTooLongKey, codeKeyColumnDoesNotExits: mysql.ErrKeyColumnDoesNotExits, codeDupKeyName: mysql.ErrDupKeyName, + codeWrongDBName: mysql.ErrWrongDBName, + codeWrongTableName: mysql.ErrWrongTableName, } terror.ErrClassToMySQLCodes[terror.ClassDDL] = ddlMySQLErrCodes } diff --git a/ddl/ddl_db_test.go b/ddl/ddl_db_test.go index 96965192ec0a0..68bb5bd0126e4 100644 --- a/ddl/ddl_db_test.go +++ b/ddl/ddl_db_test.go @@ -502,6 +502,7 @@ func (s *testDBSuite) TestColumn(c *C) { s.tk.MustExec("use " + s.schemaName) s.testAddColumn(c) s.testDropColumn(c) + s.testChangeColumn(c) } func sessionExec(c *C, s kv.Storage, sql string) { @@ -659,6 +660,18 @@ LOOP: ctx.CommitTxn() } +func (s *testDBSuite) testChangeColumn(c *C) { + s.mustExec(c, "create table t3 (a int, b varchar(10))") + s.mustExec(c, "insert into t3 values(1, 'a'), (2, 'b')") + s.tk.MustQuery("select a from t3").Check(testkit.Rows("1", "2")) + s.mustExec(c, "alter table t3 change a aa bigint") + s.tk.MustQuery("select aa from t3").Check(testkit.Rows("1", "2")) + sql := "alter table t3 change a testx.t3.aa bigint" + s.testErrorCode(c, sql, tmysql.ErrWrongDBName) + sql = "alter table t3 change t.a aa bigint" + s.testErrorCode(c, sql, tmysql.ErrWrongTableName) +} + func (s *testDBSuite) mustExec(c *C, query string, args ...interface{}) { s.tk.MustExec(query, args...) } diff --git a/parser/misc.go b/parser/misc.go index f71b86e6ae061..fcc849aefc313 100644 --- a/parser/misc.go +++ b/parser/misc.go @@ -161,6 +161,7 @@ var tokenMap = map[string]int{ "CAST": cast, "CEIL": ceil, "CEILING": ceiling, + "CHANGE": change, "CHARACTER": character, "CHARSET": charsetKwd, "CHECK": check, diff --git a/parser/parser.y b/parser/parser.y index 0c4387bed6541..2b708f9c0b419 100644 --- a/parser/parser.y +++ b/parser/parser.y @@ -64,11 +64,12 @@ import ( binaryType "BINARY" blobType "BLOB" both "BOTH" - charType "CHAR" by "BY" cascade "CASCADE" caseKwd "CASE" + change "CHANGE" character "CHARACTER" + charType "CHAR" check "CHECK" collate "COLLATE" column "COLUMN" @@ -788,7 +789,7 @@ AlterTableSpec: { $$ = &ast.AlterTableSpec{ Tp: ast.AlterTableAddColumn, - Column: $3.(*ast.ColumnDef), + NewColumn: $3.(*ast.ColumnDef), Position: $4.(*ast.ColumnPosition), } } @@ -804,7 +805,7 @@ AlterTableSpec: { $$ = &ast.AlterTableSpec{ Tp: ast.AlterTableDropColumn, - DropColumn: $3.(*ast.ColumnName), + OldColumnName: $3.(*ast.ColumnName), } } | "DROP" "PRIMARY" "KEY" @@ -837,10 +838,19 @@ AlterTableSpec: { $$ = &ast.AlterTableSpec{ Tp: ast.AlterTableModifyColumn, - Column: $3.(*ast.ColumnDef), + NewColumn: $3.(*ast.ColumnDef), Position: $4.(*ast.ColumnPosition), } } +| "CHANGE" ColumnKeywordOpt ColumnName ColumnDef + { + $$ = &ast.AlterTableSpec{ + Tp: ast.AlterTableChangeColumn, + OldColumnName: $3.(*ast.ColumnName), + NewColumn: $4.(*ast.ColumnDef), + } + } + KeyOrIndex: "KEY" | "INDEX" @@ -2044,7 +2054,7 @@ UnReservedKeyword: ReservedKeyword: "ADD" | "ALL" | "ALTER" | "ANALYZE" | "AND" | "AS" | "ASC" | "BETWEEN" | "BIGINT" -| "BINARY" | "BLOB" | "BOTH" | "BY" | "CASCADE" | "CASE" | "CHARACTER" | "CHECK" | "COLLATE" +| "BINARY" | "BLOB" | "BOTH" | "BY" | "CASCADE" | "CASE" | "CHANGE" | "CHARACTER" | "CHECK" | "COLLATE" | "COLUMN" | "CONSTRAINT" | "CONVERT" | "CREATE" | "CROSS" | "CURRENT_DATE" | "CURRENT_TIME" | "CURRENT_TIMESTAMP" | "CURRENT_USER" | "DATABASE" | "DATABASES" | "DAY_HOUR" | "DAY_MICROSECOND" | "DAY_MINUTE" | "DAY_SECOND" | "DECIMAL" | "DEFAULT" | "DELETE" | "DESC" | "DESCRIBE" diff --git a/parser/parser_test.go b/parser/parser_test.go index aaf4c963a28f9..76c79b0b71391 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -40,7 +40,7 @@ func (s *testParserSuite) TestSimple(c *C) { reservedKws := []string{ "add", "all", "alter", "analyze", "and", "as", "asc", "between", "bigint", - "binary", "blob", "both", "by", "cascade", "case", "character", "check", "collate", + "binary", "blob", "both", "by", "cascade", "case", "change", "character", "check", "collate", "column", "constraint", "convert", "create", "cross", "current_date", "current_time", "current_timestamp", "current_user", "database", "databases", "day_hour", "day_microsecond", "day_minute", "day_second", "decimal", "default", "delete", "desc", "describe", @@ -288,6 +288,7 @@ func (s *testParserSuite) TestDMLStmt(c *C) { {"ALTER TABLE t DISABLE KEYS", true}, {"ALTER TABLE t ENABLE KEYS", true}, {"ALTER TABLE t MODIFY COLUMN a varchar(255)", true}, + {"ALTER TABLE t CHANGE COLUMN a b varchar(255)", true}, // from join {"SELECT * from t1, t2, t3", true},