1use crate::error::GraphError;
6use std::collections::HashMap;
7
8#[derive(Debug, Clone)]
10pub struct CypherQuery {
11 pub cypher: String,
12 pub parameters: HashMap<String, serde_json::Value>,
13}
14
15impl CypherQuery {
16 pub fn new(cypher: &str) -> Self {
17 Self {
18 cypher: cypher.to_string(),
19 parameters: HashMap::new(),
20 }
21 }
22
23 pub fn with_params(cypher: &str, params: HashMap<String, serde_json::Value>) -> Self {
24 Self {
25 cypher: cypher.to_string(),
26 parameters: params,
27 }
28 }
29
30 pub fn add_param(&mut self, key: &str, value: serde_json::Value) {
31 self.parameters.insert(key.to_string(), value);
32 }
33}
34
35pub struct CypherQueryBuilder {
37 query: CypherQuery,
38}
39
40impl CypherQueryBuilder {
41 pub fn new(cypher: &str) -> Self {
42 Self {
43 query: CypherQuery::new(cypher),
44 }
45 }
46
47 pub fn param(mut self, key: &str, value: impl Into<serde_json::Value>) -> Self {
48 self.query.add_param(key, value.into());
49 self
50 }
51
52 pub fn build(self) -> CypherQuery {
53 self.query
54 }
55}
56
57#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
59pub struct GraphNode {
60 pub id: String,
61 pub labels: Vec<String>,
62 pub properties: serde_json::Value,
63}
64
65#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
67pub struct GraphRelationship {
68 pub id: String,
69 pub rel_type: String,
70 pub start_node_id: String,
71 pub end_node_id: String,
72 pub properties: serde_json::Value,
73}
74
75#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
77pub struct GraphPath {
78 pub nodes: Vec<GraphNode>,
79 pub relationships: Vec<GraphRelationship>,
80}
81
82#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
84#[serde(tag = "type")]
85pub enum GraphResult {
86 Node { node: GraphNode },
87 Relationship { relationship: GraphRelationship },
88 Path { path: GraphPath },
89 Scalar { value: serde_json::Value },
90}
91
92impl GraphResult {
93 pub fn as_node(&self) -> Option<&GraphNode> {
94 match self {
95 GraphResult::Node { node } => Some(node),
96 _ => None,
97 }
98 }
99
100 pub fn as_relationship(&self) -> Option<&GraphRelationship> {
101 match self {
102 GraphResult::Relationship { relationship } => Some(relationship),
103 _ => None,
104 }
105 }
106
107 pub fn as_scalar(&self) -> Option<&serde_json::Value> {
108 match self {
109 GraphResult::Scalar { value } => Some(value),
110 _ => None,
111 }
112 }
113}
114
115pub async fn execute_query(
117 conn: &crate::connection::GraphConnection,
118 query: &CypherQuery,
119) -> Result<Vec<GraphResult>, GraphError> {
120 if !conn.is_connected() {
121 return Err(GraphError::ConnectionError("not connected".into()));
122 }
123 if query.cypher.is_empty() {
124 return Err(GraphError::QueryError("empty query".into()));
125 }
126 crate::validator::CypherValidator::validate(query)?;
127 let engine = conn
128 .engine()
129 .ok_or_else(|| GraphError::ConnectionError("engine not initialized".into()))?;
130 engine.execute(query)
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136 use crate::connection::{GraphConfig, GraphConnection};
137 use std::collections::HashMap;
138
139 #[test]
140 fn test_cypher_query_new() {
141 let q = CypherQuery::new("MATCH (n:Person) RETURN n");
142 assert_eq!(q.cypher, "MATCH (n:Person) RETURN n");
143 assert!(q.parameters.is_empty());
144 }
145
146 #[test]
147 fn test_cypher_query_with_params() {
148 let mut params = HashMap::new();
149 params.insert("name".into(), serde_json::json!("Alice"));
150 let q = CypherQuery::with_params("MATCH (n {name: $name}) RETURN n", params);
151 assert_eq!(q.parameters.len(), 1);
152 assert_eq!(q.parameters["name"], serde_json::json!("Alice"));
153 }
154
155 #[test]
156 fn test_cypher_query_add_param() {
157 let mut q = CypherQuery::new("MATCH (n) RETURN n");
158 assert!(q.parameters.is_empty());
159 q.add_param("age", serde_json::json!(30));
160 q.add_param("city", serde_json::json!("Beijing"));
161 assert_eq!(q.parameters.len(), 2);
162 assert_eq!(q.parameters["age"], serde_json::json!(30));
163 }
164
165 #[test]
166 fn test_cypher_query_builder_chain() {
167 let q = CypherQueryBuilder::new("MATCH (n {name: $name, age: $age}) RETURN n")
168 .param("name", "Alice")
169 .param("age", 30i64)
170 .build();
171 assert_eq!(q.parameters.len(), 2);
172 assert_eq!(q.parameters["name"], serde_json::json!("Alice"));
173 assert_eq!(q.parameters["age"], serde_json::json!(30));
174 }
175
176 #[test]
177 fn test_graph_result_as_node() {
178 let node = GraphNode {
179 id: "1".into(),
180 labels: vec!["Person".into()],
181 properties: serde_json::json!({}),
182 };
183 let result = GraphResult::Node { node };
184 assert!(result.as_node().is_some());
185 assert!(result.as_relationship().is_none());
186 assert!(result.as_scalar().is_none());
187 }
188
189 #[test]
190 fn test_graph_result_as_relationship_and_scalar() {
191 let rel = GraphRelationship {
192 id: "r1".into(),
193 rel_type: "KNOWS".into(),
194 start_node_id: "1".into(),
195 end_node_id: "2".into(),
196 properties: serde_json::json!({}),
197 };
198 let rel_result = GraphResult::Relationship { relationship: rel };
199 assert!(rel_result.as_relationship().is_some());
200 assert!(rel_result.as_node().is_none());
201
202 let scalar_result = GraphResult::Scalar {
203 value: serde_json::json!(42),
204 };
205 assert_eq!(scalar_result.as_scalar(), Some(&serde_json::json!(42)));
206 assert!(scalar_result.as_node().is_none());
207 }
208
209 #[tokio::test]
210 async fn test_execute_query_not_connected() {
211 let conn = GraphConnection::new(GraphConfig::new("bolt://localhost:7687"));
212 assert!(!conn.is_connected());
213 let q = CypherQuery::new("MATCH (n) RETURN n");
214 let result = execute_query(&conn, &q).await;
215 assert!(result.is_err());
216 }
217
218 #[tokio::test]
219 async fn test_execute_query_empty_cypher() {
220 let conn = GraphConnection::new(GraphConfig::new("bolt://localhost:7687"));
221 let q = CypherQuery::new("");
222 let result = execute_query(&conn, &q).await;
223 assert!(result.is_err());
224 }
225}