diff --git a/include/errcode.h b/include/errcode.h index 72ae7d9420e31529cb52afaeedf0eb5df2566eea..b74a91522047eb31d80be45b33161c65c0cde4f0 100644 --- a/include/errcode.h +++ b/include/errcode.h @@ -101,14 +101,14 @@ enum { TNT_ERRMSG_MAX = 512 }; /* 46 */_(ER_UNUSED46, 2, "Unused46") \ /* 47 */_(ER_KEY_PART_COUNT, 2, "Key part count %d is greater than index part count %d") \ /* 48 */_(ER_PROC_RET, 2, "Return type '%s' is not supported in the binary protocol") \ - /* 49 */_(ER_TUPLE_NOT_FOUND, 2, "Tuple doesn't exist") \ + /* 49 */_(ER_TUPLE_NOT_FOUND, 2, "Tuple doesn't exist in index %d") \ /* 50 */_(ER_NO_SUCH_PROC, 2, "Procedure '%.*s' is not defined") \ /* 51 */_(ER_PROC_LUA, 2, "Lua error: %s") \ /* 52 */_(ER_SPACE_DISABLED, 2, "Space %u is disabled") \ /* 53 */_(ER_NO_SUCH_INDEX, 2, "No index #%u is defined in space %u") \ /* 54 */_(ER_NO_SUCH_FIELD, 2, "Field %u was not found in the tuple") \ - /* 55 */_(ER_TUPLE_FOUND, 2, "Tuple already exists") \ - /* 56 */_(ER_INDEX_VIOLATION, 2, "Duplicate key exists in unique index %d") \ + /* 55 */_(ER_TUPLE_FOUND, 2, "Duplicate key exists in unique index %d") \ + /* 56 */_(ER_UNUSED, 2, "") \ /* 57 */_(ER_NO_SUCH_SPACE, 2, "Space %u does not exist") diff --git a/include/errinj.h b/include/errinj.h index 6276020c026e4e74abff46a1d1362e594adacace..c9e13c3ee758d659bc2144397ba4438e28f228cb 100644 --- a/include/errinj.h +++ b/include/errinj.h @@ -42,7 +42,8 @@ struct errinj { #define ERRINJ_LIST(_) \ _(ERRINJ_TESTING, false) \ _(ERRINJ_WAL_IO, false) \ - _(ERRINJ_WAL_ROTATE, false) + _(ERRINJ_WAL_ROTATE, false) \ + _(ERRINJ_INDEX_ALLOC, false) ENUM0(errinj_enum, ERRINJ_LIST); extern struct errinj errinjs[]; @@ -52,6 +53,7 @@ bool errinj_get(int id); void errinj_set(int id, bool state); int errinj_set_byname(char *name, bool state); +struct tbuf; void errinj_info(struct tbuf *out); #ifdef NDEBUG diff --git a/include/mhash.h b/include/mhash.h index d7e97fa4020f88705a550999576f69367869044a..a1fa30916c7869d4d90aef399efd1c87eb5887e4 100644 --- a/include/mhash.h +++ b/include/mhash.h @@ -282,6 +282,37 @@ _mh(put)(struct _mh(t) *h, const mh_node_t *node, return x; } + +/** + * Find a node in the hash and replace it with a new value. + * Save the old node in p_old pointer, if it is provided. + * If the old node didn't exist, just insert the new node. + */ +static inline mh_int_t +_mh(replace)(struct _mh(t) *h, const mh_node_t *node, mh_node_t **p_old, + mh_hash_arg_t hash_arg, mh_eq_arg_t eq_arg) +{ + mh_int_t k = _mh(get)(h, node, hash_arg, eq_arg); + if (k == mh_end(h)) { + /* No such node yet: insert a new one. */ + if (p_old) { + *p_old = NULL; + } + return _mh(put)(h, node, hash_arg, eq_arg, NULL); + } else { + /* + * Maintain uniqueness: replace the old node + * with a new value. + */ + if (p_old) { + /* Save the old value. */ + memcpy(*p_old, &(h->p[k]), sizeof(mh_node_t)); + } + memcpy(&(h->p[k]), node, sizeof(mh_node_t)); + return k; + } +} + static inline void _mh(del)(struct _mh(t) *h, mh_int_t x, mh_hash_arg_t hash_arg, mh_eq_arg_t eq_arg) @@ -299,6 +330,15 @@ _mh(del)(struct _mh(t) *h, mh_int_t x, } #endif +static inline void +_mh(remove)(struct _mh(t) *h, const mh_node_t *node, + mh_hash_arg_t hash_arg, mh_eq_arg_t eq_arg) +{ + mh_int_t k = _mh(get)(h, node, hash_arg, eq_arg); + if (k != mh_end(h)) + _mh(del)(h, k, hash_arg, eq_arg); +} + #ifdef MH_SOURCE diff --git a/src/box/box.m b/src/box/box.m index 79e032367aac92728735e88a3d0a7ec435be4f4b..35a0d4427465daff402313e935fc88ec07f51a83 100644 --- a/src/box/box.m +++ b/src/box/box.m @@ -493,9 +493,6 @@ static void snapshot_write_tuple(struct log_io *l, struct fio_batch *batch, unsigned n, struct tuple *tuple) { - if (tuple->flags & GHOST) // do not save fictive rows - return; - struct box_snap_row header; header.space = n; header.tuple_size = tuple->field_count; diff --git a/src/box/box_lua.m b/src/box/box_lua.m index 81b695171198f97c71a79841859dae0551e7131c..a792245eaec40fed0cb3d323f6c4d4b0d1b9160c 100644 --- a/src/box/box_lua.m +++ b/src/box/box_lua.m @@ -835,11 +835,9 @@ lbox_index_count(struct lua_State *L) [index initIterator: it :ITER_EQ :key :key_part_count]; /* iterating over the index and counting tuples */ struct tuple *tuple; - while ((tuple = it->next(it)) != NULL) { - if (tuple->flags & GHOST) - continue; + while ((tuple = it->next(it)) != NULL) count++; - } + /* returning subtree size */ lua_pushnumber(L, count); return 1; diff --git a/src/box/index.h b/src/box/index.h index bf9697c48b3b98826d39af543a4588688ffa4d71..fb81e2e79fd5ecee675b77f03bb1c922221efb9b 100644 --- a/src/box/index.h +++ b/src/box/index.h @@ -123,6 +123,29 @@ struct index_traits bool allows_partial_key; }; +/** + * The manner in which replace in a unique index must treat + * duplicates (tuples with the same value of indexed key), + * possibly present in the index. + */ +enum dup_replace_mode { + /** + * If a duplicate is found, delete it and insert + * a new tuple instead. Otherwise, insert a new tuple. + */ + DUP_REPLACE_OR_INSERT, + /** + * If a duplicate is found, produce an error. + * I.e. require that no old key exists with the same + * value. + */ + DUP_INSERT, + /** + * Unless a duplicate exists, throw an error. + */ + DUP_REPLACE +}; + @interface Index: tnt_Object { /* Index features. */ struct index_traits *traits; @@ -172,9 +195,9 @@ struct index_traits - (struct tuple *) min; - (struct tuple *) max; - (struct tuple *) findByKey: (void *) key :(int) part_count; -- (struct tuple *) findByTuple: (struct tuple *) tuple; -- (void) remove: (struct tuple *) tuple; -- (void) replace: (struct tuple *) old_tuple :(struct tuple *) new_tuple; +- (struct tuple *) replace: (struct tuple *) old_tuple + :(struct tuple *) new_tuple + :(enum dup_replace_mode) mode; /** * Create a structure to represent an iterator. Must be * initialized separately. @@ -184,10 +207,6 @@ struct index_traits :(enum iterator_type) type :(void *) key :(int) part_count; -/** - * Unsafe search methods that do not check key part count. - */ -- (struct tuple *) findUnsafe: (void *) key :(int) part_count; @end struct iterator { @@ -199,4 +218,9 @@ void check_key_parts(struct key_def *key_def, int part_count, bool partial_key_allowed); +uint32_t +replace_check_dup(struct tuple *old_tuple, + struct tuple *dup_tuple, + enum dup_replace_mode mode); + #endif /* TARANTOOL_BOX_INDEX_H_INCLUDED */ diff --git a/src/box/index.m b/src/box/index.m index 3c01401ca30ce1d32003cf839525a4a61bed8763..8d02ed5238bb587b90199cbc769ebb8247c92f04 100644 --- a/src/box/index.m +++ b/src/box/index.m @@ -34,9 +34,10 @@ #include "exception.h" #include "space.h" #include "assoc.h" +#include "errinj.h" static struct index_traits index_traits = { - .allows_partial_key = true, + .allows_partial_key = false, }; static struct index_traits hash_index_traits = { @@ -60,6 +61,39 @@ check_key_parts(struct key_def *key_def, part_count, key_def->part_count); } +/** + * Check if replacement of an old tuple with a new one is + * allowed. + */ +uint32_t +replace_check_dup(struct tuple *old_tuple, + struct tuple *dup_tuple, + enum dup_replace_mode mode) +{ + if (dup_tuple == NULL) { + if (mode == DUP_REPLACE) { + /* + * dup_replace_mode is DUP_REPLACE, and + * a tuple with the same key is not found. + */ + return ER_TUPLE_NOT_FOUND; + } + } else { /* dup_tuple != NULL */ + if (dup_tuple != old_tuple && + (old_tuple != NULL || mode == DUP_INSERT)) { + /* + * There is a duplicate of new_tuple, + * and it's not old_tuple: we can't + * possibly delete more than one tuple + * at once. + */ + return ER_TUPLE_FOUND; + } + } + return 0; +} + + /* {{{ Index -- base class for all indexes. ********************/ @interface HashIndex: Index @@ -182,12 +216,6 @@ check_key_parts(struct key_def *key_def, } - (struct tuple *) findByKey: (void *) key :(int) part_count -{ - check_key_parts(key_def, part_count, false); - return [self findUnsafe: key :part_count]; -} - -- (struct tuple *) findUnsafe: (void *) key :(int) part_count { (void) key; (void) part_count; @@ -195,25 +223,15 @@ check_key_parts(struct key_def *key_def, return NULL; } -- (struct tuple *) findByTuple: (struct tuple *) pattern -{ - (void) pattern; - [self subclassResponsibility: _cmd]; - return NULL; -} - -- (void) remove: (struct tuple *) tuple -{ - (void) tuple; - [self subclassResponsibility: _cmd]; -} - -- (void) replace: (struct tuple *) old_tuple - :(struct tuple *) new_tuple +- (struct tuple *) replace: (struct tuple *) old_tuple + : (struct tuple *) new_tuple + : (enum dup_replace_mode) mode { (void) old_tuple; (void) new_tuple; + (void) mode; [self subclassResponsibility: _cmd]; + return NULL; } - (struct iterator *) allocIterator @@ -353,7 +371,7 @@ hash_iterator_lstr_eq(struct iterator *it) - (void) buildNext: (struct tuple *)tuple { - [self replace: NULL :tuple]; + [self replace: NULL :tuple :DUP_INSERT]; } - (void) endBuild @@ -377,7 +395,7 @@ hash_iterator_lstr_eq(struct iterator *it) [pk initIterator: it :ITER_ALL :NULL :0]; while ((tuple = it->next(it))) - [self replace: NULL :tuple]; + [self replace: NULL :tuple :DUP_INSERT]; } - (void) free @@ -397,31 +415,30 @@ hash_iterator_lstr_eq(struct iterator *it) return NULL; } -- (struct tuple *) findByTuple: (struct tuple *) tuple -{ - /* Hash index currently is always single-part. */ - void *field = tuple_field(tuple, key_def->parts[0].fieldno); - if (field == NULL) - tnt_raise(ClientError, :ER_NO_SUCH_FIELD, - key_def->parts[0].fieldno); - return [self findUnsafe :field :1]; -} - @end /* }}} */ /* {{{ Hash32Index ************************************************/ -static u32 -int32_key_to_value(void *key) +static inline struct mh_i32ptr_node_t +int32_key_to_node(void *key) { u32 key_size = load_varint32(&key); if (key_size != 4) tnt_raise(ClientError, :ER_KEY_FIELD_TYPE, "u32"); - return *((u32 *) key); + struct mh_i32ptr_node_t node = { .key = *(u32 *) key }; + return node; } +static inline struct mh_i32ptr_node_t +int32_tuple_to_node(struct tuple *tuple, struct key_def *key_def) +{ + void *field = tuple_field(tuple, key_def->parts[0].fieldno); + struct mh_i32ptr_node_t node = int32_key_to_node(field); + node.val = tuple; + return node; +} @implementation Hash32Index @@ -450,65 +467,73 @@ int32_key_to_value(void *key) return mh_size(int_hash); } -- (struct tuple *) findUnsafe: (void *) key :(int) part_count +- (struct tuple *) findByKey: (void *) key :(int) part_count { + assert(key_def->is_unique); + check_key_parts(key_def, part_count, false); + (void) part_count; struct tuple *ret = NULL; - u32 num = int32_key_to_value(key); - const struct mh_i32ptr_node_t node = { .key = num }; + struct mh_i32ptr_node_t node = int32_key_to_node(key); mh_int_t k = mh_i32ptr_get(int_hash, &node, NULL, NULL); if (k != mh_end(int_hash)) ret = mh_i32ptr_node(int_hash, k)->val; #ifdef DEBUG - say_debug("Hash32Index find(self:%p, key:%i) = %p", self, num, ret); + say_debug("Hash32Index find(self:%p, key:%i) = %p", self, node.key, ret); #endif return ret; } -- (void) remove: (struct tuple *) tuple +- (struct tuple *) replace: (struct tuple *) old_tuple + :(struct tuple *) new_tuple + :(enum dup_replace_mode) mode { - void *field = tuple_field(tuple, key_def->parts[0].fieldno); - u32 num = int32_key_to_value(field); - const struct mh_i32ptr_node_t node = { .key = num }; - mh_int_t k = mh_i32ptr_get(int_hash, &node, NULL, NULL); - if (k != mh_end(int_hash)) - mh_i32ptr_del(int_hash, k, NULL, NULL); -#ifdef DEBUG - say_debug("Hash32Index remove(self:%p, key:%i)", self, num); -#endif -} + struct mh_i32ptr_node_t new_node, old_node; + uint32_t errcode; -- (void) replace: (struct tuple *) old_tuple - :(struct tuple *) new_tuple -{ - void *field = tuple_field(new_tuple, key_def->parts[0].fieldno); - u32 num = int32_key_to_value(field); + if (new_tuple) { + struct mh_i32ptr_node_t *dup_node = &old_node; + new_node = int32_tuple_to_node(new_tuple, key_def); + mh_int_t pos = mh_i32ptr_replace(int_hash, &new_node, + &dup_node, NULL, NULL); - if (old_tuple != NULL) { - void *old_field = tuple_field(old_tuple, - key_def->parts[0].fieldno); - load_varint32(&old_field); - u32 old_num = *(u32 *)old_field; - const struct mh_i32ptr_node_t node = { .key = old_num }; - mh_int_t k = mh_i32ptr_get(int_hash, &node, NULL, NULL); - if (k != mh_end(int_hash)) - mh_i32ptr_del(int_hash, k, NULL, NULL); - } + ERROR_INJECT(ERRINJ_INDEX_ALLOC, + { + mh_i32ptr_del(int_hash, pos, NULL, NULL); + pos = mh_end(int_hash); + }); - const struct mh_i32ptr_node_t node = { .key = num, .val = new_tuple }; - mh_int_t pos = mh_i32ptr_put(int_hash, &node, NULL, NULL, NULL); - - if (pos == mh_end(int_hash)) - tnt_raise(LoggedError, :ER_MEMORY_ISSUE, (ssize_t) pos, - "int hash", "key"); - -#ifdef DEBUG - say_debug("Hash32Index replace(self:%p, old_tuple:%p, new_tuple:%p) key:%i", - self, old_tuple, new_tuple, num); -#endif + if (pos == mh_end(int_hash)) { + tnt_raise(LoggedError, :ER_MEMORY_ISSUE, (ssize_t) pos, + "int hash", "key"); + } + struct tuple *dup_tuple = dup_node ? dup_node->val : NULL; + errcode = replace_check_dup(old_tuple, dup_tuple, mode); + + if (errcode) { + mh_i32ptr_remove(int_hash, &new_node, NULL, NULL); + if (dup_node) { + pos = mh_i32ptr_replace(int_hash, dup_node, + NULL, NULL, NULL); + if (pos == mh_end(int_hash)) { + panic("Failed to allocate memory in " + "recover of int hash"); + } + } + tnt_raise(ClientError, :errcode, index_n(self)); + } + if (dup_tuple) + return dup_tuple; + } + if (old_tuple) { + old_node = int32_tuple_to_node(old_tuple, key_def); + mh_i32ptr_remove(int_hash, &old_node, NULL, NULL); + } + return old_tuple; } + - (struct iterator *) allocIterator { struct hash_i32_iterator *it = malloc(sizeof(struct hash_i32_iterator)); @@ -523,17 +548,16 @@ int32_key_to_value(void *key) - (void) initIterator: (struct iterator *) ptr: (enum iterator_type) type :(void *) key :(int) part_count { - (void) part_count; assert(ptr->free == hash_iterator_free); struct hash_i32_iterator *it = (struct hash_i32_iterator *) ptr; + struct mh_i32ptr_node_t node; switch (type) { case ITER_GE: if (key != NULL) { check_key_parts(key_def, part_count, traits->allows_partial_key); - u32 num = int32_key_to_value(key); - const struct mh_i32ptr_node_t node = { .key = num }; + node = int32_key_to_node(key); it->h_pos = mh_i32ptr_get(int_hash, &node, NULL, NULL); it->base.next = hash_iterator_i32_ge; break; @@ -546,8 +570,7 @@ int32_key_to_value(void *key) case ITER_EQ: check_key_parts(key_def, part_count, traits->allows_partial_key); - u32 num = int32_key_to_value(key); - const struct mh_i32ptr_node_t node = { .key = num }; + node = int32_key_to_node(key); it->h_pos = mh_i32ptr_get(int_hash, &node, NULL, NULL); it->base.next = hash_iterator_i32_eq; break; @@ -563,13 +586,23 @@ int32_key_to_value(void *key) /* {{{ Hash64Index ************************************************/ -static u64 -int64_key_to_value(void *key) +static inline struct mh_i64ptr_node_t +int64_key_to_node(void *key) { u32 key_size = load_varint32(&key); if (key_size != 8) tnt_raise(ClientError, :ER_KEY_FIELD_TYPE, "u64"); - return *((u64 *) key); + struct mh_i64ptr_node_t node = { .key = *(u64 *) key }; + return node; +} + +static inline struct mh_i64ptr_node_t +int64_tuple_to_node(struct tuple *tuple, struct key_def *key_def) +{ + void *field = tuple_field(tuple, key_def->parts[0].fieldno); + struct mh_i64ptr_node_t node = int64_key_to_node(field); + node.val = tuple; + return node; } @implementation Hash64Index @@ -598,64 +631,70 @@ int64_key_to_value(void *key) return mh_size(int64_hash); } -- (struct tuple *) findUnsafe: (void *) key :(int) part_count +- (struct tuple *) findByKey: (void *) key :(int) part_count { - (void) part_count; + assert(key_def->is_unique); + check_key_parts(key_def, part_count, false); struct tuple *ret = NULL; - u64 num = int64_key_to_value(key); - const struct mh_i64ptr_node_t node = { .key = num }; + struct mh_i64ptr_node_t node = int64_key_to_node(key); mh_int_t k = mh_i64ptr_get(int64_hash, &node, NULL, NULL); if (k != mh_end(int64_hash)) ret = mh_i64ptr_node(int64_hash, k)->val; #ifdef DEBUG - say_debug("Hash64Index find(self:%p, key:%"PRIu64") = %p", self, num, ret); + say_debug("Hash64Index find(self:%p, key:%"PRIu64") = %p", self, node.key, ret); #endif return ret; } -- (void) remove: (struct tuple *) tuple +- (struct tuple *) replace: (struct tuple *) old_tuple + :(struct tuple *) new_tuple + :(enum dup_replace_mode) mode { - void *field = tuple_field(tuple, key_def->parts[0].fieldno); - u64 num = int64_key_to_value(field); + struct mh_i64ptr_node_t new_node, old_node; + uint32_t errcode; - const struct mh_i64ptr_node_t node = { .key = num }; - mh_int_t k = mh_i64ptr_get(int64_hash, &node, NULL, NULL); - if (k != mh_end(int64_hash)) - mh_i64ptr_del(int64_hash, k, NULL, NULL); -#ifdef DEBUG - say_debug("Hash64Index remove(self:%p, key:%"PRIu64")", self, num); -#endif -} + if (new_tuple) { + struct mh_i64ptr_node_t *dup_node = &old_node; + new_node = int64_tuple_to_node(new_tuple, key_def); + mh_int_t pos = mh_i64ptr_replace(int64_hash, &new_node, + &dup_node, NULL, NULL); -- (void) replace: (struct tuple *) old_tuple - :(struct tuple *) new_tuple -{ - void *field = tuple_field(new_tuple, key_def->parts[0].fieldno); - u64 num = int64_key_to_value(field); - - if (old_tuple != NULL) { - void *old_field = tuple_field(old_tuple, - key_def->parts[0].fieldno); - load_varint32(&old_field); - u64 old_num = *(u64 *)old_field; - const struct mh_i64ptr_node_t node = { .key = old_num }; - mh_int_t k = mh_i64ptr_get(int64_hash, &node, NULL, NULL); - if (k != mh_end(int64_hash)) - mh_i64ptr_del(int64_hash, k, NULL, NULL); + ERROR_INJECT(ERRINJ_INDEX_ALLOC, + { + mh_i64ptr_del(int64_hash, pos, NULL, NULL); + pos = mh_end(int64_hash); + }); + if (pos == mh_end(int64_hash)) { + tnt_raise(LoggedError, :ER_MEMORY_ISSUE, (ssize_t) pos, + "int64 hash", "key"); + } + struct tuple *dup_tuple = dup_node ? dup_node->val : NULL; + errcode = replace_check_dup(old_tuple, dup_tuple, mode); + + if (errcode) { + mh_i64ptr_remove(int64_hash, &new_node, NULL, NULL); + if (dup_node) { + pos = mh_i64ptr_replace(int64_hash, dup_node, + NULL, NULL, NULL); + if (pos == mh_end(int64_hash)) { + panic("Failed to allocate memory in " + "recover of int64 hash"); + } + } + tnt_raise(ClientError, :errcode, index_n(self)); + } + if (dup_tuple) + return dup_tuple; } - - const struct mh_i64ptr_node_t node = { .key = num, .val = new_tuple }; - mh_int_t pos = mh_i64ptr_put(int64_hash, &node, NULL, NULL, NULL); - if (pos == mh_end(int64_hash)) - tnt_raise(LoggedError, :ER_MEMORY_ISSUE, (ssize_t) pos, - "int64 hash", "key"); -#ifdef DEBUG - say_debug("Hash64Index replace(self:%p, old_tuple:%p, tuple:%p) key:%"PRIu64, - self, old_tuple, new_tuple, num); -#endif + if (old_tuple) { + old_node = int64_tuple_to_node(old_tuple, key_def); + mh_i64ptr_remove(int64_hash, &old_node, NULL, NULL); + } + return old_tuple; } + - (struct iterator *) allocIterator { struct hash_i64_iterator *it = malloc(sizeof(struct hash_i64_iterator)); @@ -674,14 +713,14 @@ int64_key_to_value(void *key) (void) part_count; assert(ptr->free == hash_iterator_free); struct hash_i64_iterator *it = (struct hash_i64_iterator *) ptr; + struct mh_i64ptr_node_t node; switch (type) { case ITER_GE: if (key != NULL) { check_key_parts(key_def, part_count, traits->allows_partial_key); - u64 num = int64_key_to_value(key); - const struct mh_i64ptr_node_t node = { .key = num }; + node = int64_key_to_node(key); it->h_pos = mh_i64ptr_get(int64_hash, &node, NULL, NULL); it->base.next = hash_iterator_i64_ge; break; @@ -694,8 +733,7 @@ int64_key_to_value(void *key) case ITER_EQ: check_key_parts(key_def, part_count, traits->allows_partial_key); - u64 num = int64_key_to_value(key); - const struct mh_i64ptr_node_t node = { .key = num }; + node = int64_key_to_node(key); it->h_pos = mh_i64ptr_get(int64_hash, &node, NULL, NULL); it->base.next = hash_iterator_i64_eq; break; @@ -711,6 +749,19 @@ int64_key_to_value(void *key) /* {{{ HashStrIndex ***********************************************/ +static inline struct mh_lstrptr_node_t +lstrptr_tuple_to_node(struct tuple *tuple, struct key_def *key_def) +{ + void *field = tuple_field(tuple, key_def->parts[0].fieldno); + if (field == NULL) + tnt_raise(ClientError, :ER_NO_SUCH_FIELD, + key_def->parts[0].fieldno); + + struct mh_lstrptr_node_t node = { .key = field, .val = tuple }; + return node; +} + + @implementation HashStrIndex - (void) reserve: (u32) n_tuples { @@ -737,9 +788,11 @@ int64_key_to_value(void *key) return mh_size(str_hash); } -- (struct tuple *) findUnsafe: (void *) key :(int) part_count +- (struct tuple *) findByKey: (void *) key :(int) part_count { - (void) part_count; + assert(key_def->is_unique); + check_key_parts(key_def, part_count, false); + struct tuple *ret = NULL; const struct mh_lstrptr_node_t node = { .key = key }; mh_int_t k = mh_lstrptr_get(str_hash, &node, NULL, NULL); @@ -753,49 +806,52 @@ int64_key_to_value(void *key) return ret; } -- (void) remove: (struct tuple *) tuple +- (struct tuple *) replace: (struct tuple *) old_tuple + :(struct tuple *) new_tuple + :(enum dup_replace_mode) mode { - void *field = tuple_field(tuple, key_def->parts[0].fieldno); - - const struct mh_lstrptr_node_t node = { .key = field }; - mh_int_t k = mh_lstrptr_get(str_hash, &node, NULL, NULL); - if (k != mh_end(str_hash)) - mh_lstrptr_del(str_hash, k, NULL, NULL); -#ifdef DEBUG - u32 field_size = load_varint32(&field); - say_debug("HashStrIndex remove(self:%p, key:'%.*s')", - self, field_size, (u8 *)field); -#endif -} + struct mh_lstrptr_node_t new_node, old_node; + uint32_t errcode; -- (void) replace: (struct tuple *) old_tuple - :(struct tuple *) new_tuple -{ - void *field = tuple_field(new_tuple, key_def->parts[0].fieldno); + if (new_tuple) { + struct mh_lstrptr_node_t *dup_node = &old_node; + new_node = lstrptr_tuple_to_node(new_tuple, key_def); + mh_int_t pos = mh_lstrptr_replace(str_hash, &new_node, + &dup_node, NULL, NULL); - if (field == NULL) - tnt_raise(ClientError, :ER_NO_SUCH_FIELD, - key_def->parts[0].fieldno); + ERROR_INJECT(ERRINJ_INDEX_ALLOC, + { + mh_lstrptr_del(str_hash, pos, NULL, NULL); + pos = mh_end(str_hash); + }); - if (old_tuple != NULL) { - void *old_field = tuple_field(old_tuple, - key_def->parts[0].fieldno); - const struct mh_lstrptr_node_t node = { .key = old_field }; - mh_int_t k = mh_lstrptr_get(str_hash, &node, NULL, NULL); - if (k != mh_end(str_hash)) - mh_lstrptr_del(str_hash, k, NULL, NULL); + if (pos == mh_end(str_hash)) { + tnt_raise(LoggedError, :ER_MEMORY_ISSUE, (ssize_t) pos, + "str hash", "key"); + } + struct tuple *dup_tuple = dup_node ? dup_node->val : NULL; + errcode = replace_check_dup(old_tuple, dup_tuple, mode); + + if (errcode) { + mh_lstrptr_remove(str_hash, &new_node, NULL, NULL); + if (dup_node) { + pos = mh_lstrptr_replace(str_hash, dup_node, + NULL, NULL, NULL); + if (pos == mh_end(str_hash)) { + panic("Failed to allocate memory in " + "recover of str hash"); + } + } + tnt_raise(ClientError, :errcode, index_n(self)); + } + if (dup_tuple) + return dup_tuple; } - - const struct mh_lstrptr_node_t node = { .key = field, .val = new_tuple}; - mh_int_t pos = mh_lstrptr_put(str_hash, &node, NULL, NULL, NULL); - if (pos == mh_end(str_hash)) - tnt_raise(LoggedError, :ER_MEMORY_ISSUE, (ssize_t) pos, - "str hash", "key"); -#ifdef DEBUG - u32 field_size = load_varint32(&field); - say_debug("HashStrIndex replace(self:%p, old_tuple:%p, tuple:%p) key:'%.*s'", - self, old_tuple, new_tuple, field_size, (u8 *)field); -#endif + if (old_tuple) { + old_node = lstrptr_tuple_to_node(old_tuple, key_def); + mh_lstrptr_remove(str_hash, &old_node, NULL, NULL); + } + return old_tuple; } - (struct iterator *) allocIterator @@ -818,13 +874,14 @@ int64_key_to_value(void *key) assert(ptr->free == hash_iterator_free); struct hash_lstr_iterator *it = (struct hash_lstr_iterator *) ptr; + struct mh_lstrptr_node_t node; switch (type) { case ITER_GE: if (key != NULL) { check_key_parts(key_def, part_count, traits->allows_partial_key); - const struct mh_lstrptr_node_t node = { .key = key }; + node.key = key; it->h_pos = mh_lstrptr_get(str_hash, &node, NULL, NULL); it->base.next = hash_iterator_lstr_ge; break; @@ -837,7 +894,7 @@ int64_key_to_value(void *key) case ITER_EQ: check_key_parts(key_def, part_count, traits->allows_partial_key); - const struct mh_lstrptr_node_t node = { .key = key }; + node.key = key; it->h_pos = mh_lstrptr_get(str_hash, &node, NULL, NULL); it->base.next = hash_iterator_lstr_eq; break; diff --git a/src/box/request.m b/src/box/request.m index b42ac6dfbe23398d9123e5ead51d380d9ce36d9c..e8e590f532ae419f027c27e172af1dd6a8a7579e 100644 --- a/src/box/request.m +++ b/src/box/request.m @@ -65,6 +65,14 @@ read_space(struct tbuf *data) return space_find(space_no); } +enum dup_replace_mode +dup_replace_mode(uint32_t flags) +{ + return flags & BOX_ADD ? DUP_INSERT : + flags & BOX_REPLACE ? + DUP_REPLACE : DUP_REPLACE_OR_INSERT; +} + static void execute_replace(struct request *request, struct txn *txn) { @@ -80,31 +88,19 @@ execute_replace(struct request *request, struct txn *txn) if (data->size == 0 || data->size != valid_tuple(data, field_count)) tnt_raise(IllegalParams, :"incorrect tuple length"); - txn->new_tuple = tuple_alloc(data->size); - tuple_ref(txn->new_tuple, 1); - txn->new_tuple->field_count = field_count; - memcpy(txn->new_tuple->data, data->data, data->size); + struct tuple *new_tuple = tuple_alloc(data->size); + new_tuple->field_count = field_count; + memcpy(new_tuple->data, data->data, data->size); - /* Try to find tuple by primary key */ - Index *pk = space_index(sp, 0); + @try { + space_validate_tuple(sp, new_tuple); + enum dup_replace_mode mode = dup_replace_mode(request->flags); + txn_replace(txn, sp, NULL, new_tuple, mode); - /* Check to see if the tuple has a sufficient number of fields. */ - if (unlikely(txn->new_tuple->field_count < sp->max_fieldno)) { - tnt_raise(IllegalParams, :"tuple must have all indexed fields"); + } @catch (tnt_Exception *e) { + tuple_free(new_tuple); + @throw; } - - /* lookup old_tuple only when we have enough fields in new_tuple */ - struct tuple *old_tuple = [pk findByTuple: txn->new_tuple]; - - if (request->flags & BOX_ADD && old_tuple != NULL) - tnt_raise(ClientError, :ER_TUPLE_FOUND); - - if (request->flags & BOX_REPLACE && old_tuple == NULL) - tnt_raise(ClientError, :ER_TUPLE_NOT_FOUND); - - space_validate(sp, old_tuple, txn->new_tuple); - - txn_add_undo(txn, sp, old_tuple, txn->new_tuple); } /** {{{ UPDATE request implementation. @@ -708,20 +704,27 @@ execute_update(struct request *request, struct txn *txn) /* Try to find the tuple by primary key. */ struct tuple *old_tuple = [pk findByKey :key :key_part_count]; - if (old_tuple != NULL) { - /* number of operations */ - u32 op_cnt = read_u32(data); - struct update_op *ops = update_read_ops(data, op_cnt); - struct rope *rope = update_create_rope(ops, ops + op_cnt, - old_tuple); - /* allocate new tuple */ - size_t new_tuple_len = update_calc_new_tuple_length(rope); - txn->new_tuple = tuple_alloc(new_tuple_len); - tuple_ref(txn->new_tuple, 1); - do_update_ops(rope, txn->new_tuple); - space_validate(sp, old_tuple, txn->new_tuple); + if (old_tuple == NULL) + return; + + /* number of operations */ + u32 op_cnt = read_u32(data); + struct update_op *ops = update_read_ops(data, op_cnt); + struct rope *rope = update_create_rope(ops, ops + op_cnt, + old_tuple); + /* Allocate a new tuple. */ + size_t new_tuple_len = update_calc_new_tuple_length(rope); + struct tuple *new_tuple = tuple_alloc(new_tuple_len); + + @try { + do_update_ops(rope, new_tuple); + space_validate_tuple(sp, new_tuple); + txn_replace(txn, sp, old_tuple, new_tuple, DUP_INSERT); + + } @catch (tnt_Exception *e) { + tuple_free(new_tuple); + @throw; } - txn_add_undo(txn, sp, old_tuple, txn->new_tuple); } /** }}} */ @@ -759,9 +762,6 @@ execute_select(struct request *request, struct port *port) struct tuple *tuple; while ((tuple = it->next(it)) != NULL) { - if (tuple->flags & GHOST) - continue; - if (offset > 0) { offset--; continue; @@ -794,7 +794,10 @@ execute_delete(struct request *request, struct txn *txn) Index *pk = space_index(sp, 0); struct tuple *old_tuple = [pk findByKey :key :key_part_count]; - txn_add_undo(txn, sp, old_tuple, NULL); + if (old_tuple == NULL) + return; + + txn_replace(txn, sp, old_tuple, NULL, DUP_REPLACE_OR_INSERT); } /** To collects stats, we need a valid request type. diff --git a/src/box/space.h b/src/box/space.h index dec54040830ba05593935d4bacd85b2ffaf46e44..aff311369bd59b79c05bcf286b574ffaf70babc9 100644 --- a/src/box/space.h +++ b/src/box/space.h @@ -85,12 +85,101 @@ struct space { /** Get space ordinal number. */ static inline i32 space_n(struct space *sp) { return sp->no; } -void space_validate(struct space *sp, struct tuple *old_tuple, - struct tuple *new_tuple); -void space_replace(struct space *sp, struct tuple *old_tuple, - struct tuple *new_tuple); -void space_remove(struct space *sp, struct tuple *tuple); +/** + * @brief A single method to handle REPLACE, DELETE and UPDATE. + * + * @param sp space + * @param old_tuple the tuple that should be removed (can be NULL) + * @param new_tuple the tuple that should be inserted (can be NULL) + * @param mode dup_replace_mode, used only if new_tuple is not + * NULL and old_tuple is NULL, and only for the + * primary key. + * + * For DELETE, new_tuple must be NULL. old_tuple must be + * previously found in the primary key. + * + * For REPLACE, old_tuple must be NULL. The additional + * argument dup_replace_mode further defines how REPLACE + * should proceed. + * + * For UPDATE, both old_tuple and new_tuple must be given, + * where old_tuple must be previously found in the primary key. + * + * Let's consider these three cases in detail: + * + * 1. DELETE, old_tuple is not NULL, new_tuple is NULL + * The effect is that old_tuple is removed from all + * indexes. dup_replace_mode is ignored. + * + * 2. REPLACE, old_tuple is NULL, new_tuple is not NULL, + * has one simple sub-case and two with further + * ramifications: + * + * A. dup_replace_mode is DUP_INSERT. Attempts to insert the + * new tuple into all indexes. If *any* of the unique indexes + * has a duplicate key, deletion is aborted, all of its + * effects are removed, and an error is thrown. + * + * B. dup_replace_mode is DUP_REPLACE. It means an existing + * tuple has to be replaced with the new one. To do it, tries + * to find a tuple with a duplicate key in the primary index. + * If the tuple is not found, throws an error. Otherwise, + * replaces the old tuple with a new one in the primary key. + * Continues on to secondary keys, but if there is any + * secondary key, which has a duplicate tuple, but one which + * is different from the duplicate found in the primary key, + * aborts, puts everything back, throws an exception. + * + * For example, if there is a space with 3 unique keys and + * two tuples { 1, 2, 3 } and { 3, 1, 2 }: + * + * This REPLACE/DUP_REPLACE is OK: { 1, 5, 5 } + * This REPLACE/DUP_REPLACE is not OK: { 2, 2, 2 } (there + * is no tuple with key '2' in the primary key) + * This REPLACE/DUP_REPLACE is not OK: { 1, 1, 1 } (there + * is a conflicting tuple in the secondary unique key). + * + * C. dup_replace_mode is DUP_REPLACE_OR_INSERT. If + * there is a duplicate tuple in the primary key, behaves the + * same way as DUP_REPLACE, otherwise behaves the same way as + * DUP_INSERT. + * + * 3. UPDATE has to delete the old tuple and insert a new one. + * dup_replace_mode is ignored. + * Note that old_tuple primary key doesn't have to match + * new_tuple primary key, thus a duplicate can be found. + * For this reason, and since there can be duplicates in + * other indexes, UPDATE is the same as DELETE + + * REPLACE/DUP_INSERT. + * + * @return old_tuple. DELETE, UPDATE and REPLACE/DUP_REPLACE + * always produce an old tuple. REPLACE/DUP_INSERT always returns + * NULL. REPLACE/DUP_REPLACE_OR_INSERT may or may not find + * a duplicate. + * + * The method is all-or-nothing in all cases. Changes are either + * applied to all indexes, or nothing applied at all. + * + * Note, that even in case of REPLACE, dup_replace_mode only + * affects the primary key, for secondary keys it's always + * DUP_INSERT. + * + * @return tuple that was removed from the space. + * The call never removes more than one tuple: if + * old_tuple is given, dup_replace_mode is ignored. + * Otherwise, it's taken into account only for the + * primary key. + */ +struct tuple * +space_replace(struct space *space, struct tuple *old_tuple, + struct tuple *new_tuple, enum dup_replace_mode mode); +/** + * Check that the tuple has correct arity and correct field + * types (a pre-requisite for an INSERT). + */ +void +space_validate_tuple(struct space *sp, struct tuple *new_tuple); /** * Get index by index number. diff --git a/src/box/space.m b/src/box/space.m index 7f3dbfab014c211d6b70fe8cbb06e6a4101014da..523f7ae05e550e8eafce018310cac73698975eed 100644 --- a/src/box/space.m +++ b/src/box/space.m @@ -120,31 +120,51 @@ key_free(struct key_def *key_def) free(key_def->cmp_order); } -void +struct tuple * space_replace(struct space *sp, struct tuple *old_tuple, - struct tuple *new_tuple) + struct tuple *new_tuple, enum dup_replace_mode mode) { - int n = index_count(sp); - for (int i = 0; i < n; i++) { - Index *index = sp->index[i]; - [index replace: old_tuple :new_tuple]; + int i = 0; + @try { + /* Update the primary key */ + Index *pk = sp->index[0]; + assert(pk->key_def->is_unique); + /* + * If old_tuple is not NULL, the index + * has to find and delete it, or raise an + * error. + */ + old_tuple = [pk replace: old_tuple :new_tuple :mode]; + + assert(old_tuple || new_tuple); + int n = index_count(sp); + /* Update secondary keys */ + for (i = i + 1; i < n; i++) { + Index *index = sp->index[i]; + [index replace: old_tuple :new_tuple :DUP_INSERT]; + } + return old_tuple; + } @catch (tnt_Exception *e) { + /* Rollback all changes */ + for (i = i - 1; i >= 0; i--) { + Index *index = sp->index[i]; + [index replace: new_tuple: old_tuple: DUP_INSERT]; + } + @throw; } } void -space_validate(struct space *sp, struct tuple *old_tuple, - struct tuple *new_tuple) +space_validate_tuple(struct space *sp, struct tuple *new_tuple) { - int n = index_count(sp); - - /* Only secondary indexes are validated here. So check to see - if there are any.*/ - if (n <= 1) { - return; - } + /* Check to see if the tuple has a sufficient number of fields. */ + if (new_tuple->field_count < sp->max_fieldno) + tnt_raise(IllegalParams, + :"tuple must have all indexed fields"); if (sp->arity > 0 && sp->arity != new_tuple->field_count) - tnt_raise(IllegalParams, :"tuple field count must match space cardinality"); + tnt_raise(IllegalParams, + :"tuple field count must match space cardinality"); /* Sweep through the tuple and check the field sizes. */ u8 *data = new_tuple->data; @@ -152,38 +172,20 @@ space_validate(struct space *sp, struct tuple *old_tuple, /* Get the size of the current field and advance. */ u32 len = load_varint32((void **) &data); data += len; - - /* Check fixed size fields (NUM and NUM64) and skip undefined - size fields (STRING and UNKNOWN). */ + /* + * Check fixed size fields (NUM and NUM64) and + * skip undefined size fields (STRING and UNKNOWN). + */ if (sp->field_types[f] == NUM) { if (len != sizeof(u32)) - tnt_raise(IllegalParams, :"field must be NUM"); + tnt_raise(ClientError, :ER_KEY_FIELD_TYPE, + "u32"); } else if (sp->field_types[f] == NUM64) { if (len != sizeof(u64)) - tnt_raise(IllegalParams, :"field must be NUM64"); + tnt_raise(ClientError, :ER_KEY_FIELD_TYPE, + "u64"); } } - - /* Check key uniqueness */ - for (int i = 1; i < n; ++i) { - Index *index = sp->index[i]; - if (index->key_def->is_unique) { - struct tuple *tuple = [index findByTuple: new_tuple]; - if (tuple != NULL && tuple != old_tuple) - tnt_raise(ClientError, :ER_INDEX_VIOLATION, - index_n(index)); - } - } -} - -void -space_remove(struct space *sp, struct tuple *tuple) -{ - int n = index_count(sp); - for (int i = 0; i < n; i++) { - Index *index = sp->index[i]; - [index remove: tuple]; - } } void diff --git a/src/box/tree.h b/src/box/tree.h index d6b4276fa94b06f08ac446638e72448ae9eed565..6cacb96a192aaba6aec68189faf6c8bc5e8392ac 100644 --- a/src/box/tree.h +++ b/src/box/tree.h @@ -45,6 +45,8 @@ typedef int (*tree_cmp_t)(const void *, const void *, void *); sptree_index tree; }; ++ (struct index_traits *) traits; + + (Index *) alloc: (struct key_def *) key_def :(struct space *) space; - (void) buildNext: (struct tuple *) tuple; diff --git a/src/box/tree.m b/src/box/tree.m index 79f99cb824b5dad24bf0ac6c6534a0773ec1947f..b437e987f1b12130c9d82e80888508cd7dc8ea22 100644 --- a/src/box/tree.m +++ b/src/box/tree.m @@ -30,10 +30,15 @@ #include "tuple.h" #include "space.h" #include "exception.h" +#include "errinj.h" #include <pickle.h> /* {{{ Utilities. *************************************************/ +static struct index_traits tree_index_traits = { + .allows_partial_key = true, +}; + /** * Unsigned 32-bit int comparison. */ @@ -867,6 +872,11 @@ tree_iterator_gt(struct iterator *iterator) @implementation TreeIndex ++ (struct index_traits *) traits +{ + return &tree_index_traits; +} + + (Index *) alloc: (struct key_def *) key_def :(struct space *) space { enum tree_type type = find_tree_type(space, key_def); @@ -915,8 +925,11 @@ tree_iterator_gt(struct iterator *iterator) return [self unfold: node]; } -- (struct tuple *) findUnsafe: (void *) key : (int) part_count +- (struct tuple *) findByKey: (void *) key : (int) part_count { + assert(key_def->is_unique); + check_key_parts(key_def, part_count, false); + struct key_data *key_data = alloca(sizeof(struct key_data) + _SIZEOF_SPARSE_PARTS(part_count)); @@ -929,40 +942,39 @@ tree_iterator_gt(struct iterator *iterator) return [self unfold: node]; } -- (struct tuple *) findByTuple: (struct tuple *) tuple +- (struct tuple *) replace: (struct tuple *) old_tuple + :(struct tuple *) new_tuple + :(enum dup_replace_mode) mode { - struct key_data *key_data - = alloca(sizeof(struct key_data) + _SIZEOF_SPARSE_PARTS(tuple->field_count)); - - key_data->data = tuple->data; - key_data->part_count = tuple->field_count; - fold_with_sparse_parts(key_def, tuple, key_data->parts); + size_t node_size = [self node_size]; + void *new_node = alloca(node_size); + void *old_node = alloca(node_size); + uint32_t errcode; - void *node = sptree_index_find(&tree, key_data); - return [self unfold: node]; -} + if (new_tuple) { + void *dup_node = old_node; + [self fold: new_node :new_tuple]; -- (void) remove: (struct tuple *) tuple -{ - void *node = alloca([self node_size]); - [self fold: node :tuple]; - sptree_index_delete(&tree, node); -} + /* Try to optimistically replace the new_tuple. */ + sptree_index_replace(&tree, new_node, &dup_node); -- (void) replace: (struct tuple *) old_tuple - : (struct tuple *) new_tuple -{ - if (new_tuple->field_count < key_def->max_fieldno) - tnt_raise(ClientError, :ER_NO_SUCH_FIELD, - key_def->max_fieldno); + struct tuple *dup_tuple = [self unfold: dup_node]; + errcode = replace_check_dup(old_tuple, dup_tuple, mode); - void *node = alloca([self node_size]); + if (errcode) { + sptree_index_delete(&tree, new_node); + if (dup_node) + sptree_index_replace(&tree, dup_node, NULL); + tnt_raise(ClientError, :errcode, index_n(self)); + } + if (dup_tuple) + return dup_tuple; + } if (old_tuple) { - [self fold: node :old_tuple]; - sptree_index_delete(&tree, node); + [self fold: old_node :old_tuple]; + sptree_index_delete(&tree, old_node); } - [self fold: node :new_tuple]; - sptree_index_insert(&tree, node); + return old_tuple; } - (struct iterator *) allocIterator diff --git a/src/box/txn.h b/src/box/txn.h index dedc66f930ccc6d91575044eaa91f2e0285b4f8c..60ab3fe437a7b485709889bb41d0782c044ef190 100644 --- a/src/box/txn.h +++ b/src/box/txn.h @@ -29,16 +29,12 @@ * SUCH DAMAGE. */ #include <tbuf.h> +#include "index.h" + struct tuple; struct space; -enum txn_flags { - BOX_NOT_STORE = 0x1 -}; - struct txn { - u32 txn_flags; - /* Undo info. */ struct space *space; struct tuple *old_tuple; @@ -49,19 +45,12 @@ struct txn { struct tbuf req; }; -/** Tuple flags used for locking. */ -enum tuple_flags { - /** Waiting on WAL write to complete. */ - WAL_WAIT = 0x1, - /** A new primary key is created but not yet written to WAL. */ - GHOST = 0x2, -}; - struct txn *txn_begin(); void txn_commit(struct txn *txn); void txn_finish(struct txn *txn); void txn_rollback(struct txn *txn); void txn_add_redo(struct txn *txn, u16 op, struct tbuf *data); -void txn_add_undo(struct txn *txn, struct space *space, - struct tuple *old_tuple, struct tuple *new_tuple); +void txn_replace(struct txn *txn, struct space *space, + struct tuple *old_tuple, struct tuple *new_tuple, + enum dup_replace_mode mode); #endif /* TARANTOOL_BOX_TXN_H_INCLUDED */ diff --git a/src/box/txn.m b/src/box/txn.m index 1dbeafdcb97f4b08ab0a86d5d275a83e52415046..925aa950dbadaca42ecf08efc75757d3e916fdbe 100644 --- a/src/box/txn.m +++ b/src/box/txn.m @@ -32,20 +32,6 @@ #include <recovery.h> #include <fiber.h> -static void -txn_lock(struct txn *txn __attribute__((unused)), struct tuple *tuple) -{ - say_debug("txn_lock(%p)", tuple); - tuple->flags |= WAL_WAIT; -} - -static void -txn_unlock(struct txn *txn __attribute__((unused)), struct tuple *tuple) -{ - assert(tuple->flags & WAL_WAIT); - tuple->flags &= ~WAL_WAIT; -} - void txn_add_redo(struct txn *txn, u16 op, struct tbuf *data) { @@ -59,31 +45,23 @@ txn_add_redo(struct txn *txn, u16 op, struct tbuf *data) } void -txn_add_undo(struct txn *txn, struct space *space, - struct tuple *old_tuple, struct tuple *new_tuple) +txn_replace(struct txn *txn, struct space *space, + struct tuple *old_tuple, struct tuple *new_tuple, + enum dup_replace_mode mode) { /* txn_add_undo() must be done after txn_add_redo() */ assert(txn->op != 0); - txn->new_tuple = new_tuple; - if (new_tuple == NULL && old_tuple == NULL) { - /* - * There is no subject tuple we could write to - * WAL, which means, to do a write, we would have - * to allocate one. Too complicated, for now, just - * do no logging for DELETEs that do nothing. - */ - txn->txn_flags |= BOX_NOT_STORE; - } else if (new_tuple == NULL) { - space_remove(space, old_tuple); - } else { - space_replace(space, old_tuple, new_tuple); - txn_lock(txn, new_tuple); - } - /* Remember the old tuple only if we locked it - * successfully, to not unlock a tuple locked by another - * transaction in rollback(). + assert(old_tuple || new_tuple); + /* + * Remember the old tuple only if we replaced it + * successfully, to not remove a tuple inserted by + * another transaction in rollback(). */ - txn->old_tuple = old_tuple; + txn->old_tuple = space_replace(space, old_tuple, new_tuple, mode); + if (new_tuple) { + txn->new_tuple = new_tuple; + tuple_ref(txn->new_tuple, 1); + } txn->space = space; } @@ -97,9 +75,7 @@ txn_begin() void txn_commit(struct txn *txn) { - if (txn->op == 0) /* Nothing to do. */ - return; - if (! (txn->txn_flags & BOX_NOT_STORE)) { + if (txn->old_tuple || txn->new_tuple) { int64_t lsn = next_lsn(recovery_state); int res = wal_write(recovery_state, lsn, 0, txn->op, &txn->req); @@ -114,22 +90,16 @@ txn_finish(struct txn *txn) { if (txn->old_tuple) tuple_ref(txn->old_tuple, -1); - if (txn->new_tuple) - txn_unlock(txn, txn->new_tuple); TRASH(txn); } void txn_rollback(struct txn *txn) { - if (txn->op == 0) /* Nothing to do. */ - return; - if (txn->old_tuple) { - space_replace(txn->space, txn->new_tuple, txn->old_tuple); - } else if (txn->new_tuple && txn->new_tuple->flags & WAL_WAIT) { - space_remove(txn->space, txn->new_tuple); + if (txn->old_tuple || txn->new_tuple) { + space_replace(txn->space, txn->new_tuple, txn->old_tuple, DUP_INSERT); + if (txn->new_tuple) + tuple_ref(txn->new_tuple, -1); } - if (txn->new_tuple) - tuple_ref(txn->new_tuple, -1); TRASH(txn); } diff --git a/test/big/hash.result b/test/big/hash.result index d619ae4248c3658d1ec0ac910f4ceb0668753c37..56c1ba70d46f0fcd88c52e3dfd648c40b068759d 100644 --- a/test/big/hash.result +++ b/test/big/hash.result @@ -420,3 +420,128 @@ lua box.space[11]:truncate() lua box.space[12]:truncate() --- ... +lua box.space[21]:truncate() +--- +... +insert into t21 values (0, 0, 0, 0) +Insert OK, 1 row affected +insert into t21 values (1, 1, 1, 1) +Insert OK, 1 row affected +insert into t21 values (2, 2, 2, 2) +Insert OK, 1 row affected +replace into t21 values (1, 1, 1, 1) +Replace OK, 1 row affected +replace into t21 values (1, 10, 10, 10) +Replace OK, 1 row affected +replace into t21 values (1, 1, 1, 1) +Replace OK, 1 row affected +select * from t21 WHERE k0 = 10 +No match +select * from t21 WHERE k1 = 10 +No match +select * from t21 WHERE k2 = 10 +No match +select * from t21 WHERE k3 = 10 +No match +select * from t21 WHERE k0 = 1 +Found 1 tuple: +[1, 1, 1, 1] +select * from t21 WHERE k1 = 1 +Found 1 tuple: +[1, 1, 1, 1] +select * from t21 WHERE k2 = 1 +Found 1 tuple: +[1, 1, 1, 1] +select * from t21 WHERE k3 = 1 +Found 1 tuple: +[1, 1, 1, 1] +insert into t21 values (10, 10, 10, 10) +Insert OK, 1 row affected +delete from t21 WHERE k0 = 10 +Delete OK, 1 row affected +select * from t21 WHERE k0 = 10 +No match +select * from t21 WHERE k1 = 10 +No match +select * from t21 WHERE k2 = 10 +No match +select * from t21 WHERE k3 = 10 +No match +insert into t21 values (1, 10, 10, 10) +An error occurred: ER_TUPLE_FOUND, 'Duplicate key exists in unique index 0' +select * from t21 WHERE k0 = 10 +No match +select * from t21 WHERE k1 = 10 +No match +select * from t21 WHERE k2 = 10 +No match +select * from t21 WHERE k3 = 10 +No match +select * from t21 WHERE k0 = 1 +Found 1 tuple: +[1, 1, 1, 1] +replace into t21 values (10, 10, 10, 10) +An error occurred: ER_TUPLE_NOT_FOUND, 'Tuple doesn't exist in index 0' +select * from t21 WHERE k0 = 10 +No match +select * from t21 WHERE k1 = 10 +No match +select * from t21 WHERE k2 = 10 +No match +select * from t21 WHERE k3 = 10 +No match +insert into t21 values (10, 0, 10, 10) +An error occurred: ER_TUPLE_FOUND, 'Duplicate key exists in unique index 1' +select * from t21 WHERE k0 = 10 +No match +select * from t21 WHERE k1 = 10 +No match +select * from t21 WHERE k2 = 10 +No match +select * from t21 WHERE k3 = 10 +No match +select * from t21 WHERE k1 = 0 +Found 1 tuple: +[0, 0, 0, 0] +replace into t21 values (2, 0, 10, 10) +An error occurred: ER_TUPLE_FOUND, 'Duplicate key exists in unique index 1' +select * from t21 WHERE k0 = 10 +No match +select * from t21 WHERE k1 = 10 +No match +select * from t21 WHERE k2 = 10 +No match +select * from t21 WHERE k3 = 10 +No match +select * from t21 WHERE k1 = 0 +Found 1 tuple: +[0, 0, 0, 0] +insert into t21 values (10, 10, 10, 0) +An error occurred: ER_TUPLE_FOUND, 'Duplicate key exists in unique index 3' +select * from t21 WHERE k0 = 10 +No match +select * from t21 WHERE k1 = 10 +No match +select * from t21 WHERE k2 = 10 +No match +select * from t21 WHERE k3 = 10 +No match +select * from t21 WHERE k3 = 0 +Found 1 tuple: +[0, 0, 0, 0] +replace into t21 values (2, 10, 10, 0) +An error occurred: ER_TUPLE_FOUND, 'Duplicate key exists in unique index 3' +select * from t21 WHERE k0 = 10 +No match +select * from t21 WHERE k1 = 10 +No match +select * from t21 WHERE k2 = 10 +No match +select * from t21 WHERE k3 = 10 +No match +select * from t21 WHERE k3 = 0 +Found 1 tuple: +[0, 0, 0, 0] +lua box.space[21]:truncate() +--- +... diff --git a/test/big/hash.test b/test/big/hash.test index de42a55988cea7f296f064f46f2369d77eb47f4c..51c46574092d540d1b34acf510427594c2ee57da 100644 --- a/test/big/hash.test +++ b/test/big/hash.test @@ -265,3 +265,84 @@ exec admin "lua box.space[12]:delete('key 1', 'key 2')" exec admin "lua box.space[10]:truncate()" exec admin "lua box.space[11]:truncate()" exec admin "lua box.space[12]:truncate()" + +# +# hash::replace tests +# + +exec admin "lua box.space[21]:truncate()" + +exec sql "insert into t21 values (0, 0, 0, 0)" +exec sql "insert into t21 values (1, 1, 1, 1)" +exec sql "insert into t21 values (2, 2, 2, 2)" + +# OK +exec sql "replace into t21 values (1, 1, 1, 1)" +exec sql "replace into t21 values (1, 10, 10, 10)" +exec sql "replace into t21 values (1, 1, 1, 1)" +exec sql "select * from t21 WHERE k0 = 10" +exec sql "select * from t21 WHERE k1 = 10" +exec sql "select * from t21 WHERE k2 = 10" +exec sql "select * from t21 WHERE k3 = 10" +exec sql "select * from t21 WHERE k0 = 1" +exec sql "select * from t21 WHERE k1 = 1" +exec sql "select * from t21 WHERE k2 = 1" +exec sql "select * from t21 WHERE k3 = 1" + +# OK +exec sql "insert into t21 values (10, 10, 10, 10)" +exec sql "delete from t21 WHERE k0 = 10" +exec sql "select * from t21 WHERE k0 = 10" +exec sql "select * from t21 WHERE k1 = 10" +exec sql "select * from t21 WHERE k2 = 10" +exec sql "select * from t21 WHERE k3 = 10" + + +# TupleFound (primary key) +exec sql "insert into t21 values (1, 10, 10, 10)" +exec sql "select * from t21 WHERE k0 = 10" +exec sql "select * from t21 WHERE k1 = 10" +exec sql "select * from t21 WHERE k2 = 10" +exec sql "select * from t21 WHERE k3 = 10" +exec sql "select * from t21 WHERE k0 = 1" + +# TupleNotFound (primary key) +exec sql "replace into t21 values (10, 10, 10, 10)" +exec sql "select * from t21 WHERE k0 = 10" +exec sql "select * from t21 WHERE k1 = 10" +exec sql "select * from t21 WHERE k2 = 10" +exec sql "select * from t21 WHERE k3 = 10" + +# TupleFound (key #1) +exec sql "insert into t21 values (10, 0, 10, 10)" +exec sql "select * from t21 WHERE k0 = 10" +exec sql "select * from t21 WHERE k1 = 10" +exec sql "select * from t21 WHERE k2 = 10" +exec sql "select * from t21 WHERE k3 = 10" +exec sql "select * from t21 WHERE k1 = 0" + +# TupleFound (key #1) +exec sql "replace into t21 values (2, 0, 10, 10)" +exec sql "select * from t21 WHERE k0 = 10" +exec sql "select * from t21 WHERE k1 = 10" +exec sql "select * from t21 WHERE k2 = 10" +exec sql "select * from t21 WHERE k3 = 10" +exec sql "select * from t21 WHERE k1 = 0" + +# TupleFound (key #3) +exec sql "insert into t21 values (10, 10, 10, 0)" +exec sql "select * from t21 WHERE k0 = 10" +exec sql "select * from t21 WHERE k1 = 10" +exec sql "select * from t21 WHERE k2 = 10" +exec sql "select * from t21 WHERE k3 = 10" +exec sql "select * from t21 WHERE k3 = 0" + +# TupleFound (key #3) +exec sql "replace into t21 values (2, 10, 10, 0)" +exec sql "select * from t21 WHERE k0 = 10" +exec sql "select * from t21 WHERE k1 = 10" +exec sql "select * from t21 WHERE k2 = 10" +exec sql "select * from t21 WHERE k3 = 10" +exec sql "select * from t21 WHERE k3 = 0" + +exec admin "lua box.space[21]:truncate()" diff --git a/test/big/suite.ini b/test/big/suite.ini index a7d20861193f0e45a52e5ed574549ae238cd7ac3..4c9d65bcc5f5f528c7e51e57a5d3bdf7de8fc46b 100644 --- a/test/big/suite.ini +++ b/test/big/suite.ini @@ -5,3 +5,4 @@ config = tarantool.cfg # disabled = lua.test # put disabled in valgrind test here #valgrind_disabled = ... +release_disabled = hash_errinj.test diff --git a/test/big/tarantool.cfg b/test/big/tarantool.cfg index 5ecbbbd01a3a48e39bf7df5f36499941e3d1897a..b3334affc2dfc6e962f41ba0fe9bbbf8de2b063d 100644 --- a/test/big/tarantool.cfg +++ b/test/big/tarantool.cfg @@ -282,3 +282,49 @@ space[20].index[4].type = "HASH" space[20].index[4].unique = 1 space[20].index[4].key_field[0].fieldno = 0 space[20].index[4].key_field[0].type = "STR" + +# hash::replace +space[21].enabled = true + +space[21].index[0].type = "HASH" +space[21].index[0].unique = true +space[21].index[0].key_field[0].fieldno = 0 +space[21].index[0].key_field[0].type = "NUM" + +space[21].index[1].type = "HASH" +space[21].index[1].unique = true +space[21].index[1].key_field[0].fieldno = 1 +space[21].index[1].key_field[0].type = "NUM" + +space[21].index[2].type = "HASH" +space[21].index[2].unique = true +space[21].index[2].key_field[0].fieldno = 2 +space[21].index[2].key_field[0].type = "NUM" + +space[21].index[3].type = "HASH" +space[21].index[3].unique = true +space[21].index[3].key_field[0].fieldno = 3 +space[21].index[3].key_field[0].type = "NUM" + +# tree::replace test +space[22].enabled = true + +space[22].index[0].type = "TREE" +space[22].index[0].unique = true +space[22].index[0].key_field[0].fieldno = 0 +space[22].index[0].key_field[0].type = "NUM" + +space[22].index[1].type = "TREE" +space[22].index[1].unique = true +space[22].index[1].key_field[0].fieldno = 1 +space[22].index[1].key_field[0].type = "NUM" + +space[22].index[2].type = "TREE" +space[22].index[2].unique = false +space[22].index[2].key_field[0].fieldno = 2 +space[22].index[2].key_field[0].type = "NUM" + +space[22].index[3].type = "TREE" +space[22].index[3].unique = true +space[22].index[3].key_field[0].fieldno = 3 +space[22].index[3].key_field[0].type = "NUM" diff --git a/test/big/tree_pk.result b/test/big/tree_pk.result index 7ee015ed0c60748af23ab04c3df245f00cdcb501..47d1de4390b9638e389cbc474e7b066e80b16474 100644 --- a/test/big/tree_pk.result +++ b/test/big/tree_pk.result @@ -129,3 +129,149 @@ lua box.space[15].index[0]:select_range(3, 'abcdb') lua box.space[15]:truncate() --- ... +lua box.space[22]:truncate() +--- +... +insert into t22 values (0, 0, 0, 0) +Insert OK, 1 row affected +insert into t22 values (1, 1, 1, 1) +Insert OK, 1 row affected +insert into t22 values (2, 2, 2, 2) +Insert OK, 1 row affected +replace into t22 values (1, 1, 1, 1) +Replace OK, 1 row affected +replace into t22 values (1, 10, 10, 10) +Replace OK, 1 row affected +replace into t22 values (1, 1, 1, 1) +Replace OK, 1 row affected +select * from t22 WHERE k0 = 10 +No match +select * from t22 WHERE k1 = 10 +No match +select * from t22 WHERE k2 = 10 +No match +select * from t22 WHERE k3 = 10 +No match +select * from t22 WHERE k0 = 1 +Found 1 tuple: +[1, 1, 1, 1] +select * from t22 WHERE k1 = 1 +Found 1 tuple: +[1, 1, 1, 1] +select * from t22 WHERE k2 = 1 +Found 1 tuple: +[1, 1, 1, 1] +select * from t22 WHERE k3 = 1 +Found 1 tuple: +[1, 1, 1, 1] +insert into t22 values (10, 10, 10, 10) +Insert OK, 1 row affected +delete from t22 WHERE k0 = 10 +Delete OK, 1 row affected +select * from t22 WHERE k0 = 10 +No match +select * from t22 WHERE k1 = 10 +No match +select * from t22 WHERE k2 = 10 +No match +select * from t22 WHERE k3 = 10 +No match +insert into t22 values (1, 10, 10, 10) +An error occurred: ER_TUPLE_FOUND, 'Duplicate key exists in unique index 0' +select * from t22 WHERE k0 = 10 +No match +select * from t22 WHERE k1 = 10 +No match +select * from t22 WHERE k2 = 10 +No match +select * from t22 WHERE k3 = 10 +No match +select * from t22 WHERE k0 = 1 +Found 1 tuple: +[1, 1, 1, 1] +replace into t22 values (10, 10, 10, 10) +An error occurred: ER_TUPLE_NOT_FOUND, 'Tuple doesn't exist in index 0' +select * from t22 WHERE k0 = 10 +No match +select * from t22 WHERE k1 = 10 +No match +select * from t22 WHERE k2 = 10 +No match +select * from t22 WHERE k3 = 10 +No match +insert into t22 values (10, 0, 10, 10) +An error occurred: ER_TUPLE_FOUND, 'Duplicate key exists in unique index 1' +select * from t22 WHERE k0 = 10 +No match +select * from t22 WHERE k1 = 10 +No match +select * from t22 WHERE k2 = 10 +No match +select * from t22 WHERE k3 = 10 +No match +select * from t22 WHERE k1 = 0 +Found 1 tuple: +[0, 0, 0, 0] +replace into t22 values (2, 0, 10, 10) +An error occurred: ER_TUPLE_FOUND, 'Duplicate key exists in unique index 1' +select * from t22 WHERE k0 = 10 +No match +select * from t22 WHERE k1 = 10 +No match +select * from t22 WHERE k2 = 10 +No match +select * from t22 WHERE k3 = 10 +No match +select * from t22 WHERE k1 = 0 +Found 1 tuple: +[0, 0, 0, 0] +insert into t22 values (10, 10, 10, 0) +An error occurred: ER_TUPLE_FOUND, 'Duplicate key exists in unique index 3' +select * from t22 WHERE k0 = 10 +No match +select * from t22 WHERE k1 = 10 +No match +select * from t22 WHERE k2 = 10 +No match +select * from t22 WHERE k3 = 10 +No match +select * from t22 WHERE k3 = 0 +Found 1 tuple: +[0, 0, 0, 0] +replace into t22 values (2, 10, 10, 0) +An error occurred: ER_TUPLE_FOUND, 'Duplicate key exists in unique index 3' +select * from t22 WHERE k0 = 10 +No match +select * from t22 WHERE k1 = 10 +No match +select * from t22 WHERE k2 = 10 +No match +select * from t22 WHERE k3 = 10 +No match +select * from t22 WHERE k3 = 0 +Found 1 tuple: +[0, 0, 0, 0] +insert into t22 values (4, 4, 0, 4) +Insert OK, 1 row affected +insert into t22 values (5, 5, 0, 5) +Insert OK, 1 row affected +insert into t22 values (6, 6, 0, 6) +Insert OK, 1 row affected +replace into t22 values (5, 5, 0, 5) +Replace OK, 1 row affected +select * from t22 WHERE k2 = 0 +Found 4 tuples: +[0, 0, 0, 0] +[4, 4, 0, 4] +[6, 6, 0, 6] +[5, 5, 0, 5] +delete from t22 WHERE k0 = 5 +Delete OK, 1 row affected +select * from t22 WHERE k2 = 0 +Found 3 tuples: +[0, 0, 0, 0] +[4, 4, 0, 4] +[6, 6, 0, 6] +lua box.space[22]:truncate() +--- +... diff --git a/test/big/tree_pk.test b/test/big/tree_pk.test index 7537b8581e10de58a66d1713f566ddf1c7ec295f..91f5fe57b4ecad0d6dade64f0fec71cf69d12514 100644 --- a/test/big/tree_pk.test +++ b/test/big/tree_pk.test @@ -79,3 +79,94 @@ exec sql "insert into t15 values ('abcdc')" exec sql "insert into t15 values ('abcdc_')" exec admin "lua box.space[15].index[0]:select_range(3, 'abcdb')" exec admin "lua box.space[15]:truncate()" + +# +# tree::replace tests +# + +exec admin "lua box.space[22]:truncate()" + +exec sql "insert into t22 values (0, 0, 0, 0)" +exec sql "insert into t22 values (1, 1, 1, 1)" +exec sql "insert into t22 values (2, 2, 2, 2)" + +# OK +exec sql "replace into t22 values (1, 1, 1, 1)" +exec sql "replace into t22 values (1, 10, 10, 10)" +exec sql "replace into t22 values (1, 1, 1, 1)" +exec sql "select * from t22 WHERE k0 = 10" +exec sql "select * from t22 WHERE k1 = 10" +exec sql "select * from t22 WHERE k2 = 10" +exec sql "select * from t22 WHERE k3 = 10" +exec sql "select * from t22 WHERE k0 = 1" +exec sql "select * from t22 WHERE k1 = 1" +exec sql "select * from t22 WHERE k2 = 1" +exec sql "select * from t22 WHERE k3 = 1" + +# OK +exec sql "insert into t22 values (10, 10, 10, 10)" +exec sql "delete from t22 WHERE k0 = 10" +exec sql "select * from t22 WHERE k0 = 10" +exec sql "select * from t22 WHERE k1 = 10" +exec sql "select * from t22 WHERE k2 = 10" +exec sql "select * from t22 WHERE k3 = 10" + + +# TupleFound (primary key) +exec sql "insert into t22 values (1, 10, 10, 10)" +exec sql "select * from t22 WHERE k0 = 10" +exec sql "select * from t22 WHERE k1 = 10" +exec sql "select * from t22 WHERE k2 = 10" +exec sql "select * from t22 WHERE k3 = 10" +exec sql "select * from t22 WHERE k0 = 1" + +# TupleNotFound (primary key) +exec sql "replace into t22 values (10, 10, 10, 10)" +exec sql "select * from t22 WHERE k0 = 10" +exec sql "select * from t22 WHERE k1 = 10" +exec sql "select * from t22 WHERE k2 = 10" +exec sql "select * from t22 WHERE k3 = 10" + +# TupleFound (key #1) +exec sql "insert into t22 values (10, 0, 10, 10)" +exec sql "select * from t22 WHERE k0 = 10" +exec sql "select * from t22 WHERE k1 = 10" +exec sql "select * from t22 WHERE k2 = 10" +exec sql "select * from t22 WHERE k3 = 10" +exec sql "select * from t22 WHERE k1 = 0" + +# TupleFound (key #1) +exec sql "replace into t22 values (2, 0, 10, 10)" +exec sql "select * from t22 WHERE k0 = 10" +exec sql "select * from t22 WHERE k1 = 10" +exec sql "select * from t22 WHERE k2 = 10" +exec sql "select * from t22 WHERE k3 = 10" +exec sql "select * from t22 WHERE k1 = 0" + +# TupleFound (key #3) +exec sql "insert into t22 values (10, 10, 10, 0)" +exec sql "select * from t22 WHERE k0 = 10" +exec sql "select * from t22 WHERE k1 = 10" +exec sql "select * from t22 WHERE k2 = 10" +exec sql "select * from t22 WHERE k3 = 10" +exec sql "select * from t22 WHERE k3 = 0" + +# TupleFound (key #3) +exec sql "replace into t22 values (2, 10, 10, 0)" +exec sql "select * from t22 WHERE k0 = 10" +exec sql "select * from t22 WHERE k1 = 10" +exec sql "select * from t22 WHERE k2 = 10" +exec sql "select * from t22 WHERE k3 = 10" +exec sql "select * from t22 WHERE k3 = 0" + +# Non-Uniq test (key #2) +exec sql "insert into t22 values (4, 4, 0, 4)" +exec sql "insert into t22 values (5, 5, 0, 5)" +exec sql "insert into t22 values (6, 6, 0, 6)" +exec sql "replace into t22 values (5, 5, 0, 5)" +exec sql "select * from t22 WHERE k2 = 0" +exec sql "delete from t22 WHERE k0 = 5" +exec sql "select * from t22 WHERE k2 = 0" + +exec admin "lua box.space[22]:truncate()" + diff --git a/test/box/errinj.result b/test/box/errinj.result index 63245d8c26f1e45f11d7335b20a052a93fe85869..8156e496766c40c24f700da34457e0fe9c4dd27c 100644 --- a/test/box/errinj.result +++ b/test/box/errinj.result @@ -7,6 +7,8 @@ error injections: state: off - name: ERRINJ_WAL_ROTATE state: off + - name: ERRINJ_INDEX_ALLOC + state: off ... set injection some-injection on --- diff --git a/test/box/fiber.result b/test/box/fiber.result index b44c7713d2bd616d5509798e322ecc3fa0aafefc..8a333e0aa16bd8b5b34177ded344b9267241affe 100644 --- a/test/box/fiber.result +++ b/test/box/fiber.result @@ -93,7 +93,7 @@ call box.insert(0, 'test', 'old', 'abcd') Found 1 tuple: [1953719668, 'old', 1684234849] call box.insert(0, 'test', 'old', 'abcd') -An error occurred: ER_TUPLE_FOUND, 'Tuple already exists' +An error occurred: ER_TUPLE_FOUND, 'Duplicate key exists in unique index 0' call box.update(0, 'test', '=p=p', 0, 'pass', 1, 'new') Found 1 tuple: [1936941424, 'new', 1684234849] diff --git a/test/box/lua.result b/test/box/lua.result index fad4d96661d197ebad07fe4ac9d93dd583726fb3..53e88664a3bd991e0d71edf8d8130f9f86c71782 100644 --- a/test/box/lua.result +++ b/test/box/lua.result @@ -314,7 +314,7 @@ call box.insert(0, 'test', 'old', 'abcd') Found 1 tuple: [1953719668, 'old', 1684234849] call box.insert(0, 'test', 'old', 'abcd') -An error occurred: ER_TUPLE_FOUND, 'Tuple already exists' +An error occurred: ER_TUPLE_FOUND, 'Duplicate key exists in unique index 0' call box.update(0, 'test', '=p=p', 0, 'pass', 1, 'new') Found 1 tuple: [1936941424, 'new', 1684234849] @@ -828,7 +828,7 @@ lua f = box.fiber.create(function () return true end) # lua box.space[0]:insert('test', 'something to splice') --- -error: 'Tuple already exists' +error: 'Duplicate key exists in unique index 0' ... lua box.space[0]:update('test', ':p', 1, box.pack('ppp', 0, 4, 'no')) --- @@ -980,7 +980,7 @@ lua pcall(box.insert, 0, 1, 'hello') lua pcall(box.insert, 0, 1, 'hello') --- - false - - Tuple already exists + - Duplicate key exists in unique index 0 ... lua box.space[0]:truncate() --- diff --git a/third_party/sptree.h b/third_party/sptree.h index 1bab0347d1919fbf2eee94f6ba318a73a5ebadd9..e8c8c4be555febd6f0b07f97fcdb8d4d4d5ba16a 100644 --- a/third_party/sptree.h +++ b/third_party/sptree.h @@ -77,7 +77,7 @@ typedef struct sptree_node_pointers { * int (*elemcompare)(const void *e1, const void *e2, void *arg), * void *arg) * - * void sptree_NAME_insert(sptree_NAME *tree, void *value) + * void sptree_NAME_replace(sptree_NAME *tree, void *value, void **p_oldvalue) * void sptree_NAME_delete(sptree_NAME *tree, void *value) * void* sptree_NAME_find(sptree_NAME *tree, void *key) * @@ -300,7 +300,7 @@ sptree_##name##_balance(sptree_##name *t, spnode_t node, spnode_t size) { } \ \ static inline void \ -sptree_##name##_insert(sptree_##name *t, void *v) { \ +sptree_##name##_replace(sptree_##name *t, void *v, void **p_old) { \ spnode_t node, depth = 0; \ spnode_t path[ t->max_depth + 2]; \ \ @@ -312,6 +312,8 @@ sptree_##name##_insert(sptree_##name *t, void *v) { t->garbage_head = SPNIL; \ t->nmember = 1; \ t->size=1; \ + if (p_old) \ + *p_old = NULL; \ return; \ } else { \ spnode_t parent = t->root; \ @@ -319,6 +321,8 @@ sptree_##name##_insert(sptree_##name *t, void *v) { for(;;) { \ int r = t->elemcompare(v, ITHELEM(t, parent), t->arg); \ if (r==0) { \ + if (p_old) \ + memcpy(*p_old, ITHELEM(t, parent), t->elemsize); \ memcpy(ITHELEM(t, parent), v, t->elemsize); \ return; \ } \ @@ -345,6 +349,8 @@ sptree_##name##_insert(sptree_##name *t, void *v) { } \ } \ } \ + if (p_old) \ + *p_old = NULL; \ \ t->size++; \ if ( t->size > t->max_size ) \