rocksdb/
prop_name.rs

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