Skip to main content

reifydb_core/value/index/
range.rs

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