Skip to content

Commit

Permalink
refactor(queries): add query projection API to the builder
Browse files Browse the repository at this point in the history
Signed-off-by: ⭐️NINIKA⭐️ <[email protected]>
  • Loading branch information
DCNick3 committed Nov 14, 2024
1 parent 7840250 commit 063786b
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 10 deletions.
38 changes: 36 additions & 2 deletions crates/iroha_data_model/src/query/builder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ use serde::{Deserialize, Serialize};

use crate::query::{
builder::batch_downcast::HasTypedBatchIter,
dsl::{BaseProjector, CompoundPredicate, HasPrototype, PredicateMarker, SelectorTuple},
dsl::{
BaseProjector, CompoundPredicate, HasPrototype, IntoSelectorTuple, PredicateMarker,
SelectorMarker, SelectorTuple,
},
parameters::{FetchSize, Pagination, QueryParams, Sorting},
Query, QueryBox, QueryOutputBatchBoxTuple, QueryWithFilter, QueryWithParams, SingularQueryBox,
SingularQueryOutputBox,
Expand Down Expand Up @@ -105,6 +108,7 @@ where
pagination: Pagination,
sorting: Sorting,
fetch_size: FetchSize,
// NOTE: T is a phantom type used to denote the selected tuple in `selector`
phantom: PhantomData<T>,
}

Expand All @@ -127,7 +131,7 @@ where
}
}

impl<E, Q, T> QueryBuilder<'_, E, Q, T>
impl<'a, E, Q, T> QueryBuilder<'a, E, Q, T>
where
Q: Query,
{
Expand Down Expand Up @@ -163,6 +167,36 @@ where
self.filter(predicate_builder(Default::default()))
}

#[must_use]
pub fn select_with<B, O>(self, f: B) -> QueryBuilder<'a, E, Q, O::SelectedTuple>
where
Q::Item: HasPrototype,
B: FnOnce(
<Q::Item as HasPrototype>::Prototype<
SelectorMarker,
BaseProjector<SelectorMarker, Q::Item>,
>,
) -> O,
<Q::Item as HasPrototype>::Prototype<
SelectorMarker,
BaseProjector<SelectorMarker, Q::Item>,
>: Default,
O: IntoSelectorTuple<SelectingType = Q::Item>,
{
let new_selector = f(Default::default()).into_selector_tuple();

QueryBuilder {
query_executor: self.query_executor,
query: self.query,
filter: self.filter,
selector: new_selector,
pagination: self.pagination,
sorting: self.sorting,
fetch_size: self.fetch_size,
phantom: PhantomData,
}
}

/// Sort the results according to the specified sorting.
#[must_use]
pub fn with_sorting(self, sorting: Sorting) -> Self {
Expand Down
7 changes: 6 additions & 1 deletion crates/iroha_data_model/src/query/dsl/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,17 @@ use derive_where::derive_where;

mod compound_predicate;
pub mod predicates;
mod selector_traits;
mod selector_tuple;
pub mod type_descriptions;

use iroha_schema::IntoSchema;

pub use self::{compound_predicate::CompoundPredicate, selector_tuple::SelectorTuple};
pub use self::{
compound_predicate::CompoundPredicate,
selector_traits::{IntoSelector, IntoSelectorTuple},
selector_tuple::SelectorTuple,
};
use crate::query::QueryOutputBatchBox;

pub trait EvaluatePredicate<T: ?Sized> {
Expand Down
63 changes: 63 additions & 0 deletions crates/iroha_data_model/src/query/dsl/selector_traits.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#[cfg(not(feature = "std"))]
use alloc::{vec, vec::Vec};

use crate::{
prelude::SelectorTuple,
query::dsl::{HasProjection, SelectorMarker},
};

pub trait IntoSelector {
type SelectingType: HasProjection<SelectorMarker, AtomType = ()>;
type SelectedType;
fn into_selector(self) -> <Self::SelectingType as HasProjection<SelectorMarker>>::Projection;
}

pub trait IntoSelectorTuple {
type SelectingType: HasProjection<SelectorMarker, AtomType = ()>;
type SelectedTuple;
fn into_selector_tuple(self) -> SelectorTuple<Self::SelectingType>;
}

impl<T: IntoSelector> IntoSelectorTuple for T {
type SelectingType = T::SelectingType;
type SelectedTuple = T::SelectedType;

fn into_selector_tuple(self) -> SelectorTuple<Self::SelectingType> {
SelectorTuple::new(vec![self.into_selector()])
}
}

impl<T1: IntoSelector> IntoSelectorTuple for (T1,) {
type SelectingType = T1::SelectingType;
type SelectedTuple = (T1::SelectedType,);

fn into_selector_tuple(self) -> SelectorTuple<Self::SelectingType> {
SelectorTuple::new(vec![self.0.into_selector()])
}
}

macro_rules! impl_into_selector_tuple {
($t1_name:ident, $($t_name:ident),*) => {
impl<$t1_name: IntoSelector, $($t_name: IntoSelector<SelectingType = T1::SelectingType>),*> IntoSelectorTuple for ($t1_name, $($t_name),*)
{
type SelectingType = $t1_name::SelectingType;
type SelectedTuple = ($t1_name::SelectedType, $($t_name::SelectedType),*);

#[allow(non_snake_case)] // we re-use the type names as variable names to not require the user to come up with new ones in the macro invocation
fn into_selector_tuple(self) -> SelectorTuple<Self::SelectingType> {
let ($t1_name, $($t_name),*) = self;
SelectorTuple::new(vec![
$t1_name.into_selector(),
$($t_name.into_selector(),)*
])
}
}
};
}
impl_into_selector_tuple!(T1, T2);
impl_into_selector_tuple!(T1, T2, T3);
impl_into_selector_tuple!(T1, T2, T3, T4);
impl_into_selector_tuple!(T1, T2, T3, T4, T5);
impl_into_selector_tuple!(T1, T2, T3, T4, T5, T6);
impl_into_selector_tuple!(T1, T2, T3, T4, T5, T6, T7);
impl_into_selector_tuple!(T1, T2, T3, T4, T5, T6, T7, T8);
4 changes: 4 additions & 0 deletions crates/iroha_data_model/src/query/dsl/selector_tuple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ use crate::query::{
pub struct SelectorTuple<T: HasProjection<SelectorMarker, AtomType = ()>>(Vec<T::Projection>);

impl<T: HasProjection<SelectorMarker, AtomType = ()>> SelectorTuple<T> {
pub fn new(selectors: Vec<T::Projection>) -> Self {
Self(selectors)
}

pub fn iter(&self) -> impl Iterator<Item = &T::Projection> {
self.0.iter()
}
Expand Down
17 changes: 15 additions & 2 deletions crates/iroha_data_model/src/query/dsl/type_descriptions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ use serde::{Deserialize, Serialize};

// used in the macro
use crate::query::dsl::{
EvaluatePredicate, EvaluateSelector, HasProjection, HasPrototype, ObjectProjector,
PredicateMarker, Projectable, SelectorMarker,
EvaluatePredicate, EvaluateSelector, HasProjection, HasPrototype, IntoSelector,
ObjectProjector, PredicateMarker, Projectable, SelectorMarker,
};
use crate::{
account::{Account, AccountId},
Expand Down Expand Up @@ -199,6 +199,19 @@ macro_rules! type_descriptions {
{
type Prototype<Marker, Projector> = $prototype_name<Marker, Projector>;
}

impl<Projector> IntoSelector for $prototype_name<SelectorMarker, Projector>
where
Projector: ObjectProjector<SelectorMarker, InputType = $ty>,
Projector::OutputType: HasProjection<SelectorMarker, AtomType = ()>,
{
type SelectingType = Projector::OutputType;
type SelectedType = Projector::InputType;

fn into_selector(self) -> <Projector::OutputType as HasProjection<SelectorMarker>>::Projection {
Projector::wrap_atom(())
}
}
)*

mod projections {
Expand Down
34 changes: 29 additions & 5 deletions wasm/samples/smart_contract_can_filter_queries/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ fn main(host: Iroha, _context: Context) {
// genesis registers some more asset definitions, but we apply a filter to find only the ones from the `looking_glass` domain
let cursor = host
.query(FindAssetsDefinitions)
.filter_with(|asset_definition| asset_definition.id.domain.eq(domain_id))
.filter_with(|asset_definition| asset_definition.id.domain.eq(domain_id.clone()))
.execute()
.dbg_unwrap();

Expand All @@ -52,8 +52,32 @@ fn main(host: Iroha, _context: Context) {
asset_definition_ids.insert(asset_definition.id().clone());
}

assert_eq!(
asset_definition_ids,
[time_id, space_id].into_iter().collect()
);
let expected_asset_definition_ids = [time_id.clone(), space_id.clone()].into_iter().collect();

assert_eq!(asset_definition_ids, expected_asset_definition_ids);

// do the same as above, but utilizing server-side projections
let asset_definition_ids = host
.query(FindAssetsDefinitions)
.filter_with(|asset_definition| asset_definition.id.domain.eq(domain_id.clone()))
.select_with(|asset_definition| asset_definition.id)
.execute()
.dbg_unwrap()
.map(|v| v.dbg_unwrap())
.collect::<BTreeSet<_>>();

assert_eq!(asset_definition_ids, expected_asset_definition_ids);

// do the same as above, but passing the asset definition id as a 2-tuple
let asset_definition_ids = host
.query(FindAssetsDefinitions)
.filter_with(|asset_definition| asset_definition.id.domain.eq(domain_id))
.select_with(|asset_definition| (asset_definition.id.domain, asset_definition.id.name))
.execute()
.dbg_unwrap()
.map(|v| v.dbg_unwrap())
.map(|(domain, name)| AssetDefinitionId::new(domain, name))
.collect::<BTreeSet<_>>();

assert_eq!(asset_definition_ids, expected_asset_definition_ids);
}

0 comments on commit 063786b

Please sign in to comment.