Skip to content

Commit 75e1355

Browse files
authored
Merge pull request #31 from skytable/dynlists
protocol: Support dynamic lists
2 parents fa5638f + 8d637fe commit 75e1355

File tree

3 files changed

+184
-0
lines changed

3 files changed

+184
-0
lines changed

src/query.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,3 +390,44 @@ impl SQParam for String {
390390
self.as_str().append_param(buf)
391391
}
392392
}
393+
394+
const LIST_SYM_OPEN: u8 = 0x07;
395+
const LIST_SYM_CLOSE: u8 = ']' as u8;
396+
397+
/// A list type representing a Skyhash list type, used in parameter lists
398+
#[derive(Debug, PartialEq, Clone)]
399+
pub struct QList<'a, T: SQParam> {
400+
l: &'a [T],
401+
}
402+
403+
impl<'a, T: SQParam> QList<'a, T> {
404+
/// create a new list
405+
pub fn new(l: &'a [T]) -> Self {
406+
Self { l }
407+
}
408+
}
409+
410+
impl<'a, T: SQParam> SQParam for QList<'a, T> {
411+
fn append_param(&self, q: &mut Vec<u8>) -> usize {
412+
q.push(LIST_SYM_OPEN);
413+
for param in self.l {
414+
param.append_param(q);
415+
}
416+
q.push(LIST_SYM_CLOSE);
417+
1
418+
}
419+
}
420+
421+
#[test]
422+
fn list_param() {
423+
let data = vec!["hello", "giant", "world"];
424+
let list = QList::new(&data);
425+
let q = query!(
426+
"insert into apps.social(?, ?, ?)",
427+
"username",
428+
"password",
429+
list
430+
);
431+
assert_eq!(q.param_cnt(), 3);
432+
dbg!(String::from_utf8(q.debug_encode_packet())).unwrap();
433+
}

src/response.rs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,12 @@ impl Row {
128128
}
129129
}
130130

131+
impl From<Vec<Value>> for Row {
132+
fn from(values: Vec<Value>) -> Self {
133+
Self { values }
134+
}
135+
}
136+
131137
#[derive(Debug, PartialEq, Clone)]
132138
/// A response returned by the server
133139
pub enum Response {
@@ -405,3 +411,57 @@ impl<T: FromRow> Deref for Rows<T> {
405411
&self.0
406412
}
407413
}
414+
415+
/// A list received from a response
416+
#[derive(Debug, PartialEq, Clone)]
417+
pub struct RList<T: FromValue = Value>(Vec<T>);
418+
419+
impl<T: FromValue> From<Vec<T>> for RList<T> {
420+
fn from(values: Vec<T>) -> Self {
421+
Self(values)
422+
}
423+
}
424+
425+
impl<T: FromValue> RList<T> {
426+
/// Returns the values of the list
427+
pub fn into_values(self) -> Vec<T> {
428+
self.0
429+
}
430+
}
431+
432+
impl<T: FromValue> Deref for RList<T> {
433+
type Target = [T];
434+
fn deref(&self) -> &Self::Target {
435+
&self.0
436+
}
437+
}
438+
439+
impl<T: FromValue> FromValue for RList<T> {
440+
fn from_value(v: Value) -> ClientResult<Self> {
441+
match v {
442+
Value::List(l) => {
443+
let mut ret = Vec::new();
444+
for value in l {
445+
ret.push(T::from_value(value)?);
446+
}
447+
Ok(Self(ret))
448+
}
449+
_ => Err(Error::ParseError(ParseError::TypeMismatch)),
450+
}
451+
}
452+
}
453+
454+
#[test]
455+
fn resp_list_parse() {
456+
let response_list = Response::Row(Row::new(vec![
457+
Value::String("sayan".to_owned()),
458+
Value::List(vec![
459+
Value::String("c".to_owned()),
460+
Value::String("assembly".to_owned()),
461+
Value::String("rust".to_owned()),
462+
]),
463+
]));
464+
let (name, languages) = response_list.parse::<(String, RList<String>)>().unwrap();
465+
assert_eq!(name, "sayan");
466+
assert_eq!(languages.as_ref(), vec!["c", "assembly", "rust"]);
467+
}

tests/custom_query_resp.rs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
use skytable::{
2+
query,
3+
query::{QList, SQParam},
4+
response::{FromResponse, RList, Response, Row, Value},
5+
};
6+
7+
/*
8+
our model looks like:
9+
10+
create model myapp.data(
11+
username: string,
12+
password: string,
13+
notes: list { type: string },
14+
)
15+
*/
16+
17+
#[derive(Debug, PartialEq)]
18+
struct BookmarkUser {
19+
username: String,
20+
password: String,
21+
notes: Vec<String>,
22+
}
23+
24+
impl BookmarkUser {
25+
fn test_user() -> Self {
26+
Self {
27+
username: "sayan".to_owned(),
28+
password: "pass123".to_owned(),
29+
notes: vec![
30+
"https://example.com".to_owned(),
31+
"https://skytable.org".to_owned(),
32+
"https://docs.skytable.org".to_owned(),
33+
],
34+
}
35+
}
36+
}
37+
38+
impl<'a> SQParam for &'a BookmarkUser {
39+
fn append_param(&self, q: &mut Vec<u8>) -> usize {
40+
self.username.append_param(q)
41+
+ self.password.append_param(q)
42+
+ QList::new(&self.notes).append_param(q)
43+
}
44+
}
45+
46+
impl FromResponse for BookmarkUser {
47+
fn from_response(resp: Response) -> skytable::ClientResult<Self> {
48+
let (username, password, notes) = resp.parse::<(String, String, RList<String>)>()?;
49+
Ok(Self {
50+
username,
51+
password,
52+
notes: notes.into_values(),
53+
})
54+
}
55+
}
56+
57+
#[test]
58+
fn dynlist_q() {
59+
let bu = BookmarkUser::test_user();
60+
let q = query!(
61+
"insert into myapp.data { username: ?, password: ?, notes: ? }",
62+
&bu
63+
);
64+
assert_eq!(q.param_cnt(), 3);
65+
}
66+
67+
#[test]
68+
fn dynlist_r() {
69+
// 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
70+
// under the hood, so don't worry! you'll never have to write this yourself!
71+
let resp_from_server = Response::Row(Row::from(vec![
72+
Value::String("sayan".to_owned()),
73+
Value::String("pass123".to_owned()),
74+
Value::List(vec![
75+
Value::String("https://example.com".to_owned()),
76+
Value::String("https://skytable.org".to_owned()),
77+
Value::String("https://docs.skytable.org".to_owned()),
78+
]),
79+
]));
80+
// now this is our "fetch code"
81+
let user: BookmarkUser = resp_from_server.parse().unwrap();
82+
assert_eq!(user, BookmarkUser::test_user());
83+
}

0 commit comments

Comments
 (0)