Skip to content

Commit

Permalink
[query] Add support for db id in user types #667 (#669)
Browse files Browse the repository at this point in the history
* Update db_user_value.rs

* Update query_result.rs

* Update db_user_value_test.rs

* Update lib.rs
  • Loading branch information
michaelvlach authored Aug 12, 2023
1 parent 517dda7 commit 635c010
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 46 deletions.
5 changes: 4 additions & 1 deletion agdb/src/db/db_user_value.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
use crate::DbElement;
use crate::DbError;
use crate::DbId;
use crate::DbKey;
use crate::DbKeyValue;

pub trait DbUserValue: Sized {
fn db_id(&self) -> Option<DbId>;
fn db_keys() -> Vec<DbKey>;
fn from_db_values(values: &[DbKeyValue]) -> Result<Self, DbError>;
fn from_db_element(element: &DbElement) -> Result<Self, DbError>;
fn to_db_values(&self) -> Vec<DbKeyValue>;
}
2 changes: 1 addition & 1 deletion agdb/src/query/query_result.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ impl<T: DbUserValue> TryInto<Vec<T>> for QueryResult {
self.elements
.iter()
.try_for_each(|e| -> Result<(), DbError> {
result.push(T::from_db_values(&e.values)?);
result.push(T::from_db_element(e)?);
Ok(())
})?;
Ok(result)
Expand Down
96 changes: 75 additions & 21 deletions agdb/tests/db_user_value_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ use agdb::DbElement;
use agdb::DbError;
use agdb::DbId;
use agdb::DbKey;
use agdb::DbKeyValue;
use agdb::DbUserValue;
use agdb::DbValue;
use agdb::QueryBuilder;
Expand Down Expand Up @@ -105,28 +104,31 @@ fn db_user_value() {
"custom_enum".into(),
];

let values: Vec<DbKeyValue> = vec![
("bytes", vec![1_u8]).into(),
("u64", 1_u64).into(),
("u32", 2_u64).into(),
("i64", -1_i64).into(),
("i32", -2_i64).into(),
("f64", 1.1_f64).into(),
("f32", 2.2_f32).into(),
("string", "hello").into(),
("vec_u64", vec![1_u64]).into(),
("vec_u32", vec![2_u32]).into(),
("vec_i64", vec![-1_i64]).into(),
("vec_i32", vec![-2_i32]).into(),
("vec_f64", vec![1.1_f64]).into(),
("vec_f32", vec![2.2_f32]).into(),
("vec_string", vec!["world"]).into(),
("custom_enum", Status::Active).into(),
];
let element = DbElement {
id: DbId(0),
values: vec![
("bytes", vec![1_u8]).into(),
("u64", 1_u64).into(),
("u32", 2_u64).into(),
("i64", -1_i64).into(),
("i32", -2_i64).into(),
("f64", 1.1_f64).into(),
("f32", 2.2_f32).into(),
("string", "hello").into(),
("vec_u64", vec![1_u64]).into(),
("vec_u32", vec![2_u32]).into(),
("vec_i64", vec![-1_i64]).into(),
("vec_i32", vec![-2_i32]).into(),
("vec_f64", vec![1.1_f64]).into(),
("vec_f32", vec![2.2_f32]).into(),
("vec_string", vec!["world"]).into(),
("custom_enum", Status::Active).into(),
],
};

assert_eq!(MyData::db_keys(), keys);
assert_eq!(&my_data.to_db_values(), &values);
assert_eq!(MyData::from_db_values(&values).unwrap(), my_data);
assert_eq!(&my_data.to_db_values(), &element.values);
assert_eq!(MyData::from_db_element(&element).unwrap(), my_data);
}

#[test]
Expand Down Expand Up @@ -234,3 +236,55 @@ fn select_values_custom() {

assert_eq!(db_values, vec![my_value.clone(), my_value]);
}

#[test]
fn db_user_value_with_id() {
#[derive(Debug, Clone, PartialEq, UserValue)]
struct MyValue {
db_id: Option<DbId>,
name: String,
age: u64,
}

let mut db = TestDb::new();
let my_value = MyValue {
db_id: None,
name: "my name".to_string(),
age: 20,
};

db.exec_mut(
QueryBuilder::insert()
.nodes()
.count(2)
.values_uniform(&my_value)
.query(),
2,
);

let db_values: Vec<MyValue> = db
.exec_result(
QueryBuilder::select()
.values(MyValue::db_keys())
.ids(vec![1, 2])
.query(),
)
.try_into()
.unwrap();

assert_eq!(
db_values,
vec![
MyValue {
db_id: Some(DbId(1)),
name: "my name".to_string(),
age: 20
},
MyValue {
db_id: Some(DbId(2)),
name: "my name".to_string(),
age: 20
}
]
);
}
74 changes: 51 additions & 23 deletions agdb_derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,68 +4,96 @@ use syn::parse_macro_input;
use syn::DeriveInput;

#[proc_macro_derive(UserValue)]
#[allow(clippy::manual_map)]
pub fn db_user_value_derive(item: TokenStream) -> TokenStream {
let input = parse_macro_input!(item as DeriveInput);
let name = input.ident;
let syn::Data::Struct(data) = input.data else { unimplemented!() };
let db_id = data
.fields
.iter()
.find_map(|f| {
if let Some(name) = &f.ident {
if name == "db_id" {
return Some(quote! {
self.db_id
});
}
}

None
})
.unwrap_or(quote! {
None
});
let mut counter: usize = 0;
let from_db_fields = data.fields.iter().filter_map(|f| {
let from_db_element = data.fields.iter().filter_map(|f| {
if let Some(name) = &f.ident {
let i = counter;
counter += 1;
return Some(quote! {
#name: values[#i].value.clone().try_into()?
});
if name == "db_id" {
return Some(quote! {
#name: Some(element.id)
});
} else {
let i = counter;
counter += 1;
return Some(quote! {
#name: element.values[#i].value.clone().try_into()?
});
}
}

None
});
let db_values = data.fields.iter().filter_map(|f| {
if let Some(name) = &f.ident {
let key = name.to_string();
if name != "db_id" {
let key = name.to_string();

return Some(quote! {
(#key, self.#name.clone()).into()
});
return Some(quote! {
(#key, self.#name.clone()).into()
});
}
}

None
});
let db_keys = data.fields.iter().filter_map(|f| {
if let Some(name) = &f.ident {
Some(name.to_string())
} else {
None
if name != "db_id" {
return Some(name.to_string());
}
}
None
});
let tokens = quote! {
impl agdb::DbUserValue for #name {
fn from_db_values(values: &[agdb::DbKeyValue]) -> Result<Self, agdb::DbError> {
fn db_id(&self) -> Option<DbId> {
#db_id
}

fn db_keys() -> Vec<agdb::DbKey> {
vec![#(#db_keys.into()),*]
}

fn from_db_element(element: &DbElement) -> Result<Self, DbError> {
Ok(Self {
#(#from_db_fields),*
#(#from_db_element),*
})
}

fn to_db_values(&self) -> Vec<agdb::DbKeyValue> {
vec![#(#db_values),*]
}

fn db_keys() -> Vec<agdb::DbKey> {
vec![#(#db_keys.into()),*]
}
}

impl TryFrom<agdb::QueryResult> for #name {
type Error = agdb::DbError;

fn try_from(value: agdb::QueryResult) -> Result<Self, Self::Error> {
#name::from_db_values(
&value
#name::from_db_element(
value
.elements
.get(0)
.ok_or(Self::Error::from("No element found"))?
.values,
)
}
}
Expand Down

0 comments on commit 635c010

Please sign in to comment.