iqdb_build/builder.rs
1//! The sequential builder and its Tier-1 free-function shortcuts.
2//!
3//! [`IndexBuilder`] is the configured (Tier-2) entry point; [`build`] and
4//! [`build_into`] are the one-call (Tier-1) shortcuts over it.
5
6use std::sync::Arc;
7
8use iqdb_index::{Index, IndexCore};
9use iqdb_types::{DistanceMetric, Metadata, Result, VectorId};
10
11/// One vector to feed into a build: its [`VectorId`], its components owned as an
12/// [`Arc<[f32]>`](std::sync::Arc), and its optional [`Metadata`].
13///
14/// This is exactly the element type of
15/// [`IndexCore::insert_batch`](iqdb_index::IndexCore::insert_batch), so building
16/// never reshapes or re-copies your data: the [`Arc`] you hand in is the
17/// allocation the index stores. Vectors without metadata pass [`None`].
18///
19/// # Examples
20///
21/// ```
22/// use std::sync::Arc;
23/// use iqdb_build::BuildItem;
24/// use iqdb_types::VectorId;
25///
26/// let item: BuildItem = (VectorId::from(7u64), Arc::from([0.1_f32, 0.2].as_slice()), None);
27/// assert_eq!(item.0, VectorId::from(7u64));
28/// assert_eq!(item.1.len(), 2);
29/// ```
30pub type BuildItem = (VectorId, Arc<[f32]>, Option<Metadata>);
31
32/// A configured, reusable plan for constructing an [`Index`].
33///
34/// `IndexBuilder` holds the three things [`Index::new`] needs — the
35/// dimensionality, the [`DistanceMetric`], and the backend's own
36/// [`Config`](iqdb_index::Index::Config) — and turns a stream of [`BuildItem`]s
37/// into a finished index with [`build`](IndexBuilder::build). It is the Tier-2
38/// surface: reach for it when you want to tune the backend (graph degree, probe
39/// count, …). For the common case with default configuration, the free function
40/// [`build`](crate::build) is one call.
41///
42/// The same builder constructs **any** backend that implements [`Index`] — flat,
43/// HNSW, IVF, or your own — because it never names a concrete type.
44///
45/// A builder is cheap to clone and reuse: the same plan can build many indexes
46/// from different inputs.
47///
48/// # Examples
49///
50/// ```
51/// use std::sync::Arc;
52/// use iqdb_build::IndexBuilder;
53/// use iqdb_types::{DistanceMetric, VectorId};
54/// # use iqdb_index::{Index, IndexCore, IndexStats};
55/// # use iqdb_types::{Hit, IqdbError, Metadata, Result, SearchParams};
56/// # struct Flat { dim: usize, metric: DistanceMetric, rows: Vec<(VectorId, Arc<[f32]>)> }
57/// # #[derive(Clone, Default)] struct FlatConfig;
58/// # impl IndexCore for Flat {
59/// # fn insert(&mut self, id: VectorId, v: Arc<[f32]>, _m: Option<Metadata>) -> Result<()> {
60/// # if v.len() != self.dim { return Err(IqdbError::DimensionMismatch { expected: self.dim, found: v.len() }); }
61/// # if self.rows.iter().any(|(e, _)| e == &id) { return Err(IqdbError::Duplicate); }
62/// # self.rows.push((id, v)); Ok(())
63/// # }
64/// # fn delete(&mut self, id: &VectorId) -> Result<()> { match self.rows.iter().position(|(e, _)| e == id) { Some(p) => { let _ = self.rows.remove(p); Ok(()) } None => Err(IqdbError::NotFound) } }
65/// # fn search(&self, q: &[f32], p: &SearchParams) -> Result<Vec<Hit>> { let mut h: Vec<Hit> = self.rows.iter().map(|(id, v)| Hit { id: id.clone(), distance: q.iter().zip(v.iter()).map(|(a, b)| (a - b) * (a - b)).sum(), metadata: None }).collect(); h.sort_by(|a, b| a.distance.total_cmp(&b.distance)); h.truncate(p.k); Ok(h) }
66/// # fn len(&self) -> usize { self.rows.len() }
67/// # fn dim(&self) -> usize { self.dim }
68/// # fn metric(&self) -> DistanceMetric { self.metric }
69/// # fn flush(&mut self) -> Result<()> { Ok(()) }
70/// # fn stats(&self) -> IndexStats { IndexStats { n_vectors: self.rows.len(), index_type: "flat", ..IndexStats::default() } }
71/// # }
72/// # impl Index for Flat { type Config = FlatConfig; fn new(dim: usize, metric: DistanceMetric, _c: Self::Config) -> Result<Self> { if dim == 0 { return Err(IqdbError::InvalidConfig { reason: "dim must be > 0" }); } Ok(Flat { dim, metric, rows: Vec::new() }) } }
73/// # fn main() -> iqdb_types::Result<()> {
74/// let builder = IndexBuilder::new(2, DistanceMetric::Euclidean);
75/// let index: Flat = builder.build(vec![
76/// (VectorId::from(1u64), Arc::from([0.0_f32, 0.0].as_slice()), None),
77/// (VectorId::from(2u64), Arc::from([1.0_f32, 1.0].as_slice()), None),
78/// ])?;
79/// assert_eq!(index.len(), 2);
80/// # Ok(()) }
81/// ```
82pub struct IndexBuilder<I: Index> {
83 pub(crate) dim: usize,
84 pub(crate) metric: DistanceMetric,
85 pub(crate) config: I::Config,
86 /// Shard count for [`build_parallel`](IndexBuilder::build_parallel). `None`
87 /// means "auto" — one shard per available CPU. Ignored by the sequential
88 /// [`build`](IndexBuilder::build).
89 pub(crate) shards: Option<usize>,
90 /// Optional progress callback, invoked as shards finish building. Wrapped in
91 /// an [`Arc`] so the builder stays [`Clone`] and the closure can be shared
92 /// across rayon worker threads.
93 pub(crate) progress: Option<Arc<dyn Fn(crate::BuildProgress) + Send + Sync>>,
94}
95
96impl<I: Index> Clone for IndexBuilder<I> {
97 fn clone(&self) -> Self {
98 Self {
99 dim: self.dim,
100 metric: self.metric,
101 config: self.config.clone(),
102 shards: self.shards,
103 progress: self.progress.clone(),
104 }
105 }
106}
107
108impl<I: Index> IndexBuilder<I> {
109 /// Create a builder for a `dim`-dimensional index under `metric`, using the
110 /// backend's default [`Config`](iqdb_index::Index::Config).
111 ///
112 /// Use [`with_config`](IndexBuilder::with_config) when the backend has
113 /// parameters to tune.
114 ///
115 /// # Examples
116 ///
117 /// ```
118 /// use iqdb_build::IndexBuilder;
119 /// use iqdb_types::DistanceMetric;
120 /// # use std::sync::Arc;
121 /// # use iqdb_index::{Index, IndexCore, IndexStats};
122 /// # use iqdb_types::{Hit, IqdbError, Metadata, Result, SearchParams, VectorId};
123 /// # struct Flat { dim: usize, metric: DistanceMetric, rows: Vec<(VectorId, Arc<[f32]>)> }
124 /// # #[derive(Clone, Default)] struct FlatConfig;
125 /// # impl IndexCore for Flat { fn insert(&mut self, id: VectorId, v: Arc<[f32]>, _m: Option<Metadata>) -> Result<()> { self.rows.push((id, v)); Ok(()) } fn delete(&mut self, _id: &VectorId) -> Result<()> { Ok(()) } fn search(&self, _q: &[f32], _p: &SearchParams) -> Result<Vec<Hit>> { Ok(Vec::new()) } fn len(&self) -> usize { self.rows.len() } fn dim(&self) -> usize { self.dim } fn metric(&self) -> DistanceMetric { self.metric } fn flush(&mut self) -> Result<()> { Ok(()) } fn stats(&self) -> IndexStats { IndexStats::default() } }
126 /// # impl Index for Flat { type Config = FlatConfig; fn new(dim: usize, metric: DistanceMetric, _c: Self::Config) -> Result<Self> { Ok(Flat { dim, metric, rows: Vec::new() }) } }
127 /// let builder = IndexBuilder::<Flat>::new(128, DistanceMetric::Cosine);
128 /// assert_eq!(builder.dim(), 128);
129 /// assert_eq!(builder.metric(), DistanceMetric::Cosine);
130 /// ```
131 #[inline]
132 pub fn new(dim: usize, metric: DistanceMetric) -> Self {
133 Self {
134 dim,
135 metric,
136 config: I::Config::default(),
137 shards: None,
138 progress: None,
139 }
140 }
141
142 /// Create a builder with an explicit backend
143 /// [`Config`](iqdb_index::Index::Config).
144 ///
145 /// This is the Tier-2 tuning entry point: pass the backend's own parameter
146 /// struct (graph degree, cluster/probe counts, …) and every index this
147 /// builder produces is constructed with it.
148 ///
149 /// # Examples
150 ///
151 /// ```
152 /// use iqdb_build::IndexBuilder;
153 /// use iqdb_types::DistanceMetric;
154 /// # use std::sync::Arc;
155 /// # use iqdb_index::{Index, IndexCore, IndexStats};
156 /// # use iqdb_types::{Hit, IqdbError, Metadata, Result, SearchParams, VectorId};
157 /// # struct Graph { dim: usize, metric: DistanceMetric, degree: usize, n: usize }
158 /// # #[derive(Clone)] struct GraphConfig { degree: usize }
159 /// # impl Default for GraphConfig { fn default() -> Self { Self { degree: 16 } } }
160 /// # impl IndexCore for Graph { fn insert(&mut self, _id: VectorId, _v: Arc<[f32]>, _m: Option<Metadata>) -> Result<()> { self.n += 1; Ok(()) } fn delete(&mut self, _id: &VectorId) -> Result<()> { Ok(()) } fn search(&self, _q: &[f32], _p: &SearchParams) -> Result<Vec<Hit>> { Ok(Vec::new()) } fn len(&self) -> usize { self.n } fn dim(&self) -> usize { self.dim } fn metric(&self) -> DistanceMetric { self.metric } fn flush(&mut self) -> Result<()> { Ok(()) } fn stats(&self) -> IndexStats { IndexStats::default() } }
161 /// # impl Index for Graph { type Config = GraphConfig; fn new(dim: usize, metric: DistanceMetric, c: Self::Config) -> Result<Self> { Ok(Graph { dim, metric, degree: c.degree, n: 0 }) } }
162 /// // Tune the graph degree away from its default of 16.
163 /// let builder = IndexBuilder::<Graph>::with_config(64, DistanceMetric::Euclidean, GraphConfig { degree: 32 });
164 /// assert_eq!(builder.config().degree, 32);
165 /// ```
166 #[inline]
167 pub fn with_config(dim: usize, metric: DistanceMetric, config: I::Config) -> Self {
168 Self {
169 dim,
170 metric,
171 config,
172 shards: None,
173 progress: None,
174 }
175 }
176
177 /// The dimensionality this builder constructs indexes for.
178 ///
179 /// # Examples
180 ///
181 /// ```
182 /// # use iqdb_build::IndexBuilder;
183 /// # use iqdb_types::DistanceMetric;
184 /// # use std::sync::Arc;
185 /// # use iqdb_index::{Index, IndexCore, IndexStats};
186 /// # use iqdb_types::{Hit, Metadata, Result, SearchParams, VectorId};
187 /// # struct Flat { dim: usize, metric: DistanceMetric }
188 /// # #[derive(Clone, Default)] struct FlatConfig;
189 /// # impl IndexCore for Flat { fn insert(&mut self, _id: VectorId, _v: Arc<[f32]>, _m: Option<Metadata>) -> Result<()> { Ok(()) } fn delete(&mut self, _id: &VectorId) -> Result<()> { Ok(()) } fn search(&self, _q: &[f32], _p: &SearchParams) -> Result<Vec<Hit>> { Ok(Vec::new()) } fn len(&self) -> usize { 0 } fn dim(&self) -> usize { self.dim } fn metric(&self) -> DistanceMetric { self.metric } fn flush(&mut self) -> Result<()> { Ok(()) } fn stats(&self) -> IndexStats { IndexStats::default() } }
190 /// # impl Index for Flat { type Config = FlatConfig; fn new(dim: usize, metric: DistanceMetric, _c: Self::Config) -> Result<Self> { Ok(Flat { dim, metric }) } }
191 /// let builder = IndexBuilder::<Flat>::new(96, DistanceMetric::Cosine);
192 /// assert_eq!(builder.dim(), 96);
193 /// ```
194 #[inline]
195 pub fn dim(&self) -> usize {
196 self.dim
197 }
198
199 /// The [`DistanceMetric`] this builder constructs indexes under.
200 ///
201 /// # Examples
202 ///
203 /// ```
204 /// # use iqdb_build::IndexBuilder;
205 /// # use iqdb_types::DistanceMetric;
206 /// # use std::sync::Arc;
207 /// # use iqdb_index::{Index, IndexCore, IndexStats};
208 /// # use iqdb_types::{Hit, Metadata, Result, SearchParams, VectorId};
209 /// # struct Flat { dim: usize, metric: DistanceMetric }
210 /// # #[derive(Clone, Default)] struct FlatConfig;
211 /// # impl IndexCore for Flat { fn insert(&mut self, _id: VectorId, _v: Arc<[f32]>, _m: Option<Metadata>) -> Result<()> { Ok(()) } fn delete(&mut self, _id: &VectorId) -> Result<()> { Ok(()) } fn search(&self, _q: &[f32], _p: &SearchParams) -> Result<Vec<Hit>> { Ok(Vec::new()) } fn len(&self) -> usize { 0 } fn dim(&self) -> usize { self.dim } fn metric(&self) -> DistanceMetric { self.metric } fn flush(&mut self) -> Result<()> { Ok(()) } fn stats(&self) -> IndexStats { IndexStats::default() } }
212 /// # impl Index for Flat { type Config = FlatConfig; fn new(dim: usize, metric: DistanceMetric, _c: Self::Config) -> Result<Self> { Ok(Flat { dim, metric }) } }
213 /// let builder = IndexBuilder::<Flat>::new(96, DistanceMetric::Manhattan);
214 /// assert_eq!(builder.metric(), DistanceMetric::Manhattan);
215 /// ```
216 #[inline]
217 pub fn metric(&self) -> DistanceMetric {
218 self.metric
219 }
220
221 /// The backend [`Config`](iqdb_index::Index::Config) this builder constructs
222 /// indexes with.
223 ///
224 /// # Examples
225 ///
226 /// ```
227 /// # use iqdb_build::IndexBuilder;
228 /// # use iqdb_types::DistanceMetric;
229 /// # use std::sync::Arc;
230 /// # use iqdb_index::{Index, IndexCore, IndexStats};
231 /// # use iqdb_types::{Hit, Metadata, Result, SearchParams, VectorId};
232 /// # struct Flat { dim: usize, metric: DistanceMetric }
233 /// # #[derive(Clone, Default, Debug, PartialEq)] struct FlatConfig;
234 /// # impl IndexCore for Flat { fn insert(&mut self, _id: VectorId, _v: Arc<[f32]>, _m: Option<Metadata>) -> Result<()> { Ok(()) } fn delete(&mut self, _id: &VectorId) -> Result<()> { Ok(()) } fn search(&self, _q: &[f32], _p: &SearchParams) -> Result<Vec<Hit>> { Ok(Vec::new()) } fn len(&self) -> usize { 0 } fn dim(&self) -> usize { self.dim } fn metric(&self) -> DistanceMetric { self.metric } fn flush(&mut self) -> Result<()> { Ok(()) } fn stats(&self) -> IndexStats { IndexStats::default() } }
235 /// # impl Index for Flat { type Config = FlatConfig; fn new(dim: usize, metric: DistanceMetric, _c: Self::Config) -> Result<Self> { Ok(Flat { dim, metric }) } }
236 /// let builder = IndexBuilder::<Flat>::new(8, DistanceMetric::Euclidean);
237 /// assert_eq!(builder.config(), &FlatConfig);
238 /// ```
239 #[inline]
240 pub fn config(&self) -> &I::Config {
241 &self.config
242 }
243
244 /// Construct a fresh index and bulk-insert every item into it.
245 ///
246 /// The index is created with this builder's `dim`, `metric`, and `config`
247 /// via [`Index::new`], then every item is inserted through the backend's
248 /// [`insert_batch`](iqdb_index::IndexCore::insert_batch) path (which a
249 /// backend may accelerate). On success the returned index holds exactly the
250 /// supplied vectors.
251 ///
252 /// # Errors
253 ///
254 /// - [`IqdbError::InvalidConfig`](iqdb_types::IqdbError::InvalidConfig) if
255 /// [`Index::new`] rejects the `dim`/`config` (for example `dim == 0`).
256 /// - [`IqdbError::DimensionMismatch`](iqdb_types::IqdbError::DimensionMismatch)
257 /// if any item's vector length differs from `dim`.
258 /// - [`IqdbError::Duplicate`](iqdb_types::IqdbError::Duplicate) if two items
259 /// share a [`VectorId`] (or one collides with an id the backend rejects).
260 ///
261 /// Insertion is fail-fast: the first failing item returns its error and the
262 /// half-built index is dropped, so a failed `build` leaves nothing behind.
263 ///
264 /// # Examples
265 ///
266 /// ```
267 /// use std::sync::Arc;
268 /// use iqdb_build::IndexBuilder;
269 /// use iqdb_types::{DistanceMetric, IqdbError, VectorId};
270 /// # use iqdb_index::{Index, IndexCore, IndexStats};
271 /// # use iqdb_types::{Hit, Metadata, Result, SearchParams};
272 /// # struct Flat { dim: usize, metric: DistanceMetric, rows: Vec<(VectorId, Arc<[f32]>)> }
273 /// # #[derive(Clone, Default)] struct FlatConfig;
274 /// # impl IndexCore for Flat {
275 /// # fn insert(&mut self, id: VectorId, v: Arc<[f32]>, _m: Option<Metadata>) -> Result<()> { if v.len() != self.dim { return Err(IqdbError::DimensionMismatch { expected: self.dim, found: v.len() }); } if self.rows.iter().any(|(e, _)| e == &id) { return Err(IqdbError::Duplicate); } self.rows.push((id, v)); Ok(()) }
276 /// # fn delete(&mut self, id: &VectorId) -> Result<()> { match self.rows.iter().position(|(e, _)| e == id) { Some(p) => { let _ = self.rows.remove(p); Ok(()) } None => Err(IqdbError::NotFound) } }
277 /// # fn search(&self, _q: &[f32], _p: &SearchParams) -> Result<Vec<Hit>> { Ok(Vec::new()) }
278 /// # fn len(&self) -> usize { self.rows.len() }
279 /// # fn dim(&self) -> usize { self.dim }
280 /// # fn metric(&self) -> DistanceMetric { self.metric }
281 /// # fn flush(&mut self) -> Result<()> { Ok(()) }
282 /// # fn stats(&self) -> IndexStats { IndexStats { n_vectors: self.rows.len(), index_type: "flat", ..IndexStats::default() } }
283 /// # }
284 /// # impl Index for Flat { type Config = FlatConfig; fn new(dim: usize, metric: DistanceMetric, _c: Self::Config) -> Result<Self> { if dim == 0 { return Err(IqdbError::InvalidConfig { reason: "dim must be > 0" }); } Ok(Flat { dim, metric, rows: Vec::new() }) } }
285 /// # fn main() -> iqdb_types::Result<()> {
286 /// let index: Flat = IndexBuilder::new(2, DistanceMetric::Euclidean).build(vec![
287 /// (VectorId::from(1u64), Arc::from([0.0_f32, 0.0].as_slice()), None),
288 /// (VectorId::from(2u64), Arc::from([1.0_f32, 0.0].as_slice()), None),
289 /// ])?;
290 /// assert_eq!(index.len(), 2);
291 ///
292 /// // A duplicate id is rejected.
293 /// let dup: Result<Flat> = IndexBuilder::new(2, DistanceMetric::Euclidean).build(vec![
294 /// (VectorId::from(1u64), Arc::from([0.0_f32, 0.0].as_slice()), None),
295 /// (VectorId::from(1u64), Arc::from([1.0_f32, 0.0].as_slice()), None),
296 /// ]);
297 /// assert!(matches!(dup, Err(IqdbError::Duplicate)));
298 /// # Ok(()) }
299 /// ```
300 pub fn build<It>(&self, items: It) -> Result<I>
301 where
302 It: IntoIterator<Item = BuildItem>,
303 {
304 let mut index = I::new(self.dim, self.metric, self.config.clone())?;
305 let _ = build_into(&mut index, items)?;
306 Ok(index)
307 }
308}
309
310/// Construct a fresh index from a stream of vectors, using the backend's default
311/// [`Config`](iqdb_index::Index::Config).
312///
313/// This is the Tier-1 lazy path: the whole common case in one call. It is
314/// shorthand for `IndexBuilder::new(dim, metric).build(items)`. Reach for
315/// [`IndexBuilder::with_config`] instead when the backend needs tuning.
316///
317/// # Errors
318///
319/// Propagates the same errors as [`IndexBuilder::build`]:
320/// [`IqdbError::InvalidConfig`](iqdb_types::IqdbError::InvalidConfig) from
321/// construction, and
322/// [`IqdbError::DimensionMismatch`](iqdb_types::IqdbError::DimensionMismatch) /
323/// [`IqdbError::Duplicate`](iqdb_types::IqdbError::Duplicate) from insertion.
324///
325/// # Examples
326///
327/// ```
328/// use std::sync::Arc;
329/// use iqdb_types::{DistanceMetric, VectorId};
330/// # use iqdb_index::{Index, IndexCore, IndexStats};
331/// # use iqdb_types::{Hit, IqdbError, Metadata, Result, SearchParams};
332/// # struct Flat { dim: usize, metric: DistanceMetric, rows: Vec<(VectorId, Arc<[f32]>)> }
333/// # #[derive(Clone, Default)] struct FlatConfig;
334/// # impl IndexCore for Flat {
335/// # fn insert(&mut self, id: VectorId, v: Arc<[f32]>, _m: Option<Metadata>) -> Result<()> { if v.len() != self.dim { return Err(IqdbError::DimensionMismatch { expected: self.dim, found: v.len() }); } if self.rows.iter().any(|(e, _)| e == &id) { return Err(IqdbError::Duplicate); } self.rows.push((id, v)); Ok(()) }
336/// # fn delete(&mut self, id: &VectorId) -> Result<()> { match self.rows.iter().position(|(e, _)| e == id) { Some(p) => { let _ = self.rows.remove(p); Ok(()) } None => Err(IqdbError::NotFound) } }
337/// # fn search(&self, _q: &[f32], _p: &SearchParams) -> Result<Vec<Hit>> { Ok(Vec::new()) }
338/// # fn len(&self) -> usize { self.rows.len() }
339/// # fn dim(&self) -> usize { self.dim }
340/// # fn metric(&self) -> DistanceMetric { self.metric }
341/// # fn flush(&mut self) -> Result<()> { Ok(()) }
342/// # fn stats(&self) -> IndexStats { IndexStats { n_vectors: self.rows.len(), index_type: "flat", ..IndexStats::default() } }
343/// # }
344/// # impl Index for Flat { type Config = FlatConfig; fn new(dim: usize, metric: DistanceMetric, _c: Self::Config) -> Result<Self> { if dim == 0 { return Err(IqdbError::InvalidConfig { reason: "dim must be > 0" }); } Ok(Flat { dim, metric, rows: Vec::new() }) } }
345/// # fn main() -> iqdb_types::Result<()> {
346/// let index: Flat = iqdb_build::build(3, DistanceMetric::Cosine, vec![
347/// (VectorId::from(10u64), Arc::from([1.0_f32, 0.0, 0.0].as_slice()), None),
348/// (VectorId::from(20u64), Arc::from([0.0_f32, 1.0, 0.0].as_slice()), None),
349/// ])?;
350/// assert_eq!(index.len(), 2);
351/// # Ok(()) }
352/// ```
353#[inline]
354pub fn build<I, It>(dim: usize, metric: DistanceMetric, items: It) -> Result<I>
355where
356 I: Index,
357 It: IntoIterator<Item = BuildItem>,
358{
359 IndexBuilder::<I>::new(dim, metric).build(items)
360}
361
362/// Bulk-insert a stream of vectors into an index you already hold, returning the
363/// number inserted.
364///
365/// This is the incremental Tier-1 path: load an existing index, add more
366/// vectors, keep it. It works over the object-safe
367/// [`IndexCore`](iqdb_index::IndexCore) surface, so it accepts a concrete index
368/// **or** a `&mut dyn IndexCore` trait object — the form the engine holds.
369///
370/// The items are inserted through
371/// [`IndexCore::insert_batch`](iqdb_index::IndexCore::insert_batch), which a
372/// backend may accelerate over a per-vector loop.
373///
374/// # Errors
375///
376/// - [`IqdbError::DimensionMismatch`](iqdb_types::IqdbError::DimensionMismatch)
377/// if any vector's length differs from the index's `dim()`.
378/// - [`IqdbError::Duplicate`](iqdb_types::IqdbError::Duplicate) if an id collides
379/// with one already present (or repeated within the batch).
380///
381/// Insertion is fail-fast and **not transactional**: on error, items inserted
382/// before the failing one remain in the index. The returned count is only
383/// meaningful on `Ok`, where it equals the number of items supplied.
384///
385/// # Examples
386///
387/// ```
388/// use std::sync::Arc;
389/// use iqdb_build::{build, build_into};
390/// use iqdb_types::{DistanceMetric, VectorId};
391/// # use iqdb_index::{Index, IndexCore, IndexStats};
392/// # use iqdb_types::{Hit, IqdbError, Metadata, Result, SearchParams};
393/// # struct Flat { dim: usize, metric: DistanceMetric, rows: Vec<(VectorId, Arc<[f32]>)> }
394/// # #[derive(Clone, Default)] struct FlatConfig;
395/// # impl IndexCore for Flat {
396/// # fn insert(&mut self, id: VectorId, v: Arc<[f32]>, _m: Option<Metadata>) -> Result<()> { if v.len() != self.dim { return Err(IqdbError::DimensionMismatch { expected: self.dim, found: v.len() }); } if self.rows.iter().any(|(e, _)| e == &id) { return Err(IqdbError::Duplicate); } self.rows.push((id, v)); Ok(()) }
397/// # fn delete(&mut self, id: &VectorId) -> Result<()> { match self.rows.iter().position(|(e, _)| e == id) { Some(p) => { let _ = self.rows.remove(p); Ok(()) } None => Err(IqdbError::NotFound) } }
398/// # fn search(&self, _q: &[f32], _p: &SearchParams) -> Result<Vec<Hit>> { Ok(Vec::new()) }
399/// # fn len(&self) -> usize { self.rows.len() }
400/// # fn dim(&self) -> usize { self.dim }
401/// # fn metric(&self) -> DistanceMetric { self.metric }
402/// # fn flush(&mut self) -> Result<()> { Ok(()) }
403/// # fn stats(&self) -> IndexStats { IndexStats { n_vectors: self.rows.len(), index_type: "flat", ..IndexStats::default() } }
404/// # }
405/// # impl Index for Flat { type Config = FlatConfig; fn new(dim: usize, metric: DistanceMetric, _c: Self::Config) -> Result<Self> { if dim == 0 { return Err(IqdbError::InvalidConfig { reason: "dim must be > 0" }); } Ok(Flat { dim, metric, rows: Vec::new() }) } }
406/// # fn main() -> iqdb_types::Result<()> {
407/// // Start from an index, then append more vectors incrementally.
408/// let mut index: Flat = build(2, DistanceMetric::Euclidean, vec![
409/// (VectorId::from(1u64), Arc::from([0.0_f32, 0.0].as_slice()), None),
410/// ])?;
411/// let added = build_into(&mut index, vec![
412/// (VectorId::from(2u64), Arc::from([1.0_f32, 0.0].as_slice()), None),
413/// (VectorId::from(3u64), Arc::from([0.0_f32, 1.0].as_slice()), None),
414/// ])?;
415/// assert_eq!(added, 2);
416/// assert_eq!(index.len(), 3);
417/// # Ok(()) }
418/// ```
419#[inline]
420pub fn build_into<I, It>(index: &mut I, items: It) -> Result<usize>
421where
422 I: IndexCore + ?Sized,
423 It: IntoIterator<Item = BuildItem>,
424{
425 let batch: Vec<BuildItem> = items.into_iter().collect();
426 let inserted = batch.len();
427 index.insert_batch(batch)?;
428 Ok(inserted)
429}