diff --git a/ddl/column.go b/ddl/column.go index 3137af944e93f..3a701bc56d654 100644 --- a/ddl/column.go +++ b/ddl/column.go @@ -15,10 +15,12 @@ package ddl import ( "fmt" + "math/bits" "strings" "sync/atomic" "time" + "github.com/cznic/mathutil" "github.com/pingcap/errors" "github.com/pingcap/failpoint" "github.com/pingcap/parser/ast" @@ -27,6 +29,7 @@ import ( "github.com/pingcap/tidb/ddl/util" "github.com/pingcap/tidb/infoschema" "github.com/pingcap/tidb/meta" + "github.com/pingcap/tidb/meta/autoid" "github.com/pingcap/tidb/sessionctx" "github.com/pingcap/tidb/table" "github.com/pingcap/tidb/types" @@ -334,17 +337,20 @@ func (w *worker) onModifyColumn(t *meta.Meta, job *model.Job) (ver int64, _ erro oldColName := &model.CIStr{} pos := &ast.ColumnPosition{} var modifyColumnTp byte - err := job.DecodeArgs(newCol, oldColName, pos, &modifyColumnTp) + var updatedAutoRandomBits uint64 + err := job.DecodeArgs(newCol, oldColName, pos, &modifyColumnTp, &updatedAutoRandomBits) if err != nil { job.State = model.JobStateCancelled return ver, errors.Trace(err) } - return w.doModifyColumn(t, job, newCol, oldColName, pos, modifyColumnTp) + return w.doModifyColumn(t, job, newCol, oldColName, pos, modifyColumnTp, updatedAutoRandomBits) } // doModifyColumn updates the column information and reorders all columns. -func (w *worker) doModifyColumn(t *meta.Meta, job *model.Job, newCol *model.ColumnInfo, oldName *model.CIStr, pos *ast.ColumnPosition, modifyColumnTp byte) (ver int64, _ error) { +func (w *worker) doModifyColumn( + t *meta.Meta, job *model.Job, newCol *model.ColumnInfo, oldName *model.CIStr, + pos *ast.ColumnPosition, modifyColumnTp byte, newAutoRandBits uint64) (ver int64, _ error) { dbInfo, err := checkSchemaExistAndCancelNotExistJob(t, job) if err != nil { return ver, errors.Trace(err) @@ -386,6 +392,12 @@ func (w *worker) doModifyColumn(t *meta.Meta, job *model.Job, newCol *model.Colu } }) + if newAutoRandBits > 0 { + if err := checkAndApplyNewAutoRandomBits(job, t, tblInfo, newCol, oldName, newAutoRandBits); err != nil { + return ver, errors.Trace(err) + } + } + // Column from null to not null. if !mysql.HasNotNullFlag(oldCol.Flag) && mysql.HasNotNullFlag(newCol.Flag) { noPreventNullFlag := !mysql.HasPreventNullInsertFlag(oldCol.Flag) @@ -476,6 +488,34 @@ func (w *worker) doModifyColumn(t *meta.Meta, job *model.Job, newCol *model.Colu return ver, nil } +func checkAndApplyNewAutoRandomBits(job *model.Job, t *meta.Meta, tblInfo *model.TableInfo, + newCol *model.ColumnInfo, oldName *model.CIStr, newAutoRandBits uint64) error { + schemaID := job.SchemaID + newLayout := autoid.NewAutoRandomIDLayout(&newCol.FieldType, newAutoRandBits) + + // GenAutoRandomID first to prevent concurrent update. + _, err := t.GenAutoRandomID(schemaID, tblInfo.ID, 1) + if err != nil { + return err + } + currentIncBitsVal, err := t.GetAutoRandomID(schemaID, tblInfo.ID) + if err != nil { + return err + } + // Find the max number of available shard bits by + // counting leading zeros in current inc part of auto_random ID. + availableBits := bits.LeadingZeros64(uint64(currentIncBitsVal)) + isOccupyingIncBits := newLayout.TypeBitsLength-newLayout.IncrementalBits > uint64(availableBits) + if isOccupyingIncBits { + availableBits := mathutil.Min(autoid.MaxAutoRandomBits, availableBits) + errMsg := fmt.Sprintf(autoid.AutoRandomOverflowErrMsg, availableBits, newAutoRandBits, oldName.O) + job.State = model.JobStateCancelled + return ErrInvalidAutoRandom.GenWithStackByArgs(errMsg) + } + tblInfo.AutoRandomBits = newAutoRandBits + return nil +} + // checkForNullValue ensure there are no null values of the column of this table. // `isDataTruncated` indicates whether the new field and the old field type are the same, in order to be compatible with mysql. func checkForNullValue(ctx sessionctx.Context, isDataTruncated bool, schema, table, newCol model.CIStr, oldCols ...*model.ColumnInfo) error { diff --git a/ddl/column_change_test.go b/ddl/column_change_test.go index e72ae945a3196..8865adede8c08 100644 --- a/ddl/column_change_test.go +++ b/ddl/column_change_test.go @@ -17,12 +17,14 @@ import ( "context" "fmt" "sync" + "sync/atomic" "time" . "github.com/pingcap/check" "github.com/pingcap/errors" "github.com/pingcap/parser/ast" "github.com/pingcap/parser/model" + "github.com/pingcap/parser/mysql" "github.com/pingcap/tidb/kv" "github.com/pingcap/tidb/meta" "github.com/pingcap/tidb/meta/autoid" @@ -151,6 +153,76 @@ func (s *testColumnChangeSuite) TestColumnChange(c *C) { s.testAddColumnNoDefault(c, ctx, d, tblInfo) } +func (s *testColumnChangeSuite) TestModifyAutoRandColumnWithMetaKeyChanged(c *C) { + d := testNewDDLAndStart( + context.Background(), + c, + WithStore(s.store), + WithLease(testLease), + ) + defer d.Stop() + + ids, err := d.genGlobalIDs(1) + tableID := ids[0] + c.Assert(err, IsNil) + colInfo := &model.ColumnInfo{ + Name: model.NewCIStr("a"), + Offset: 0, + State: model.StatePublic, + FieldType: *types.NewFieldType(mysql.TypeLonglong), + } + tblInfo := &model.TableInfo{ + ID: tableID, + Name: model.NewCIStr("auto_random_table_name"), + Columns: []*model.ColumnInfo{colInfo}, + AutoRandomBits: 5, + } + colInfo.ID = allocateColumnID(tblInfo) + ctx := testNewContext(d) + testCreateTable(c, ctx, d, s.dbInfo, tblInfo) + + tc := &TestDDLCallback{} + var errCount int32 = 3 + var genAutoRandErr error + tc.onJobRunBefore = func(job *model.Job) { + if atomic.LoadInt32(&errCount) > 0 && job.Type == model.ActionModifyColumn { + atomic.AddInt32(&errCount, -1) + genAutoRandErr = kv.RunInNewTxn(s.store, false, func(txn kv.Transaction) error { + t := meta.NewMeta(txn) + _, err1 := t.GenAutoRandomID(s.dbInfo.ID, tableID, 1) + return err1 + }) + } + } + d.SetHook(tc) + const newAutoRandomBits uint64 = 10 + job := &model.Job{ + SchemaID: s.dbInfo.ID, + TableID: tblInfo.ID, + SchemaName: s.dbInfo.Name.L, + Type: model.ActionModifyColumn, + BinlogInfo: &model.HistoryInfo{}, + Args: []interface{}{colInfo, colInfo.Name, ast.ColumnPosition{}, 0, newAutoRandomBits}, + } + err = d.doDDLJob(ctx, job) + c.Assert(err, IsNil) + c.Assert(errCount == 0, IsTrue) + c.Assert(genAutoRandErr, IsNil) + testCheckJobDone(c, d, job, true) + var newTbInfo *model.TableInfo + err = kv.RunInNewTxn(d.store, false, func(txn kv.Transaction) error { + t := meta.NewMeta(txn) + var err error + newTbInfo, err = t.GetTable(s.dbInfo.ID, tableID) + if err != nil { + return errors.Trace(err) + } + return nil + }) + c.Assert(err, IsNil) + c.Assert(newTbInfo.AutoRandomBits, Equals, newAutoRandomBits) +} + func (s *testColumnChangeSuite) testAddColumnNoDefault(c *C, ctx sessionctx.Context, d *ddl, tblInfo *model.TableInfo) { tc := &TestDDLCallback{} // set up hook diff --git a/ddl/ddl_api.go b/ddl/ddl_api.go index 69cc92d079086..887efefb3ee50 100644 --- a/ddl/ddl_api.go +++ b/ddl/ddl_api.go @@ -3031,7 +3031,8 @@ func (d *ddl) getModifiableColumnJob(ctx sessionctx.Context, ident ast.Ident, or return nil, errors.Trace(err) } - if err = checkAutoRandom(t.Meta(), col, specNewColumn); err != nil { + var newAutoRandBits uint64 + if newAutoRandBits, err = checkAutoRandom(t.Meta(), col, specNewColumn); err != nil { return nil, errors.Trace(err) } @@ -3041,7 +3042,7 @@ func (d *ddl) getModifiableColumnJob(ctx sessionctx.Context, ident ast.Ident, or SchemaName: schema.Name.L, Type: model.ActionModifyColumn, BinlogInfo: &model.HistoryInfo{}, - Args: []interface{}{&newCol, originalColName, spec.Position, modifyColumnTp}, + Args: []interface{}{&newCol, originalColName, spec.Position, modifyColumnTp, newAutoRandBits}, } return job, nil } @@ -3083,34 +3084,46 @@ func checkColumnWithIndexConstraint(tbInfo *model.TableInfo, originalCol, newCol return nil } -func checkAutoRandom(tableInfo *model.TableInfo, originCol *table.Column, specNewColumn *ast.ColumnDef) error { +func checkAutoRandom(tableInfo *model.TableInfo, originCol *table.Column, specNewColumn *ast.ColumnDef) (uint64, error) { if !config.GetGlobalConfig().Experimental.AllowAutoRandom && containsColumnOption(specNewColumn, ast.ColumnOptionAutoRandom) { - return ErrInvalidAutoRandom.GenWithStackByArgs(autoid.AutoRandomExperimentalDisabledErrMsg) + return 0, ErrInvalidAutoRandom.GenWithStackByArgs(autoid.AutoRandomExperimentalDisabledErrMsg) } - // Disallow add/drop/modify actions on auto_random. - newAutoRandomBit, err := extractAutoRandomBitsFromColDef(specNewColumn) + // Disallow add/drop actions on auto_random. + oldRandBits := tableInfo.AutoRandomBits + newRandBits, err := extractAutoRandomBitsFromColDef(specNewColumn) if err != nil { - return errors.Trace(err) - } - if tableInfo.AutoRandomBits != newAutoRandomBit { - return ErrInvalidAutoRandom.GenWithStackByArgs(autoid.AutoRandomAlterErrMsg) + return 0, errors.Trace(err) } - - if tableInfo.AutoRandomBits != 0 { + switch { + case oldRandBits == newRandBits: + break + case oldRandBits == 0 || newRandBits == 0: + return 0, ErrInvalidAutoRandom.GenWithStackByArgs(autoid.AutoRandomAlterErrMsg) + case autoid.MaxAutoRandomBits < newRandBits: + errMsg := fmt.Sprintf(autoid.AutoRandomOverflowErrMsg, + autoid.MaxAutoRandomBits, newRandBits, specNewColumn.Name.Name.O) + return 0, ErrInvalidAutoRandom.GenWithStackByArgs(errMsg) + case oldRandBits < newRandBits: + break // Increasing auto_random shard bits is allowed. + case oldRandBits > newRandBits: + return 0, ErrInvalidAutoRandom.GenWithStackByArgs(autoid.AutoRandomDecreaseBitErrMsg) + } + + if oldRandBits != 0 { // Disallow changing the column field type. if originCol.Tp != specNewColumn.Tp.Tp { - return ErrInvalidAutoRandom.GenWithStackByArgs(autoid.AutoRandomModifyColTypeErrMsg) + return 0, ErrInvalidAutoRandom.GenWithStackByArgs(autoid.AutoRandomModifyColTypeErrMsg) } // Disallow changing auto_increment on auto_random column. if containsColumnOption(specNewColumn, ast.ColumnOptionAutoIncrement) != mysql.HasAutoIncrementFlag(originCol.Flag) { - return ErrInvalidAutoRandom.GenWithStackByArgs(autoid.AutoRandomIncompatibleWithAutoIncErrMsg) + return 0, ErrInvalidAutoRandom.GenWithStackByArgs(autoid.AutoRandomIncompatibleWithAutoIncErrMsg) } // Disallow specifying a default value on auto_random column. if containsColumnOption(specNewColumn, ast.ColumnOptionDefaultValue) { - return ErrInvalidAutoRandom.GenWithStackByArgs(autoid.AutoRandomIncompatibleWithDefaultValueErrMsg) + return 0, ErrInvalidAutoRandom.GenWithStackByArgs(autoid.AutoRandomIncompatibleWithDefaultValueErrMsg) } } - return nil + return newRandBits, nil } // ChangeColumn renames an existing column and modifies the column's definition, diff --git a/ddl/serial_test.go b/ddl/serial_test.go index 90f709dac7946..bcbd56ff97274 100644 --- a/ddl/serial_test.go +++ b/ddl/serial_test.go @@ -830,6 +830,7 @@ func (s *testSerialSuite) TestAutoRandom(c *C) { defer tk.MustExec("drop database if exists auto_random_db") tk.MustExec("use auto_random_db") tk.MustExec("drop table if exists t") + tk.MustExec("set @@allow_auto_random_explicit_insert = true") assertInvalidAutoRandomErr := func(sql string, errMsg string, args ...interface{}) { _, err := tk.Exec(sql) @@ -846,10 +847,16 @@ func (s *testSerialSuite) TestAutoRandom(c *C) { assertAlterValue := func(sql string) { assertInvalidAutoRandomErr(sql, autoid.AutoRandomAlterErrMsg) } + assertDecreaseBitErr := func(sql string) { + assertInvalidAutoRandomErr(sql, autoid.AutoRandomDecreaseBitErrMsg) + } assertWithAutoInc := func(sql string) { assertInvalidAutoRandomErr(sql, autoid.AutoRandomIncompatibleWithAutoIncErrMsg) } - assertOverflow := func(sql, colName string, autoRandBits uint64) { + assertOverflow := func(sql, colName string, maxAutoRandBits, actualAutoRandBits uint64) { + assertInvalidAutoRandomErr(sql, autoid.AutoRandomOverflowErrMsg, maxAutoRandBits, actualAutoRandBits, colName) + } + assertMaxOverflow := func(sql, colName string, autoRandBits uint64) { assertInvalidAutoRandomErr(sql, autoid.AutoRandomOverflowErrMsg, autoid.MaxAutoRandomBits, autoRandBits, colName) } assertModifyColType := func(sql string) { @@ -905,8 +912,12 @@ func (s *testSerialSuite) TestAutoRandom(c *C) { }) // Overflow data type max length. - assertOverflow("create table t (a bigint auto_random(64) primary key)", "a", 64) - assertOverflow("create table t (a bigint auto_random(16) primary key)", "a", 16) + assertMaxOverflow("create table t (a bigint auto_random(64) primary key)", "a", 64) + assertMaxOverflow("create table t (a bigint auto_random(16) primary key)", "a", 16) + mustExecAndDrop("create table t (a bigint auto_random(5) primary key)", func() { + assertMaxOverflow("alter table t modify a bigint auto_random(64)", "a", 64) + assertMaxOverflow("alter table t modify a bigint auto_random(16)", "a", 16) + }) assertNonPositive("create table t (a bigint auto_random(0) primary key)") tk.MustGetErrMsg("create table t (a bigint auto_random(-1) primary key)", @@ -919,6 +930,13 @@ func (s *testSerialSuite) TestAutoRandom(c *C) { mustExecAndDrop("create table t (a bigint primary key auto_random(4))") mustExecAndDrop("create table t (a bigint auto_random(4), primary key (a))") + // Increase auto_random bits. + mustExecAndDrop("create table t (a bigint auto_random(5) primary key)", func() { + tk.MustExec("alter table t modify a bigint auto_random(8)") + tk.MustExec("alter table t modify a bigint auto_random(10)") + tk.MustExec("alter table t modify a bigint auto_random(12)") + }) + // Auto_random can occur multiple times like other column attributes. mustExecAndDrop("create table t (a bigint auto_random(3) auto_random(2) primary key)") mustExecAndDrop("create table t (a bigint, b bigint auto_random(3) primary key auto_random(2))") @@ -935,9 +953,30 @@ func (s *testSerialSuite) TestAutoRandom(c *C) { }) mustExecAndDrop("create table t (a bigint primary key)", func() { assertAlterValue("alter table t modify column a bigint auto_random(3)") - assertAlterValue("alter table t change column a b bigint auto_random(3)") }) + // Decrease auto_random bits is not allowed. + mustExecAndDrop("create table t (a bigint auto_random(10) primary key)", func() { + assertDecreaseBitErr("alter table t modify column a bigint auto_random(6)") + }) + mustExecAndDrop("create table t (a bigint auto_random(10) primary key)", func() { + assertDecreaseBitErr("alter table t modify column a bigint auto_random(1)") + }) + + originStep := autoid.GetStep() + autoid.SetStep(1) + // Increase auto_random bits but it will overlap with incremental bits. + mustExecAndDrop("create table t (a bigint unsigned auto_random(5) primary key)", func() { + const alterTryCnt, rebaseOffset = 3, 1 + insertSQL := fmt.Sprintf("insert into t values (%d)", ((1<<(64-10))-1)-rebaseOffset-alterTryCnt) + tk.MustExec(insertSQL) + // Try to rebase to 0..0011..1111 (54 `1`s). + tk.MustExec("alter table t modify a bigint unsigned auto_random(6)") + tk.MustExec("alter table t modify a bigint unsigned auto_random(10)") + assertOverflow("alter table t modify a bigint unsigned auto_random(11)", "a", 10, 11) + }) + autoid.SetStep(originStep) + // Modifying the field type of a auto_random column is not allowed. // Here the throw error is `ERROR 8200 (HY000): Unsupported modify column: length 11 is less than origin 20`, // instead of `ERROR 8216 (HY000): Invalid auto random: modifying the auto_random column type is not supported` diff --git a/meta/autoid/errors.go b/meta/autoid/errors.go index 234db6c6bdf0e..5f34b978e614a 100644 --- a/meta/autoid/errors.go +++ b/meta/autoid/errors.go @@ -38,11 +38,13 @@ const ( // AutoRandomIncompatibleWithDefaultValueErrMsg is reported when auto_random and default are specified on the same column. AutoRandomIncompatibleWithDefaultValueErrMsg = "auto_random is incompatible with default" // AutoRandomOverflowErrMsg is reported when auto_random is greater than max length of a MySQL data type. - AutoRandomOverflowErrMsg = "max allowed auto_random bits is %d, but got %d on column `%s`" + AutoRandomOverflowErrMsg = "max allowed auto_random shard bits is %d, but got %d on column `%s`" // AutoRandomModifyColTypeErrMsg is reported when a user is trying to modify the type of a column specified with auto_random. AutoRandomModifyColTypeErrMsg = "modifying the auto_random column type is not supported" // AutoRandomAlterErrMsg is reported when a user is trying to add/drop/modify the value of auto_random attribute. AutoRandomAlterErrMsg = "adding/dropping/modifying auto_random is not supported" + // AutoRandomDecreaseBitErrMsg is reported when the auto_random shard bits is decreased. + AutoRandomDecreaseBitErrMsg = "decreasing auto_random shard bits is not supported" // AutoRandomNonPositive is reported then a user specifies a non-positive value for auto_random. AutoRandomNonPositive = "the value of auto_random should be positive" // AutoRandomAvailableAllocTimesNote is reported when a table containing auto_random is created. diff --git a/meta/meta.go b/meta/meta.go index 6514ffd96f726..fcc1e63be40f2 100644 --- a/meta/meta.go +++ b/meta/meta.go @@ -212,7 +212,7 @@ func (m *Meta) GetAutoTableID(dbID int64, tableID int64) (int64, error) { return m.txn.HGetInt64(m.dbKey(dbID), m.autoTableIDKey(tableID)) } -// GetAutoRandomID gets current auto shard id with table id. +// GetAutoRandomID gets current auto random id with table id. func (m *Meta) GetAutoRandomID(dbID int64, tableID int64) (int64, error) { return m.txn.HGetInt64(m.dbKey(dbID), m.autoRandomTableIDKey(tableID)) }