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 ///
148 /// # Errors
149 ///
150 /// Returns an error if storage retrieval fails.
151 pub fn text_search(&self, query: &str, k: usize) -> Result<Vec<SearchResult>> {
152 self.inner.text_search(query, k)
153 }
154
155 // -------------------------------------------------------------------------
156 // VelesQL
157 // -------------------------------------------------------------------------
158
159 /// Executes a `VelesQL` query.
160 ///
161 /// # Errors
162 ///
163 /// Returns an error if the query is invalid or execution fails.
164 pub fn execute_query(
165 &self,
166 query: &crate::velesql::Query,
167 params: &HashMap<String, serde_json::Value>,
168 ) -> Result<Vec<SearchResult>> {
169 self.inner.execute_query(query, params)
170 }
171
172 /// Executes a raw VelesQL string.
173 ///
174 /// # Errors
175 ///
176 /// Returns an error if parsing or execution fails.
177 pub fn execute_query_str(
178 &self,
179 sql: &str,
180 params: &HashMap<String, serde_json::Value>,
181 ) -> Result<Vec<SearchResult>> {
182 self.inner.execute_query_str(sql, params)
183 }
184}