Skip to content
Snippets Groups Projects
Commit d61b7c43 authored by Pascal Engélibert's avatar Pascal Engélibert :bicyclist:
Browse files

[feat] gva: blocks

parent f3ff5567
No related branches found
No related tags found
No related merge requests found
......@@ -15,6 +15,26 @@
use crate::*;
#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd)]
pub struct BlockCursor {
pub number: BlockNumber,
}
impl std::fmt::Display for BlockCursor {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.number)
}
}
impl FromStr for BlockCursor {
type Err = WrongCursor;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self {
number: s.parse().map_err(|_| WrongCursor)?,
})
}
}
impl DbsReader {
pub fn block(
&self,
......@@ -23,6 +43,100 @@ impl DbsReader {
) -> KvResult<Option<duniter_dbs::BlockMetaV2>> {
bc_db.blocks_meta().get(&number)
}
pub fn find_blocks(
&self,
bc_db: &BcV2DbRo<FileBackend>,
page_info: PageInfo<BlockCursor>,
) -> KvResult<PagedData<Vec<(BlockCursor, duniter_dbs::BlockMetaV2)>>> {
let block_count = bc_db.blocks_meta().count()? as u32;
let first_cursor_opt = if page_info.not_all() {
Some(BlockCursor {
number: BlockNumber(0),
})
} else {
None
};
let last_cursor_opt = if page_info.not_all() {
Some(BlockCursor {
number: BlockNumber(block_count - 1),
})
} else {
None
};
let k_min = U32BE(if page_info.order {
page_info.pos.map_or(0, |pos| pos.number.0)
} else {
page_info.limit_opt.map_or(0, |limit| {
page_info
.pos
.map_or(block_count, |pos| pos.number.0)
.checked_sub(limit as u32)
.unwrap_or(0)
.checked_add(1)
.unwrap_or(u32::MAX)
})
});
let k_max = U32BE(if page_info.order {
page_info.limit_opt.map_or(block_count, |limit| {
page_info.pos.map_or(limit as u32, |pos| {
pos.number.0.checked_add(limit as u32).unwrap_or(u32::MAX)
})
})
} else {
page_info.pos.map_or(block_count, |pos| {
pos.number.0.checked_add(1).unwrap_or(u32::MAX)
})
});
let filter = |block: Result<(U32BE, BlockMetaV2), KvError>| {
block
.and_then(|o| {
Ok((
BlockCursor {
number: BlockNumber(o.0 .0),
},
o.1,
))
})
.ok()
};
let blocks: Vec<(BlockCursor, duniter_dbs::BlockMetaV2)> = if page_info.order {
bc_db
.blocks_meta()
.iter(k_min..k_max, |it| it.filter_map(filter))
.collect()
} else {
bc_db
.blocks_meta()
.iter_rev(k_min..k_max, |it| it.filter_map(filter))
.collect()
};
Ok(PagedData {
has_next_page: has_next_page(
blocks
.iter()
.map(|(block_cursor, _block)| block_cursor.into()),
last_cursor_opt,
page_info,
page_info.order,
),
has_previous_page: has_previous_page(
blocks
.iter()
.map(|(block_cursor, _block)| block_cursor.into()),
first_cursor_opt,
page_info,
page_info.order,
),
data: blocks,
})
}
}
#[cfg(test)]
......@@ -49,4 +163,60 @@ mod tests {
Ok(())
}
#[test]
fn test_find_blocks() -> KvResult<()> {
let bc_db = duniter_dbs::databases::bc_v2::BcV2Db::<Mem>::open(MemConf::default())?;
let gva_db = duniter_gva_db::GvaV1Db::<Mem>::open(MemConf::default())?;
let bc_db_ro = bc_db.get_ro_handler();
let db_reader = create_dbs_reader(unsafe { std::mem::transmute(&gva_db.get_ro_handler()) });
for i in 0..20 {
bc_db.blocks_meta_write().upsert(
U32BE(i),
duniter_dbs::BlockMetaV2 {
number: i,
..Default::default()
},
)?;
}
let blocks = db_reader.find_blocks(
&bc_db_ro,
PageInfo {
pos: Some(BlockCursor {
number: BlockNumber(10),
}),
order: true,
limit_opt: Some(3),
},
)?;
assert_eq!(blocks.data.len(), 3);
assert_eq!(blocks.data[0].1.number, 10);
assert_eq!(blocks.data[1].1.number, 11);
assert_eq!(blocks.data[2].1.number, 12);
assert!(blocks.has_previous_page);
assert!(blocks.has_next_page);
let blocks = db_reader.find_blocks(
&bc_db_ro,
PageInfo {
pos: Some(BlockCursor {
number: BlockNumber(10),
}),
order: false,
limit_opt: Some(3),
},
)?;
assert_eq!(blocks.data.len(), 3);
assert_eq!(blocks.data[0].1.number, 10);
assert_eq!(blocks.data[1].1.number, 9);
assert_eq!(blocks.data[2].1.number, 8);
assert!(blocks.has_previous_page);
assert!(blocks.has_next_page);
Ok(())
}
}
......@@ -94,6 +94,7 @@ mod tests {
page_info: PageInfo<BlockNumber>,
) -> KvResult<PagedData<duniter_gva_dbs_reader::uds_of_pubkey::UdsWithSum>>;
fn block(&self, bc_db: &BcV2DbRo<FileBackend>, number: U32BE) -> KvResult<Option<BlockMetaV2>>;
fn find_blocks(&self, bc_db: &BcV2DbRo<FileBackend>, page_info: PageInfo<duniter_gva_dbs_reader::block::BlockCursor>) -> KvResult<PagedData<Vec<(duniter_gva_dbs_reader::block::BlockCursor, BlockMetaV2)>>>;
fn find_inputs<BcDb: 'static + BcV2DbReadable, TxsMpDb: 'static + TxsMpV2DbReadable>(
&self,
bc_db: &BcDb,
......
......@@ -14,6 +14,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::*;
use duniter_gva_dbs_reader::PagedData;
#[derive(Default)]
pub(crate) struct BlockQuery;
......@@ -35,14 +36,44 @@ impl BlockQuery {
Ok(block.map(|block| BlockMeta::from(&block)))
}
/// Get blocks by number
async fn blocks(
&self,
ctx: &async_graphql::Context<'_>,
#[graphql(desc = "pagination", default)] pagination: Pagination,
) -> async_graphql::Result<Connection<String, BlockMeta, EmptyFields, EmptyFields>> {
let page_info = Pagination::convert_to_page_info(pagination)?;
let data = ctx.data::<GvaSchemaData>()?;
let dbs_reader = data.dbs_reader();
let PagedData {
data: blocks,
has_next_page,
has_previous_page,
} = data
.dbs_pool
.execute(move |dbs| dbs_reader.find_blocks(&dbs.bc_db_ro, page_info))
.await??;
let mut conn = Connection::new(has_previous_page, has_next_page);
conn.append(blocks.into_iter().map(|(block_cursor, block)| {
Edge::new(block_cursor.to_string(), BlockMeta::from(block))
}));
Ok(conn)
}
}
#[cfg(test)]
mod tests {
use super::BlockNumber;
use crate::tests::*;
#[tokio::test]
async fn test_block() -> anyhow::Result<()> {
async fn test_block_by_number() -> anyhow::Result<()> {
let mut dbs_reader = MockDbsReader::new();
dbs_reader
.expect_block()
......@@ -62,4 +93,47 @@ mod tests {
);
Ok(())
}
#[tokio::test]
async fn test_blocks() -> anyhow::Result<()> {
let mut dbs_reader = MockDbsReader::new();
dbs_reader.expect_find_blocks().times(1).returning(|_, _| {
Ok(duniter_gva_dbs_reader::PagedData {
data: vec![(
duniter_gva_dbs_reader::block::BlockCursor {
number: BlockNumber(0),
},
duniter_dbs::BlockMetaV2::default(),
)],
has_next_page: false,
has_previous_page: false,
})
});
let schema = create_schema(dbs_reader)?;
assert_eq!(
exec_graphql_request(
&schema,
r#"{ blocks{pageInfo{startCursor,endCursor},edges{node{number}}} }"#
)
.await?,
serde_json::json!({
"data": {
"blocks": {
"edges": [
{
"node": {
"number": duniter_dbs::BlockMetaV2::default().number,
}
}
],
"pageInfo": {
"endCursor": duniter_dbs::BlockMetaV2::default().number.to_string(),
"startCursor": duniter_dbs::BlockMetaV2::default().number.to_string(),
}
}
}
})
);
Ok(())
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment