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