Skip to content

Commit

Permalink
refactor(queries): update documentation for the query dsl
Browse files Browse the repository at this point in the history
Signed-off-by: ⭐️NINIKA⭐️ <[email protected]>
  • Loading branch information
DCNick3 committed Nov 18, 2024
1 parent 063786b commit 3965e8c
Show file tree
Hide file tree
Showing 11 changed files with 1,207 additions and 293 deletions.
5 changes: 3 additions & 2 deletions crates/iroha_core/src/query/cursor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ where

while let Some(item) = iter.next() {
if iter.peek().is_none() {
// do not clone the last item
batch_tuple.push(item.project(batch.into_iter()));
return QueryOutputBatchBoxTuple { tuple: batch_tuple };
}
Expand Down Expand Up @@ -141,7 +142,7 @@ where
}
}

/// A query output iterator that combines batching and type erasure.
/// A query output iterator that combines evaluating selectors, batching and type erasure.
pub struct ErasedQueryIterator {
inner: Box<dyn BatchedTrait + Send + Sync>,
}
Expand All @@ -153,7 +154,7 @@ impl Debug for ErasedQueryIterator {
}

impl ErasedQueryIterator {
/// Creates a new batched iterator. Boxes the inner iterator to erase its type.
/// Creates a new erased query iterator. Boxes the inner iterator to erase its type.
pub fn new<I>(iter: I, selector: SelectorTuple<I::Item>, batch_size: NonZeroU64) -> Self
where
I: ExactSizeIterator + Send + Sync + 'static,
Expand Down
43 changes: 0 additions & 43 deletions crates/iroha_data_model/src/query/builder/batch_downcast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,6 @@ where
}

macro_rules! typed_batch_tuple {
// (@repeat_none @recur) => {};
// (@repeat_none @recur $dummy1:ident $($rest:ident)*) => {
// None, typed_batch_tuple!(@repeat_none @recur $($rest)*)
// };
// (@repeat_none $($rest:ident)*) => {
// (typed_batch_tuple!(@repeat_none @recur $($rest)*))
// };
// (@first $first:tt $($rest:tt)*) => { $first };
(
$(
$name:ident($($ty_name:ident: $ty:ident),+);
Expand Down Expand Up @@ -160,38 +152,3 @@ typed_batch_tuple! {
TypedBatch8(t1: T1, t2: T2, t3: T3, t4: T4, t5: T5, t6: T6, t7: T7, t8: T8);
// who needs more than 8 values in their query, right?
}

// pub struct QueryIterator<T>
// where
// T: HasTypedBatchIter,
// {
// batch: T::TypedBatchIter,
// // TODO: include the query executor in here
// }
//
// impl<T> QueryIterator<T>
// where
// T: HasTypedBatchIter,
// {
// pub fn new(first_batch: QueryOutputBatchBoxTuple) -> Result<Self, TypedBatchDowncastError> {
// let batch = T::downcast(first_batch)?;
//
// Ok(Self { batch })
// }
// }
//
// impl<T> Iterator for QueryIterator<T>
// where
// T: HasTypedBatchIter,
// {
// type Item = Result<T, ()>;
//
// fn next(&mut self) -> Option<Self::Item> {
// if let Some(item) = self.batch.next() {
// return Some(Ok(item));
// }
//
// // TODO: request next batch
// None
// }
// }
3 changes: 3 additions & 0 deletions crates/iroha_data_model/src/query/builder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,9 @@ where
self.filter(predicate_builder(Default::default()))
}

/// Return only the fields of the results specified by the given closure.
///
/// You can select multiple fields by returning a tuple from the closure.
#[must_use]
pub fn select_with<B, O>(self, f: B) -> QueryBuilder<'a, E, Q, O::SelectedTuple>
where
Expand Down
10 changes: 10 additions & 0 deletions crates/iroha_data_model/src/query/dsl/compound_predicate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,26 @@ use crate::query::dsl::{
BaseProjector, EvaluatePredicate, HasProjection, HasPrototype, PredicateMarker,
};

/// A compound predicate that is be used to combine multiple predicates using logical operators.
#[derive_where(Debug, Eq, PartialEq, Clone; T::Projection)]
#[serde_where(T::Projection)]
#[derive(Decode, Encode, Deserialize, Serialize, IntoSchema)]
pub enum CompoundPredicate<T: HasProjection<PredicateMarker>> {
/// A predicate as-is
Atom(T::Projection),
/// A negation of a compound predicate.
Not(Box<CompoundPredicate<T>>),
/// A conjunction of multiple predicates.
And(Vec<CompoundPredicate<T>>),
/// A disjunction of multiple predicates.
Or(Vec<CompoundPredicate<T>>),
}

impl<T: HasProjection<PredicateMarker>> CompoundPredicate<T> {
/// A compound predicate that always evaluates to `true`.
pub const PASS: Self = Self::And(Vec::new());
/// A compound predicate that always evaluates to `false`.
pub const FAIL: Self = Self::Or(Vec::new());

// aliases for logical operations
/// Negate the predicate.
Expand All @@ -49,6 +57,7 @@ where
T: HasProjection<PredicateMarker>,
T::Projection: EvaluatePredicate<T>,
{
/// Evaluate the predicate on the given input.
pub fn applies(&self, input: &T) -> bool {
match self {
CompoundPredicate::Atom(projection) => projection.applies(input),
Expand Down Expand Up @@ -112,6 +121,7 @@ impl<T: HasProjection<PredicateMarker>> core::ops::BitOr for CompoundPredicate<T
}

impl<T: HasProjection<PredicateMarker>> CompoundPredicate<T> {
/// Build a new compound predicate using the provided closure.
pub fn build<F>(f: F) -> Self
where
T: HasPrototype,
Expand Down
98 changes: 94 additions & 4 deletions crates/iroha_data_model/src/query/dsl/mod.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,77 @@
// TODO
#![allow(unused, missing_docs)]
//! This module contains the domain-specific language (DSL) for constructing queries.
//!
//! # Prototypes and Projections
//!
//! Each data type that can be returned from a query (including nested types) has corresponding prototype and projection types.
//!
//! ## Purpose
//!
//! Prototypes exist to allow constructing queries with a type-safe API.
//! They do not get encoded in the query, existing only for the DSL purposes.
//! They are zero-sized objects that mimic the actual data model types by having the same files as them.
//! They allow constructing query predicates and selectors with a type-safe API.
//!
//! Projections are used as part of the representation of predicates and selectors.
//! Projections by themselves are used to select a subfield of the data model type (possibly deeply nested).
//!
//! ## Usage of prototypes
//!
//! The end-user of iroha gets exposed to prototypes when constructing query predicates and selectors.
//!
//! For both of these, they have to provide a function that takes a prototype and returns something representing the predicates or selector they want to construct.
//!
//! For predicates, they have to return the [`CompoundPredicate`] type, which is by itself a predicate.
//!
//! To get this [`CompoundPredicate`] they have to call one of the helper methods on any of the prototypes they've got access to.
//!
//! ```rust
//! # use iroha_data_model::{domain::DomainId, account::AccountId, query::dsl::CompoundPredicate};
//! let filter_by_domain_name = CompoundPredicate::<AccountId>::build(|account_id| account_id.domain.name.eq("wonderland"));
//! ```
//! For selectors, they have to return a type implementing the [1IntoSelectorTuple1] trait.
//!
//! It can be either a standalone prototype or a tuple of prototypes.
//!
//! ```rust
//! # use iroha_data_model::{domain::DomainId, account::AccountId, query::dsl::SelectorTuple};
//! let select_domain_name = SelectorTuple::<AccountId>::build(|account_id| account_id.domain.name);
//! let select_domain_name_and_signatory =
//! SelectorTuple::<AccountId>::build(|account_id| (account_id.domain.name, account_id.signatory));
//! ```
//!
//! ## Implementation details
//!
//! Projections types are shared between the filters and selectors by using the [`Projectable`] trait and its marker parameter.
//! For predicates the marker parameter is [`PredicateMarker`], for selectors it is [`SelectorMarker`].
//!
//! All projections have an `Atom` variant, representing the end of field traversal.
//! They also have variants for each field of the data model type, containing a projection for that field type inside.
//!
//! What is stored in the `Atom` variant is decided by the [`Projectable`] trait implementation for the type.
//!
//! # Object projectors
//!
//! To facilitate conversion of prototypes into actual predicates and selectors, there also exist object projectors implementing the [`ObjectProjector`] trait.
//!
//! They get passed as a type parameter to the prototype and describe the path over the type hierarchy that this particular prototype comes from.
//! An object projector accepts a projection or a selector of a more specific type and returns a projection or a selector of a more general type wrapped in a projection.
//!
//! For example, [`type_descriptions::AccountIdDomainProjector`] accepts a predicate or a selector on [`DomainId`](crate::domain::DomainId) and returns a predicate or a selector on [`AccountId`](crate::account::AccountId) by wrapping it with [`type_descriptions::AccountIdProjection`].
//! Notice the difference between projector and projection: projector is just zero-sized utility type, while projection is actually a predicate or a selector.
//!
//! A special kind of projector is a [`BaseProjector`]: it does not change the type of the projection, it just returns it as is.
//! It used to terminate the recursion in the projector hierarchy.
//!
//! # Compound predicates and selectors
//!
//! Normally a predicates has just a single condition on a single field.
//! [`CompoundPredicate`] allows composition of multiple predicates using logical operators.
//! This is the type that is actually

#[cfg(not(feature = "std"))]
use alloc::{format, string::String, vec::Vec};
use core::marker::PhantomData;

use derive_where::derive_where;

mod compound_predicate;
pub mod predicates;
mod selector_traits;
Expand All @@ -22,57 +87,80 @@ pub use self::{
};
use crate::query::QueryOutputBatchBox;

/// Trait implemented on all evaluable predicates for type `T`.
pub trait EvaluatePredicate<T: ?Sized> {
/// Evaluate the predicate on the given input.
fn applies(&self, input: &T) -> bool;
}

/// Trait that allows to get the predicate type for a given type.
pub trait HasPredicateAtom {
/// The type of the predicate for this type.
type Predicate: EvaluatePredicate<Self>;
}

/// Trait implemented on all evaluable selectors for type `T`.
pub trait EvaluateSelector<T: 'static> {
/// Select the field from each of the elements in the input and type-erase the result. Cloning version.
#[expect(single_use_lifetimes)] // FP, this the suggested change is not allowed on stable
fn project_clone<'a>(&self, batch: impl Iterator<Item = &'a T>) -> QueryOutputBatchBox;
/// Select the field from each of the elements in the input and type-erase the result.
fn project(&self, batch: impl Iterator<Item = T>) -> QueryOutputBatchBox;
}
// The IntoSchema derive is only needed for `PredicateMarker` to have `type_name`
// the actual value of these types is never encoded
/// A marker type to be used as parameter in the [`Projectable`] trait. This marker is used for predicates.
#[derive(IntoSchema)]
#[allow(missing_copy_implementations)]
pub struct PredicateMarker;
/// A marker type to be used as parameter in the [`Projectable`] trait. This marker is used for selectors.
#[derive(IntoSchema)]
#[allow(missing_copy_implementations)]
pub struct SelectorMarker;

/// A trait implemented on all types that want to get projection implemented on. It is used by the projection implementation to determine the atom type.
pub trait Projectable<Marker> {
/// The type of the atom for this type. Atom gets stored in the projection when this type ends up being the destination of the type hierarchy traversal.
type AtomType;
}

impl<T: HasPredicateAtom> Projectable<PredicateMarker> for T {
// Predicate is the atom for predicates
type AtomType = T::Predicate;
}

impl<T> Projectable<SelectorMarker> for T {
// Selectors don't store anything in the atom
type AtomType = ();
}

/// A trait allowing to get the projection for the type.
pub trait HasProjection<Marker>: Projectable<Marker> {
/// The type of the projection for this type.
type Projection;
/// Construct an atom projection for this type.
fn atom(atom: Self::AtomType) -> Self::Projection;
}

/// A trait allowing to get the prototype for the type.
pub trait HasPrototype {
/// The prototype type for this type.
type Prototype<Marker, Projector>: Default + Copy;
}

/// Describes how to convert a projection on `InputType` to a projection on `OutputType` by wrapping it in a projection.
pub trait ObjectProjector<Marker> {
/// The type of input projection.
type InputType: HasProjection<Marker>;
/// The type of output projection.
type OutputType: HasProjection<Marker>;

/// Convert the projection on [`Self::InputType`] to a projection on [`Self::OutputType`].
fn project(
projection: <Self::InputType as HasProjection<Marker>>::Projection,
) -> <Self::OutputType as HasProjection<Marker>>::Projection;

/// Construct a projection from an atom and convert it to a projection on [`Self::OutputType`].
fn wrap_atom(
atom: <Self::InputType as Projectable<Marker>>::AtomType,
) -> <Self::OutputType as HasProjection<Marker>>::Projection {
Expand All @@ -81,6 +169,7 @@ pub trait ObjectProjector<Marker> {
}
}

/// An [`ObjectProjector`] that does not change the type, serving as a base case for the recursion.
pub struct BaseProjector<Marker, T>(PhantomData<(Marker, T)>);

impl<Marker, T> ObjectProjector<Marker> for BaseProjector<Marker, T>
Expand All @@ -95,6 +184,7 @@ where
}
}

/// The prelude re-exports most commonly used traits, structs and macros from this crate.
pub mod prelude {
pub use super::{
predicates::prelude::*, type_descriptions::prelude::*, CompoundPredicate, SelectorTuple,
Expand Down
Loading

0 comments on commit 3965e8c

Please sign in to comment.