Browse Source

Adding missing commands into the string data-types (#26)

* Adding missing commands into the string data-types

* Add getrange command

* First persist bug.

Persisting a key was not removing the key from the purge data structure.

* Added getex command

* Added support for MSET

* Add MSETNX command handler

* Added SETNX command

* Add STRLEN command

* Adding all features for SET
César D. Rodas 3 years ago
parent
commit
efc05eb0a7
6 changed files with 687 additions and 20 deletions
  1. 9 12
      src/cmd/key.rs
  2. 11 0
      src/cmd/mod.rs
  3. 425 5
      src/cmd/string.rs
  4. 165 1
      src/db/mod.rs
  5. 65 2
      src/dispatcher/mod.rs
  6. 12 0
      src/macros.rs

+ 9 - 12
src/cmd/key.rs

@@ -1,4 +1,5 @@
 //! # Key-related command handlers
+use super::now;
 use crate::{
     check_arg, connection::Connection, error::Error, value::bytes_to_number, value::Value,
 };
@@ -6,14 +7,6 @@ use bytes::Bytes;
 use std::time::{SystemTime, UNIX_EPOCH};
 use tokio::time::{Duration, Instant};
 
-/// Returns the current time
-fn now() -> Duration {
-    let start = SystemTime::now();
-    start
-        .duration_since(UNIX_EPOCH)
-        .expect("Time went backwards")
-}
-
 /// Removes the specified keys. A key is ignored if it does not exist.
 pub async fn del(conn: &Connection, args: &[Bytes]) -> Result<Value, Error> {
     Ok(conn.db().del(&args[1..]))
@@ -44,10 +37,12 @@ pub async fn expire(conn: &Connection, args: &[Bytes]) -> Result<Value, Error> {
         return Ok(conn.db().del(&args[1..2]));
     }
 
+    let expires_in: u64 = expires_in as u64;
+
     let expires_at = if check_arg!(args, 0, "EXPIRE") {
-        Duration::from_secs(expires_in as u64)
+        Duration::from_secs(expires_in)
     } else {
-        Duration::from_millis(expires_in as u64)
+        Duration::from_millis(expires_in)
     };
 
     Ok(conn.db().set_ttl(&args[1], expires_at))
@@ -70,10 +65,12 @@ pub async fn expire_at(conn: &Connection, args: &[Bytes]) -> Result<Value, Error
         return Ok(conn.db().del(&args[1..2]));
     }
 
+    let expires_in: u64 = expires_in as u64;
+
     let expires_at = if secs {
-        Duration::from_secs(expires_in as u64)
+        Duration::from_secs(expires_in)
     } else {
-        Duration::from_millis(expires_in as u64)
+        Duration::from_millis(expires_in)
     };
 
     Ok(conn.db().set_ttl(&args[1], expires_at))

+ 11 - 0
src/cmd/mod.rs

@@ -1,4 +1,7 @@
 //! # All commands handlers
+use std::time::{SystemTime, UNIX_EPOCH};
+use tokio::time::{Duration, Instant};
+
 pub mod client;
 pub mod hash;
 pub mod key;
@@ -10,6 +13,14 @@ pub mod set;
 pub mod string;
 pub mod transaction;
 
+/// Returns the current time
+pub fn now() -> Duration {
+    let start = SystemTime::now();
+    start
+        .duration_since(UNIX_EPOCH)
+        .expect("Time went backwards")
+}
+
 #[cfg(test)]
 mod test {
     use crate::{

+ 425 - 5
src/cmd/string.rs

@@ -1,11 +1,24 @@
 //! # String command handlers
+use super::now;
 use crate::{
-    check_arg, connection::Connection, error::Error, value::bytes_to_number, value::Value,
+    check_arg, connection::Connection, db::Override, error::Error, try_get_arg,
+    value::bytes_to_number, value::Value,
 };
 use bytes::Bytes;
-use std::{convert::TryInto, ops::Neg};
+use std::{
+    cmp::min,
+    convert::TryInto,
+    ops::{Bound, Neg},
+};
 use tokio::time::Duration;
 
+/// If key already exists and is a string, this command appends the value at the
+/// end of the string. If key does not exist it is created and set as an empty
+/// string, so APPEND will be similar to SET in this special case.
+pub async fn append(conn: &Connection, args: &[Bytes]) -> Result<Value, Error> {
+    conn.db().append(&args[1], &args[2])
+}
+
 /// Increments the number stored at key by one. If the key does not exist, it is set to 0 before
 /// performing the operation. An error is returned if the key contains a value of the wrong type or
 /// contains a string that can not be represented as integer. This operation is limited to 64 bit
@@ -55,6 +68,88 @@ pub async fn get(conn: &Connection, args: &[Bytes]) -> Result<Value, Error> {
     Ok(conn.db().get(&args[1]))
 }
 
+/// Get the value of key and optionally set its expiration. GETEX is similar to
+/// GET, but is a write command with additional options.
+pub async fn getex(conn: &Connection, args: &[Bytes]) -> Result<Value, Error> {
+    let (expire_at, persist) = match args.len() {
+        2 => (None, false),
+        3 => {
+            if check_arg!(args, 2, "PERSIST") {
+                (None, Default::default())
+            } else {
+                return Err(Error::Syntax);
+            }
+        }
+        4 => {
+            let expires_in: i64 = bytes_to_number(&args[3])?;
+            if expires_in <= 0 {
+                // Delete key right away after returning
+                return Ok(conn.db().getdel(&args[1]));
+            }
+
+            let expires_in: u64 = expires_in as u64;
+
+            match String::from_utf8_lossy(&args[2]).to_uppercase().as_str() {
+                "EX" => (Some(Duration::from_secs(expires_in)), false),
+                "PX" => (Some(Duration::from_millis(expires_in)), false),
+                "EXAT" => (
+                    Some(Duration::from_secs(expires_in - now().as_secs())),
+                    false,
+                ),
+                "PXAT" => (
+                    Some(Duration::from_millis(expires_in - now().as_millis() as u64)),
+                    false,
+                ),
+                "PERSIST" => (None, Default::default()),
+                _ => return Err(Error::Syntax),
+            }
+        }
+        _ => return Err(Error::Syntax),
+    };
+    Ok(conn.db().getex(&args[1], expire_at, persist))
+}
+
+/// Get the value of key. If the key does not exist the special value nil is returned. An error is
+/// returned if the value stored at key is not a string, because GET only handles string values.
+pub async fn getrange(conn: &Connection, args: &[Bytes]) -> Result<Value, Error> {
+    match conn.db().get(&args[1]) {
+        Value::Blob(binary) => {
+            let start = bytes_to_number::<i64>(&args[2])?;
+            let end = bytes_to_number::<i64>(&args[3])?;
+            let len = binary.len();
+
+            // resolve negative positions
+            let start: usize = if start < 0 {
+                (start + len as i64).try_into().unwrap_or(0)
+            } else {
+                start.try_into().expect("Positive number")
+            };
+
+            // resolve negative positions
+            let end: usize = if end < 0 {
+                if let Ok(val) = (end + len as i64).try_into() {
+                    val
+                } else {
+                    return Ok("".into());
+                }
+            } else {
+                end.try_into().expect("Positive number")
+            };
+            let end = min(end, len - 1);
+
+            if end < start {
+                return Ok("".into());
+            }
+
+            Ok(Value::Blob(
+                binary.slice((Bound::Included(start), Bound::Included(end))),
+            ))
+        }
+        Value::Null => Ok("".into()),
+        _ => Err(Error::WrongType),
+    }
+}
+
 /// Get the value of key and delete the key. This command is similar to GET, except for the fact
 /// that it also deletes the key on success (if and only if the key's value type is a string).
 pub async fn getdel(conn: &Connection, args: &[Bytes]) -> Result<Value, Error> {
@@ -78,9 +173,124 @@ pub async fn mget(conn: &Connection, args: &[Bytes]) -> Result<Value, Error> {
 /// of its type. Any previous time to live associated with the key is discarded on successful SET
 /// operation.
 pub async fn set(conn: &Connection, args: &[Bytes]) -> Result<Value, Error> {
-    Ok(conn
-        .db()
-        .set(&args[1], Value::Blob(args[2].to_owned()), None))
+    match args.len() {
+        3 => Ok(conn
+            .db()
+            .set(&args[1], Value::Blob(args[2].to_owned()), None)),
+        4 | 5 | 6 | 7 => {
+            let mut offset = 3;
+            let mut expiration = None;
+            let mut override_value = Override::Yes;
+            let mut return_previous = false;
+            let mut keep_ttl = false;
+            match String::from_utf8_lossy(&args[offset])
+                .to_uppercase()
+                .as_str()
+            {
+                "EX" => {
+                    expiration = Some(Duration::from_secs(bytes_to_number::<u64>(try_get_arg!(
+                        args, 4
+                    ))?));
+                    offset += 2;
+                }
+                "PX" => {
+                    expiration = Some(Duration::from_millis(bytes_to_number::<u64>(
+                        try_get_arg!(args, 4),
+                    )?));
+                    offset += 2;
+                }
+                "EXAT" => {
+                    expiration = Some(Duration::from_secs(
+                        bytes_to_number::<u64>(try_get_arg!(args, 4))? - now().as_secs(),
+                    ));
+                    offset += 2;
+                }
+                "PXAT" => {
+                    expiration = Some(Duration::from_millis(
+                        bytes_to_number::<u64>(try_get_arg!(args, 4))? - (now().as_millis() as u64),
+                    ));
+                    offset += 2;
+                }
+                "KEEPTTL" => {
+                    keep_ttl = true;
+                    offset += 1;
+                }
+                "NX" | "XX" | "GET" => {}
+                _ => return Err(Error::Syntax),
+            };
+
+            if offset < args.len() {
+                match String::from_utf8_lossy(&args[offset])
+                    .to_uppercase()
+                    .as_str()
+                {
+                    "NX" => {
+                        override_value = Override::No;
+                        offset += 1;
+                    }
+                    "XX" => {
+                        override_value = Override::Only;
+                        offset += 1;
+                    }
+                    "GET" => {}
+                    _ => return Err(Error::Syntax),
+                };
+            }
+
+            if offset < args.len() {
+                if String::from_utf8_lossy(&args[offset])
+                    .to_uppercase()
+                    .as_str()
+                    == "GET"
+                {
+                    return_previous = true;
+                } else {
+                    return Err(Error::Syntax);
+                }
+            }
+
+            Ok(
+                match conn.db().set_advanced(
+                    &args[1],
+                    Value::Blob(args[2].to_owned()),
+                    expiration,
+                    override_value,
+                    keep_ttl,
+                    return_previous,
+                ) {
+                    Value::Integer(1) => Value::Ok,
+                    Value::Integer(0) => Value::Null,
+                    any_return => any_return,
+                },
+            )
+        }
+        _ => Err(Error::Syntax),
+    }
+}
+
+/// Sets the given keys to their respective values. MSET replaces existing
+/// values with new values, just as regular SET. See MSETNX if you don't want to
+/// overwrite existing values.  MSET is atomic, so all given keys are set at
+/// once.
+///
+/// It is not possible for clients to see that some of the keys were
+/// updated while others are unchanged.
+pub async fn mset(conn: &Connection, args: &[Bytes]) -> Result<Value, Error> {
+    Ok(conn.db().multi_set(&args[1..], true))
+}
+
+/// Sets the given keys to their respective values. MSETNX will not perform any
+/// operation at all even if just a single key already exists.
+///
+/// Because of this semantic MSETNX can be used in order to set different keys
+/// representing different fields of an unique logic object in a way that
+/// ensures that either all the fields or none at all are set.
+///
+/// MSETNX is atomic, so all given keys are set at once. It is not possible for
+/// clients to see that some of the keys were updated while others are
+/// unchanged.
+pub async fn msetnx(conn: &Connection, args: &[Bytes]) -> Result<Value, Error> {
+    Ok(conn.db().multi_set(&args[1..], false))
 }
 
 /// Set key to hold the string value and set key to timeout after a given number of seconds. This
@@ -100,6 +310,20 @@ pub async fn setex(conn: &Connection, args: &[Bytes]) -> Result<Value, Error> {
         .set(&args[1], Value::Blob(args[2].to_owned()), Some(ttl)))
 }
 
+/// Set key to hold string value if key does not exist. In that case, it is
+/// equal to SET. When key already holds a value, no operation is performed.
+/// SETNX is short for "SET if Not eXists".
+pub async fn setnx(conn: &Connection, args: &[Bytes]) -> Result<Value, Error> {
+    Ok(conn.db().set_advanced(
+        &args[1],
+        Value::Blob(args[2].to_owned()),
+        None,
+        Override::No,
+        false,
+        false,
+    ))
+}
+
 /// Returns the length of the string value stored at key. An error is returned when key holds a
 /// non-string value.
 pub async fn strlen(conn: &Connection, args: &[Bytes]) -> Result<Value, Error> {
@@ -120,6 +344,25 @@ mod test {
     };
 
     #[tokio::test]
+    async fn append() {
+        let c = create_connection();
+        assert_eq!(
+            Ok(5.into()),
+            run_command(&c, &["append", "foo", "cesar"]).await,
+        );
+        assert_eq!(
+            Ok(10.into()),
+            run_command(&c, &["append", "foo", "rodas"]).await,
+        );
+
+        let _ = run_command(&c, &["hset", "hash", "foo", "bar"]).await;
+        assert_eq!(
+            Err(Error::WrongType),
+            run_command(&c, &["append", "hash", "rodas"]).await,
+        );
+    }
+
+    #[tokio::test]
     async fn incr() {
         let c = create_connection();
         let r = run_command(&c, &["incr", "foo"]).await;
@@ -181,6 +424,56 @@ mod test {
     }
 
     #[tokio::test]
+    async fn setnx() {
+        let c = create_connection();
+        assert_eq!(
+            Ok(1.into()),
+            run_command(&c, &["setnx", "foo", "bar"]).await
+        );
+
+        assert_eq!(
+            Ok(0.into()),
+            run_command(&c, &["setnx", "foo", "barx"]).await
+        );
+
+        assert_eq!(
+            Ok(Value::Array(vec!["bar".into()])),
+            run_command(&c, &["mget", "foo"]).await
+        );
+    }
+
+    #[tokio::test]
+    async fn mset() {
+        let c = create_connection();
+        let x = run_command(&c, &["mset", "foo", "bar", "bar", "foo"]).await;
+        assert_eq!(Ok(Value::Ok), x);
+
+        assert_eq!(
+            Ok(Value::Array(vec!["bar".into(), "foo".into()])),
+            run_command(&c, &["mget", "foo", "bar"]).await
+        );
+    }
+
+    #[tokio::test]
+    async fn msetnx() {
+        let c = create_connection();
+        assert_eq!(
+            Ok(1.into()),
+            run_command(&c, &["msetnx", "foo", "bar", "bar", "foo"]).await
+        );
+
+        assert_eq!(
+            Ok(0.into()),
+            run_command(&c, &["msetnx", "foo", "bar1", "bar", "foo1"]).await
+        );
+
+        assert_eq!(
+            Ok(Value::Array(vec!["bar".into(), "foo".into()])),
+            run_command(&c, &["mget", "foo", "bar"]).await
+        );
+    }
+
+    #[tokio::test]
     async fn get_and_set() {
         let c = create_connection();
         let x = run_command(&c, &["set", "foo", "bar"]).await;
@@ -191,6 +484,133 @@ mod test {
     }
 
     #[tokio::test]
+    async fn setkeepttl() {
+        let c = create_connection();
+        assert_eq!(
+            Ok(Value::Ok),
+            run_command(&c, &["set", "foo", "bar", "ex", "60"]).await
+        );
+        assert_eq!(
+            Ok(Value::Ok),
+            run_command(&c, &["set", "foo", "bar1", "keepttl"]).await
+        );
+        assert_eq!(Ok("bar1".into()), run_command(&c, &["get", "foo"]).await);
+        assert_eq!(Ok(59.into()), run_command(&c, &["ttl", "foo"]).await);
+
+        assert_eq!(
+            Ok(Value::Ok),
+            run_command(&c, &["set", "foo", "bar2"]).await
+        );
+        assert_eq!(Ok("bar2".into()), run_command(&c, &["get", "foo"]).await);
+        assert_eq!(
+            Ok(Value::Integer(-1)),
+            run_command(&c, &["ttl", "foo"]).await
+        );
+    }
+
+    #[tokio::test]
+    async fn set_and_get_previous_result() {
+        let c = create_connection();
+        assert_eq!(
+            Ok(Value::Ok),
+            run_command(&c, &["set", "foo", "bar", "ex", "60"]).await
+        );
+        assert_eq!(
+            Ok("bar".into()),
+            run_command(&c, &["set", "foo", "bar1", "get"]).await
+        );
+    }
+
+    #[tokio::test]
+    async fn set_nx() {
+        let c = create_connection();
+        assert_eq!(
+            Ok(Value::Ok),
+            run_command(&c, &["set", "foo", "bar", "ex", "60", "nx"]).await
+        );
+        assert_eq!(
+            Ok(Value::Null),
+            run_command(&c, &["set", "foo", "bar1", "nx"]).await
+        );
+        assert_eq!(Ok("bar".into()), run_command(&c, &["get", "foo"]).await);
+    }
+
+    #[tokio::test]
+    async fn set_xx() {
+        let c = create_connection();
+        assert_eq!(
+            Ok(Value::Null),
+            run_command(&c, &["set", "foo", "bar1", "ex", "60", "xx"]).await
+        );
+        assert_eq!(
+            Ok(Value::Ok),
+            run_command(&c, &["set", "foo", "bar2", "ex", "60", "nx"]).await
+        );
+        assert_eq!(
+            Ok(Value::Ok),
+            run_command(&c, &["set", "foo", "bar3", "ex", "60", "xx"]).await
+        );
+        assert_eq!(Ok("bar3".into()), run_command(&c, &["get", "foo"]).await);
+    }
+
+    #[tokio::test]
+    async fn set_incorrect_params() {
+        let c = create_connection();
+        assert_eq!(
+            Err(Error::NotANumber),
+            run_command(&c, &["set", "foo", "bar1", "ex", "xx"]).await
+        );
+        assert_eq!(
+            Err(Error::Syntax),
+            run_command(&c, &["set", "foo", "bar1", "ex"]).await
+        );
+    }
+
+    #[tokio::test]
+    async fn getrange() {
+        let c = create_connection();
+        let x = run_command(&c, &["set", "foo", "this is a long string"]).await;
+        assert_eq!(Ok(Value::Ok), x);
+
+        assert_eq!(
+            Ok("this is a long str".into()),
+            run_command(&c, &["getrange", "foo", "0", "-4"]).await
+        );
+
+        assert_eq!(
+            Ok("ring".into()),
+            run_command(&c, &["getrange", "foo", "-4", "-1"]).await
+        );
+
+        assert_eq!(
+            Ok("".into()),
+            run_command(&c, &["getrange", "foo", "-4", "1"]).await
+        );
+
+        assert_eq!(
+            Ok("ring".into()),
+            run_command(&c, &["getrange", "foo", "-4", "1000000"]).await
+        );
+
+        assert_eq!(
+            Ok("this is a long string".into()),
+            run_command(&c, &["getrange", "foo", "-400", "1000000"]).await
+        );
+
+        assert_eq!(
+            Ok("".into()),
+            run_command(&c, &["getrange", "foo", "-400", "-1000000"]).await
+        );
+
+        assert_eq!(
+            Ok("t".into()),
+            run_command(&c, &["getrange", "foo", "0", "0"]).await
+        );
+
+        assert_eq!(Ok(Value::Null), run_command(&c, &["get", "fox"]).await);
+    }
+
+    #[tokio::test]
     async fn getdel() {
         let c = create_connection();
         let x = run_command(&c, &["set", "foo", "bar"]).await;

+ 165 - 1
src/db/mod.rs

@@ -21,6 +21,33 @@ use std::{
 };
 use tokio::time::{Duration, Instant};
 
+/// Override database entries
+#[derive(PartialEq, Debug, Clone, Copy)]
+pub enum Override {
+    /// Allow override
+    Yes,
+    /// Do not allow override, only new entries
+    No,
+    /// Allow only override
+    Only,
+}
+
+impl From<bool> for Override {
+    fn from(v: bool) -> Override {
+        if v {
+            Override::Yes
+        } else {
+            Override::No
+        }
+    }
+}
+
+impl Default for Override {
+    fn default() -> Self {
+        Self::Yes
+    }
+}
+
 /// Databas structure
 ///
 /// Each connection has their own clone of the database and the conn_id is stored in each instance.
@@ -334,6 +361,26 @@ impl Db {
             .map_or(Value::Null, |x| x.clone_value())
     }
 
+    /// Get a copy of an entry and modifies the expiration of the key
+    pub fn getex(&self, key: &Bytes, expires_in: Option<Duration>, make_persistent: bool) -> Value {
+        let mut entries = self.entries[self.get_slot(key)].write();
+        entries
+            .get_mut(key)
+            .filter(|x| x.is_valid())
+            .map(|value| {
+                if make_persistent {
+                    self.expirations.lock().remove(key);
+                    value.persist();
+                } else if let Some(expires_in) = expires_in {
+                    let expires_at = Instant::now() + expires_in;
+                    self.expirations.lock().add(key, expires_at);
+                    value.set_ttl(expires_at);
+                }
+                value
+            })
+            .map_or(Value::Null, |x| x.clone_value())
+    }
+
     /// Get multiple copies of entries
     pub fn get_multi(&self, keys: &[Bytes]) -> Value {
         keys.iter()
@@ -366,17 +413,134 @@ impl Db {
             x.clone_value()
         })
     }
+    ///
+    /// Set a key, value with an optional expiration time
+    pub fn append(&self, key: &Bytes, value_to_append: &Bytes) -> Result<Value, Error> {
+        let mut entries = self.entries[self.get_slot(key)].write();
+        let mut entry = entries.get_mut(key).filter(|x| x.is_valid());
+
+        if let Some(entry) = entries.get_mut(key).filter(|x| x.is_valid()) {
+            match entry.get() {
+                Value::Blob(value) => {
+                    let binary: Bytes = [value.as_ref(), value_to_append.as_ref()].concat().into();
+                    let len = binary.len();
+                    entry.change_value(Value::Blob(binary));
+                    Ok(len.into())
+                }
+                _ => Err(Error::WrongType),
+            }
+        } else {
+            entries.insert(
+                key.clone(),
+                Entry::new(Value::Blob(value_to_append.clone()), None),
+            );
+            Ok(value_to_append.len().into())
+        }
+    }
+
+    /// Set multiple key/value pairs. Are involved keys are locked exclusively
+    /// like a transaction.
+    ///
+    /// If override_all is set to false, all entries must be new entries or the
+    /// entire operation fails, in this case 1 or is returned. Otherwise `Ok` is
+    /// returned.
+    pub fn multi_set(&self, key_values: &[Bytes], override_all: bool) -> Value {
+        let keys = key_values
+            .iter()
+            .step_by(2)
+            .cloned()
+            .collect::<Vec<Bytes>>();
+
+        self.lock_keys(&keys);
+
+        if !override_all {
+            for key in keys.iter() {
+                let entries = self.entries[self.get_slot(key)].read();
+                if entries.get(key).is_some() {
+                    self.unlock_keys(&keys);
+                    return 0.into();
+                }
+            }
+        }
+
+        for (i, _) in key_values.iter().enumerate().step_by(2) {
+            let mut entries = self.entries[self.get_slot(&key_values[i])].write();
+            entries.insert(
+                key_values[i].clone(),
+                Entry::new(Value::Blob(key_values[i + 1].clone()), None),
+            );
+        }
+
+        self.unlock_keys(&keys);
+
+        if override_all {
+            Value::Ok
+        } else {
+            1.into()
+        }
+    }
 
     /// Set a key, value with an optional expiration time
     pub fn set(&self, key: &Bytes, value: Value, expires_in: Option<Duration>) -> Value {
+        self.set_advanced(key, value, expires_in, Default::default(), false, false)
+    }
+
+    /// Set a value in the database with various settings
+    pub fn set_advanced(
+        &self,
+        key: &Bytes,
+        value: Value,
+        expires_in: Option<Duration>,
+        override_value: Override,
+        keep_ttl: bool,
+        return_previous: bool,
+    ) -> Value {
         let mut entries = self.entries[self.get_slot(key)].write();
         let expires_at = expires_in.map(|duration| Instant::now() + duration);
+        let previous = entries.get(key);
+
+        let expires_at = if keep_ttl {
+            if let Some(previous) = previous {
+                previous.get_ttl()
+            } else {
+                expires_at
+            }
+        } else {
+            expires_at
+        };
+
+        let to_return = if return_previous {
+            Some(previous.map_or(Value::Null, |v| v.clone_value()))
+        } else {
+            None
+        };
+
+        match override_value {
+            Override::No => {
+                if previous.is_some() {
+                    return 0.into();
+                }
+            }
+            Override::Only => {
+                if previous.is_none() {
+                    return 0.into();
+                }
+            }
+            _ => {}
+        };
 
         if let Some(expires_at) = expires_at {
             self.expirations.lock().add(key, expires_at);
         }
         entries.insert(key.clone(), Entry::new(value, expires_at));
-        Value::Ok
+
+        if let Some(to_return) = to_return {
+            to_return
+        } else if override_value == Override::Yes {
+            Value::Ok
+        } else {
+            1.into()
+        }
     }
 
     /// Returns the TTL of a given key

+ 65 - 2
src/dispatcher/mod.rs

@@ -580,6 +580,15 @@ dispatcher! {
         },
     },
     string {
+        append {
+            cmd::string::append,
+            [Flag::Write Flag::DenyOom Flag::Fast],
+            3,
+            1,
+            1,
+            1,
+            true,
+        },
         decr {
             cmd::string::decr,
             [Flag::Write Flag::DenyOom Flag::Fast],
@@ -607,6 +616,24 @@ dispatcher! {
             1,
             true,
         },
+        getex {
+            cmd::string::getex,
+            [Flag::Write Flag::Fast],
+            -2,
+            1,
+            1,
+            1,
+            true,
+        },
+        getrange {
+            cmd::string::getrange,
+            [Flag::ReadOnly],
+            4,
+            1,
+            1,
+            1,
+            true,
+        },
         getdel {
             cmd::string::getdel,
             [Flag::Write Flag::Fast],
@@ -661,6 +688,24 @@ dispatcher! {
             1,
             true,
         },
+        mset {
+            cmd::string::mset,
+            [Flag::Write Flag::DenyOom],
+            -2,
+            1,
+            -1,
+            1,
+            true,
+        },
+        msetnx {
+            cmd::string::msetnx,
+            [Flag::Write Flag::DenyOom],
+            -2,
+            1,
+            -1,
+            1,
+            true,
+        },
         set {
             cmd::string::set,
             [Flag::Write Flag::DenyOom],
@@ -679,6 +724,15 @@ dispatcher! {
             1,
             true,
         },
+        setnx {
+            cmd::string::setnx,
+            [Flag::Write Flag::DenyOom],
+            3,
+            1,
+            1,
+            1,
+            true,
+        },
         psetex {
             cmd::string::setex,
             [Flag::Write Flag::DenyOom],
@@ -690,13 +744,22 @@ dispatcher! {
         },
         strlen {
             cmd::string::strlen,
-            [Flag::Random Flag::Fast],
+            [Flag::ReadOnly Flag::Fast],
+            2,
+            1,
+            1,
+            1,
+            true,
+        },
+        substr {
+            cmd::string::getrange,
+            [Flag::ReadOnly],
             2,
             1,
             1,
             1,
             true,
-        }
+        },
     },
     connection {
         client {

+ 12 - 0
src/macros.rs

@@ -214,6 +214,18 @@ macro_rules! check_arg {
     }}
 }
 
+/// Reads an argument index. If the index is not provided an Err(Error:Syntax)
+/// is thrown
+#[macro_export]
+macro_rules! try_get_arg {
+    {$args: tt, $pos: tt} => {{
+        match $args.get($pos) {
+            Some(bytes) => bytes,
+            None => return Err(Error::Syntax),
+        }
+    }}
+}
+
 /// Convert a stream to a Bytes
 #[macro_export]
 macro_rules! bytes {