Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

*: support check an index #5932

Merged
merged 11 commits into from Mar 5, 2018
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions ast/misc.go
Expand Up @@ -570,13 +570,15 @@ const (
AdminCheckTable
AdminShowDDLJobs
AdminCancelDDLJobs
AdminCheckIndex
)

// AdminStmt is the struct for Admin statement.
type AdminStmt struct {
stmtNode

Tp AdminStmtType
Index string
Tables []*TableName
JobIDs []int64
}
Expand Down
23 changes: 23 additions & 0 deletions executor/builder.go
Expand Up @@ -69,6 +69,8 @@ func (b *executorBuilder) build(p plan.Plan) Executor {
return nil
case *plan.CheckTable:
return b.buildCheckTable(v)
case *plan.CheckIndex:
return b.buildCheckIndex(v)
case *plan.DDL:
return b.buildDDL(v)
case *plan.Deallocate:
Expand Down Expand Up @@ -202,6 +204,27 @@ func (b *executorBuilder) buildShowDDLJobs(v *plan.ShowDDLJobs) Executor {
return e
}

func (b *executorBuilder) buildCheckIndex(v *plan.CheckIndex) Executor {
readerExec, err := buildNoRangeIndexLookUpReader(b, v.IndexLookUpReader)
if err != nil {
b.err = errors.Trace(err)
return nil
}
readerExec.ranges = ranger.FullNewRange()
readerExec.isCheckOp = true

e := &CheckIndexExec{
baseExecutor: newBaseExecutor(b.ctx, v.Schema(), v.ExplainID()),
dbName: v.DBName,
tableName: readerExec.table.Meta().Name.L,
idxName: v.IdxName,
is: b.is,
src: readerExec,
}
e.supportChk = true
return e
}

func (b *executorBuilder) buildCheckTable(v *plan.CheckTable) Executor {
e := &CheckTableExec{
baseExecutor: newBaseExecutor(b.ctx, v.Schema(), v.ExplainID()),
Expand Down
14 changes: 13 additions & 1 deletion executor/distsql.go
Expand Up @@ -518,6 +518,9 @@ type IndexLookUpExecutor struct {
resultCh chan *lookupTableTask
resultCurr *lookupTableTask
feedback *statistics.QueryFeedback

// isCheckOp is used to determine whether we need to check the consistency of the index data.
isCheckOp bool
}

// Open implements the Executor Open interface.
Expand Down Expand Up @@ -614,6 +617,7 @@ func (e *IndexLookUpExecutor) startTableWorker(ctx context.Context, workCh <-cha
buildTblReader: e.buildTableReader,
keepOrder: e.keepOrder,
handleIdx: e.handleIdx,
isCheckOp: e.isCheckOp,
}
ctx1, cancel := context.WithCancel(ctx)
go func() {
Expand Down Expand Up @@ -813,6 +817,9 @@ type tableWorker struct {
buildTblReader func(ctx context.Context, handles []int64) (Executor, error)
keepOrder bool
handleIdx int

// isCheckOp is used to determine whether we need to check the consistency of the index data.
isCheckOp bool
}

// pickAndExecTask picks tasks from workCh, and execute them.
Expand Down Expand Up @@ -854,7 +861,8 @@ func (w *tableWorker) executeTask(ctx context.Context, task *lookupTableTask) {
return
}
defer terror.Call(tableReader.Close)
task.rows = make([]chunk.Row, 0, len(task.handles))
handleCnt := len(task.handles)
task.rows = make([]chunk.Row, 0, handleCnt)
for {
chk := tableReader.newChunk()
err = tableReader.NextChunk(ctx, chk)
Expand All @@ -878,6 +886,10 @@ func (w *tableWorker) executeTask(ctx context.Context, task *lookupTableTask) {
}
sort.Sort(task)
}

if w.isCheckOp && handleCnt != len(task.rows) {
err = errors.Errorf("handle count %d isn't equal to value count %d", handleCnt, len(task.rows))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add the missing handle and corresponding index value in the error message.

}
}

type tableResultHandler struct {
Expand Down
64 changes: 64 additions & 0 deletions executor/executor.go
Expand Up @@ -409,6 +409,70 @@ func (e *CheckTableExec) run(ctx context.Context) error {
return nil
}

// CheckIndexExec represents the executor of check an index.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check -> checking

// It is built from the "admin check index" statement, and it checks if the
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it checks the consistency of the index data with the records of the table.

// index matches the records in the table.
type CheckIndexExec struct {
baseExecutor

dbName string
tableName string
idxName string
src Executor
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/Executor/*IndexLookUpExecutor/ is more readable ?

done bool
is infoschema.InfoSchema
}

// Open implements the Executor Open interface.
func (e *CheckIndexExec) Open(ctx context.Context) error {
if err := e.baseExecutor.Open(ctx); err != nil {
return errors.Trace(err)
}
if err := e.src.Open(ctx); err != nil {
return errors.Trace(err)
}
e.done = false
return nil
}

// Next implements the Executor Next interface.
func (e *CheckIndexExec) Next(ctx context.Context) (Row, error) {
if e.done {
return nil, nil
}
err := e.run(ctx)
e.done = true
return nil, errors.Trace(err)
}

// NextChunk implements the Executor NextChunk interface.
func (e *CheckIndexExec) NextChunk(ctx context.Context, chk *chunk.Chunk) error {
if e.done {
return nil
}
err := e.run(ctx)
e.done = true
return errors.Trace(err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we just call e.Next or extract a function?

}

func (e *CheckIndexExec) run(ctx context.Context) error {
err := admin.CheckIndicesCount(e.ctx, e.dbName, e.tableName, []string{e.idxName})
if err != nil {
return errors.Trace(err)
}

for {
row, err := e.src.Next(ctx)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use NextChunk?

if err != nil {
return errors.Trace(err)
}
if row == nil {
break
}
}
return nil
}

// SelectLockExec represents a select lock executor.
// It is built from the "SELECT .. FOR UPDATE" or the "SELECT .. LOCK IN SHARE MODE" statement.
// For "SELECT .. FOR UPDATE" statement, it locks every row key from source Executor.
Expand Down
105 changes: 105 additions & 0 deletions executor/executor_test.go
Expand Up @@ -38,14 +38,18 @@ import (
"github.com/pingcap/tidb/parser"
"github.com/pingcap/tidb/plan"
"github.com/pingcap/tidb/sessionctx"
"github.com/pingcap/tidb/sessionctx/stmtctx"
"github.com/pingcap/tidb/sessionctx/variable"
"github.com/pingcap/tidb/store/mockstore"
"github.com/pingcap/tidb/store/mockstore/mocktikv"
"github.com/pingcap/tidb/store/tikv"
"github.com/pingcap/tidb/store/tikv/tikvrpc"
"github.com/pingcap/tidb/table/tables"
"github.com/pingcap/tidb/tablecodec"
"github.com/pingcap/tidb/terror"
"github.com/pingcap/tidb/types"
"github.com/pingcap/tidb/util/admin"
"github.com/pingcap/tidb/util/codec"
"github.com/pingcap/tidb/util/logutil"
"github.com/pingcap/tidb/util/mock"
"github.com/pingcap/tidb/util/testkit"
Expand All @@ -71,6 +75,7 @@ type testSuite struct {
mvccStore *mocktikv.MvccStore
store kv.Storage
*parser.Parser
ctx *mock.Context

autoIDStep int64
}
Expand Down Expand Up @@ -2376,3 +2381,103 @@ func (s *testSuite) TestEarlyClose(c *C) {
rs.Close()
}
}

func (s *testSuite) TestCheckIndex(c *C) {
s.ctx = mock.NewContext()
s.ctx.Store = s.store
dom, err := tidb.BootstrapSession(s.store)
c.Assert(err, IsNil)
se, err := tidb.CreateSession4Test(s.store)
c.Assert(err, IsNil)
defer se.Close()

_, err = se.Execute(context.Background(), "create database test_admin")
c.Assert(err, IsNil)
_, err = se.Execute(context.Background(), "use test_admin")
c.Assert(err, IsNil)
_, err = se.Execute(context.Background(), "create table t (pk int primary key, c int default 1, c1 int default 1, unique key c(c))")
c.Assert(err, IsNil)
is := dom.InfoSchema()
db := model.NewCIStr("test_admin")
dbInfo, ok := is.SchemaByName(db)
c.Assert(ok, IsTrue)
tblName := model.NewCIStr("t")
tbl, err := is.TableByName(db, tblName)
c.Assert(err, IsNil)
tbInfo := tbl.Meta()

alloc := autoid.NewAllocator(s.store, dbInfo.ID)
tb, err := tables.TableFromMeta(alloc, tbInfo)
c.Assert(err, IsNil)

// set data to:
// index data (handle, data): (1, 10), (2, 20)
// table data (handle, data): (1, 10), (2, 20)
recordVal1 := types.MakeDatums(int64(1), int64(10), int64(11))
recordVal2 := types.MakeDatums(int64(2), int64(20), int64(21))
c.Assert(s.ctx.NewTxn(), IsNil)
_, err = tb.AddRecord(s.ctx, recordVal1, false)
c.Assert(err, IsNil)
_, err = tb.AddRecord(s.ctx, recordVal2, false)
c.Assert(err, IsNil)
c.Assert(s.ctx.Txn().Commit(context.Background()), IsNil)

mockCtx := mock.NewContext()
idx := tb.Indices()[0]
sc := &stmtctx.StatementContext{TimeZone: time.Local}

// set data to:
// index data (handle, data): (1, 10), (2, 20), (3, 30)
// table data (handle, data): (1, 10), (2, 20), (4, 40)
txn, err := s.store.Begin()
c.Assert(err, IsNil)
_, err = idx.Create(mockCtx, 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(context.Background())
c.Assert(err, IsNil)
_, err = se.Execute(context.Background(), "admin check index t c")
c.Assert(strings.Contains(err.Error(), "isn't equal to value count"), IsTrue)

// 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)
txn, err = s.store.Begin()
c.Assert(err, IsNil)
_, err = idx.Create(mockCtx, txn, types.MakeDatums(int64(40)), 4)
c.Assert(err, IsNil)
err = txn.Commit(context.Background())
c.Assert(err, IsNil)
_, err = se.Execute(context.Background(), "admin check index t c")
c.Assert(strings.Contains(err.Error(), "table count 3 != index(c) count 4"), IsTrue)

// set data to:
// index data (handle, data): (1, 10), (4, 40)
// table data (handle, data): (1, 10), (2, 20), (4, 40)
txn, err = s.store.Begin()
c.Assert(err, IsNil)
err = idx.Delete(sc, txn, types.MakeDatums(int64(30)), 3)
c.Assert(err, IsNil)
err = idx.Delete(sc, txn, types.MakeDatums(int64(20)), 2)
c.Assert(err, IsNil)
err = txn.Commit(context.Background())
c.Assert(err, IsNil)
_, err = se.Execute(context.Background(), "admin check index t c")
c.Assert(strings.Contains(err.Error(), "table count 3 != index(c) count 2"), IsTrue)

// TODO: pass the case below:
// set data to:
// index data (handle, data): (1, 10), (4, 40), (2, 30)
// table data (handle, data): (1, 10), (2, 20), (4, 40)
}

func setColValue(c *C, txn kv.Transaction, key kv.Key, v types.Datum) {
row := []types.Datum{v, {}}
colIDs := []int64{2, 3}
sc := &stmtctx.StatementContext{TimeZone: time.Local}
value, err := tablecodec.EncodeRow(sc, row, colIDs, nil, nil)
c.Assert(err, IsNil)
err = txn.Set(key, value)
c.Assert(err, IsNil)
}
8 changes: 8 additions & 0 deletions parser/parser.y
Expand Up @@ -4736,6 +4736,14 @@ AdminStmt:
Tables: $4.([]*ast.TableName),
}
}
| "ADMIN" "CHECK" "INDEX" TableName IndexName
{
$$ = &ast.AdminStmt{
Tp: ast.AdminCheckIndex,
Tables: []*ast.TableName{$4.(*ast.TableName)},
Index: $5.(string),
}
}
| "ADMIN" "CANCEL" "DDL" "JOBS" NumList
{
$$ = &ast.AdminStmt{
Expand Down
1 change: 1 addition & 0 deletions parser/parser_test.go
Expand Up @@ -403,6 +403,7 @@ func (s *testParserSuite) TestDMLStmt(c *C) {
{"admin show ddl;", true},
{"admin show ddl jobs;", true},
{"admin check table t1, t2;", true},
{"admin check index idx;", true},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the idx is the table name, and index name is empty?

{"admin cancel ddl jobs 1", true},
{"admin cancel ddl jobs 1, 2", true},

Expand Down
9 changes: 9 additions & 0 deletions plan/common_plans.go
Expand Up @@ -50,6 +50,15 @@ type CheckTable struct {
Tables []*ast.TableName
}

// CheckIndex is used for checking index data, built from the 'admin check index' statement.
type CheckIndex struct {
baseSchemaProducer

IndexLookUpReader *PhysicalIndexLookUpReader
DBName string
IdxName string
}

// CancelDDLJobs represents a cancel DDL jobs plan.
type CancelDDLJobs struct {
baseSchemaProducer
Expand Down