Skip to content

Commit 20c099b

Browse files
committed
add key commands and unit tests
1 parent 7de2d99 commit 20c099b

File tree

7 files changed

+176
-2
lines changed

7 files changed

+176
-2
lines changed

docs/src/guide/overview/commands.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ Implemented commands:
3333
- RENAME
3434
- EXPIRE
3535
- EXPIREAT
36+
- DUMP
37+
- RESTORE
38+
- TYPE
3639

3740
</details>
3841

server/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ dependencies {
2525

2626
implementation 'com.google.guava:guava:31.0.1-jre'
2727
implementation 'org.reflections:reflections:0.10.2'
28+
implementation 'org.apache.commons:commons-lang3:3.0'
2829

2930
// Experimental
3031
implementation files('libs/keva-ioc-0.1.0-SNAPSHOT.jar')
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package dev.keva.server.command.impl.key;
2+
3+
import dev.keva.ioc.annotation.Autowired;
4+
import dev.keva.ioc.annotation.Component;
5+
import dev.keva.protocol.resp.reply.BulkReply;
6+
import dev.keva.protocol.resp.reply.Reply;
7+
import dev.keva.server.command.annotation.CommandImpl;
8+
import dev.keva.server.command.annotation.Execute;
9+
import dev.keva.server.command.annotation.ParamLength;
10+
import dev.keva.store.KevaDatabase;
11+
import lombok.val;
12+
13+
import java.util.Base64;
14+
15+
@Component
16+
@CommandImpl("dump")
17+
@ParamLength(1)
18+
public class Dump {
19+
private final KevaDatabase database;
20+
21+
@Autowired
22+
public Dump(KevaDatabase database) {
23+
this.database = database;
24+
}
25+
26+
@Execute
27+
public Reply<?> execute(byte[] key) {
28+
val got = database.get(key);
29+
if (got == null) {
30+
return BulkReply.NIL_REPLY;
31+
}
32+
String dumped = Base64.getEncoder().encodeToString(got);
33+
return new BulkReply(dumped);
34+
}
35+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package dev.keva.server.command.impl.key;
2+
3+
import dev.keva.ioc.annotation.Autowired;
4+
import dev.keva.ioc.annotation.Component;
5+
import dev.keva.protocol.resp.reply.ErrorReply;
6+
import dev.keva.protocol.resp.reply.Reply;
7+
import dev.keva.protocol.resp.reply.StatusReply;
8+
import dev.keva.server.command.annotation.CommandImpl;
9+
import dev.keva.server.command.annotation.Execute;
10+
import dev.keva.server.command.annotation.ParamLength;
11+
import dev.keva.server.command.impl.key.manager.ExpirationManager;
12+
import dev.keva.store.KevaDatabase;
13+
import lombok.val;
14+
15+
import java.math.BigInteger;
16+
import java.util.Base64;
17+
18+
@Component
19+
@CommandImpl("restore")
20+
@ParamLength(type = ParamLength.Type.AT_LEAST, value = 3)
21+
public class Restore {
22+
private final KevaDatabase database;
23+
private final ExpirationManager expirationManager;
24+
25+
@Autowired
26+
public Restore(KevaDatabase database, ExpirationManager expirationManager) {
27+
this.database = database;
28+
this.expirationManager = expirationManager;
29+
}
30+
31+
@Execute
32+
public Reply<?> execute(byte[] key, byte[] ttl, byte[] dump, byte[] replace) {
33+
val old = database.get(key);
34+
boolean isReplace = replace != null && new String(replace).equalsIgnoreCase("REPLACE");
35+
if (old != null && !isReplace) {
36+
return new ErrorReply("ERR Target key name is busy");
37+
}
38+
byte[] value = Base64.getDecoder().decode(dump);
39+
database.put(key, value);
40+
long expireTime = new BigInteger(ttl).longValue();
41+
if (expireTime > 0) {
42+
expirationManager.expireAfter(key, expireTime);
43+
}
44+
return StatusReply.OK;
45+
}
46+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package dev.keva.server.command.impl.key;
2+
3+
import dev.keva.ioc.annotation.Autowired;
4+
import dev.keva.ioc.annotation.Component;
5+
import dev.keva.protocol.resp.reply.StatusReply;
6+
import dev.keva.server.command.annotation.CommandImpl;
7+
import dev.keva.server.command.annotation.Execute;
8+
import dev.keva.server.command.annotation.ParamLength;
9+
import dev.keva.store.KevaDatabase;
10+
import lombok.val;
11+
import org.apache.commons.lang3.SerializationException;
12+
import org.apache.commons.lang3.SerializationUtils;
13+
14+
import java.util.HashMap;
15+
import java.util.HashSet;
16+
import java.util.LinkedList;
17+
18+
@Component
19+
@CommandImpl("type")
20+
@ParamLength(1)
21+
public class Type {
22+
private final KevaDatabase database;
23+
24+
@Autowired
25+
public Type(KevaDatabase database) {
26+
this.database = database;
27+
}
28+
29+
@Execute
30+
public StatusReply execute(byte[] key) {
31+
val got = database.get(key);
32+
if (got == null) {
33+
return new StatusReply("none");
34+
}
35+
try {
36+
Object ignored = SerializationUtils.deserialize(got);
37+
} catch (SerializationException e) {
38+
return new StatusReply("string");
39+
}
40+
Object value = SerializationUtils.deserialize(got);
41+
try {
42+
String ignored = (String) value;
43+
return new StatusReply("string");
44+
} catch (ClassCastException e) {
45+
if (value instanceof HashMap) {
46+
return new StatusReply("hash");
47+
} else if (value instanceof LinkedList) {
48+
return new StatusReply("list");
49+
} else if (value instanceof HashSet) {
50+
return new StatusReply("set");
51+
} else {
52+
return new StatusReply("unknown");
53+
}
54+
}
55+
}
56+
}

server/src/test/java/dev/keva/server/core/AbstractServerTest.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -791,4 +791,37 @@ void setrange() {
791791
}
792792
}
793793

794+
@Test
795+
void dumpAndRestore() {
796+
try {
797+
val set1 = jedis.set("key1", "Hello World");
798+
assertEquals("OK", set1);
799+
val dump1 = jedis.dump("key1");
800+
assertNotNull(dump1);
801+
val restore1 = jedis.restore("key2", 0, dump1);
802+
assertEquals("OK", restore1);
803+
val key2 = jedis.get("key2");
804+
assertEquals("Hello World", key2);
805+
} catch (Exception e) {
806+
fail(e);
807+
}
808+
}
809+
810+
@Test
811+
void type() {
812+
try {
813+
val set1 = jedis.set("key1", "Hello World");
814+
assertEquals("OK", set1);
815+
val type1 = jedis.type("key1");
816+
assertEquals("string", type1);
817+
val type2 = jedis.type("key2");
818+
assertEquals("none", type2);
819+
val hashSet = jedis.hset("key3", "field1", "value1");
820+
assertEquals(1, hashSet.intValue());
821+
val type3 = jedis.type("key3");
822+
assertEquals("hash", type3);
823+
} catch (Exception e) {
824+
fail(e);
825+
}
826+
}
794827
}

store/src/main/java/dev/keva/store/impl/OnHeapDatabaseImpl.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ public boolean hdel(byte[] key, byte[] field) {
198198
public int lpush(byte[] key, byte[]... values) {
199199
lock.lock();
200200
try {
201-
byte[] value = map.get(key).getBytes();
201+
byte[] value = map.get(new BytesKey(key)).getBytes();
202202
LinkedList<BytesValue> list;
203203
list = value == null ? new LinkedList<>() : (LinkedList<BytesValue>) SerializationUtils.deserialize(value);
204204
for (byte[] v : values) {
@@ -216,7 +216,7 @@ public int lpush(byte[] key, byte[]... values) {
216216
public int rpush(byte[] key, byte[]... values) {
217217
lock.lock();
218218
try {
219-
byte[] value = map.get(key).getBytes();
219+
byte[] value = map.get(new BytesKey(key)).getBytes();
220220
LinkedList<BytesValue> list;
221221
list = value == null ? new LinkedList<>() : (LinkedList<BytesValue>) SerializationUtils.deserialize(value);
222222
for (byte[] v : values) {

0 commit comments

Comments
 (0)