diff --git a/ddl/ddl_api.go b/ddl/ddl_api.go index f19b0fc88cf76..5f7b6cdf240c5 100644 --- a/ddl/ddl_api.go +++ b/ddl/ddl_api.go @@ -36,6 +36,7 @@ import ( "github.com/pingcap/tidb/table" "github.com/pingcap/tidb/types" "github.com/pingcap/tidb/util/charset" + "github.com/pingcap/tidb/util/schemautil" ) func (d *ddl) CreateSchema(ctx sessionctx.Context, schema model.CIStr, charsetInfo *ast.CharsetOpt) (err error) { @@ -1720,7 +1721,7 @@ func (d *ddl) CreateIndex(ctx sessionctx.Context, ti ast.Ident, unique bool, ind indexName = getAnonymousIndex(t, idxColNames[0].Column.Name) } - if indexInfo := findIndexByName(indexName.L, t.Meta().Indices); indexInfo != nil { + if indexInfo := schemautil.FindIndexByName(indexName.L, t.Meta().Indices); indexInfo != nil { return ErrDupKeyName.Gen("index already exist %s", indexName) } @@ -1844,7 +1845,7 @@ func (d *ddl) DropIndex(ctx sessionctx.Context, ti ast.Ident, indexName model.CI return errors.Trace(infoschema.ErrTableNotExists.GenByArgs(ti.Schema, ti.Name)) } - if indexInfo := findIndexByName(indexName.L, t.Meta().Indices); indexInfo == nil { + if indexInfo := schemautil.FindIndexByName(indexName.L, t.Meta().Indices); indexInfo == nil { return ErrCantDropFieldOrKey.Gen("index %s doesn't exist", indexName) } diff --git a/ddl/ddl_db_test.go b/ddl/ddl_db_test.go index f7c9275e06116..abb089dc66db2 100644 --- a/ddl/ddl_db_test.go +++ b/ddl/ddl_db_test.go @@ -46,6 +46,7 @@ import ( "github.com/pingcap/tidb/types" "github.com/pingcap/tidb/util/admin" "github.com/pingcap/tidb/util/mock" + "github.com/pingcap/tidb/util/schemautil" "github.com/pingcap/tidb/util/testkit" "github.com/pingcap/tidb/util/testleak" "github.com/pingcap/tidb/util/testutil" @@ -506,6 +507,101 @@ func (s *testDBSuite) TestCancelAddIndex1(c *C) { s.mustExec(c, "alter table t drop index idx_c2") } +// TestCancelDropIndex tests cancel ddl job which type is drop index. +func (s *testDBSuite) TestCancelDropIndex(c *C) { + s.tk = testkit.NewTestKit(c, s.store) + s.mustExec(c, "use test_db") + s.mustExec(c, "drop table if exists t") + s.mustExec(c, "create table t(c1 int, c2 int)") + defer s.mustExec(c, "drop table t;") + for i := 0; i < 5; i++ { + s.mustExec(c, "insert into t values (?, ?)", i, i) + } + + testCases := []struct { + needAddIndex bool + jobState model.JobState + JobSchemaState model.SchemaState + cancelSucc bool + }{ + // model.JobStateNone means the jobs is canceled before the first run. + {true, model.JobStateNone, model.StateNone, true}, + {false, model.JobStateRunning, model.StateWriteOnly, true}, + {false, model.JobStateRunning, model.StateDeleteOnly, false}, + {true, model.JobStateRunning, model.StateDeleteReorganization, false}, + } + + var checkErr error + oldReorgWaitTimeout := ddl.ReorgWaitTimeout + ddl.ReorgWaitTimeout = 50 * time.Millisecond + defer func() { ddl.ReorgWaitTimeout = oldReorgWaitTimeout }() + hook := &ddl.TestDDLCallback{} + var jobID int64 + testCase := &testCases[0] + hook.OnJobRunBeforeExported = func(job *model.Job) { + if job.Type == model.ActionDropIndex && job.State == testCase.jobState && job.SchemaState == testCase.JobSchemaState { + jobID = job.ID + jobIDs := []int64{job.ID} + hookCtx := mock.NewContext() + hookCtx.Store = s.store + err := hookCtx.NewTxn() + if err != nil { + checkErr = errors.Trace(err) + return + } + txn, err := hookCtx.Txn(true) + if err != nil { + checkErr = errors.Trace(err) + return + } + errs, err := admin.CancelJobs(txn, jobIDs) + if err != nil { + checkErr = errors.Trace(err) + return + } + + if errs[0] != nil { + checkErr = errors.Trace(errs[0]) + return + } + + checkErr = txn.Commit(context.Background()) + } + } + s.dom.DDL().SetHook(hook) + for i := range testCases { + testCase = &testCases[i] + if testCase.needAddIndex { + s.mustExec(c, "alter table t add index idx_c2(c2)") + } + rs, err := s.tk.Exec("alter table t drop index idx_c2") + if rs != nil { + rs.Close() + } + + t := s.testGetTable(c, "t") + indexInfo := schemautil.FindIndexByName("idx_c2", t.Meta().Indices) + if testCase.cancelSucc { + c.Assert(checkErr, IsNil) + c.Assert(err, NotNil) + c.Assert(err.Error(), Equals, "[ddl:12]cancelled DDL job") + + c.Assert(indexInfo, NotNil) + c.Assert(indexInfo.State, Equals, model.StatePublic) + } else { + err1 := admin.ErrCannotCancelDDLJob.GenByArgs(jobID) + c.Assert(err, IsNil) + c.Assert(checkErr, NotNil) + c.Assert(checkErr.Error(), Equals, err1.Error()) + + c.Assert(indexInfo, IsNil) + } + } + s.dom.DDL().SetHook(&ddl.TestDDLCallback{}) + s.mustExec(c, "alter table t add index idx_c2(c2)") + s.mustExec(c, "alter table t drop index idx_c2") +} + func (s *testDBSuite) TestAddAnonymousIndex(c *C) { s.tk = testkit.NewTestKit(c, s.store) s.tk.MustExec("use " + s.schemaName) diff --git a/ddl/ddl_worker.go b/ddl/ddl_worker.go index 2a5ee07d2fdaf..70a736f867c27 100644 --- a/ddl/ddl_worker.go +++ b/ddl/ddl_worker.go @@ -25,6 +25,7 @@ import ( "github.com/pingcap/tidb/sessionctx/binloginfo" "github.com/pingcap/tidb/terror" "github.com/pingcap/tidb/util" + "github.com/pingcap/tidb/util/schemautil" log "github.com/sirupsen/logrus" "golang.org/x/net/context" ) @@ -571,7 +572,7 @@ func (d *ddl) cleanAddIndexQueueJobs(txn kv.Transaction) error { if err != nil { return errors.Trace(err) } - indexInfo := findIndexByName(indexName.L, tblInfo.Indices) + indexInfo := schemautil.FindIndexByName(indexName.L, tblInfo.Indices) _, err = convertAddIdxJob2RollbackJob(m, job, tblInfo, indexInfo, nil) if err == nil { _, err = m.DeQueueDDLJob() diff --git a/ddl/ddl_worker_test.go b/ddl/ddl_worker_test.go index d4e5bcddff031..192706aaef986 100644 --- a/ddl/ddl_worker_test.go +++ b/ddl/ddl_worker_test.go @@ -469,7 +469,6 @@ type testCancelJob struct { func buildCancelJobTests(firstID int64) []testCancelJob { err := errCancelledDDLJob - errs := []error{err} noErrs := []error{nil} tests := []testCancelJob{ {act: model.ActionAddIndex, jobIDs: []int64{firstID + 1}, cancelRetErrs: noErrs, cancelState: model.StateDeleteOnly, ddlRetErr: err}, @@ -477,22 +476,19 @@ func buildCancelJobTests(firstID int64) []testCancelJob { {act: model.ActionAddIndex, jobIDs: []int64{firstID + 3}, cancelRetErrs: noErrs, cancelState: model.StateWriteReorganization, ddlRetErr: err}, {act: model.ActionAddIndex, jobIDs: []int64{firstID + 4}, cancelRetErrs: []error{admin.ErrCancelFinishedDDLJob.GenByArgs(firstID + 4)}, cancelState: model.StatePublic, ddlRetErr: err}, - // TODO: after fix drop index and create table rollback bug, the below test cases maybe need to change. - {act: model.ActionDropIndex, jobIDs: []int64{firstID + 5}, cancelRetErrs: errs, cancelState: model.StateWriteOnly, ddlRetErr: err}, - {act: model.ActionDropIndex, jobIDs: []int64{firstID + 6}, cancelRetErrs: errs, cancelState: model.StateDeleteOnly, ddlRetErr: err}, - {act: model.ActionDropIndex, jobIDs: []int64{firstID + 7}, cancelRetErrs: errs, cancelState: model.StateDeleteReorganization, ddlRetErr: err}, - {act: model.ActionDropIndex, jobIDs: []int64{firstID + 8}, cancelRetErrs: []error{admin.ErrCancelFinishedDDLJob.GenByArgs(firstID + 8)}, cancelState: model.StateNone, ddlRetErr: err}, + // Test cancel drop index job , see TestCancelDropIndex. // TODO: add create table back after we fix the cancel bug. //{act: model.ActionCreateTable, jobIDs: []int64{firstID + 9}, cancelRetErrs: noErrs, cancelState: model.StatePublic, ddlRetErr: err}, - {act: model.ActionAddColumn, jobIDs: []int64{firstID + 9}, cancelRetErrs: noErrs, cancelState: model.StateDeleteOnly, ddlRetErr: err}, - {act: model.ActionAddColumn, jobIDs: []int64{firstID + 10}, cancelRetErrs: noErrs, cancelState: model.StateWriteOnly, ddlRetErr: err}, - {act: model.ActionAddColumn, jobIDs: []int64{firstID + 11}, cancelRetErrs: noErrs, cancelState: model.StateWriteReorganization, ddlRetErr: err}, - {act: model.ActionAddColumn, jobIDs: []int64{firstID + 12}, cancelRetErrs: []error{admin.ErrCancelFinishedDDLJob.GenByArgs(firstID + 12)}, cancelState: model.StatePublic, ddlRetErr: err}, - - {act: model.ActionDropColumn, jobIDs: []int64{firstID + 13}, cancelRetErrs: []error{admin.ErrCannotCancelDDLJob.GenByArgs(firstID + 13)}, cancelState: model.StateWriteOnly, ddlRetErr: err}, - {act: model.ActionDropColumn, jobIDs: []int64{firstID + 14}, cancelRetErrs: []error{admin.ErrCannotCancelDDLJob.GenByArgs(firstID + 14)}, cancelState: model.StateDeleteOnly, ddlRetErr: err}, - {act: model.ActionDropColumn, jobIDs: []int64{firstID + 15}, cancelRetErrs: []error{admin.ErrCannotCancelDDLJob.GenByArgs(firstID + 15)}, cancelState: model.StateDeleteReorganization, ddlRetErr: err}, + + {act: model.ActionAddColumn, jobIDs: []int64{firstID + 5}, cancelRetErrs: noErrs, cancelState: model.StateDeleteOnly, ddlRetErr: err}, + {act: model.ActionAddColumn, jobIDs: []int64{firstID + 6}, cancelRetErrs: noErrs, cancelState: model.StateWriteOnly, ddlRetErr: err}, + {act: model.ActionAddColumn, jobIDs: []int64{firstID + 7}, cancelRetErrs: noErrs, cancelState: model.StateWriteReorganization, ddlRetErr: err}, + {act: model.ActionAddColumn, jobIDs: []int64{firstID + 8}, cancelRetErrs: []error{admin.ErrCancelFinishedDDLJob.GenByArgs(firstID + 8)}, cancelState: model.StatePublic, ddlRetErr: err}, + + {act: model.ActionDropColumn, jobIDs: []int64{firstID + 9}, cancelRetErrs: []error{admin.ErrCannotCancelDDLJob.GenByArgs(firstID + 9)}, cancelState: model.StateWriteOnly, ddlRetErr: err}, + {act: model.ActionDropColumn, jobIDs: []int64{firstID + 10}, cancelRetErrs: []error{admin.ErrCannotCancelDDLJob.GenByArgs(firstID + 10)}, cancelState: model.StateDeleteOnly, ddlRetErr: err}, + {act: model.ActionDropColumn, jobIDs: []int64{firstID + 11}, cancelRetErrs: []error{admin.ErrCannotCancelDDLJob.GenByArgs(firstID + 11)}, cancelState: model.StateDeleteReorganization, ddlRetErr: err}, } return tests @@ -626,23 +622,8 @@ func (s *testDDLSuite) TestCancelJob(c *C) { c.Assert(txn.Commit(context.Background()), IsNil) s.checkAddIdx(c, d, dbInfo.ID, tblInfo.ID, idxOrigName, true) - // for dropping index - idxName := []interface{}{model.NewCIStr("idx")} - test = &tests[4] - doDDLJobErrWithSchemaState(ctx, d, c, dbInfo.ID, tblInfo.ID, model.ActionDropIndex, idxName, &test.cancelState) - c.Check(errors.ErrorStack(checkErr), Equals, "") - test = &tests[5] - doDDLJobErrWithSchemaState(ctx, d, c, dbInfo.ID, tblInfo.ID, model.ActionDropIndex, idxName, &test.cancelState) - c.Check(errors.ErrorStack(checkErr), Equals, "") - test = &tests[6] - doDDLJobErrWithSchemaState(ctx, d, c, dbInfo.ID, tblInfo.ID, model.ActionDropIndex, idxName, &test.cancelState) - c.Check(errors.ErrorStack(checkErr), Equals, "") - test = &tests[7] - testDropIndex(c, ctx, d, dbInfo, tblInfo, "idx") - c.Check(errors.ErrorStack(checkErr), Equals, "") - // for add column - test = &tests[8] + test = &tests[4] addingColName := "colA" newColumnDef := &ast.ColumnDef{ Name: &ast.ColumnName{Name: model.NewCIStr(addingColName)}, @@ -655,21 +636,21 @@ func (s *testDDLSuite) TestCancelJob(c *C) { doDDLJobErrWithSchemaState(ctx, d, c, dbInfo.ID, tblInfo.ID, model.ActionAddColumn, addColumnArgs, &cancelState) c.Check(errors.ErrorStack(checkErr), Equals, "") s.checkAddColumn(c, d, dbInfo.ID, tblInfo.ID, addingColName, false) - test = &tests[9] + test = &tests[5] doDDLJobErrWithSchemaState(ctx, d, c, dbInfo.ID, tblInfo.ID, model.ActionAddColumn, addColumnArgs, &cancelState) c.Check(errors.ErrorStack(checkErr), Equals, "") s.checkAddColumn(c, d, dbInfo.ID, tblInfo.ID, addingColName, false) - test = &tests[10] + test = &tests[6] doDDLJobErrWithSchemaState(ctx, d, c, dbInfo.ID, tblInfo.ID, model.ActionAddColumn, addColumnArgs, &cancelState) c.Check(errors.ErrorStack(checkErr), Equals, "") s.checkAddColumn(c, d, dbInfo.ID, tblInfo.ID, addingColName, false) - test = &tests[11] + test = &tests[7] testAddColumn(c, ctx, d, dbInfo, tblInfo, addColumnArgs) c.Check(errors.ErrorStack(checkErr), Equals, "") s.checkAddColumn(c, d, dbInfo.ID, tblInfo.ID, addingColName, true) // for drop column. - test = &tests[12] + test = &tests[8] dropColName := "c1" dropColumnArgs := []interface{}{model.NewCIStr(dropColName)} s.checkCancelDropColumn(c, d, dbInfo.ID, tblInfo.ID, dropColName, false) @@ -677,17 +658,16 @@ func (s *testDDLSuite) TestCancelJob(c *C) { c.Check(errors.ErrorStack(checkErr), Equals, "") s.checkCancelDropColumn(c, d, dbInfo.ID, tblInfo.ID, dropColName, true) - test = &tests[13] - - dropColName = "c2" + test = &tests[9] + dropColName = "c3" dropColumnArgs = []interface{}{model.NewCIStr(dropColName)} s.checkCancelDropColumn(c, d, dbInfo.ID, tblInfo.ID, dropColName, false) testCancelDropColumn(c, ctx, d, dbInfo, tblInfo, dropColumnArgs) c.Check(errors.ErrorStack(checkErr), Equals, "") s.checkCancelDropColumn(c, d, dbInfo.ID, tblInfo.ID, dropColName, true) - test = &tests[14] - dropColName = "c3" + test = &tests[10] + dropColName = "c4" dropColumnArgs = []interface{}{model.NewCIStr(dropColName)} s.checkCancelDropColumn(c, d, dbInfo.ID, tblInfo.ID, dropColName, false) testCancelDropColumn(c, ctx, d, dbInfo, tblInfo, dropColumnArgs) diff --git a/ddl/index.go b/ddl/index.go index 8ec591269304a..445c469d1252c 100644 --- a/ddl/index.go +++ b/ddl/index.go @@ -34,6 +34,7 @@ import ( "github.com/pingcap/tidb/tablecodec" "github.com/pingcap/tidb/types" "github.com/pingcap/tidb/util" + "github.com/pingcap/tidb/util/schemautil" log "github.com/sirupsen/logrus" ) @@ -206,7 +207,7 @@ func (d *ddl) onCreateIndex(t *meta.Meta, job *model.Job) (ver int64, err error) return ver, errors.Trace(err) } - indexInfo := findIndexByName(indexName.L, tblInfo.Indices) + indexInfo := schemautil.FindIndexByName(indexName.L, tblInfo.Indices) if indexInfo != nil && indexInfo.State == model.StatePublic { job.State = model.JobStateCancelled return ver, ErrDupKeyName.Gen("index already exist %s", indexName) @@ -319,7 +320,7 @@ func (d *ddl) onDropIndex(t *meta.Meta, job *model.Job) (ver int64, _ error) { return ver, errors.Trace(err) } - indexInfo := findIndexByName(indexName.L, tblInfo.Indices) + indexInfo := schemautil.FindIndexByName(indexName.L, tblInfo.Indices) if indexInfo == nil { job.State = model.JobStateCancelled return ver, ErrCantDropFieldOrKey.Gen("index %s doesn't exist", indexName) @@ -961,15 +962,6 @@ func (d *ddl) addTableIndex(t table.Table, indexInfo *model.IndexInfo, reorgInfo return d.backfillKVRangesIndex(t, workers, kvRanges, job, reorgInfo) } -func findIndexByName(idxName string, indices []*model.IndexInfo) *model.IndexInfo { - for _, idx := range indices { - if idx.Name.L == idxName { - return idx - } - } - return nil -} - func allocateIndexID(tblInfo *model.TableInfo) int64 { tblInfo.MaxIndexID++ return tblInfo.MaxIndexID diff --git a/ddl/rollingback.go b/ddl/rollingback.go index 0025ce027ab7d..511dafb3549c7 100644 --- a/ddl/rollingback.go +++ b/ddl/rollingback.go @@ -20,6 +20,7 @@ import ( "github.com/pingcap/tidb/kv" "github.com/pingcap/tidb/meta" "github.com/pingcap/tidb/model" + "github.com/pingcap/tidb/util/schemautil" log "github.com/sirupsen/logrus" ) @@ -63,7 +64,7 @@ func convertNotStartAddIdxJob2RollbackJob(t *meta.Meta, job *model.Job, occuredE job.State = model.JobStateCancelled return ver, errors.Trace(err) } - indexInfo := findIndexByName(indexName.L, tblInfo.Indices) + indexInfo := schemautil.FindIndexByName(indexName.L, tblInfo.Indices) if indexInfo == nil { job.State = model.JobStateCancelled return ver, errCancelledDDLJob @@ -106,6 +107,51 @@ func rollingbackAddColumn(t *meta.Meta, job *model.Job) (ver int64, err error) { } return ver, errCancelledDDLJob } + +func rollingbackDropIndex(t *meta.Meta, job *model.Job) (ver int64, err error) { + schemaID := job.SchemaID + tblInfo, err := getTableInfo(t, job, schemaID) + if err != nil { + return ver, errors.Trace(err) + } + + var indexName model.CIStr + err = job.DecodeArgs(&indexName) + if err != nil { + job.State = model.JobStateCancelled + return ver, errors.Trace(err) + } + + indexInfo := schemautil.FindIndexByName(indexName.L, tblInfo.Indices) + if indexInfo == nil { + job.State = model.JobStateCancelled + return ver, ErrCantDropFieldOrKey.Gen("index %s doesn't exist", indexName) + } + + originalState := indexInfo.State + switch indexInfo.State { + case model.StateDeleteOnly, model.StateDeleteReorganization, model.StateNone: + // We can not rollback now, so just continue to drop index. + // Normally won't fetch here, because there is check when cancel ddl jobs. see function: isJobRollbackable. + job.State = model.JobStateRunning + return ver, nil + case model.StatePublic, model.StateWriteOnly: + job.State = model.JobStateRollbackDone + indexInfo.State = model.StatePublic + default: + return ver, ErrInvalidIndexState.Gen("invalid index state %v", indexInfo.State) + } + + job.SchemaState = indexInfo.State + job.Args = []interface{}{indexInfo.Name} + ver, err = updateVersionAndTableInfo(t, job, tblInfo, originalState != indexInfo.State) + if err != nil { + return ver, errors.Trace(err) + } + job.FinishTableJob(model.JobStateRollbackDone, model.StatePublic, ver, tblInfo) + return ver, errCancelledDDLJob +} + func rollingbackAddindex(d *ddl, t *meta.Meta, job *model.Job) (ver int64, err error) { // If the value of SnapshotVer isn't zero, it means the work is backfilling the indexes. if job.SchemaState == model.StateWriteReorganization && job.SnapshotVer != 0 { @@ -160,6 +206,8 @@ func convertJob2RollbackJob(d *ddl, t *meta.Meta, job *model.Job) (ver int64, er ver, err = rollingbackAddindex(d, t, job) case model.ActionDropColumn: ver, err = rollingbackDropColumn(t, job) + case model.ActionDropIndex: + ver, err = rollingbackDropIndex(t, job) default: job.State = model.JobStateCancelled err = errCancelledDDLJob diff --git a/plan/planbuilder.go b/plan/planbuilder.go index 2a86897430afc..208d1f6cfd68e 100644 --- a/plan/planbuilder.go +++ b/plan/planbuilder.go @@ -28,6 +28,7 @@ import ( "github.com/pingcap/tidb/table" "github.com/pingcap/tidb/types" "github.com/pingcap/tidb/util/ranger" + "github.com/pingcap/tidb/util/schemautil" ) type visitInfo struct { @@ -356,15 +357,6 @@ func removeIgnoredPaths(paths, ignoredPaths []*accessPath, tblInfo *model.TableI return remainedPaths } -func findIndexByName(indices []*model.IndexInfo, name model.CIStr) *model.IndexInfo { - for _, idx := range indices { - if idx.Name.L == name.L { - return idx - } - } - return nil -} - func (b *planBuilder) buildSelectLock(src LogicalPlan, lock ast.SelectLockType) *LogicalLock { selectLock := LogicalLock{Lock: lock}.init(b.ctx) selectLock.SetChildren(src) @@ -607,7 +599,7 @@ func (b *planBuilder) buildAnalyzeIndex(as *ast.AnalyzeTableStmt) Plan { p := &Analyze{} tblInfo := as.TableNames[0].TableInfo for _, idxName := range as.IndexNames { - idx := findIndexByName(tblInfo.Indices, idxName) + idx := schemautil.FindIndexByName(idxName.L, tblInfo.Indices) if idx == nil || idx.State != model.StatePublic { b.err = ErrAnalyzeMissIndex.GenByArgs(idxName.O, tblInfo.Name.O) break diff --git a/util/admin/admin.go b/util/admin/admin.go index 1df9b029e3971..088abe339621d 100644 --- a/util/admin/admin.go +++ b/util/admin/admin.go @@ -72,6 +72,12 @@ func isJobRollbackable(job *model.Job, id int64) error { if job.SchemaState != model.StateNone { return ErrCannotCancelDDLJob.GenByArgs(id) } + case model.ActionDropIndex: + // We can't cancel if index current state is in StateDeleteOnly or StateDeleteReorganization, otherwise will cause inconsistent between record and index. + if job.SchemaState == model.StateDeleteOnly || + job.SchemaState == model.StateDeleteReorganization { + return ErrCannotCancelDDLJob.GenByArgs(id) + } } return nil } diff --git a/util/schemautil/schemautil.go b/util/schemautil/schemautil.go new file mode 100644 index 0000000000000..660e347b597bb --- /dev/null +++ b/util/schemautil/schemautil.go @@ -0,0 +1,26 @@ +// Copyright 2018 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 schemautil + +import "github.com/pingcap/tidb/model" + +// FindIndexByName finds index by name. +func FindIndexByName(idxName string, indices []*model.IndexInfo) *model.IndexInfo { + for _, idx := range indices { + if idx.Name.L == idxName { + return idx + } + } + return nil +}