diff --git a/Cargo.lock b/Cargo.lock index 5392550e46e..c589a2c65d1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1385,6 +1385,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "derive-where" +version = "1.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62d671cc41a825ebabc75757b62d3d168c577f9149b2d49ece1dad1f72119d25" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.75", +] + [[package]] name = "derive_arbitrary" version = "1.3.2" @@ -3135,6 +3146,7 @@ version = "2.0.0-rc.1.0" dependencies = [ "base64 0.22.1", "criterion", + "derive-where", "derive_more", "displaydoc", "getset", diff --git a/Cargo.toml b/Cargo.toml index 740812a3f2a..8f503b36eba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -102,6 +102,7 @@ async-trait = "0.1.81" strum = { version = "0.25.0", default-features = false } getset = "0.1.2" hex-literal = "0.4.1" +derive-where = "1.2.7" rand = { version = "0.8.5", default-features = false, features = ["getrandom", "alloc"] } axum = { version = "0.7.5", default-features = false } diff --git a/crates/iroha/src/query.rs b/crates/iroha/src/query.rs index 42880b28727..11e9284f40e 100644 --- a/crates/iroha/src/query.rs +++ b/crates/iroha/src/query.rs @@ -4,6 +4,7 @@ use std::{collections::HashMap, fmt::Debug}; use eyre::{eyre, Context, Result}; use http::StatusCode; +use iroha_data_model::query::QueryOutputBatchBoxTuple; use iroha_torii_const::uri as torii_uri; use parity_scale_codec::{DecodeAll, Encode}; use url::Url; @@ -16,9 +17,8 @@ use crate::{ query::{ builder::{QueryBuilder, QueryExecutor}, parameters::ForwardCursor, - predicate::HasPredicateBox, - Query, QueryOutput, QueryOutputBatchBox, QueryRequest, QueryResponse, QueryWithParams, - SingularQuery, SingularQueryBox, SingularQueryOutputBox, + Query, QueryOutput, QueryRequest, QueryResponse, QueryWithParams, SingularQuery, + SingularQueryBox, SingularQueryOutputBox, }, ValidationFail, }, @@ -158,7 +158,7 @@ impl QueryExecutor for Client { fn start_query( &self, query: QueryWithParams, - ) -> Result<(QueryOutputBatchBox, u64, Option), Self::Error> { + ) -> Result<(QueryOutputBatchBoxTuple, u64, Option), Self::Error> { let request_head = self.get_query_request_head(); let request = QueryRequest::Start(query); @@ -178,7 +178,7 @@ impl QueryExecutor for Client { fn continue_query( cursor: Self::Cursor, - ) -> Result<(QueryOutputBatchBox, u64, Option), Self::Error> { + ) -> Result<(QueryOutputBatchBoxTuple, u64, Option), Self::Error> { let QueryCursor { request_head, cursor, @@ -235,10 +235,7 @@ impl Client { } /// Build an iterable query and return a builder object - pub fn query( - &self, - query: Q, - ) -> QueryBuilder::Item as HasPredicateBox>::PredicateBoxType> + pub fn query(&self, query: Q) -> QueryBuilder where Q: Query, { diff --git a/crates/iroha/tests/extra_functional/multiple_blocks_created.rs b/crates/iroha/tests/extra_functional/multiple_blocks_created.rs index 8dc7a00086c..9e438b45673 100644 --- a/crates/iroha/tests/extra_functional/multiple_blocks_created.rs +++ b/crates/iroha/tests/extra_functional/multiple_blocks_created.rs @@ -96,7 +96,7 @@ async fn multiple_blocks_created() -> Result<()> { client .query(FindAssets::new()) .filter_with(|asset| { - asset.id.account.eq(account_id) & asset.id.definition_id.eq(definition) + asset.id.account.eq(account_id) & asset.id.definition.eq(definition) }) .execute_all() }) diff --git a/crates/iroha/tests/pagination.rs b/crates/iroha/tests/pagination.rs index 2c8b40405d9..fe710d21411 100644 --- a/crates/iroha/tests/pagination.rs +++ b/crates/iroha/tests/pagination.rs @@ -3,6 +3,7 @@ use iroha::{ client::Client, data_model::{asset::AssetDefinition, prelude::*}, }; +use iroha_data_model::query::dsl::SelectorTuple; use iroha_test_network::*; use nonzero_ext::nonzero; @@ -60,7 +61,12 @@ fn fetch_size_should_work() -> Result<()> { register_assets(&client)?; let query = QueryWithParams::new( - QueryWithFilter::new(FindAssetsDefinitions::new(), CompoundPredicate::PASS).into(), + QueryWithFilter::new( + FindAssetsDefinitions::new(), + CompoundPredicate::PASS, + SelectorTuple::default(), + ) + .into(), QueryParams::new( Pagination::new(Some(nonzero!(7_u64)), 1), Sorting::default(), diff --git a/crates/iroha/tests/sorting.rs b/crates/iroha/tests/sorting.rs index a5983b99dc1..802ee8d2582 100644 --- a/crates/iroha/tests/sorting.rs +++ b/crates/iroha/tests/sorting.rs @@ -4,10 +4,7 @@ use eyre::{Result, WrapErr as _}; use iroha::{ client::QueryResult, crypto::KeyPair, - data_model::{ - account::Account, name::Name, prelude::*, - query::predicate::predicate_atoms::asset::AssetPredicateBox, - }, + data_model::{account::Account, name::Name, prelude::*}, }; use iroha_test_network::*; use iroha_test_samples::ALICE_ID; @@ -24,7 +21,7 @@ fn correct_pagination_assets_after_creating_new_one() { let missing_indices = vec![N_ASSETS / 2]; let pagination = Pagination::new(Some(nonzero!(N_ASSETS as u64 / 3)), N_ASSETS as u64 / 3); let xor_filter = - AssetPredicateBox::build(|asset| asset.id.definition_id.name.starts_with("xor")); + CompoundPredicate::::build(|asset| asset.id.definition.name.starts_with("xor")); let sort_by_metadata_key = "sort".parse::().expect("Valid"); let sorting = Sorting::by_metadata_key(sort_by_metadata_key.clone()); @@ -201,7 +198,7 @@ fn correct_sorting_of_entities() { let res = test_client .query(FindAccounts::new()) .with_sorting(Sorting::by_metadata_key(sort_by_metadata_key.clone())) - .filter_with(|account| account.id.domain_id.eq(domain_id)) + .filter_with(|account| account.id.domain.eq(domain_id)) .execute_all() .expect("Valid"); @@ -339,7 +336,7 @@ fn sort_only_elements_which_have_sorting_key() -> Result<()> { let res = test_client .query(FindAccounts::new()) .with_sorting(Sorting::by_metadata_key(sort_by_metadata_key)) - .filter_with(|account| account.id.domain_id.eq(domain_id)) + .filter_with(|account| account.id.domain.eq(domain_id)) .execute_all() .wrap_err("Failed to submit request")?; diff --git a/crates/iroha_cli/src/main.rs b/crates/iroha_cli/src/main.rs index 8e80d366888..2a523bad29d 100644 --- a/crates/iroha_cli/src/main.rs +++ b/crates/iroha_cli/src/main.rs @@ -251,12 +251,7 @@ fn submit( } mod filter { - use iroha::data_model::query::predicate::{ - predicate_atoms::{ - account::AccountPredicateBox, asset::AssetPredicateBox, domain::DomainPredicateBox, - }, - CompoundPredicate, - }; + use iroha::data_model::query::dsl::CompoundPredicate; use serde::Deserialize; use super::*; @@ -265,32 +260,32 @@ mod filter { #[derive(Clone, Debug, clap::Parser)] pub struct DomainFilter { /// Predicate for filtering given as JSON5 string - #[clap(value_parser = parse_json5::>)] - pub predicate: CompoundPredicate, + #[clap(value_parser = parse_json5::>)] + pub predicate: CompoundPredicate, } /// Filter for account queries #[derive(Clone, Debug, clap::Parser)] pub struct AccountFilter { /// Predicate for filtering given as JSON5 string - #[clap(value_parser = parse_json5::>)] - pub predicate: CompoundPredicate, + #[clap(value_parser = parse_json5::>)] + pub predicate: CompoundPredicate, } /// Filter for asset queries #[derive(Clone, Debug, clap::Parser)] pub struct AssetFilter { /// Predicate for filtering given as JSON5 string - #[clap(value_parser = parse_json5::>)] - pub predicate: CompoundPredicate, + #[clap(value_parser = parse_json5::>)] + pub predicate: CompoundPredicate, } /// Filter for asset definition queries #[derive(Clone, Debug, clap::Parser)] pub struct AssetDefinitionFilter { /// Predicate for filtering given as JSON5 string - #[clap(value_parser = parse_json5::>)] - pub predicate: CompoundPredicate, + #[clap(value_parser = parse_json5::>)] + pub predicate: CompoundPredicate, } fn parse_json5(s: &str) -> Result @@ -1219,18 +1214,40 @@ mod json { // we can't really do type-erased iterable queries in a nice way right now... use iroha::data_model::query::builder::QueryExecutor; - let (mut first_batch, _remaining_items, mut continue_cursor) = + let (mut accumulated_batch, _remaining_items, mut continue_cursor) = client.start_query(query)?; while let Some(cursor) = continue_cursor { let (next_batch, _remaining_items, next_continue_cursor) = ::continue_query(cursor)?; - first_batch.extend(next_batch); + accumulated_batch.extend(next_batch); continue_cursor = next_continue_cursor; } - context.print_data(&first_batch)?; + // for efficiency reasons iroha encodes query results in a columnar format, + // so we need to transpose the batch to get the format that is more natural for humans + let mut batches = vec![Vec::new(); accumulated_batch.len()]; + for batch in accumulated_batch.into_iter() { + // downcast to json and extract the actual array + // dynamic typing is just easier to use here than introducing a bunch of new types only for iroha_cli + let batch = serde_json::to_value(batch)?; + let serde_json::Value::Object(batch) = batch else { + panic!("Expected the batch serialization to be a JSON object"); + }; + let (_ty, batch) = batch + .into_iter() + .next() + .expect("Expected the batch to have exactly one key"); + let serde_json::Value::Array(batch_vec) = batch else { + panic!("Expected the batch payload to be a JSON array"); + }; + for (target, value) in batches.iter_mut().zip(batch_vec) { + target.push(value); + } + } + + context.print_data(&batches)?; } } diff --git a/crates/iroha_core/src/query/cursor.rs b/crates/iroha_core/src/query/cursor.rs index cd572ead5c0..fad98fcb255 100644 --- a/crates/iroha_core/src/query/cursor.rs +++ b/crates/iroha_core/src/query/cursor.rs @@ -2,7 +2,13 @@ use std::{fmt::Debug, num::NonZeroU64}; -use iroha_data_model::query::QueryOutputBatchBox; +use iroha_data_model::{ + prelude::SelectorTuple, + query::{ + dsl::{EvaluateSelector, HasProjection, SelectorMarker}, + QueryOutputBatchBox, QueryOutputBatchBoxTuple, + }, +}; use parity_scale_codec::{Decode, Encode}; use serde::{Deserialize, Serialize}; @@ -25,16 +31,47 @@ pub enum Error { Done, } +fn evaluate_selector_tuple( + batch: Vec, + selector: &SelectorTuple, +) -> QueryOutputBatchBoxTuple +where + T: HasProjection + 'static, + T::Projection: EvaluateSelector, +{ + let mut batch_tuple = Vec::new(); + + let mut iter = selector.iter().peekable(); + + 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 }; + } + + batch_tuple.push(item.project_clone(batch.iter())); + } + + // this should only happen for empty selectors + QueryOutputBatchBoxTuple { tuple: batch_tuple } +} + trait BatchedTrait { fn next_batch( &mut self, cursor: u64, - ) -> Result<(QueryOutputBatchBox, Option), Error>; + ) -> Result<(QueryOutputBatchBoxTuple, Option), Error>; fn remaining(&self) -> u64; } -struct BatchedInner { +struct BatchedInner +where + I: ExactSizeIterator, + I::Item: HasProjection, +{ iter: I, + selector: SelectorTuple, batch_size: NonZeroU64, cursor: Option, } @@ -42,12 +79,14 @@ struct BatchedInner { impl BatchedTrait for BatchedInner where I: ExactSizeIterator, + I::Item: HasProjection + 'static, + >::Projection: EvaluateSelector, QueryOutputBatchBox: From>, { fn next_batch( &mut self, cursor: u64, - ) -> Result<(QueryOutputBatchBox, Option), Error> { + ) -> Result<(QueryOutputBatchBoxTuple, Option), Error> { let Some(server_cursor) = self.cursor else { // the server is done with the iterator return Err(Error::Done); @@ -76,7 +115,9 @@ where .expect("`u32` should always fit into `usize`"), ) .collect(); - let batch = batch.into(); + + // evaluate the requested projections + let batch = evaluate_selector_tuple(batch, &self.selector); // did we get enough elements to continue? if current_batch_size >= expected_batch_size { @@ -101,27 +142,31 @@ where } } -/// A query output iterator that combines batching and type erasure. -pub struct QueryBatchedErasedIterator { +/// A query output iterator that combines evaluating selectors, batching and type erasure. +pub struct ErasedQueryIterator { inner: Box, } -impl Debug for QueryBatchedErasedIterator { +impl Debug for ErasedQueryIterator { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("QueryBatchedErasedIterator").finish() } } -impl QueryBatchedErasedIterator { - /// Creates a new batched iterator. Boxes the inner iterator to erase its type. - pub fn new(iter: I, batch_size: NonZeroU64) -> Self +impl ErasedQueryIterator { + /// Creates a new erased query iterator. Boxes the inner iterator to erase its type. + pub fn new(iter: I, selector: SelectorTuple, batch_size: NonZeroU64) -> Self where I: ExactSizeIterator + Send + Sync + 'static, + I::Item: HasProjection + 'static, + >::Projection: + EvaluateSelector + Send + Sync, QueryOutputBatchBox: From>, { Self { inner: Box::new(BatchedInner { iter, + selector, batch_size, cursor: Some(0), }), @@ -141,7 +186,7 @@ impl QueryBatchedErasedIterator { pub fn next_batch( &mut self, cursor: u64, - ) -> Result<(QueryOutputBatchBox, Option), Error> { + ) -> Result<(QueryOutputBatchBoxTuple, Option), Error> { self.inner.next_batch(cursor) } diff --git a/crates/iroha_core/src/query/store.rs b/crates/iroha_core/src/query/store.rs index 3db0c98e476..6694ce4a333 100644 --- a/crates/iroha_core/src/query/store.rs +++ b/crates/iroha_core/src/query/store.rs @@ -13,7 +13,7 @@ use iroha_data_model::{ query::{ error::QueryExecutionFail, parameters::{ForwardCursor, QueryId}, - QueryOutput, QueryOutputBatchBox, + QueryOutput, QueryOutputBatchBoxTuple, }, }; use iroha_futures::supervisor::{Child, OnShutdown, ShutdownSignal}; @@ -22,7 +22,7 @@ use parity_scale_codec::{Decode, Encode}; use serde::{Deserialize, Serialize}; use tokio::task::JoinHandle; -use super::cursor::{Error as CursorError, QueryBatchedErasedIterator}; +use super::cursor::{ErasedQueryIterator, Error as CursorError}; /// Query service error. #[derive( @@ -66,7 +66,7 @@ impl From for QueryExecutionFail { /// Result type for [`LiveQueryStore`] methods. pub type Result = std::result::Result; -type LiveQuery = QueryBatchedErasedIterator; +type LiveQuery = ErasedQueryIterator; /// Service which stores queries which might be non fully consumed by a client. /// @@ -156,12 +156,7 @@ impl LiveQueryStore { }) } - fn insert( - &self, - query_id: QueryId, - live_query: QueryBatchedErasedIterator, - authority: AccountId, - ) { + fn insert(&self, query_id: QueryId, live_query: ErasedQueryIterator, authority: AccountId) { *self.queries_per_user.entry(authority.clone()).or_insert(0) += 1; let query_info = QueryInfo { live_query, @@ -189,7 +184,7 @@ impl LiveQueryStore { fn insert_new_query( &self, query_id: QueryId, - live_query: QueryBatchedErasedIterator, + live_query: ErasedQueryIterator, authority: AccountId, ) -> Result<()> { trace!(%query_id, "Inserting new query"); @@ -204,7 +199,7 @@ impl LiveQueryStore { &self, query_id: QueryId, cursor: NonZeroU64, - ) -> Result<(QueryOutputBatchBox, u64, Option)> { + ) -> Result<(QueryOutputBatchBoxTuple, u64, Option)> { trace!(%query_id, "Advancing existing query"); let QueryInfo { mut live_query, @@ -256,7 +251,7 @@ impl LiveQueryStoreHandle { /// - Otherwise throws up query output handling errors. pub fn handle_iter_start( &self, - mut live_query: QueryBatchedErasedIterator, + mut live_query: ErasedQueryIterator, authority: &AccountId, ) -> Result { let query_id = uuid::Uuid::new_v4().to_string(); @@ -307,7 +302,7 @@ impl LiveQueryStoreHandle { } fn construct_query_response( - batch: QueryOutputBatchBox, + batch: QueryOutputBatchBoxTuple, remaining_items: u64, query_id: QueryId, cursor: Option, @@ -327,6 +322,7 @@ impl LiveQueryStoreHandle { mod tests { use iroha_data_model::{ permission::Permission, + prelude::SelectorTuple, query::parameters::{FetchSize, Pagination, QueryParams, Sorting}, }; use iroha_primitives::json::Json; @@ -358,6 +354,7 @@ mod tests { (0..100).map(|_| Permission::new(String::default(), Json::from(false))); let query_output = crate::smartcontracts::query::apply_query_postprocessing( query_output, + SelectorTuple::default(), &query_params, ) .unwrap(); diff --git a/crates/iroha_core/src/smartcontracts/isi/account.rs b/crates/iroha_core/src/smartcontracts/isi/account.rs index 914b1311b12..aee01dc458c 100644 --- a/crates/iroha_core/src/smartcontracts/isi/account.rs +++ b/crates/iroha_core/src/smartcontracts/isi/account.rs @@ -442,16 +442,7 @@ pub mod query { use iroha_data_model::{ account::Account, permission::Permission, - query::{ - error::QueryExecutionFail as Error, - predicate::{ - predicate_atoms::{ - account::AccountPredicateBox, permission::PermissionPredicateBox, - role::RoleIdPredicateBox, - }, - CompoundPredicate, - }, - }, + query::{dsl::CompoundPredicate, error::QueryExecutionFail as Error}, }; use iroha_primitives::json::Json; @@ -462,7 +453,7 @@ pub mod query { #[metrics(+"find_roles_by_account_id")] fn execute( self, - filter: CompoundPredicate, + filter: CompoundPredicate, state_ro: &impl StateReadOnly, ) -> Result, Error> { let account_id = &self.id; @@ -479,7 +470,7 @@ pub mod query { #[metrics(+"find_permissions_by_account_id")] fn execute( self, - filter: CompoundPredicate, + filter: CompoundPredicate, state_ro: &impl StateReadOnly, ) -> Result, Error> { let account_id = &self.id; @@ -495,7 +486,7 @@ pub mod query { #[metrics(+"find_accounts")] fn execute( self, - filter: CompoundPredicate, + filter: CompoundPredicate, state_ro: &impl StateReadOnly, ) -> Result, Error> { Ok(state_ro @@ -524,7 +515,7 @@ pub mod query { #[metrics(+"find_accounts_with_asset")] fn execute( self, - filter: CompoundPredicate, + filter: CompoundPredicate, state_ro: &impl StateReadOnly, ) -> std::result::Result, Error> { let asset_definition_id = self.asset_definition.clone(); diff --git a/crates/iroha_core/src/smartcontracts/isi/asset.rs b/crates/iroha_core/src/smartcontracts/isi/asset.rs index 9db957022ca..7e06775ccb8 100644 --- a/crates/iroha_core/src/smartcontracts/isi/asset.rs +++ b/crates/iroha_core/src/smartcontracts/isi/asset.rs @@ -425,13 +425,7 @@ pub mod query { use eyre::Result; use iroha_data_model::{ asset::{Asset, AssetDefinition, AssetValue}, - query::{ - error::QueryExecutionFail as Error, - predicate::{ - predicate_atoms::asset::{AssetDefinitionPredicateBox, AssetPredicateBox}, - CompoundPredicate, - }, - }, + query::{dsl::CompoundPredicate, error::QueryExecutionFail as Error}, }; use iroha_primitives::json::Json; @@ -442,7 +436,7 @@ pub mod query { #[metrics(+"find_assets")] fn execute( self, - filter: CompoundPredicate, + filter: CompoundPredicate, state_ro: &impl StateReadOnly, ) -> Result, Error> { Ok(state_ro @@ -456,7 +450,7 @@ pub mod query { #[metrics(+"find_asset_definitions")] fn execute( self, - filter: CompoundPredicate, + filter: CompoundPredicate, state_ro: &impl StateReadOnly, ) -> Result, Error> { Ok(state_ro diff --git a/crates/iroha_core/src/smartcontracts/isi/block.rs b/crates/iroha_core/src/smartcontracts/isi/block.rs index c398c7a7378..341e35fcb19 100644 --- a/crates/iroha_core/src/smartcontracts/isi/block.rs +++ b/crates/iroha_core/src/smartcontracts/isi/block.rs @@ -1,11 +1,8 @@ //! This module contains trait implementations related to block queries use eyre::Result; -use iroha_data_model::query::{ - error::QueryExecutionFail, - predicate::{ - predicate_atoms::block::{BlockHeaderPredicateBox, SignedBlockPredicateBox}, - CompoundPredicate, - }, +use iroha_data_model::{ + block::{BlockHeader, SignedBlock}, + query::{dsl::CompoundPredicate, error::QueryExecutionFail}, }; use iroha_telemetry::metrics; use nonzero_ext::nonzero; @@ -17,7 +14,7 @@ impl ValidQuery for FindBlocks { #[metrics(+"find_blocks")] fn execute( self, - filter: CompoundPredicate, + filter: CompoundPredicate, state_ro: &impl StateReadOnly, ) -> Result, QueryExecutionFail> { Ok(state_ro @@ -32,7 +29,7 @@ impl ValidQuery for FindBlockHeaders { #[metrics(+"find_block_headers")] fn execute( self, - filter: CompoundPredicate, + filter: CompoundPredicate, state_ro: &impl StateReadOnly, ) -> Result, QueryExecutionFail> { Ok(state_ro diff --git a/crates/iroha_core/src/smartcontracts/isi/domain.rs b/crates/iroha_core/src/smartcontracts/isi/domain.rs index 7131c4ec2f2..5f579b195e1 100644 --- a/crates/iroha_core/src/smartcontracts/isi/domain.rs +++ b/crates/iroha_core/src/smartcontracts/isi/domain.rs @@ -383,10 +383,7 @@ pub mod query { use eyre::Result; use iroha_data_model::{ domain::Domain, - query::{ - error::QueryExecutionFail as Error, - predicate::{predicate_atoms::domain::DomainPredicateBox, CompoundPredicate}, - }, + query::{dsl::CompoundPredicate, error::QueryExecutionFail}, }; use iroha_primitives::json::Json; @@ -397,9 +394,9 @@ pub mod query { #[metrics(+"find_domains")] fn execute( self, - filter: CompoundPredicate, + filter: CompoundPredicate, state_ro: &impl StateReadOnly, - ) -> std::result::Result, Error> { + ) -> std::result::Result, QueryExecutionFail> { Ok(state_ro .world() .domains_iter() @@ -410,7 +407,7 @@ pub mod query { impl ValidSingularQuery for FindDomainMetadata { #[metrics(+"find_domain_key_value_by_id_and_key")] - fn execute(&self, state_ro: &impl StateReadOnly) -> Result { + fn execute(&self, state_ro: &impl StateReadOnly) -> Result { let id = &self.id; let key = &self.key; iroha_logger::trace!(%id, %key); @@ -424,7 +421,7 @@ pub mod query { impl ValidSingularQuery for FindAssetDefinitionMetadata { #[metrics(+"find_asset_definition_key_value_by_id_and_key")] - fn execute(&self, state_ro: &impl StateReadOnly) -> Result { + fn execute(&self, state_ro: &impl StateReadOnly) -> Result { let id = &self.id; let key = &self.key; iroha_logger::trace!(%id, %key); diff --git a/crates/iroha_core/src/smartcontracts/isi/query.rs b/crates/iroha_core/src/smartcontracts/isi/query.rs index 8eb0263ef43..72f28da4d72 100644 --- a/crates/iroha_core/src/smartcontracts/isi/query.rs +++ b/crates/iroha_core/src/smartcontracts/isi/query.rs @@ -6,17 +6,17 @@ use eyre::Result; use iroha_data_model::{ prelude::*, query::{ - error::QueryExecutionFail as Error, parameters::QueryParams, CommittedTransaction, - QueryBox, QueryOutputBatchBox, QueryRequest, QueryRequestWithAuthority, QueryResponse, - SingularQueryBox, SingularQueryOutputBox, + dsl::{EvaluateSelector, HasProjection, SelectorMarker}, + error::QueryExecutionFail as Error, + parameters::QueryParams, + CommittedTransaction, QueryBox, QueryOutputBatchBox, QueryRequest, + QueryRequestWithAuthority, QueryResponse, SingularQueryBox, SingularQueryOutputBox, }, }; use crate::{ prelude::ValidSingularQuery, - query::{ - cursor::QueryBatchedErasedIterator, pagination::Paginate as _, store::LiveQueryStoreHandle, - }, + query::{cursor::ErasedQueryIterator, pagination::Paginate as _, store::LiveQueryStoreHandle}, smartcontracts::{wasm, ValidQuery}, state::{StateReadOnly, WorldReadOnly}, }; @@ -117,14 +117,17 @@ impl SortableQueryOutput for iroha_data_model::block::BlockHeader { /// Returns an error if the fetch size is too big pub fn apply_query_postprocessing( iter: I, + selector: SelectorTuple, &QueryParams { pagination, ref sorting, fetch_size, }: &QueryParams, -) -> Result +) -> Result where I: Iterator, + I::Item: HasProjection + 'static, + >::Projection: EvaluateSelector + Send + Sync, QueryOutputBatchBox: From>, { // validate the fetch (aka batch) size @@ -153,8 +156,9 @@ where }, ); - QueryBatchedErasedIterator::new( + ErasedQueryIterator::new( pairs.into_iter().map(|(_, val)| val).paginate(pagination), + selector, fetch_size, ) } else { @@ -169,7 +173,7 @@ where // TODO: investigate this .collect::>(); - QueryBatchedErasedIterator::new(output.into_iter(), fetch_size) + ErasedQueryIterator::new(output.into_iter(), selector, fetch_size) }; Ok(output) @@ -265,62 +269,77 @@ impl ValidQueryRequest { // dispatch on a concrete query type, erasing the type with `QueryBatchedErasedIterator` in the end QueryBox::FindDomains(q) => apply_query_postprocessing( ValidQuery::execute(q.query, q.predicate, state)?, + q.selector, &iter_query.params, )?, QueryBox::FindAccounts(q) => apply_query_postprocessing( ValidQuery::execute(q.query, q.predicate, state)?, + q.selector, &iter_query.params, )?, QueryBox::FindAssets(q) => apply_query_postprocessing( ValidQuery::execute(q.query, q.predicate, state)?, + q.selector, &iter_query.params, )?, QueryBox::FindAssetsDefinitions(q) => apply_query_postprocessing( ValidQuery::execute(q.query, q.predicate, state)?, + q.selector, &iter_query.params, )?, QueryBox::FindRoles(q) => apply_query_postprocessing( ValidQuery::execute(q.query, q.predicate, state)?, + q.selector, &iter_query.params, )?, QueryBox::FindRoleIds(q) => apply_query_postprocessing( ValidQuery::execute(q.query, q.predicate, state)?, + q.selector, &iter_query.params, )?, QueryBox::FindPermissionsByAccountId(q) => apply_query_postprocessing( ValidQuery::execute(q.query, q.predicate, state)?, + q.selector, &iter_query.params, )?, QueryBox::FindRolesByAccountId(q) => apply_query_postprocessing( ValidQuery::execute(q.query, q.predicate, state)?, + q.selector, &iter_query.params, )?, QueryBox::FindAccountsWithAsset(q) => apply_query_postprocessing( ValidQuery::execute(q.query, q.predicate, state)?, + q.selector, &iter_query.params, )?, QueryBox::FindPeers(q) => apply_query_postprocessing( ValidQuery::execute(q.query, q.predicate, state)?, + q.selector, &iter_query.params, )?, QueryBox::FindActiveTriggerIds(q) => apply_query_postprocessing( ValidQuery::execute(q.query, q.predicate, state)?, + q.selector, &iter_query.params, )?, QueryBox::FindTriggers(q) => apply_query_postprocessing( ValidQuery::execute(q.query, q.predicate, state)?, + q.selector, &iter_query.params, )?, QueryBox::FindTransactions(q) => apply_query_postprocessing( ValidQuery::execute(q.query, q.predicate, state)?, + q.selector, &iter_query.params, )?, QueryBox::FindBlocks(q) => apply_query_postprocessing( ValidQuery::execute(q.query, q.predicate, state)?, + q.selector, &iter_query.params, )?, QueryBox::FindBlockHeaders(q) => apply_query_postprocessing( ValidQuery::execute(q.query, q.predicate, state)?, + q.selector, &iter_query.params, )?, }; @@ -339,7 +358,7 @@ impl ValidQueryRequest { #[cfg(test)] mod tests { use iroha_crypto::{Hash, KeyPair}; - use iroha_data_model::query::predicate::CompoundPredicate; + use iroha_data_model::{block::BlockHeader, query::dsl::CompoundPredicate}; use iroha_primitives::json::Json; use iroha_test_samples::{gen_account_in, ALICE_ID, ALICE_KEYPAIR}; use nonzero_ext::nonzero; @@ -539,7 +558,7 @@ mod tests { assert_eq!( FindBlockHeaders::new() .execute( - BlockHeaderPredicateBox::build(|header| header.hash.eq(block.hash())), + CompoundPredicate::::build(|header| header.hash.eq(block.hash())), &state_view, ) .expect("Query execution should not fail") @@ -550,7 +569,7 @@ mod tests { assert!( FindBlockHeaders::new() .execute( - BlockHeaderPredicateBox::build(|header| { + CompoundPredicate::::build(|header| { header .hash .eq(HashOf::from_untyped_unchecked(Hash::new([42]))) @@ -634,7 +653,7 @@ mod tests { let not_found = FindTransactions::new() .execute( - CommittedTransactionPredicateBox::build(|tx| tx.value.hash.eq(wrong_hash)), + CompoundPredicate::::build(|tx| tx.value.hash.eq(wrong_hash)), &state_view, ) .expect("Query execution should not fail") @@ -643,7 +662,7 @@ mod tests { let found_accepted = FindTransactions::new() .execute( - CommittedTransactionPredicateBox::build(|tx| { + CompoundPredicate::::build(|tx| { tx.value.hash.eq(va_tx.as_ref().hash()) }), &state_view, diff --git a/crates/iroha_core/src/smartcontracts/isi/triggers/mod.rs b/crates/iroha_core/src/smartcontracts/isi/triggers/mod.rs index 411076e5a56..ed1c9d70785 100644 --- a/crates/iroha_core/src/smartcontracts/isi/triggers/mod.rs +++ b/crates/iroha_core/src/smartcontracts/isi/triggers/mod.rs @@ -311,9 +311,7 @@ pub mod query { //! Queries associated to triggers. use iroha_data_model::{ query::{ - error::QueryExecutionFail as Error, - predicate::{predicate_atoms::trigger::TriggerIdPredicateBox, CompoundPredicate}, - trigger::FindTriggers, + dsl::CompoundPredicate, error::QueryExecutionFail as Error, trigger::FindTriggers, }, trigger::{Trigger, TriggerId}, }; @@ -330,7 +328,7 @@ pub mod query { #[metrics(+"find_active_triggers")] fn execute( self, - filter: CompoundPredicate, + filter: CompoundPredicate, state_ro: &impl StateReadOnly, ) -> Result, Error> { Ok(state_ro @@ -346,7 +344,7 @@ pub mod query { #[metrics(+"find_triggers")] fn execute( self, - filter: CompoundPredicate, + filter: CompoundPredicate, state_ro: &impl StateReadOnly, ) -> Result, Error> { let triggers = state_ro.world().triggers(); diff --git a/crates/iroha_core/src/smartcontracts/isi/tx.rs b/crates/iroha_core/src/smartcontracts/isi/tx.rs index 8464b3c2a10..1e94062e48e 100644 --- a/crates/iroha_core/src/smartcontracts/isi/tx.rs +++ b/crates/iroha_core/src/smartcontracts/isi/tx.rs @@ -7,11 +7,7 @@ use iroha_crypto::HashOf; use iroha_data_model::{ block::{BlockHeader, SignedBlock}, prelude::*, - query::{ - error::QueryExecutionFail, - predicate::{predicate_atoms::block::CommittedTransactionPredicateBox, CompoundPredicate}, - CommittedTransaction, - }, + query::{dsl::CompoundPredicate, error::QueryExecutionFail, CommittedTransaction}, transaction::error::TransactionRejectionReason, }; use iroha_telemetry::metrics; @@ -65,7 +61,7 @@ impl ValidQuery for FindTransactions { #[metrics(+"find_transactions")] fn execute( self, - filter: CompoundPredicate, + filter: CompoundPredicate, state_ro: &impl StateReadOnly, ) -> Result, QueryExecutionFail> { Ok(state_ro diff --git a/crates/iroha_core/src/smartcontracts/isi/world.rs b/crates/iroha_core/src/smartcontracts/isi/world.rs index 38d44dd6868..b5da5a99c82 100644 --- a/crates/iroha_core/src/smartcontracts/isi/world.rs +++ b/crates/iroha_core/src/smartcontracts/isi/world.rs @@ -451,16 +451,7 @@ pub mod query { use iroha_data_model::{ parameter::Parameters, prelude::*, - query::{ - error::QueryExecutionFail as Error, - predicate::{ - predicate_atoms::{ - peer::PeerPredicateBox, - role::{RoleIdPredicateBox, RolePredicateBox}, - }, - CompoundPredicate, - }, - }, + query::{dsl::CompoundPredicate, error::QueryExecutionFail as Error}, role::Role, }; @@ -471,7 +462,7 @@ pub mod query { #[metrics(+"find_roles")] fn execute( self, - filter: CompoundPredicate, + filter: CompoundPredicate, state_ro: &impl StateReadOnly, ) -> Result, Error> { Ok(state_ro @@ -488,7 +479,7 @@ pub mod query { #[metrics(+"find_role_ids")] fn execute( self, - filter: CompoundPredicate, + filter: CompoundPredicate, state_ro: &impl StateReadOnly, ) -> Result, Error> { Ok(state_ro @@ -506,7 +497,7 @@ pub mod query { #[metrics(+"find_peers")] fn execute( self, - filter: CompoundPredicate, + filter: CompoundPredicate, state_ro: &impl StateReadOnly, ) -> Result, Error> { Ok(state_ro diff --git a/crates/iroha_core/src/smartcontracts/mod.rs b/crates/iroha_core/src/smartcontracts/mod.rs index 009ba6b1157..4a2ca1aeb92 100644 --- a/crates/iroha_core/src/smartcontracts/mod.rs +++ b/crates/iroha_core/src/smartcontracts/mod.rs @@ -10,10 +10,7 @@ pub mod wasm; use iroha_data_model::{ isi::error::InstructionExecutionError as Error, prelude::*, - query::{ - error::QueryExecutionFail, - predicate::{CompoundPredicate, HasPredicateBox}, - }, + query::{dsl::CompoundPredicate, error::QueryExecutionFail}, }; pub use isi::*; @@ -33,10 +30,7 @@ pub trait Execute { } /// This trait defines how an Iroha Iterable query is executed. -pub trait ValidQuery: iroha_data_model::query::Query -where - Self::Item: HasPredicateBox, -{ +pub trait ValidQuery: iroha_data_model::query::Query { /// Execute a query on a read-only state. /// /// The filter is deliberately passed to the query implementation, @@ -46,7 +40,7 @@ where /// Concrete to each implementer fn execute( self, - filter: CompoundPredicate<::PredicateBoxType>, + filter: CompoundPredicate, state_ro: &impl StateReadOnly, ) -> Result, QueryExecutionFail>; } diff --git a/crates/iroha_core/src/sumeragi/main_loop.rs b/crates/iroha_core/src/sumeragi/main_loop.rs index f94df318ed5..ac3a05d813a 100644 --- a/crates/iroha_core/src/sumeragi/main_loop.rs +++ b/crates/iroha_core/src/sumeragi/main_loop.rs @@ -312,10 +312,20 @@ impl Sumeragi { .unpack(|e| self.send_event(e)) .expect("Genesis invalid"); - assert!( - genesis.as_ref().errors().next().is_none(), - "Genesis contains invalid transactions" - ); + if genesis.as_ref().errors().next().is_some() { + let errors = genesis + .as_ref() + .errors() + .map(|(&transaction_index, rejection_reason)| { + format!( + "===\nTx {transaction_index}:\n{:?}", + eyre::Error::new(rejection_reason.clone()) + ) + }) + .collect::>() + .join("\n"); + panic!("Genesis contains invalid transactions:\n{}", errors); + } // NOTE: By this time genesis block is executed and list of trusted peers is updated self.topology = Topology::new(state_block.world.peers.clone()); diff --git a/crates/iroha_data_model/Cargo.toml b/crates/iroha_data_model/Cargo.toml index c10264d929f..2e074fc9bbb 100644 --- a/crates/iroha_data_model/Cargo.toml +++ b/crates/iroha_data_model/Cargo.toml @@ -44,6 +44,7 @@ serde_json = { workspace = true } thiserror = { workspace = true, optional = true } displaydoc = { workspace = true } getset = { workspace = true } +derive-where = { workspace = true } strum = { workspace = true, features = ["derive"] } base64 = { workspace = true, features = ["alloc"] } nonzero_ext = { workspace = true } diff --git a/crates/iroha_data_model/src/query/builder/batch_downcast.rs b/crates/iroha_data_model/src/query/builder/batch_downcast.rs new file mode 100644 index 00000000000..230d023dfeb --- /dev/null +++ b/crates/iroha_data_model/src/query/builder/batch_downcast.rs @@ -0,0 +1,154 @@ +#[cfg(not(feature = "std"))] +use alloc::vec::{self, Vec}; +#[cfg(feature = "std")] +use std::vec; + +use crate::query::{QueryOutputBatchBox, QueryOutputBatchBoxTuple}; + +#[derive(Debug)] +pub struct TypedBatchIterUntupled { + t: vec::IntoIter, +} + +impl Iterator for TypedBatchIterUntupled { + type Item = T; + + fn next(&mut self) -> Option { + self.t.next() + } +} + +impl ExactSizeIterator for TypedBatchIterUntupled { + fn len(&self) -> usize { + self.t.len() + } +} + +#[derive(Debug, Copy, Clone, displaydoc::Display)] +#[cfg_attr(feature = "std", derive(thiserror::Error))] +pub enum TypedBatchDowncastError { + /// Not enough slices in the tuple + NotEnoughSlices, + /// Too many slices in the tuple + TooManySlices, + /// Wrong type at index {0} + WrongType(usize), +} + +pub trait HasTypedBatchIter { + type TypedBatchIter: Iterator + ExactSizeIterator; + fn downcast( + erased_batch: QueryOutputBatchBoxTuple, + ) -> Result; +} + +impl HasTypedBatchIter for T +where + Vec: TryFrom, +{ + type TypedBatchIter = TypedBatchIterUntupled; + fn downcast( + erased_batch_tuple: QueryOutputBatchBoxTuple, + ) -> Result { + let mut iter = erased_batch_tuple.tuple.into_iter(); + let t1 = iter + .next() + .ok_or(TypedBatchDowncastError::NotEnoughSlices)?; + if iter.next().is_some() { + return Err(TypedBatchDowncastError::TooManySlices); + } + + let t1 = as TryFrom>::try_from(t1) + .ok() + .ok_or(TypedBatchDowncastError::WrongType(0))? + .into_iter(); + + Ok(TypedBatchIterUntupled { t: t1 }) + } +} + +macro_rules! typed_batch_tuple { + ( + $( + $name:ident($($ty_name:ident: $ty:ident),+); + )* + ) => { + $( + #[derive(Debug)] + pub struct $name<$($ty),+> { + $($ty_name: vec::IntoIter<$ty>),+ + } + + impl<$($ty),+> Iterator for $name<$($ty),+> { + type Item = ($($ty,)+); + #[allow(unreachable_patterns)] // for batch size the panic will be unreachable. this is fine + fn next(&mut self) -> Option { + $( + let $ty_name = self.$ty_name.next(); + )+ + + match ($($ty_name,)+) { + ( $(Some($ty_name),)+ ) => Some(($($ty_name,)+)), + ( $(None::<$ty>,)* ) => None, + _ => panic!("BUG: TypedBatch length mismatch"), + } + } + } + + impl<$($ty),+> ExactSizeIterator for $name<$($ty),+> { + #[allow(unreachable_code)] + fn len(&self) -> usize { + // the length of all the iterators in the batch tuple should be the same + // HACK: get the length of the first iterator, making the code for other branches unreachable + $(return self.$ty_name.len();)+ + } + } + + impl<$($ty),+> HasTypedBatchIter for ($($ty,)+) + where + $(Vec<$ty>: TryFrom),+ + { + type TypedBatchIter = $name<$($ty),+>; + #[expect(unused_assignments)] // the last increment of `index` will be unreachable. this is fine + fn downcast( + erased_batch: QueryOutputBatchBoxTuple, + ) -> Result { + let mut iter = erased_batch.tuple.into_iter(); + $( + let $ty_name = iter + .next() + .ok_or(TypedBatchDowncastError::NotEnoughSlices)?; + )+ + if iter.next().is_some() { + return Err(TypedBatchDowncastError::TooManySlices); + } + + let mut index = 0; + $( + let $ty_name = as TryFrom>::try_from($ty_name) + .ok() + .ok_or(TypedBatchDowncastError::WrongType(index))? + .into_iter(); + index += 1; + )+ + + Ok($name { + $($ty_name),+ + }) + } + } + )* + }; +} + +typed_batch_tuple! { + TypedBatch1(t1: T1); + TypedBatch2(t1: T1, t2: T2); + TypedBatch3(t1: T1, t2: T2, t3: T3); + TypedBatch4(t1: T1, t2: T2, t3: T3, t4: T4); + TypedBatch5(t1: T1, t2: T2, t3: T3, t4: T4, t5: T5); + TypedBatch6(t1: T1, t2: T2, t3: T3, t4: T4, t5: T5, t6: T6); + TypedBatch7(t1: T1, t2: T2, t3: T3, t4: T4, t5: T5, t6: T6, t7: T7); + 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? +} diff --git a/crates/iroha_data_model/src/query/builder/iter.rs b/crates/iroha_data_model/src/query/builder/iter.rs new file mode 100644 index 00000000000..fc72d6932fb --- /dev/null +++ b/crates/iroha_data_model/src/query/builder/iter.rs @@ -0,0 +1,85 @@ +use crate::query::{ + builder::{ + batch_downcast::{HasTypedBatchIter, TypedBatchDowncastError}, + QueryExecutor, + }, + QueryOutputBatchBoxTuple, +}; + +/// An iterator over results of an iterable query. +#[derive(Debug)] +pub struct QueryIterator { + current_batch_iter: T::TypedBatchIter, + remaining_items: u64, + continue_cursor: Option, +} + +impl QueryIterator +where + E: QueryExecutor, + T: HasTypedBatchIter, +{ + /// Create a new iterator over iterable query results. + /// + /// # Errors + /// + /// Returns an error if the type of the batch does not match the expected type `T`. + pub fn new( + first_batch: QueryOutputBatchBoxTuple, + remaining_items: u64, + continue_cursor: Option, + ) -> Result { + let batch_iter = T::downcast(first_batch)?; + + Ok(Self { + current_batch_iter: batch_iter, + remaining_items, + continue_cursor, + }) + } +} + +impl Iterator for QueryIterator +where + E: QueryExecutor, + T: HasTypedBatchIter, +{ + type Item = Result; + + fn next(&mut self) -> Option { + // if we haven't exhausted the current batch yet - return it + if let Some(item) = self.current_batch_iter.next() { + return Some(Ok(item)); + } + + // no cursor means the query result is exhausted or an error occurred on one of the previous iterations + let cursor = self.continue_cursor.take()?; + + // get a next batch from iroha + let (batch, remaining_items, cursor) = match E::continue_query(cursor) { + Ok(r) => r, + Err(e) => return Some(Err(e)), + }; + self.continue_cursor = cursor; + + // downcast the batch to the expected type + // we've already downcast the first batch to the expected type, so if iroha returns a different type here, it surely is a bug + let batch_iter = + T::downcast(batch).expect("BUG: iroha returned unexpected type in iterable query"); + + self.current_batch_iter = batch_iter; + self.remaining_items = remaining_items; + + self.next() + } +} + +impl ExactSizeIterator for QueryIterator +where + E: QueryExecutor, + T: HasTypedBatchIter, +{ + fn len(&self) -> usize { + self.current_batch_iter.len() + self.remaining_items as usize + } +} diff --git a/crates/iroha_data_model/src/query/builder.rs b/crates/iroha_data_model/src/query/builder/mod.rs similarity index 58% rename from crates/iroha_data_model/src/query/builder.rs rename to crates/iroha_data_model/src/query/builder/mod.rs index bafe5e1df1d..71d752c6300 100644 --- a/crates/iroha_data_model/src/query/builder.rs +++ b/crates/iroha_data_model/src/query/builder/mod.rs @@ -1,18 +1,26 @@ //! Contains common types and traits to facilitate building and sending queries, either from the client or from smart contracts. +mod batch_downcast; +mod iter; + #[cfg(not(feature = "std"))] -use alloc::vec::{self, Vec}; -#[cfg(feature = "std")] -use std::vec; +use alloc::vec::Vec; +use core::marker::PhantomData; +use derive_where::derive_where; +pub use iter::QueryIterator; use parity_scale_codec::{Decode, Encode}; use serde::{Deserialize, Serialize}; use crate::query::{ + builder::batch_downcast::HasTypedBatchIter, + dsl::{ + BaseProjector, CompoundPredicate, HasPrototype, IntoSelectorTuple, PredicateMarker, + SelectorMarker, SelectorTuple, + }, parameters::{FetchSize, Pagination, QueryParams, Sorting}, - predicate::{projectors, AstPredicate, CompoundPredicate, HasPredicateBox, HasPrototype}, - Query, QueryBox, QueryOutputBatchBox, QueryWithFilter, QueryWithFilterFor, QueryWithParams, - SingularQueryBox, SingularQueryOutputBox, + Query, QueryBox, QueryOutputBatchBoxTuple, QueryWithFilter, QueryWithParams, SingularQueryBox, + SingularQueryOutputBox, }; /// A trait abstracting away concrete backend for executing queries against iroha. @@ -42,7 +50,7 @@ pub trait QueryExecutor { fn start_query( &self, query: QueryWithParams, - ) -> Result<(QueryOutputBatchBox, u64, Option), Self::Error>; + ) -> Result<(QueryOutputBatchBoxTuple, u64, Option), Self::Error>; /// Continues an iterable query from the given cursor and returns the next batch of results, the remaining number of results and a cursor to continue the query. /// @@ -51,88 +59,7 @@ pub trait QueryExecutor { /// Returns an error if the query execution fails. fn continue_query( cursor: Self::Cursor, - ) -> Result<(QueryOutputBatchBox, u64, Option), Self::Error>; -} - -/// An iterator over results of an iterable query. -#[derive(Debug)] -pub struct QueryIterator { - current_batch_iter: vec::IntoIter, - remaining_items: u64, - continue_cursor: Option, -} - -impl QueryIterator -where - E: QueryExecutor, - Vec: TryFrom, -{ - /// Create a new iterator over iterable query results. - /// - /// # Errors - /// - /// Returns an error if the type of the batch does not match the expected type `T`. - pub fn new( - first_batch: QueryOutputBatchBox, - remaining_items: u64, - continue_cursor: Option, - ) -> Result as TryFrom>::Error> { - let batch: Vec = first_batch.try_into()?; - - Ok(Self { - current_batch_iter: batch.into_iter(), - remaining_items, - continue_cursor, - }) - } -} - -impl Iterator for QueryIterator -where - E: QueryExecutor, - Vec: TryFrom, - as TryFrom>::Error: core::fmt::Debug, -{ - type Item = Result; - - fn next(&mut self) -> Option { - // if we haven't exhausted the current batch yet - return it - if let Some(item) = self.current_batch_iter.next() { - return Some(Ok(item)); - } - - // no cursor means the query result is exhausted or an error occurred on one of the previous iterations - let cursor = self.continue_cursor.take()?; - - // get a next batch from iroha - let (batch, remaining_items, cursor) = match E::continue_query(cursor) { - Ok(r) => r, - Err(e) => return Some(Err(e)), - }; - self.continue_cursor = cursor; - - // downcast the batch to the expected type - // we've already downcast the first batch to the expected type, so if iroha returns a different type here, it surely is a bug - let batch: Vec = batch - .try_into() - .expect("BUG: iroha returned unexpected type in iterable query"); - - self.current_batch_iter = batch.into_iter(); - self.remaining_items = remaining_items; - - self.next() - } -} - -impl ExactSizeIterator for QueryIterator -where - E: QueryExecutor, - Vec: TryFrom, - as TryFrom>::Error: core::fmt::Debug, -{ - fn len(&self) -> usize { - self.current_batch_iter.len() + self.remaining_items as usize - } + ) -> Result<(QueryOutputBatchBoxTuple, u64, Option), Self::Error>; } /// An error that can occur when constraining the number of results of an iterable query to one. @@ -169,19 +96,25 @@ impl From for SingleQueryError { } /// Struct that simplifies construction of an iterable query. -pub struct QueryBuilder<'e, E, Q, P> { +#[derive_where(Clone; Q, CompoundPredicate, SelectorTuple)] +pub struct QueryBuilder<'e, E, Q, T> +where + Q: Query, +{ query_executor: &'e E, query: Q, - filter: CompoundPredicate

, + filter: CompoundPredicate, + selector: SelectorTuple, pagination: Pagination, sorting: Sorting, fetch_size: FetchSize, + // NOTE: T is a phantom type used to denote the selected tuple in `selector` + phantom: PhantomData, } -impl<'a, E, Q, P> QueryBuilder<'a, E, Q, P> +impl<'a, E, Q> QueryBuilder<'a, E, Q, Q::Item> where Q: Query, - Q::Item: HasPredicateBox, { /// Create a new iterable query builder for a given backend and query. pub fn new(query_executor: &'a E, query: Q) -> Self { @@ -189,19 +122,24 @@ where query_executor, query, filter: CompoundPredicate::PASS, + selector: SelectorTuple::default(), pagination: Pagination::default(), sorting: Sorting::default(), fetch_size: FetchSize::default(), + phantom: PhantomData, } } } -impl QueryBuilder<'_, E, Q, P> { +impl<'a, E, Q, T> QueryBuilder<'a, E, Q, T> +where + Q: Query, +{ /// Only return results that match the given predicate. /// /// If multiple filters are added, they are combined with a logical AND. #[must_use] - pub fn filter(self, filter: CompoundPredicate

) -> Self { + pub fn filter(self, filter: CompoundPredicate) -> Self { Self { filter: self.filter.and(filter), ..self @@ -212,15 +150,54 @@ impl QueryBuilder<'_, E, Q, P> { /// /// If multiple filters are added, they are combined with a logical AND. #[must_use] - pub fn filter_with(self, predicate_builder: B) -> Self + pub fn filter_with(self, predicate_builder: B) -> Self where - P: HasPrototype, - B: FnOnce(P::Prototype>) -> O, - O: AstPredicate

, + Q::Item: HasPrototype, + B: FnOnce( + ::Prototype< + PredicateMarker, + BaseProjector, + >, + ) -> CompoundPredicate, + ::Prototype< + PredicateMarker, + BaseProjector, + >: Default, { - use crate::query::predicate::predicate_ast_extensions::AstPredicateExt as _; + self.filter(predicate_builder(Default::default())) + } - self.filter(predicate_builder(Default::default()).normalize()) + /// 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(self, f: B) -> QueryBuilder<'a, E, Q, O::SelectedTuple> + where + Q::Item: HasPrototype, + B: FnOnce( + ::Prototype< + SelectorMarker, + BaseProjector, + >, + ) -> O, + ::Prototype< + SelectorMarker, + BaseProjector, + >: Default, + O: IntoSelectorTuple, + { + 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. @@ -244,22 +221,20 @@ impl QueryBuilder<'_, E, Q, P> { } } -impl QueryBuilder<'_, E, Q, P> +impl QueryBuilder<'_, E, Q, T> where - E: QueryExecutor, Q: Query, - Q::Item: HasPredicateBox, - QueryBox: From>, - Vec: TryFrom, - as TryFrom>::Error: core::fmt::Debug, + E: QueryExecutor, + QueryBox: From>, + T: HasTypedBatchIter, { /// Execute the query, returning an iterator over its results. /// /// # Errors /// /// Returns an error if the query execution fails. - pub fn execute(self) -> Result, E::Error> { - let with_filter = QueryWithFilter::new(self.query, self.filter); + pub fn execute(self) -> Result, E::Error> { + let with_filter = QueryWithFilter::new(self.query, self.filter, self.selector); let boxed: QueryBox = with_filter.into(); let query = QueryWithParams { @@ -274,7 +249,7 @@ where let (first_batch, remaining_items, continue_cursor) = self.query_executor.start_query(query)?; - let iterator = QueryIterator::::new(first_batch, remaining_items, continue_cursor) + let iterator = QueryIterator::::new(first_batch, remaining_items, continue_cursor) .expect( "INTERNAL BUG: iroha returned unexpected type in iterable query. Is there a schema mismatch?", ); @@ -283,65 +258,47 @@ where } } -impl Clone for QueryBuilder<'_, E, Q, P> -where - Q: Clone, - P: Clone, -{ - fn clone(&self) -> Self { - Self { - query_executor: self.query_executor, - query: self.query.clone(), - filter: self.filter.clone(), - pagination: self.pagination, - sorting: self.sorting.clone(), - fetch_size: self.fetch_size, - } - } -} - /// An extension trait for query builders that provides convenience methods to execute queries. -pub trait QueryBuilderExt +pub trait QueryBuilderExt where E: QueryExecutor, Q: Query, + T: HasTypedBatchIter, { /// Execute the query, returning all the results collected into a vector. /// /// # Errors /// /// Returns an error if the query execution fails. - fn execute_all(self) -> Result, E::Error>; + fn execute_all(self) -> Result, E::Error>; /// Execute the query, constraining the number of results to zero or one. /// /// # Errors /// /// Returns an error if the query execution fails or if more than one result is returned. - fn execute_single_opt(self) -> Result, SingleQueryError>; + fn execute_single_opt(self) -> Result, SingleQueryError>; /// Execute the query, constraining the number of results to exactly one. /// /// # Errors /// /// Returns an error if the query execution fails or if zero or more than one result is returned. - fn execute_single(self) -> Result>; + fn execute_single(self) -> Result>; } -impl QueryBuilderExt for QueryBuilder<'_, E, Q, P> +impl QueryBuilderExt for QueryBuilder<'_, E, Q, T> where E: QueryExecutor, Q: Query, - Q::Item: HasPredicateBox, - QueryBox: From>, - Vec: TryFrom, - as TryFrom>::Error: core::fmt::Debug, + QueryBox: From>, + T: HasTypedBatchIter, { - fn execute_all(self) -> Result, E::Error> { + fn execute_all(self) -> Result, E::Error> { self.execute()?.collect::, _>>() } - fn execute_single_opt(self) -> Result, SingleQueryError> { + fn execute_single_opt(self) -> Result, SingleQueryError> { let mut iter = self.execute()?; let first = iter.next().transpose()?; let second = iter.next().transpose()?; @@ -356,7 +313,7 @@ where } } - fn execute_single(self) -> Result> { + fn execute_single(self) -> Result> { let mut iter = self.execute()?; let first = iter.next().transpose()?; let second = iter.next().transpose()?; diff --git a/crates/iroha_data_model/src/query/dsl/compound_predicate.rs b/crates/iroha_data_model/src/query/dsl/compound_predicate.rs new file mode 100644 index 00000000000..b2535510611 --- /dev/null +++ b/crates/iroha_data_model/src/query/dsl/compound_predicate.rs @@ -0,0 +1,135 @@ +#[cfg(not(feature = "std"))] +use alloc::{boxed::Box, format, string::String, vec, vec::Vec}; + +use derive_where::derive_where; +use iroha_macro::serde_where; +use iroha_schema::IntoSchema; +use parity_scale_codec::{Decode, Encode}; +use serde::{Deserialize, Serialize}; + +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> { + /// A predicate as-is + Atom(T::Projection), + /// A negation of a compound predicate. + Not(Box>), + /// A conjunction of multiple predicates. + And(Vec>), + /// A disjunction of multiple predicates. + Or(Vec>), +} + +impl> CompoundPredicate { + /// 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. + #[must_use] + pub fn not(self) -> Self { + !self + } + + /// Combine two predicates with an "and" operation. + #[must_use] + pub fn and(self, other: Self) -> Self { + self & other + } + + /// Combine two predicates with an "or" operation. + #[must_use] + pub fn or(self, other: Self) -> Self { + self | other + } +} + +impl CompoundPredicate +where + T: HasProjection, + T::Projection: EvaluatePredicate, +{ + /// Evaluate the predicate on the given input. + pub fn applies(&self, input: &T) -> bool { + match self { + CompoundPredicate::Atom(projection) => projection.applies(input), + CompoundPredicate::Not(expr) => !expr.applies(input), + CompoundPredicate::And(and_list) => and_list.iter().all(|expr| expr.applies(input)), + CompoundPredicate::Or(or_list) => or_list.iter().any(|expr| expr.applies(input)), + } + } +} + +impl> core::ops::Not for CompoundPredicate { + type Output = CompoundPredicate; + + fn not(self) -> Self::Output { + match self { + // if the top-level predicate is a negation, we can just remove it + CompoundPredicate::Not(expr) => *expr, + this => CompoundPredicate::Not(Box::new(this)), + } + } +} + +impl> core::ops::BitAnd for CompoundPredicate { + type Output = CompoundPredicate; + + fn bitand(self, other: Self) -> Self::Output { + match (self, other) { + // if any of the predicates is an and - flatten it + (CompoundPredicate::And(mut and_list), other) => { + and_list.push(other); + CompoundPredicate::And(and_list) + } + (this, CompoundPredicate::And(mut and_list)) => { + // push to front to preserve user-specified order (our predicates are short-circuiting) + and_list.insert(0, this); + CompoundPredicate::And(and_list) + } + (this, other) => CompoundPredicate::And(vec![this, other]), + } + } +} + +impl> core::ops::BitOr for CompoundPredicate { + type Output = CompoundPredicate; + + fn bitor(self, other: Self) -> Self::Output { + match (self, other) { + // if any of the predicates is an or - flatten it + (CompoundPredicate::Or(mut or_list), other) => { + or_list.push(other); + CompoundPredicate::Or(or_list) + } + (this, CompoundPredicate::Or(mut or_list)) => { + // push to front to preserve user-specified order (our predicates are short-circuiting) + or_list.insert(0, this); + CompoundPredicate::Or(or_list) + } + (this, other) => CompoundPredicate::Or(vec![this, other]), + } + } +} + +impl> CompoundPredicate { + /// Build a new compound predicate using the provided closure. + pub fn build(f: F) -> Self + where + T: HasPrototype, + F: FnOnce( + ::Prototype>, + ) -> CompoundPredicate, + ::Prototype>: Default, + { + f(Default::default()) + } +} diff --git a/crates/iroha_data_model/src/query/dsl/mod.rs b/crates/iroha_data_model/src/query/dsl/mod.rs new file mode 100644 index 00000000000..153ee2a3da4 --- /dev/null +++ b/crates/iroha_data_model/src/query/dsl/mod.rs @@ -0,0 +1,194 @@ +//! 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::::build(|account_id| account_id.domain.name.eq("wonderland")); +//! ``` +//! For selectors, they have to return a type implementing the [`IntoSelectorTuple`] 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::::build(|account_id| account_id.domain.name); +//! let select_domain_name_and_signatory = +//! SelectorTuple::::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 predicate 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 sent when a query is requested. +//! +//! A selector also selects just a single field. To allow selecting multiple fields, [`SelectorTuple`] is used in queries. + +#[cfg(not(feature = "std"))] +use alloc::{format, string::String, vec::Vec}; +use core::marker::PhantomData; + +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_traits::{IntoSelector, IntoSelectorTuple}, + selector_tuple::SelectorTuple, +}; +use crate::query::QueryOutputBatchBox; + +/// Trait implemented on all evaluable predicates for type `T`. +pub trait EvaluatePredicate { + /// 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; +} + +/// Trait implemented on all evaluable selectors for type `T`. +pub trait EvaluateSelector { + /// 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) -> QueryOutputBatchBox; + /// Select the field from each of the elements in the input and type-erase the result. + fn project(&self, batch: impl Iterator) -> 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 { + /// 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 Projectable for T { + // Predicate is the atom for predicates + type AtomType = T::Predicate; +} + +impl Projectable 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: Projectable { + /// 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: Default + Copy; +} + +/// Describes how to convert a projection on `InputType` to a projection on `OutputType` by wrapping it in a projection. +pub trait ObjectProjector { + /// The type of input projection. + type InputType: HasProjection; + /// The type of output projection. + type OutputType: HasProjection; + + /// Convert the projection on [`Self::InputType`] to a projection on [`Self::OutputType`]. + fn project( + projection: >::Projection, + ) -> >::Projection; + + /// Construct a projection from an atom and convert it to a projection on [`Self::OutputType`]. + fn wrap_atom( + atom: >::AtomType, + ) -> >::Projection { + let input_projection = >::atom(atom); + Self::project(input_projection) + } +} + +/// An [`ObjectProjector`] that does not change the type, serving as a base case for the recursion. +pub struct BaseProjector(PhantomData<(Marker, T)>); + +impl ObjectProjector for BaseProjector +where + T: HasProjection, +{ + type InputType = T; + type OutputType = T; + + fn project(projection: T::Projection) -> T::Projection { + projection + } +} + +/// 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, + }; +} diff --git a/crates/iroha_data_model/src/query/dsl/predicates.rs b/crates/iroha_data_model/src/query/dsl/predicates.rs new file mode 100644 index 00000000000..3a6d9e23895 --- /dev/null +++ b/crates/iroha_data_model/src/query/dsl/predicates.rs @@ -0,0 +1,342 @@ +//! This module contains predicate definitions for all queryable types. See the [module-level documentation](crate::query::dsl) for more information. + +#[cfg(not(feature = "std"))] +use alloc::{format, string::String, vec::Vec}; + +use iroha_crypto::{HashOf, PublicKey}; + +use crate::{ + account::{Account, AccountId}, + asset::{Asset, AssetDefinition, AssetDefinitionId, AssetId, AssetValue}, + block::{BlockHeader, SignedBlock}, + domain::{Domain, DomainId}, + metadata::Metadata, + name::Name, + parameter::Parameter, + peer::PeerId, + permission::Permission, + query::{ + dsl::{ + type_descriptions::{ + AccountIdPrototype, AccountPrototype, AssetDefinitionIdPrototype, + AssetDefinitionPrototype, AssetIdPrototype, AssetPrototype, AssetValuePrototype, + BlockHeaderHashPrototype, BlockHeaderPrototype, CommittedTransactionPrototype, + DomainIdPrototype, DomainPrototype, MetadataPrototype, NamePrototype, + ParameterPrototype, PeerIdPrototype, PermissionPrototype, PublicKeyPrototype, + RoleIdPrototype, RolePrototype, SignedBlockPrototype, SignedTransactionPrototype, + StringPrototype, TransactionErrorPrototype, TransactionHashPrototype, + TriggerIdPrototype, TriggerPrototype, + }, + CompoundPredicate, ObjectProjector, PredicateMarker, + }, + CommittedTransaction, + }, + role::{Role, RoleId}, + transaction::{error::TransactionRejectionReason, SignedTransaction}, + trigger::{Trigger, TriggerId}, +}; + +macro_rules! impl_predicate_atom { + (@impl_evaluate_for_all_types $atom_name:ident $input_name:ident ($($ty_name:ty),*) $body:expr) => { + $( + impl crate::query::dsl::EvaluatePredicate<$ty_name> for $atom_name { + fn applies(&self, $input_name: &$ty_name) -> bool { + ($body)(self) + } + } + )* + }; + ( + $( + $(#[$($atom_attrs:tt)*])* + $atom_name:ident($input_name:ident: $ty_name:ty) [$prototype_name:ident] { + $( + $(#[$($variant_attrs:tt)*])* + $variant_name:ident$(($variant_pat:ident: $variant_ty:ty))? [$constructor_name:ident] => $variant_expr:expr + ),* + $(,)? + } + )* + ) => { + $( + #[doc = concat!("At atomic predicate on [`", stringify!($ty_name), "`]")] + #[derive( + Debug, Clone, PartialEq, Eq, + parity_scale_codec::Decode, parity_scale_codec::Encode, serde::Deserialize, serde::Serialize, iroha_schema::IntoSchema + )] + // we can't know whether the atom can implement `Copy` or not in this macro + // it's also better for future compatibility, since adding branches can make the atom non-`Copy` + #[allow(missing_copy_implementations)] + $(#[$($atom_attrs)*])* + pub enum $atom_name { + $( + $(#[$($variant_attrs)*])* + $variant_name$(($variant_ty))?, + )* + } + + + impl crate::query::dsl::HasPredicateAtom for $ty_name { + type Predicate = $atom_name; + } + + // cannot directly put all of the impl blocks here, because rust gets confused with repetitions over $variant_* not being enclosed by repetitions over $ty_name + impl_predicate_atom!{ @impl_evaluate_for_all_types $atom_name $input_name ($ty_name) + // can't use `self` directly because of the macro hygiene, hence using a closure instead + |this: &$atom_name| match *this { + $($atom_name::$variant_name$((ref $variant_pat))? => $variant_expr,)* + } + } + + // add constructor methods on the prototype + impl $prototype_name + where + Projector: ObjectProjector, + { + $( + $(#[$($variant_attrs)*])* + pub fn $constructor_name(self $(, $variant_pat: $variant_ty)?) -> CompoundPredicate { + CompoundPredicate::Atom(Projector::wrap_atom( + $atom_name::$variant_name$(($variant_pat))? + )) + } + )* + } + )* + }; +} + +/// An atomic predicate on [`String`] or [`Name`] +// Defined separately because it is shared between [String] and [Name] +#[derive( + Debug, + Clone, + PartialEq, + Eq, + parity_scale_codec::Decode, + parity_scale_codec::Encode, + serde::Deserialize, + serde::Serialize, + iroha_schema::IntoSchema, +)] +pub enum StringPredicateAtom { + /// Checks if the input is equal to the expected value. + Equals(String), + /// Checks if the input contains an expected substring, like [`str::contains()`]. + Contains(String), + /// Checks if the input starts with an expected substring, like [`str::starts_with()`]. + StartsWith(String), + /// Checks if the input ends with an expected substring, like [`str::ends_with()`]. + EndsWith(String), +} + +impl super::HasPredicateAtom for String { + type Predicate = StringPredicateAtom; +} + +impl super::HasPredicateAtom for Name { + type Predicate = StringPredicateAtom; +} + +impl StringPredicateAtom { + fn applies_to_str(&self, input: &str) -> bool { + match self { + StringPredicateAtom::Equals(content) => input == content, + StringPredicateAtom::Contains(content) => input.contains(content), + StringPredicateAtom::StartsWith(content) => input.starts_with(content), + StringPredicateAtom::EndsWith(content) => input.ends_with(content), + } + } +} + +impl super::EvaluatePredicate for StringPredicateAtom { + fn applies(&self, input: &String) -> bool { + self.applies_to_str(input.as_str()) + } +} + +impl super::EvaluatePredicate for StringPredicateAtom { + fn applies(&self, input: &Name) -> bool { + self.applies_to_str(input.as_ref()) + } +} + +// It is unfortunate that we have to repeat the prototype methods on String and Name, but I don't think it's possible to remove this duplication +impl StringPrototype +where + Projection: ObjectProjector, +{ + /// Checks if the input is equal to the expected value. + pub fn eq(self, expected: impl Into) -> CompoundPredicate { + CompoundPredicate::Atom(Projection::wrap_atom(StringPredicateAtom::Equals( + expected.into(), + ))) + } + + /// Checks if the input contains an expected substring, like [`str::contains()`]. + pub fn contains( + self, + expected: impl Into, + ) -> CompoundPredicate { + CompoundPredicate::Atom(Projection::wrap_atom(StringPredicateAtom::Contains( + expected.into(), + ))) + } + + /// Checks if the input starts with an expected substring, like [`str::starts_with()`]. + pub fn starts_with( + self, + expected: impl Into, + ) -> CompoundPredicate { + CompoundPredicate::Atom(Projection::wrap_atom(StringPredicateAtom::StartsWith( + expected.into(), + ))) + } + + /// Checks if the input ends with an expected substring, like [`str::ends_with()`]. + pub fn ends_with( + self, + expected: impl Into, + ) -> CompoundPredicate { + CompoundPredicate::Atom(Projection::wrap_atom(StringPredicateAtom::EndsWith( + expected.into(), + ))) + } +} + +impl NamePrototype +where + Projection: ObjectProjector, +{ + /// Checks if the input is equal to the expected value. + pub fn eq(self, expected: impl Into) -> CompoundPredicate { + CompoundPredicate::Atom(Projection::wrap_atom(StringPredicateAtom::Equals( + expected.into(), + ))) + } + + /// Checks if the input contains an expected substring, like [`str::contains()`]. + pub fn contains( + self, + expected: impl Into, + ) -> CompoundPredicate { + CompoundPredicate::Atom(Projection::wrap_atom(StringPredicateAtom::Contains( + expected.into(), + ))) + } + + /// Checks if the input starts with an expected substring, like [`str::starts_with()`]. + pub fn starts_with( + self, + expected: impl Into, + ) -> CompoundPredicate { + CompoundPredicate::Atom(Projection::wrap_atom(StringPredicateAtom::StartsWith( + expected.into(), + ))) + } + + /// Checks if the input ends with an expected substring, like [`str::ends_with()`]. + pub fn ends_with( + self, + expected: impl Into, + ) -> CompoundPredicate { + CompoundPredicate::Atom(Projection::wrap_atom(StringPredicateAtom::EndsWith( + expected.into(), + ))) + } +} + +impl_predicate_atom! { + MetadataPredicateAtom(_input: Metadata) [MetadataPrototype] { + // TODO: populate + } + PublicKeyPredicateAtom(input: PublicKey) [PublicKeyPrototype] { + /// Checks if the input is equal to the expected value. + Equals(expected: PublicKey) [eq] => input == expected, + } + + // account + AccountIdPredicateAtom(input: AccountId) [AccountIdPrototype] { + /// Checks if the input is equal to the expected value. + Equals(expected: AccountId) [eq] => input == expected, + } + AccountPredicateAtom(_input: Account) [AccountPrototype] {} + + // asset + AssetDefinitionPredicateAtom(_input: AssetDefinition) [AssetDefinitionPrototype] {} + AssetPredicateAtom(_input: Asset) [AssetPrototype] {} + AssetValuePredicateAtom(_input: AssetValue) [AssetValuePrototype] { + // TODO: populate + } + AssetIdPredicateAtom(input: AssetId) [AssetIdPrototype] { + /// Checks if the input is equal to the expected value. + Equals(expected: AssetId) [eq] => input == expected, + } + AssetDefinitionIdPredicateAtom(input: AssetDefinitionId) [AssetDefinitionIdPrototype] { + /// Checks if the input is equal to the expected value. + Equals(expected: AssetDefinitionId) [eq] => input == expected, + } + + // block + BlockHeaderHashPredicateAtom(input: HashOf) [BlockHeaderHashPrototype] { + /// Checks if the input is equal to the expected value. + Equals(expected: HashOf) [eq] => input == expected, + } + BlockHeaderPredicateAtom(_input: BlockHeader) [BlockHeaderPrototype] {} + SignedBlockPredicateAtom(_input: SignedBlock) [SignedBlockPrototype] {} + TransactionHashPredicateAtom(input: HashOf) [TransactionHashPrototype] { + /// Checks if the input is equal to the expected value. + Equals(expected: HashOf) [eq] => input == expected, + } + SignedTransactionPredicateAtom(_input: SignedTransaction) [SignedTransactionPrototype] {} + TransactionErrorPredicateAtom(input: Option) [TransactionErrorPrototype] { + /// Checks if there was an error while applying the transaction. + IsSome [is_some] => input.is_some(), + } + CommittedTransactionPredicateAtom(_input: CommittedTransaction) [CommittedTransactionPrototype] {} + + // domain + DomainPredicateAtom(_input: Domain) [DomainPrototype] {} + DomainIdPredicateAtom(input: DomainId) [DomainIdPrototype] { + /// Checks if the input is equal to the expected value. + Equals(expected: DomainId) [eq] => input == expected, + } + + // peer + PeerIdPredicateAtom(_input: PeerId) [PeerIdPrototype] {} + + // permission + PermissionPredicateAtom(_input: Permission) [PermissionPrototype] {} + + // parameter + ParameterPredicateAtom(_input: Parameter) [ParameterPrototype] {} + + // role + RoleIdPredicateAtom(input: RoleId) [RoleIdPrototype] { + /// Checks if the input is equal to the expected value. + Equals(expected: RoleId) [eq] => input == expected, + } + RolePredicateAtom(_input: Role) [RolePrototype] {} + + // trigger + TriggerIdPredicateAtom(input: TriggerId) [TriggerIdPrototype] { + /// Checks if the input is equal to the expected value. + Equals(expected: TriggerId) [eq] => input == expected, + } + TriggerPredicateAtom(_input: Trigger) [TriggerPrototype] {} +} + +pub mod prelude { + //! Re-export all predicate boxes for a glob import `(::*)` + pub use super::{ + AccountIdPredicateAtom, AccountPredicateAtom, AssetDefinitionIdPredicateAtom, + AssetDefinitionPredicateAtom, AssetIdPredicateAtom, AssetPredicateAtom, + AssetValuePredicateAtom, BlockHeaderHashPredicateAtom, BlockHeaderPredicateAtom, + CommittedTransactionPredicateAtom, DomainIdPredicateAtom, DomainPredicateAtom, + MetadataPredicateAtom, ParameterPredicateAtom, PeerIdPredicateAtom, + PermissionPredicateAtom, PublicKeyPredicateAtom, RoleIdPredicateAtom, RolePredicateAtom, + SignedBlockPredicateAtom, SignedTransactionPredicateAtom, StringPredicateAtom, + TransactionErrorPredicateAtom, TransactionHashPredicateAtom, TriggerIdPredicateAtom, + TriggerPredicateAtom, + }; +} diff --git a/crates/iroha_data_model/src/query/dsl/selector_traits.rs b/crates/iroha_data_model/src/query/dsl/selector_traits.rs new file mode 100644 index 00000000000..7e9e28f9473 --- /dev/null +++ b/crates/iroha_data_model/src/query/dsl/selector_traits.rs @@ -0,0 +1,73 @@ +#[cfg(not(feature = "std"))] +use alloc::vec; + +use crate::{ + prelude::SelectorTuple, + query::dsl::{HasProjection, SelectorMarker}, +}; + +/// A trait implemented on all types that can be converted into a selector (usually prototypes). +pub trait IntoSelector { + /// A type that the selector is selecting from + type SelectingType: HasProjection; + /// A type that the selector ends up selecting + // Note that this type is not exposed by the converted selector + // As such, it is not possible to do type-safe queries just by looking at the selector, a type implementing this trait must be used + type SelectedType; + /// Convert the type into a selector + fn into_selector(self) -> >::Projection; +} + +/// A trait implemented on all types that can be converted into a selector tuple (usually prototypes). +pub trait IntoSelectorTuple { + /// A type that the selector is selecting from + type SelectingType: HasProjection; + /// A tuple of types that the selector ends up selecting + type SelectedTuple; + /// Convert the type into a selector tuple + fn into_selector_tuple(self) -> SelectorTuple; +} + +impl IntoSelectorTuple for T { + type SelectingType = T::SelectingType; + type SelectedTuple = T::SelectedType; + + fn into_selector_tuple(self) -> SelectorTuple { + SelectorTuple::new(vec![self.into_selector()]) + } +} + +impl IntoSelectorTuple for (T1,) { + type SelectingType = T1::SelectingType; + type SelectedTuple = (T1::SelectedType,); + + fn into_selector_tuple(self) -> SelectorTuple { + 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),*> 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 { + 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); diff --git a/crates/iroha_data_model/src/query/dsl/selector_tuple.rs b/crates/iroha_data_model/src/query/dsl/selector_tuple.rs new file mode 100644 index 00000000000..e6797dbf34e --- /dev/null +++ b/crates/iroha_data_model/src/query/dsl/selector_tuple.rs @@ -0,0 +1,51 @@ +#[cfg(not(feature = "std"))] +use alloc::{format, string::String, vec, vec::Vec}; + +use derive_where::derive_where; +use iroha_macro::serde_where; +use iroha_schema::IntoSchema; +use parity_scale_codec::{Decode, Encode}; +use serde::{Deserialize, Serialize}; + +use crate::query::dsl::{ + BaseProjector, HasProjection, HasPrototype, IntoSelectorTuple, SelectorMarker, +}; + +/// A tuple of selectors selecting some subfields from `T`. +#[derive_where(Debug, Eq, PartialEq, Clone; T::Projection)] +#[serde_where(T::Projection)] +#[derive(Decode, Encode, Deserialize, Serialize, IntoSchema)] +pub struct SelectorTuple>(Vec); + +impl> SelectorTuple { + /// Create a new selector tuple from a list of selectors. + pub fn new(selectors: Vec) -> Self { + Self(selectors) + } + + /// Build a selector tuple using a prototype. + /// + /// Note that unlike predicates, the result of this function cannot be fed into the query builder. + pub fn build(f: F) -> Self + where + T: HasPrototype, + F: FnOnce( + ::Prototype>, + ) -> O, + ::Prototype>: Default, + O: IntoSelectorTuple, + { + f(Default::default()).into_selector_tuple() + } + + /// Iterate over the selectors in the tuple. + pub fn iter(&self) -> impl Iterator { + self.0.iter() + } +} + +impl> Default for SelectorTuple { + fn default() -> Self { + Self(vec![T::atom(())]) + } +} diff --git a/crates/iroha_data_model/src/query/dsl/type_descriptions.rs b/crates/iroha_data_model/src/query/dsl/type_descriptions.rs new file mode 100644 index 00000000000..31d0512eb3d --- /dev/null +++ b/crates/iroha_data_model/src/query/dsl/type_descriptions.rs @@ -0,0 +1,440 @@ +//! This module contains definitions of prototypes and projections for the data model types. See the [module-level documentation](crate::query::dsl) for more information. + +#[cfg(not(feature = "std"))] +use alloc::{format, string::String, vec::Vec}; + +use derive_where::derive_where; +use iroha_crypto::{HashOf, PublicKey}; + +// used in the macro +use crate::query::dsl::{ + EvaluatePredicate, EvaluateSelector, HasProjection, HasPrototype, IntoSelector, + ObjectProjector, PredicateMarker, Projectable, SelectorMarker, +}; +use crate::{ + account::{Account, AccountId}, + asset::{Asset, AssetDefinition, AssetDefinitionId, AssetId, AssetValue}, + block::{BlockHeader, SignedBlock}, + domain::{Domain, DomainId}, + metadata::Metadata, + name::Name, + parameter::Parameter, + peer::PeerId, + permission::Permission, + query::{CommittedTransaction, QueryOutputBatchBox}, + role::{Role, RoleId}, + transaction::{error::TransactionRejectionReason, SignedTransaction}, + trigger::{Trigger, TriggerId}, +}; + +macro_rules! type_descriptions { + (@check_attrs) => {}; + (@check_attrs #[custom_evaluate] $($rest:tt)*) => { + type_descriptions!(@check_attrs $($rest)*); + }; + + (@evaluate + (#[custom_evaluate] $($rest:tt)*) + ($ty:ty) $projection_name:ident + ($($field_name:ident($proj_variant:ident))*) + ) => { + // the user has requested a custom evaluation impl, so do not derive it + }; + (@evaluate + () + ($ty:ty) $projection_name:ident + ($($field_name:ident($proj_variant:ident))*) + ) => { + impl EvaluatePredicate<$ty> for $projection_name { + fn applies(&self, input: &$ty) -> bool { + match self { + $projection_name::Atom(atom) => atom.applies(input), + $( + $projection_name::$proj_variant(field) => EvaluatePredicate::applies(field, &input.$field_name), + )* + } + } + } + + impl EvaluateSelector<$ty> for $projection_name { + #[expect(single_use_lifetimes)] // FP, this the suggested change is not allowed on stable + fn project_clone<'a>(&self, batch: impl Iterator) -> QueryOutputBatchBox { + match self { + $projection_name::Atom(_) => batch.cloned().collect::>().into(), + $( + $projection_name::$proj_variant(field) => field.project_clone(batch.map(|item| &item.$field_name)), + )* + } + } + + fn project(&self, batch: impl Iterator) -> QueryOutputBatchBox { + match self { + $projection_name::Atom(_) => batch.collect::>().into(), + $( + $projection_name::$proj_variant(field) => field.project(batch.map(|item| item.$field_name)), + )* + } + } + } + }; + (@object_projector + ($ty:ty) $projection_name:ident + ($field_name:ident($proj_variant:ident, $projector_name:ident): $field_ty:ty) + // unpack tt sequence back + ($($dep_ty_bounds:tt)*) + ) => { + #[doc = concat!("A projector on [`", stringify!($ty), "`] for its `", stringify!($field_name), "` field.")] + pub struct $projector_name(core::marker::PhantomData<(Marker, Base)>); + + impl ObjectProjector for $projector_name + where + Base: ObjectProjector, + $ty: Projectable + $($dep_ty_bounds)* + { + type InputType = $field_ty; + type OutputType = Base::OutputType; + + fn project( + projection: <$field_ty as HasProjection>::Projection + ) -> >::Projection { + Base::project($projection_name::$proj_variant(projection)) + } + } + }; + (@object_projector_repeated + ($ty:ty) $projection_name:ident + ($( + $field_name:ident($proj_variant:ident, $projector_name:ident): $field_ty:ty, + )*) + // smuggle a tt sequence as a single tt + $dep_ty_bounds:tt + ) => { + $( + type_descriptions!{ @object_projector ($ty) $projection_name + ($field_name($proj_variant, $projector_name): $field_ty) + $dep_ty_bounds + } + )* + }; + + ($( + $(#[$($attrs:tt)*])* + $ty:ty[$projection_name:ident, $prototype_name:ident] $(: $($dep_ty:ty),+)? { + $( + $field_name:ident($proj_variant:ident, $projector_name:ident): $field_ty:ty, + )* + } + )*) => { + $( + type_descriptions!(@check_attrs $(#[$($attrs)*])*); + + // hints to the IDE that these are types + #[allow(unused_variables)] + const _:() = { + let t: $ty; + $($(let t: $dep_ty;)+)? + $(let t: $field_ty;)* + }; + + // projection enum + #[doc = concat!("A projection for the [`", stringify!($ty), "`] type.")] + // use derive_where to generate trait bounds + #[derive_where::derive_where(Debug, Eq, PartialEq, Copy, Clone; + <$ty as Projectable>::AtomType + $(, <$field_ty as HasProjection>::Projection)* + )] + // parity-scale-codec and iroha_schema generates correct bounds by themselves + #[derive(parity_scale_codec::Decode, parity_scale_codec::Encode, iroha_schema::IntoSchema)] + // use serde_where macro to generate the correct #[serde(bounds(...))] attribute + #[iroha_macro::serde_where( + <$ty as Projectable>::AtomType + $(, <$field_ty as HasProjection>::Projection)* + )] + #[derive(serde::Deserialize, serde::Serialize)] + pub enum $projection_name + where + $ty: Projectable + $($(, $dep_ty: HasProjection)*)? + { + #[doc = "Finish the projection with an atom."] + Atom(<$ty as Projectable>::AtomType), + $( + #[doc = concat!("Projection for the `", stringify!($field_name), "` field.")] + $proj_variant( + <$field_ty as HasProjection>::Projection + ), + )* + } + + impl HasProjection for $ty + where + $ty: Projectable + $($(, $dep_ty: HasProjection)*)? + { + type Projection = $projection_name; + + fn atom(atom: Self::AtomType) -> Self::Projection { + $projection_name::Atom(atom) + } + } + + type_descriptions!(@evaluate + ($(#[$($attrs)*])*) + ($ty) $projection_name + ($($field_name($proj_variant))*) + ); + + // projector structs + // because we need to repeat $dep_ty inside a disjoint repetition, use another macro + type_descriptions!(@object_projector_repeated ($ty) $projection_name ($( + $field_name($proj_variant, $projector_name): $field_ty, + )*) ($(, $($dep_ty: Projectable),*)?)); + + // prototype struct + #[doc = concat!("A prototype for the [`", stringify!($ty), "`] type.")] + #[derive_where::derive_where(Default, Copy, Clone)] + pub struct $prototype_name { + $( + // TODO: I think it might make sense to provide field documentation here. How would we do that without copying the docs to the type description macro though? + #[doc = concat!("Accessor for the `", stringify!($field_name), "` field.")] + pub $field_name: <$field_ty as HasPrototype>::Prototype>, + )* + phantom: core::marker::PhantomData<(Marker, Projector)>, + } + + impl HasPrototype for $ty + { + type Prototype = $prototype_name; + } + + impl IntoSelector for $prototype_name + where + Projector: ObjectProjector, + Projector::OutputType: HasProjection, + { + type SelectingType = Projector::OutputType; + type SelectedType = Projector::InputType; + + fn into_selector(self) -> >::Projection { + Projector::wrap_atom(()) + } + } + )* + + mod projections { + $( + pub use super::$projection_name; + )* + } + }; +} + +type_descriptions! { + // Type[ProjectionName, PrototypeName]: Dependency1, Dependency2, ... + Account[AccountProjection, AccountPrototype]: AccountId, DomainId, Name, PublicKey, Metadata { + // field_name(ProjectionVariant, ProjectorName): FieldType + id(Id, AccountIdProjector): AccountId, + metadata(Metadata, AccountMetadataProjector): Metadata, + } + AccountId[AccountIdProjection, AccountIdPrototype]: DomainId, Name, PublicKey { + domain(Domain, AccountIdDomainProjector): DomainId, + signatory(Signatory, AccountIdSignatoryProjector): PublicKey, + } + + // asset + AssetDefinition[AssetDefinitionProjection, AssetDefinitionPrototype]: AssetDefinitionId, DomainId, Name, Metadata { + id(Id, AssetDefinitionIdProjector): AssetDefinitionId, + metadata(Metadata, AssetDefinitionMetadataProjector): Metadata, + } + AssetDefinitionId[AssetDefinitionIdProjection, AssetDefinitionIdPrototype]: DomainId, Name { + domain(Domain, AssetDefinitionIdDomainProjector): DomainId, + name(Name, AssetDefinitionIdNameProjector): Name, + } + Asset[AssetProjection, AssetPrototype]: AssetId, AccountId, DomainId, Name, PublicKey, AssetDefinitionId, AssetValue { + id(Id, AssetIdProjector): AssetId, + value(Value, AssetValueProjector): AssetValue, + } + AssetId[AssetIdProjection, AssetIdPrototype]: AccountId, DomainId, Name, PublicKey, AssetDefinitionId { + account(Account, AssetIdAccountProjector): AccountId, + definition(Definition, AssetIdDefinitionProjector): AssetDefinitionId, + } + AssetValue[AssetValueProjection, AssetValuePrototype] {} + + // block + HashOf[BlockHeaderHashProjection, BlockHeaderHashPrototype] {} + #[custom_evaluate] // hash needs to be computed on-the-fly + BlockHeader[BlockHeaderProjection, BlockHeaderPrototype]: HashOf { + hash(Hash, BlockHeaderHashProjector): HashOf, + } + #[custom_evaluate] // SignedBlock is opaque, so `header` is a method + SignedBlock[SignedBlockProjection, SignedBlockPrototype]: BlockHeader, HashOf { + header(Header, SignedBlockHeaderProjector): BlockHeader, + } + HashOf[TransactionHashProjection, TransactionHashPrototype] {} + #[custom_evaluate] // hash needs to be computed on-the-fly + SignedTransaction[SignedTransactionProjection, SignedTransactionPrototype]: HashOf, AccountId, DomainId, Name, PublicKey { + hash(Hash, SignedTransactionHashProjector): HashOf, + authority(Authority, SignedTransactionAuthorityProjector): AccountId, + } + Option[TransactionErrorProjection, TransactionErrorPrototype] {} + CommittedTransaction[CommittedTransactionProjection, CommittedTransactionPrototype]: HashOf, SignedTransaction, HashOf, AccountId, DomainId, Name, PublicKey, Option { + block_hash(BlockHash, CommittedTransactionBlockHashProjector): HashOf, + value(Value, CommittedTransactionValueProjector): SignedTransaction, + error(Error, CommittedTransactionErrorProjector): Option, + } + + // domain + Domain[DomainProjection, DomainPrototype]: DomainId, Name, Metadata { + id(Id, DomainIdProjector): DomainId, + metadata(Metadata, DomainMetadataProjector): Metadata, + } + DomainId[DomainIdProjection, DomainIdPrototype]: Name { + name(Name, DomainIdNameProjector): Name, + } + + // peer + PeerId[PeerIdProjection, PeerIdPrototype]: PublicKey { + public_key(PublicKey, PeerIdPublicKeyProjector): PublicKey, + } + + // permission + Permission[PermissionProjection, PermissionPrototype] {} + + // parameter + Parameter[ParameterProjection, ParameterPrototype] {} + + // role + RoleId[RoleIdProjection, RoleIdPrototype]: Name { + name(Name, RoleIdNameProjector): Name, + } + Role[RoleProjection, RolePrototype]: RoleId, Name { + id(Id, RoleIdProjector): RoleId, + // TODO: it would be nice to have predicate on permissions, but we do not support predicates on collections yet + // permissions(Permissions, RolePermissionsProjector): Permissions, + } + + // trigger + TriggerId[TriggerIdProjection, TriggerIdPrototype]: Name { + name(Name, TriggerIdNameProjector): Name, + } + Trigger[TriggerProjection, TriggerPrototype]: TriggerId, Name { + id(Id, TriggerIdProjector): TriggerId, + } + + // note: even though `NameProjection` and `StringProjection` are distinct types, + // their predicates types are the same + Name[NameProjection, NamePrototype] {} + String[StringProjection, StringPrototype] {} + + PublicKey[PublicKeyProjection, PublicKeyPrototype] {} + Metadata[MetadataProjection, MetadataPrototype] { + // TODO: we will probably want to have a special-cased metadata projection that allows accessing fields by string keys (because metadata is not statically typed) + } +} + +// evaluate implementations that could not be implemented in a macro +impl EvaluatePredicate for BlockHeaderProjection { + fn applies(&self, input: &BlockHeader) -> bool { + match self { + BlockHeaderProjection::Atom(atom) => atom.applies(input), + BlockHeaderProjection::Hash(hash) => hash.applies(&input.hash()), + } + } +} + +impl EvaluateSelector for BlockHeaderProjection { + #[expect(single_use_lifetimes)] // FP, this the suggested change is not allowed on stable + fn project_clone<'a>( + &self, + batch: impl Iterator, + ) -> QueryOutputBatchBox { + match self { + BlockHeaderProjection::Atom(_) => batch.cloned().collect::>().into(), + BlockHeaderProjection::Hash(hash) => hash.project(batch.map(|item| item.hash())), + } + } + + fn project(&self, batch: impl Iterator) -> QueryOutputBatchBox { + match self { + BlockHeaderProjection::Atom(_) => batch.collect::>().into(), + BlockHeaderProjection::Hash(hash) => hash.project(batch.map(|item| item.hash())), + } + } +} + +impl EvaluatePredicate for SignedBlockProjection { + fn applies(&self, input: &SignedBlock) -> bool { + match self { + SignedBlockProjection::Atom(atom) => atom.applies(input), + SignedBlockProjection::Header(header) => header.applies(&input.header()), + } + } +} + +impl EvaluateSelector for SignedBlockProjection { + #[expect(single_use_lifetimes)] // FP, this the suggested change is not allowed on stable + fn project_clone<'a>( + &self, + batch: impl Iterator, + ) -> QueryOutputBatchBox { + match self { + SignedBlockProjection::Atom(_) => batch.cloned().collect::>().into(), + SignedBlockProjection::Header(header) => { + header.project(batch.map(|item| item.header())) + } + } + } + + fn project(&self, batch: impl Iterator) -> QueryOutputBatchBox { + match self { + SignedBlockProjection::Atom(_) => batch.collect::>().into(), + SignedBlockProjection::Header(header) => { + header.project(batch.map(|item| item.header())) + } + } + } +} + +impl EvaluatePredicate for SignedTransactionProjection { + fn applies(&self, input: &SignedTransaction) -> bool { + match self { + SignedTransactionProjection::Atom(atom) => atom.applies(input), + SignedTransactionProjection::Hash(hash) => hash.applies(&input.hash()), + SignedTransactionProjection::Authority(authority) => { + authority.applies(&input.authority()) + } + } + } +} + +impl EvaluateSelector for SignedTransactionProjection { + #[expect(single_use_lifetimes)] // FP, this the suggested change is not allowed on stable + fn project_clone<'a>( + &self, + batch: impl Iterator, + ) -> QueryOutputBatchBox { + match self { + SignedTransactionProjection::Atom(_) => batch.cloned().collect::>().into(), + SignedTransactionProjection::Hash(hash) => hash.project(batch.map(|item| item.hash())), + SignedTransactionProjection::Authority(authority) => { + authority.project_clone(batch.map(|item| item.authority())) + } + } + } + + fn project(&self, batch: impl Iterator) -> QueryOutputBatchBox { + match self { + SignedTransactionProjection::Atom(_) => batch.collect::>().into(), + SignedTransactionProjection::Hash(hash) => hash.project(batch.map(|item| item.hash())), + SignedTransactionProjection::Authority(authority) => { + authority.project(batch.map(|item| item.authority().clone())) + } + } + } +} + +pub mod prelude { + //! Re-export all projections for a glob import `(::*)` + pub use super::projections::*; +} diff --git a/crates/iroha_data_model/src/query/mod.rs b/crates/iroha_data_model/src/query/mod.rs index 39038062783..4a488943a27 100644 --- a/crates/iroha_data_model/src/query/mod.rs +++ b/crates/iroha_data_model/src/query/mod.rs @@ -18,14 +18,16 @@ use serde::{Deserialize, Serialize}; pub use self::model::*; use self::{ - account::*, asset::*, block::*, domain::*, executor::*, peer::*, permission::*, predicate::*, + account::*, asset::*, block::*, domain::*, dsl::*, executor::*, peer::*, permission::*, role::*, transaction::*, trigger::*, }; use crate::{ account::{Account, AccountId}, - asset::{Asset, AssetDefinition}, + asset::{Asset, AssetDefinition, AssetDefinitionId, AssetId, AssetValue}, block::{BlockHeader, SignedBlock}, - domain::Domain, + domain::{Domain, DomainId}, + metadata::Metadata, + name::Name, parameter::{Parameter, Parameters}, peer::PeerId, permission::Permission, @@ -36,8 +38,8 @@ use crate::{ }; pub mod builder; +pub mod dsl; pub mod parameters; -pub mod predicate; /// A query that either returns a single value or errors out // NOTE: we are planning to remove this class of queries (https://github.com/hyperledger-iroha/iroha/issues/4933) @@ -53,30 +55,39 @@ pub trait SingularQuery: Sealed { /// [`builder::QueryIterator`] abstracts over this and allows the query consumer to use a familiar [`Iterator`] interface to iterate over the results. pub trait Query: Sealed { /// The type of single element of the output collection - type Item: HasPredicateBox; + type Item: HasProjection + HasProjection; } #[model] mod model { + use derive_where::derive_where; use getset::Getters; use iroha_crypto::HashOf; + use iroha_macro::serde_where; use super::*; /// An iterable query bundled with a filter - /// - /// The `P` type doesn't have any bounds to simplify generic trait bounds in some places. - /// Use [`super::QueryWithFilterFor`] if you have a concrete query type to avoid specifying `P` manually. - #[derive( - Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema, Constructor, + #[serde_where(Q, CompoundPredicate, SelectorTuple)] + #[derive_where( + Debug, Clone, PartialEq, Eq; Q, CompoundPredicate, SelectorTuple )] - pub struct QueryWithFilter { + #[derive(Decode, Encode, Constructor, IntoSchema, Deserialize, Serialize)] + pub struct QueryWithFilter + where + Q: Query, + { pub query: Q, #[serde(default = "predicate_default")] - pub predicate: CompoundPredicate

, + pub predicate: CompoundPredicate, + #[serde(default)] + pub selector: SelectorTuple, } - fn predicate_default

() -> CompoundPredicate

{ + fn predicate_default() -> CompoundPredicate + where + T: HasProjection, + { CompoundPredicate::PASS } @@ -85,23 +96,23 @@ mod model { Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema, FromVariant, )] pub enum QueryBox { - FindDomains(QueryWithFilterFor), - FindAccounts(QueryWithFilterFor), - FindAssets(QueryWithFilterFor), - FindAssetsDefinitions(QueryWithFilterFor), - FindRoles(QueryWithFilterFor), - - FindRoleIds(QueryWithFilterFor), - FindPermissionsByAccountId(QueryWithFilterFor), - FindRolesByAccountId(QueryWithFilterFor), - FindAccountsWithAsset(QueryWithFilterFor), - - FindPeers(QueryWithFilterFor), - FindActiveTriggerIds(QueryWithFilterFor), - FindTriggers(QueryWithFilterFor), - FindTransactions(QueryWithFilterFor), - FindBlocks(QueryWithFilterFor), - FindBlockHeaders(QueryWithFilterFor), + FindDomains(QueryWithFilter), + FindAccounts(QueryWithFilter), + FindAssets(QueryWithFilter), + FindAssetsDefinitions(QueryWithFilter), + FindRoles(QueryWithFilter), + + FindRoleIds(QueryWithFilter), + FindPermissionsByAccountId(QueryWithFilter), + FindRolesByAccountId(QueryWithFilter), + FindAccountsWithAsset(QueryWithFilter), + + FindPeers(QueryWithFilter), + FindActiveTriggerIds(QueryWithFilter), + FindTriggers(QueryWithFilter), + FindTransactions(QueryWithFilter), + FindBlocks(QueryWithFilter), + FindBlockHeaders(QueryWithFilter), } /// An enum of all possible iterable query batches. @@ -111,20 +122,38 @@ mod model { Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema, FromVariant, )] pub enum QueryOutputBatchBox { + PublicKey(Vec), + String(Vec), + Metadata(Vec), + Name(Vec), + DomainId(Vec), Domain(Vec), + AccountId(Vec), Account(Vec), + AssetId(Vec), Asset(Vec), + AssetValue(Vec), + AssetDefinitionId(Vec), AssetDefinition(Vec), Role(Vec), Parameter(Vec), Permission(Vec), - Transaction(Vec), + CommittedTransaction(Vec), + SignedTransaction(Vec), + TransactionHash(Vec>), + TransactionRejectionReason(Vec>), Peer(Vec), RoleId(Vec), TriggerId(Vec), Trigger(Vec), Block(Vec), BlockHeader(Vec), + BlockHeaderHash(Vec>), + } + + #[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] + pub struct QueryOutputBatchBoxTuple { + pub tuple: Vec, } /// An enum of all possible singular queries @@ -161,7 +190,7 @@ mod model { #[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] pub struct QueryOutput { /// A single batch of results - pub batch: QueryOutputBatchBox, + pub batch: QueryOutputBatchBoxTuple, /// The number of items in the query remaining to be fetched after this batch pub remaining_items: u64, /// If not `None`, contains a cursor that can be used to fetch the next batch of results. Otherwise the current batch is the last one. @@ -251,10 +280,6 @@ mod model { } } -/// A type alias to refer to a [`QueryWithFilter`] paired with a correct predicate -pub type QueryWithFilterFor = - QueryWithFilter::Item as HasPredicateBox>::PredicateBoxType>; - impl QueryOutputBatchBox { // this is used in client cli to do type-erased iterable queries /// Extends this batch with another batch of the same type @@ -264,20 +289,35 @@ impl QueryOutputBatchBox { /// Panics if the types of the two batches do not match pub fn extend(&mut self, other: QueryOutputBatchBox) { match (self, other) { + (Self::PublicKey(v1), Self::PublicKey(v2)) => v1.extend(v2), + (Self::String(v1), Self::String(v2)) => v1.extend(v2), + (Self::Metadata(v1), Self::Metadata(v2)) => v1.extend(v2), + (Self::Name(v1), Self::Name(v2)) => v1.extend(v2), + (Self::DomainId(v1), Self::DomainId(v2)) => v1.extend(v2), (Self::Domain(v1), Self::Domain(v2)) => v1.extend(v2), + (Self::AccountId(v1), Self::AccountId(v2)) => v1.extend(v2), (Self::Account(v1), Self::Account(v2)) => v1.extend(v2), + (Self::AssetId(v1), Self::AssetId(v2)) => v1.extend(v2), (Self::Asset(v1), Self::Asset(v2)) => v1.extend(v2), + (Self::AssetValue(v1), Self::AssetValue(v2)) => v1.extend(v2), + (Self::AssetDefinitionId(v1), Self::AssetDefinitionId(v2)) => v1.extend(v2), (Self::AssetDefinition(v1), Self::AssetDefinition(v2)) => v1.extend(v2), (Self::Role(v1), Self::Role(v2)) => v1.extend(v2), (Self::Parameter(v1), Self::Parameter(v2)) => v1.extend(v2), (Self::Permission(v1), Self::Permission(v2)) => v1.extend(v2), - (Self::Transaction(v1), Self::Transaction(v2)) => v1.extend(v2), + (Self::CommittedTransaction(v1), Self::CommittedTransaction(v2)) => v1.extend(v2), + (Self::SignedTransaction(v1), Self::SignedTransaction(v2)) => v1.extend(v2), + (Self::TransactionHash(v1), Self::TransactionHash(v2)) => v1.extend(v2), + (Self::TransactionRejectionReason(v1), Self::TransactionRejectionReason(v2)) => { + v1.extend(v2) + } (Self::Peer(v1), Self::Peer(v2)) => v1.extend(v2), (Self::RoleId(v1), Self::RoleId(v2)) => v1.extend(v2), (Self::TriggerId(v1), Self::TriggerId(v2)) => v1.extend(v2), (Self::Trigger(v1), Self::Trigger(v2)) => v1.extend(v2), (Self::Block(v1), Self::Block(v2)) => v1.extend(v2), (Self::BlockHeader(v1), Self::BlockHeader(v2)) => v1.extend(v2), + (Self::BlockHeaderHash(v1), Self::BlockHeaderHash(v2)) => v1.extend(v2), _ => panic!("Cannot extend different types of IterableQueryOutputBatchBox"), } } @@ -286,24 +326,71 @@ impl QueryOutputBatchBox { #[allow(clippy::len_without_is_empty)] // having a len without `is_empty` is fine, we don't return empty batches pub fn len(&self) -> usize { match self { + Self::PublicKey(v) => v.len(), + Self::String(v) => v.len(), + Self::Metadata(v) => v.len(), + Self::Name(v) => v.len(), + Self::DomainId(v) => v.len(), Self::Domain(v) => v.len(), + Self::AccountId(v) => v.len(), Self::Account(v) => v.len(), + Self::AssetId(v) => v.len(), Self::Asset(v) => v.len(), + Self::AssetValue(v) => v.len(), + Self::AssetDefinitionId(v) => v.len(), Self::AssetDefinition(v) => v.len(), Self::Role(v) => v.len(), Self::Parameter(v) => v.len(), Self::Permission(v) => v.len(), - Self::Transaction(v) => v.len(), + Self::CommittedTransaction(v) => v.len(), + Self::SignedTransaction(v) => v.len(), + Self::TransactionHash(v) => v.len(), + Self::TransactionRejectionReason(v) => v.len(), Self::Peer(v) => v.len(), Self::RoleId(v) => v.len(), Self::TriggerId(v) => v.len(), Self::Trigger(v) => v.len(), Self::Block(v) => v.len(), Self::BlockHeader(v) => v.len(), + Self::BlockHeaderHash(v) => v.len(), } } } +impl QueryOutputBatchBoxTuple { + /// Extends this batch tuple with another batch tuple of the same type + /// + /// # Panics + /// + /// Panics if the types or lengths of the two batche tuples do not match + pub fn extend(&mut self, other: Self) { + if self.tuple.len() != other.tuple.len() { + panic!("Cannot extend QueryOutputBatchBoxTuple with different number of elements"); + } + + self.tuple + .iter_mut() + .zip(other.tuple.into_iter()) + .for_each(|(self_batch, other_batch)| self_batch.extend(other_batch)); + } + + /// Returns length of this batch tuple + // This works under assumption that all batches in the tuples have the same length, which should be true for iroha + pub fn len(&self) -> usize { + self.tuple[0].len() + } + + /// Returns an iterator over the batches in this tuple + pub fn iter(&self) -> impl Iterator { + self.tuple.iter() + } + + /// Consumes this batch tuple and returns an iterator over the batches + pub fn into_iter(self) -> impl Iterator { + self.tuple.into_iter() + } +} + impl SingularQuery for SingularQueryBox { type Output = SingularQueryOutputBox; } @@ -311,7 +398,7 @@ impl SingularQuery for SingularQueryBox { impl QueryOutput { /// Create a new [`QueryOutput`] from the iroha response parts. pub fn new( - batch: QueryOutputBatchBox, + batch: QueryOutputBatchBoxTuple, remaining_items: u64, continue_cursor: Option, ) -> Self { @@ -323,7 +410,7 @@ impl QueryOutput { } /// Split this [`QueryOutput`] into its constituent parts. - pub fn into_parts(self) -> (QueryOutputBatchBox, u64, Option) { + pub fn into_parts(self) -> (QueryOutputBatchBoxTuple, u64, Option) { (self.batch, self.remaining_items, self.continue_cursor) } } @@ -1097,8 +1184,8 @@ pub mod error { pub mod prelude { pub use super::{ account::prelude::*, asset::prelude::*, block::prelude::*, builder::prelude::*, - domain::prelude::*, executor::prelude::*, parameters::prelude::*, peer::prelude::*, - permission::prelude::*, predicate::prelude::*, role::prelude::*, transaction::prelude::*, + domain::prelude::*, dsl::prelude::*, executor::prelude::*, parameters::prelude::*, + peer::prelude::*, permission::prelude::*, role::prelude::*, transaction::prelude::*, trigger::prelude::*, CommittedTransaction, QueryBox, QueryRequest, SingularQueryBox, }; } diff --git a/crates/iroha_data_model/src/query/predicate/mod.rs b/crates/iroha_data_model/src/query/predicate/mod.rs deleted file mode 100644 index d9e77669afe..00000000000 --- a/crates/iroha_data_model/src/query/predicate/mod.rs +++ /dev/null @@ -1,370 +0,0 @@ -//! Contain definitions of predicates for different types and a DSL to build them. -//! -//! # Implementation details of the predicate DSL -//! -//! There are three main components to the predicate DSL: -//! - Prototypes -//! - Projectors -//! - Atomic predicates and combinators -//! -//! Prototype is a structure that mimics an object the predicate is being built on. -//! You can call methods on it to build predicate directly (like [`prototypes::account::AccountIdPrototype::eq`]) or access one of its fields, which are all prototypes of the elements of the object (like `account_id.domain_id`). -//! -//! Projectors are needed for inner elements of prototypes to remember the object they are part of, so that `account_id.domain_id` would still build `AccountIdPredicateBox`es, while still being an `DomainIdPrototype`. -//! -//! This is achieved by passing an implementation of [`projectors::ObjectProjector`] trait to the prototype. An object projector accepts a predicate of a more specific type and returns a predicate of a more general type wrapped in a projection. -//! -//! For example, `AccountIdDomainIdProjector` accepts a predicate on `DomainId` makes a predicate on `AccountId` by wrapping it with `AccountIdDomainIdProjection`. Notice the difference between projector and projection: projector is just an utility type, while projection is a predicate. -//! -//! ## Predicate combinators and normalization -//! -//! There are also two representations of the predicates: -//! - Normalized representation, which is designed for serialization and evaluation -//! - AST representation, which is designed for type-checking and easy & efficient composition -//! -//! Normalized representation consists of [`CompoundPredicate`], with `T` being an atomic predicate box for that type (like [`predicate_atoms::account::AccountIdPredicateBox`]). -//! The [`CompoundPredicate`] layer implements logical operators on top of the atomic predicate, while the projections are handled with the atomic predicate itself, with variants like [`predicate_atoms::account::AccountIdPredicateBox::DomainId`]. -//! -//! Normalized representation aims to reduce the number of types not to bloat the schema and reduce redundancy. -//! -//! Predicates in the normalized representation can be evaluated using the [`EvaluatePredicate`] trait. -//! -//! Ast predicates are more numerous: they include atomic predicates (like [`predicate_atoms::account::AccountIdPredicateBox`]), logical combinators (three types in [`predicate_combinators`]), and projections (like [`projectors::AccountIdDomainIdProjection`]). -//! -//! Ast predicates implement [`AstPredicate`] the trait with `T` being the atomic predicate box they normalize into. -//! The [`AstPredicate`] defines the logic for converting the AST into the normalized representation. - -pub mod predicate_ast_extensions; -pub mod predicate_atoms; -pub mod predicate_combinators; -pub mod projectors; -pub mod prototypes; - -#[cfg(not(feature = "std"))] -use alloc::{boxed::Box, vec, vec::Vec}; - -use super::*; - -/// Trait defining how to apply a predicate to a value `T`. -pub trait EvaluatePredicate { - /// The result of applying the predicate to a value. - fn applies(&self, input: &T) -> bool; -} - -/// A predicate combinator adding support for logical operations on some atomic (basis) predicate type. -#[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] -pub enum CompoundPredicate { - /// An atomic predicate as-is. - Atom(Atom), - /// A negation of a compound predicate. - Not(Box), - /// A conjunction of multiple predicates. - And(Vec), - /// A disjunction of multiple predicates. - Or(Vec), -} - -impl CompoundPredicate { - /// 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()); - - /// Negate the predicate. - #[allow(clippy::should_implement_trait)] - // not implementing std::ops::Not, because - // - don't need it used as an overloaded operator (most of this happens with AST predicates) - // - making this symmetric with `and` and `or` would require renaming them to `bitand` and `bitor` which is a downgrade IMO - #[must_use] - pub fn not(self) -> Self { - match self { - // if the top-level predicate is a negation, we can just remove it - CompoundPredicate::Not(expr) => *expr, - this => CompoundPredicate::Not(Box::new(this)), - } - } - - /// Combine two predicates with an "and" operation. - #[must_use] - pub fn and(self, other: Self) -> Self { - match (self, other) { - // if any of the predicates is an and - flatten it - (CompoundPredicate::And(mut and_list), other) => { - and_list.push(other); - CompoundPredicate::And(and_list) - } - (this, CompoundPredicate::And(mut and_list)) => { - // push to front to preserve user-specified order (our predicates are short-circuiting) - and_list.insert(0, this); - CompoundPredicate::And(and_list) - } - (this, other) => CompoundPredicate::And(vec![this, other]), - } - } - - /// Combine two predicates with an "or" operation. - #[must_use] - pub fn or(self, other: Self) -> Self { - match (self, other) { - // if any of the predicates is an or - flatten it - (CompoundPredicate::Or(mut or_list), other) => { - or_list.push(other); - CompoundPredicate::Or(or_list) - } - (this, CompoundPredicate::Or(mut or_list)) => { - // push to front to preserve user-specified order (our predicates are short-circuiting) - or_list.insert(0, this); - CompoundPredicate::Or(or_list) - } - (this, other) => CompoundPredicate::Or(vec![this, other]), - } - } -} - -/// A marker trait allowing to associate a predicate box type corresponding to the type. -pub trait HasPredicateBox { - /// The type of the atomic predicate corresponding to the type. - type PredicateBoxType: EvaluatePredicate; -} - -/// A marker trait allowing to get a type of prototype for the type. -/// -/// Not that it is implemented on predicate, not on concrete types. That is because some predicates, like [`predicate_atoms::StringPredicateBox`] are applicable to multiple types. -pub trait HasPrototype { - /// Get a prototype for the predicate, with the given projector. - type Prototype: Default; -} - -impl EvaluatePredicate for CompoundPredicate -where - Atom: EvaluatePredicate, -{ - fn applies(&self, input: &T) -> bool { - match self { - CompoundPredicate::Atom(atom) => atom.applies(input), - CompoundPredicate::Not(expr) => !expr.applies(input), - CompoundPredicate::And(exprs) => exprs.iter().all(|expr| expr.applies(input)), - CompoundPredicate::Or(exprs) => exprs.iter().any(|expr| expr.applies(input)), - } - } -} - -/// Trait that marks a predicate in AST representation. The `PredType` generic parameters defines the type of the atomic predicate this predicate normalizes into. -/// -/// The main task is to facilitate normalization: -/// the extraction of all logical combinators ("not", "and" and "or") to the outer scope, -/// leaving only projections as "atomic" predicates -pub trait AstPredicate { - /// Normalize the predicate, applying `proj` to every atomic predicate emitted. - fn normalize_with_proj(self, proj: Proj) -> CompoundPredicate - where - Proj: Fn(PredType) -> OutputType + Copy; -} - -pub mod prelude { - //! Re-export important types and traits for glob import `(::*)` - pub use super::{predicate_atoms::prelude::*, CompoundPredicate, EvaluatePredicate}; -} - -#[cfg(test)] -mod test { - use iroha_crypto::PublicKey; - - use crate::{ - account::AccountId, - asset::AssetId, - domain::DomainId, - prelude::{AssetDefinitionIdPredicateBox, AssetIdPredicateBox, StringPredicateBox}, - query::predicate::{ - predicate_ast_extensions::AstPredicateExt as _, - predicate_atoms::{ - account::{AccountIdPredicateBox, AccountPredicateBox}, - domain::DomainIdPredicateBox, - PublicKeyPredicateBox, - }, - projectors::BaseProjector, - prototypes::account::AccountPrototype, - CompoundPredicate, - }, - }; - - /// Smoke test that creates a simple predicate using a prototype - #[test] - fn test_prototype_api() { - let alice_account_id: AccountId = - "ed0120CE7FA46C9DCE7EA4B125E2E36BDB63EA33073E7590AC92816AE1E861B7048B03@wonderland" - .parse() - .unwrap(); - - // construct a prototype (done by the machinery) - let account = AccountPrototype::>::default(); - // take a look at the type name (it should be `AccountIdPrototype>>`) - #[allow(unused)] - let account_id_prototype = account.id; - // make a predicate from it (done by the user) - let predicate = account.id.eq(alice_account_id.clone()); - // normalize it (done by the machinery) - let predicate_norm = predicate.normalize(); - - // check that the predicate is correct - assert_eq!( - predicate_norm, - CompoundPredicate::Atom(AccountPredicateBox::Id(AccountIdPredicateBox::Equals( - alice_account_id - ))) - ); - } - - /// Same as [`test_prototype_api`], but uses the `AccountPredicateBox::build()` method - #[test] - fn test_builder_api() { - let alice_account_id: AccountId = - "ed0120CE7FA46C9DCE7EA4B125E2E36BDB63EA33073E7590AC92816AE1E861B7048B03@wonderland" - .parse() - .unwrap(); - - let predicate_norm = - AccountPredicateBox::build(|account| account.id.eq(alice_account_id.clone())); - - // check that the predicate is correct - assert_eq!( - predicate_norm, - CompoundPredicate::Atom(AccountPredicateBox::Id(AccountIdPredicateBox::Equals( - alice_account_id - ))) - ); - } - - /// Create a denormalized predicate (logical combinator inside a projection), check that it normalizes correctly - #[test] - fn test_prototype_normalization() { - let alice_signatory: PublicKey = - "ed0120CE7FA46C9DCE7EA4B125E2E36BDB63EA33073E7590AC92816AE1E861B7048B03" - .parse() - .unwrap(); - let alice_domain_id: DomainId = "wonderland".parse().unwrap(); - - let account_predicate_denorm = AccountPredicateBox::build_fragment(|account| { - let account_id_predicate = AccountIdPredicateBox::build_fragment(|account_id| { - account_id - .signatory - .eq(alice_signatory.clone()) - .and(account_id.domain_id.eq(alice_domain_id.clone())) - }); - - // note that we use a non-normalized predicate, built with `build_fragment` - account.id.satisfies(account_id_predicate) - }); - let account_predicate = account_predicate_denorm.normalize(); - - // check that the predicate is correct - assert_eq!( - account_predicate, - CompoundPredicate::And(vec![ - CompoundPredicate::Atom(AccountPredicateBox::Id(AccountIdPredicateBox::Signatory( - PublicKeyPredicateBox::Equals(alice_signatory) - ))), - CompoundPredicate::Atom(AccountPredicateBox::Id(AccountIdPredicateBox::DomainId( - DomainIdPredicateBox::Equals(alice_domain_id) - ))) - ]) - ); - } - - /// Tests operator overloading shorthand combinators for various cases - #[test] - fn test_operator_overloading() { - let asset_id1: AssetId = "xor##ed0120CE7FA46C9DCE7EA4B125E2E36BDB63EA33073E7590AC92816AE1E861B7048B03@wonderland".parse().unwrap(); - let asset_id2: AssetId = "rose##ed0120CE7FA46C9DCE7EA4B125E2E36BDB63EA33073E7590AC92816AE1E861B7048B03@wonderland".parse().unwrap(); - - let asset_predicate = AssetIdPredicateBox::build(|id| { - // DSL uses `|` and `&` operators, even though the actual logic is short-circuiting internally. - // this is because `||` and `&&` are not overloadable in Rust - - // check that operator overloading works on atomic predicates - let or_predicate1 = id.eq(asset_id1.clone()) | id.eq(asset_id2.clone()); - - // check that operator overloading works on projection predicates - let or_predicate2 = id.definition_id.name.starts_with("xor") - | id.account.domain_id.eq("wonderland".parse().unwrap()); - - // check that operator overloading works on predicate combinators - or_predicate1 & or_predicate2 - }); - - assert_eq!( - asset_predicate, - CompoundPredicate::And(vec![ - CompoundPredicate::Or(vec![ - CompoundPredicate::Atom(AssetIdPredicateBox::Equals(asset_id1)), - CompoundPredicate::Atom(AssetIdPredicateBox::Equals(asset_id2)), - ]), - CompoundPredicate::Or(vec![ - CompoundPredicate::Atom(AssetIdPredicateBox::DefinitionId( - AssetDefinitionIdPredicateBox::Name(StringPredicateBox::StartsWith( - "xor".to_string() - )) - )), - CompoundPredicate::Atom(AssetIdPredicateBox::AccountId( - AccountIdPredicateBox::DomainId(DomainIdPredicateBox::Equals( - "wonderland".parse().unwrap() - )) - )), - ]), - ]) - ); - } - - #[test] - fn test_flattening() { - // check `and` flattening - let right_assoc = StringPredicateBox::build(|s| { - s.starts_with("a") & (s.ends_with("b") & s.ends_with("c")) - }); - let left_assoc = StringPredicateBox::build(|s| { - (s.starts_with("a") & s.ends_with("b")) & s.ends_with("c") - }); - - // the user ordering should be preserved, to mimic the short-circuiting behavior of `&&` - assert_eq!(right_assoc, left_assoc); - - // the predicates should get flattened - assert_eq!( - right_assoc, - CompoundPredicate::And(vec![ - CompoundPredicate::Atom(StringPredicateBox::StartsWith("a".to_string())), - CompoundPredicate::Atom(StringPredicateBox::EndsWith("b".to_string())), - CompoundPredicate::Atom(StringPredicateBox::EndsWith("c".to_string())), - ]) - ); - - // check the same for `or` - let right_assoc = StringPredicateBox::build(|s| { - s.starts_with("a") | (s.ends_with("b") | s.ends_with("c")) - }); - let left_assoc = StringPredicateBox::build(|s| { - (s.starts_with("a") | s.ends_with("b")) | s.ends_with("c") - }); - - // the user ordering should be preserved, to mimic the short-circuiting behavior of `||` - assert_eq!(right_assoc, left_assoc); - - // the predicates should get flattened - assert_eq!( - right_assoc, - CompoundPredicate::Or(vec![ - CompoundPredicate::Atom(StringPredicateBox::StartsWith("a".to_string())), - CompoundPredicate::Atom(StringPredicateBox::EndsWith("b".to_string())), - CompoundPredicate::Atom(StringPredicateBox::EndsWith("c".to_string())), - ]) - ); - - // check `not` flattening - let not_flat = StringPredicateBox::build(|s| !!s.starts_with("a")); - - assert_eq!( - not_flat, - CompoundPredicate::Atom(StringPredicateBox::StartsWith("a".to_string())) - ); - } -} diff --git a/crates/iroha_data_model/src/query/predicate/predicate_ast_extensions.rs b/crates/iroha_data_model/src/query/predicate/predicate_ast_extensions.rs deleted file mode 100644 index 1c17d09690c..00000000000 --- a/crates/iroha_data_model/src/query/predicate/predicate_ast_extensions.rs +++ /dev/null @@ -1,40 +0,0 @@ -//! A module providing extensions to the [`AstPredicate`] trait. - -use super::{AstPredicate, CompoundPredicate}; -use crate::query::predicate::predicate_combinators::{ - AndAstPredicate, NotAstPredicate, OrAstPredicate, -}; - -/// Extension trait for [`AstPredicate`]. -pub trait AstPredicateExt: AstPredicate -where - Self: Sized, -{ - /// Normalize this predicate without applying additional projections. - fn normalize(self) -> CompoundPredicate { - self.normalize_with_proj(|p| p) - } - - /// Negate this AST predicate. - fn not(self) -> NotAstPredicate { - NotAstPredicate(self) - } - - /// Combine this AST predicate with another AST predicate using logical AND. - fn and(self, other: PLhs) -> AndAstPredicate - where - PLhs: AstPredicate, - { - AndAstPredicate(self, other) - } - - /// Combine this AST predicate with another AST predicate using logical OR. - fn or(self, other: PLhs) -> OrAstPredicate - where - PLhs: AstPredicate, - { - OrAstPredicate(self, other) - } -} - -impl AstPredicateExt for P where P: AstPredicate {} diff --git a/crates/iroha_data_model/src/query/predicate/predicate_atoms/account.rs b/crates/iroha_data_model/src/query/predicate/predicate_atoms/account.rs deleted file mode 100644 index 6a77d6182b9..00000000000 --- a/crates/iroha_data_model/src/query/predicate/predicate_atoms/account.rs +++ /dev/null @@ -1,77 +0,0 @@ -//! This module contains predicates for account-related objects, mirroring [`crate::account`]. - -#[cfg(not(feature = "std"))] -use alloc::{format, string::String, vec::Vec}; - -use iroha_schema::IntoSchema; -use iroha_version::{Decode, Encode}; -use serde::{Deserialize, Serialize}; - -use super::impl_predicate_box; -use crate::{ - account::{Account, AccountId}, - query::{ - predicate::{ - predicate_ast_extensions::AstPredicateExt as _, - predicate_atoms::{ - domain::DomainIdPredicateBox, MetadataPredicateBox, PublicKeyPredicateBox, - }, - predicate_combinators::{AndAstPredicate, NotAstPredicate, OrAstPredicate}, - projectors::BaseProjector, - EvaluatePredicate, - }, - AstPredicate, CompoundPredicate, HasPredicateBox, HasPrototype, - }, -}; - -/// A predicate that can be applied to an [`AccountId`]. -#[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] -pub enum AccountIdPredicateBox { - // object-specific predicates - /// Checks if the input is equal to the expected value. - Equals(AccountId), - // projections - /// Checks if a predicate applies to the domain ID of the input. - DomainId(DomainIdPredicateBox), - /// Checks if a predicate applies to the signatory of the input. - Signatory(PublicKeyPredicateBox), -} - -impl_predicate_box!(AccountId: AccountIdPredicateBox); - -impl EvaluatePredicate for AccountIdPredicateBox { - fn applies(&self, input: &AccountId) -> bool { - match self { - AccountIdPredicateBox::Equals(expected) => expected == input, - AccountIdPredicateBox::DomainId(domain) => domain.applies(&input.domain), - AccountIdPredicateBox::Signatory(public_key) => public_key.applies(&input.signatory), - } - } -} - -/// A predicate that can be applied to an [`Account`]. -#[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] -#[allow(unreachable_patterns)] -pub enum AccountPredicateBox { - // projections - /// Checks if a predicate applies to the ID of the input. - Id(AccountIdPredicateBox), - /// Checks if a predicate applies to the metadata of the input. - Metadata(MetadataPredicateBox), -} - -impl_predicate_box!(Account: AccountPredicateBox); - -impl EvaluatePredicate for AccountPredicateBox { - fn applies(&self, input: &Account) -> bool { - match self { - AccountPredicateBox::Id(id) => id.applies(&input.id), - AccountPredicateBox::Metadata(metadata) => metadata.applies(&input.metadata), - } - } -} - -pub mod prelude { - //! Re-export all predicate boxes for a glob import `(::*)` - pub use super::{AccountIdPredicateBox, AccountPredicateBox}; -} diff --git a/crates/iroha_data_model/src/query/predicate/predicate_atoms/asset.rs b/crates/iroha_data_model/src/query/predicate/predicate_atoms/asset.rs deleted file mode 100644 index 60f80883728..00000000000 --- a/crates/iroha_data_model/src/query/predicate/predicate_atoms/asset.rs +++ /dev/null @@ -1,149 +0,0 @@ -//! This module contains predicates for asset-related objects, mirroring [`crate::asset`]. - -#[cfg(not(feature = "std"))] -use alloc::{format, string::String, vec::Vec}; - -use iroha_schema::IntoSchema; -use iroha_version::{Decode, Encode}; -use serde::{Deserialize, Serialize}; - -use super::impl_predicate_box; -use crate::{ - asset::{Asset, AssetDefinition, AssetDefinitionId, AssetId, AssetValue}, - query::{ - predicate::{ - predicate_ast_extensions::AstPredicateExt as _, - predicate_atoms::{ - account::AccountIdPredicateBox, domain::DomainIdPredicateBox, MetadataPredicateBox, - StringPredicateBox, - }, - predicate_combinators::{AndAstPredicate, NotAstPredicate, OrAstPredicate}, - projectors::BaseProjector, - EvaluatePredicate, - }, - AstPredicate, CompoundPredicate, HasPredicateBox, HasPrototype, - }, -}; - -/// A predicate that can be applied to an [`AssetDefinitionId`]. -#[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] -#[allow(unreachable_patterns)] -pub enum AssetDefinitionPredicateBox { - // projections - /// Checks if a predicate applies to the ID of the input. - Id(AssetDefinitionIdPredicateBox), - /// Checks if a predicate applies to the metadata of the input. - Metadata(MetadataPredicateBox), - /// Checks if a predicate applies to the owner of the input. - OwnedBy(AccountIdPredicateBox), -} - -impl_predicate_box!(AssetDefinition: AssetDefinitionPredicateBox); - -impl EvaluatePredicate for AssetDefinitionPredicateBox { - fn applies(&self, input: &AssetDefinition) -> bool { - match self { - AssetDefinitionPredicateBox::Id(id) => id.applies(&input.id), - AssetDefinitionPredicateBox::Metadata(metadata) => metadata.applies(&input.metadata), - AssetDefinitionPredicateBox::OwnedBy(account_id) => account_id.applies(&input.owned_by), - } - } -} - -/// A predicate that can be applied to an [`Asset`]. -#[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] -#[allow(unreachable_patterns)] -pub enum AssetPredicateBox { - // projections - /// Checks if a predicate applies to the ID of the input. - Id(AssetIdPredicateBox), - /// Checks if a predicate applies to the value of the input. - Value(AssetValuePredicateBox), -} - -impl_predicate_box!(Asset: AssetPredicateBox); - -impl EvaluatePredicate for AssetPredicateBox { - fn applies(&self, input: &Asset) -> bool { - match self { - AssetPredicateBox::Id(id) => id.applies(&input.id), - AssetPredicateBox::Value(value) => value.applies(&input.value), - } - } -} - -/// A predicate that can be applied to an [`AssetValue`]. -#[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] -pub enum AssetValuePredicateBox { - // TODO: populate - // FIX: Remove all `#[allow(unreachable_patterns)]` from all use sites - // once some variants are added into this enum -} - -impl_predicate_box!(AssetValue: AssetValuePredicateBox); - -impl EvaluatePredicate for AssetValuePredicateBox { - fn applies(&self, _input: &AssetValue) -> bool { - match *self {} - } -} - -/// A predicate that can be applied to an [`AssetId`]. -#[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] -pub enum AssetIdPredicateBox { - // object-specific predicates - /// Checks if the input is equal to the expected value. - Equals(AssetId), - // projections - /// Checks if a predicate applies to the definition ID of the input. - DefinitionId(AssetDefinitionIdPredicateBox), - /// Checks if a predicate applies to the account ID of the input. - AccountId(AccountIdPredicateBox), -} - -impl_predicate_box!(AssetId: AssetIdPredicateBox); - -impl EvaluatePredicate for AssetIdPredicateBox { - fn applies(&self, input: &AssetId) -> bool { - match self { - AssetIdPredicateBox::Equals(expected) => expected == input, - AssetIdPredicateBox::DefinitionId(definition_id) => { - definition_id.applies(&input.definition) - } - AssetIdPredicateBox::AccountId(account_id) => account_id.applies(&input.account), - } - } -} - -/// A predicate that can be applied to an [`AssetDefinitionId`]. -#[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] -pub enum AssetDefinitionIdPredicateBox { - // object-specific predicates - /// Checks if the input is equal to the expected value. - Equals(AssetDefinitionId), - // projections - /// Checks if a predicate applies to the domain ID of the input. - DomainId(DomainIdPredicateBox), - /// Checks if a predicate applies to the name of the input. - Name(StringPredicateBox), -} - -impl_predicate_box!(AssetDefinitionId: AssetDefinitionIdPredicateBox); - -impl EvaluatePredicate for AssetDefinitionIdPredicateBox { - fn applies(&self, input: &AssetDefinitionId) -> bool { - match self { - AssetDefinitionIdPredicateBox::Equals(expected) => expected == input, - AssetDefinitionIdPredicateBox::DomainId(domain) => domain.applies(&input.domain), - AssetDefinitionIdPredicateBox::Name(name) => name.applies(&input.name), - } - } -} - -pub mod prelude { - //! Re-export all predicate boxes for a glob import `(::*)` - pub use super::{ - AssetDefinitionIdPredicateBox, AssetDefinitionPredicateBox, AssetIdPredicateBox, - AssetPredicateBox, AssetValuePredicateBox, - }; -} diff --git a/crates/iroha_data_model/src/query/predicate/predicate_atoms/block.rs b/crates/iroha_data_model/src/query/predicate/predicate_atoms/block.rs deleted file mode 100644 index 1d26a8f5057..00000000000 --- a/crates/iroha_data_model/src/query/predicate/predicate_atoms/block.rs +++ /dev/null @@ -1,248 +0,0 @@ -//! This module contains predicates for block-related objects, mirroring [`crate::block`]. - -#[cfg(not(feature = "std"))] -use alloc::{format, string::String, vec::Vec}; - -use iroha_crypto::HashOf; -use iroha_schema::IntoSchema; -use parity_scale_codec::{Decode, Encode}; -use serde::{Deserialize, Serialize}; - -use super::impl_predicate_box; -use crate::{ - block::{BlockHeader, SignedBlock}, - prelude::{AccountIdPredicateBox, TransactionRejectionReason}, - query::{ - predicate::{ - predicate_ast_extensions::AstPredicateExt as _, - predicate_combinators::{AndAstPredicate, NotAstPredicate, OrAstPredicate}, - projectors::BaseProjector, - AstPredicate, CompoundPredicate, EvaluatePredicate, HasPredicateBox, HasPrototype, - }, - CommittedTransaction, - }, - transaction::SignedTransaction, -}; - -/// A predicate that can be applied to a [`HashOf`] -#[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] -pub enum BlockHashPredicateBox { - // object-specific predicates - /// Checks if the input is equal to the expected value. - Equals(HashOf), -} - -impl_predicate_box!(HashOf: BlockHashPredicateBox); - -impl EvaluatePredicate> for BlockHashPredicateBox { - fn applies(&self, input: &HashOf) -> bool { - match self { - BlockHashPredicateBox::Equals(hash) => input == hash, - } - } -} - -/// A predicate that can be applied to a [`BlockHeader`]. -#[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] -pub enum BlockHeaderPredicateBox { - // projections - /// Checks if a predicate applies to the hash of the block header. - Hash(BlockHashPredicateBox), -} - -impl_predicate_box!(BlockHeader: BlockHeaderPredicateBox); - -impl EvaluatePredicate for BlockHeaderPredicateBox { - fn applies(&self, input: &BlockHeader) -> bool { - match self { - BlockHeaderPredicateBox::Hash(hash) => hash.applies(&input.hash()), - } - } -} - -/// A predicate that can be applied to a [`SignedBlock`]. -#[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] -pub enum SignedBlockPredicateBox { - // projections - /// Checks if a predicate applies to the header of the block. - Header(BlockHeaderPredicateBox), -} - -impl_predicate_box!(SignedBlock: SignedBlockPredicateBox); - -impl EvaluatePredicate for SignedBlockPredicateBox { - fn applies(&self, input: &SignedBlock) -> bool { - match self { - SignedBlockPredicateBox::Header(header) => header.applies(&input.header()), - } - } -} - -/// A predicate that can be applied to a [`HashOf`]. -#[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] -pub enum TransactionHashPredicateBox { - // object-specific predicates - /// Checks if the input is equal to the expected value. - Equals(HashOf), -} - -impl_predicate_box!(HashOf: TransactionHashPredicateBox); - -impl EvaluatePredicate> for TransactionHashPredicateBox { - fn applies(&self, input: &HashOf) -> bool { - match self { - TransactionHashPredicateBox::Equals(hash) => input == hash, - } - } -} - -/// A predicate that can be applied to a [`SignedTransaction`] -#[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] -pub enum SignedTransactionPredicateBox { - // projections - /// Checks if a predicate applies to the hash of the signed transaction. - Hash(TransactionHashPredicateBox), - /// Checks if a predicate applies to the authority of the signed transaction. - Authority(AccountIdPredicateBox), -} - -impl_predicate_box!(SignedTransaction: SignedTransactionPredicateBox); - -impl EvaluatePredicate for SignedTransactionPredicateBox { - fn applies(&self, input: &SignedTransaction) -> bool { - match self { - SignedTransactionPredicateBox::Hash(hash) => hash.applies(&input.hash()), - SignedTransactionPredicateBox::Authority(authority) => { - authority.applies(input.authority()) - } - } - } -} - -// TODO: maybe we would want to have a generic `Option` predicate box & predicate -/// A predicate that can be applied to an [`Option`] -#[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] -pub enum TransactionErrorPredicateBox { - // object-specific predicates - /// Checks if there was an error while applying the transaction. - IsSome, -} - -impl_predicate_box!(Option: TransactionErrorPredicateBox); - -impl EvaluatePredicate> for TransactionErrorPredicateBox { - fn applies(&self, input: &Option) -> bool { - match self { - TransactionErrorPredicateBox::IsSome => input.is_some(), - } - } -} - -/// A predicate that can be applied to a [`CommittedTransaction`]. -#[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] -pub enum CommittedTransactionPredicateBox { - /// Checks if a predicate applies to the hash of the block the transaction was included in. - BlockHash(BlockHashPredicateBox), - /// Checks if a predicate applies to the committed transaction inside. - Value(SignedTransactionPredicateBox), - /// Checks if a predicate applies to the error of the transaction. - Error(TransactionErrorPredicateBox), -} - -impl_predicate_box!(CommittedTransaction: CommittedTransactionPredicateBox); - -impl EvaluatePredicate for CommittedTransactionPredicateBox { - fn applies(&self, input: &CommittedTransaction) -> bool { - match self { - CommittedTransactionPredicateBox::BlockHash(block_hash) => { - block_hash.applies(&input.block_hash) - } - CommittedTransactionPredicateBox::Value(committed_transaction) => { - committed_transaction.applies(&input.value) - } - CommittedTransactionPredicateBox::Error(error) => error.applies(&input.error), - } - } -} - -pub mod prelude { - //! Re-export all predicate boxes for a glob import `(::*)` - pub use super::{ - BlockHashPredicateBox, BlockHeaderPredicateBox, CommittedTransactionPredicateBox, - SignedBlockPredicateBox, SignedTransactionPredicateBox, TransactionErrorPredicateBox, - TransactionHashPredicateBox, - }; -} - -#[cfg(test)] -mod test { - use iroha_crypto::{Hash, HashOf}; - - use crate::{ - account::AccountId, - prelude::{ - AccountIdPredicateBox, BlockHeaderPredicateBox, CompoundPredicate, - SignedBlockPredicateBox, - }, - query::predicate::predicate_atoms::block::{ - BlockHashPredicateBox, CommittedTransactionPredicateBox, SignedTransactionPredicateBox, - TransactionErrorPredicateBox, TransactionHashPredicateBox, - }, - }; - - #[test] - fn transaction_smoke() { - let hash = Hash::prehashed([0; 32]); - let account_id: AccountId = - "ed0120CE7FA46C9DCE7EA4B125E2E36BDB63EA33073E7590AC92816AE1E861B7048B03@wonderland" - .parse() - .unwrap(); - - let predicate = CommittedTransactionPredicateBox::build(|tx| { - tx.block_hash.eq(HashOf::from_untyped_unchecked(hash)) - & tx.value.authority.eq(account_id.clone()) - & tx.value.hash.eq(HashOf::from_untyped_unchecked(hash)) - & tx.error.is_some() - }); - - assert_eq!( - predicate, - CompoundPredicate::And(vec![ - CompoundPredicate::Atom(CommittedTransactionPredicateBox::BlockHash( - BlockHashPredicateBox::Equals(HashOf::from_untyped_unchecked(hash)) - )), - CompoundPredicate::Atom(CommittedTransactionPredicateBox::Value( - SignedTransactionPredicateBox::Authority(AccountIdPredicateBox::Equals( - account_id.clone() - )) - )), - CompoundPredicate::Atom(CommittedTransactionPredicateBox::Value( - SignedTransactionPredicateBox::Hash(TransactionHashPredicateBox::Equals( - HashOf::from_untyped_unchecked(hash) - )) - )), - CompoundPredicate::Atom(CommittedTransactionPredicateBox::Error( - TransactionErrorPredicateBox::IsSome - )), - ]) - ); - } - - #[test] - fn block_smoke() { - let hash = Hash::prehashed([0; 32]); - - let predicate = SignedBlockPredicateBox::build(|block| { - block.header.hash.eq(HashOf::from_untyped_unchecked(hash)) - }); - - assert_eq!( - predicate, - CompoundPredicate::Atom(SignedBlockPredicateBox::Header( - BlockHeaderPredicateBox::Hash(BlockHashPredicateBox::Equals( - HashOf::from_untyped_unchecked(hash) - )) - )) - ); - } -} diff --git a/crates/iroha_data_model/src/query/predicate/predicate_atoms/domain.rs b/crates/iroha_data_model/src/query/predicate/predicate_atoms/domain.rs deleted file mode 100644 index 6c02c5bd56f..00000000000 --- a/crates/iroha_data_model/src/query/predicate/predicate_atoms/domain.rs +++ /dev/null @@ -1,69 +0,0 @@ -//! This module contains predicates for domain-related objects, mirroring [`crate::domain`]. - -#[cfg(not(feature = "std"))] -use alloc::{format, string::String, vec::Vec}; - -use iroha_schema::IntoSchema; -use iroha_version::{Decode, Encode}; -use serde::{Deserialize, Serialize}; - -use super::{impl_predicate_box, MetadataPredicateBox}; -use crate::{ - domain::{Domain, DomainId}, - query::predicate::{ - predicate_ast_extensions::AstPredicateExt as _, - predicate_atoms::StringPredicateBox, - predicate_combinators::{AndAstPredicate, NotAstPredicate, OrAstPredicate}, - projectors::BaseProjector, - AstPredicate, CompoundPredicate, EvaluatePredicate, HasPredicateBox, HasPrototype, - }, -}; - -/// A predicate that can be applied to a [`Domain`]. -#[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] -#[allow(unreachable_patterns)] -pub enum DomainPredicateBox { - // projections - /// Checks if a predicate applies to the ID of the input. - Id(DomainIdPredicateBox), - /// Checks if a predicate applies to the metadata of the input. - Metadata(MetadataPredicateBox), -} - -impl_predicate_box!(Domain: DomainPredicateBox); - -impl EvaluatePredicate for DomainPredicateBox { - fn applies(&self, input: &Domain) -> bool { - match self { - DomainPredicateBox::Id(id) => id.applies(&input.id), - DomainPredicateBox::Metadata(metadata) => metadata.applies(&input.metadata), - } - } -} - -/// A predicate that can be applied to a [`DomainId`]. -#[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] -pub enum DomainIdPredicateBox { - // object-specific predicates - /// Checks if the input is equal to the expected value. - Equals(DomainId), - // projections - /// Checks if a predicate applies to the name of the input. - Name(StringPredicateBox), -} - -impl_predicate_box!(DomainId: DomainIdPredicateBox); - -impl EvaluatePredicate for DomainIdPredicateBox { - fn applies(&self, input: &DomainId) -> bool { - match self { - DomainIdPredicateBox::Equals(expected) => expected == input, - DomainIdPredicateBox::Name(name) => name.applies(&input.name), - } - } -} - -pub mod prelude { - //! Re-export all predicate boxes for a glob import `(::*)` - pub use super::{DomainIdPredicateBox, DomainPredicateBox}; -} diff --git a/crates/iroha_data_model/src/query/predicate/predicate_atoms/mod.rs b/crates/iroha_data_model/src/query/predicate/predicate_atoms/mod.rs deleted file mode 100644 index 4f1b3458491..00000000000 --- a/crates/iroha_data_model/src/query/predicate/predicate_atoms/mod.rs +++ /dev/null @@ -1,187 +0,0 @@ -//! This module contains atomic predicates for all the different types supported by the predicate system. -//! -//! Generally, each of the atomic predicates is an enum that has two categories of variants: -//! - Object-specific predicates, which check something that applies to the whole object, like [`account::AccountIdPredicateBox::Equals`] or [`StringPredicateBox::Contains`] -//! - Projections, which check a predicate on some of the fields/inner values of the object, like [`account::AccountIdPredicateBox::DomainId`] - -#![allow(missing_copy_implementations)] // some predicates are not yet populated, but will be. They will stop being `Copy`able later, so don't bother with marking them as such now. - -pub mod account; -pub mod asset; -pub mod block; -pub mod domain; -pub mod parameter; -pub mod peer; -pub mod permission; -pub mod role; -pub mod trigger; - -#[cfg(not(feature = "std"))] -use alloc::{format, string::String, vec::Vec}; - -use iroha_crypto::PublicKey; -use iroha_schema::IntoSchema; -use parity_scale_codec::{Decode, Encode}; -use serde::{Deserialize, Serialize}; - -use super::{ - predicate_ast_extensions::AstPredicateExt as _, - predicate_combinators::{AndAstPredicate, NotAstPredicate, OrAstPredicate}, - projectors::BaseProjector, - AstPredicate, CompoundPredicate, EvaluatePredicate, HasPredicateBox, HasPrototype, -}; -use crate::{metadata::Metadata, name::Name}; - -/// Adds common methods to a predicate box. -/// -/// Implements: -/// 1. `build` and `build_fragment` methods for building a predicate using the dsl. -/// 2. base-case `AstPredicate` for the predicate box (emits an atom expression). -/// 3. `Not`, `BitAnd`, and `BitOr` operators for combining predicates. -macro_rules! impl_predicate_box { - ($($ty:ty),+: $predicate_ty:ty) => { - impl $predicate_ty { - /// Build a new predicate in a normalized form. This predicate has limited composability and is generally useful only to be passed to queries. - pub fn build(predicate: F) -> CompoundPredicate - where - F: FnOnce(::Prototype>) -> O, - O: AstPredicate, - { - predicate(Default::default()).normalize() - } - - /// Build a new predicate without normalizing it. The resulting predicate can be freely composed with other predicates using logical operators, or by calling `.satisfies` method on a prototype. - pub fn build_fragment(predicate: F) -> O - where - F: FnOnce(::Prototype>) -> O, - O: AstPredicate, - { - predicate(Default::default()) - } - } - - $( - impl HasPredicateBox for $ty { - type PredicateBoxType = $predicate_ty; - } - )+ - - impl AstPredicate<$predicate_ty> for $predicate_ty { - fn normalize_with_proj(self, proj: Proj) -> CompoundPredicate - where - Proj: Fn($predicate_ty) -> OutputType + Copy, - { - CompoundPredicate::Atom(proj(self)) - } - } - - impl core::ops::Not for $predicate_ty - where - Self: AstPredicate<$predicate_ty>, - { - type Output = NotAstPredicate; - - fn not(self) -> Self::Output { - NotAstPredicate(self) - } - } - - impl core::ops::BitAnd for $predicate_ty - where - Self: AstPredicate<$predicate_ty>, - PRhs: AstPredicate<$predicate_ty>, - { - type Output = AndAstPredicate; - - fn bitand(self, rhs: PRhs) -> Self::Output { - AndAstPredicate(self, rhs) - } - } - - impl core::ops::BitOr for $predicate_ty - where - Self: AstPredicate<$predicate_ty>, - PRhs: AstPredicate<$predicate_ty>, - { - type Output = OrAstPredicate; - - fn bitor(self, rhs: PRhs) -> Self::Output { - OrAstPredicate(self, rhs) - } - } - }; -} -pub(crate) use impl_predicate_box; - -/// A predicate that can be applied to a [`String`]-like types. -#[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] -pub enum StringPredicateBox { - /// Checks if the input is equal to the expected value - Equals(String), - /// Checks if the input contains an expected substring, like [`str::contains()`] - Contains(String), - /// Checks if the input starts with an expected substring, like [`str::starts_with()`] - StartsWith(String), - /// Checks if the input ends with an expected substring, like [`str::ends_with()`] - EndsWith(String), -} - -impl_predicate_box!(String, Name: StringPredicateBox); - -impl EvaluatePredicate for StringPredicateBox -where - T: AsRef, -{ - fn applies(&self, input: &T) -> bool { - let input = input.as_ref(); - match self { - StringPredicateBox::Contains(content) => input.contains(content), - StringPredicateBox::StartsWith(content) => input.starts_with(content), - StringPredicateBox::EndsWith(content) => input.ends_with(content), - StringPredicateBox::Equals(content) => *input == *content, - } - } -} - -/// A predicate that can be applied to [`Metadata`]. -#[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] -pub enum MetadataPredicateBox { - // TODO: populate - // FIX: Remove all `#[allow(unreachable_patterns)]` from all use sites - // once some variants are added into this enum -} - -impl_predicate_box!(Metadata: MetadataPredicateBox); - -impl EvaluatePredicate for MetadataPredicateBox { - fn applies(&self, _input: &Metadata) -> bool { - match *self {} - } -} - -/// A predicate that can be applied to a [`PublicKey`]. -#[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] -pub enum PublicKeyPredicateBox { - // object-specific predicates - /// Checks if the input is equal to the expected value. - Equals(PublicKey), -} - -impl_predicate_box!(PublicKey: PublicKeyPredicateBox); - -impl EvaluatePredicate for PublicKeyPredicateBox { - fn applies(&self, input: &PublicKey) -> bool { - match self { - PublicKeyPredicateBox::Equals(expected) => expected == input, - } - } -} - -pub mod prelude { - //! Re-export all predicate boxes for a glob import `(::*)` - pub use super::{ - account::prelude::*, asset::prelude::*, block::prelude::*, domain::prelude::*, - parameter::prelude::*, peer::prelude::*, permission::prelude::*, role::prelude::*, - trigger::prelude::*, MetadataPredicateBox, PublicKeyPredicateBox, StringPredicateBox, - }; -} diff --git a/crates/iroha_data_model/src/query/predicate/predicate_atoms/parameter.rs b/crates/iroha_data_model/src/query/predicate/predicate_atoms/parameter.rs deleted file mode 100644 index fe1a74a10e5..00000000000 --- a/crates/iroha_data_model/src/query/predicate/predicate_atoms/parameter.rs +++ /dev/null @@ -1,38 +0,0 @@ -//! This module contains predicates for parameter-related objects, mirroring [`crate::parameter`]. - -#[cfg(not(feature = "std"))] -use alloc::{format, string::String, vec::Vec}; - -use iroha_schema::IntoSchema; -use parity_scale_codec::{Decode, Encode}; -use serde::{Deserialize, Serialize}; - -use super::impl_predicate_box; -use crate::{ - parameter::Parameter, - query::predicate::{ - predicate_ast_extensions::AstPredicateExt as _, - predicate_combinators::{AndAstPredicate, NotAstPredicate, OrAstPredicate}, - projectors::BaseProjector, - AstPredicate, CompoundPredicate, EvaluatePredicate, HasPredicateBox, HasPrototype, - }, -}; - -/// A predicate that can be applied to a [`Parameter`]. -#[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] -pub enum ParameterPredicateBox { - // nothing here yet -} - -impl_predicate_box!(Parameter: ParameterPredicateBox); - -impl EvaluatePredicate for ParameterPredicateBox { - fn applies(&self, _input: &Parameter) -> bool { - match *self {} - } -} - -pub mod prelude { - //! Re-export all predicate boxes for a glob import `(::*)` - pub use super::ParameterPredicateBox; -} diff --git a/crates/iroha_data_model/src/query/predicate/predicate_atoms/peer.rs b/crates/iroha_data_model/src/query/predicate/predicate_atoms/peer.rs deleted file mode 100644 index a3512c8ee4d..00000000000 --- a/crates/iroha_data_model/src/query/predicate/predicate_atoms/peer.rs +++ /dev/null @@ -1,38 +0,0 @@ -//! This module contains predicates for peer-related objects, mirroring [`crate::peer`]. - -#[cfg(not(feature = "std"))] -use alloc::{format, string::String, vec::Vec}; - -use iroha_schema::IntoSchema; -use parity_scale_codec::{Decode, Encode}; -use serde::{Deserialize, Serialize}; - -use super::impl_predicate_box; -use crate::{ - peer::PeerId, - query::predicate::{ - predicate_ast_extensions::AstPredicateExt as _, - predicate_combinators::{AndAstPredicate, NotAstPredicate, OrAstPredicate}, - projectors::BaseProjector, - AstPredicate, CompoundPredicate, EvaluatePredicate, HasPredicateBox, HasPrototype, - }, -}; - -/// A predicate that can be applied to a [`Peer`]. -#[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] -pub enum PeerPredicateBox { - // nothing here yet -} - -impl_predicate_box!(PeerId: PeerPredicateBox); - -impl EvaluatePredicate for PeerPredicateBox { - fn applies(&self, _input: &PeerId) -> bool { - match *self {} - } -} - -pub mod prelude { - //! Re-export all predicate boxes for a glob import `(::*)` - pub use super::PeerPredicateBox; -} diff --git a/crates/iroha_data_model/src/query/predicate/predicate_atoms/permission.rs b/crates/iroha_data_model/src/query/predicate/predicate_atoms/permission.rs deleted file mode 100644 index 5d0bc4494bc..00000000000 --- a/crates/iroha_data_model/src/query/predicate/predicate_atoms/permission.rs +++ /dev/null @@ -1,38 +0,0 @@ -//! This module contains predicates for permission-related objects, mirroring [`crate::permission`]. - -#[cfg(not(feature = "std"))] -use alloc::{format, string::String, vec::Vec}; - -use iroha_schema::IntoSchema; -use parity_scale_codec::{Decode, Encode}; -use serde::{Deserialize, Serialize}; - -use super::impl_predicate_box; -use crate::{ - permission::Permission, - query::predicate::{ - predicate_ast_extensions::AstPredicateExt as _, - predicate_combinators::{AndAstPredicate, NotAstPredicate, OrAstPredicate}, - projectors::BaseProjector, - AstPredicate, CompoundPredicate, EvaluatePredicate, HasPredicateBox, HasPrototype, - }, -}; - -/// A predicate that can be applied to a [`Permission`]. -#[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] -pub enum PermissionPredicateBox { - // nothing here yet -} - -impl_predicate_box!(Permission: PermissionPredicateBox); - -impl EvaluatePredicate for PermissionPredicateBox { - fn applies(&self, _input: &Permission) -> bool { - match *self {} - } -} - -pub mod prelude { - //! Re-export all predicate boxes for a glob import `(::*)` - pub use super::PermissionPredicateBox; -} diff --git a/crates/iroha_data_model/src/query/predicate/predicate_atoms/role.rs b/crates/iroha_data_model/src/query/predicate/predicate_atoms/role.rs deleted file mode 100644 index 7c2636bee01..00000000000 --- a/crates/iroha_data_model/src/query/predicate/predicate_atoms/role.rs +++ /dev/null @@ -1,67 +0,0 @@ -//! This module contains predicates for role-related objects, mirroring [`crate::role`]. - -#[cfg(not(feature = "std"))] -use alloc::{format, string::String, vec::Vec}; - -use iroha_schema::IntoSchema; -use iroha_version::{Decode, Encode}; -use serde::{Deserialize, Serialize}; - -use super::impl_predicate_box; -use crate::{ - prelude::{Role, RoleId}, - query::{ - predicate::{ - predicate_ast_extensions::AstPredicateExt as _, - predicate_atoms::StringPredicateBox, - predicate_combinators::{AndAstPredicate, NotAstPredicate, OrAstPredicate}, - projectors::BaseProjector, - }, - AstPredicate, CompoundPredicate, EvaluatePredicate, HasPredicateBox, HasPrototype, - }, -}; - -/// A predicate that can be applied to a [`RoleId`]. -#[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] -pub enum RoleIdPredicateBox { - // object-specific predicates - /// Checks if the input is equal to the expected value. - Equals(RoleId), - // projections - /// Checks if a predicate applies to the name of the input. - Name(StringPredicateBox), -} - -impl_predicate_box!(RoleId: RoleIdPredicateBox); - -impl EvaluatePredicate for RoleIdPredicateBox { - fn applies(&self, input: &RoleId) -> bool { - match self { - RoleIdPredicateBox::Equals(expected) => expected == input, - RoleIdPredicateBox::Name(name) => name.applies(&input.name), - } - } -} - -/// A predicate that can be applied to a [`Role`]. -#[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] -pub enum RolePredicateBox { - // projections - /// Checks if a predicate applies to the ID of the input. - Id(RoleIdPredicateBox), -} - -impl_predicate_box!(Role: RolePredicateBox); - -impl EvaluatePredicate for RolePredicateBox { - fn applies(&self, input: &Role) -> bool { - match self { - RolePredicateBox::Id(id) => id.applies(&input.id), - } - } -} - -pub mod prelude { - //! Re-export all predicate boxes for a glob import `(::*)` - pub use super::{RoleIdPredicateBox, RolePredicateBox}; -} diff --git a/crates/iroha_data_model/src/query/predicate/predicate_atoms/trigger.rs b/crates/iroha_data_model/src/query/predicate/predicate_atoms/trigger.rs deleted file mode 100644 index 3998ebf78f7..00000000000 --- a/crates/iroha_data_model/src/query/predicate/predicate_atoms/trigger.rs +++ /dev/null @@ -1,65 +0,0 @@ -//! This module contains predicates for trigger-related objects, mirroring [`crate::trigger`] - -#[cfg(not(feature = "std"))] -use alloc::{format, string::String, vec::Vec}; - -use iroha_schema::IntoSchema; -use iroha_version::{Decode, Encode}; -use serde::{Deserialize, Serialize}; - -use super::impl_predicate_box; -use crate::{ - prelude::{Trigger, TriggerId}, - query::predicate::{ - predicate_ast_extensions::AstPredicateExt as _, - predicate_atoms::StringPredicateBox, - predicate_combinators::{AndAstPredicate, NotAstPredicate, OrAstPredicate}, - projectors::BaseProjector, - AstPredicate, CompoundPredicate, EvaluatePredicate, HasPredicateBox, HasPrototype, - }, -}; - -/// A predicate that can be applied to a [`TriggerId`]. -#[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] -pub enum TriggerIdPredicateBox { - // object-specific predicates - /// Checks if the input is equal to the expected value. - Equals(TriggerId), - // projections - /// Checks if a predicate applies to the name of the input. - Name(StringPredicateBox), -} - -impl EvaluatePredicate for TriggerIdPredicateBox { - fn applies(&self, input: &TriggerId) -> bool { - match self { - TriggerIdPredicateBox::Equals(expected) => expected == input, - TriggerIdPredicateBox::Name(name) => name.applies(&input.name), - } - } -} - -impl_predicate_box!(TriggerId: TriggerIdPredicateBox); - -/// A predicate that can be applied to a [`Trigger`]. -#[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] -pub enum TriggerPredicateBox { - // projections - /// Checks if a predicate applies to the ID of the input. - Id(TriggerIdPredicateBox), -} - -impl_predicate_box!(Trigger: TriggerPredicateBox); - -impl EvaluatePredicate for TriggerPredicateBox { - fn applies(&self, input: &Trigger) -> bool { - match self { - TriggerPredicateBox::Id(id) => id.applies(&input.id), - } - } -} - -pub mod prelude { - //! Re-export all predicate boxes for a glob import `(::*)` - pub use super::{TriggerIdPredicateBox, TriggerPredicateBox}; -} diff --git a/crates/iroha_data_model/src/query/predicate/predicate_combinators.rs b/crates/iroha_data_model/src/query/predicate/predicate_combinators.rs deleted file mode 100644 index 44012705e46..00000000000 --- a/crates/iroha_data_model/src/query/predicate/predicate_combinators.rs +++ /dev/null @@ -1,103 +0,0 @@ -//! Module containing AST predicate combinators, implementing logical operations. - -use super::{AstPredicate, CompoundPredicate}; - -/// Overrides the `Not`, `BitAnd`, and `BitOr` operators for easier predicate composition. -macro_rules! impl_ops { - ($name:ident<$($generic_name:ident),*>) => { - impl<$($generic_name),*> core::ops::Not for $name<$($generic_name),*> - // it would be nice to have a `Self: AstPredicate` bound here, but it is not possible due to `E0207`: unconstrained type parameter - { - type Output = NotAstPredicate; - - fn not(self) -> Self::Output { - NotAstPredicate(self) - } - } - - impl core::ops::BitAnd for $name<$($generic_name),*> - { - type Output = AndAstPredicate; - - fn bitand(self, rhs: PRhs) -> Self::Output { - AndAstPredicate(self, rhs) - } - } - - impl core::ops::BitOr for $name<$($generic_name),*> - { - type Output = OrAstPredicate; - - fn bitor(self, rhs: PRhs) -> Self::Output { - OrAstPredicate(self, rhs) - } - } - }; -} - -/// Invert the AST predicate - apply not operation. -pub struct NotAstPredicate

(pub P); - -impl_ops!(NotAstPredicate

); - -impl AstPredicate for NotAstPredicate

-where - P: AstPredicate, -{ - fn normalize_with_proj(self, proj: Proj) -> CompoundPredicate - where - Proj: Fn(PredType) -> OutputType + Copy, - { - let NotAstPredicate(inner) = self; - - // project the inner predicate and negate it - // use `CompoundPredicate` combinator methods that have flattening optimization - inner.normalize_with_proj(proj).not() - } -} - -/// Combine two AST predicates with logical OR. -pub struct OrAstPredicate(pub P1, pub P2); - -impl AstPredicate for OrAstPredicate -where - P1: AstPredicate, - P2: AstPredicate, -{ - fn normalize_with_proj(self, proj: Proj) -> CompoundPredicate - where - Proj: Fn(PredType) -> OutputType + Copy, - { - let OrAstPredicate(lhs, rhs) = self; - - // project the inner predicates and combine them with an or - // use `CompoundPredicate` combinator methods that have flattening optimization - lhs.normalize_with_proj(proj) - .or(rhs.normalize_with_proj(proj)) - } -} - -impl_ops!(OrAstPredicate); - -/// Combine two AST predicates with logical AND. -pub struct AndAstPredicate(pub P1, pub P2); - -impl AstPredicate for AndAstPredicate -where - P1: AstPredicate, - P2: AstPredicate, -{ - fn normalize_with_proj(self, proj: Proj) -> CompoundPredicate - where - Proj: Fn(PredType) -> OutputType + Copy, - { - let AndAstPredicate(lhs, rhs) = self; - - // project the inner predicates and combine them with an and - // use `CompoundPredicate` combinator methods that have flattening optimization - lhs.normalize_with_proj(proj) - .and(rhs.normalize_with_proj(proj)) - } -} - -impl_ops!(AndAstPredicate); diff --git a/crates/iroha_data_model/src/query/predicate/projectors.rs b/crates/iroha_data_model/src/query/predicate/projectors.rs deleted file mode 100644 index 45ea67cba26..00000000000 --- a/crates/iroha_data_model/src/query/predicate/projectors.rs +++ /dev/null @@ -1,217 +0,0 @@ -//! Defines projectors and projections for predicates DSL. -//! -//! Projection is an AST predicate combinator that changes the type of the inner predicate by wrapping it in a projection variant on normalization. -//! -//! Projector is a helper struct implementing the [`ObjectProjector`] trait that applies the projection. - -use core::marker::PhantomData; - -use super::{AstPredicate, CompoundPredicate}; -use crate::{ - prelude::{BlockHeaderPredicateBox, SignedBlockPredicateBox}, - query::predicate::{ - predicate_atoms::{ - account::{AccountIdPredicateBox, AccountPredicateBox}, - asset::{ - AssetDefinitionIdPredicateBox, AssetDefinitionPredicateBox, AssetIdPredicateBox, - AssetPredicateBox, AssetValuePredicateBox, - }, - block::{ - BlockHashPredicateBox, CommittedTransactionPredicateBox, - SignedTransactionPredicateBox, TransactionErrorPredicateBox, - TransactionHashPredicateBox, - }, - domain::{DomainIdPredicateBox, DomainPredicateBox}, - role::{RoleIdPredicateBox, RolePredicateBox}, - trigger::{TriggerIdPredicateBox, TriggerPredicateBox}, - MetadataPredicateBox, PublicKeyPredicateBox, StringPredicateBox, - }, - predicate_combinators::{AndAstPredicate, NotAstPredicate, OrAstPredicate}, - }, -}; - -/// Describes how to convert `AstPredicate` to `AstPredicate` by wrapping them in some projection predicate combinator. -pub trait ObjectProjector: Default + Copy + Clone { - /// The type of the input atomic predicate (the more concrete type). - type Input; - /// The type of the output atomic predicate (the more general type). - type Output; - - /// The type of result of projecting `P` AST predicate. - type ProjectedPredicate>: AstPredicate; - - /// Project the given predicate. - fn project_predicate>(predicate: P) - -> Self::ProjectedPredicate

; -} - -/// An [`ObjectProjector`] that does not change the type, serving as a base case for the recursion. -pub struct BaseProjector(PhantomData); - -// manual implementation of traits to not add bounds on `T` -impl Default for BaseProjector { - fn default() -> Self { - Self(PhantomData) - } -} - -impl Copy for BaseProjector {} - -impl Clone for BaseProjector { - fn clone(&self) -> Self { - *self - } -} - -impl ObjectProjector for BaseProjector { - type Input = T; - type Output = T; - type ProjectedPredicate> = P; - - fn project_predicate>(predicate: P) -> Self::ProjectedPredicate

{ - predicate - } -} - -/// A helper macro to define a projector and a projection -macro_rules! proj { - ($projector:ident($projection:ident): $in_predicate:ident => $out_predicate:ident::$proj_variant:ident) => { - #[doc = "An AST predicate that projects [`"] - #[doc = stringify!($in_predicate)] - #[doc = "`] to [`"] - #[doc = stringify!($out_predicate)] - #[doc = "`] by wrapping it in [`"] - #[doc = concat!(stringify!($out_predicate), "::", stringify!($proj_variant))] - #[doc = "`]."] - #[derive(Default, Copy, Clone)] - pub struct $projection

(P); - - impl

AstPredicate<$out_predicate> for $projection

- where - P: AstPredicate<$in_predicate>, - { - fn normalize_with_proj( - self, - proj: Proj, - ) -> CompoundPredicate - where - Proj: Fn($out_predicate) -> OutputType + Copy, - { - self.0 - .normalize_with_proj(|p| proj($out_predicate::$proj_variant(p))) - } - } - - impl

core::ops::Not for $projection

- where - Self: AstPredicate<$out_predicate>, - { - type Output = NotAstPredicate; - - fn not(self) -> Self::Output { - NotAstPredicate(self) - } - } - - impl core::ops::BitAnd for $projection

- where - Self: AstPredicate<$out_predicate>, - PRhs: AstPredicate<$out_predicate>, - { - type Output = AndAstPredicate; - - fn bitand(self, rhs: PRhs) -> Self::Output { - AndAstPredicate(self, rhs) - } - } - - impl core::ops::BitOr for $projection

- where - Self: AstPredicate<$out_predicate>, - PRhs: AstPredicate<$out_predicate>, - { - type Output = OrAstPredicate; - - fn bitor(self, rhs: PRhs) -> Self::Output { - OrAstPredicate(self, rhs) - } - } - - #[doc = "An [`ObjectProjector`] that applies [`"] - #[doc = stringify!($projection)] - #[doc = "`]."] - #[derive(Default, Copy, Clone)] - pub struct $projector(PhantomData); - - impl> ObjectProjector for $projector { - type Input = $in_predicate; - type Output = Base::Output; - type ProjectedPredicate> = - Base::ProjectedPredicate<$projection

>; - - fn project_predicate>( - predicate: P, - ) -> Self::ProjectedPredicate

{ - Base::project_predicate($projection(predicate)) - } - } - }; -} - -// projections on AccountId -proj!(AccountIdDomainIdProjector(AccountIdDomainIdProjection): DomainIdPredicateBox => AccountIdPredicateBox::DomainId); -proj!(AccountIdSignatoryProjector(AccountIdSignatoryProjection): PublicKeyPredicateBox => AccountIdPredicateBox::Signatory); - -// projections on Account -proj!(AccountIdProjector(AccountIdProjection): AccountIdPredicateBox => AccountPredicateBox::Id); -proj!(AccountMetadataProjector(AccountMetadataProjection): MetadataPredicateBox => AccountPredicateBox::Metadata); - -// projections on AssetDefinitionId -proj!(AssetDefinitionIdDomainIdProjector(AssetDefinitionIdDomainIdProjection): DomainIdPredicateBox => AssetDefinitionIdPredicateBox::DomainId); -proj!(AssetDefinitionIdNameProjector(AssetDefinitionIdNameProjection): StringPredicateBox => AssetDefinitionIdPredicateBox::Name); - -// projections on AssetId -proj!(AssetIdDefinitionIdProjector(AssetIdDefinitionIdProjection): AssetDefinitionIdPredicateBox => AssetIdPredicateBox::DefinitionId); -proj!(AssetIdAccountIdProjector(AssetIdAccountIdProjection): AccountIdPredicateBox => AssetIdPredicateBox::AccountId); - -// projections on AssetDefinition -proj!(AssetDefinitionIdProjector(AssetDefinitionIdProjection): AssetDefinitionIdPredicateBox => AssetDefinitionPredicateBox::Id); -proj!(AssetDefinitionMetadataProjector(AssetDefinitionMetadataProjection): MetadataPredicateBox => AssetDefinitionPredicateBox::Metadata); - -// projections on Asset -proj!(AssetIdProjector(AssetIdProjection): AssetIdPredicateBox => AssetPredicateBox::Id); -proj!(AssetValueProjector(AssetValueProjection): AssetValuePredicateBox => AssetPredicateBox::Value); - -// projections on DomainId -proj!(DomainIdNameProjector(DomainIdNameProjection): StringPredicateBox => DomainIdPredicateBox::Name); - -// projections on Domain -proj!(DomainIdProjector(DomainIdProjection): DomainIdPredicateBox => DomainPredicateBox::Id); -proj!(DomainMetadataProjector(DomainMetadataProjection): MetadataPredicateBox => DomainPredicateBox::Metadata); - -// projections on RoleId -proj!(RoleIdNameProjector(RoleIdNameProjection): StringPredicateBox => RoleIdPredicateBox::Name); - -// projections on Role -proj!(RoleIdProjector(RoleIdProjection): RoleIdPredicateBox => RolePredicateBox::Id); - -// projections on TriggerId -proj!(TriggerIdNameProjector(TriggerIdNameProjection): StringPredicateBox => TriggerIdPredicateBox::Name); - -// projections on Trigger -proj!(TriggerIdProjector(TriggerIdProjection): TriggerIdPredicateBox => TriggerPredicateBox::Id); - -// projections on BlockHeader -proj!(BlockHeaderHashProjector(BlockHeaderHashProjection): BlockHashPredicateBox => BlockHeaderPredicateBox::Hash); - -// projections on SignedBlock -proj!(SignedBlockHeaderProjector(SignedBlockHeaderProjection): BlockHeaderPredicateBox => SignedBlockPredicateBox::Header); - -// projections on SignedTransaction -proj!(SignedTransactionHashProjector(SignedTransactionHashProjection): TransactionHashPredicateBox => SignedTransactionPredicateBox::Hash); -proj!(SignedTransactionAuthorityProjector(SignedTransactionAuthorityProjection): AccountIdPredicateBox => SignedTransactionPredicateBox::Authority); - -// projections on CommittedTransaction -proj!(CommittedTransactionBlockHashProjector(CommittedTransactionBlockHashProjection): BlockHashPredicateBox => CommittedTransactionPredicateBox::BlockHash); -proj!(CommittedTransactionValueProjector(CommittedTransactionValueProjection): SignedTransactionPredicateBox => CommittedTransactionPredicateBox::Value); -proj!(CommittedTransactionErrorProjector(CommittedTransactionErrorProjection): TransactionErrorPredicateBox => CommittedTransactionPredicateBox::Error); diff --git a/crates/iroha_data_model/src/query/predicate/prototypes/account.rs b/crates/iroha_data_model/src/query/predicate/prototypes/account.rs deleted file mode 100644 index 003819598af..00000000000 --- a/crates/iroha_data_model/src/query/predicate/prototypes/account.rs +++ /dev/null @@ -1,49 +0,0 @@ -//! Account-related prototypes, mirroring types in [`crate::account`]. - -use super::impl_prototype; -use crate::{ - account::AccountId, - query::{ - predicate::{ - predicate_atoms::account::{AccountIdPredicateBox, AccountPredicateBox}, - projectors::{ - AccountIdDomainIdProjector, AccountIdProjector, AccountIdSignatoryProjector, - AccountMetadataProjector, ObjectProjector, - }, - prototypes::{domain::DomainIdPrototype, MetadataPrototype, PublicKeyPrototype}, - }, - AstPredicate, HasPrototype, - }, -}; - -/// A prototype of [`AccountId`] for predicate construction. -#[derive(Default, Copy, Clone)] -pub struct AccountIdPrototype { - /// Build a predicate on domain ID of this [`AccountId`] - pub domain_id: DomainIdPrototype>, - /// Build a predicate on signatory of this [`AccountId`] - pub signatory: PublicKeyPrototype>, -} - -impl_prototype!(AccountIdPrototype: AccountIdPredicateBox); - -impl AccountIdPrototype -where - Projector: ObjectProjector, -{ - /// Creates a predicate that checks if the account ID equals the expected value. - pub fn eq(&self, expected: AccountId) -> Projector::ProjectedPredicate { - Projector::project_predicate(AccountIdPredicateBox::Equals(expected)) - } -} - -/// A prototype of [`crate::account::Account`] for predicate construction. -#[derive(Default, Copy, Clone)] -pub struct AccountPrototype { - /// Build a predicate on ID of this [`crate::account::Account`] - pub id: AccountIdPrototype>, - /// Build a predicate on metadata of this [`crate::account::Account`] - pub metadata: MetadataPrototype>, -} - -impl_prototype!(AccountPrototype: AccountPredicateBox); diff --git a/crates/iroha_data_model/src/query/predicate/prototypes/asset.rs b/crates/iroha_data_model/src/query/predicate/prototypes/asset.rs deleted file mode 100644 index aa48f671363..00000000000 --- a/crates/iroha_data_model/src/query/predicate/prototypes/asset.rs +++ /dev/null @@ -1,99 +0,0 @@ -//! Account-related prototypes, mirroring types in [`crate::asset`]. - -use core::marker::PhantomData; - -use super::{impl_prototype, MetadataPrototype, StringPrototype}; -use crate::{ - asset::{AssetDefinitionId, AssetId}, - query::{ - predicate::{ - predicate_atoms::asset::{ - AssetDefinitionIdPredicateBox, AssetDefinitionPredicateBox, AssetIdPredicateBox, - AssetPredicateBox, AssetValuePredicateBox, - }, - projectors::{ - AssetDefinitionIdDomainIdProjector, AssetDefinitionIdNameProjector, - AssetDefinitionIdProjector, AssetDefinitionMetadataProjector, - AssetIdAccountIdProjector, AssetIdDefinitionIdProjector, AssetIdProjector, - AssetValueProjector, - }, - prototypes::{account::AccountIdPrototype, domain::DomainIdPrototype, ObjectProjector}, - }, - AstPredicate, HasPrototype, - }, -}; - -/// A prototype of [`AssetDefinitionId`] for predicate construction. -#[derive(Default, Copy, Clone)] -pub struct AssetDefinitionIdPrototype { - /// Build a predicate on domain ID of this [`AssetDefinitionId`] - pub domain_id: DomainIdPrototype>, - /// Build a predicate on name of this [`AssetDefinitionId`] - pub name: StringPrototype>, -} - -impl_prototype!(AssetDefinitionIdPrototype: AssetDefinitionIdPredicateBox); - -impl AssetDefinitionIdPrototype -where - Projector: ObjectProjector, -{ - /// Creates a predicate that checks if the asset definition ID equals the expected value. - pub fn eq( - &self, - expected: AssetDefinitionId, - ) -> Projector::ProjectedPredicate { - Projector::project_predicate(AssetDefinitionIdPredicateBox::Equals(expected)) - } -} - -/// A prototype of [`AssetId`] for predicate construction. -#[derive(Default, Copy, Clone)] -pub struct AssetIdPrototype { - /// Build a predicate on definition ID of this [`AssetId`] - pub definition_id: AssetDefinitionIdPrototype>, - /// Build a predicate on account ID of this [`AssetId`] - pub account: AccountIdPrototype>, -} - -impl_prototype!(AssetIdPrototype: AssetIdPredicateBox); - -impl AssetIdPrototype -where - Projector: ObjectProjector, -{ - /// Creates a predicate that checks if the asset ID equals the expected value. - pub fn eq(&self, expected: AssetId) -> Projector::ProjectedPredicate { - Projector::project_predicate(AssetIdPredicateBox::Equals(expected)) - } -} - -/// A prototype of [`crate::asset::AssetDefinition`] for predicate construction. -#[derive(Default, Copy, Clone)] -pub struct AssetDefinitionPrototype { - /// Build a predicate on ID of this [`crate::asset::AssetDefinition`] - pub id: AssetDefinitionIdPrototype>, - /// Build a predicate on metadata of this [`crate::asset::AssetDefinition`] - pub metadata: MetadataPrototype>, -} - -impl_prototype!(AssetDefinitionPrototype: AssetDefinitionPredicateBox); - -/// A prototype of [`crate::asset::Asset`] for predicate construction. -#[derive(Default, Copy, Clone)] -pub struct AssetPrototype { - /// Build a predicate on ID of this [`crate::asset::Asset`] - pub id: AssetIdPrototype>, - /// Build a predicate on value of this [`crate::asset::Asset`] - pub value: AssetValuePrototype>, -} - -impl_prototype!(AssetPrototype: AssetPredicateBox); - -/// A prototype of [`crate::asset::AssetValue`] for predicate construction. -#[derive(Default, Copy, Clone)] -pub struct AssetValuePrototype { - phantom: PhantomData, -} - -impl_prototype!(AssetValuePrototype: AssetValuePredicateBox); diff --git a/crates/iroha_data_model/src/query/predicate/prototypes/block.rs b/crates/iroha_data_model/src/query/predicate/prototypes/block.rs deleted file mode 100644 index f72e59a0211..00000000000 --- a/crates/iroha_data_model/src/query/predicate/prototypes/block.rs +++ /dev/null @@ -1,137 +0,0 @@ -//! Account-related prototypes, mirroring types in [`crate::block`]. - -use core::marker::PhantomData; - -use iroha_crypto::HashOf; - -use super::impl_prototype; -use crate::{ - block::BlockHeader, - query::predicate::{ - predicate_ast_extensions::AstPredicateExt, - predicate_atoms::block::{ - BlockHashPredicateBox, BlockHeaderPredicateBox, CommittedTransactionPredicateBox, - SignedBlockPredicateBox, SignedTransactionPredicateBox, TransactionErrorPredicateBox, - TransactionHashPredicateBox, - }, - predicate_combinators::NotAstPredicate, - projectors::{ - BlockHeaderHashProjector, CommittedTransactionBlockHashProjector, - CommittedTransactionErrorProjector, CommittedTransactionValueProjector, - ObjectProjector, SignedBlockHeaderProjector, SignedTransactionAuthorityProjector, - SignedTransactionHashProjector, - }, - prototypes::account::AccountIdPrototype, - AstPredicate, HasPrototype, - }, - transaction::SignedTransaction, -}; - -/// A prototype of [`HashOf`] for predicate construction. -#[derive(Default, Copy, Clone)] -pub struct BlockHashPrototype { - phantom: PhantomData, -} - -impl_prototype!(BlockHashPrototype: BlockHashPredicateBox); - -impl BlockHashPrototype -where - Projector: ObjectProjector, -{ - /// Creates a predicate that checks if the hash equals the expected value. - pub fn eq( - &self, - expected: HashOf, - ) -> Projector::ProjectedPredicate { - Projector::project_predicate(BlockHashPredicateBox::Equals(expected)) - } -} - -/// A prototype of [`BlockHeader`] for predicate construction. -#[derive(Default, Copy, Clone)] -pub struct BlockHeaderPrototype { - /// Build a predicate on hash of this [`BlockHeader`] - pub hash: BlockHashPrototype>, -} - -impl_prototype!(BlockHeaderPrototype: BlockHeaderPredicateBox); - -/// A prototype of [`crate::block::SignedBlock`] for predicate construction. -#[derive(Default, Copy, Clone)] -pub struct SignedBlockPrototype { - /// Build a predicate on header of this [`crate::block::SignedBlock`] - pub header: BlockHeaderPrototype>, -} - -impl_prototype!(SignedBlockPrototype: SignedBlockPredicateBox); - -/// A prototype of [`HashOf`] -#[derive(Default, Copy, Clone)] -pub struct TransactionHashPrototype { - phantom: PhantomData, -} - -impl_prototype!(TransactionHashPrototype: TransactionHashPredicateBox); - -impl TransactionHashPrototype -where - Projector: ObjectProjector, -{ - /// Creates a predicate that checks if the hash equals the expected value. - pub fn eq( - &self, - expected: HashOf, - ) -> Projector::ProjectedPredicate { - Projector::project_predicate(TransactionHashPredicateBox::Equals(expected)) - } -} - -/// A prototype of [`SignedTransaction`] -#[derive(Default, Copy, Clone)] -pub struct SignedTransactionPrototype { - /// Build a predicate on hash of this [`SignedTransaction`] - pub hash: TransactionHashPrototype>, - /// Build a predicate on the transaction authority - pub authority: AccountIdPrototype>, -} - -impl_prototype!(SignedTransactionPrototype: SignedTransactionPredicateBox); - -/// A prototype of [`Option`] -#[derive(Default, Copy, Clone)] -pub struct TransactionErrorPrototype { - phantom: PhantomData, -} - -impl_prototype!(TransactionErrorPrototype: TransactionErrorPredicateBox); - -impl TransactionErrorPrototype -where - Projector: ObjectProjector, -{ - /// Creates a predicate that checks if there is an error. - pub fn is_some(&self) -> Projector::ProjectedPredicate { - Projector::project_predicate(TransactionErrorPredicateBox::IsSome) - } - - /// Creates a predicate that checks if there is no error. - pub fn is_none( - &self, - ) -> NotAstPredicate> { - Projector::project_predicate(TransactionErrorPredicateBox::IsSome).not() - } -} - -/// A prototype of [`crate::query::CommittedTransaction`] for predicate construction. -#[derive(Default, Clone)] -pub struct CommittedTransactionPrototype { - /// Build a predicate on the block hash inside - pub block_hash: BlockHashPrototype>, - /// Build a predicate on the transaction inside - pub value: SignedTransactionPrototype>, - /// Build a predicate on the transaction error - pub error: TransactionErrorPrototype>, -} - -impl_prototype!(CommittedTransactionPrototype: CommittedTransactionPredicateBox); diff --git a/crates/iroha_data_model/src/query/predicate/prototypes/domain.rs b/crates/iroha_data_model/src/query/predicate/prototypes/domain.rs deleted file mode 100644 index 4f8966bac55..00000000000 --- a/crates/iroha_data_model/src/query/predicate/prototypes/domain.rs +++ /dev/null @@ -1,43 +0,0 @@ -//! Account-related prototypes, mirroring types in [`crate::domain`]. - -use super::{impl_prototype, MetadataPrototype, StringPrototype}; -use crate::{ - domain::DomainId, - query::predicate::{ - predicate_atoms::domain::{DomainIdPredicateBox, DomainPredicateBox}, - projectors::{ - DomainIdNameProjector, DomainIdProjector, DomainMetadataProjector, ObjectProjector, - }, - AstPredicate, HasPrototype, - }, -}; - -/// A prototype of [`DomainId`] for predicate construction. -#[derive(Default, Copy, Clone)] -pub struct DomainIdPrototype { - /// Build a predicate on name of this [`DomainId`] - pub name: StringPrototype>, -} - -impl_prototype!(DomainIdPrototype: DomainIdPredicateBox); - -impl DomainIdPrototype -where - Projector: ObjectProjector, -{ - /// Creates a predicate that checks if the domain ID is equal to the expected value. - pub fn eq(&self, expected: DomainId) -> Projector::ProjectedPredicate { - Projector::project_predicate(DomainIdPredicateBox::Equals(expected)) - } -} - -/// A prototype of [`crate::domain::Domain`] for predicate construction. -#[derive(Default, Copy, Clone)] -pub struct DomainPrototype { - /// Build a predicate on ID of this [`crate::domain::Domain`] - pub id: DomainIdPrototype>, - /// Build a predicate on metadata of this [`crate::domain::Domain`] - pub metadata: MetadataPrototype>, -} - -impl_prototype!(DomainPrototype: DomainPredicateBox); diff --git a/crates/iroha_data_model/src/query/predicate/prototypes/mod.rs b/crates/iroha_data_model/src/query/predicate/prototypes/mod.rs deleted file mode 100644 index 0bede28ec3c..00000000000 --- a/crates/iroha_data_model/src/query/predicate/prototypes/mod.rs +++ /dev/null @@ -1,117 +0,0 @@ -//! This module contains prototypes for all types predicates can be applied to. The prototypes are used to construct predicates. -//! -//! The prototypes are zero-sized types that mimic the shape of objects in the data model, allowing for an ergonomic way to construct predicates. - -pub mod account; -pub mod asset; -pub mod block; -pub mod domain; -pub mod parameter; -pub mod peer; -pub mod permission; -pub mod role; -pub mod trigger; - -#[cfg(not(feature = "std"))] -use alloc::string::String; -use core::marker::PhantomData; - -use iroha_crypto::PublicKey; - -use super::{projectors::ObjectProjector, AstPredicate, HasPrototype}; -use crate::query::predicate::predicate_atoms::{ - MetadataPredicateBox, PublicKeyPredicateBox, StringPredicateBox, -}; - -macro_rules! impl_prototype { - ($prototype:ident: $predicate:ty) => { - impl $prototype - where - Projector: ObjectProjector, - { - /// Creates a predicate that delegates to the given predicate. - pub fn satisfies

(&self, predicate: P) -> Projector::ProjectedPredicate

- where - P: AstPredicate<$predicate>, - { - Projector::project_predicate(predicate) - } - } - - impl HasPrototype for $predicate { - type Prototype = $prototype; - } - }; -} -pub(crate) use impl_prototype; - -/// A prototype of [`String`] for predicate construction. -#[derive(Default, Copy, Clone)] -pub struct StringPrototype { - phantom: PhantomData, -} - -impl_prototype!(StringPrototype: StringPredicateBox); - -impl StringPrototype -where - Projector: ObjectProjector, -{ - /// Creates a predicate that checks if the string is equal to the expected value. - pub fn eq( - &self, - expected: impl Into, - ) -> Projector::ProjectedPredicate { - Projector::project_predicate(StringPredicateBox::Equals(expected.into())) - } - - /// Creates a predicate that checks if the string contains the expected value. - pub fn contains( - &self, - expected: impl Into, - ) -> Projector::ProjectedPredicate { - Projector::project_predicate(StringPredicateBox::Contains(expected.into())) - } - - /// Creates a predicate that checks if the string starts with the expected value. - pub fn starts_with( - &self, - expected: impl Into, - ) -> Projector::ProjectedPredicate { - Projector::project_predicate(StringPredicateBox::StartsWith(expected.into())) - } - - /// Creates a predicate that checks if the string ends with the expected value. - pub fn ends_with( - &self, - expected: impl Into, - ) -> Projector::ProjectedPredicate { - Projector::project_predicate(StringPredicateBox::EndsWith(expected.into())) - } -} - -/// A prototype of [`crate::metadata::Metadata`] for predicate construction. -#[derive(Default, Copy, Clone)] -pub struct MetadataPrototype { - phantom: PhantomData, -} - -impl_prototype!(MetadataPrototype: MetadataPredicateBox); - -/// A prototype of [`PublicKey`] for predicate construction. -#[derive(Default, Copy, Clone)] -pub struct PublicKeyPrototype { - phantom: PhantomData, -} - -impl_prototype!(PublicKeyPrototype: PublicKeyPredicateBox); - -impl PublicKeyPrototype -where - Projector: ObjectProjector, -{ - /// Creates a predicate that checks if the public key is equal to the expected value. - pub fn eq(&self, expected: PublicKey) -> Projector::ProjectedPredicate { - Projector::project_predicate(PublicKeyPredicateBox::Equals(expected)) - } -} diff --git a/crates/iroha_data_model/src/query/predicate/prototypes/parameter.rs b/crates/iroha_data_model/src/query/predicate/prototypes/parameter.rs deleted file mode 100644 index 6044e05441f..00000000000 --- a/crates/iroha_data_model/src/query/predicate/prototypes/parameter.rs +++ /dev/null @@ -1,16 +0,0 @@ -//! Account-related prototypes, mirroring types in [`crate::parameter`]. - -use core::marker::PhantomData; - -use super::impl_prototype; -use crate::query::{ - predicate::{predicate_atoms::parameter::ParameterPredicateBox, projectors::ObjectProjector}, - AstPredicate, HasPrototype, -}; - -/// A prototype of [`crate::parameter::Parameter`] for predicate construction. -#[derive(Default, Copy, Clone)] -pub struct ParameterPrototype { - phantom: PhantomData, -} -impl_prototype!(ParameterPrototype: ParameterPredicateBox); diff --git a/crates/iroha_data_model/src/query/predicate/prototypes/peer.rs b/crates/iroha_data_model/src/query/predicate/prototypes/peer.rs deleted file mode 100644 index da12cdcc092..00000000000 --- a/crates/iroha_data_model/src/query/predicate/prototypes/peer.rs +++ /dev/null @@ -1,16 +0,0 @@ -//! Account-related prototypes, mirroring types in [`crate::peer`]. - -use core::marker::PhantomData; - -use super::impl_prototype; -use crate::query::{ - predicate::{predicate_atoms::peer::PeerPredicateBox, projectors::ObjectProjector}, - AstPredicate, HasPrototype, -}; - -/// A prototype of [`crate::peer::Peer`] for predicate construction. -#[derive(Default, Copy, Clone)] -pub struct PeerPrototype { - phantom: PhantomData, -} -impl_prototype!(PeerPrototype: PeerPredicateBox); diff --git a/crates/iroha_data_model/src/query/predicate/prototypes/permission.rs b/crates/iroha_data_model/src/query/predicate/prototypes/permission.rs deleted file mode 100644 index 9f91121b452..00000000000 --- a/crates/iroha_data_model/src/query/predicate/prototypes/permission.rs +++ /dev/null @@ -1,16 +0,0 @@ -//! Account-related prototypes, mirroring types in [`crate::permission`]. - -use core::marker::PhantomData; - -use super::impl_prototype; -use crate::query::{ - predicate::{predicate_atoms::permission::PermissionPredicateBox, projectors::ObjectProjector}, - AstPredicate, HasPrototype, -}; - -/// A prototype of [`crate::permission::Permission`] for predicate construction. -#[derive(Default, Copy, Clone)] -pub struct PermissionPrototype { - phantom: PhantomData, -} -impl_prototype!(PermissionPrototype: PermissionPredicateBox); diff --git a/crates/iroha_data_model/src/query/predicate/prototypes/role.rs b/crates/iroha_data_model/src/query/predicate/prototypes/role.rs deleted file mode 100644 index 309ea2f48a8..00000000000 --- a/crates/iroha_data_model/src/query/predicate/prototypes/role.rs +++ /dev/null @@ -1,40 +0,0 @@ -//! Account-related prototypes, mirroring types in [`crate::role`]. - -use super::impl_prototype; -use crate::{ - query::predicate::{ - predicate_atoms::role::{RoleIdPredicateBox, RolePredicateBox}, - projectors::{ObjectProjector, RoleIdNameProjector, RoleIdProjector}, - prototypes::StringPrototype, - AstPredicate, HasPrototype, - }, - role::RoleId, -}; - -/// A prototype of [`RoleId`] for predicate construction. -#[derive(Default, Copy, Clone)] -pub struct RoleIdPrototype { - /// Build a predicate on name of this [`RoleId`]. - pub name: StringPrototype>, -} - -impl_prototype!(RoleIdPrototype: RoleIdPredicateBox); - -impl RoleIdPrototype -where - Projector: ObjectProjector, -{ - /// Creates a predicate that checks if the role ID is equal to the expected value. - pub fn eq(&self, expected: RoleId) -> Projector::ProjectedPredicate { - Projector::project_predicate(RoleIdPredicateBox::Equals(expected)) - } -} - -/// A prototype of [`crate::role::Role`] for predicate construction. -#[derive(Default, Copy, Clone)] -pub struct RolePrototype { - /// Build a predicate on ID of this [`crate::role::Role`]. - pub id: RoleIdPrototype>, -} - -impl_prototype!(RolePrototype: RolePredicateBox); diff --git a/crates/iroha_data_model/src/query/predicate/prototypes/trigger.rs b/crates/iroha_data_model/src/query/predicate/prototypes/trigger.rs deleted file mode 100644 index 9ee8c29c044..00000000000 --- a/crates/iroha_data_model/src/query/predicate/prototypes/trigger.rs +++ /dev/null @@ -1,40 +0,0 @@ -//! Account-related prototypes, mirroring types in [`crate::trigger`]. - -use super::impl_prototype; -use crate::{ - prelude::TriggerId, - query::predicate::{ - predicate_atoms::trigger::{TriggerIdPredicateBox, TriggerPredicateBox}, - projectors::{ObjectProjector, TriggerIdProjector}, - prototypes::StringPrototype, - AstPredicate, HasPrototype, - }, -}; - -/// A prototype of [`TriggerId`] for predicate construction. -#[derive(Default, Copy, Clone)] -pub struct TriggerIdPrototype { - /// Build a predicate on name of this [`TriggerId`] - pub name: StringPrototype>, -} - -impl_prototype!(TriggerIdPrototype: TriggerIdPredicateBox); - -impl TriggerIdPrototype -where - Projector: ObjectProjector, -{ - /// Creates a predicate that checks if the trigger ID is equal to the expected value. - pub fn eq(&self, expected: TriggerId) -> Projector::ProjectedPredicate { - Projector::project_predicate(TriggerIdPredicateBox::Equals(expected)) - } -} - -/// A prototype of [`crate::trigger::Trigger`] for predicate construction. -#[derive(Default, Copy, Clone)] -pub struct TriggerPrototype { - /// Build a predicate on ID of this [`crate::trigger::Trigger`] - pub id: TriggerIdPrototype>, -} - -impl_prototype!(TriggerPrototype: TriggerPredicateBox); diff --git a/crates/iroha_data_model/src/visit.rs b/crates/iroha_data_model/src/visit.rs index dae06cd82cd..bd4f163bd85 100644 --- a/crates/iroha_data_model/src/visit.rs +++ b/crates/iroha_data_model/src/visit.rs @@ -7,7 +7,7 @@ use crate::{ isi::Log, prelude::*, query::{ - trigger::FindTriggers, AnyQueryBox, QueryWithFilterFor, QueryWithParams, SingularQueryBox, + trigger::FindTriggers, AnyQueryBox, QueryWithFilter, QueryWithParams, SingularQueryBox, }, }; @@ -62,21 +62,21 @@ pub trait Visit { visit_find_trigger_metadata(&FindTriggerMetadata), // Visit IterableQueryBox - visit_find_domains(&QueryWithFilterFor), - visit_find_accounts(&QueryWithFilterFor), - visit_find_assets(&QueryWithFilterFor), - visit_find_assets_definitions(&QueryWithFilterFor), - visit_find_roles(&QueryWithFilterFor), - visit_find_role_ids(&QueryWithFilterFor), - visit_find_permissions_by_account_id(&QueryWithFilterFor), - visit_find_roles_by_account_id(&QueryWithFilterFor), - visit_find_accounts_with_asset(&QueryWithFilterFor), - visit_find_peers(&QueryWithFilterFor), - visit_find_active_trigger_ids(&QueryWithFilterFor), - visit_find_triggers(&QueryWithFilterFor), - visit_find_transactions(&QueryWithFilterFor), - visit_find_blocks(&QueryWithFilterFor), - visit_find_block_headers(&QueryWithFilterFor), + visit_find_domains(&QueryWithFilter), + visit_find_accounts(&QueryWithFilter), + visit_find_assets(&QueryWithFilter), + visit_find_assets_definitions(&QueryWithFilter), + visit_find_roles(&QueryWithFilter), + visit_find_role_ids(&QueryWithFilter), + visit_find_permissions_by_account_id(&QueryWithFilter), + visit_find_roles_by_account_id(&QueryWithFilter), + visit_find_accounts_with_asset(&QueryWithFilter), + visit_find_peers(&QueryWithFilter), + visit_find_active_trigger_ids(&QueryWithFilter), + visit_find_triggers(&QueryWithFilter), + visit_find_transactions(&QueryWithFilter), + visit_find_blocks(&QueryWithFilter), + visit_find_block_headers(&QueryWithFilter), // Visit RegisterBox visit_register_peer(&Register), @@ -386,19 +386,19 @@ leaf_visitors! { visit_find_trigger_metadata(&FindTriggerMetadata), // Iterable Query visitors - visit_find_domains(&QueryWithFilterFor), - visit_find_accounts(&QueryWithFilterFor), - visit_find_assets(&QueryWithFilterFor), - visit_find_assets_definitions(&QueryWithFilterFor), - visit_find_roles(&QueryWithFilterFor), - visit_find_role_ids(&QueryWithFilterFor), - visit_find_permissions_by_account_id(&QueryWithFilterFor), - visit_find_roles_by_account_id(&QueryWithFilterFor), - visit_find_accounts_with_asset(&QueryWithFilterFor), - visit_find_peers(&QueryWithFilterFor), - visit_find_active_trigger_ids(&QueryWithFilterFor), - visit_find_triggers(&QueryWithFilterFor), - visit_find_transactions(&QueryWithFilterFor), - visit_find_blocks(&QueryWithFilterFor), - visit_find_block_headers(&QueryWithFilterFor), + visit_find_domains(&QueryWithFilter), + visit_find_accounts(&QueryWithFilter), + visit_find_assets(&QueryWithFilter), + visit_find_assets_definitions(&QueryWithFilter), + visit_find_roles(&QueryWithFilter), + visit_find_role_ids(&QueryWithFilter), + visit_find_permissions_by_account_id(&QueryWithFilter), + visit_find_roles_by_account_id(&QueryWithFilter), + visit_find_accounts_with_asset(&QueryWithFilter), + visit_find_peers(&QueryWithFilter), + visit_find_active_trigger_ids(&QueryWithFilter), + visit_find_triggers(&QueryWithFilter), + visit_find_transactions(&QueryWithFilter), + visit_find_blocks(&QueryWithFilter), + visit_find_block_headers(&QueryWithFilter), } diff --git a/crates/iroha_executor/src/permission.rs b/crates/iroha_executor/src/permission.rs index e5ef2a51b6c..c9c3c92edc1 100644 --- a/crates/iroha_executor/src/permission.rs +++ b/crates/iroha_executor/src/permission.rs @@ -151,7 +151,7 @@ pub trait ExecutorPermission: Permission + PartialEq { .expect("INTERNAL BUG: `FindRolesByAccountId` must never fail") .map(|role_id| role_id.dbg_expect("Failed to get role from cursor")) .fold(CompoundPredicate::Or(Vec::new()), |predicate, role_id| { - predicate.or(RolePredicateBox::build(|role| role.id.eq(role_id))) + predicate.or(CompoundPredicate::::build(|role| role.id.eq(role_id))) }); // check if any of the roles have the permission we need diff --git a/crates/iroha_schema/src/lib.rs b/crates/iroha_schema/src/lib.rs index 45c9e78c5e6..cd628a7596f 100644 --- a/crates/iroha_schema/src/lib.rs +++ b/crates/iroha_schema/src/lib.rs @@ -280,6 +280,22 @@ pub struct BitmapMask { #[derive(Debug, Clone, Serialize)] pub struct Compact(T); +impl TypeId for () { + fn id() -> String { + "()".to_owned() + } +} +impl IntoSchema for () { + fn type_name() -> String { + "()".to_owned() + } + fn update_schema_map(map: &mut MetaMap) { + if !map.contains_key::() { + map.insert::(Metadata::Tuple(UnnamedFieldsMeta { types: vec![] })); + } + } +} + macro_rules! impl_schema_int { ($($t:ty),*) => {$( impl TypeId for $t { diff --git a/crates/iroha_schema_gen/src/lib.rs b/crates/iroha_schema_gen/src/lib.rs index 82fa07d5d93..ea09d3ce920 100644 --- a/crates/iroha_schema_gen/src/lib.rs +++ b/crates/iroha_schema_gen/src/lib.rs @@ -115,9 +115,13 @@ types!( AccountEventFilter, AccountEventSet, AccountId, - AccountIdPredicateBox, + AccountIdPredicateAtom, + AccountIdProjection, + AccountIdProjection, AccountPermissionChanged, - AccountPredicateBox, + AccountPredicateAtom, + AccountProjection, + AccountProjection, AccountRoleChanged, Action, Algorithm, @@ -128,20 +132,30 @@ types!( AssetDefinitionEventFilter, AssetDefinitionEventSet, AssetDefinitionId, - AssetDefinitionIdPredicateBox, + AssetDefinitionIdPredicateAtom, + AssetDefinitionIdProjection, + AssetDefinitionIdProjection, AssetDefinitionOwnerChanged, - AssetDefinitionPredicateBox, + AssetDefinitionPredicateAtom, + AssetDefinitionProjection, + AssetDefinitionProjection, AssetDefinitionTotalQuantityChanged, AssetEvent, AssetEventFilter, AssetEventSet, AssetId, - AssetIdPredicateBox, - AssetPredicateBox, + AssetIdPredicateAtom, + AssetIdProjection, + AssetIdProjection, + AssetPredicateAtom, + AssetProjection, + AssetProjection, AssetTransferBox, AssetType, AssetValue, - AssetValuePredicateBox, + AssetValuePredicateAtom, + AssetValueProjection, + AssetValueProjection, BTreeMap, BTreeMap, BTreeMap, @@ -150,9 +164,13 @@ types!( BTreeSet, BlockEvent, BlockEventFilter, - BlockHashPredicateBox, + BlockHeaderHashPredicateAtom, + BlockHeaderHashProjection, + BlockHeaderHashProjection, BlockHeader, - BlockHeaderPredicateBox, + BlockHeaderPredicateAtom, + BlockHeaderProjection, + BlockHeaderProjection, BlockMessage, BlockParameter, BlockParameters, @@ -161,39 +179,41 @@ types!( BlockSignature, BlockStatus, BlockSubscriptionRequest, - Box>, - Box>, - Box>, - Box>, - Box>, - Box>, - Box>, - Box>, - Box>, - Box>, - Box>, - Box>, - Box>, + Box>, + Box>, + Box>, + Box>, + Box>, + Box>, + Box>, + Box>, + Box>, + Box>, + Box>, + Box>, + Box>, Box, Burn, Burn, BurnBox, ChainId, CommittedTransaction, - CommittedTransactionPredicateBox, - CompoundPredicate, - CompoundPredicate, - CompoundPredicate, - CompoundPredicate, - CompoundPredicate, - CompoundPredicate, - CompoundPredicate, - CompoundPredicate, - CompoundPredicate, - CompoundPredicate, - CompoundPredicate, - CompoundPredicate, - CompoundPredicate, + CommittedTransactionPredicateAtom, + CommittedTransactionProjection, + CommittedTransactionProjection, + CompoundPredicate, + CompoundPredicate, + CompoundPredicate, + CompoundPredicate, + CompoundPredicate, + CompoundPredicate, + CompoundPredicate, + CompoundPredicate, + CompoundPredicate, + CompoundPredicate, + CompoundPredicate, + CompoundPredicate, + CompoundPredicate, ConfigurationEvent, ConfigurationEventFilter, ConfigurationEventSet, @@ -210,9 +230,13 @@ types!( DomainEventFilter, DomainEventSet, DomainId, - DomainIdPredicateBox, + DomainIdPredicateAtom, + DomainIdProjection, + DomainIdProjection, DomainOwnerChanged, - DomainPredicateBox, + DomainPredicateAtom, + DomainProjection, + DomainProjection, EventBox, EventFilterBox, EventMessage, @@ -287,7 +311,9 @@ types!( MetadataChanged, MetadataChanged, MetadataChanged, - MetadataPredicateBox, + MetadataPredicateAtom, + MetadataProjection, + MetadataProjection, Mint, Mint, MintBox, @@ -295,6 +321,8 @@ types!( Mintable, Mismatch, Name, + NameProjection, + NameProjection, NewAccount, NewAssetDefinition, NewDomain, @@ -335,37 +363,44 @@ types!( PeerEventSet, Peer, PeerId, - PeerPredicateBox, + PeerIdPredicateAtom, + PeerIdProjection, + PeerIdProjection, Permission, - PermissionPredicateBox, + PermissionPredicateAtom, + PermissionProjection, + PermissionProjection, PipelineEventBox, PipelineEventFilterBox, PublicKey, - PublicKeyPredicateBox, + PublicKeyPredicateAtom, + PublicKeyProjection, + PublicKeyProjection, QueryBox, QueryExecutionFail, QueryOutput, QueryOutputBatchBox, + QueryOutputBatchBoxTuple, QueryParams, QueryRequest, QueryRequestWithAuthority, QueryResponse, QuerySignature, - QueryWithFilter, - QueryWithFilter, - QueryWithFilter, - QueryWithFilter, - QueryWithFilter, - QueryWithFilter, - QueryWithFilter, - QueryWithFilter, - QueryWithFilter, - QueryWithFilter, - QueryWithFilter, - QueryWithFilter, - QueryWithFilter, - QueryWithFilter, - QueryWithFilter, + QueryWithFilter, + QueryWithFilter, + QueryWithFilter, + QueryWithFilter, + QueryWithFilter, + QueryWithFilter, + QueryWithFilter, + QueryWithFilter, + QueryWithFilter, + QueryWithFilter, + QueryWithFilter, + QueryWithFilter, + QueryWithFilter, + QueryWithFilter, + QueryWithFilter, QueryWithParams, Register, Register, @@ -392,9 +427,26 @@ types!( RoleEventFilter, RoleEventSet, RoleId, - RoleIdPredicateBox, + RoleIdPredicateAtom, + RoleIdProjection, + RoleIdProjection, RolePermissionChanged, - RolePredicateBox, + RolePredicateAtom, + RoleProjection, + RoleProjection, + SelectorTuple, + SelectorTuple, + SelectorTuple, + SelectorTuple, + SelectorTuple, + SelectorTuple, + SelectorTuple, + SelectorTuple, + SelectorTuple, + SelectorTuple, + SelectorTuple, + SelectorTuple, + SelectorTuple, SetKeyValue, SetKeyValue, SetKeyValue, @@ -407,12 +459,16 @@ types!( SignatureOf, SignatureOf, SignedBlock, - SignedBlockPredicateBox, + SignedBlockPredicateAtom, + SignedBlockProjection, + SignedBlockProjection, SignedBlockV1, SignedQuery, SignedQueryV1, SignedTransaction, - SignedTransactionPredicateBox, + SignedTransactionPredicateAtom, + SignedTransactionProjection, + SignedTransactionProjection, SignedTransactionV1, SingularQueryBox, SingularQueryOutputBox, @@ -424,17 +480,21 @@ types!( SocketAddrV6, Sorting, String, - StringPredicateBox, + StringPredicateAtom, SumeragiParameter, SumeragiParameters, TimeEvent, TimeEventFilter, TimeInterval, TimeSchedule, - TransactionErrorPredicateBox, + TransactionErrorPredicateAtom, + TransactionErrorProjection, + TransactionErrorProjection, TransactionEvent, TransactionEventFilter, - TransactionHashPredicateBox, + TransactionHashPredicateAtom, + TransactionHashProjection, + TransactionHashProjection, TransactionLimitError, TransactionParameter, TransactionParameters, @@ -456,9 +516,13 @@ types!( TriggerEventFilter, TriggerEventSet, TriggerId, - TriggerIdPredicateBox, + TriggerIdPredicateAtom, + TriggerIdProjection, + TriggerIdProjection, TriggerNumberOfExecutionsChanged, - TriggerPredicateBox, + TriggerPredicateAtom, + TriggerProjection, + TriggerProjection, TypeError, Unregister, Unregister, @@ -471,41 +535,68 @@ types!( Upgrade, ValidationFail, Vec, + Vec, Vec, + Vec, Vec, + Vec, + Vec, Vec, Vec, Vec, - Vec>, - Vec>, - Vec>, - Vec>, - Vec>, - Vec>, - Vec>, - Vec>, - Vec>, - Vec>, - Vec>, - Vec>, - Vec>, + Vec>, + Vec>, + Vec>, + Vec>, + Vec>, + Vec>, + Vec>, + Vec>, + Vec>, + Vec>, + Vec>, + Vec>, + Vec>, Vec, + Vec, Vec, Vec, Vec, Vec, Vec, Vec, + Vec, Vec, Vec, Vec, Vec, + Vec>, + Vec>, + Vec>, + Vec>, + Vec>, + Vec>, + Vec>, + Vec>, + Vec, + Vec, + Vec>, + Vec>, + Vec>, + Vec, + Vec>, + Vec>, + Vec>, + Vec, + Vec>, + Vec>, Vec, Vec, Vec, WasmExecutionFail, WasmSmartContract, + (), [u16; 8], [u8; 4], [u8; 32], @@ -550,12 +641,12 @@ pub mod complete_data_model { }, prelude::*, query::{ + dsl::{CompoundPredicate, PredicateMarker, SelectorMarker}, error::{FindError, QueryExecutionFail}, parameters::{ForwardCursor, QueryParams}, - predicate::CompoundPredicate, - CommittedTransaction, QueryOutput, QueryOutputBatchBox, QueryRequestWithAuthority, - QueryResponse, QuerySignature, QueryWithFilter, QueryWithParams, SignedQuery, - SignedQueryV1, SingularQueryOutputBox, + CommittedTransaction, QueryOutput, QueryOutputBatchBox, QueryOutputBatchBoxTuple, + QueryRequestWithAuthority, QueryResponse, QuerySignature, QueryWithFilter, + QueryWithParams, SignedQuery, SignedQueryV1, SingularQueryOutputBox, }, transaction::{ error::TransactionLimitError, SignedTransactionV1, TransactionPayload, diff --git a/crates/iroha_smart_contract/src/lib.rs b/crates/iroha_smart_contract/src/lib.rs index d9754948ad9..99f75872704 100644 --- a/crates/iroha_smart_contract/src/lib.rs +++ b/crates/iroha_smart_contract/src/lib.rs @@ -17,8 +17,7 @@ use data_model::{ pub use iroha_data_model as data_model; use iroha_data_model::query::{ builder::{QueryBuilder, QueryExecutor}, - predicate::HasPredicateBox, - QueryOutputBatchBox, QueryRequest, QueryResponse, QueryWithParams, SingularQuery, + QueryOutputBatchBoxTuple, QueryRequest, QueryResponse, QueryWithParams, SingularQuery, SingularQueryBox, SingularQueryOutputBox, }; pub use iroha_smart_contract_derive::main; @@ -100,10 +99,7 @@ impl Iroha { } /// Build an iterable query for execution in a smart contract. - pub fn query( - &self, - query: Q, - ) -> QueryBuilder::Item as HasPredicateBox>::PredicateBoxType> + pub fn query(&self, query: Q) -> QueryBuilder where Q: Query, { @@ -164,7 +160,7 @@ impl QueryExecutor for Iroha { fn start_query( &self, query: QueryWithParams, - ) -> Result<(QueryOutputBatchBox, u64, Option), Self::Error> { + ) -> Result<(QueryOutputBatchBoxTuple, u64, Option), Self::Error> { let QueryResponse::Iterable(output) = Self::execute_query(&QueryRequest::Start(query))? else { dbg_panic!("BUG: iroha returned unexpected type in iterable query"); @@ -181,7 +177,7 @@ impl QueryExecutor for Iroha { fn continue_query( cursor: Self::Cursor, - ) -> Result<(QueryOutputBatchBox, u64, Option), Self::Error> { + ) -> Result<(QueryOutputBatchBoxTuple, u64, Option), Self::Error> { let QueryResponse::Iterable(output) = Self::execute_query(&QueryRequest::Continue(cursor.cursor))? else { diff --git a/docs/source/references/schema.json b/docs/source/references/schema.json index 49dbe788535..78dadd174c0 100644 --- a/docs/source/references/schema.json +++ b/docs/source/references/schema.json @@ -1,4 +1,5 @@ { + "()": null, "Account": { "Struct": [ { @@ -127,22 +128,50 @@ } ] }, - "AccountIdPredicateBox": { + "AccountIdPredicateAtom": { "Enum": [ { "tag": "Equals", "discriminant": 0, "type": "AccountId" + } + ] + }, + "AccountIdProjection": { + "Enum": [ + { + "tag": "Atom", + "discriminant": 0, + "type": "AccountIdPredicateAtom" }, { - "tag": "DomainId", + "tag": "Domain", + "discriminant": 1, + "type": "DomainIdProjection" + }, + { + "tag": "Signatory", + "discriminant": 2, + "type": "PublicKeyProjection" + } + ] + }, + "AccountIdProjection": { + "Enum": [ + { + "tag": "Atom", + "discriminant": 0, + "type": "()" + }, + { + "tag": "Domain", "discriminant": 1, - "type": "DomainIdPredicateBox" + "type": "DomainIdProjection" }, { "tag": "Signatory", "discriminant": 2, - "type": "PublicKeyPredicateBox" + "type": "PublicKeyProjection" } ] }, @@ -158,17 +187,44 @@ } ] }, - "AccountPredicateBox": { + "AccountPredicateAtom": { + "Enum": [] + }, + "AccountProjection": { "Enum": [ { - "tag": "Id", + "tag": "Atom", "discriminant": 0, - "type": "AccountIdPredicateBox" + "type": "AccountPredicateAtom" + }, + { + "tag": "Id", + "discriminant": 1, + "type": "AccountIdProjection" }, { "tag": "Metadata", + "discriminant": 2, + "type": "MetadataProjection" + } + ] + }, + "AccountProjection": { + "Enum": [ + { + "tag": "Atom", + "discriminant": 0, + "type": "()" + }, + { + "tag": "Id", "discriminant": 1, - "type": "MetadataPredicateBox" + "type": "AccountIdProjection" + }, + { + "tag": "Metadata", + "discriminant": 2, + "type": "MetadataProjection" } ] }, @@ -400,22 +456,50 @@ } ] }, - "AssetDefinitionIdPredicateBox": { + "AssetDefinitionIdPredicateAtom": { "Enum": [ { "tag": "Equals", "discriminant": 0, "type": "AssetDefinitionId" + } + ] + }, + "AssetDefinitionIdProjection": { + "Enum": [ + { + "tag": "Atom", + "discriminant": 0, + "type": "AssetDefinitionIdPredicateAtom" }, { - "tag": "DomainId", + "tag": "Domain", + "discriminant": 1, + "type": "DomainIdProjection" + }, + { + "tag": "Name", + "discriminant": 2, + "type": "NameProjection" + } + ] + }, + "AssetDefinitionIdProjection": { + "Enum": [ + { + "tag": "Atom", + "discriminant": 0, + "type": "()" + }, + { + "tag": "Domain", "discriminant": 1, - "type": "DomainIdPredicateBox" + "type": "DomainIdProjection" }, { "tag": "Name", "discriminant": 2, - "type": "StringPredicateBox" + "type": "NameProjection" } ] }, @@ -431,22 +515,44 @@ } ] }, - "AssetDefinitionPredicateBox": { + "AssetDefinitionPredicateAtom": { + "Enum": [] + }, + "AssetDefinitionProjection": { "Enum": [ { - "tag": "Id", + "tag": "Atom", "discriminant": 0, - "type": "AssetDefinitionIdPredicateBox" + "type": "AssetDefinitionPredicateAtom" + }, + { + "tag": "Id", + "discriminant": 1, + "type": "AssetDefinitionIdProjection" }, { "tag": "Metadata", + "discriminant": 2, + "type": "MetadataProjection" + } + ] + }, + "AssetDefinitionProjection": { + "Enum": [ + { + "tag": "Atom", + "discriminant": 0, + "type": "()" + }, + { + "tag": "Id", "discriminant": 1, - "type": "MetadataPredicateBox" + "type": "AssetDefinitionIdProjection" }, { - "tag": "OwnedBy", + "tag": "Metadata", "discriminant": 2, - "type": "AccountIdPredicateBox" + "type": "MetadataProjection" } ] }, @@ -551,36 +657,91 @@ } ] }, - "AssetIdPredicateBox": { + "AssetIdPredicateAtom": { "Enum": [ { "tag": "Equals", "discriminant": 0, "type": "AssetId" + } + ] + }, + "AssetIdProjection": { + "Enum": [ + { + "tag": "Atom", + "discriminant": 0, + "type": "AssetIdPredicateAtom" }, { - "tag": "DefinitionId", + "tag": "Account", "discriminant": 1, - "type": "AssetDefinitionIdPredicateBox" + "type": "AccountIdProjection" }, { - "tag": "AccountId", + "tag": "Definition", "discriminant": 2, - "type": "AccountIdPredicateBox" + "type": "AssetDefinitionIdProjection" } ] }, - "AssetPredicateBox": { + "AssetIdProjection": { "Enum": [ { - "tag": "Id", + "tag": "Atom", + "discriminant": 0, + "type": "()" + }, + { + "tag": "Account", + "discriminant": 1, + "type": "AccountIdProjection" + }, + { + "tag": "Definition", + "discriminant": 2, + "type": "AssetDefinitionIdProjection" + } + ] + }, + "AssetPredicateAtom": { + "Enum": [] + }, + "AssetProjection": { + "Enum": [ + { + "tag": "Atom", "discriminant": 0, - "type": "AssetIdPredicateBox" + "type": "AssetPredicateAtom" + }, + { + "tag": "Id", + "discriminant": 1, + "type": "AssetIdProjection" }, { "tag": "Value", + "discriminant": 2, + "type": "AssetValueProjection" + } + ] + }, + "AssetProjection": { + "Enum": [ + { + "tag": "Atom", + "discriminant": 0, + "type": "()" + }, + { + "tag": "Id", "discriminant": 1, - "type": "AssetValuePredicateBox" + "type": "AssetIdProjection" + }, + { + "tag": "Value", + "discriminant": 2, + "type": "AssetValueProjection" } ] }, @@ -625,9 +786,27 @@ } ] }, - "AssetValuePredicateBox": { + "AssetValuePredicateAtom": { "Enum": [] }, + "AssetValueProjection": { + "Enum": [ + { + "tag": "Atom", + "discriminant": 0, + "type": "AssetValuePredicateAtom" + } + ] + }, + "AssetValueProjection": { + "Enum": [ + { + "tag": "Atom", + "discriminant": 0, + "type": "()" + } + ] + }, "BlockEvent": { "Struct": [ { @@ -652,15 +831,6 @@ } ] }, - "BlockHashPredicateBox": { - "Enum": [ - { - "tag": "Equals", - "discriminant": 0, - "type": "HashOf" - } - ] - }, "BlockHeader": { "Struct": [ { @@ -685,12 +855,61 @@ } ] }, - "BlockHeaderPredicateBox": { + "BlockHeaderHashPredicateAtom": { + "Enum": [ + { + "tag": "Equals", + "discriminant": 0, + "type": "HashOf" + } + ] + }, + "BlockHeaderHashProjection": { + "Enum": [ + { + "tag": "Atom", + "discriminant": 0, + "type": "BlockHeaderHashPredicateAtom" + } + ] + }, + "BlockHeaderHashProjection": { "Enum": [ + { + "tag": "Atom", + "discriminant": 0, + "type": "()" + } + ] + }, + "BlockHeaderPredicateAtom": { + "Enum": [] + }, + "BlockHeaderProjection": { + "Enum": [ + { + "tag": "Atom", + "discriminant": 0, + "type": "BlockHeaderPredicateAtom" + }, { "tag": "Hash", + "discriminant": 1, + "type": "BlockHeaderHashProjection" + } + ] + }, + "BlockHeaderProjection": { + "Enum": [ + { + "tag": "Atom", "discriminant": 0, - "type": "BlockHashPredicateBox" + "type": "()" + }, + { + "tag": "Hash", + "discriminant": 1, + "type": "BlockHeaderHashProjection" } ] }, @@ -1016,22 +1235,54 @@ } ] }, - "CommittedTransactionPredicateBox": { + "CommittedTransactionPredicateAtom": { + "Enum": [] + }, + "CommittedTransactionProjection": { "Enum": [ { - "tag": "BlockHash", + "tag": "Atom", "discriminant": 0, - "type": "BlockHashPredicateBox" + "type": "CommittedTransactionPredicateAtom" }, { - "tag": "Value", + "tag": "BlockHash", "discriminant": 1, - "type": "SignedTransactionPredicateBox" + "type": "BlockHeaderHashProjection" + }, + { + "tag": "Value", + "discriminant": 2, + "type": "SignedTransactionProjection" }, { "tag": "Error", + "discriminant": 3, + "type": "TransactionErrorProjection" + } + ] + }, + "CommittedTransactionProjection": { + "Enum": [ + { + "tag": "Atom", + "discriminant": 0, + "type": "()" + }, + { + "tag": "BlockHash", + "discriminant": 1, + "type": "BlockHeaderHashProjection" + }, + { + "tag": "Value", "discriminant": 2, - "type": "TransactionErrorPredicateBox" + "type": "SignedTransactionProjection" + }, + { + "tag": "Error", + "discriminant": 3, + "type": "TransactionErrorProjection" } ] }, @@ -1041,315 +1292,315 @@ "Compact": { "Int": "Compact" }, - "CompoundPredicate": { + "CompoundPredicate": { "Enum": [ { "tag": "Atom", "discriminant": 0, - "type": "AccountPredicateBox" + "type": "AccountProjection" }, { "tag": "Not", "discriminant": 1, - "type": "CompoundPredicate" + "type": "CompoundPredicate" }, { "tag": "And", "discriminant": 2, - "type": "Vec>" + "type": "Vec>" }, { "tag": "Or", "discriminant": 3, - "type": "Vec>" + "type": "Vec>" } ] }, - "CompoundPredicate": { + "CompoundPredicate": { "Enum": [ { "tag": "Atom", "discriminant": 0, - "type": "AssetDefinitionPredicateBox" + "type": "AssetProjection" }, { "tag": "Not", "discriminant": 1, - "type": "CompoundPredicate" + "type": "CompoundPredicate" }, { "tag": "And", "discriminant": 2, - "type": "Vec>" + "type": "Vec>" }, { "tag": "Or", "discriminant": 3, - "type": "Vec>" + "type": "Vec>" } ] }, - "CompoundPredicate": { + "CompoundPredicate": { "Enum": [ { "tag": "Atom", "discriminant": 0, - "type": "AssetPredicateBox" + "type": "AssetDefinitionProjection" }, { "tag": "Not", "discriminant": 1, - "type": "CompoundPredicate" + "type": "CompoundPredicate" }, { "tag": "And", "discriminant": 2, - "type": "Vec>" + "type": "Vec>" }, { "tag": "Or", "discriminant": 3, - "type": "Vec>" + "type": "Vec>" } ] }, - "CompoundPredicate": { + "CompoundPredicate": { "Enum": [ { "tag": "Atom", "discriminant": 0, - "type": "BlockHeaderPredicateBox" + "type": "BlockHeaderProjection" }, { "tag": "Not", "discriminant": 1, - "type": "CompoundPredicate" + "type": "CompoundPredicate" }, { "tag": "And", "discriminant": 2, - "type": "Vec>" + "type": "Vec>" }, { "tag": "Or", "discriminant": 3, - "type": "Vec>" + "type": "Vec>" } ] }, - "CompoundPredicate": { + "CompoundPredicate": { "Enum": [ { "tag": "Atom", "discriminant": 0, - "type": "CommittedTransactionPredicateBox" + "type": "CommittedTransactionProjection" }, { "tag": "Not", "discriminant": 1, - "type": "CompoundPredicate" + "type": "CompoundPredicate" }, { "tag": "And", "discriminant": 2, - "type": "Vec>" + "type": "Vec>" }, { "tag": "Or", "discriminant": 3, - "type": "Vec>" + "type": "Vec>" } ] }, - "CompoundPredicate": { + "CompoundPredicate": { "Enum": [ { "tag": "Atom", "discriminant": 0, - "type": "DomainPredicateBox" + "type": "DomainProjection" }, { "tag": "Not", "discriminant": 1, - "type": "CompoundPredicate" + "type": "CompoundPredicate" }, { "tag": "And", "discriminant": 2, - "type": "Vec>" + "type": "Vec>" }, { "tag": "Or", "discriminant": 3, - "type": "Vec>" + "type": "Vec>" } ] }, - "CompoundPredicate": { + "CompoundPredicate": { "Enum": [ { "tag": "Atom", "discriminant": 0, - "type": "PeerPredicateBox" + "type": "PeerIdProjection" }, { "tag": "Not", "discriminant": 1, - "type": "CompoundPredicate" + "type": "CompoundPredicate" }, { "tag": "And", "discriminant": 2, - "type": "Vec>" + "type": "Vec>" }, { "tag": "Or", "discriminant": 3, - "type": "Vec>" + "type": "Vec>" } ] }, - "CompoundPredicate": { + "CompoundPredicate": { "Enum": [ { "tag": "Atom", "discriminant": 0, - "type": "PermissionPredicateBox" + "type": "PermissionProjection" }, { "tag": "Not", "discriminant": 1, - "type": "CompoundPredicate" + "type": "CompoundPredicate" }, { "tag": "And", "discriminant": 2, - "type": "Vec>" + "type": "Vec>" }, { "tag": "Or", "discriminant": 3, - "type": "Vec>" + "type": "Vec>" } ] }, - "CompoundPredicate": { + "CompoundPredicate": { "Enum": [ { "tag": "Atom", "discriminant": 0, - "type": "RoleIdPredicateBox" + "type": "RoleProjection" }, { "tag": "Not", "discriminant": 1, - "type": "CompoundPredicate" + "type": "CompoundPredicate" }, { "tag": "And", "discriminant": 2, - "type": "Vec>" + "type": "Vec>" }, { "tag": "Or", "discriminant": 3, - "type": "Vec>" + "type": "Vec>" } ] }, - "CompoundPredicate": { + "CompoundPredicate": { "Enum": [ { "tag": "Atom", "discriminant": 0, - "type": "RolePredicateBox" + "type": "RoleIdProjection" }, { "tag": "Not", "discriminant": 1, - "type": "CompoundPredicate" + "type": "CompoundPredicate" }, { "tag": "And", "discriminant": 2, - "type": "Vec>" + "type": "Vec>" }, { "tag": "Or", "discriminant": 3, - "type": "Vec>" + "type": "Vec>" } ] }, - "CompoundPredicate": { + "CompoundPredicate": { "Enum": [ { "tag": "Atom", "discriminant": 0, - "type": "SignedBlockPredicateBox" + "type": "SignedBlockProjection" }, { "tag": "Not", "discriminant": 1, - "type": "CompoundPredicate" + "type": "CompoundPredicate" }, { "tag": "And", "discriminant": 2, - "type": "Vec>" + "type": "Vec>" }, { "tag": "Or", "discriminant": 3, - "type": "Vec>" + "type": "Vec>" } ] }, - "CompoundPredicate": { + "CompoundPredicate": { "Enum": [ { "tag": "Atom", "discriminant": 0, - "type": "TriggerIdPredicateBox" + "type": "TriggerProjection" }, { "tag": "Not", "discriminant": 1, - "type": "CompoundPredicate" + "type": "CompoundPredicate" }, { "tag": "And", "discriminant": 2, - "type": "Vec>" + "type": "Vec>" }, { "tag": "Or", "discriminant": 3, - "type": "Vec>" + "type": "Vec>" } ] }, - "CompoundPredicate": { + "CompoundPredicate": { "Enum": [ { "tag": "Atom", "discriminant": 0, - "type": "TriggerPredicateBox" + "type": "TriggerIdProjection" }, { "tag": "Not", "discriminant": 1, - "type": "CompoundPredicate" + "type": "CompoundPredicate" }, { "tag": "And", "discriminant": 2, - "type": "Vec>" + "type": "Vec>" }, { "tag": "Or", "discriminant": 3, - "type": "Vec>" + "type": "Vec>" } ] }, @@ -1603,17 +1854,40 @@ } ] }, - "DomainIdPredicateBox": { + "DomainIdPredicateAtom": { "Enum": [ { "tag": "Equals", "discriminant": 0, "type": "DomainId" + } + ] + }, + "DomainIdProjection": { + "Enum": [ + { + "tag": "Atom", + "discriminant": 0, + "type": "DomainIdPredicateAtom" }, { "tag": "Name", "discriminant": 1, - "type": "StringPredicateBox" + "type": "NameProjection" + } + ] + }, + "DomainIdProjection": { + "Enum": [ + { + "tag": "Atom", + "discriminant": 0, + "type": "()" + }, + { + "tag": "Name", + "discriminant": 1, + "type": "NameProjection" } ] }, @@ -1629,17 +1903,44 @@ } ] }, - "DomainPredicateBox": { + "DomainPredicateAtom": { + "Enum": [] + }, + "DomainProjection": { "Enum": [ { - "tag": "Id", + "tag": "Atom", "discriminant": 0, - "type": "DomainIdPredicateBox" + "type": "DomainPredicateAtom" + }, + { + "tag": "Id", + "discriminant": 1, + "type": "DomainIdProjection" }, { "tag": "Metadata", + "discriminant": 2, + "type": "MetadataProjection" + } + ] + }, + "DomainProjection": { + "Enum": [ + { + "tag": "Atom", + "discriminant": 0, + "type": "()" + }, + { + "tag": "Id", "discriminant": 1, - "type": "MetadataPredicateBox" + "type": "DomainIdProjection" + }, + { + "tag": "Metadata", + "discriminant": 2, + "type": "MetadataProjection" } ] }, @@ -2549,9 +2850,27 @@ } ] }, - "MetadataPredicateBox": { + "MetadataPredicateAtom": { "Enum": [] }, + "MetadataProjection": { + "Enum": [ + { + "tag": "Atom", + "discriminant": 0, + "type": "MetadataPredicateAtom" + } + ] + }, + "MetadataProjection": { + "Enum": [ + { + "tag": "Atom", + "discriminant": 0, + "type": "()" + } + ] + }, "Mint": { "Struct": [ { @@ -2694,6 +3013,24 @@ ] }, "Name": "String", + "NameProjection": { + "Enum": [ + { + "tag": "Atom", + "discriminant": 0, + "type": "StringPredicateAtom" + } + ] + }, + "NameProjection": { + "Enum": [ + { + "tag": "Atom", + "discriminant": 0, + "type": "()" + } + ] + }, "NewAccount": { "Struct": [ { @@ -2994,9 +3331,37 @@ } ] }, - "PeerPredicateBox": { + "PeerIdPredicateAtom": { "Enum": [] }, + "PeerIdProjection": { + "Enum": [ + { + "tag": "Atom", + "discriminant": 0, + "type": "PeerIdPredicateAtom" + }, + { + "tag": "PublicKey", + "discriminant": 1, + "type": "PublicKeyProjection" + } + ] + }, + "PeerIdProjection": { + "Enum": [ + { + "tag": "Atom", + "discriminant": 0, + "type": "()" + }, + { + "tag": "PublicKey", + "discriminant": 1, + "type": "PublicKeyProjection" + } + ] + }, "Permission": { "Struct": [ { @@ -3009,9 +3374,27 @@ } ] }, - "PermissionPredicateBox": { + "PermissionPredicateAtom": { "Enum": [] }, + "PermissionProjection": { + "Enum": [ + { + "tag": "Atom", + "discriminant": 0, + "type": "PermissionPredicateAtom" + } + ] + }, + "PermissionProjection": { + "Enum": [ + { + "tag": "Atom", + "discriminant": 0, + "type": "()" + } + ] + }, "PipelineEventBox": { "Enum": [ { @@ -3052,7 +3435,7 @@ } ] }, - "PublicKeyPredicateBox": { + "PublicKeyPredicateAtom": { "Enum": [ { "tag": "Equals", @@ -3061,82 +3444,100 @@ } ] }, + "PublicKeyProjection": { + "Enum": [ + { + "tag": "Atom", + "discriminant": 0, + "type": "PublicKeyPredicateAtom" + } + ] + }, + "PublicKeyProjection": { + "Enum": [ + { + "tag": "Atom", + "discriminant": 0, + "type": "()" + } + ] + }, "QueryBox": { "Enum": [ { "tag": "FindDomains", "discriminant": 0, - "type": "QueryWithFilter" + "type": "QueryWithFilter" }, { "tag": "FindAccounts", "discriminant": 1, - "type": "QueryWithFilter" + "type": "QueryWithFilter" }, { "tag": "FindAssets", "discriminant": 2, - "type": "QueryWithFilter" + "type": "QueryWithFilter" }, { "tag": "FindAssetsDefinitions", "discriminant": 3, - "type": "QueryWithFilter" + "type": "QueryWithFilter" }, { "tag": "FindRoles", "discriminant": 4, - "type": "QueryWithFilter" + "type": "QueryWithFilter" }, { "tag": "FindRoleIds", "discriminant": 5, - "type": "QueryWithFilter" + "type": "QueryWithFilter" }, { "tag": "FindPermissionsByAccountId", "discriminant": 6, - "type": "QueryWithFilter" + "type": "QueryWithFilter" }, { "tag": "FindRolesByAccountId", "discriminant": 7, - "type": "QueryWithFilter" + "type": "QueryWithFilter" }, { "tag": "FindAccountsWithAsset", "discriminant": 8, - "type": "QueryWithFilter" + "type": "QueryWithFilter" }, { "tag": "FindPeers", "discriminant": 9, - "type": "QueryWithFilter" + "type": "QueryWithFilter" }, { "tag": "FindActiveTriggerIds", "discriminant": 10, - "type": "QueryWithFilter" + "type": "QueryWithFilter" }, { "tag": "FindTriggers", "discriminant": 11, - "type": "QueryWithFilter" + "type": "QueryWithFilter" }, { "tag": "FindTransactions", "discriminant": 12, - "type": "QueryWithFilter" + "type": "QueryWithFilter" }, { "tag": "FindBlocks", "discriminant": 13, - "type": "QueryWithFilter" + "type": "QueryWithFilter" }, { "tag": "FindBlockHeaders", "discriminant": 14, - "type": "QueryWithFilter" + "type": "QueryWithFilter" } ] }, @@ -3182,89 +3583,162 @@ "Struct": [ { "name": "batch", - "type": "QueryOutputBatchBox" + "type": "QueryOutputBatchBoxTuple" }, { "name": "remaining_items", "type": "u64" }, { - "name": "continue_cursor", - "type": "Option" - } - ] - }, - "QueryOutputBatchBox": { - "Enum": [ + "name": "continue_cursor", + "type": "Option" + } + ] + }, + "QueryOutputBatchBox": { + "Enum": [ + { + "tag": "PublicKey", + "discriminant": 0, + "type": "Vec" + }, + { + "tag": "String", + "discriminant": 1, + "type": "Vec" + }, + { + "tag": "Metadata", + "discriminant": 2, + "type": "Vec" + }, + { + "tag": "Name", + "discriminant": 3, + "type": "Vec" + }, + { + "tag": "DomainId", + "discriminant": 4, + "type": "Vec" + }, { "tag": "Domain", - "discriminant": 0, + "discriminant": 5, "type": "Vec" }, + { + "tag": "AccountId", + "discriminant": 6, + "type": "Vec" + }, { "tag": "Account", - "discriminant": 1, + "discriminant": 7, "type": "Vec" }, + { + "tag": "AssetId", + "discriminant": 8, + "type": "Vec" + }, { "tag": "Asset", - "discriminant": 2, + "discriminant": 9, "type": "Vec" }, + { + "tag": "AssetValue", + "discriminant": 10, + "type": "Vec" + }, + { + "tag": "AssetDefinitionId", + "discriminant": 11, + "type": "Vec" + }, { "tag": "AssetDefinition", - "discriminant": 3, + "discriminant": 12, "type": "Vec" }, { "tag": "Role", - "discriminant": 4, + "discriminant": 13, "type": "Vec" }, { "tag": "Parameter", - "discriminant": 5, + "discriminant": 14, "type": "Vec" }, { "tag": "Permission", - "discriminant": 6, + "discriminant": 15, "type": "Vec" }, { - "tag": "Transaction", - "discriminant": 7, + "tag": "CommittedTransaction", + "discriminant": 16, "type": "Vec" }, + { + "tag": "SignedTransaction", + "discriminant": 17, + "type": "Vec" + }, + { + "tag": "TransactionHash", + "discriminant": 18, + "type": "Vec>" + }, + { + "tag": "TransactionRejectionReason", + "discriminant": 19, + "type": "Vec>" + }, { "tag": "Peer", - "discriminant": 8, + "discriminant": 20, "type": "Vec" }, { "tag": "RoleId", - "discriminant": 9, + "discriminant": 21, "type": "Vec" }, { "tag": "TriggerId", - "discriminant": 10, + "discriminant": 22, "type": "Vec" }, { "tag": "Trigger", - "discriminant": 11, + "discriminant": 23, "type": "Vec" }, { "tag": "Block", - "discriminant": 12, + "discriminant": 24, "type": "Vec" }, { "tag": "BlockHeader", - "discriminant": 13, + "discriminant": 25, "type": "Vec" + }, + { + "tag": "BlockHeaderHash", + "discriminant": 26, + "type": "Vec>" + } + ] + }, + "QueryOutputBatchBoxTuple": { + "Struct": [ + { + "name": "tuple", + "type": "Vec" } ] }, @@ -3330,7 +3804,7 @@ ] }, "QuerySignature": "SignatureOf", - "QueryWithFilter": { + "QueryWithFilter": { "Struct": [ { "name": "query", @@ -3338,11 +3812,15 @@ }, { "name": "predicate", - "type": "CompoundPredicate" + "type": "CompoundPredicate" + }, + { + "name": "selector", + "type": "SelectorTuple" } ] }, - "QueryWithFilter": { + "QueryWithFilter": { "Struct": [ { "name": "query", @@ -3350,11 +3828,15 @@ }, { "name": "predicate", - "type": "CompoundPredicate" + "type": "CompoundPredicate" + }, + { + "name": "selector", + "type": "SelectorTuple" } ] }, - "QueryWithFilter": { + "QueryWithFilter": { "Struct": [ { "name": "query", @@ -3362,11 +3844,15 @@ }, { "name": "predicate", - "type": "CompoundPredicate" + "type": "CompoundPredicate" + }, + { + "name": "selector", + "type": "SelectorTuple" } ] }, - "QueryWithFilter": { + "QueryWithFilter": { "Struct": [ { "name": "query", @@ -3374,11 +3860,15 @@ }, { "name": "predicate", - "type": "CompoundPredicate" + "type": "CompoundPredicate" + }, + { + "name": "selector", + "type": "SelectorTuple" } ] }, - "QueryWithFilter": { + "QueryWithFilter": { "Struct": [ { "name": "query", @@ -3386,11 +3876,15 @@ }, { "name": "predicate", - "type": "CompoundPredicate" + "type": "CompoundPredicate" + }, + { + "name": "selector", + "type": "SelectorTuple" } ] }, - "QueryWithFilter": { + "QueryWithFilter": { "Struct": [ { "name": "query", @@ -3398,11 +3892,15 @@ }, { "name": "predicate", - "type": "CompoundPredicate" + "type": "CompoundPredicate" + }, + { + "name": "selector", + "type": "SelectorTuple" } ] }, - "QueryWithFilter": { + "QueryWithFilter": { "Struct": [ { "name": "query", @@ -3410,11 +3908,15 @@ }, { "name": "predicate", - "type": "CompoundPredicate" + "type": "CompoundPredicate" + }, + { + "name": "selector", + "type": "SelectorTuple" } ] }, - "QueryWithFilter": { + "QueryWithFilter": { "Struct": [ { "name": "query", @@ -3422,11 +3924,15 @@ }, { "name": "predicate", - "type": "CompoundPredicate" + "type": "CompoundPredicate" + }, + { + "name": "selector", + "type": "SelectorTuple" } ] }, - "QueryWithFilter": { + "QueryWithFilter": { "Struct": [ { "name": "query", @@ -3434,11 +3940,15 @@ }, { "name": "predicate", - "type": "CompoundPredicate" + "type": "CompoundPredicate" + }, + { + "name": "selector", + "type": "SelectorTuple" } ] }, - "QueryWithFilter": { + "QueryWithFilter": { "Struct": [ { "name": "query", @@ -3446,11 +3956,15 @@ }, { "name": "predicate", - "type": "CompoundPredicate" + "type": "CompoundPredicate" + }, + { + "name": "selector", + "type": "SelectorTuple" } ] }, - "QueryWithFilter": { + "QueryWithFilter": { "Struct": [ { "name": "query", @@ -3458,11 +3972,15 @@ }, { "name": "predicate", - "type": "CompoundPredicate" + "type": "CompoundPredicate" + }, + { + "name": "selector", + "type": "SelectorTuple" } ] }, - "QueryWithFilter": { + "QueryWithFilter": { "Struct": [ { "name": "query", @@ -3470,11 +3988,15 @@ }, { "name": "predicate", - "type": "CompoundPredicate" + "type": "CompoundPredicate" + }, + { + "name": "selector", + "type": "SelectorTuple" } ] }, - "QueryWithFilter": { + "QueryWithFilter": { "Struct": [ { "name": "query", @@ -3482,11 +4004,15 @@ }, { "name": "predicate", - "type": "CompoundPredicate" + "type": "CompoundPredicate" + }, + { + "name": "selector", + "type": "SelectorTuple" } ] }, - "QueryWithFilter": { + "QueryWithFilter": { "Struct": [ { "name": "query", @@ -3494,11 +4020,15 @@ }, { "name": "predicate", - "type": "CompoundPredicate" + "type": "CompoundPredicate" + }, + { + "name": "selector", + "type": "SelectorTuple" } ] }, - "QueryWithFilter": { + "QueryWithFilter": { "Struct": [ { "name": "query", @@ -3506,7 +4036,11 @@ }, { "name": "predicate", - "type": "CompoundPredicate" + "type": "CompoundPredicate" + }, + { + "name": "selector", + "type": "SelectorTuple" } ] }, @@ -3897,17 +4431,40 @@ } ] }, - "RoleIdPredicateBox": { + "RoleIdPredicateAtom": { "Enum": [ { "tag": "Equals", "discriminant": 0, "type": "RoleId" + } + ] + }, + "RoleIdProjection": { + "Enum": [ + { + "tag": "Atom", + "discriminant": 0, + "type": "RoleIdPredicateAtom" + }, + { + "tag": "Name", + "discriminant": 1, + "type": "NameProjection" + } + ] + }, + "RoleIdProjection": { + "Enum": [ + { + "tag": "Atom", + "discriminant": 0, + "type": "()" }, { "tag": "Name", "discriminant": 1, - "type": "StringPredicateBox" + "type": "NameProjection" } ] }, @@ -3923,12 +4480,34 @@ } ] }, - "RolePredicateBox": { + "RolePredicateAtom": { + "Enum": [] + }, + "RoleProjection": { "Enum": [ + { + "tag": "Atom", + "discriminant": 0, + "type": "RolePredicateAtom" + }, { "tag": "Id", + "discriminant": 1, + "type": "RoleIdProjection" + } + ] + }, + "RoleProjection": { + "Enum": [ + { + "tag": "Atom", "discriminant": 0, - "type": "RoleIdPredicateBox" + "type": "()" + }, + { + "tag": "Id", + "discriminant": 1, + "type": "RoleIdProjection" } ] }, @@ -3944,6 +4523,19 @@ } ] }, + "SelectorTuple": "Vec>", + "SelectorTuple": "Vec>", + "SelectorTuple": "Vec>", + "SelectorTuple": "Vec>", + "SelectorTuple": "Vec>", + "SelectorTuple": "Vec>", + "SelectorTuple": "Vec>", + "SelectorTuple": "Vec>", + "SelectorTuple": "Vec>", + "SelectorTuple": "Vec>", + "SelectorTuple": "Vec>", + "SelectorTuple": "Vec>", + "SelectorTuple": "Vec>", "SetKeyValue": { "Struct": [ { @@ -4074,12 +4666,34 @@ } ] }, - "SignedBlockPredicateBox": { + "SignedBlockPredicateAtom": { + "Enum": [] + }, + "SignedBlockProjection": { "Enum": [ + { + "tag": "Atom", + "discriminant": 0, + "type": "SignedBlockPredicateAtom" + }, { "tag": "Header", + "discriminant": 1, + "type": "BlockHeaderProjection" + } + ] + }, + "SignedBlockProjection": { + "Enum": [ + { + "tag": "Atom", "discriminant": 0, - "type": "BlockHeaderPredicateBox" + "type": "()" + }, + { + "tag": "Header", + "discriminant": 1, + "type": "BlockHeaderProjection" } ] }, @@ -4129,17 +4743,44 @@ } ] }, - "SignedTransactionPredicateBox": { + "SignedTransactionPredicateAtom": { + "Enum": [] + }, + "SignedTransactionProjection": { "Enum": [ { - "tag": "Hash", + "tag": "Atom", "discriminant": 0, - "type": "TransactionHashPredicateBox" + "type": "SignedTransactionPredicateAtom" + }, + { + "tag": "Hash", + "discriminant": 1, + "type": "TransactionHashProjection" }, { "tag": "Authority", + "discriminant": 2, + "type": "AccountIdProjection" + } + ] + }, + "SignedTransactionProjection": { + "Enum": [ + { + "tag": "Atom", + "discriminant": 0, + "type": "()" + }, + { + "tag": "Hash", "discriminant": 1, - "type": "AccountIdPredicateBox" + "type": "TransactionHashProjection" + }, + { + "tag": "Authority", + "discriminant": 2, + "type": "AccountIdProjection" } ] }, @@ -4358,7 +4999,7 @@ ] }, "String": "String", - "StringPredicateBox": { + "StringPredicateAtom": { "Enum": [ { "tag": "Equals", @@ -4438,7 +5079,7 @@ } ] }, - "TransactionErrorPredicateBox": { + "TransactionErrorPredicateAtom": { "Enum": [ { "tag": "IsSome", @@ -4446,6 +5087,24 @@ } ] }, + "TransactionErrorProjection": { + "Enum": [ + { + "tag": "Atom", + "discriminant": 0, + "type": "TransactionErrorPredicateAtom" + } + ] + }, + "TransactionErrorProjection": { + "Enum": [ + { + "tag": "Atom", + "discriminant": 0, + "type": "()" + } + ] + }, "TransactionEvent": { "Struct": [ { @@ -4478,7 +5137,7 @@ } ] }, - "TransactionHashPredicateBox": { + "TransactionHashPredicateAtom": { "Enum": [ { "tag": "Equals", @@ -4487,6 +5146,24 @@ } ] }, + "TransactionHashProjection": { + "Enum": [ + { + "tag": "Atom", + "discriminant": 0, + "type": "TransactionHashPredicateAtom" + } + ] + }, + "TransactionHashProjection": { + "Enum": [ + { + "tag": "Atom", + "discriminant": 0, + "type": "()" + } + ] + }, "TransactionLimitError": { "Struct": [ { @@ -4833,17 +5510,40 @@ } ] }, - "TriggerIdPredicateBox": { + "TriggerIdPredicateAtom": { "Enum": [ { "tag": "Equals", "discriminant": 0, "type": "TriggerId" + } + ] + }, + "TriggerIdProjection": { + "Enum": [ + { + "tag": "Atom", + "discriminant": 0, + "type": "TriggerIdPredicateAtom" + }, + { + "tag": "Name", + "discriminant": 1, + "type": "NameProjection" + } + ] + }, + "TriggerIdProjection": { + "Enum": [ + { + "tag": "Atom", + "discriminant": 0, + "type": "()" }, { "tag": "Name", "discriminant": 1, - "type": "StringPredicateBox" + "type": "NameProjection" } ] }, @@ -4859,12 +5559,34 @@ } ] }, - "TriggerPredicateBox": { + "TriggerPredicateAtom": { + "Enum": [] + }, + "TriggerProjection": { "Enum": [ + { + "tag": "Atom", + "discriminant": 0, + "type": "TriggerPredicateAtom" + }, { "tag": "Id", + "discriminant": 1, + "type": "TriggerIdProjection" + } + ] + }, + "TriggerProjection": { + "Enum": [ + { + "tag": "Atom", "discriminant": 0, - "type": "TriggerIdPredicateBox" + "type": "()" + }, + { + "tag": "Id", + "discriminant": 1, + "type": "TriggerIdProjection" } ] }, @@ -5015,99 +5737,177 @@ "Vec": { "Vec": "Account" }, + "Vec": { + "Vec": "AccountId" + }, + "Vec>": { + "Vec": "AccountProjection" + }, "Vec": { "Vec": "Asset" }, "Vec": { "Vec": "AssetDefinition" }, + "Vec": { + "Vec": "AssetDefinitionId" + }, + "Vec>": { + "Vec": "AssetDefinitionProjection" + }, + "Vec": { + "Vec": "AssetId" + }, + "Vec>": { + "Vec": "AssetProjection" + }, + "Vec": { + "Vec": "AssetValue" + }, "Vec": { "Vec": "BlockHeader" }, + "Vec>": { + "Vec": "BlockHeaderProjection" + }, "Vec": { "Vec": "BlockSignature" }, "Vec": { "Vec": "CommittedTransaction" }, - "Vec>": { - "Vec": "CompoundPredicate" + "Vec>": { + "Vec": "CommittedTransactionProjection" }, - "Vec>": { - "Vec": "CompoundPredicate" + "Vec>": { + "Vec": "CompoundPredicate" }, - "Vec>": { - "Vec": "CompoundPredicate" + "Vec>": { + "Vec": "CompoundPredicate" }, - "Vec>": { - "Vec": "CompoundPredicate" + "Vec>": { + "Vec": "CompoundPredicate" }, - "Vec>": { - "Vec": "CompoundPredicate" + "Vec>": { + "Vec": "CompoundPredicate" }, - "Vec>": { - "Vec": "CompoundPredicate" + "Vec>": { + "Vec": "CompoundPredicate" }, - "Vec>": { - "Vec": "CompoundPredicate" + "Vec>": { + "Vec": "CompoundPredicate" }, - "Vec>": { - "Vec": "CompoundPredicate" + "Vec>": { + "Vec": "CompoundPredicate" }, - "Vec>": { - "Vec": "CompoundPredicate" + "Vec>": { + "Vec": "CompoundPredicate" }, - "Vec>": { - "Vec": "CompoundPredicate" + "Vec>": { + "Vec": "CompoundPredicate" }, - "Vec>": { - "Vec": "CompoundPredicate" + "Vec>": { + "Vec": "CompoundPredicate" }, - "Vec>": { - "Vec": "CompoundPredicate" + "Vec>": { + "Vec": "CompoundPredicate" }, - "Vec>": { - "Vec": "CompoundPredicate" + "Vec>": { + "Vec": "CompoundPredicate" + }, + "Vec>": { + "Vec": "CompoundPredicate" }, "Vec": { "Vec": "Domain" }, + "Vec": { + "Vec": "DomainId" + }, + "Vec>": { + "Vec": "DomainProjection" + }, "Vec": { "Vec": "EventFilterBox" }, "Vec": { "Vec": "GenesisWasmTrigger" }, + "Vec>": { + "Vec": "HashOf" + }, + "Vec>": { + "Vec": "HashOf" + }, "Vec": { "Vec": "InstructionBox" }, + "Vec": { + "Vec": "Metadata" + }, + "Vec": { + "Vec": "Name" + }, + "Vec>": { + "Vec": "Option" + }, "Vec": { "Vec": "Parameter" }, "Vec": { "Vec": "PeerId" }, + "Vec>": { + "Vec": "PeerIdProjection" + }, "Vec": { "Vec": "Permission" }, + "Vec>": { + "Vec": "PermissionProjection" + }, + "Vec": { + "Vec": "PublicKey" + }, + "Vec": { + "Vec": "QueryOutputBatchBox" + }, "Vec": { "Vec": "Role" }, "Vec": { "Vec": "RoleId" }, + "Vec>": { + "Vec": "RoleIdProjection" + }, + "Vec>": { + "Vec": "RoleProjection" + }, "Vec": { "Vec": "SignedBlock" }, + "Vec>": { + "Vec": "SignedBlockProjection" + }, "Vec": { "Vec": "SignedTransaction" }, + "Vec": { + "Vec": "String" + }, "Vec": { "Vec": "Trigger" }, "Vec": { "Vec": "TriggerId" }, + "Vec>": { + "Vec": "TriggerIdProjection" + }, + "Vec>": { + "Vec": "TriggerProjection" + }, "Vec": { "Vec": "u8" }, diff --git a/pytests/iroha_cli_tests/src/iroha_cli/have.py b/pytests/iroha_cli_tests/src/iroha_cli/have.py index a27db42c0dd..62359660a59 100644 --- a/pytests/iroha_cli_tests/src/iroha_cli/have.py +++ b/pytests/iroha_cli_tests/src/iroha_cli/have.py @@ -44,7 +44,9 @@ def domain(expected: Any, owned_by: Optional[Any] = None) -> bool: """ def domain_in_domains() -> bool: - domains = iroha.list_filter({"Atom": {"Id": {"Equals": expected}}}).domains() + domains = iroha.list_filter( + {"Atom": {"Id": {"Atom": {"Equals": expected}}}} + ).domains() if not expected_in_actual(expected, domains): return False if owned_by: @@ -65,7 +67,9 @@ def account(expected: Any) -> bool: """ def account_in_accounts() -> bool: - accounts = iroha.list_filter({"Atom": {"Id": {"Equals": expected}}}).accounts() + accounts = iroha.list_filter( + {"Atom": {"Id": {"Atom": {"Equals": expected}}}} + ).accounts() return expected_in_actual(expected, accounts) return iroha_cli.wait_for(account_in_accounts) @@ -81,7 +85,7 @@ def asset_definition(expected: Any) -> bool: def asset_definition_in_asset_definitions() -> bool: asset_definitions = iroha.list_filter( - {"Atom": {"Id": {"Equals": expected}}} + {"Atom": {"Id": {"Atom": {"Equals": expected}}}} ).asset_definitions() return expected_in_actual(expected, asset_definitions) @@ -97,7 +101,9 @@ def asset(expected: Any) -> bool: """ def asset_in_assets() -> bool: - assets = iroha.list_filter({"Atom": {"Id": {"Equals": expected}}}).assets() + assets = iroha.list_filter( + {"Atom": {"Id": {"Atom": {"Equals": expected}}}} + ).assets() return expected_in_actual(expected, assets) return iroha_cli.wait_for(asset_in_assets) @@ -114,7 +120,7 @@ def asset_has_quantity(expected_asset_id: Any, expected_quantity: str) -> bool: def check_quantity() -> bool: assets = iroha.list_filter( - {"Atom": {"Id": {"Equals": expected_asset_id}}} + {"Atom": {"Id": {"Atom": {"Equals": expected_asset_id}}}} ).assets() actual_quantity = None for asset_item in assets: diff --git a/pytests/iroha_cli_tests/test/accounts/test_accounts_query_filters.py b/pytests/iroha_cli_tests/test/accounts/test_accounts_query_filters.py index fa14dff4918..25695639843 100644 --- a/pytests/iroha_cli_tests/test/accounts/test_accounts_query_filters.py +++ b/pytests/iroha_cli_tests/test/accounts/test_accounts_query_filters.py @@ -11,7 +11,7 @@ def condition(): domain = GIVEN_registered_account.domain with allure.step(f"WHEN iroha_cli query accounts " f'in the "{domain}" domain'): accounts = iroha.list_filter( - {"Atom": {"Id": {"DomainId": {"Equals": domain}}}} + {"Atom": {"Id": {"Domain": {"Atom": {"Equals": domain}}}}} ).accounts() with allure.step("THEN Iroha should return only accounts with this domain"): allure.attach( @@ -33,7 +33,7 @@ def condition(): f'WHEN iroha_cli query accounts with account id "{account_id}"' ): accounts = iroha.list_filter( - {"Atom": {"Id": {"Equals": account_id}}} + {"Atom": {"Id": {"Atom": {"Equals": account_id}}}} ).accounts() with allure.step("THEN Iroha should return only accounts with this id"): allure.attach( diff --git a/pytests/iroha_cli_tests/test/assets/test_assets_query_filters.py b/pytests/iroha_cli_tests/test/assets/test_assets_query_filters.py index 8f6dc5ecd0d..4c473964fc0 100644 --- a/pytests/iroha_cli_tests/test/assets/test_assets_query_filters.py +++ b/pytests/iroha_cli_tests/test/assets/test_assets_query_filters.py @@ -13,7 +13,11 @@ def condition(): ) with allure.step(f"WHEN iroha_cli query assets" f'in the "{domain}" domain'): assets = iroha.list_filter( - {"Atom": {"Id": {"DefinitionId": {"DomainId": {"Equals": domain}}}}} + { + "Atom": { + "Id": {"Definition": {"Domain": {"Atom": {"Equals": domain}}}} + } + } ).assets() with allure.step("THEN Iroha should return only assets with this domain"): allure.attach( @@ -38,7 +42,7 @@ def condition(): ) with allure.step(f'WHEN iroha_cli query assets with name "{name}"'): assets = iroha.list_filter( - {"Atom": {"Id": {"DefinitionId": {"Name": {"Equals": name}}}}} + {"Atom": {"Id": {"Definition": {"Name": {"Atom": {"Equals": name}}}}}} ).assets() with allure.step("THEN Iroha should return only assets with this name"): allure.attach( @@ -64,7 +68,9 @@ def condition(): + GIVEN_currently_authorized_account.domain ) with allure.step(f'WHEN iroha_cli query assets with asset id "{asset_id}"'): - assets = iroha.list_filter({"Atom": {"Id": {"Equals": asset_id}}}).assets() + assets = iroha.list_filter( + {"Atom": {"Id": {"Atom": {"Equals": asset_id}}}} + ).assets() with allure.step("THEN Iroha should return only assets with this id"): allure.attach( json.dumps(assets), diff --git a/pytests/iroha_cli_tests/test/domains/test_domains_query_filters.py b/pytests/iroha_cli_tests/test/domains/test_domains_query_filters.py index 4c365012825..19b5f7da07e 100644 --- a/pytests/iroha_cli_tests/test/domains/test_domains_query_filters.py +++ b/pytests/iroha_cli_tests/test/domains/test_domains_query_filters.py @@ -12,7 +12,7 @@ def condition(): f'WHEN iroha_cli query domains filtered by name "{domain_name}"' ): domains = iroha.list_filter( - {"Atom": {"Id": {"Equals": domain_name}}} + {"Atom": {"Id": {"Atom": {"Equals": domain_name}}}} ).domains() with allure.step( f'THEN Iroha should return only return domains with "{domain_name}" name' diff --git a/wasm/samples/query_assets_and_save_cursor/src/lib.rs b/wasm/samples/query_assets_and_save_cursor/src/lib.rs index e7261d9b9b4..576b46e9948 100644 --- a/wasm/samples/query_assets_and_save_cursor/src/lib.rs +++ b/wasm/samples/query_assets_and_save_cursor/src/lib.rs @@ -9,8 +9,8 @@ use dlmalloc::GlobalDlmalloc; use iroha_smart_contract::{ data_model::query::{ builder::QueryExecutor, + dsl::{CompoundPredicate, SelectorTuple}, parameters::{ForwardCursor, QueryParams}, - predicate::CompoundPredicate, QueryWithFilter, QueryWithParams, }, prelude::*, @@ -32,7 +32,12 @@ fn main(host: Iroha, context: Context) { let (_batch, _remaining_items, cursor) = host .start_query(QueryWithParams::new( - QueryWithFilter::new(FindAssets, CompoundPredicate::PASS).into(), + QueryWithFilter::new( + FindAssets, + CompoundPredicate::PASS, + SelectorTuple::default(), + ) + .into(), QueryParams::new( Default::default(), Default::default(), diff --git a/wasm/samples/smart_contract_can_filter_queries/src/lib.rs b/wasm/samples/smart_contract_can_filter_queries/src/lib.rs index 798f719629c..68b670256e1 100644 --- a/wasm/samples/smart_contract_can_filter_queries/src/lib.rs +++ b/wasm/samples/smart_contract_can_filter_queries/src/lib.rs @@ -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_id.eq(domain_id)) + .filter_with(|asset_definition| asset_definition.id.domain.eq(domain_id.clone())) .execute() .dbg_unwrap(); @@ -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::>(); + + 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::>(); + + assert_eq!(asset_definition_ids, expected_asset_definition_ids); }