Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ddl:support the definition of null change to not null using alter table #7771

Merged
merged 42 commits into from
Oct 17, 2018
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
f16f213
ddl:support the definition of null change to not null using alter table
ciscoxll Sep 23, 2018
e9d24dd
Update function comment
ciscoxll Sep 23, 2018
7e2cea2
Merge branch 'master' into modify-column
ciscoxll Sep 23, 2018
478e2d4
Fix ci error
ciscoxll Sep 23, 2018
63ebfe5
Fix ci error
ciscoxll Sep 23, 2018
96d7210
Fix ci error
ciscoxll Sep 23, 2018
10aa28f
Update function comment
ciscoxll Sep 24, 2018
41b003e
Address comment
ciscoxll Sep 26, 2018
e4485ce
Address comment
ciscoxll Sep 26, 2018
8be9eec
Address comment
ciscoxll Sep 29, 2018
f5910fd
Change function entry condition
ciscoxll Sep 29, 2018
14f8559
Address comment
ciscoxll Sep 29, 2018
936d81f
Address comment
ciscoxll Sep 29, 2018
78c29f5
Address comment
ciscoxll Sep 29, 2018
fab2c06
Address comment
ciscoxll Oct 10, 2018
afc1f01
Address comment
ciscoxll Oct 11, 2018
7de5a88
Add job state JobStateRollbackDone
ciscoxll Oct 11, 2018
14326b4
Address comment
ciscoxll Oct 11, 2018
0cec56e
Address comment
ciscoxll Oct 12, 2018
99cf471
Remove modifyColumn2RollbackJob function
ciscoxll Oct 12, 2018
bd66996
Address comment
ciscoxll Oct 12, 2018
286c61e
Fix CI
ciscoxll Oct 12, 2018
f273c6c
Address comment
ciscoxll Oct 15, 2018
d17e77d
Address comment
ciscoxll Oct 15, 2018
86b7200
Address comment
ciscoxll Oct 15, 2018
0f2c134
Address comment
ciscoxll Oct 15, 2018
b3706a8
Address comment
ciscoxll Oct 15, 2018
53321b9
Add function comment
ciscoxll Oct 15, 2018
3eff6bb
Fix CI
ciscoxll Oct 15, 2018
eb8ac2d
Change function comment
ciscoxll Oct 15, 2018
a682f9c
Address comment
ciscoxll Oct 15, 2018
d7b7785
Fix CI
ciscoxll Oct 15, 2018
613fe46
Fix CI
ciscoxll Oct 16, 2018
3b638bf
Fix CI
ciscoxll Oct 16, 2018
64c83b6
Merge branch 'master' into modify-column
ciscoxll Oct 16, 2018
6b1beef
Merge branch 'master' into modify-column
crazycs520 Oct 16, 2018
d8f5133
Address comment
ciscoxll Oct 17, 2018
84c3c2e
Address comment
ciscoxll Oct 17, 2018
f87f096
Address comment
ciscoxll Oct 17, 2018
869f9b8
Address comment
ciscoxll Oct 17, 2018
44a592e
Address comment
ciscoxll Oct 17, 2018
e16ed3c
Merge branch 'master' into modify-column
ciscoxll Oct 17, 2018
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
79 changes: 76 additions & 3 deletions ddl/column.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,16 @@
package ddl

import (
"fmt"

"github.com/pingcap/tidb/ast"
"github.com/pingcap/tidb/ddl/util"
"github.com/pingcap/tidb/infoschema"
"github.com/pingcap/tidb/meta"
"github.com/pingcap/tidb/model"
"github.com/pingcap/tidb/mysql"
"github.com/pingcap/tidb/sessionctx"
"github.com/pingcap/tidb/util/sqlexec"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
)
Expand Down Expand Up @@ -267,7 +272,7 @@ func onSetDefaultValue(t *meta.Meta, job *model.Job) (ver int64, _ error) {
return updateColumn(t, job, newCol, &newCol.Name)
}

func onModifyColumn(t *meta.Meta, job *model.Job) (ver int64, _ error) {
func (w *worker) onModifyColumn(t *meta.Meta, job *model.Job) (ver int64, _ error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why we need to change this to member function?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@winkyao I need to get sessionctx from context resource pool.

newCol := &model.ColumnInfo{}
oldColName := &model.CIStr{}
pos := &ast.ColumnPosition{}
Expand All @@ -277,17 +282,32 @@ func onModifyColumn(t *meta.Meta, job *model.Job) (ver int64, _ error) {
return ver, errors.Trace(err)
}

return doModifyColumn(t, job, newCol, oldColName, pos)
return w.doModifyColumn(t, job, newCol, oldColName, pos)
}

// doModifyColumn updates the column information and reorders all columns.
func doModifyColumn(t *meta.Meta, job *model.Job, newCol *model.ColumnInfo, oldName *model.CIStr, pos *ast.ColumnPosition) (ver int64, _ error) {
func (w *worker) doModifyColumn(t *meta.Meta, job *model.Job, newCol *model.ColumnInfo, oldName *model.CIStr, pos *ast.ColumnPosition) (ver int64, _ error) {
dbInfo, err := t.GetDatabase(job.SchemaID)
if err != nil {
return ver, errors.Trace(err)
}

tblInfo, err := getTableInfo(t, job, job.SchemaID)
if err != nil {
return ver, errors.Trace(err)
}

oldCol := model.FindColumnInfo(tblInfo.Columns, oldName.L)
if job.IsRollingback() {
// field flag reset to null.
tblInfo.Columns[oldCol.Offset].Flag = oldCol.Flag &^ mysql.NotNullFlag
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How can you confirm the oldCol definitely set to not null flag? What if the job is not alter column from not null to null?

ver, err = updateVersionAndTableInfo(t, job, tblInfo, true)
if err != nil {
return ver, errors.Trace(err)
}
return ver, nil
crazycs520 marked this conversation as resolved.
Show resolved Hide resolved
}

if oldCol == nil || oldCol.State != model.StatePublic {
job.State = model.JobStateCancelled
return ver, infoschema.ErrColumnNotExists.GenWithStackByArgs(oldName, tblInfo.Name)
Expand All @@ -308,6 +328,35 @@ func doModifyColumn(t *meta.Meta, job *model.Job, newCol *model.ColumnInfo, oldN
// }
// }

if !mysql.HasNotNullFlag(oldCol.Flag) && mysql.HasNotNullFlag(newCol.Flag) {
// Get sessionctx from context resource pool.
var ctx sessionctx.Context
ctx, err = w.sessPool.get()
if err != nil {
return ver, errors.Trace(err)
}
defer w.sessPool.put(ctx)

// Modify the type defined Flag to NotNullFlag.
tblInfo.Columns[oldCol.Offset].Flag = newCol.Flag
ver, err = updateVersionAndTableInfo(t, job, tblInfo, true)
// Wait for two leases to ensure that all nodes in the cluster are updated successfully.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function can not do it.
This transaction is not committed, so no new schema needs to be updated by other nodes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zimulala I am doing this for the purpose, modify the field definition to NotNullFlag to prevent insertion of null values.

ver, err = updateVersionAndTableInfo(t, job, tblInfo, true)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why update it again? What if tidb panics after this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This question needs to be discussed.

if err != nil {
job.State = model.JobStateRollingback
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If "err != nil" is true, I don't think we need this state.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zimulala Here need to rollback the second time you enter the function.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ciscoxll This comment is for your old commit.

return ver, errors.Trace(err)
}

err = CheckForNullValue(ctx, dbInfo.Name, tblInfo.Name, oldCol.Name, newCol.Name)
// If there is a null value inserted, it cannot be modified and needs to be rollback.
if ErrWarnDataTruncated.Equal(err) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what if err != nil and err != ErrWarnDataTruncated

ver, err = modifyColumn2RollbackJob(t, tblInfo, job, oldCol)
if err != nil {
return ver, errors.Trace(err)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can put line365-368 after lint369. Then we can remove line358-361.

}
}

// We need the latest column's offset and state. This information can be obtained from the store.
newCol.Offset = oldCol.Offset
newCol.State = oldCol.State
Expand Down Expand Up @@ -379,6 +428,20 @@ func doModifyColumn(t *meta.Meta, job *model.Job, newCol *model.ColumnInfo, oldN
return ver, nil
}

// CheckForNullValue ensure there are no null values of the column of this table.
func CheckForNullValue(ctx sessionctx.Context, schema, table, oldCol, newCol model.CIStr) error {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this function needn't to export.

sql := fmt.Sprintf("select count(*) from `%s`.`%s` where `%s` is null;", schema.L, table.L, oldCol.L)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to know the number of rows? If not, use limit 1 is better.

Copy link
Contributor

@winkyao winkyao Sep 29, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add limit 1

rows, _, err := ctx.(sqlexec.RestrictedSQLExecutor).ExecRestrictedSQL(ctx, sql)
if err != nil {
return errors.Trace(err)
}
rowCount := rows[0].GetInt64(0)
if rowCount != 0 {
return ErrWarnDataTruncated.GenWithStackByArgs(newCol.L, rowCount)
}
return nil
}

func updateColumn(t *meta.Meta, job *model.Job, newCol *model.ColumnInfo, oldColName *model.CIStr) (ver int64, _ error) {
tblInfo, err := getTableInfo(t, job, job.SchemaID)
if err != nil {
Expand Down Expand Up @@ -423,3 +486,13 @@ func checkAddColumnTooManyColumns(oldCols int) error {
}
return nil
}

func modifyColumn2RollbackJob(t *meta.Meta, tblInfo *model.TableInfo, job *model.Job, oldCol *model.ColumnInfo) (ver int64, _ error) {
job.State = model.JobStateRollingback
tblInfo.Columns[oldCol.Offset].Flag = oldCol.Flag
ver, err := updateVersionAndTableInfo(t, job, tblInfo, true)
if err != nil {
return ver, errors.Trace(err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe here use this code is enough.

}
return ver, nil
}
12 changes: 12 additions & 0 deletions ddl/db_change_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,18 @@ func (s *testStateChangeSuite) TestParallelAlterModifyColumn(c *C) {
s.testControlParallelExecSQL(c, sql, sql, f)
}

func (s *testStateChangeSuite) TestParallelColumnModifyingDefinition(c *C) {
sql1 := "insert into t(b) values (null);"
sql2 := "alter table t change b b2 bigint not null;"
f := func(c *C, err1, err2 error) {
c.Assert(err1, IsNil)
if err2 != nil {
c.Assert(err2.Error(), Equals, "[ddl:1265]Data truncated for column 'b2' at row 1")
}
}
s.testControlParallelExecSQL(c, sql1, sql2, f)
}

func (s *testStateChangeSuite) TestParallelChangeColumnName(c *C) {
sql1 := "ALTER TABLE t CHANGE a aa int;"
sql2 := "ALTER TABLE t CHANGE b aa int;"
Expand Down
18 changes: 16 additions & 2 deletions ddl/db_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1226,8 +1226,10 @@ func (s *testDBSuite) TestChangeColumn(c *C) {
s.testErrorCode(c, sql, tmysql.ErrWrongDBName)
sql = "alter table t3 change t.a aa bigint"
s.testErrorCode(c, sql, tmysql.ErrWrongTableName)
sql = "alter table t3 change aa a bigint not null"
s.testErrorCode(c, sql, tmysql.ErrUnknown)
s.mustExec(c, "create table t4 (a int default '0', b varchar(10), d int not null default '0')")
s.tk.MustExec("insert into t4(a) values (null);")
sql = "alter table t4 change a a1 int not null"
s.testErrorCode(c, sql, tmysql.WarnDataTruncated)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the same with mysql? In my environment:

mysql> alter table t4 change a a1 int not null;
ERROR 1138 (22004): Invalid use of NULL value

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lamxTyler Done

mysql

mysql> create table test2 (c1 int, c2 int, c3 int default 1, index (c1));
Query OK, 0 rows affected (0.03 sec)

mysql> insert into test2(c2) values (null);
Query OK, 1 row affected (0.00 sec)
mysql> alter table test2 change c2 a bigint not null;
ERROR 1265 (01000): Data truncated for column 'a' at row 1

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your test also changes the column type, but if you use alter table test2 change c2 a int not null;, you can get a different result.

Copy link
Contributor

@winkyao winkyao Oct 15, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mysql> create table test2 (c1 int, c2 int, c3 int default 1, index (c1));
Query OK, 0 rows affected (0.01 sec)

mysql> insert into test2(c2) values (null);
Query OK, 1 row affected (0.00 sec)

mysql> alter table test2 change c2 a bigint not null;
ERROR 1265 (01000): Data truncated for column 'a' at row 1
mysql> alter table test2 change c2 a int not null;
ERROR 1138 (22004): Invalid use of NULL value

FYI @ciscoxll

sql = "alter table t3 modify en enum('a', 'z', 'b', 'c') not null default 'a'"
s.testErrorCode(c, sql, tmysql.ErrUnknown)
// Rename to an existing column.
Expand Down Expand Up @@ -3416,6 +3418,18 @@ func backgroundExecOnJobUpdatedExported(c *C, s *testDBSuite, hook *ddl.TestDDLC
return hook.OnJobUpdatedExported, c3IdxInfo
}

func (s *testDBSuite) TestColumnModifyingDefinition(c *C) {
s.tk = testkit.NewTestKit(c, s.store)
s.tk.MustExec("use test")
s.tk.MustExec("drop table if exists test2;")
s.tk.MustExec("create table test2 (c1 int, c2 int, c3 int default 1, index (c1));")
s.tk.MustExec("alter table test2 change c2 a bigint not null;")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SQL change to alter table test2 change c2 a int not null; To make the alter type is just null to not null.


s.tk.MustExec("drop table if exists test2;")
s.tk.MustExec("create table test2 (c1 int, c2 int, c3 int default 1, index (c1));")
s.tk.MustExec("insert into test2(c2) values (null);")
s.testErrorCode(c, "alter table test2 change c2 a bigint not null;", tmysql.WarnDataTruncated)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

change the sql to alter table test2 change c2 a int not null

}
func (s *testDBSuite) TestPartitionAddIndex(c *C) {
tk := testkit.NewTestKit(c, s.store)
tk.MustExec("use test")
Expand Down
4 changes: 4 additions & 0 deletions ddl/ddl.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,8 @@ var (
// ErrUniqueKeyNeedAllFieldsInPf returns must include all columns in the table's partitioning function.
ErrUniqueKeyNeedAllFieldsInPf = terror.ClassDDL.New(codeUniqueKeyNeedAllFieldsInPf, mysql.MySQLErrName[mysql.ErrUniqueKeyNeedAllFieldsInPf])
errWrongExprInPartitionFunc = terror.ClassDDL.New(codeWrongExprInPartitionFunc, mysql.MySQLErrName[mysql.ErrWrongExprInPartitionFunc])
// ErrWarnDataTruncated returns data truncated error.
ErrWarnDataTruncated = terror.ClassDDL.New(codeWarnDataTruncated, mysql.MySQLErrName[mysql.WarnDataTruncated])
)

// DDL is responsible for updating schema in data store and maintaining in-memory InfoSchema cache.
Expand Down Expand Up @@ -620,6 +622,7 @@ const (
codeUniqueKeyNeedAllFieldsInPf = terror.ErrCode(mysql.ErrUniqueKeyNeedAllFieldsInPf)
codePrimaryCantHaveNull = terror.ErrCode(mysql.ErrPrimaryCantHaveNull)
codeWrongExprInPartitionFunc = terror.ErrCode(mysql.ErrWrongExprInPartitionFunc)
codeWarnDataTruncated = terror.ErrCode(mysql.WarnDataTruncated)
)

func init() {
Expand Down Expand Up @@ -667,6 +670,7 @@ func init() {
codeUniqueKeyNeedAllFieldsInPf: mysql.ErrUniqueKeyNeedAllFieldsInPf,
codePrimaryCantHaveNull: mysql.ErrPrimaryCantHaveNull,
codeWrongExprInPartitionFunc: mysql.ErrWrongExprInPartitionFunc,
codeWarnDataTruncated: mysql.WarnDataTruncated,
}
terror.ErrClassToMySQLCodes[terror.ClassDDL] = ddlMySQLErrCodes
}
6 changes: 4 additions & 2 deletions ddl/ddl_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -1635,9 +1635,11 @@ func (d *ddl) getModifiableColumnJob(ctx sessionctx.Context, ident ast.Ident, or
return nil, errUnsupportedModifyColumn.GenWithStackByArgs("set auto_increment")
}

// We don't support modifying the type definitions from 'null' to 'not null' now.
// We support modifying the type definitions of 'null' to 'not null' now.
if !mysql.HasNotNullFlag(col.Flag) && mysql.HasNotNullFlag(newCol.Flag) {
return nil, errUnsupportedModifyColumn.GenWithStackByArgs("null to not null")
if err = CheckForNullValue(ctx, ident.Schema, ident.Name, col.Name, newCol.Name); err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why check it here? We will check it two times.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Job entering the ddl queue requires two checks on the job.

return nil, errors.Trace(err)
}
}
// As same with MySQL, we don't support modifying the stored status for generated columns.
if err = checkModifyGeneratedColumn(t.Cols(), col, newCol); err != nil {
Expand Down
2 changes: 1 addition & 1 deletion ddl/ddl_worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -482,7 +482,7 @@ func (w *worker) runDDLJob(d *ddlCtx, t *meta.Meta, job *model.Job) (ver int64,
case model.ActionDropColumn:
ver, err = onDropColumn(t, job)
case model.ActionModifyColumn:
ver, err = onModifyColumn(t, job)
ver, err = w.onModifyColumn(t, job)
case model.ActionSetDefaultValue:
ver, err = onSetDefaultValue(t, job)
case model.ActionAddIndex:
Expand Down