mirror of
https://github.com/dragonflydb/dragonfly.git
synced 2025-05-10 18:05:44 +02:00
feat(memcache): Add support for GETS (#5087)
Support is added for the GETS command. A placeholder CAS token of 0 is always returned. Signed-off-by: Abhijat Malviya <abhijat@dragonflydb.io>
This commit is contained in:
parent
fe495cde3f
commit
fc00f2c972
7 changed files with 34 additions and 11 deletions
|
@ -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,
|
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);
|
ReplyScope scope(this);
|
||||||
if (flag_.meta) {
|
if (flag_.meta) {
|
||||||
string flags;
|
string flags;
|
||||||
|
@ -227,7 +227,7 @@ void MCReplyBuilder::SendValue(std::string_view key, std::string_view value, uin
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
WritePieces("VALUE ", key, " ", mc_flag, " ", value.size());
|
WritePieces("VALUE ", key, " ", mc_flag, " ", value.size());
|
||||||
if (mc_ver)
|
if (send_cas_token)
|
||||||
WritePieces(" ", mc_ver);
|
WritePieces(" ", mc_ver);
|
||||||
|
|
||||||
if (value.size() <= kMaxInlineSize) {
|
if (value.size() <= kMaxInlineSize) {
|
||||||
|
|
|
@ -177,7 +177,8 @@ class MCReplyBuilder : public SinkReplyBuilder {
|
||||||
void SendDeleted();
|
void SendDeleted();
|
||||||
void SendGetEnd();
|
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 SendSimpleString(std::string_view str) final;
|
||||||
void SendProtocolError(std::string_view str) final;
|
void SendProtocolError(std::string_view str) final;
|
||||||
|
|
||||||
|
|
|
@ -341,6 +341,9 @@ TEST_F(DflyEngineTest, Memcache) {
|
||||||
auto resp = RunMC(MP::SET, "key", "bar", 1);
|
auto resp = RunMC(MP::SET, "key", "bar", 1);
|
||||||
EXPECT_THAT(resp, ElementsAre("STORED"));
|
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");
|
resp = RunMC(MP::GET, "key");
|
||||||
EXPECT_THAT(resp, ElementsAre("VALUE key 1 3", "bar", "END"));
|
EXPECT_THAT(resp, ElementsAre("VALUE key 1 3", "bar", "END"));
|
||||||
|
|
||||||
|
@ -366,6 +369,9 @@ TEST_F(DflyEngineTest, Memcache) {
|
||||||
|
|
||||||
resp = RunMC(MP::GET, "unkn");
|
resp = RunMC(MP::GET, "unkn");
|
||||||
EXPECT_THAT(resp, ElementsAre("END"));
|
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) {
|
TEST_F(DflyEngineTest, MemcacheFlags) {
|
||||||
|
|
|
@ -1536,6 +1536,8 @@ void Service::DispatchMC(const MemcacheParser::Command& cmd, std::string_view va
|
||||||
strcpy(cmd_name, "PREPEND");
|
strcpy(cmd_name, "PREPEND");
|
||||||
break;
|
break;
|
||||||
case MemcacheParser::GET:
|
case MemcacheParser::GET:
|
||||||
|
[[fallthrough]];
|
||||||
|
case MemcacheParser::GETS:
|
||||||
strcpy(cmd_name, "MGET");
|
strcpy(cmd_name, "MGET");
|
||||||
break;
|
break;
|
||||||
case MemcacheParser::FLUSHALL:
|
case MemcacheParser::FLUSHALL:
|
||||||
|
@ -1589,6 +1591,9 @@ void Service::DispatchMC(const MemcacheParser::Command& cmd, std::string_view va
|
||||||
char* key = const_cast<char*>(s.data());
|
char* key = const_cast<char*>(s.data());
|
||||||
args.emplace_back(key, s.size());
|
args.emplace_back(key, s.size());
|
||||||
}
|
}
|
||||||
|
if (cmd.type == MemcacheParser::GETS) {
|
||||||
|
dfly_cntx->conn_state.memcache_flag |= ConnectionState::FETCH_CAS_VER;
|
||||||
|
}
|
||||||
} else { // write commands.
|
} else { // write commands.
|
||||||
if (store_opt[0]) {
|
if (store_opt[0]) {
|
||||||
args.emplace_back(store_opt, strlen(store_opt));
|
args.emplace_back(store_opt, strlen(store_opt));
|
||||||
|
|
|
@ -1366,7 +1366,7 @@ void StringFamily::MGet(CmdArgList args, const CommandContext& cmnd_cntx) {
|
||||||
DCHECK(dynamic_cast<CapturingReplyBuilder*>(builder) == nullptr);
|
DCHECK(dynamic_cast<CapturingReplyBuilder*>(builder) == nullptr);
|
||||||
for (const auto& entry : res) {
|
for (const auto& entry : res) {
|
||||||
if (entry) {
|
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 {
|
} else {
|
||||||
rb->SendMiss();
|
rb->SendMiss();
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ from meta_memcache import (
|
||||||
CacheClient,
|
CacheClient,
|
||||||
connection_pool_factory_builder,
|
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}
|
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.get("key2") is None
|
||||||
assert pool.delete("key1")
|
assert pool.delete("key1")
|
||||||
assert pool.delete("key1") is False
|
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"
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
import logging
|
import logging
|
||||||
import pytest
|
|
||||||
from pymemcache.client.base import Client as MCClient
|
|
||||||
from redis import Redis
|
|
||||||
import socket
|
|
||||||
import random
|
import random
|
||||||
import time
|
import socket
|
||||||
import ssl
|
import ssl
|
||||||
|
import time
|
||||||
|
|
||||||
|
from pymemcache.client.base import Client as MCClient
|
||||||
|
|
||||||
from . import dfly_args
|
from . import dfly_args
|
||||||
from .instance import DflyInstance
|
from .instance import DflyInstance
|
||||||
|
|
||||||
DEFAULT_ARGS = {"memcached_port": 11211, "proactor_threads": 4}
|
DEFAULT_ARGS = {"memcached_port": 11211, "proactor_threads": 4}
|
||||||
|
|
||||||
|
|
||||||
# Generic basic tests
|
# Generic basic tests
|
||||||
|
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ def test_basic(memcached_client: MCClient):
|
||||||
# delete
|
# delete
|
||||||
assert memcached_client.delete("key1")
|
assert memcached_client.delete("key1")
|
||||||
assert not memcached_client.delete("key3")
|
assert not memcached_client.delete("key3")
|
||||||
assert memcached_client.get("key1") == None
|
assert memcached_client.get("key1") is None
|
||||||
|
|
||||||
# prepend append
|
# prepend append
|
||||||
assert memcached_client.set("key4", "B")
|
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.incr("key5", 1) == 2
|
||||||
assert memcached_client.decr("key5", 1) == 1
|
assert memcached_client.decr("key5", 1) == 1
|
||||||
|
|
||||||
|
assert memcached_client.gets("key5") == (b"1", b"0")
|
||||||
|
|
||||||
|
|
||||||
# Noreply (and pipeline) tests
|
# Noreply (and pipeline) tests
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue