Skip to main content

neug_rust/
connection.rs

1use crate::error::{Error, Result};
2use crate::worker::WorkerClient;
3use neug_protocol::{RequestPayload, ResponsePayload};
4use std::collections::HashMap;
5use std::sync::Arc;
6
7const ACCESS_MODE_PREFIX: &str = "/*__NEUG_ACCESS_MODE__=";
8const ACCESS_MODE_SUFFIX: &str = "*/";
9
10/// Represents the access mode for a query.
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum AccessMode {
13    Read,
14    Insert,
15    Update,
16    Schema,
17}
18
19impl AccessMode {
20    pub fn as_str(&self) -> &'static str {
21        match self {
22            AccessMode::Read => "r",
23            AccessMode::Insert => "i",
24            AccessMode::Update => "u",
25            AccessMode::Schema => "s",
26        }
27    }
28}
29
30use std::fmt;
31
32/// Represents the result of a query.
33#[derive(Debug)]
34pub struct QueryResult {
35    result_string: String,
36}
37
38impl fmt::Display for QueryResult {
39    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40        write!(f, "{}", self.result_string)
41    }
42}
43
44fn encode_execute_query(query: &str, access_mode: Option<AccessMode>) -> String {
45    match access_mode {
46        Some(mode) => format!(
47            "{ACCESS_MODE_PREFIX}{}{ACCESS_MODE_SUFFIX}{query}",
48            mode.as_str()
49        ),
50        None => query.to_string(),
51    }
52}
53
54/// Represents a connection to the NeuG database.
55pub struct Connection {
56    conn_id: u64,
57    worker: Arc<WorkerClient>,
58}
59
60impl Connection {
61    pub(crate) fn new(conn_id: u64, worker: Arc<WorkerClient>) -> Self {
62        Self { conn_id, worker }
63    }
64
65    /// Checks if the connection is currently open.
66    pub fn is_open(&self) -> bool {
67        self.conn_id != 0
68    }
69
70    /// Executes a Cypher query on the database.
71    pub fn execute(&self, query: &str) -> Result<QueryResult> {
72        self.execute_with_options(query, None, None)
73    }
74
75    /// Executes a Cypher query with a specific access mode and parameters.
76    pub fn execute_with_options(
77        &self,
78        query: &str,
79        access_mode: Option<AccessMode>,
80        _parameters: Option<&HashMap<String, String>>, // Future: implement parameter mapping
81    ) -> Result<QueryResult> {
82        if !self.is_open() {
83            return Err(Error::ConnectionClosed);
84        }
85
86        let res = self.worker.send_request(RequestPayload::Execute {
87            conn_id: self.conn_id,
88            query: encode_execute_query(query, access_mode),
89        })?;
90
91        match res {
92            ResponsePayload::OkResult { result_string } => Ok(QueryResult { result_string }),
93            ResponsePayload::Error(msg) => Err(Error::ExecutionFailed(msg)),
94            _ => Err(Error::ExecutionFailed("Unexpected response".into())),
95        }
96    }
97
98    /// Closes the connection.
99    pub fn close(&mut self) {
100        if self.conn_id != 0 {
101            let _ = self.worker.send_request(RequestPayload::CloseConn {
102                conn_id: self.conn_id,
103            });
104            self.conn_id = 0;
105        }
106    }
107}
108
109impl Drop for Connection {
110    fn drop(&mut self) {
111        self.close();
112    }
113}
114
115#[cfg(test)]
116mod tests {
117    use super::{ACCESS_MODE_PREFIX, ACCESS_MODE_SUFFIX, AccessMode, encode_execute_query};
118
119    #[test]
120    fn execute_query_encoding_keeps_plain_queries_unchanged() {
121        assert_eq!(
122            encode_execute_query("MATCH (n) RETURN n", None),
123            "MATCH (n) RETURN n"
124        );
125    }
126
127    #[test]
128    fn execute_query_encoding_prefixes_access_mode() {
129        let encoded = encode_execute_query("RETURN 1", Some(AccessMode::Read));
130        assert_eq!(
131            encoded,
132            format!("{ACCESS_MODE_PREFIX}r{ACCESS_MODE_SUFFIX}RETURN 1")
133        );
134    }
135}