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 within the file of the external SST file global seqno value, 0 when the file has
232    /// no such property.
233    pub fn external_sst_file_global_seqno_offset(&self) -> u64 {
234        unsafe { ffi::rocksdb_table_properties_external_sst_file_global_seqno_offset(self.inner) }
235    }
236
237    /// Offset where the tail of the file begins, meaning every block after the data blocks.
238    pub fn tail_start_offset(&self) -> u64 {
239        unsafe { ffi::rocksdb_table_properties_tail_start_offset(self.inner) }
240    }
241
242    /// Value of `AdvancedColumnFamilyOptions::persist_user_defined_timestamps` when the file
243    /// was written. Defaults to true and is only recorded in the file when false.
244    pub fn user_defined_timestamps_persisted(&self) -> bool {
245        unsafe { ffi::rocksdb_table_properties_user_defined_timestamps_persisted(self.inner) != 0 }
246    }
247
248    /// Largest sequence number among the keys in this file. Only meaningful when
249    /// [`Self::has_key_largest_seqno`] is true, otherwise it reads back as `u64::MAX`.
250    pub fn key_largest_seqno(&self) -> u64 {
251        unsafe { ffi::rocksdb_table_properties_key_largest_seqno(self.inner) }
252    }
253
254    /// Smallest sequence number among the keys in this file. Only meaningful when
255    /// [`Self::has_key_smallest_seqno`] is true, otherwise it reads back as `u64::MAX`.
256    pub fn key_smallest_seqno(&self) -> u64 {
257        unsafe { ffi::rocksdb_table_properties_key_smallest_seqno(self.inner) }
258    }
259
260    /// Whether [`Self::key_largest_seqno`] holds a real sequence number. It should be true
261    /// unless the file is empty.
262    pub fn has_key_largest_seqno(&self) -> bool {
263        unsafe { ffi::rocksdb_table_properties_has_key_largest_seqno(self.inner) != 0 }
264    }
265
266    /// Whether [`Self::key_smallest_seqno`] holds a real sequence number. It should be true
267    /// unless the file is empty.
268    pub fn has_key_smallest_seqno(&self) -> bool {
269        unsafe { ffi::rocksdb_table_properties_has_key_smallest_seqno(self.inner) != 0 }
270    }
271
272    /// Restart interval used for data blocks when the file was written, 0 when unknown.
273    pub fn data_block_restart_interval(&self) -> u64 {
274        unsafe { ffi::rocksdb_table_properties_data_block_restart_interval(self.inner) }
275    }
276
277    /// Restart interval used for index blocks when the file was written, 0 when unknown.
278    pub fn index_block_restart_interval(&self) -> u64 {
279        unsafe { ffi::rocksdb_table_properties_index_block_restart_interval(self.inner) }
280    }
281
282    /// Whether data blocks store keys and values separately. The block footer is the real
283    /// source of truth, this property exists for debugging and validation.
284    pub fn separate_key_value_in_data_block(&self) -> bool {
285        unsafe { ffi::rocksdb_table_properties_separate_key_value_in_data_block(self.inner) != 0 }
286    }
287
288    /// DB identity, generated the first time the DB was created. Empty when unset.
289    pub fn db_id(&self) -> &'a [u8] {
290        self.string_field(ffi::rocksdb_table_properties_db_id)
291    }
292
293    /// DB session identity, regenerated every time the DB is opened. Empty when unset.
294    pub fn db_session_id(&self) -> &'a [u8] {
295        self.string_field(ffi::rocksdb_table_properties_db_session_id)
296    }
297
298    /// Location of the machine hosting the DB, the hostname by default. It can change whenever
299    /// the DB is reopened.
300    pub fn db_host_id(&self) -> &'a [u8] {
301        self.string_field(ffi::rocksdb_table_properties_db_host_id)
302    }
303
304    /// Name of the column family this file belongs to. Empty when the column family is unknown.
305    pub fn column_family_name(&self) -> &'a [u8] {
306        self.string_field(ffi::rocksdb_table_properties_column_family_name)
307    }
308
309    /// Name of the filter policy used for this file. Empty when no filter policy was used.
310    pub fn filter_policy_name(&self) -> &'a [u8] {
311        self.string_field(ffi::rocksdb_table_properties_filter_policy_name)
312    }
313
314    /// Name of the comparator used for this file.
315    pub fn comparator_name(&self) -> &'a [u8] {
316        self.string_field(ffi::rocksdb_table_properties_comparator_name)
317    }
318
319    /// Name of the merge operator used for this file. Reads back as `nullptr` when no merge
320    /// operator was used.
321    pub fn merge_operator_name(&self) -> &'a [u8] {
322        self.string_field(ffi::rocksdb_table_properties_merge_operator_name)
323    }
324
325    /// Name of the prefix extractor used for this file. Reads back as `nullptr` when no prefix
326    /// extractor was used.
327    pub fn prefix_extractor_name(&self) -> &'a [u8] {
328        self.string_field(ffi::rocksdb_table_properties_prefix_extractor_name)
329    }
330
331    /// Comma separated names of the property collector factories used for this file.
332    pub fn property_collectors_names(&self) -> &'a [u8] {
333        self.string_field(ffi::rocksdb_table_properties_property_collectors_names)
334    }
335
336    /// Identifies the compression algorithm or schema used for this file. Below format version
337    /// 7 it is a built in compression type name, from version 7 on it is
338    /// `<compatibility_name>;<hex coded compression types>;<future use>`.
339    pub fn compression_name(&self) -> &'a [u8] {
340        self.string_field(ffi::rocksdb_table_properties_compression_name)
341    }
342
343    /// Compression options used to compress this file.
344    pub fn compression_options(&self) -> &'a [u8] {
345        self.string_field(ffi::rocksdb_table_properties_compression_options)
346    }
347
348    /// Delta encoded sequence number to time mapping.
349    pub fn seqno_to_time_mapping(&self) -> &'a [u8] {
350        self.string_field(ffi::rocksdb_table_properties_seqno_to_time_mapping)
351    }
352
353    /// Number of user collected properties recorded for this file.
354    pub fn user_collected_properties_count(&self) -> usize {
355        unsafe { ffi::rocksdb_table_properties_user_collected_properties_count(self.inner) }
356    }
357
358    /// The user collected property at `pos` in key order, or `None` once `pos` runs past the
359    /// end.
360    ///
361    /// Costs O(pos): the C API walks the underlying `std::map` from the beginning on every
362    /// call, so random access here is not cheap.
363    pub fn user_collected_property_at(&self, pos: usize) -> Option<(&'a [u8], &'a [u8])> {
364        self.map_entry_at(
365            pos,
366            ffi::rocksdb_table_properties_user_collected_properties_key_at,
367            ffi::rocksdb_table_properties_user_collected_properties_value_at,
368        )
369    }
370
371    /// Walks the user collected properties in key order, borrowing every key and value.
372    ///
373    /// Lazy and allocation free, but each step costs O(pos) because the C API walks the map
374    /// from the beginning for every lookup. A full pass over n properties is therefore O(n^2),
375    /// which is fine for the handful of entries most collectors emit and slow if you have
376    /// thousands.
377    pub fn user_collected_properties(&self) -> impl Iterator<Item = (&'a [u8], &'a [u8])> + '_ {
378        (0..self.user_collected_properties_count())
379            .map_while(move |pos| self.user_collected_property_at(pos))
380    }
381
382    /// Number of human readable properties recorded for this file. These are what collectors
383    /// return from `GetReadableProperties` and exist for logging.
384    pub fn readable_properties_count(&self) -> usize {
385        unsafe { ffi::rocksdb_table_properties_readable_properties_count(self.inner) }
386    }
387
388    /// The readable property at `pos` in key order, or `None` once `pos` runs past the end.
389    ///
390    /// Costs O(pos): the C API walks the underlying `std::map` from the beginning on every
391    /// call, so random access here is not cheap.
392    pub fn readable_property_at(&self, pos: usize) -> Option<(&'a [u8], &'a [u8])> {
393        self.map_entry_at(
394            pos,
395            ffi::rocksdb_table_properties_readable_properties_key_at,
396            ffi::rocksdb_table_properties_readable_properties_value_at,
397        )
398    }
399
400    /// Walks the human readable properties in key order, borrowing every key and value.
401    ///
402    /// Lazy and allocation free, but each step costs O(pos) because the C API walks the map
403    /// from the beginning for every lookup. A full pass over n properties is therefore O(n^2),
404    /// which is fine for the handful of entries most collectors emit and slow if you have
405    /// thousands.
406    pub fn readable_properties(&self) -> impl Iterator<Item = (&'a [u8], &'a [u8])> + '_ {
407        (0..self.readable_properties_count()).map_while(move |pos| self.readable_property_at(pos))
408    }
409
410    /// Reads one of the borrowed string fields as raw bytes.
411    fn string_field(&self, getter: StringGetter) -> &'a [u8] {
412        let mut len: usize = 0;
413        // SAFETY: `self.inner` is valid for `'a` and the getter writes the byte length through
414        // `len`, returning an interior pointer into a string RocksDB owns.
415        unsafe {
416            let ptr = getter(self.inner, &raw mut len);
417            bytes_from_raw(ptr, len)
418        }
419    }
420
421    /// Reads one key and value pair out of a property map, or `None` when `pos` is out of range.
422    ///
423    /// The C API signals out of range with a null key pointer, and the value pointer of an
424    /// entry that exists is never null even when the value is empty.
425    fn map_entry_at(
426        &self,
427        pos: usize,
428        key_at: MapEntryGetter,
429        value_at: MapEntryGetter,
430    ) -> Option<(&'a [u8], &'a [u8])> {
431        let mut key_len: usize = 0;
432        let mut value_len: usize = 0;
433        // SAFETY: `self.inner` is valid for `'a`, both getters bounds check `pos` themselves,
434        // and they write the byte lengths through the out params they are given.
435        unsafe {
436            let key_ptr = key_at(self.inner, pos, &raw mut key_len);
437            if key_ptr.is_null() {
438                return None;
439            }
440            let value_ptr = value_at(self.inner, pos, &raw mut value_len);
441            Some((
442                bytes_from_raw(key_ptr, key_len),
443                bytes_from_raw(value_ptr, value_len),
444            ))
445        }
446    }
447}