1use bytes::{Buf, BufMut, Bytes, BytesMut};
2use cobble::{Error, MergeOperator, Result, TimeProvider, ValueType};
3use serde::{Deserialize, Serialize};
4use serde_json::Value as JsonValue;
5use std::collections::VecDeque;
6use std::mem::size_of;
7use std::sync::Arc;
8
9pub(crate) const LIST_OPERATOR_ID: &str = "cobble.list.v1";
10
11#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
12pub struct ListConfig {
13 pub max_elements: Option<usize>,
14 pub retain_mode: ListRetainMode,
15 pub preserve_element_ttl: bool,
16}
17
18impl Default for ListConfig {
19 fn default() -> Self {
20 Self {
21 max_elements: None,
22 retain_mode: ListRetainMode::Last,
23 preserve_element_ttl: false,
24 }
25 }
26}
27
28#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
29#[serde(rename_all = "snake_case")]
30pub enum ListRetainMode {
31 First,
32 #[default]
33 Last,
34}
35
36#[derive(Clone)]
37struct ListMergeOperator {
38 config: ListConfig,
39}
40
41impl ListMergeOperator {
42 fn new(config: ListConfig) -> Self {
43 Self { config }
44 }
45}
46
47impl MergeOperator for ListMergeOperator {
48 fn id(&self) -> String {
49 LIST_OPERATOR_ID.to_string()
50 }
51
52 fn metadata(&self) -> Option<JsonValue> {
53 serde_json::to_value(&self.config).ok()
54 }
55
56 fn merge(
57 &self,
58 existing_value: Bytes,
59 value: Bytes,
60 time_provider: Option<&dyn TimeProvider>,
61 ) -> Result<(Bytes, Option<ValueType>)> {
62 self.merge_batch(existing_value, vec![value], time_provider)
63 }
64
65 fn merge_batch(
66 &self,
67 existing_value: Bytes,
68 operands: Vec<Bytes>,
69 time_provider: Option<&dyn TimeProvider>,
70 ) -> Result<(Bytes, Option<ValueType>)> {
71 if operands.is_empty() {
72 return Ok((existing_value, None));
73 }
74
75 if !self.config.preserve_element_ttl
76 && self.config.max_elements.is_none()
77 && existing_value.is_empty()
78 && operands.len() == 1
79 {
80 let operand = operands.into_iter().next().expect("len checked to be one");
81 let _ = parse_payload_body(&operand)?;
82 return Ok((operand, None));
83 }
84
85 if let Some(merged) = try_fast_append_batch(&existing_value, &operands, &self.config)? {
87 return Ok((merged, None));
88 }
89
90 let now_seconds = time_provider
91 .map(|provider| provider.now_seconds())
92 .unwrap_or(0);
93 if let (ListRetainMode::Last, Some(max_elements)) =
94 (self.config.retain_mode, self.config.max_elements)
95 {
96 let (elements, reached_last_cap) = collect_last_from_newest(
97 &existing_value,
98 &operands,
99 &self.config,
100 now_seconds,
101 max_elements,
102 )?;
103 let output = encode_list_payload(&elements, &self.config)?;
104 let value_type = if reached_last_cap && !self.config.preserve_element_ttl {
105 Some(ValueType::Put)
106 } else {
107 None
108 };
109 return Ok((output, value_type));
110 }
111 let mut accumulator = ListAccumulator::new(&self.config);
112 accumulator.ingest_payload(&existing_value, now_seconds)?;
113 if !accumulator.should_stop() {
114 for operand in &operands {
115 accumulator.ingest_payload(operand, now_seconds)?;
116 if accumulator.should_stop() {
117 break;
118 }
119 }
120 }
121 let (elements, reached_last_cap) = accumulator.into_parts();
122 let output = encode_list_payload(&elements, &self.config)?;
123 let value_type = if reached_last_cap
124 && self.config.retain_mode == ListRetainMode::Last
125 && !self.config.preserve_element_ttl
126 {
127 Some(ValueType::Put)
128 } else {
129 None
130 };
131 Ok((output, value_type))
132 }
133}
134
135pub(crate) fn list_operator(config: ListConfig) -> Arc<dyn MergeOperator> {
136 Arc::new(ListMergeOperator::new(config))
137}
138
139pub(crate) fn list_operator_from_metadata(
140 id: &str,
141 metadata: Option<&JsonValue>,
142) -> Option<Arc<dyn MergeOperator>> {
143 if id != LIST_OPERATOR_ID {
144 return None;
145 }
146 let config = serde_json::from_value::<ListConfig>(metadata?.clone()).ok()?;
147 Some(list_operator(config))
148}
149
150pub(crate) fn encode_list_for_write(
151 elements: Vec<Bytes>,
152 config: &ListConfig,
153 ttl_seconds: Option<u32>,
154 now_seconds: u32,
155) -> Result<Bytes> {
156 let expires_at_secs = if config.preserve_element_ttl {
157 ttl_seconds.map(|ttl| now_seconds.saturating_add(ttl))
158 } else {
159 None
160 };
161 let decoded = elements
162 .into_iter()
163 .map(|value| DecodedListElement {
164 value,
165 expires_at_secs,
166 })
167 .collect::<Vec<_>>();
168 encode_list_payload(&decoded, config)
169}
170
171#[cfg(feature = "ffi")]
172pub(crate) fn encode_borrowed_list_for_write(
173 elements: &[&[u8]],
174 config: &ListConfig,
175 ttl_seconds: Option<u32>,
176 now_seconds: u32,
177) -> Result<Bytes> {
178 let expires_at_secs = if config.preserve_element_ttl {
179 ttl_seconds.map(|ttl| now_seconds.saturating_add(ttl))
180 } else {
181 None
182 };
183 encode_list_parts(
184 elements.iter().map(|value| (*value, expires_at_secs)),
185 config,
186 )
187}
188
189pub(crate) fn decode_list_for_read(
190 raw: &Bytes,
191 config: &ListConfig,
192 now_seconds: u32,
193) -> Result<Vec<Bytes>> {
194 let mut accumulator = ListAccumulator::new(config);
195 accumulator.ingest_payload(raw, now_seconds)?;
196 let (elements, _) = accumulator.into_parts();
197 Ok(elements.into_iter().map(|element| element.value).collect())
198}
199
200pub(crate) fn transform_list_elements<F>(
202 payload: Bytes,
203 preserve_element_ttl: bool,
204 transform: &F,
205) -> Result<Bytes>
206where
207 F: Fn(Bytes) -> Result<Bytes>,
208{
209 if payload.is_empty() {
210 return Ok(payload);
211 }
212 let mut cursor = ListPayloadCursor::new(&payload, preserve_element_ttl)?;
213 let mut output = BytesMut::with_capacity(payload.len());
214 output.put_u32_le(cursor.remaining_elements as u32);
215 while let Some(element) = cursor.next()? {
216 let value = transform(element.value)?;
217 let len = u32::try_from(value.len()).map_err(|_| {
218 Error::InputError(format!(
219 "list element is too large to encode: {} bytes",
220 value.len()
221 ))
222 })?;
223 if preserve_element_ttl {
224 output.put_u32_le(element.expires_at_secs.unwrap_or(0));
225 }
226 output.put_u32_le(len);
227 output.extend_from_slice(&value);
228 }
229 Ok(output.freeze())
230}
231
232#[derive(Clone)]
233struct DecodedListElement {
234 value: Bytes,
235 expires_at_secs: Option<u32>,
236}
237
238struct ListPayloadCursor {
242 remaining: Bytes,
243 preserve_element_ttl: bool,
244 remaining_elements: usize,
245}
246
247impl ListPayloadCursor {
248 fn new(payload: &Bytes, preserve_element_ttl: bool) -> Result<Self> {
249 if payload.is_empty() {
250 return Ok(Self {
251 remaining: Bytes::new(),
252 preserve_element_ttl,
253 remaining_elements: 0,
254 });
255 }
256 let mut remaining = payload.clone();
257 if remaining.remaining() < size_of::<u32>() {
258 return Err(Error::FileFormatError(
259 "invalid list payload: missing element count".to_string(),
260 ));
261 }
262 let remaining_elements = remaining.get_u32_le() as usize;
263 Ok(Self {
264 remaining,
265 preserve_element_ttl,
266 remaining_elements,
267 })
268 }
269
270 fn next(&mut self) -> Result<Option<DecodedListElement>> {
271 if self.remaining_elements == 0 {
272 if self.remaining.has_remaining() {
273 return Err(Error::InvalidState(
274 "invalid list payload: trailing bytes found".to_string(),
275 ));
276 }
277 return Ok(None);
278 }
279 let expires_at_secs = if self.preserve_element_ttl {
280 if self.remaining.remaining() < size_of::<u32>() {
281 return Err(Error::InvalidState(
282 "invalid list payload: missing element ttl timestamp".to_string(),
283 ));
284 }
285 let expires_at = self.remaining.get_u32_le();
286 if expires_at == 0 {
287 None
288 } else {
289 Some(expires_at)
290 }
291 } else {
292 None
293 };
294 if self.remaining.remaining() < size_of::<u32>() {
295 return Err(Error::InvalidState(
296 "invalid list payload: missing element length".to_string(),
297 ));
298 }
299 let element_len = self.remaining.get_u32_le() as usize;
300 if self.remaining.remaining() < element_len {
301 return Err(Error::InvalidState(format!(
302 "invalid list payload: element length {} exceeds remaining {}",
303 element_len,
304 self.remaining.remaining()
305 )));
306 }
307 self.remaining_elements -= 1;
308 Ok(Some(DecodedListElement {
309 value: self.remaining.split_to(element_len),
310 expires_at_secs,
311 }))
312 }
313}
314
315struct ListAccumulator {
320 config: ListConfig,
321 mode: ListAccumulatorMode,
322 reached_last_cap: bool,
323}
324
325enum ListAccumulatorMode {
326 All(Vec<DecodedListElement>),
327 First {
328 max: usize,
329 kept: Vec<DecodedListElement>,
330 },
331 Last {
332 max: usize,
333 kept: VecDeque<DecodedListElement>,
334 },
335}
336
337impl ListAccumulator {
338 fn new(config: &ListConfig) -> Self {
339 let mode = match (config.max_elements, config.retain_mode) {
340 (Some(max), ListRetainMode::First) => ListAccumulatorMode::First {
341 max,
342 kept: Vec::with_capacity(max),
343 },
344 (Some(max), ListRetainMode::Last) => ListAccumulatorMode::Last {
345 max,
346 kept: VecDeque::with_capacity(max),
347 },
348 (None, _) => ListAccumulatorMode::All(Vec::new()),
349 };
350 Self {
351 config: config.clone(),
352 mode,
353 reached_last_cap: false,
354 }
355 }
356
357 fn ingest_payload(&mut self, payload: &Bytes, now_seconds: u32) -> Result<()> {
358 let mut cursor = ListPayloadCursor::new(payload, self.config.preserve_element_ttl)?;
359 while let Some(element) = cursor.next()? {
360 if self.config.preserve_element_ttl
361 && element
362 .expires_at_secs
363 .is_some_and(|expires_at| expires_at <= now_seconds)
364 {
365 continue;
366 }
367 match &mut self.mode {
368 ListAccumulatorMode::All(values) => {
369 values.push(element);
370 }
371 ListAccumulatorMode::First { max, kept } => {
372 if kept.len() < *max {
373 kept.push(element);
374 }
375 }
376 ListAccumulatorMode::Last { max, kept } => {
377 kept.push_back(element);
378 if kept.len() > *max {
379 let _ = kept.pop_front();
380 }
381 if kept.len() == *max {
382 self.reached_last_cap = true;
383 }
384 }
385 }
386 if self.should_stop() {
387 break;
388 }
389 }
390 Ok(())
391 }
392
393 fn should_stop(&self) -> bool {
394 match &self.mode {
395 ListAccumulatorMode::First { max, kept } => kept.len() >= *max,
396 ListAccumulatorMode::All(_) | ListAccumulatorMode::Last { .. } => false,
397 }
398 }
399
400 fn into_parts(self) -> (Vec<DecodedListElement>, bool) {
401 let elements = match self.mode {
402 ListAccumulatorMode::All(values) => values,
403 ListAccumulatorMode::First { kept, .. } => kept,
404 ListAccumulatorMode::Last { kept, .. } => kept.into_iter().collect(),
405 };
406 (elements, self.reached_last_cap)
407 }
408}
409
410fn encode_list_payload(elements: &[DecodedListElement], config: &ListConfig) -> Result<Bytes> {
411 encode_list_parts(
412 elements
413 .iter()
414 .map(|element| (element.value.as_ref(), element.expires_at_secs)),
415 config,
416 )
417}
418
419fn encode_list_parts<'a, I>(elements: I, config: &ListConfig) -> Result<Bytes>
420where
421 I: Clone + ExactSizeIterator<Item = (&'a [u8], Option<u32>)>,
422{
423 if elements.len() > u32::MAX as usize {
424 return Err(Error::InputError(format!(
425 "too many list elements to encode: {}",
426 elements.len()
427 )));
428 }
429 let ttl_bytes = if config.preserve_element_ttl {
430 size_of::<u32>()
431 } else {
432 0
433 };
434 let body_size = elements.clone().try_fold(0usize, |total, (value, _)| {
435 if value.len() > u32::MAX as usize {
436 return Err(Error::InputError(format!(
437 "list element is too large to encode: {} bytes",
438 value.len()
439 )));
440 }
441 total
442 .checked_add(ttl_bytes)
443 .and_then(|size| size.checked_add(size_of::<u32>()))
444 .and_then(|size| size.checked_add(value.len()))
445 .ok_or_else(|| Error::InputError("encoded list size overflows usize".to_string()))
446 })?;
447 let total_size = size_of::<u32>()
448 .checked_add(body_size)
449 .ok_or_else(|| Error::InputError("encoded list size overflows usize".to_string()))?;
450 let mut out = BytesMut::with_capacity(total_size);
451 out.put_u32_le(elements.len() as u32);
452 for (value, expires_at_secs) in elements {
453 if config.preserve_element_ttl {
454 out.put_u32_le(expires_at_secs.unwrap_or(0));
455 }
456 out.put_u32_le(value.len() as u32);
457 out.extend_from_slice(value);
458 }
459 Ok(out.freeze())
460}
461
462fn try_fast_append_batch(
463 existing: &Bytes,
464 operands: &[Bytes],
465 config: &ListConfig,
466) -> Result<Option<Bytes>> {
467 if config.preserve_element_ttl {
468 return Ok(None);
469 }
470 let (mut total_count, existing_body) = parse_payload_body(existing)?;
471 let mut operand_bodies = Vec::with_capacity(operands.len());
472 for operand in operands {
473 let (count, body) = parse_payload_body(operand)?;
474 total_count = total_count.checked_add(count).ok_or_else(|| {
475 Error::InputError(format!(
476 "list element count overflow during merge: {} + {}",
477 total_count, count
478 ))
479 })?;
480 operand_bodies.push(body);
481 }
482 if let Some(max_elements) = config.max_elements
483 && total_count > max_elements
484 {
485 return Ok(None);
486 }
487 if total_count > u32::MAX as usize {
488 return Err(Error::InputError(format!(
489 "too many list elements to encode: {}",
490 total_count
491 )));
492 }
493 let total_body_size =
494 existing_body.len() + operand_bodies.iter().map(Bytes::len).sum::<usize>();
495 let mut out = BytesMut::with_capacity(size_of::<u32>() + total_body_size);
496 out.put_u32_le(total_count as u32);
497 out.extend_from_slice(existing_body.as_ref());
498 for body in operand_bodies {
499 out.extend_from_slice(body.as_ref());
500 }
501 Ok(Some(out.freeze()))
502}
503
504fn parse_payload_body(payload: &Bytes) -> Result<(usize, Bytes)> {
507 if payload.is_empty() {
508 return Ok((0, Bytes::new()));
509 }
510 if payload.len() < size_of::<u32>() {
511 return Err(Error::FileFormatError(
512 "invalid list payload: missing element count".to_string(),
513 ));
514 }
515 let mut header = payload.slice(..size_of::<u32>());
516 let element_count = header.get_u32_le() as usize;
517 Ok((element_count, payload.slice(size_of::<u32>()..)))
518}
519
520fn collect_last_from_newest(
524 existing_value: &Bytes,
525 operands: &[Bytes],
526 config: &ListConfig,
527 now_seconds: u32,
528 max_elements: usize,
529) -> Result<(Vec<DecodedListElement>, bool)> {
530 if max_elements == 0 {
531 return Ok((Vec::new(), true));
532 }
533 let mut newest_to_oldest = Vec::with_capacity(max_elements);
534 for payload in operands.iter().rev().chain(std::iter::once(existing_value)) {
535 if newest_to_oldest.len() >= max_elements {
536 break;
537 }
538 let needed = max_elements - newest_to_oldest.len();
539 collect_last_from_single_payload(
540 payload,
541 config,
542 now_seconds,
543 needed,
544 &mut newest_to_oldest,
545 )?;
546 }
547 let reached_last_cap = newest_to_oldest.len() >= max_elements;
548 newest_to_oldest.reverse();
549 Ok((newest_to_oldest, reached_last_cap))
550}
551
552fn collect_last_from_single_payload(
553 payload: &Bytes,
554 config: &ListConfig,
555 now_seconds: u32,
556 needed: usize,
557 out_newest_to_oldest: &mut Vec<DecodedListElement>,
558) -> Result<()> {
559 if needed == 0 {
560 return Ok(());
561 }
562 let mut cursor = ListPayloadCursor::new(payload, config.preserve_element_ttl)?;
563 let mut tail = VecDeque::with_capacity(needed);
564 while let Some(element) = cursor.next()? {
565 if config.preserve_element_ttl
566 && element
567 .expires_at_secs
568 .is_some_and(|expires_at| expires_at <= now_seconds)
569 {
570 continue;
571 }
572 tail.push_back(element);
573 if tail.len() > needed {
574 let _ = tail.pop_front();
575 }
576 }
577 while let Some(element) = tail.pop_back() {
578 out_newest_to_oldest.push(element);
579 }
580 Ok(())
581}
582
583#[cfg(test)]
584#[path = "../tests/unit/list.rs"]
585mod tests;