diff --git a/src/facade/reply_builder.cc b/src/facade/reply_builder.cc index 01300b202..04f3e6926 100644 --- a/src/facade/reply_builder.cc +++ b/src/facade/reply_builder.cc @@ -212,7 +212,7 @@ MCReplyBuilder::MCReplyBuilder(::io::Sink* sink) : SinkReplyBuilder(sink), all_( } void MCReplyBuilder::SendValue(std::string_view key, std::string_view value, uint64_t mc_ver, - uint32_t mc_flag) { + uint32_t mc_flag, bool send_cas_token) { ReplyScope scope(this); if (flag_.meta) { string flags; @@ -227,7 +227,7 @@ void MCReplyBuilder::SendValue(std::string_view key, std::string_view value, uin } } else { WritePieces("VALUE ", key, " ", mc_flag, " ", value.size()); - if (mc_ver) + if (send_cas_token) WritePieces(" ", mc_ver); if (value.size() <= kMaxInlineSize) { diff --git a/src/facade/reply_builder.h b/src/facade/reply_builder.h index 9bfc6b7c4..2ebd2dbb6 100644 --- a/src/facade/reply_builder.h +++ b/src/facade/reply_builder.h @@ -177,7 +177,8 @@ class MCReplyBuilder : public SinkReplyBuilder { void SendDeleted(); void SendGetEnd(); - void SendValue(std::string_view key, std::string_view value, uint64_t mc_ver, uint32_t mc_flag); + void SendValue(std::string_view key, std::string_view value, uint64_t mc_ver, uint32_t mc_flag, + bool send_cas_token); void SendSimpleString(std::string_view str) final; void SendProtocolError(std::string_view str) final; diff --git a/src/server/dragonfly_test.cc b/src/server/dragonfly_test.cc index 0caacd765..d883559c2 100644 --- a/src/server/dragonfly_test.cc +++ b/src/server/dragonfly_test.cc @@ -341,6 +341,9 @@ TEST_F(DflyEngineTest, Memcache) { auto resp = RunMC(MP::SET, "key", "bar", 1); EXPECT_THAT(resp, ElementsAre("STORED")); + resp = RunMC(MP::GETS, "key"); + EXPECT_THAT(resp, ElementsAre("VALUE key 1 3 0", "bar", "END")); + resp = RunMC(MP::GET, "key"); EXPECT_THAT(resp, ElementsAre("VALUE key 1 3", "bar", "END")); @@ -366,6 +369,9 @@ TEST_F(DflyEngineTest, Memcache) { resp = RunMC(MP::GET, "unkn"); EXPECT_THAT(resp, ElementsAre("END")); + + resp = GetMC(MP::GETS, {"key", "key2", "unknown"}); + EXPECT_THAT(resp, ElementsAre("VALUE key 1 3 0", "bar", "VALUE key2 2 8 0", "bar2val2", "END")); } TEST_F(DflyEngineTest, MemcacheFlags) { diff --git a/src/server/main_service.cc b/src/server/main_service.cc index 20efa1a4a..d0f399d78 100644 --- a/src/server/main_service.cc +++ b/src/server/main_service.cc @@ -1536,6 +1536,8 @@ void Service::DispatchMC(const MemcacheParser::Command& cmd, std::string_view va strcpy(cmd_name, "PREPEND"); break; case MemcacheParser::GET: + [[fallthrough]]; + case MemcacheParser::GETS: strcpy(cmd_name, "MGET"); break; case MemcacheParser::FLUSHALL: @@ -1589,6 +1591,9 @@ void Service::DispatchMC(const MemcacheParser::Command& cmd, std::string_view va char* key = const_cast(s.data()); args.emplace_back(key, s.size()); } + if (cmd.type == MemcacheParser::GETS) { + dfly_cntx->conn_state.memcache_flag |= ConnectionState::FETCH_CAS_VER; + } } else { // write commands. if (store_opt[0]) { args.emplace_back(store_opt, strlen(store_opt)); diff --git a/src/server/string_family.cc b/src/server/string_family.cc index 6ad5210bf..270fade1c 100644 --- a/src/server/string_family.cc +++ b/src/server/string_family.cc @@ -1366,7 +1366,7 @@ void StringFamily::MGet(CmdArgList args, const CommandContext& cmnd_cntx) { DCHECK(dynamic_cast(builder) == nullptr); for (const auto& entry : res) { if (entry) { - rb->SendValue(entry->key, entry->value, entry->mc_ver, entry->mc_flag); + rb->SendValue(entry->key, entry->value, 0, entry->mc_flag, fetch_mask & FETCH_MCVER); } else { rb->SendMiss(); } diff --git a/tests/dragonfly/memcache_meta.py b/tests/dragonfly/memcache_meta.py index 243339d31..2bba0d3f6 100644 --- a/tests/dragonfly/memcache_meta.py +++ b/tests/dragonfly/memcache_meta.py @@ -6,7 +6,7 @@ from meta_memcache import ( CacheClient, connection_pool_factory_builder, ) -from meta_memcache.protocol import RequestFlags, Miss, Value, Success +from meta_memcache.protocol import RequestFlags, Success DEFAULT_ARGS = {"memcached_port": 11211, "proactor_threads": 4} @@ -30,3 +30,12 @@ def test_basic(df_server: DflyInstance): assert pool.get("key2") is None assert pool.delete("key1") assert pool.delete("key1") is False + + assert pool.set("cask", "v", 100) + value, cas_token = pool.get_cas("cask") + assert value == "v" and cas_token == 0 + + k = Key("cask") + response = pool.meta_multiget([k], RequestFlags(return_cas_token=True, return_value=True)) + assert k in response + assert response[k].flags.cas_token == 0 and response[k].value == "v" diff --git a/tests/dragonfly/pymemcached_test.py b/tests/dragonfly/pymemcached_test.py index f7345c8cd..38a0966f4 100644 --- a/tests/dragonfly/pymemcached_test.py +++ b/tests/dragonfly/pymemcached_test.py @@ -1,17 +1,17 @@ import logging -import pytest -from pymemcache.client.base import Client as MCClient -from redis import Redis -import socket import random -import time +import socket import ssl +import time + +from pymemcache.client.base import Client as MCClient from . import dfly_args from .instance import DflyInstance DEFAULT_ARGS = {"memcached_port": 11211, "proactor_threads": 4} + # Generic basic tests @@ -32,7 +32,7 @@ def test_basic(memcached_client: MCClient): # delete assert memcached_client.delete("key1") assert not memcached_client.delete("key3") - assert memcached_client.get("key1") == None + assert memcached_client.get("key1") is None # prepend append assert memcached_client.set("key4", "B") @@ -46,6 +46,8 @@ def test_basic(memcached_client: MCClient): assert memcached_client.incr("key5", 1) == 2 assert memcached_client.decr("key5", 1) == 1 + assert memcached_client.gets("key5") == (b"1", b"0") + # Noreply (and pipeline) tests