sz-orm-graph 0.1.0

Graph database support for sz-orm: Neo4j Cypher query, typed mapping, declarative modeling
Documentation
//! # Connection — Bolt 协议连接与连接池
//!
//! GraphConfig + GraphConnection + GraphPool

use crate::error::{sanitize_dsn, GraphError};
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::Mutex;

/// 图数据库连接配置
#[derive(Debug, Clone)]
pub struct GraphConfig {
    /// Bolt DSN,如 `neo4j://neo4j:password@127.0.0.1:7687`
    pub dsn: String,
    /// 连接超时(秒)
    pub connect_timeout_secs: u64,
    /// 查询超时(秒)
    pub query_timeout_secs: u64,
    /// 连接池最大大小
    pub max_pool_size: usize,
}

impl GraphConfig {
    pub fn new(dsn: &str) -> Self {
        Self {
            dsn: dsn.to_string(),
            connect_timeout_secs: 10,
            query_timeout_secs: 30,
            max_pool_size: 10,
        }
    }

    pub fn with_connect_timeout(mut self, secs: u64) -> Self {
        self.connect_timeout_secs = secs;
        self
    }

    pub fn with_query_timeout(mut self, secs: u64) -> Self {
        self.query_timeout_secs = secs;
        self
    }

    pub fn with_pool_size(mut self, size: usize) -> Self {
        self.max_pool_size = size;
        self
    }

    /// 脱敏的 DSN(不泄露密码)
    pub fn sanitized_dsn(&self) -> String {
        sanitize_dsn(&self.dsn)
    }
}

/// Bolt 连接句柄
pub struct GraphConnection {
    config: GraphConfig,
    connected: bool,
}

impl GraphConnection {
    pub fn new(config: GraphConfig) -> Self {
        Self {
            config,
            connected: false,
        }
    }

    pub fn config(&self) -> &GraphConfig {
        &self.config
    }

    pub fn is_connected(&self) -> bool {
        self.connected
    }

    pub fn connect(&mut self) -> Result<(), GraphError> {
        if self.config.dsn.is_empty() {
            return Err(GraphError::ConnectionError("empty DSN".into()));
        }
        if !self.config.dsn.starts_with("neo4j://") && !self.config.dsn.starts_with("bolt://") {
            return Err(GraphError::ConnectionError(format!(
                "invalid DSN scheme: {}",
                self.config.sanitized_dsn()
            )));
        }
        self.connected = true;
        Ok(())
    }

    pub fn disconnect(&mut self) {
        self.connected = false;
    }
}

/// 图数据库连接池
pub struct GraphPool {
    config: GraphConfig,
    connections: Arc<Mutex<Vec<GraphConnection>>>,
}

impl GraphPool {
    pub fn new(config: GraphConfig) -> Self {
        Self {
            config,
            connections: Arc::new(Mutex::new(Vec::new())),
        }
    }

    pub fn config(&self) -> &GraphConfig {
        &self.config
    }

    pub async fn acquire(&self) -> Result<GraphConnection, GraphError> {
        let mut conns = self.connections.lock().await;
        if let Some(conn) = conns.pop() {
            return Ok(conn);
        }
        if conns.len() >= self.config.max_pool_size {
            return Err(GraphError::ConnectionError(format!(
                "pool exhausted (max={}), DSN: {}",
                self.config.max_pool_size,
                self.config.sanitized_dsn()
            )));
        }
        let mut conn = GraphConnection::new(self.config.clone());
        conn.connect()?;
        Ok(conn)
    }

    pub async fn release(&self, conn: GraphConnection) {
        let mut conns = self.connections.lock().await;
        conns.push(conn);
    }

    pub async fn size(&self) -> usize {
        self.connections.lock().await.len()
    }
}

impl GraphConfig {
    pub fn connect_timeout(&self) -> Duration {
        Duration::from_secs(self.connect_timeout_secs)
    }

    pub fn query_timeout(&self) -> Duration {
        Duration::from_secs(self.query_timeout_secs)
    }
}