Skip to content

Commit

Permalink
ddl, meta: allow increasing auto_random bits (#17423)
Browse files Browse the repository at this point in the history
  • Loading branch information
tangenta authored Jun 10, 2020
1 parent c9c9f87 commit 0454898
Show file tree
Hide file tree
Showing 6 changed files with 191 additions and 25 deletions.
46 changes: 43 additions & 3 deletions ddl/column.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
Expand Down Expand Up @@ -585,17 +588,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)
Expand Down Expand Up @@ -637,6 +643,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)
Expand Down Expand Up @@ -727,6 +739,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 {
Expand Down
72 changes: 72 additions & 0 deletions ddl/column_change_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down
45 changes: 29 additions & 16 deletions ddl/ddl_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -3291,7 +3291,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)
}

Expand All @@ -3301,7 +3302,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
}
Expand Down Expand Up @@ -3343,34 +3344,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,
Expand Down
47 changes: 43 additions & 4 deletions ddl/serial_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -832,6 +832,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)
Expand All @@ -848,10 +849,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) {
Expand Down Expand Up @@ -907,8 +914,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)",
Expand All @@ -921,6 +932,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))")
Expand All @@ -937,9 +955,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`
Expand Down
4 changes: 3 additions & 1 deletion meta/autoid/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion meta/meta.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
Expand Down

0 comments on commit 0454898

Please sign in to comment.