Skip to main content

reifydb_core/value/index/
range.rs

1// SPDX-License-Identifier: AGPL-3.0-or-later
2// Copyright (c) 2025 ReifyDB
3
4use std::collections::Bound;
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	/// Constructs a key range from optional start and end index keys.
26	///
27	/// - `start`: If provided, marks the inclusive lower bound of the range. If `None`, the range is unbounded
28	///   below.
29	/// - `end`: If provided, marks the exclusive upper bound of the range. If `None`, the range is unbounded above.
30	///
31	/// This is useful for creating ranges for index scans.
32	pub fn start_end(start: Option<EncodedIndexKey>, end: Option<EncodedIndexKey>) -> Self {
33		let start = match start {
34			Some(s) => Bound::Included(s),
35			None => Bound::Unbounded,
36		};
37
38		let end = match end {
39			Some(e) => Bound::Excluded(e),
40			None => Bound::Unbounded,
41		};
42
43		Self {
44			start,
45			end,
46		}
47	}
48
49	/// Constructs a key range from optional inclusive start and end index
50	/// keys.
51	///
52	/// - `start`: If provided, marks the inclusive lower bound of the range. If `None`, the range is unbounded
53	///   below.
54	/// - `end`: If provided, marks the inclusive upper bound of the range. If `None`, the range is unbounded above.
55	///
56	/// Both bounds are inclusive when provided.
57	pub fn start_end_inclusive(start: Option<EncodedIndexKey>, end: Option<EncodedIndexKey>) -> Self {
58		let start = match start {
59			Some(s) => Bound::Included(s),
60			None => Bound::Unbounded,
61		};
62
63		let end = match end {
64			Some(e) => Bound::Included(e),
65			None => Bound::Unbounded,
66		};
67
68		Self {
69			start,
70			end,
71		}
72	}
73
74	/// Generates a key range for an index key prefix, used for prefix
75	/// scans.
76	///
77	/// The exclusive end bound is generated by adding 1 to the value of the
78	/// last byte. If the last byte(s) is 0xff (so adding 1 would
79	/// overflow), we instead find the latest non-0xff byte, increment
80	/// that, and truncate the rest. If all bytes are 0xff, we scan to the
81	/// end of the range.
82	pub fn prefix(prefix: &[u8]) -> Self {
83		let start = Bound::Included(EncodedIndexKey::from_bytes(prefix));
84		let end = match prefix.iter().rposition(|&b| b != 0xff) {
85			Some(i) => Bound::Excluded(EncodedIndexKey::from_bytes(
86				&prefix.iter()
87					.take(i)
88					.copied()
89					.chain(std::iter::once(prefix[i] + 1))
90					.collect::<Vec<_>>(),
91			)),
92			None => Bound::Unbounded,
93		};
94		Self {
95			start,
96			end,
97		}
98	}
99
100	/// Constructs a key range that fragments the entire keyspace.
101	pub fn all() -> Self {
102		Self {
103			start: Bound::Unbounded,
104			end: Bound::Unbounded,
105		}
106	}
107
108	/// Converts this EncodedIndexKeyRange to an EncodedKeyRange by
109	/// converting the bounds. This is useful when you need to use index
110	/// ranges with storage APIs that expect EncodedKeyRange.
111	pub fn to_encoded_key_range(&self) -> EncodedKeyRange {
112		let start = match &self.start {
113			Bound::Included(key) => Bound::Included(EncodedKey::new(key.as_slice())),
114			Bound::Excluded(key) => Bound::Excluded(EncodedKey::new(key.as_slice())),
115			Bound::Unbounded => Bound::Unbounded,
116		};
117
118		let end = match &self.end {
119			Bound::Included(key) => Bound::Included(EncodedKey::new(key.as_slice())),
120			Bound::Excluded(key) => Bound::Excluded(EncodedKey::new(key.as_slice())),
121			Bound::Unbounded => Bound::Unbounded,
122		};
123
124		EncodedKeyRange::new(start, end)
125	}
126
127	/// Creates a range from an EncodedIndexKey prefix.
128	/// This will match all keys that start with the given prefix.
129	pub fn from_prefix(key: &EncodedIndexKey) -> Self {
130		Self::prefix(key.as_slice())
131	}
132}
133
134impl From<EncodedIndexKeyRange> for EncodedKeyRange {
135	fn from(range: EncodedIndexKeyRange) -> Self {
136		range.to_encoded_key_range()
137	}
138}
139
140#[cfg(test)]
141pub mod tests {
142	use reifydb_type::value::r#type::Type;
143
144	use super::*;
145	use crate::{sort::SortDirection, value::index::layout::EncodedIndexLayout};
146
147	#[test]
148	fn test_start_end() {
149		let layout = EncodedIndexLayout::new(&[Type::Uint8], &[SortDirection::Asc]).unwrap();
150
151		let mut key1 = layout.allocate_key();
152		layout.set_u64(&mut key1, 0, 100u64);
153
154		let mut key2 = layout.allocate_key();
155		layout.set_u64(&mut key2, 0, 200u64);
156
157		let range = EncodedIndexKeyRange::start_end(Some(key1.clone()), Some(key2.clone()));
158
159		match &range.start {
160			Bound::Included(k) => {
161				assert_eq!(k.as_slice(), key1.as_slice())
162			}
163			_ => panic!("Expected Included start bound"),
164		}
165
166		match &range.end {
167			Bound::Excluded(k) => {
168				assert_eq!(k.as_slice(), key2.as_slice())
169			}
170			_ => panic!("Expected Excluded end bound"),
171		}
172	}
173
174	#[test]
175	fn test_start_end_inclusive() {
176		let layout = EncodedIndexLayout::new(&[Type::Uint8], &[SortDirection::Asc]).unwrap();
177
178		let mut key1 = layout.allocate_key();
179		layout.set_u64(&mut key1, 0, 100u64);
180
181		let mut key2 = layout.allocate_key();
182		layout.set_u64(&mut key2, 0, 200u64);
183
184		let range = EncodedIndexKeyRange::start_end_inclusive(Some(key1.clone()), Some(key2.clone()));
185
186		match &range.start {
187			Bound::Included(k) => {
188				assert_eq!(k.as_slice(), key1.as_slice())
189			}
190			_ => panic!("Expected Included start bound"),
191		}
192
193		match &range.end {
194			Bound::Included(k) => {
195				assert_eq!(k.as_slice(), key2.as_slice())
196			}
197			_ => panic!("Expected Included end bound"),
198		}
199	}
200
201	#[test]
202	fn test_unbounded() {
203		let range = EncodedIndexKeyRange::start_end(None, None);
204		assert!(matches!(range.start, Bound::Unbounded));
205		assert!(matches!(range.end, Bound::Unbounded));
206	}
207
208	#[test]
209	fn test_prefix() {
210		let prefix = &[0x12, 0x34];
211		let range = EncodedIndexKeyRange::prefix(prefix);
212
213		match &range.start {
214			Bound::Included(k) => assert_eq!(k.as_slice(), prefix),
215			_ => panic!("Expected Included start bound"),
216		}
217
218		match &range.end {
219			Bound::Excluded(k) => {
220				assert_eq!(k.as_slice(), &[0x12, 0x35])
221			}
222			_ => panic!("Expected Excluded end bound"),
223		}
224	}
225
226	#[test]
227	fn test_prefix_with_ff() {
228		let prefix = &[0x12, 0xff];
229		let range = EncodedIndexKeyRange::prefix(prefix);
230
231		match &range.start {
232			Bound::Included(k) => assert_eq!(k.as_slice(), prefix),
233			_ => panic!("Expected Included start bound"),
234		}
235
236		match &range.end {
237			Bound::Excluded(k) => assert_eq!(k.as_slice(), &[0x13]),
238			_ => panic!("Expected Excluded end bound"),
239		}
240	}
241
242	#[test]
243	fn test_prefix_all_ff() {
244		let prefix = &[0xff, 0xff];
245		let range = EncodedIndexKeyRange::prefix(prefix);
246
247		match &range.start {
248			Bound::Included(k) => assert_eq!(k.as_slice(), prefix),
249			_ => panic!("Expected Included start bound"),
250		}
251
252		assert!(matches!(range.end, Bound::Unbounded));
253	}
254
255	#[test]
256	fn test_to_encoded_key_range() {
257		let layout = EncodedIndexLayout::new(&[Type::Uint8], &[SortDirection::Asc]).unwrap();
258
259		let mut key = layout.allocate_key();
260		layout.set_u64(&mut key, 0, 100u64);
261
262		let index_range = EncodedIndexKeyRange::start_end(Some(key.clone()), None);
263		let key_range = index_range.to_encoded_key_range();
264
265		match &key_range.start {
266			Bound::Included(k) => {
267				assert_eq!(k.as_slice(), key.as_slice())
268			}
269			_ => panic!("Expected Included start bound"),
270		}
271
272		assert!(matches!(key_range.end, Bound::Unbounded));
273	}
274
275	#[test]
276	fn test_all() {
277		let range = EncodedIndexKeyRange::all();
278		assert!(matches!(range.start, Bound::Unbounded));
279		assert!(matches!(range.end, Bound::Unbounded));
280	}
281}