1use std::collections::HashMap;
7
8use tonic::Request;
9use tonic::transport::{Channel, Endpoint};
10
11use crate::client::{Column, Page};
12use crate::dsn::Dsn;
13use crate::error::{Error, Result};
14use crate::proto;
15use crate::proto::execution_response::Payload;
16use crate::proto::geode_service_client::GeodeServiceClient;
17use crate::types::Value;
18
19pub struct GrpcClient {
24 client: GeodeServiceClient<Channel>,
25 session_id: String,
26}
27
28impl GrpcClient {
29 pub async fn connect(dsn: &Dsn) -> Result<Self> {
48 let addr = if dsn.tls_enabled() {
49 format!("https://{}", dsn.address())
50 } else {
51 format!("http://{}", dsn.address())
52 };
53
54 let endpoint = Endpoint::from_shared(addr.clone())
55 .map_err(|e| Error::connection(format!("Invalid endpoint: {}", e)))?;
56
57 let endpoint = if dsn.tls_enabled() && dsn.skip_verify() {
59 endpoint
61 .tls_config(tonic::transport::ClientTlsConfig::new().with_enabled_roots())
62 .map_err(|e| Error::tls(format!("TLS config error: {}", e)))?
63 } else {
64 endpoint
65 };
66
67 let channel = endpoint
68 .connect()
69 .await
70 .map_err(|e| Error::connection(format!("gRPC connection failed to {}: {}", addr, e)))?;
71
72 let grpc_client = GeodeServiceClient::new(channel);
73
74 let mut client = Self {
75 client: grpc_client,
76 session_id: String::new(),
77 };
78
79 client
81 .handshake(dsn.username(), dsn.password(), dsn.graph())
82 .await?;
83
84 Ok(client)
85 }
86
87 async fn handshake(
89 &mut self,
90 username: Option<&str>,
91 password: Option<&str>,
92 graph: Option<&str>,
93 ) -> Result<()> {
94 let request = proto::HelloRequest {
95 username: username.unwrap_or("").to_string(),
96 password: password.unwrap_or("").to_string(),
97 tenant_id: None,
98 client_name: "geode-rust".to_string(),
99 client_version: crate::VERSION.to_string(),
100 wanted_conformance: "minimum".to_string(),
101 graph: graph.map(String::from),
102 role: None,
103 };
104
105 let response = self
106 .client
107 .handshake(Request::new(request))
108 .await
109 .map_err(|e| Error::connection(format!("Handshake failed: {}", e)))?;
110
111 let resp = response.into_inner();
112 if !resp.success {
113 return Err(Error::auth(resp.error_message));
114 }
115
116 self.session_id = resp.session_id;
117 Ok(())
118 }
119
120 pub async fn query(&mut self, gql: &str) -> Result<(Page, Option<String>)> {
122 self.query_with_params(gql, &HashMap::new()).await
123 }
124
125 pub async fn query_with_params(
127 &mut self,
128 gql: &str,
129 params: &HashMap<String, Value>,
130 ) -> Result<(Page, Option<String>)> {
131 let proto_params: Vec<proto::Param> = params
132 .iter()
133 .map(|(k, v)| proto::Param {
134 name: k.clone(),
135 value: Some(v.to_proto_value()),
136 })
137 .collect();
138
139 let request = proto::ExecuteRequest {
140 session_id: self.session_id.clone(),
141 query: gql.to_string(),
142 params: proto_params,
143 };
144
145 let response = self
146 .client
147 .execute(Request::new(request))
148 .await
149 .map_err(|e| Error::query(format!("Query execution failed: {}", e)))?;
150
151 let mut stream = response.into_inner();
153 let mut columns = Vec::new();
154 let mut rows = Vec::new();
155 let mut final_page = true;
156 let mut ordered = false;
157 let mut order_keys = Vec::new();
158
159 while let Some(exec_resp) = stream
160 .message()
161 .await
162 .map_err(|e| Error::query(format!("Failed to read response: {}", e)))?
163 {
164 if let Some(payload) = exec_resp.payload {
165 match payload {
166 Payload::Schema(schema) => {
167 columns = schema
168 .columns
169 .into_iter()
170 .map(|c| Column {
171 name: c.name,
172 col_type: c.r#type,
173 })
174 .collect();
175 }
176 Payload::Page(page) => {
177 for row in page.rows {
178 let mut row_map = HashMap::new();
179 for (i, col) in columns.iter().enumerate() {
180 let value = if i < row.values.len() {
181 crate::convert::proto_to_value(&row.values[i])
182 } else {
183 Value::null()
184 };
185 row_map.insert(col.name.clone(), value);
186 }
187 rows.push(row_map);
188 }
189 final_page = page.r#final;
190 ordered = page.ordered;
191 order_keys = page.order_keys;
192 }
193 Payload::Error(err) => {
194 return Err(Error::Query {
195 code: err.code,
196 message: err.message,
197 });
198 }
199 Payload::Metrics(_) | Payload::Heartbeat(_) => {
200 }
202 Payload::Explain(_) | Payload::Profile(_) => {
203 }
205 }
206 }
207 }
208
209 Ok((
210 Page {
211 columns,
212 rows,
213 ordered,
214 order_keys,
215 final_page,
216 },
217 None,
218 ))
219 }
220
221 pub async fn begin(&mut self) -> Result<()> {
223 let request = proto::BeginRequest {
224 read_only: false,
225 session_id: self.session_id.clone(),
226 };
227
228 self.client
229 .begin(Request::new(request))
230 .await
231 .map_err(|e| Error::connection(format!("Begin transaction failed: {}", e)))?;
232
233 Ok(())
234 }
235
236 pub async fn commit(&mut self) -> Result<()> {
238 let request = proto::CommitRequest {
239 session_id: self.session_id.clone(),
240 };
241
242 self.client
243 .commit(Request::new(request))
244 .await
245 .map_err(|e| Error::connection(format!("Commit failed: {}", e)))?;
246
247 Ok(())
248 }
249
250 pub async fn rollback(&mut self) -> Result<()> {
252 let request = proto::RollbackRequest {
253 session_id: self.session_id.clone(),
254 };
255
256 self.client
257 .rollback(Request::new(request))
258 .await
259 .map_err(|e| Error::connection(format!("Rollback failed: {}", e)))?;
260
261 Ok(())
262 }
263
264 pub async fn savepoint(&mut self, _name: &str) -> Result<()> {
269 Err(Error::connection(
270 "savepoint is not yet supported via gRPC transport",
271 ))
272 }
273
274 pub async fn rollback_to(&mut self, _name: &str) -> Result<()> {
279 Err(Error::connection(
280 "rollback_to is not yet supported via gRPC transport",
281 ))
282 }
283
284 pub async fn ping(&mut self) -> Result<bool> {
286 let response = self
287 .client
288 .ping(Request::new(proto::PingRequest {}))
289 .await
290 .map_err(|e| Error::connection(format!("Ping failed: {}", e)))?;
291
292 Ok(response.into_inner().ok)
293 }
294
295 pub fn close(&mut self) -> Result<()> {
297 Ok(())
299 }
300}
301
302#[cfg(test)]
303mod tests {
304 use crate::proto;
305
306 #[test]
307 fn test_convert_proto_value_string() {
308 let proto_val = proto::Value {
309 kind: Some(proto::value::Kind::StringVal(proto::StringValue {
310 value: "hello".to_string(),
311 kind: 0,
312 })),
313 };
314 let val = crate::convert::proto_to_value(&proto_val);
315 assert_eq!(val.as_string().unwrap(), "hello");
316 }
317
318 #[test]
319 fn test_convert_proto_value_int() {
320 let proto_val = proto::Value {
321 kind: Some(proto::value::Kind::IntVal(proto::IntValue {
322 value: 42,
323 kind: 0,
324 })),
325 };
326 let val = crate::convert::proto_to_value(&proto_val);
327 assert_eq!(val.as_int().unwrap(), 42);
328 }
329
330 #[test]
331 fn test_convert_proto_value_bool() {
332 let proto_val = proto::Value {
333 kind: Some(proto::value::Kind::BoolVal(true)),
334 };
335 let val = crate::convert::proto_to_value(&proto_val);
336 assert!(val.as_bool().unwrap());
337 }
338
339 #[test]
340 fn test_convert_proto_value_null() {
341 let proto_val = proto::Value {
342 kind: Some(proto::value::Kind::NullVal(proto::NullValue {})),
343 };
344 let val = crate::convert::proto_to_value(&proto_val);
345 assert!(val.is_null());
346 }
347
348 #[test]
349 fn test_convert_proto_value_none() {
350 let proto_val = proto::Value { kind: None };
351 let val = crate::convert::proto_to_value(&proto_val);
352 assert!(val.is_null());
353 }
354
355 #[test]
356 fn test_convert_proto_value_double() {
357 let proto_val = proto::Value {
358 kind: Some(proto::value::Kind::DoubleVal(proto::DoubleValue {
359 value: 3.15,
360 kind: 0,
361 })),
362 };
363 let val = crate::convert::proto_to_value(&proto_val);
364 assert!(val.as_decimal().is_ok());
365 }
366
367 #[test]
368 fn test_convert_proto_value_list() {
369 let proto_val = proto::Value {
370 kind: Some(proto::value::Kind::ListVal(proto::ListValue {
371 values: vec![
372 proto::Value {
373 kind: Some(proto::value::Kind::IntVal(proto::IntValue {
374 value: 1,
375 kind: 0,
376 })),
377 },
378 proto::Value {
379 kind: Some(proto::value::Kind::IntVal(proto::IntValue {
380 value: 2,
381 kind: 0,
382 })),
383 },
384 ],
385 })),
386 };
387 let val = crate::convert::proto_to_value(&proto_val);
388 let arr = val.as_array().unwrap();
389 assert_eq!(arr.len(), 2);
390 assert_eq!(arr[0].as_int().unwrap(), 1);
391 assert_eq!(arr[1].as_int().unwrap(), 2);
392 }
393
394 #[test]
395 fn test_convert_proto_value_map() {
396 let proto_val = proto::Value {
397 kind: Some(proto::value::Kind::MapVal(proto::MapValue {
398 entries: vec![proto::MapEntry {
399 key: "name".to_string(),
400 value: Some(proto::Value {
401 kind: Some(proto::value::Kind::StringVal(proto::StringValue {
402 value: "Alice".to_string(),
403 kind: 0,
404 })),
405 }),
406 }],
407 })),
408 };
409 let val = crate::convert::proto_to_value(&proto_val);
410 let obj = val.as_object().unwrap();
411 assert_eq!(obj.get("name").unwrap().as_string().unwrap(), "Alice");
412 }
413}