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