rocksdb/
prop_name.rs

1use crate::ffi_util::CStrLike;
2
3use std::ffi::{CStr, CString};
4use std::ptr;
5
6/// A borrowed name of a RocksDB property.
7///
8/// The value is guaranteed to be a nul-terminated UTF-8 string. This means it
9/// can be converted to [`CStr`] and [`str`] at zero cost.
10#[derive(PartialEq, Eq, PartialOrd, Ord, Hash)]
11#[repr(transparent)]
12pub struct PropName(CStr);
13
14impl PropName {
15    /// Creates a new object from a nul-terminated string with no internal nul
16    /// bytes.
17    ///
18    /// Panics if the `value` isn’t terminated by a nul byte or contains
19    /// interior nul bytes.
20    pub(crate) const fn new_unwrap(value: &str) -> &Self {
21        let Some((&0, bytes)) = value.as_bytes().split_last() else {
22            panic!("input was not nul-terminated");
23        };
24        let mut idx = 0;
25        while idx < bytes.len() {
26            assert!(bytes[idx] != 0, "input contained interior nul byte");
27            idx += 1;
28        }
29
30        // SAFETY: 1. We’ve just verified `value` is a nul-terminated with no
31        // interior nul bytes and since its `str` it’s also valid UTF-8.
32        // 2. Self and CStr have the same representation so casting is sound.
33        unsafe {
34            let value = CStr::from_bytes_with_nul_unchecked(value.as_bytes());
35            &*(ptr::from_ref::<CStr>(value) as *const Self)
36        }
37    }
38
39    /// Converts the value into a C string slice.
40    #[inline]
41    pub fn as_c_str(&self) -> &CStr {
42        &self.0
43    }
44
45    /// Converts the value into a string slice.
46    ///
47    /// Nul byte terminating the underlying C string is not included in the
48    /// returned slice.
49    #[inline]
50    pub fn as_str(&self) -> &str {
51        // SAFETY: self.0 is guaranteed to be valid ASCII string.
52        unsafe { std::str::from_utf8_unchecked(self.0.to_bytes()) }
53    }
54}
55
56impl core::ops::Deref for PropName {
57    type Target = CStr;
58
59    #[inline]
60    fn deref(&self) -> &Self::Target {
61        self.as_c_str()
62    }
63}
64
65impl core::convert::AsRef<CStr> for PropName {
66    #[inline]
67    fn as_ref(&self) -> &CStr {
68        self.as_c_str()
69    }
70}
71
72impl core::convert::AsRef<str> for PropName {
73    #[inline]
74    fn as_ref(&self) -> &str {
75        self.as_str()
76    }
77}
78
79impl std::borrow::ToOwned for PropName {
80    type Owned = PropertyName;
81
82    #[inline]
83    fn to_owned(&self) -> Self::Owned {
84        PropertyName(self.0.to_owned())
85    }
86
87    #[inline]
88    fn clone_into(&self, target: &mut Self::Owned) {
89        self.0.clone_into(&mut target.0);
90    }
91}
92
93impl core::fmt::Display for PropName {
94    #[inline]
95    fn fmt(&self, fmtr: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
96        self.as_str().fmt(fmtr)
97    }
98}
99
100impl core::fmt::Debug for PropName {
101    #[inline]
102    fn fmt(&self, fmtr: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
103        self.as_str().fmt(fmtr)
104    }
105}
106
107impl core::cmp::PartialEq<CStr> for PropName {
108    #[inline]
109    fn eq(&self, other: &CStr) -> bool {
110        self.as_c_str().eq(other)
111    }
112}
113
114impl core::cmp::PartialEq<str> for PropName {
115    #[inline]
116    fn eq(&self, other: &str) -> bool {
117        self.as_str().eq(other)
118    }
119}
120
121impl core::cmp::PartialEq<PropName> for CStr {
122    #[inline]
123    fn eq(&self, other: &PropName) -> bool {
124        self.eq(other.as_c_str())
125    }
126}
127
128impl core::cmp::PartialEq<PropName> for str {
129    #[inline]
130    fn eq(&self, other: &PropName) -> bool {
131        self.eq(other.as_str())
132    }
133}
134
135impl<'a> CStrLike for &'a PropName {
136    type Baked = &'a CStr;
137    type Error = std::convert::Infallible;
138
139    #[inline]
140    fn bake(self) -> Result<Self::Baked, Self::Error> {
141        Ok(&self.0)
142    }
143
144    #[inline]
145    fn into_c_string(self) -> Result<CString, Self::Error> {
146        Ok(self.0.to_owned())
147    }
148}
149
150/// An owned name of a RocksDB property.
151///
152/// The value is guaranteed to be a nul-terminated UTF-8 string. This means it
153/// can be converted to [`CString`] and [`String`] at zero cost.
154#[derive(PartialEq, Eq, PartialOrd, Ord, Hash)]
155#[repr(transparent)]
156pub struct PropertyName(CString);
157
158impl PropertyName {
159    /// Creates a new object from valid nul-terminated UTF-8 string. The string
160    /// must not contain interior nul bytes.
161    #[inline]
162    unsafe fn from_vec_with_nul_unchecked(inner: Vec<u8>) -> Self {
163        // SAFETY: Caller promises inner is nul-terminated and valid UTF-8.
164        Self(unsafe { CString::from_vec_with_nul_unchecked(inner) })
165    }
166
167    /// Converts the value into a C string.
168    #[inline]
169    pub fn into_c_string(self) -> CString {
170        self.0
171    }
172
173    /// Converts the property name into a string.
174    ///
175    /// Nul byte terminating the underlying C string is not included in the
176    /// returned value.
177    #[inline]
178    pub fn into_string(self) -> String {
179        // SAFETY: self.0 is guaranteed to be valid UTF-8.
180        unsafe { String::from_utf8_unchecked(self.0.into_bytes()) }
181    }
182}
183
184impl std::ops::Deref for PropertyName {
185    type Target = PropName;
186
187    #[inline]
188    fn deref(&self) -> &Self::Target {
189        // SAFETY: 1. PropName and CStr have the same representation so casting
190        // is safe. 2. self.0 is guaranteed to be valid nul-terminated UTF-8
191        // string.
192        unsafe { &*(ptr::from_ref::<CStr>(self.0.as_c_str()) as *const PropName) }
193    }
194}
195
196impl core::convert::AsRef<CStr> for PropertyName {
197    #[inline]
198    fn as_ref(&self) -> &CStr {
199        self.as_c_str()
200    }
201}
202
203impl core::convert::AsRef<str> for PropertyName {
204    #[inline]
205    fn as_ref(&self) -> &str {
206        self.as_str()
207    }
208}
209
210impl std::borrow::Borrow<PropName> for PropertyName {
211    #[inline]
212    fn borrow(&self) -> &PropName {
213        self
214    }
215}
216
217impl core::fmt::Display for PropertyName {
218    #[inline]
219    fn fmt(&self, fmtr: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
220        self.as_str().fmt(fmtr)
221    }
222}
223
224impl core::fmt::Debug for PropertyName {
225    #[inline]
226    fn fmt(&self, fmtr: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
227        self.as_str().fmt(fmtr)
228    }
229}
230
231impl core::cmp::PartialEq<CString> for PropertyName {
232    #[inline]
233    fn eq(&self, other: &CString) -> bool {
234        self.as_c_str().eq(other.as_c_str())
235    }
236}
237
238impl core::cmp::PartialEq<String> for PropertyName {
239    #[inline]
240    fn eq(&self, other: &String) -> bool {
241        self.as_str().eq(other.as_str())
242    }
243}
244
245impl core::cmp::PartialEq<PropertyName> for CString {
246    #[inline]
247    fn eq(&self, other: &PropertyName) -> bool {
248        self.as_c_str().eq(other.as_c_str())
249    }
250}
251
252impl core::cmp::PartialEq<PropertyName> for String {
253    #[inline]
254    fn eq(&self, other: &PropertyName) -> bool {
255        self.as_str().eq(other.as_str())
256    }
257}
258
259impl CStrLike for PropertyName {
260    type Baked = CString;
261    type Error = std::convert::Infallible;
262
263    #[inline]
264    fn bake(self) -> Result<Self::Baked, Self::Error> {
265        Ok(self.0)
266    }
267
268    #[inline]
269    fn into_c_string(self) -> Result<CString, Self::Error> {
270        Ok(self.0)
271    }
272}
273
274impl<'a> CStrLike for &'a PropertyName {
275    type Baked = &'a CStr;
276    type Error = std::convert::Infallible;
277
278    #[inline]
279    fn bake(self) -> Result<Self::Baked, Self::Error> {
280        Ok(self.as_c_str())
281    }
282
283    #[inline]
284    fn into_c_string(self) -> Result<CString, Self::Error> {
285        Ok(self.0.clone())
286    }
287}
288
289/// Constructs a property name for an ‘at level’ property.
290///
291/// `name` is the infix of the property name (e.g. `"num-files-at-level"`) and
292/// `level` is level to get statistics of. The property name is constructed as
293/// `"rocksdb.<name><level>"`.
294///
295/// Expects `name` not to contain any interior nul bytes.
296pub(crate) unsafe fn level_property(name: &str, level: usize) -> PropertyName {
297    let bytes = format!("rocksdb.{name}{level}\0").into_bytes();
298    // SAFETY: We’re appending terminating nul and caller promises `name` has no
299    // interior nul bytes.
300    unsafe { PropertyName::from_vec_with_nul_unchecked(bytes) }
301}
302
303#[test]
304fn sanity_checks() {
305    let want = "rocksdb.cfstats-no-file-histogram";
306    assert_eq!(want, crate::properties::CFSTATS_NO_FILE_HISTOGRAM);
307
308    let want = "rocksdb.num-files-at-level5";
309    assert_eq!(want, &*crate::properties::num_files_at_level(5));
310}
311
312#[test]
313#[should_panic(expected = "input contained interior nul byte")]
314fn test_interior_nul() {
315    PropName::new_unwrap("interior nul\0\0");
316}
317
318#[test]
319#[should_panic(expected = "input was not nul-terminated")]
320fn test_non_nul_terminated() {
321    PropName::new_unwrap("no nul terminator");
322}