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    /// Flushes to disk.
68    ///
69    /// # Errors
70    ///
71    /// Returns an error if the flush fails.
72    pub fn flush(&self) -> Result<()> {
73        self.inner.flush()
74    }
75
76    // -------------------------------------------------------------------------
77    // Metadata
78    // -------------------------------------------------------------------------
79
80    /// Returns the collection name.
81    #[must_use]
82    pub fn name(&self) -> String {
83        self.inner.config().name
84    }
85
86    /// Returns the number of items in the collection.
87    #[must_use]
88    pub fn len(&self) -> usize {
89        self.inner.len()
90    }
91
92    /// Returns `true` if the collection is empty.
93    #[must_use]
94    pub fn is_empty(&self) -> bool {
95        self.inner.is_empty()
96    }
97
98    /// Returns all stored IDs.
99    #[must_use]
100    pub fn all_ids(&self) -> Vec<u64> {
101        self.inner.all_ids()
102    }
103
104    // -------------------------------------------------------------------------
105    // CRUD
106    // -------------------------------------------------------------------------
107
108    /// Inserts or updates metadata points (must have no vector).
109    ///
110    /// # Errors
111    ///
112    /// Returns an error if a point carries a non-empty vector,
113    /// or if storage operations fail.
114    pub fn upsert(&self, points: impl IntoIterator<Item = Point>) -> Result<()> {
115        let points: Vec<Point> = points.into_iter().collect();
116        let name = self.inner.config().name;
117
118        for point in &points {
119            if !point.vector.is_empty() {
120                return Err(Error::VectorNotAllowed(name.clone()));
121            }
122        }
123
124        self.inner.upsert_metadata(points)
125    }
126
127    /// Retrieves items by IDs.
128    #[must_use]
129    pub fn get(&self, ids: &[u64]) -> Vec<Option<Point>> {
130        self.inner.get(ids)
131    }
132
133    /// Deletes items by IDs.
134    ///
135    /// # Errors
136    ///
137    /// Returns an error if storage operations fail.
138    pub fn delete(&self, ids: &[u64]) -> Result<()> {
139        self.inner.delete(ids)
140    }
141
142    // -------------------------------------------------------------------------
143    // Text search
144    // -------------------------------------------------------------------------
145
146    /// Performs BM25 full-text search over payloads.
147    #[must_use]
148    pub fn text_search(&self, query: &str, k: usize) -> Vec<SearchResult> {
149        self.inner.text_search(query, k)
150    }
151
152    // -------------------------------------------------------------------------
153    // VelesQL
154    // -------------------------------------------------------------------------
155
156    /// Executes a `VelesQL` query.
157    ///
158    /// # Errors
159    ///
160    /// Returns an error if the query is invalid or execution fails.
161    pub fn execute_query(
162        &self,
163        query: &crate::velesql::Query,
164        params: &HashMap<String, serde_json::Value>,
165    ) -> Result<Vec<SearchResult>> {
166        self.inner.execute_query(query, params)
167    }
168
169    /// Executes a raw VelesQL string.
170    ///
171    /// # Errors
172    ///
173    /// Returns an error if parsing or execution fails.
174    pub fn execute_query_str(
175        &self,
176        sql: &str,
177        params: &HashMap<String, serde_json::Value>,
178    ) -> Result<Vec<SearchResult>> {
179        let query = self
180            .inner
181            .query_cache
182            .parse(sql)
183            .map_err(|e| Error::Query(e.to_string()))?;
184        self.inner.execute_query(&query, params)
185    }
186}