From 27067ef01b7790f7a7467430ec3ed8f1c4679865 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Wed, 21 Aug 2024 11:04:05 +0700 Subject: [PATCH 1/2] feat: Path Query Bincode Serialization --- grovedb/src/query/mod.rs | 269 +++++++++++++++++++++++- merk/Cargo.toml | 1 + merk/src/proofs/query/mod.rs | 113 +++++++++- merk/src/proofs/query/query_item/mod.rs | 170 +++++++++++++++ 4 files changed, 549 insertions(+), 4 deletions(-) diff --git a/grovedb/src/query/mod.rs b/grovedb/src/query/mod.rs index 7f896cca..a1443836 100644 --- a/grovedb/src/query/mod.rs +++ b/grovedb/src/query/mod.rs @@ -6,6 +6,7 @@ use std::{ fmt, }; +use bincode::{Decode, Encode}; #[cfg(any(feature = "full", feature = "verify"))] use grovedb_merk::proofs::query::query_item::QueryItem; use grovedb_merk::proofs::query::{Key, SubqueryBranch}; @@ -21,14 +22,13 @@ use crate::query_result_type::PathKey; use crate::Error; #[cfg(any(feature = "full", feature = "verify"))] -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Encode, Decode)] /// Path query /// /// Represents a path to a specific GroveDB tree and a corresponding query to /// apply to the given tree. pub struct PathQuery { /// Path - // TODO: Make generic over path type pub path: Vec>, /// Query pub query: SizedQuery, @@ -49,7 +49,7 @@ impl fmt::Display for PathQuery { } #[cfg(any(feature = "full", feature = "verify"))] -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Encode, Decode)] /// Holds a query to apply to a tree and an optional limit/offset value. /// Limit and offset values affect the size of the result set. pub struct SizedQuery { @@ -585,6 +585,7 @@ impl<'a> SinglePathSubquery<'a> { mod tests { use std::{borrow::Cow, ops::RangeFull}; + use bincode::{config::standard, decode_from_slice, encode_to_vec}; use grovedb_merk::proofs::{ query::{query_item::QueryItem, SubqueryBranch}, Query, @@ -1753,4 +1754,266 @@ mod tests { assert_eq!(query.max_depth(), None); } + + #[test] + fn test_simple_path_query_serialization() { + let path_query = PathQuery { + path: vec![b"root".to_vec(), b"subtree".to_vec()], + query: SizedQuery { + query: Query { + items: vec![QueryItem::Key(b"key1".to_vec())], + default_subquery_branch: SubqueryBranch::default(), + conditional_subquery_branches: None, + left_to_right: true, + }, + limit: None, + offset: None, + }, + }; + + let encoded = encode_to_vec(&path_query, standard()).unwrap(); + let decoded: PathQuery = decode_from_slice(&encoded, standard()).unwrap().0; + + assert_eq!(path_query, decoded); + } + + #[test] + fn test_range_query_serialization() { + let path_query = PathQuery { + path: vec![b"root".to_vec()], + query: SizedQuery { + query: Query { + items: vec![QueryItem::Range(b"a".to_vec()..b"z".to_vec())], + default_subquery_branch: SubqueryBranch::default(), + conditional_subquery_branches: None, + left_to_right: false, + }, + limit: Some(10), + offset: Some(2), + }, + }; + + let encoded = encode_to_vec(&path_query, standard()).unwrap(); + let decoded: PathQuery = decode_from_slice(&encoded, standard()).unwrap().0; + + assert_eq!(path_query, decoded); + } + + #[test] + fn test_range_inclusive_query_serialization() { + let path_query = PathQuery { + path: vec![b"root".to_vec()], + query: SizedQuery { + query: Query { + items: vec![QueryItem::RangeInclusive(b"a".to_vec()..=b"z".to_vec())], + default_subquery_branch: SubqueryBranch::default(), + conditional_subquery_branches: None, + left_to_right: true, + }, + limit: Some(5), + offset: None, + }, + }; + + let encoded = encode_to_vec(&path_query, standard()).unwrap(); + let decoded: PathQuery = decode_from_slice(&encoded, standard()).unwrap().0; + + assert_eq!(path_query, decoded); + } + + #[test] + fn test_conditional_subquery_serialization() { + let mut conditional_branches = IndexMap::new(); + conditional_branches.insert( + QueryItem::Key(b"key1".to_vec()), + SubqueryBranch { + subquery_path: Some(vec![b"conditional_path".to_vec()]), + subquery: Some(Box::new(Query::default())), + }, + ); + + let path_query = PathQuery { + path: vec![b"root".to_vec()], + query: SizedQuery { + query: Query { + items: vec![QueryItem::Key(b"key1".to_vec())], + default_subquery_branch: SubqueryBranch::default(), + conditional_subquery_branches: Some(conditional_branches), + left_to_right: true, + }, + limit: None, + offset: None, + }, + }; + + let encoded = encode_to_vec(&path_query, standard()).unwrap(); + let decoded: PathQuery = decode_from_slice(&encoded, standard()).unwrap().0; + + assert_eq!(path_query, decoded); + } + + #[test] + fn test_empty_path_query_serialization() { + let path_query = PathQuery { + path: vec![], + query: SizedQuery { + query: Query::default(), + limit: None, + offset: None, + }, + }; + + let encoded = encode_to_vec(&path_query, standard()).unwrap(); + let decoded: PathQuery = decode_from_slice(&encoded, standard()).unwrap().0; + + assert_eq!(path_query, decoded); + } + + #[test] + fn test_path_query_with_multiple_keys() { + let path_query = PathQuery { + path: vec![b"root".to_vec()], + query: SizedQuery { + query: Query { + items: vec![ + QueryItem::Key(b"key1".to_vec()), + QueryItem::Key(b"key2".to_vec()), + QueryItem::Key(b"key3".to_vec()), + ], + default_subquery_branch: SubqueryBranch::default(), + conditional_subquery_branches: None, + left_to_right: true, + }, + limit: None, + offset: None, + }, + }; + + let encoded = encode_to_vec(&path_query, standard()).unwrap(); + let decoded: PathQuery = decode_from_slice(&encoded, standard()).unwrap().0; + + assert_eq!(path_query, decoded); + } + + #[test] + fn test_path_query_with_full_range() { + let path_query = PathQuery { + path: vec![b"root".to_vec()], + query: SizedQuery { + query: Query { + items: vec![QueryItem::RangeFull(RangeFull)], + default_subquery_branch: SubqueryBranch::default(), + conditional_subquery_branches: None, + left_to_right: false, + }, + limit: Some(100), + offset: Some(10), + }, + }; + + let encoded = encode_to_vec(&path_query, standard()).unwrap(); + let decoded: PathQuery = decode_from_slice(&encoded, standard()).unwrap().0; + + assert_eq!(path_query, decoded); + } + + #[test] + fn test_path_query_with_complex_conditions() { + let mut conditional_branches = IndexMap::new(); + conditional_branches.insert( + QueryItem::Key(b"key1".to_vec()), + SubqueryBranch { + subquery_path: Some(vec![b"conditional_path1".to_vec()]), + subquery: Some(Box::new(Query { + items: vec![QueryItem::Range(b"a".to_vec()..b"m".to_vec())], + default_subquery_branch: SubqueryBranch::default(), + conditional_subquery_branches: None, + left_to_right: true, + })), + }, + ); + conditional_branches.insert( + QueryItem::Range(b"n".to_vec()..b"z".to_vec()), + SubqueryBranch { + subquery_path: Some(vec![b"conditional_path2".to_vec()]), + subquery: Some(Box::new(Query { + items: vec![QueryItem::Key(b"key2".to_vec())], + default_subquery_branch: SubqueryBranch::default(), + conditional_subquery_branches: None, + left_to_right: false, + })), + }, + ); + + let path_query = PathQuery { + path: vec![b"root".to_vec()], + query: SizedQuery { + query: Query { + items: vec![QueryItem::Key(b"key3".to_vec())], + default_subquery_branch: SubqueryBranch::default(), + conditional_subquery_branches: Some(conditional_branches), + left_to_right: true, + }, + limit: Some(50), + offset: Some(5), + }, + }; + + let encoded = encode_to_vec(&path_query, standard()).unwrap(); + let decoded: PathQuery = decode_from_slice(&encoded, standard()).unwrap().0; + + assert_eq!(path_query, decoded); + } + + #[test] + fn test_path_query_with_subquery_path() { + let path_query = PathQuery { + path: vec![b"root".to_vec()], + query: SizedQuery { + query: Query { + items: vec![QueryItem::Key(b"key1".to_vec())], + default_subquery_branch: SubqueryBranch { + subquery_path: Some(vec![b"subtree_path".to_vec()]), + subquery: Some(Box::new(Query { + items: vec![QueryItem::Key(b"key2".to_vec())], + default_subquery_branch: SubqueryBranch::default(), + conditional_subquery_branches: None, + left_to_right: true, + })), + }, + conditional_subquery_branches: None, + left_to_right: true, + }, + limit: None, + offset: None, + }, + }; + + let encoded = encode_to_vec(&path_query, standard()).unwrap(); + let decoded: PathQuery = decode_from_slice(&encoded, standard()).unwrap().0; + + assert_eq!(path_query, decoded); + } + + #[test] + fn test_path_query_with_empty_query_items() { + let path_query = PathQuery { + path: vec![b"root".to_vec()], + query: SizedQuery { + query: Query { + items: vec![], // No items in the query + default_subquery_branch: SubqueryBranch::default(), + conditional_subquery_branches: None, + left_to_right: true, + }, + limit: Some(20), + offset: None, + }, + }; + + let encoded = encode_to_vec(&path_query, standard()).unwrap(); + let decoded: PathQuery = decode_from_slice(&encoded, standard()).unwrap().0; + + assert_eq!(path_query, decoded); + } } diff --git a/merk/Cargo.toml b/merk/Cargo.toml index 6fcfadd2..689d57e4 100644 --- a/merk/Cargo.toml +++ b/merk/Cargo.toml @@ -12,6 +12,7 @@ documentation = "https://docs.rs/grovedb-merk" [dependencies] thiserror = "1.0.58" +bincode = { version = "2.0.0-rc.3" } grovedb-storage = { version = "1.0.0", path = "../storage", optional = true } failure = "0.1.8" integer-encoding = "4.0.0" diff --git a/merk/src/proofs/query/mod.rs b/merk/src/proofs/query/mod.rs index 8bbb07e9..c8ffa8ea 100644 --- a/merk/src/proofs/query/mod.rs +++ b/merk/src/proofs/query/mod.rs @@ -18,6 +18,11 @@ mod verify; use std::cmp::Ordering; use std::{collections::HashSet, fmt, ops::RangeFull}; +use bincode::{ + enc::{write::Writer, Encoder}, + error::{DecodeError, EncodeError}, + BorrowDecode, Decode, Encode, +}; #[cfg(feature = "full")] use grovedb_costs::{cost_return_on_error, CostContext, CostResult, CostsExt, OperationCost}; use grovedb_version::version::GroveVersion; @@ -61,7 +66,7 @@ pub type Key = Vec; pub type PathKey = (Path, Key); #[cfg(any(feature = "full", feature = "verify"))] -#[derive(Debug, Default, Clone, PartialEq)] +#[derive(Debug, Default, Clone, PartialEq, Encode, Decode)] /// Subquery branch pub struct SubqueryBranch { /// Subquery path @@ -115,6 +120,112 @@ pub struct Query { pub left_to_right: bool, } +#[cfg(any(feature = "full", feature = "verify"))] +impl Encode for Query { + fn encode(&self, encoder: &mut E) -> Result<(), EncodeError> { + // Encode the items vector + self.items.encode(encoder)?; + + // Encode the default subquery branch + self.default_subquery_branch.encode(encoder)?; + + // Encode the conditional subquery branches + match &self.conditional_subquery_branches { + Some(conditional_subquery_branches) => { + encoder.writer().write(&[1])?; // Write a flag indicating presence of data + // Encode the length of the map + (conditional_subquery_branches.len() as u64).encode(encoder)?; + // Encode each key-value pair in the IndexMap + for (key, value) in conditional_subquery_branches { + key.encode(encoder)?; + value.encode(encoder)?; + } + } + None => { + encoder.writer().write(&[0])?; // Write a flag indicating + // absence of data + } + } + + // Encode the left_to_right boolean + self.left_to_right.encode(encoder)?; + + Ok(()) + } +} + +#[cfg(any(feature = "full", feature = "verify"))] +impl Decode for Query { + fn decode(decoder: &mut D) -> Result { + // Decode the items vector + let items = Vec::::decode(decoder)?; + + // Decode the default subquery branch + let default_subquery_branch = SubqueryBranch::decode(decoder)?; + + // Decode the conditional subquery branches + let conditional_subquery_branches = if u8::decode(decoder)? == 1 { + let len = u64::decode(decoder)? as usize; + let mut map = IndexMap::with_capacity(len); + for _ in 0..len { + let key = QueryItem::decode(decoder)?; + let value = SubqueryBranch::decode(decoder)?; + map.insert(key, value); + } + Some(map) + } else { + None + }; + + // Decode the left_to_right boolean + let left_to_right = bool::decode(decoder)?; + + Ok(Query { + items, + default_subquery_branch, + conditional_subquery_branches, + left_to_right, + }) + } +} + +#[cfg(any(feature = "full", feature = "verify"))] +impl<'de> BorrowDecode<'de> for Query { + fn borrow_decode>( + decoder: &mut D, + ) -> Result { + // Borrow-decode the items vector + let items = Vec::::borrow_decode(decoder)?; + + // Borrow-decode the default subquery branch + let default_subquery_branch = SubqueryBranch::borrow_decode(decoder)?; + + // Borrow-decode the conditional subquery branches + let conditional_subquery_branches = if u8::borrow_decode(decoder)? == 1 { + let len = u64::borrow_decode(decoder)? as usize; + let mut map = IndexMap::with_capacity(len); + for _ in 0..len { + let key = QueryItem::borrow_decode(decoder)?; + let value = SubqueryBranch::borrow_decode(decoder)?; + map.insert(key, value); + } + Some(map) + } else { + None + }; + + // Borrow-decode the left_to_right boolean + let left_to_right = bool::borrow_decode(decoder)?; + + Ok(Query { + items, + default_subquery_branch, + conditional_subquery_branches, + left_to_right, + }) + } +} + #[cfg(any(feature = "full", feature = "verify"))] impl fmt::Display for SubqueryBranch { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/merk/src/proofs/query/query_item/mod.rs b/merk/src/proofs/query/query_item/mod.rs index 7c81a27e..2209f583 100644 --- a/merk/src/proofs/query/query_item/mod.rs +++ b/merk/src/proofs/query/query_item/mod.rs @@ -10,6 +10,7 @@ use std::{ ops::{Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive}, }; +use bincode::{enc::write::Writer, error::DecodeError, BorrowDecode, Decode, Encode}; #[cfg(feature = "full")] use grovedb_costs::{CostContext, CostsExt, OperationCost}; #[cfg(feature = "full")] @@ -35,6 +36,175 @@ pub enum QueryItem { RangeAfterToInclusive(RangeInclusive>), } +#[cfg(any(feature = "full", feature = "verify"))] +impl Encode for QueryItem { + fn encode( + &self, + encoder: &mut E, + ) -> Result<(), bincode::error::EncodeError> { + match self { + QueryItem::Key(key) => { + encoder.writer().write(&[0])?; + key.encode(encoder) + } + QueryItem::Range(range) => { + encoder.writer().write(&[1])?; + range.start.encode(encoder)?; + range.end.encode(encoder) + } + QueryItem::RangeInclusive(range) => { + encoder.writer().write(&[2])?; + range.start().encode(encoder)?; + range.end().encode(encoder) + } + QueryItem::RangeFull(_) => { + encoder.writer().write(&[3])?; + Ok(()) + } + QueryItem::RangeFrom(range) => { + encoder.writer().write(&[4])?; + range.start.encode(encoder) + } + QueryItem::RangeTo(range) => { + encoder.writer().write(&[5])?; + range.end.encode(encoder) + } + QueryItem::RangeToInclusive(range) => { + encoder.writer().write(&[6])?; + range.end.encode(encoder) + } + QueryItem::RangeAfter(range) => { + encoder.writer().write(&[7])?; + range.start.encode(encoder) + } + QueryItem::RangeAfterTo(range) => { + encoder.writer().write(&[8])?; + range.start.encode(encoder)?; + range.end.encode(encoder) + } + QueryItem::RangeAfterToInclusive(range) => { + encoder.writer().write(&[9])?; + range.start().encode(encoder)?; + range.end().encode(encoder) + } + } + } +} + +#[cfg(any(feature = "full", feature = "verify"))] +impl Decode for QueryItem { + fn decode(decoder: &mut D) -> Result { + let variant_id = u8::decode(decoder)?; + + match variant_id { + 0 => { + let key = Vec::::decode(decoder)?; + Ok(QueryItem::Key(key)) + } + 1 => { + let start = Vec::::decode(decoder)?; + let end = Vec::::decode(decoder)?; + Ok(QueryItem::Range(start..end)) + } + 2 => { + let start = Vec::::decode(decoder)?; + let end = Vec::::decode(decoder)?; + Ok(QueryItem::RangeInclusive(start..=end)) + } + 3 => Ok(QueryItem::RangeFull(RangeFull)), + 4 => { + let start = Vec::::decode(decoder)?; + Ok(QueryItem::RangeFrom(start..)) + } + 5 => { + let end = Vec::::decode(decoder)?; + Ok(QueryItem::RangeTo(..end)) + } + 6 => { + let end = Vec::::decode(decoder)?; + Ok(QueryItem::RangeToInclusive(..=end)) + } + 7 => { + let start = Vec::::decode(decoder)?; + Ok(QueryItem::RangeAfter(start..)) + } + 8 => { + let start = Vec::::decode(decoder)?; + let end = Vec::::decode(decoder)?; + Ok(QueryItem::RangeAfterTo(start..end)) + } + 9 => { + let start = Vec::::decode(decoder)?; + let end = Vec::::decode(decoder)?; + Ok(QueryItem::RangeAfterToInclusive(start..=end)) + } + _ => Err(DecodeError::UnexpectedVariant { + type_name: "QueryItem", + allowed: &bincode::error::AllowedEnumVariants::Range { min: 0, max: 9 }, + found: variant_id as u32, + }), + } + } +} + +#[cfg(any(feature = "full", feature = "verify"))] +impl<'de> BorrowDecode<'de> for QueryItem { + fn borrow_decode>( + decoder: &mut D, + ) -> Result { + let variant_id = u8::decode(decoder)?; + + match variant_id { + 0 => { + let key = Vec::::borrow_decode(decoder)?; + Ok(QueryItem::Key(key)) + } + 1 => { + let start = Vec::::borrow_decode(decoder)?; + let end = Vec::::borrow_decode(decoder)?; + Ok(QueryItem::Range(start..end)) + } + 2 => { + let start = Vec::::borrow_decode(decoder)?; + let end = Vec::::borrow_decode(decoder)?; + Ok(QueryItem::RangeInclusive(start..=end)) + } + 3 => Ok(QueryItem::RangeFull(RangeFull)), + 4 => { + let start = Vec::::borrow_decode(decoder)?; + Ok(QueryItem::RangeFrom(start..)) + } + 5 => { + let end = Vec::::borrow_decode(decoder)?; + Ok(QueryItem::RangeTo(..end)) + } + 6 => { + let end = Vec::::borrow_decode(decoder)?; + Ok(QueryItem::RangeToInclusive(..=end)) + } + 7 => { + let start = Vec::::borrow_decode(decoder)?; + Ok(QueryItem::RangeAfter(start..)) + } + 8 => { + let start = Vec::::borrow_decode(decoder)?; + let end = Vec::::borrow_decode(decoder)?; + Ok(QueryItem::RangeAfterTo(start..end)) + } + 9 => { + let start = Vec::::borrow_decode(decoder)?; + let end = Vec::::borrow_decode(decoder)?; + Ok(QueryItem::RangeAfterToInclusive(start..=end)) + } + _ => Err(DecodeError::UnexpectedVariant { + type_name: "QueryItem", + allowed: &bincode::error::AllowedEnumVariants::Range { min: 0, max: 9 }, + found: variant_id as u32, + }), + } + } +} + #[cfg(any(feature = "full", feature = "verify"))] impl fmt::Display for QueryItem { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { From 24175c99a8816995451d288712acb50756b7bdac Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Wed, 21 Aug 2024 11:23:24 +0700 Subject: [PATCH 2/2] removed unused import --- merk/src/proofs/query/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/merk/src/proofs/query/mod.rs b/merk/src/proofs/query/mod.rs index c8ffa8ea..6c543950 100644 --- a/merk/src/proofs/query/mod.rs +++ b/merk/src/proofs/query/mod.rs @@ -18,8 +18,9 @@ mod verify; use std::cmp::Ordering; use std::{collections::HashSet, fmt, ops::RangeFull}; +#[cfg(any(feature = "full", feature = "verify"))] use bincode::{ - enc::{write::Writer, Encoder}, + enc::write::Writer, error::{DecodeError, EncodeError}, BorrowDecode, Decode, Encode, };