Skip to main content

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}