From c0557c1aad111381275d87a7d50513ac50a74815 Mon Sep 17 00:00:00 2001 From: michaelvlach Date: Sat, 12 Aug 2023 17:29:31 +0200 Subject: [PATCH 1/7] add insert elements --- agdb/src/query_builder/insert.rs | 21 +++++++++++++ agdb/tests/db_user_value_test.rs | 53 ++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/agdb/src/query_builder/insert.rs b/agdb/src/query_builder/insert.rs index 763a70d4b..ca19a83ad 100644 --- a/agdb/src/query_builder/insert.rs +++ b/agdb/src/query_builder/insert.rs @@ -2,6 +2,7 @@ use super::insert_aliases::InsertAliases; use super::insert_edge::InsertEdges; use super::insert_nodes::InsertNodes; use super::insert_values::InsertValues; +use super::insert_values::InsertValuesIds; use crate::query::insert_aliases_query::InsertAliasesQuery; use crate::query::insert_edges_query::InsertEdgesQuery; use crate::query::insert_nodes_query::InsertNodesQuery; @@ -11,6 +12,8 @@ use crate::query::query_ids::QueryIds; use crate::query::query_values::MultiValues; use crate::query::query_values::QueryValues; use crate::query::query_values::SingleValues; +use crate::DbId; +use crate::DbUserValue; /// Insert builder for inserting various data /// into the database. @@ -54,6 +57,24 @@ impl Insert { }) } + pub fn element(self, e: &T) -> InsertValuesIds { + InsertValuesIds(InsertValuesQuery { + ids: e.db_id().unwrap_or_default().into(), + values: QueryValues::Multi(vec![e.to_db_values()]), + }) + } + + pub fn elements(self, elems: &[T]) -> InsertValuesIds { + InsertValuesIds(InsertValuesQuery { + ids: elems + .iter() + .map(|e| e.db_id().unwrap_or_default().into()) + .collect::>() + .into(), + values: QueryValues::Multi(elems.iter().map(|e| e.to_db_values()).collect()), + }) + } + /// Inserts nodes into the database: /// /// Options: diff --git a/agdb/tests/db_user_value_test.rs b/agdb/tests/db_user_value_test.rs index 73e047222..cffa70d5a 100644 --- a/agdb/tests/db_user_value_test.rs +++ b/agdb/tests/db_user_value_test.rs @@ -288,3 +288,56 @@ fn db_user_value_with_id() { ] ); } + +#[test] +fn insert_user_value_with_id() { + #[derive(Debug, Clone, PartialEq, UserValue)] + struct MyValue { + db_id: Option, + 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 mut db_values: Vec = db + .exec_result( + QueryBuilder::select() + .values(MyValue::db_keys()) + .ids(vec![1, 2]) + .query(), + ) + .try_into() + .unwrap(); + + db_values[0].age = 30; + db_values[1].age = 40; + + db.exec_mut(QueryBuilder::insert().elements(&db_values).query(), 4); + + let other: Vec = db + .exec_result( + QueryBuilder::select() + .values(MyValue::db_keys()) + .ids(vec![1, 2]) + .query(), + ) + .try_into() + .unwrap(); + + assert_eq!(other, db_values); +} From 9d122772b2c78cd6e61e85e5a3e84efeb855b1e5 Mon Sep 17 00:00:00 2001 From: michaelvlach Date: Mon, 14 Aug 2023 21:57:21 +0200 Subject: [PATCH 2/7] fix query insert elements --- agdb/src/query_builder/insert.rs | 25 +++++++++++++++---------- agdb/tests/db_user_value_test.rs | 1 + 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/agdb/src/query_builder/insert.rs b/agdb/src/query_builder/insert.rs index ca19a83ad..3155652cc 100644 --- a/agdb/src/query_builder/insert.rs +++ b/agdb/src/query_builder/insert.rs @@ -12,7 +12,6 @@ use crate::query::query_ids::QueryIds; use crate::query::query_values::MultiValues; use crate::query::query_values::QueryValues; use crate::query::query_values::SingleValues; -use crate::DbId; use crate::DbUserValue; /// Insert builder for inserting various data @@ -57,21 +56,27 @@ impl Insert { }) } - pub fn element(self, e: &T) -> InsertValuesIds { + pub fn element(self, elem: &T) -> InsertValuesIds { InsertValuesIds(InsertValuesQuery { - ids: e.db_id().unwrap_or_default().into(), - values: QueryValues::Multi(vec![e.to_db_values()]), + ids: QueryIds::Ids(vec![elem.db_id().unwrap_or_default().into()]), + values: QueryValues::Multi(vec![elem.to_db_values()]), }) } pub fn elements(self, elems: &[T]) -> InsertValuesIds { + let mut ids = vec![]; + let mut values = vec![]; + ids.reserve(elems.len()); + values.reserve(elems.len()); + + elems.iter().for_each(|v| { + ids.push(v.db_id().unwrap_or_default().into()); + values.push(v.to_db_values()); + }); + InsertValuesIds(InsertValuesQuery { - ids: elems - .iter() - .map(|e| e.db_id().unwrap_or_default().into()) - .collect::>() - .into(), - values: QueryValues::Multi(elems.iter().map(|e| e.to_db_values()).collect()), + ids: QueryIds::Ids(ids), + values: QueryValues::Multi(values), }) } diff --git a/agdb/tests/db_user_value_test.rs b/agdb/tests/db_user_value_test.rs index cffa70d5a..03c8de8ef 100644 --- a/agdb/tests/db_user_value_test.rs +++ b/agdb/tests/db_user_value_test.rs @@ -327,6 +327,7 @@ fn insert_user_value_with_id() { db_values[0].age = 30; db_values[1].age = 40; + db.exec_mut(QueryBuilder::insert().element(&my_value).query(), 4); db.exec_mut(QueryBuilder::insert().elements(&db_values).query(), 4); let other: Vec = db From d21cc3bd0e20e4caded8d7a489ebf7e38f777460 Mon Sep 17 00:00:00 2001 From: michaelvlach Date: Mon, 14 Aug 2023 22:04:54 +0200 Subject: [PATCH 3/7] Update db_user_value_test.rs --- agdb/tests/db_user_value_test.rs | 59 +++++++++++++++++++++++++++++--- 1 file changed, 55 insertions(+), 4 deletions(-) diff --git a/agdb/tests/db_user_value_test.rs b/agdb/tests/db_user_value_test.rs index 03c8de8ef..68402cb2d 100644 --- a/agdb/tests/db_user_value_test.rs +++ b/agdb/tests/db_user_value_test.rs @@ -191,7 +191,7 @@ fn insert_node_values_uniform_custom() { } #[test] -fn select_values_custom() { +fn select_custom_value_keys() { #[derive(Debug, Clone, PartialEq, UserValue)] struct MyValue { name: String, @@ -238,7 +238,7 @@ fn select_values_custom() { } #[test] -fn db_user_value_with_id() { +fn select_custom_value_with_id() { #[derive(Debug, Clone, PartialEq, UserValue)] struct MyValue { db_id: Option, @@ -290,7 +290,59 @@ fn db_user_value_with_id() { } #[test] -fn insert_user_value_with_id() { +fn insert_single_element() { + #[derive(Debug, Clone, PartialEq, UserValue)] + struct MyValue { + db_id: Option, + 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(1) + .values_uniform(&my_value) + .query(), + 1, + ); + + let mut db_value: MyValue = db + .exec_result( + QueryBuilder::select() + .values(MyValue::db_keys()) + .ids(1) + .query(), + ) + .try_into() + .unwrap(); + + db_value.age = 30; + + db.exec_mut(QueryBuilder::insert().element(&db_value).query(), 2); + + let other: MyValue = db + .exec_result( + QueryBuilder::select() + .values(MyValue::db_keys()) + .ids(1) + .query(), + ) + .try_into() + .unwrap(); + + assert_eq!(other, db_value); +} + +#[test] +fn insert_multiple_elements() { #[derive(Debug, Clone, PartialEq, UserValue)] struct MyValue { db_id: Option, @@ -327,7 +379,6 @@ fn insert_user_value_with_id() { db_values[0].age = 30; db_values[1].age = 40; - db.exec_mut(QueryBuilder::insert().element(&my_value).query(), 4); db.exec_mut(QueryBuilder::insert().elements(&db_values).query(), 4); let other: Vec = db From 7ac3d31d0a8a98d75678ea21e0c30030890c47bc Mon Sep 17 00:00:00 2001 From: michaelvlach Date: Mon, 14 Aug 2023 22:26:22 +0200 Subject: [PATCH 4/7] add docs to query builder --- agdb/src/query_builder.rs | 7 ++++++- agdb/src/query_builder/insert.rs | 10 ++++++++++ agdb/tests/db_user_value_test.rs | 2 +- agdb_derive/src/lib.rs | 5 ++++- 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/agdb/src/query_builder.rs b/agdb/src/query_builder.rs index 0c25dd144..0438276c7 100644 --- a/agdb/src/query_builder.rs +++ b/agdb/src/query_builder.rs @@ -41,12 +41,17 @@ impl QueryBuilder { /// Options: /// /// ``` - /// use agdb::QueryBuilder; + /// use agdb::{DbId, QueryBuilder, UserValue}; + /// + /// #[derive(UserValue)] + /// struct MyValue { db_id: Option, key: String } /// /// QueryBuilder::insert().nodes(); /// QueryBuilder::insert().edges(); /// QueryBuilder::insert().aliases("a"); /// QueryBuilder::insert().aliases(vec!["a", "b"]); + /// QueryBuilder::insert().element(&MyValue { db_id: Some(DbId(1)), key: "a".to_string(), }); + /// QueryBuilder::insert().elements(&[MyValue { db_id: Some(DbId(1)), key: "a".to_string(), }]); /// QueryBuilder::insert().values(vec![vec![("k", 1).into()]]); /// QueryBuilder::insert().values_uniform(vec![("k", 1).into()]); /// ``` diff --git a/agdb/src/query_builder/insert.rs b/agdb/src/query_builder/insert.rs index 3155652cc..fff1ba0ee 100644 --- a/agdb/src/query_builder/insert.rs +++ b/agdb/src/query_builder/insert.rs @@ -56,6 +56,11 @@ impl Insert { }) } + /// Inserts `elem` into the database. The `elem` + /// must implement (or derive) `DbUserValue` that will + /// provide the `DbId` to be inserted to and conversion + /// to the values. The ids must be `Some` and valid + /// int the database. pub fn element(self, elem: &T) -> InsertValuesIds { InsertValuesIds(InsertValuesQuery { ids: QueryIds::Ids(vec![elem.db_id().unwrap_or_default().into()]), @@ -63,6 +68,11 @@ impl Insert { }) } + /// Inserts the `elems` into the database. Each `elem` + /// must implement (or derive) `DbUserValue` that will + /// provide the `DbId` to be inserted to and conversion + /// to the values. The ids must be `Some` and valid + /// int the database. pub fn elements(self, elems: &[T]) -> InsertValuesIds { let mut ids = vec![]; let mut values = vec![]; diff --git a/agdb/tests/db_user_value_test.rs b/agdb/tests/db_user_value_test.rs index 68402cb2d..11bdfafbe 100644 --- a/agdb/tests/db_user_value_test.rs +++ b/agdb/tests/db_user_value_test.rs @@ -7,7 +7,7 @@ use agdb::DbKey; use agdb::DbUserValue; use agdb::DbValue; use agdb::QueryBuilder; -use agdb_derive::UserValue; +use agdb::UserValue; use test_db::TestDb; #[derive(Default, Debug, Clone, PartialEq)] diff --git a/agdb_derive/src/lib.rs b/agdb_derive/src/lib.rs index 1f7feafd4..5eee567e9 100644 --- a/agdb_derive/src/lib.rs +++ b/agdb_derive/src/lib.rs @@ -65,6 +65,8 @@ pub fn db_user_value_derive(item: TokenStream) -> TokenStream { None }); let tokens = quote! { + use agdb::DbUserValue; + impl agdb::DbUserValue for #name { fn db_id(&self) -> Option { #db_id @@ -74,7 +76,7 @@ pub fn db_user_value_derive(item: TokenStream) -> TokenStream { vec![#(#db_keys.into()),*] } - fn from_db_element(element: &DbElement) -> Result { + fn from_db_element(element: &agdb::DbElement) -> Result { Ok(Self { #(#from_db_element),* }) @@ -89,6 +91,7 @@ pub fn db_user_value_derive(item: TokenStream) -> TokenStream { type Error = agdb::DbError; fn try_from(value: agdb::QueryResult) -> Result { + #name::from_db_element( value .elements From 92c9ac8c0aee424b739cf100aad9065f7dc85ec9 Mon Sep 17 00:00:00 2001 From: michaelvlach Date: Mon, 14 Aug 2023 22:28:30 +0200 Subject: [PATCH 5/7] Update db_user_value_test.rs --- agdb/tests/db_user_value_test.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/agdb/tests/db_user_value_test.rs b/agdb/tests/db_user_value_test.rs index 11bdfafbe..3d3014f05 100644 --- a/agdb/tests/db_user_value_test.rs +++ b/agdb/tests/db_user_value_test.rs @@ -4,7 +4,6 @@ use agdb::DbElement; use agdb::DbError; use agdb::DbId; use agdb::DbKey; -use agdb::DbUserValue; use agdb::DbValue; use agdb::QueryBuilder; use agdb::UserValue; From 81c28e7736b5fe34c61acdcffcc7490ee4a5222f Mon Sep 17 00:00:00 2001 From: michaelvlach Date: Mon, 14 Aug 2023 22:47:13 +0200 Subject: [PATCH 6/7] add more docs --- agdb/src/db/db_error.rs | 6 ++++++ agdb/src/db/db_f64.rs | 1 + agdb/src/db/db_user_value.rs | 41 ++++++++++++++++++++++++++++++++++++ agdb_derive/src/lib.rs | 4 +--- 4 files changed, 49 insertions(+), 3 deletions(-) diff --git a/agdb/src/db/db_error.rs b/agdb/src/db/db_error.rs index b40e38b19..bf1aab943 100644 --- a/agdb/src/db/db_error.rs +++ b/agdb/src/db/db_error.rs @@ -13,12 +13,18 @@ use std::string::FromUtf8Error; /// loading a database, writing data etc. #[derive(Debug)] pub struct DbError { + /// Error description pub description: String, + + /// Optional error that caused this error pub cause: Option>, + + /// Location where the error originated in the sources pub source_location: Location<'static>, } impl DbError { + /// Sets the `cause` of this error to `error`. pub fn caused_by(mut self, error: Self) -> Self { self.cause = Some(Box::new(error)); diff --git a/agdb/src/db/db_f64.rs b/agdb/src/db/db_f64.rs index 986e3e26f..28f3a8636 100644 --- a/agdb/src/db/db_f64.rs +++ b/agdb/src/db/db_f64.rs @@ -15,6 +15,7 @@ use std::hash::Hasher; pub struct DbF64(f64); impl DbF64 { + /// Extracts the underlying `f64` value. #[allow(clippy::wrong_self_convention)] pub fn to_f64(&self) -> f64 { self.0 diff --git a/agdb/src/db/db_user_value.rs b/agdb/src/db/db_user_value.rs index 029e682c4..69a253728 100644 --- a/agdb/src/db/db_user_value.rs +++ b/agdb/src/db/db_user_value.rs @@ -4,9 +4,50 @@ use crate::DbId; use crate::DbKey; use crate::DbKeyValue; +/// Trait that allows use of user defined values +/// directly by facilitating translation from/to +/// database primitive types. The names of fields +/// becomes keys of type `String`. Values must be +/// of types that are convertible to/from database +/// primitive types. +/// +/// The special type `db_id` of type `Option` +/// can be used to allow direct insertion and select +/// of a user value tied with a particular database +/// element. The field `db_id` is skipped in the derive +/// macro and used only for the `db_id()` method. +/// +/// The trait is derivable using `agdb::UserValue` +/// derive macro. When implementing it by hand you +/// can apply additional logic, use different keys +/// and/or their type, skip fields or change their +/// types etc. +/// +/// Examples: +/// +/// ``` +/// use agdb::{DbId, UserValue}; +/// +/// #[derive(UserValue)] +/// struct MyValueNoId { key: String, another_key: i32 } // "key": "value", "another_key": 10_i64 +/// +/// #[derive(UserValue)] +/// struct MyValue { db_id: Option, key: String } // "key": "value" +/// ``` pub trait DbUserValue: Sized { + /// Returns the database id if present. fn db_id(&self) -> Option; + + /// Returns the list of database keys of + /// this type. fn db_keys() -> Vec; + + /// Constructs the user value from the `element` + /// extracting the values from element `values` + /// and the `db_id` if that field is present. fn from_db_element(element: &DbElement) -> Result; + + /// Converts the fields (skipping `db_id` if present) + /// to the database key-values. fn to_db_values(&self) -> Vec; } diff --git a/agdb_derive/src/lib.rs b/agdb_derive/src/lib.rs index 5eee567e9..2d7896345 100644 --- a/agdb_derive/src/lib.rs +++ b/agdb_derive/src/lib.rs @@ -65,8 +65,6 @@ pub fn db_user_value_derive(item: TokenStream) -> TokenStream { None }); let tokens = quote! { - use agdb::DbUserValue; - impl agdb::DbUserValue for #name { fn db_id(&self) -> Option { #db_id @@ -91,7 +89,7 @@ pub fn db_user_value_derive(item: TokenStream) -> TokenStream { type Error = agdb::DbError; fn try_from(value: agdb::QueryResult) -> Result { - + use agdb::DbUserValue; #name::from_db_element( value .elements From 52e7a967274de0681c969567a666896c45340695 Mon Sep 17 00:00:00 2001 From: michaelvlach Date: Mon, 14 Aug 2023 22:47:52 +0200 Subject: [PATCH 7/7] Update db_user_value_test.rs --- agdb/tests/db_user_value_test.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/agdb/tests/db_user_value_test.rs b/agdb/tests/db_user_value_test.rs index 3d3014f05..11bdfafbe 100644 --- a/agdb/tests/db_user_value_test.rs +++ b/agdb/tests/db_user_value_test.rs @@ -4,6 +4,7 @@ use agdb::DbElement; use agdb::DbError; use agdb::DbId; use agdb::DbKey; +use agdb::DbUserValue; use agdb::DbValue; use agdb::QueryBuilder; use agdb::UserValue;