Skip to main content

velesdb_core/collection/
metadata_collection.rs

1//! `MetadataCollection`: payload-only storage without vectors.
2//!
3//! Ideal for reference tables, catalogs, and structured metadata.
4//! Supports CRUD and VelesQL queries on payload — NOT vector search.
5//!
6//! # Design
7//!
8//! `MetadataCollection` is a pure newtype over `Collection` — all operations
9//! delegate to the single `inner` instance, matching the `VectorCollection` pattern
10//! and eliminating any dual-storage desync risk (C-02).
11
12use std::collections::HashMap;
13use std::path::PathBuf;
14
15use crate::collection::types::Collection;
16use crate::error::{Error, Result};
17use crate::point::{Point, SearchResult};
18
19/// A metadata-only collection storing structured payloads without vector indexes.
20///
21/// # Examples
22///
23/// ```rust,no_run
24/// use velesdb_core::{MetadataCollection, Point};
25/// use serde_json::json;
26///
27/// let coll = MetadataCollection::create("./data/products".into(), "products")?;
28///
29/// coll.upsert(vec![
30///     Point::metadata_only(1, json!({"name": "Widget", "price": 9.99})),
31/// ])?;
32/// # Ok::<(), velesdb_core::Error>(())
33/// ```
34#[derive(Clone)]
35pub struct MetadataCollection {
36    /// Single source of truth — all operations delegate here (C-02 pure newtype).
37    pub(crate) inner: Collection,
38}
39
40impl MetadataCollection {
41    // -------------------------------------------------------------------------
42    // Lifecycle
43    // -------------------------------------------------------------------------
44
45    /// Creates a new `MetadataCollection`.
46    ///
47    /// # Errors
48    ///
49    /// Returns an error if the directory cannot be created or storage fails.
50    pub fn create(path: PathBuf, name: &str) -> Result<Self> {
51        Ok(Self {
52            inner: Collection::create_metadata_only(path, name)?,
53        })
54    }
55
56    /// Opens an existing `MetadataCollection` from disk.
57    ///
58    /// # Errors
59    ///
60    /// Returns an error if config or storage cannot be opened.
61    pub fn open(path: PathBuf) -> Result<Self> {
62        Ok(Self {
63            inner: Collection::open(path)?,
64        })
65    }
66
67    /// Consumes `self` and returns a [`VectorCollection`](super::VectorCollection)
68    /// **structural view** over this metadata collection's shared `inner` store.
69    ///
70    /// Exact mirror of
71    /// [`GraphCollection::into_vector_view`](super::GraphCollection::into_vector_view)
72    /// — see that method for the full contract (purely structural re-wrap, no
73    /// vector-kind assertion, Python-binding-only rationale).
74    #[must_use]
75    pub fn into_vector_view(self) -> super::VectorCollection {
76        super::VectorCollection { inner: self.inner }
77    }
78
79    /// Flushes to disk.
80    ///
81    /// Issue #423: This fast-path flush skips `vectors.idx` serialization.
82    /// The WAL provides crash recovery for the vector index.
83    ///
84    /// # Errors
85    ///
86    /// Returns an error if the flush fails.
87    pub fn flush(&self) -> Result<()> {
88        self.inner.flush()
89    }
90
91    /// Full durability flush including `vectors.idx` serialization.
92    ///
93    /// Issue #423: Use on graceful shutdown to avoid a full WAL replay
94    /// on the next startup.
95    ///
96    /// # Errors
97    ///
98    /// Returns an error if the flush fails.
99    pub fn flush_full(&self) -> Result<()> {
100        self.inner.flush_full()
101    }
102
103    // -------------------------------------------------------------------------
104    // Metadata
105    // -------------------------------------------------------------------------
106
107    /// Returns the collection name.
108    #[must_use]
109    pub fn name(&self) -> String {
110        self.inner.config().name
111    }
112
113    /// Returns the number of items in the collection.
114    #[must_use]
115    pub fn len(&self) -> usize {
116        self.inner.len()
117    }
118
119    /// Returns `true` if the collection is empty.
120    #[must_use]
121    pub fn is_empty(&self) -> bool {
122        self.inner.is_empty()
123    }
124
125    /// Returns the collection configuration.
126    #[must_use]
127    pub fn config(&self) -> crate::collection::CollectionConfig {
128        self.inner.config()
129    }
130
131    /// Returns `true` — metadata collections are always metadata-only.
132    #[must_use]
133    pub fn is_metadata_only(&self) -> bool {
134        true
135    }
136
137    /// Inserts or updates metadata-only points (convenience alias for `upsert`).
138    ///
139    /// # Errors
140    ///
141    /// Returns an error if a point carries a non-empty vector.
142    pub fn upsert_metadata(&self, points: impl IntoIterator<Item = Point>) -> Result<()> {
143        self.upsert(points)
144    }
145
146    /// Returns all stored IDs.
147    #[must_use]
148    pub fn all_ids(&self) -> Vec<u64> {
149        self.inner.all_ids()
150    }
151
152    /// Returns the next batch of points for scroll iteration.
153    ///
154    /// Delegates to the inner collection's `scroll_batch` (parallel
155    /// implementation to [`VectorCollection::scroll_batch`](crate::VectorCollection::scroll_batch)).
156    ///
157    /// # Errors
158    ///
159    /// Returns an error if `batch_size` is 0.
160    pub fn scroll_batch(
161        &self,
162        cursor: Option<u64>,
163        batch_size: usize,
164        filter: Option<&crate::filter::Filter>,
165    ) -> Result<crate::collection::ScrollBatch> {
166        self.inner.scroll_batch(cursor, batch_size, filter)
167    }
168
169    // -------------------------------------------------------------------------
170    // CRUD
171    // -------------------------------------------------------------------------
172
173    /// Inserts or updates metadata points (must have no vector).
174    ///
175    /// # Errors
176    ///
177    /// Returns an error if a point carries a non-empty vector,
178    /// or if storage operations fail.
179    pub fn upsert(&self, points: impl IntoIterator<Item = Point>) -> Result<()> {
180        let points: Vec<Point> = points.into_iter().collect();
181        let name = self.inner.config().name;
182
183        for point in &points {
184            if !point.vector.is_empty() {
185                return Err(Error::VectorNotAllowed(name.clone()));
186            }
187        }
188
189        self.inner.upsert_metadata(points)
190    }
191
192    /// Retrieves items by IDs.
193    #[must_use]
194    pub fn get(&self, ids: &[u64]) -> Vec<Option<Point>> {
195        self.inner.get(ids)
196    }
197
198    /// Deletes items by IDs.
199    ///
200    /// # Errors
201    ///
202    /// Returns an error if storage operations fail.
203    pub fn delete(&self, ids: &[u64]) -> Result<()> {
204        self.inner.delete(ids)
205    }
206
207    // -------------------------------------------------------------------------
208    // Text search
209    // -------------------------------------------------------------------------
210
211    /// Performs BM25 full-text search over payloads.
212    ///
213    /// # Errors
214    ///
215    /// Returns an error if storage retrieval fails.
216    pub fn text_search(&self, query: &str, k: usize) -> Result<Vec<SearchResult>> {
217        self.inner.text_search(query, k)
218    }
219
220    /// Performs vector similarity search.
221    ///
222    /// Note: metadata-only collections have no vectors, so this will
223    /// return an empty result set.
224    ///
225    /// # Errors
226    ///
227    /// Returns an error if the search fails.
228    pub fn search(&self, query: &[f32], k: usize) -> Result<Vec<SearchResult>> {
229        self.inner.search(query, k)
230    }
231
232    // -------------------------------------------------------------------------
233    // VelesQL
234    // -------------------------------------------------------------------------
235
236    /// Executes a `VelesQL` query.
237    ///
238    /// # Errors
239    ///
240    /// Returns an error if the query is invalid or execution fails.
241    pub fn execute_query(
242        &self,
243        query: &crate::velesql::Query,
244        params: &HashMap<String, serde_json::Value>,
245    ) -> Result<Vec<SearchResult>> {
246        self.inner.execute_query(query, params)
247    }
248
249    /// Executes a raw VelesQL string.
250    ///
251    /// # Errors
252    ///
253    /// Returns an error if parsing or execution fails.
254    pub fn execute_query_str(
255        &self,
256        sql: &str,
257        params: &HashMap<String, serde_json::Value>,
258    ) -> Result<Vec<SearchResult>> {
259        self.inner.execute_query_str(sql, params)
260    }
261}