Skip to main content

rust_rocksdb/
table_properties.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//! Read only properties of a single SST file.
15//!
16//! [`TableProperties`] is the wrapper over RocksDB's `TableProperties`: the sizes, counts,
17//! and names recorded when the file was written, plus anything a `TablePropertiesCollector`
18//! added on top.
19//!
20//! You never build or own one of these. RocksDB hands out a borrowed pointer from a flush
21//! job info, a compaction job info, or an external file ingestion info, and the properties
22//! stay alive only as long as that event object does. The `'a` lifetime ties the wrapper
23//! and every byte slice it hands back to that borrow, so nothing here outlives the callback
24//! it came from.
25//!
26//! String-like getters return raw bytes rather than `str`. RocksDB does not guarantee UTF-8
27//! for user collected properties, and the built in names are only ASCII by convention. The
28//! slices point straight into the C++ strings, so reading them copies and allocates nothing.
29
30use std::marker::PhantomData;
31
32use libc::c_char;
33
34use crate::ffi;
35use crate::ffi_util::bytes_from_raw;
36
37/// Shared signature of the `rocksdb_table_properties_*` string getters.
38type StringGetter =
39    unsafe extern "C" fn(*const ffi::rocksdb_table_properties_t, *mut usize) -> *const c_char;
40
41/// Shared signature of the property map key and value accessors.
42type MapEntryGetter = unsafe extern "C" fn(
43    *const ffi::rocksdb_table_properties_t,
44    usize,
45    *mut usize,
46) -> *const c_char;
47
48/// Properties of one SST file, borrowed from the event that produced it.
49pub struct TableProperties<'a> {
50    inner: *const ffi::rocksdb_table_properties_t,
51    _marker: PhantomData<&'a ()>,
52}
53
54impl<'a> TableProperties<'a> {
55    /// Wraps a table properties pointer owned by RocksDB.
56    ///
57    /// # Safety
58    ///
59    /// `inner` must point to a live `rocksdb_table_properties_t` that stays valid for all of
60    /// `'a`. RocksDB owns the object, so the caller must never free it and must not pick an
61    /// `'a` that outlives the flush, compaction, or ingestion info it was read from.
62    pub(crate) unsafe fn from_ptr(
63        inner: *const ffi::rocksdb_table_properties_t,
64    ) -> TableProperties<'a> {
65        TableProperties {
66            inner,
67            _marker: PhantomData,
68        }
69    }
70
71    /// File number at creation time, or 0 when unknown. When known it identifies the SST file
72    /// uniquely in combination with [`Self::db_session_id`].
73    pub fn orig_file_number(&self) -> u64 {
74        unsafe { ffi::rocksdb_table_properties_orig_file_number(self.inner) }
75    }
76
77    /// Total size of all data blocks.
78    pub fn data_size(&self) -> u64 {
79        unsafe { ffi::rocksdb_table_properties_data_size(self.inner) }
80    }
81
82    /// Total uncompressed size of all data blocks. Recorded since RocksDB 10.7.
83    pub fn uncompressed_data_size(&self) -> u64 {
84        unsafe { ffi::rocksdb_table_properties_uncompressed_data_size(self.inner) }
85    }
86
87    /// Size of the index block.
88    pub fn index_size(&self) -> u64 {
89        unsafe { ffi::rocksdb_table_properties_index_size(self.inner) }
90    }
91
92    /// Number of index partitions, set only when the two level index search is used.
93    pub fn index_partitions(&self) -> u64 {
94        unsafe { ffi::rocksdb_table_properties_index_partitions(self.inner) }
95    }
96
97    /// Size of the top level index, set only when the two level index search is used.
98    pub fn top_level_index_size(&self) -> u64 {
99        unsafe { ffi::rocksdb_table_properties_top_level_index_size(self.inner) }
100    }
101
102    /// Whether index keys are plain user keys. When false they also carry the 8 byte sequence
103    /// number of the internal key format.
104    pub fn index_key_is_user_key(&self) -> bool {
105        unsafe { ffi::rocksdb_table_properties_index_key_is_user_key(self.inner) != 0 }
106    }
107
108    /// Whether index values are delta encoded.
109    pub fn index_value_is_delta_encoded(&self) -> bool {
110        unsafe { ffi::rocksdb_table_properties_index_value_is_delta_encoded(self.inner) != 0 }
111    }
112
113    /// Whether the UDI is the primary index for reads. The standard index is still fully
114    /// populated alongside it.
115    pub fn udi_is_primary_index(&self) -> bool {
116        unsafe { ffi::rocksdb_table_properties_udi_is_primary_index(self.inner) != 0 }
117    }
118
119    /// Size of the filter block.
120    pub fn filter_size(&self) -> u64 {
121        unsafe { ffi::rocksdb_table_properties_filter_size(self.inner) }
122    }
123
124    /// Total key size before compression and block encoding.
125    pub fn raw_key_size(&self) -> u64 {
126        unsafe { ffi::rocksdb_table_properties_raw_key_size(self.inner) }
127    }
128
129    /// Total value size before compression and block encoding.
130    pub fn raw_value_size(&self) -> u64 {
131        unsafe { ffi::rocksdb_table_properties_raw_value_size(self.inner) }
132    }
133
134    /// Number of data blocks in this file.
135    pub fn num_data_blocks(&self) -> u64 {
136        unsafe { ffi::rocksdb_table_properties_num_data_blocks(self.inner) }
137    }
138
139    /// Data blocks stored uncompressed because the compressed output blew past the ratio limit
140    /// in `CompressionOptions::max_compressed_bytes_per_kb`.
141    pub fn num_data_blocks_compression_rejected(&self) -> u64 {
142        unsafe { ffi::rocksdb_table_properties_num_data_blocks_compression_rejected(self.inner) }
143    }
144
145    /// Data blocks stored uncompressed because compression was never attempted, for example
146    /// with `kNoCompression` or with no compressor available.
147    pub fn num_data_blocks_compression_bypassed(&self) -> u64 {
148        unsafe { ffi::rocksdb_table_properties_num_data_blocks_compression_bypassed(self.inner) }
149    }
150
151    /// Number of uniform blocks in this file.
152    pub fn num_uniform_blocks(&self) -> u64 {
153        unsafe { ffi::rocksdb_table_properties_num_uniform_blocks(self.inner) }
154    }
155
156    /// Number of entries in this file.
157    pub fn num_entries(&self) -> u64 {
158        unsafe { ffi::rocksdb_table_properties_num_entries(self.inner) }
159    }
160
161    /// Number of unique entries, keys or prefixes, added to the filter.
162    pub fn num_filter_entries(&self) -> u64 {
163        unsafe { ffi::rocksdb_table_properties_num_filter_entries(self.inner) }
164    }
165
166    /// Number of deletions in this file.
167    pub fn num_deletions(&self) -> u64 {
168        unsafe { ffi::rocksdb_table_properties_num_deletions(self.inner) }
169    }
170
171    /// Number of merge operands in this file.
172    pub fn num_merge_operands(&self) -> u64 {
173        unsafe { ffi::rocksdb_table_properties_num_merge_operands(self.inner) }
174    }
175
176    /// Number of range deletions in this file.
177    pub fn num_range_deletions(&self) -> u64 {
178        unsafe { ffi::rocksdb_table_properties_num_range_deletions(self.inner) }
179    }
180
181    /// SST format version, reserved for backward compatibility.
182    pub fn format_version(&self) -> u64 {
183        unsafe { ffi::rocksdb_table_properties_format_version(self.inner) }
184    }
185
186    /// Byte length shared by every key, or 0 when keys are variable length.
187    pub fn fixed_key_len(&self) -> u64 {
188        unsafe { ffi::rocksdb_table_properties_fixed_key_len(self.inner) }
189    }
190
191    /// Id of the column family this file belongs to, matching [`Self::column_family_name`]. An
192    /// unknown column family reads back as `i32::MAX`.
193    pub fn column_family_id(&self) -> u64 {
194        unsafe { ffi::rocksdb_table_properties_column_family_id(self.inner) }
195    }
196
197    /// Oldest ancestor time, 0 when unknown. For a flush this is the oldest key time in the
198    /// file, falling back to the flush time. For a compaction it is the oldest such time across
199    /// all input files, falling back to when this output file was created.
200    pub fn creation_time(&self) -> u64 {
201        unsafe { ffi::rocksdb_table_properties_creation_time(self.inner) }
202    }
203
204    /// Timestamp of the earliest key, 0 when unknown.
205    pub fn oldest_key_time(&self) -> u64 {
206        unsafe { ffi::rocksdb_table_properties_oldest_key_time(self.inner) }
207    }
208
209    /// Timestamp of the newest key, 0 when unknown.
210    pub fn newest_key_time(&self) -> u64 {
211        unsafe { ffi::rocksdb_table_properties_newest_key_time(self.inner) }
212    }
213
214    /// Time the SST file was actually created, 0 when unknown.
215    pub fn file_creation_time(&self) -> u64 {
216        unsafe { ffi::rocksdb_table_properties_file_creation_time(self.inner) }
217    }
218
219    /// Estimated size of the data blocks under a relatively slower compression algorithm, 0
220    /// when unknown. Comes from `ColumnFamilyOptions::sample_for_compression`.
221    pub fn slow_compression_estimated_data_size(&self) -> u64 {
222        unsafe { ffi::rocksdb_table_properties_slow_compression_estimated_data_size(self.inner) }
223    }
224
225    /// Estimated size of the data blocks under a relatively faster compression algorithm, 0
226    /// when unknown. Comes from `ColumnFamilyOptions::sample_for_compression`.
227    pub fn fast_compression_estimated_data_size(&self) -> u64 {
228        unsafe { ffi::rocksdb_table_properties_fast_compression_estimated_data_size(self.inner) }
229    }
230
231    /// Offset where the tail of the file begins, meaning every block after the data blocks.
232    pub fn tail_start_offset(&self) -> u64 {
233        unsafe { ffi::rocksdb_table_properties_tail_start_offset(self.inner) }
234    }
235
236    /// Value of `AdvancedColumnFamilyOptions::persist_user_defined_timestamps` when the file
237    /// was written. Defaults to true and is only recorded in the file when false.
238    pub fn user_defined_timestamps_persisted(&self) -> bool {
239        unsafe { ffi::rocksdb_table_properties_user_defined_timestamps_persisted(self.inner) != 0 }
240    }
241
242    /// Largest sequence number among the keys in this file. Only meaningful when
243    /// [`Self::has_key_largest_seqno`] is true, otherwise it reads back as `u64::MAX`.
244    pub fn key_largest_seqno(&self) -> u64 {
245        unsafe { ffi::rocksdb_table_properties_key_largest_seqno(self.inner) }
246    }
247
248    /// Smallest sequence number among the keys in this file. Only meaningful when
249    /// [`Self::has_key_smallest_seqno`] is true, otherwise it reads back as `u64::MAX`.
250    pub fn key_smallest_seqno(&self) -> u64 {
251        unsafe { ffi::rocksdb_table_properties_key_smallest_seqno(self.inner) }
252    }
253
254    /// Whether [`Self::key_largest_seqno`] holds a real sequence number. It should be true
255    /// unless the file is empty.
256    pub fn has_key_largest_seqno(&self) -> bool {
257        unsafe { ffi::rocksdb_table_properties_has_key_largest_seqno(self.inner) != 0 }
258    }
259
260    /// Whether [`Self::key_smallest_seqno`] holds a real sequence number. It should be true
261    /// unless the file is empty.
262    pub fn has_key_smallest_seqno(&self) -> bool {
263        unsafe { ffi::rocksdb_table_properties_has_key_smallest_seqno(self.inner) != 0 }
264    }
265
266    /// Restart interval used for data blocks when the file was written, 0 when unknown.
267    pub fn data_block_restart_interval(&self) -> u64 {
268        unsafe { ffi::rocksdb_table_properties_data_block_restart_interval(self.inner) }
269    }
270
271    /// Restart interval used for index blocks when the file was written, 0 when unknown.
272    pub fn index_block_restart_interval(&self) -> u64 {
273        unsafe { ffi::rocksdb_table_properties_index_block_restart_interval(self.inner) }
274    }
275
276    /// Whether data blocks store keys and values separately. The block footer is the real
277    /// source of truth, this property exists for debugging and validation.
278    pub fn separate_key_value_in_data_block(&self) -> bool {
279        unsafe { ffi::rocksdb_table_properties_separate_key_value_in_data_block(self.inner) != 0 }
280    }
281
282    /// DB identity, generated the first time the DB was created. Empty when unset.
283    pub fn db_id(&self) -> &'a [u8] {
284        self.string_field(ffi::rocksdb_table_properties_db_id)
285    }
286
287    /// DB session identity, regenerated every time the DB is opened. Empty when unset.
288    pub fn db_session_id(&self) -> &'a [u8] {
289        self.string_field(ffi::rocksdb_table_properties_db_session_id)
290    }
291
292    /// Location of the machine hosting the DB, the hostname by default. It can change whenever
293    /// the DB is reopened.
294    pub fn db_host_id(&self) -> &'a [u8] {
295        self.string_field(ffi::rocksdb_table_properties_db_host_id)
296    }
297
298    /// Name of the column family this file belongs to. Empty when the column family is unknown.
299    pub fn column_family_name(&self) -> &'a [u8] {
300        self.string_field(ffi::rocksdb_table_properties_column_family_name)
301    }
302
303    /// Name of the filter policy used for this file. Empty when no filter policy was used.
304    pub fn filter_policy_name(&self) -> &'a [u8] {
305        self.string_field(ffi::rocksdb_table_properties_filter_policy_name)
306    }
307
308    /// Name of the comparator used for this file.
309    pub fn comparator_name(&self) -> &'a [u8] {
310        self.string_field(ffi::rocksdb_table_properties_comparator_name)
311    }
312
313    /// Name of the merge operator used for this file. Reads back as `nullptr` when no merge
314    /// operator was used.
315    pub fn merge_operator_name(&self) -> &'a [u8] {
316        self.string_field(ffi::rocksdb_table_properties_merge_operator_name)
317    }
318
319    /// Name of the prefix extractor used for this file. Reads back as `nullptr` when no prefix
320    /// extractor was used.
321    pub fn prefix_extractor_name(&self) -> &'a [u8] {
322        self.string_field(ffi::rocksdb_table_properties_prefix_extractor_name)
323    }
324
325    /// Comma separated names of the property collector factories used for this file.
326    pub fn property_collectors_names(&self) -> &'a [u8] {
327        self.string_field(ffi::rocksdb_table_properties_property_collectors_names)
328    }
329
330    /// Identifies the compression algorithm or schema used for this file. Below format version
331    /// 7 it is a built in compression type name, from version 7 on it is
332    /// `<compatibility_name>;<hex coded compression types>;<future use>`.
333    pub fn compression_name(&self) -> &'a [u8] {
334        self.string_field(ffi::rocksdb_table_properties_compression_name)
335    }
336
337    /// Compression options used to compress this file.
338    pub fn compression_options(&self) -> &'a [u8] {
339        self.string_field(ffi::rocksdb_table_properties_compression_options)
340    }
341
342    /// Delta encoded sequence number to time mapping.
343    pub fn seqno_to_time_mapping(&self) -> &'a [u8] {
344        self.string_field(ffi::rocksdb_table_properties_seqno_to_time_mapping)
345    }
346
347    /// Number of user collected properties recorded for this file.
348    pub fn user_collected_properties_count(&self) -> usize {
349        unsafe { ffi::rocksdb_table_properties_user_collected_properties_count(self.inner) }
350    }
351
352    /// The user collected property at `pos` in key order, or `None` once `pos` runs past the
353    /// end.
354    ///
355    /// Costs O(pos): the C API walks the underlying `std::map` from the beginning on every
356    /// call, so random access here is not cheap.
357    pub fn user_collected_property_at(&self, pos: usize) -> Option<(&'a [u8], &'a [u8])> {
358        self.map_entry_at(
359            pos,
360            ffi::rocksdb_table_properties_user_collected_properties_key_at,
361            ffi::rocksdb_table_properties_user_collected_properties_value_at,
362        )
363    }
364
365    /// Walks the user collected properties in key order, borrowing every key and value.
366    ///
367    /// Lazy and allocation free, but each step costs O(pos) because the C API walks the map
368    /// from the beginning for every lookup. A full pass over n properties is therefore O(n^2),
369    /// which is fine for the handful of entries most collectors emit and slow if you have
370    /// thousands.
371    pub fn user_collected_properties(&self) -> impl Iterator<Item = (&'a [u8], &'a [u8])> + '_ {
372        (0..self.user_collected_properties_count())
373            .map_while(move |pos| self.user_collected_property_at(pos))
374    }
375
376    /// Number of human readable properties recorded for this file. These are what collectors
377    /// return from `GetReadableProperties` and exist for logging.
378    pub fn readable_properties_count(&self) -> usize {
379        unsafe { ffi::rocksdb_table_properties_readable_properties_count(self.inner) }
380    }
381
382    /// The readable property at `pos` in key order, or `None` once `pos` runs past the end.
383    ///
384    /// Costs O(pos): the C API walks the underlying `std::map` from the beginning on every
385    /// call, so random access here is not cheap.
386    pub fn readable_property_at(&self, pos: usize) -> Option<(&'a [u8], &'a [u8])> {
387        self.map_entry_at(
388            pos,
389            ffi::rocksdb_table_properties_readable_properties_key_at,
390            ffi::rocksdb_table_properties_readable_properties_value_at,
391        )
392    }
393
394    /// Walks the human readable properties in key order, borrowing every key and value.
395    ///
396    /// Lazy and allocation free, but each step costs O(pos) because the C API walks the map
397    /// from the beginning for every lookup. A full pass over n properties is therefore O(n^2),
398    /// which is fine for the handful of entries most collectors emit and slow if you have
399    /// thousands.
400    pub fn readable_properties(&self) -> impl Iterator<Item = (&'a [u8], &'a [u8])> + '_ {
401        (0..self.readable_properties_count()).map_while(move |pos| self.readable_property_at(pos))
402    }
403
404    /// Reads one of the borrowed string fields as raw bytes.
405    fn string_field(&self, getter: StringGetter) -> &'a [u8] {
406        let mut len: usize = 0;
407        // SAFETY: `self.inner` is valid for `'a` and the getter writes the byte length through
408        // `len`, returning an interior pointer into a string RocksDB owns.
409        unsafe {
410            let ptr = getter(self.inner, &raw mut len);
411            bytes_from_raw(ptr, len)
412        }
413    }
414
415    /// Reads one key and value pair out of a property map, or `None` when `pos` is out of range.
416    ///
417    /// The C API signals out of range with a null key pointer, and the value pointer of an
418    /// entry that exists is never null even when the value is empty.
419    fn map_entry_at(
420        &self,
421        pos: usize,
422        key_at: MapEntryGetter,
423        value_at: MapEntryGetter,
424    ) -> Option<(&'a [u8], &'a [u8])> {
425        let mut key_len: usize = 0;
426        let mut value_len: usize = 0;
427        // SAFETY: `self.inner` is valid for `'a`, both getters bounds check `pos` themselves,
428        // and they write the byte lengths through the out params they are given.
429        unsafe {
430            let key_ptr = key_at(self.inner, pos, &raw mut key_len);
431            if key_ptr.is_null() {
432                return None;
433            }
434            let value_ptr = value_at(self.inner, pos, &raw mut value_len);
435            Some((
436                bytes_from_raw(key_ptr, key_len),
437                bytes_from_raw(value_ptr, value_len),
438            ))
439        }
440    }
441}