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