1use crate::drive::credit_pools::epochs;
7use crate::drive::identity::IdentityRootStructure;
8use crate::drive::{credit_pools, tokens, RootTree};
9use crate::util::batch::grovedb_op_batch::KnownPath::{
10 TokenBalancesRoot, TokenContractInfoRoot, TokenDirectSellPriceRoot, TokenDistributionRoot,
11 TokenIdentityInfoRoot, TokenPerpetualDistributionRoot, TokenPreProgrammedDistributionRoot,
12 TokenStatusRoot, TokenTimedDistributionRoot,
13};
14use crate::util::storage_flags::StorageFlags;
15use dpp::block::epoch::Epoch;
16use dpp::identity::{Purpose, SecurityLevel};
17use dpp::prelude::Identifier;
18use grovedb::batch::key_info::KeyInfo;
19use grovedb::batch::{
20 GroveDbOpConsistencyResults, GroveOp, KeyInfoPath, QualifiedGroveDbOp,
21 SubelementsDeletionBehavior,
22};
23use grovedb::operations::proof::util::hex_to_ascii;
24use grovedb::{Element, TreeType};
25use std::borrow::Cow;
26use std::fmt;
27
28#[derive(Debug, Default, Clone)]
31pub struct GroveDbOpBatch {
32 pub(crate) operations: Vec<QualifiedGroveDbOp>,
34}
35
36#[derive(Debug, PartialEq, Eq, Copy, Clone)]
37enum KnownPath {
38 Root, DataContractAndDocumentsRoot, DataContractStorage, DocumentsRoot, IdentitiesRoot, IdentityTreeRevisionRoot, IdentityTreeNonceRoot, IdentityTreeKeysRoot, IdentityTreeKeyReferencesRoot, IdentityTreeKeyReferencesInPurpose(Purpose), IdentityTreeKeyReferencesInSecurityLevel(Purpose, SecurityLevel), IdentityTreeNegativeCreditRoot, IdentityContractInfoRoot, UniquePublicKeyHashesToIdentitiesRoot, NonUniquePublicKeyKeyHashesToIdentitiesRoot, PoolsRoot, PoolsInsideEpoch(Epoch), PreFundedSpecializedBalancesRoot, SavedBlockTransactionsRoot, SpentAssetLockTransactionsRoot, MiscRoot, WithdrawalTransactionsRoot, BalancesRoot, TokenRoot, TokenBalancesRoot, TokenDistributionRoot, TokenDirectSellPriceRoot, TokenTimedDistributionRoot, TokenPreProgrammedDistributionRoot, TokenPerpetualDistributionRoot, TokenIdentityInfoRoot, TokenContractInfoRoot, TokenStatusRoot, VersionsRoot, VotesRoot, GroupActionsRoot, SingleUseKeyBalancesRoot, }
76
77impl From<RootTree> for KnownPath {
78 fn from(value: RootTree) -> Self {
79 match value {
80 RootTree::DataContractDocuments => KnownPath::DataContractAndDocumentsRoot,
81 RootTree::Identities => KnownPath::IdentitiesRoot,
82 RootTree::UniquePublicKeyHashesToIdentities => {
83 KnownPath::UniquePublicKeyHashesToIdentitiesRoot
84 }
85 RootTree::NonUniquePublicKeyKeyHashesToIdentities => {
86 KnownPath::NonUniquePublicKeyKeyHashesToIdentitiesRoot
87 }
88 RootTree::Pools => KnownPath::PoolsRoot,
89 RootTree::PreFundedSpecializedBalances => KnownPath::PreFundedSpecializedBalancesRoot,
90 RootTree::SavedBlockTransactions => KnownPath::SavedBlockTransactionsRoot,
91 RootTree::SpentAssetLockTransactions => KnownPath::SpentAssetLockTransactionsRoot,
92 RootTree::Misc => KnownPath::MiscRoot,
93 RootTree::WithdrawalTransactions => KnownPath::WithdrawalTransactionsRoot,
94 RootTree::Balances => KnownPath::BalancesRoot,
95 RootTree::Tokens => KnownPath::TokenRoot,
96 RootTree::Versions => KnownPath::VersionsRoot,
97 RootTree::Votes => KnownPath::VotesRoot,
98 RootTree::GroupActions => KnownPath::GroupActionsRoot,
99 RootTree::AddressBalances => KnownPath::SingleUseKeyBalancesRoot,
100 }
101 }
102}
103
104impl From<IdentityRootStructure> for KnownPath {
105 fn from(value: IdentityRootStructure) -> Self {
106 match value {
107 IdentityRootStructure::IdentityTreeRevision => KnownPath::IdentityTreeRevisionRoot,
108 IdentityRootStructure::IdentityTreeNonce => KnownPath::IdentityTreeNonceRoot,
109 IdentityRootStructure::IdentityTreeKeys => KnownPath::IdentityTreeKeysRoot,
110 IdentityRootStructure::IdentityTreeKeyReferences => {
111 KnownPath::IdentityTreeKeyReferencesRoot
112 }
113 IdentityRootStructure::IdentityTreeNegativeCredit => {
114 KnownPath::IdentityTreeNegativeCreditRoot
115 }
116 IdentityRootStructure::IdentityContractInfo => KnownPath::IdentityContractInfoRoot,
117 }
118 }
119}
120
121fn readable_key_info(known_path: KnownPath, key_info: &KeyInfo) -> (String, Option<KnownPath>) {
122 match key_info {
123 KeyInfo::KnownKey(key) => {
124 match known_path {
125 KnownPath::Root => {
126 if let Ok(root_tree) = RootTree::try_from(key[0]) {
127 (
128 format!("{}({})", root_tree, key[0]),
129 Some(root_tree.into()),
130 )
131 } else {
132 (hex_to_ascii(key), None)
133 }
134 }
135 KnownPath::BalancesRoot | KnownPath::IdentitiesRoot if key.len() == 32 => (
136 format!(
137 "IdentityId(bs58::{})",
138 Identifier::from_vec(key.clone()).unwrap()
139 ),
140 None,
141 ),
142 KnownPath::DataContractAndDocumentsRoot if key.len() == 32 => (
143 format!(
144 "ContractId(bs58::{})",
145 Identifier::from_vec(key.clone()).unwrap()
146 ),
147 None,
148 ),
149 KnownPath::DataContractAndDocumentsRoot if key.len() == 1 => match key[0] {
150 0 => (
151 "DataContractStorage(0)".to_string(),
152 Some(KnownPath::DataContractStorage),
153 ),
154 1 => (
155 "DataContractDocuments(1)".to_string(),
156 Some(KnownPath::DocumentsRoot),
157 ),
158 _ => (hex_to_ascii(key), None),
159 },
160 KnownPath::IdentitiesRoot if key.len() == 1 => {
161 if let Ok(root_tree) = IdentityRootStructure::try_from(key[0]) {
162 (
163 format!("{}({})", root_tree, key[0]),
164 Some(root_tree.into()),
165 )
166 } else {
167 (hex_to_ascii(key), None)
168 }
169 }
170 KnownPath::IdentityTreeKeyReferencesRoot if key.len() == 1 => {
171 if let Ok(purpose) = Purpose::try_from(key[0]) {
172 (
173 format!("Purpose::{}({})", purpose, key[0]),
174 Some(KnownPath::IdentityTreeKeyReferencesInPurpose(purpose)),
175 )
176 } else {
177 (hex_to_ascii(key), None)
178 }
179 }
180 KnownPath::IdentityTreeKeyReferencesInPurpose(purpose) if key.len() == 1 => {
181 if let Ok(security_level) = SecurityLevel::try_from(key[0]) {
182 (
183 format!("SecurityLevel::{}({})", security_level, key[0]),
184 Some(KnownPath::IdentityTreeKeyReferencesInSecurityLevel(
185 purpose,
186 security_level,
187 )),
188 )
189 } else {
190 (hex_to_ascii(key), None)
191 }
192 }
193
194 KnownPath::PoolsRoot if key.len() == 1 => match key[0] {
195 epochs::epochs_root_tree_key_constants::KEY_STORAGE_FEE_POOL_U8 => {
196 ("StorageFeePool(ascii:'s')".to_string(), None)
197 }
198 epochs::epochs_root_tree_key_constants::KEY_UNPAID_EPOCH_INDEX_U8 => {
199 ("UnpaidEpochIndex(ascii:'u')".to_string(), None)
200 }
201 epochs::epochs_root_tree_key_constants::KEY_PENDING_EPOCH_REFUNDS_U8 => {
202 ("PendingEpochRefunds(ascii:'p')".to_string(), None)
203 }
204 _ => (hex_to_ascii(key), None),
205 },
206 KnownPath::PoolsRoot if key.len() == 2 => {
207 if let Ok(epoch) = Epoch::try_from(key) {
209 (
210 format!("Epoch::{}({})", epoch.index, hex::encode(key)),
211 Some(KnownPath::PoolsInsideEpoch(epoch)),
212 )
213 } else {
214 (hex_to_ascii(key), None)
215 }
216 }
217 KnownPath::PoolsInsideEpoch(_) if key.len() == 1 => {
218 match key[0] {
220 credit_pools::epochs::epoch_key_constants::KEY_POOL_PROCESSING_FEES_U8 => {
221 ("PoolProcessingFees(ascii:'p')".to_string(), None)
222 }
223 credit_pools::epochs::epoch_key_constants::KEY_POOL_STORAGE_FEES_U8 => {
224 ("PoolStorageFees(ascii:'s')".to_string(), None)
225 }
226 credit_pools::epochs::epoch_key_constants::KEY_START_TIME_U8 => {
227 ("StartTime(ascii:'t')".to_string(), None)
228 }
229 credit_pools::epochs::epoch_key_constants::KEY_PROTOCOL_VERSION_U8 => {
230 ("ProtocolVersion(ascii:'v')".to_string(), None)
231 }
232 credit_pools::epochs::epoch_key_constants::KEY_START_BLOCK_HEIGHT_U8 => {
233 ("StartBlockHeight(ascii:'h')".to_string(), None)
234 }
235 credit_pools::epochs::epoch_key_constants::KEY_START_BLOCK_CORE_HEIGHT_U8 => {
236 ("StartBlockCoreHeight(ascii:'c')".to_string(), None)
237 }
238 credit_pools::epochs::epoch_key_constants::KEY_PROPOSERS_U8 => {
239 ("Proposers(ascii:'m')".to_string(), None)
240 }
241 credit_pools::epochs::epoch_key_constants::KEY_FEE_MULTIPLIER_U8 => {
242 ("FeeMultiplier(ascii:'x')".to_string(), None)
243 }
244 _ => (hex_to_ascii(key), None),
245 }
246 }
247 KnownPath::TokenRoot if key.len() == 1 => match key[0] {
248 tokens::paths::TOKEN_DISTRIBUTIONS_KEY => {
249 (format!("Distribution({})", tokens::paths::TOKEN_DISTRIBUTIONS_KEY), Some(TokenDistributionRoot))
250 }
251 tokens::paths::TOKEN_DIRECT_SELL_PRICE_KEY => {
252 (format!("SellPrice({})", tokens::paths::TOKEN_DIRECT_SELL_PRICE_KEY), Some(TokenDirectSellPriceRoot))
253 }
254 tokens::paths::TOKEN_BALANCES_KEY => {
255 (format!("Balances({})", tokens::paths::TOKEN_BALANCES_KEY), Some(TokenBalancesRoot))
256 }
257 tokens::paths::TOKEN_IDENTITY_INFO_KEY => {
258 (format!("IdentityInfo({})", tokens::paths::TOKEN_IDENTITY_INFO_KEY), Some(TokenIdentityInfoRoot))
259 }
260 tokens::paths::TOKEN_CONTRACT_INFO_KEY => {
261 (format!("ContractInfo({})", tokens::paths::TOKEN_CONTRACT_INFO_KEY), Some(TokenContractInfoRoot))
262 }
263 tokens::paths::TOKEN_STATUS_INFO_KEY => {
264 (format!("Status({})", tokens::paths::TOKEN_STATUS_INFO_KEY), Some(TokenStatusRoot))
265 }
266 _ => (hex_to_ascii(key), None),
267 },
268 KnownPath::TokenDistributionRoot if key.len() == 1 => match key[0] {
269 tokens::paths::TOKEN_TIMED_DISTRIBUTIONS_KEY => {
270 (format!("TimedDistribution({})", tokens::paths::TOKEN_TIMED_DISTRIBUTIONS_KEY), Some(TokenTimedDistributionRoot))
271 }
272 tokens::paths::TOKEN_PERPETUAL_DISTRIBUTIONS_KEY => {
273 (format!("PerpetualDistribution({})", tokens::paths::TOKEN_PERPETUAL_DISTRIBUTIONS_KEY), Some(TokenPerpetualDistributionRoot))
274 }
275 tokens::paths::TOKEN_PRE_PROGRAMMED_DISTRIBUTIONS_KEY => {
276 (format!("PreProgrammedDistribution({})", tokens::paths::TOKEN_PRE_PROGRAMMED_DISTRIBUTIONS_KEY), Some(TokenPreProgrammedDistributionRoot))
277 }
278 _ => (hex_to_ascii(key), None),
279 },
280 KnownPath::TokenTimedDistributionRoot if key.len() == 1 => match key[0] {
281 tokens::paths::TOKEN_MS_TIMED_DISTRIBUTIONS_KEY => {
282 (format!("MillisecondTimedDistribution({})", tokens::paths::TOKEN_MS_TIMED_DISTRIBUTIONS_KEY), None)
283 }
284 tokens::paths::TOKEN_BLOCK_TIMED_DISTRIBUTIONS_KEY => {
285 (format!("BlockTimedDistribution({})", tokens::paths::TOKEN_BLOCK_TIMED_DISTRIBUTIONS_KEY), None)
286 }
287 tokens::paths::TOKEN_EPOCH_TIMED_DISTRIBUTIONS_KEY => {
288 (format!("EpochTimedDistribution({})", tokens::paths::TOKEN_EPOCH_TIMED_DISTRIBUTIONS_KEY), None)
289 }
290 _ => (hex_to_ascii(key), None),
291 },
292 KnownPath::TokenPerpetualDistributionRoot if key.len() == 1 => match key[0] {
293 tokens::paths::TOKEN_PERPETUAL_DISTRIBUTIONS_INFO_KEY => {
294 (format!("PerpetualDistributionInfo({})", tokens::paths::TOKEN_PERPETUAL_DISTRIBUTIONS_INFO_KEY), None)
295 }
296 tokens::paths::TOKEN_PERPETUAL_DISTRIBUTIONS_FOR_IDENTITIES_LAST_CLAIM_KEY => {
297 (format!("PerpetualDistributionLastClaim({})", tokens::paths::TOKEN_PERPETUAL_DISTRIBUTIONS_FOR_IDENTITIES_LAST_CLAIM_KEY), None)
298 }
299 _ => (hex_to_ascii(key), None),
300 },
301 _ => (hex_to_ascii(key), None),
302 }
303 }
304 KeyInfo::MaxKeySize {
305 unique_id,
306 max_size,
307 } => (
308 format!(
309 "MaxKeySize(unique_id: {:?}, max_size: {})",
310 unique_id, max_size
311 ),
312 None,
313 ),
314 }
315}
316
317fn readable_path(path: &KeyInfoPath) -> (String, KnownPath) {
318 let mut known_path = KnownPath::Root;
319 let string = path
320 .0
321 .iter()
322 .map(|key_info| {
323 let (string, new_known_path) = readable_key_info(known_path, key_info);
324 if let Some(new_known_path) = new_known_path {
325 known_path = new_known_path;
326 }
327 string
328 })
329 .collect::<Vec<_>>()
330 .join("/");
331 (string, known_path)
332}
333
334impl fmt::Display for GroveDbOpBatch {
335 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
336 for op in &self.operations {
337 let (path_string, known_path) = readable_path(&op.path);
338 let (key_string, _) = if let Some(ref key) = op.key {
339 readable_key_info(known_path, key)
340 } else {
341 ("(none)".to_string(), None)
342 };
343 writeln!(f, "{{")?;
344 writeln!(f, " Path: {}", path_string)?;
345 writeln!(f, " Key: {}", key_string)?;
346 match &op.op {
347 GroveOp::InsertOrReplace { element }
348 | GroveOp::InsertWithKnownToNotAlreadyExist { element }
349 | GroveOp::InsertIfNotExists { element, .. } => {
350 let flags = element.get_flags();
351 let flag_info = match flags {
352 None => "No Flags".to_string(),
353 Some(flags) => format!("Flags are 0x{}", hex::encode(flags)),
354 };
355 match element {
356 Element::Item(data, _) => {
357 let num = match data.len() {
358 8 => format!(
359 " u64({})",
360 u64::from_be_bytes(data.clone().try_into().unwrap())
361 ),
362 4 => format!(
363 " u32({})",
364 u32::from_be_bytes(data.clone().try_into().unwrap())
365 ),
366 _ => String::new(),
367 };
368 writeln!(
369 f,
370 " Operation: Insert Item with length: {}{} {}",
371 data.len(),
372 num,
373 flag_info
374 )?
375 }
376 Element::Tree(None, _) => {
377 writeln!(f, " Operation: Insert Empty Tree {}", flag_info)?
378 }
379 Element::SumTree(None, _, _) => {
380 writeln!(f, " Operation: Insert Empty Sum Tree {}", flag_info)?
381 }
382 _ => writeln!(f, " Operation: Insert {}", element)?,
383 }
384 }
385 _ => {
386 writeln!(f, " Operation: {:?}", op.op)?;
387 }
388 }
389 writeln!(f, "}}")?;
390 }
391 Ok(())
392 }
393}
394
395pub trait GroveDbOpBatchV0Methods {
397 fn new() -> Self;
399
400 fn len(&self) -> usize;
402
403 fn is_empty(&self) -> bool;
405
406 fn push(&mut self, op: QualifiedGroveDbOp);
408
409 fn append(&mut self, other: &mut Self);
411
412 fn extend<I: IntoIterator<Item = QualifiedGroveDbOp>>(&mut self, other_ops: I);
414
415 fn from_operations(operations: Vec<QualifiedGroveDbOp>) -> Self;
417
418 fn add_insert_empty_tree(&mut self, path: Vec<Vec<u8>>, key: Vec<u8>);
420
421 fn add_insert_empty_tree_with_flags(
423 &mut self,
424 path: Vec<Vec<u8>>,
425 key: Vec<u8>,
426 storage_flags: &Option<Cow<StorageFlags>>,
427 );
428
429 fn add_insert_empty_sum_tree(&mut self, path: Vec<Vec<u8>>, key: Vec<u8>);
431
432 fn add_insert_empty_sum_tree_with_flags(
434 &mut self,
435 path: Vec<Vec<u8>>,
436 key: Vec<u8>,
437 storage_flags: &Option<Cow<StorageFlags>>,
438 );
439
440 fn add_delete(&mut self, path: Vec<Vec<u8>>, key: Vec<u8>);
442
443 fn add_delete_tree(&mut self, path: Vec<Vec<u8>>, key: Vec<u8>, tree_type: TreeType);
445
446 fn add_insert(&mut self, path: Vec<Vec<u8>>, key: Vec<u8>, element: Element);
448
449 fn verify_consistency_of_operations(&self) -> GroveDbOpConsistencyResults;
451
452 fn contains<'c, P>(&self, path: P, key: &[u8]) -> Option<&GroveOp>
463 where
464 P: IntoIterator<Item = &'c [u8]>,
465 <P as IntoIterator>::IntoIter: ExactSizeIterator + DoubleEndedIterator + Clone;
466
467 fn remove<'c, P>(&mut self, path: P, key: &[u8]) -> Option<GroveOp>
478 where
479 P: IntoIterator<Item = &'c [u8]>,
480 <P as IntoIterator>::IntoIter: ExactSizeIterator + DoubleEndedIterator + Clone;
481
482 fn remove_if_insert(&mut self, path: Vec<Vec<u8>>, key: &[u8]) -> Option<GroveOp>;
495}
496
497impl GroveDbOpBatchV0Methods for GroveDbOpBatch {
498 fn new() -> Self {
500 GroveDbOpBatch {
501 operations: Vec::new(),
502 }
503 }
504
505 fn len(&self) -> usize {
507 self.operations.len()
508 }
509
510 fn is_empty(&self) -> bool {
512 self.operations.is_empty()
513 }
514
515 fn push(&mut self, op: QualifiedGroveDbOp) {
517 self.operations.push(op);
518 }
519
520 fn append(&mut self, other: &mut Self) {
522 self.operations.append(&mut other.operations);
523 }
524
525 fn extend<I: IntoIterator<Item = QualifiedGroveDbOp>>(&mut self, other_ops: I) {
527 self.operations.extend(other_ops);
528 }
529
530 fn from_operations(operations: Vec<QualifiedGroveDbOp>) -> Self {
532 GroveDbOpBatch { operations }
533 }
534
535 fn add_insert_empty_tree(&mut self, path: Vec<Vec<u8>>, key: Vec<u8>) {
537 self.operations
538 .push(QualifiedGroveDbOp::insert_or_replace_op(
539 path,
540 key,
541 Element::empty_tree(),
542 ))
543 }
544
545 fn add_insert_empty_tree_with_flags(
547 &mut self,
548 path: Vec<Vec<u8>>,
549 key: Vec<u8>,
550 storage_flags: &Option<Cow<StorageFlags>>,
551 ) {
552 self.operations
553 .push(QualifiedGroveDbOp::insert_or_replace_op(
554 path,
555 key,
556 Element::empty_tree_with_flags(
557 StorageFlags::map_borrowed_cow_to_some_element_flags(storage_flags),
558 ),
559 ))
560 }
561
562 fn add_insert_empty_sum_tree(&mut self, path: Vec<Vec<u8>>, key: Vec<u8>) {
564 self.operations
565 .push(QualifiedGroveDbOp::insert_or_replace_op(
566 path,
567 key,
568 Element::empty_sum_tree(),
569 ))
570 }
571
572 fn add_insert_empty_sum_tree_with_flags(
574 &mut self,
575 path: Vec<Vec<u8>>,
576 key: Vec<u8>,
577 storage_flags: &Option<Cow<StorageFlags>>,
578 ) {
579 self.operations
580 .push(QualifiedGroveDbOp::insert_or_replace_op(
581 path,
582 key,
583 Element::empty_sum_tree_with_flags(
584 StorageFlags::map_borrowed_cow_to_some_element_flags(storage_flags),
585 ),
586 ))
587 }
588
589 fn add_delete(&mut self, path: Vec<Vec<u8>>, key: Vec<u8>) {
591 self.operations
592 .push(QualifiedGroveDbOp::delete_op(path, key))
593 }
594
595 fn add_delete_tree(&mut self, path: Vec<Vec<u8>>, key: Vec<u8>, tree_type: TreeType) {
599 self.operations.push(QualifiedGroveDbOp::delete_tree_op(
600 path,
601 key,
602 tree_type,
603 SubelementsDeletionBehavior::DontCheckWithNoCleanup,
604 ))
605 }
606
607 fn add_insert(&mut self, path: Vec<Vec<u8>>, key: Vec<u8>, element: Element) {
609 self.operations
610 .push(QualifiedGroveDbOp::insert_or_replace_op(path, key, element))
611 }
612
613 fn verify_consistency_of_operations(&self) -> GroveDbOpConsistencyResults {
615 QualifiedGroveDbOp::verify_consistency_of_operations(&self.operations)
616 }
617
618 fn contains<'c, P>(&self, path: P, key: &[u8]) -> Option<&GroveOp>
629 where
630 P: IntoIterator<Item = &'c [u8]>,
631 <P as IntoIterator>::IntoIter: ExactSizeIterator + DoubleEndedIterator + Clone,
632 {
633 let path = KeyInfoPath(
634 path.into_iter()
635 .map(|item| KeyInfo::KnownKey(item.to_vec()))
636 .collect(),
637 );
638
639 self.operations.iter().find_map(|op| {
640 if op.path == path && op.key == Some(KeyInfo::KnownKey(key.to_vec())) {
641 Some(&op.op)
642 } else {
643 None
644 }
645 })
646 }
647
648 fn remove<'c, P>(&mut self, path: P, key: &[u8]) -> Option<GroveOp>
659 where
660 P: IntoIterator<Item = &'c [u8]>,
661 <P as IntoIterator>::IntoIter: ExactSizeIterator + DoubleEndedIterator + Clone,
662 {
663 let path = KeyInfoPath(
664 path.into_iter()
665 .map(|item| KeyInfo::KnownKey(item.to_vec()))
666 .collect(),
667 );
668
669 if let Some(index) = self
670 .operations
671 .iter()
672 .position(|op| op.path == path && op.key == Some(KeyInfo::KnownKey(key.to_vec())))
673 {
674 Some(self.operations.remove(index).op)
675 } else {
676 None
677 }
678 }
679
680 fn remove_if_insert(&mut self, path: Vec<Vec<u8>>, key: &[u8]) -> Option<GroveOp> {
693 let path = KeyInfoPath(
694 path.into_iter()
695 .map(|item| KeyInfo::KnownKey(item.to_vec()))
696 .collect(),
697 );
698
699 if let Some(index) = self
700 .operations
701 .iter()
702 .position(|op| op.path == path && op.key == Some(KeyInfo::KnownKey(key.to_vec())))
703 {
704 let op = &self.operations[index].op;
705 let op = if matches!(
706 op,
707 &GroveOp::InsertOrReplace { .. }
708 | &GroveOp::InsertWithKnownToNotAlreadyExist { .. }
709 | &GroveOp::InsertIfNotExists { .. }
710 | &GroveOp::Replace { .. }
711 | &GroveOp::Patch { .. }
712 ) {
713 self.operations.remove(index).op
714 } else {
715 op.clone()
716 };
717 Some(op)
718 } else {
719 None
720 }
721 }
722}
723
724impl IntoIterator for GroveDbOpBatch {
725 type Item = QualifiedGroveDbOp;
726 type IntoIter = std::vec::IntoIter<QualifiedGroveDbOp>;
727
728 fn into_iter(self) -> Self::IntoIter {
729 self.operations.into_iter()
730 }
731}