Skip to main content

velesdb_core/database/
graph_ops.rs

1//! Graph collection creation and retrieval operations.
2
3use crate::collection::GraphCollection;
4use crate::{CollectionType, DistanceMetric, Result};
5
6use super::Database;
7
8impl Database {
9    /// Creates a new graph collection.
10    ///
11    /// # Errors
12    ///
13    /// Returns an error if a collection with the same name already exists.
14    #[allow(clippy::needless_pass_by_value)] // Public API — changing to &ref would be breaking.
15    pub fn create_graph_collection(
16        &self,
17        name: &str,
18        schema: crate::collection::GraphSchema,
19    ) -> Result<()> {
20        self.ensure_collection_name_available(name)?;
21        let path = self.data_dir.join(name);
22        let coll =
23            GraphCollection::create(path, name, None, DistanceMetric::Cosine, schema.clone())?;
24        self.register_graph_collection(name, &coll, None, DistanceMetric::Cosine, &schema);
25        Ok(())
26    }
27
28    /// Creates a new graph collection with node embeddings.
29    ///
30    /// Unlike [`create_graph_collection`](Self::create_graph_collection), this
31    /// variant configures a vector dimension and distance metric so that nodes
32    /// can store embeddings and support similarity search.
33    ///
34    /// The node-embedding index takes its parameters from the configured
35    /// `[hnsw]` section on top of the auto-tuned defaults — see
36    /// [`Database::resolve_hnsw_params`].
37    ///
38    /// # Errors
39    ///
40    /// Returns an error if a collection with the same name already exists.
41    #[allow(clippy::needless_pass_by_value)] // Public API — changing to &ref would be breaking.
42    pub fn create_graph_collection_with_embeddings(
43        &self,
44        name: &str,
45        schema: crate::collection::GraphSchema,
46        dimension: usize,
47        metric: DistanceMetric,
48    ) -> Result<()> {
49        self.ensure_collection_name_available(name)?;
50        self.enforce_vector_dimension_limit(dimension)?;
51        let path = self.data_dir.join(name);
52        // #2087: node embeddings mean a real HNSW index, so the `[hnsw]`
53        // section applies here exactly as it does to a vector collection.
54        let params = self.resolve_hnsw_params(dimension, None, None);
55        let coll = GraphCollection::create_with_hnsw_params(
56            path,
57            name,
58            Some(dimension),
59            metric,
60            schema.clone(),
61            params,
62        )?;
63        self.register_graph_collection(name, &coll, Some(dimension), metric, &schema);
64        Ok(())
65    }
66
67    /// Internal helper for `create_collection_typed` with `Graph` variant.
68    pub(super) fn create_graph_collection_from_type(
69        &self,
70        name: &str,
71        dimension: Option<usize>,
72        metric: DistanceMetric,
73        schema: &crate::collection::GraphSchema,
74    ) -> Result<()> {
75        self.ensure_collection_name_available(name)?;
76        if let Some(d) = dimension {
77            self.enforce_vector_dimension_limit(d)?;
78        }
79        let path = self.data_dir.join(name);
80        // #2087: only a graph collection that carries embeddings has an HNSW
81        // index to configure; without a dimension there is nothing to apply.
82        let params = dimension.and_then(|d| self.resolve_hnsw_params(d, None, None));
83        let coll = GraphCollection::create_with_hnsw_params(
84            path,
85            name,
86            dimension,
87            metric,
88            schema.clone(),
89            params,
90        )?;
91        self.register_graph_collection(name, &coll, dimension, metric, schema);
92        Ok(())
93    }
94
95    /// Registers a graph collection in the typed registry,
96    /// notifies the observer, and bumps the schema version.
97    fn register_graph_collection(
98        &self,
99        name: &str,
100        coll: &GraphCollection,
101        dimension: Option<usize>,
102        metric: DistanceMetric,
103        schema: &crate::collection::GraphSchema,
104    ) {
105        // Parity item E: thread the live LimitsConfig caps into the collection.
106        self.push_runtime_limits(&coll.inner);
107
108        self.graph_colls
109            .write()
110            .insert(name.to_string(), coll.clone());
111
112        if let Some(ref obs) = self.observer {
113            let kind = CollectionType::Graph {
114                dimension,
115                metric,
116                schema: schema.clone(),
117            };
118            obs.on_collection_created(name, &kind);
119        }
120
121        self.schema_version
122            .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
123    }
124
125    /// Returns a `GraphCollection` by name.
126    ///
127    /// Checks the typed registry first.  Falls back to opening from disk if the
128    /// collection was not registered in-memory (e.g. after a restart or when
129    /// the collection was auto-created by a graph handler).  The instance is
130    /// cached into the registry so subsequent calls are free.
131    ///
132    /// Returns `None` if the collection does not exist on disk.
133    #[must_use]
134    pub fn get_graph_collection(&self, name: &str) -> Option<GraphCollection> {
135        // Bound before the `if let` — see `get_vector_collection`: the disk
136        // fallback takes `graph_colls` for WRITE.
137        let cached = self.graph_colls.read().get(name).cloned();
138        if let Some(c) = cached {
139            return Some(c);
140        }
141        self.open_graph_collection_from_disk(name)
142    }
143
144    /// Disk fallback for `get_graph_collection`.
145    fn open_graph_collection_from_disk(&self, name: &str) -> Option<GraphCollection> {
146        let cfg = self.read_collection_config(name)?;
147        cfg.graph_schema.as_ref()?;
148        let coll = GraphCollection::open(self.data_dir.join(name)).ok()?;
149        // Parity item E: re-push runtime limits on disk-open (not persisted).
150        self.push_runtime_limits(&coll.inner);
151        self.graph_colls
152            .write()
153            .insert(name.to_string(), coll.clone());
154        Some(coll)
155    }
156}