Skip to content

Commit 0dea1f8

Browse files
authored
feat: support RDB v14 (Array, stream XNACK) via librdb v2.3.0 (#1)
Follow librdb v2.3.0: surface the RDB v14 Array type and stream XNACK (NACK-zone) callbacks through the safe wrapper. Regenerate bindgen bindings and fix update-bindings.sh for bindgen 0.71.1. Correct librdb-sys SPDX to MIT AND BSD-3-Clause AND BSD-2-Clause AND Zlib. Versions: librdb-sys 0.2.0+2.3.0, librdb 0.2.0.
1 parent 53762a2 commit 0dea1f8

12 files changed

Lines changed: 279 additions & 25 deletions

File tree

Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
Rust wrapper for [librdb](https://github.com/redis/librdb), the Redis RDB file parser.
44

55
Parses RDB dump files and delivers Redis data types (strings, lists, hashes,
6-
sets, sorted sets, streams) through a callback trait, without loading the
7-
entire file into memory.
6+
sets, sorted sets, streams, arrays, and more) through a callback trait, without
7+
loading the entire file into memory.
88

99
| Crate | Description |
1010
|-------|-------------|
@@ -15,7 +15,7 @@ entire file into memory.
1515

1616
```toml
1717
[dependencies]
18-
librdb = "0.1"
18+
librdb = "0.2"
1919
```
2020

2121
By default, librdb is compiled from the vendored source (git submodule).

librdb-sys/Cargo.toml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
[package]
22
name = "librdb-sys"
3-
version = "0.1.0+2.2.0"
3+
version = "0.2.0+2.3.0"
44
links = "librdb"
55
repository = "https://github.com/funcpp/rust-librdb"
6-
license = "MIT"
6+
# Crate's own code is MIT; it vendors librdb (MIT) plus deps/redis sources under
7+
# BSD-3-Clause (Redis core), BSD-2-Clause (lzf), and Zlib (crcspeed). sha256 is
8+
# public-domain (no SPDX obligation). File headers are retained for attribution.
9+
license = "MIT AND BSD-3-Clause AND BSD-2-Clause AND Zlib"
710
description = "Native bindings to the librdb library"
811
keywords = ["redis"]
912
categories = ["external-ffi-bindings"]

librdb-sys/src/bindings.rs

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,8 @@ pub const RdbRes_RDB_ERR_MAX_RAW_LEN_EXCEEDED_FOR_KEY: RdbRes = 53;
128128
pub const RdbRes_RDB_ERR_EXCLUSIVE_RAW_HANDLERS: RdbRes = 54;
129129
#[doc = " error codes - reported by parser's blocks"]
130130
pub const RdbRes_RDB_ERR_MODULE_INVALID_WHEN_OPCODE: RdbRes = 55;
131+
#[doc = " error codes - reported by parser's blocks"]
132+
pub const RdbRes_RDB_ERR_ARRAY_INVALID_STATE: RdbRes = 56;
131133
#[doc = " api-ext error codes (see file: rp-ext-api.h)"]
132134
pub const RdbRes__RDB_ERR_EXTENSION_FIRST: RdbRes = 4096;
133135
#[doc = " user-defined error codes - reported by user-defined handlers or reader"]
@@ -835,7 +837,7 @@ pub struct RdbHandlersDataCallbacks {
835837
serializedSize: usize,
836838
) -> RdbRes,
837839
>,
838-
#[doc = " Stream cb - invoked in this order for each stream key. Indentation represent nesting.\n handleStreamMetadata(meta)\n handleStreamItem(id, field, value, itemsLeft) [repeated per entry/field]\n handleStreamNewCGroup(grpName, meta) [per consumer group]\n handleStreamCGroupPendingEntry(entry) [per group PEL entry]\n handleStreamNewConsumer(consName, meta) [per consumer]\n handleStreamConsumerPendingEntry(id) [per consumer PEL entry]\n handleStreamIdmpMeta(meta) [once, if IDMP enabled]\n handleStreamIdmpProducer(producer) [per producer]\n handleStreamIdmpEntry(entry) [per IID mapping]"]
840+
#[doc = " Stream cb - invoked in this order for each stream key. Indentation represent nesting.\n handleStreamMetadata(meta)\n handleStreamItem(id, field, value, itemsLeft) [repeated per entry/field]\n handleStreamNewCGroup(grpName, meta) [per consumer group]\n handleStreamCGroupPendingEntry(entry) [per group PEL entry]\n handleStreamNewConsumer(consName, meta) [per consumer]\n handleStreamConsumerPendingEntry(id) [per consumer PEL entry]\n handleStreamNackZoneEntry(id, itemsLeft) [per NACKed entry, v14+]\n handleStreamIdmpMeta(meta) [once, if IDMP enabled]\n handleStreamIdmpProducer(producer) [per producer]\n handleStreamIdmpEntry(entry) [per IID mapping]"]
839841
pub handleStreamMetadata: ::std::option::Option<
840842
unsafe extern "C" fn(
841843
p: *mut RdbParser,
@@ -883,6 +885,14 @@ pub struct RdbHandlersDataCallbacks {
883885
streamId: *mut RdbStreamID,
884886
) -> RdbRes,
885887
>,
888+
pub handleStreamNackZoneEntry: ::std::option::Option<
889+
unsafe extern "C" fn(
890+
p: *mut RdbParser,
891+
userData: *mut ::std::os::raw::c_void,
892+
id: *mut RdbStreamID,
893+
itemsLeft: i64,
894+
) -> RdbRes,
895+
>,
886896
pub handleStreamIdmpMeta: ::std::option::Option<
887897
unsafe extern "C" fn(
888898
p: *mut RdbParser,
@@ -904,11 +914,28 @@ pub struct RdbHandlersDataCallbacks {
904914
entry: *mut RdbStreamIdmpEntry,
905915
) -> RdbRes,
906916
>,
917+
#[doc = " Array cb (RDB_TYPE_ARRAY, v14+) - invoked in this order per array key:\n handleArrayMetadata(count, insertIdx) [once]\n handleArrayElement(idx, value) [repeated count times, ascending idx]\n\n insertIdx == RDB_ARRAY_INSERT_IDX_NONE means the array was saved with no\n insert cursor. Otherwise it carries the persisted insert_idx value."]
918+
pub handleArrayMetadata: ::std::option::Option<
919+
unsafe extern "C" fn(
920+
p: *mut RdbParser,
921+
userData: *mut ::std::os::raw::c_void,
922+
count: u64,
923+
insertIdx: u64,
924+
) -> RdbRes,
925+
>,
926+
pub handleArrayElement: ::std::option::Option<
927+
unsafe extern "C" fn(
928+
p: *mut RdbParser,
929+
userData: *mut ::std::os::raw::c_void,
930+
idx: u64,
931+
value: RdbBulk,
932+
) -> RdbRes,
933+
>,
907934
}
908935
#[allow(clippy::unnecessary_operation, clippy::identity_op)]
909936
const _: () = {
910937
["Size of RdbHandlersDataCallbacks"]
911-
[::std::mem::size_of::<RdbHandlersDataCallbacks>() - 192usize];
938+
[::std::mem::size_of::<RdbHandlersDataCallbacks>() - 216usize];
912939
["Alignment of RdbHandlersDataCallbacks"]
913940
[::std::mem::align_of::<RdbHandlersDataCallbacks>() - 8usize];
914941
["Offset of field: RdbHandlersDataCallbacks::handleStartRdb"]
@@ -957,12 +984,18 @@ const _: () = {
957984
RdbHandlersDataCallbacks,
958985
handleStreamConsumerPendingEntry
959986
) - 160usize];
987+
["Offset of field: RdbHandlersDataCallbacks::handleStreamNackZoneEntry"]
988+
[::std::mem::offset_of!(RdbHandlersDataCallbacks, handleStreamNackZoneEntry) - 168usize];
960989
["Offset of field: RdbHandlersDataCallbacks::handleStreamIdmpMeta"]
961-
[::std::mem::offset_of!(RdbHandlersDataCallbacks, handleStreamIdmpMeta) - 168usize];
990+
[::std::mem::offset_of!(RdbHandlersDataCallbacks, handleStreamIdmpMeta) - 176usize];
962991
["Offset of field: RdbHandlersDataCallbacks::handleStreamIdmpProducer"]
963-
[::std::mem::offset_of!(RdbHandlersDataCallbacks, handleStreamIdmpProducer) - 176usize];
992+
[::std::mem::offset_of!(RdbHandlersDataCallbacks, handleStreamIdmpProducer) - 184usize];
964993
["Offset of field: RdbHandlersDataCallbacks::handleStreamIdmpEntry"]
965-
[::std::mem::offset_of!(RdbHandlersDataCallbacks, handleStreamIdmpEntry) - 184usize];
994+
[::std::mem::offset_of!(RdbHandlersDataCallbacks, handleStreamIdmpEntry) - 192usize];
995+
["Offset of field: RdbHandlersDataCallbacks::handleArrayMetadata"]
996+
[::std::mem::offset_of!(RdbHandlersDataCallbacks, handleArrayMetadata) - 200usize];
997+
["Offset of field: RdbHandlersDataCallbacks::handleArrayElement"]
998+
[::std::mem::offset_of!(RdbHandlersDataCallbacks, handleArrayElement) - 208usize];
966999
};
9671000
unsafe extern "C" {
9681001
#[doc = " Parser creation and deletion"]
@@ -1174,7 +1207,8 @@ pub const RdbDataType_RDB_DATA_TYPE_HASH: RdbDataType = 4;
11741207
pub const RdbDataType_RDB_DATA_TYPE_MODULE: RdbDataType = 5;
11751208
pub const RdbDataType_RDB_DATA_TYPE_STREAM: RdbDataType = 6;
11761209
pub const RdbDataType_RDB_DATA_TYPE_FUNCTION: RdbDataType = 7;
1177-
pub const RdbDataType_RDB_DATA_TYPE_MAX: RdbDataType = 8;
1210+
pub const RdbDataType_RDB_DATA_TYPE_ARRAY: RdbDataType = 8;
1211+
pub const RdbDataType_RDB_DATA_TYPE_MAX: RdbDataType = 9;
11781212
#[doc = " Multiple levels registration\n Some of the more advanced configuration might require parsing different data\n types at different levels of the parser.\n\n The callbacks that are common to all levels (lookup HANDLERS_COMMON_CALLBACKS),\n if registered at different levels then all of them will be called, one by one,\n starting from level 0.\n\n As for the callbacks of RDB object types, each level has its own way to\n handle the data with distinct set of callbacks interfaces. In case of multiple\n levels registration, the application should configure for each RDB data type\n at what level it is needed to get parsed by calling `RDB_handleByLevel()`.\n Otherwise, the parser will resolve it by parsing and calling handlers that are\n registered at lowest level."]
11791213
pub type RdbDataType = ::std::os::raw::c_uint;
11801214
unsafe extern "C" {

librdb-sys/src/lib.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,11 @@
66
#![allow(clippy::pedantic)]
77

88
include!("bindings.rs");
9+
10+
/// `RDB_ARRAY_INSERT_IDX_NONE` — the "no insert cursor" sentinel for `RDB_TYPE_ARRAY`.
11+
///
12+
/// Declared by hand instead of via bindgen: the C macro is
13+
/// `#define RDB_ARRAY_INSERT_IDX_NONE UINT64_MAX`, but bindgen mis-evaluates the
14+
/// all-bits-set value as `i32 = -1`. The upstream type is `uint64_t`, so the var
15+
/// is blocklisted in `update-bindings.sh` and re-declared here with correct width.
16+
pub const RDB_ARRAY_INSERT_IDX_NONE: u64 = u64::MAX;

librdb-sys/update-bindings.sh

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,22 @@ if ! command -v bindgen &>/dev/null; then
1818
exit 1
1919
fi
2020

21+
# Clang args (the -I include paths) must follow `--`; bindgen derives Debug by
22+
# default, so only Default needs an explicit flag.
2123
bindgen "$WRAPPER_H" \
22-
-I "$LIBRDB_ROOT/api" \
23-
-I "$LIBRDB_ROOT/src" \
24-
-I "$LIBRDB_ROOT/deps/redis" \
2524
--allowlist-function 'RDB_.*' \
2625
--allowlist-function 'RDBX_.*' \
2726
--allowlist-type 'Rdb.*' \
2827
--allowlist-type 'Rdbx.*' \
2928
--allowlist-var 'RDB_.*' \
3029
--allowlist-var 'RDBX_.*' \
31-
--with-derive-debug \
30+
--blocklist-var 'RDB_ARRAY_INSERT_IDX_NONE' \
3231
--with-derive-default \
33-
-o "$OUTPUT"
32+
-o "$OUTPUT" \
33+
-- \
34+
-I "$LIBRDB_ROOT/api" \
35+
-I "$LIBRDB_ROOT/src" \
36+
-I "$LIBRDB_ROOT/deps/redis"
3437

3538
rustfmt "$OUTPUT"
3639

librdb/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "librdb"
3-
version = "0.1.1"
3+
version = "0.2.0"
44
repository = "https://github.com/funcpp/rust-librdb"
55
readme = "../README.md"
66
license = "MIT"
@@ -11,7 +11,7 @@ edition = "2024"
1111
rust-version = "1.94"
1212

1313
[dependencies]
14-
librdb-sys = { version = "0.1.0", path = "../librdb-sys" }
14+
librdb-sys = { version = "0.2.0", path = "../librdb-sys" }
1515
thiserror = "2"
1616

1717
[lints]

librdb/src/handlers.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,12 @@ pub trait RdbHandlers {
115115
Ok(())
116116
}
117117

118+
/// A NACK-zone (not-yet-acknowledged) entry within a consumer group
119+
/// (`RDB_TYPE_STREAM_LISTPACKS_5`, RDB v14+).
120+
fn handle_stream_nack_zone_entry(&mut self, _id: &StreamId, _items_left: i64) -> Result<()> {
121+
Ok(())
122+
}
123+
118124
fn handle_stream_idmp_meta(&mut self, _meta: &StreamIdmpMeta) -> Result<()> {
119125
Ok(())
120126
}
@@ -126,4 +132,18 @@ pub trait RdbHandlers {
126132
fn handle_stream_idmp_entry(&mut self, _iid: &[u8], _stream_id: &StreamId) -> Result<()> {
127133
Ok(())
128134
}
135+
136+
/// Metadata for a sparse array (`RDB_TYPE_ARRAY`, RDB v14+), called once
137+
/// before its elements.
138+
///
139+
/// `insert_idx` is the persisted insert cursor, or `None` if the array was
140+
/// saved without one (librdb's `RDB_ARRAY_INSERT_IDX_NONE` sentinel).
141+
fn handle_array_metadata(&mut self, _count: u64, _insert_idx: Option<u64>) -> Result<()> {
142+
Ok(())
143+
}
144+
145+
/// A single sparse-array element, called `count` times in ascending `idx` order.
146+
fn handle_array_element(&mut self, _idx: u64, _value: &[u8]) -> Result<()> {
147+
Ok(())
148+
}
129149
}

librdb/src/trampoline.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,19 @@ pub unsafe extern "C" fn trampoline_stream_consumer_pending_entry<H: RdbHandlers
321321
})
322322
}
323323

324+
pub unsafe extern "C" fn trampoline_stream_nack_zone_entry<H: RdbHandlers>(
325+
_p: *mut RdbParser,
326+
user_data: *mut c_void,
327+
id: *mut RdbStreamID,
328+
items_left: i64,
329+
) -> RdbRes {
330+
let state = unsafe { &mut *(user_data.cast::<HandlerState<H>>()) };
331+
guarded(state, |h| {
332+
let stream_id = StreamId::from_raw(unsafe { &*id });
333+
h.handle_stream_nack_zone_entry(&stream_id, items_left)
334+
})
335+
}
336+
324337
pub unsafe extern "C" fn trampoline_stream_idmp_meta<H: RdbHandlers>(
325338
_p: *mut RdbParser,
326339
user_data: *mut c_void,
@@ -360,6 +373,37 @@ pub unsafe extern "C" fn trampoline_stream_idmp_entry<H: RdbHandlers>(
360373
})
361374
}
362375

376+
pub unsafe extern "C" fn trampoline_array_metadata<H: RdbHandlers>(
377+
_p: *mut RdbParser,
378+
user_data: *mut c_void,
379+
count: u64,
380+
insert_idx: u64,
381+
) -> RdbRes {
382+
let state = unsafe { &mut *(user_data.cast::<HandlerState<H>>()) };
383+
guarded(state, |h| {
384+
// librdb encodes "no insert cursor" as the RDB_ARRAY_INSERT_IDX_NONE sentinel.
385+
let insert_idx = if insert_idx == librdb_sys::RDB_ARRAY_INSERT_IDX_NONE {
386+
None
387+
} else {
388+
Some(insert_idx)
389+
};
390+
h.handle_array_metadata(count, insert_idx)
391+
})
392+
}
393+
394+
pub unsafe extern "C" fn trampoline_array_element<H: RdbHandlers>(
395+
p: *mut RdbParser,
396+
user_data: *mut c_void,
397+
idx: u64,
398+
value: RdbBulk,
399+
) -> RdbRes {
400+
let state = unsafe { &mut *(user_data.cast::<HandlerState<H>>()) };
401+
guarded(state, |h| {
402+
let value_slice = unsafe { bulk_to_slice(p, value) };
403+
h.handle_array_element(idx, value_slice)
404+
})
405+
}
406+
363407
pub fn build_callbacks<H: RdbHandlers>() -> librdb_sys::RdbHandlersDataCallbacks {
364408
librdb_sys::RdbHandlersDataCallbacks {
365409
handleStartRdb: Some(trampoline_start_rdb::<H>),
@@ -383,8 +427,11 @@ pub fn build_callbacks<H: RdbHandlers>() -> librdb_sys::RdbHandlersDataCallbacks
383427
handleStreamCGroupPendingEntry: Some(trampoline_stream_cgroup_pending_entry::<H>),
384428
handleStreamNewConsumer: Some(trampoline_stream_new_consumer::<H>),
385429
handleStreamConsumerPendingEntry: Some(trampoline_stream_consumer_pending_entry::<H>),
430+
handleStreamNackZoneEntry: Some(trampoline_stream_nack_zone_entry::<H>),
386431
handleStreamIdmpMeta: Some(trampoline_stream_idmp_meta::<H>),
387432
handleStreamIdmpProducer: Some(trampoline_stream_idmp_producer::<H>),
388433
handleStreamIdmpEntry: Some(trampoline_stream_idmp_entry::<H>),
434+
handleArrayMetadata: Some(trampoline_array_metadata::<H>),
435+
handleArrayElement: Some(trampoline_array_element::<H>),
389436
}
390437
}

0 commit comments

Comments
 (0)