-
Notifications
You must be signed in to change notification settings - Fork 5.8k
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
Changes from 9 commits
f16f213
e9d24dd
7e2cea2
478e2d4
63ebfe5
96d7210
10aa28f
41b003e
e4485ce
8be9eec
f5910fd
14f8559
936d81f
78c29f5
fab2c06
afc1f01
7de5a88
14326b4
0cec56e
99cf471
bd66996
286c61e
f273c6c
d17e77d
86b7200
0f2c134
b3706a8
53321b9
3eff6bb
eb8ac2d
a682f9c
d7b7785
613fe46
3b638bf
64c83b6
6b1beef
d8f5133
84c3c2e
f87f096
869f9b8
44a592e
e16ed3c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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" | ||
) | ||
|
@@ -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) { | ||
newCol := &model.ColumnInfo{} | ||
oldColName := &model.CIStr{} | ||
pos := &ast.ColumnPosition{} | ||
|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
|
@@ -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. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This function can not do it. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
ver, err = updateVersionAndTableInfo(t, job, tblInfo, true) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why update it again? What if tidb panics after this? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @zimulala Here need to rollback the second time you enter the function. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
@@ -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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need to know the number of rows? If not, use There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 { | ||
|
@@ -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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe here use this code is enough. |
||
} | ||
return ver, nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this the same with mysql? In my environment:
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Your test also changes the column type, but if you use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
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. | ||
|
@@ -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;") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. SQL change to |
||
|
||
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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. change the sql to |
||
} | ||
func (s *testDBSuite) TestPartitionAddIndex(c *C) { | ||
tk := testkit.NewTestKit(c, s.store) | ||
tk.MustExec("use test") | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why check it here? We will check it two times. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Job entering the |
||
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 { | ||
|
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.