#[cfg(feature = "ladybug")]
pub mod ladybug_conn;
#[cfg(feature = "neo4j")]
pub mod neo4j_conn;
use crate::foundation::DbResult;
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct GraphNode {
pub label: String,
pub properties: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct GraphRel {
pub rel_type: String,
pub src_id: i64,
pub dst_id: i64,
pub properties: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum GraphValue {
Node(GraphNode),
Rel(GraphRel),
Path(Vec<GraphNode>),
Scalar(serde_json::Value),
}
#[derive(Debug, Clone, PartialEq)]
pub struct GraphRow {
pub columns: Vec<(String, GraphValue)>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct GraphQueryResult {
pub rows: Vec<GraphRow>,
pub rows_affected: usize,
}
#[derive(Debug, Clone, PartialEq)]
pub enum GraphExecResult {
Query(GraphQueryResult),
Write {
rows_affected: usize,
},
}
#[async_trait]
pub trait GraphConnection: Send + Sync {
async fn execute_cypher(&self, cypher: &str) -> DbResult<GraphExecResult>;
async fn health_check(&self) -> DbResult<()>;
async fn begin_graph_txn(&self) -> DbResult<Box<dyn GraphTransaction + Send>>;
fn backend_name(&self) -> &'static str;
}
#[async_trait]
pub trait GraphTransaction: Send {
async fn commit(self: Box<Self>) -> DbResult<()>;
async fn rollback(self: Box<Self>) -> DbResult<()>;
async fn execute_cypher(&self, cypher: &str) -> DbResult<GraphExecResult>;
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_graph_node_serde_roundtrip() {
let node = GraphNode {
label: "Person".to_string(),
properties: json!({"name": "Alice", "age": 30}),
};
let json_str = serde_json::to_string(&node).expect("serialize should succeed");
let restored: GraphNode = serde_json::from_str(&json_str).expect("deserialize should succeed");
assert_eq!(node, restored);
}
#[test]
fn test_graph_node_empty_properties() {
let node = GraphNode {
label: "Empty".to_string(),
properties: json!({}),
};
let json_str = serde_json::to_string(&node).expect("serialize should succeed");
let restored: GraphNode = serde_json::from_str(&json_str).expect("deserialize should succeed");
assert_eq!(node, restored);
}
#[test]
fn test_graph_rel_serde_roundtrip() {
let rel = GraphRel {
rel_type: "KNOWS".to_string(),
src_id: 1,
dst_id: 2,
properties: json!({"since": "2024-01-01"}),
};
let json_str = serde_json::to_string(&rel).expect("serialize should succeed");
let restored: GraphRel = serde_json::from_str(&json_str).expect("deserialize should succeed");
assert_eq!(rel, restored);
}
#[test]
fn test_graph_rel_negative_ids() {
let rel = GraphRel {
rel_type: "BLOCKS".to_string(),
src_id: -1,
dst_id: -2,
properties: json!(null),
};
let json_str = serde_json::to_string(&rel).expect("serialize should succeed");
let restored: GraphRel = serde_json::from_str(&json_str).expect("deserialize should succeed");
assert_eq!(rel, restored);
}
#[test]
fn test_graph_value_node_variant() {
let val = GraphValue::Node(GraphNode {
label: "Movie".to_string(),
properties: json!({"title": "Inception"}),
});
let json_str = serde_json::to_string(&val).expect("serialize should succeed");
let restored: GraphValue = serde_json::from_str(&json_str).expect("deserialize should succeed");
assert_eq!(val, restored);
}
#[test]
fn test_graph_value_rel_variant() {
let val = GraphValue::Rel(GraphRel {
rel_type: "ACTED_IN".to_string(),
src_id: 10,
dst_id: 20,
properties: json!({"role": "Cobb"}),
});
let json_str = serde_json::to_string(&val).expect("serialize should succeed");
let restored: GraphValue = serde_json::from_str(&json_str).expect("deserialize should succeed");
assert_eq!(val, restored);
}
#[test]
fn test_graph_value_path_variant() {
let val = GraphValue::Path(vec![
GraphNode {
label: "A".to_string(),
properties: json!({}),
},
GraphNode {
label: "B".to_string(),
properties: json!({}),
},
]);
let json_str = serde_json::to_string(&val).expect("serialize should succeed");
let restored: GraphValue = serde_json::from_str(&json_str).expect("deserialize should succeed");
assert_eq!(val, restored);
}
#[test]
fn test_graph_value_scalar_variant() {
let val = GraphValue::Scalar(json!(42));
let json_str = serde_json::to_string(&val).expect("serialize should succeed");
let restored: GraphValue = serde_json::from_str(&json_str).expect("deserialize should succeed");
assert_eq!(val, restored);
}
#[test]
fn test_graph_value_scalar_string() {
let val = GraphValue::Scalar(json!("hello"));
let json_str = serde_json::to_string(&val).expect("serialize should succeed");
let restored: GraphValue = serde_json::from_str(&json_str).expect("deserialize should succeed");
assert_eq!(val, restored);
}
#[test]
fn test_graph_value_scalar_null() {
let val = GraphValue::Scalar(json!(null));
let json_str = serde_json::to_string(&val).expect("serialize should succeed");
let restored: GraphValue = serde_json::from_str(&json_str).expect("deserialize should succeed");
assert_eq!(val, restored);
}
#[test]
fn test_graph_value_empty_path() {
let val = GraphValue::Path(vec![]);
let json_str = serde_json::to_string(&val).expect("serialize should succeed");
let restored: GraphValue = serde_json::from_str(&json_str).expect("deserialize should succeed");
assert_eq!(val, restored);
}
#[test]
fn test_graph_row_construction() {
let row = GraphRow {
columns: vec![
("n".to_string(), GraphValue::Scalar(json!(1))),
("name".to_string(), GraphValue::Scalar(json!("Alice"))),
],
};
assert_eq!(row.columns.len(), 2);
assert_eq!(row.columns[0].0, "n");
assert_eq!(row.columns[1].0, "name");
}
#[test]
fn test_graph_row_empty() {
let row = GraphRow { columns: vec![] };
assert!(row.columns.is_empty());
}
#[test]
fn test_graph_query_result_construction() {
let result = GraphQueryResult {
rows: vec![GraphRow {
columns: vec![("count".to_string(), GraphValue::Scalar(json!(5)))],
}],
rows_affected: 0,
};
assert_eq!(result.rows.len(), 1);
assert_eq!(result.rows_affected, 0);
}
#[test]
fn test_graph_query_result_empty() {
let result = GraphQueryResult {
rows: vec![],
rows_affected: 0,
};
assert!(result.rows.is_empty());
}
#[test]
fn test_graph_exec_result_query_variant() {
let result = GraphExecResult::Query(GraphQueryResult {
rows: vec![GraphRow {
columns: vec![("n".to_string(), GraphValue::Scalar(json!(1)))],
}],
rows_affected: 0,
});
match result {
GraphExecResult::Query(q) => {
assert_eq!(q.rows.len(), 1);
assert_eq!(q.rows_affected, 0);
}
GraphExecResult::Write { .. } => panic!("expected Query variant"),
}
}
#[test]
fn test_graph_exec_result_write_variant() {
let result = GraphExecResult::Write { rows_affected: 42 };
match result {
GraphExecResult::Query(_) => panic!("expected Write variant"),
GraphExecResult::Write { rows_affected } => {
assert_eq!(rows_affected, 42);
}
}
}
}