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