-
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
*: Show key content in DuplicateEntry error message #828
Changes from 1 commit
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 |
---|---|---|
|
@@ -50,12 +50,20 @@ var ( | |
}) | ||
) | ||
|
||
// conditionPair is used to store lazy check condition. | ||
// If condition not match (value is not equal as expected one), returns err. | ||
type conditionPair struct { | ||
key Key | ||
value []byte | ||
err error | ||
} | ||
|
||
// UnionStore is an in-memory Store which contains a buffer for write and a | ||
// snapshot for read. | ||
type unionStore struct { | ||
*BufferStore | ||
snapshot Snapshot // for read | ||
lazyConditionPairs MemBuffer // for delay check | ||
snapshot Snapshot // for read | ||
lazyConditionPairs map[string](*conditionPair) // for delay check | ||
opts options | ||
} | ||
|
||
|
@@ -64,7 +72,7 @@ func NewUnionStore(snapshot Snapshot) UnionStore { | |
return &unionStore{ | ||
BufferStore: NewBufferStore(snapshot), | ||
snapshot: snapshot, | ||
lazyConditionPairs: &lazyMemBuffer{}, | ||
lazyConditionPairs: make(map[string](*conditionPair)), | ||
opts: make(map[Option]interface{}), | ||
} | ||
} | ||
|
@@ -121,7 +129,12 @@ func (us *unionStore) Get(k Key) ([]byte, error) { | |
v, err := us.MemBuffer.Get(k) | ||
if IsErrNotFound(err) { | ||
if _, ok := us.opts.Get(PresumeKeyNotExists); ok { | ||
err = us.markLazyConditionPair(k, nil) | ||
e, ok := us.opts.Get(PresumeKeyNotExistsError) | ||
if ok { | ||
err = us.markLazyConditionPair(k, nil, e.(error)) | ||
} else { | ||
err = us.markLazyConditionPair(k, nil, ErrKeyExists) | ||
} | ||
if err != nil { | ||
return nil, errors.Trace(err) | ||
} | ||
|
@@ -141,45 +154,36 @@ func (us *unionStore) Get(k Key) ([]byte, error) { | |
} | ||
|
||
// markLazyConditionPair marks a kv pair for later check. | ||
func (us *unionStore) markLazyConditionPair(k Key, v []byte) error { | ||
if len(v) == 0 { | ||
return errors.Trace(us.lazyConditionPairs.Delete(k)) | ||
func (us *unionStore) markLazyConditionPair(k Key, v []byte, e error) 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. Can we remove the return error? |
||
us.lazyConditionPairs[string(k)] = &conditionPair{ | ||
key: k, | ||
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 Key should be cloned before store in map, the caller may change the content after calling |
||
value: v, | ||
err: e, | ||
} | ||
return errors.Trace(us.lazyConditionPairs.Set(k, v)) | ||
return nil | ||
} | ||
|
||
// CheckLazyConditionPairs implements the UnionStore interface. | ||
func (us *unionStore) CheckLazyConditionPairs() error { | ||
var keys []Key | ||
it, err := us.lazyConditionPairs.Seek(nil) | ||
if err != nil { | ||
return errors.Trace(err) | ||
} | ||
for ; it.Valid(); it.Next() { | ||
keys = append(keys, it.Key().Clone()) | ||
} | ||
it.Close() | ||
|
||
if len(keys) == 0 { | ||
if len(us.lazyConditionPairs) == 0 { | ||
return nil | ||
} | ||
values, err := us.snapshot.BatchGet(keys) | ||
if err != nil { | ||
return errors.Trace(err) | ||
var keys []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. This slice can be made with capacity of the map's length. |
||
for _, v := range us.lazyConditionPairs { | ||
keys = append(keys, v.key.Clone()) | ||
} | ||
it, err = us.lazyConditionPairs.Seek(nil) | ||
values, err := us.snapshot.BatchGet(keys) | ||
if err != nil { | ||
return errors.Trace(err) | ||
} | ||
defer it.Close() | ||
for ; it.Valid(); it.Next() { | ||
keyStr := string(it.Key()) | ||
if len(it.Value()) == 0 { | ||
if _, exist := values[keyStr]; exist { | ||
return errors.Trace(ErrKeyExists) | ||
|
||
for k, v := range us.lazyConditionPairs { | ||
if len(v.value) == 0 { | ||
if _, exist := values[k]; exist { | ||
return errors.Trace(v.err) | ||
} | ||
} else { | ||
if bytes.Compare(values[keyStr], it.Value()) != 0 { | ||
if bytes.Compare(values[k], v.value) != 0 { | ||
return errors.Trace(ErrLazyConditionPairsNotMatch) | ||
} | ||
} | ||
|
@@ -201,7 +205,6 @@ func (us *unionStore) DelOption(opt Option) { | |
func (us *unionStore) Release() { | ||
us.snapshot.Release() | ||
us.BufferStore.Release() | ||
us.lazyConditionPairs.Release() | ||
} | ||
|
||
type options map[Option]interface{} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -416,12 +416,15 @@ func (t *Table) AddRecord(ctx context.Context, r []interface{}) (recordID int64, | |
if t.meta.PKIsHandle { | ||
// Check key exists. | ||
recordKey := t.RecordKey(recordID, nil) | ||
e := kv.ErrKeyExists.Gen("Duplicate entry '%d' for key 'PRIMARY'", recordID) | ||
txn.SetOption(kv.PresumeKeyNotExistsError, e) | ||
_, err = txn.Get(recordKey) | ||
if err == nil { | ||
return recordID, kv.ErrKeyExists | ||
return recordID, errors.Trace(e) | ||
} else if !terror.ErrorEqual(err, kv.ErrNotExist) { | ||
return 0, errors.Trace(err) | ||
} | ||
txn.DelOption(kv.PresumeKeyNotExistsError) | ||
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 think it is better to use a |
||
} | ||
|
||
for _, v := range t.indices { | ||
|
@@ -430,6 +433,24 @@ func (t *Table) AddRecord(ctx context.Context, r []interface{}) (recordID int64, | |
continue | ||
} | ||
colVals, _ := v.FetchValues(r) | ||
|
||
if v.Unique || v.Primary { | ||
// Pass pre-composed error to txn. | ||
strVals := make([]string, 0, len(colVals)) | ||
for _, cv := range colVals { | ||
cvs := "NULL" | ||
if cv != nil { | ||
cvs, err = types.ToString(cv) | ||
if err != nil { | ||
return 0, errors.Trace(err) | ||
} | ||
} | ||
strVals = append(strVals, cvs) | ||
} | ||
entryKey := strings.Join(strVals, "-") | ||
e := kv.ErrKeyExists.Gen("Duplicate entry '%s' for key '%s'", entryKey, v.Name) | ||
txn.SetOption(kv.PresumeKeyNotExistsError, e) | ||
} | ||
if err = v.X.Create(bs, colVals, recordID); err != nil { | ||
if terror.ErrorEqual(err, kv.ErrKeyExists) { | ||
// Get the duplicate row handle | ||
|
@@ -446,6 +467,7 @@ func (t *Table) AddRecord(ctx context.Context, r []interface{}) (recordID int64, | |
} | ||
return 0, errors.Trace(err) | ||
} | ||
txn.DelOption(kv.PresumeKeyNotExistsError) | ||
} | ||
|
||
if err = t.LockRow(ctx, recordID); err != nil { | ||
|
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.
Should we check the type conversion here in order to make sure it will not panic?
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.
We should only put error into the entry with PresumeKeyNotExistsError as key.
But may be we should check if e is nil.