Skip to main content

drive/util/batch/grovedb_op_batch/
mod.rs

1//! GroveDB Operations Batch.
2//!
3//! This module defines the GroveDbOpBatch struct and implements its functions.
4//!
5
6use 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, TokenOncePerIdentityDistributionRoot, TokenPerpetualDistributionRoot,
12    TokenPreProgrammedDistributionRoot, 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/// A batch of GroveDB operations as a vector.
29// TODO move to GroveDB
30#[derive(Debug, Default, Clone)]
31pub struct GroveDbOpBatch {
32    /// Operations
33    pub(crate) operations: Vec<QualifiedGroveDbOp>,
34}
35
36#[derive(Debug, PartialEq, Eq, Copy, Clone)]
37enum KnownPath {
38    Root,                                                             //Level 0
39    DataContractAndDocumentsRoot,                                     //Level 1
40    DataContractStorage,                                              //Level 2
41    DocumentsRoot,                                                    //Level 2
42    IdentitiesRoot,                                                   //Level 1
43    IdentityTreeRevisionRoot,                                         //Level 2
44    IdentityTreeNonceRoot,                                            //Level 2
45    IdentityTreeKeysRoot,                                             //Level 2
46    IdentityTreeKeyReferencesRoot,                                    //Level 2
47    IdentityTreeKeyReferencesInPurpose(Purpose),                      //Level 3
48    IdentityTreeKeyReferencesInSecurityLevel(Purpose, SecurityLevel), //Level 4
49    IdentityTreeNegativeCreditRoot,                                   //Level 2
50    IdentityContractInfoRoot,                                         //Level 2
51    IdentityTreeKeyBudgetsRoot,                                       //Level 2
52    UniquePublicKeyHashesToIdentitiesRoot,                            //Level 1
53    NonUniquePublicKeyKeyHashesToIdentitiesRoot,                      //Level 1
54    PoolsRoot,                                                        //Level 1
55    PoolsInsideEpoch(Epoch),                                          //Level 2
56    PreFundedSpecializedBalancesRoot,                                 //Level 1
57    SavedBlockTransactionsRoot,                                       //Level 1
58    SpentAssetLockTransactionsRoot,                                   //Level 1
59    MiscRoot,                                                         //Level 1
60    WithdrawalTransactionsRoot,                                       //Level 1
61    BalancesRoot,                                                     //Level 1
62    TokenRoot,                                                        //Level 1
63    TokenBalancesRoot,                                                //Level 2
64    TokenDistributionRoot,                                            //Level 2
65    TokenDirectSellPriceRoot,                                         //Level 2
66    TokenTimedDistributionRoot,                                       //Level 3
67    TokenPreProgrammedDistributionRoot,                               //Level 3
68    TokenPerpetualDistributionRoot,                                   //Level 3
69    TokenOncePerIdentityDistributionRoot,                             //Level 3
70    TokenIdentityInfoRoot,                                            //Level 2
71    TokenContractInfoRoot,                                            //Level 2
72    TokenStatusRoot,                                                  //Level 2
73    VersionsRoot,                                                     //Level 1
74    VotesRoot,                                                        //Level 1
75    GroupActionsRoot,                                                 //Level 1
76    SingleUseKeyBalancesRoot,                                         //Level 1
77    ShieldedBalancesRoot,                                             //Level 1
78    ContractGroupsRoot,                                               //Level 1
79}
80
81impl From<RootTree> for KnownPath {
82    fn from(value: RootTree) -> Self {
83        match value {
84            RootTree::DataContractDocuments => KnownPath::DataContractAndDocumentsRoot,
85            RootTree::Identities => KnownPath::IdentitiesRoot,
86            RootTree::UniquePublicKeyHashesToIdentities => {
87                KnownPath::UniquePublicKeyHashesToIdentitiesRoot
88            }
89            RootTree::NonUniquePublicKeyKeyHashesToIdentities => {
90                KnownPath::NonUniquePublicKeyKeyHashesToIdentitiesRoot
91            }
92            RootTree::Pools => KnownPath::PoolsRoot,
93            RootTree::PreFundedSpecializedBalances => KnownPath::PreFundedSpecializedBalancesRoot,
94            RootTree::SavedBlockTransactions => KnownPath::SavedBlockTransactionsRoot,
95            RootTree::SpentAssetLockTransactions => KnownPath::SpentAssetLockTransactionsRoot,
96            RootTree::Misc => KnownPath::MiscRoot,
97            RootTree::WithdrawalTransactions => KnownPath::WithdrawalTransactionsRoot,
98            RootTree::Balances => KnownPath::BalancesRoot,
99            RootTree::Tokens => KnownPath::TokenRoot,
100            RootTree::Versions => KnownPath::VersionsRoot,
101            RootTree::Votes => KnownPath::VotesRoot,
102            RootTree::GroupActions => KnownPath::GroupActionsRoot,
103            RootTree::AddressBalances => KnownPath::SingleUseKeyBalancesRoot,
104            RootTree::ShieldedBalances => KnownPath::ShieldedBalancesRoot,
105            RootTree::ContractGroups => KnownPath::ContractGroupsRoot,
106        }
107    }
108}
109
110impl From<IdentityRootStructure> for KnownPath {
111    fn from(value: IdentityRootStructure) -> Self {
112        match value {
113            IdentityRootStructure::IdentityTreeRevision => KnownPath::IdentityTreeRevisionRoot,
114            IdentityRootStructure::IdentityTreeNonce => KnownPath::IdentityTreeNonceRoot,
115            IdentityRootStructure::IdentityTreeKeys => KnownPath::IdentityTreeKeysRoot,
116            IdentityRootStructure::IdentityTreeKeyReferences => {
117                KnownPath::IdentityTreeKeyReferencesRoot
118            }
119            IdentityRootStructure::IdentityTreeNegativeCredit => {
120                KnownPath::IdentityTreeNegativeCreditRoot
121            }
122            IdentityRootStructure::IdentityContractInfo => KnownPath::IdentityContractInfoRoot,
123            IdentityRootStructure::IdentityTreeKeyBudgets => KnownPath::IdentityTreeKeyBudgetsRoot,
124        }
125    }
126}
127
128fn readable_key_info(known_path: KnownPath, key_info: &KeyInfo) -> (String, Option<KnownPath>) {
129    match key_info {
130        KeyInfo::KnownKey(key) => {
131            match known_path {
132                KnownPath::Root => {
133                    if let Ok(root_tree) = RootTree::try_from(key[0]) {
134                        (
135                            format!("{}({})", root_tree, key[0]),
136                            Some(root_tree.into()),
137                        )
138                    } else {
139                        (hex_to_ascii(key), None)
140                    }
141                }
142                KnownPath::BalancesRoot | KnownPath::IdentitiesRoot if key.len() == 32 => (
143                    format!(
144                        "IdentityId(bs58::{})",
145                        Identifier::from_vec(key.clone()).unwrap()
146                    ),
147                    None,
148                ),
149                KnownPath::DataContractAndDocumentsRoot if key.len() == 32 => (
150                    format!(
151                        "ContractId(bs58::{})",
152                        Identifier::from_vec(key.clone()).unwrap()
153                    ),
154                    None,
155                ),
156                KnownPath::DataContractAndDocumentsRoot if key.len() == 1 => match key[0] {
157                    0 => (
158                        "DataContractStorage(0)".to_string(),
159                        Some(KnownPath::DataContractStorage),
160                    ),
161                    1 => (
162                        "DataContractDocuments(1)".to_string(),
163                        Some(KnownPath::DocumentsRoot),
164                    ),
165                    _ => (hex_to_ascii(key), None),
166                },
167                KnownPath::IdentitiesRoot if key.len() == 1 => {
168                    if let Ok(root_tree) = IdentityRootStructure::try_from(key[0]) {
169                        (
170                            format!("{}({})", root_tree, key[0]),
171                            Some(root_tree.into()),
172                        )
173                    } else {
174                        (hex_to_ascii(key), None)
175                    }
176                }
177                KnownPath::IdentityTreeKeyReferencesRoot if key.len() == 1 => {
178                    if let Ok(purpose) = Purpose::try_from(key[0]) {
179                        (
180                            format!("Purpose::{}({})", purpose, key[0]),
181                            Some(KnownPath::IdentityTreeKeyReferencesInPurpose(purpose)),
182                        )
183                    } else {
184                        (hex_to_ascii(key), None)
185                    }
186                }
187                KnownPath::IdentityTreeKeyReferencesInPurpose(purpose) if key.len() == 1 => {
188                    if let Ok(security_level) = SecurityLevel::try_from(key[0]) {
189                        (
190                            format!("SecurityLevel::{}({})", security_level, key[0]),
191                            Some(KnownPath::IdentityTreeKeyReferencesInSecurityLevel(
192                                purpose,
193                                security_level,
194                            )),
195                        )
196                    } else {
197                        (hex_to_ascii(key), None)
198                    }
199                }
200
201                KnownPath::PoolsRoot if key.len() == 1 => match key[0] {
202                    epochs::epochs_root_tree_key_constants::KEY_STORAGE_FEE_POOL_U8 => {
203                        ("StorageFeePool(ascii:'s')".to_string(), None)
204                    }
205                    epochs::epochs_root_tree_key_constants::KEY_UNPAID_EPOCH_INDEX_U8 => {
206                        ("UnpaidEpochIndex(ascii:'u')".to_string(), None)
207                    }
208                    epochs::epochs_root_tree_key_constants::KEY_PENDING_EPOCH_REFUNDS_U8 => {
209                        ("PendingEpochRefunds(ascii:'p')".to_string(), None)
210                    }
211                    _ => (hex_to_ascii(key), None),
212                },
213                KnownPath::PoolsRoot if key.len() == 2 => {
214                    // this is an epoch
215                    if let Ok(epoch) = Epoch::try_from(key) {
216                        (
217                            format!("Epoch::{}({})", epoch.index, hex::encode(key)),
218                            Some(KnownPath::PoolsInsideEpoch(epoch)),
219                        )
220                    } else {
221                        (hex_to_ascii(key), None)
222                    }
223                }
224                KnownPath::PoolsInsideEpoch(_) if key.len() == 1 => {
225                    // this is an epoch
226                    match key[0] {
227                        credit_pools::epochs::epoch_key_constants::KEY_POOL_PROCESSING_FEES_U8 => {
228                            ("PoolProcessingFees(ascii:'p')".to_string(), None)
229                        }
230                        credit_pools::epochs::epoch_key_constants::KEY_POOL_STORAGE_FEES_U8 => {
231                            ("PoolStorageFees(ascii:'s')".to_string(), None)
232                        }
233                        credit_pools::epochs::epoch_key_constants::KEY_START_TIME_U8 => {
234                            ("StartTime(ascii:'t')".to_string(), None)
235                        }
236                        credit_pools::epochs::epoch_key_constants::KEY_PROTOCOL_VERSION_U8 => {
237                            ("ProtocolVersion(ascii:'v')".to_string(), None)
238                        }
239                        credit_pools::epochs::epoch_key_constants::KEY_START_BLOCK_HEIGHT_U8 => {
240                            ("StartBlockHeight(ascii:'h')".to_string(), None)
241                        }
242                        credit_pools::epochs::epoch_key_constants::KEY_START_BLOCK_CORE_HEIGHT_U8 => {
243                            ("StartBlockCoreHeight(ascii:'c')".to_string(), None)
244                        }
245                        credit_pools::epochs::epoch_key_constants::KEY_PROPOSERS_U8 => {
246                            ("Proposers(ascii:'m')".to_string(), None)
247                        }
248                        credit_pools::epochs::epoch_key_constants::KEY_FEE_MULTIPLIER_U8 => {
249                            ("FeeMultiplier(ascii:'x')".to_string(), None)
250                        }
251                        _ => (hex_to_ascii(key), None),
252                    }
253                }
254                KnownPath::TokenRoot if key.len() == 1 => match key[0] {
255                    tokens::paths::TOKEN_DISTRIBUTIONS_KEY => {
256                            (format!("Distribution({})", tokens::paths::TOKEN_DISTRIBUTIONS_KEY), Some(TokenDistributionRoot))
257                    }
258                    tokens::paths::TOKEN_DIRECT_SELL_PRICE_KEY => {
259                        (format!("SellPrice({})", tokens::paths::TOKEN_DIRECT_SELL_PRICE_KEY), Some(TokenDirectSellPriceRoot))
260                    }
261                    tokens::paths::TOKEN_BALANCES_KEY => {
262                            (format!("Balances({})", tokens::paths::TOKEN_BALANCES_KEY), Some(TokenBalancesRoot))
263                    }
264                    tokens::paths::TOKEN_IDENTITY_INFO_KEY => {
265                            (format!("IdentityInfo({})", tokens::paths::TOKEN_IDENTITY_INFO_KEY), Some(TokenIdentityInfoRoot))
266                    }
267                    tokens::paths::TOKEN_CONTRACT_INFO_KEY => {
268                        (format!("ContractInfo({})", tokens::paths::TOKEN_CONTRACT_INFO_KEY), Some(TokenContractInfoRoot))
269                    }
270                    tokens::paths::TOKEN_STATUS_INFO_KEY => {
271                        (format!("Status({})", tokens::paths::TOKEN_STATUS_INFO_KEY), Some(TokenStatusRoot))
272                    }
273                    _ => (hex_to_ascii(key), None),
274                },
275                KnownPath::TokenDistributionRoot if key.len() == 1 => match key[0] {
276                    tokens::paths::TOKEN_TIMED_DISTRIBUTIONS_KEY => {
277                        (format!("TimedDistribution({})", tokens::paths::TOKEN_TIMED_DISTRIBUTIONS_KEY), Some(TokenTimedDistributionRoot))
278                    }
279                    tokens::paths::TOKEN_PERPETUAL_DISTRIBUTIONS_KEY => {
280                        (format!("PerpetualDistribution({})", tokens::paths::TOKEN_PERPETUAL_DISTRIBUTIONS_KEY), Some(TokenPerpetualDistributionRoot))
281                    }
282                    tokens::paths::TOKEN_PRE_PROGRAMMED_DISTRIBUTIONS_KEY => {
283                        (format!("PreProgrammedDistribution({})", tokens::paths::TOKEN_PRE_PROGRAMMED_DISTRIBUTIONS_KEY), Some(TokenPreProgrammedDistributionRoot))
284                    }
285                    tokens::paths::TOKEN_ONCE_PER_IDENTITY_DISTRIBUTIONS_KEY => {
286                        (format!("OncePerIdentityDistribution({})", tokens::paths::TOKEN_ONCE_PER_IDENTITY_DISTRIBUTIONS_KEY), Some(TokenOncePerIdentityDistributionRoot))
287                    }
288                    _ => (hex_to_ascii(key), None),
289                },
290                KnownPath::TokenTimedDistributionRoot if key.len() == 1 => match key[0] {
291                    tokens::paths::TOKEN_MS_TIMED_DISTRIBUTIONS_KEY => {
292                        (format!("MillisecondTimedDistribution({})", tokens::paths::TOKEN_MS_TIMED_DISTRIBUTIONS_KEY), None)
293                    }
294                    tokens::paths::TOKEN_BLOCK_TIMED_DISTRIBUTIONS_KEY => {
295                        (format!("BlockTimedDistribution({})", tokens::paths::TOKEN_BLOCK_TIMED_DISTRIBUTIONS_KEY), None)
296                    }
297                    tokens::paths::TOKEN_EPOCH_TIMED_DISTRIBUTIONS_KEY => {
298                        (format!("EpochTimedDistribution({})", tokens::paths::TOKEN_EPOCH_TIMED_DISTRIBUTIONS_KEY), None)
299                    }
300                    _ => (hex_to_ascii(key), None),
301                },
302                KnownPath::TokenPerpetualDistributionRoot if key.len() == 1 => match key[0] {
303                    tokens::paths::TOKEN_PERPETUAL_DISTRIBUTIONS_INFO_KEY => {
304                        (format!("PerpetualDistributionInfo({})", tokens::paths::TOKEN_PERPETUAL_DISTRIBUTIONS_INFO_KEY), None)
305                    }
306                    tokens::paths::TOKEN_PERPETUAL_DISTRIBUTIONS_FOR_IDENTITIES_LAST_CLAIM_KEY => {
307                        (format!("PerpetualDistributionLastClaim({})", tokens::paths::TOKEN_PERPETUAL_DISTRIBUTIONS_FOR_IDENTITIES_LAST_CLAIM_KEY), None)
308                    }
309                    _ => (hex_to_ascii(key), None),
310                },
311                _ => (hex_to_ascii(key), None),
312            }
313        }
314        KeyInfo::MaxKeySize {
315            unique_id,
316            max_size,
317        } => (
318            format!(
319                "MaxKeySize(unique_id: {:?}, max_size: {})",
320                unique_id, max_size
321            ),
322            None,
323        ),
324    }
325}
326
327fn readable_path(path: &KeyInfoPath) -> (String, KnownPath) {
328    let mut known_path = KnownPath::Root;
329    let string = path
330        .0
331        .iter()
332        .map(|key_info| {
333            let (string, new_known_path) = readable_key_info(known_path, key_info);
334            if let Some(new_known_path) = new_known_path {
335                known_path = new_known_path;
336            }
337            string
338        })
339        .collect::<Vec<_>>()
340        .join("/");
341    (string, known_path)
342}
343
344impl fmt::Display for GroveDbOpBatch {
345    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
346        for op in &self.operations {
347            let (path_string, known_path) = readable_path(&op.path);
348            let (key_string, _) = if let Some(ref key) = op.key {
349                readable_key_info(known_path, key)
350            } else {
351                ("(none)".to_string(), None)
352            };
353            writeln!(f, "{{")?;
354            writeln!(f, "   Path: {}", path_string)?;
355            writeln!(f, "   Key: {}", key_string)?;
356            match &op.op {
357                GroveOp::InsertOrReplace { element }
358                | GroveOp::InsertOrReplaceDontCheckForBackwardsReferences { element }
359                | GroveOp::InsertWithKnownToNotAlreadyExist { element }
360                | GroveOp::InsertIfNotExists { element, .. } => {
361                    let flags = element.get_flags();
362                    let flag_info = match flags {
363                        None => "No Flags".to_string(),
364                        Some(flags) => format!("Flags are 0x{}", hex::encode(flags)),
365                    };
366                    match element {
367                        Element::Item(data, _) => {
368                            let num = match data.len() {
369                                8 => format!(
370                                    " u64({})",
371                                    u64::from_be_bytes(data.clone().try_into().unwrap())
372                                ),
373                                4 => format!(
374                                    " u32({})",
375                                    u32::from_be_bytes(data.clone().try_into().unwrap())
376                                ),
377                                _ => String::new(),
378                            };
379                            writeln!(
380                                f,
381                                "   Operation: Insert Item with length: {}{} {}",
382                                data.len(),
383                                num,
384                                flag_info
385                            )?
386                        }
387                        Element::Tree(None, _) => {
388                            writeln!(f, "   Operation: Insert Empty Tree {}", flag_info)?
389                        }
390                        Element::SumTree(None, _, _) => {
391                            writeln!(f, "   Operation: Insert Empty Sum Tree {}", flag_info)?
392                        }
393                        _ => writeln!(f, "   Operation: Insert {}", element)?,
394                    }
395                }
396                _ => {
397                    writeln!(f, "   Operation: {:?}", op.op)?;
398                }
399            }
400            writeln!(f, "}}")?;
401        }
402        Ok(())
403    }
404}
405
406/// Trait defining a batch of GroveDB operations.
407pub trait GroveDbOpBatchV0Methods {
408    /// Creates a new empty batch of GroveDB operations.
409    fn new() -> Self;
410
411    /// Gets the number of operations from a list of GroveDB ops.
412    fn len(&self) -> usize;
413
414    /// Checks to see if the operation batch is empty.
415    fn is_empty(&self) -> bool;
416
417    /// Pushes an operation into a list of GroveDB ops.
418    fn push(&mut self, op: QualifiedGroveDbOp);
419
420    /// Appends operations into a list of GroveDB ops.
421    fn append(&mut self, other: &mut Self);
422
423    /// Extend operations into a list of GroveDB ops.
424    fn extend<I: IntoIterator<Item = QualifiedGroveDbOp>>(&mut self, other_ops: I);
425
426    /// Puts a list of GroveDB operations into a batch.
427    fn from_operations(operations: Vec<QualifiedGroveDbOp>) -> Self;
428
429    /// Adds an `Insert` operation with an empty tree at the specified path and key to a list of GroveDB ops.
430    fn add_insert_empty_tree(&mut self, path: Vec<Vec<u8>>, key: Vec<u8>);
431
432    /// Adds an `Insert` operation with an empty tree with storage flags to a list of GroveDB ops.
433    fn add_insert_empty_tree_with_flags(
434        &mut self,
435        path: Vec<Vec<u8>>,
436        key: Vec<u8>,
437        storage_flags: &Option<Cow<StorageFlags>>,
438    );
439
440    /// Adds an `Insert` operation with an empty sum tree at the specified path and key to a list of GroveDB ops.
441    fn add_insert_empty_sum_tree(&mut self, path: Vec<Vec<u8>>, key: Vec<u8>);
442
443    /// Adds an `Insert` operation with an empty sum tree with storage flags to a list of GroveDB ops.
444    fn add_insert_empty_sum_tree_with_flags(
445        &mut self,
446        path: Vec<Vec<u8>>,
447        key: Vec<u8>,
448        storage_flags: &Option<Cow<StorageFlags>>,
449    );
450
451    /// Adds a `Delete` operation to a list of GroveDB ops.
452    fn add_delete(&mut self, path: Vec<Vec<u8>>, key: Vec<u8>);
453
454    /// Adds a `Delete` tree operation to a list of GroveDB ops.
455    fn add_delete_tree(&mut self, path: Vec<Vec<u8>>, key: Vec<u8>, tree_type: TreeType);
456
457    /// Adds an `Insert` operation with an element to a list of GroveDB ops.
458    fn add_insert(&mut self, path: Vec<Vec<u8>>, key: Vec<u8>, element: Element);
459
460    /// Verify consistency of operations
461    fn verify_consistency_of_operations(&self) -> GroveDbOpConsistencyResults;
462
463    /// Check if the batch contains a specific path and key.
464    ///
465    /// # Arguments
466    ///
467    /// * `path` - The path to search for.
468    /// * `key` - The key to search for.
469    ///
470    /// # Returns
471    ///
472    /// * `Option<&Op>` - Returns a reference to the `Op` if found, or `None` otherwise.
473    fn contains<'c, P>(&self, path: P, key: &[u8]) -> Option<&GroveOp>
474    where
475        P: IntoIterator<Item = &'c [u8]>,
476        <P as IntoIterator>::IntoIter: ExactSizeIterator + DoubleEndedIterator + Clone;
477
478    /// Remove a specific path and key from the batch and return the removed `Op`.
479    ///
480    /// # Arguments
481    ///
482    /// * `path` - The path to search for.
483    /// * `key` - The key to search for.
484    ///
485    /// # Returns
486    ///
487    /// * `Option<Op>` - Returns the removed `Op` if found, or `None` otherwise.
488    fn remove<'c, P>(&mut self, path: P, key: &[u8]) -> Option<GroveOp>
489    where
490        P: IntoIterator<Item = &'c [u8]>,
491        <P as IntoIterator>::IntoIter: ExactSizeIterator + DoubleEndedIterator + Clone;
492
493    /// Find and remove a specific path and key from the batch if it is an
494    /// `GroveOp::InsertOrReplace`, `GroveOp::Replace`, or `GroveOp::Patch`. Return the found `Op` regardless of whether it was removed.
495    ///
496    /// # Arguments
497    ///
498    /// * `path` - The path to search for.
499    /// * `key` - The key to search for.
500    ///
501    /// # Returns
502    ///
503    /// * `Option<Op>` - Returns the found `Op` if it exists. If the `Op` is an `GroveOp::InsertOrReplace`, `GroveOp::Replace`,
504    ///   or `GroveOp::Patch`, it will be removed from the batch.
505    fn remove_if_insert(&mut self, path: Vec<Vec<u8>>, key: &[u8]) -> Option<GroveOp>;
506}
507
508impl GroveDbOpBatchV0Methods for GroveDbOpBatch {
509    /// Creates a new empty batch of GroveDB operations.
510    fn new() -> Self {
511        GroveDbOpBatch {
512            operations: Vec::new(),
513        }
514    }
515
516    /// Gets the number of operations from a list of GroveDB ops.
517    fn len(&self) -> usize {
518        self.operations.len()
519    }
520
521    /// Checks to see if the operation batch is empty
522    fn is_empty(&self) -> bool {
523        self.operations.is_empty()
524    }
525
526    /// Pushes an operation into a list of GroveDB ops.
527    fn push(&mut self, op: QualifiedGroveDbOp) {
528        self.operations.push(op);
529    }
530
531    /// Appends operations into a list of GroveDB ops.
532    fn append(&mut self, other: &mut Self) {
533        self.operations.append(&mut other.operations);
534    }
535
536    /// Extend operations into a list of GroveDB ops.
537    fn extend<I: IntoIterator<Item = QualifiedGroveDbOp>>(&mut self, other_ops: I) {
538        self.operations.extend(other_ops);
539    }
540
541    /// Puts a list of GroveDB operations into a batch.
542    fn from_operations(operations: Vec<QualifiedGroveDbOp>) -> Self {
543        GroveDbOpBatch { operations }
544    }
545
546    /// Adds an `Insert` operation with an empty tree at the specified path and key to a list of GroveDB ops.
547    fn add_insert_empty_tree(&mut self, path: Vec<Vec<u8>>, key: Vec<u8>) {
548        self.operations.push(
549            QualifiedGroveDbOp::insert_or_replace_op(path, key, Element::empty_tree())
550                .dont_check_for_backwards_references(),
551        )
552    }
553
554    /// Adds an `Insert` operation with an empty tree with storage flags to a list of GroveDB ops.
555    fn add_insert_empty_tree_with_flags(
556        &mut self,
557        path: Vec<Vec<u8>>,
558        key: Vec<u8>,
559        storage_flags: &Option<Cow<StorageFlags>>,
560    ) {
561        self.operations.push(
562            QualifiedGroveDbOp::insert_or_replace_op(
563                path,
564                key,
565                Element::empty_tree_with_flags(
566                    StorageFlags::map_borrowed_cow_to_some_element_flags(storage_flags),
567                ),
568            )
569            .dont_check_for_backwards_references(),
570        )
571    }
572
573    /// Adds an `Insert` operation with an empty sum tree at the specified path and key to a list of GroveDB ops.
574    fn add_insert_empty_sum_tree(&mut self, path: Vec<Vec<u8>>, key: Vec<u8>) {
575        self.operations.push(
576            QualifiedGroveDbOp::insert_or_replace_op(path, key, Element::empty_sum_tree())
577                .dont_check_for_backwards_references(),
578        )
579    }
580
581    /// Adds an `Insert` operation with an empty sum tree with storage flags to a list of GroveDB ops.
582    fn add_insert_empty_sum_tree_with_flags(
583        &mut self,
584        path: Vec<Vec<u8>>,
585        key: Vec<u8>,
586        storage_flags: &Option<Cow<StorageFlags>>,
587    ) {
588        self.operations.push(
589            QualifiedGroveDbOp::insert_or_replace_op(
590                path,
591                key,
592                Element::empty_sum_tree_with_flags(
593                    StorageFlags::map_borrowed_cow_to_some_element_flags(storage_flags),
594                ),
595            )
596            .dont_check_for_backwards_references(),
597        )
598    }
599
600    /// Adds a `Delete` operation to a list of GroveDB ops.
601    fn add_delete(&mut self, path: Vec<Vec<u8>>, key: Vec<u8>) {
602        self.operations
603            .push(QualifiedGroveDbOp::delete_op(path, key).dont_check_for_backwards_references())
604    }
605
606    /// Adds a `Delete` tree operation to a list of GroveDB ops.
607    /// Uses `DontCheckWithNoCleanup` because callers (e.g. `batch_delete_up_tree_while_empty`)
608    /// have already verified the tree is empty.
609    fn add_delete_tree(&mut self, path: Vec<Vec<u8>>, key: Vec<u8>, tree_type: TreeType) {
610        self.operations.push(
611            QualifiedGroveDbOp::delete_tree_op(
612                path,
613                key,
614                tree_type,
615                SubelementsDeletionBehavior::DontCheckWithNoCleanup,
616            )
617            .dont_check_for_backwards_references(),
618        )
619    }
620
621    /// Adds an `Insert` operation with an element to a list of GroveDB ops.
622    fn add_insert(&mut self, path: Vec<Vec<u8>>, key: Vec<u8>, element: Element) {
623        self.operations.push(
624            QualifiedGroveDbOp::insert_or_replace_op(path, key, element)
625                .dont_check_for_backwards_references(),
626        )
627    }
628
629    /// Verify consistency of operations
630    fn verify_consistency_of_operations(&self) -> GroveDbOpConsistencyResults {
631        QualifiedGroveDbOp::verify_consistency_of_operations(&self.operations)
632    }
633
634    /// Check if the batch contains a specific path and key.
635    ///
636    /// # Arguments
637    ///
638    /// * `path` - The path to search for.
639    /// * `key` - The key to search for.
640    ///
641    /// # Returns
642    ///
643    /// * `Option<&Op>` - Returns a reference to the `Op` if found, or `None` otherwise.
644    fn contains<'c, P>(&self, path: P, key: &[u8]) -> Option<&GroveOp>
645    where
646        P: IntoIterator<Item = &'c [u8]>,
647        <P as IntoIterator>::IntoIter: ExactSizeIterator + DoubleEndedIterator + Clone,
648    {
649        let path = KeyInfoPath(
650            path.into_iter()
651                .map(|item| KeyInfo::KnownKey(item.to_vec()))
652                .collect(),
653        );
654
655        self.operations.iter().find_map(|op| {
656            if op.path == path && op.key == Some(KeyInfo::KnownKey(key.to_vec())) {
657                Some(&op.op)
658            } else {
659                None
660            }
661        })
662    }
663
664    /// Remove a specific path and key from the batch and return the removed `Op`.
665    ///
666    /// # Arguments
667    ///
668    /// * `path` - The path to search for.
669    /// * `key` - The key to search for.
670    ///
671    /// # Returns
672    ///
673    /// * `Option<Op>` - Returns the removed `Op` if found, or `None` otherwise.
674    fn remove<'c, P>(&mut self, path: P, key: &[u8]) -> Option<GroveOp>
675    where
676        P: IntoIterator<Item = &'c [u8]>,
677        <P as IntoIterator>::IntoIter: ExactSizeIterator + DoubleEndedIterator + Clone,
678    {
679        let path = KeyInfoPath(
680            path.into_iter()
681                .map(|item| KeyInfo::KnownKey(item.to_vec()))
682                .collect(),
683        );
684
685        if let Some(index) = self
686            .operations
687            .iter()
688            .position(|op| op.path == path && op.key == Some(KeyInfo::KnownKey(key.to_vec())))
689        {
690            Some(self.operations.remove(index).op)
691        } else {
692            None
693        }
694    }
695
696    /// Find and remove a specific path and key from the batch if it is an
697    /// `GroveOp::InsertOrReplace`, `GroveOp::Replace`, or `GroveOp::Patch`. Return the found `Op` regardless of whether it was removed.
698    ///
699    /// # Arguments
700    ///
701    /// * `path` - The path to search for.
702    /// * `key` - The key to search for.
703    ///
704    /// # Returns
705    ///
706    /// * `Option<Op>` - Returns the found `Op` if it exists. If the `Op` is an `GroveOp::InsertOrReplace`, `GroveOp::Replace`,
707    ///   or `GroveOp::Patch`, it will be removed from the batch.
708    fn remove_if_insert(&mut self, path: Vec<Vec<u8>>, key: &[u8]) -> Option<GroveOp> {
709        let path = KeyInfoPath(
710            path.into_iter()
711                .map(|item| KeyInfo::KnownKey(item.to_vec()))
712                .collect(),
713        );
714
715        if let Some(index) = self
716            .operations
717            .iter()
718            .position(|op| op.path == path && op.key == Some(KeyInfo::KnownKey(key.to_vec())))
719        {
720            let op = &self.operations[index].op;
721            let op = if matches!(
722                op,
723                &GroveOp::InsertOrReplace { .. }
724                    | &GroveOp::InsertOrReplaceDontCheckForBackwardsReferences { .. }
725                    | &GroveOp::InsertWithKnownToNotAlreadyExist { .. }
726                    | &GroveOp::InsertIfNotExists { .. }
727                    | &GroveOp::Replace { .. }
728                    | &GroveOp::ReplaceDontCheckForBackwardsReferences { .. }
729                    | &GroveOp::Patch { .. }
730                    | &GroveOp::PatchDontCheckForBackwardsReferences { .. }
731            ) {
732                self.operations.remove(index).op
733            } else {
734                op.clone()
735            };
736            Some(op)
737        } else {
738            None
739        }
740    }
741}
742
743impl IntoIterator for GroveDbOpBatch {
744    type Item = QualifiedGroveDbOp;
745    type IntoIter = std::vec::IntoIter<QualifiedGroveDbOp>;
746
747    fn into_iter(self) -> Self::IntoIter {
748        self.operations.into_iter()
749    }
750}