Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

protocol: Support dynamic lists #31

Merged
merged 4 commits into from
Aug 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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());
}
Loading