use std::sync::Arc;
use bytes::Bytes;
use crate::convert;
pub(crate) type WireHeader = (String, Option<Bytes>);
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct ProducerId {
pub(crate) transactional_id: String,
pub(crate) epoch: u64,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum Visibility {
Committed,
Pending(ProducerId),
Aborted,
}
#[derive(Debug, Clone)]
pub(crate) enum Entry {
Data(Arc<Stored>, Visibility),
Control,
}
#[derive(Debug)]
pub(crate) struct Stored {
pub(crate) sequence: u64,
pub(crate) timestamp: i64,
pub(crate) payload: Bytes,
pub(crate) key: Option<Bytes>,
pub(crate) headers: Vec<WireHeader>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Isolation {
ReadCommitted,
ReadUncommitted,
}
impl Isolation {
pub(crate) fn next(self, entries: &[Entry], from: i64) -> Option<(i64, &Arc<Stored>)> {
let start = usize::try_from(from).unwrap_or(0);
for (index, entry) in entries.iter().enumerate().skip(start) {
match entry {
Entry::Data(stored, Visibility::Committed) => {
return Some((offset_of(index), stored));
}
Entry::Data(stored, Visibility::Pending(_) | Visibility::Aborted)
if self == Self::ReadUncommitted =>
{
return Some((offset_of(index), stored));
}
Entry::Data(_, Visibility::Pending(_)) => return None,
Entry::Control | Entry::Data(_, Visibility::Aborted) => {}
}
}
None
}
pub(crate) fn backlog(self, entries: &[Entry], from: i64) -> usize {
let mut count = 0;
let mut at = from;
while let Some((offset, _)) = self.next(entries, at) {
count += 1;
at = offset + 1;
}
count
}
}
pub(crate) fn offset_of(index: usize) -> i64 {
i64::try_from(index).unwrap_or(i64::MAX)
}
#[derive(Debug, Default)]
pub(crate) struct Partition {
pub(crate) entries: Vec<Entry>,
}
impl Partition {
pub(crate) fn end(&self) -> i64 {
offset_of(self.entries.len())
}
pub(crate) fn offset_for_time(&self, when: i64) -> i64 {
self.entries
.iter()
.position(|entry| matches!(entry, Entry::Data(stored, _) if stored.timestamp >= when))
.map_or_else(|| self.end(), offset_of)
}
}
#[derive(Debug)]
pub(crate) struct Topic {
pub(crate) partitions: Vec<Partition>,
keyless: usize,
}
pub(crate) const DEFAULT_PARTITIONS: usize = 1;
impl Topic {
pub(crate) fn new(partitions: usize) -> Self {
Self {
partitions: (0..partitions.max(1))
.map(|_| Partition::default())
.collect(),
keyless: 0,
}
}
pub(crate) fn place(&mut self, named: Option<i32>, key: Option<&[u8]>) -> Option<usize> {
let count = self.partitions.len();
match (named, key) {
(Some(partition), _) => usize::try_from(partition)
.ok()
.filter(|index| *index < count),
(None, Some(key)) if !key.is_empty() => {
let hash = usize::try_from(crc32(key)).unwrap_or(0);
Some(hash % count)
}
(None, _) => {
let index = self.keyless % count;
self.keyless = self.keyless.wrapping_add(1);
Some(index)
}
}
}
}
const RECORD_OVERHEAD: usize = 5 + 1 + 10 + 5 + 5 + 5 + 5;
pub(crate) fn record_size(payload: &[u8], key: Option<&[u8]>, headers: &[WireHeader]) -> usize {
let headers: usize = headers
.iter()
.map(|(name, value)| {
let value = value.as_ref().map_or(0, Bytes::len);
name.len() + value + length_varint(name.len()) + length_varint(value)
})
.sum();
RECORD_OVERHEAD + key.map_or(0, <[u8]>::len) + payload.len() + headers
}
fn length_varint(len: usize) -> usize {
let bits = match len {
0 => 0,
_ => (usize::BITS - len.leading_zeros() + 1) as usize,
};
bits.div_ceil(7).max(1)
}
pub(crate) fn wire_headers(headers: &ruststream::HeaderMap) -> Vec<WireHeader> {
headers
.iter()
.filter(|(name, _)| convert::rides_the_wire(name))
.map(|(name, value)| (name.to_owned(), Some(Bytes::copy_from_slice(value))))
.collect()
}
fn crc32(bytes: &[u8]) -> u32 {
let mut crc = !0u32;
for byte in bytes {
crc ^= u32::from(*byte);
for _ in 0..8 {
let mask = (crc & 1).wrapping_neg();
crc = (crc >> 1) ^ (0xEDB8_8320 & mask);
}
}
!crc
}
#[cfg(test)]
mod tests {
use super::*;
fn data(visibility: Visibility) -> Entry {
Entry::Data(
Arc::new(Stored {
sequence: 0,
timestamp: 0,
payload: Bytes::new(),
key: None,
headers: Vec::new(),
}),
visibility,
)
}
fn pending() -> Visibility {
Visibility::Pending(ProducerId {
transactional_id: "tx".to_owned(),
epoch: 1,
})
}
#[test]
fn the_crc_is_the_ieee_one() {
assert_eq!(crc32(b"123456789"), 0xCBF4_3926);
}
#[test]
fn a_committed_reader_stops_at_an_open_transaction_and_skips_an_aborted_one() {
let entries = vec![
data(Visibility::Committed),
data(Visibility::Aborted),
Entry::Control,
data(Visibility::Committed),
data(pending()),
data(Visibility::Committed),
];
let committed = Isolation::ReadCommitted;
assert_eq!(committed.next(&entries, 0).map(|(at, _)| at), Some(0));
assert_eq!(committed.next(&entries, 1).map(|(at, _)| at), Some(3));
assert_eq!(committed.next(&entries, 4).map(|(at, _)| at), None);
assert_eq!(committed.backlog(&entries, 0), 2);
let uncommitted = Isolation::ReadUncommitted;
assert_eq!(uncommitted.next(&entries, 1).map(|(at, _)| at), Some(1));
assert_eq!(uncommitted.backlog(&entries, 0), 5);
}
#[test]
fn a_record_counts_as_librdkafka_counts_it() {
assert_eq!(record_size(b"", None, &[]), 36);
assert_eq!(record_size(&vec![0; 999_964], None, &[]), 1_000_000);
let header =
|name: usize, value: usize| ("n".repeat(name), Some(Bytes::from(vec![0; value])));
assert_eq!(
record_size(b"", Some(b"key"), &[header(1, 63)]),
36 + 3 + 1 + 63 + 1 + 1
);
assert_eq!(
record_size(b"", None, &[header(1, 64)]),
36 + 1 + 64 + 1 + 2
);
}
#[test]
fn a_key_always_lands_on_one_partition_and_a_named_partition_must_exist() {
let mut topic = Topic::new(4);
let first = topic.place(None, Some(b"tenant-1"));
assert_eq!(first, topic.place(None, Some(b"tenant-1")));
assert_eq!(topic.place(Some(3), Some(b"tenant-1")), Some(3));
assert_eq!(topic.place(Some(4), None), None);
assert_eq!(topic.place(Some(-1), None), None);
let keyless: Vec<_> = (0..4).map(|_| topic.place(None, None)).collect();
assert_eq!(keyless, [Some(0), Some(1), Some(2), Some(3)]);
}
}