Skip to content

Commit

Permalink
Merge pull request #31 from skytable/dynlists
Browse files Browse the repository at this point in the history
protocol: Support dynamic lists
  • Loading branch information
ohsayan authored Aug 7, 2024
2 parents fa5638f + 8d637fe commit 75e1355
Show file tree
Hide file tree
Showing 3 changed files with 184 additions and 0 deletions.
41 changes: 41 additions & 0 deletions src/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -390,3 +390,44 @@ impl SQParam for String {
self.as_str().append_param(buf)
}
}

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],
}

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 QList<'a, T> {
fn append_param(&self, q: &mut Vec<u8>) -> 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 = QList::new(&data);
let q = query!(
"insert into apps.social(?, ?, ?)",
"username",
"password",
list
);
assert_eq!(q.param_cnt(), 3);
dbg!(String::from_utf8(q.debug_encode_packet())).unwrap();
}
60 changes: 60 additions & 0 deletions src/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,12 @@ impl Row {
}
}

impl From<Vec<Value>> for Row {
fn from(values: Vec<Value>) -> Self {
Self { values }
}
}

#[derive(Debug, PartialEq, Clone)]
/// A response returned by the server
pub enum Response {
Expand Down Expand Up @@ -405,3 +411,57 @@ impl<T: FromRow> Deref for Rows<T> {
&self.0
}
}

/// A list received from a response
#[derive(Debug, PartialEq, Clone)]
pub struct RList<T: FromValue = Value>(Vec<T>);

impl<T: FromValue> From<Vec<T>> for RList<T> {
fn from(values: Vec<T>) -> Self {
Self(values)
}
}

impl<T: FromValue> RList<T> {
/// Returns the values of the list
pub fn into_values(self) -> Vec<T> {
self.0
}
}

impl<T: FromValue> Deref for RList<T> {
type Target = [T];
fn deref(&self) -> &Self::Target {
&self.0
}
}

impl<T: FromValue> FromValue for RList<T> {
fn from_value(v: Value) -> ClientResult<Self> {
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<String>)>().unwrap();
assert_eq!(name, "sayan");
assert_eq!(languages.as_ref(), vec!["c", "assembly", "rust"]);
}
83 changes: 83 additions & 0 deletions tests/custom_query_resp.rs
Original file line number Diff line number Diff line change
@@ -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<String>,
}

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<u8>) -> 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<Self> {
let (username, password, notes) = resp.parse::<(String, String, RList<String>)>()?;
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());
}

0 comments on commit 75e1355

Please sign in to comment.