diff --git a/src/box/memtx_space.c b/src/box/memtx_space.c index a3771a71fe3b..17d35f42fc92 100644 --- a/src/box/memtx_space.c +++ b/src/box/memtx_space.c @@ -395,9 +395,6 @@ memtx_space_execute_replace(struct space *space, struct txn *txn, return -1; tuple_ref(new_tuple); - if (mode == DUP_INSERT) - stmt->does_require_old_tuple = true; - if (memtx_space_replace_tuple(space, stmt, NULL, new_tuple, mode) != 0) { tuple_unref(new_tuple); @@ -429,12 +426,6 @@ memtx_space_execute_delete(struct space *space, struct txn *txn, return 0; } - /* - * We have to delete exactly old_tuple just because we return it as - * a result. - */ - stmt->does_require_old_tuple = true; - if (memtx_space_replace_tuple(space, stmt, old_tuple, NULL, DUP_REPLACE_OR_INSERT) != 0) return -1; @@ -490,8 +481,6 @@ memtx_space_execute_update(struct space *space, struct txn *txn, return -1; tuple_ref(new_tuple); - stmt->does_require_old_tuple = true; - if (memtx_space_replace_tuple(space, stmt, old_tuple, new_tuple, DUP_REPLACE) != 0) { tuple_unref(new_tuple); @@ -611,8 +600,6 @@ memtx_space_execute_upsert(struct space *space, struct txn *txn, } assert(new_tuple != NULL); - stmt->does_require_old_tuple = true; - /* * It's OK to use DUP_REPLACE_OR_INSERT: we don't risk * inserting a new tuple if the old one exists, since diff --git a/src/box/memtx_tx.c b/src/box/memtx_tx.c index 793cd84c5dc5..390ac68bf73b 100644 --- a/src/box/memtx_tx.c +++ b/src/box/memtx_tx.c @@ -2025,10 +2025,9 @@ memtx_tx_history_add_insert_stmt(struct txn_stmt *stmt, memtx_tx_story_link_top(add_story, secondary_replaced, i, true); } + struct memtx_story *del_story = NULL; if (old_tuple != NULL) { assert(old_tuple->is_dirty); - - struct memtx_story *del_story = NULL; if (old_tuple == replaced) del_story = replaced_story; else @@ -2037,6 +2036,30 @@ memtx_tx_history_add_insert_stmt(struct txn_stmt *stmt, } else if (is_own_change) stmt->is_pure_insert = true; + /* + * In case of DUP_INSERT there must be no visible replaced tuple. It is + * correct by now (checked in check_dup), but we must prevent further + * insertion to this place, so we have to track gap. + * In case of replace we usually does not depend on presence of absence + * of old tuple, but if there is a trigger - it takes old_tuple (NULL or + * non-NULL) as a side effect, so we must track it to remain the same. + * Note that none of the above is needed the previous action in this + * point of in index is made by the same transaction. For example, if + * a transaction replaces, deletes and then inserts some key - no other + * transaction can interfere with insert: due to serialization the + * previous delete statement guarantees that the insert will not fail. + */ + if (!is_own_change && + (mode == DUP_INSERT || + !rlist_empty(&stmt->space->before_replace) || + !rlist_empty(&stmt->space->on_replace))) { + assert(mode != DUP_INSERT || del_story == NULL); + if (del_story == NULL) + memtx_tx_track_story_gap(stmt->txn, add_story, 0); + else + memtx_tx_track_read_story(stmt->txn, space, del_story); + } + *result = old_tuple; if (*result != NULL) { /* @@ -2307,9 +2330,6 @@ memtx_tx_history_prepare_insert_stmt(struct txn_stmt *stmt) == test_stmt->txn); continue; } - if (test_stmt->does_require_old_tuple) - memtx_tx_handle_conflict(stmt->txn, - test_stmt->txn); memtx_tx_story_link_deleted_by(story, test_stmt); } @@ -2363,10 +2383,6 @@ memtx_tx_history_prepare_insert_stmt(struct txn_stmt *stmt) test_stmt->next_in_del_list = NULL; test_stmt->del_story = NULL; - if (test_stmt->does_require_old_tuple) - memtx_tx_handle_conflict(stmt->txn, - test_stmt->txn); - /* Link to story's list. */ test_stmt->del_story = story; *to = test_stmt; @@ -2468,9 +2484,6 @@ memtx_tx_history_prepare_delete_stmt(struct txn_stmt *stmt) *itr = test_stmt->next_in_del_list; test_stmt->next_in_del_list = NULL; test_stmt->del_story = NULL; - /* Conflict only in case of dependance. */ - if (test_stmt->does_require_old_tuple) - memtx_tx_handle_conflict(stmt->txn, test_stmt->txn); } struct tx_read_tracker *tracker; diff --git a/src/box/txn.c b/src/box/txn.c index 4d215e8192b7..8ceddb74b373 100644 --- a/src/box/txn.c +++ b/src/box/txn.c @@ -311,7 +311,6 @@ txn_stmt_new(struct txn *txn) stmt->engine_savepoint = NULL; stmt->row = NULL; stmt->has_triggers = false; - stmt->does_require_old_tuple = false; stmt->is_pure_insert = false; return stmt; } @@ -636,20 +635,7 @@ txn_commit_stmt(struct txn *txn, struct request *request) */ if (stmt->space != NULL && stmt->space->run_triggers && (stmt->old_tuple || stmt->new_tuple)) { - if (!rlist_empty(&stmt->space->before_replace)) { - /* - * Triggers see old_tuple and that tuple - * must remain the same - */ - stmt->does_require_old_tuple = true; - } if (!rlist_empty(&stmt->space->on_replace)) { - /* - * Triggers see old_tuple and that tuple - * must remain the same - */ - stmt->does_require_old_tuple = true; - txn->space_on_replace_triggers_depth++; int rc = trigger_run(&stmt->space->on_replace, txn); txn->space_on_replace_triggers_depth--; diff --git a/src/box/txn.h b/src/box/txn.h index 2b8cec820d4c..bdfb8d420ad0 100644 --- a/src/box/txn.h +++ b/src/box/txn.h @@ -298,19 +298,6 @@ struct txn_stmt { struct xrow_header *row; /** on_commit and/or on_rollback list is not empty. */ bool has_triggers; - /** - * Whether the stmt requires to replace exactly old_tuple (member). - * That flag is needed for transactional conflict manager - if any - * other transaction commits a replacement of old_tuple before current - * one and the flag is set - the current transaction will be aborted. - * For example REPLACE just replaces a key, no matter what tuple - * lays in the index and thus does_require_old_tuple = false. - * In contrast, UPDATE makes new tuple using old_tuple and thus - * the statement will require old_tuple (does_require_old_tuple = true). - * INSERT also does_require_old_tuple = true because it requires - * old_tuple to be NULL. - */ - bool does_require_old_tuple; /* * `insert` statement is guaranteed not to delete anything * from the transaction's point of view (i.e., there was a preceding