rocksdb/
compaction_filter.rs1use libc::{c_char, c_int, c_uchar, c_void, size_t};
17use std::ffi::{CStr, CString};
18use std::slice;
19
20pub enum Decision {
26 Keep,
28 Remove,
30 Change(&'static [u8]),
32}
33
34pub trait CompactionFilter {
37 fn filter(&mut self, level: u32, key: &[u8], value: &[u8]) -> Decision;
61
62 fn name(&self) -> &CStr;
65}
66
67pub trait CompactionFilterFn: FnMut(u32, &[u8], &[u8]) -> Decision {}
76impl<F> CompactionFilterFn for F where F: FnMut(u32, &[u8], &[u8]) -> Decision + Send + 'static {}
77
78pub struct CompactionFilterCallback<F>
79where
80 F: CompactionFilterFn,
81{
82 pub name: CString,
83 pub filter_fn: F,
84}
85
86impl<F> CompactionFilter for CompactionFilterCallback<F>
87where
88 F: CompactionFilterFn,
89{
90 fn name(&self) -> &CStr {
91 self.name.as_c_str()
92 }
93
94 fn filter(&mut self, level: u32, key: &[u8], value: &[u8]) -> Decision {
95 (self.filter_fn)(level, key, value)
96 }
97}
98
99pub unsafe extern "C" fn destructor_callback<F>(raw_cb: *mut c_void)
100where
101 F: CompactionFilter,
102{
103 drop(unsafe { Box::from_raw(raw_cb as *mut F) });
104}
105
106pub unsafe extern "C" fn name_callback<F>(raw_cb: *mut c_void) -> *const c_char
107where
108 F: CompactionFilter,
109{
110 let cb = unsafe { &*(raw_cb as *mut F) };
111 cb.name().as_ptr()
112}
113
114pub unsafe extern "C" fn filter_callback<F>(
115 raw_cb: *mut c_void,
116 level: c_int,
117 raw_key: *const c_char,
118 key_length: size_t,
119 existing_value: *const c_char,
120 value_length: size_t,
121 new_value: *mut *mut c_char,
122 new_value_length: *mut size_t,
123 value_changed: *mut c_uchar,
124) -> c_uchar
125where
126 F: CompactionFilter,
127{
128 use self::Decision::{Change, Keep, Remove};
129
130 let cb = unsafe { &mut *(raw_cb as *mut F) };
131 let key = unsafe { slice::from_raw_parts(raw_key as *const u8, key_length) };
132 let oldval = unsafe { slice::from_raw_parts(existing_value as *const u8, value_length) };
133 let result = cb.filter(level as u32, key, oldval);
134 match result {
135 Keep => 0,
136 Remove => 1,
137 Change(newval) => {
138 unsafe { *new_value = newval.as_ptr() as *mut c_char };
139 unsafe { *new_value_length = newval.len() as size_t };
140 unsafe { *value_changed = 1_u8 };
141 0
142 }
143 }
144}
145
146#[cfg(test)]
147#[allow(unused_variables)]
148fn test_filter(level: u32, key: &[u8], value: &[u8]) -> Decision {
149 use self::Decision::{Change, Keep, Remove};
150 match key.first() {
151 Some(&b'_') => Remove,
152 Some(&b'%') => Change(b"secret"),
153 _ => Keep,
154 }
155}
156
157#[test]
158fn compaction_filter_test() {
159 use crate::{Options, DB};
160
161 let tempdir = tempfile::Builder::new()
162 .prefix("_rust_rocksdb_filter_test")
163 .tempdir()
164 .expect("Failed to create temporary path for the _rust_rocksdb_filter_test");
165 let path = tempdir.path();
166 let mut opts = Options::default();
167 opts.create_if_missing(true);
168 opts.set_compaction_filter("test", test_filter);
169 {
170 let db = DB::open(&opts, path).unwrap();
171 let _r = db.put(b"k1", b"a");
172 let _r = db.put(b"_k", b"b");
173 let _r = db.put(b"%k", b"c");
174 db.compact_range(None::<&[u8]>, None::<&[u8]>);
175 assert_eq!(&*db.get(b"k1").unwrap().unwrap(), b"a");
176 assert!(db.get(b"_k").unwrap().is_none());
177 assert_eq!(&*db.get(b"%k").unwrap().unwrap(), b"secret");
178 }
179 let result = DB::destroy(&opts, path);
180 assert!(result.is_ok());
181}