-
Notifications
You must be signed in to change notification settings - Fork 5.9k
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
fix issue #463 #476
Changes from 5 commits
8772365
8ac556d
83a71b4
1da983a
71b9cd7
3428404
0eabdd4
4ef2722
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
|
@@ -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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. CheckDuplicated may be better with above comment. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,6 +18,7 @@ | |
package tables | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
"reflect" | ||
"strings" | ||
|
@@ -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. | ||
|
@@ -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 { | ||
|
@@ -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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. checkCreateUnqIdxAvail seems a little ugly, we should consider another better name :-) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ..um, I know, do you have a better name? haha There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the getDuplicatRow is for the "Insert xx ON update" statement handle. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
@@ -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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. s/duplicateID/rowID/g There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
} | ||
} | ||
|
@@ -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 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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;") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 ? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
would fail is because the val2 = 2 is a duplicate unique key.. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
|
There was a problem hiding this comment.
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.