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::write_batch::get_ts_size_callback;
4use crate::{
5    AsColumnFamilyRef, Comparator, DBAccess, DBCommon, DBPinnableSlice,
6    DBRawIteratorWithThreadMode, Error, Options, ReadOptions, ThreadMode, ffi,
7};
8use libc::{c_char, c_uchar, c_void, size_t};
9use std::sync::Arc;
10
11/// A write batch that can also be read from, and that can be layered on top of
12/// a database iterator.
13///
14/// There is deliberately no vectored write here, unlike
15/// [`WriteBatch::put_vectored`](crate::WriteBatch::put_vectored).
16/// `WriteBatchWithIndex` does not override the `SliceParts` overloads, so
17/// `rocksdb_writebatch_wi_putv` and friends fall through to
18/// `WriteBatchBase`, which concatenates the parts into a temporary
19/// `std::string` and calls the single-slice path anyway. Joining the parts in
20/// Rust costs the same copy and lets the caller reuse the buffer.
21///
22/// Values read out of the batch are copied, but iterators and pinned slices
23/// borrow, so the borrow checker is what keeps them from outliving their owner.
24///
25/// An iterator built with [`Self::iterator_with_base`] reads directly out of the
26/// batch's internal skip-list, so it cannot outlive the batch:
27///
28/// ```compile_fail,E0597
29/// use rust_rocksdb::{DB, WriteBatchWithIndex};
30///
31/// let db = DB::open_default("foo").unwrap();
32/// let mut iter = {
33///     let mut wbwi = WriteBatchWithIndex::new(0, true);
34///     wbwi.put(b"k", b"v");
35///     wbwi.iterator_with_base(db.raw_iterator())
36/// };
37/// iter.seek_to_first();
38/// ```
39///
40/// A slice from [`Self::get_pinned_from_batch_and_db`] pins a block in the
41/// database's block cache, so it cannot outlive the database:
42///
43/// ```compile_fail,E0597
44/// use rust_rocksdb::{DB, ReadOptions, WriteBatchWithIndex};
45///
46/// let wbwi = WriteBatchWithIndex::new(0, true);
47/// let readopts = ReadOptions::default();
48/// let _value = {
49///     let db = DB::open_default("foo").unwrap();
50///     wbwi.get_pinned_from_batch_and_db(&db, b"k", &readopts).unwrap()
51/// };
52/// ```
53pub struct WriteBatchWithIndex {
54    pub(crate) inner: *mut ffi::rocksdb_writebatch_wi_t,
55    /// RocksDB stores the comparator by pointer and never takes ownership, so
56    /// the batch has to keep it alive for as long as its index exists.
57    _comparator: Option<Arc<Comparator>>,
58}
59
60/// How a batch built by [`WriteBatchWithIndex::builder`] is indexed and bounded.
61///
62/// Every field is optional. The defaults match
63/// [`WriteBatchWithIndex::new`] with `overwrite_key` set to false.
64pub struct WriteBatchWithIndexBuilder {
65    comparator: Option<Arc<Comparator>>,
66    reserved_bytes: usize,
67    overwrite_key: bool,
68    max_bytes: usize,
69    protection_bytes_per_key: usize,
70}
71
72impl WriteBatchWithIndexBuilder {
73    /// Orders the index by this comparator instead of by bytewise order.
74    ///
75    /// This must be the comparator of the column family the batch is read
76    /// against, otherwise reads through the batch return the wrong entries. The
77    /// batch keeps a reference, so the comparator cannot be dropped early.
78    pub fn comparator(mut self, comparator: Arc<Comparator>) -> Self {
79        self.comparator = Some(comparator);
80        self
81    }
82
83    /// Preallocates this many bytes for the batch's serialized form.
84    pub fn reserved_bytes(mut self, reserved_bytes: usize) -> Self {
85        self.reserved_bytes = reserved_bytes;
86        self
87    }
88
89    /// Makes a later write to a key replace the earlier one in the index.
90    ///
91    /// With this off the index keeps every version, iteration sees all of them,
92    /// and reads return the newest. With it on the batch holds one entry per
93    /// key, which is what a transaction wants. Merge operands are still kept in
94    /// full either way.
95    pub fn overwrite_key(mut self, overwrite_key: bool) -> Self {
96        self.overwrite_key = overwrite_key;
97        self
98    }
99
100    /// Fails writes once the batch's serialized size would exceed this many
101    /// bytes. Zero means no limit.
102    pub fn max_bytes(mut self, max_bytes: usize) -> Self {
103        self.max_bytes = max_bytes;
104        self
105    }
106
107    /// Stores this many bytes of per-key checksum alongside each entry so
108    /// RocksDB can detect memory corruption in the batch before it is written.
109    ///
110    /// Only 0 and 8 are supported. Zero disables the protection.
111    pub fn protection_bytes_per_key(mut self, protection_bytes_per_key: usize) -> Self {
112        self.protection_bytes_per_key = protection_bytes_per_key;
113        self
114    }
115
116    /// Creates the batch.
117    pub fn build(self) -> WriteBatchWithIndex {
118        let comparator_ptr = self
119            .comparator
120            .as_ref()
121            .map_or(std::ptr::null_mut(), |cmp| cmp.inner.as_ptr());
122        WriteBatchWithIndex {
123            inner: unsafe {
124                ffi::rocksdb_writebatch_wi_create_with_params(
125                    comparator_ptr,
126                    self.reserved_bytes,
127                    c_uchar::from(self.overwrite_key),
128                    self.max_bytes,
129                    self.protection_bytes_per_key,
130                )
131            },
132            _comparator: self.comparator,
133        }
134    }
135}
136
137impl WriteBatchWithIndex {
138    pub fn new(reserved_bytes: usize, overwrite_key: bool) -> Self {
139        Self {
140            inner: unsafe {
141                ffi::rocksdb_writebatch_wi_create(
142                    reserved_bytes as size_t,
143                    c_uchar::from(overwrite_key),
144                )
145            },
146            _comparator: None,
147        }
148    }
149
150    /// Starts building a batch with a custom comparator, size cap, or per-key
151    /// checksums.
152    ///
153    /// ```
154    /// use rust_rocksdb::WriteBatchWithIndex;
155    ///
156    /// let mut batch = WriteBatchWithIndex::builder()
157    ///     .overwrite_key(true)
158    ///     .max_bytes(1 << 20)
159    ///     .protection_bytes_per_key(8)
160    ///     .build();
161    /// batch.put(b"k", b"v");
162    /// assert_eq!(batch.len(), 1);
163    /// ```
164    pub fn builder() -> WriteBatchWithIndexBuilder {
165        WriteBatchWithIndexBuilder {
166            comparator: None,
167            reserved_bytes: 0,
168            overwrite_key: false,
169            max_bytes: 0,
170            protection_bytes_per_key: 0,
171        }
172    }
173
174    pub fn len(&self) -> usize {
175        unsafe { ffi::rocksdb_writebatch_wi_count(self.inner) as usize }
176    }
177
178    /// Return WriteBatch serialized size (in bytes).
179    pub fn size_in_bytes(&self) -> usize {
180        unsafe {
181            let mut batch_size: size_t = 0;
182            ffi::rocksdb_writebatch_wi_data(self.inner, &raw mut batch_size);
183            batch_size
184        }
185    }
186
187    /// Return a reference to a byte array which represents a serialized version of the batch.
188    pub fn data(&self) -> &[u8] {
189        unsafe {
190            let mut batch_size: size_t = 0;
191            let batch_data = ffi::rocksdb_writebatch_wi_data(self.inner, &raw mut batch_size);
192            std::slice::from_raw_parts(batch_data as _, batch_size)
193        }
194    }
195
196    pub fn is_empty(&self) -> bool {
197        self.len() == 0
198    }
199
200    pub fn get_from_batch<K>(&self, key: K, options: &Options) -> Result<Option<Vec<u8>>, Error>
201    where
202        K: AsRef<[u8]>,
203    {
204        let key = key.as_ref();
205        unsafe {
206            let mut value_size: size_t = 0;
207            let value_data = ffi_try!(ffi::rocksdb_writebatch_wi_get_from_batch(
208                self.inner,
209                options.inner,
210                key.as_ptr() as *const c_char,
211                key.len() as size_t,
212                &raw mut value_size
213            ));
214
215            // `value_data` was allocated by `malloc` on the C++ side; copy it
216            // out and release it with `rocksdb_free`.
217            Ok(raw_data_and_free(value_data, value_size))
218        }
219    }
220
221    pub fn get_from_batch_cf<K>(
222        &self,
223        cf: &impl AsColumnFamilyRef,
224        key: K,
225        options: &Options,
226    ) -> Result<Option<Vec<u8>>, Error>
227    where
228        K: AsRef<[u8]>,
229    {
230        let key = key.as_ref();
231        unsafe {
232            let mut value_size: size_t = 0;
233            let value_data = ffi_try!(ffi::rocksdb_writebatch_wi_get_from_batch_cf(
234                self.inner,
235                options.inner,
236                cf.inner(),
237                key.as_ptr() as *const c_char,
238                key.len() as size_t,
239                &raw mut value_size
240            ));
241
242            // `value_data` was allocated by `malloc` on the C++ side; copy it
243            // out and release it with `rocksdb_free`.
244            Ok(raw_data_and_free(value_data, value_size))
245        }
246    }
247
248    pub fn get_from_batch_and_db<T, I, K>(
249        &self,
250        db: &DBCommon<T, I>,
251        key: K,
252        readopts: &ReadOptions,
253    ) -> Result<Option<Vec<u8>>, Error>
254    where
255        T: ThreadMode,
256        I: DBInner,
257        K: AsRef<[u8]>,
258    {
259        if readopts.inner.is_null() {
260            return Err(Error::new(
261                "Unable to create RocksDB read options. This is a fairly trivial call, and its \
262                 failure may be indicative of a mis-compiled or mis-loaded RocksDB library."
263                    .to_owned(),
264            ));
265        }
266
267        let key = key.as_ref();
268        unsafe {
269            let mut value_size: size_t = 0;
270            let value_data = ffi_try!(ffi::rocksdb_writebatch_wi_get_from_batch_and_db(
271                self.inner,
272                db.inner.inner(),
273                readopts.inner,
274                key.as_ptr() as *const c_char,
275                key.len() as size_t,
276                &raw mut value_size
277            ));
278
279            // `value_data` was allocated by `malloc` on the C++ side; copy it
280            // out and release it with `rocksdb_free`.
281            Ok(raw_data_and_free(value_data, value_size))
282        }
283    }
284
285    /// The returned slice pins a block inside `db`'s block cache, so its
286    /// lifetime is tied to `db` rather than to `self`. Letting lifetime elision
287    /// pick `&self` here would allow the slice to outlive the database and
288    /// release a cache handle into a destroyed cache.
289    pub fn get_pinned_from_batch_and_db<'db, T, I, K>(
290        &self,
291        db: &'db DBCommon<T, I>,
292        key: K,
293        readopts: &ReadOptions,
294    ) -> Result<Option<DBPinnableSlice<'db>>, Error>
295    where
296        T: ThreadMode,
297        I: DBInner,
298        K: AsRef<[u8]>,
299    {
300        if readopts.inner.is_null() {
301            return Err(Error::new(
302                "Unable to create RocksDB read options. This is a fairly trivial call, and its \
303                 failure may be indicative of a mis-compiled or mis-loaded RocksDB library."
304                    .to_owned(),
305            ));
306        }
307
308        let key = key.as_ref();
309        unsafe {
310            let value_data = ffi_try!(ffi::rocksdb_writebatch_wi_get_pinned_from_batch_and_db(
311                self.inner,
312                db.inner.inner(),
313                readopts.inner,
314                key.as_ptr() as *const c_char,
315                key.len() as size_t,
316            ));
317
318            if value_data.is_null() {
319                Ok(None)
320            } else {
321                Ok(Some(DBPinnableSlice::from_c(value_data)))
322            }
323        }
324    }
325
326    pub fn get_from_batch_and_db_cf<T, I, K>(
327        &self,
328        db: &DBCommon<T, I>,
329        cf: &impl AsColumnFamilyRef,
330        key: K,
331        readopts: &ReadOptions,
332    ) -> Result<Option<Vec<u8>>, Error>
333    where
334        T: ThreadMode,
335        I: DBInner,
336        K: AsRef<[u8]>,
337    {
338        if readopts.inner.is_null() {
339            return Err(Error::new(
340                "Unable to create RocksDB read options. This is a fairly trivial call, and its \
341                 failure may be indicative of a mis-compiled or mis-loaded RocksDB library."
342                    .to_owned(),
343            ));
344        }
345
346        let key = key.as_ref();
347        unsafe {
348            let mut value_size: size_t = 0;
349            let value_data = ffi_try!(ffi::rocksdb_writebatch_wi_get_from_batch_and_db_cf(
350                self.inner,
351                db.inner.inner(),
352                readopts.inner,
353                cf.inner(),
354                key.as_ptr() as *const c_char,
355                key.len() as size_t,
356                &raw mut value_size
357            ));
358
359            // `value_data` was allocated by `malloc` on the C++ side; copy it
360            // out and release it with `rocksdb_free`.
361            Ok(raw_data_and_free(value_data, value_size))
362        }
363    }
364
365    /// The returned slice pins a block inside `db`'s block cache, so its
366    /// lifetime is tied to `db` rather than to `self`. See
367    /// [`Self::get_pinned_from_batch_and_db`].
368    pub fn get_pinned_from_batch_and_db_cf<'db, T, I, K>(
369        &self,
370        db: &'db DBCommon<T, I>,
371        cf: &impl AsColumnFamilyRef,
372        key: K,
373        readopts: &ReadOptions,
374    ) -> Result<Option<DBPinnableSlice<'db>>, Error>
375    where
376        T: ThreadMode,
377        I: DBInner,
378        K: AsRef<[u8]>,
379    {
380        if readopts.inner.is_null() {
381            return Err(Error::new(
382                "Unable to create RocksDB read options. This is a fairly trivial call, and its \
383                 failure may be indicative of a mis-compiled or mis-loaded RocksDB library."
384                    .to_owned(),
385            ));
386        }
387
388        let key = key.as_ref();
389        unsafe {
390            let value_data = ffi_try!(ffi::rocksdb_writebatch_wi_get_pinned_from_batch_and_db_cf(
391                self.inner,
392                db.inner.inner(),
393                readopts.inner,
394                cf.inner(),
395                key.as_ptr() as *const c_char,
396                key.len() as size_t,
397            ));
398
399            if value_data.is_null() {
400                Ok(None)
401            } else {
402                Ok(Some(DBPinnableSlice::from_c(value_data)))
403            }
404        }
405    }
406
407    /// Insert a value into the database under the given key.
408    pub fn put<K, V>(&mut self, key: K, value: V)
409    where
410        K: AsRef<[u8]>,
411        V: AsRef<[u8]>,
412    {
413        let key = key.as_ref();
414        let value = value.as_ref();
415
416        unsafe {
417            ffi::rocksdb_writebatch_wi_put(
418                self.inner,
419                key.as_ptr() as *const c_char,
420                key.len() as size_t,
421                value.as_ptr() as *const c_char,
422                value.len() as size_t,
423            );
424        }
425    }
426
427    pub fn put_cf<K, V>(&mut self, cf: &impl AsColumnFamilyRef, key: K, value: V)
428    where
429        K: AsRef<[u8]>,
430        V: AsRef<[u8]>,
431    {
432        let key = key.as_ref();
433        let value = value.as_ref();
434
435        unsafe {
436            ffi::rocksdb_writebatch_wi_put_cf(
437                self.inner,
438                cf.inner(),
439                key.as_ptr() as *const c_char,
440                key.len() as size_t,
441                value.as_ptr() as *const c_char,
442                value.len() as size_t,
443            );
444        }
445    }
446
447    pub fn merge<K, V>(&mut self, key: K, value: V)
448    where
449        K: AsRef<[u8]>,
450        V: AsRef<[u8]>,
451    {
452        let key = key.as_ref();
453        let value = value.as_ref();
454
455        unsafe {
456            ffi::rocksdb_writebatch_wi_merge(
457                self.inner,
458                key.as_ptr() as *const c_char,
459                key.len() as size_t,
460                value.as_ptr() as *const c_char,
461                value.len() as size_t,
462            );
463        }
464    }
465
466    pub fn merge_cf<K, V>(&mut self, cf: &impl AsColumnFamilyRef, key: K, value: V)
467    where
468        K: AsRef<[u8]>,
469        V: AsRef<[u8]>,
470    {
471        let key = key.as_ref();
472        let value = value.as_ref();
473
474        unsafe {
475            ffi::rocksdb_writebatch_wi_merge_cf(
476                self.inner,
477                cf.inner(),
478                key.as_ptr() as *const c_char,
479                key.len() as size_t,
480                value.as_ptr() as *const c_char,
481                value.len() as size_t,
482            );
483        }
484    }
485
486    /// Removes the database entry for key. Does nothing if the key was not found.
487    pub fn delete<K: AsRef<[u8]>>(&mut self, key: K) {
488        let key = key.as_ref();
489
490        unsafe {
491            ffi::rocksdb_writebatch_wi_delete(
492                self.inner,
493                key.as_ptr() as *const c_char,
494                key.len() as size_t,
495            );
496        }
497    }
498
499    pub fn delete_cf<K: AsRef<[u8]>>(&mut self, cf: &impl AsColumnFamilyRef, key: K) {
500        let key = key.as_ref();
501
502        unsafe {
503            ffi::rocksdb_writebatch_wi_delete_cf(
504                self.inner,
505                cf.inner(),
506                key.as_ptr() as *const c_char,
507                key.len() as size_t,
508            );
509        }
510    }
511
512    /// Removes the database entry for a key that was written exactly once.
513    ///
514    /// This is a cheaper delete than [`delete`](Self::delete), but it is only
515    /// correct when the key has had at most one `put` and no `merge` since the
516    /// last delete of that key. Using it on a key that was written more than
517    /// once leaves an older version visible, and RocksDB does not report that
518    /// as an error.
519    pub fn single_delete<K: AsRef<[u8]>>(&mut self, key: K) {
520        let key = key.as_ref();
521
522        unsafe {
523            ffi::rocksdb_writebatch_wi_singledelete(
524                self.inner,
525                key.as_ptr() as *const c_char,
526                key.len() as size_t,
527            );
528        }
529    }
530
531    /// Removes the entry for a write-once key in the given column family.
532    ///
533    /// See [`single_delete`](Self::single_delete) for when this is safe to use.
534    pub fn single_delete_cf<K: AsRef<[u8]>>(&mut self, cf: &impl AsColumnFamilyRef, key: K) {
535        let key = key.as_ref();
536
537        unsafe {
538            ffi::rocksdb_writebatch_wi_singledelete_cf(
539                self.inner,
540                cf.inner(),
541                key.as_ptr() as *const c_char,
542                key.len() as size_t,
543            );
544        }
545    }
546
547    /// Removes entries in the range `[from, to)`.
548    ///
549    /// Range deletes are recorded in the batch but they are not indexed. Reads
550    /// and iterators that go through the batch, such as
551    /// [`get_from_batch`](Self::get_from_batch) and
552    /// [`iterator_with_base`](Self::iterator_with_base), do not see them. They
553    /// only take effect once the batch is written to the database.
554    pub fn delete_range<K: AsRef<[u8]>>(&mut self, from: K, to: K) {
555        let (start_key, end_key) = (from.as_ref(), to.as_ref());
556
557        unsafe {
558            ffi::rocksdb_writebatch_wi_delete_range(
559                self.inner,
560                start_key.as_ptr() as *const c_char,
561                start_key.len() as size_t,
562                end_key.as_ptr() as *const c_char,
563                end_key.len() as size_t,
564            );
565        }
566    }
567
568    /// Removes entries in the range `[from, to)` of one column family.
569    ///
570    /// See [`delete_range`](Self::delete_range) for why these are invisible to
571    /// reads through the batch.
572    pub fn delete_range_cf<K: AsRef<[u8]>>(&mut self, cf: &impl AsColumnFamilyRef, from: K, to: K) {
573        let (start_key, end_key) = (from.as_ref(), to.as_ref());
574
575        unsafe {
576            ffi::rocksdb_writebatch_wi_delete_range_cf(
577                self.inner,
578                cf.inner(),
579                start_key.as_ptr() as *const c_char,
580                start_key.len() as size_t,
581                end_key.as_ptr() as *const c_char,
582                end_key.len() as size_t,
583            );
584        }
585    }
586
587    /// Append a blob of arbitrary size to the records in this batch.
588    ///
589    /// The blob goes to the write-ahead log but never to an SST file, and it
590    /// consumes no sequence number and does not change [`len`](Self::len).
591    pub fn put_log_data<V: AsRef<[u8]>>(&mut self, log_data: V) {
592        let log_data = log_data.as_ref();
593
594        unsafe {
595            ffi::rocksdb_writebatch_wi_put_log_data(
596                self.inner,
597                log_data.as_ptr() as *const c_char,
598                log_data.len() as size_t,
599            );
600        }
601    }
602
603    /// Record the current state of the batch so it can be undone later.
604    ///
605    /// Save points nest, so each call pushes onto a stack that
606    /// [`rollback_to_save_point`](Self::rollback_to_save_point) pops from.
607    pub fn set_save_point(&mut self) {
608        unsafe {
609            ffi::rocksdb_writebatch_wi_set_save_point(self.inner);
610        }
611    }
612
613    /// Undo every operation recorded since the most recent save point, and pop
614    /// that save point.
615    ///
616    /// Returns an error if there is no save point to roll back to.
617    pub fn rollback_to_save_point(&mut self) -> Result<(), Error> {
618        unsafe {
619            ffi_try!(ffi::rocksdb_writebatch_wi_rollback_to_save_point(
620                self.inner
621            ));
622        }
623        Ok(())
624    }
625
626    /// Overwrite the user-defined timestamp on every entry in the batch.
627    ///
628    /// `get_ts_size` is called with each column family id the batch touches and
629    /// must return the timestamp width configured for that column family, or 0
630    /// if it does not use timestamps. `ts` must be exactly as wide as every
631    /// non-zero size it returns.
632    ///
633    /// # Safety
634    ///
635    /// Every key already recorded for a column family whose `get_ts_size`
636    /// returns a non-zero width must be at least that many bytes long, which in
637    /// practice means it was written through one of the `_with_ts` methods and
638    /// already carries a timestamp suffix of exactly that width. RocksDB
639    /// overwrites the last `width` bytes of each key without checking that the
640    /// key is that long, so a shorter key makes it write in front of the key and
641    /// corrupt the heap. Mixing plain [`put`](Self::put) with a non-zero width
642    /// for the same column family is what usually triggers this.
643    ///
644    /// # Errors
645    ///
646    /// Returns an error if `ts` is empty, if its length differs from a non-zero
647    /// width returned by `get_ts_size`, or if `get_ts_size` reports that it
648    /// could not find the width for a column family.
649    pub unsafe fn update_timestamps<S, F>(&mut self, ts: S, mut get_ts_size: F) -> Result<(), Error>
650    where
651        S: AsRef<[u8]>,
652        F: FnMut(u32) -> usize,
653    {
654        let ts = ts.as_ref();
655        let state = std::ptr::from_mut(&mut get_ts_size).cast::<c_void>();
656        unsafe {
657            ffi_try!(ffi::rocksdb_writebatch_wi_update_timestamps(
658                self.inner,
659                ts.as_ptr() as *const c_char,
660                ts.len() as size_t,
661                state,
662                Some(get_ts_size_callback::<F>),
663            ));
664        }
665        Ok(())
666    }
667
668    /// Clear all updates buffered in this batch.
669    pub fn clear(&mut self) {
670        unsafe {
671            ffi::rocksdb_writebatch_wi_clear(self.inner);
672        }
673    }
674
675    /// The returned iterator reads directly out of this batch's internal
676    /// skip-list and write buffer, so it must not outlive the batch. Binding
677    /// `&self` to the same lifetime as the base iterator is what enforces that;
678    /// with an independent lifetime on `&self` the iterator could outlive the
679    /// batch and read freed memory.
680    pub fn iterator_with_base<'a, D>(
681        &'a self,
682        base_iterator: DBRawIteratorWithThreadMode<'a, D>,
683    ) -> DBRawIteratorWithThreadMode<'a, D>
684    where
685        D: DBAccess,
686    {
687        let (base_iterator_inner, readopts) = base_iterator.into_inner();
688
689        let iterator = unsafe {
690            ffi::rocksdb_writebatch_wi_create_iterator_with_base_readopts(
691                self.inner,
692                base_iterator_inner.as_ptr(),
693                readopts.as_ptr(),
694            )
695        };
696
697        // The delta iterator keeps its own raw pointers to the iterate bounds
698        // in these options, so it has to hold the same object the base
699        // iterator was built from, not an equivalent copy.
700        DBRawIteratorWithThreadMode::from_inner(iterator, readopts)
701    }
702
703    /// The returned iterator reads directly out of this batch, so it must not
704    /// outlive the batch. See [`Self::iterator_with_base`].
705    pub fn iterator_with_base_cf<'a, D>(
706        &'a self,
707        base_iterator: DBRawIteratorWithThreadMode<'a, D>,
708        cf: &impl AsColumnFamilyRef,
709    ) -> DBRawIteratorWithThreadMode<'a, D>
710    where
711        D: DBAccess,
712    {
713        let (base_iterator_inner, readopts) = base_iterator.into_inner();
714
715        let iterator = unsafe {
716            ffi::rocksdb_writebatch_wi_create_iterator_with_base_cf_readopts(
717                self.inner,
718                base_iterator_inner.as_ptr(),
719                cf.inner(),
720                readopts.as_ptr(),
721            )
722        };
723
724        DBRawIteratorWithThreadMode::from_inner(iterator, readopts)
725    }
726}
727
728impl Drop for WriteBatchWithIndex {
729    fn drop(&mut self) {
730        unsafe {
731            ffi::rocksdb_writebatch_wi_destroy(self.inner);
732        }
733    }
734}
735
736unsafe impl Send for WriteBatchWithIndex {}