Skip to main content

reifydb_core/key/
ringbuffer.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4use reifydb_codec::key::{deserializer::KeyDeserializer, encoded::EncodedKey};
5use reifydb_macro::KeyCodec;
6use reifydb_value::value::Value;
7
8use super::KeyTag;
9use crate::{
10	interface::catalog::{id::RingBufferId, object::ObjectId, storage::StorageId},
11	key::{
12		any::{Field, KeyFields, RawEncoding, Width, encode_values},
13		bound::{TaggedKeyBoundRange, object_fields},
14		catalog::{KeyDeserializerCatalogExt, KeySerializerCatalogExt},
15	},
16};
17
18#[derive(Debug, Clone, PartialEq, KeyCodec, Hash)]
19#[key(tag = RingBuffer)]
20pub struct RingBufferKey {
21	pub ringbuffer: RingBufferId,
22}
23
24impl RingBufferKey {
25	pub fn new(ringbuffer: RingBufferId) -> Self {
26		Self {
27			ringbuffer,
28		}
29	}
30
31	pub fn encoded(ringbuffer: impl Into<RingBufferId>) -> EncodedKey {
32		Self::new(ringbuffer.into()).encode()
33	}
34
35	pub fn full_scan() -> TaggedKeyBoundRange {
36		TaggedKeyBoundRange::kind(Self::TAG)
37	}
38}
39
40#[derive(Debug, Clone, PartialEq, KeyCodec, Hash)]
41#[key(tag = RingBufferMetadata)]
42pub struct RingBufferMetadataKey {
43	pub storage: StorageId,
44	pub encoded_partition: Vec<u8>,
45}
46
47impl RingBufferMetadataKey {
48	pub fn new(storage: impl Into<StorageId>) -> Self {
49		Self {
50			storage: storage.into(),
51			encoded_partition: Vec::new(),
52		}
53	}
54
55	pub fn encoded(storage: impl Into<StorageId>) -> EncodedKey {
56		Self::new(storage).encode()
57	}
58
59	pub fn partition(storage: impl Into<StorageId>, partition_values: Vec<Value>) -> Self {
60		Self {
61			storage: storage.into(),
62			encoded_partition: encode_values(&partition_values).as_slice().to_vec(),
63		}
64	}
65
66	pub fn encoded_partition(storage: impl Into<StorageId>, partition_values: Vec<Value>) -> EncodedKey {
67		Self::partition(storage, partition_values).encode()
68	}
69
70	pub fn partition_values(&self) -> Option<Vec<Value>> {
71		let mut de = KeyDeserializer::from_bytes(&self.encoded_partition);
72		let mut partition_values = Vec::new();
73		while !de.is_empty() {
74			partition_values.push(de.read_value().ok()?);
75		}
76		Some(partition_values)
77	}
78
79	pub fn full_scan_for_storage(storage: impl Into<StorageId>) -> TaggedKeyBoundRange {
80		TaggedKeyBoundRange::prefix(Self::TAG, object_fields(ObjectId::from(storage.into())))
81	}
82}
83
84#[cfg(test)]
85mod tests {
86	use std::ops::RangeBounds;
87
88	use reifydb_codec::key::serializer::KeySerializer;
89
90	use super::*;
91	use crate::interface::catalog::id::ViewId;
92
93	#[test]
94	fn test_metadata_key_encode_decode_roundtrip() {
95		let key = RingBufferMetadataKey::encoded_partition(
96			RingBufferId(42),
97			vec![Value::Utf8("east".to_string())],
98		);
99		let mut de = KeyDeserializer::from_bytes(key.as_slice());
100		let _ = (de.read_u8(), de.read_object_id());
101		let value = de.read_value().unwrap();
102		assert_eq!(value, Value::Utf8("east".to_string()));
103	}
104
105	#[test]
106	fn test_metadata_key_encode_decode_multiple() {
107		let key = RingBufferMetadataKey::encoded_partition(
108			RingBufferId(7),
109			vec![Value::Utf8("us".to_string()), Value::Uint8(42)],
110		);
111		let mut de = KeyDeserializer::from_bytes(key.as_slice());
112		let _ = (de.read_u8(), de.read_object_id());
113		assert_eq!(de.read_value().unwrap(), Value::Utf8("us".to_string()));
114		assert_eq!(de.read_value().unwrap(), Value::Uint8(42));
115	}
116
117	#[test]
118	fn test_metadata_key_roundtrip_ringbuffer() {
119		// The tag byte is what keeps a ring buffer's metadata out of a view's; a bare id would collide.
120		let key = RingBufferMetadataKey::partition(
121			StorageId::RingBuffer(RingBufferId(42)),
122			vec![Value::Utf8("east".to_string())],
123		);
124		assert_eq!(RingBufferMetadataKey::decode(&key.encode()).unwrap(), key);
125	}
126
127	#[test]
128	fn test_metadata_key_roundtrip_view() {
129		// A ring-buffer-backed view keeps its own metadata under its own id, not a backing object's.
130		let key = RingBufferMetadataKey::partition(
131			StorageId::View(ViewId(42)),
132			vec![Value::Utf8("east".to_string())],
133		);
134		assert_eq!(RingBufferMetadataKey::decode(&key.encode()).unwrap(), key);
135	}
136
137	#[test]
138	fn test_full_scan_for_storage_excludes_a_view_with_the_same_id() {
139		// Ring buffer 42 and view 42 share a numeric id, so only the tag byte separates their scans.
140		let range = RingBufferMetadataKey::full_scan_for_storage(RingBufferId(42)).encode();
141		let ringbuffer = RingBufferMetadataKey::encoded(RingBufferId(42));
142		let view = RingBufferMetadataKey::encoded(ViewId(42));
143		assert!(range.contains(&ringbuffer));
144		assert!(!range.contains(&view));
145	}
146
147	#[test]
148	fn test_ring_buffer_key_matches_legacy_byte_layout() {
149		for id in [RingBufferId(0), RingBufferId(1), RingBufferId(u64::MAX)] {
150			let mut legacy = KeySerializer::with_capacity(9);
151			legacy.extend_u8(KeyTag::RingBuffer as u8).extend_u64(id);
152			assert_eq!(legacy.to_encoded_key().as_slice(), RingBufferKey::encoded(id).as_slice());
153		}
154	}
155}