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#[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#[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
54pub 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 pub fn is_open(&self) -> bool {
67 self.conn_id != 0
68 }
69
70 pub fn execute(&self, query: &str) -> Result<QueryResult> {
72 self.execute_with_options(query, None, None)
73 }
74
75 pub fn execute_with_options(
77 &self,
78 query: &str,
79 access_mode: Option<AccessMode>,
80 _parameters: Option<&HashMap<String, String>>, ) -> 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 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}