Browse Source

Added SCAN support (#36)

Parsing all commands
César D. Rodas 2 years ago
parent
commit
7e9447e917
2 changed files with 120 additions and 5 deletions
  1. 109 5
      src/cmd/key.rs
  2. 11 0
      src/value/mod.rs

+ 109 - 5
src/cmd/key.rs

@@ -5,12 +5,14 @@ use crate::{
     connection::Connection,
     db::scan::Scan,
     error::Error,
-    value::bytes_to_number,
-    value::{cursor::Cursor, Value},
+    value::{bytes_to_number, cursor::Cursor, typ::Typ, Value},
 };
 use bytes::Bytes;
-use std::convert::TryInto;
-use std::time::{SystemTime, UNIX_EPOCH};
+use std::{
+    convert::TryInto,
+    str::FromStr,
+    time::{SystemTime, UNIX_EPOCH},
+};
 use tokio::time::{Duration, Instant};
 
 /// This command copies the value stored at the source key to the destination
@@ -225,7 +227,44 @@ pub async fn rename(conn: &Connection, args: &[Bytes]) -> Result<Value, Error> {
 /// the cursor argument in the next call.
 pub async fn scan(conn: &Connection, args: &[Bytes]) -> Result<Value, Error> {
     let cursor: Cursor = (&args[1]).try_into()?;
-    Ok(conn.db().scan(cursor, None, None, None)?.into())
+    let mut current = 2;
+    let pattern = if check_arg!(args, current, "MATCH") {
+        current += 2;
+        Some(
+            args.get(current - 1)
+                .ok_or_else(|| Error::InvalidArgsCount("SCAN".to_owned()))?,
+        )
+    } else {
+        None
+    };
+    let count = if check_arg!(args, current, "COUNT") {
+        current += 2;
+        let number: usize = bytes_to_number(
+            args.get(current - 1)
+                .ok_or_else(|| Error::InvalidArgsCount("SCAN".to_owned()))?,
+        )?;
+        Some(number)
+    } else {
+        None
+    };
+    let typ = if check_arg!(args, current, "TYPE") {
+        current += 2;
+        Some(
+            Typ::from_str(&String::from_utf8_lossy(
+                args.get(current - 1)
+                    .ok_or_else(|| Error::InvalidArgsCount("SCAN".to_owned()))?,
+            ))
+            .map_err(|_| Error::Syntax)?,
+        )
+    } else {
+        None
+    };
+
+    if current != args.len() {
+        return Err(Error::Syntax);
+    }
+
+    Ok(conn.db().scan(cursor, pattern, count, typ)?.into())
 }
 
 /// Returns the remaining time to live of a key that has a timeout. This introspection capability
@@ -256,6 +295,8 @@ pub async fn persist(conn: &Connection, args: &[Bytes]) -> Result<Value, Error>
 
 #[cfg(test)]
 mod test {
+    use std::convert::TryInto;
+
     use crate::{
         cmd::test::{create_connection, run_command},
         error::Error,
@@ -482,4 +523,67 @@ mod test {
             run_command(&c, &["renamenx", "xxx", "bar-1650"]).await
         );
     }
+
+    #[tokio::test]
+    async fn scan_no_args() {
+        let c = create_connection();
+        for i in (1..100) {
+            assert_eq!(
+                Ok(1.into()),
+                run_command(&c, &["incr", &format!("foo-{}", i)]).await
+            );
+        }
+
+        let r: Vec<Value> = run_command(&c, &["scan", "0"])
+            .await
+            .unwrap()
+            .try_into()
+            .unwrap();
+        let values: Vec<Value> = r[1].clone().try_into().unwrap();
+
+        assert_eq!(2, r.len());
+        assert_eq!(10, values.len());
+    }
+
+    #[tokio::test]
+    async fn scan_with_count_match() {
+        let c = create_connection();
+        for i in (1..100) {
+            assert_eq!(
+                Ok(1.into()),
+                run_command(&c, &["incr", &format!("foo-{}", i)]).await
+            );
+        }
+
+        let r: Vec<Value> = run_command(&c, &["scan", "0", "match", "foo-1*", "count", "50"])
+            .await
+            .unwrap()
+            .try_into()
+            .unwrap();
+        let values: Vec<Value> = r[1].clone().try_into().unwrap();
+
+        assert_eq!(2, r.len());
+        assert_eq!(11, values.len());
+    }
+
+    #[tokio::test]
+    async fn scan_with_count() {
+        let c = create_connection();
+        for i in (1..100) {
+            assert_eq!(
+                Ok(1.into()),
+                run_command(&c, &["incr", &format!("foo-{}", i)]).await
+            );
+        }
+
+        let r: Vec<Value> = run_command(&c, &["scan", "0", "count", "50"])
+            .await
+            .unwrap()
+            .try_into()
+            .unwrap();
+        let values: Vec<Value> = r[1].clone().try_into().unwrap();
+
+        assert_eq!(2, r.len());
+        assert_eq!(50, values.len());
+    }
 }

+ 11 - 0
src/value/mod.rs

@@ -204,3 +204,14 @@ impl From<Vec<Value>> for Value {
         Value::Array(value)
     }
 }
+
+impl TryInto<Vec<Value>> for Value {
+    type Error = Error;
+
+    fn try_into(self) -> Result<Vec<Value>, Self::Error> {
+        match self {
+            Self::Array(x) => Ok(x),
+            _ => Err(Error::Internal),
+        }
+    }
+}