pub(super) fn wrap_single_map_as_array(payload: Vec<u8>) -> Vec<u8> {
use nodedb_query::msgpack_scan;
if payload.is_empty() {
return payload;
}
if msgpack_scan::array_header(&payload, 0).is_some() {
return payload;
}
let mut buf = Vec::with_capacity(1 + payload.len());
buf.push(0x91); buf.extend_from_slice(&payload);
buf
}
pub(super) fn merge_msgpack_arrays(a: &[u8], b: &[u8]) -> crate::Result<Vec<u8>> {
use nodedb_query::msgpack_scan;
if a.is_empty() {
return Ok(b.to_vec());
}
if b.is_empty() {
return Ok(a.to_vec());
}
let (count_a, body_a_start) = msgpack_scan::array_header(a, 0).ok_or_else(|| {
crate::Error::Storage {
engine: "clone_merge".into(),
detail: format!(
"merge_msgpack_arrays: left input is not a msgpack array (len={}, first_byte=0x{:02x})",
a.len(),
a.first().copied().unwrap_or(0)
),
}
})?;
let (count_b, body_b_start) = msgpack_scan::array_header(b, 0).ok_or_else(|| {
crate::Error::Storage {
engine: "clone_merge".into(),
detail: format!(
"merge_msgpack_arrays: right input is not a msgpack array (len={}, first_byte=0x{:02x})",
b.len(),
b.first().copied().unwrap_or(0)
),
}
})?;
let total = count_a + count_b;
let body_a = &a[body_a_start..];
let body_b = &b[body_b_start..];
let mut buf = Vec::with_capacity(5 + body_a.len() + body_b.len());
if total <= 15 {
buf.push(0x90 | (total as u8));
} else if total <= 0xFFFF {
buf.push(0xdc);
buf.push((total >> 8) as u8);
buf.push(total as u8);
} else {
buf.push(0xdd);
buf.push((total >> 24) as u8);
buf.push((total >> 16) as u8);
buf.push((total >> 8) as u8);
buf.push(total as u8);
}
buf.extend_from_slice(body_a);
buf.extend_from_slice(body_b);
Ok(buf)
}
pub(super) fn filter_kv_tombstoned_rows(
payload: &[u8],
tombstoned: &std::collections::HashSet<String>,
) -> Option<Vec<u8>> {
use nodedb_query::msgpack_scan;
if tombstoned.is_empty() || payload.is_empty() {
return Some(payload.to_vec());
}
let (count, body_start) = msgpack_scan::array_header(payload, 0)?;
let mut kept_ranges: Vec<(usize, usize)> = Vec::with_capacity(count);
let mut pos = body_start;
for _ in 0..count {
let row_start = pos;
pos = msgpack_scan::skip_value(payload, pos)?;
let row_bytes = &payload[row_start..pos];
let extracted_key = msgpack_scan::extract_field(row_bytes, 0, "key")
.and_then(|(start, _)| msgpack_scan::read_str(row_bytes, start));
let is_tombstoned = match extracted_key {
Some(k) => tombstoned.contains(k),
None => {
tracing::warn!(
row_len = row_bytes.len(),
"clone read: KV row in source response has no `key` field; including unfiltered (protocol contract violation upstream)"
);
false
}
};
if !is_tombstoned {
kept_ranges.push((row_start, pos));
}
}
if kept_ranges.len() == count {
return Some(payload.to_vec());
}
let kept = kept_ranges.len();
let mut buf = Vec::with_capacity(payload.len());
if kept <= 15 {
buf.push(0x90 | (kept as u8));
} else if kept <= 0xFFFF {
buf.push(0xdc);
buf.push((kept >> 8) as u8);
buf.push(kept as u8);
} else {
buf.push(0xdd);
buf.push((kept >> 24) as u8);
buf.push((kept >> 16) as u8);
buf.push((kept >> 8) as u8);
buf.push(kept as u8);
}
for (start, end) in kept_ranges {
buf.extend_from_slice(&payload[start..end]);
}
Some(buf)
}