Skip to main content

rust_rocksdb/
metadata.rs

1//! Per-level and per-file LSM metadata, plus the live files storage info API.
2//!
3//! Two independent trees live here:
4//!
5//! * [`LevelMetaData`] / [`SstFileMetaData`] expand
6//!   [`ColumnFamilyMetaData`](crate::ColumnFamilyMetaData) into the levels and
7//!   SST files it summarises, optionally filtered by
8//!   [`ColumnFamilyMetaDataOptions`].
9//! * [`LiveFilesStorageInfo`] lists every file needed to make a consistent copy
10//!   of the DB (SSTs, blobs, WALs, MANIFEST, CURRENT, OPTIONS), which is what
11//!   checkpoint and backup are built on.
12
13use crate::ffi;
14use crate::ffi_util::{from_cstr_and_free, raw_data, raw_data_and_free};
15use libc::{c_char, c_uchar};
16use std::borrow::Cow;
17use std::ffi::CStr;
18use std::fmt;
19use std::iter::FusedIterator;
20use std::ops::Range;
21use std::sync::Arc;
22
23/// Reads a borrowed, NUL-terminated C string as bytes without copying.
24///
25/// The returned lifetime is chosen by the caller, so only call this with a
26/// pointer that is known to stay valid and immutable for that long.
27unsafe fn borrowed_cstr<'a>(ptr: *const c_char) -> &'a [u8] {
28    if ptr.is_null() {
29        return &[];
30    }
31    unsafe { CStr::from_ptr(ptr.cast()) }.to_bytes()
32}
33
34/// Owns a `rocksdb_column_family_metadata_t` so that the level and SST handles
35/// carved out of it stay valid.
36///
37/// `rocksdb_level_metadata_t` and `rocksdb_sst_file_metadata_t` are bare
38/// pointers into the parent's `std::vector`s (see `db/c.cc`), and `c.h` states
39/// the child handles must be released before the parent. Holding this behind an
40/// `Arc` in every child enforces that ordering instead of leaving it to the
41/// caller.
42struct CfMetaDataRoot {
43    inner: *mut ffi::rocksdb_column_family_metadata_t,
44}
45
46impl Drop for CfMetaDataRoot {
47    fn drop(&mut self) {
48        unsafe { ffi::rocksdb_column_family_metadata_destroy(self.inner) }
49    }
50}
51
52// SAFETY: the pointee is a snapshot that RocksDB fills in once and never
53// touches again; it shares no state with the DB. Nothing here mutates it
54// through a shared reference, so it is safe both to move between threads and to
55// read from several at once.
56unsafe impl Send for CfMetaDataRoot {}
57unsafe impl Sync for CfMetaDataRoot {}
58
59/// The metadata that describes one level of a column family's LSM tree.
60///
61/// Obtained from a [`ColumnFamilyMetaData`](crate::ColumnFamilyMetaData) query.
62/// The value is a snapshot taken when the metadata was collected and does not
63/// track later compactions or flushes.
64pub struct LevelMetaData {
65    inner: *mut ffi::rocksdb_level_metadata_t,
66    /// `None` when the handle merely borrows a caller-owned parent.
67    root: Option<Arc<CfMetaDataRoot>>,
68}
69
70impl LevelMetaData {
71    /// The level this metadata describes, for example 0 for L0.
72    ///
73    /// Do not assume this equals the index the value was read at: the filtered
74    /// `GetColumnFamilyMetaData` overload skips levels with no matching files,
75    /// so indices are not level numbers.
76    pub fn level(&self) -> i32 {
77        unsafe { ffi::rocksdb_level_metadata_get_level(self.inner) }
78    }
79
80    /// Total size of the level in bytes, the sum of its files' sizes.
81    pub fn size(&self) -> u64 {
82        unsafe { ffi::rocksdb_level_metadata_get_size(self.inner) }
83    }
84
85    /// Number of SST files in this level.
86    pub fn file_count(&self) -> usize {
87        unsafe { ffi::rocksdb_level_metadata_get_file_count(self.inner) }
88    }
89
90    /// Metadata for the `index`th SST file of this level, or `None` if `index`
91    /// is out of range.
92    ///
93    /// For level 0 the files are ordered most-recently-updated first; for level
94    /// 1 and above they are ordered by increasing key range.
95    pub fn sst_file(&self, index: usize) -> Option<SstFileMetaData> {
96        let inner = unsafe { ffi::rocksdb_level_metadata_get_sst_file_metadata(self.inner, index) };
97        if inner.is_null() {
98            return None;
99        }
100        Some(SstFileMetaData {
101            inner,
102            _root: self.root.clone(),
103        })
104    }
105
106    /// Iterates the SST files of this level in order.
107    pub fn sst_files(&self) -> impl Iterator<Item = SstFileMetaData> + '_ {
108        (0..self.file_count()).filter_map(|index| self.sst_file(index))
109    }
110}
111
112impl Drop for LevelMetaData {
113    fn drop(&mut self) {
114        // Frees only the handle. The `LevelMetaData` it points at belongs to the
115        // parent column family metadata.
116        unsafe { ffi::rocksdb_level_metadata_destroy(self.inner) }
117    }
118}
119
120impl fmt::Debug for LevelMetaData {
121    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
122        f.debug_struct("LevelMetaData")
123            .field("level", &self.level())
124            .field("size", &self.size())
125            .field("file_count", &self.file_count())
126            .finish()
127    }
128}
129
130// SAFETY: the handle and the snapshot behind it are plain data with no ties to
131// the originating thread, and the parent stays alive through `root`.
132// `Sync` is also sound (every accessor is a read), but is deliberately not
133// implemented until something needs it.
134unsafe impl Send for LevelMetaData {}
135
136/// The metadata that describes a single SST file within a level.
137pub struct SstFileMetaData {
138    inner: *mut ffi::rocksdb_sst_file_metadata_t,
139    /// Held, never read: keeps the parent column family metadata alive.
140    _root: Option<Arc<CfMetaDataRoot>>,
141}
142
143impl SstFileMetaData {
144    /// The file name within its directory, for example `123456.sst`.
145    pub fn relative_filename(&self) -> String {
146        // `strdup`ed by the C API, so this frees it.
147        unsafe {
148            from_cstr_and_free(ffi::rocksdb_sst_file_metadata_get_relative_filename(
149                self.inner,
150            ))
151        }
152    }
153
154    /// The directory holding the file, without a trailing `/`. This is a DB path
155    /// or column family path, not necessarily the main DB directory.
156    pub fn directory(&self) -> String {
157        // `strdup`ed by the C API, so this frees it.
158        unsafe { from_cstr_and_free(ffi::rocksdb_sst_file_metadata_get_directory(self.inner)) }
159    }
160
161    /// File size in bytes.
162    pub fn size(&self) -> u64 {
163        unsafe { ffi::rocksdb_sst_file_metadata_get_size(self.inner) }
164    }
165
166    /// Smallest user key in the file. Empty if the file's smallest key is empty.
167    pub fn smallest_key(&self) -> Vec<u8> {
168        let mut len: usize = 0;
169        // `malloc`ed by `CopyString`, so this copies and frees.
170        let ptr =
171            unsafe { ffi::rocksdb_sst_file_metadata_get_smallestkey(self.inner, &raw mut len) };
172        unsafe { raw_data_and_free(ptr, len) }.unwrap_or_default()
173    }
174
175    /// Largest user key in the file. Empty if the file's largest key is empty.
176    pub fn largest_key(&self) -> Vec<u8> {
177        let mut len: usize = 0;
178        // `malloc`ed by `CopyString`, so this copies and frees.
179        let ptr =
180            unsafe { ffi::rocksdb_sst_file_metadata_get_largestkey(self.inner, &raw mut len) };
181        unsafe { raw_data_and_free(ptr, len) }.unwrap_or_default()
182    }
183}
184
185impl Drop for SstFileMetaData {
186    fn drop(&mut self) {
187        // Frees only the handle. The `SstFileMetaData` it points at belongs to
188        // the parent column family metadata.
189        unsafe { ffi::rocksdb_sst_file_metadata_destroy(self.inner) }
190    }
191}
192
193impl fmt::Debug for SstFileMetaData {
194    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
195        f.debug_struct("SstFileMetaData")
196            .field("relative_filename", &self.relative_filename())
197            .field("directory", &self.directory())
198            .field("size", &self.size())
199            .finish_non_exhaustive()
200    }
201}
202
203// SAFETY: see the note on `LevelMetaData`.
204unsafe impl Send for SstFileMetaData {}
205
206/// Takes ownership of a column family metadata object and returns its levels.
207///
208/// The parent is destroyed once the last returned [`LevelMetaData`] and every
209/// [`SstFileMetaData`] derived from it are dropped, so nothing can dangle.
210///
211/// # Safety
212///
213/// `ptr` must be a live `rocksdb_column_family_metadata_t` that nothing else
214/// owns or will destroy.
215pub(crate) unsafe fn levels_from_cf_metadata_owned(
216    ptr: *mut ffi::rocksdb_column_family_metadata_t,
217) -> Vec<LevelMetaData> {
218    if ptr.is_null() {
219        return Vec::new();
220    }
221    let root = Arc::new(CfMetaDataRoot { inner: ptr });
222    unsafe { collect_levels(ptr, Some(root)) }
223}
224
225unsafe fn collect_levels(
226    ptr: *mut ffi::rocksdb_column_family_metadata_t,
227    root: Option<Arc<CfMetaDataRoot>>,
228) -> Vec<LevelMetaData> {
229    if ptr.is_null() {
230        return Vec::new();
231    }
232    let count = unsafe { ffi::rocksdb_column_family_metadata_get_level_count(ptr) };
233    let mut levels = Vec::with_capacity(count);
234    for i in 0..count {
235        let inner = unsafe { ffi::rocksdb_column_family_metadata_get_level_metadata(ptr, i) };
236        if inner.is_null() {
237            continue;
238        }
239        levels.push(LevelMetaData {
240            inner,
241            root: root.clone(),
242        });
243    }
244    levels
245}
246
247/// Filters applied when collecting column family metadata.
248///
249/// Both filters narrow which SST files are reported:
250///
251/// * `level` picks a single LSM level. The default, `-1`, reports every level.
252/// * `start_key` and `end_key` bound a user key range. A file is reported when
253///   its key range overlaps the bound, so a file that merely straddles the
254///   boundary is included. An unset bound is open-ended on that side.
255///
256/// The filtered query also drops levels that end up with no files, so the
257/// resulting `Vec<LevelMetaData>` is not indexed by level number. Read
258/// [`LevelMetaData::level`] to find out which level a value describes.
259///
260/// The reported `size` and `file_count` cover only the files that passed the
261/// filter, not the whole column family.
262pub struct ColumnFamilyMetaDataOptions {
263    pub(crate) inner: *mut ffi::rocksdb_column_family_metadata_options_t,
264}
265
266impl ColumnFamilyMetaDataOptions {
267    /// Creates options that filter nothing: all levels, unbounded key range.
268    pub fn new() -> Self {
269        Self {
270            inner: unsafe { ffi::rocksdb_column_family_metadata_options_create() },
271        }
272    }
273
274    /// Restricts the query to a single level. Pass `-1` to report every level.
275    pub fn set_level(&mut self, level: i32) {
276        unsafe { ffi::rocksdb_column_family_metadata_options_set_level(self.inner, level) }
277    }
278
279    /// Returns the level filter. `-1` means every level.
280    pub fn get_level(&self) -> i32 {
281        unsafe { ffi::rocksdb_column_family_metadata_options_get_level(self.inner) }
282    }
283
284    /// Sets the inclusive lower bound of the user key range to report.
285    pub fn set_start_key(&mut self, key: impl AsRef<[u8]>) {
286        let key = key.as_ref();
287        unsafe {
288            ffi::rocksdb_column_family_metadata_options_set_start_key(
289                self.inner,
290                key.as_ptr().cast::<c_char>(),
291                key.len(),
292            );
293        }
294    }
295
296    /// Removes the lower bound, leaving the range open on that side.
297    pub fn clear_start_key(&mut self) {
298        unsafe {
299            ffi::rocksdb_column_family_metadata_options_set_start_key(
300                self.inner,
301                std::ptr::null(),
302                0,
303            );
304        }
305    }
306
307    /// Returns the lower bound, or `None` if unset.
308    ///
309    /// The C API hands back a borrowed pointer into a `std::string` owned by the
310    /// options object, so the bytes are copied here rather than freed.
311    pub fn get_start_key(&self) -> Option<Vec<u8>> {
312        let mut len: usize = 0;
313        let ptr = unsafe {
314            ffi::rocksdb_column_family_metadata_options_get_start_key(
315                self.inner.cast_const(),
316                &raw mut len,
317            )
318        };
319        unsafe { raw_data(ptr, len) }
320    }
321
322    /// Sets the inclusive upper bound of the user key range to report.
323    pub fn set_end_key(&mut self, key: impl AsRef<[u8]>) {
324        let key = key.as_ref();
325        unsafe {
326            ffi::rocksdb_column_family_metadata_options_set_end_key(
327                self.inner,
328                key.as_ptr().cast::<c_char>(),
329                key.len(),
330            );
331        }
332    }
333
334    /// Removes the upper bound, leaving the range open on that side.
335    pub fn clear_end_key(&mut self) {
336        unsafe {
337            ffi::rocksdb_column_family_metadata_options_set_end_key(
338                self.inner,
339                std::ptr::null(),
340                0,
341            );
342        }
343    }
344
345    /// Returns the upper bound, or `None` if unset.
346    ///
347    /// The C API hands back a borrowed pointer into a `std::string` owned by the
348    /// options object, so the bytes are copied here rather than freed.
349    pub fn get_end_key(&self) -> Option<Vec<u8>> {
350        let mut len: usize = 0;
351        let ptr = unsafe {
352            ffi::rocksdb_column_family_metadata_options_get_end_key(
353                self.inner.cast_const(),
354                &raw mut len,
355            )
356        };
357        unsafe { raw_data(ptr, len) }
358    }
359}
360
361impl Default for ColumnFamilyMetaDataOptions {
362    fn default() -> Self {
363        Self::new()
364    }
365}
366
367impl Drop for ColumnFamilyMetaDataOptions {
368    fn drop(&mut self) {
369        unsafe { ffi::rocksdb_column_family_metadata_options_destroy(self.inner) }
370    }
371}
372
373impl fmt::Debug for ColumnFamilyMetaDataOptions {
374    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
375        f.debug_struct("ColumnFamilyMetaDataOptions")
376            .field("level", &self.get_level())
377            .field("start_key", &self.get_start_key())
378            .field("end_key", &self.get_end_key())
379            .finish()
380    }
381}
382
383// SAFETY: the pointee is a plain options bag with no thread affinity, and the
384// setters take `&mut self` so shared access cannot mutate it.
385unsafe impl Send for ColumnFamilyMetaDataOptions {}
386unsafe impl Sync for ColumnFamilyMetaDataOptions {}
387
388/// The role a file plays in a DB directory.
389///
390/// Mirrors `rocksdb::FileType` from `include/rocksdb/types.h`. Values RocksDB
391/// adds in future versions decode as [`FileType::Unknown`].
392#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
393pub enum FileType {
394    /// Write-ahead log, `<number>.log`.
395    WalFile,
396    /// The `LOCK` file guarding single-process access.
397    DBLockFile,
398    /// SST table file, `<number>.sst` (or `.ldb` for LevelDB-era files).
399    TableFile,
400    /// A `MANIFEST-<number>` file.
401    DescriptorFile,
402    /// The `CURRENT` file naming the live manifest.
403    CurrentFile,
404    /// A `<number>.dbtmp` file written while another file is being staged.
405    TempFile,
406    /// A `LOG` or `LOG.old.<number>` info log.
407    InfoLogFile,
408    /// A `METADB-<number>` metadata database.
409    MetaDatabase,
410    /// The `IDENTITY` file holding the DB's unique id.
411    IdentityFile,
412    /// An `OPTIONS-<number>` file.
413    OptionsFile,
414    /// Blob file, `<number>.blob`.
415    BlobFile,
416    /// A `COMPACTION_PROGRESS-<timestamp>` file.
417    CompactionProgressFile,
418    /// A value this build of the crate does not know about.
419    Unknown,
420}
421
422impl From<i32> for FileType {
423    fn from(value: i32) -> Self {
424        for candidate in [
425            FileType::WalFile,
426            FileType::DBLockFile,
427            FileType::TableFile,
428            FileType::DescriptorFile,
429            FileType::CurrentFile,
430            FileType::TempFile,
431            FileType::InfoLogFile,
432            FileType::MetaDatabase,
433            FileType::IdentityFile,
434            FileType::OptionsFile,
435            FileType::BlobFile,
436            FileType::CompactionProgressFile,
437        ] {
438            if value == candidate as i32 {
439                return candidate;
440            }
441        }
442        FileType::Unknown
443    }
444}
445
446impl FileType {
447    pub fn as_str(self) -> &'static str {
448        match self {
449            FileType::WalFile => "WalFile",
450            FileType::DBLockFile => "DBLockFile",
451            FileType::TableFile => "TableFile",
452            FileType::DescriptorFile => "DescriptorFile",
453            FileType::CurrentFile => "CurrentFile",
454            FileType::TempFile => "TempFile",
455            FileType::InfoLogFile => "InfoLogFile",
456            FileType::MetaDatabase => "MetaDatabase",
457            FileType::IdentityFile => "IdentityFile",
458            FileType::OptionsFile => "OptionsFile",
459            FileType::BlobFile => "BlobFile",
460            FileType::CompactionProgressFile => "CompactionProgressFile",
461            FileType::Unknown => "Unknown",
462        }
463    }
464}
465
466/// Storage tier hint for a file, passed through to the `FileSystem` so it can
467/// place or encode the file differently.
468///
469/// Mirrors `rocksdb::Temperature` from `include/rocksdb/types.h`, including its
470/// discriminants, so `Temperature::Warm as i32` is a valid argument to the
471/// `*_temperature` setters on [`Options`](crate::Options). Values RocksDB adds
472/// in future versions decode as [`Temperature::Unknown`], as does the
473/// `kLastTemperature` sentinel, which is not a real tier.
474///
475/// Upstream leaves gaps between the discriminants so new tiers can be slotted
476/// in. This feature is experimental upstream and subject to change.
477#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
478#[repr(i32)]
479pub enum Temperature {
480    /// No tier recorded. Also the decoding of any unrecognised value.
481    Unknown = 0,
482    /// Frequently read.
483    Hot = 0x04,
484    /// Read now and then.
485    Warm = 0x08,
486    /// Rarely read.
487    Cool = 0x0A,
488    /// Read very rarely.
489    Cold = 0x0C,
490    /// Archival; expect slow reads.
491    Ice = 0x10,
492}
493
494impl From<i32> for Temperature {
495    fn from(value: i32) -> Self {
496        match value {
497            0x04 => Temperature::Hot,
498            0x08 => Temperature::Warm,
499            0x0A => Temperature::Cool,
500            0x0C => Temperature::Cold,
501            0x10 => Temperature::Ice,
502            _ => Temperature::Unknown,
503        }
504    }
505}
506
507impl Temperature {
508    pub fn as_str(self) -> &'static str {
509        match self {
510            Temperature::Unknown => "Unknown",
511            Temperature::Hot => "Hot",
512            Temperature::Warm => "Warm",
513            Temperature::Cool => "Cool",
514            Temperature::Cold => "Cold",
515            Temperature::Ice => "Ice",
516        }
517    }
518}
519
520/// Options controlling how live files storage info is collected.
521pub struct LiveFilesStorageInfoOptions {
522    pub(crate) inner: *mut ffi::rocksdb_livefiles_storage_info_options_t,
523}
524
525impl LiveFilesStorageInfoOptions {
526    /// Creates options with RocksDB's defaults: no checksum info, always flush,
527    /// and follow the DB-wide atomic flush setting.
528    pub fn new() -> Self {
529        Self {
530            inner: unsafe { ffi::rocksdb_livefiles_storage_info_options_create() },
531        }
532    }
533
534    /// Whether to populate the checksum fields on each entry. Off by default, in
535    /// which case
536    /// [`file_checksum_func_name`](LiveFileStorageInfoEntry::file_checksum_func_name)
537    /// comes back empty.
538    pub fn set_include_checksum_info(&mut self, val: bool) {
539        unsafe {
540            ffi::rocksdb_livefiles_storage_info_options_set_include_checksum_info(
541                self.inner,
542                c_uchar::from(val),
543            );
544        }
545    }
546
547    /// Returns the value of the `include_checksum_info` option.
548    pub fn get_include_checksum_info(&self) -> bool {
549        unsafe {
550            ffi::rocksdb_livefiles_storage_info_options_get_include_checksum_info(self.inner) != 0
551        }
552    }
553
554    /// Flush memtables when the total size of live WAL files in bytes is at
555    /// least this value and the DB is writable.
556    ///
557    /// The default, `0`, always flushes.
558    pub fn set_wal_size_for_flush(&mut self, val: u64) {
559        unsafe {
560            ffi::rocksdb_livefiles_storage_info_options_set_wal_size_for_flush(self.inner, val);
561        }
562    }
563
564    /// Returns the value of the `wal_size_for_flush` option.
565    pub fn get_wal_size_for_flush(&self) -> u64 {
566        unsafe { ffi::rocksdb_livefiles_storage_info_options_get_wal_size_for_flush(self.inner) }
567    }
568
569    /// Flush all column families atomically regardless of
570    /// [`Options::set_atomic_flush`](crate::Options::set_atomic_flush), giving a
571    /// consistent view across them.
572    ///
573    /// Only matters when a flush actually happens, so it has no effect if
574    /// `wal_size_for_flush` suppressed the flush. Defaults to off, which follows
575    /// the DB-wide setting.
576    pub fn set_atomic_flush(&mut self, val: bool) {
577        unsafe {
578            ffi::rocksdb_livefiles_storage_info_options_set_atomic_flush(
579                self.inner,
580                c_uchar::from(val),
581            );
582        }
583    }
584
585    /// Returns the value of the `atomic_flush` option.
586    pub fn get_atomic_flush(&self) -> bool {
587        unsafe { ffi::rocksdb_livefiles_storage_info_options_get_atomic_flush(self.inner) != 0 }
588    }
589}
590
591impl Default for LiveFilesStorageInfoOptions {
592    fn default() -> Self {
593        Self::new()
594    }
595}
596
597impl Drop for LiveFilesStorageInfoOptions {
598    fn drop(&mut self) {
599        unsafe { ffi::rocksdb_livefiles_storage_info_options_destroy(self.inner) }
600    }
601}
602
603impl fmt::Debug for LiveFilesStorageInfoOptions {
604    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
605        f.debug_struct("LiveFilesStorageInfoOptions")
606            .field("include_checksum_info", &self.get_include_checksum_info())
607            .field("wal_size_for_flush", &self.get_wal_size_for_flush())
608            .field("atomic_flush", &self.get_atomic_flush())
609            .finish()
610    }
611}
612
613// SAFETY: the pointee is a plain options bag with no thread affinity, and the
614// setters take `&mut self` so shared access cannot mutate it.
615unsafe impl Send for LiveFilesStorageInfoOptions {}
616unsafe impl Sync for LiveFilesStorageInfoOptions {}
617
618/// Everything needed to make a consistent copy of a DB: SST and blob files,
619/// WALs, the MANIFEST, CURRENT and OPTIONS.
620///
621/// This is a snapshot. It does not pin the files on disk, so a file listed here
622/// can still be deleted by a background job unless the DB is otherwise held
623/// still.
624pub struct LiveFilesStorageInfo {
625    inner: *mut ffi::rocksdb_livefiles_storage_info_t,
626    /// Cached because the underlying vector is filled in once and never
627    /// resized, and every bounds check would otherwise cost an FFI call.
628    len: usize,
629}
630
631impl LiveFilesStorageInfo {
632    /// Takes ownership of a raw storage info handle.
633    ///
634    /// # Safety
635    ///
636    /// `ptr` must be a non-null handle from `rocksdb_get_livefiles_storage_info`
637    /// that nothing else owns. It is destroyed when the returned value drops.
638    pub(crate) unsafe fn from_ptr(ptr: *mut ffi::rocksdb_livefiles_storage_info_t) -> Self {
639        let len = unsafe { ffi::rocksdb_livefiles_storage_info_count(ptr.cast_const()) };
640        Self { inner: ptr, len }
641    }
642
643    /// Number of files listed.
644    pub fn len(&self) -> usize {
645        self.len
646    }
647
648    /// Whether no files are listed.
649    pub fn is_empty(&self) -> bool {
650        self.len() == 0
651    }
652
653    /// Borrows the entry at `index`, or `None` if out of range.
654    ///
655    /// The bounds check matters: the underlying C accessors index a
656    /// `std::vector` without checking, so an out of range index there is
657    /// undefined behaviour.
658    pub fn get(&self, index: usize) -> Option<LiveFileStorageInfoEntry<'_>> {
659        if index >= self.len {
660            return None;
661        }
662        Some(LiveFileStorageInfoEntry { info: self, index })
663    }
664
665    /// Iterates over every entry.
666    pub fn iter(&self) -> LiveFilesStorageInfoIter<'_> {
667        LiveFilesStorageInfoIter {
668            info: self,
669            range: 0..self.len,
670        }
671    }
672}
673
674impl Drop for LiveFilesStorageInfo {
675    fn drop(&mut self) {
676        unsafe { ffi::rocksdb_livefiles_storage_info_destroy(self.inner) }
677    }
678}
679
680impl fmt::Debug for LiveFilesStorageInfo {
681    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
682        f.debug_list().entries(self.iter()).finish()
683    }
684}
685
686impl<'a> IntoIterator for &'a LiveFilesStorageInfo {
687    type Item = LiveFileStorageInfoEntry<'a>;
688    type IntoIter = LiveFilesStorageInfoIter<'a>;
689
690    fn into_iter(self) -> Self::IntoIter {
691        self.iter()
692    }
693}
694
695// SAFETY: the pointee is a `std::vector<LiveFileStorageInfo>` that RocksDB fills
696// in once and this type never mutates. Reads through `&self` cannot race, so
697// both moving it between threads and sharing it across them are sound. `Sync` is
698// what lets a `LiveFileStorageInfoEntry` be `Send`.
699unsafe impl Send for LiveFilesStorageInfo {}
700unsafe impl Sync for LiveFilesStorageInfo {}
701
702/// Iterator over the entries of a [`LiveFilesStorageInfo`].
703pub struct LiveFilesStorageInfoIter<'a> {
704    info: &'a LiveFilesStorageInfo,
705    range: Range<usize>,
706}
707
708impl<'a> Iterator for LiveFilesStorageInfoIter<'a> {
709    type Item = LiveFileStorageInfoEntry<'a>;
710
711    fn next(&mut self) -> Option<Self::Item> {
712        let index = self.range.next()?;
713        Some(LiveFileStorageInfoEntry {
714            info: self.info,
715            index,
716        })
717    }
718
719    fn size_hint(&self) -> (usize, Option<usize>) {
720        self.range.size_hint()
721    }
722}
723
724impl DoubleEndedIterator for LiveFilesStorageInfoIter<'_> {
725    fn next_back(&mut self) -> Option<Self::Item> {
726        let index = self.range.next_back()?;
727        Some(LiveFileStorageInfoEntry {
728            info: self.info,
729            index,
730        })
731    }
732}
733
734impl ExactSizeIterator for LiveFilesStorageInfoIter<'_> {}
735
736impl FusedIterator for LiveFilesStorageInfoIter<'_> {}
737
738/// One file in a [`LiveFilesStorageInfo`] listing.
739///
740/// Every string accessor borrows straight out of the parent listing: the C API
741/// returns interior pointers into the `std::string`s it holds, so nothing is
742/// copied or freed here.
743#[derive(Copy, Clone)]
744pub struct LiveFileStorageInfoEntry<'a> {
745    info: &'a LiveFilesStorageInfo,
746    index: usize,
747}
748
749impl<'a> LiveFileStorageInfoEntry<'a> {
750    /// Position of this entry within the parent listing.
751    pub fn index(&self) -> usize {
752        self.index
753    }
754
755    /// The file name within its directory, for example `123456.sst`.
756    pub fn relative_filename(&self) -> &'a [u8] {
757        unsafe {
758            borrowed_cstr(ffi::rocksdb_livefiles_storage_info_relative_filename(
759                self.info.inner.cast_const(),
760                self.index,
761            ))
762        }
763    }
764
765    /// [`relative_filename`](Self::relative_filename) as UTF-8, replacing
766    /// invalid sequences.
767    pub fn relative_filename_lossy(&self) -> Cow<'a, str> {
768        String::from_utf8_lossy(self.relative_filename())
769    }
770
771    /// The directory holding the file, without a trailing `/`. This could be a
772    /// DB path, the WAL directory, and so on.
773    pub fn directory(&self) -> &'a [u8] {
774        unsafe {
775            borrowed_cstr(ffi::rocksdb_livefiles_storage_info_directory(
776                self.info.inner.cast_const(),
777                self.index,
778            ))
779        }
780    }
781
782    /// [`directory`](Self::directory) as UTF-8, replacing invalid sequences.
783    pub fn directory_lossy(&self) -> Cow<'a, str> {
784        String::from_utf8_lossy(self.directory())
785    }
786
787    /// The file's number within the DB, or `0` for files that have none, such
788    /// as `CURRENT`.
789    pub fn file_number(&self) -> u64 {
790        unsafe {
791            ffi::rocksdb_livefiles_storage_info_file_number(
792                self.info.inner.cast_const(),
793                self.index,
794            )
795        }
796    }
797
798    /// The role this file plays in the DB.
799    pub fn file_type(&self) -> FileType {
800        FileType::from(unsafe {
801            ffi::rocksdb_livefiles_storage_info_file_type(self.info.inner.cast_const(), self.index)
802        })
803    }
804
805    /// File size in bytes. See [`trim_to_size`](Self::trim_to_size) and
806    /// [`replacement_contents`](Self::replacement_contents) for when the file on
807    /// disk may differ.
808    pub fn size(&self) -> u64 {
809        unsafe {
810            ffi::rocksdb_livefiles_storage_info_size(self.info.inner.cast_const(), self.index)
811        }
812    }
813
814    /// When true the file on disk may be longer than [`size`](Self::size) and
815    /// only the first `size` bytes belong in the copy. When false a length
816    /// mismatch means the file is corrupt.
817    pub fn trim_to_size(&self) -> bool {
818        unsafe {
819            ffi::rocksdb_livefiles_storage_info_trim_to_size(
820                self.info.inner.cast_const(),
821                self.index,
822            ) != 0
823        }
824    }
825
826    /// Contents to write instead of reading the file from disk, used for
827    /// `CURRENT`. Empty means read the file on disk as usual; otherwise this is
828    /// exactly [`size`](Self::size) bytes long.
829    ///
830    /// Unlike the other borrowed strings this may contain NUL bytes, so it comes
831    /// from the length out-param rather than `strlen`.
832    pub fn replacement_contents(&self) -> &'a [u8] {
833        let mut size: usize = 0;
834        let ptr = unsafe {
835            ffi::rocksdb_livefiles_storage_info_replacement_contents(
836                self.info.inner.cast_const(),
837                self.index,
838                &raw mut size,
839            )
840        };
841        if ptr.is_null() || size == 0 {
842            return &[];
843        }
844        unsafe { std::slice::from_raw_parts(ptr.cast::<u8>(), size) }
845    }
846
847    // There is deliberately no `file_checksum` accessor. RocksDB stores a binary
848    // checksum in this field, and the built-in CRC32c generator writes four raw
849    // big-endian bytes, but the only C accessor
850    // (`rocksdb_livefiles_storage_info_file_checksum`) hands it back as a
851    // NUL-terminated string with no length. Any byte of the digest can be zero, so
852    // the value would be silently truncated for roughly one file in sixty-five with
853    // no way for a caller to tell. Exposing the function name below is safe because
854    // that really is a string.
855
856    /// Name of the checksum function that produced this file's checksum.
857    /// `Unknown` when no checksum function is configured, empty when checksum
858    /// info was not requested.
859    pub fn file_checksum_func_name(&self) -> &'a [u8] {
860        unsafe {
861            borrowed_cstr(ffi::rocksdb_livefiles_storage_info_file_checksum_func_name(
862                self.info.inner.cast_const(),
863                self.index,
864            ))
865        }
866    }
867
868    /// [`file_checksum_func_name`](Self::file_checksum_func_name) as UTF-8,
869    /// replacing invalid sequences.
870    pub fn file_checksum_func_name_lossy(&self) -> Cow<'a, str> {
871        String::from_utf8_lossy(self.file_checksum_func_name())
872    }
873
874    /// The storage tier the file is placed on.
875    pub fn temperature(&self) -> Temperature {
876        Temperature::from(unsafe {
877            ffi::rocksdb_livefiles_storage_info_temperature(
878                self.info.inner.cast_const(),
879                self.index,
880            )
881        })
882    }
883}
884
885impl fmt::Debug for LiveFileStorageInfoEntry<'_> {
886    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
887        f.debug_struct("LiveFileStorageInfoEntry")
888            .field("relative_filename", &self.relative_filename_lossy())
889            .field("directory", &self.directory_lossy())
890            .field("file_number", &self.file_number())
891            .field("file_type", &self.file_type())
892            .field("size", &self.size())
893            .field("trim_to_size", &self.trim_to_size())
894            .field("temperature", &self.temperature())
895            .finish_non_exhaustive()
896    }
897}