/rust/registry/src/index.crates.io-1949cf8c6b5b557f/diskann-0.53.0/src/provider.rs
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) Microsoft Corporation. |
3 | | * Licensed under the MIT license. |
4 | | */ |
5 | | |
6 | | //! The [`DataProvider`] trait encompasses the following concepts: |
7 | | //! |
8 | | //! * Storage for arbitrary data that is contextually accessed through the [`Accessor`] trait. |
9 | | //! |
10 | | //! * Mapping between an "external id" (a unique external identifier for an entry in data |
11 | | //! store) and an "internal id", which is a simple typed used as a handle to vector data |
12 | | //! internally. |
13 | | //! |
14 | | //! * Support for deletion via the [`Delete`] sub-trait. |
15 | | //! |
16 | | //! # Important Related Traits |
17 | | //! |
18 | | //! The [`DataProvider`] trait is really an ensemble of multiple related traits all working |
19 | | //! together to solve a problem. |
20 | | //! |
21 | | //! Important associated traits are described here. |
22 | | //! |
23 | | //! * [`SetElement`]: This is the method that allows assigning of data into the |
24 | | //! [`DataProvider`] via external ID. This trait is parameterized by the type of the |
25 | | //! "vector" being assigned, providing a mechanism for inclusion of arbitrary associated |
26 | | //! data along with the raw vector. |
27 | | //! |
28 | | //! The responsibility of the `set_element` method is several fold: |
29 | | //! |
30 | | //! * It must assign an internal ID for the provided external ID. It can do this by using |
31 | | //! a naive identity map. |
32 | | //! |
33 | | //! * After insertion, the implementer may return a guard that will notify the provider |
34 | | //! of an unsuccessful operation, allowing automatic cleanup and consistency control |
35 | | //! over the external ID to internal ID mapping. |
36 | | //! |
37 | | //! * [`Delete`]: A sub-trait of [`DataProvider`] indicating that the provider supports the |
38 | | //! notion of deleted items. |
39 | | //! |
40 | | //! We differentiate between two different kinds of deletion: |
41 | | //! |
42 | | //! * Soft Deletion: Where a external ID is marked as deleted, but the internal vector id |
43 | | //! may still be reachable in an index. |
44 | | //! |
45 | | //! * Hard Deletion (aka "release): This deletes an item by internal ID, removing it |
46 | | //! completely from the store. |
47 | | //! |
48 | | //! Note that the exact semantics of deletion are defined by the implementer. Some |
49 | | //! implementations may allow internal IDs to be reused immediately following the deletion |
50 | | //! of the external ID while others may wait for a "release". |
51 | | //! |
52 | | //! * [`HasId`]: Traits such as [`Accessor`], [`NeighborAccessor`] and [`DelegateNeighbors`] |
53 | | //! all need to interact with the underlying [`DataProvider`] using an internal ID type. |
54 | | //! |
55 | | //! The [`HasId`] trait provides a common base-trait for these related concepts, which |
56 | | //! ensures implementers only need to define and constrain it once. |
57 | | //! |
58 | | //! * [`Accessor`]: A contextual proxy object for retrieving data from the [`DataProvider`]. |
59 | | //! The key idea behind an accessor is that data to be retrieved from a data store |
60 | | //! is contextual. In other words, in some contexts, we may retrieve one kind of data |
61 | | //! (for example, full precision vectors), while in other contexts we may want to |
62 | | //! retrieve another kind (such as quantized vectors). |
63 | | //! |
64 | | //! Accessors can be implemented as simple handles to a provider, or can have local |
65 | | //! scratch to assist in bulk operations. |
66 | | //! |
67 | | //! Finally, one handy feature of accessors is that they can carry a lifetime, which |
68 | | //! surprisingly can make writing algorithms involving borrows easier than trying to |
69 | | //! manage a lifetime strictly through associated types with lifetimes. |
70 | | //! |
71 | | //! * [`BuildDistanceComputer`]: A sub-trait of [`Accessor`] that allows for random-access |
72 | | //! distance computations on the retrieved elements. |
73 | | //! |
74 | | //! * [`BuildQueryComputer`]: A sub-trait of [`Accessor`] that allows for specialized query |
75 | | //! based computations. This allows a query to be pre-processed in a way that allows |
76 | | //! faster computations. |
77 | | //! |
78 | | //! # Neighbor Delegation |
79 | | //! |
80 | | //! Index search requires that accessor types implement both the data-centric [`Accessor`] |
81 | | //! trait and the graph retrieval [`NeighborAccessor`]/[`NeighborAccessorMut`] traits. |
82 | | //! |
83 | | //! While having multiple implementations of [`Accessor`] is common to support different |
84 | | //! kinds of searches in the quantized space, neighbor retrieval commonly need not vary. |
85 | | //! Instead of requiring all implementations of [`Accessor`] to manually forward the methods |
86 | | //! (both required and provided) in the [`NeighborAccesosr`] traits, we use a delegation |
87 | | //! technique supplied by the [`DelegateNeighbor`] trait. |
88 | | //! |
89 | | //! [`Accessor`] types should implement [`DelegateNeighbor`] to return a [`NeighborAccessor`]. |
90 | | //! Implementation of [`DelegateNeighbors`] will automatically implement [`AsNeighbor`] and |
91 | | //! [`AsNeighborMut`] (the latter is only applicable if the returned type implements |
92 | | //! [`NeighborAccessorMut`]. |
93 | | //! |
94 | | //! Similarly, algorithms requiring graph access should accept `&mut T` where |
95 | | //! `T: AsNeighbor` or `T: AsNeighborMut`. This provides access to blanket implementations |
96 | | //! of [`NeighborAccessor`] and [`NeighborAccessorMut`] for `&mut T`. |
97 | | |
98 | | use std::ops::Deref; |
99 | | |
100 | | use diskann_utils::Reborrow; |
101 | | use diskann_vector::{DistanceFunction, PreprocessedDistanceFunction}; |
102 | | use sealed::{BoundTo, Sealed}; |
103 | | |
104 | | use crate::{ANNError, ANNResult, error::ToRanked, graph::AdjacencyList, utils::VectorId}; |
105 | | |
106 | | ////////////////////// |
107 | | // ExecutionContext // |
108 | | ////////////////////// |
109 | | |
110 | | /// An execution context given to various providers, threaded through insertions, searches |
111 | | /// etc. for information forwarding. |
112 | | pub trait ExecutionContext: Send + Sync + Clone + 'static { |
113 | | ////////////////////// |
114 | | // Provided Methods // |
115 | | ////////////////////// |
116 | | |
117 | | /// Provide a customization point for tasks spawned while under this context. |
118 | | /// |
119 | | /// The future `f` is a Future that DiskANN intends to spawn as a task using an spawn |
120 | | /// method such as `tokio::spawn`. `DiskANN will pass this Future through this function |
121 | | /// before creating the task. |
122 | | /// |
123 | | /// This allows the `ExecutionContext` to nest that Future inside another Future if desired. |
124 | | /// An example of such a nesting would be to nest `f` inside of a profiling future |
125 | | /// to record the CPU cycles spent executing `f`. |
126 | | /// |
127 | | /// The default implementation of this method is the identity, simply passing through |
128 | | /// the future unmodified. |
129 | 0 | fn wrap_spawn<F, T>(&self, f: F) -> impl std::future::Future<Output = T> + Send + 'static |
130 | 0 | where |
131 | 0 | F: std::future::Future<Output = T> + Send + 'static, |
132 | | { |
133 | 0 | f |
134 | 0 | } |
135 | | } |
136 | | |
137 | | ////////////////// |
138 | | // DataProvider // |
139 | | ////////////////// |
140 | | |
141 | | /// A base trait for the struct providing data into the index. |
142 | | /// |
143 | | /// The requirements on this trait are quite sparse. Instead, additional behavior is accessed |
144 | | /// through derived and related traits, namely |
145 | | /// |
146 | | /// * [`SetElement`]: An overloadable version of `set_vector`. |
147 | | /// * [`Accessor`]: An overloadable, contextual class for retrieving data elements from |
148 | | /// the data provider. |
149 | | /// |
150 | | /// Indexing algorithms explose overloadable "strategies" that allow data provider to |
151 | | /// select the accessor. |
152 | | /// |
153 | | /// Example strategies include: |
154 | | /// |
155 | | /// * [`crate::graph::glue::SearchStrategy`] |
156 | | /// * [`crate::graph::glue::PruneStrategy`] |
157 | | /// * [`crate::graph::glue::InsertStrategy`] |
158 | | /// |
159 | | /// # Type Constraints: |
160 | | /// |
161 | | /// * `Sized`: Data provider types are used in contexts where `Self` is used to instantiate |
162 | | /// a generic. This can only be done if `Self: Sized`. |
163 | | /// |
164 | | /// * `Send` and `Sync`: Mostly to help async code compile and be `Send`. |
165 | | /// |
166 | | /// * `'static`: Helpful for avoiding excess lifetime constraints. |
167 | | pub trait DataProvider: Sized + Send + Sync + 'static { |
168 | | type Context: ExecutionContext; |
169 | | type InternalId: VectorId; |
170 | | type ExternalId: PartialEq + Send + Sync + 'static; |
171 | | |
172 | | type Error: ToRanked + std::fmt::Debug + Send + Sync + 'static; |
173 | | |
174 | | /// The operation guard returned by [`SetElement::set_element`] to notify `self` if an |
175 | | /// operation fails. |
176 | | /// |
177 | | /// This is required to be `'static` for multi-insert compatibility (where it is required |
178 | | /// to cross spawn boundaries). |
179 | | type Guard: Guard<Id = Self::InternalId> + 'static; |
180 | | |
181 | | /// Translate an external id to its corresponding internal id. |
182 | | /// |
183 | | /// The vector referenced by `gid` must already have been added to the provider via |
184 | | /// [`SetElement`]. The mapping is undefined until then. |
185 | | fn to_internal_id( |
186 | | &self, |
187 | | context: &Self::Context, |
188 | | gid: &Self::ExternalId, |
189 | | ) -> Result<Self::InternalId, Self::Error>; |
190 | | |
191 | | /// Translate an internal id to its corresponding external id. |
192 | | fn to_external_id( |
193 | | &self, |
194 | | context: &Self::Context, |
195 | | id: Self::InternalId, |
196 | | ) -> Result<Self::ExternalId, Self::Error>; |
197 | | } |
198 | | |
199 | | //////////// |
200 | | // Delete // |
201 | | //////////// |
202 | | |
203 | | pub trait Delete: DataProvider { |
204 | | /// Delete an item by external ID. |
205 | | /// |
206 | | /// Note that internal vector IDs may still be reachable. In the context of a graph |
207 | | /// index, this is equivalent to a "soft" delete where the deleted ID should no longer |
208 | | /// be returned as the result of search methods, but may still be accessed by its |
209 | | /// private ID during graph node expansion. |
210 | | fn delete( |
211 | | &self, |
212 | | context: &Self::Context, |
213 | | gid: &Self::ExternalId, |
214 | | ) -> impl std::future::Future<Output = Result<(), Self::Error>> + Send; |
215 | | |
216 | | /// Release a node by an internal ID. |
217 | | /// |
218 | | /// This is called by the index only when there are no longer any incoming edges to |
219 | | /// a particular data point. |
220 | | /// |
221 | | /// In particular, the index makes the guarantee that when it invokes `release` on an |
222 | | /// internal ID, it will not try to retrive an element via the same internal ID via |
223 | | /// an accessor derived from `self` until `SetElement` yields the internal ID. |
224 | | fn release( |
225 | | &self, |
226 | | context: &Self::Context, |
227 | | id: Self::InternalId, |
228 | | ) -> impl std::future::Future<Output = Result<(), Self::Error>> + Send; |
229 | | |
230 | | /// Check the status via internal ID. |
231 | | fn status_by_internal_id( |
232 | | &self, |
233 | | context: &Self::Context, |
234 | | id: Self::InternalId, |
235 | | ) -> impl std::future::Future<Output = Result<ElementStatus, Self::Error>> + Send; |
236 | | |
237 | | /// Check the status via external ID. |
238 | | fn status_by_external_id( |
239 | | &self, |
240 | | context: &Self::Context, |
241 | | gid: &Self::ExternalId, |
242 | | ) -> impl std::future::Future<Output = Result<ElementStatus, Self::Error>> + Send; |
243 | | |
244 | | /// A potentially optimized bulk version of `status_by_internal_id`. |
245 | 0 | fn statuses_unordered<Itr, F>( |
246 | 0 | &self, |
247 | 0 | context: &Self::Context, |
248 | 0 | itr: Itr, |
249 | 0 | mut f: F, |
250 | 0 | ) -> impl std::future::Future<Output = Result<(), Self::Error>> + Send |
251 | 0 | where |
252 | 0 | Itr: Iterator<Item = Self::InternalId> + Send, |
253 | 0 | F: FnMut(Result<ElementStatus, Self::Error>, Self::InternalId) + Send, |
254 | | { |
255 | 0 | async move { |
256 | 0 | for i in itr { |
257 | 0 | f(self.status_by_internal_id(context, i).await, i); |
258 | | } |
259 | 0 | Ok(()) |
260 | 0 | } |
261 | 0 | } |
262 | | } |
263 | | |
264 | | /// Describe the status of accessing a vector by internal or external id. |
265 | | #[derive(Debug, Clone, Copy, PartialEq)] |
266 | | pub enum ElementStatus { |
267 | | /// The ID is valid. |
268 | | Valid, |
269 | | /// The ID used to be valid but points to a deleted element. Some values behind the |
270 | | /// deleted may still be accessible until it is removed entirely from the graph. |
271 | | Deleted, |
272 | | } |
273 | | |
274 | | impl ElementStatus { |
275 | | /// Return `true` if `ElementStatus::Valid`. |
276 | 0 | pub fn is_valid(self) -> bool { |
277 | 0 | self == Self::Valid |
278 | 0 | } |
279 | | |
280 | | /// Return `true` if `ElementStatus::Deleted`. |
281 | 0 | pub fn is_deleted(self) -> bool { |
282 | 0 | self == Self::Deleted |
283 | 0 | } |
284 | | } |
285 | | |
286 | | /////////// |
287 | | // HasId // |
288 | | /////////// |
289 | | |
290 | | /// Indicate an association with an Id type. |
291 | | pub trait HasId { |
292 | | type Id: VectorId; |
293 | | } |
294 | | |
295 | | impl<T> HasId for &T |
296 | | where |
297 | | T: HasId, |
298 | | { |
299 | | type Id = T::Id; |
300 | | } |
301 | | |
302 | | impl<T> HasId for &mut T |
303 | | where |
304 | | T: HasId, |
305 | | { |
306 | | type Id = T::Id; |
307 | | } |
308 | | |
309 | | //////////////// |
310 | | // SetElement // |
311 | | //////////////// |
312 | | |
313 | | /// An overloadable `DataProvider` sub-trait allowing element assignment. |
314 | | pub trait SetElement<T>: DataProvider { |
315 | | /// The kind of error yielded by `set_element`. |
316 | | type SetError: ToRanked + std::fmt::Debug + Send + Sync + 'static; |
317 | | |
318 | | /// Internally store the value of `element` and associate it with `id`. |
319 | | /// |
320 | | /// The storing does not necessarily need to be lossless if, for example, the parent |
321 | | /// data provider solely uses a quantized representation. |
322 | | /// |
323 | | /// Note that it is suggests that a well-behaved implementation rolls-back internal |
324 | | /// state in the event that an error is returned. |
325 | | /// |
326 | | /// Furthermore, a guard is returned. The caller of `set_element` will `complete` the |
327 | | /// guard once operation completes successfully. Unsuccessful execution may roll back |
328 | | /// external to internal mappings, but may not reclaim the local slot. |
329 | | fn set_element( |
330 | | &self, |
331 | | context: &Self::Context, |
332 | | id: &Self::ExternalId, |
333 | | element: T, |
334 | | ) -> impl std::future::Future<Output = Result<Self::Guard, Self::SetError>> + Send; |
335 | | } |
336 | | |
337 | | /// A guard object that will be completed when an insert operation is successful. |
338 | | /// |
339 | | /// This is used as the return type of [`SetElement`] (for example, at the beginning of |
340 | | /// an insert operation), and has three main jobs: |
341 | | /// |
342 | | /// 1. It provides a means for the data provider to associate a private ID with a public ID |
343 | | /// and communicate that association to algorithms. |
344 | | /// |
345 | | /// 2. The guard's lifetime is associated with the duration of an operation (e.g. insert), |
346 | | /// and calling the `complete` method indicates a successful completion of that operation. |
347 | | /// |
348 | | /// 3. Dropping the guard before calling `complete` can notifies the data provider of an |
349 | | /// unsuccessful completion of the operation, allowing the provider to clean up internal |
350 | | /// state. |
351 | | pub trait Guard: Send + Sync + 'static { |
352 | | /// The Id type associated with the Guard. |
353 | | type Id; |
354 | | |
355 | | /// Successfully complete the guarded operation. |
356 | | fn complete(self) -> impl std::future::Future<Output = ()> + Send; |
357 | | |
358 | | /// Retrieve the inernal ID the data provider assigns to the external ID. |
359 | | fn id(&self) -> Self::Id; |
360 | | } |
361 | | |
362 | | /// A simple `Guard` implementation where completion and dropping is a no-op. |
363 | | #[derive(Debug, Default)] |
364 | | pub struct NoopGuard<I>(I); |
365 | | |
366 | | impl<I> NoopGuard<I> { |
367 | | /// Construct a new guard that yields `id` on `retrieve`. |
368 | 0 | pub fn new(id: I) -> Self { |
369 | 0 | Self(id) |
370 | 0 | } Unexecuted instantiation: <diskann::provider::NoopGuard<u64>>::new Unexecuted instantiation: <diskann::provider::NoopGuard<_>>::new |
371 | | } |
372 | | |
373 | | impl<I> Guard for NoopGuard<I> |
374 | | where |
375 | | I: Send + Sync + Copy + 'static, |
376 | | { |
377 | | type Id = I; |
378 | 0 | async fn complete(self) {}Unexecuted instantiation: <diskann::provider::NoopGuard<u64> as diskann::provider::Guard>::complete Unexecuted instantiation: <diskann::provider::NoopGuard<_> as diskann::provider::Guard>::complete Unexecuted instantiation: <diskann::provider::NoopGuard<u64> as diskann::provider::Guard>::complete::{closure#0}Unexecuted instantiation: <diskann::provider::NoopGuard<_> as diskann::provider::Guard>::complete::{closure#0} |
379 | 0 | fn id(&self) -> Self::Id { |
380 | 0 | self.0 |
381 | 0 | } Unexecuted instantiation: <diskann::provider::NoopGuard<u64> as diskann::provider::Guard>::id Unexecuted instantiation: <diskann::provider::NoopGuard<_> as diskann::provider::Guard>::id |
382 | | } |
383 | | |
384 | | ////////////// |
385 | | // Accessor // |
386 | | ////////////// |
387 | | |
388 | | /// A lens through which [`DataProvider`]s contextually viewed. |
389 | | /// |
390 | | /// Accessors are **not** required to be `'static` and almost always contain a scoped |
391 | | /// reference to their parent provider. |
392 | | /// |
393 | | /// # Element Relationship |
394 | | /// |
395 | | /// Accessors are expected to define two associated element types: |
396 | | /// |
397 | | /// * `Element<'_>`: The type returned by `get_element`. This is scoped to the borrow |
398 | | /// of the accessor at the `get_element` call site. As a consequence, there may only |
399 | | /// be one such `Element` active at a time. |
400 | | /// |
401 | | /// * `ElementRef<'_>`: A generalized borrowed form of `Element` obtainable via |
402 | | /// `Reborrow`. This is the type on which distance computations are defined and is the |
403 | | /// element type provided to the `on_element_unordered` bulk operation. |
404 | | /// |
405 | | /// The below diagram summarizes the relationship. |
406 | | /// |
407 | | /// ```text |
408 | | /// Element<'_> ------ Reborrow ----> ElementRef<'_> |
409 | | /// ~~~~ ~~~~ |
410 | | /// ^ ^ |
411 | | /// | | |
412 | | /// Lifetime tied Arbitrarily short |
413 | | /// to the Accessor lifetime decoupled |
414 | | /// from the Accessor |
415 | | /// ``` |
416 | | /// |
417 | | /// ## Technical Details |
418 | | /// |
419 | | /// The need for `ElementRef` arises to allow HRTB bounds to distance computers without |
420 | | /// inducing a `'static` bound on `Self`. In traits like [`BuildQueryComputer`], attempting |
421 | | /// to use `Element` directly will result in such a requirement on the implementing Accessor. |
422 | | pub trait Accessor: HasId + Send + Sync { |
423 | | /// A generalized reference type used for distance computations. |
424 | | /// |
425 | | /// Note that the lifetime of `ElementRef` is unconstrained and thus using it in a |
426 | | /// [HRTB](https://doc.rust-lang.org/nomicon/hrtb.html) will not induce a `'static` |
427 | | /// requirement on `Self`. |
428 | | type ElementRef<'a>; |
429 | | |
430 | | /// The concrete type of the data element associated with this accessor. |
431 | | /// |
432 | | /// For distance computations, this should be cheaply convertible via [`Reborrow`] to |
433 | | /// `Self::ElementRef`. |
434 | | type Element<'a>: for<'b> Reborrow<'b, Target = Self::ElementRef<'b>> + Send + Sync |
435 | | where |
436 | | Self: 'a; |
437 | | |
438 | | /// The error (if any) returned by [`Self::get_element`]. |
439 | | type GetError: ToRanked + std::fmt::Debug + Send + Sync + 'static; |
440 | | |
441 | | /// Return the value associated with the key `id`. |
442 | | /// |
443 | | /// It is expected that index algorithms will only invoke `get_element` on valid IDs, |
444 | | /// that can be derived from [`SetElement::set_element`] or by some other means. |
445 | | /// |
446 | | /// Implementations are suggested to return an error if this invariant is broken, but |
447 | | /// may also panic if that is an acceptable error mode. |
448 | | fn get_element( |
449 | | &mut self, |
450 | | id: Self::Id, |
451 | | ) -> impl std::future::Future<Output = Result<Self::Element<'_>, Self::GetError>> + Send; |
452 | | |
453 | | /// A bulk interface for invoking [`Self::get_element`] on each item in an iterator and |
454 | | /// invoking the closure with the reborrowed element. |
455 | | /// |
456 | | /// Algorithms are encouraged to use this interface if appropriate as accessor |
457 | | /// implementations may specialize the implementation for better performance. |
458 | 0 | fn on_elements_unordered<Itr, F>( |
459 | 0 | &mut self, |
460 | 0 | itr: Itr, |
461 | 0 | mut f: F, |
462 | 0 | ) -> impl std::future::Future<Output = Result<(), Self::GetError>> + Send |
463 | 0 | where |
464 | 0 | Self: Sync, |
465 | 0 | Itr: Iterator<Item = Self::Id> + Send, |
466 | 0 | F: Send + for<'a> FnMut(Self::ElementRef<'a>, Self::Id), |
467 | | { |
468 | 0 | async move { |
469 | 0 | for i in itr { |
470 | 0 | f(self.get_element(i).await?.reborrow(), i); |
471 | | } |
472 | 0 | Ok(()) |
473 | 0 | } |
474 | 0 | } |
475 | | } |
476 | | |
477 | | /// A specialized [`Accessor`] that provides random-access distance computations. |
478 | | pub trait BuildDistanceComputer: Accessor { |
479 | | /// The error type (if any) associated with distance computer construction. |
480 | | /// |
481 | | /// Implementations are encouraged to make distance computer construction infallible. |
482 | | type DistanceComputerError: std::error::Error + Into<ANNError> + Send + Sync + 'static; |
483 | | |
484 | | /// The concrete type of the distance computer, which must be applicable to all pairs |
485 | | /// of elements yielded by the [`Accessor`]. |
486 | | type DistanceComputer: for<'a, 'b> DistanceFunction<Self::ElementRef<'a>, Self::ElementRef<'b>> |
487 | | + Send |
488 | | + Sync; |
489 | | |
490 | | /// Build the random-access distance computer for this accessor. |
491 | | /// |
492 | | /// This method is expected to be relatively cheap to invoke and implementations are |
493 | | /// encouraged to make this method infallible. |
494 | | fn build_distance_computer( |
495 | | &self, |
496 | | ) -> Result<Self::DistanceComputer, Self::DistanceComputerError>; |
497 | | } |
498 | | |
499 | | /// A specialized [`Accessor`] that provides query computations for a query type `T`. |
500 | | /// |
501 | | /// Query computers are allowed to preprocess the query to enable more efficient distance |
502 | | /// computations. |
503 | | pub trait BuildQueryComputer<T>: Accessor { |
504 | | /// The error type (if any) associated with distance computer construction. |
505 | | type QueryComputerError: std::error::Error + Into<ANNError> + Send + Sync + 'static; |
506 | | |
507 | | /// The concrete type of the distance computer, which must be applicable for all |
508 | | /// elements yielded by the [`Accessor`]. |
509 | | type QueryComputer: for<'a> PreprocessedDistanceFunction<Self::ElementRef<'a>, f32> |
510 | | + Send |
511 | | + Sync; |
512 | | |
513 | | /// Build the query computer for this accessor. |
514 | | /// |
515 | | /// This method is encouraged to be as fast as possible, but will generally only be |
516 | | /// invoked once per search or graph insert. |
517 | | fn build_query_computer( |
518 | | &self, |
519 | | from: T, |
520 | | ) -> Result<Self::QueryComputer, Self::QueryComputerError>; |
521 | | |
522 | | /// Compute the distances for the elements in the iterator `itr` using the |
523 | | /// `computer` and apply the closure `f` to each distance and ID. The default |
524 | | /// implementation uses on_elements_unordered to iterate over the elements |
525 | | /// and compute the distances using `computer` parameter. |
526 | 0 | fn distances_unordered<Itr, F>( |
527 | 0 | &mut self, |
528 | 0 | vec_id_itr: Itr, |
529 | 0 | computer: &Self::QueryComputer, |
530 | 0 | mut f: F, |
531 | 0 | ) -> impl std::future::Future<Output = Result<(), Self::GetError>> + Send |
532 | 0 | where |
533 | 0 | Itr: Iterator<Item = Self::Id> + Send, |
534 | 0 | F: Send + FnMut(f32, Self::Id), |
535 | | { |
536 | 0 | self.on_elements_unordered(vec_id_itr, move |element, i| { |
537 | | // Default is to use the computer to evaluate the similarity. |
538 | 0 | let distance = computer.evaluate_similarity(element); |
539 | 0 | f(distance, i); |
540 | 0 | }) Unexecuted instantiation: <surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<half::binary16::f16> as diskann::provider::BuildQueryComputer<&[half::binary16::f16]>>::distances_unordered::<std::collections::hash::set::IntoIter<u64>, <surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<half::binary16::f16> as diskann::graph::glue::ExpandBeam<&[half::binary16::f16]>>::expand_beam<core::iter::adapters::copied::Copied<core::slice::iter::Iter<u64>>, diskann::graph::glue::NotInMut<u64>, <diskann::graph::index::DiskANNIndex<surrealdb_core::idx::trees::diskann::provider::DiskAnnProvider>>::search_internal<surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<half::binary16::f16>, &[half::binary16::f16], diskann::graph::search::record::VisitedSearchRecord<u64>, diskann::neighbor::queue::NeighborPriorityQueue<u64>>::{closure#0}::{closure#0}>::{closure#0}::{closure#2}>::{closure#0}Unexecuted instantiation: <surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<half::binary16::f16> as diskann::provider::BuildQueryComputer<&[half::binary16::f16]>>::distances_unordered::<std::collections::hash::set::IntoIter<u64>, <surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<half::binary16::f16> as diskann::graph::glue::ExpandBeam<&[half::binary16::f16]>>::expand_beam<core::iter::adapters::copied::Copied<core::slice::iter::Iter<u64>>, diskann::graph::glue::NotInMut<u64>, <diskann::graph::index::DiskANNIndex<surrealdb_core::idx::trees::diskann::provider::DiskAnnProvider>>::search_internal<surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<half::binary16::f16>, &[half::binary16::f16], diskann::graph::search::record::NoopSearchRecord, diskann::neighbor::queue::NeighborPriorityQueue<u64>>::{closure#0}::{closure#0}>::{closure#0}::{closure#2}>::{closure#0}Unexecuted instantiation: <surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<i8> as diskann::provider::BuildQueryComputer<&[i8]>>::distances_unordered::<std::collections::hash::set::IntoIter<u64>, <surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<i8> as diskann::graph::glue::ExpandBeam<&[i8]>>::expand_beam<core::iter::adapters::copied::Copied<core::slice::iter::Iter<u64>>, diskann::graph::glue::NotInMut<u64>, <diskann::graph::index::DiskANNIndex<surrealdb_core::idx::trees::diskann::provider::DiskAnnProvider>>::search_internal<surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<i8>, &[i8], diskann::graph::search::record::VisitedSearchRecord<u64>, diskann::neighbor::queue::NeighborPriorityQueue<u64>>::{closure#0}::{closure#0}>::{closure#0}::{closure#2}>::{closure#0}Unexecuted instantiation: <surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<i8> as diskann::provider::BuildQueryComputer<&[i8]>>::distances_unordered::<std::collections::hash::set::IntoIter<u64>, <surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<i8> as diskann::graph::glue::ExpandBeam<&[i8]>>::expand_beam<core::iter::adapters::copied::Copied<core::slice::iter::Iter<u64>>, diskann::graph::glue::NotInMut<u64>, <diskann::graph::index::DiskANNIndex<surrealdb_core::idx::trees::diskann::provider::DiskAnnProvider>>::search_internal<surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<i8>, &[i8], diskann::graph::search::record::NoopSearchRecord, diskann::neighbor::queue::NeighborPriorityQueue<u64>>::{closure#0}::{closure#0}>::{closure#0}::{closure#2}>::{closure#0}Unexecuted instantiation: <surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<f32> as diskann::provider::BuildQueryComputer<&[f32]>>::distances_unordered::<std::collections::hash::set::IntoIter<u64>, <surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<f32> as diskann::graph::glue::ExpandBeam<&[f32]>>::expand_beam<core::iter::adapters::copied::Copied<core::slice::iter::Iter<u64>>, diskann::graph::glue::NotInMut<u64>, <diskann::graph::index::DiskANNIndex<surrealdb_core::idx::trees::diskann::provider::DiskAnnProvider>>::search_internal<surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<f32>, &[f32], diskann::graph::search::record::VisitedSearchRecord<u64>, diskann::neighbor::queue::NeighborPriorityQueue<u64>>::{closure#0}::{closure#0}>::{closure#0}::{closure#2}>::{closure#0}Unexecuted instantiation: <surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<f32> as diskann::provider::BuildQueryComputer<&[f32]>>::distances_unordered::<std::collections::hash::set::IntoIter<u64>, <surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<f32> as diskann::graph::glue::ExpandBeam<&[f32]>>::expand_beam<core::iter::adapters::copied::Copied<core::slice::iter::Iter<u64>>, diskann::graph::glue::NotInMut<u64>, <diskann::graph::index::DiskANNIndex<surrealdb_core::idx::trees::diskann::provider::DiskAnnProvider>>::search_internal<surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<f32>, &[f32], diskann::graph::search::record::NoopSearchRecord, diskann::neighbor::queue::NeighborPriorityQueue<u64>>::{closure#0}::{closure#0}>::{closure#0}::{closure#2}>::{closure#0}Unexecuted instantiation: <surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<u8> as diskann::provider::BuildQueryComputer<&[u8]>>::distances_unordered::<std::collections::hash::set::IntoIter<u64>, <surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<u8> as diskann::graph::glue::ExpandBeam<&[u8]>>::expand_beam<core::iter::adapters::copied::Copied<core::slice::iter::Iter<u64>>, diskann::graph::glue::NotInMut<u64>, <diskann::graph::index::DiskANNIndex<surrealdb_core::idx::trees::diskann::provider::DiskAnnProvider>>::search_internal<surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<u8>, &[u8], diskann::graph::search::record::VisitedSearchRecord<u64>, diskann::neighbor::queue::NeighborPriorityQueue<u64>>::{closure#0}::{closure#0}>::{closure#0}::{closure#2}>::{closure#0}Unexecuted instantiation: <surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<u8> as diskann::provider::BuildQueryComputer<&[u8]>>::distances_unordered::<std::collections::hash::set::IntoIter<u64>, <surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<u8> as diskann::graph::glue::ExpandBeam<&[u8]>>::expand_beam<core::iter::adapters::copied::Copied<core::slice::iter::Iter<u64>>, diskann::graph::glue::NotInMut<u64>, <diskann::graph::index::DiskANNIndex<surrealdb_core::idx::trees::diskann::provider::DiskAnnProvider>>::search_internal<surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<u8>, &[u8], diskann::graph::search::record::NoopSearchRecord, diskann::neighbor::queue::NeighborPriorityQueue<u64>>::{closure#0}::{closure#0}>::{closure#0}::{closure#2}>::{closure#0}Unexecuted instantiation: <_ as diskann::provider::BuildQueryComputer<_>>::distances_unordered::<_, _>::{closure#0} |
541 | 0 | } Unexecuted instantiation: <surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<half::binary16::f16> as diskann::provider::BuildQueryComputer<&[half::binary16::f16]>>::distances_unordered::<std::collections::hash::set::IntoIter<u64>, <surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<half::binary16::f16> as diskann::graph::glue::ExpandBeam<&[half::binary16::f16]>>::expand_beam<core::iter::adapters::copied::Copied<core::slice::iter::Iter<u64>>, diskann::graph::glue::NotInMut<u64>, <diskann::graph::index::DiskANNIndex<surrealdb_core::idx::trees::diskann::provider::DiskAnnProvider>>::search_internal<surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<half::binary16::f16>, &[half::binary16::f16], diskann::graph::search::record::VisitedSearchRecord<u64>, diskann::neighbor::queue::NeighborPriorityQueue<u64>>::{closure#0}::{closure#0}>::{closure#0}::{closure#2}>Unexecuted instantiation: <surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<half::binary16::f16> as diskann::provider::BuildQueryComputer<&[half::binary16::f16]>>::distances_unordered::<std::collections::hash::set::IntoIter<u64>, <surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<half::binary16::f16> as diskann::graph::glue::ExpandBeam<&[half::binary16::f16]>>::expand_beam<core::iter::adapters::copied::Copied<core::slice::iter::Iter<u64>>, diskann::graph::glue::NotInMut<u64>, <diskann::graph::index::DiskANNIndex<surrealdb_core::idx::trees::diskann::provider::DiskAnnProvider>>::search_internal<surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<half::binary16::f16>, &[half::binary16::f16], diskann::graph::search::record::NoopSearchRecord, diskann::neighbor::queue::NeighborPriorityQueue<u64>>::{closure#0}::{closure#0}>::{closure#0}::{closure#2}>Unexecuted instantiation: <surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<i8> as diskann::provider::BuildQueryComputer<&[i8]>>::distances_unordered::<std::collections::hash::set::IntoIter<u64>, <surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<i8> as diskann::graph::glue::ExpandBeam<&[i8]>>::expand_beam<core::iter::adapters::copied::Copied<core::slice::iter::Iter<u64>>, diskann::graph::glue::NotInMut<u64>, <diskann::graph::index::DiskANNIndex<surrealdb_core::idx::trees::diskann::provider::DiskAnnProvider>>::search_internal<surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<i8>, &[i8], diskann::graph::search::record::VisitedSearchRecord<u64>, diskann::neighbor::queue::NeighborPriorityQueue<u64>>::{closure#0}::{closure#0}>::{closure#0}::{closure#2}>Unexecuted instantiation: <surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<i8> as diskann::provider::BuildQueryComputer<&[i8]>>::distances_unordered::<std::collections::hash::set::IntoIter<u64>, <surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<i8> as diskann::graph::glue::ExpandBeam<&[i8]>>::expand_beam<core::iter::adapters::copied::Copied<core::slice::iter::Iter<u64>>, diskann::graph::glue::NotInMut<u64>, <diskann::graph::index::DiskANNIndex<surrealdb_core::idx::trees::diskann::provider::DiskAnnProvider>>::search_internal<surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<i8>, &[i8], diskann::graph::search::record::NoopSearchRecord, diskann::neighbor::queue::NeighborPriorityQueue<u64>>::{closure#0}::{closure#0}>::{closure#0}::{closure#2}>Unexecuted instantiation: <surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<f32> as diskann::provider::BuildQueryComputer<&[f32]>>::distances_unordered::<std::collections::hash::set::IntoIter<u64>, <surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<f32> as diskann::graph::glue::ExpandBeam<&[f32]>>::expand_beam<core::iter::adapters::copied::Copied<core::slice::iter::Iter<u64>>, diskann::graph::glue::NotInMut<u64>, <diskann::graph::index::DiskANNIndex<surrealdb_core::idx::trees::diskann::provider::DiskAnnProvider>>::search_internal<surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<f32>, &[f32], diskann::graph::search::record::VisitedSearchRecord<u64>, diskann::neighbor::queue::NeighborPriorityQueue<u64>>::{closure#0}::{closure#0}>::{closure#0}::{closure#2}>Unexecuted instantiation: <surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<f32> as diskann::provider::BuildQueryComputer<&[f32]>>::distances_unordered::<std::collections::hash::set::IntoIter<u64>, <surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<f32> as diskann::graph::glue::ExpandBeam<&[f32]>>::expand_beam<core::iter::adapters::copied::Copied<core::slice::iter::Iter<u64>>, diskann::graph::glue::NotInMut<u64>, <diskann::graph::index::DiskANNIndex<surrealdb_core::idx::trees::diskann::provider::DiskAnnProvider>>::search_internal<surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<f32>, &[f32], diskann::graph::search::record::NoopSearchRecord, diskann::neighbor::queue::NeighborPriorityQueue<u64>>::{closure#0}::{closure#0}>::{closure#0}::{closure#2}>Unexecuted instantiation: <surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<u8> as diskann::provider::BuildQueryComputer<&[u8]>>::distances_unordered::<std::collections::hash::set::IntoIter<u64>, <surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<u8> as diskann::graph::glue::ExpandBeam<&[u8]>>::expand_beam<core::iter::adapters::copied::Copied<core::slice::iter::Iter<u64>>, diskann::graph::glue::NotInMut<u64>, <diskann::graph::index::DiskANNIndex<surrealdb_core::idx::trees::diskann::provider::DiskAnnProvider>>::search_internal<surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<u8>, &[u8], diskann::graph::search::record::VisitedSearchRecord<u64>, diskann::neighbor::queue::NeighborPriorityQueue<u64>>::{closure#0}::{closure#0}>::{closure#0}::{closure#2}>Unexecuted instantiation: <surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<u8> as diskann::provider::BuildQueryComputer<&[u8]>>::distances_unordered::<std::collections::hash::set::IntoIter<u64>, <surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<u8> as diskann::graph::glue::ExpandBeam<&[u8]>>::expand_beam<core::iter::adapters::copied::Copied<core::slice::iter::Iter<u64>>, diskann::graph::glue::NotInMut<u64>, <diskann::graph::index::DiskANNIndex<surrealdb_core::idx::trees::diskann::provider::DiskAnnProvider>>::search_internal<surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<u8>, &[u8], diskann::graph::search::record::NoopSearchRecord, diskann::neighbor::queue::NeighborPriorityQueue<u64>>::{closure#0}::{closure#0}>::{closure#0}::{closure#2}>Unexecuted instantiation: <_ as diskann::provider::BuildQueryComputer<_>>::distances_unordered::<_, _> |
542 | | } |
543 | | |
544 | | ///////////////////////// |
545 | | // Neighbor Delegation // |
546 | | ///////////////////////// |
547 | | |
548 | | /// An accessor that provides random-access neighbor retrieval from a graph. |
549 | | /// |
550 | | /// Generally, neighbor access and data access are logically decoupled, being served from |
551 | | /// different stores. However, there are situations where data and neighbors are |
552 | | /// interleaved in the underlying storage medium. |
553 | | /// |
554 | | /// As such, [`Accessors`] used in congunction with graph operations need to additionally |
555 | | /// provide an implementation of this trait. |
556 | | /// |
557 | | /// To avoid repeating implementations for every [`Accessor`] flavor, the trait |
558 | | /// [`DelegateNeighbor`] should be used instead to route the implementation of |
559 | | /// [`NeighborAccessor`] to a single type if applicable. |
560 | | /// |
561 | | /// # Note |
562 | | /// |
563 | | /// The `NeighborAccessor` method receive by value. Implementations are strongly encouraged |
564 | | /// to be cheap to construct, copy, or clone. This can generally be achieved by implementing |
565 | | /// `NeighborAccessor` for `&T`, `&mut T`, or a thing wrapper around such references. |
566 | | pub trait NeighborAccessor: HasId + Sized + Send + Sync { |
567 | | /// Get the neighbors for the node associated with `id`. |
568 | | /// |
569 | | /// Populate the neighbors into the `neighbors` out parameter. |
570 | | /// |
571 | | /// Implementations are expected to clear `neighbors` prior to populating. |
572 | | fn get_neighbors( |
573 | | self, |
574 | | id: Self::Id, |
575 | | neighbors: &mut AdjacencyList<Self::Id>, |
576 | | ) -> impl std::future::Future<Output = ANNResult<Self>> + Send; |
577 | | } |
578 | | |
579 | | /// A mutable extension of [`NeighborAccessor`] that enables the underlying graph to be |
580 | | /// mutated. |
581 | | /// |
582 | | /// Generally, [`Accessors`] should implement [`DelegateNeighbor`] instead of extending this |
583 | | /// trait if graph and data access are naturally decoupled. |
584 | | pub trait NeighborAccessorMut: NeighborAccessor { |
585 | | /// Overwrite the neighbor list for the node associated with `id`. |
586 | | fn set_neighbors( |
587 | | self, |
588 | | id: Self::Id, |
589 | | neighbors: &[Self::Id], |
590 | | ) -> impl std::future::Future<Output = ANNResult<Self>> + Send; |
591 | | |
592 | | /// Append all entries in `neighbors` tothe neighbor list currently associated with `id`. |
593 | | /// |
594 | | /// The behavior when the resulting list exceeds some pre-configured capacity or |
595 | | /// contains duplicates is implementation defined. |
596 | | fn append_vector( |
597 | | self, |
598 | | id: Self::Id, |
599 | | neighbors: &[Self::Id], |
600 | | ) -> impl std::future::Future<Output = ANNResult<Self>> + Send; |
601 | | |
602 | | /// A potentially optimized bulk implementation of [`Self::set_neighbors`]. |
603 | | /// |
604 | | /// For each `id`/`neighbors` pair in `iter`, set the adjacency list for the node associated |
605 | | /// with `id` to `neighbors`. |
606 | | /// |
607 | | /// Implementations are allowed to commit entries out of order. |
608 | 0 | fn set_neighbors_bulk<I, T>( |
609 | 0 | mut self, |
610 | 0 | iter: I, |
611 | 0 | ) -> impl std::future::Future<Output = ANNResult<Self>> + Send |
612 | 0 | where |
613 | 0 | I: Iterator<Item = (Self::Id, T)> + Send, |
614 | 0 | T: Deref<Target = [Self::Id]> + Send, |
615 | | { |
616 | 0 | async move { |
617 | 0 | for (vector_id, neighbors) in iter { |
618 | 0 | self = self.set_neighbors(vector_id, neighbors.deref()).await?; |
619 | | } |
620 | 0 | Ok(self) |
621 | 0 | } |
622 | 0 | } |
623 | | } |
624 | | |
625 | | /// This implementation allows `&mut T` to be used as a [`NeighborAccessor`] without |
626 | | /// requiring manual invocation of [`DelegateNeighbor`]. |
627 | | impl<T> NeighborAccessor for &mut T |
628 | | where |
629 | | T: AsNeighbor, |
630 | | { |
631 | 0 | async fn get_neighbors( |
632 | 0 | self, |
633 | 0 | id: Self::Id, |
634 | 0 | neighbors: &mut AdjacencyList<Self::Id>, |
635 | 0 | ) -> ANNResult<Self> {Unexecuted instantiation: <&mut surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<half::binary16::f16> as diskann::provider::NeighborAccessor>::get_neighbors Unexecuted instantiation: <&mut surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<i8> as diskann::provider::NeighborAccessor>::get_neighbors Unexecuted instantiation: <&mut surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<f32> as diskann::provider::NeighborAccessor>::get_neighbors Unexecuted instantiation: <&mut surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<u8> as diskann::provider::NeighborAccessor>::get_neighbors Unexecuted instantiation: <&mut _ as diskann::provider::NeighborAccessor>::get_neighbors |
636 | 0 | self.delegate_neighbor() |
637 | 0 | .get_neighbors(id, neighbors) |
638 | 0 | .await?; |
639 | 0 | Ok(self) |
640 | 0 | } Unexecuted instantiation: <&mut surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<half::binary16::f16> as diskann::provider::NeighborAccessor>::get_neighbors::{closure#0}Unexecuted instantiation: <&mut surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<i8> as diskann::provider::NeighborAccessor>::get_neighbors::{closure#0}Unexecuted instantiation: <&mut surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<f32> as diskann::provider::NeighborAccessor>::get_neighbors::{closure#0}Unexecuted instantiation: <&mut surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<u8> as diskann::provider::NeighborAccessor>::get_neighbors::{closure#0}Unexecuted instantiation: <&mut _ as diskann::provider::NeighborAccessor>::get_neighbors::{closure#0} |
641 | | } |
642 | | |
643 | | /// This implementation allows `&mut T` to be used as a [`NeighborAccessorMut`] without |
644 | | /// requiring manual invocation of [`DelegateNeighbor`]. |
645 | | impl<T> NeighborAccessorMut for &mut T |
646 | | where |
647 | | T: AsNeighborMut, |
648 | | { |
649 | 0 | async fn set_neighbors(self, id: Self::Id, neighbors: &[Self::Id]) -> ANNResult<Self> {Unexecuted instantiation: <&mut surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<half::binary16::f16> as diskann::provider::NeighborAccessorMut>::set_neighbors Unexecuted instantiation: <&mut surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<i8> as diskann::provider::NeighborAccessorMut>::set_neighbors Unexecuted instantiation: <&mut surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<f32> as diskann::provider::NeighborAccessorMut>::set_neighbors Unexecuted instantiation: <&mut surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<u8> as diskann::provider::NeighborAccessorMut>::set_neighbors Unexecuted instantiation: <&mut _ as diskann::provider::NeighborAccessorMut>::set_neighbors |
650 | 0 | self.delegate_neighbor() |
651 | 0 | .set_neighbors(id, neighbors) |
652 | 0 | .await?; |
653 | 0 | Ok(self) |
654 | 0 | } Unexecuted instantiation: <&mut surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<half::binary16::f16> as diskann::provider::NeighborAccessorMut>::set_neighbors::{closure#0}Unexecuted instantiation: <&mut surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<i8> as diskann::provider::NeighborAccessorMut>::set_neighbors::{closure#0}Unexecuted instantiation: <&mut surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<f32> as diskann::provider::NeighborAccessorMut>::set_neighbors::{closure#0}Unexecuted instantiation: <&mut surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<u8> as diskann::provider::NeighborAccessorMut>::set_neighbors::{closure#0}Unexecuted instantiation: <&mut _ as diskann::provider::NeighborAccessorMut>::set_neighbors::{closure#0} |
655 | 0 | async fn append_vector(self, id: Self::Id, neighbors: &[Self::Id]) -> ANNResult<Self> {Unexecuted instantiation: <&mut surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<half::binary16::f16> as diskann::provider::NeighborAccessorMut>::append_vector Unexecuted instantiation: <&mut surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<i8> as diskann::provider::NeighborAccessorMut>::append_vector Unexecuted instantiation: <&mut surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<f32> as diskann::provider::NeighborAccessorMut>::append_vector Unexecuted instantiation: <&mut surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<u8> as diskann::provider::NeighborAccessorMut>::append_vector Unexecuted instantiation: <&mut _ as diskann::provider::NeighborAccessorMut>::append_vector |
656 | 0 | self.delegate_neighbor() |
657 | 0 | .append_vector(id, neighbors) |
658 | 0 | .await?; |
659 | 0 | Ok(self) |
660 | 0 | } Unexecuted instantiation: <&mut surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<half::binary16::f16> as diskann::provider::NeighborAccessorMut>::append_vector::{closure#0}Unexecuted instantiation: <&mut surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<i8> as diskann::provider::NeighborAccessorMut>::append_vector::{closure#0}Unexecuted instantiation: <&mut surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<f32> as diskann::provider::NeighborAccessorMut>::append_vector::{closure#0}Unexecuted instantiation: <&mut surrealdb_core::idx::trees::diskann::provider::DiskAnnAccessor<u8> as diskann::provider::NeighborAccessorMut>::append_vector::{closure#0}Unexecuted instantiation: <&mut _ as diskann::provider::NeighborAccessorMut>::append_vector::{closure#0} |
661 | 0 | async fn set_neighbors_bulk<I, U>(self, iter: I) -> ANNResult<Self> |
662 | 0 | where |
663 | 0 | I: Iterator<Item = (Self::Id, U)> + Send, |
664 | 0 | U: Deref<Target = [Self::Id]> + Send, |
665 | 0 | { |
666 | 0 | self.delegate_neighbor().set_neighbors_bulk(iter).await?; |
667 | 0 | Ok(self) |
668 | 0 | } |
669 | | } |
670 | | |
671 | | /// Accessor may delegate the responsibility of being a [`NeighborAccessor`] to an auxiliary |
672 | | /// type by implementing this trait. |
673 | | /// |
674 | | /// If the implementation of a [`NeighborAccessor`]/[`NeighborAccessorMut`] are coupled with |
675 | | /// the [`Accessor`] itself (meaning that no delegation can take place), then implementations |
676 | | /// may do the following. Assume the accessor has type `T`. Then: |
677 | | /// |
678 | | /// 1. Implement [`NeighborAccessor`]/[`NeighborAccessorMut`] for a thin wrapper around |
679 | | /// `&[mut] T`. |
680 | | /// |
681 | | /// 2. Implement [`DelegateNeighbor`] to return this wrapper as its delegate. |
682 | | /// |
683 | | /// Implementation code can use this trait through the [`AsNeighbor`] and [`AsNeighborMut`] |
684 | | /// convenience traits to ensure proper application of the HRTB requirements. |
685 | | /// |
686 | | /// Additionally, the `&mut T` blanket implementation of [`NeighborAccessor`] and |
687 | | /// [`NeighborAccessorMut`] can be used to avoid manually invoking `delegate_neighbor`. |
688 | | pub trait DelegateNeighbor<'this, Lifetime: Sealed = BoundTo<&'this Self>>: |
689 | | HasId + Send + Sync |
690 | | { |
691 | | /// The type of the delegated [`NeighborAccessor`]. |
692 | | type Delegate: NeighborAccessor<Id = Self::Id>; |
693 | | |
694 | | /// Construct the delegate. |
695 | | fn delegate_neighbor(&'this mut self) -> Self::Delegate; |
696 | | } |
697 | | |
698 | | impl<'this, T> DelegateNeighbor<'this> for T |
699 | | where |
700 | | T: Copy + NeighborAccessor, |
701 | | { |
702 | | type Delegate = Self; |
703 | 0 | fn delegate_neighbor(&'this mut self) -> Self::Delegate { |
704 | 0 | *self |
705 | 0 | } |
706 | | } |
707 | | |
708 | | /// A convenience HRTB wrapper for [`DelegateNeighbor`]. Accessors should implement |
709 | | /// [`DelegateNeighbor`]. |
710 | | /// |
711 | | /// # Note |
712 | | /// |
713 | | /// This trait should never be implemented manually. Instead, this trait is automatically |
714 | | /// implemented for a type `T` when it implements [`DelegateNeighbor`]. |
715 | | pub trait AsNeighbor: for<'a> DelegateNeighbor<'a> {} |
716 | | |
717 | | /// A convenience HRTB wrapper for [`DelegateNeighbor`]. Accessors should implement |
718 | | /// [`DelegateNeighbor`]. |
719 | | /// |
720 | | /// # Note |
721 | | /// |
722 | | /// This trait should never be implemented manually. Instead, this trait is automatically |
723 | | /// implemented for a type `T` when it implements [`DelegateNeighbor`]. |
724 | | pub trait AsNeighborMut: for<'a> DelegateNeighbor<'a, Delegate: NeighborAccessorMut> {} |
725 | | |
726 | | impl<T> AsNeighbor for T where T: for<'a> DelegateNeighbor<'a> {} |
727 | | impl<T> AsNeighborMut for T where T: for<'a> DelegateNeighbor<'a, Delegate: NeighborAccessorMut> {} |
728 | | |
729 | | /// Get a default accessor from a provider. |
730 | | /// |
731 | | /// Some providers are able to produce accessors directly from the provides, and will implement |
732 | | /// this to make getting an accessor convenient. |
733 | | pub trait DefaultAccessor: DataProvider { |
734 | | type Accessor<'a>: HasId<Id = Self::InternalId> |
735 | | where |
736 | | Self: 'a; |
737 | | fn default_accessor(&self) -> Self::Accessor<'_>; |
738 | | } |
739 | | |
740 | | //////////////////// |
741 | | // DefaultContext // |
742 | | //////////////////// |
743 | | |
744 | | /// A light-weight struct implementing [`ExecutionContext`]. |
745 | | /// |
746 | | /// Used for situations where a more refined execution context is not needed. |
747 | | #[derive(Default, Clone)] |
748 | | pub struct DefaultContext; |
749 | | |
750 | | impl std::fmt::Display for DefaultContext { |
751 | 0 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { |
752 | 0 | write!(f, "default context") |
753 | 0 | } |
754 | | } |
755 | | |
756 | | impl ExecutionContext for DefaultContext {} |
757 | | |
758 | | ////////// |
759 | | // Misc // |
760 | | ////////// |
761 | | |
762 | | // Constraint for HRBT-style associated types. |
763 | | mod sealed { |
764 | | pub trait Sealed: Sized {} |
765 | | pub struct BoundTo<T>(T); |
766 | | impl<T> Sealed for BoundTo<T> {} |
767 | | } |
768 | | |
769 | | /////////// |
770 | | // Tests // |
771 | | /////////// |
772 | | |
773 | | #[cfg(test)] |
774 | | mod tests { |
775 | | use std::{ |
776 | | collections::HashMap, |
777 | | future::Future, |
778 | | pin::Pin, |
779 | | sync::{ |
780 | | Arc, Mutex, |
781 | | atomic::{AtomicUsize, Ordering}, |
782 | | }, |
783 | | task, |
784 | | }; |
785 | | |
786 | | use pin_project::{pin_project, pinned_drop}; |
787 | | |
788 | | use super::*; |
789 | | use crate::{always_escalate, error::Infallible}; |
790 | | |
791 | | //////////////////// |
792 | | // DefaultContext // |
793 | | //////////////////// |
794 | | |
795 | | #[test] |
796 | | fn test_default_context() { |
797 | | let ctx = DefaultContext; |
798 | | |
799 | | // Check that the implementation of `Display` is correct. |
800 | | assert_eq!(ctx.to_string(), "default context"); |
801 | | |
802 | | assert_eq!( |
803 | | std::mem::size_of::<DefaultContext>(), |
804 | | 0, |
805 | | "expected DefaultContext to be an empty class" |
806 | | ); |
807 | | } |
808 | | |
809 | | ////////////////// |
810 | | // Test Context // |
811 | | ////////////////// |
812 | | |
813 | | #[derive(Debug)] |
814 | | struct TestContextInner { |
815 | | /// The number of tasks spawned. |
816 | | spawned: AtomicUsize, |
817 | | /// THe number of tasks dropped. |
818 | | dropped: AtomicUsize, |
819 | | } |
820 | | |
821 | | /// The goal of this test is to exercise the functionality of `wrap_spawn`. |
822 | | /// We want to make sure that we can correctly hook into tasks spawning. |
823 | | #[derive(Debug, Clone)] |
824 | | struct TestContext { |
825 | | inner: Arc<TestContextInner>, |
826 | | } |
827 | | |
828 | | impl Default for TestContext { |
829 | | fn default() -> Self { |
830 | | Self { |
831 | | inner: Arc::new(TestContextInner { |
832 | | spawned: AtomicUsize::new(0), |
833 | | dropped: AtomicUsize::new(0), |
834 | | }), |
835 | | } |
836 | | } |
837 | | } |
838 | | |
839 | | impl std::fmt::Display for TestContext { |
840 | | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { |
841 | | write!(f, "test context") |
842 | | } |
843 | | } |
844 | | |
845 | | /// A Future wrapper that when dropped, increments the `dropped` field of its parent |
846 | | /// `TestContext`. |
847 | | #[pin_project(PinnedDrop)] |
848 | | pub struct SpawnCounter<F> { |
849 | | #[pin] |
850 | | inner: F, |
851 | | parent: TestContext, |
852 | | } |
853 | | |
854 | | #[pinned_drop] |
855 | | impl<F> PinnedDrop for SpawnCounter<F> { |
856 | | fn drop(self: Pin<&mut Self>) { |
857 | | self.parent.inner.dropped.fetch_add(1, Ordering::AcqRel); |
858 | | } |
859 | | } |
860 | | |
861 | | impl<F> Future for SpawnCounter<F> |
862 | | where |
863 | | F: Future, |
864 | | { |
865 | | type Output = F::Output; |
866 | | fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> task::Poll<Self::Output> { |
867 | | self.project().inner.poll(cx) |
868 | | } |
869 | | } |
870 | | |
871 | | impl ExecutionContext for TestContext { |
872 | | /// Override task spawning to record the number of tasks spawned and the number |
873 | | /// of tasks dropped. |
874 | | fn wrap_spawn<F, T>(&self, f: F) -> impl Future<Output = T> + Send + 'static |
875 | | where |
876 | | F: Future<Output = T> + Send + 'static, |
877 | | { |
878 | | // Increment spawn count. |
879 | | self.inner.spawned.fetch_add(1, Ordering::AcqRel); |
880 | | |
881 | | // Create a future that will increment drop count when dropped. |
882 | | SpawnCounter { |
883 | | inner: f, |
884 | | parent: self.clone(), |
885 | | } |
886 | | } |
887 | | } |
888 | | |
889 | | /////////////////// |
890 | | // Spawning Test // |
891 | | /////////////////// |
892 | | |
893 | | /// This is a recursive function. At each level, it spawns `width` new instances of |
894 | | /// itself with `depth` decreased by 1. |
895 | | /// |
896 | | /// Each spawned instance uses `context.wrap_spawn`. |
897 | | /// |
898 | | /// This needs to be manually `async` so we can aply the `'static` bound. Since it's |
899 | | /// recursive, Rust struggles to properly deduce the hidden type for the opaque return |
900 | | /// type. |
901 | | #[allow(clippy::manual_async_fn)] |
902 | | fn test_spawning<Context>( |
903 | | context: Context, |
904 | | width: usize, |
905 | | depth: usize, |
906 | | ) -> impl Future<Output = ()> + Send + 'static |
907 | | where |
908 | | Context: ExecutionContext + 'static + std::fmt::Debug, |
909 | | { |
910 | | async move { |
911 | | if depth == 0 { |
912 | | return; |
913 | | } |
914 | | |
915 | | let handles: Box<[_]> = (0..width) |
916 | | .map(|_| { |
917 | | let clone = context.clone(); |
918 | | tokio::spawn(context.wrap_spawn(test_spawning(clone, width, depth - 1))) |
919 | | }) |
920 | | .collect(); |
921 | | |
922 | | for h in handles { |
923 | | h.await.unwrap(); |
924 | | } |
925 | | } |
926 | | } |
927 | | |
928 | | #[tokio::test(flavor = "multi_thread", worker_threads = 4)] |
929 | | async fn test_task_spawning() { |
930 | | let context = TestContext::default(); |
931 | | assert_eq!(context.inner.spawned.load(Ordering::Acquire), 0); |
932 | | assert_eq!(context.inner.dropped.load(Ordering::Acquire), 0); |
933 | | |
934 | | // How to we compute the number of spawned tasks: |
935 | | // 1. The first invocation of `test_spawning` spawned `width` tasks. |
936 | | // 2. Each tasks spawns `width` tasks, meaning the second level creates `width ^ 2` |
937 | | // tasks. |
938 | | // 3. In general, depth `d` spawnS `width ^ d` tasks. |
939 | | // |
940 | | // The total number of tasks is then: |
941 | | // ``` |
942 | | // S = width + width^2 + width^3 ... width^depth |
943 | | // ``` |
944 | | // This forms a finite geometric series with a closed for solution of |
945 | | // ``` |
946 | | // S = (width ^ (depth + 1) - 1) / (width - 1) - 1 |
947 | | // ``` |
948 | | let width = 10; |
949 | | let depth = 3; |
950 | | test_spawning(context.clone(), width, depth).await; |
951 | | |
952 | | let expected = (width.pow((depth + 1).try_into().unwrap()) - 1) / (width - 1) - 1; |
953 | | assert_eq!(context.inner.spawned.load(Ordering::Acquire), expected); |
954 | | assert_eq!(context.inner.dropped.load(Ordering::Acquire), expected); |
955 | | } |
956 | | |
957 | | /////////////////// |
958 | | // Data Provider // |
959 | | /////////////////// |
960 | | |
961 | | #[tokio::test] |
962 | | async fn test_noop_guard() { |
963 | | // A guard that completes successfully. |
964 | | { |
965 | | let guard = NoopGuard::<usize>::new(10); |
966 | | assert_eq!(guard.id(), 10); |
967 | | guard.complete().await; |
968 | | } |
969 | | |
970 | | // A guard that completes unsuccessfully. |
971 | | // The Noop guard specifically does not complain if `complete` is not invoked. |
972 | | { |
973 | | let guard = NoopGuard::<usize>::new(5); |
974 | | assert_eq!(guard.id(), 5); |
975 | | // Destructor runs here. |
976 | | } |
977 | | } |
978 | | |
979 | | #[test] |
980 | | fn simple_status_test() { |
981 | | let valid = ElementStatus::Valid; |
982 | | assert!(valid.is_valid()); |
983 | | assert!(!valid.is_deleted()); |
984 | | |
985 | | let deleted = ElementStatus::Deleted; |
986 | | assert!(!deleted.is_valid()); |
987 | | assert!(deleted.is_deleted()); |
988 | | } |
989 | | |
990 | | /// A simple data provider that contains values consisting of floats and strings. |
991 | | /// |
992 | | /// The start point for this provider is as `u32::MAX`. |
993 | | struct SimpleProvider { |
994 | | data: Mutex<HashMap<u32, (f32, String)>>, |
995 | | } |
996 | | |
997 | | impl SimpleProvider { |
998 | | fn new(v: f32, st: String) -> Self { |
999 | | let mut data = HashMap::new(); |
1000 | | data.insert(u32::MAX, (v, st)); |
1001 | | Self { |
1002 | | data: Mutex::new(data), |
1003 | | } |
1004 | | } |
1005 | | } |
1006 | | |
1007 | | impl DataProvider for SimpleProvider { |
1008 | | type Context = DefaultContext; |
1009 | | // Use the identity mapping for IDs. |
1010 | | type InternalId = u32; |
1011 | | type ExternalId = u32; |
1012 | | type Error = ANNError; |
1013 | | type Guard = NoopGuard<u32>; |
1014 | | |
1015 | | fn to_internal_id(&self, _context: &DefaultContext, gid: &u32) -> Result<u32, ANNError> { |
1016 | | Ok(*gid) |
1017 | | } |
1018 | | |
1019 | | fn to_external_id(&self, _context: &DefaultContext, id: u32) -> Result<u32, ANNError> { |
1020 | | Ok(id) |
1021 | | } |
1022 | | } |
1023 | | |
1024 | | #[derive(Debug, Clone, Copy, PartialEq)] |
1025 | | pub struct Missing; |
1026 | | |
1027 | | impl std::fmt::Display for Missing { |
1028 | | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
1029 | | write!(f, "key is missing") |
1030 | | } |
1031 | | } |
1032 | | |
1033 | | impl std::error::Error for Missing {} |
1034 | | impl From<Missing> for ANNError { |
1035 | | #[cold] |
1036 | | fn from(missing: Missing) -> ANNError { |
1037 | | ANNError::log_async_error(missing) |
1038 | | } |
1039 | | } |
1040 | | |
1041 | | always_escalate!(Missing); |
1042 | | |
1043 | | // An accessor for the `f32` portion of the data stored in the SimpleProvider. |
1044 | | struct FloatAccessor<'a>(&'a SimpleProvider); |
1045 | | impl HasId for FloatAccessor<'_> { |
1046 | | type Id = u32; |
1047 | | } |
1048 | | impl Accessor for FloatAccessor<'_> { |
1049 | | type Element<'a> |
1050 | | = f32 |
1051 | | where |
1052 | | Self: 'a; |
1053 | | type ElementRef<'a> = f32; |
1054 | | |
1055 | | type GetError = Missing; |
1056 | | |
1057 | | fn get_element( |
1058 | | &mut self, |
1059 | | id: u32, |
1060 | | ) -> impl Future<Output = Result<Self::Element<'_>, Self::GetError>> + Send { |
1061 | | let guard = self.0.data.lock().unwrap(); |
1062 | | let v = match guard.get(&id) { |
1063 | | None => Err(Missing), |
1064 | | Some(v) => Ok(v.0), |
1065 | | }; |
1066 | | std::future::ready(v) |
1067 | | } |
1068 | | |
1069 | | // Implement `on_elements_unordered` by only acquiring the lock once. |
1070 | | // |
1071 | | // Real implementations will need to take care to avoid deadlocks. |
1072 | | async fn on_elements_unordered<Itr, F>( |
1073 | | &mut self, |
1074 | | itr: Itr, |
1075 | | mut f: F, |
1076 | | ) -> Result<(), Self::GetError> |
1077 | | where |
1078 | | Self: Sync, |
1079 | | Itr: Iterator<Item = u32>, |
1080 | | F: Send + FnMut(f32, u32), |
1081 | | { |
1082 | | let guard = self.0.data.lock().unwrap(); |
1083 | | for i in itr { |
1084 | | match guard.get(&i) { |
1085 | | None => return Err(Missing), |
1086 | | Some(v) => f(v.0, i), |
1087 | | } |
1088 | | } |
1089 | | Ok(()) |
1090 | | } |
1091 | | } |
1092 | | |
1093 | | // An accessor for the `String` portion of the data stored in the SimpleProvider. |
1094 | | // |
1095 | | // We keep a local buffer `buf` into which the contents of the string are copied. |
1096 | | // This allows us to elide allocating on `get_element` calls. |
1097 | | struct StringAccessor<'a> { |
1098 | | provider: &'a SimpleProvider, |
1099 | | buf: String, |
1100 | | } |
1101 | | |
1102 | | impl<'a> StringAccessor<'a> { |
1103 | | fn new(provider: &'a SimpleProvider) -> Self { |
1104 | | Self { |
1105 | | provider, |
1106 | | buf: String::new(), |
1107 | | } |
1108 | | } |
1109 | | } |
1110 | | |
1111 | | impl HasId for StringAccessor<'_> { |
1112 | | type Id = u32; |
1113 | | } |
1114 | | impl Accessor for StringAccessor<'_> { |
1115 | | type Element<'a> |
1116 | | = &'a str |
1117 | | where |
1118 | | Self: 'a; |
1119 | | type ElementRef<'a> = &'a str; |
1120 | | |
1121 | | type GetError = Missing; |
1122 | | |
1123 | | fn get_element( |
1124 | | &mut self, |
1125 | | id: u32, |
1126 | | ) -> impl Future<Output = Result<Self::Element<'_>, Self::GetError>> + Send { |
1127 | | let guard = self.provider.data.lock().unwrap(); |
1128 | | let v = match guard.get(&id) { |
1129 | | None => Err(Missing), |
1130 | | Some(v) => { |
1131 | | self.buf.clone_from(&v.1); |
1132 | | Ok(&*self.buf) |
1133 | | } |
1134 | | }; |
1135 | | std::future::ready(v) |
1136 | | } |
1137 | | } |
1138 | | |
1139 | | #[tokio::test] |
1140 | | async fn test_default_implementations() { |
1141 | | let provider = SimpleProvider::new(-1.0, "hello".to_string()); |
1142 | | { |
1143 | | let mut data = provider.data.lock().unwrap(); |
1144 | | data.insert(0, (0.0, "world".to_string())); |
1145 | | data.insert(1, (1.0, "foo".to_string())); |
1146 | | data.insert(2, (2.0, "bar".to_string())); |
1147 | | } |
1148 | | |
1149 | | // Float accessor |
1150 | | { |
1151 | | let mut accessor = FloatAccessor(&provider); |
1152 | | assert_eq!(accessor.get_element(0).await.unwrap(), 0.0); |
1153 | | assert_eq!(accessor.get_element(1).await.unwrap(), 1.0); |
1154 | | assert_eq!(accessor.get_element(u32::MAX).await.unwrap(), -1.0); |
1155 | | |
1156 | | let mut v = Vec::new(); |
1157 | | accessor |
1158 | | .on_elements_unordered([2, 1, 0].into_iter(), |element, id| v.push((element, id))) |
1159 | | .await |
1160 | | .unwrap(); |
1161 | | |
1162 | | assert_eq!(&v, &[(2.0, 2), (1.0, 1), (0.0, 0)]); |
1163 | | |
1164 | | // Test error propagation. |
1165 | | // Trying to access element 3 will result in an error, which should be propagated |
1166 | | // up. |
1167 | | let err = accessor |
1168 | | .on_elements_unordered([2, 1, 0, 3].into_iter(), |element, id| { |
1169 | | v.push((element, id)) |
1170 | | }) |
1171 | | .await |
1172 | | .unwrap_err(); |
1173 | | assert_eq!(err, Missing); |
1174 | | } |
1175 | | |
1176 | | // String accessor |
1177 | | { |
1178 | | let mut accessor = StringAccessor::new(&provider); |
1179 | | assert_eq!(accessor.get_element(0).await.unwrap(), "world"); |
1180 | | assert_eq!(accessor.get_element(1).await.unwrap(), "foo"); |
1181 | | assert_eq!(accessor.get_element(u32::MAX).await.unwrap(), "hello"); |
1182 | | |
1183 | | // This method tests the provided implementation of `on_elements_unordered`. |
1184 | | let expected = [("bar", 2), ("foo", 1), ("world", 0)]; |
1185 | | |
1186 | | let mut expected_iter = expected.into_iter(); |
1187 | | accessor |
1188 | | .on_elements_unordered([2, 1, 0].into_iter(), |element, id| { |
1189 | | assert_eq!((element, id), expected_iter.next().unwrap()); |
1190 | | }) |
1191 | | .await |
1192 | | .unwrap(); |
1193 | | assert!(expected_iter.next().is_none()); |
1194 | | |
1195 | | // Test error propagation. |
1196 | | // Trying to access element 3 will result in an error, which should be propagated |
1197 | | // up. |
1198 | | let mut expected_iter = expected.into_iter(); |
1199 | | let err = accessor |
1200 | | .on_elements_unordered([2, 1, 0, 3].into_iter(), |element, id| { |
1201 | | assert_eq!((element, id), expected_iter.next().unwrap()); |
1202 | | }) |
1203 | | .await |
1204 | | .unwrap_err(); |
1205 | | assert_eq!(err, Missing); |
1206 | | assert!(expected_iter.next().is_none()); |
1207 | | } |
1208 | | } |
1209 | | |
1210 | | ///////////////////////////////// |
1211 | | // Supported Accessor Patterns // |
1212 | | ///////////////////////////////// |
1213 | | |
1214 | | // This suite of tests ensure that patterns we want out of the `Accessor` associated |
1215 | | // trait hierarchy are all supported. |
1216 | | // |
1217 | | // These include: |
1218 | | // |
1219 | | // * Accessors that always allocate. |
1220 | | // * Accessors that simply reference the underlying store directly. |
1221 | | // * Accessors that use a local buffer. |
1222 | | |
1223 | | #[derive(Debug)] |
1224 | | struct Store { |
1225 | | data: Box<[u8]>, |
1226 | | } |
1227 | | |
1228 | | impl Store { |
1229 | | fn new() -> Self { |
1230 | | Self { |
1231 | | data: Box::from([1, 2, 3, 4]), |
1232 | | } |
1233 | | } |
1234 | | |
1235 | | fn dim(&self) -> usize { |
1236 | | self.data.len() |
1237 | | } |
1238 | | } |
1239 | | |
1240 | | macro_rules! common_test_accessor { |
1241 | | ($T:ty) => { |
1242 | | impl HasId for $T { |
1243 | | type Id = u32; |
1244 | | } |
1245 | | |
1246 | | impl BuildDistanceComputer for $T { |
1247 | | type DistanceComputerError = Infallible; |
1248 | | type DistanceComputer = <u8 as crate::utils::VectorRepr>::Distance; |
1249 | | |
1250 | | fn build_distance_computer(&self) -> Result<Self::DistanceComputer, Infallible> { |
1251 | | Ok(<u8 as crate::utils::VectorRepr>::distance( |
1252 | | diskann_vector::distance::Metric::L2, |
1253 | | None, |
1254 | | )) |
1255 | | } |
1256 | | } |
1257 | | }; |
1258 | | } |
1259 | | |
1260 | | // An accessor that always allocates. |
1261 | | struct Allocating<'a> { |
1262 | | store: &'a Store, |
1263 | | } |
1264 | | |
1265 | | impl<'a> Allocating<'a> { |
1266 | | fn new(store: &'a Store) -> Self { |
1267 | | Self { store } |
1268 | | } |
1269 | | } |
1270 | | |
1271 | | common_test_accessor!(Allocating<'_>); |
1272 | | |
1273 | | impl Accessor for Allocating<'_> { |
1274 | | type Element<'a> |
1275 | | = Box<[u8]> |
1276 | | where |
1277 | | Self: 'a; |
1278 | | type ElementRef<'a> = &'a [u8]; |
1279 | | type GetError = Infallible; |
1280 | | |
1281 | | async fn get_element(&mut self, _: u32) -> Result<Box<[u8]>, Infallible> { |
1282 | | Ok(self.store.data.clone()) |
1283 | | } |
1284 | | } |
1285 | | |
1286 | | // An accessor that forwards - returning references directly into the underlying |
1287 | | // store without reallocation or copying. |
1288 | | struct Forwarding<'a> { |
1289 | | store: &'a Store, |
1290 | | } |
1291 | | |
1292 | | impl<'a> Forwarding<'a> { |
1293 | | fn new(store: &'a Store) -> Self { |
1294 | | Self { store } |
1295 | | } |
1296 | | } |
1297 | | |
1298 | | common_test_accessor!(Forwarding<'_>); |
1299 | | |
1300 | | impl<'provider> Accessor for Forwarding<'provider> { |
1301 | | // NOTE: The lifetime of `Element` is `'provider` - not `'a`. This is what makes |
1302 | | // it a forwarding accessor. |
1303 | | type Element<'a> |
1304 | | = &'provider [u8] |
1305 | | where |
1306 | | Self: 'a; |
1307 | | type ElementRef<'a> = &'a [u8]; |
1308 | | type GetError = Infallible; |
1309 | | |
1310 | | async fn get_element(&mut self, _: u32) -> Result<&'provider [u8], Infallible> { |
1311 | | Ok(&*self.store.data) |
1312 | | } |
1313 | | } |
1314 | | |
1315 | | // An accessor that returns a non-reference type with a lifetime. |
1316 | | struct Wrapping<'a> { |
1317 | | store: &'a Store, |
1318 | | } |
1319 | | |
1320 | | impl<'a> Wrapping<'a> { |
1321 | | fn new(store: &'a Store) -> Self { |
1322 | | Self { store } |
1323 | | } |
1324 | | } |
1325 | | |
1326 | | #[derive(Debug)] |
1327 | | struct Wrapped<'a>(&'a [u8]); |
1328 | | |
1329 | | impl<'a> Reborrow<'a> for Wrapped<'_> { |
1330 | | type Target = &'a [u8]; |
1331 | | fn reborrow(&'a self) -> Self::Target { |
1332 | | self.0 |
1333 | | } |
1334 | | } |
1335 | | |
1336 | | impl From<Wrapped<'_>> for Box<[u8]> { |
1337 | | fn from(wrapped: Wrapped<'_>) -> Self { |
1338 | | wrapped.0.into() |
1339 | | } |
1340 | | } |
1341 | | |
1342 | | common_test_accessor!(Wrapping<'_>); |
1343 | | |
1344 | | impl Accessor for Wrapping<'_> { |
1345 | | type Element<'a> |
1346 | | = Wrapped<'a> |
1347 | | where |
1348 | | Self: 'a; |
1349 | | type ElementRef<'a> = &'a [u8]; |
1350 | | type GetError = Infallible; |
1351 | | |
1352 | | async fn get_element(&mut self, _: u32) -> Result<Wrapped<'_>, Infallible> { |
1353 | | Ok(Wrapped(&self.store.data)) |
1354 | | } |
1355 | | } |
1356 | | |
1357 | | // An accessor that shares local state. |
1358 | | #[derive(Debug)] |
1359 | | struct Sharing<'a> { |
1360 | | store: &'a Store, |
1361 | | local: Box<[u8]>, |
1362 | | } |
1363 | | |
1364 | | impl<'a> Sharing<'a> { |
1365 | | fn new(store: &'a Store) -> Self { |
1366 | | Self { |
1367 | | store, |
1368 | | local: (0..store.dim()).map(|_| 0).collect(), |
1369 | | } |
1370 | | } |
1371 | | } |
1372 | | |
1373 | | common_test_accessor!(Sharing<'_>); |
1374 | | |
1375 | | impl Accessor for Sharing<'_> { |
1376 | | type Element<'a> |
1377 | | = &'a [u8] |
1378 | | where |
1379 | | Self: 'a; |
1380 | | type ElementRef<'a> = &'a [u8]; |
1381 | | type GetError = Infallible; |
1382 | | |
1383 | | async fn get_element(&mut self, _: u32) -> Result<&[u8], Infallible> { |
1384 | | self.local.copy_from_slice(&self.store.data); |
1385 | | Ok(&self.local) |
1386 | | } |
1387 | | } |
1388 | | |
1389 | | #[tokio::test] |
1390 | | async fn test_accessor_patterns() { |
1391 | | let store = Store::new(); |
1392 | | |
1393 | | // A slice against which we compute distances. |
1394 | | let base: &[u8] = &[2, 3, 4, 5]; |
1395 | | |
1396 | | { |
1397 | | let mut accessor = Allocating::new(&store); |
1398 | | let computer = accessor.build_distance_computer().unwrap(); |
1399 | | |
1400 | | let element = accessor.get_element(0).await.unwrap(); |
1401 | | assert_eq!(computer.evaluate_similarity(base, element.reborrow()), 4.0); |
1402 | | } |
1403 | | |
1404 | | { |
1405 | | let mut accessor = Forwarding::new(&store); |
1406 | | let computer = accessor.build_distance_computer().unwrap(); |
1407 | | |
1408 | | let element = accessor.get_element(0).await.unwrap(); |
1409 | | assert_eq!(computer.evaluate_similarity(base, element.reborrow()), 4.0); |
1410 | | } |
1411 | | |
1412 | | { |
1413 | | let mut accessor = Wrapping::new(&store); |
1414 | | let computer = accessor.build_distance_computer().unwrap(); |
1415 | | |
1416 | | let element = accessor.get_element(0).await.unwrap(); |
1417 | | assert_eq!(computer.evaluate_similarity(base, element.reborrow()), 4.0); |
1418 | | } |
1419 | | |
1420 | | { |
1421 | | let mut accessor = Sharing::new(&store); |
1422 | | let computer = accessor.build_distance_computer().unwrap(); |
1423 | | |
1424 | | let element = accessor.get_element(0).await.unwrap(); |
1425 | | assert_eq!(computer.evaluate_similarity(base, element.reborrow()), 4.0); |
1426 | | } |
1427 | | } |
1428 | | } |