Skip to content

Commit c0ae076

Browse files
committed
Expose log_data blobs in WriteBatchIterator and WriteBatchIteratorCf
1 parent eaf990c commit c0ae076

6 files changed

Lines changed: 292 additions & 11 deletions

File tree

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
# Changelog
22

3+
## Unreleased
4+
5+
- feat: expose `log_data` blobs during write-batch iteration.
6+
`WriteBatchIterator` and `WriteBatchIteratorCf` both grow an optional
7+
`log_data(&mut self, blob: &[u8])` method (default: no-op) that is
8+
invoked for each blob written via `WriteBatchWithTransaction::put_log_data`
9+
when iterating with `iterate()` / `iterate_cf()`.
10+
Previously these blobs were silently dropped at the Rust layer even though
11+
the underlying C API forwarded them. (lucas.vuillier)
12+
313
## 0.50.0 (2026-05-23)
414

515
- breaking: bump MSRV to 1.91.0 per the rolling 6-month policy. 1.91.0

librocksdb-sys/c-api-extensions/c_api_extensions.cc

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "rocksdb/listener.h"
1818
#include "rocksdb/options.h"
1919
#include "rocksdb/table.h"
20+
#include "rocksdb/write_batch.h"
2021

2122
using ROCKSDB_NAMESPACE::BackgroundErrorRecoveryInfo;
2223
using ROCKSDB_NAMESPACE::BlockBasedTableOptions;
@@ -28,8 +29,10 @@ using ROCKSDB_NAMESPACE::ExternalFileIngestionInfo;
2829
using ROCKSDB_NAMESPACE::FlushJobInfo;
2930
using ROCKSDB_NAMESPACE::Options;
3031
using ROCKSDB_NAMESPACE::ReadOptions;
32+
using ROCKSDB_NAMESPACE::Slice;
3133
using ROCKSDB_NAMESPACE::Status;
3234
using ROCKSDB_NAMESPACE::SubcompactionJobInfo;
35+
using ROCKSDB_NAMESPACE::WriteBatch;
3336
using ROCKSDB_NAMESPACE::WriteStallInfo;
3437
using ROCKSDB_NAMESPACE::MemTableInfo;
3538

@@ -350,3 +353,109 @@ extern "C" double rocksdb_compactoptions_get_blob_garbage_collection_age_cutoff(
350353
rocksdb_compactoptions_t* opt) {
351354
return reinterpret_cast<CompactRangeOptions*>(opt)->blob_garbage_collection_age_cutoff;
352355
}
356+
357+
// -----------------------------------------------------------------------------
358+
// WriteBatch iteration with log_data support
359+
//
360+
// rocksdb_writebatch_t is defined in rocksdb/db/c.cc as:
361+
// struct rocksdb_writebatch_t { WriteBatch rep; };
362+
// The `rep` field is the first (and only) member, so a pointer to the opaque
363+
// C type is also a valid pointer to its embedded WriteBatch — same layout
364+
// trick used for ReadOptions, Options, and BlockBasedTableOptions above.
365+
// -----------------------------------------------------------------------------
366+
367+
namespace {
368+
369+
class RustWriteBatchHandler : public WriteBatch::Handler {
370+
public:
371+
void* state;
372+
void (*put_fn)(void*, const char*, size_t, const char*, size_t);
373+
void (*delete_fn)(void*, const char*, size_t);
374+
void (*log_data_fn)(void*, const char*, size_t);
375+
376+
Status PutCF(uint32_t cf, const Slice& key, const Slice& value) override {
377+
if (cf == 0 && put_fn != nullptr) {
378+
put_fn(state, key.data(), key.size(), value.data(), value.size());
379+
}
380+
return Status::OK();
381+
}
382+
383+
Status DeleteCF(uint32_t cf, const Slice& key) override {
384+
if (cf == 0 && delete_fn != nullptr) {
385+
delete_fn(state, key.data(), key.size());
386+
}
387+
return Status::OK();
388+
}
389+
390+
void LogData(const Slice& blob) override {
391+
if (log_data_fn != nullptr) {
392+
log_data_fn(state, blob.data(), blob.size());
393+
}
394+
}
395+
};
396+
397+
class RustWriteBatchCfHandler : public WriteBatch::Handler {
398+
public:
399+
void* state;
400+
void (*put_cf_fn)(void*, uint32_t, const char*, size_t, const char*, size_t);
401+
void (*delete_cf_fn)(void*, uint32_t, const char*, size_t);
402+
void (*merge_cf_fn)(void*, uint32_t, const char*, size_t, const char*, size_t);
403+
void (*log_data_fn)(void*, const char*, size_t);
404+
405+
Status PutCF(uint32_t cf, const Slice& key, const Slice& value) override {
406+
if (put_cf_fn != nullptr) {
407+
put_cf_fn(state, cf, key.data(), key.size(), value.data(), value.size());
408+
}
409+
return Status::OK();
410+
}
411+
412+
Status DeleteCF(uint32_t cf, const Slice& key) override {
413+
if (delete_cf_fn != nullptr) {
414+
delete_cf_fn(state, cf, key.data(), key.size());
415+
}
416+
return Status::OK();
417+
}
418+
419+
Status MergeCF(uint32_t cf, const Slice& key, const Slice& value) override {
420+
if (merge_cf_fn != nullptr) {
421+
merge_cf_fn(state, cf, key.data(), key.size(), value.data(), value.size());
422+
}
423+
return Status::OK();
424+
}
425+
426+
void LogData(const Slice& blob) override {
427+
if (log_data_fn != nullptr) {
428+
log_data_fn(state, blob.data(), blob.size());
429+
}
430+
}
431+
};
432+
433+
} // namespace
434+
435+
extern "C" void rust_rocksdb_writebatch_iterate(
436+
rocksdb_writebatch_t* b, void* state,
437+
void (*put)(void*, const char*, size_t, const char*, size_t),
438+
void (*deleted)(void*, const char*, size_t),
439+
void (*log_data)(void*, const char*, size_t)) {
440+
RustWriteBatchHandler handler;
441+
handler.state = state;
442+
handler.put_fn = put;
443+
handler.delete_fn = deleted;
444+
handler.log_data_fn = log_data;
445+
reinterpret_cast<WriteBatch*>(b)->Iterate(&handler);
446+
}
447+
448+
extern "C" void rust_rocksdb_writebatch_iterate_cf(
449+
rocksdb_writebatch_t* b, void* state,
450+
void (*put_cf)(void*, uint32_t, const char*, size_t, const char*, size_t),
451+
void (*deleted_cf)(void*, uint32_t, const char*, size_t),
452+
void (*merge_cf)(void*, uint32_t, const char*, size_t, const char*, size_t),
453+
void (*log_data)(void*, const char*, size_t)) {
454+
RustWriteBatchCfHandler handler;
455+
handler.state = state;
456+
handler.put_cf_fn = put_cf;
457+
handler.delete_cf_fn = deleted_cf;
458+
handler.merge_cf_fn = merge_cf;
459+
handler.log_data_fn = log_data;
460+
reinterpret_cast<WriteBatch*>(b)->Iterate(&handler);
461+
}

librocksdb-sys/c-api-extensions/c_api_extensions.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,29 @@ extern ROCKSDB_LIBRARY_API void rust_rocksdb_eventlistener_destroy(
164164
extern ROCKSDB_LIBRARY_API void rust_rocksdb_options_add_eventlistener(
165165
rocksdb_options_t*, rust_rocksdb_eventlistener_t*);
166166

167+
/* -------------------------------------------------------------------------
168+
* WriteBatch iteration with log_data support
169+
*
170+
* The upstream rocksdb_writebatch_iterate / rocksdb_writebatch_iterate_cf
171+
* do not expose the WriteBatch::Handler::LogData callback, so blobs written
172+
* via rocksdb_writebatch_put_log_data are silently dropped during iteration.
173+
* These wrappers add a log_data parameter so callers can observe those blobs.
174+
* ------------------------------------------------------------------------- */
175+
extern ROCKSDB_LIBRARY_API void rust_rocksdb_writebatch_iterate(
176+
rocksdb_writebatch_t*, void* state,
177+
void (*put)(void*, const char* k, size_t klen, const char* v, size_t vlen),
178+
void (*deleted)(void*, const char* k, size_t klen),
179+
void (*log_data)(void*, const char* blob, size_t blen));
180+
181+
extern ROCKSDB_LIBRARY_API void rust_rocksdb_writebatch_iterate_cf(
182+
rocksdb_writebatch_t*, void* state,
183+
void (*put_cf)(void*, uint32_t cfid, const char* k, size_t klen,
184+
const char* v, size_t vlen),
185+
void (*deleted_cf)(void*, uint32_t cfid, const char* k, size_t klen),
186+
void (*merge_cf)(void*, uint32_t cfid, const char* k, size_t klen,
187+
const char* v, size_t vlen),
188+
void (*log_data)(void*, const char* blob, size_t blen));
189+
167190
#ifdef __cplusplus
168191
}
169192
#endif

librocksdb-sys/tests/ffi.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,14 @@ unsafe extern "C" fn CheckDel(ptr: *mut c_void, k: *const c_char, klen: size_t)
192192
*state += 1;
193193
}
194194

195+
// Callback from rocksdb_writebatch_iterate()
196+
unsafe extern "C" fn CheckLogData(ptr: *mut c_void, blob: *const c_char, blen: size_t) {
197+
let mut state: *mut c_int = ptr as *mut c_int;
198+
CheckCondition!(*state == 3);
199+
CheckEqual(cstrp!("blob"), blob, blen);
200+
*state += 1;
201+
}
202+
195203
unsafe extern "C" fn CmpDestroy(arg: *mut c_void) {}
196204

197205
unsafe extern "C" fn CmpCompare(
@@ -566,6 +574,25 @@ fn ffi() {
566574
rocksdb_writebatch_destroy(wb);
567575
}
568576

577+
StartPhase("writebatch_log_data");
578+
{
579+
let wb = rocksdb_writebatch_create();
580+
rocksdb_writebatch_put(wb, cstrp!("bar"), 3, cstrp!("b"), 1);
581+
rocksdb_writebatch_put(wb, cstrp!("box"), 3, cstrp!("c"), 1);
582+
rocksdb_writebatch_put_log_data(wb, cstrp!("blob"), 4);
583+
rocksdb_writebatch_delete(wb, cstrp!("bar"), 3);
584+
let mut pos: c_int = 0;
585+
rust_rocksdb_writebatch_iterate(
586+
wb,
587+
(&mut pos as *mut c_int).cast::<c_void>(),
588+
Some(CheckPut),
589+
Some(CheckDel),
590+
Some(CheckLogData),
591+
);
592+
CheckCondition!(pos == 4);
593+
rocksdb_writebatch_destroy(wb);
594+
}
595+
569596
StartPhase("writebatch_vectors");
570597
{
571598
let wb = rocksdb_writebatch_create();

src/write_batch.rs

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ pub struct WriteBatchWithTransaction<const TRANSACTION: bool> {
5353
pub(crate) inner: *mut ffi::rocksdb_writebatch_t,
5454
}
5555

56-
/// Receives the puts and deletes of a write batch.
56+
/// Receives the puts, deletes, and log data of a write batch.
5757
///
5858
/// The application must provide an implementation of this trait when
5959
/// iterating the operations within a `WriteBatch`
@@ -62,6 +62,10 @@ pub trait WriteBatchIterator {
6262
fn put(&mut self, key: &[u8], value: &[u8]);
6363
/// Called with a key that was `delete`d from the batch.
6464
fn delete(&mut self, key: &[u8]);
65+
/// Called with a blob written via [`WriteBatchWithTransaction::put_log_data`].
66+
///
67+
/// The default implementation is a no-op; override to observe log data.
68+
fn log_data(&mut self, _blob: &[u8]) {}
6569
}
6670

6771
/// Receives the puts, deletes, and merges of a write batch with column family
@@ -84,6 +88,10 @@ pub trait WriteBatchIteratorCf {
8488
/// Merge operations combine the provided value with the existing value at
8589
/// the key using a database-defined merge operator.
8690
fn merge_cf(&mut self, cf_id: u32, key: &[u8], value: &[u8]);
91+
/// Called with a blob written via [`WriteBatchWithTransaction::put_log_data`].
92+
///
93+
/// The default implementation is a no-op; override to observe log data.
94+
fn log_data(&mut self, _blob: &[u8]) {}
8795
}
8896

8997
unsafe extern "C" fn writebatch_put_callback<T: WriteBatchIterator>(
@@ -158,6 +166,30 @@ unsafe extern "C" fn writebatch_merge_cf_callback<T: WriteBatchIteratorCf>(
158166
}
159167
}
160168

169+
unsafe extern "C" fn writebatch_log_data_callback<T: WriteBatchIterator>(
170+
state: *mut c_void,
171+
blob: *const c_char,
172+
blen: usize,
173+
) {
174+
unsafe {
175+
let callbacks = &mut *(state as *mut T);
176+
let blob = slice::from_raw_parts(blob as *const u8, blen);
177+
callbacks.log_data(blob);
178+
}
179+
}
180+
181+
unsafe extern "C" fn writebatch_log_data_cf_callback<T: WriteBatchIteratorCf>(
182+
state: *mut c_void,
183+
blob: *const c_char,
184+
blen: usize,
185+
) {
186+
unsafe {
187+
let callbacks = &mut *(state as *mut T);
188+
let blob = slice::from_raw_parts(blob as *const u8, blen);
189+
callbacks.log_data(blob);
190+
}
191+
}
192+
161193
impl<const TRANSACTION: bool> WriteBatchWithTransaction<TRANSACTION> {
162194
/// Create a new `WriteBatch` without allocating memory.
163195
pub fn new() -> Self {
@@ -215,39 +247,41 @@ impl<const TRANSACTION: bool> WriteBatchWithTransaction<TRANSACTION> {
215247
self.len() == 0
216248
}
217249

218-
/// Iterate the put and delete operations within this write batch. Note that
219-
/// this does _not_ return an `Iterator` but instead will invoke the `put()`
220-
/// and `delete()` member functions of the provided `WriteBatchIterator`
221-
/// trait implementation.
250+
/// Iterate the put, delete, and log-data operations within this write batch.
251+
/// Note that this does _not_ return an `Iterator` but instead will invoke
252+
/// the `put()`, `delete()`, and `log_data()` member functions of the
253+
/// provided `WriteBatchIterator` trait implementation.
222254
pub fn iterate<T: WriteBatchIterator>(&self, callbacks: &mut T) {
223255
let state = std::ptr::from_mut::<T>(callbacks) as *mut c_void;
224256
unsafe {
225-
ffi::rocksdb_writebatch_iterate(
257+
ffi::rust_rocksdb_writebatch_iterate(
226258
self.inner,
227259
state,
228260
Some(writebatch_put_callback::<T>),
229261
Some(writebatch_delete_callback::<T>),
262+
Some(writebatch_log_data_callback::<T>),
230263
);
231264
}
232265
}
233266

234-
/// Iterate the put, delete, and merge operations within this write batch with column family
235-
/// information. Note that this does _not_ return an `Iterator` but instead will invoke the
236-
/// `put_cf()`, `delete_cf()`, and `merge_cf()` member functions of the provided
237-
/// `WriteBatchIteratorCf` trait implementation.
267+
/// Iterate the put, delete, merge, and log-data operations within this write batch with
268+
/// column family information. Note that this does _not_ return an `Iterator` but instead
269+
/// will invoke the `put_cf()`, `delete_cf()`, `merge_cf()`, and `log_data()` member
270+
/// functions of the provided `WriteBatchIteratorCf` trait implementation.
238271
///
239272
/// # Notes
240273
/// - For operations on the default column family ("default"), the `cf_id` parameter passed to
241274
/// the callbacks will be 0
242275
pub fn iterate_cf<T: WriteBatchIteratorCf>(&self, callbacks: &mut T) {
243276
let state = std::ptr::from_mut::<T>(callbacks) as *mut c_void;
244277
unsafe {
245-
ffi::rocksdb_writebatch_iterate_cf(
278+
ffi::rust_rocksdb_writebatch_iterate_cf(
246279
self.inner,
247280
state,
248281
Some(writebatch_put_cf_callback::<T>),
249282
Some(writebatch_delete_cf_callback::<T>),
250283
Some(writebatch_merge_cf_callback::<T>),
284+
Some(writebatch_log_data_cf_callback::<T>),
251285
);
252286
}
253287
}

0 commit comments

Comments
 (0)