uqa_storage/vector_index/
collection.rs1use std::{borrow::Borrow, collections::BTreeMap, sync::Arc};
10use uqa_core::{
11 memory::{Budgeted, BudgetedMap, BudgetedMapIter, BudgetedString, MemoryReservation},
12 FieldName,
13};
14
15use super::VectorIndex;
16use crate::{
17 read_control::StorageReadControl, ReadOnlySnapshot, StorageBackendError, StorageBackendResult,
18};
19
20type LiveIndexes = BTreeMap<FieldName, Box<dyn VectorIndex>>;
21type RetainedIndexes = Arc<Budgeted<BudgetedMap<FieldName, Box<dyn VectorIndex>>>>;
22
23pub struct VectorIndexes(Contents);
25
26enum Contents {
27 Live(LiveIndexes),
28 Retained {
29 indexes: Option<RetainedIndexes>,
30 control: StorageReadControl,
31 },
32}
33
34impl Default for VectorIndexes {
35 fn default() -> Self {
36 Self(Contents::Live(BTreeMap::new()))
37 }
38}
39
40impl From<LiveIndexes> for VectorIndexes {
41 fn from(indexes: LiveIndexes) -> Self {
42 Self(Contents::Live(indexes))
43 }
44}
45
46impl VectorIndexes {
47 pub fn live_mut(&mut self) -> StorageBackendResult<&mut LiveIndexes> {
48 match &mut self.0 {
49 Contents::Live(indexes) => Ok(indexes),
50 Contents::Retained { control, .. } => {
51 control.check()?;
52 Err(StorageBackendError::Other(
53 "cannot mutate retained vector registrations".into(),
54 ))
55 }
56 }
57 }
58
59 pub fn get<Q: Ord + ?Sized>(&self, field: &Q) -> Option<&(dyn VectorIndex + 'static)>
60 where
61 FieldName: Borrow<Q>,
62 {
63 let index = match &self.0 {
64 Contents::Live(indexes) => indexes.get(field),
65 Contents::Retained { indexes, .. } => {
66 indexes.as_ref().and_then(|indexes| indexes.get(field))
67 }
68 };
69 index.map(Box::as_ref)
70 }
71
72 pub fn contains_key<Q: Ord + ?Sized>(&self, field: &Q) -> bool
73 where
74 FieldName: Borrow<Q>,
75 {
76 self.get(field).is_some()
77 }
78
79 pub fn len(&self) -> usize {
80 match &self.0 {
81 Contents::Live(indexes) => indexes.len(),
82 Contents::Retained { indexes, .. } => {
83 indexes.as_ref().map_or(0, |indexes| indexes.len())
84 }
85 }
86 }
87
88 pub fn is_empty(&self) -> bool {
89 self.len() == 0
90 }
91
92 pub fn iter(&self) -> VectorIndexesIter<'_> {
93 VectorIndexesIter(match &self.0 {
94 Contents::Live(indexes) => Entries::Live(indexes.iter()),
95 Contents::Retained { indexes, .. } => indexes
96 .as_ref()
97 .map_or(Entries::Empty, |indexes| Entries::Retained(indexes.iter())),
98 })
99 }
100
101 pub fn keys(&self) -> impl ExactSizeIterator<Item = &FieldName> {
102 self.iter().map(|(field, _)| field)
103 }
104
105 pub fn values(&self) -> impl ExactSizeIterator<Item = &(dyn VectorIndex + 'static)> {
106 self.iter().map(|(_, index)| index)
107 }
108
109 pub fn capture(
111 source: &dyn VectorIndexSource,
112 control: &StorageReadControl,
113 ) -> StorageBackendResult<Self> {
114 control.check()?;
115 if let Some(retained) = source.retained_collection()? {
116 match &retained.0 {
117 Contents::Retained { control, .. } => control.check()?,
118 Contents::Live(_) => {
119 return Err(StorageBackendError::Other(
120 "vector provider returned live registrations as a retained collection"
121 .into(),
122 ))
123 }
124 }
125 return Ok(retained);
126 }
127 let mut retained = RetainedVectorIndexesBuilder::new(control);
128 source.visit(&mut |field, index| {
129 let (field, memory) = copy_field(field, control)?;
130 let index = index.snapshot_with_control(control)?;
131 retained.insert_admitted(field, ReadOnlySnapshot::new(index), memory)
132 })?;
133 retained.finish()
134 }
135}
136
137impl<Q: Ord + ?Sized> std::ops::Index<&Q> for VectorIndexes
138where
139 FieldName: Borrow<Q>,
140{
141 type Output = dyn VectorIndex;
142 fn index(&self, field: &Q) -> &Self::Output {
143 self.get(field).expect("missing vector index field")
144 }
145}
146
147pub struct VectorIndexesIter<'a>(Entries<'a>);
148
149#[expect(
150 clippy::large_enum_variant,
151 reason = "fixed iterator stack avoids unadmitted heap scratch"
152)]
153enum Entries<'a> {
154 Empty,
155 Live(std::collections::btree_map::Iter<'a, FieldName, Box<dyn VectorIndex>>),
156 Retained(BudgetedMapIter<'a, FieldName, Box<dyn VectorIndex>>),
157}
158
159impl<'a> Iterator for VectorIndexesIter<'a> {
160 type Item = (&'a FieldName, &'a (dyn VectorIndex + 'static));
161
162 fn next(&mut self) -> Option<Self::Item> {
163 let next = match &mut self.0 {
164 Entries::Empty => None,
165 Entries::Live(iter) => iter.next(),
166 Entries::Retained(iter) => iter.next(),
167 };
168 next.map(|(field, index)| (field, index.as_ref()))
169 }
170
171 fn size_hint(&self) -> (usize, Option<usize>) {
172 match &self.0 {
173 Entries::Empty => (0, Some(0)),
174 Entries::Live(iter) => iter.size_hint(),
175 Entries::Retained(iter) => iter.size_hint(),
176 }
177 }
178}
179
180impl ExactSizeIterator for VectorIndexesIter<'_> {}
181impl std::iter::FusedIterator for VectorIndexesIter<'_> {}
182
183impl<'a> IntoIterator for &'a VectorIndexes {
184 type Item = (&'a FieldName, &'a (dyn VectorIndex + 'static));
185 type IntoIter = VectorIndexesIter<'a>;
186 fn into_iter(self) -> Self::IntoIter {
187 self.iter()
188 }
189}
190
191pub trait VectorIndexSource {
193 fn visit(
194 &self,
195 visitor: &mut dyn FnMut(&str, &dyn VectorIndex) -> StorageBackendResult<()>,
196 ) -> StorageBackendResult<()>;
197
198 fn retained_collection(&self) -> StorageBackendResult<Option<VectorIndexes>> {
200 Ok(None)
201 }
202}
203
204impl VectorIndexSource for LiveIndexes {
205 fn visit(
206 &self,
207 visitor: &mut dyn FnMut(&str, &dyn VectorIndex) -> StorageBackendResult<()>,
208 ) -> StorageBackendResult<()> {
209 for (field, index) in self {
210 visitor(field, index.as_ref())?;
211 }
212 Ok(())
213 }
214}
215
216impl VectorIndexSource for VectorIndexes {
217 fn visit(
218 &self,
219 visitor: &mut dyn FnMut(&str, &dyn VectorIndex) -> StorageBackendResult<()>,
220 ) -> StorageBackendResult<()> {
221 for (field, index) in self {
222 visitor(field, index)?;
223 }
224 Ok(())
225 }
226
227 fn retained_collection(&self) -> StorageBackendResult<Option<VectorIndexes>> {
228 match &self.0 {
229 Contents::Live(_) => Ok(None),
230 Contents::Retained { indexes, control } => {
231 control.check()?;
232 Ok(Some(Self(Contents::Retained {
233 indexes: indexes.clone(),
234 control: control.clone(),
235 })))
236 }
237 }
238 }
239}
240
241pub struct RetainedVectorIndexesBuilder {
243 indexes: BudgetedMap<FieldName, Box<dyn VectorIndex>>,
244 control: StorageReadControl,
245}
246
247impl RetainedVectorIndexesBuilder {
248 pub fn new(control: &StorageReadControl) -> Self {
249 Self {
250 indexes: BudgetedMap::new(control.memory()),
251 control: control.clone(),
252 }
253 }
254
255 pub fn copy_field(
257 field: &str,
258 control: &StorageReadControl,
259 ) -> StorageBackendResult<(FieldName, MemoryReservation)> {
260 copy_field(field, control)
261 }
262
263 pub fn insert_admitted<T: VectorIndex + 'static>(
265 &mut self,
266 field: FieldName,
267 index: T,
268 mut memory: MemoryReservation,
269 ) -> StorageBackendResult<()> {
270 self.control.check()?;
271 if !memory.budget().shares_allowance(self.control.memory())
272 || memory.bytes() < field.capacity()
273 {
274 return Err(StorageBackendError::Other(
275 "retained vector name lacks its original allowance".into(),
276 ));
277 }
278 if self.indexes.contains_key(&field) {
279 return Err(StorageBackendError::Other(
280 "retained vector field is registered twice".into(),
281 ));
282 }
283 memory.grow(size_of::<ReadOnlySnapshot<T>>())?;
284 let index = ReadOnlySnapshot::from_budgeted(Budgeted::new(index, memory))?;
285 let index: Box<dyn VectorIndex> = Box::new(index);
286 self.control.check()?;
287 self.indexes.insert(field, index)?;
288 Ok(())
289 }
290
291 pub fn finish(self) -> StorageBackendResult<VectorIndexes> {
292 self.control.check()?;
293 let indexes = if self.indexes.is_empty() {
294 None
295 } else {
296 Some(
297 Budgeted::new(self.indexes, self.control.memory().empty_reservation())
298 .into_shared()?,
299 )
300 };
301 Ok(VectorIndexes(Contents::Retained {
302 indexes,
303 control: self.control,
304 }))
305 }
306}
307
308fn copy_field(
309 field: &str,
310 control: &StorageReadControl,
311) -> StorageBackendResult<(FieldName, MemoryReservation)> {
312 control.check()?;
313 let mut name = BudgetedString::new(control.memory());
314 name.reserve(field.len())?;
315 for (offset, character) in field.chars().enumerate() {
316 if offset % 1024 == 0 {
317 control.check()?;
318 }
319 name.push(character)?;
320 }
321 Ok(name.into_parts())
322}
323
324#[cfg(test)]
325mod tests;