func rowWithCols(txn kv.Retriever, t table.Table, h int64, cols []*table.Column) ([]types.Datum, error) { v := make([]types.Datum, len(cols)) for i, col := range cols { if col.State != model.StatePublic { return nil, errInvalidColumnState.Gen("Cannot use none public column - %v", cols) } if col.IsPKHandleColumn(t.Meta()) { v[i].SetInt64(h) continue } k := t.RecordKey(h, col) data, err := txn.Get(k) if terror.ErrorEqual(err, kv.ErrNotExist) && !mysql.HasNotNullFlag(col.Flag) { continue } else if err != nil { return nil, errors.Trace(err) } val, err := tables.DecodeValue(data, &col.FieldType) if err != nil { return nil, errors.Trace(err) } v[i] = val } return v, nil }
func (s *testColumnChangeSuite) testColumnDrop(c *C, ctx context.Context, d *ddl, tbl table.Table) { d.close() dropCol := tbl.Cols()[2] tc := &testDDLCallback{} // set up hook prevState := model.StateNone var checkErr error tc.onJobUpdated = func(job *model.Job) { if job.SchemaState == prevState { return } prevState = job.SchemaState currentTbl, err := getCurrentTable(d, s.dbInfo.ID, tbl.Meta().ID) if err != nil { checkErr = errors.Trace(err) } for _, col := range currentTbl.Cols() { if col.ID == dropCol.ID { checkErr = errors.Errorf("column is not dropped") } } } d.hook = tc d.start() testDropColumn(c, ctx, d, s.dbInfo, tbl.Meta(), dropCol.Name.L, false) }
func rowWithCols(txn kv.Retriever, t table.Table, h int64, cols []*column.Col) ([]types.Datum, error) { v := make([]types.Datum, len(cols)) for i, col := range cols { if col.State != model.StatePublic { return nil, errors.Errorf("Cannot use none public column - %v", cols) } if col.IsPKHandleColumn(t.Meta()) { v[i].SetInt64(h) continue } k := t.RecordKey(h, col) data, err := txn.Get(k) if err != nil { return nil, errors.Trace(err) } val, err := tables.DecodeValue(data, &col.FieldType) if err != nil { return nil, errors.Trace(err) } v[i] = val } return v, nil }
func (d *ddl) backfillColumnData(t table.Table, columnInfo *model.ColumnInfo, handles []int64, reorgInfo *reorgInfo) error { defaultVal, _, err := table.GetColDefaultValue(nil, columnInfo) if err != nil { return errors.Trace(err) } colMap := make(map[int64]*types.FieldType) for _, col := range t.Meta().Columns { colMap[col.ID] = &col.FieldType } for _, handle := range handles { log.Info("[ddl] backfill column...", handle) err := kv.RunInNewTxn(d.store, true, func(txn kv.Transaction) error { if err := d.isReorgRunnable(txn); err != nil { return errors.Trace(err) } rowKey := t.RecordKey(handle) rowVal, err := txn.Get(rowKey) if terror.ErrorEqual(err, kv.ErrNotExist) { // If row doesn't exist, skip it. return nil } if err != nil { return errors.Trace(err) } rowColumns, err := tablecodec.DecodeRow(rowVal, colMap) if err != nil { return errors.Trace(err) } if _, ok := rowColumns[columnInfo.ID]; ok { // The column is already added by update or insert statement, skip it. return nil } newColumnIDs := make([]int64, 0, len(rowColumns)+1) newRow := make([]types.Datum, 0, len(rowColumns)+1) for colID, val := range rowColumns { newColumnIDs = append(newColumnIDs, colID) newRow = append(newRow, val) } newColumnIDs = append(newColumnIDs, columnInfo.ID) newRow = append(newRow, defaultVal) newRowVal, err := tablecodec.EncodeRow(newRow, newColumnIDs) if err != nil { return errors.Trace(err) } err = txn.Set(rowKey, newRowVal) if err != nil { return errors.Trace(err) } return errors.Trace(reorgInfo.UpdateHandle(txn, handle)) }) if err != nil { return errors.Trace(err) } } return nil }
func (e *DeleteExec) removeRow(ctx context.Context, t table.Table, h int64, data []types.Datum) error { err := t.RemoveRecord(ctx, h, data) if err != nil { return errors.Trace(err) } getDirtyDB(ctx).deleteRow(t.Meta().ID, h) ctx.GetSessionVars().StmtCtx.AddAffectedRows(1) return nil }
func (d *ddl) backfillTableIndex(t table.Table, indexInfo *model.IndexInfo, handles []int64, reorgInfo *reorgInfo) error { kvX := tables.NewIndex(t.Meta(), indexInfo) for _, handle := range handles { log.Debug("[ddl] building index...", handle) err := kv.RunInNewTxn(d.store, true, func(txn kv.Transaction) error { if err := d.isReorgRunnable(txn); err != nil { return errors.Trace(err) } // first check row exists exist, err := checkRowExist(txn, t, handle) if err != nil { return errors.Trace(err) } else if !exist { // row doesn't exist, skip it. return nil } var vals []types.Datum vals, err = fetchRowColVals(txn, t, handle, indexInfo) if err != nil { return errors.Trace(err) } exist, _, err = kvX.Exist(txn, vals, handle) if err != nil { return errors.Trace(err) } else if exist { // index already exists, skip it. return nil } err = lockRow(txn, t, handle) if err != nil { return errors.Trace(err) } // create the index. err = kvX.Create(txn, vals, handle) if err != nil { return errors.Trace(err) } // update reorg next handle return errors.Trace(reorgInfo.UpdateHandle(txn, handle)) }) if err != nil { return errors.Trace(err) } } return nil }
func iterRecords(retriever kv.Retriever, t table.Table, startKey kv.Key, cols []*table.Column, fn table.RecordIterFunc) error { it, err := retriever.Seek(startKey) if err != nil { return errors.Trace(err) } defer it.Close() if !it.Valid() { return nil } log.Debugf("startKey:%q, key:%q, value:%q", startKey, it.Key(), it.Value()) colMap := make(map[int64]*types.FieldType, len(cols)) for _, col := range cols { colMap[col.ID] = &col.FieldType } prefix := t.RecordPrefix() for it.Valid() && it.Key().HasPrefix(prefix) { // first kv pair is row lock information. // TODO: check valid lock // get row handle handle, err := tablecodec.DecodeRowKey(it.Key()) if err != nil { return errors.Trace(err) } rowMap, err := tablecodec.DecodeRow(it.Value(), colMap) if err != nil { return errors.Trace(err) } data := make([]types.Datum, 0, len(cols)) for _, col := range cols { if col.IsPKHandleColumn(t.Meta()) { data = append(data, types.NewIntDatum(handle)) } else { data = append(data, rowMap[col.ID]) } } more, err := fn(handle, data, cols) if !more || err != nil { return errors.Trace(err) } rk := t.RecordKey(handle) err = kv.NextUntil(it, util.RowKeyPrefixFilter(rk)) if err != nil { return errors.Trace(err) } } return nil }
func findColumnByName(t table.Table, tableName, colName string) (*table.Column, error) { if len(tableName) > 0 && tableName != t.Meta().Name.O { return nil, errors.Errorf("unknown field %s.%s", tableName, colName) } c := table.FindCol(t.Cols(), colName) if c == nil { return nil, errors.Errorf("unknown field %s", colName) } return c, nil }
func getForeignKey(t table.Table, name string) *model.FKInfo { for _, fk := range t.Meta().ForeignKeys { // only public foreign key can be read. if fk.State != model.StatePublic { continue } if fk.Name.L == strings.ToLower(name) { return fk } } return nil }
func findColumnByName(t table.Table, name string) (*column.Col, error) { _, tableName, colName := splitQualifiedName(name) if len(tableName) > 0 && tableName != t.Meta().Name.O { return nil, errors.Errorf("unknown field %s.%s", tableName, colName) } c := column.FindCol(t.Cols(), colName) if c == nil { return nil, errors.Errorf("unknown field %s", colName) } return c, nil }
func (e *UpdateExec) getTableOffset(t table.Table) int { fields := e.SelectExec.Fields() i := 0 for i < len(fields) { field := fields[i] if field.Table.Name.L == t.Meta().Name.L { return i } i += len(field.Table.Columns) } return 0 }
func (d *ddl) backfillTableIndex(t table.Table, indexInfo *model.IndexInfo, handles []int64, reorgInfo *reorgInfo) error { kvX := tables.NewIndex(t.Meta(), indexInfo) for _, handle := range handles { log.Debug("[ddl] building index...", handle) err := kv.RunInNewTxn(d.store, true, func(txn kv.Transaction) error { if err := d.isReorgRunnable(txn); err != nil { return errors.Trace(err) } vals, err1 := fetchRowColVals(txn, t, handle, indexInfo) if terror.ErrorEqual(err1, kv.ErrNotExist) { // row doesn't exist, skip it. return nil } if err1 != nil { return errors.Trace(err1) } exist, _, err1 := kvX.Exist(txn, vals, handle) if err1 != nil { return errors.Trace(err1) } else if exist { // index already exists, skip it. return nil } rowKey := tablecodec.EncodeRecordKey(t.RecordPrefix(), handle) err1 = txn.LockKeys(rowKey) if err1 != nil { return errors.Trace(err1) } // create the index. err1 = kvX.Create(txn, vals, handle) if err1 != nil { return errors.Trace(err1) } // update reorg next handle return errors.Trace(reorgInfo.UpdateHandle(txn, handle)) }) if err != nil { return errors.Trace(err) } } return nil }
func rowWithCols(txn kv.Retriever, t table.Table, h int64, cols []*table.Column) ([]types.Datum, error) { key := t.RecordKey(h) value, err := txn.Get(key) if err != nil { return nil, errors.Trace(err) } v := make([]types.Datum, len(cols)) colTps := make(map[int64]*types.FieldType, len(cols)) for i, col := range cols { if col == nil { continue } if col.State != model.StatePublic { return nil, errInvalidColumnState.Gen("Cannot use none public column - %v", cols) } if col.IsPKHandleColumn(t.Meta()) { if mysql.HasUnsignedFlag(col.Flag) { v[i].SetUint64(uint64(h)) } else { v[i].SetInt64(h) } continue } colTps[col.ID] = &col.FieldType } row, err := tablecodec.DecodeRow(value, colTps) if err != nil { return nil, errors.Trace(err) } for i, col := range cols { if col == nil { continue } if col.State != model.StatePublic { // TODO: check this return nil, errInvalidColumnState.Gen("Cannot use none public column - %v", cols) } if col.IsPKHandleColumn(t.Meta()) { continue } ri, ok := row[col.ID] if !ok && mysql.HasNotNullFlag(col.Flag) { return nil, errors.New("Miss") } v[i] = ri } return v, nil }
func (s *InsertValues) initDefaultValues(ctx context.Context, t table.Table, row []interface{}, marked map[int]struct{}) error { var defaultValueCols []*column.Col for i, c := range t.Cols() { if row[i] != nil { // Column value is not nil, continue. continue } // If the nil value is evaluated in insert list, we will use nil except auto increment column. if _, ok := marked[i]; ok && !mysql.HasAutoIncrementFlag(c.Flag) && !mysql.HasTimestampFlag(c.Flag) { continue } if mysql.HasAutoIncrementFlag(c.Flag) { recordID, err := t.AllocAutoID() if err != nil { return errors.Trace(err) } row[i] = recordID if c.IsPKHandleColumn(t.Meta()) { // Notes: incompatible with mysql // MySQL will set last insert id to the first row, as follows: // `t(id int AUTO_INCREMENT, c1 int, PRIMARY KEY (id))` // `insert t (c1) values(1),(2),(3);` // Last insert id will be 1, not 3. variable.GetSessionVars(ctx).SetLastInsertID(uint64(recordID)) } } else { var value interface{} value, _, err := tables.GetColDefaultValue(ctx, &c.ColumnInfo) if err != nil { return errors.Trace(err) } row[i] = value } defaultValueCols = append(defaultValueCols, c) } if err := column.CastValues(ctx, row, defaultValueCols); err != nil { return errors.Trace(err) } return nil }
func (e *UpdateExec) getTableOffset(t table.Table) int { fields := e.SelectExec.Fields() i := 0 for i < len(fields) { field := fields[i] if field.Table.Name.L == t.Meta().Name.L { return i } for _, col := range field.Table.Columns { if col.State == model.StateDeleteOnly || col.State == model.StateDeleteReorganization { continue } i++ } } return 0 }
func (d *ddl) backfillColumn(ctx context.Context, t table.Table, columnInfo *model.ColumnInfo, handles []int64, reorgInfo *reorgInfo) error { var defaultVal types.Datum var err error if columnInfo.DefaultValue != nil { defaultVal, _, err = table.GetColDefaultValue(ctx, columnInfo) if err != nil { return errors.Trace(err) } } else if mysql.HasNotNullFlag(columnInfo.Flag) { defaultVal = table.GetZeroValue(columnInfo) } colMap := make(map[int64]*types.FieldType) for _, col := range t.Meta().Columns { colMap[col.ID] = &col.FieldType } var endIdx int for len(handles) > 0 { if len(handles) >= defaultSmallBatchCnt { endIdx = defaultSmallBatchCnt } else { endIdx = len(handles) } err = kv.RunInNewTxn(d.store, true, func(txn kv.Transaction) error { if err := d.isReorgRunnable(txn, ddlJobFlag); err != nil { return errors.Trace(err) } nextHandle, err1 := d.backfillColumnInTxn(t, columnInfo.ID, handles[:endIdx], colMap, defaultVal, txn) if err1 != nil { return errors.Trace(err1) } return errors.Trace(reorgInfo.UpdateHandle(txn, nextHandle)) }) if err != nil { return errors.Trace(err) } handles = handles[endIdx:] } return nil }
func (d *ddl) backfillTableIndex(t table.Table, indexInfo *model.IndexInfo, handles []int64, reorgInfo *reorgInfo) error { kvIdx := tables.NewIndex(t.Meta(), indexInfo) for len(handles) > 0 { endIdx := int(math.Min(float64(defaultSmallBatchCnt), float64(len(handles)))) err := kv.RunInNewTxn(d.store, true, func(txn kv.Transaction) error { if err1 := d.isReorgRunnable(txn, ddlJobFlag); err1 != nil { return errors.Trace(err1) } nextHandle, err1 := d.backfillIndexInTxn(t, kvIdx, handles[:endIdx], txn) if err1 != nil { return errors.Trace(err1) } // Update reorg next handle. return errors.Trace(reorgInfo.UpdateHandle(txn, nextHandle)) }) if err != nil { return errors.Trace(err) } handles = handles[endIdx:] } return nil }
func updateRecord(ctx context.Context, h int64, oldData, newData []types.Datum, assignFlag []bool, t table.Table, offset int, onDuplicateUpdate bool) error { cols := t.Cols() touched := make(map[int]bool, len(cols)) assignExists := false sc := ctx.GetSessionVars().StmtCtx var newHandle types.Datum for i, hasSetExpr := range assignFlag { if !hasSetExpr { if onDuplicateUpdate { newData[i] = oldData[i] } continue } if i < offset || i >= offset+len(cols) { // The assign expression is for another table, not this. continue } colIndex := i - offset col := cols[colIndex] if col.IsPKHandleColumn(t.Meta()) { newHandle = newData[i] } if mysql.HasAutoIncrementFlag(col.Flag) { if newData[i].IsNull() { return errors.Errorf("Column '%v' cannot be null", col.Name.O) } val, err := newData[i].ToInt64(sc) if err != nil { return errors.Trace(err) } t.RebaseAutoID(val, true) } touched[colIndex] = true assignExists = true } // If no assign list for this table, no need to update. if !assignExists { return nil } // Check whether new value is valid. if err := table.CastValues(ctx, newData, cols, false); err != nil { return errors.Trace(err) } if err := table.CheckNotNull(cols, newData); err != nil { return errors.Trace(err) } // If row is not changed, we should do nothing. rowChanged := false for i := range oldData { if !touched[i] { continue } n, err := newData[i].CompareDatum(sc, oldData[i]) if err != nil { return errors.Trace(err) } if n != 0 { rowChanged = true break } } if !rowChanged { // See https://dev.mysql.com/doc/refman/5.7/en/mysql-real-connect.html CLIENT_FOUND_ROWS if ctx.GetSessionVars().ClientCapability&mysql.ClientFoundRows > 0 { sc.AddAffectedRows(1) } return nil } var err error if !newHandle.IsNull() { err = t.RemoveRecord(ctx, h, oldData) if err != nil { return errors.Trace(err) } _, err = t.AddRecord(ctx, newData) } else { // Update record to new value and update index. err = t.UpdateRecord(ctx, h, oldData, newData, touched) } if err != nil { return errors.Trace(err) } dirtyDB := getDirtyDB(ctx) tid := t.Meta().ID dirtyDB.deleteRow(tid, h) dirtyDB.addRow(tid, h, newData) // Record affected rows. if !onDuplicateUpdate { sc.AddAffectedRows(1) } else { sc.AddAffectedRows(2) } return nil }
func (d *ddl) dropTableIndex(t table.Table, indexInfo *model.IndexInfo) error { prefix := tablecodec.EncodeTableIndexPrefix(t.Meta().ID, indexInfo.ID) err := d.delKeysWithPrefix(prefix) return errors.Trace(err) }
func updateRecord(ctx context.Context, h int64, data []interface{}, t table.Table, updateColumns map[int]*expression.Assignment, evalMap map[interface{}]interface{}, offset int, onDuplicateUpdate bool) error { if err := t.LockRow(ctx, h); err != nil { return errors.Trace(err) } cols := t.Cols() oldData := data newData := make([]interface{}, len(cols)) touched := make(map[int]bool, len(cols)) copy(newData, oldData) assignExists := false var newHandle interface{} for i, asgn := range updateColumns { if i < offset || i >= offset+len(cols) { // The assign expression is for another table, not this. continue } val, err := asgn.Expr.Eval(ctx, evalMap) if err != nil { return errors.Trace(err) } colIndex := i - offset col := cols[colIndex] if col.IsPKHandleColumn(t.Meta()) { newHandle = val } touched[colIndex] = true newData[colIndex] = val assignExists = true } // If no assign list for this table, no need to update. if !assignExists { return nil } // Check whether new value is valid. if err := column.CastValues(ctx, newData, cols); err != nil { return errors.Trace(err) } if err := column.CheckNotNull(cols, newData); err != nil { return errors.Trace(err) } // If row is not changed, we should do nothing. rowChanged := false for i := range oldData { if !touched[i] { continue } n, err := types.Compare(newData[i], oldData[i]) if err != nil { return errors.Trace(err) } if n != 0 { rowChanged = true break } } if !rowChanged { // See: https://dev.mysql.com/doc/refman/5.7/en/mysql-real-connect.html CLIENT_FOUND_ROWS if variable.GetSessionVars(ctx).ClientCapability&mysql.ClientFoundRows > 0 { variable.GetSessionVars(ctx).AddAffectedRows(1) } return nil } var err error if newHandle != nil { err = t.RemoveRecord(ctx, h, oldData) if err != nil { return errors.Trace(err) } _, err = t.AddRecord(ctx, newData) } else { // Update record to new value and update index. err = t.UpdateRecord(ctx, h, oldData, newData, touched) } if err != nil { return errors.Trace(err) } // Record affected rows. if !onDuplicateUpdate { variable.GetSessionVars(ctx).AddAffectedRows(1) } else { variable.GetSessionVars(ctx).AddAffectedRows(2) } return nil }
func (s *testSuite) testIndex(c *C, tb table.Table, idx table.Index) { txn, err := s.store.Begin() c.Assert(err, IsNil) err = CompareIndexData(txn, tb, idx) c.Assert(err, IsNil) cnt, err := GetIndexRecordsCount(txn, idx, nil) c.Assert(err, IsNil) c.Assert(cnt, Equals, int64(2)) // set data to: // index data (handle, data): (1, 10), (2, 20), (3, 30) // table data (handle, data): (1, 10), (2, 20), (4, 40) err = idx.Create(txn, types.MakeDatums(int64(30)), 3) c.Assert(err, IsNil) key := tablecodec.EncodeRowKey(tb.Meta().ID, codec.EncodeInt(nil, 4)) setColValue(c, txn, key, types.NewDatum(int64(40))) err = txn.Commit() c.Assert(err, IsNil) txn, err = s.store.Begin() c.Assert(err, IsNil) err = CompareIndexData(txn, tb, idx) c.Assert(err, NotNil) record1 := &RecordData{Handle: int64(3), Values: types.MakeDatums(int64(30))} diffMsg := newDiffRetError("index", record1, nil) c.Assert(err.Error(), DeepEquals, diffMsg) // set data to: // index data (handle, data): (1, 10), (2, 20), (3, 30), (4, 40) // table data (handle, data): (1, 10), (2, 20), (4, 40), (3, 31) err = idx.Create(txn, types.MakeDatums(int64(40)), 4) c.Assert(err, IsNil) key = tablecodec.EncodeRowKey(tb.Meta().ID, codec.EncodeInt(nil, 3)) setColValue(c, txn, key, types.NewDatum(int64(31))) err = txn.Commit() c.Assert(err, IsNil) txn, err = s.store.Begin() c.Assert(err, IsNil) err = CompareIndexData(txn, tb, idx) c.Assert(err, NotNil) record2 := &RecordData{Handle: int64(3), Values: types.MakeDatums(int64(31))} diffMsg = newDiffRetError("index", record1, record2) c.Assert(err.Error(), DeepEquals, diffMsg) // set data to: // index data (handle, data): (1, 10), (2, 20), (3, 30), (4, 40) // table data (handle, data): (1, 10), (2, 20), (4, 40), (5, 30) key = tablecodec.EncodeRowKey(tb.Meta().ID, codec.EncodeInt(nil, 3)) txn.Delete(key) key = tablecodec.EncodeRowKey(tb.Meta().ID, codec.EncodeInt(nil, 5)) setColValue(c, txn, key, types.NewDatum(int64(30))) err = txn.Commit() c.Assert(err, IsNil) txn, err = s.store.Begin() c.Assert(err, IsNil) err = checkRecordAndIndex(txn, tb, idx) c.Assert(err, NotNil) record2 = &RecordData{Handle: int64(5), Values: types.MakeDatums(int64(30))} diffMsg = newDiffRetError("index", record1, record2) c.Assert(err.Error(), DeepEquals, diffMsg) // set data to: // index data (handle, data): (1, 10), (2, 20), (3, 30), (4, 40) // table data (handle, data): (1, 10), (2, 20), (3, 30) key = tablecodec.EncodeRowKey(tb.Meta().ID, codec.EncodeInt(nil, 4)) txn.Delete(key) key = tablecodec.EncodeRowKey(tb.Meta().ID, codec.EncodeInt(nil, 3)) setColValue(c, txn, key, types.NewDatum(int64(30))) err = txn.Commit() c.Assert(err, IsNil) txn, err = s.store.Begin() c.Assert(err, IsNil) err = CompareIndexData(txn, tb, idx) c.Assert(err, NotNil) record1 = &RecordData{Handle: int64(4), Values: types.MakeDatums(int64(40))} diffMsg = newDiffRetError("index", record1, nil) c.Assert(err.Error(), DeepEquals, diffMsg) // set data to: // index data (handle, data): (1, 10), (2, 20), (3, 30) // table data (handle, data): (1, 10), (2, 20), (3, 30), (4, 40) err = idx.Delete(txn, types.MakeDatums(int64(40)), 4) c.Assert(err, IsNil) key = tablecodec.EncodeRowKey(tb.Meta().ID, codec.EncodeInt(nil, 4)) setColValue(c, txn, key, types.NewDatum(int64(40))) err = txn.Commit() c.Assert(err, IsNil) txn, err = s.store.Begin() c.Assert(err, IsNil) err = CompareIndexData(txn, tb, idx) c.Assert(err, NotNil) diffMsg = newDiffRetError("index", nil, record1) c.Assert(err.Error(), DeepEquals, diffMsg) }
func (d *ddl) dropTableData(t table.Table) error { err := d.delKeysWithPrefix(tablecodec.EncodeTablePrefix(t.Meta().ID)) return errors.Trace(err) }
func (s *testIndexChangeSuite) TestIndexChange(c *C) { defer testleak.AfterTest(c)() d := newDDL(s.store, nil, nil, testLease) // create table t (c1 int primary key, c2 int); tblInfo := testTableInfo(c, d, "t", 2) tblInfo.Columns[0].Flag = mysql.PriKeyFlag | mysql.NotNullFlag tblInfo.PKIsHandle = true ctx := testNewContext(c, d) _, err := ctx.GetTxn(true) c.Assert(err, IsNil) testCreateTable(c, ctx, d, s.dbInfo, tblInfo) originTable := testGetTable(c, d, s.dbInfo.ID, tblInfo.ID) // insert t values (1, 1), (2, 2), (3, 3) _, err = originTable.AddRecord(ctx, types.MakeDatums(1, 1)) c.Assert(err, IsNil) _, err = originTable.AddRecord(ctx, types.MakeDatums(2, 2)) c.Assert(err, IsNil) _, err = originTable.AddRecord(ctx, types.MakeDatums(3, 3)) c.Assert(err, IsNil) err = ctx.CommitTxn() c.Assert(err, IsNil) tc := &testDDLCallback{} // set up hook prevState := model.StateNone var ( deleteOnlyTable table.Table writeOnlyTable table.Table publicTable table.Table checkErr error ) tc.onJobUpdated = func(job *model.Job) { if job.SchemaState == prevState { return } ctx1 := testNewContext(c, d) prevState = job.SchemaState var err error switch job.SchemaState { case model.StateDeleteOnly: deleteOnlyTable, err = getCurrentTable(d, s.dbInfo.ID, tblInfo.ID) if err != nil { checkErr = errors.Trace(err) } case model.StateWriteOnly: writeOnlyTable, err = getCurrentTable(d, s.dbInfo.ID, tblInfo.ID) if err != nil { checkErr = errors.Trace(err) } err = s.checkAddWriteOnly(d, ctx1, deleteOnlyTable, writeOnlyTable) if err != nil { checkErr = errors.Trace(err) } case model.StatePublic: publicTable, err = getCurrentTable(d, s.dbInfo.ID, tblInfo.ID) if err != nil { checkErr = errors.Trace(err) } err = s.checkAddPublic(d, ctx1, writeOnlyTable, publicTable) if err != nil { checkErr = errors.Trace(err) } } } d.setHook(tc) testCreateIndex(c, ctx, d, s.dbInfo, originTable.Meta(), false, "c2", "c2") c.Check(errors.ErrorStack(checkErr), Equals, "") d.Stop() prevState = model.StatePublic var noneTable table.Table tc.onJobUpdated = func(job *model.Job) { if job.SchemaState == prevState { return } prevState = job.SchemaState var err error switch job.SchemaState { case model.StateWriteOnly: writeOnlyTable, err = getCurrentTable(d, s.dbInfo.ID, tblInfo.ID) if err != nil { checkErr = errors.Trace(err) } err = s.checkDropWriteOnly(d, ctx, publicTable, writeOnlyTable) if err != nil { checkErr = errors.Trace(err) } case model.StateDeleteOnly: deleteOnlyTable, err = getCurrentTable(d, s.dbInfo.ID, tblInfo.ID) if err != nil { checkErr = errors.Trace(err) } err = s.checkDropDeleteOnly(d, ctx, writeOnlyTable, deleteOnlyTable) if err != nil { checkErr = errors.Trace(err) } case model.StateNone: noneTable, err = getCurrentTable(d, s.dbInfo.ID, tblInfo.ID) if err != nil { checkErr = errors.Trace(err) } if len(noneTable.Indices()) != 0 { checkErr = errors.New("index should have been dropped") } } } d.start() testDropIndex(c, ctx, d, s.dbInfo, publicTable.Meta(), "c2") c.Check(errors.ErrorStack(checkErr), Equals, "") }