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    UniquePublicKeyHashesToIdentitiesRoot,                            //Level 1
52    NonUniquePublicKeyKeyHashesToIdentitiesRoot,                      //Level 1
53    PoolsRoot,                                                        //Level 1
54    PoolsInsideEpoch(Epoch),                                          //Level 2
55    PreFundedSpecializedBalancesRoot,                                 //Level 1
56    SavedBlockTransactionsRoot,                                       //Level 1
57    SpentAssetLockTransactionsRoot,                                   //Level 1
58    MiscRoot,                                                         //Level 1
59    WithdrawalTransactionsRoot,                                       //Level 1
60    BalancesRoot,                                                     //Level 1
61    TokenRoot,                                                        //Level 1
62    TokenBalancesRoot,                                                //Level 2
63    TokenDistributionRoot,                                            //Level 2
64    TokenDirectSellPriceRoot,                                         //Level 2
65    TokenTimedDistributionRoot,                                       //Level 3
66    TokenPreProgrammedDistributionRoot,                               //Level 3
67    TokenPerpetualDistributionRoot,                                   //Level 3
68    TokenIdentityInfoRoot,                                            //Level 2
69    TokenContractInfoRoot,                                            //Level 2
70    TokenStatusRoot,                                                  //Level 2
71    VersionsRoot,                                                     //Level 1
72    VotesRoot,                                                        //Level 1
73    GroupActionsRoot,                                                 //Level 1
74    SingleUseKeyBalancesRoot,                                         //Level 1
75    ShieldedBalancesRoot,                                             //Level 1
76}
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                    // this is an epoch
210                    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                    // this is an epoch
221                    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
397/// Trait defining a batch of GroveDB operations.
398pub trait GroveDbOpBatchV0Methods {
399    /// Creates a new empty batch of GroveDB operations.
400    fn new() -> Self;
401
402    /// Gets the number of operations from a list of GroveDB ops.
403    fn len(&self) -> usize;
404
405    /// Checks to see if the operation batch is empty.
406    fn is_empty(&self) -> bool;
407
408    /// Pushes an operation into a list of GroveDB ops.
409    fn push(&mut self, op: QualifiedGroveDbOp);
410
411    /// Appends operations into a list of GroveDB ops.
412    fn append(&mut self, other: &mut Self);
413
414    /// Extend operations into a list of GroveDB ops.
415    fn extend<I: IntoIterator<Item = QualifiedGroveDbOp>>(&mut self, other_ops: I);
416
417    /// Puts a list of GroveDB operations into a batch.
418    fn from_operations(operations: Vec<QualifiedGroveDbOp>) -> Self;
419
420    /// Adds an `Insert` operation with an empty tree at the specified path and key to a list of GroveDB ops.
421    fn add_insert_empty_tree(&mut self, path: Vec<Vec<u8>>, key: Vec<u8>);
422
423    /// Adds an `Insert` operation with an empty tree with storage flags to a list of GroveDB ops.
424    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    /// Adds an `Insert` operation with an empty sum tree at the specified path and key to a list of GroveDB ops.
432    fn add_insert_empty_sum_tree(&mut self, path: Vec<Vec<u8>>, key: Vec<u8>);
433
434    /// Adds an `Insert` operation with an empty sum tree with storage flags to a list of GroveDB ops.
435    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    /// Adds a `Delete` operation to a list of GroveDB ops.
443    fn add_delete(&mut self, path: Vec<Vec<u8>>, key: Vec<u8>);
444
445    /// Adds a `Delete` tree operation to a list of GroveDB ops.
446    fn add_delete_tree(&mut self, path: Vec<Vec<u8>>, key: Vec<u8>, tree_type: TreeType);
447
448    /// Adds an `Insert` operation with an element to a list of GroveDB ops.
449    fn add_insert(&mut self, path: Vec<Vec<u8>>, key: Vec<u8>, element: Element);
450
451    /// Verify consistency of operations
452    fn verify_consistency_of_operations(&self) -> GroveDbOpConsistencyResults;
453
454    /// Check if the batch contains a specific path and key.
455    ///
456    /// # Arguments
457    ///
458    /// * `path` - The path to search for.
459    /// * `key` - The key to search for.
460    ///
461    /// # Returns
462    ///
463    /// * `Option<&Op>` - Returns a reference to the `Op` if found, or `None` otherwise.
464    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    /// Remove a specific path and key from the batch and return the removed `Op`.
470    ///
471    /// # Arguments
472    ///
473    /// * `path` - The path to search for.
474    /// * `key` - The key to search for.
475    ///
476    /// # Returns
477    ///
478    /// * `Option<Op>` - Returns the removed `Op` if found, or `None` otherwise.
479    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    /// Find and remove a specific path and key from the batch if it is an
485    /// `GroveOp::InsertOrReplace`, `GroveOp::Replace`, or `GroveOp::Patch`. Return the found `Op` regardless of whether it was removed.
486    ///
487    /// # Arguments
488    ///
489    /// * `path` - The path to search for.
490    /// * `key` - The key to search for.
491    ///
492    /// # Returns
493    ///
494    /// * `Option<Op>` - Returns the found `Op` if it exists. If the `Op` is an `GroveOp::InsertOrReplace`, `GroveOp::Replace`,
495    ///   or `GroveOp::Patch`, it will be removed from the batch.
496    fn remove_if_insert(&mut self, path: Vec<Vec<u8>>, key: &[u8]) -> Option<GroveOp>;
497}
498
499impl GroveDbOpBatchV0Methods for GroveDbOpBatch {
500    /// Creates a new empty batch of GroveDB operations.
501    fn new() -> Self {
502        GroveDbOpBatch {
503            operations: Vec::new(),
504        }
505    }
506
507    /// Gets the number of operations from a list of GroveDB ops.
508    fn len(&self) -> usize {
509        self.operations.len()
510    }
511
512    /// Checks to see if the operation batch is empty
513    fn is_empty(&self) -> bool {
514        self.operations.is_empty()
515    }
516
517    /// Pushes an operation into a list of GroveDB ops.
518    fn push(&mut self, op: QualifiedGroveDbOp) {
519        self.operations.push(op);
520    }
521
522    /// Appends operations into a list of GroveDB ops.
523    fn append(&mut self, other: &mut Self) {
524        self.operations.append(&mut other.operations);
525    }
526
527    /// Extend operations into a list of GroveDB ops.
528    fn extend<I: IntoIterator<Item = QualifiedGroveDbOp>>(&mut self, other_ops: I) {
529        self.operations.extend(other_ops);
530    }
531
532    /// Puts a list of GroveDB operations into a batch.
533    fn from_operations(operations: Vec<QualifiedGroveDbOp>) -> Self {
534        GroveDbOpBatch { operations }
535    }
536
537    /// Adds an `Insert` operation with an empty tree at the specified path and key to a list of GroveDB ops.
538    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    /// Adds an `Insert` operation with an empty tree with storage flags to a list of GroveDB ops.
548    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    /// Adds an `Insert` operation with an empty sum tree at the specified path and key to a list of GroveDB ops.
565    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    /// Adds an `Insert` operation with an empty sum tree with storage flags to a list of GroveDB ops.
575    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    /// Adds a `Delete` operation to a list of GroveDB ops.
592    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    /// Adds a `Delete` tree operation to a list of GroveDB ops.
598    /// Uses `DontCheckWithNoCleanup` because callers (e.g. `batch_delete_up_tree_while_empty`)
599    /// have already verified the tree is empty.
600    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    /// Adds an `Insert` operation with an element to a list of GroveDB ops.
610    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    /// Verify consistency of operations
616    fn verify_consistency_of_operations(&self) -> GroveDbOpConsistencyResults {
617        QualifiedGroveDbOp::verify_consistency_of_operations(&self.operations)
618    }
619
620    /// Check if the batch contains a specific path and key.
621    ///
622    /// # Arguments
623    ///
624    /// * `path` - The path to search for.
625    /// * `key` - The key to search for.
626    ///
627    /// # Returns
628    ///
629    /// * `Option<&Op>` - Returns a reference to the `Op` if found, or `None` otherwise.
630    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    /// Remove a specific path and key from the batch and return the removed `Op`.
651    ///
652    /// # Arguments
653    ///
654    /// * `path` - The path to search for.
655    /// * `key` - The key to search for.
656    ///
657    /// # Returns
658    ///
659    /// * `Option<Op>` - Returns the removed `Op` if found, or `None` otherwise.
660    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    /// Find and remove a specific path and key from the batch if it is an
683    /// `GroveOp::InsertOrReplace`, `GroveOp::Replace`, or `GroveOp::Patch`. Return the found `Op` regardless of whether it was removed.
684    ///
685    /// # Arguments
686    ///
687    /// * `path` - The path to search for.
688    /// * `key` - The key to search for.
689    ///
690    /// # Returns
691    ///
692    /// * `Option<Op>` - Returns the found `Op` if it exists. If the `Op` is an `GroveOp::InsertOrReplace`, `GroveOp::Replace`,
693    ///   or `GroveOp::Patch`, it will be removed from the batch.
694    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}