use std::collections::HashMap;
use std::path::PathBuf;
use crate::collection::graph::{GraphEdge, GraphSchema, TraversalConfig, TraversalResult};
use crate::collection::types::Collection;
use crate::distance::DistanceMetric;
use crate::error::Result;
use crate::point::{Point, SearchResult};
#[derive(Clone)]
pub struct GraphCollection {
pub(crate) inner: Collection,
}
impl GraphCollection {
pub fn create(
path: PathBuf,
name: &str,
dimension: Option<usize>,
metric: DistanceMetric,
schema: GraphSchema,
) -> Result<Self> {
Ok(Self {
inner: Collection::create_graph_collection(path, name, schema, dimension, metric)?,
})
}
pub fn open(path: PathBuf) -> Result<Self> {
Ok(Self {
inner: Collection::open(path)?,
})
}
pub fn flush(&self) -> Result<()> {
self.inner.flush()
}
pub fn flush_full(&self) -> Result<()> {
self.inner.flush_full()
}
#[must_use]
pub fn name(&self) -> String {
self.inner.config().name
}
#[must_use]
pub fn schema(&self) -> GraphSchema {
self.inner
.graph_schema()
.unwrap_or_else(GraphSchema::schemaless)
}
#[must_use]
pub fn has_embeddings(&self) -> bool {
self.inner.has_embeddings()
}
pub fn add_edge(&self, edge: GraphEdge) -> Result<()> {
self.inner.add_edge(edge)
}
#[must_use]
pub fn get_edges(&self, label: Option<&str>) -> Vec<GraphEdge> {
match label {
Some(lbl) => self.inner.get_edges_by_label(lbl),
None => self.inner.get_all_edges(),
}
}
#[must_use]
pub fn get_outgoing(&self, node_id: u64) -> Vec<GraphEdge> {
self.inner.get_outgoing_edges(node_id)
}
#[must_use]
pub fn get_incoming(&self, node_id: u64) -> Vec<GraphEdge> {
self.inner.get_incoming_edges(node_id)
}
#[must_use]
pub fn edge_count(&self) -> usize {
self.inner.edge_count()
}
#[must_use]
pub fn node_degree(&self, node_id: u64) -> (usize, usize) {
self.inner.get_node_degree(node_id)
}
#[must_use]
pub fn all_node_ids(&self) -> Vec<u64> {
self.inner.all_ids()
}
#[must_use]
pub fn len(&self) -> usize {
self.inner.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
#[must_use]
pub fn get(&self, ids: &[u64]) -> Vec<Option<Point>> {
self.inner.get(ids)
}
pub fn delete(&self, ids: &[u64]) -> Result<()> {
self.inner.delete(ids)
}
#[must_use]
pub fn remove_edge(&self, edge_id: u64) -> bool {
self.inner.remove_edge(edge_id)
}
#[must_use]
pub fn traverse_bfs(&self, source_id: u64, config: &TraversalConfig) -> Vec<TraversalResult> {
self.inner.traverse_bfs_config(source_id, config)
}
#[must_use]
pub fn traverse_dfs(&self, source_id: u64, config: &TraversalConfig) -> Vec<TraversalResult> {
self.inner.traverse_dfs_config(source_id, config)
}
pub fn upsert_node_payload(&self, node_id: u64, payload: &serde_json::Value) -> Result<()> {
self.inner.store_node_payload(node_id, payload)
}
#[deprecated(since = "1.6.0", note = "Use upsert_node_payload() instead")]
pub fn store_node_payload(&self, node_id: u64, payload: &serde_json::Value) -> Result<()> {
self.upsert_node_payload(node_id, payload)
}
pub fn get_node_payload(&self, node_id: u64) -> Result<Option<serde_json::Value>> {
self.inner.get_node_payload(node_id)
}
pub fn search_by_embedding(&self, query: &[f32], k: usize) -> Result<Vec<SearchResult>> {
self.inner.search_by_embedding(query, k)
}
pub fn search(&self, query: &[f32], k: usize) -> Result<Vec<SearchResult>> {
self.search_by_embedding(query, k)
}
pub fn execute_query(
&self,
query: &crate::velesql::Query,
params: &HashMap<String, serde_json::Value>,
) -> Result<Vec<SearchResult>> {
self.inner.execute_query(query, params)
}
pub fn execute_query_str(
&self,
sql: &str,
params: &HashMap<String, serde_json::Value>,
) -> Result<Vec<SearchResult>> {
self.inner.execute_query_str(sql, params)
}
pub fn execute_match(
&self,
match_clause: &crate::velesql::MatchClause,
params: &HashMap<String, serde_json::Value>,
) -> Result<Vec<crate::collection::search::query::match_exec::MatchResult>> {
self.inner.execute_match(match_clause, params)
}
pub fn execute_match_with_similarity(
&self,
match_clause: &crate::velesql::MatchClause,
query_vector: &[f32],
similarity_threshold: f32,
params: &HashMap<String, serde_json::Value>,
) -> Result<Vec<crate::collection::search::query::match_exec::MatchResult>> {
self.inner.execute_match_with_similarity(
match_clause,
query_vector,
similarity_threshold,
params,
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::collection::graph::GraphSchema;
use crate::distance::DistanceMetric;
use tempfile::tempdir;
#[test]
fn test_all_node_ids_returns_ids_with_payload() {
let dir = tempdir().unwrap();
let col = GraphCollection::create(
dir.path().to_path_buf(),
"kg",
None,
DistanceMetric::Cosine,
GraphSchema::schemaless(),
)
.unwrap();
col.upsert_node_payload(10, &serde_json::json!({"name": "Alice"}))
.unwrap();
col.upsert_node_payload(20, &serde_json::json!({"name": "Bob"}))
.unwrap();
let ids = col.all_node_ids();
assert!(ids.contains(&10), "node 10 should be present");
assert!(ids.contains(&20), "node 20 should be present");
assert_eq!(ids.len(), 2);
}
#[test]
fn test_edge_count_returns_correct_count() {
let dir = tempdir().unwrap();
let col = GraphCollection::create(
dir.path().to_path_buf(),
"kg",
None,
DistanceMetric::Cosine,
GraphSchema::schemaless(),
)
.unwrap();
assert_eq!(col.edge_count(), 0);
let edge1 = crate::collection::graph::GraphEdge::new(1, 10, 20, "knows").unwrap();
col.add_edge(edge1).unwrap();
assert_eq!(col.edge_count(), 1);
let edge2 = crate::collection::graph::GraphEdge::new(2, 20, 30, "likes").unwrap();
col.add_edge(edge2).unwrap();
assert_eq!(col.edge_count(), 2);
}
#[test]
fn test_execute_match_finds_edges() {
let dir = tempdir().unwrap();
let col = GraphCollection::create(
dir.path().to_path_buf(),
"kg",
None,
DistanceMetric::Cosine,
GraphSchema::schemaless(),
)
.unwrap();
col.upsert_node_payload(
10,
&serde_json::json!({"_labels": ["Person"], "name": "Alice"}),
)
.unwrap();
col.upsert_node_payload(
20,
&serde_json::json!({"_labels": ["Person"], "name": "Bob"}),
)
.unwrap();
let edge = crate::collection::graph::GraphEdge::new(1, 10, 20, "KNOWS").unwrap();
col.add_edge(edge).unwrap();
let match_clause = crate::velesql::MatchClause {
patterns: vec![crate::velesql::GraphPattern {
name: None,
nodes: vec![
crate::velesql::NodePattern::new().with_alias("a"),
crate::velesql::NodePattern::new().with_alias("b"),
],
relationships: vec![crate::velesql::RelationshipPattern::new(
crate::velesql::Direction::Outgoing,
)],
}],
where_clause: None,
return_clause: crate::velesql::ReturnClause {
items: vec![],
order_by: None,
limit: Some(10),
},
};
let params = HashMap::new();
let results = col.execute_match(&match_clause, ¶ms).unwrap();
assert!(
!results.is_empty(),
"execute_match should find the KNOWS edge"
);
assert_eq!(results[0].node_id, 20, "target should be Bob (id=20)");
}
}