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

fix issue #463 #476

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions kv/index_iter.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,40 @@ func (c *kvIndex) Create(txn Transaction, indexedValues []interface{}, h int64)
return errors.Trace(ErrKeyExists)
}

// Create creates a new entry in the kvIndex data.
// If the index is unique and there already exists an entry with the same key, Create will continue, and failed when commit
// MustCreate must call after the CheckUnique, which tell there is no duplicate key, and if someone create the same index between CheckUnique
// and MustCreate, finally our transaction commit will fail, so it is ok.
func (c *kvIndex) MustCreate(txn Transaction, indexedValues []interface{}, h int64) error {
Copy link
Member

Choose a reason for hiding this comment

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

The comment should start with the function name MustCreate.

Copy link
Member

Choose a reason for hiding this comment

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

There is no reason to fail if we call it "Must". "Create" is fine.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

But the Index.Create is already exist..so what name you think is better?

key, err := c.genIndexKey(indexedValues, h)
if err != nil {
return errors.Trace(err)
}
if !c.unique {
// TODO: reconsider value
err = txn.Set(key, []byte("timestamp?"))
return errors.Trace(err)
}

// unique index
err = txn.LockKeys(key)
if err != nil {
return errors.Trace(err)
}
_, err = txn.Get(key)
if err != nil && !IsErrNotFound(err) {
return errors.Trace(err)
}

// Set anyway
err = txn.Set(key, encodeHandle(h))
if err != nil {
return errors.Trace(err)
}

return nil
}

// Delete removes the entry for handle h and indexdValues from KV index.
func (c *kvIndex) Delete(txn Transaction, indexedValues []interface{}, h int64) error {
key, err := c.genIndexKey(indexedValues, h)
Expand Down Expand Up @@ -237,6 +271,27 @@ func (c *kvIndex) Seek(txn Transaction, indexedValues []interface{}) (iter Index
return &indexIter{it: it, idx: c, prefix: c.prefix}, hit, nil
}

// check if the index is duplicated, only check the unique index
func (c *kvIndex) CheckUnique(txn Transaction, indexedValues []interface{}, h int64) (duplicate bool, err error) {
Copy link
Member

Choose a reason for hiding this comment

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

CheckDuplicated may be better with above comment.

Copy link
Member

Choose a reason for hiding this comment

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

Same comment problem.

if !c.unique {
return false, nil
}

key, err := c.genIndexKey(indexedValues, h)
if err != nil {
return false, errors.Trace(err)
}

val, err := txn.Get(key)
if IsErrNotFound(err) {
return false, nil
} else if err == nil && len(val) > 0 {
return true, nil
} else {
return false, errors.Trace(err)
}
}

// SeekFirst returns an iterator which points to the first entry of the KV index.
func (c *kvIndex) SeekFirst(txn Transaction) (iter IndexIterator, err error) {
prefix := []byte(c.prefix)
Expand Down
12 changes: 7 additions & 5 deletions kv/kv.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,9 +190,11 @@ type IndexIterator interface {

// Index is the interface for index data on KV store.
type Index interface {
Create(txn Transaction, indexedValues []interface{}, h int64) error // supports insert into statement
Delete(txn Transaction, indexedValues []interface{}, h int64) error // supports delete from statement
Drop(txn Transaction) error // supports drop table, drop index statements
Seek(txn Transaction, indexedValues []interface{}) (iter IndexIterator, hit bool, err error) // supports where clause
SeekFirst(txn Transaction) (iter IndexIterator, err error) // supports aggregate min / ascending order by
Create(txn Transaction, indexedValues []interface{}, h int64) error // supports insert into statement
Delete(txn Transaction, indexedValues []interface{}, h int64) error // supports delete from statement
Drop(txn Transaction) error // supports drop table, drop index statements
Seek(txn Transaction, indexedValues []interface{}) (iter IndexIterator, hit bool, err error) // supports where clause
SeekFirst(txn Transaction) (iter IndexIterator, err error) // supports aggregate min / ascending order by
CheckUnique(txn Transaction, indexedValues []interface{}, h int64) (duplicate bool, err error) // supports check unique index is duplicate with indexedValues
MustCreate(txn Transaction, indexedValues []interface{}, h int64) error // for issue #463
}
107 changes: 91 additions & 16 deletions table/tables/tables.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
package tables

import (
"bytes"
"fmt"
"reflect"
"strings"
Expand All @@ -35,7 +36,6 @@ import (
"github.com/pingcap/tidb/sessionctx/variable"
"github.com/pingcap/tidb/table"
"github.com/pingcap/tidb/util"
"github.com/pingcap/tidb/util/errors2"
)

// Table implements table.Table interface.
Expand Down Expand Up @@ -291,6 +291,19 @@ func (t *Table) setNewData(ctx context.Context, h int64, data []interface{}) err
}

func (t *Table) rebuildIndices(ctx context.Context, h int64, touched []bool, oldData, newData []interface{}) error {
txn, err := ctx.GetTxn(false)
if err != nil {
return err
}

duplicate, err := t.checkUpdateUnqIdxAvail(txn, oldData, newData, h)
if err != nil {
return errors.Trace(err)
}
if duplicate {
return errors.Trace(kv.ErrKeyExists)
}

for _, idx := range t.Indices() {
idxTouched := false
for _, ic := range idx.Columns {
Expand Down Expand Up @@ -323,6 +336,74 @@ func (t *Table) rebuildIndices(ctx context.Context, h int64, touched []bool, old
return nil
}

// check unique indices constrains
func (t *Table) checkCreateUnqIdxAvail(txn kv.Transaction, r []interface{}, h int64) (duplicate bool, recordID int64, err error) {
Copy link
Member

Choose a reason for hiding this comment

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

you can return kv.ErrKeyExists directly for duplicated and use it for outer check.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

you are right.

for _, v := range t.indices {
if v == nil {
continue
}
colVals, _ := v.FetchValues(r)
duplicate, err = v.X.CheckUnique(txn, colVals, h)
if err != nil {
return false, 0, errors.Trace(err)
}

if duplicate {
recordID, err = getDuplicateRow(txn, colVals, v)
if err != nil {
return duplicate, 0, errors.Trace(err)
}
return duplicate, recordID, nil
}
}

return false, 0, nil
}

func isIndexValsEqual(vals1, vals2 []interface{}) bool {
byteVals1, _ := kv.EncodeValue(vals1...)
byteVals2, _ := kv.EncodeValue(vals2...)

return bytes.Equal(byteVals1, byteVals2)
}

func (t *Table) checkUpdateUnqIdxAvail(txn kv.Transaction, oldData, newData []interface{}, h int64) (duplicate bool, err error) {
Copy link
Member

Choose a reason for hiding this comment

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

checkCreateUnqIdxAvail seems a little ugly, we should consider another better name :-)
the same for checkInsertUnqIdxAvail

Copy link
Contributor Author

Choose a reason for hiding this comment

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

..um, I know, do you have a better name? haha

Copy link
Member

Choose a reason for hiding this comment

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

maybe checkUpdateIndex is enough. :-)

for _, v := range t.indices {
if v == nil {
continue
}
oldVals, _ := v.FetchValues(oldData)
newVals, _ := v.FetchValues(newData)
if isIndexValsEqual(oldVals, newVals) { // update the same index value is not necessary but available
continue
}

duplicate, err = v.X.CheckUnique(txn, newVals, h)
if err != nil {
return false, errors.Trace(err)
}

if duplicate {
return true, nil
}
}

return false, nil
}

func getDuplicateRow(txn kv.Transaction, colVals []interface{}, v *column.IndexedCol) (recordID int64, err error) {
Copy link
Member

Choose a reason for hiding this comment

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

CheckDuplicated may return handle directly at same time, so we don't need to call getDuplicatRow again.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

the getDuplicatRow is for the "Insert xx ON update" statement handle.

Copy link
Member

Choose a reason for hiding this comment

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

no different, for CheckDuplicated, you can returns the duplicated handle and kv.ErrKeyExist at same time, the following v.X.Seek and iter.Next is getting the duplicated handle too.

iter, _, err := v.X.Seek(txn, colVals)
if err != nil {
return 0, errors.Trace(err)
}
_, h, err := iter.Next()
if err != nil {
return 0, errors.Trace(err)
}

return h, nil
}

// AddRecord implements table.Table AddRecord interface.
func (t *Table) AddRecord(ctx context.Context, r []interface{}) (recordID int64, err error) {
id := variable.GetSessionVars(ctx).LastInsertID
Expand All @@ -339,25 +420,19 @@ func (t *Table) AddRecord(ctx context.Context, r []interface{}) (recordID int64,
if err != nil {
return 0, err
}
duplicate, duplicateID, err := t.checkCreateUnqIdxAvail(txn, r, recordID)
Copy link
Member

Choose a reason for hiding this comment

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

s/duplicateID/rowID/g

Copy link
Member

Choose a reason for hiding this comment

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

s/duplicate/exist/g

if err != nil {
return 0, errors.Trace(err)
}
if duplicate {
return duplicateID, errors.Trace(kv.ErrKeyExists)
}
for _, v := range t.indices {
if v == nil {
continue
}
colVals, _ := v.FetchValues(r)
if err = v.X.Create(txn, colVals, recordID); err != nil {
if errors2.ErrorEqual(err, kv.ErrKeyExists) {
// Get the duplicate row handle
// For insert on duplicate syntax, we should update the row
iter, _, err1 := v.X.Seek(txn, colVals)
if err1 != nil {
return 0, errors.Trace(err1)
}
_, h, err1 := iter.Next()
if err1 != nil {
return 0, errors.Trace(err1)
}
return h, errors.Trace(err)
}
if err = v.X.MustCreate(txn, colVals, recordID); err != nil {
return 0, errors.Trace(err)
}
}
Expand Down Expand Up @@ -517,7 +592,7 @@ func (t *Table) BuildIndexForRow(ctx context.Context, h int64, vals []interface{
if err != nil {
return err
}
if err = idx.X.Create(txn, vals, h); err != nil {
if err = idx.X.MustCreate(txn, vals, h); err != nil {
return err
}
return nil
Expand Down
3 changes: 2 additions & 1 deletion table/tables/tables_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,8 @@ func (ts *testSuite) TestBasic(c *C) {
prefix := tb.IndexPrefix()
cnt, err := countEntriesWithPrefix(ctx, prefix)
c.Assert(err, IsNil)
c.Assert(cnt, Greater, 0)
//c.Assert(cnt, Greater, 0) // it is not a correct assert, because after fix issue 463, there is no index in the table anymore
c.Assert(cnt, Equals, 0)
c.Assert(tb.Truncate(ctx), IsNil)
// Make sure index data is also removed after tb.Truncate().
cnt, err = countEntriesWithPrefix(ctx, prefix)
Expand Down
44 changes: 44 additions & 0 deletions tidb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1262,6 +1262,50 @@ func (s *testSessionSuite) TestIssue461(c *C) {
mustExecSQL(c, se, "drop table test;")
}

func (s *testSessionSuite) TestIssue463(c *C) {
// Testcase for https://github.com/pingcap/tidb/issues/463
store := newStore(c, s.dbName)
se := newSession(c, store, s.dbName)
mustExecSQL(c, se,
`CREATE TABLE test (
id int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
val int UNIQUE,
PRIMARY KEY (id)
);`)
mustExecSQL(c, se, "insert into test(id, val) values(1, 1);")
mustExecFailed(c, se, "insert into test(id, val) values(2, 1);")
mustExecSQL(c, se, "insert into test(id, val) values(2, 2);")

mustExecSQL(c, se, "begin;")
mustExecSQL(c, se, "insert into test(id, val) values(3, 3);")
mustExecFailed(c, se, "insert into test(id, val) values(4, 3);")
mustExecSQL(c, se, "insert into test(id, val) values(4, 4);")
mustExecSQL(c, se, "commit;")
se1 := newSession(c, store, s.dbName)
mustExecSQL(c, se1, "begin;")
mustExecSQL(c, se1, "insert into test(id, val) values(5, 6);")
mustExecSQL(c, se, "begin;")
mustExecSQL(c, se, "insert into test(id, val) values(20, 6);")
mustExecSQL(c, se, "commit;")
mustExecFailed(c, se1, "commit;")
mustExecSQL(c, se1, "insert into test(id, val) values(5, 5);")

mustExecSQL(c, se, "drop table test;")

mustExecSQL(c, se,
`CREATE TABLE test (
id int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
val1 int UNIQUE,
val2 int UNIQUE,
PRIMARY KEY (id)
);`)
mustExecSQL(c, se, "insert into test(id, val1, val2) values(1, 1, 1);")
mustExecSQL(c, se, "insert into test(id, val1, val2) values(2, 2, 2);")
mustExecFailed(c, se, "update test set val1 = 3, val2 = 2 where id = 1;")
mustExecSQL(c, se, "insert into test(id, val1, val2) values(3, 3, 3);")
mustExecSQL(c, se, "drop table test;")
Copy link
Member

Choose a reason for hiding this comment

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

Update row multiple times should be ok if insert successfully. Could you add more tests ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

"Update row multiple times should be ok if insert successfully." what do you mean?

in here, the

mustExecFailed(c, se, "update test set val1 = 3, val2 = 2 where id = 1;")

would fail is because the val2 = 2 is a duplicate unique key..

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll add more test case for it, as you mention above..thank you

}

func (s *testSessionSuite) TestBuiltin(c *C) {
store := newStore(c, s.dbName)
se := newSession(c, store, s.dbName)
Expand Down