velesdb_core/collection/graph/node.rs
1//! Graph node and element types for knowledge graph storage.
2//!
3//! This module provides the core types for representing nodes in a knowledge graph:
4//! - `GraphNode`: A typed entity with properties and optional vector embedding
5//! - `Element`: An enum that unifies Points (vector data) and Nodes (graph entities)
6
7use crate::Point;
8use serde::{Deserialize, Serialize};
9use serde_json::Value;
10use std::collections::HashMap;
11
12/// A node in the knowledge graph.
13///
14/// Represents a typed entity with properties and an optional vector embedding.
15/// Nodes are distinct from Points in that they have a label (type) and structured
16/// properties, while Points are primarily vector data with metadata.
17///
18/// Note: this is a construction/value type. The query runtime stores node
19/// data as JSON payloads (`upsert_node_payload` / `INSERT NODE`) and MATCH
20/// evaluates against those payloads; no production query path consumes
21/// `GraphNode` instances directly.
22///
23/// # Example
24///
25/// ```rust,ignore
26/// use velesdb_core::collection::graph::GraphNode;
27/// use serde_json::json;
28/// use std::collections::HashMap;
29///
30/// let mut props = HashMap::new();
31/// props.insert("name".to_string(), json!("Alice"));
32/// props.insert("age".to_string(), json!(30));
33///
34/// let node = GraphNode::new(1, "Person")
35/// .with_properties(props)
36/// .with_vector(vec![0.1, 0.2, 0.3]);
37/// ```
38#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
39pub struct GraphNode {
40 id: u64,
41 label: String,
42 properties: HashMap<String, Value>,
43 #[serde(skip_serializing_if = "Option::is_none")]
44 vector: Option<Vec<f32>>,
45}
46
47impl GraphNode {
48 /// Creates a new graph node with the given ID and label.
49 #[must_use]
50 pub fn new(id: u64, label: &str) -> Self {
51 Self {
52 id,
53 label: label.to_string(),
54 properties: HashMap::new(),
55 vector: None,
56 }
57 }
58
59 /// Adds properties to this node (builder pattern).
60 #[must_use]
61 pub fn with_properties(mut self, properties: HashMap<String, Value>) -> Self {
62 self.properties = properties;
63 self
64 }
65
66 /// Adds a vector embedding to this node (builder pattern).
67 #[must_use]
68 pub fn with_vector(mut self, vector: Vec<f32>) -> Self {
69 self.vector = Some(vector);
70 self
71 }
72
73 /// Returns the node ID.
74 #[must_use]
75 pub fn id(&self) -> u64 {
76 self.id
77 }
78
79 /// Returns the node label (type).
80 #[must_use]
81 pub fn label(&self) -> &str {
82 &self.label
83 }
84
85 /// Returns all properties of this node.
86 #[must_use]
87 pub fn properties(&self) -> &HashMap<String, Value> {
88 &self.properties
89 }
90
91 /// Returns a specific property value, if it exists.
92 #[must_use]
93 pub fn property(&self, name: &str) -> Option<&Value> {
94 self.properties.get(name)
95 }
96
97 /// Returns the optional vector embedding.
98 #[must_use]
99 pub fn vector(&self) -> Option<&Vec<f32>> {
100 self.vector.as_ref()
101 }
102
103 /// Sets a property value.
104 pub fn set_property(&mut self, name: &str, value: Value) {
105 self.properties.insert(name.to_string(), value);
106 }
107}
108
109/// A unified element that can be either a Point or a Node.
110///
111/// This enum allows storing both vector data (Points) and graph entities (Nodes)
112/// in the same collection, enabling hybrid graph+vector storage.
113#[derive(Debug, Clone, Serialize, Deserialize)]
114#[serde(tag = "type", rename_all = "lowercase")]
115#[allow(dead_code)] // Scaffolded for hybrid graph+vector storage
116pub(crate) enum Element {
117 /// A vector point with optional metadata.
118 Point(Point),
119 /// A graph node with label, properties, and optional vector.
120 Node(GraphNode),
121}
122
123#[allow(dead_code)] // Scaffolded for hybrid graph+vector storage
124impl Element {
125 /// Returns the element ID.
126 #[must_use]
127 pub fn id(&self) -> u64 {
128 match self {
129 Self::Point(p) => p.id,
130 Self::Node(n) => n.id(),
131 }
132 }
133
134 /// Returns true if this is a Point.
135 #[must_use]
136 pub fn is_point(&self) -> bool {
137 matches!(self, Self::Point(_))
138 }
139
140 /// Returns true if this is a Node.
141 #[must_use]
142 pub fn is_node(&self) -> bool {
143 matches!(self, Self::Node(_))
144 }
145
146 /// Returns the inner Point if this is a Point.
147 #[must_use]
148 pub fn as_point(&self) -> Option<&Point> {
149 match self {
150 Self::Point(p) => Some(p),
151 Self::Node(_) => None,
152 }
153 }
154
155 /// Returns the inner Node if this is a Node.
156 #[must_use]
157 pub fn as_node(&self) -> Option<&GraphNode> {
158 match self {
159 Self::Point(_) => None,
160 Self::Node(n) => Some(n),
161 }
162 }
163
164 /// Returns true if this element has a vector embedding.
165 ///
166 /// Points always have vectors. Nodes may or may not have vectors.
167 #[must_use]
168 pub fn has_vector(&self) -> bool {
169 match self {
170 Self::Point(_) => true,
171 Self::Node(n) => n.vector().is_some(),
172 }
173 }
174
175 /// Returns the vector embedding if available.
176 #[must_use]
177 pub fn vector(&self) -> Option<&Vec<f32>> {
178 match self {
179 Self::Point(p) => Some(&p.vector),
180 Self::Node(n) => n.vector(),
181 }
182 }
183}