Skip to main content

rust_rocksdb/
write_batch_with_index.rs

1use crate::db::DBInner;
2use crate::ffi_util::raw_data_and_free;
3use crate::{
4    AsColumnFamilyRef, DBAccess, DBCommon, DBPinnableSlice, DBRawIteratorWithThreadMode, Error,
5    Options, ReadOptions, ThreadMode, ffi,
6};
7use libc::{c_char, c_uchar, size_t};
8
9/// A write batch that can also be read from, and that can be layered on top of
10/// a database iterator.
11///
12/// Values read out of the batch are copied, but iterators and pinned slices
13/// borrow, so the borrow checker is what keeps them from outliving their owner.
14///
15/// An iterator built with [`Self::iterator_with_base`] reads directly out of the
16/// batch's internal skip-list, so it cannot outlive the batch:
17///
18/// ```compile_fail,E0597
19/// use rust_rocksdb::{DB, WriteBatchWithIndex};
20///
21/// let db = DB::open_default("foo").unwrap();
22/// let mut iter = {
23///     let mut wbwi = WriteBatchWithIndex::new(0, true);
24///     wbwi.put(b"k", b"v");
25///     wbwi.iterator_with_base(db.raw_iterator())
26/// };
27/// iter.seek_to_first();
28/// ```
29///
30/// A slice from [`Self::get_pinned_from_batch_and_db`] pins a block in the
31/// database's block cache, so it cannot outlive the database:
32///
33/// ```compile_fail,E0597
34/// use rust_rocksdb::{DB, ReadOptions, WriteBatchWithIndex};
35///
36/// let wbwi = WriteBatchWithIndex::new(0, true);
37/// let readopts = ReadOptions::default();
38/// let _value = {
39///     let db = DB::open_default("foo").unwrap();
40///     wbwi.get_pinned_from_batch_and_db(&db, b"k", &readopts).unwrap()
41/// };
42/// ```
43pub struct WriteBatchWithIndex {
44    pub(crate) inner: *mut ffi::rocksdb_writebatch_wi_t,
45}
46
47impl WriteBatchWithIndex {
48    pub fn new(reserved_bytes: usize, overwrite_key: bool) -> Self {
49        Self {
50            inner: unsafe {
51                ffi::rocksdb_writebatch_wi_create(
52                    reserved_bytes as size_t,
53                    c_uchar::from(overwrite_key),
54                )
55            },
56        }
57    }
58
59    pub fn len(&self) -> usize {
60        unsafe { ffi::rocksdb_writebatch_wi_count(self.inner) as usize }
61    }
62
63    /// Return WriteBatch serialized size (in bytes).
64    pub fn size_in_bytes(&self) -> usize {
65        unsafe {
66            let mut batch_size: size_t = 0;
67            ffi::rocksdb_writebatch_wi_data(self.inner, &raw mut batch_size);
68            batch_size
69        }
70    }
71
72    /// Return a reference to a byte array which represents a serialized version of the batch.
73    pub fn data(&self) -> &[u8] {
74        unsafe {
75            let mut batch_size: size_t = 0;
76            let batch_data = ffi::rocksdb_writebatch_wi_data(self.inner, &raw mut batch_size);
77            std::slice::from_raw_parts(batch_data as _, batch_size)
78        }
79    }
80
81    pub fn is_empty(&self) -> bool {
82        self.len() == 0
83    }
84
85    pub fn get_from_batch<K>(&self, key: K, options: &Options) -> Result<Option<Vec<u8>>, Error>
86    where
87        K: AsRef<[u8]>,
88    {
89        let key = key.as_ref();
90        unsafe {
91            let mut value_size: size_t = 0;
92            let value_data = ffi_try!(ffi::rocksdb_writebatch_wi_get_from_batch(
93                self.inner,
94                options.inner,
95                key.as_ptr() as *const c_char,
96                key.len() as size_t,
97                &raw mut value_size
98            ));
99
100            // `value_data` was allocated by `malloc` on the C++ side; copy it
101            // out and release it with `rocksdb_free`.
102            Ok(raw_data_and_free(value_data, value_size))
103        }
104    }
105
106    pub fn get_from_batch_cf<K>(
107        &self,
108        cf: &impl AsColumnFamilyRef,
109        key: K,
110        options: &Options,
111    ) -> Result<Option<Vec<u8>>, Error>
112    where
113        K: AsRef<[u8]>,
114    {
115        let key = key.as_ref();
116        unsafe {
117            let mut value_size: size_t = 0;
118            let value_data = ffi_try!(ffi::rocksdb_writebatch_wi_get_from_batch_cf(
119                self.inner,
120                options.inner,
121                cf.inner(),
122                key.as_ptr() as *const c_char,
123                key.len() as size_t,
124                &raw mut value_size
125            ));
126
127            // `value_data` was allocated by `malloc` on the C++ side; copy it
128            // out and release it with `rocksdb_free`.
129            Ok(raw_data_and_free(value_data, value_size))
130        }
131    }
132
133    pub fn get_from_batch_and_db<T, I, K>(
134        &self,
135        db: &DBCommon<T, I>,
136        key: K,
137        readopts: &ReadOptions,
138    ) -> Result<Option<Vec<u8>>, Error>
139    where
140        T: ThreadMode,
141        I: DBInner,
142        K: AsRef<[u8]>,
143    {
144        if readopts.inner.is_null() {
145            return Err(Error::new(
146                "Unable to create RocksDB read options. This is a fairly trivial call, and its \
147                 failure may be indicative of a mis-compiled or mis-loaded RocksDB library."
148                    .to_owned(),
149            ));
150        }
151
152        let key = key.as_ref();
153        unsafe {
154            let mut value_size: size_t = 0;
155            let value_data = ffi_try!(ffi::rocksdb_writebatch_wi_get_from_batch_and_db(
156                self.inner,
157                db.inner.inner(),
158                readopts.inner,
159                key.as_ptr() as *const c_char,
160                key.len() as size_t,
161                &raw mut value_size
162            ));
163
164            // `value_data` was allocated by `malloc` on the C++ side; copy it
165            // out and release it with `rocksdb_free`.
166            Ok(raw_data_and_free(value_data, value_size))
167        }
168    }
169
170    /// The returned slice pins a block inside `db`'s block cache, so its
171    /// lifetime is tied to `db` rather than to `self`. Letting lifetime elision
172    /// pick `&self` here would allow the slice to outlive the database and
173    /// release a cache handle into a destroyed cache.
174    pub fn get_pinned_from_batch_and_db<'db, T, I, K>(
175        &self,
176        db: &'db DBCommon<T, I>,
177        key: K,
178        readopts: &ReadOptions,
179    ) -> Result<Option<DBPinnableSlice<'db>>, Error>
180    where
181        T: ThreadMode,
182        I: DBInner,
183        K: AsRef<[u8]>,
184    {
185        if readopts.inner.is_null() {
186            return Err(Error::new(
187                "Unable to create RocksDB read options. This is a fairly trivial call, and its \
188                 failure may be indicative of a mis-compiled or mis-loaded RocksDB library."
189                    .to_owned(),
190            ));
191        }
192
193        let key = key.as_ref();
194        unsafe {
195            let value_data = ffi_try!(ffi::rocksdb_writebatch_wi_get_pinned_from_batch_and_db(
196                self.inner,
197                db.inner.inner(),
198                readopts.inner,
199                key.as_ptr() as *const c_char,
200                key.len() as size_t,
201            ));
202
203            if value_data.is_null() {
204                Ok(None)
205            } else {
206                Ok(Some(DBPinnableSlice::from_c(value_data)))
207            }
208        }
209    }
210
211    pub fn get_from_batch_and_db_cf<T, I, K>(
212        &self,
213        db: &DBCommon<T, I>,
214        cf: &impl AsColumnFamilyRef,
215        key: K,
216        readopts: &ReadOptions,
217    ) -> Result<Option<Vec<u8>>, Error>
218    where
219        T: ThreadMode,
220        I: DBInner,
221        K: AsRef<[u8]>,
222    {
223        if readopts.inner.is_null() {
224            return Err(Error::new(
225                "Unable to create RocksDB read options. This is a fairly trivial call, and its \
226                 failure may be indicative of a mis-compiled or mis-loaded RocksDB library."
227                    .to_owned(),
228            ));
229        }
230
231        let key = key.as_ref();
232        unsafe {
233            let mut value_size: size_t = 0;
234            let value_data = ffi_try!(ffi::rocksdb_writebatch_wi_get_from_batch_and_db_cf(
235                self.inner,
236                db.inner.inner(),
237                readopts.inner,
238                cf.inner(),
239                key.as_ptr() as *const c_char,
240                key.len() as size_t,
241                &raw mut value_size
242            ));
243
244            // `value_data` was allocated by `malloc` on the C++ side; copy it
245            // out and release it with `rocksdb_free`.
246            Ok(raw_data_and_free(value_data, value_size))
247        }
248    }
249
250    /// The returned slice pins a block inside `db`'s block cache, so its
251    /// lifetime is tied to `db` rather than to `self`. See
252    /// [`Self::get_pinned_from_batch_and_db`].
253    pub fn get_pinned_from_batch_and_db_cf<'db, T, I, K>(
254        &self,
255        db: &'db DBCommon<T, I>,
256        cf: &impl AsColumnFamilyRef,
257        key: K,
258        readopts: &ReadOptions,
259    ) -> Result<Option<DBPinnableSlice<'db>>, Error>
260    where
261        T: ThreadMode,
262        I: DBInner,
263        K: AsRef<[u8]>,
264    {
265        if readopts.inner.is_null() {
266            return Err(Error::new(
267                "Unable to create RocksDB read options. This is a fairly trivial call, and its \
268                 failure may be indicative of a mis-compiled or mis-loaded RocksDB library."
269                    .to_owned(),
270            ));
271        }
272
273        let key = key.as_ref();
274        unsafe {
275            let value_data = ffi_try!(ffi::rocksdb_writebatch_wi_get_pinned_from_batch_and_db_cf(
276                self.inner,
277                db.inner.inner(),
278                readopts.inner,
279                cf.inner(),
280                key.as_ptr() as *const c_char,
281                key.len() as size_t,
282            ));
283
284            if value_data.is_null() {
285                Ok(None)
286            } else {
287                Ok(Some(DBPinnableSlice::from_c(value_data)))
288            }
289        }
290    }
291
292    /// Insert a value into the database under the given key.
293    pub fn put<K, V>(&mut self, key: K, value: V)
294    where
295        K: AsRef<[u8]>,
296        V: AsRef<[u8]>,
297    {
298        let key = key.as_ref();
299        let value = value.as_ref();
300
301        unsafe {
302            ffi::rocksdb_writebatch_wi_put(
303                self.inner,
304                key.as_ptr() as *const c_char,
305                key.len() as size_t,
306                value.as_ptr() as *const c_char,
307                value.len() as size_t,
308            );
309        }
310    }
311
312    pub fn put_cf<K, V>(&mut self, cf: &impl AsColumnFamilyRef, key: K, value: V)
313    where
314        K: AsRef<[u8]>,
315        V: AsRef<[u8]>,
316    {
317        let key = key.as_ref();
318        let value = value.as_ref();
319
320        unsafe {
321            ffi::rocksdb_writebatch_wi_put_cf(
322                self.inner,
323                cf.inner(),
324                key.as_ptr() as *const c_char,
325                key.len() as size_t,
326                value.as_ptr() as *const c_char,
327                value.len() as size_t,
328            );
329        }
330    }
331
332    pub fn merge<K, V>(&mut self, key: K, value: V)
333    where
334        K: AsRef<[u8]>,
335        V: AsRef<[u8]>,
336    {
337        let key = key.as_ref();
338        let value = value.as_ref();
339
340        unsafe {
341            ffi::rocksdb_writebatch_wi_merge(
342                self.inner,
343                key.as_ptr() as *const c_char,
344                key.len() as size_t,
345                value.as_ptr() as *const c_char,
346                value.len() as size_t,
347            );
348        }
349    }
350
351    pub fn merge_cf<K, V>(&mut self, cf: &impl AsColumnFamilyRef, key: K, value: V)
352    where
353        K: AsRef<[u8]>,
354        V: AsRef<[u8]>,
355    {
356        let key = key.as_ref();
357        let value = value.as_ref();
358
359        unsafe {
360            ffi::rocksdb_writebatch_wi_merge_cf(
361                self.inner,
362                cf.inner(),
363                key.as_ptr() as *const c_char,
364                key.len() as size_t,
365                value.as_ptr() as *const c_char,
366                value.len() as size_t,
367            );
368        }
369    }
370
371    /// Removes the database entry for key. Does nothing if the key was not found.
372    pub fn delete<K: AsRef<[u8]>>(&mut self, key: K) {
373        let key = key.as_ref();
374
375        unsafe {
376            ffi::rocksdb_writebatch_wi_delete(
377                self.inner,
378                key.as_ptr() as *const c_char,
379                key.len() as size_t,
380            );
381        }
382    }
383
384    pub fn delete_cf<K: AsRef<[u8]>>(&mut self, cf: &impl AsColumnFamilyRef, key: K) {
385        let key = key.as_ref();
386
387        unsafe {
388            ffi::rocksdb_writebatch_wi_delete_cf(
389                self.inner,
390                cf.inner(),
391                key.as_ptr() as *const c_char,
392                key.len() as size_t,
393            );
394        }
395    }
396
397    /// Clear all updates buffered in this batch.
398    pub fn clear(&mut self) {
399        unsafe {
400            ffi::rocksdb_writebatch_wi_clear(self.inner);
401        }
402    }
403
404    /// The returned iterator reads directly out of this batch's internal
405    /// skip-list and write buffer, so it must not outlive the batch. Binding
406    /// `&self` to the same lifetime as the base iterator is what enforces that;
407    /// with an independent lifetime on `&self` the iterator could outlive the
408    /// batch and read freed memory.
409    pub fn iterator_with_base<'a, D>(
410        &'a self,
411        base_iterator: DBRawIteratorWithThreadMode<'a, D>,
412    ) -> DBRawIteratorWithThreadMode<'a, D>
413    where
414        D: DBAccess,
415    {
416        let (base_iterator_inner, readopts) = base_iterator.into_inner();
417
418        let iterator = unsafe {
419            ffi::rocksdb_writebatch_wi_create_iterator_with_base_readopts(
420                self.inner,
421                base_iterator_inner.as_ptr(),
422                readopts.as_ptr(),
423            )
424        };
425
426        // The delta iterator keeps its own raw pointers to the iterate bounds
427        // in these options, so it has to hold the same object the base
428        // iterator was built from, not an equivalent copy.
429        DBRawIteratorWithThreadMode::from_inner(iterator, readopts)
430    }
431
432    /// The returned iterator reads directly out of this batch, so it must not
433    /// outlive the batch. See [`Self::iterator_with_base`].
434    pub fn iterator_with_base_cf<'a, D>(
435        &'a self,
436        base_iterator: DBRawIteratorWithThreadMode<'a, D>,
437        cf: &impl AsColumnFamilyRef,
438    ) -> DBRawIteratorWithThreadMode<'a, D>
439    where
440        D: DBAccess,
441    {
442        let (base_iterator_inner, readopts) = base_iterator.into_inner();
443
444        let iterator = unsafe {
445            ffi::rocksdb_writebatch_wi_create_iterator_with_base_cf_readopts(
446                self.inner,
447                base_iterator_inner.as_ptr(),
448                cf.inner(),
449                readopts.as_ptr(),
450            )
451        };
452
453        DBRawIteratorWithThreadMode::from_inner(iterator, readopts)
454    }
455}
456
457impl Drop for WriteBatchWithIndex {
458    fn drop(&mut self) {
459        unsafe {
460            ffi::rocksdb_writebatch_wi_destroy(self.inner);
461        }
462    }
463}
464
465unsafe impl Send for WriteBatchWithIndex {}