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 3, 2022
1 parent b8fc0c6 commit cb6b398
Show file tree
Hide file tree
Showing 8 changed files with 249 additions and 10 deletions.
22 changes: 20 additions & 2 deletions cli/src/torii/routing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,12 +119,29 @@ 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 value) = result {
if let Some(ref key) = sorting.sort_by_key {
value.sort_by_key(|value0| match value0 {
Value::Identifiable(IdentifiableBox::Asset(asset)) => match asset.value() {
AssetValue::Store(metadata) => metadata.get(key).map_or(0, |value1| {
if let Value::U128(x) = value1 {
*x
} else {
0
}
}),
_ => 0,
},
_ => 0,
});
}

(
value.len(),
Value::Vec(value.into_iter().paginate(pagination).collect()),
Expand Down Expand Up @@ -482,11 +499,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
2 changes: 2 additions & 0 deletions cli/src/torii/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,12 @@ async fn torii_pagination() {
.expect("Failed to verify");

let pagination = Pagination { start, limit };
let sorting = Sorting::default();
handle_queries(
Arc::clone(&torii.wsv),
Arc::clone(&torii.query_judge),
pagination,
sorting,
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 and add the 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::new(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(),
)?;

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<_>>()
);

println!();

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(())
}
2 changes: 2 additions & 0 deletions data_model/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,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 @@ -977,6 +978,7 @@ pub mod prelude {
pagination::{prelude::*, Pagination},
peer::prelude::*,
role::prelude::*,
sorting::prelude::*,
trigger::prelude::*,
EnumTryAsError, HasMetadata, IdBox, Identifiable, IdentifiableBox, Parameter,
PredicateTrait, RegistrableBox, TryAsMut, TryAsRef, ValidationError, Value,
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
68 changes: 68 additions & 0 deletions data_model/src/sorting.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
//! Structures and traits related to sorting.

#[cfg(not(feature = "std"))]
use alloc::{
borrow::ToOwned as _,
collections::btree_map,
format,
string::{String, ToString as _},
vec,
vec::Vec,
};
#[cfg(feature = "std")]
use std::collections::btree_map;

use serde::{Deserialize, Serialize};
#[cfg(feature = "warp")]
use warp::{Filter, Rejection};

use crate::prelude::*;

const SORT_BY_KEY: &str = "sort_by_key";

/// Structure for sorting requests
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Sorting {
/// [`Name`] of the key in [`Asset`]'s metadata used to order query result.
pub sort_by_key: Option<Name>,
}

impl Sorting {
///
pub fn new(key: Name) -> Self {
Self {
sort_by_key: Some(key),
}
}
}

impl From<Sorting> for btree_map::BTreeMap<String, String> {
fn from(sorting: Sorting) -> Self {
let mut btree = Self::new();
if let Some(key) = sorting.sort_by_key {
btree.insert(String::from(SORT_BY_KEY), key.to_string());
}
btree
}
}

impl From<Sorting> for Vec<(&'static str, String)> {
fn from(sorting: Sorting) -> Self {
let mut vec = Vec::new();
if let Some(key) = sorting.sort_by_key {
vec.push((SORT_BY_KEY, key.to_string()));
}
vec
}
}

#[cfg(feature = "warp")]
/// Filter for warp which extracts sorting
pub fn sorting() -> impl Filter<Extract = (Sorting,), Error = Rejection> + Copy {
warp::query()
}

pub mod prelude {
//! Prelude: re-export most commonly used traits, structs and macros from this module.
pub use super::*;
}

0 comments on commit cb6b398

Please sign in to comment.