Skip to main content

rust_rocksdb/
wal.rs

1//! Write ahead log inspection.
2//!
3//! RocksDB can list the WAL files backing a DB, both the live ones in the DB
4//! directory and the ones already moved to the archive. [`WalFiles`] is that
5//! listing, [`WalFile`] is one entry in it, and [`OwnedWalFile`] is the single
6//! file returned when asking only about the WAL currently being written.
7//!
8//! [`WalReadOptions`] belongs to the other half of the feature, reading the
9//! updates recorded in the WAL back out with a
10//! [`DBWALIterator`](crate::DBWALIterator).
11//!
12//! Wraps `WalFile`, `WalFileType` and `TransactionLogIterator::ReadOptions`
13//! from `include/rocksdb/transaction_log.h`.
14
15use crate::ffi;
16use libc::c_uchar;
17use std::borrow::Cow;
18use std::ffi::CStr;
19use std::fmt;
20use std::iter::FusedIterator;
21use std::marker::PhantomData;
22use std::ops::Range;
23
24/// Where a WAL file lives.
25///
26/// Mirrors `rocksdb::WalFileType` from `include/rocksdb/transaction_log.h`.
27/// Values RocksDB adds in future versions decode as [`WalFileType::Unknown`].
28#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
29pub enum WalFileType {
30    /// Moved out of the main DB directory into the archive because it is no
31    /// longer live. Cleaned up according to
32    /// [`set_wal_size_limit_mb`](crate::Options::set_wal_size_limit_mb) and
33    /// [`set_wal_ttl_seconds`](crate::Options::set_wal_ttl_seconds).
34    ArchivedLogFile,
35    /// Still live, in the main DB directory.
36    AliveLogFile,
37    /// A value this build of the crate does not know about.
38    Unknown,
39}
40
41impl From<i32> for WalFileType {
42    fn from(value: i32) -> Self {
43        const ARCHIVED: i32 = ffi::rocksdb_wal_file_type_archived_log as i32;
44        const ALIVE: i32 = ffi::rocksdb_wal_file_type_alive_log as i32;
45        match value {
46            ARCHIVED => WalFileType::ArchivedLogFile,
47            ALIVE => WalFileType::AliveLogFile,
48            _ => WalFileType::Unknown,
49        }
50    }
51}
52
53impl WalFileType {
54    /// The variant name, for logs and error messages.
55    pub fn as_str(self) -> &'static str {
56        match self {
57            WalFileType::ArchivedLogFile => "ArchivedLogFile",
58            WalFileType::AliveLogFile => "AliveLogFile",
59            WalFileType::Unknown => "Unknown",
60        }
61    }
62}
63
64/// Options for streaming updates out of the WAL.
65pub struct WalReadOptions {
66    pub(crate) inner: *mut ffi::rocksdb_wal_readoptions_t,
67}
68
69impl Default for WalReadOptions {
70    fn default() -> Self {
71        let opts = unsafe { ffi::rocksdb_wal_readoptions_create() };
72        assert!(!opts.is_null(), "Could not create RocksDB WAL Read Options");
73
74        Self { inner: opts }
75    }
76}
77
78impl Drop for WalReadOptions {
79    fn drop(&mut self) {
80        unsafe {
81            ffi::rocksdb_wal_readoptions_destroy(self.inner);
82        }
83    }
84}
85
86// SAFETY: the pointee is a plain options bag with no thread affinity, and the
87// setters take `&mut self` so shared access cannot mutate it.
88unsafe impl Send for WalReadOptions {}
89unsafe impl Sync for WalReadOptions {}
90
91impl WalReadOptions {
92    /// Whether to check each WAL record's checksum while reading it. Turning
93    /// this off trades corruption detection for speed.
94    ///
95    /// Default: true
96    pub fn set_verify_checksums(&mut self, verify_checksums: bool) {
97        unsafe {
98            ffi::rocksdb_wal_readoptions_set_verify_checksums(
99                self.inner,
100                c_uchar::from(verify_checksums),
101            );
102        }
103    }
104
105    /// Returns the current `verify_checksums` setting.
106    ///
107    /// See [`Self::set_verify_checksums`] for what this controls.
108    pub fn get_verify_checksums(&self) -> bool {
109        unsafe { ffi::rocksdb_wal_readoptions_get_verify_checksums(self.inner) != 0 }
110    }
111}
112
113/// A DB's WAL files, sorted oldest first.
114///
115/// This is a snapshot taken when the listing was made. It does not pin
116/// anything, so a file listed here can still be archived or deleted by a
117/// background job.
118pub struct WalFiles {
119    inner: *mut ffi::rocksdb_wal_files_t,
120    /// Cached because the underlying vector is filled in once and never
121    /// resized, and every bounds check would otherwise cost an FFI call.
122    len: usize,
123}
124
125impl WalFiles {
126    /// Takes ownership of a raw WAL file listing.
127    ///
128    /// # Safety
129    ///
130    /// `ptr` must be a non-null handle from `rocksdb_get_sorted_wal_files`
131    /// that nothing else owns. It is destroyed when the returned value drops.
132    pub(crate) unsafe fn from_ptr(ptr: *mut ffi::rocksdb_wal_files_t) -> Self {
133        let len = unsafe { ffi::rocksdb_wal_files_count(ptr.cast_const()) };
134        Self { inner: ptr, len }
135    }
136
137    /// Number of WAL files listed.
138    pub fn len(&self) -> usize {
139        self.len
140    }
141
142    /// Whether no WAL files are listed.
143    pub fn is_empty(&self) -> bool {
144        self.len() == 0
145    }
146
147    /// Borrows the entry at `index`, or `None` if out of range.
148    pub fn get(&self, index: usize) -> Option<WalFile<'_>> {
149        // `rocksdb_wal_files_get_wal_file` hands back a pointer straight into
150        // the parent's `std::vector<rocksdb_wal_file_t>`, and already returns
151        // null for an out of range index (see `db/c.cc`). Nothing is allocated,
152        // so the result must not be passed to `rocksdb_wal_file_destroy`.
153        let inner = unsafe { ffi::rocksdb_wal_files_get_wal_file(self.inner.cast_const(), index) };
154        if inner.is_null() {
155            return None;
156        }
157        Some(WalFile {
158            inner,
159            _files: PhantomData,
160        })
161    }
162
163    /// Iterates the WAL files oldest first.
164    pub fn iter(&self) -> WalFilesIter<'_> {
165        WalFilesIter {
166            files: self,
167            range: 0..self.len,
168        }
169    }
170}
171
172impl Drop for WalFiles {
173    fn drop(&mut self) {
174        unsafe { ffi::rocksdb_wal_files_destroy(self.inner) }
175    }
176}
177
178impl fmt::Debug for WalFiles {
179    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
180        f.debug_list().entries(self.iter()).finish()
181    }
182}
183
184impl<'a> IntoIterator for &'a WalFiles {
185    type Item = WalFile<'a>;
186    type IntoIter = WalFilesIter<'a>;
187
188    fn into_iter(self) -> Self::IntoIter {
189        self.iter()
190    }
191}
192
193// SAFETY: the pointee is a `std::vector<rocksdb_wal_file_t>` of plain values
194// that RocksDB fills in once and this type never mutates. Reads through `&self`
195// cannot race, so both moving it between threads and sharing it across them are
196// sound.
197unsafe impl Send for WalFiles {}
198unsafe impl Sync for WalFiles {}
199
200/// Iterator over the entries of a [`WalFiles`] listing.
201pub struct WalFilesIter<'a> {
202    files: &'a WalFiles,
203    range: Range<usize>,
204}
205
206impl<'a> Iterator for WalFilesIter<'a> {
207    type Item = WalFile<'a>;
208
209    fn next(&mut self) -> Option<Self::Item> {
210        self.files.get(self.range.next()?)
211    }
212
213    fn size_hint(&self) -> (usize, Option<usize>) {
214        self.range.size_hint()
215    }
216}
217
218impl DoubleEndedIterator for WalFilesIter<'_> {
219    fn next_back(&mut self) -> Option<Self::Item> {
220        self.files.get(self.range.next_back()?)
221    }
222}
223
224impl ExactSizeIterator for WalFilesIter<'_> {}
225
226impl FusedIterator for WalFilesIter<'_> {}
227
228/// One WAL file in a [`WalFiles`] listing.
229///
230/// A borrowed view. The values live inside the parent listing and are freed
231/// with it.
232#[derive(Copy, Clone)]
233pub struct WalFile<'a> {
234    inner: *const ffi::rocksdb_wal_file_t,
235    _files: PhantomData<&'a WalFiles>,
236}
237
238// SAFETY: the pointee is a plain struct of a `std::string` and four integers
239// that RocksDB fills in once, and this type only reads it. It stays alive for
240// `'a`, so it is sound both to move a view between threads and to read one from
241// several at once.
242unsafe impl Send for WalFile<'_> {}
243unsafe impl Sync for WalFile<'_> {}
244
245impl<'a> WalFile<'a> {
246    /// The file's path relative to the main DB directory, for example
247    /// `/000003.log` for a live file or `/archive/000003.log` for an archived
248    /// one.
249    ///
250    /// Borrowed from the `std::string` inside the parent: the C API returns
251    /// `path_name.c_str()`, not a copy, so nothing is allocated or freed here.
252    pub fn path_name(self) -> &'a [u8] {
253        unsafe { CStr::from_ptr(ffi::rocksdb_wal_file_path_name(self.inner).cast()) }.to_bytes()
254    }
255
256    /// [`path_name`](Self::path_name) as UTF-8, replacing invalid sequences.
257    pub fn path_name_lossy(self) -> Cow<'a, str> {
258        String::from_utf8_lossy(self.path_name())
259    }
260
261    /// The file's log number, RocksDB's primary identifier for it. It grows
262    /// with creation time, so a higher number means a newer file.
263    pub fn log_number(self) -> u64 {
264        unsafe { ffi::rocksdb_wal_file_log_number(self.inner) }
265    }
266
267    /// Position of the last flushed write in the file, which for a recycled WAL
268    /// is usually less than the file's size on disk.
269    pub fn size_file_bytes(self) -> u64 {
270        unsafe { ffi::rocksdb_wal_file_size_file_bytes(self.inner) }
271    }
272
273    /// Sequence number of the first write batch in the file.
274    ///
275    /// Always 0 for the file [`get_current_wal_file`] returns. RocksDB reads the first
276    /// batch to find this, which it only does for the files it has stopped writing to, so
277    /// the live WAL is reported with a placeholder rather than a real sequence number.
278    ///
279    /// [`get_current_wal_file`]: crate::DBCommon::get_current_wal_file
280    pub fn start_sequence(self) -> u64 {
281        unsafe { ffi::rocksdb_wal_file_start_sequence(self.inner) }
282    }
283
284    /// Whether the file is still live or has been archived.
285    pub fn file_type(self) -> WalFileType {
286        WalFileType::from(unsafe { ffi::rocksdb_wal_file_type(self.inner) })
287    }
288}
289
290impl fmt::Debug for WalFile<'_> {
291    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
292        f.debug_struct("WalFile")
293            .field("path_name", &self.path_name_lossy())
294            .field("log_number", &self.log_number())
295            .field("size_file_bytes", &self.size_file_bytes())
296            .field("start_sequence", &self.start_sequence())
297            .field("file_type", &self.file_type())
298            .finish_non_exhaustive()
299    }
300}
301
302/// A WAL file handle that owns its own copy of the metadata.
303///
304/// This is what asking for the current WAL file gives back: `db/c.cc` allocates
305/// a fresh `rocksdb_wal_file_t` for it rather than pointing into a listing, so
306/// it has to be freed on its own. Borrow it as a [`WalFile`] with
307/// [`as_wal_file`](Self::as_wal_file) to share code with entries that came out
308/// of a [`WalFiles`] listing.
309pub struct OwnedWalFile {
310    inner: *mut ffi::rocksdb_wal_file_t,
311}
312
313// SAFETY: the pointee is a plain struct of a `std::string` and four integers
314// that RocksDB fills in once, and this type only reads it.
315unsafe impl Send for OwnedWalFile {}
316unsafe impl Sync for OwnedWalFile {}
317
318impl OwnedWalFile {
319    /// Takes ownership of a raw WAL file handle.
320    ///
321    /// # Safety
322    ///
323    /// `ptr` must be a non-null handle from `rocksdb_get_current_wal_file`
324    /// that nothing else owns. It is destroyed when the returned value drops,
325    /// so do not pass a pointer borrowed from a `rocksdb_wal_files_t`.
326    pub(crate) unsafe fn from_ptr(ptr: *mut ffi::rocksdb_wal_file_t) -> Self {
327        Self { inner: ptr }
328    }
329
330    /// Borrows this handle as a [`WalFile`] view.
331    pub fn as_wal_file(&self) -> WalFile<'_> {
332        WalFile {
333            inner: self.inner.cast_const(),
334            _files: PhantomData,
335        }
336    }
337
338    /// See [`WalFile::path_name`].
339    pub fn path_name(&self) -> &[u8] {
340        self.as_wal_file().path_name()
341    }
342
343    /// See [`WalFile::path_name_lossy`].
344    pub fn path_name_lossy(&self) -> Cow<'_, str> {
345        self.as_wal_file().path_name_lossy()
346    }
347
348    /// See [`WalFile::log_number`].
349    pub fn log_number(&self) -> u64 {
350        self.as_wal_file().log_number()
351    }
352
353    /// See [`WalFile::size_file_bytes`].
354    pub fn size_file_bytes(&self) -> u64 {
355        self.as_wal_file().size_file_bytes()
356    }
357
358    /// See [`WalFile::start_sequence`].
359    pub fn start_sequence(&self) -> u64 {
360        self.as_wal_file().start_sequence()
361    }
362
363    /// See [`WalFile::file_type`].
364    pub fn file_type(&self) -> WalFileType {
365        self.as_wal_file().file_type()
366    }
367}
368
369impl Drop for OwnedWalFile {
370    fn drop(&mut self) {
371        unsafe { ffi::rocksdb_wal_file_destroy(self.inner) }
372    }
373}
374
375impl fmt::Debug for OwnedWalFile {
376    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
377        f.debug_struct("OwnedWalFile")
378            .field("path_name", &self.path_name_lossy())
379            .field("log_number", &self.log_number())
380            .field("size_file_bytes", &self.size_file_bytes())
381            .field("start_sequence", &self.start_sequence())
382            .field("file_type", &self.file_type())
383            .finish_non_exhaustive()
384    }
385}