Skip to main content

rust_rocksdb/
write_batch.rs

1// Copyright 2020 Tyler Neely
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use crate::{AsColumnFamilyRef, ffi};
16use libc::{c_char, c_int, c_void, size_t};
17use std::{io::IoSlice, slice};
18
19const INLINE_WRITE_BATCH_PARTS: usize = 4;
20
21enum WriteBatchPartStorage {
22    Inline([ffi::rocksdb_slice_t; INLINE_WRITE_BATCH_PARTS]),
23    Heap(Vec<ffi::rocksdb_slice_t>),
24}
25
26struct WriteBatchParts {
27    storage: WriteBatchPartStorage,
28    count: c_int,
29}
30
31impl WriteBatchParts {
32    fn new(parts: &[IoSlice<'_>], name: &str) -> Result<Self, crate::Error> {
33        let count = c_int::try_from(parts.len()).map_err(|_| {
34            crate::Error::new(format!(
35                "{name} has {} parts; expected at most {}",
36                parts.len(),
37                c_int::MAX
38            ))
39        })?;
40        let storage = if parts.len() <= INLINE_WRITE_BATCH_PARTS {
41            Self::inline(parts)
42        } else {
43            Self::heap(parts)
44        };
45        Ok(Self { storage, count })
46    }
47
48    fn inline(parts: &[IoSlice<'_>]) -> WriteBatchPartStorage {
49        let mut slices = std::array::from_fn(|_| ffi::rocksdb_slice_t {
50            data: std::ptr::null(),
51            size: 0,
52        });
53        for (index, part) in parts.iter().enumerate() {
54            slices[index] = ffi::rocksdb_slice_t {
55                data: part.as_ptr().cast(),
56                size: part.len(),
57            };
58        }
59        WriteBatchPartStorage::Inline(slices)
60    }
61
62    fn heap(parts: &[IoSlice<'_>]) -> WriteBatchPartStorage {
63        WriteBatchPartStorage::Heap(
64            parts
65                .iter()
66                .map(|part| ffi::rocksdb_slice_t {
67                    data: part.as_ptr().cast(),
68                    size: part.len(),
69                })
70                .collect(),
71        )
72    }
73
74    fn as_ptr(&self) -> *const ffi::rocksdb_slice_t {
75        match &self.storage {
76            WriteBatchPartStorage::Inline(slices) => slices.as_ptr(),
77            WriteBatchPartStorage::Heap(slices) => slices.as_ptr(),
78        }
79    }
80}
81
82/// A type alias to keep compatibility. See [`WriteBatchWithTransaction`] for details
83pub type WriteBatch = WriteBatchWithTransaction<false>;
84
85/// An atomic batch of write operations.
86///
87/// [`delete_range`](#method.delete_range) is not supported in [`Transaction`].
88///
89/// Making an atomic commit of several writes:
90///
91/// ```
92/// use rust_rocksdb::{DB, Options, WriteBatchWithTransaction};
93///
94/// let tempdir = tempfile::Builder::new()
95///     .prefix("_path_for_rocksdb_storage1")
96///     .tempdir()
97///     .expect("Failed to create temporary path for the _path_for_rocksdb_storage1");
98/// let path = tempdir.path();
99/// {
100///     let db = DB::open_default(path).unwrap();
101///     let mut batch = WriteBatchWithTransaction::<false>::default();
102///     batch.put(b"my key", b"my value");
103///     batch.put(b"key2", b"value2");
104///     batch.put(b"key3", b"value3");
105///
106///     // delete_range is supported when use without transaction
107///     batch.delete_range(b"key2", b"key3");
108///
109///     db.write(&batch); // Atomically commits the batch
110/// }
111/// let _ = DB::destroy(&Options::default(), path);
112/// ```
113///
114/// [`Transaction`]: crate::Transaction
115pub struct WriteBatchWithTransaction<const TRANSACTION: bool> {
116    pub(crate) inner: *mut ffi::rocksdb_writebatch_t,
117}
118
119/// Receives the puts and deletes of a write batch.
120///
121/// The application must provide an implementation of this trait when
122/// iterating the operations within a `WriteBatch`
123pub trait WriteBatchIterator {
124    /// Called with a key and value that were `put` into the batch.
125    fn put(&mut self, key: &[u8], value: &[u8]);
126    /// Called with a key that was `delete`d from the batch.
127    fn delete(&mut self, key: &[u8]);
128    /// Called with a blob that was appended by
129    /// [`put_log_data`](WriteBatchWithTransaction::put_log_data).
130    ///
131    /// Log data is interleaved with the puts and deletes in insertion order.
132    /// The default implementation ignores it.
133    fn log_data(&mut self, _blob: &[u8]) {}
134}
135
136/// Receives the puts, deletes, and merges of a write batch with column family
137/// information.
138///
139/// This trait extends write batch iteration to support column family-specific
140/// operations. The application must implement this trait when iterating
141/// operations within a WriteBatch that contains column family-aware writes.
142///
143/// Note that for the default column family "default", the column family ID is 0.
144pub trait WriteBatchIteratorCf {
145    /// Called with a column family ID, key, and value that were put into
146    /// the specific column family of the batch.
147    fn put_cf(&mut self, cf_id: u32, key: &[u8], value: &[u8]);
148    /// Called with a column family ID and key that were `delete`d from the
149    /// specific column family of the batch.
150    fn delete_cf(&mut self, cf_id: u32, key: &[u8]);
151    /// Called with a column family ID, key, and value that were `merge`d into
152    /// the specific column family of the batch.
153    /// Merge operations combine the provided value with the existing value at
154    /// the key using a database-defined merge operator.
155    fn merge_cf(&mut self, cf_id: u32, key: &[u8], value: &[u8]);
156    /// Called with a blob that was appended by
157    /// [`put_log_data`](WriteBatchWithTransaction::put_log_data).
158    ///
159    /// Log data belongs to no column family, so there is no id here. It is
160    /// interleaved with the other records in insertion order. The default
161    /// implementation ignores it.
162    fn log_data(&mut self, _blob: &[u8]) {}
163}
164
165pub(crate) unsafe extern "C" fn writebatch_put_callback<T: WriteBatchIterator>(
166    state: *mut c_void,
167    k: *const c_char,
168    klen: usize,
169    v: *const c_char,
170    vlen: usize,
171) {
172    unsafe {
173        let callbacks = &mut *(state as *mut T);
174        let key = slice::from_raw_parts(k.cast::<u8>(), klen);
175        let value = slice::from_raw_parts(v.cast::<u8>(), vlen);
176        callbacks.put(key, value);
177    }
178}
179
180pub(crate) unsafe extern "C" fn writebatch_delete_callback<T: WriteBatchIterator>(
181    state: *mut c_void,
182    k: *const c_char,
183    klen: usize,
184) {
185    unsafe {
186        let callbacks = &mut *(state as *mut T);
187        let key = slice::from_raw_parts(k.cast::<u8>(), klen);
188        callbacks.delete(key);
189    }
190}
191
192unsafe extern "C" fn writebatch_log_data_callback<T: WriteBatchIterator>(
193    state: *mut c_void,
194    blob: *const c_char,
195    blob_len: usize,
196) {
197    unsafe {
198        let callbacks = &mut *(state as *mut T);
199        callbacks.log_data(slice::from_raw_parts(blob.cast::<u8>(), blob_len));
200    }
201}
202
203unsafe extern "C" fn writebatch_log_data_cf_callback<T: WriteBatchIteratorCf>(
204    state: *mut c_void,
205    blob: *const c_char,
206    blob_len: usize,
207) {
208    unsafe {
209        let callbacks = &mut *(state as *mut T);
210        callbacks.log_data(slice::from_raw_parts(blob.cast::<u8>(), blob_len));
211    }
212}
213
214/// Trampoline for `rocksdb_writebatch{,_wi}_update_timestamps`, which asks the
215/// caller how wide the timestamp is for each column family it encounters.
216pub(crate) unsafe extern "C" fn get_ts_size_callback<F: FnMut(u32) -> usize>(
217    state: *mut c_void,
218    cf_id: u32,
219) -> size_t {
220    unsafe { (*(state as *mut F))(cf_id) }
221}
222
223unsafe extern "C" fn writebatch_put_cf_callback<T: WriteBatchIteratorCf>(
224    state: *mut c_void,
225    cfid: u32,
226    k: *const c_char,
227    klen: usize,
228    v: *const c_char,
229    vlen: usize,
230) {
231    unsafe {
232        let callbacks = &mut *(state as *mut T);
233        let key = slice::from_raw_parts(k.cast::<u8>(), klen);
234        let value = slice::from_raw_parts(v.cast::<u8>(), vlen);
235        callbacks.put_cf(cfid, key, value);
236    }
237}
238
239unsafe extern "C" fn writebatch_delete_cf_callback<T: WriteBatchIteratorCf>(
240    state: *mut c_void,
241    cfid: u32,
242    k: *const c_char,
243    klen: usize,
244) {
245    unsafe {
246        let callbacks = &mut *(state as *mut T);
247        let key = slice::from_raw_parts(k.cast::<u8>(), klen);
248        callbacks.delete_cf(cfid, key);
249    }
250}
251
252unsafe extern "C" fn writebatch_merge_cf_callback<T: WriteBatchIteratorCf>(
253    state: *mut c_void,
254    cfid: u32,
255    k: *const c_char,
256    klen: usize,
257    v: *const c_char,
258    vlen: usize,
259) {
260    unsafe {
261        let callbacks = &mut *(state as *mut T);
262        let key = slice::from_raw_parts(k.cast::<u8>(), klen);
263        let value = slice::from_raw_parts(v.cast::<u8>(), vlen);
264        callbacks.merge_cf(cfid, key, value);
265    }
266}
267
268impl<const TRANSACTION: bool> WriteBatchWithTransaction<TRANSACTION> {
269    /// Create a new `WriteBatch` without allocating memory.
270    pub fn new() -> Self {
271        Self {
272            inner: unsafe { ffi::rocksdb_writebatch_create() },
273        }
274    }
275
276    /// Creates `WriteBatch` with the specified `capacity` in bytes. Allocates immediately.
277    pub fn with_capacity_bytes(capacity_bytes: usize) -> Self {
278        Self {
279            // zeroes from default constructor
280            // https://github.com/facebook/rocksdb/blob/0f35db55d86ea8699ea936c9e2a4e34c82458d6b/include/rocksdb/write_batch.h#L66
281            inner: unsafe { ffi::rocksdb_writebatch_create_with_params(capacity_bytes, 0, 0, 0) },
282        }
283    }
284
285    /// Construct with a reference to a byte array serialized by [`WriteBatch`].
286    pub fn from_data(data: &[u8]) -> Self {
287        unsafe {
288            let ptr = data.as_ptr();
289            let len = data.len();
290            Self {
291                inner: ffi::rocksdb_writebatch_create_from(
292                    ptr as *const libc::c_char,
293                    len as size_t,
294                ),
295            }
296        }
297    }
298
299    pub fn len(&self) -> usize {
300        unsafe { ffi::rocksdb_writebatch_count(self.inner) as usize }
301    }
302
303    /// Return WriteBatch serialized size (in bytes).
304    pub fn size_in_bytes(&self) -> usize {
305        unsafe {
306            let mut batch_size: size_t = 0;
307            ffi::rocksdb_writebatch_data(self.inner, &raw mut batch_size);
308            batch_size
309        }
310    }
311
312    /// Return a reference to a byte array which represents a serialized version of the batch.
313    pub fn data(&self) -> &[u8] {
314        unsafe {
315            let mut batch_size: size_t = 0;
316            let batch_data = ffi::rocksdb_writebatch_data(self.inner, &raw mut batch_size);
317            std::slice::from_raw_parts(batch_data as _, batch_size)
318        }
319    }
320
321    pub fn is_empty(&self) -> bool {
322        self.len() == 0
323    }
324
325    /// Iterate the put, delete, and log data operations within this write
326    /// batch. Note that this does _not_ return an `Iterator` but instead will
327    /// invoke the `put()`, `delete()`, and `log_data()` member functions of the
328    /// provided `WriteBatchIterator` trait implementation.
329    pub fn iterate<T: WriteBatchIterator>(&self, callbacks: &mut T) {
330        let state = std::ptr::from_mut::<T>(callbacks) as *mut c_void;
331        unsafe {
332            ffi::rocksdb_writebatch_iterate_ld(
333                self.inner,
334                state,
335                Some(writebatch_put_callback::<T>),
336                Some(writebatch_delete_callback::<T>),
337                Some(writebatch_log_data_callback::<T>),
338            );
339        }
340    }
341
342    /// Iterate the put, delete, merge, and log data operations within this write batch with
343    /// column family information. Note that this does _not_ return an `Iterator` but instead will
344    /// invoke the `put_cf()`, `delete_cf()`, `merge_cf()`, and `log_data()` member functions of
345    /// the provided `WriteBatchIteratorCf` trait implementation.
346    ///
347    /// # Notes
348    /// - For operations on the default column family ("default"), the `cf_id` parameter passed to
349    ///   the callbacks will be 0
350    pub fn iterate_cf<T: WriteBatchIteratorCf>(&self, callbacks: &mut T) {
351        let state = std::ptr::from_mut::<T>(callbacks) as *mut c_void;
352        unsafe {
353            ffi::rocksdb_writebatch_iterate_cf_ld(
354                self.inner,
355                state,
356                Some(writebatch_put_cf_callback::<T>),
357                Some(writebatch_delete_cf_callback::<T>),
358                Some(writebatch_merge_cf_callback::<T>),
359                Some(writebatch_log_data_cf_callback::<T>),
360            );
361        }
362    }
363
364    /// Insert a value into the database under the given key.
365    pub fn put<K, V>(&mut self, key: K, value: V)
366    where
367        K: AsRef<[u8]>,
368        V: AsRef<[u8]>,
369    {
370        let key = key.as_ref();
371        let value = value.as_ref();
372
373        unsafe {
374            ffi::rocksdb_writebatch_put(
375                self.inner,
376                key.as_ptr() as *const c_char,
377                key.len() as size_t,
378                value.as_ptr() as *const c_char,
379                value.len() as size_t,
380            );
381        }
382    }
383
384    /// Inserts one key and value assembled from multiple byte slices.
385    ///
386    /// This avoids concatenating the parts in Rust. RocksDB copies the key and
387    /// value parts into the write batch during this call, so the slices do not
388    /// need to outlive the method.
389    pub fn put_vectored(
390        &mut self,
391        key: &[IoSlice<'_>],
392        value: &[IoSlice<'_>],
393    ) -> Result<(), crate::Error> {
394        let key = WriteBatchParts::new(key, "key")?;
395        let value = WriteBatchParts::new(value, "value")?;
396        unsafe {
397            ffi_try!(ffi::rust_rocksdb_writebatch_put_slices(
398                self.inner,
399                key.count,
400                key.as_ptr(),
401                value.count,
402                value.as_ptr(),
403            ));
404        }
405        Ok(())
406    }
407
408    /// Insert a value into the specific column family of the database under the given key.
409    pub fn put_cf<K, V>(&mut self, cf: &impl AsColumnFamilyRef, key: K, value: V)
410    where
411        K: AsRef<[u8]>,
412        V: AsRef<[u8]>,
413    {
414        let key = key.as_ref();
415        let value = value.as_ref();
416
417        unsafe {
418            ffi::rocksdb_writebatch_put_cf(
419                self.inner,
420                cf.inner(),
421                key.as_ptr() as *const c_char,
422                key.len() as size_t,
423                value.as_ptr() as *const c_char,
424                value.len() as size_t,
425            );
426        }
427    }
428
429    /// Inserts one key and value assembled from multiple byte slices into a column family.
430    ///
431    /// This avoids concatenating the parts in Rust. RocksDB copies the key and
432    /// value parts into the write batch during this call, so the slices do not
433    /// need to outlive the method.
434    pub fn put_cf_vectored(
435        &mut self,
436        cf: &impl AsColumnFamilyRef,
437        key: &[IoSlice<'_>],
438        value: &[IoSlice<'_>],
439    ) -> Result<(), crate::Error> {
440        let key = WriteBatchParts::new(key, "key")?;
441        let value = WriteBatchParts::new(value, "value")?;
442        unsafe {
443            ffi_try!(ffi::rust_rocksdb_writebatch_put_slices_cf(
444                self.inner,
445                cf.inner(),
446                key.count,
447                key.as_ptr(),
448                value.count,
449                value.as_ptr(),
450            ));
451        }
452        Ok(())
453    }
454
455    /// Insert a value into the specific column family of the database
456    /// under the given key with timestamp.
457    pub fn put_cf_with_ts<K, V, S>(&mut self, cf: &impl AsColumnFamilyRef, key: K, ts: S, value: V)
458    where
459        K: AsRef<[u8]>,
460        V: AsRef<[u8]>,
461        S: AsRef<[u8]>,
462    {
463        let key = key.as_ref();
464        let value = value.as_ref();
465        let ts = ts.as_ref();
466        unsafe {
467            ffi::rocksdb_writebatch_put_cf_with_ts(
468                self.inner,
469                cf.inner(),
470                key.as_ptr() as *const c_char,
471                key.len() as size_t,
472                ts.as_ptr() as *const c_char,
473                ts.len() as size_t,
474                value.as_ptr() as *const c_char,
475                value.len() as size_t,
476            );
477        }
478    }
479
480    pub fn merge<K, V>(&mut self, key: K, value: V)
481    where
482        K: AsRef<[u8]>,
483        V: AsRef<[u8]>,
484    {
485        let key = key.as_ref();
486        let value = value.as_ref();
487
488        unsafe {
489            ffi::rocksdb_writebatch_merge(
490                self.inner,
491                key.as_ptr() as *const c_char,
492                key.len() as size_t,
493                value.as_ptr() as *const c_char,
494                value.len() as size_t,
495            );
496        }
497    }
498
499    /// Merges one key and value assembled from multiple byte slices.
500    ///
501    /// This avoids concatenating the parts in Rust. RocksDB copies the key and
502    /// value parts into the write batch during this call, so the slices do not
503    /// need to outlive the method.
504    pub fn merge_vectored(
505        &mut self,
506        key: &[IoSlice<'_>],
507        value: &[IoSlice<'_>],
508    ) -> Result<(), crate::Error> {
509        let key = WriteBatchParts::new(key, "key")?;
510        let value = WriteBatchParts::new(value, "value")?;
511        unsafe {
512            ffi_try!(ffi::rust_rocksdb_writebatch_merge_slices(
513                self.inner,
514                key.count,
515                key.as_ptr(),
516                value.count,
517                value.as_ptr(),
518            ));
519        }
520        Ok(())
521    }
522
523    pub fn merge_cf<K, V>(&mut self, cf: &impl AsColumnFamilyRef, key: K, value: V)
524    where
525        K: AsRef<[u8]>,
526        V: AsRef<[u8]>,
527    {
528        let key = key.as_ref();
529        let value = value.as_ref();
530
531        unsafe {
532            ffi::rocksdb_writebatch_merge_cf(
533                self.inner,
534                cf.inner(),
535                key.as_ptr() as *const c_char,
536                key.len() as size_t,
537                value.as_ptr() as *const c_char,
538                value.len() as size_t,
539            );
540        }
541    }
542
543    /// Merges one key and value assembled from multiple byte slices in a column family.
544    ///
545    /// This avoids concatenating the parts in Rust. RocksDB copies the key and
546    /// value parts into the write batch during this call, so the slices do not
547    /// need to outlive the method.
548    pub fn merge_cf_vectored(
549        &mut self,
550        cf: &impl AsColumnFamilyRef,
551        key: &[IoSlice<'_>],
552        value: &[IoSlice<'_>],
553    ) -> Result<(), crate::Error> {
554        let key = WriteBatchParts::new(key, "key")?;
555        let value = WriteBatchParts::new(value, "value")?;
556        unsafe {
557            ffi_try!(ffi::rust_rocksdb_writebatch_merge_slices_cf(
558                self.inner,
559                cf.inner(),
560                key.count,
561                key.as_ptr(),
562                value.count,
563                value.as_ptr(),
564            ));
565        }
566        Ok(())
567    }
568
569    /// Removes the database entry for key. Does nothing if the key was not found.
570    pub fn delete<K: AsRef<[u8]>>(&mut self, key: K) {
571        let key = key.as_ref();
572
573        unsafe {
574            ffi::rocksdb_writebatch_delete(
575                self.inner,
576                key.as_ptr() as *const c_char,
577                key.len() as size_t,
578            );
579        }
580    }
581
582    /// Removes the entry for one key assembled from multiple byte slices.
583    ///
584    /// This avoids concatenating the parts in Rust. RocksDB copies the key
585    /// parts into the write batch during this call, so the slices do not need
586    /// to outlive the method.
587    pub fn delete_vectored(&mut self, key: &[IoSlice<'_>]) -> Result<(), crate::Error> {
588        let key = WriteBatchParts::new(key, "key")?;
589        unsafe {
590            ffi_try!(ffi::rust_rocksdb_writebatch_delete_slices(
591                self.inner,
592                key.count,
593                key.as_ptr(),
594            ));
595        }
596        Ok(())
597    }
598
599    /// Removes the database entry in the specific column family for key.
600    /// Does nothing if the key was not found.
601    pub fn delete_cf<K: AsRef<[u8]>>(&mut self, cf: &impl AsColumnFamilyRef, key: K) {
602        let key = key.as_ref();
603
604        unsafe {
605            ffi::rocksdb_writebatch_delete_cf(
606                self.inner,
607                cf.inner(),
608                key.as_ptr() as *const c_char,
609                key.len() as size_t,
610            );
611        }
612    }
613
614    /// Removes the entry for one key assembled from multiple byte slices in a column family.
615    ///
616    /// This avoids concatenating the parts in Rust. RocksDB copies the key
617    /// parts into the write batch during this call, so the slices do not need
618    /// to outlive the method.
619    pub fn delete_cf_vectored(
620        &mut self,
621        cf: &impl AsColumnFamilyRef,
622        key: &[IoSlice<'_>],
623    ) -> Result<(), crate::Error> {
624        let key = WriteBatchParts::new(key, "key")?;
625        unsafe {
626            ffi_try!(ffi::rust_rocksdb_writebatch_delete_slices_cf(
627                self.inner,
628                cf.inner(),
629                key.count,
630                key.as_ptr(),
631            ));
632        }
633        Ok(())
634    }
635
636    /// Removes the database entry for a key that was written exactly once.
637    ///
638    /// This is a cheaper delete than [`delete`](Self::delete), but it is only
639    /// correct when the key has had at most one `put` and no `merge` since the
640    /// last delete of that key. Using it on a key that was written more than
641    /// once leaves an older version of the key visible, and RocksDB does not
642    /// report that as an error.
643    pub fn single_delete<K: AsRef<[u8]>>(&mut self, key: K) {
644        let key = key.as_ref();
645
646        unsafe {
647            ffi::rocksdb_writebatch_singledelete(
648                self.inner,
649                key.as_ptr() as *const c_char,
650                key.len() as size_t,
651            );
652        }
653    }
654
655    /// Removes the entry for a write-once key in the given column family.
656    ///
657    /// See [`single_delete`](Self::single_delete) for when this is safe to use.
658    pub fn single_delete_cf<K: AsRef<[u8]>>(&mut self, cf: &impl AsColumnFamilyRef, key: K) {
659        let key = key.as_ref();
660
661        unsafe {
662            ffi::rocksdb_writebatch_singledelete_cf(
663                self.inner,
664                cf.inner(),
665                key.as_ptr() as *const c_char,
666                key.len() as size_t,
667            );
668        }
669    }
670
671    /// Removes the entry for a write-once key in a column family that uses
672    /// user-defined timestamps.
673    ///
674    /// See [`single_delete`](Self::single_delete) for when this is safe to use.
675    pub fn single_delete_cf_with_ts<K: AsRef<[u8]>, S: AsRef<[u8]>>(
676        &mut self,
677        cf: &impl AsColumnFamilyRef,
678        key: K,
679        ts: S,
680    ) {
681        let key = key.as_ref();
682        let ts = ts.as_ref();
683
684        unsafe {
685            ffi::rocksdb_writebatch_singledelete_cf_with_ts(
686                self.inner,
687                cf.inner(),
688                key.as_ptr() as *const c_char,
689                key.len() as size_t,
690                ts.as_ptr() as *const c_char,
691                ts.len() as size_t,
692            );
693        }
694    }
695
696    /// Removes the database entry in the specific column family with timestamp for key.
697    /// Does nothing if the key was not found.
698    pub fn delete_cf_with_ts<K: AsRef<[u8]>, S: AsRef<[u8]>>(
699        &mut self,
700        cf: &impl AsColumnFamilyRef,
701        key: K,
702        ts: S,
703    ) {
704        let key = key.as_ref();
705        let ts = ts.as_ref();
706        unsafe {
707            ffi::rocksdb_writebatch_delete_cf_with_ts(
708                self.inner,
709                cf.inner(),
710                key.as_ptr() as *const c_char,
711                key.len() as size_t,
712                ts.as_ptr() as *const c_char,
713                ts.len() as size_t,
714            );
715        }
716    }
717
718    // Append a blob of arbitrary size to the records in this batch. The blob will
719    // be stored in the transaction log but not in any other file. In particular,
720    // it will not be persisted to the SST files. When iterating over this
721    // WriteBatch, WriteBatch::Handler::LogData will be called with the contents
722    // of the blob as it is encountered. Blobs, puts, deletes, and merges will be
723    // encountered in the same order in which they were inserted. The blob will
724    // NOT consume sequence number(s) and will NOT increase the count of the batch
725    //
726    // Example application: add timestamps to the transaction log for use in
727    // replication.
728    pub fn put_log_data<V: AsRef<[u8]>>(&mut self, log_data: V) {
729        let log_data = log_data.as_ref();
730
731        unsafe {
732            ffi::rocksdb_writebatch_put_log_data(
733                self.inner,
734                log_data.as_ptr() as *const c_char,
735                log_data.len() as size_t,
736            );
737        }
738    }
739
740    /// Clear all updates buffered in this batch.
741    pub fn clear(&mut self) {
742        unsafe {
743            ffi::rocksdb_writebatch_clear(self.inner);
744        }
745    }
746
747    /// Record the current state of the batch so it can be undone later.
748    ///
749    /// Save points nest. Each [`set_save_point`](Self::set_save_point) pushes
750    /// onto a stack that [`rollback_to_save_point`](Self::rollback_to_save_point)
751    /// and [`pop_save_point`](Self::pop_save_point) pop from.
752    pub fn set_save_point(&mut self) {
753        unsafe {
754            ffi::rocksdb_writebatch_set_save_point(self.inner);
755        }
756    }
757
758    /// Undo every operation recorded since the most recent save point, and pop
759    /// that save point.
760    ///
761    /// Returns an error if there is no save point to roll back to.
762    pub fn rollback_to_save_point(&mut self) -> Result<(), crate::Error> {
763        unsafe {
764            ffi_try!(ffi::rocksdb_writebatch_rollback_to_save_point(self.inner));
765        }
766        Ok(())
767    }
768
769    /// Pop the most recent save point without undoing anything.
770    ///
771    /// Returns an error if there is no save point to pop.
772    pub fn pop_save_point(&mut self) -> Result<(), crate::Error> {
773        unsafe {
774            ffi_try!(ffi::rocksdb_writebatch_pop_save_point(self.inner));
775        }
776        Ok(())
777    }
778
779    /// Recompute the per-key protection info over the batch and check it
780    /// against what was stored when each entry was added.
781    ///
782    /// Only meaningful for a batch built with a non-zero
783    /// `protection_bytes_per_key`, which is the fourth argument to
784    /// `rocksdb_writebatch_create_with_params`. On a batch without protection
785    /// this succeeds without checking anything.
786    pub fn verify_checksum(&self) -> Result<(), crate::Error> {
787        unsafe {
788            ffi_try!(ffi::rocksdb_writebatch_verify_checksum(self.inner));
789        }
790        Ok(())
791    }
792
793    /// Overwrite the user-defined timestamp on every entry in the batch.
794    ///
795    /// `get_ts_size` is called with each column family id the batch touches and
796    /// must return the timestamp width configured for that column family, or 0
797    /// if it does not use timestamps. `ts` must be exactly as wide as every
798    /// non-zero size it returns.
799    ///
800    /// This is for reassigning a timestamp to an already-built batch, such as
801    /// when a commit timestamp is only known at write time.
802    ///
803    /// # Safety
804    ///
805    /// Every key already recorded for a column family whose `get_ts_size`
806    /// returns a non-zero width must be at least that many bytes long, which in
807    /// practice means it was written through one of the `_with_ts` methods and
808    /// already carries a timestamp suffix of exactly that width. RocksDB
809    /// overwrites the last `width` bytes of each key without checking that the
810    /// key is that long, so a shorter key makes it write in front of the key and
811    /// corrupt the heap. Mixing plain [`put`](Self::put) with a non-zero width
812    /// for the same column family is what usually triggers this.
813    ///
814    /// # Errors
815    ///
816    /// Returns an error if `ts` is empty, if its length differs from a non-zero
817    /// width returned by `get_ts_size`, or if `get_ts_size` reports that it
818    /// could not find the width for a column family.
819    pub unsafe fn update_timestamps<S, F>(
820        &mut self,
821        ts: S,
822        mut get_ts_size: F,
823    ) -> Result<(), crate::Error>
824    where
825        S: AsRef<[u8]>,
826        F: FnMut(u32) -> usize,
827    {
828        let ts = ts.as_ref();
829        let state = std::ptr::from_mut(&mut get_ts_size).cast::<c_void>();
830        unsafe {
831            ffi_try!(ffi::rocksdb_writebatch_update_timestamps(
832                self.inner,
833                ts.as_ptr() as *const c_char,
834                ts.len() as size_t,
835                state,
836                Some(get_ts_size_callback::<F>),
837            ));
838        }
839        Ok(())
840    }
841}
842
843impl WriteBatchWithTransaction<false> {
844    /// Remove database entries from start key to end key.
845    ///
846    /// Removes the database entries in the range ["begin_key", "end_key"), i.e.,
847    /// including "begin_key" and excluding "end_key". It is not an error if no
848    /// keys exist in the range ["begin_key", "end_key").
849    pub fn delete_range<K: AsRef<[u8]>>(&mut self, from: K, to: K) {
850        let (start_key, end_key) = (from.as_ref(), to.as_ref());
851
852        unsafe {
853            ffi::rocksdb_writebatch_delete_range(
854                self.inner,
855                start_key.as_ptr() as *const c_char,
856                start_key.len() as size_t,
857                end_key.as_ptr() as *const c_char,
858                end_key.len() as size_t,
859            );
860        }
861    }
862
863    /// Removes entries in a range whose bounds are assembled from byte slices.
864    ///
865    /// The range includes `from` and excludes `to`. Both bounds must be split
866    /// into the same number of parts: the System backend forwards them to
867    /// `rocksdb_writebatch_delete_rangev`, which takes one part count for the
868    /// pair. Split them differently and this returns an error.
869    ///
870    /// RocksDB copies both bounds into the write batch during this call, so the
871    /// slices do not need to outlive the method.
872    pub fn delete_range_vectored(
873        &mut self,
874        from: &[IoSlice<'_>],
875        to: &[IoSlice<'_>],
876    ) -> Result<(), crate::Error> {
877        if from.len() != to.len() {
878            return Err(crate::Error::new(format!(
879                "range start has {} parts but range end has {} parts; expected equal counts",
880                from.len(),
881                to.len()
882            )));
883        }
884        let from = WriteBatchParts::new(from, "range start")?;
885        let to = WriteBatchParts::new(to, "range end")?;
886        unsafe {
887            ffi_try!(ffi::rust_rocksdb_writebatch_delete_range_slices(
888                self.inner,
889                from.count,
890                from.as_ptr(),
891                to.count,
892                to.as_ptr(),
893            ));
894        }
895        Ok(())
896    }
897
898    /// Remove database entries in column family from start key to end key.
899    ///
900    /// Removes the database entries in the range ["begin_key", "end_key"), i.e.,
901    /// including "begin_key" and excluding "end_key". It is not an error if no
902    /// keys exist in the range ["begin_key", "end_key").
903    pub fn delete_range_cf<K: AsRef<[u8]>>(&mut self, cf: &impl AsColumnFamilyRef, from: K, to: K) {
904        let (start_key, end_key) = (from.as_ref(), to.as_ref());
905
906        unsafe {
907            ffi::rocksdb_writebatch_delete_range_cf(
908                self.inner,
909                cf.inner(),
910                start_key.as_ptr() as *const c_char,
911                start_key.len() as size_t,
912                end_key.as_ptr() as *const c_char,
913                end_key.len() as size_t,
914            );
915        }
916    }
917
918    /// Removes entries in a column family range whose bounds are assembled from byte slices.
919    ///
920    /// The range includes `from` and excludes `to`, and both bounds must be
921    /// split into the same number of parts. See
922    /// [`delete_range_vectored`](Self::delete_range_vectored).
923    ///
924    /// RocksDB copies both bounds into the write batch during this call, so the
925    /// slices do not need to outlive the method.
926    pub fn delete_range_cf_vectored(
927        &mut self,
928        cf: &impl AsColumnFamilyRef,
929        from: &[IoSlice<'_>],
930        to: &[IoSlice<'_>],
931    ) -> Result<(), crate::Error> {
932        if from.len() != to.len() {
933            return Err(crate::Error::new(format!(
934                "range start has {} parts but range end has {} parts; expected equal counts",
935                from.len(),
936                to.len()
937            )));
938        }
939        let from = WriteBatchParts::new(from, "range start")?;
940        let to = WriteBatchParts::new(to, "range end")?;
941        unsafe {
942            ffi_try!(ffi::rust_rocksdb_writebatch_delete_range_slices_cf(
943                self.inner,
944                cf.inner(),
945                from.count,
946                from.as_ptr(),
947                to.count,
948                to.as_ptr(),
949            ));
950        }
951        Ok(())
952    }
953}
954
955impl<const TRANSACTION: bool> Default for WriteBatchWithTransaction<TRANSACTION> {
956    fn default() -> Self {
957        Self::new()
958    }
959}
960
961impl<const TRANSACTION: bool> Drop for WriteBatchWithTransaction<TRANSACTION> {
962    fn drop(&mut self) {
963        unsafe {
964            ffi::rocksdb_writebatch_destroy(self.inner);
965        }
966    }
967}
968
969unsafe impl<const TRANSACTION: bool> Send for WriteBatchWithTransaction<TRANSACTION> {}