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