Skip to content

Commit

Permalink
FEATURE: Add gat command
Browse files Browse the repository at this point in the history
add get and touch command
  • Loading branch information
cheesecrust committed Aug 1, 2024
1 parent 447601d commit f74c376
Show file tree
Hide file tree
Showing 3 changed files with 157 additions and 6 deletions.
28 changes: 28 additions & 0 deletions docs/ascii-protocol/ch04-command-key-value.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,34 @@ END\r\n

mget 명령에서 메모리 부족으로 일부 key에 대해서만 정상 조회한 후 실패한 경우, 전체 연산을 서버 에러 처리한다.

## gat/gats (get and touch)

gat, gats 명령은 item을 가져옴과 동시에 exptime을 재설정한다.
위 두 명령은 item이 collection 이 아닌 경우에만 동작합니다.

```
gat <exptime> <key>*\r\n
gats <exptime> <key>*\r\n
```

- \<key\>* - 대상 item의 key string 복수개의 key를 공백을 두고 지정할 수 있다.
- \<exptime\> - 재설정할 expiretime 값 이다.

gat 명령이 정상 수행되었을 경우, Response string 은 아래와 같이 구성된다.

- key hit된 아이템 정보를 모두 출력
- key miss된 아이템은 별도 response 없이 생략
- 응답의 끝에 "END\r\n" 출력

gats 명령의 경우 cas value 도 같이 출력된다.

실패시 string은 아래와 같다.

| Response String | 설명 |
|----------------------|------------------------ |
| "CLIENT_ERROR" | 클라이언트에서 잘못된 질의를 했음을 의미. 이어 나오는 문자열을 통해 오류의 원인을 파악 가능. 예) bad command line format
| "SERVER ERROR" | 서버 측의 오류로 조회하지 못했음을 의미. 이어 나오는 문자열을 통해 오류의 원인을 파악 가능. 예) out of memory writing get response

## deletion 명령

delete 명령이 있으며 syntax는 다음과 같다.
Expand Down
65 changes: 59 additions & 6 deletions memcached.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
#include "config.h"
#include "memcached.h"
#include "memcached/extension_loggers.h"
#include "engines/default/item_base.h"
#ifdef ENABLE_ZK_INTEGRATION
#include "arcus_zk.h"
#include "arcus_hb.h"
Expand Down Expand Up @@ -2977,7 +2978,7 @@ static char *get_suffix_buffer(conn *c)
}

static ENGINE_ERROR_CODE
process_get_single(conn *c, char *key, size_t nkey, bool return_cas)
process_get_single(conn *c, char *key, size_t nkey, bool return_cas, bool* coll)
{
item *it;
char *cas_val = NULL;
Expand All @@ -2988,6 +2989,9 @@ process_get_single(conn *c, char *key, size_t nkey, bool return_cas)
if (ret != ENGINE_SUCCESS) {
it = NULL;
}
if (ret == ENGINE_EBADTYPE) {
*coll = true;
}
if (settings.detail_enabled) {
stats_prefix_record_get(key, nkey, (it != NULL));
}
Expand Down Expand Up @@ -3070,6 +3074,7 @@ static void process_mget_complete(conn *c, bool return_cas)

ENGINE_ERROR_CODE ret = ENGINE_SUCCESS;
token_t *key_tokens;
bool coll = false;

do {
key_tokens = (token_t*)token_buff_get(&c->thread->token_buff, c->coll_numkeys);
Expand All @@ -3087,7 +3092,7 @@ static void process_mget_complete(conn *c, bool return_cas)
/* do get operation for each key */
for (int k = 0; k < c->coll_numkeys; k++) {
ret = process_get_single(c, key_tokens[k].value, key_tokens[k].length,
return_cas);
return_cas, &coll);
if (ret != ENGINE_SUCCESS) {
break; /* ret == ENGINE_ENOMEM*/
}
Expand Down Expand Up @@ -8345,11 +8350,23 @@ static void process_stats_command(conn *c, token_t *tokens, const size_t ntokens
}

/* ntokens is overwritten here... shrug.. */
static inline void process_get_command(conn *c, token_t *tokens, size_t ntokens, bool return_cas)
static inline void process_get_command(conn *c, token_t *tokens, size_t ntokens, bool return_cas, bool should_touch)
{
assert(c != NULL);

token_t *key_token = &tokens[KEY_TOKEN];
ENGINE_ERROR_CODE ret = ENGINE_SUCCESS;
int64_t exptime = 0;
bool coll = false;

if (should_touch) {
// For get and touch commands, use first token as exptime
if (!safe_strtoll(tokens[1].value, &exptime)) {
out_string(c, "CLIENT_ERROR invalid exptime argument");
return;
}
key_token++;
}

do {
while (key_token->length != 0) {
Expand All @@ -8358,10 +8375,38 @@ static inline void process_get_command(conn *c, token_t *tokens, size_t ntokens,
}
/* do get operation for each key */
ret = process_get_single(c, key_token->value, key_token->length,
return_cas);
return_cas, &coll);
if (ret != ENGINE_SUCCESS) {
break; /* ret == ENGINE_ENOMEM */
}

if (should_touch && !coll) {
ENGINE_ITEM_ATTR attr_id = ATTR_EXPIRETIME;
ENGINE_ERROR_CODE gat_ret = ENGINE_SUCCESS;
item_attr attr_data;

attr_data.exptime = realtime(exptime);
gat_ret = mc_engine.v1->setattr(mc_engine.v0, c, key_token->value, key_token->length,
&attr_id, 1, &attr_data, 0);
CONN_CHECK_AND_SET_EWOULDBLOCK(gat_ret, c);
if (settings.detail_enabled) {
stats_prefix_record_setattr(key_token->value, key_token->length);
}

if (gat_ret == ENGINE_KEY_ENOENT) {
STATS_MISSES(c, setattr, key_token->value, key_token->length);
out_string(c, "CLIENT_ERROR key not found");
return;
} else if (gat_ret == ENGINE_EBADVALUE) {
STATS_CMD_NOKEY(c, setattr);
out_string(c, "CLIENT_ERROR bad value");
return;
} else if (gat_ret != ENGINE_SUCCESS) {
STATS_CMD_NOKEY(c, setattr);
handle_unexpected_errorcode_ascii(c, __func__, gat_ret);
return;
}
}
key_token++;
}
if (ret != ENGINE_SUCCESS) break;
Expand Down Expand Up @@ -13124,11 +13169,19 @@ static void process_command_ascii(conn *c, char *command, int cmdlen)

if ((ntokens >= 3) && (strcmp(tokens[COMMAND_TOKEN].value, "get") == 0))
{
process_get_command(c, tokens, ntokens, false);
process_get_command(c, tokens, ntokens, false, false);
}
else if ((ntokens >= 3) && (strcmp(tokens[COMMAND_TOKEN].value, "gets") == 0))
{
process_get_command(c, tokens, ntokens, true);
process_get_command(c, tokens, ntokens, true, false);
}
else if ((ntokens >= 3) && (strcmp(tokens[COMMAND_TOKEN].value, "gat") == 0))
{
process_get_command(c, tokens, ntokens, false, true);
}
else if ((ntokens >= 3) && (strcmp(tokens[COMMAND_TOKEN].value, "gats") == 0))
{
process_get_command(c, tokens, ntokens, true, true);
}
else if ((ntokens == 4) && (strcmp(tokens[COMMAND_TOKEN].value, "mget") == 0))
{
Expand Down
70 changes: 70 additions & 0 deletions t/gat.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#!/usr/bin/perl

use strict;
use Test::More tests =>12;
use FindBin qw($Bin);
use lib "$Bin/lib";
use MemcachedTest;

my $engine = shift;
my $server = get_memcached($engine);
my $sock = $server->sock;

my $cmd;
my $val;
my $rst;
my $expire;

# Initialize
$cmd = "set key 0 0 5"; $val = "datum"; $rst = "STORED";
mem_cmd_is($sock, $cmd, $val, $rst);

# Success Cases
# key value
$cmd = "gat 1 key";
$rst = "VALUE key 0 5\n"
. "datum\n"
. "END";
mem_cmd_is($sock, $cmd, "", $rst);
$cmd = "getattr key expiretime";
$rst = "ATTR expiretime=1\n"
. "END";
mem_cmd_is($sock, $cmd, "", $rst);
# gats command
$cmd = "gats 1 key";
$rst = "VALUE key 0 5 1\n"
. "datum\n"
. "END";
mem_cmd_is($sock, $cmd, "", $rst);
$cmd = "getattr key expiretime";
$rst = "ATTR expiretime=1\n"
. "END";
mem_cmd_is($sock, $cmd, "", $rst);

# Fail Cases
# bad value
$cmd = "set key 0 0 5"; $val = "datum"; $rst = "STORED";
mem_cmd_is($sock, $cmd, $val, $rst);
$cmd = "gat str key"; $rst = "CLIENT_ERROR invalid exptime argument";
mem_cmd_is($sock, $cmd, "", $rst);
# not exist key
$expire = time() - 1;
$cmd = "gat $expire key";
$rst = "VALUE key 0 5\n"
. "datum\n"
. "END";
mem_cmd_is($sock, $cmd, "", $rst);
$cmd = "gat 1 key"; $rst = "CLIENT_ERROR key not found";
mem_cmd_is($sock, $cmd, "", $rst);
# collection type
$cmd = "lop create lkey 0 0 5"; $rst = "CREATED";
mem_cmd_is($sock, $cmd, "", $rst);
$cmd = "gat 1 lkey"; $rst = "END";
mem_cmd_is($sock, $cmd, "", $rst);
$cmd = "getattr lkey expiretime";
$rst = "ATTR expiretime=0\n"
. "END";
mem_cmd_is($sock, $cmd, "", $rst);

# after test
release_memcached($engine, $server);

0 comments on commit f74c376

Please sign in to comment.