Skip to content

Commit

Permalink
*: Support for changing column (pingcap#2174)
Browse files Browse the repository at this point in the history
*: support for changing column in parser
* ddl: support for changing column in ddl
  • Loading branch information
zimulala authored Dec 7, 2016
1 parent 4260c3b commit 196b6b1
Show file tree
Hide file tree
Showing 8 changed files with 119 additions and 42 deletions.
27 changes: 14 additions & 13 deletions ast/ddl.go
Original file line number Diff line number Diff line change
Expand Up @@ -604,6 +604,7 @@ const (
AlterTableDropIndex
AlterTableDropForeignKey
AlterTableModifyColumn
AlterTableChangeColumn

// TODO: Add more actions
)
Expand All @@ -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.
Expand All @@ -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)
Expand Down
7 changes: 5 additions & 2 deletions ddl/column.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion ddl/column_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
88 changes: 68 additions & 20 deletions ddl/ddl.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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:
Expand All @@ -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.
}
Expand All @@ -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)
}
Expand All @@ -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)
Expand All @@ -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)
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -1458,6 +1502,8 @@ const (
codeIncorrectPrefixKey = 1089
codeCantRemoveAllFields = 1090
codeCantDropFieldOrKey = 1091
codeWrongDBName = 1102
codeWrongTableName = 1103
codeBlobKeyWithoutLength = 1170
codeInvalidOnUpdate = 1294
)
Expand All @@ -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
}
13 changes: 13 additions & 0 deletions ddl/ddl_db_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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...)
}
Expand Down
1 change: 1 addition & 0 deletions parser/misc.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ var tokenMap = map[string]int{
"CAST": cast,
"CEIL": ceil,
"CEILING": ceiling,
"CHANGE": change,
"CHARACTER": character,
"CHARSET": charsetKwd,
"CHECK": check,
Expand Down
20 changes: 15 additions & 5 deletions parser/parser.y
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -788,7 +789,7 @@ AlterTableSpec:
{
$$ = &ast.AlterTableSpec{
Tp: ast.AlterTableAddColumn,
Column: $3.(*ast.ColumnDef),
NewColumn: $3.(*ast.ColumnDef),
Position: $4.(*ast.ColumnPosition),
}
}
Expand All @@ -804,7 +805,7 @@ AlterTableSpec:
{
$$ = &ast.AlterTableSpec{
Tp: ast.AlterTableDropColumn,
DropColumn: $3.(*ast.ColumnName),
OldColumnName: $3.(*ast.ColumnName),
}
}
| "DROP" "PRIMARY" "KEY"
Expand Down Expand Up @@ -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"

Expand Down Expand Up @@ -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"
Expand Down
3 changes: 2 additions & 1 deletion parser/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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},
Expand Down

0 comments on commit 196b6b1

Please sign in to comment.