From 7e50a47abd92b34ea420f7e7891063aeacc829ab Mon Sep 17 00:00:00 2001 From: Sayan Nandan Date: Tue, 23 Jul 2024 22:57:34 +0530 Subject: [PATCH 1/4] protocol, query: Add limited dynamic list support --- src/query.rs | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/query.rs b/src/query.rs index 3254c9b..99f5fce 100644 --- a/src/query.rs +++ b/src/query.rs @@ -390,3 +390,42 @@ impl SQParam for String { self.as_str().append_param(buf) } } + +const LIST_SYM_OPEN: u8 = 0x09; +const LIST_SYM_CLOSE: u8 = ']' as u8; + +/// A list type representing a Skyhash list type, used in parameter lists +pub struct List<'a, T: SQParam> { + l: &'a [T], +} + +impl<'a, T: SQParam> List<'a, T> { + /// create a new list + pub fn new(l: &'a [T]) -> Self { + Self { l } + } +} + +impl<'a, T: SQParam> SQParam for List<'a, T> { + fn append_param(&self, q: &mut Vec) -> usize { + q.push(LIST_SYM_OPEN); + for param in self.l { + param.append_param(q); + } + q.push(LIST_SYM_CLOSE); + 1 + } +} + +#[test] +fn list_param() { + let data = vec!["hello", "giant", "world"]; + let list = List::new(&data); + let q = query!( + "insert into apps.social(?, ?, ?)", + "username", + "password", + list + ); + assert_eq!(q.param_cnt(), 3); +} From 330a8f513ad514bd82955029c85bdcc0a5704990 Mon Sep 17 00:00:00 2001 From: Sayan Nandan Date: Tue, 23 Jul 2024 23:18:41 +0530 Subject: [PATCH 2/4] resp: Add RList for decoding lists in responses --- src/query.rs | 8 ++++---- src/response.rs | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/src/query.rs b/src/query.rs index 99f5fce..787214e 100644 --- a/src/query.rs +++ b/src/query.rs @@ -395,18 +395,18 @@ const LIST_SYM_OPEN: u8 = 0x09; const LIST_SYM_CLOSE: u8 = ']' as u8; /// A list type representing a Skyhash list type, used in parameter lists -pub struct List<'a, T: SQParam> { +pub struct QList<'a, T: SQParam> { l: &'a [T], } -impl<'a, T: SQParam> List<'a, T> { +impl<'a, T: SQParam> QList<'a, T> { /// create a new list pub fn new(l: &'a [T]) -> Self { Self { l } } } -impl<'a, T: SQParam> SQParam for List<'a, T> { +impl<'a, T: SQParam> SQParam for QList<'a, T> { fn append_param(&self, q: &mut Vec) -> usize { q.push(LIST_SYM_OPEN); for param in self.l { @@ -420,7 +420,7 @@ impl<'a, T: SQParam> SQParam for List<'a, T> { #[test] fn list_param() { let data = vec!["hello", "giant", "world"]; - let list = List::new(&data); + let list = QList::new(&data); let q = query!( "insert into apps.social(?, ?, ?)", "username", diff --git a/src/response.rs b/src/response.rs index 1da5f85..b117d34 100644 --- a/src/response.rs +++ b/src/response.rs @@ -405,3 +405,50 @@ impl Deref for Rows { &self.0 } } + +/// A list received from a response +pub struct RList(Vec); + +impl RList { + /// Returns the values of the list + pub fn into_values(self) -> Vec { + self.0 + } +} + +impl Deref for RList { + type Target = [T]; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl FromValue for RList { + fn from_value(v: Value) -> ClientResult { + match v { + Value::List(l) => { + let mut ret = Vec::new(); + for value in l { + ret.push(T::from_value(value)?); + } + Ok(Self(ret)) + } + _ => Err(Error::ParseError(ParseError::TypeMismatch)), + } + } +} + +#[test] +fn resp_list_parse() { + let response_list = Response::Row(Row::new(vec![ + Value::String("sayan".to_owned()), + Value::List(vec![ + Value::String("c".to_owned()), + Value::String("assembly".to_owned()), + Value::String("rust".to_owned()), + ]), + ])); + let (name, languages) = response_list.parse::<(String, RList)>().unwrap(); + assert_eq!(name, "sayan"); + assert_eq!(languages.as_ref(), vec!["c", "assembly", "rust"]); +} From 753b69f3db5463599f4caacc85713e8029a6ebda Mon Sep 17 00:00:00 2001 From: Sayan Nandan Date: Tue, 23 Jul 2024 23:45:05 +0530 Subject: [PATCH 3/4] query, response: Add dynlist lib type trait impls and fix proto --- src/query.rs | 4 +++- src/response.rs | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/query.rs b/src/query.rs index 787214e..8fb0e33 100644 --- a/src/query.rs +++ b/src/query.rs @@ -391,10 +391,11 @@ impl SQParam for String { } } -const LIST_SYM_OPEN: u8 = 0x09; +const LIST_SYM_OPEN: u8 = 0x07; const LIST_SYM_CLOSE: u8 = ']' as u8; /// A list type representing a Skyhash list type, used in parameter lists +#[derive(Debug, PartialEq, Clone)] pub struct QList<'a, T: SQParam> { l: &'a [T], } @@ -428,4 +429,5 @@ fn list_param() { list ); assert_eq!(q.param_cnt(), 3); + dbg!(String::from_utf8(q.debug_encode_packet())).unwrap(); } diff --git a/src/response.rs b/src/response.rs index b117d34..332ce08 100644 --- a/src/response.rs +++ b/src/response.rs @@ -407,6 +407,7 @@ impl Deref for Rows { } /// A list received from a response +#[derive(Debug, PartialEq, Clone)] pub struct RList(Vec); impl RList { From 8d637fe2aadfbd883bb36deb7d21922eb5cb2549 Mon Sep 17 00:00:00 2001 From: Sayan Nandan Date: Wed, 24 Jul 2024 13:42:38 +0530 Subject: [PATCH 4/4] test: Add dynlist example --- src/response.rs | 12 ++++++ tests/custom_query_resp.rs | 83 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 tests/custom_query_resp.rs diff --git a/src/response.rs b/src/response.rs index 332ce08..5ed2aaf 100644 --- a/src/response.rs +++ b/src/response.rs @@ -128,6 +128,12 @@ impl Row { } } +impl From> for Row { + fn from(values: Vec) -> Self { + Self { values } + } +} + #[derive(Debug, PartialEq, Clone)] /// A response returned by the server pub enum Response { @@ -410,6 +416,12 @@ impl Deref for Rows { #[derive(Debug, PartialEq, Clone)] pub struct RList(Vec); +impl From> for RList { + fn from(values: Vec) -> Self { + Self(values) + } +} + impl RList { /// Returns the values of the list pub fn into_values(self) -> Vec { diff --git a/tests/custom_query_resp.rs b/tests/custom_query_resp.rs new file mode 100644 index 0000000..25109f1 --- /dev/null +++ b/tests/custom_query_resp.rs @@ -0,0 +1,83 @@ +use skytable::{ + query, + query::{QList, SQParam}, + response::{FromResponse, RList, Response, Row, Value}, +}; + +/* + our model looks like: + + create model myapp.data( + username: string, + password: string, + notes: list { type: string }, + ) +*/ + +#[derive(Debug, PartialEq)] +struct BookmarkUser { + username: String, + password: String, + notes: Vec, +} + +impl BookmarkUser { + fn test_user() -> Self { + Self { + username: "sayan".to_owned(), + password: "pass123".to_owned(), + notes: vec![ + "https://example.com".to_owned(), + "https://skytable.org".to_owned(), + "https://docs.skytable.org".to_owned(), + ], + } + } +} + +impl<'a> SQParam for &'a BookmarkUser { + fn append_param(&self, q: &mut Vec) -> usize { + self.username.append_param(q) + + self.password.append_param(q) + + QList::new(&self.notes).append_param(q) + } +} + +impl FromResponse for BookmarkUser { + fn from_response(resp: Response) -> skytable::ClientResult { + let (username, password, notes) = resp.parse::<(String, String, RList)>()?; + Ok(Self { + username, + password, + notes: notes.into_values(), + }) + } +} + +#[test] +fn dynlist_q() { + let bu = BookmarkUser::test_user(); + let q = query!( + "insert into myapp.data { username: ?, password: ?, notes: ? }", + &bu + ); + assert_eq!(q.param_cnt(), 3); +} + +#[test] +fn dynlist_r() { + // assume that this is the response we got from the server (as a row); this may look messy but in a real-world application, the library does this for you + // under the hood, so don't worry! you'll never have to write this yourself! + let resp_from_server = Response::Row(Row::from(vec![ + Value::String("sayan".to_owned()), + Value::String("pass123".to_owned()), + Value::List(vec![ + Value::String("https://example.com".to_owned()), + Value::String("https://skytable.org".to_owned()), + Value::String("https://docs.skytable.org".to_owned()), + ]), + ])); + // now this is our "fetch code" + let user: BookmarkUser = resp_from_server.parse().unwrap(); + assert_eq!(user, BookmarkUser::test_user()); +}