Repository: fppt/jedis-mock
Pull request #3 (2018-11-27 15:08:53)

Multiple Changes: Design Rework, hget, hset, hdel support
1. Internal design changes to make future work easier.
2. Support for `hset`, `hget`, & `hdel`


Patch:
diff --git a/pom.xml b/pom.xml
index dd5895f..53429a3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -43,6 +43,7 @@
         <redis-embedded.version>0.6</redis-embedded.version>
         <findbugs.version>3.0.1</findbugs.version>
         <slf4j.version>1.7.20</slf4j.version>
+        <auto-value.version>1.6.2</auto-value.version>
     </properties>
 
     <distributionManagement>
@@ -94,6 +95,18 @@
             <artifactId>slf4j-api</artifactId>
             <version>${slf4j.version}</version>
         </dependency>
+        <!-- Used For immutable data objects -->
+        <dependency>
+            <groupId>com.google.auto.value</groupId>
+            <artifactId>auto-value-annotations</artifactId>
+            <version>${auto-value.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.google.auto.value</groupId>
+            <artifactId>auto-value</artifactId>
+            <version>${auto-value.version}</version>
+            <scope>provided</scope>
+        </dependency>
     </dependencies>
 
     <build>
diff --git a/src/main/java/com/github/fppt/jedismock/RedisBase.java b/src/main/java/com/github/fppt/jedismock/RedisBase.java
index 9614eab..d1f3945 100644
--- a/src/main/java/com/github/fppt/jedismock/RedisBase.java
+++ b/src/main/java/com/github/fppt/jedismock/RedisBase.java
@@ -1,6 +1,6 @@
 package com.github.fppt.jedismock;
 
-import com.google.common.base.Preconditions;
+import com.github.fppt.jedismock.storage.ExpiringKeyValueStorage;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -9,14 +9,14 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Consumer;
 
 /**
  * Created by Xiaolu on 2015/4/20.
  */
 public class RedisBase {
+    private final ExpiringKeyValueStorage keyValueStorage = ExpiringKeyValueStorage.create();
     private final Map<Slice, Set<RedisClient>> subscribers = new ConcurrentHashMap<>();
-    private final Map<Slice, Slice> base = new ConcurrentHashMap<>();
-    private final Map<Slice, Long> deadlines = new ConcurrentHashMap<>();
     private final Set<RedisBase> syncBases = ConcurrentHashMap.newKeySet();
 
     public RedisBase() {}
@@ -25,100 +25,69 @@ public class RedisBase {
         syncBases.add(base);
     }
 
+
     public Set<Slice> keys(){
-        return base.keySet();
+        return keyValueStorage.values().rowMap().keySet();
     }
 
-    public Slice rawGet(Slice key) {
-        Preconditions.checkNotNull(key);
+    public Slice getValue(Slice key) {
+        return keyValueStorage.get(key);
+    }
 
-        Long deadline = deadlines.get(key);
-        if (deadline != null && deadline != -1 && deadline <= System.currentTimeMillis()) {
-            base.remove(key);
-            deadlines.remove(key);
-            return null;
-        }
-        return base.get(key);
+    public Slice getValue(Slice key1, Slice key2) {
+        return keyValueStorage.get(key1, key2);
     }
 
     public Long getTTL(Slice key) {
-        Preconditions.checkNotNull(key);
+        return keyValueStorage.getTTL(key);
+    }
 
-        Long deadline = deadlines.get(key);
-        if (deadline == null) {
-            return null;
-        }
-        if (deadline == -1) {
-            return deadline;
-        }
-        long now = System.currentTimeMillis();
-        if (now < deadline) {
-            return deadline - now;
+    private void syncBases(Consumer<RedisBase> syncFunction){
+        for (RedisBase base : syncBases) {
+            syncFunction.accept(base);
         }
-        base.remove(key);
-        deadlines.remove(key);
-        return null;
     }
 
     public long setTTL(Slice key, long ttl) {
-        Preconditions.checkNotNull(key);
-
-        if (base.containsKey(key)) {
-            deadlines.put(key, ttl + System.currentTimeMillis());
-            for (RedisBase base : syncBases) {
-                base.setTTL(key, ttl);
-            }
-            return 1L;
-        }
-        return 0L;
+        long result = keyValueStorage.setTTL(key, ttl);
+        syncBases((base) -> base.setTTL(key, ttl));
+        return result;
     }
 
     public long setDeadline(Slice key, long deadline) {
-        Preconditions.checkNotNull(key);
-
-        if (base.containsKey(key)) {
-            deadlines.put(key, deadline);
-            for (RedisBase base : syncBases) {
-                base.setDeadline(key, deadline);
-            }
-            return 1L;
-        }
-        return 0L;
+        long result = keyValueStorage.setDeadline(key, deadline);
+        syncBases((base) -> base.setDeadline(key, deadline));
+        return result;
     }
 
     public void clear(){
-        base.clear();
+        keyValueStorage.clear();
         subscribers.clear();
-        deadlines.clear();
         syncBases.clear();
     }
 
-    public void rawPut(Slice key, Slice value, Long ttl) {
-        Preconditions.checkNotNull(key);
-        Preconditions.checkNotNull(value);
+    public void putValue(Slice key, Slice value){
+        putValue(key, value, -1L);
+    }
 
-        base.put(key, value);
-        if (ttl != null) {
-            if (ttl != -1) {
-                deadlines.put(key, ttl + System.currentTimeMillis());
-            } else {
-                deadlines.put(key, -1L);
-            }
-        }
-        for (RedisBase base : syncBases) {
-            base.rawPut(key, value, ttl);
-        }
+    public void putValue(Slice key, Slice value, Long ttl) {
+        keyValueStorage.put(key, value, ttl);
+        syncBases((base) -> base.putValue(key, value, ttl));
     }
 
-    public void del(Slice key) {
-        Preconditions.checkNotNull(key);
+    public void putValue(Slice key1, Slice key2, Slice value, Long ttl) {
+        keyValueStorage.put(key1, key2, value, ttl);
+        syncBases((base) -> base.putValue(key1, key2, value, ttl));
+    }
 
-        base.remove(key);
-        deadlines.remove(key);
+    public void deleteValue(Slice key) {
+        keyValueStorage.delete(key);
+        syncBases((base) -> base.deleteValue(key));
+    }
 
-        for (RedisBase base : syncBases) {
-            base.del(key);
-        }
+    public void deleteValue(Slice key1, Slice key2) {
+        keyValueStorage.delete(key1, key2);
+        syncBases((base) -> base.deleteValue(key1, key2));
     }
 
     public void addSubscriber(Slice channel, RedisClient client){
diff --git a/src/main/java/com/github/fppt/jedismock/RedisClient.java b/src/main/java/com/github/fppt/jedismock/RedisClient.java
index 91a392e..2708c49 100644
--- a/src/main/java/com/github/fppt/jedismock/RedisClient.java
+++ b/src/main/java/com/github/fppt/jedismock/RedisClient.java
@@ -1,7 +1,7 @@
 package com.github.fppt.jedismock;
 
 import com.github.fppt.jedismock.commands.RedisOperationExecutor;
-import com.github.fppt.jedismock.exception.EOFException;
+import com.github.fppt.jedismock.exception.ParseErrorException;
 import com.google.common.base.Preconditions;
 import org.slf4j.LoggerFactory;
 
@@ -49,8 +49,7 @@ public class RedisClient implements Runnable {
                 sendResponse(response, command.toString());
 
                 count++;
-                if (options.getCloseSocketAfterSeveralCommands() != 0
-                        && options.getCloseSocketAfterSeveralCommands() == count) {
+                if (options.autoCloseOn() != 0 && options.autoCloseOn() == count) {
                     break;
                 }
             }
@@ -67,10 +66,9 @@ public class RedisClient implements Runnable {
     private Optional<RedisCommand> nextCommand(){
         try {
             return Optional.of(RedisCommandParser.parse(in));
-        } catch (EOFException e) {
-            //IGNORED often there will be nothing on the stream
+        } catch (ParseErrorException e){
+            return Optional.empty(); // This simply means there is no next command
         }
-        return Optional.empty();
     }
 
     /**
diff --git a/src/main/java/com/github/fppt/jedismock/RedisCommand.java b/src/main/java/com/github/fppt/jedismock/RedisCommand.java
index 0f54b02..f2c6d4f 100644
--- a/src/main/java/com/github/fppt/jedismock/RedisCommand.java
+++ b/src/main/java/com/github/fppt/jedismock/RedisCommand.java
@@ -1,29 +1,22 @@
 package com.github.fppt.jedismock;
 
+import com.google.auto.value.AutoValue;
 import com.google.common.collect.Lists;
 
 import java.util.List;
 import java.util.stream.Collectors;
 
-/**
- * Created by Xiaolu on 2015/4/20.
- */
-public class RedisCommand {
+@AutoValue
+public abstract class RedisCommand {
 
-    private List<Slice> params = Lists.newArrayList();
+    public abstract List<Slice> parameters();
 
-    RedisCommand() {}
-
-    void addParameter(Slice token) {
-        this.params.add(token);
-    }
-
-    public List<Slice> getParameters() {
-        return params;
+    static RedisCommand create(){
+        return new AutoValue_RedisCommand(Lists.newArrayList());
     }
 
     @Override
     public String toString(){
-        return params.stream().map(Slice::toString).collect(Collectors.joining(" "));
+        return parameters().stream().map(Slice::toString).collect(Collectors.joining(" "));
     }
 }
diff --git a/src/main/java/com/github/fppt/jedismock/RedisCommandParser.java b/src/main/java/com/github/fppt/jedismock/RedisCommandParser.java
index ccea66f..e8b26e5 100644
--- a/src/main/java/com/github/fppt/jedismock/RedisCommandParser.java
+++ b/src/main/java/com/github/fppt/jedismock/RedisCommandParser.java
@@ -1,6 +1,5 @@
 package com.github.fppt.jedismock;
 
-import com.github.fppt.jedismock.exception.EOFException;
 import com.github.fppt.jedismock.exception.ParseErrorException;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Preconditions;
@@ -13,24 +12,23 @@ import java.io.InputStream;
  */
 public class RedisCommandParser {
 
-
     @VisibleForTesting
-    static RedisCommand parse(String stringInput) throws ParseErrorException, EOFException {
+    static RedisCommand parse(String stringInput) throws ParseErrorException {
         Preconditions.checkNotNull(stringInput);
 
         return parse(new ByteArrayInputStream(stringInput.getBytes()));
     }
 
-    static RedisCommand parse(InputStream messageInput) throws ParseErrorException, EOFException {
+    static RedisCommand parse(InputStream messageInput) throws ParseErrorException {
         Preconditions.checkNotNull(messageInput);
 
         long count = SliceParser.consumeCount(messageInput);
         if (count == 0) {
             throw new ParseErrorException();
         }
-        RedisCommand command = new RedisCommand();
+        RedisCommand command = RedisCommand.create();
         for (long i = 0; i < count; i++) {
-            command.addParameter(SliceParser.consumeParameter(messageInput));
+            command.parameters().add(SliceParser.consumeParameter(messageInput));
         }
         return command;
     }
diff --git a/src/main/java/com/github/fppt/jedismock/RedisServer.java b/src/main/java/com/github/fppt/jedismock/RedisServer.java
index 99692e7..a0e89bc 100644
--- a/src/main/java/com/github/fppt/jedismock/RedisServer.java
+++ b/src/main/java/com/github/fppt/jedismock/RedisServer.java
@@ -11,7 +11,7 @@ import java.net.ServerSocket;
 public class RedisServer {
 
     private final int bindPort;
-    private ServiceOptions options = new ServiceOptions();
+    private ServiceOptions options = ServiceOptions.defaultOptions();
     private ServerSocket server = null;
     private Thread service = null;
     private final RedisBase base = new RedisBase();
diff --git a/src/main/java/com/github/fppt/jedismock/Response.java b/src/main/java/com/github/fppt/jedismock/Response.java
index aa87417..fc23f4b 100644
--- a/src/main/java/com/github/fppt/jedismock/Response.java
+++ b/src/main/java/com/github/fppt/jedismock/Response.java
@@ -14,29 +14,29 @@ public class Response {
     private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(Response.class);
     private static final String LINE_SEPARATOR = "\r\n";
 
-    public static final Slice OK = new Slice("+OK" + LINE_SEPARATOR);
-    public static final Slice NULL = new Slice("$-1" + LINE_SEPARATOR);
-    public static final Slice SKIP = new Slice("Skip this submission");
+    public static final Slice OK = Slice.create("+OK" + LINE_SEPARATOR);
+    public static final Slice NULL = Slice.create("$-1" + LINE_SEPARATOR);
+    public static final Slice SKIP = Slice.create("Skip this submission");
 
     private Response() {}
 
-    public static Slice bulkString(Slice s) {
-        if (s == null) {
+    public static Slice bulkString(Slice slice) {
+        if (slice == null) {
             return NULL;
         }
         ByteArrayDataOutput bo = ByteStreams.newDataOutput();
-        bo.write(String.format("$%d%s", s.length(), LINE_SEPARATOR).getBytes());
-        bo.write(s.data());
+        bo.write(String.format("$%d%s", slice.data().length, LINE_SEPARATOR).getBytes());
+        bo.write(slice.data());
         bo.write(LINE_SEPARATOR.getBytes());
-        return new Slice(bo.toByteArray());
+        return Slice.create(bo.toByteArray());
     }
 
     public static Slice error(String s) {
-        return new Slice(String.format("-%s%s", s, LINE_SEPARATOR));
+        return Slice.create(String.format("-%s%s", s, LINE_SEPARATOR));
     }
 
     public static Slice integer(long v) {
-        return new Slice(String.format(":%d%s", v, LINE_SEPARATOR));
+        return Slice.create(String.format(":%d%s", v, LINE_SEPARATOR));
     }
 
     public static Slice array(List<Slice> values) {
@@ -45,7 +45,7 @@ public class Response {
         for (Slice value : values) {
             bo.write(value.data());
         }
-        return new Slice(bo.toByteArray());
+        return Slice.create(bo.toByteArray());
     }
 
     public static Slice publishedMessage(Slice channel, Slice message){
diff --git a/src/main/java/com/github/fppt/jedismock/ServiceOptions.java b/src/main/java/com/github/fppt/jedismock/ServiceOptions.java
index 22bb7cc..a8da0ab 100644
--- a/src/main/java/com/github/fppt/jedismock/ServiceOptions.java
+++ b/src/main/java/com/github/fppt/jedismock/ServiceOptions.java
@@ -1,19 +1,19 @@
 package com.github.fppt.jedismock;
 
+import com.google.auto.value.AutoValue;
+
 /**
  * Created by Xiaolu on 2015/4/22.
  */
-public class ServiceOptions {
-
-    private int closeSocketAfterSeveralCommands = 0;
-
-    public ServiceOptions() {}
+@AutoValue
+public abstract class ServiceOptions {
+    public abstract int autoCloseOn();
 
-    public void setCloseSocketAfterSeveralCommands(int count) {
-        this.closeSocketAfterSeveralCommands = count;
+    public static ServiceOptions defaultOptions() {
+        return new AutoValue_ServiceOptions(0);
     }
 
-    public int getCloseSocketAfterSeveralCommands() {
-        return closeSocketAfterSeveralCommands;
+    public static ServiceOptions create(int autoCloseOn) {
+        return new AutoValue_ServiceOptions(autoCloseOn);
     }
 }
diff --git a/src/main/java/com/github/fppt/jedismock/Slice.java b/src/main/java/com/github/fppt/jedismock/Slice.java
index 8bd79e6..969b527 100644
--- a/src/main/java/com/github/fppt/jedismock/Slice.java
+++ b/src/main/java/com/github/fppt/jedismock/Slice.java
@@ -1,56 +1,68 @@
 package com.github.fppt.jedismock;
 
+import com.google.auto.value.AutoValue;
+
 import java.io.Serializable;
 import java.util.Arrays;
 
 /**
  * Created by Xiaolu on 2015/4/23.
  */
-public class Slice implements Comparable<Slice>, Serializable {
-    private static final long serialVersionUID = 247772234876073528L;
+@AutoValue
+public abstract class Slice implements Comparable<Slice>, Serializable {
+    private static final String RESERVED_SLICE_NAME = "Reserved String In Jedis Mock";
+    private static final byte[] RESERVED_SLICE_BYTES = RESERVED_SLICE_NAME.getBytes();
+    private static Slice RESERVED_SLICE = null;
 
-    private final byte[] data;
+    private static final long serialVersionUID = 247772234876073528L;
+    public abstract byte[] data();
 
-    public Slice(byte[] data) {
-        this.data = data.clone();
+    public static Slice create(byte[] data){
+        if(Arrays.equals(data, RESERVED_SLICE_BYTES)){
+           throw new RuntimeException("Cannot create key/value in mock due to [" + RESERVED_SLICE_NAME + "] being reserved");
+        }
+        return new AutoValue_Slice(data);
     }
 
-    public Slice(String data) {
-        this.data = data.getBytes().clone();
+    public static Slice create(String data){
+        return create(data.getBytes().clone());
     }
 
-    public byte[] data() {
-        return this.data.clone();
+    public static synchronized Slice reserved(){
+        if (RESERVED_SLICE == null){
+            RESERVED_SLICE = new AutoValue_Slice(RESERVED_SLICE_BYTES);
+        }
+        return RESERVED_SLICE;
     }
 
-    public int length() {
-        return this.data.length;
+    public int length(){
+        return data().length;
     }
 
     @Override
     public String toString() {
-        return new String(data);
+        return new String(data());
     }
 
     @Override
     public boolean equals(Object b) {
-        return b instanceof Slice && Arrays.equals(data, ((Slice) b).data());
+        return b instanceof Slice && Arrays.equals(data(), ((Slice) b).data());
     }
 
     @Override
     public int hashCode() {
-        return Arrays.hashCode(data);
+        return Arrays.hashCode(data());
     }
 
     public int compareTo(Slice b) {
-        int len1 = data.length;
-        int len2 = b.data.length;
+        int len1 = data().length;
+        int len2 = b.data().length;
         int lim = Math.min(len1, len2);
 
         int k = 0;
         while (k < lim) {
-            byte b1 = data[k];
-            byte b2 = b.data[k];
+            byte b1 = data()[k];
+            byte b2 = b.data()[k];
             if (b1 != b2) {
                 return b1 - b2;
             }
diff --git a/src/main/java/com/github/fppt/jedismock/SliceParser.java b/src/main/java/com/github/fppt/jedismock/SliceParser.java
index 591ad37..fcaa96e 100644
--- a/src/main/java/com/github/fppt/jedismock/SliceParser.java
+++ b/src/main/java/com/github/fppt/jedismock/SliceParser.java
@@ -69,7 +69,7 @@ public class SliceParser {
                 throw new ParseErrorException();
             }
         }
-        return new Slice(bo.toByteArray());
+        return Slice.create(bo.toByteArray());
     }
 
     @VisibleForTesting
diff --git a/src/main/java/com/github/fppt/jedismock/Utils.java b/src/main/java/com/github/fppt/jedismock/Utils.java
index 46121ad..944a783 100644
--- a/src/main/java/com/github/fppt/jedismock/Utils.java
+++ b/src/main/java/com/github/fppt/jedismock/Utils.java
@@ -1,7 +1,5 @@
 package com.github.fppt.jedismock;
 
-import com.github.fppt.jedismock.exception.InternalException;
-import com.github.fppt.jedismock.exception.WrongNumberOfArgumentsException;
 import com.github.fppt.jedismock.exception.WrongValueTypeException;
 
 import java.io.ByteArrayInputStream;
@@ -25,21 +23,21 @@ public class Utils {
         }
     }
 
-    public static void checkArgumentsNumberEquals(List<Slice> args, int expect) throws WrongNumberOfArgumentsException {
+    public static void checkArgumentsNumberEquals(List<Slice> args, int expect) {
         if (args.size() != expect) {
-            throw new WrongNumberOfArgumentsException();
+            throw new IllegalArgumentException("The number of arguments [" + args.size() + "] does not match the expected value [" + expect + "]");
         }
     }
 
-    public static void checkArgumentsNumberGreater(List<Slice> args, int expect) throws WrongNumberOfArgumentsException {
+    public static void checkArgumentsNumberGreater(List<Slice> args, int expect) {
         if (args.size() <= expect) {
-            throw new WrongNumberOfArgumentsException();
+            throw new IllegalArgumentException("The number of arguments [" + args.size() + "] is less than the expected value [" + expect + "]");
         }
     }
 
-    public static void checkArgumentsNumberFactor(List<Slice> args, int factor) throws WrongNumberOfArgumentsException {
+    public static void checkArgumentsNumberFactor(List<Slice> args, int factor) {
         if (args.size() % factor != 0) {
-            throw new WrongNumberOfArgumentsException();
+            throw new IllegalArgumentException("The number of arguments [" + args.size() + "] is not a factor of the expected value [" + factor + "]");
         }
     }
 
@@ -48,12 +46,12 @@ public class Utils {
             ByteArrayOutputStream bo = new ByteArrayOutputStream();
             ObjectOutputStream oo = new ObjectOutputStream(bo);
             oo.writeObject(o);
-            Slice encode = new Slice(bo.toByteArray());
+            Slice encode = Slice.create(bo.toByteArray());
             oo.close();
             bo.close();
             return encode;
         } catch (IOException e){
-            throw new InternalException(e.getMessage());
+            throw new RuntimeException(e.getMessage());
         }
     }
 
diff --git a/src/main/java/com/github/fppt/jedismock/commands/AbstractRedisOperation.java b/src/main/java/com/github/fppt/jedismock/commands/AbstractRedisOperation.java
index 5553c66..1776d8d 100644
--- a/src/main/java/com/github/fppt/jedismock/commands/AbstractRedisOperation.java
+++ b/src/main/java/com/github/fppt/jedismock/commands/AbstractRedisOperation.java
@@ -20,6 +20,7 @@ abstract class AbstractRedisOperation implements RedisOperation {
     }
 
     void doOptionalWork(){
+        //Place Holder For Ops which need to so some operational work
     }
 
     abstract Slice response();
diff --git a/src/main/java/com/github/fppt/jedismock/commands/RO_append.java b/src/main/java/com/github/fppt/jedismock/commands/RO_append.java
index 63e6718..6f98d43 100644
--- a/src/main/java/com/github/fppt/jedismock/commands/RO_append.java
+++ b/src/main/java/com/github/fppt/jedismock/commands/RO_append.java
@@ -14,9 +14,9 @@ class RO_append extends AbstractRedisOperation {
     Slice response() {
         Slice key = params().get(0);
         Slice value = params().get(1);
-        Slice s = base().rawGet(key);
+        Slice s = base().getValue(key);
         if (s == null) {
-            base().rawPut(key, value, -1L);
+            base().putValue(key, value);
             return Response.integer(value.length());
         }
         byte[] b = new byte[s.length() + value.length()];
@@ -26,7 +26,7 @@ class RO_append extends AbstractRedisOperation {
         for (int i = s.length(); i < s.length() + value.length(); i++) {
             b[i] = value.data()[i - s.length()];
         }
-        base().rawPut(key, new Slice(b), -1L);
+        base().putValue(key, Slice.create(b));
         return Response.integer(b.length);
     }
 }
diff --git a/src/main/java/com/github/fppt/jedismock/commands/RO_brpoplpush.java b/src/main/java/com/github/fppt/jedismock/commands/RO_brpoplpush.java
index f9bcc21..cafe338 100644
--- a/src/main/java/com/github/fppt/jedismock/commands/RO_brpoplpush.java
+++ b/src/main/java/com/github/fppt/jedismock/commands/RO_brpoplpush.java
@@ -44,7 +44,7 @@ class RO_brpoplpush extends RO_rpoplpush {
     }
 
     private long getCount(Slice source){
-        Slice index = new Slice("0");
+        Slice index = Slice.create("0");
         List<Slice> commands = Arrays.asList(source, index, index);
         Slice result = new RO_lrange(base(), commands).execute();
         return SliceParser.consumeCount(result.data());
diff --git a/src/main/java/com/github/fppt/jedismock/commands/RO_del.java b/src/main/java/com/github/fppt/jedismock/commands/RO_del.java
index 78accee..e681bba 100644
--- a/src/main/java/com/github/fppt/jedismock/commands/RO_del.java
+++ b/src/main/java/com/github/fppt/jedismock/commands/RO_del.java
@@ -14,8 +14,8 @@ class RO_del extends AbstractRedisOperation {
     Slice response(){
         int count = 0;
         for (Slice key : params()) {
-            Slice value = base().rawGet(key);
-            base().del(key);
+            Slice value = base().getValue(key);
+            base().deleteValue(key);
             if (value != null) {
                 count++;
             }
diff --git a/src/main/java/com/github/fppt/jedismock/commands/RO_exists.java b/src/main/java/com/github/fppt/jedismock/commands/RO_exists.java
index 158421a..6b5d803 100644
--- a/src/main/java/com/github/fppt/jedismock/commands/RO_exists.java
+++ b/src/main/java/com/github/fppt/jedismock/commands/RO_exists.java
@@ -12,7 +12,7 @@ class RO_exists extends AbstractRedisOperation {
     }
 
     Slice response() {
-        if (base().rawGet(params().get(0)) != null) {
+        if (base().getValue(params().get(0)) != null) {
             return Response.integer(1);
         }
         return Response.integer(0);
diff --git a/src/main/java/com/github/fppt/jedismock/commands/RO_get.java b/src/main/java/com/github/fppt/jedismock/commands/RO_get.java
index 8fbab9e..0ed28c4 100644
--- a/src/main/java/com/github/fppt/jedismock/commands/RO_get.java
+++ b/src/main/java/com/github/fppt/jedismock/commands/RO_get.java
@@ -12,6 +12,6 @@ class RO_get extends AbstractRedisOperation {
     }
 
     Slice response() {
-        return Response.bulkString(base().rawGet(params().get(0)));
+        return Response.bulkString(base().getValue(params().get(0)));
     }
 }
diff --git a/src/main/java/com/github/fppt/jedismock/commands/RO_getbit.java b/src/main/java/com/github/fppt/jedismock/commands/RO_getbit.java
index 62c4455..38629f5 100644
--- a/src/main/java/com/github/fppt/jedismock/commands/RO_getbit.java
+++ b/src/main/java/com/github/fppt/jedismock/commands/RO_getbit.java
@@ -14,7 +14,7 @@ class RO_getbit extends AbstractRedisOperation {
     }
 
     Slice response() {
-        Slice value = base().rawGet(params().get(0));
+        Slice value = base().getValue(params().get(0));
         int pos = convertToNonNegativeInteger(params().get(1).toString());
 
         if (value == null) {
diff --git a/src/main/java/com/github/fppt/jedismock/commands/RO_getset.java b/src/main/java/com/github/fppt/jedismock/commands/RO_getset.java
index 6d344b2..46794ba 100644
--- a/src/main/java/com/github/fppt/jedismock/commands/RO_getset.java
+++ b/src/main/java/com/github/fppt/jedismock/commands/RO_getset.java
@@ -12,8 +12,8 @@ class RO_getset extends AbstractRedisOperation {
     }
 
     Slice response() {
-        Slice value = base().rawGet(params().get(0));
-        base().rawPut(params().get(0), params().get(1), -1L);
+        Slice value = base().getValue(params().get(0));
+        base().putValue(params().get(0), params().get(1));
         return Response.bulkString(value);
     }
 }
diff --git a/src/main/java/com/github/fppt/jedismock/commands/RO_hdel.java b/src/main/java/com/github/fppt/jedismock/commands/RO_hdel.java
new file mode 100644
index 0000000..a25b903
--- /dev/null
+++ b/src/main/java/com/github/fppt/jedismock/commands/RO_hdel.java
@@ -0,0 +1,28 @@
+package com.github.fppt.jedismock.commands;
+
+import com.github.fppt.jedismock.RedisBase;
+import com.github.fppt.jedismock.Response;
+import com.github.fppt.jedismock.Slice;
+
+import java.util.List;
+
+class RO_hdel extends AbstractRedisOperation {
+    RO_hdel(RedisBase base, List<Slice> params) {
+        super(base, params,2, null, null);
+    }
+
+    Slice response(){
+        Slice key1 = params().get(0);
+        Slice key2 = params().get(1);
+
+        Slice oldValue = base().getValue(key1, key2);
+
+        base().deleteValue(key1, key2);
+
+        if(oldValue == null){
+            return Response.integer(0);
+        } else {
+            return Response.integer(1);
+        }
+    }
+}
diff --git a/src/main/java/com/github/fppt/jedismock/commands/RO_hget.java b/src/main/java/com/github/fppt/jedismock/commands/RO_hget.java
new file mode 100644
index 0000000..53d6293
--- /dev/null
+++ b/src/main/java/com/github/fppt/jedismock/commands/RO_hget.java
@@ -0,0 +1,17 @@
+package com.github.fppt.jedismock.commands;
+
+import com.github.fppt.jedismock.RedisBase;
+import com.github.fppt.jedismock.Response;
+import com.github.fppt.jedismock.Slice;
+
+import java.util.List;
+
+class RO_hget extends AbstractRedisOperation {
+    RO_hget(RedisBase base, List<Slice> params) {
+        super(base, params, 2, null, null);
+    }
+
+    Slice response() {
+        return Response.bulkString(base().getValue(params().get(0), params().get(1)));
+    }
+}
diff --git a/src/main/java/com/github/fppt/jedismock/commands/RO_hset.java b/src/main/java/com/github/fppt/jedismock/commands/RO_hset.java
new file mode 100644
index 0000000..a4e0b67
--- /dev/null
+++ b/src/main/java/com/github/fppt/jedismock/commands/RO_hset.java
@@ -0,0 +1,32 @@
+package com.github.fppt.jedismock.commands;
+
+import com.github.fppt.jedismock.RedisBase;
+import com.github.fppt.jedismock.Response;
+import com.github.fppt.jedismock.Slice;
+
+import java.util.List;
+
+class RO_hset extends AbstractRedisOperation {
+    RO_hset(RedisBase base, List<Slice> params) {
+        super(base, params, 3, null, null);
+    }
+
+    public RO_hset(RedisBase base, List<Slice> params, Integer expectedParams) {
+        super(base, params, expectedParams, null,null);
+    }
+
+    Slice response() {
+        Slice key1 = params().get(0);
+        Slice key2 = params().get(1);
+        Slice value = params().get(2);
+
+        Slice oldValue = base().getValue(key1, key2);
+        base().putValue(key1, key2, value, -1L);
+
+        if(oldValue == null){
+            return Response.integer(1);
+        } else {
+            return Response.integer(0);
+        }
+    }
+}
diff --git a/src/main/java/com/github/fppt/jedismock/commands/RO_incrOrDecrBy.java b/src/main/java/com/github/fppt/jedismock/commands/RO_incrOrDecrBy.java
index 2712244..64f4bb2 100644
--- a/src/main/java/com/github/fppt/jedismock/commands/RO_incrOrDecrBy.java
+++ b/src/main/java/com/github/fppt/jedismock/commands/RO_incrOrDecrBy.java
@@ -18,14 +18,14 @@ abstract class RO_incrOrDecrBy extends AbstractRedisOperation {
     Slice response() {
         Slice key = params().get(0);
         long d = incrementOrDecrementValue(params());
-        Slice v = base().rawGet(key);
+        Slice v = base().getValue(key);
         if (v == null) {
-            base().rawPut(key, new Slice(String.valueOf(d)), -1L);
+            base().putValue(key, Slice.create(String.valueOf(d)));
             return Response.integer(d);
         }
 
         long r = convertToLong(new String(v.data())) + d;
-        base().rawPut(key, new Slice(String.valueOf(r)), -1L);
+        base().putValue(key, Slice.create(String.valueOf(r)));
         return Response.integer(r);
     }
 }
diff --git a/src/main/java/com/github/fppt/jedismock/commands/RO_lindex.java b/src/main/java/com/github/fppt/jedismock/commands/RO_lindex.java
index 7600524..5808246 100644
--- a/src/main/java/com/github/fppt/jedismock/commands/RO_lindex.java
+++ b/src/main/java/com/github/fppt/jedismock/commands/RO_lindex.java
@@ -17,7 +17,7 @@ class RO_lindex extends AbstractRedisOperation {
 
     Slice response() {
         Slice key = params().get(0);
-        Slice data = base().rawGet(key);
+        Slice data = base().getValue(key);
         LinkedList<Slice> list;
         if (data != null) {
             list = deserializeObject(data);
diff --git a/src/main/java/com/github/fppt/jedismock/commands/RO_llen.java b/src/main/java/com/github/fppt/jedismock/commands/RO_llen.java
index 25e0454..86a73cc 100644
--- a/src/main/java/com/github/fppt/jedismock/commands/RO_llen.java
+++ b/src/main/java/com/github/fppt/jedismock/commands/RO_llen.java
@@ -17,7 +17,7 @@ class RO_llen extends AbstractRedisOperation {
 
     Slice response() {
         Slice key = params().get(0);
-        Slice data = base().rawGet(key);
+        Slice data = base().getValue(key);
         LinkedList<Slice> list;
         if (data != null) {
             list = deserializeObject(data);
diff --git a/src/main/java/com/github/fppt/jedismock/commands/RO_lpushx.java b/src/main/java/com/github/fppt/jedismock/commands/RO_lpushx.java
index 79ecb5d..42f7f98 100644
--- a/src/main/java/com/github/fppt/jedismock/commands/RO_lpushx.java
+++ b/src/main/java/com/github/fppt/jedismock/commands/RO_lpushx.java
@@ -13,7 +13,7 @@ class RO_lpushx extends RO_lpush {
 
     Slice response(){
         Slice key = params().get(0);
-        Slice data = base().rawGet(key);
+        Slice data = base().getValue(key);
 
         if(data != null){
             return super.response();
diff --git a/src/main/java/com/github/fppt/jedismock/commands/RO_lrange.java b/src/main/java/com/github/fppt/jedismock/commands/RO_lrange.java
index 3dd4461..e0ad3ed 100644
--- a/src/main/java/com/github/fppt/jedismock/commands/RO_lrange.java
+++ b/src/main/java/com/github/fppt/jedismock/commands/RO_lrange.java
@@ -19,7 +19,7 @@ class RO_lrange extends AbstractRedisOperation {
 
     Slice response() {
         Slice key = params().get(0);
-        Slice data = base().rawGet(key);
+        Slice data = base().getValue(key);
         LinkedList<Slice> list;
         if (data != null) {
             list = deserializeObject(data);
diff --git a/src/main/java/com/github/fppt/jedismock/commands/RO_lrem.java b/src/main/java/com/github/fppt/jedismock/commands/RO_lrem.java
index 2766094..a762449 100644
--- a/src/main/java/com/github/fppt/jedismock/commands/RO_lrem.java
+++ b/src/main/java/com/github/fppt/jedismock/commands/RO_lrem.java
@@ -21,7 +21,7 @@ class RO_lrem extends AbstractRedisOperation {
         Slice key = params().get(0);
         int numRemove = convertToInteger(new String(params().get(1).data()));
         Slice target = params().get(2);
-        Slice data = base().rawGet(key);
+        Slice data = base().getValue(key);
 
         if(data == null){
             return Response.integer(0);
@@ -48,7 +48,7 @@ class RO_lrem extends AbstractRedisOperation {
             }
         }
 
-        base().rawPut(key, serializeObject(list), -1L);
+        base().putValue(key, serializeObject(list));
 
         return Response.integer(numRemoved);
     }
diff --git a/src/main/java/com/github/fppt/jedismock/commands/RO_mget.java b/src/main/java/com/github/fppt/jedismock/commands/RO_mget.java
index 5b8707d..3f7be6a 100644
--- a/src/main/java/com/github/fppt/jedismock/commands/RO_mget.java
+++ b/src/main/java/com/github/fppt/jedismock/commands/RO_mget.java
@@ -15,7 +15,7 @@ class RO_mget extends AbstractRedisOperation {
     Slice response(){
         ImmutableList.Builder<Slice> builder = new ImmutableList.Builder<Slice>();
         for (Slice key : params()) {
-            builder.add(Response.bulkString(base().rawGet(key)));
+            builder.add(Response.bulkString(base().getValue(key)));
 
         }
         return Response.array(builder.build());
diff --git a/src/main/java/com/github/fppt/jedismock/commands/RO_mset.java b/src/main/java/com/github/fppt/jedismock/commands/RO_mset.java
index 8cf5da4..3b40383 100644
--- a/src/main/java/com/github/fppt/jedismock/commands/RO_mset.java
+++ b/src/main/java/com/github/fppt/jedismock/commands/RO_mset.java
@@ -13,7 +13,7 @@ class RO_mset extends AbstractRedisOperation {
 
     Slice response() {
         for (int i = 0; i < params().size(); i += 2) {
-            base().rawPut(params().get(i), params().get(i + 1), -1L);
+            base().putValue(params().get(i), params().get(i + 1));
         }
         return Response.OK;
     }
diff --git a/src/main/java/com/github/fppt/jedismock/commands/RO_pfadd.java b/src/main/java/com/github/fppt/jedismock/commands/RO_pfadd.java
index 510a45f..1faa1c8 100644
--- a/src/main/java/com/github/fppt/jedismock/commands/RO_pfadd.java
+++ b/src/main/java/com/github/fppt/jedismock/commands/RO_pfadd.java
@@ -18,7 +18,7 @@ class RO_pfadd extends AbstractRedisOperation {
 
     Slice response(){
         Slice key = params().get(0);
-        Slice data = base().rawGet(key);
+        Slice data = base().getValue(key);
         boolean first;
 
         Set<Slice> set;
@@ -39,9 +39,9 @@ class RO_pfadd extends AbstractRedisOperation {
 
         Slice out = serializeObject(set);
         if (first) {
-            base().rawPut(key, out, -1L);
+            base().putValue(key, out);
         } else {
-            base().rawPut(key, out, null);
+            base().putValue(key, out, null);
         }
 
         if (prev != set.size()) {
diff --git a/src/main/java/com/github/fppt/jedismock/commands/RO_pfcount.java b/src/main/java/com/github/fppt/jedismock/commands/RO_pfcount.java
index 87ec4af..f6f6e5b 100644
--- a/src/main/java/com/github/fppt/jedismock/commands/RO_pfcount.java
+++ b/src/main/java/com/github/fppt/jedismock/commands/RO_pfcount.java
@@ -18,7 +18,7 @@ class RO_pfcount extends AbstractRedisOperation {
     Slice response() {
         Set<Slice> set = Sets.newHashSet();
         for (Slice key : params()) {
-            Slice data = base().rawGet(key);
+            Slice data = base().getValue(key);
             if (data == null) {
                 continue;
             }
diff --git a/src/main/java/com/github/fppt/jedismock/commands/RO_pfmerge.java b/src/main/java/com/github/fppt/jedismock/commands/RO_pfmerge.java
index f32d91d..a8d9926 100644
--- a/src/main/java/com/github/fppt/jedismock/commands/RO_pfmerge.java
+++ b/src/main/java/com/github/fppt/jedismock/commands/RO_pfmerge.java
@@ -18,7 +18,7 @@ class RO_pfmerge extends AbstractRedisOperation {
 
     Slice response() {
         Slice key = params().get(0);
-        Slice data = base().rawGet(key);
+        Slice data = base().getValue(key);
         boolean first;
 
         Set<Slice> set;
@@ -30,7 +30,7 @@ class RO_pfmerge extends AbstractRedisOperation {
             first = false;
         }
         for (Slice v : params().subList(1, params().size())) {
-            Slice src = base().rawGet(v);
+            Slice src = base().getValue(v);
             if (src != null) {
                 Set<Slice> s = deserializeObject(src);
                 set.addAll(s);
@@ -39,9 +39,9 @@ class RO_pfmerge extends AbstractRedisOperation {
 
         Slice out = serializeObject(set);
         if (first) {
-            base().rawPut(key, out, -1L);
+            base().putValue(key, out);
         } else {
-            base().rawPut(key, out, null);
+            base().putValue(key, out, null);
         }
         return Response.OK;
     }
diff --git a/src/main/java/com/github/fppt/jedismock/commands/RO_ping.java b/src/main/java/com/github/fppt/jedismock/commands/RO_ping.java
index 0602d2f..001f21c 100644
--- a/src/main/java/com/github/fppt/jedismock/commands/RO_ping.java
+++ b/src/main/java/com/github/fppt/jedismock/commands/RO_ping.java
@@ -13,7 +13,7 @@ class RO_ping extends AbstractRedisOperation {
 
     Slice response() {
         if (params().isEmpty()){
-            return Response.bulkString(new Slice("PONG"));
+            return Response.bulkString(Slice.create("PONG"));
         }
 
         return Response.bulkString(params().get(0));
diff --git a/src/main/java/com/github/fppt/jedismock/commands/RO_pop.java b/src/main/java/com/github/fppt/jedismock/commands/RO_pop.java
index 90aa48e..662f830 100644
--- a/src/main/java/com/github/fppt/jedismock/commands/RO_pop.java
+++ b/src/main/java/com/github/fppt/jedismock/commands/RO_pop.java
@@ -19,7 +19,7 @@ abstract class RO_pop extends AbstractRedisOperation {
 
     Slice response() {
         Slice key = params().get(0);
-        Slice data = base().rawGet(key);
+        Slice data = base().getValue(key);
         LinkedList<Slice> list;
         if (data != null) {
             list = deserializeObject(data);
@@ -31,7 +31,7 @@ abstract class RO_pop extends AbstractRedisOperation {
             return Response.NULL;
         }
         Slice v = popper(list);
-        base().rawPut(key, serializeObject(list), -1L);
+        base().putValue(key, serializeObject(list));
         return Response.bulkString(v);
     }
 }
diff --git a/src/main/java/com/github/fppt/jedismock/commands/RO_push.java b/src/main/java/com/github/fppt/jedismock/commands/RO_push.java
index 566bdc4..7d9b295 100644
--- a/src/main/java/com/github/fppt/jedismock/commands/RO_push.java
+++ b/src/main/java/com/github/fppt/jedismock/commands/RO_push.java
@@ -3,7 +3,6 @@ package com.github.fppt.jedismock.commands;
 import com.github.fppt.jedismock.RedisBase;
 import com.github.fppt.jedismock.Response;
 import com.github.fppt.jedismock.Slice;
-import com.github.fppt.jedismock.exception.InternalException;
 import com.google.common.collect.Lists;
 
 import java.util.LinkedList;
@@ -21,7 +20,7 @@ abstract class RO_push extends AbstractRedisOperation {
 
     Slice response() {
         Slice key = params().get(0);
-        Slice data = base().rawGet(key);
+        Slice data = base().getValue(key);
         LinkedList<Slice> list;
 
         if (data != null) {
@@ -34,9 +33,9 @@ abstract class RO_push extends AbstractRedisOperation {
             pusher(list, params().get(i));
         }
         try {
-            base().rawPut(key, serializeObject(list), -1L);
+            base().putValue(key, serializeObject(list));
         } catch (Exception e) {
-            throw new InternalException(e.getMessage());
+            throw new RuntimeException(e.getMessage());
         }
         return Response.integer(list.size());
     }
diff --git a/src/main/java/com/github/fppt/jedismock/commands/RO_sadd.java b/src/main/java/com/github/fppt/jedismock/commands/RO_sadd.java
index e2a87c7..7e8f60c 100644
--- a/src/main/java/com/github/fppt/jedismock/commands/RO_sadd.java
+++ b/src/main/java/com/github/fppt/jedismock/commands/RO_sadd.java
@@ -3,7 +3,6 @@ package com.github.fppt.jedismock.commands;
 import com.github.fppt.jedismock.RedisBase;
 import com.github.fppt.jedismock.Response;
 import com.github.fppt.jedismock.Slice;
-import com.github.fppt.jedismock.exception.InternalException;
 
 import java.util.HashSet;
 import java.util.List;
@@ -19,7 +18,7 @@ class RO_sadd extends AbstractRedisOperation {
 
     Slice response() {
         Slice key = params().get(0);
-        Slice data = base().rawGet(key);
+        Slice data = base().getValue(key);
         Set<Slice> set;
 
         if (data != null) {
@@ -32,9 +31,9 @@ class RO_sadd extends AbstractRedisOperation {
             set.add(params().get(i));
         }
         try {
-            base().rawPut(key, serializeObject(set), -1L);
+            base().putValue(key, serializeObject(set));
         } catch (Exception e) {
-            throw new InternalException(e.getMessage());
+            throw new RuntimeException(e.getMessage());
         }
         return Response.integer(set.size());
     }
diff --git a/src/main/java/com/github/fppt/jedismock/commands/RO_set.java b/src/main/java/com/github/fppt/jedismock/commands/RO_set.java
index a7a50d6..5ae8aff 100644
--- a/src/main/java/com/github/fppt/jedismock/commands/RO_set.java
+++ b/src/main/java/com/github/fppt/jedismock/commands/RO_set.java
@@ -11,16 +11,12 @@ class RO_set extends AbstractRedisOperation {
         super(base, params, 2, null, null);
     }
 
-    public RO_set(RedisBase base, List<Slice> params, Integer i) {
-        super(base, params, i, null,null);
-    }
-
-    long valueToSet(List<Slice> params){
-        return -1L;
+    public RO_set(RedisBase base, List<Slice> params, Integer expectedParams) {
+        super(base, params, expectedParams, null,null);
     }
 
     Slice response() {
-        base().rawPut(params().get(0), params().get(1), valueToSet(params()));
+        base().putValue(params().get(0), params().get(1));
         return Response.OK;
     }
 }
diff --git a/src/main/java/com/github/fppt/jedismock/commands/RO_setbit.java b/src/main/java/com/github/fppt/jedismock/commands/RO_setbit.java
index 3ab1519..32c9c57 100644
--- a/src/main/java/com/github/fppt/jedismock/commands/RO_setbit.java
+++ b/src/main/java/com/github/fppt/jedismock/commands/RO_setbit.java
@@ -16,7 +16,7 @@ class RO_setbit extends AbstractRedisOperation {
     }
 
     Slice response() {
-        Slice value = base().rawGet(params().get(0));
+        Slice value = base().getValue(params().get(0));
 
         byte bit = convertToByte(params().get(2).toString());
         int pos = convertToNonNegativeInteger(params().get(1).toString());
@@ -25,7 +25,7 @@ class RO_setbit extends AbstractRedisOperation {
             byte[] data = new byte[pos / 8 + 1];
             Arrays.fill(data, (byte) 0);
             data[pos / 8] = (byte) (bit << (pos % 8));
-            base().rawPut(params().get(0), new Slice(data), -1L);
+            base().putValue(params().get(0), Slice.create(data));
             return Response.integer(0L);
         }
 
@@ -38,7 +38,7 @@ class RO_setbit extends AbstractRedisOperation {
             }
             data[pos / 8] = (byte) (bit << (pos % 8));
             original = 0;
-            base().rawPut(params().get(0), new Slice(data), -1L);
+            base().putValue(params().get(0), Slice.create(data));
         } else {
             byte[] data = value.data();
             if ((data[pos / 8] & (1 << (pos % 8))) != 0) {
@@ -48,7 +48,7 @@ class RO_setbit extends AbstractRedisOperation {
             }
             data[pos / 8] |= (byte) (1 << (pos % 8));
             data[pos / 8] &= (byte) (bit << (pos % 8));
-            base().rawPut(params().get(0), new Slice(data), -1L);
+            base().putValue(params().get(0), Slice.create(data));
         }
         return Response.integer(original);
     }
diff --git a/src/main/java/com/github/fppt/jedismock/commands/RO_setex.java b/src/main/java/com/github/fppt/jedismock/commands/RO_setex.java
index 2743953..7f6b28e 100644
--- a/src/main/java/com/github/fppt/jedismock/commands/RO_setex.java
+++ b/src/main/java/com/github/fppt/jedismock/commands/RO_setex.java
@@ -17,13 +17,12 @@ class RO_setex extends RO_set {
         super(base, params, expectedParams);
     }
 
-    @Override
     long valueToSet(List<Slice> params){
         return convertToLong(new String(params.get(1).data())) * 1000;
     }
 
     Slice response() {
-        base().rawPut(params().get(0), params().get(2), valueToSet(params()));
+        base().putValue(params().get(0), params().get(2), valueToSet(params()));
         return Response.OK;
     }
 }
diff --git a/src/main/java/com/github/fppt/jedismock/commands/RO_setnx.java b/src/main/java/com/github/fppt/jedismock/commands/RO_setnx.java
index 23f25e2..330175e 100644
--- a/src/main/java/com/github/fppt/jedismock/commands/RO_setnx.java
+++ b/src/main/java/com/github/fppt/jedismock/commands/RO_setnx.java
@@ -12,8 +12,8 @@ class RO_setnx extends AbstractRedisOperation {
     }
 
     Slice response(){
-        if (base().rawGet(params().get(0)) == null) {
-            base().rawPut(params().get(0), params().get(1), -1L);
+        if (base().getValue(params().get(0)) == null) {
+            base().putValue(params().get(0), params().get(1));
             return Response.integer(1);
         }
         return Response.integer(0);
diff --git a/src/main/java/com/github/fppt/jedismock/commands/RO_smembers.java b/src/main/java/com/github/fppt/jedismock/commands/RO_smembers.java
index 851803e..eb935fb 100644
--- a/src/main/java/com/github/fppt/jedismock/commands/RO_smembers.java
+++ b/src/main/java/com/github/fppt/jedismock/commands/RO_smembers.java
@@ -18,7 +18,7 @@ class RO_smembers extends AbstractRedisOperation {
 
     Slice response() {
         Slice key = params().get(0);
-        Slice data = base().rawGet(key);
+        Slice data = base().getValue(key);
         //Has to be a list because Jedis can only deserialize lists
         LinkedList<Slice> set;
         if (data != null) {
diff --git a/src/main/java/com/github/fppt/jedismock/commands/RO_spop.java b/src/main/java/com/github/fppt/jedismock/commands/RO_spop.java
index 43c0e06..485b6af 100644
--- a/src/main/java/com/github/fppt/jedismock/commands/RO_spop.java
+++ b/src/main/java/com/github/fppt/jedismock/commands/RO_spop.java
@@ -18,7 +18,7 @@ class RO_spop extends AbstractRedisOperation {
 
     Slice response() {
         Slice key = params().get(0);
-        Slice data = base().rawGet(key);
+        Slice data = base().getValue(key);
         Set<Slice> set;
         if (data != null) {
             set = deserializeObject(data);
@@ -32,7 +32,7 @@ class RO_spop extends AbstractRedisOperation {
         Iterator<Slice> it = set.iterator();
         Slice v = it.next();
         it.remove();
-        base().rawPut(key, serializeObject(set), -1L);
+        base().putValue(key, serializeObject(set));
         return Response.bulkString(v);
     }
 }
diff --git a/src/main/java/com/github/fppt/jedismock/commands/RO_strlen.java b/src/main/java/com/github/fppt/jedismock/commands/RO_strlen.java
index b04d16a..e2910da 100644
--- a/src/main/java/com/github/fppt/jedismock/commands/RO_strlen.java
+++ b/src/main/java/com/github/fppt/jedismock/commands/RO_strlen.java
@@ -12,7 +12,7 @@ class RO_strlen extends AbstractRedisOperation {
     }
 
     Slice response() {
-        Slice value = base().rawGet(params().get(0));
+        Slice value = base().getValue(params().get(0));
         if (value == null) {
             return Response.integer(0);
         }
diff --git a/src/main/java/com/github/fppt/jedismock/commands/RedisOperationExecutor.java b/src/main/java/com/github/fppt/jedismock/commands/RedisOperationExecutor.java
index 8fecbd1..e5b56cd 100644
--- a/src/main/java/com/github/fppt/jedismock/commands/RedisOperationExecutor.java
+++ b/src/main/java/com/github/fppt/jedismock/commands/RedisOperationExecutor.java
@@ -5,7 +5,6 @@ import com.github.fppt.jedismock.RedisClient;
 import com.github.fppt.jedismock.RedisCommand;
 import com.github.fppt.jedismock.Response;
 import com.github.fppt.jedismock.Slice;
-import com.github.fppt.jedismock.exception.WrongNumberOfArgumentsException;
 import com.github.fppt.jedismock.exception.WrongValueTypeException;
 import com.google.common.base.Preconditions;
 import org.slf4j.LoggerFactory;
@@ -131,15 +130,20 @@ public class RedisOperationExecutor {
                 return new RO_smembers(base, params);
             case "spop":
                 return new RO_spop(base, params);
+            case "hget":
+                return new RO_hget(base, params);
+            case "hset":
+                return new RO_hset(base, params);
+            case "hdel":
+                return new RO_hdel(base, params);
             default:
                 throw new UnsupportedOperationException(String.format("Unsupported operation '%s'", name));
         }
     }
 
     public synchronized Slice execCommand(RedisCommand command) {
-        Preconditions.checkArgument(command.getParameters().size() > 0);
-
-        List<Slice> params = command.getParameters();
+        Preconditions.checkArgument(command.parameters().size() > 0);
+        List<Slice> params = command.parameters();
         List<Slice> commandParams = params.subList(1, params.size());
         String name = new String(params.get(0).data()).toLowerCase();
 
@@ -159,12 +163,9 @@ public class RedisOperationExecutor {
             }
 
             return Response.clientResponse(name, Response.OK);
-        } catch(UnsupportedOperationException | WrongValueTypeException e){
+        } catch(UnsupportedOperationException | WrongValueTypeException | IllegalArgumentException e){
             LOG.error("Malformed request", e);
             return Response.error(e.getMessage());
-        } catch (WrongNumberOfArgumentsException e){
-            LOG.error("Malformed request", e);
-            return Response.error(String.format("ERR wrong number of arguments for '%s' command", name));
         }
     }
 
diff --git a/src/main/java/com/github/fppt/jedismock/exception/InternalException.java b/src/main/java/com/github/fppt/jedismock/exception/InternalException.java
deleted file mode 100644
index 472b367..0000000
--- a/src/main/java/com/github/fppt/jedismock/exception/InternalException.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.github.fppt.jedismock.exception;
-
-/**
- * Created by Xiaolu on 2015/4/22.
- */
-public class InternalException extends RuntimeException {
-
-    public InternalException() {
-        super();
-    }
-
-    public InternalException(String message) {
-        super(message);
-    }
-}
diff --git a/src/main/java/com/github/fppt/jedismock/exception/WrongNumberOfArgumentsException.java b/src/main/java/com/github/fppt/jedismock/exception/WrongNumberOfArgumentsException.java
deleted file mode 100644
index be180eb..0000000
--- a/src/main/java/com/github/fppt/jedismock/exception/WrongNumberOfArgumentsException.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package com.github.fppt.jedismock.exception;
-
-/**
- * Created by Xiaolu on 2015/4/21.
- */
-public class WrongNumberOfArgumentsException extends RuntimeException {
-
-    public WrongNumberOfArgumentsException() {
-        super();
-    }
-}
diff --git a/src/main/java/com/github/fppt/jedismock/exception/WrongValueTypeException.java b/src/main/java/com/github/fppt/jedismock/exception/WrongValueTypeException.java
index 7ea64f7..6c4c662 100644
--- a/src/main/java/com/github/fppt/jedismock/exception/WrongValueTypeException.java
+++ b/src/main/java/com/github/fppt/jedismock/exception/WrongValueTypeException.java
@@ -5,10 +5,6 @@ package com.github.fppt.jedismock.exception;
  */
 public class WrongValueTypeException extends RuntimeException {
 
-    public WrongValueTypeException() {
-        super();
-    }
-
     public WrongValueTypeException(String message) {
         super(message);
     }
diff --git a/src/main/java/com/github/fppt/jedismock/storage/ExpiringKeyValueStorage.java b/src/main/java/com/github/fppt/jedismock/storage/ExpiringKeyValueStorage.java
new file mode 100644
index 0000000..2cbd721
--- /dev/null
+++ b/src/main/java/com/github/fppt/jedismock/storage/ExpiringKeyValueStorage.java
@@ -0,0 +1,118 @@
+package com.github.fppt.jedismock.storage;
+
+import com.github.fppt.jedismock.Slice;
+import com.google.auto.value.AutoValue;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.HashBasedTable;
+import com.google.common.collect.Table;
+
+
+/**
+ * Used to represent an expiring storage layer.
+ */
+@AutoValue
+public abstract class ExpiringKeyValueStorage {
+    public abstract Table<Slice, Slice, Slice> values();
+    public abstract Table<Slice, Slice, Long> ttls();
+
+    public static ExpiringKeyValueStorage create(){
+        return new AutoValue_ExpiringKeyValueStorage(HashBasedTable.create(), HashBasedTable.create());
+    }
+
+    public void delete(Slice key){
+        delete(key, Slice.reserved());
+    }
+
+    public void delete(Slice key1, Slice key2){
+        Preconditions.checkNotNull(key1);
+        Preconditions.checkNotNull(key2);
+        values().remove(key1, key2);
+        ttls().remove(key1, key2);
+    }
+
+    public void clear(){
+        values().clear();
+        ttls().clear();
+    }
+
+    public Slice get(Slice key){
+        return get(key, Slice.reserved());
+    }
+
+    public Slice get(Slice key1, Slice key2){
+        Preconditions.checkNotNull(key1);
+        Preconditions.checkNotNull(key2);
+
+        Long deadline = ttls().get(key1, key2);
+        if (deadline != null && deadline != -1 && deadline <= System.currentTimeMillis()) {
+            delete(key1, key2);
+            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);
+
+        Long deadline = ttls().get(key1, key2);
+        if (deadline == null) {
+            return null;
+        }
+        if (deadline == -1) {
+            return deadline;
+        }
+        long now = System.currentTimeMillis();
+        if (now < deadline) {
+            return deadline - now;
+        }
+        delete(key1, key1);
+        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());
+    }
+
+    public void put(Slice key, Slice value, Long ttl){
+        put(key, Slice.reserved(), value, ttl);
+    }
+
+    public void put(Slice key1, Slice key2, Slice value, Long ttl){
+        Preconditions.checkNotNull(key1);
+        Preconditions.checkNotNull(key2);
+        Preconditions.checkNotNull(value);
+
+        values().put(key1, key2, value);
+        if (ttl != null) {
+            if (ttl != -1) {
+                setTTL(key1, key2, ttl);
+            } else {
+                setDeadline(key1, key2, -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);
+
+        if (values().contains(key1, key2)) {
+            ttls().put(key1, key2, deadline);
+            return 1L;
+        }
+        return 0L;
+    }
+}


Test patch:
diff --git a/src/test/java/com/github/fppt/jedismock/TestCommandParser.java b/src/test/java/com/github/fppt/jedismock/TestCommandParser.java
index b95959c..ee96fa9 100644
--- a/src/test/java/com/github/fppt/jedismock/TestCommandParser.java
+++ b/src/test/java/com/github/fppt/jedismock/TestCommandParser.java
@@ -67,9 +67,9 @@ public class TestCommandParser {
     @Test
     public void testParse() throws ParseErrorException, EOFException {
         RedisCommand cmd = RedisCommandParser.parse("*3\r\n$0\r\n\r\n$4\r\nabcd\r\n$2\r\nef\r\n");
-        assertEquals(cmd.getParameters().get(0).toString(), "");
-        assertEquals(cmd.getParameters().get(1).toString(), "abcd");
-        assertEquals(cmd.getParameters().get(2).toString(), "ef");
+        assertEquals(cmd.parameters().get(0).toString(), "");
+        assertEquals(cmd.parameters().get(1).toString(), "abcd");
+        assertEquals(cmd.parameters().get(2).toString(), "ef");
     }
 
     @Test
diff --git a/src/test/java/com/github/fppt/jedismock/TestRedisOperationExecutor.java b/src/test/java/com/github/fppt/jedismock/TestRedisOperationExecutor.java
index a0a8c49..c565882 100644
--- a/src/test/java/com/github/fppt/jedismock/TestRedisOperationExecutor.java
+++ b/src/test/java/com/github/fppt/jedismock/TestRedisOperationExecutor.java
@@ -183,10 +183,10 @@ public class TestRedisOperationExecutor {
         assertCommandEquals(1, array("getbit", "mykey", "7"));
         assertCommandEquals(0, array("getbit", "mykey", "6"));
         assertCommandEquals(1, array("setbit", "mykey", "7", "0"));
-        assertEquals(Response.bulkString(new Slice(new byte[]{0})),
+        assertEquals(Response.bulkString(Slice.create(new byte[]{0})),
                 executor.execCommand(RedisCommandParser.parse(array("get", "mykey"))));
         assertCommandEquals(0, array("setbit", "mykey", "33", "1"));
-        assertEquals(Response.bulkString(new Slice(new byte[]{0, 0, 0, 0, 2})),
+        assertEquals(Response.bulkString(Slice.create(new byte[]{0, 0, 0, 0, 2})),
                 executor.execCommand(RedisCommandParser.parse(array("get", "mykey"))));
         assertCommandEquals(0, array("setbit", "mykey", "22", "1"));
         assertCommandEquals(0, array("getbit", "mykey", "117"));
diff --git a/src/test/java/com/github/fppt/jedismock/TestRedisServer.java b/src/test/java/com/github/fppt/jedismock/TestRedisServer.java
index f882506..e5e83f5 100644
--- a/src/test/java/com/github/fppt/jedismock/TestRedisServer.java
+++ b/src/test/java/com/github/fppt/jedismock/TestRedisServer.java
@@ -24,14 +24,14 @@ public class TestRedisServer {
     }
 
     @Test
-    public void testBindRandomPort() throws IOException, InterruptedException {
+    public void testBindRandomPort() throws IOException {
         RedisServer server = RedisServer.newRedisServer();
         server.start();
         server.stop();
     }
 
     @Test
-    public void testBindErrorPort() throws IOException, InterruptedException {
+    public void testBindErrorPort() throws IOException {
         RedisServer server = RedisServer.newRedisServer(100000);
         try {
             server.start();
@@ -42,7 +42,7 @@ public class TestRedisServer {
     }
 
     @Test
-    public void testBindUsedPort() throws IOException, InterruptedException {
+    public void testBindUsedPort() throws IOException {
         RedisServer server1 = RedisServer.newRedisServer();
         server1.start();
         RedisServer server2 = RedisServer.newRedisServer(server1.getBindPort());
@@ -130,8 +130,7 @@ public class TestRedisServer {
     @Test
     public void testCloseSocket() throws IOException {
         RedisServer server = RedisServer.newRedisServer();
-        ServiceOptions options = new ServiceOptions();
-        options.setCloseSocketAfterSeveralCommands(3);
+        ServiceOptions options = ServiceOptions.create(3);
         server.setOptions(options);
         server.start();
         Jedis jedis = new Jedis(server.getHost(), server.getBindPort());
diff --git a/src/test/java/com/github/fppt/jedismock/TestUtils.java b/src/test/java/com/github/fppt/jedismock/TestUtils.java
index 7f1c8de..33920ad 100644
--- a/src/test/java/com/github/fppt/jedismock/TestUtils.java
+++ b/src/test/java/com/github/fppt/jedismock/TestUtils.java
@@ -1,27 +1,31 @@
 package com.github.fppt.jedismock;
 
-import com.github.fppt.jedismock.exception.WrongNumberOfArgumentsException;
 import com.google.common.collect.Lists;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.ExpectedException;
 
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.List;
+import java.util.function.BiConsumer;
 
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.*;
 
 /**
  * Created by Xiaolu on 2015/4/20.
  */
 public class TestUtils {
 
+    @Rule
+    public final ExpectedException exception = ExpectedException.none();
+
     @Test
-    public void testCloseQuietly() throws IOException {
+    public void testCloseQuietly() {
         Utils.closeQuietly(null);
         Utils.closeQuietly(new InputStream() {
             @Override
-            public int read() throws IOException {
+            public int read() {
                 return 0;
             }
 
@@ -32,52 +36,33 @@ public class TestUtils {
         });
     }
 
+    private void assertArgumentCheckThrowsException(BiConsumer<List<Slice>, Integer> check, List<Slice> args, int passValue, int failValue){
+        check.accept(args, passValue);
+        exception.expect(IllegalArgumentException.class);
+        check.accept(args, failValue);
+    }
+
     @Test
-    public void testCheckArgumentsNumberEquals() throws WrongNumberOfArgumentsException {
-        List<Slice> args = Lists.newArrayList(new Slice(""), new Slice(""));
-        Utils.checkArgumentsNumberEquals(args, 2);
-        try {
-            Utils.checkArgumentsNumberEquals(args, 1);
-            assertTrue(false);
-        } catch (WrongNumberOfArgumentsException e) {
-            // ok
-        }
-        try {
-            Utils.checkArgumentsNumberEquals(args, 3);
-            assertTrue(false);
-        } catch (WrongNumberOfArgumentsException e) {
-            // ok
-        }
+    public void testCheckArgumentsNumberEquals() {
+        List<Slice> args = Lists.newArrayList(Slice.create(""), Slice.create(""));
+        assertArgumentCheckThrowsException(Utils::checkArgumentsNumberEquals, args, 2, 1);
     }
 
     @Test
-    public void testCheckArgumentsNumberGreater() throws WrongNumberOfArgumentsException {
-        List<Slice> args = Lists.newArrayList(new Slice(""), new Slice(""));
-        Utils.checkArgumentsNumberGreater(args, 1);
-        try {
-            Utils.checkArgumentsNumberGreater(args, 2);
-            assertTrue(false);
-        } catch (WrongNumberOfArgumentsException e) {
-            // ok
-        }
+    public void testCheckArgumentsNumberGreater() {
+        List<Slice> args = Lists.newArrayList(Slice.create(""), Slice.create(""));
+        assertArgumentCheckThrowsException(Utils::checkArgumentsNumberGreater, args, 1, 2);
     }
 
     @Test
-    public void testCheckArgumentsNumberFactor() throws WrongNumberOfArgumentsException {
-        List<Slice> args = Lists.newArrayList(new Slice(""), new Slice(""), new Slice(""));
-        Utils.checkArgumentsNumberFactor(args, 1);
-        Utils.checkArgumentsNumberFactor(args, 3);
-        try {
-            Utils.checkArgumentsNumberFactor(args, 2);
-            assertTrue(false);
-        } catch (WrongNumberOfArgumentsException e) {
-            // ok
-        }
+    public void testCheckArgumentsNumberFactor() {
+        List<Slice> args = Lists.newArrayList(Slice.create(""), Slice.create(""), Slice.create(""));
+        assertArgumentCheckThrowsException(Utils::checkArgumentsNumberFactor, args, 1, 2);
     }
 
     @Test
-    public void testSerializeAndDeserialize() throws Exception {
-        Slice a = new Slice("abcdef");
+    public void testSerializeAndDeserialize() {
+        Slice a = Slice.create("abcdef");
         Slice b = Utils.deserializeObject(Utils.serializeObject(a));
         assertArrayEquals(a.data(), b.data());
     }
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 e370014..3937df4 100644
--- a/src/test/java/com/github/fppt/jedismock/comparisontests/SimpleOperationsTest.java
+++ b/src/test/java/com/github/fppt/jedismock/comparisontests/SimpleOperationsTest.java
@@ -212,4 +212,35 @@ public class SimpleOperationsTest extends ComparisonBase {
         } while (poppedValue != null);
     }
 
+    @Theory
+    public void whenHSettingOnTheSameKeys_EnsureReturnTypeIs1WhenKeysAreNew(Jedis jedis){
+        String field = "my-field";
+        String hash = "my-hash";
+        assertEquals(new Long(1L), jedis.hset(hash, field, "some value"));
+        assertEquals(new Long(0L), jedis.hset(hash, field, "some other value"));
+    }
+
+    @Theory
+    public void whenHSettingAndHGetting_EnsureValuesAreSetAndRetreived(Jedis jedis){
+        String field = "my-field";
+        String hash = "my-hash";
+        String value = "my-value";
+
+        assertNull(jedis.hget(hash, field));
+        jedis.hset(hash, field, value);
+        assertEquals(value, jedis.hget(hash, field));
+    }
+
+    @Theory
+    public void whenHDeleting_EnsureValuesAreRemoved(Jedis jedis){
+        String field = "my-field-2";
+        String hash = "my-hash-2";
+        String value = "my-value-2";
+
+        assertEquals(new Long(0L), jedis.hdel(hash, field));
+        jedis.hset(hash, field, value);
+        assertEquals(value, jedis.hget(hash, field));
+        assertEquals(new Long(1L), jedis.hdel(hash, field));
+        assertNull(jedis.hget(hash, field));
+    }
 }