Skip to content

Commit

Permalink
[feature] hyperledger-iroha#2553: Add sorting to asset queries
Browse files Browse the repository at this point in the history
Signed-off-by: Vladimir Pesterev <pesterev@pm.me>
  • Loading branch information
pesterev committed Aug 9, 2022
1 parent 33ab59a commit d17c82e
Show file tree
Hide file tree
Showing 10 changed files with 264 additions and 35 deletions.
31 changes: 27 additions & 4 deletions cli/src/torii/routing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,15 +119,37 @@ pub(crate) async fn handle_queries(
wsv: Arc<WorldStateView>,
query_judge: QueryJudgeArc,
pagination: Pagination,
sorting: Sorting,
request: VerifiedQueryRequest,
) -> Result<Scale<VersionedPaginatedQueryResult>> {
let (valid_request, filter) = request.validate(&wsv, query_judge.as_ref())?;
let original_result = valid_request.execute(&wsv)?;
let result = filter.filter(original_result);
let (total, result) = if let Value::Vec(value) = result {
let (total, result) = if let Value::Vec(mut vec) = result {
if let Some(ref key) = sorting.sort_by_metadata_key {
let f = |value1: &Value| {
if let Value::U128(num) = value1 {
*num
} else {
0
}
};

vec.sort_by_key(|value0| match value0 {
Value::Identifiable(IdentifiableBox::Asset(asset)) => match asset.value() {
AssetValue::Store(store) => store.get(key).map_or(0, f),
_ => 0,
},
Value::Identifiable(v) => TryInto::<&dyn HasMetadata>::try_into(v)
.map(|has_metadata| has_metadata.metadata().get(key).map_or(0, f))
.unwrap_or(0),
_ => 0,
});
}

(
value.len(),
Value::Vec(value.into_iter().paginate(pagination).collect()),
vec.len(),
Value::Vec(vec.into_iter().paginate(pagination).collect()),
)
} else {
(1, result)
Expand Down Expand Up @@ -482,11 +504,12 @@ impl Torii {
))
.and(body::versioned()),
)
.or(endpoint4(
.or(endpoint5(
handle_queries,
warp::path(uri::QUERY)
.and(add_state!(self.wsv, self.query_judge))
.and(paginate())
.and(sorting())
.and(body::query()),
))
.or(endpoint2(
Expand Down
3 changes: 2 additions & 1 deletion cli/src/torii/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,11 +108,12 @@ async fn torii_pagination() {
.try_into()
.expect("Failed to verify");

let pagination = Pagination { start, limit };
let pagination = Pagination::new(start, limit);
handle_queries(
Arc::clone(&torii.wsv),
Arc::clone(&torii.query_judge),
pagination,
Sorting::default(),
query,
)
.map(|result| {
Expand Down
2 changes: 1 addition & 1 deletion cli/src/torii/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,4 @@ impl<O: Reply, E: Reply> Reply for WarpResult<O, E> {
}
}

generate_endpoints!(2, 3, 4);
generate_endpoints!(2, 3, 4, 5);
61 changes: 55 additions & 6 deletions client/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,7 @@ impl Client {
&self,
request: R,
pagination: Pagination,
sorting: Sorting,
filter: PredicateBox,
) -> Result<(B, QueryResponseHandler<R>)>
where
Expand All @@ -619,6 +620,7 @@ impl Client {
B: RequestBuilder,
{
let pagination: Vec<_> = pagination.into();
let sorting: Vec<_> = sorting.into();
let request = QueryRequest::new(request.into(), self.account_id.clone(), filter);
let request: VersionedSignedQueryRequest = self.sign_query(request)?.into();

Expand All @@ -628,33 +630,80 @@ impl Client {
format!("{}/{}", &self.torii_url, uri::QUERY),
)
.params(pagination)
.params(sorting)
.headers(self.headers.clone())
.body(request.encode_versioned()),
QueryResponseHandler::default(),
))
}

/// Create a request with pagination and add the filter.
/// Create a request with pagination, sorting and add the filter.
///
/// # Errors
/// Forwards from [`Self::prepare_query_request`].
pub fn request_with_pagination_and_filter<R>(
/// Fails if sending request fails
pub fn request_with_pagination_and_filter_and_sorting<R>(
&self,
request: R,
pagination: Pagination,
sorting: Sorting,
filter: PredicateBox,
) -> QueryHandlerResult<ClientQueryOutput<R>>
where
R: Query + Into<QueryBox> + Debug,
<R::Output as TryFrom<Value>>::Error: Into<eyre::Error>, // Seems redundant
{
iroha_logger::trace!(?request, %pagination, ?filter);
let (req, resp_handler) =
self.prepare_query_request::<R, DefaultRequestBuilder>(request, pagination, filter)?;
iroha_logger::trace!(?request, %pagination, ?sorting, ?filter);
let (req, resp_handler) = self.prepare_query_request::<R, DefaultRequestBuilder>(
request, pagination, sorting, filter,
)?;
let response = req.build()?.send()?;
resp_handler.handle(response)
}

/// Create a request with pagination and sorting.
///
/// # Errors
/// Fails if sending request fails
pub fn request_with_pagination_and_sorting<R>(
&self,
request: R,
pagination: Pagination,
sorting: Sorting,
) -> QueryHandlerResult<ClientQueryOutput<R>>
where
R: Query + Into<QueryBox> + Debug,
<R::Output as TryFrom<Value>>::Error: Into<eyre::Error>,
{
self.request_with_pagination_and_filter_and_sorting(
request,
pagination,
sorting,
PredicateBox::default(),
)
}

/// Create a request with pagination, sorting, and the given filter.
///
/// # Errors
/// Fails if sending request fails
pub fn request_with_pagination_and_filter<R>(
&self,
request: R,
pagination: Pagination,
filter: PredicateBox,
) -> QueryHandlerResult<ClientQueryOutput<R>>
where
R: Query + Into<QueryBox> + Debug,
<R::Output as TryFrom<Value>>::Error: Into<eyre::Error>, // Seems redundant
{
self.request_with_pagination_and_filter_and_sorting(
request,
pagination,
Sorting::default(),
filter,
)
}

/// Query API entry point. Requests queries from `Iroha` peers with pagination.
///
/// Uses default blocking http-client. If you need some custom integration, look at
Expand Down
100 changes: 100 additions & 0 deletions client/tests/integration/asset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,3 +235,103 @@ fn client_add_asset_with_name_length_more_than_limit_should_not_commit_transacti

Ok(())
}

#[test]
fn correct_pagination_assets_after_creating_new_one() -> Result<()> {
let (_rt, _peer, test_client) = <PeerBuilder>::new().start_with_runtime();

let sort_by_metadata_key = Name::from_str("sort")?;

let account_id = AccountId::from_str("alice@wonderland")?;

let mut assets = vec![];
let mut instructions: Vec<Instruction> = vec![];

for i in 0..10 {
let asset_definition_id = AssetDefinitionId::from_str(&format!("xor{}#wonderland", i))?;
let asset_definition = AssetDefinition::store(asset_definition_id.clone());
let mut asset_metadata = Metadata::new();
asset_metadata.insert_with_limits(
sort_by_metadata_key.clone(),
Value::U128(i),
MetadataLimits::new(10, 22),
)?;
let asset = Asset::new(
AssetId::new(asset_definition_id, account_id.clone()),
AssetValue::Store(asset_metadata),
);

assets.push(asset.clone());

let create_asset_definition = RegisterBox::new(asset_definition);
let create_asset = RegisterBox::new(asset);

instructions.push(create_asset_definition.into());
instructions.push(create_asset.into());
}

test_client.submit_all_blocking(instructions)?;

let sorting = Sorting::by_metadata_key(sort_by_metadata_key.clone());

let res = test_client
.request_with_pagination_and_sorting(
client::asset::by_account_id(account_id.clone()),
Pagination::new(Some(1), Some(5)),
sorting.clone(),
)
.expect("Valid");

assert_eq!(
res.output
.iter()
.map(|asset| asset.id().definition_id.name.clone())
.collect::<Vec<_>>(),
assets
.iter()
.take(5)
.map(|asset| asset.id().definition_id.name.clone())
.collect::<Vec<_>>()
);

let new_asset_definition_id = AssetDefinitionId::from_str("xor10#wonderland")?;
let new_asset_definition = AssetDefinition::store(new_asset_definition_id.clone());
let mut new_asset_metadata = Metadata::new();
new_asset_metadata.insert_with_limits(
sort_by_metadata_key,
Value::U128(10),
MetadataLimits::new(10, 22),
)?;
let new_asset = Asset::new(
AssetId::new(new_asset_definition_id, account_id.clone()),
AssetValue::Store(new_asset_metadata),
);

let create_asset_definition = RegisterBox::new(new_asset_definition);
let create_asset = RegisterBox::new(new_asset.clone());

test_client.submit_all_blocking(vec![create_asset_definition.into(), create_asset.into()])?;

let res = test_client.request_with_pagination_and_sorting(
client::asset::by_account_id(account_id),
Pagination::new(Some(6), None),
sorting,
)?;

let mut right = assets.into_iter().skip(5).take(5).collect::<Vec<_>>();

right.push(new_asset);

assert_eq!(
res.output
.into_iter()
.map(|asset| asset.id().definition_id.name.clone())
.collect::<Vec<_>>(),
right
.into_iter()
.map(|asset| asset.id().definition_id.name.clone())
.collect::<Vec<_>>()
);

Ok(())
}
8 changes: 1 addition & 7 deletions client/tests/integration/pagination.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,7 @@ fn client_add_asset_quantity_to_existing_asset_should_increase_asset_amount() {
//When

let vec = iroha_client
.request_with_pagination(
asset::all_definitions(),
Pagination {
start: Some(5),
limit: Some(5),
},
)
.request_with_pagination(asset::all_definitions(), Pagination::new(Some(5), Some(5)))
.expect("Failed to get assets")
.only_output();
assert_eq!(vec.len(), 5);
Expand Down
5 changes: 1 addition & 4 deletions client/tests/integration/tx_history.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,7 @@ fn client_has_rejected_and_acepted_txs_should_return_tx_history() {
let transactions = iroha_client
.request_with_pagination(
transaction::by_account_id(account_id.clone()),
Pagination {
start: Some(1),
limit: Some(50),
},
Pagination::new(Some(1), Some(50)),
)
.expect("Failed to get transaction history")
.only_output();
Expand Down
35 changes: 24 additions & 11 deletions data_model/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ pub mod permissions;
pub mod predicate;
pub mod query;
pub mod role;
pub mod sorting;
pub mod transaction;
pub mod trigger;

Expand Down Expand Up @@ -397,6 +398,24 @@ impl IdentifiableBox {
}
}

impl<'live> TryFrom<&'live IdentifiableBox> for &'live dyn HasMetadata {
type Error = ();

fn try_from(
v: &'live IdentifiableBox,
) -> Result<&'live (dyn HasMetadata + 'live), Self::Error> {
match v {
IdentifiableBox::NewDomain(v) => Ok(v.as_ref()),
IdentifiableBox::NewAccount(v) => Ok(v.as_ref()),
IdentifiableBox::NewAssetDefinition(v) => Ok(v.as_ref()),
IdentifiableBox::Domain(v) => Ok(v.as_ref()),
IdentifiableBox::Account(v) => Ok(v.as_ref()),
IdentifiableBox::AssetDefinition(v) => Ok(v.as_ref()),
_ => Err(()),
}
}
}

/// Boxed [`Value`].
pub type ValueBox = Box<Value>;

Expand Down Expand Up @@ -996,17 +1015,11 @@ pub mod prelude {
#[cfg(feature = "mutable_api")]
pub use super::Registrable;
pub use super::{
account::prelude::*,
asset::prelude::*,
block_value::prelude::*,
domain::prelude::*,
name::prelude::*,
pagination::{prelude::*, Pagination},
peer::prelude::*,
role::prelude::*,
trigger::prelude::*,
EnumTryAsError, HasMetadata, IdBox, Identifiable, IdentifiableBox, Parameter,
PredicateTrait, RegistrableBox, TryAsMut, TryAsRef, ValidationError, Value,
account::prelude::*, asset::prelude::*, block_value::prelude::*, domain::prelude::*,
name::prelude::*, pagination::prelude::*, peer::prelude::*, role::prelude::*,
sorting::prelude::*, trigger::prelude::*, EnumTryAsError, HasMetadata, IdBox, Identifiable,
IdentifiableBox, Parameter, PredicateTrait, RegistrableBox, TryAsMut, TryAsRef,
ValidationError, Value,
};
pub use crate::{
events::prelude::*, expression::prelude::*, isi::prelude::*, metadata::prelude::*,
Expand Down
2 changes: 1 addition & 1 deletion data_model/src/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -990,7 +990,7 @@ pub mod asset {
impl FindAllAssets {
/// Construct [`FindAllAssets`].
pub const fn new() -> Self {
FindAllAssets
Self
}
}

Expand Down
Loading

0 comments on commit d17c82e

Please sign in to comment.