Skip to main content

rust_rocksdb/
compaction.rs

1// Licensed under the Apache License, Version 2.0 (the "License");
2// you may not use this file except in compliance with the License.
3// You may obtain a copy of the License at
4//
5// http://www.apache.org/licenses/LICENSE-2.0
6//
7// Unless required by applicable law or agreed to in writing, software
8// distributed under the License is distributed on an "AS IS" BASIS,
9// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10// See the License for the specific language governing permissions and
11// limitations under the License.
12//
13
14//! Inputs to a manual compaction and read only views of what one did.
15//!
16//! Two independent halves live here.
17//!
18//! [`CompactionOptions`] is the owned options object for `CompactFiles`, the manual
19//! compaction entry point that takes an explicit list of input files. It carries the output
20//! compression, the output file size limit, the subcompaction count, the trivial move
21//! switch, the output temperature override, and an optional [`CompactionCancellationToken`]
22//! that lets another thread abort the job while it runs.
23//!
24//! Everything else is a borrowed view over data RocksDB hands to an event listener:
25//! [`CompactionJobStats`], [`CompactionFileInfo`], [`BlobFileAdditionInfo`], and
26//! [`BlobFileGarbageInfo`]. You never build or own one of these. RocksDB owns the underlying
27//! object and it stays alive only as long as the job info it was read from, so the `'a`
28//! lifetime ties each view and every byte slice it hands back to that borrow.
29//!
30//! String-like getters return raw bytes rather than `str`. RocksDB does not guarantee UTF-8
31//! for key prefixes or file paths, and the slices point straight into the C++ strings, so
32//! reading them copies and allocates nothing.
33
34use std::marker::PhantomData;
35use std::sync::Arc;
36
37use libc::{c_char, c_int, c_uchar};
38
39use crate::ffi_util::bytes_from_raw;
40use crate::{DBCompressionType, Temperature, ffi};
41
42/// `kDisableCompressionOption` from `include/rocksdb/compression_type.h`.
43///
44/// This is the default for `CompactionOptions::compression` and is not a compression
45/// algorithm. It tells RocksDB to pick the output compression from the column family
46/// options instead.
47const DISABLE_COMPRESSION_OPTION: c_int = 0xff;
48
49/// A cancellation flag that aborts an in progress `CompactFiles` job.
50///
51/// Create one, hand it to [`CompactionOptions::set_canceled`], keep a clone of the [`Arc`]
52/// somewhere else, and call [`cancel`](Self::cancel) from that other thread to stop the
53/// compaction. Cancellation is one shot and best effort. The compaction iterator checks the
54/// flag as it walks the input, so the job stops at the next check rather than immediately,
55/// and upstream notes that cancellation can be delayed waiting on automatic compactions when
56/// `exclusive_manual_compaction` is set.
57///
58/// There is no C API to read the flag back, so this wrapper is write only and there is no
59/// way to un-cancel. Use a fresh token per compaction.
60pub struct CompactionCancellationToken {
61    inner: *mut c_uchar,
62}
63
64// SAFETY: the `unsigned char*` in the C API is a lie of convenience. Every access treats it
65// as a `std::atomic<bool>*`: `rocksdb_compaction_options_canceled_create` allocates one with
66// `new std::atomic<bool>(false)` (db/c.cc:7236), `rocksdb_compaction_options_canceled_set`
67// does an atomic store through it (db/c.cc:7248), the compaction thread reads it with
68// `manual_compaction_canceled_.load(std::memory_order_relaxed)`
69// (db/compaction/compaction_iterator.h:653), and `..._canceled_destroy` deletes it as
70// `std::atomic<bool>*` (db/c.cc:7241). Setting the flag on one thread while a background
71// compaction polls it is therefore an atomic access rather than a data race, which is the
72// whole point of the token, so sharing `&CompactionCancellationToken` across threads is
73// sound. Moving one between threads is sound for the same reason.
74unsafe impl Send for CompactionCancellationToken {}
75unsafe impl Sync for CompactionCancellationToken {}
76
77impl Default for CompactionCancellationToken {
78    fn default() -> Self {
79        Self::new()
80    }
81}
82
83impl CompactionCancellationToken {
84    /// Allocates a fresh token in the not cancelled state.
85    pub fn new() -> Self {
86        let inner = unsafe { ffi::rocksdb_compaction_options_canceled_create() };
87        assert!(
88            !inner.is_null(),
89            "Could not create RocksDB compaction cancellation token"
90        );
91        Self { inner }
92    }
93
94    /// Signals that the compaction using this token should stop.
95    ///
96    /// Returns as soon as the flag is stored. The compaction winds down on its own thread and
97    /// the `CompactFiles` call it belongs to then fails with
98    /// [`ErrorKind::Incomplete`](crate::ErrorKind::Incomplete) and the message
99    /// `Result incomplete: Manual compaction paused`.
100    pub fn cancel(&self) {
101        unsafe {
102            ffi::rocksdb_compaction_options_canceled_set(self.inner, 1);
103        }
104    }
105
106    /// The raw flag pointer, for handing to `rocksdb_compaction_options_set_canceled`.
107    fn as_ptr(&self) -> *mut c_uchar {
108        self.inner
109    }
110}
111
112impl Drop for CompactionCancellationToken {
113    fn drop(&mut self) {
114        unsafe {
115            ffi::rocksdb_compaction_options_canceled_destroy(self.inner);
116        }
117    }
118}
119
120/// Options for a `CompactFiles` call, which compacts an explicit list of input files.
121///
122/// These are RocksDB's `CompactionOptions` from `include/rocksdb/options.h`, not the
123/// `CompactRangeOptions` behind [`CompactOptions`](crate::CompactOptions).
124pub struct CompactionOptions {
125    pub(crate) inner: *mut ffi::rocksdb_compaction_options_t,
126    /// Keeps the cancellation flag alive for as long as the C struct points at it. See
127    /// [`Self::set_canceled`] for why this is an `Arc` and not a lifetime.
128    canceled: Option<Arc<CompactionCancellationToken>>,
129}
130
131// SAFETY: the C struct is a plain `CompactionOptions` value (db/c.cc:453) with no interior
132// mutability and no thread affinity. Every setter here takes `&mut self`, so the raw pointer
133// is never aliased mutably, and the getters only read. The cancellation flag it can point at
134// is an atomic owned by a `Send + Sync` token.
135unsafe impl Send for CompactionOptions {}
136unsafe impl Sync for CompactionOptions {}
137
138impl Default for CompactionOptions {
139    fn default() -> Self {
140        let inner = unsafe { ffi::rocksdb_compaction_options_create() };
141        assert!(
142            !inner.is_null(),
143            "Could not create RocksDB compaction options"
144        );
145        Self {
146            inner,
147            canceled: None,
148        }
149    }
150}
151
152impl Drop for CompactionOptions {
153    fn drop(&mut self) {
154        // The C struct holds a borrowed pointer to the token's flag, so it has to go first.
155        // `canceled` is dropped after this body returns, which is the right order.
156        unsafe {
157            ffi::rocksdb_compaction_options_destroy(self.inner);
158        }
159    }
160}
161
162impl CompactionOptions {
163    /// Sets the compression used for the compaction output.
164    ///
165    /// Deprecated upstream, because the `CompressionOptions` still come from the column
166    /// family options and so the algorithm picked here can end up paired with tuning meant
167    /// for a different one. Unset by default. [`Self::unset_compression`] puts it back.
168    pub fn set_compression(&mut self, t: DBCompressionType) {
169        unsafe {
170            ffi::rocksdb_compaction_options_set_compression(self.inner, t as c_int);
171        }
172    }
173
174    /// Restores the default, letting RocksDB choose the output compression from the column
175    /// family options.
176    ///
177    /// RocksDB takes the output level into account, so level specific settings still apply.
178    pub fn unset_compression(&mut self) {
179        unsafe {
180            ffi::rocksdb_compaction_options_set_compression(self.inner, DISABLE_COMPRESSION_OPTION);
181        }
182    }
183
184    /// The compression set for the compaction output, or `None` when RocksDB will pick it
185    /// from the column family options.
186    ///
187    /// `None` also covers a compression type this crate does not name, currently only xpress,
188    /// which is Windows only.
189    pub fn get_compression(&self) -> Option<DBCompressionType> {
190        let raw = unsafe { ffi::rocksdb_compaction_options_get_compression(self.inner) };
191        DBCompressionType::try_from_raw(raw)
192    }
193
194    /// Caps the size of each file the compaction creates.
195    ///
196    /// Defaults to `u64::MAX`, which means the compaction writes a single output file.
197    pub fn set_output_file_size_limit(&mut self, v: u64) {
198        unsafe {
199            ffi::rocksdb_compaction_options_set_output_file_size_limit(self.inner, v);
200        }
201    }
202
203    /// The current output file size limit.
204    pub fn get_output_file_size_limit(&self) -> u64 {
205        unsafe { ffi::rocksdb_compaction_options_get_output_file_size_limit(self.inner) }
206    }
207
208    /// Overrides `DBOptions::max_subcompactions` for this compaction when greater than 0.
209    ///
210    /// Defaults to 0, meaning the DB level setting wins.
211    pub fn set_max_subcompactions(&mut self, v: u32) {
212        unsafe {
213            ffi::rocksdb_compaction_options_set_max_subcompactions(self.inner, v);
214        }
215    }
216
217    /// The current subcompaction override, 0 when the DB level setting is in effect.
218    pub fn get_max_subcompactions(&self) -> u32 {
219        unsafe { ffi::rocksdb_compaction_options_get_max_subcompactions(self.inner) }
220    }
221
222    /// Lets the compaction move non overlapping input files to the output level instead of
223    /// rewriting them.
224    ///
225    /// Defaults to false.
226    pub fn set_allow_trivial_move(&mut self, v: bool) {
227        unsafe {
228            ffi::rocksdb_compaction_options_set_allow_trivial_move(self.inner, c_uchar::from(v));
229        }
230    }
231
232    /// Whether trivial moves are allowed for this compaction.
233    pub fn get_allow_trivial_move(&self) -> bool {
234        unsafe { ffi::rocksdb_compaction_options_get_allow_trivial_move(self.inner) != 0 }
235    }
236
237    /// Writes the output files with this file temperature.
238    ///
239    /// Leaving it at the default [`Temperature::Unknown`] means no override: the output
240    /// falls back to `last_level_temperature` when the output level is the last level and
241    /// to `default_write_temperature` otherwise.
242    pub fn set_output_temperature_override(&mut self, v: Temperature) {
243        unsafe {
244            ffi::rocksdb_compaction_options_set_output_temperature_override(self.inner, v as c_int);
245        }
246    }
247
248    /// The output temperature override, [`Temperature::Unknown`] when nothing is
249    /// overridden.
250    pub fn get_output_temperature_override(&self) -> Temperature {
251        let raw =
252            unsafe { ffi::rocksdb_compaction_options_get_output_temperature_override(self.inner) };
253        Temperature::from(raw)
254    }
255
256    /// Attaches a cancellation token so another thread can abort this compaction.
257    ///
258    /// The C side stores the token as a borrowed `std::atomic<bool>*` inside the options
259    /// struct, so the flag has to outlive both these options and the `CompactFiles` call that
260    /// reads them. Holding an [`Arc`] clone enforces that at runtime and keeps
261    /// `CompactionOptions` free of a lifetime parameter, which would otherwise spread to
262    /// every signature that passes the options around. Sharing the token is also the normal
263    /// case, since something on another thread has to own a handle in order to cancel, and
264    /// that already wants an `Arc`.
265    pub fn set_canceled(&mut self, token: Arc<CompactionCancellationToken>) {
266        let ptr = token.as_ptr();
267        // Point the C struct at the new flag before replacing the field, so a token being
268        // swapped out is only released once nothing references it.
269        unsafe {
270            ffi::rocksdb_compaction_options_set_canceled(self.inner, ptr);
271        }
272        self.canceled = Some(token);
273    }
274
275    /// Detaches the cancellation token, if any, and releases this object's share of it.
276    pub fn clear_canceled(&mut self) {
277        if self.canceled.is_none() {
278            return;
279        }
280        unsafe {
281            ffi::rocksdb_compaction_options_set_canceled(self.inner, std::ptr::null_mut());
282        }
283        self.canceled = None;
284    }
285
286    /// The cancellation token attached by [`Self::set_canceled`], if there is one.
287    ///
288    /// Handed back as the [`Arc`] so you can clone another handle out of it.
289    pub fn canceled(&self) -> Option<&Arc<CompactionCancellationToken>> {
290        self.canceled.as_ref()
291    }
292}
293
294/// Shared signature of the `rocksdb_compaction_job_stats_*_output_key_prefix` getters.
295type KeyPrefixGetter =
296    unsafe extern "C" fn(*const ffi::rocksdb_compaction_job_stats_t, *mut usize) -> *const c_char;
297
298/// What one compaction job did, borrowed from the event that reported it.
299///
300/// Read from a compaction job info or a subcompaction job info. Counters that are not
301/// applicable to the compaction, or that RocksDB was not asked to collect, read back as 0.
302pub struct CompactionJobStats<'a> {
303    inner: *const ffi::rocksdb_compaction_job_stats_t,
304    _marker: PhantomData<&'a ()>,
305}
306
307impl<'a> CompactionJobStats<'a> {
308    /// Wraps a compaction job stats pointer owned by RocksDB.
309    ///
310    /// # Safety
311    ///
312    /// `inner` must point to a live `rocksdb_compaction_job_stats_t` that stays valid for all
313    /// of `'a`. RocksDB owns the object, so the caller must never free it and must not pick
314    /// an `'a` that outlives the compaction or subcompaction job info it was read from.
315    pub(crate) unsafe fn from_ptr(
316        inner: *const ffi::rocksdb_compaction_job_stats_t,
317    ) -> CompactionJobStats<'a> {
318        CompactionJobStats {
319            inner,
320            _marker: PhantomData,
321        }
322    }
323
324    /// Wall clock time this compaction took, in microseconds.
325    pub fn elapsed_micros(&self) -> u64 {
326        unsafe { ffi::rocksdb_compaction_job_stats_elapsed_micros(self.inner) }
327    }
328
329    /// CPU time this compaction took, in microseconds.
330    pub fn cpu_micros(&self) -> u64 {
331        unsafe { ffi::rocksdb_compaction_job_stats_cpu_micros(self.inner) }
332    }
333
334    /// Whether [`Self::num_input_records`] is accurate across all subcompactions.
335    pub fn has_accurate_num_input_records(&self) -> bool {
336        unsafe { ffi::rocksdb_compaction_job_stats_has_accurate_num_input_records(self.inner) != 0 }
337    }
338
339    /// Number of compaction input records. Only trustworthy when
340    /// [`Self::has_accurate_num_input_records`] is true.
341    pub fn num_input_records(&self) -> u64 {
342        unsafe { ffi::rocksdb_compaction_job_stats_num_input_records(self.inner) }
343    }
344
345    /// Number of blobs read from blob files.
346    pub fn num_blobs_read(&self) -> u64 {
347        unsafe { ffi::rocksdb_compaction_job_stats_num_blobs_read(self.inner) }
348    }
349
350    /// Number of compaction input files, counting table files only.
351    pub fn num_input_files(&self) -> usize {
352        unsafe { ffi::rocksdb_compaction_job_stats_num_input_files(self.inner) }
353    }
354
355    /// Number of compaction input table files that were already at the output level.
356    pub fn num_input_files_at_output_level(&self) -> usize {
357        unsafe { ffi::rocksdb_compaction_job_stats_num_input_files_at_output_level(self.inner) }
358    }
359
360    /// Number of compaction input files filtered out by compaction optimizations.
361    pub fn num_filtered_input_files(&self) -> usize {
362        unsafe { ffi::rocksdb_compaction_job_stats_num_filtered_input_files(self.inner) }
363    }
364
365    /// Number of compaction input files at the output level that were filtered out by
366    /// compaction optimizations.
367    pub fn num_filtered_input_files_at_output_level(&self) -> usize {
368        unsafe {
369            ffi::rocksdb_compaction_job_stats_num_filtered_input_files_at_output_level(self.inner)
370        }
371    }
372
373    /// Number of compaction output records.
374    pub fn num_output_records(&self) -> u64 {
375        unsafe { ffi::rocksdb_compaction_job_stats_num_output_records(self.inner) }
376    }
377
378    /// Number of compaction output table files.
379    pub fn num_output_files(&self) -> usize {
380        unsafe { ffi::rocksdb_compaction_job_stats_num_output_files(self.inner) }
381    }
382
383    /// Number of compaction output blob files.
384    pub fn num_output_files_blob(&self) -> usize {
385        unsafe { ffi::rocksdb_compaction_job_stats_num_output_files_blob(self.inner) }
386    }
387
388    /// Whether this was a full compaction, meaning every live SST file was an input.
389    pub fn is_full_compaction(&self) -> bool {
390        unsafe { ffi::rocksdb_compaction_job_stats_is_full_compaction(self.inner) != 0 }
391    }
392
393    /// Whether this was a manual compaction.
394    pub fn is_manual_compaction(&self) -> bool {
395        unsafe { ffi::rocksdb_compaction_job_stats_is_manual_compaction(self.inner) != 0 }
396    }
397
398    /// Whether the compaction ran in a remote worker.
399    ///
400    /// Only the compaction completed event carries the truth. On the compaction begin event
401    /// RocksDB sets this to true whenever a `compaction_service` is configured, before it
402    /// knows whether the job will really be scheduled remotely or fall back to local.
403    pub fn is_remote_compaction(&self) -> bool {
404        unsafe { ffi::rocksdb_compaction_job_stats_is_remote_compaction(self.inner) != 0 }
405    }
406
407    /// Total size of the table files in the compaction input.
408    pub fn total_input_bytes(&self) -> u64 {
409        unsafe { ffi::rocksdb_compaction_job_stats_total_input_bytes(self.inner) }
410    }
411
412    /// Total size of the input table files that were skipped because compaction
413    /// optimizations filtered them out.
414    pub fn total_skipped_input_bytes(&self) -> u64 {
415        unsafe { ffi::rocksdb_compaction_job_stats_total_skipped_input_bytes(self.inner) }
416    }
417
418    /// Total size of the blobs read from blob files.
419    pub fn total_blob_bytes_read(&self) -> u64 {
420        unsafe { ffi::rocksdb_compaction_job_stats_total_blob_bytes_read(self.inner) }
421    }
422
423    /// Total size of the table files in the compaction output.
424    pub fn total_output_bytes(&self) -> u64 {
425        unsafe { ffi::rocksdb_compaction_job_stats_total_output_bytes(self.inner) }
426    }
427
428    /// Total size of the blob files in the compaction output.
429    pub fn total_output_bytes_blob(&self) -> u64 {
430        unsafe { ffi::rocksdb_compaction_job_stats_total_output_bytes_blob(self.inner) }
431    }
432
433    /// Number of input files that were trivially moved rather than rewritten.
434    pub fn num_input_files_trivially_moved(&self) -> usize {
435        unsafe { ffi::rocksdb_compaction_job_stats_num_input_files_trivially_moved(self.inner) }
436    }
437
438    /// Number of records superseded by a newer record for the same key. Counts both updates
439    /// and deletions.
440    pub fn num_records_replaced(&self) -> u64 {
441        unsafe { ffi::rocksdb_compaction_job_stats_num_records_replaced(self.inner) }
442    }
443
444    /// Sum of the uncompressed input keys, in bytes.
445    pub fn total_input_raw_key_bytes(&self) -> u64 {
446        unsafe { ffi::rocksdb_compaction_job_stats_total_input_raw_key_bytes(self.inner) }
447    }
448
449    /// Sum of the uncompressed input values, in bytes.
450    pub fn total_input_raw_value_bytes(&self) -> u64 {
451        unsafe { ffi::rocksdb_compaction_job_stats_total_input_raw_value_bytes(self.inner) }
452    }
453
454    /// Number of deletion entries before the compaction. Deletion entries can disappear
455    /// during compaction because they expired.
456    pub fn num_input_deletion_records(&self) -> u64 {
457        unsafe { ffi::rocksdb_compaction_job_stats_num_input_deletion_records(self.inner) }
458    }
459
460    /// Number of deletion records dropped as obsolete because every deletion they could still
461    /// cause has already happened.
462    pub fn num_expired_deletion_records(&self) -> u64 {
463        unsafe { ffi::rocksdb_compaction_job_stats_num_expired_deletion_records(self.inner) }
464    }
465
466    /// Number of corrupt keys encountered and written out, meaning keys that failed to parse
467    /// as internal keys.
468    pub fn num_corrupt_keys(&self) -> u64 {
469        unsafe { ffi::rocksdb_compaction_job_stats_num_corrupt_keys(self.inner) }
470    }
471
472    /// Time spent in file `Append` calls, in nanoseconds.
473    ///
474    /// Only populated when
475    /// [`report_bg_io_stats`](crate::Options::set_report_bg_io_stats) is on.
476    pub fn file_write_nanos(&self) -> u64 {
477        unsafe { ffi::rocksdb_compaction_job_stats_file_write_nanos(self.inner) }
478    }
479
480    /// Time spent syncing file ranges, in nanoseconds.
481    ///
482    /// Only populated when
483    /// [`report_bg_io_stats`](crate::Options::set_report_bg_io_stats) is on.
484    pub fn file_range_sync_nanos(&self) -> u64 {
485        unsafe { ffi::rocksdb_compaction_job_stats_file_range_sync_nanos(self.inner) }
486    }
487
488    /// Time spent in file fsync, in nanoseconds.
489    ///
490    /// Only populated when
491    /// [`report_bg_io_stats`](crate::Options::set_report_bg_io_stats) is on.
492    pub fn file_fsync_nanos(&self) -> u64 {
493        unsafe { ffi::rocksdb_compaction_job_stats_file_fsync_nanos(self.inner) }
494    }
495
496    /// Time spent preparing file writes, such as `fallocate`, in nanoseconds.
497    ///
498    /// Only populated when
499    /// [`report_bg_io_stats`](crate::Options::set_report_bg_io_stats) is on.
500    pub fn file_prepare_write_nanos(&self) -> u64 {
501        unsafe { ffi::rocksdb_compaction_job_stats_file_prepare_write_nanos(self.inner) }
502    }
503
504    /// First 8 bytes of the smallest user key in the output, or fewer if the key is shorter.
505    ///
506    /// Empty when the compaction wrote no table files.
507    pub fn smallest_output_key_prefix(&self) -> &'a [u8] {
508        self.key_prefix(ffi::rocksdb_compaction_job_stats_smallest_output_key_prefix)
509    }
510
511    /// First 8 bytes of the largest user key in the output, or fewer if the key is shorter.
512    ///
513    /// Empty when the compaction wrote no table files.
514    pub fn largest_output_key_prefix(&self) -> &'a [u8] {
515        self.key_prefix(ffi::rocksdb_compaction_job_stats_largest_output_key_prefix)
516    }
517
518    /// Number of single deletes that did not meet a put.
519    pub fn num_single_del_fallthru(&self) -> u64 {
520        unsafe { ffi::rocksdb_compaction_job_stats_num_single_del_fallthru(self.inner) }
521    }
522
523    /// Number of single deletes that met something other than a put.
524    pub fn num_single_del_mismatch(&self) -> u64 {
525        unsafe { ffi::rocksdb_compaction_job_stats_num_single_del_mismatch(self.inner) }
526    }
527
528    /// Reads one of the borrowed output key prefixes as raw bytes.
529    fn key_prefix(&self, getter: KeyPrefixGetter) -> &'a [u8] {
530        let mut len: usize = 0;
531        // SAFETY: `self.inner` is valid for `'a` and the getter writes the byte length
532        // through `len`, returning an interior pointer into a string RocksDB owns.
533        unsafe {
534            let ptr = getter(self.inner, &raw mut len);
535            bytes_from_raw(ptr, len)
536        }
537    }
538}
539
540/// One input or output file of a compaction, borrowed from the job info that listed it.
541pub struct CompactionFileInfo<'a> {
542    inner: *const ffi::rocksdb_compaction_file_info_t,
543    _marker: PhantomData<&'a ()>,
544}
545
546impl<'a> CompactionFileInfo<'a> {
547    /// Wraps a compaction file info pointer owned by RocksDB.
548    ///
549    /// # Safety
550    ///
551    /// `inner` must point to a live `rocksdb_compaction_file_info_t` that stays valid for all
552    /// of `'a`. RocksDB owns the object, so the caller must never free it and must not pick
553    /// an `'a` that outlives the compaction job info it was read from.
554    pub(crate) unsafe fn from_ptr(
555        inner: *const ffi::rocksdb_compaction_file_info_t,
556    ) -> CompactionFileInfo<'a> {
557        CompactionFileInfo {
558            inner,
559            _marker: PhantomData,
560        }
561    }
562
563    /// File number of this file.
564    pub fn file_number(&self) -> u64 {
565        unsafe { ffi::rocksdb_compaction_file_info_file_number(self.inner) }
566    }
567
568    /// LSM level this file sits at.
569    pub fn level(&self) -> i32 {
570        unsafe { ffi::rocksdb_compaction_file_info_level(self.inner) }
571    }
572
573    /// File number of the oldest blob file this SST file references, or 0 when it references
574    /// no blob file.
575    pub fn oldest_blob_file_number(&self) -> u64 {
576        unsafe { ffi::rocksdb_compaction_file_info_oldest_blob_file_number(self.inner) }
577    }
578}
579
580/// A blob file created by a flush or compaction, borrowed from the job info that listed it.
581pub struct BlobFileAdditionInfo<'a> {
582    inner: *const ffi::rocksdb_blob_file_addition_info_t,
583    _marker: PhantomData<&'a ()>,
584}
585
586impl<'a> BlobFileAdditionInfo<'a> {
587    /// Wraps a blob file addition info pointer owned by RocksDB.
588    ///
589    /// # Safety
590    ///
591    /// `inner` must point to a live `rocksdb_blob_file_addition_info_t` that stays valid for
592    /// all of `'a`. RocksDB owns the object, so the caller must never free it and must not
593    /// pick an `'a` that outlives the flush or compaction job info it was read from.
594    pub(crate) unsafe fn from_ptr(
595        inner: *const ffi::rocksdb_blob_file_addition_info_t,
596    ) -> BlobFileAdditionInfo<'a> {
597        BlobFileAdditionInfo {
598            inner,
599            _marker: PhantomData,
600        }
601    }
602
603    /// Path of the blob file, borrowed as raw bytes.
604    pub fn blob_file_path(&self) -> &'a [u8] {
605        let mut len: usize = 0;
606        // SAFETY: `self.inner` is valid for `'a` and the getter writes the byte length
607        // through `len`, returning an interior pointer into a string RocksDB owns.
608        unsafe {
609            let ptr = ffi::rocksdb_blob_file_addition_info_blob_file_path(self.inner, &raw mut len);
610            bytes_from_raw(ptr, len)
611        }
612    }
613
614    /// File number of the blob file.
615    pub fn blob_file_number(&self) -> u64 {
616        unsafe { ffi::rocksdb_blob_file_addition_info_blob_file_number(self.inner) }
617    }
618
619    /// Number of blobs written to the file.
620    pub fn total_blob_count(&self) -> u64 {
621        unsafe { ffi::rocksdb_blob_file_addition_info_total_blob_count(self.inner) }
622    }
623
624    /// Total size of the blobs written to the file, in bytes.
625    pub fn total_blob_bytes(&self) -> u64 {
626        unsafe { ffi::rocksdb_blob_file_addition_info_total_blob_bytes(self.inner) }
627    }
628}
629
630/// Garbage a compaction produced in an existing blob file, borrowed from the job info that
631/// listed it.
632///
633/// A blob becomes garbage when the SST entry that referenced it is dropped or rewritten, so
634/// these counts are what blob garbage collection later reclaims.
635pub struct BlobFileGarbageInfo<'a> {
636    inner: *const ffi::rocksdb_blob_file_garbage_info_t,
637    _marker: PhantomData<&'a ()>,
638}
639
640impl<'a> BlobFileGarbageInfo<'a> {
641    /// Wraps a blob file garbage info pointer owned by RocksDB.
642    ///
643    /// # Safety
644    ///
645    /// `inner` must point to a live `rocksdb_blob_file_garbage_info_t` that stays valid for
646    /// all of `'a`. RocksDB owns the object, so the caller must never free it and must not
647    /// pick an `'a` that outlives the compaction job info it was read from.
648    pub(crate) unsafe fn from_ptr(
649        inner: *const ffi::rocksdb_blob_file_garbage_info_t,
650    ) -> BlobFileGarbageInfo<'a> {
651        BlobFileGarbageInfo {
652            inner,
653            _marker: PhantomData,
654        }
655    }
656
657    /// Path of the blob file, borrowed as raw bytes.
658    pub fn blob_file_path(&self) -> &'a [u8] {
659        let mut len: usize = 0;
660        // SAFETY: `self.inner` is valid for `'a` and the getter writes the byte length
661        // through `len`, returning an interior pointer into a string RocksDB owns.
662        unsafe {
663            let ptr = ffi::rocksdb_blob_file_garbage_info_blob_file_path(self.inner, &raw mut len);
664            bytes_from_raw(ptr, len)
665        }
666    }
667
668    /// File number of the blob file.
669    pub fn blob_file_number(&self) -> u64 {
670        unsafe { ffi::rocksdb_blob_file_garbage_info_blob_file_number(self.inner) }
671    }
672
673    /// Number of blobs in the file this compaction turned into garbage.
674    pub fn garbage_blob_count(&self) -> u64 {
675        unsafe { ffi::rocksdb_blob_file_garbage_info_garbage_blob_count(self.inner) }
676    }
677
678    /// Total size of the blobs this compaction turned into garbage, in bytes.
679    pub fn garbage_blob_bytes(&self) -> u64 {
680        unsafe { ffi::rocksdb_blob_file_garbage_info_garbage_blob_bytes(self.inner) }
681    }
682}