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}