Skip to main content

reifydb_core/value/index/
range.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2025 ReifyDB
3
4use std::{collections::Bound, iter};
5
6use crate::{
7	encoded::key::{EncodedKey, EncodedKeyRange},
8	value::index::encoded::EncodedIndexKey,
9};
10
11#[derive(Clone, Debug)]
12pub struct EncodedIndexKeyRange {
13	pub start: Bound<EncodedIndexKey>,
14	pub end: Bound<EncodedIndexKey>,
15}
16
17impl EncodedIndexKeyRange {
18	pub fn new(start: Bound<EncodedIndexKey>, end: Bound<EncodedIndexKey>) -> Self {
19		Self {
20			start,
21			end,
22		}
23	}
24
25	pub fn start_end(start: Option<EncodedIndexKey>, end: Option<EncodedIndexKey>) -> Self {
26		let start = match start {
27			Some(s) => Bound::Included(s),
28			None => Bound::Unbounded,
29		};
30
31		let end = match end {
32			Some(e) => Bound::Excluded(e),
33			None => Bound::Unbounded,
34		};
35
36		Self {
37			start,
38			end,
39		}
40	}
41
42	pub fn start_end_inclusive(start: Option<EncodedIndexKey>, end: Option<EncodedIndexKey>) -> Self {
43		let start = match start {
44			Some(s) => Bound::Included(s),
45			None => Bound::Unbounded,
46		};
47
48		let end = match end {
49			Some(e) => Bound::Included(e),
50			None => Bound::Unbounded,
51		};
52
53		Self {
54			start,
55			end,
56		}
57	}
58
59	pub fn prefix(prefix: &[u8]) -> Self {
60		let start = Bound::Included(EncodedIndexKey::from_bytes(prefix));
61		let end = match prefix.iter().rposition(|&b| b != 0xff) {
62			Some(i) => Bound::Excluded(EncodedIndexKey::from_bytes(
63				&prefix.iter().take(i).copied().chain(iter::once(prefix[i] + 1)).collect::<Vec<_>>(),
64			)),
65			None => Bound::Unbounded,
66		};
67		Self {
68			start,
69			end,
70		}
71	}
72
73	pub fn all() -> Self {
74		Self {
75			start: Bound::Unbounded,
76			end: Bound::Unbounded,
77		}
78	}
79
80	pub fn to_encoded_key_range(&self) -> EncodedKeyRange {
81		let start = match &self.start {
82			Bound::Included(key) => Bound::Included(EncodedKey::new(key.as_slice())),
83			Bound::Excluded(key) => Bound::Excluded(EncodedKey::new(key.as_slice())),
84			Bound::Unbounded => Bound::Unbounded,
85		};
86
87		let end = match &self.end {
88			Bound::Included(key) => Bound::Included(EncodedKey::new(key.as_slice())),
89			Bound::Excluded(key) => Bound::Excluded(EncodedKey::new(key.as_slice())),
90			Bound::Unbounded => Bound::Unbounded,
91		};
92
93		EncodedKeyRange::new(start, end)
94	}
95
96	pub fn from_prefix(key: &EncodedIndexKey) -> Self {
97		Self::prefix(key.as_slice())
98	}
99}
100
101impl From<EncodedIndexKeyRange> for EncodedKeyRange {
102	fn from(range: EncodedIndexKeyRange) -> Self {
103		range.to_encoded_key_range()
104	}
105}
106
107#[cfg(test)]
108pub mod tests {
109	use reifydb_type::value::r#type::Type;
110
111	use super::*;
112	use crate::{sort::SortDirection, value::index::shape::IndexShape};
113
114	#[test]
115	fn test_start_end() {
116		let layout = IndexShape::new(&[Type::Uint8], &[SortDirection::Asc]).unwrap();
117
118		let mut key1 = layout.allocate_key();
119		layout.set_u64(&mut key1, 0, 100u64);
120
121		let mut key2 = layout.allocate_key();
122		layout.set_u64(&mut key2, 0, 200u64);
123
124		let range = EncodedIndexKeyRange::start_end(Some(key1.clone()), Some(key2.clone()));
125
126		match &range.start {
127			Bound::Included(k) => {
128				assert_eq!(k.as_slice(), key1.as_slice())
129			}
130			_ => panic!("Expected Included start bound"),
131		}
132
133		match &range.end {
134			Bound::Excluded(k) => {
135				assert_eq!(k.as_slice(), key2.as_slice())
136			}
137			_ => panic!("Expected Excluded end bound"),
138		}
139	}
140
141	#[test]
142	fn test_start_end_inclusive() {
143		let layout = IndexShape::new(&[Type::Uint8], &[SortDirection::Asc]).unwrap();
144
145		let mut key1 = layout.allocate_key();
146		layout.set_u64(&mut key1, 0, 100u64);
147
148		let mut key2 = layout.allocate_key();
149		layout.set_u64(&mut key2, 0, 200u64);
150
151		let range = EncodedIndexKeyRange::start_end_inclusive(Some(key1.clone()), Some(key2.clone()));
152
153		match &range.start {
154			Bound::Included(k) => {
155				assert_eq!(k.as_slice(), key1.as_slice())
156			}
157			_ => panic!("Expected Included start bound"),
158		}
159
160		match &range.end {
161			Bound::Included(k) => {
162				assert_eq!(k.as_slice(), key2.as_slice())
163			}
164			_ => panic!("Expected Included end bound"),
165		}
166	}
167
168	#[test]
169	fn test_unbounded() {
170		let range = EncodedIndexKeyRange::start_end(None, None);
171		assert!(matches!(range.start, Bound::Unbounded));
172		assert!(matches!(range.end, Bound::Unbounded));
173	}
174
175	#[test]
176	fn test_prefix() {
177		let prefix = &[0x12, 0x34];
178		let range = EncodedIndexKeyRange::prefix(prefix);
179
180		match &range.start {
181			Bound::Included(k) => assert_eq!(k.as_slice(), prefix),
182			_ => panic!("Expected Included start bound"),
183		}
184
185		match &range.end {
186			Bound::Excluded(k) => {
187				assert_eq!(k.as_slice(), &[0x12, 0x35])
188			}
189			_ => panic!("Expected Excluded end bound"),
190		}
191	}
192
193	#[test]
194	fn test_prefix_with_ff() {
195		let prefix = &[0x12, 0xff];
196		let range = EncodedIndexKeyRange::prefix(prefix);
197
198		match &range.start {
199			Bound::Included(k) => assert_eq!(k.as_slice(), prefix),
200			_ => panic!("Expected Included start bound"),
201		}
202
203		match &range.end {
204			Bound::Excluded(k) => assert_eq!(k.as_slice(), &[0x13]),
205			_ => panic!("Expected Excluded end bound"),
206		}
207	}
208
209	#[test]
210	fn test_prefix_all_ff() {
211		let prefix = &[0xff, 0xff];
212		let range = EncodedIndexKeyRange::prefix(prefix);
213
214		match &range.start {
215			Bound::Included(k) => assert_eq!(k.as_slice(), prefix),
216			_ => panic!("Expected Included start bound"),
217		}
218
219		assert!(matches!(range.end, Bound::Unbounded));
220	}
221
222	#[test]
223	fn test_to_encoded_key_range() {
224		let layout = IndexShape::new(&[Type::Uint8], &[SortDirection::Asc]).unwrap();
225
226		let mut key = layout.allocate_key();
227		layout.set_u64(&mut key, 0, 100u64);
228
229		let index_range = EncodedIndexKeyRange::start_end(Some(key.clone()), None);
230		let key_range = index_range.to_encoded_key_range();
231
232		match &key_range.start {
233			Bound::Included(k) => {
234				assert_eq!(k.as_slice(), key.as_slice())
235			}
236			_ => panic!("Expected Included start bound"),
237		}
238
239		assert!(matches!(key_range.end, Bound::Unbounded));
240	}
241
242	#[test]
243	fn test_all() {
244		let range = EncodedIndexKeyRange::all();
245		assert!(matches!(range.start, Bound::Unbounded));
246		assert!(matches!(range.end, Bound::Unbounded));
247	}
248}