Skip to main content

sz_orm_graph/
query.rs

1//! # Query — Cypher 查询构造与执行
2//!
3//! CypherQuery + CypherQueryBuilder + GraphResult
4
5use crate::error::GraphError;
6use std::collections::HashMap;
7
8/// Cypher 查询
9#[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
35/// Cypher 查询构造器(链式)
36pub 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/// 图节点
58#[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/// 图关系
66#[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/// 图路径
76#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
77pub struct GraphPath {
78    pub nodes: Vec<GraphNode>,
79    pub relationships: Vec<GraphRelationship>,
80}
81
82/// 图查询结果
83#[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
115/// 查询执行器
116pub 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}