1mod canonical;
10pub(crate) mod guards;
11pub(in crate::key_value) mod origin;
12mod read;
13pub use origin::{
14 DiskANNCanonicalVectorVisitor, KeyValueDiskANNCanonical, KeyValueDiskANNHandle,
15 KeyValueDiskANNPopulationRecords, RetainedDiskANNCanonical,
16};
17
18use crate::vector_index::RetainedVectorIndex;
19
20use super::index_view::read_view;
21
22use super::codec::{
23 other_error, usize_to_u64, validate_vector_ordinal_count, vector_doc_prefix,
24 vector_field_prefix, vector_key, vector_to_blob,
25};
26use super::{
27 validate_vector_values, Arc, DocId, KeyValueBatch, KeyValueStore, PostingList,
28 StorageBackendResult, VectorIndex,
29};
30
31#[derive(Clone)]
33pub struct KeyValueVectorIndex {
34 store: Arc<dyn KeyValueStore>,
35 table: String,
36 field: String,
37 dimensions: u32,
38}
39
40impl KeyValueVectorIndex {
41 pub fn new(
42 store: Arc<dyn KeyValueStore>,
43 table: impl Into<String>,
44 field: impl Into<String>,
45 dimensions: u32,
46 ) -> Self {
47 Self {
48 store,
49 table: table.into(),
50 field: field.into(),
51 dimensions,
52 }
53 }
54
55 fn read_snapshot(&self) -> StorageBackendResult<RetainedVectorIndex> {
56 read_view(self.store.as_ref(), |read| {
57 RetainedVectorIndex::from_entries(
58 self.load_all_from(read)?,
59 self.dimensions,
60 "keyvalue-bruteforce",
61 read.control(),
62 )
63 })
64 }
65
66 pub(super) fn stage_replace(
67 &self,
68 batch: &mut dyn KeyValueBatch,
69 doc_id: DocId,
70 vectors: &[Vec<f32>],
71 ) -> StorageBackendResult<()> {
72 self.stage_replace_values(batch, doc_id, vectors)?;
73 batch.invalidate_diskann_origin(&origin::key(&self.table, &self.field, doc_id)?)
74 }
75
76 pub(super) fn stage_replace_values(
78 &self,
79 batch: &mut dyn KeyValueBatch,
80 doc_id: DocId,
81 vectors: &[Vec<f32>],
82 ) -> StorageBackendResult<()> {
83 for vector in vectors {
84 self.validate_dimensions(vector)?;
85 }
86 validate_vector_ordinal_count(usize_to_u64(vectors.len(), "vector count")?)?;
87 self.coordinate_field(batch, false)?;
88 batch.delete_prefix(&vector_doc_prefix(&self.table, &self.field, doc_id)?)?;
89 for (ordinal, vector) in vectors.iter().enumerate() {
90 let ordinal = u32::try_from(ordinal)
91 .map_err(|_| other_error("vector ordinal exceeds u32 index format"))?;
92 batch.put(
93 &vector_key(&self.table, &self.field, doc_id, ordinal)?,
94 &vector_to_blob(vector)?,
95 )?;
96 }
97 Ok(())
98 }
99
100 pub(super) fn stage_clear(&self, batch: &mut dyn KeyValueBatch) -> StorageBackendResult<()> {
101 self.coordinate_field(batch, true)?;
102 batch.delete_prefix(&origin::prefix(&self.table, &self.field)?)?;
103 batch.delete_prefix_allow_absent(&origin::journal::prefix(&self.table, &self.field)?)?;
104 for prefix in origin::populations::field_prefixes(&self.table, &self.field)? {
105 batch.delete_prefix(&prefix)?;
106 }
107 batch.delete_prefix(&vector_field_prefix(&self.table, &self.field)?)
108 }
109
110 fn validate_dimensions(&self, vector: &[f32]) -> StorageBackendResult<()> {
111 validate_vector_values(self.dimensions, vector).map_err(|error| {
112 other_error(format!(
113 "invalid vector for {}.{}: {error}",
114 self.table, self.field
115 ))
116 })
117 }
118}
119
120impl VectorIndex for KeyValueVectorIndex {
121 fn contains_document(&self, doc_id: DocId) -> StorageBackendResult<bool> {
122 read_view(self.store.as_ref(), |read| {
123 read.control().check()?;
124 let bytes = self
126 .table
127 .len()
128 .checked_add(self.field.len())
129 .and_then(|length| length.checked_add(25))
130 .and_then(|length| length.checked_mul(3))
131 .ok_or(uqa_core::memory::MemoryError::SizeOverflow)?;
132 let _key_memory = read.control().memory().reserve(bytes)?;
133 let key = vector_key(&self.table, &self.field, doc_id, 0)?;
134 let mut found = false;
135 read.visit_keys_after(&key, None, 1, read.control(), &mut |candidate| {
136 found = candidate == key;
137 Ok(())
138 })?;
139 Ok(found)
140 })
141 }
142
143 fn dimensions(&self) -> u32 {
144 self.dimensions
145 }
146
147 fn index_kind(&self) -> &'static str {
148 "keyvalue-bruteforce"
149 }
150
151 fn add(&mut self, doc_id: DocId, vector: Vec<f32>) -> StorageBackendResult<()> {
152 self.add_many(doc_id, vec![vector])
153 }
154
155 fn add_many(&mut self, doc_id: DocId, vectors: Vec<Vec<f32>>) -> StorageBackendResult<()> {
156 let mut batch = self.store.batch();
157 self.stage_replace(batch.as_mut(), doc_id, &vectors)?;
158 batch.commit()
159 }
160
161 fn delete(&mut self, doc_id: DocId) -> StorageBackendResult<()> {
162 let mut batch = self.store.batch();
163 self.stage_replace(batch.as_mut(), doc_id, &[])?;
164 batch.commit()
165 }
166
167 fn clear(&mut self) -> StorageBackendResult<()> {
168 let mut batch = self.store.batch();
169 self.stage_clear(batch.as_mut())?;
170 batch.commit()
171 }
172
173 fn search_knn(&self, query: &[f32], k: usize) -> StorageBackendResult<PostingList> {
174 self.validate_dimensions(query)?;
175 if k == 0 {
176 return Ok(PostingList::new());
177 }
178 self.read_snapshot()?.search_knn(query, k)
179 }
180
181 fn search_threshold(&self, query: &[f32], threshold: f32) -> StorageBackendResult<PostingList> {
182 self.validate_dimensions(query)?;
183 if !threshold.is_finite() {
184 return Err(other_error(format!(
185 "vector similarity threshold must be finite, got {threshold}"
186 )));
187 }
188 self.read_snapshot()?.search_threshold(query, threshold)
189 }
190
191 fn count(&self) -> StorageBackendResult<usize> {
192 self.read_snapshot()?.count()
193 }
194
195 fn snapshot(&self) -> StorageBackendResult<Arc<dyn VectorIndex>> {
196 Ok(Arc::new(self.read_snapshot()?))
197 }
198 fn vector_read_snapshot(
199 &self,
200 control: &crate::read_control::StorageReadControl,
201 ) -> StorageBackendResult<Option<crate::vector_index::VectorReadSnapshot>> {
202 canonical::capture(self, control).map(Some)
203 }
204}