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

feat!: Add traits for iterable and non-iterable queries #4638

Merged
merged 5 commits into from
May 28, 2024
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
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ tracing-subscriber = { workspace = true, features = ["fmt", "ansi"] }
tracing-flame = "0.2.0"
once_cell = { workspace = true }

trybuild = { workspace = true }

[build-dependencies]
eyre = { workspace = true }
iroha_wasm_builder = { workspace = true }
Expand Down
29 changes: 18 additions & 11 deletions client/src/query_builder.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::fmt::Debug;

use iroha_data_model::query::QueryOutputBox;
use iroha_data_model::query::{IterableQuery, QueryOutputBox};

use crate::{
client::{Client, QueryOutput, QueryResult},
Expand Down Expand Up @@ -33,6 +33,23 @@ where
}
}

pub fn execute(self) -> QueryResult<<R::Output as QueryOutput>::Target> {
self.client.request_with_filter_and_pagination_and_sorting(
self.request,
self.pagination,
self.fetch_size,
self.sorting,
self.filter,
)
}
}

impl<R> QueryRequestBuilder<'_, R>
where
R: IterableQuery + Debug,
R::Output: QueryOutput,
<R::Output as TryFrom<QueryOutputBox>>::Error: Into<eyre::Error>,
{
pub fn with_filter(mut self, filter: PredicateBox) -> Self {
self.filter = filter;
self
Expand All @@ -52,14 +69,4 @@ where
self.fetch_size = fetch_size;
self
}

pub fn execute(self) -> QueryResult<<R::Output as QueryOutput>::Target> {
self.client.request_with_filter_and_pagination_and_sorting(
self.request,
self.pagination,
self.fetch_size,
self.sorting,
self.filter,
)
}
}
8 changes: 8 additions & 0 deletions client/tests/ui.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#![cfg(not(coverage))]
use trybuild::TestCases;

#[test]
fn ui() {
let test_cases = TestCases::new();
test_cases.compile_fail("tests/ui_fail/*.rs");
}
19 changes: 19 additions & 0 deletions client/tests/ui_fail/cant_filter_singular_query.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use iroha_client::{
client::{self, Client},
config::Config,
data_model::query::predicate::{string, value, PredicateBox},
};

fn main() {
let config = Config::load("../configs/swarm/client.toml").unwrap();

let client = Client::new(config);

let result = client
.build_query(client::permission::permission_schema())
.with_filter(PredicateBox::new(
value::QueryOutputPredicate::Identifiable(string::StringPredicate::starts_with("xor_")),
))
.execute()
.unwrap();
}
24 changes: 24 additions & 0 deletions client/tests/ui_fail/cant_filter_singular_query.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
error[E0599]: the method `with_filter` exists for struct `QueryRequestBuilder<'_, FindPermissionSchema>`, but its trait bounds were not satisfied
--> tests/ui_fail/cant_filter_singular_query.rs:14:10
|
12 | let result = client
| __________________-
13 | | .build_query(client::permission::permission_schema())
14 | | .with_filter(PredicateBox::new(
| | -^^^^^^^^^^^ method cannot be called on `QueryRequestBuilder<'_, FindPermissionSchema>` due to unsatisfied trait bounds
| |_________|
|
|
::: $WORKSPACE/data_model/src/query/mod.rs
|
| / queries! {
| | /// Finds all registered permission tokens
| | #[derive(Copy, Display)]
| | #[ffi_type]
... |
| | }
| | }
| |_____- doesn't satisfy `_: IterableQuery`
|
= note: the following trait bounds were not satisfied:
`iroha_client::iroha_data_model::prelude::FindPermissionSchema: IterableQuery`
50 changes: 45 additions & 5 deletions data_model/src/query/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,15 @@ pub trait Query: Into<QueryBox> + seal::Sealed {
fn encode_as_query_box(&self) -> Vec<u8>;
}

/// A [`Query`] that either returns a single value or errors out
pub trait SingularQuery: Query {}

/// A [`Query`] that returns an iterable collection of values
pub trait IterableQuery: Query {
/// A type of single element of the output collection
type Item;
}

#[model]
mod model {
use getset::Getters;
Expand Down Expand Up @@ -342,19 +351,50 @@ impl TryFrom<QueryOutputBox> for u64 {
}
}

macro_rules! impl_query {
($($ty:ty => $output:ty),+ $(,)?) => { $(
/// Uses custom syntax to implement query-related traits on query types
///
/// Implements [`Query`] and, additionally, either [`SingularQuery`] or [`IterableQuery`],
/// depending on whether the output type is wrapped into a Vec (purely syntactically)
macro_rules! impl_queries {
// base case for the tt-muncher
() => {};
// we can't delegate matching over `Vec<$item:ty>` to an inner macro,
// as the moment a fragment is matched as `$output:ty` it becomes opaque and unmatchable to any literal
// https://doc.rust-lang.org/nightly/reference/macros-by-example.html#forwarding-a-matched-fragment
// hence we match at the top level with a tt-muncher and a use `@impl_query` inner macro to avoid duplication of the `impl Query`
($ty:ty => Vec<$item:ty> $(, $($rest:tt)*)?) => {
impl_queries!(@impl_query $ty => Vec<$item>);

impl IterableQuery for $ty {
type Item = $item;
}

$(
impl_queries!($($rest)*);
)?
};
($ty:ty => $output:ty $(, $($rest:tt)*)?) => {
impl_queries!(@impl_query $ty => $output);

impl SingularQuery for $ty {
}

$(
impl_queries!($($rest)*);
)?
};
(@impl_query $ty:ty => $output:ty) =>{
impl Query for $ty {
type Output = $output;

fn encode_as_query_box(&self) -> Vec<u8> {
QueryBoxRef::from(self).encode()
}
} )+
}
}
};
}

impl_query! {
impl_queries! {
FindAllRoles => Vec<crate::role::Role>,
FindAllRoleIds => Vec<crate::role::RoleId>,
FindRolesByAccountId => Vec<crate::role::RoleId>,
Expand Down
1 change: 1 addition & 0 deletions smart_contract/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,4 @@ webassembly-test = "0.1.0"
# Not used directly but required for compilation
getrandom = { version = "0.2", features = ["custom"] }

trybuild = { workspace = true }
121 changes: 73 additions & 48 deletions smart_contract/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use data_model::{
};
use derive_more::Display;
pub use iroha_data_model as data_model;
use iroha_data_model::query::IterableQuery;
pub use iroha_smart_contract_derive::main;
pub use iroha_smart_contract_utils::{debug, error, info, log, warn};
use iroha_smart_contract_utils::{
Expand Down Expand Up @@ -180,6 +181,20 @@ pub trait ExecuteQueryOnHost: Sized {
/// Query output type.
type Output;

/// Execute query on the host
///
/// # Errors
///
/// - If query validation failed
/// - If query execution failed
fn execute(&self) -> Result<QueryOutputCursor<Self::Output>, ValidationFail>;
}

/// Extension of [`ExecuteQueryOnHost`] for iterable queries
pub trait ExecuteIterableQueryOnHost: Sized {
/// Type of the iterable query output item
type Item;

/// Apply filter to a query
fn filter(&self, predicate: impl Into<PredicateBox>) -> SmartContractQuery<Self>;
/// Apply sorting to a query
Expand All @@ -190,14 +205,6 @@ pub trait ExecuteQueryOnHost: Sized {

/// Set fetch size for a query. Default is [`DEFAULT_FETCH_SIZE`]
fn fetch_size(&self, fetch_size: FetchSize) -> SmartContractQuery<Self>;

/// Execute query on the host
///
/// # Errors
///
/// - If query validation failed
/// - If query execution failed
fn execute(&self) -> Result<QueryOutputCursor<Self::Output>, ValidationFail>;
}

impl<Q> ExecuteQueryOnHost for Q
Expand All @@ -208,6 +215,26 @@ where
{
type Output = Q::Output;

fn execute(&self) -> Result<QueryOutputCursor<Self::Output>, ValidationFail> {
SmartContractQuery {
query: self,
filter: PredicateBox::default(),
sorting: Sorting::default(),
pagination: Pagination::default(),
fetch_size: FetchSize::default(),
}
.execute()
}
}

impl<Q> ExecuteIterableQueryOnHost for Q
where
Q: IterableQuery + Encode,
Q::Output: DecodeAll,
<Q::Output as TryFrom<QueryOutputBox>>::Error: core::fmt::Debug,
{
type Item = Q::Item;

#[must_use]
fn filter(&self, predicate: impl Into<PredicateBox>) -> SmartContractQuery<Self> {
SmartContractQuery {
Expand Down Expand Up @@ -251,17 +278,6 @@ where
fetch_size,
}
}

fn execute(&self) -> Result<QueryOutputCursor<Self::Output>, ValidationFail> {
SmartContractQuery {
query: self,
filter: PredicateBox::default(),
sorting: Sorting::default(),
pagination: Pagination::default(),
fetch_size: FetchSize::default(),
}
.execute()
}
}

impl<Q> SmartContractQuery<'_, Q>
Expand All @@ -270,34 +286,6 @@ where
Q::Output: DecodeAll,
<Q::Output as TryFrom<QueryOutputBox>>::Error: core::fmt::Debug,
{
/// Apply filter to a query
#[must_use]
pub fn filter(mut self, predicate: impl Into<PredicateBox>) -> Self {
self.filter = predicate.into();
self
}

/// Apply sorting to a query
#[must_use]
pub fn sort(mut self, sorting: Sorting) -> Self {
self.sorting = sorting;
self
}

/// Apply pagination to a query
#[must_use]
pub fn paginate(mut self, pagination: Pagination) -> Self {
self.pagination = pagination;
self
}

/// Set fetch size for a query. Default is [`DEFAULT_FETCH_SIZE`]
#[must_use]
pub fn fetch_size(mut self, fetch_size: FetchSize) -> Self {
self.fetch_size = fetch_size;
self
}

/// Execute query on the host
///
/// # Errors
Expand Down Expand Up @@ -328,6 +316,41 @@ where
}
}

impl<Q> SmartContractQuery<'_, Q>
where
Q: IterableQuery + Encode,
Q::Output: DecodeAll,
<Q::Output as TryFrom<QueryOutputBox>>::Error: core::fmt::Debug,
{
/// Apply filter to a query
#[must_use]
pub fn filter(mut self, predicate: impl Into<PredicateBox>) -> Self {
self.filter = predicate.into();
self
}

/// Apply sorting to a query
#[must_use]
pub fn sort(mut self, sorting: Sorting) -> Self {
self.sorting = sorting;
self
}

/// Apply pagination to a query
#[must_use]
pub fn paginate(mut self, pagination: Pagination) -> Self {
self.pagination = pagination;
self
}

/// Set fetch size for a query. Default is [`DEFAULT_FETCH_SIZE`]
#[must_use]
pub fn fetch_size(mut self, fetch_size: FetchSize) -> Self {
self.fetch_size = fetch_size;
self
}
}

/// Cursor over query results implementing [`IntoIterator`].
///
/// If you execute [`QueryBox`] when you probably want to use [`collect()`](Self::collect) method
Expand Down Expand Up @@ -488,7 +511,9 @@ pub mod prelude {
pub use iroha_smart_contract_derive::main;
pub use iroha_smart_contract_utils::debug::DebugUnwrapExt;

pub use crate::{data_model::prelude::*, ExecuteOnHost, ExecuteQueryOnHost};
pub use crate::{
data_model::prelude::*, ExecuteIterableQueryOnHost, ExecuteOnHost, ExecuteQueryOnHost,
};
}

#[cfg(test)]
Expand Down
10 changes: 10 additions & 0 deletions smart_contract/tests/ui.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#![cfg(not(coverage))]
#![cfg(not(target_arch = "wasm32"))]

use trybuild::TestCases;

#[test]
fn ui() {
let test_cases = TestCases::new();
test_cases.compile_fail("tests/ui_fail/*.rs");
}
Loading
Loading