kaspa_database/
key.rs

1use crate::registry::{DatabaseStorePrefixes, SEPARATOR};
2use smallvec::SmallVec;
3use std::fmt::{Debug, Display};
4
5#[derive(Clone)]
6pub struct DbKey {
7    path: SmallVec<[u8; 36]>, // Optimized for the common case of { prefix byte || HASH (32 bytes) }
8    prefix_len: usize,
9}
10
11impl DbKey {
12    pub fn new<TKey>(prefix: &[u8], key: TKey) -> Self
13    where
14        TKey: Clone + AsRef<[u8]>,
15    {
16        Self { path: prefix.iter().chain(key.as_ref().iter()).copied().collect(), prefix_len: prefix.len() }
17    }
18
19    pub fn new_with_bucket<TKey, TBucket>(prefix: &[u8], bucket: TBucket, key: TKey) -> Self
20    where
21        TKey: Clone + AsRef<[u8]>,
22        TBucket: Copy + AsRef<[u8]>,
23    {
24        let mut db_key = Self::prefix_only(prefix);
25        db_key.add_bucket(bucket);
26        db_key.add_key(key);
27        db_key
28    }
29
30    pub fn prefix_only(prefix: &[u8]) -> Self {
31        Self::new(prefix, [])
32    }
33
34    /// add a bucket to the DBkey, this adds to the prefix length
35    pub fn add_bucket<TBucket>(&mut self, bucket: TBucket)
36    where
37        TBucket: Copy + AsRef<[u8]>,
38    {
39        self.path.extend(bucket.as_ref().iter().copied());
40        self.prefix_len += bucket.as_ref().len();
41    }
42
43    pub fn add_key<TKey>(&mut self, key: TKey)
44    where
45        TKey: Clone + AsRef<[u8]>,
46    {
47        self.path.extend(key.as_ref().iter().copied());
48        self.prefix_len += key.as_ref().len();
49    }
50
51    pub fn prefix_len(&self) -> usize {
52        self.prefix_len
53    }
54}
55
56impl AsRef<[u8]> for DbKey {
57    fn as_ref(&self) -> &[u8] {
58        &self.path
59    }
60}
61
62impl Display for DbKey {
63    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64        use DatabaseStorePrefixes::*;
65        let mut pos = 0;
66
67        if self.prefix_len > 0 {
68            if let Ok(prefix) = DatabaseStorePrefixes::try_from(self.path[0]) {
69                prefix.fmt(f)?;
70                f.write_str("/")?;
71                pos += 1;
72                if self.prefix_len > 1 {
73                    match prefix {
74                        Ghostdag
75                        | GhostdagCompact
76                        | RelationsParents
77                        | RelationsChildren
78                        | Reachability
79                        | ReachabilityTreeChildren
80                        | ReachabilityFutureCoveringSet => {
81                            if self.path[1] != SEPARATOR {
82                                // Expected to be a block level so we display as a number
83                                Display::fmt(&self.path[1], f)?;
84                                f.write_str("/")?;
85                            }
86                            pos += 1;
87                        }
88                        ReachabilityRelations => {
89                            if let Ok(next_prefix) = DatabaseStorePrefixes::try_from(self.path[1]) {
90                                next_prefix.fmt(f)?;
91                                f.write_str("/")?;
92                                pos += 1;
93                            }
94                        }
95                        _ => {}
96                    }
97                }
98            }
99        }
100
101        // We expect that the key part is usually more readable as hex
102        f.write_str(&faster_hex::hex_string(&self.path[pos..]))
103    }
104}
105
106impl Debug for DbKey {
107    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
108        Display::fmt(&self, f)
109    }
110}
111
112#[cfg(test)]
113mod tests {
114    use super::*;
115    use kaspa_hashes::{Hash, HASH_SIZE};
116    use DatabaseStorePrefixes::*;
117
118    #[test]
119    fn test_key_display() {
120        let level = 37;
121        let key1 = DbKey::new(&[ReachabilityRelations.into(), RelationsParents.into()], Hash::from_u64_word(34567890));
122        let key2 = DbKey::new(&[Reachability.into(), Separator.into()], Hash::from_u64_word(345690));
123        let key3 = DbKey::new(&[Reachability.into(), level], Hash::from_u64_word(345690));
124        let key4 = DbKey::new(&[RelationsParents.into(), level], Hash::from_u64_word(345690));
125
126        assert!(key1.to_string().starts_with(&format!("{:?}/{:?}/00", ReachabilityRelations, RelationsParents)));
127        assert!(key2.to_string().starts_with(&format!("{:?}/00", Reachability)));
128        assert!(key3.to_string().starts_with(&format!("{:?}/{}/00", Reachability, level)));
129        assert!(key4.to_string().starts_with(&format!("{:?}/{}/00", RelationsParents, level)));
130
131        let key5 = DbKey::new(b"human/readable", Hash::from_bytes([SEPARATOR; HASH_SIZE]));
132        let key6 = DbKey::prefix_only(&[0xC0, 0xC1, 0xF5, 0xF6]);
133        let key7 = DbKey::prefix_only(b"direct-prefix");
134
135        // Make sure display can handle arbitrary strings
136        let _ = key5.to_string();
137        let _ = key6.to_string();
138        let _ = key7.to_string();
139    }
140}