Платформа ЦРНП "Мирокод" для разработки проектов
https://git.mirocod.ru
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
452 lines
12 KiB
452 lines
12 KiB
// 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 inspectkv |
|
|
|
import ( |
|
"io" |
|
"reflect" |
|
|
|
"github.com/juju/errors" |
|
"github.com/ngaut/log" |
|
"github.com/pingcap/tidb/column" |
|
"github.com/pingcap/tidb/kv" |
|
"github.com/pingcap/tidb/meta" |
|
"github.com/pingcap/tidb/model" |
|
"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" |
|
) |
|
|
|
// DDLInfo is for DDL information. |
|
type DDLInfo struct { |
|
SchemaVer int64 |
|
ReorgHandle int64 // it's only used for DDL information. |
|
Owner *model.Owner |
|
Job *model.Job |
|
} |
|
|
|
// GetDDLInfo returns DDL information. |
|
func GetDDLInfo(txn kv.Transaction) (*DDLInfo, error) { |
|
var err error |
|
info := &DDLInfo{} |
|
t := meta.NewMeta(txn) |
|
|
|
info.Owner, err = t.GetDDLJobOwner() |
|
if err != nil { |
|
return nil, errors.Trace(err) |
|
} |
|
info.Job, err = t.GetDDLJob(0) |
|
if err != nil { |
|
return nil, errors.Trace(err) |
|
} |
|
info.SchemaVer, err = t.GetSchemaVersion() |
|
if err != nil { |
|
return nil, errors.Trace(err) |
|
} |
|
if info.Job == nil { |
|
return info, nil |
|
} |
|
|
|
info.ReorgHandle, err = t.GetDDLReorgHandle(info.Job) |
|
if err != nil { |
|
return nil, errors.Trace(err) |
|
} |
|
|
|
return info, nil |
|
} |
|
|
|
// GetBgDDLInfo returns background DDL information. |
|
func GetBgDDLInfo(txn kv.Transaction) (*DDLInfo, error) { |
|
var err error |
|
info := &DDLInfo{} |
|
t := meta.NewMeta(txn) |
|
|
|
info.Owner, err = t.GetBgJobOwner() |
|
if err != nil { |
|
return nil, errors.Trace(err) |
|
} |
|
info.Job, err = t.GetBgJob(0) |
|
if err != nil { |
|
return nil, errors.Trace(err) |
|
} |
|
info.SchemaVer, err = t.GetSchemaVersion() |
|
if err != nil { |
|
return nil, errors.Trace(err) |
|
} |
|
|
|
return info, nil |
|
} |
|
|
|
func nextIndexVals(data []types.Datum) []types.Datum { |
|
// Add 0x0 to the end of data. |
|
return append(data, types.Datum{}) |
|
} |
|
|
|
// RecordData is the record data composed of a handle and values. |
|
type RecordData struct { |
|
Handle int64 |
|
Values []types.Datum |
|
} |
|
|
|
// GetIndexRecordsCount returns the total number of the index records from startVals. |
|
// If startVals = nil, returns the total number of the index records. |
|
func GetIndexRecordsCount(txn kv.Transaction, kvIndex kv.Index, startVals []types.Datum) (int64, error) { |
|
it, _, err := kvIndex.Seek(txn, startVals) |
|
if err != nil { |
|
return 0, errors.Trace(err) |
|
} |
|
defer it.Close() |
|
|
|
var cnt int64 |
|
for { |
|
_, _, err := it.Next() |
|
if terror.ErrorEqual(err, io.EOF) { |
|
break |
|
} else if err != nil { |
|
return 0, errors.Trace(err) |
|
} |
|
cnt++ |
|
} |
|
|
|
return cnt, nil |
|
} |
|
|
|
// ScanIndexData scans the index handles and values in a limited number, according to the index information. |
|
// It returns data and the next startVals until it doesn't have data, then returns data is nil and |
|
// the next startVals is the values which can't get data. If startVals = nil and limit = -1, |
|
// it returns the index data of the whole. |
|
func ScanIndexData(txn kv.Transaction, kvIndex kv.Index, startVals []types.Datum, limit int64) ( |
|
[]*RecordData, []types.Datum, error) { |
|
it, _, err := kvIndex.Seek(txn, startVals) |
|
if err != nil { |
|
return nil, nil, errors.Trace(err) |
|
} |
|
defer it.Close() |
|
|
|
var idxRows []*RecordData |
|
var curVals []types.Datum |
|
for limit != 0 { |
|
val, h, err1 := it.Next() |
|
if terror.ErrorEqual(err1, io.EOF) { |
|
return idxRows, nextIndexVals(curVals), nil |
|
} else if err1 != nil { |
|
return nil, nil, errors.Trace(err1) |
|
} |
|
idxRows = append(idxRows, &RecordData{Handle: h, Values: val}) |
|
limit-- |
|
curVals = val |
|
} |
|
|
|
nextVals, _, err := it.Next() |
|
if terror.ErrorEqual(err, io.EOF) { |
|
return idxRows, nextIndexVals(curVals), nil |
|
} else if err != nil { |
|
return nil, nil, errors.Trace(err) |
|
} |
|
|
|
return idxRows, nextVals, nil |
|
} |
|
|
|
// CompareIndexData compares index data one by one. |
|
// It returns nil if the data from the index is equal to the data from the table columns, |
|
// otherwise it returns an error with a different set of records. |
|
func CompareIndexData(txn kv.Transaction, t table.Table, idx *column.IndexedCol) error { |
|
err := checkIndexAndRecord(txn, t, idx) |
|
if err != nil { |
|
return errors.Trace(err) |
|
} |
|
|
|
return checkRecordAndIndex(txn, t, idx) |
|
} |
|
|
|
func checkIndexAndRecord(txn kv.Transaction, t table.Table, idx *column.IndexedCol) error { |
|
kvIndex := kv.NewKVIndex(t.IndexPrefix(), idx.Name.L, idx.ID, idx.Unique) |
|
it, err := kvIndex.SeekFirst(txn) |
|
if err != nil { |
|
return errors.Trace(err) |
|
} |
|
defer it.Close() |
|
|
|
cols := make([]*column.Col, len(idx.Columns)) |
|
for i, col := range idx.Columns { |
|
cols[i] = t.Cols()[col.Offset] |
|
} |
|
|
|
for { |
|
vals1, h, err := it.Next() |
|
if terror.ErrorEqual(err, io.EOF) { |
|
break |
|
} else if err != nil { |
|
return errors.Trace(err) |
|
} |
|
|
|
vals2, err := rowWithCols(txn, t, h, cols) |
|
if terror.ErrorEqual(err, kv.ErrNotExist) { |
|
record := &RecordData{Handle: h, Values: vals1} |
|
err = errors.Errorf("index:%v != record:%v", record, nil) |
|
} |
|
if err != nil { |
|
return errors.Trace(err) |
|
} |
|
if !reflect.DeepEqual(vals1, vals2) { |
|
record1 := &RecordData{Handle: h, Values: vals1} |
|
record2 := &RecordData{Handle: h, Values: vals2} |
|
return errors.Errorf("index:%v != record:%v", record1, record2) |
|
} |
|
} |
|
|
|
return nil |
|
} |
|
|
|
func checkRecordAndIndex(txn kv.Transaction, t table.Table, idx *column.IndexedCol) error { |
|
cols := make([]*column.Col, len(idx.Columns)) |
|
for i, col := range idx.Columns { |
|
cols[i] = t.Cols()[col.Offset] |
|
} |
|
|
|
startKey := t.RecordKey(0, nil) |
|
kvIndex := kv.NewKVIndex(t.IndexPrefix(), idx.Name.L, idx.ID, idx.Unique) |
|
filterFunc := func(h1 int64, vals1 []types.Datum, cols []*column.Col) (bool, error) { |
|
isExist, h2, err := kvIndex.Exist(txn, vals1, h1) |
|
if terror.ErrorEqual(err, kv.ErrKeyExists) { |
|
record1 := &RecordData{Handle: h1, Values: vals1} |
|
record2 := &RecordData{Handle: h2, Values: vals1} |
|
return false, errors.Errorf("index:%v != record:%v", record2, record1) |
|
} |
|
if err != nil { |
|
return false, errors.Trace(err) |
|
} |
|
if !isExist { |
|
record := &RecordData{Handle: h1, Values: vals1} |
|
return false, errors.Errorf("index:%v != record:%v", nil, record) |
|
} |
|
|
|
return true, nil |
|
} |
|
err := iterRecords(txn, t, startKey, cols, filterFunc) |
|
|
|
if err != nil { |
|
return errors.Trace(err) |
|
} |
|
|
|
return nil |
|
} |
|
|
|
func scanTableData(retriever kv.Retriever, t table.Table, cols []*column.Col, startHandle, limit int64) ( |
|
[]*RecordData, int64, error) { |
|
var records []*RecordData |
|
|
|
startKey := t.RecordKey(startHandle, nil) |
|
filterFunc := func(h int64, d []types.Datum, cols []*column.Col) (bool, error) { |
|
if limit != 0 { |
|
r := &RecordData{ |
|
Handle: h, |
|
Values: d, |
|
} |
|
records = append(records, r) |
|
limit-- |
|
return true, nil |
|
} |
|
|
|
return false, nil |
|
} |
|
err := iterRecords(retriever, t, startKey, cols, filterFunc) |
|
if err != nil { |
|
return nil, 0, errors.Trace(err) |
|
} |
|
|
|
if len(records) == 0 { |
|
return records, startHandle, nil |
|
} |
|
|
|
nextHandle := records[len(records)-1].Handle + 1 |
|
|
|
return records, nextHandle, nil |
|
} |
|
|
|
// ScanTableRecord scans table row handles and column values in a limited number. |
|
// It returns data and the next startHandle until it doesn't have data, then returns data is nil and |
|
// the next startHandle is the handle which can't get data. If startHandle = 0 and limit = -1, |
|
// it returns the table data of the whole. |
|
func ScanTableRecord(retriever kv.Retriever, t table.Table, startHandle, limit int64) ( |
|
[]*RecordData, int64, error) { |
|
return scanTableData(retriever, t, t.Cols(), startHandle, limit) |
|
} |
|
|
|
// ScanSnapshotTableRecord scans the ver version of the table data in a limited number. |
|
// It returns data and the next startHandle until it doesn't have data, then returns data is nil and |
|
// the next startHandle is the handle which can't get data. If startHandle = 0 and limit = -1, |
|
// it returns the table data of the whole. |
|
func ScanSnapshotTableRecord(store kv.Storage, ver kv.Version, t table.Table, startHandle, limit int64) ( |
|
[]*RecordData, int64, error) { |
|
snap, err := store.GetSnapshot(ver) |
|
if err != nil { |
|
return nil, 0, errors.Trace(err) |
|
} |
|
defer snap.Release() |
|
|
|
records, nextHandle, err := ScanTableRecord(snap, t, startHandle, limit) |
|
|
|
return records, nextHandle, errors.Trace(err) |
|
} |
|
|
|
// CompareTableRecord compares data and the corresponding table data one by one. |
|
// It returns nil if data is equal to the data that scans from table, otherwise |
|
// it returns an error with a different set of records. If exact is false, only compares handle. |
|
func CompareTableRecord(txn kv.Transaction, t table.Table, data []*RecordData, exact bool) error { |
|
m := make(map[int64][]types.Datum, len(data)) |
|
for _, r := range data { |
|
if _, ok := m[r.Handle]; ok { |
|
return errors.Errorf("handle:%d is repeated in data", r.Handle) |
|
} |
|
m[r.Handle] = r.Values |
|
} |
|
|
|
startKey := t.RecordKey(0, nil) |
|
filterFunc := func(h int64, vals []types.Datum, cols []*column.Col) (bool, error) { |
|
vals2, ok := m[h] |
|
if !ok { |
|
record := &RecordData{Handle: h, Values: vals} |
|
return false, errors.Errorf("data:%v != record:%v", nil, record) |
|
} |
|
if !exact { |
|
delete(m, h) |
|
return true, nil |
|
} |
|
|
|
if !reflect.DeepEqual(vals, vals2) { |
|
record1 := &RecordData{Handle: h, Values: vals2} |
|
record2 := &RecordData{Handle: h, Values: vals} |
|
return false, errors.Errorf("data:%v != record:%v", record1, record2) |
|
} |
|
|
|
delete(m, h) |
|
|
|
return true, nil |
|
} |
|
err := iterRecords(txn, t, startKey, t.Cols(), filterFunc) |
|
if err != nil { |
|
return errors.Trace(err) |
|
} |
|
|
|
for h, vals := range m { |
|
record := &RecordData{Handle: h, Values: vals} |
|
return errors.Errorf("data:%v != record:%v", record, nil) |
|
} |
|
|
|
return nil |
|
} |
|
|
|
// GetTableRecordsCount returns the total number of table records from startHandle. |
|
// If startHandle = 0, returns the total number of table records. |
|
func GetTableRecordsCount(txn kv.Transaction, t table.Table, startHandle int64) (int64, error) { |
|
startKey := t.RecordKey(startHandle, nil) |
|
it, err := txn.Seek(startKey) |
|
if err != nil { |
|
return 0, errors.Trace(err) |
|
} |
|
|
|
var cnt int64 |
|
prefix := t.RecordPrefix() |
|
for it.Valid() && it.Key().HasPrefix(prefix) { |
|
handle, err := tables.DecodeRecordKeyHandle(it.Key()) |
|
if err != nil { |
|
return 0, errors.Trace(err) |
|
} |
|
|
|
it.Close() |
|
rk := t.RecordKey(handle+1, nil) |
|
it, err = txn.Seek(rk) |
|
if err != nil { |
|
return 0, errors.Trace(err) |
|
} |
|
|
|
cnt++ |
|
} |
|
|
|
it.Close() |
|
|
|
return cnt, nil |
|
} |
|
|
|
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 iterRecords(retriever kv.Retriever, t table.Table, startKey kv.Key, cols []*column.Col, |
|
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()) |
|
|
|
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 := tables.DecodeRecordKeyHandle(it.Key()) |
|
if err != nil { |
|
return errors.Trace(err) |
|
} |
|
|
|
data, err := rowWithCols(retriever, t, handle, cols) |
|
if err != nil { |
|
return errors.Trace(err) |
|
} |
|
more, err := fn(handle, data, cols) |
|
if !more || err != nil { |
|
return errors.Trace(err) |
|
} |
|
|
|
rk := t.RecordKey(handle, nil) |
|
err = kv.NextUntil(it, util.RowKeyPrefixFilter(rk)) |
|
if err != nil { |
|
return errors.Trace(err) |
|
} |
|
} |
|
|
|
return nil |
|
}
|
|
|