Repository: fppt/jedis-mock
Pull request #73 (2019-12-20 13:13:59)

Add support for rename operation (#72)


Patch:
diff --git a/src/main/java/com/github/fppt/jedismock/operations/OperationFactory.java b/src/main/java/com/github/fppt/jedismock/operations/OperationFactory.java
index 3b9ebeb..50b8ebf 100644
--- a/src/main/java/com/github/fppt/jedismock/operations/OperationFactory.java
+++ b/src/main/java/com/github/fppt/jedismock/operations/OperationFactory.java
@@ -86,6 +86,7 @@ public class OperationFactory {
         TRANSACTIONAL_OPERATIONS.put("zrange", RO_zrange::new);
         TRANSACTIONAL_OPERATIONS.put("zrangebylex", RO_zrangebylex::new);
         TRANSACTIONAL_OPERATIONS.put("zrem", RO_zrem::new);
+        TRANSACTIONAL_OPERATIONS.put("rename", RO_rename::new);
     }
 
 
diff --git a/src/main/java/com/github/fppt/jedismock/operations/RO_rename.java b/src/main/java/com/github/fppt/jedismock/operations/RO_rename.java
new file mode 100644
index 0000000..1de3362
--- /dev/null
+++ b/src/main/java/com/github/fppt/jedismock/operations/RO_rename.java
@@ -0,0 +1,38 @@
+package com.github.fppt.jedismock.operations;
+
+import com.github.fppt.jedismock.server.Response;
+import com.github.fppt.jedismock.server.Slice;
+import com.github.fppt.jedismock.storage.RedisBase;
+
+import java.util.List;
+import java.util.Map;
+
+class RO_rename extends AbstractRedisOperation {
+
+    RO_rename(RedisBase base, List<Slice> params) {
+        super(base, params);
+    }
+
+    private boolean rename(Slice key, Slice newKey) {
+        Map<Slice, Slice> value = base().getFieldsAndValues(key);
+        final Long ttl = base().getTTL(key);
+        if (ttl == null) {
+            return false;
+        }
+        for (Map.Entry<Slice, Slice> entry : value.entrySet()) {
+            base().putValue(newKey, entry.getKey(), entry.getValue(), ttl);
+        }
+        base().deleteValue(key);
+        return true;
+    }
+
+    @Override
+    Slice response() {
+        final Slice key = params().get(0);
+        final Slice newKey = params().get(1);
+        if (!rename(key, newKey)) {
+            return Response.error("ERR no such key");
+        }
+        return Response.OK;
+    }
+}
diff --git a/src/main/java/com/github/fppt/jedismock/storage/ExpiringKeyValueStorage.java b/src/main/java/com/github/fppt/jedismock/storage/ExpiringKeyValueStorage.java
index 561a380..e58f16a 100644
--- a/src/main/java/com/github/fppt/jedismock/storage/ExpiringKeyValueStorage.java
+++ b/src/main/java/com/github/fppt/jedismock/storage/ExpiringKeyValueStorage.java
@@ -4,6 +4,7 @@ import com.github.fppt.jedismock.server.Slice;
 import com.google.auto.value.AutoValue;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.HashBasedTable;
+import com.google.common.collect.Maps;
 import com.google.common.collect.Table;
 
 import java.util.Map;
@@ -15,16 +16,14 @@ import java.util.Map;
 @AutoValue
 public abstract class ExpiringKeyValueStorage {
     public abstract Table<Slice, Slice, Slice> values();
-    public abstract Table<Slice, Slice, Long> ttls();
+    public abstract Map<Slice, Long> ttls();
 
     public static ExpiringKeyValueStorage create(){
-        return new AutoValue_ExpiringKeyValueStorage(HashBasedTable.create(), HashBasedTable.create());
+        return new AutoValue_ExpiringKeyValueStorage(HashBasedTable.create(), Maps.newHashMap());
     }
 
     public void delete(Slice key) {
-        for (Slice key2 : values().row(key).keySet()) {
-            ttls().remove(key, key2);
-        }
+        ttls().remove(key);
         values().row(key).clear();
     }
 
@@ -32,7 +31,10 @@ public abstract class ExpiringKeyValueStorage {
         Preconditions.checkNotNull(key1);
         Preconditions.checkNotNull(key2);
         values().remove(key1, key2);
-        ttls().remove(key1, key2);
+
+        if (!values().containsRow(key1)) {
+            ttls().remove(key1);
+        }
     }
 
     public void clear(){
@@ -52,23 +54,18 @@ public abstract class ExpiringKeyValueStorage {
         Preconditions.checkNotNull(key1);
         Preconditions.checkNotNull(key2);
 
-        Long deadline = ttls().get(key1, key2);
+        Long deadline = ttls().get(key1);
         if (deadline != null && deadline != -1 && deadline <= System.currentTimeMillis()) {
-            delete(key1, key2);
+            delete(key1);
             return null;
         }
         return values().get(key1, key2);
     }
 
-    public Long getTTL(Slice key){
-        return getTTL(key, Slice.reserved());
-    }
-
-    public Long getTTL(Slice key1, Slice key2){
-        Preconditions.checkNotNull(key1);
-        Preconditions.checkNotNull(key2);
+    public Long getTTL(Slice key) {
+        Preconditions.checkNotNull(key);
 
-        Long deadline = ttls().get(key1, key2);
+        Long deadline = ttls().get(key);
         if (deadline == null) {
             return null;
         }
@@ -79,16 +76,12 @@ public abstract class ExpiringKeyValueStorage {
         if (now < deadline) {
             return deadline - now;
         }
-        delete(key1, key1);
+        delete(key);
         return null;
     }
 
     public long setTTL(Slice key, long ttl){
-        return setTTL(key, Slice.reserved(), ttl);
-    }
-
-    public long setTTL(Slice key1, Slice key2, long ttl){
-        return setDeadline(key1, key2, ttl + System.currentTimeMillis());
+        return setDeadline(key, ttl + System.currentTimeMillis());
     }
 
     public void put(Slice key, Slice value, Long ttl){
@@ -104,28 +97,23 @@ public abstract class ExpiringKeyValueStorage {
         if (ttl == null) {
             // If a TTL hasn't been provided, we don't want to override the TTL. However, if no TTL is set for this key,
             // we should still set it to -1L
-            if (getTTL(key1, key2) == null) {
-                setDeadline(key1, key2, -1L);
+            if (getTTL(key1) == null) {
+                setDeadline(key1, -1L);
             }
         } else {
             if (ttl != -1) {
-                setTTL(key1, key2, ttl);
+                setTTL(key1, ttl);
             } else {
-                setDeadline(key1, key2, -1L);
+                setDeadline(key1, -1L);
             }
         }
     }
 
     public long setDeadline(Slice key, long deadline) {
-        return setDeadline(key, Slice.reserved(), deadline);
-    }
-
-    public long setDeadline(Slice key1, Slice key2, long deadline) {
-        Preconditions.checkNotNull(key1);
-        Preconditions.checkNotNull(key2);
+        Preconditions.checkNotNull(key);
 
-        if (values().contains(key1, key2)) {
-            ttls().put(key1, key2, deadline);
+        if (values().containsRow(key)) {
+            ttls().put(key, deadline);
             return 1L;
         }
         return 0L;
@@ -133,7 +121,7 @@ public abstract class ExpiringKeyValueStorage {
 
     public boolean exists(Slice slice) {
         if (values().containsRow(slice)) {
-            Long deadline = ttls().get(slice, Slice.reserved());
+            Long deadline = ttls().get(slice);
             if (deadline != null && deadline != -1 && deadline <= System.currentTimeMillis()) {
                 delete(slice, Slice.reserved());
                 return false;


Test patch:
diff --git a/src/test/java/com/github/fppt/jedismock/TestRedisOperationExecutor.java b/src/test/java/com/github/fppt/jedismock/TestRedisOperationExecutor.java
index 8be072b..0da1f61 100644
--- a/src/test/java/com/github/fppt/jedismock/TestRedisOperationExecutor.java
+++ b/src/test/java/com/github/fppt/jedismock/TestRedisOperationExecutor.java
@@ -422,4 +422,23 @@ public class TestRedisOperationExecutor {
         assertCommandOK(array("set", "a", "v"));
         assertCommandError(array("rpush", "a", "1"));
     }
+
+    @Test
+    public void testRename() throws EOFException {
+        assertCommandError(array("rename", "old", "new"));
+        assertCommandNull(array("get", "new"));
+        assertCommandOK(array("set", "old", "abc"));
+        assertCommandOK(array("rename", "old", "new"));
+        assertCommandEquals("abc", array("get", "new"));
+        assertCommandNull(array("get", "old"));
+    }
+
+    @Test
+    public void testRenameHash() throws EOFException {
+        assertCommandEquals(1, array("hset", "old", "a", "1"));
+        assertCommandOK(array("rename", "old", "new"));
+        assertCommandEquals("1", array("hget", "new", "a"));
+        assertCommandNull(array("get", "old"));
+    }
+
 }
diff --git a/src/test/java/com/github/fppt/jedismock/comparisontests/SimpleOperationsTest.java b/src/test/java/com/github/fppt/jedismock/comparisontests/SimpleOperationsTest.java
index 88f6778..e8e3372 100644
--- a/src/test/java/com/github/fppt/jedismock/comparisontests/SimpleOperationsTest.java
+++ b/src/test/java/com/github/fppt/jedismock/comparisontests/SimpleOperationsTest.java
@@ -909,4 +909,21 @@ public class SimpleOperationsTest extends ComparisonBase {
 
         assertTrue(ttl > 0);
     }
+
+    @Theory
+    public void hashExpires(Jedis jedis) throws InterruptedException {
+        jedis.flushDB();
+
+        String key = "mykey";
+        String subkey = "mysubkey";
+
+        jedis.hsetnx(key, subkey, "a");
+        jedis.expire(key, 1);
+
+        Thread.sleep(2000);
+
+        String result = jedis.hget(key, subkey);
+
+        assertNull(result);
+    }
 }