github.com/insionng/yougam@v0.0.0-20170714101924-2bc18d833463/libraries/pingcap/tidb/ddl/column.go (about)

     1  // Copyright 2015 PingCAP, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package ddl
    15  
    16  import (
    17  	"github.com/insionng/yougam/libraries/juju/errors"
    18  	"github.com/insionng/yougam/libraries/ngaut/log"
    19  	"github.com/insionng/yougam/libraries/pingcap/tidb/ast"
    20  	"github.com/insionng/yougam/libraries/pingcap/tidb/column"
    21  	"github.com/insionng/yougam/libraries/pingcap/tidb/infoschema"
    22  	"github.com/insionng/yougam/libraries/pingcap/tidb/kv"
    23  	"github.com/insionng/yougam/libraries/pingcap/tidb/meta"
    24  	"github.com/insionng/yougam/libraries/pingcap/tidb/model"
    25  	"github.com/insionng/yougam/libraries/pingcap/tidb/table"
    26  	"github.com/insionng/yougam/libraries/pingcap/tidb/table/tables"
    27  	"github.com/insionng/yougam/libraries/pingcap/tidb/terror"
    28  )
    29  
    30  func (d *ddl) adjustColumnOffset(columns []*model.ColumnInfo, indices []*model.IndexInfo, offset int, added bool) {
    31  	offsetChanged := make(map[int]int)
    32  	if added {
    33  		for i := offset + 1; i < len(columns); i++ {
    34  			offsetChanged[columns[i].Offset] = i
    35  			columns[i].Offset = i
    36  		}
    37  		columns[offset].Offset = offset
    38  	} else {
    39  		for i := offset + 1; i < len(columns); i++ {
    40  			offsetChanged[columns[i].Offset] = i - 1
    41  			columns[i].Offset = i - 1
    42  		}
    43  		columns[offset].Offset = len(columns) - 1
    44  	}
    45  
    46  	// TODO: index can't cover the add/remove column with offset now, we may check this later.
    47  
    48  	// Update index column offset info.
    49  	for _, idx := range indices {
    50  		for _, col := range idx.Columns {
    51  			newOffset, ok := offsetChanged[col.Offset]
    52  			if ok {
    53  				col.Offset = newOffset
    54  			}
    55  		}
    56  	}
    57  }
    58  
    59  func (d *ddl) addColumn(tblInfo *model.TableInfo, colInfo *model.ColumnInfo, pos *ast.ColumnPosition) (*model.ColumnInfo, int, error) {
    60  	// Check column name duplicate.
    61  	cols := tblInfo.Columns
    62  	position := len(cols)
    63  
    64  	// Get column position.
    65  	if pos.Tp == ast.ColumnPositionFirst {
    66  		position = 0
    67  	} else if pos.Tp == ast.ColumnPositionAfter {
    68  		c := findCol(cols, pos.RelativeColumn.Name.L)
    69  		if c == nil {
    70  			return nil, 0, infoschema.ErrColumnNotExists.Gen("no such column: %v", pos.RelativeColumn)
    71  		}
    72  
    73  		// Insert position is after the mentioned column.
    74  		position = c.Offset + 1
    75  	}
    76  
    77  	colInfo.State = model.StateNone
    78  	// To support add column asynchronous, we should mark its offset as the last column.
    79  	// So that we can use origin column offset to get value from row.
    80  	colInfo.Offset = len(cols)
    81  
    82  	// Insert col into the right place of the column list.
    83  	newCols := make([]*model.ColumnInfo, 0, len(cols)+1)
    84  	newCols = append(newCols, cols[:position]...)
    85  	newCols = append(newCols, colInfo)
    86  	newCols = append(newCols, cols[position:]...)
    87  
    88  	tblInfo.Columns = newCols
    89  	return colInfo, position, nil
    90  }
    91  
    92  func (d *ddl) onAddColumn(t *meta.Meta, job *model.Job) error {
    93  	schemaID := job.SchemaID
    94  	tblInfo, err := d.getTableInfo(t, job)
    95  	if err != nil {
    96  		return errors.Trace(err)
    97  	}
    98  
    99  	col := &model.ColumnInfo{}
   100  	pos := &ast.ColumnPosition{}
   101  	offset := 0
   102  	err = job.DecodeArgs(col, pos, &offset)
   103  	if err != nil {
   104  		job.State = model.JobCancelled
   105  		return errors.Trace(err)
   106  	}
   107  
   108  	columnInfo := findCol(tblInfo.Columns, col.Name.L)
   109  	if columnInfo != nil {
   110  		if columnInfo.State == model.StatePublic {
   111  			// we already have a column with same column name
   112  			job.State = model.JobCancelled
   113  			return infoschema.ErrColumnExists.Gen("ADD COLUMN: column already exist %s", col.Name.L)
   114  		}
   115  	} else {
   116  		columnInfo, offset, err = d.addColumn(tblInfo, col, pos)
   117  		if err != nil {
   118  			job.State = model.JobCancelled
   119  			return errors.Trace(err)
   120  		}
   121  
   122  		// Set offset arg to job.
   123  		if offset != 0 {
   124  			job.Args = []interface{}{columnInfo, pos, offset}
   125  		}
   126  	}
   127  
   128  	_, err = t.GenSchemaVersion()
   129  	if err != nil {
   130  		return errors.Trace(err)
   131  	}
   132  
   133  	switch columnInfo.State {
   134  	case model.StateNone:
   135  		// none -> delete only
   136  		job.SchemaState = model.StateDeleteOnly
   137  		columnInfo.State = model.StateDeleteOnly
   138  		err = t.UpdateTable(schemaID, tblInfo)
   139  		return errors.Trace(err)
   140  	case model.StateDeleteOnly:
   141  		// delete only -> write only
   142  		job.SchemaState = model.StateWriteOnly
   143  		columnInfo.State = model.StateWriteOnly
   144  		err = t.UpdateTable(schemaID, tblInfo)
   145  		return errors.Trace(err)
   146  	case model.StateWriteOnly:
   147  		// write only -> reorganization
   148  		job.SchemaState = model.StateWriteReorganization
   149  		columnInfo.State = model.StateWriteReorganization
   150  		// initialize SnapshotVer to 0 for later reorganization check.
   151  		job.SnapshotVer = 0
   152  		err = t.UpdateTable(schemaID, tblInfo)
   153  		return errors.Trace(err)
   154  	case model.StateWriteReorganization:
   155  		// reorganization -> public
   156  		// get the current version for reorganization if we don't have
   157  		reorgInfo, err := d.getReorgInfo(t, job)
   158  		if err != nil || reorgInfo.first {
   159  			// if we run reorg firstly, we should update the job snapshot version
   160  			// and then run the reorg next time.
   161  			return errors.Trace(err)
   162  		}
   163  
   164  		tbl, err := d.getTable(schemaID, tblInfo)
   165  		if err != nil {
   166  			return errors.Trace(err)
   167  		}
   168  
   169  		err = d.runReorgJob(func() error {
   170  			return d.backfillColumn(tbl, columnInfo, reorgInfo)
   171  		})
   172  
   173  		if terror.ErrorEqual(err, errWaitReorgTimeout) {
   174  			// if timeout, we should return, check for the owner and re-wait job done.
   175  			return nil
   176  		}
   177  		if err != nil {
   178  			return errors.Trace(err)
   179  		}
   180  
   181  		// Adjust column offset.
   182  		d.adjustColumnOffset(tblInfo.Columns, tblInfo.Indices, offset, true)
   183  
   184  		columnInfo.State = model.StatePublic
   185  
   186  		if err = t.UpdateTable(schemaID, tblInfo); err != nil {
   187  			return errors.Trace(err)
   188  		}
   189  
   190  		// finish this job
   191  		job.SchemaState = model.StatePublic
   192  		job.State = model.JobDone
   193  		return nil
   194  	default:
   195  		return ErrInvalidColumnState.Gen("invalid column state %v", columnInfo.State)
   196  	}
   197  }
   198  
   199  func (d *ddl) onDropColumn(t *meta.Meta, job *model.Job) error {
   200  	schemaID := job.SchemaID
   201  	tblInfo, err := d.getTableInfo(t, job)
   202  	if err != nil {
   203  		return errors.Trace(err)
   204  	}
   205  
   206  	var colName model.CIStr
   207  	err = job.DecodeArgs(&colName)
   208  	if err != nil {
   209  		job.State = model.JobCancelled
   210  		return errors.Trace(err)
   211  	}
   212  
   213  	colInfo := findCol(tblInfo.Columns, colName.L)
   214  	if colInfo == nil {
   215  		job.State = model.JobCancelled
   216  		return infoschema.ErrColumnNotExists.Gen("column %s doesn't exist", colName)
   217  	}
   218  
   219  	if len(tblInfo.Columns) == 1 {
   220  		job.State = model.JobCancelled
   221  		return ErrCantRemoveAllFields.Gen("can't drop only column %s in table %s",
   222  			colName, tblInfo.Name)
   223  	}
   224  
   225  	// we don't support drop column with index covered now.
   226  	// we must drop the index first, then drop the column.
   227  	for _, indexInfo := range tblInfo.Indices {
   228  		for _, col := range indexInfo.Columns {
   229  			if col.Name.L == colName.L {
   230  				job.State = model.JobCancelled
   231  				return errCantDropColWithIndex.Gen("can't drop column %s with index %s covered now",
   232  					colName, indexInfo.Name)
   233  			}
   234  		}
   235  	}
   236  
   237  	_, err = t.GenSchemaVersion()
   238  	if err != nil {
   239  		return errors.Trace(err)
   240  	}
   241  
   242  	switch colInfo.State {
   243  	case model.StatePublic:
   244  		// public -> write only
   245  		job.SchemaState = model.StateWriteOnly
   246  		colInfo.State = model.StateWriteOnly
   247  
   248  		// set this column's offset to the last and reset all following columns' offset
   249  		d.adjustColumnOffset(tblInfo.Columns, tblInfo.Indices, colInfo.Offset, false)
   250  
   251  		err = t.UpdateTable(schemaID, tblInfo)
   252  		return errors.Trace(err)
   253  	case model.StateWriteOnly:
   254  		// write only -> delete only
   255  		job.SchemaState = model.StateDeleteOnly
   256  		colInfo.State = model.StateDeleteOnly
   257  		err = t.UpdateTable(schemaID, tblInfo)
   258  		return errors.Trace(err)
   259  	case model.StateDeleteOnly:
   260  		// delete only -> reorganization
   261  		job.SchemaState = model.StateDeleteReorganization
   262  		colInfo.State = model.StateDeleteReorganization
   263  		// initialize SnapshotVer to 0 for later reorganization check.
   264  		job.SnapshotVer = 0
   265  		err = t.UpdateTable(schemaID, tblInfo)
   266  		return errors.Trace(err)
   267  	case model.StateDeleteReorganization:
   268  		// reorganization -> absent
   269  		reorgInfo, err := d.getReorgInfo(t, job)
   270  		if err != nil || reorgInfo.first {
   271  			// if we run reorg firstly, we should update the job snapshot version
   272  			// and then run the reorg next time.
   273  			return errors.Trace(err)
   274  		}
   275  
   276  		tbl, err := d.getTable(schemaID, tblInfo)
   277  		if err != nil {
   278  			return errors.Trace(err)
   279  		}
   280  
   281  		err = d.runReorgJob(func() error {
   282  			return d.dropTableColumn(tbl, colInfo, reorgInfo)
   283  		})
   284  
   285  		if terror.ErrorEqual(err, errWaitReorgTimeout) {
   286  			// if timeout, we should return, check for the owner and re-wait job done.
   287  			return nil
   288  		}
   289  		if err != nil {
   290  			return errors.Trace(err)
   291  		}
   292  
   293  		// all reorganization jobs done, drop this column
   294  		newColumns := make([]*model.ColumnInfo, 0, len(tblInfo.Columns))
   295  		for _, col := range tblInfo.Columns {
   296  			if col.Name.L != colName.L {
   297  				newColumns = append(newColumns, col)
   298  			}
   299  		}
   300  		tblInfo.Columns = newColumns
   301  		if err = t.UpdateTable(schemaID, tblInfo); err != nil {
   302  			return errors.Trace(err)
   303  		}
   304  
   305  		// finish this job
   306  		job.SchemaState = model.StateNone
   307  		job.State = model.JobDone
   308  		return nil
   309  	default:
   310  		return ErrInvalidTableState.Gen("invalid table state %v", tblInfo.State)
   311  	}
   312  }
   313  
   314  // How to backfill column data in reorganization state?
   315  //  1. Generate a snapshot with special version.
   316  //  2. Traverse the snapshot, get every row in the table.
   317  //  3. For one row, if the row has been already deleted, skip to next row.
   318  //  4. If not deleted, check whether column data has existed, if existed, skip to next row.
   319  //  5. If column data doesn't exist, backfill the column with default value and then continue to handle next row.
   320  func (d *ddl) backfillColumn(t table.Table, columnInfo *model.ColumnInfo, reorgInfo *reorgInfo) error {
   321  	seekHandle := reorgInfo.Handle
   322  	version := reorgInfo.SnapshotVer
   323  
   324  	for {
   325  		handles, err := d.getSnapshotRows(t, version, seekHandle)
   326  		if err != nil {
   327  			return errors.Trace(err)
   328  		} else if len(handles) == 0 {
   329  			return nil
   330  		}
   331  
   332  		seekHandle = handles[len(handles)-1] + 1
   333  		err = d.backfillColumnData(t, columnInfo, handles, reorgInfo)
   334  		if err != nil {
   335  			return errors.Trace(err)
   336  		}
   337  	}
   338  }
   339  
   340  func (d *ddl) backfillColumnData(t table.Table, columnInfo *model.ColumnInfo, handles []int64, reorgInfo *reorgInfo) error {
   341  	for _, handle := range handles {
   342  		log.Info("[ddl] backfill column...", handle)
   343  
   344  		err := kv.RunInNewTxn(d.store, true, func(txn kv.Transaction) error {
   345  			if err := d.isReorgRunnable(txn); err != nil {
   346  				return errors.Trace(err)
   347  			}
   348  
   349  			// First check if row exists.
   350  			exist, err := checkRowExist(txn, t, handle)
   351  			if err != nil {
   352  				return errors.Trace(err)
   353  			} else if !exist {
   354  				// If row doesn't exist, skip it.
   355  				return nil
   356  			}
   357  
   358  			backfillKey := t.RecordKey(handle, &column.Col{ColumnInfo: *columnInfo})
   359  			backfillValue, err := txn.Get(backfillKey)
   360  			if err != nil && !kv.IsErrNotFound(err) {
   361  				return errors.Trace(err)
   362  			}
   363  			if backfillValue != nil {
   364  				return nil
   365  			}
   366  
   367  			value, _, err := table.GetColDefaultValue(nil, columnInfo)
   368  			if err != nil {
   369  				return errors.Trace(err)
   370  			}
   371  
   372  			// must convert to the column field type.
   373  			v, err := value.ConvertTo(&columnInfo.FieldType)
   374  			if err != nil {
   375  				return errors.Trace(err)
   376  			}
   377  
   378  			err = lockRow(txn, t, handle)
   379  			if err != nil {
   380  				return errors.Trace(err)
   381  			}
   382  
   383  			err = tables.SetColValue(txn, backfillKey, v)
   384  			if err != nil {
   385  				return errors.Trace(err)
   386  			}
   387  
   388  			return errors.Trace(reorgInfo.UpdateHandle(txn, handle))
   389  		})
   390  
   391  		if err != nil {
   392  			return errors.Trace(err)
   393  		}
   394  	}
   395  
   396  	return nil
   397  }
   398  
   399  func (d *ddl) dropTableColumn(t table.Table, colInfo *model.ColumnInfo, reorgInfo *reorgInfo) error {
   400  	version := reorgInfo.SnapshotVer
   401  	seekHandle := reorgInfo.Handle
   402  
   403  	col := &column.Col{ColumnInfo: *colInfo}
   404  	for {
   405  		handles, err := d.getSnapshotRows(t, version, seekHandle)
   406  		if err != nil {
   407  			return errors.Trace(err)
   408  		} else if len(handles) == 0 {
   409  			return nil
   410  		}
   411  
   412  		seekHandle = handles[len(handles)-1] + 1
   413  
   414  		err = kv.RunInNewTxn(d.store, true, func(txn kv.Transaction) error {
   415  			if err1 := d.isReorgRunnable(txn); err1 != nil {
   416  				return errors.Trace(err1)
   417  			}
   418  
   419  			var h int64
   420  			for _, h = range handles {
   421  				key := t.RecordKey(h, col)
   422  				err1 := txn.Delete(key)
   423  				if err1 != nil && !terror.ErrorEqual(err1, kv.ErrNotExist) {
   424  					return errors.Trace(err1)
   425  				}
   426  			}
   427  			return errors.Trace(reorgInfo.UpdateHandle(txn, h))
   428  		})
   429  		if err != nil {
   430  			return errors.Trace(err)
   431  		}
   432  	}
   433  }