/* allow operation*/
v->unlock();
} else if (cas && cas != v->getCas()) {
- if (v->isTempDeletedItem() || v->isTempNonExistentItem()) {
+ if (v->isTempDeletedItem() ||
+ v->isTempNonExistentItem() ||
+ v->isDeleted()) {
return NOT_FOUND;
}
return INVALID_CAS;
// attempt set_with_meta. should fail since cas is no longer valid.
set_with_meta(h, h1, key1, keylen1, NULL, 0, 0, &last_meta, last_cas, true);
- checkeq(PROTOCOL_BINARY_RESPONSE_KEY_EEXISTS, last_status.load(),
- "Expected invalid cas error");
+
+ checkeq(PROTOCOL_BINARY_RESPONSE_KEY_ENOENT, last_status.load(),
+ (std::string{"Expected invalid cas error (KEY_EXISTS or"
+ " KEY_ENOENT), got: "} +
+ std::to_string(last_status.load())).c_str());
+
// check the stat
temp = get_int_stat(h, h1, "ep_num_ops_set_meta");
check(temp == 0, "Expect zero op");
EXPECT_EQ(1, v->getValue()->getAge());
}
+/** Regression test for MB-21448 - if an attempt is made to perform a CAS
+ * operation on a logically deleted item we should return NOT_FOUND
+ * (aka KEY_ENOENT) and *not* INVALID_CAS (aka KEY_EEXISTS).
+ */
+TEST_F(HashTableTest, MB21448_UnlockedSetWithCASDeleted) {
+ // Setup - create a key and then delete it.
+ HashTable ht(global_stats, 5, 1);
+ std::string key("key");
+ Item item(key.data(), key.length(), 0, 0, "deleted", strlen("deleted"));
+ ASSERT_EQ(WAS_CLEAN, ht.set(item));
+ ASSERT_EQ(WAS_DIRTY, ht.softDelete("key", 0));
+
+ // Attempt to perform a set on a deleted key with a CAS.
+ Item replacement(key.data(), key.length(), 0, 0, "value", strlen("value"));
+ EXPECT_EQ(NOT_FOUND,
+ ht.set(replacement, /*cas*/10, /*allowExisting*/true,
+ /*hasMetaData*/false))
+ << "When trying to replace-with-CAS a deleted item";
+}
+
/* static storage for environment variable set by putenv().
*
* (This must be static as putenv() essentially 'takes ownership' of