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

[query] Use user values with id in queries #670 #673

Merged
merged 8 commits into from
Aug 14, 2023
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
6 changes: 6 additions & 0 deletions agdb/src/db/db_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Box<DbError>>,

/// 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));

Expand Down
1 change: 1 addition & 0 deletions agdb/src/db/db_f64.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
41 changes: 41 additions & 0 deletions agdb/src/db/db_user_value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<DbId>`
/// 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<DbId>, key: String } // "key": "value"
/// ```
pub trait DbUserValue: Sized {
/// Returns the database id if present.
fn db_id(&self) -> Option<DbId>;

/// Returns the list of database keys of
/// this type.
fn db_keys() -> Vec<DbKey>;

/// 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<Self, DbError>;

/// Converts the fields (skipping `db_id` if present)
/// to the database key-values.
fn to_db_values(&self) -> Vec<DbKeyValue>;
}
7 changes: 6 additions & 1 deletion agdb/src/query_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,17 @@ impl QueryBuilder {
/// Options:
///
/// ```
/// use agdb::QueryBuilder;
/// use agdb::{DbId, QueryBuilder, UserValue};
///
/// #[derive(UserValue)]
/// struct MyValue { db_id: Option<DbId>, 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()]);
/// ```
Expand Down
36 changes: 36 additions & 0 deletions agdb/src/query_builder/insert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -11,6 +12,7 @@ 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::DbUserValue;

/// Insert builder for inserting various data
/// into the database.
Expand Down Expand Up @@ -54,6 +56,40 @@ 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<T: DbUserValue>(self, elem: &T) -> InsertValuesIds {
InsertValuesIds(InsertValuesQuery {
ids: QueryIds::Ids(vec![elem.db_id().unwrap_or_default().into()]),
values: QueryValues::Multi(vec![elem.to_db_values()]),
})
}

/// 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<T: DbUserValue>(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: QueryIds::Ids(ids),
values: QueryValues::Multi(values),
})
}

/// Inserts nodes into the database:
///
/// Options:
Expand Down
111 changes: 108 additions & 3 deletions agdb/tests/db_user_value_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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<DbId>,
Expand Down Expand Up @@ -288,3 +288,108 @@ fn db_user_value_with_id() {
]
);
}

#[test]
fn insert_single_element() {
#[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(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<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 mut db_values: Vec<MyValue> = 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<MyValue> = db
.exec_result(
QueryBuilder::select()
.values(MyValue::db_keys())
.ids(vec![1, 2])
.query(),
)
.try_into()
.unwrap();

assert_eq!(other, db_values);
}
3 changes: 2 additions & 1 deletion agdb_derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ pub fn db_user_value_derive(item: TokenStream) -> TokenStream {
vec![#(#db_keys.into()),*]
}

fn from_db_element(element: &DbElement) -> Result<Self, DbError> {
fn from_db_element(element: &agdb::DbElement) -> Result<Self, agdb::DbError> {
Ok(Self {
#(#from_db_element),*
})
Expand All @@ -89,6 +89,7 @@ pub fn db_user_value_derive(item: TokenStream) -> TokenStream {
type Error = agdb::DbError;

fn try_from(value: agdb::QueryResult) -> Result<Self, Self::Error> {
use agdb::DbUserValue;
#name::from_db_element(
value
.elements
Expand Down