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}