mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-11-03 08:02:36 +09:00 
			
		
		
		
	* Dropped unused codekit config * Integrated dynamic and static bindata for public * Ignore public bindata * Add a general generate make task * Integrated flexible public assets into web command * Updated vendoring, added all missiong govendor deps * Made the linter happy with the bindata and dynamic code * Moved public bindata definition to modules directory * Ignoring the new bindata path now * Updated to the new public modules import path * Updated public bindata command and drop the new prefix
		
			
				
	
	
		
			474 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			474 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright 2015 PingCAP, Inc.
 | 
						|
//
 | 
						|
// Licensed under the Apache License, Version 2.0 (the "License");
 | 
						|
// you may not use this file except in compliance with the License.
 | 
						|
// You may obtain a copy of the License at
 | 
						|
//
 | 
						|
//     http://www.apache.org/licenses/LICENSE-2.0
 | 
						|
//
 | 
						|
// Unless required by applicable law or agreed to in writing, software
 | 
						|
// distributed under the License is distributed on an "AS IS" BASIS,
 | 
						|
// See the License for the specific language governing permissions and
 | 
						|
// limitations under the License.
 | 
						|
 | 
						|
package ddl
 | 
						|
 | 
						|
import (
 | 
						|
	"github.com/juju/errors"
 | 
						|
	"github.com/ngaut/log"
 | 
						|
	"github.com/pingcap/tidb/ast"
 | 
						|
	"github.com/pingcap/tidb/kv"
 | 
						|
	"github.com/pingcap/tidb/meta"
 | 
						|
	"github.com/pingcap/tidb/model"
 | 
						|
	"github.com/pingcap/tidb/mysql"
 | 
						|
	"github.com/pingcap/tidb/table"
 | 
						|
	"github.com/pingcap/tidb/table/tables"
 | 
						|
	"github.com/pingcap/tidb/terror"
 | 
						|
	"github.com/pingcap/tidb/util"
 | 
						|
	"github.com/pingcap/tidb/util/types"
 | 
						|
)
 | 
						|
 | 
						|
func buildIndexInfo(tblInfo *model.TableInfo, unique bool, indexName model.CIStr, indexID int64, idxColNames []*ast.IndexColName) (*model.IndexInfo, error) {
 | 
						|
	// build offsets
 | 
						|
	idxColumns := make([]*model.IndexColumn, 0, len(idxColNames))
 | 
						|
	for _, ic := range idxColNames {
 | 
						|
		col := findCol(tblInfo.Columns, ic.Column.Name.O)
 | 
						|
		if col == nil {
 | 
						|
			return nil, errors.Errorf("CREATE INDEX: column does not exist: %s", ic.Column.Name.O)
 | 
						|
		}
 | 
						|
 | 
						|
		idxColumns = append(idxColumns, &model.IndexColumn{
 | 
						|
			Name:   col.Name,
 | 
						|
			Offset: col.Offset,
 | 
						|
			Length: ic.Length,
 | 
						|
		})
 | 
						|
	}
 | 
						|
	// create index info
 | 
						|
	idxInfo := &model.IndexInfo{
 | 
						|
		ID:      indexID,
 | 
						|
		Name:    indexName,
 | 
						|
		Columns: idxColumns,
 | 
						|
		Unique:  unique,
 | 
						|
		State:   model.StateNone,
 | 
						|
	}
 | 
						|
	return idxInfo, nil
 | 
						|
}
 | 
						|
 | 
						|
func addIndexColumnFlag(tblInfo *model.TableInfo, indexInfo *model.IndexInfo) {
 | 
						|
	col := indexInfo.Columns[0]
 | 
						|
 | 
						|
	if indexInfo.Unique && len(indexInfo.Columns) == 1 {
 | 
						|
		tblInfo.Columns[col.Offset].Flag |= mysql.UniqueKeyFlag
 | 
						|
	} else {
 | 
						|
		tblInfo.Columns[col.Offset].Flag |= mysql.MultipleKeyFlag
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func dropIndexColumnFlag(tblInfo *model.TableInfo, indexInfo *model.IndexInfo) {
 | 
						|
	col := indexInfo.Columns[0]
 | 
						|
 | 
						|
	if indexInfo.Unique && len(indexInfo.Columns) == 1 {
 | 
						|
		tblInfo.Columns[col.Offset].Flag &= ^uint(mysql.UniqueKeyFlag)
 | 
						|
	} else {
 | 
						|
		tblInfo.Columns[col.Offset].Flag &= ^uint(mysql.MultipleKeyFlag)
 | 
						|
	}
 | 
						|
 | 
						|
	// other index may still cover this col
 | 
						|
	for _, index := range tblInfo.Indices {
 | 
						|
		if index.Name.L == indexInfo.Name.L {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		if index.Columns[0].Name.L != col.Name.L {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		addIndexColumnFlag(tblInfo, index)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (d *ddl) onCreateIndex(t *meta.Meta, job *model.Job) error {
 | 
						|
	schemaID := job.SchemaID
 | 
						|
	tblInfo, err := d.getTableInfo(t, job)
 | 
						|
	if err != nil {
 | 
						|
		return errors.Trace(err)
 | 
						|
	}
 | 
						|
 | 
						|
	var (
 | 
						|
		unique      bool
 | 
						|
		indexName   model.CIStr
 | 
						|
		indexID     int64
 | 
						|
		idxColNames []*ast.IndexColName
 | 
						|
	)
 | 
						|
 | 
						|
	err = job.DecodeArgs(&unique, &indexName, &indexID, &idxColNames)
 | 
						|
	if err != nil {
 | 
						|
		job.State = model.JobCancelled
 | 
						|
		return errors.Trace(err)
 | 
						|
	}
 | 
						|
 | 
						|
	var indexInfo *model.IndexInfo
 | 
						|
	for _, idx := range tblInfo.Indices {
 | 
						|
		if idx.Name.L == indexName.L {
 | 
						|
			if idx.State == model.StatePublic {
 | 
						|
				// we already have a index with same index name
 | 
						|
				job.State = model.JobCancelled
 | 
						|
				return errors.Errorf("CREATE INDEX: index already exist %s", indexName)
 | 
						|
			}
 | 
						|
 | 
						|
			indexInfo = idx
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if indexInfo == nil {
 | 
						|
		indexInfo, err = buildIndexInfo(tblInfo, unique, indexName, indexID, idxColNames)
 | 
						|
		if err != nil {
 | 
						|
			job.State = model.JobCancelled
 | 
						|
			return errors.Trace(err)
 | 
						|
		}
 | 
						|
		tblInfo.Indices = append(tblInfo.Indices, indexInfo)
 | 
						|
	}
 | 
						|
 | 
						|
	_, err = t.GenSchemaVersion()
 | 
						|
	if err != nil {
 | 
						|
		return errors.Trace(err)
 | 
						|
	}
 | 
						|
 | 
						|
	switch indexInfo.State {
 | 
						|
	case model.StateNone:
 | 
						|
		// none -> delete only
 | 
						|
		job.SchemaState = model.StateDeleteOnly
 | 
						|
		indexInfo.State = model.StateDeleteOnly
 | 
						|
		err = t.UpdateTable(schemaID, tblInfo)
 | 
						|
		return errors.Trace(err)
 | 
						|
	case model.StateDeleteOnly:
 | 
						|
		// delete only -> write only
 | 
						|
		job.SchemaState = model.StateWriteOnly
 | 
						|
		indexInfo.State = model.StateWriteOnly
 | 
						|
		err = t.UpdateTable(schemaID, tblInfo)
 | 
						|
		return errors.Trace(err)
 | 
						|
	case model.StateWriteOnly:
 | 
						|
		// write only -> reorganization
 | 
						|
		job.SchemaState = model.StateWriteReorganization
 | 
						|
		indexInfo.State = model.StateWriteReorganization
 | 
						|
		// initialize SnapshotVer to 0 for later reorganization check.
 | 
						|
		job.SnapshotVer = 0
 | 
						|
		err = t.UpdateTable(schemaID, tblInfo)
 | 
						|
		return errors.Trace(err)
 | 
						|
	case model.StateWriteReorganization:
 | 
						|
		// reorganization -> public
 | 
						|
		reorgInfo, err := d.getReorgInfo(t, job)
 | 
						|
		if err != nil || reorgInfo.first {
 | 
						|
			// if we run reorg firstly, we should update the job snapshot version
 | 
						|
			// and then run the reorg next time.
 | 
						|
			return errors.Trace(err)
 | 
						|
		}
 | 
						|
 | 
						|
		var tbl table.Table
 | 
						|
		tbl, err = d.getTable(schemaID, tblInfo)
 | 
						|
		if err != nil {
 | 
						|
			return errors.Trace(err)
 | 
						|
		}
 | 
						|
 | 
						|
		err = d.runReorgJob(func() error {
 | 
						|
			return d.addTableIndex(tbl, indexInfo, reorgInfo)
 | 
						|
		})
 | 
						|
 | 
						|
		if terror.ErrorEqual(err, errWaitReorgTimeout) {
 | 
						|
			// if timeout, we should return, check for the owner and re-wait job done.
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
		if err != nil {
 | 
						|
			return errors.Trace(err)
 | 
						|
		}
 | 
						|
 | 
						|
		indexInfo.State = model.StatePublic
 | 
						|
		// set column index flag.
 | 
						|
		addIndexColumnFlag(tblInfo, indexInfo)
 | 
						|
		if err = t.UpdateTable(schemaID, tblInfo); err != nil {
 | 
						|
			return errors.Trace(err)
 | 
						|
		}
 | 
						|
 | 
						|
		// finish this job
 | 
						|
		job.SchemaState = model.StatePublic
 | 
						|
		job.State = model.JobDone
 | 
						|
		return nil
 | 
						|
	default:
 | 
						|
		return errors.Errorf("invalid index state %v", tblInfo.State)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (d *ddl) onDropIndex(t *meta.Meta, job *model.Job) error {
 | 
						|
	schemaID := job.SchemaID
 | 
						|
	tblInfo, err := d.getTableInfo(t, job)
 | 
						|
	if err != nil {
 | 
						|
		return errors.Trace(err)
 | 
						|
	}
 | 
						|
 | 
						|
	var indexName model.CIStr
 | 
						|
	if err = job.DecodeArgs(&indexName); err != nil {
 | 
						|
		job.State = model.JobCancelled
 | 
						|
		return errors.Trace(err)
 | 
						|
	}
 | 
						|
 | 
						|
	var indexInfo *model.IndexInfo
 | 
						|
	for _, idx := range tblInfo.Indices {
 | 
						|
		if idx.Name.L == indexName.L {
 | 
						|
			indexInfo = idx
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if indexInfo == nil {
 | 
						|
		job.State = model.JobCancelled
 | 
						|
		return errors.Errorf("index %s doesn't exist", indexName)
 | 
						|
	}
 | 
						|
 | 
						|
	_, err = t.GenSchemaVersion()
 | 
						|
	if err != nil {
 | 
						|
		return errors.Trace(err)
 | 
						|
	}
 | 
						|
 | 
						|
	switch indexInfo.State {
 | 
						|
	case model.StatePublic:
 | 
						|
		// public -> write only
 | 
						|
		job.SchemaState = model.StateWriteOnly
 | 
						|
		indexInfo.State = model.StateWriteOnly
 | 
						|
		err = t.UpdateTable(schemaID, tblInfo)
 | 
						|
		return errors.Trace(err)
 | 
						|
	case model.StateWriteOnly:
 | 
						|
		// write only -> delete only
 | 
						|
		job.SchemaState = model.StateDeleteOnly
 | 
						|
		indexInfo.State = model.StateDeleteOnly
 | 
						|
		err = t.UpdateTable(schemaID, tblInfo)
 | 
						|
		return errors.Trace(err)
 | 
						|
	case model.StateDeleteOnly:
 | 
						|
		// delete only -> reorganization
 | 
						|
		job.SchemaState = model.StateDeleteReorganization
 | 
						|
		indexInfo.State = model.StateDeleteReorganization
 | 
						|
		err = t.UpdateTable(schemaID, tblInfo)
 | 
						|
		return errors.Trace(err)
 | 
						|
	case model.StateDeleteReorganization:
 | 
						|
		// reorganization -> absent
 | 
						|
		tbl, err := d.getTable(schemaID, tblInfo)
 | 
						|
		if err != nil {
 | 
						|
			return errors.Trace(err)
 | 
						|
		}
 | 
						|
 | 
						|
		err = d.runReorgJob(func() error {
 | 
						|
			return d.dropTableIndex(tbl, indexInfo)
 | 
						|
		})
 | 
						|
 | 
						|
		if terror.ErrorEqual(err, errWaitReorgTimeout) {
 | 
						|
			// if timeout, we should return, check for the owner and re-wait job done.
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
		if err != nil {
 | 
						|
			return errors.Trace(err)
 | 
						|
		}
 | 
						|
 | 
						|
		// all reorganization jobs done, drop this index
 | 
						|
		newIndices := make([]*model.IndexInfo, 0, len(tblInfo.Indices))
 | 
						|
		for _, idx := range tblInfo.Indices {
 | 
						|
			if idx.Name.L != indexName.L {
 | 
						|
				newIndices = append(newIndices, idx)
 | 
						|
			}
 | 
						|
		}
 | 
						|
		tblInfo.Indices = newIndices
 | 
						|
		// set column index flag.
 | 
						|
		dropIndexColumnFlag(tblInfo, indexInfo)
 | 
						|
		if err = t.UpdateTable(schemaID, tblInfo); err != nil {
 | 
						|
			return errors.Trace(err)
 | 
						|
		}
 | 
						|
 | 
						|
		// finish this job
 | 
						|
		job.SchemaState = model.StateNone
 | 
						|
		job.State = model.JobDone
 | 
						|
		return nil
 | 
						|
	default:
 | 
						|
		return errors.Errorf("invalid table state %v", tblInfo.State)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func checkRowExist(txn kv.Transaction, t table.Table, handle int64) (bool, error) {
 | 
						|
	_, err := txn.Get(t.RecordKey(handle, nil))
 | 
						|
	if terror.ErrorEqual(err, kv.ErrNotExist) {
 | 
						|
		// If row doesn't exist, we may have deleted the row already,
 | 
						|
		// no need to add index again.
 | 
						|
		return false, nil
 | 
						|
	} else if err != nil {
 | 
						|
		return false, errors.Trace(err)
 | 
						|
	}
 | 
						|
 | 
						|
	return true, nil
 | 
						|
}
 | 
						|
 | 
						|
func fetchRowColVals(txn kv.Transaction, t table.Table, handle int64, indexInfo *model.IndexInfo) ([]types.Datum, error) {
 | 
						|
	// fetch datas
 | 
						|
	cols := t.Cols()
 | 
						|
	vals := make([]types.Datum, 0, len(indexInfo.Columns))
 | 
						|
	for _, v := range indexInfo.Columns {
 | 
						|
		col := cols[v.Offset]
 | 
						|
		k := t.RecordKey(handle, 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)
 | 
						|
		}
 | 
						|
		vals = append(vals, val)
 | 
						|
	}
 | 
						|
 | 
						|
	return vals, nil
 | 
						|
}
 | 
						|
 | 
						|
const maxBatchSize = 1024
 | 
						|
 | 
						|
// How to add index in reorganization state?
 | 
						|
//  1. Generate a snapshot with special version.
 | 
						|
//  2. Traverse the snapshot, get every row in the table.
 | 
						|
//  3. For one row, if the row has been already deleted, skip to next row.
 | 
						|
//  4. If not deleted, check whether index has existed, if existed, skip to next row.
 | 
						|
//  5. If index doesn't exist, create the index and then continue to handle next row.
 | 
						|
func (d *ddl) addTableIndex(t table.Table, indexInfo *model.IndexInfo, reorgInfo *reorgInfo) error {
 | 
						|
	seekHandle := reorgInfo.Handle
 | 
						|
	version := reorgInfo.SnapshotVer
 | 
						|
	for {
 | 
						|
		handles, err := d.getSnapshotRows(t, version, seekHandle)
 | 
						|
		if err != nil {
 | 
						|
			return errors.Trace(err)
 | 
						|
		} else if len(handles) == 0 {
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
 | 
						|
		seekHandle = handles[len(handles)-1] + 1
 | 
						|
 | 
						|
		err = d.backfillTableIndex(t, indexInfo, handles, reorgInfo)
 | 
						|
		if err != nil {
 | 
						|
			return errors.Trace(err)
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (d *ddl) getSnapshotRows(t table.Table, version uint64, seekHandle int64) ([]int64, error) {
 | 
						|
	ver := kv.Version{Ver: version}
 | 
						|
 | 
						|
	snap, err := d.store.GetSnapshot(ver)
 | 
						|
	if err != nil {
 | 
						|
		return nil, errors.Trace(err)
 | 
						|
	}
 | 
						|
 | 
						|
	defer snap.Release()
 | 
						|
 | 
						|
	firstKey := t.RecordKey(seekHandle, nil)
 | 
						|
 | 
						|
	it, err := snap.Seek(firstKey)
 | 
						|
	if err != nil {
 | 
						|
		return nil, errors.Trace(err)
 | 
						|
	}
 | 
						|
	defer it.Close()
 | 
						|
 | 
						|
	handles := make([]int64, 0, maxBatchSize)
 | 
						|
 | 
						|
	for it.Valid() {
 | 
						|
		if !it.Key().HasPrefix(t.RecordPrefix()) {
 | 
						|
			break
 | 
						|
		}
 | 
						|
 | 
						|
		var handle int64
 | 
						|
		handle, err = tables.DecodeRecordKeyHandle(it.Key())
 | 
						|
		if err != nil {
 | 
						|
			return nil, errors.Trace(err)
 | 
						|
		}
 | 
						|
 | 
						|
		rk := t.RecordKey(handle, nil)
 | 
						|
 | 
						|
		handles = append(handles, handle)
 | 
						|
		if len(handles) == maxBatchSize {
 | 
						|
			break
 | 
						|
		}
 | 
						|
 | 
						|
		err = kv.NextUntil(it, util.RowKeyPrefixFilter(rk))
 | 
						|
		if terror.ErrorEqual(err, kv.ErrNotExist) {
 | 
						|
			break
 | 
						|
		} else if err != nil {
 | 
						|
			return nil, errors.Trace(err)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return handles, nil
 | 
						|
}
 | 
						|
 | 
						|
func lockRow(txn kv.Transaction, t table.Table, h int64) error {
 | 
						|
	// Get row lock key
 | 
						|
	lockKey := t.RecordKey(h, nil)
 | 
						|
	// set row lock key to current txn
 | 
						|
	err := txn.Set(lockKey, []byte(txn.String()))
 | 
						|
	return errors.Trace(err)
 | 
						|
}
 | 
						|
 | 
						|
func (d *ddl) backfillTableIndex(t table.Table, indexInfo *model.IndexInfo, handles []int64, reorgInfo *reorgInfo) error {
 | 
						|
	kvX := kv.NewKVIndex(t.IndexPrefix(), indexInfo.Name.L, indexInfo.ID, indexInfo.Unique)
 | 
						|
 | 
						|
	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 (d *ddl) dropTableIndex(t table.Table, indexInfo *model.IndexInfo) error {
 | 
						|
	prefix := kv.GenIndexPrefix(t.IndexPrefix(), indexInfo.ID)
 | 
						|
	err := d.delKeysWithPrefix(prefix)
 | 
						|
 | 
						|
	return errors.Trace(err)
 | 
						|
}
 |