1use typedb_driver::concept::Concept;
2use typedb_driver::TransactionType;
3
4use super::backend::{DriverBackend, QueryResultKind};
5use super::real_driver::RealTypeDBBackend;
6use crate::config::TypeDBSection;
7use crate::error::PipelineError;
8use crate::executor::QueryExecutor;
9
10pub struct TypeDBClient {
13 backend: Box<dyn DriverBackend>,
14}
15
16impl TypeDBClient {
17 #[cfg_attr(coverage_nightly, coverage(off))]
19 pub async fn connect(config: &TypeDBSection) -> Result<Self, PipelineError> {
20 let backend = RealTypeDBBackend::connect(config).await?;
21 Ok(Self {
22 backend: Box::new(backend),
23 })
24 }
25
26 #[cfg(test)]
28 pub(crate) fn with_backend(backend: Box<dyn DriverBackend>) -> Self {
29 Self { backend }
30 }
31
32 pub async fn execute(
37 &self,
38 database: &str,
39 typeql: &str,
40 tx_type: &str,
41 ) -> Result<serde_json::Value, PipelineError> {
42 let transaction_type = parse_transaction_type(tx_type)?;
43
44 let mut tx = self
45 .backend
46 .open_transaction(database, transaction_type)
47 .await?;
48
49 let answer = tx.query(typeql).await?;
50
51 let needs_commit =
52 matches!(transaction_type, TransactionType::Write | TransactionType::Schema);
53
54 let results = match answer {
55 QueryResultKind::Ok => {
56 if needs_commit {
57 tx.commit().await?;
58 }
59 serde_json::json!({ "ok": true })
60 }
61 QueryResultKind::Rows(rows) => {
62 if needs_commit {
63 tx.commit().await?;
64 }
65 serde_json::Value::Array(rows)
66 }
67 QueryResultKind::Documents(docs) => {
68 if needs_commit {
69 let _ = tx.commit().await;
70 }
71 serde_json::Value::Array(docs)
72 }
73 };
74
75 Ok(results)
76 }
77
78 pub fn is_connected(&self) -> bool {
80 self.backend.is_open()
81 }
82}
83
84impl QueryExecutor for TypeDBClient {
85 fn execute<'a>(
86 &'a self,
87 database: &'a str,
88 typeql: &'a str,
89 transaction_type: &'a str,
90 ) -> std::pin::Pin<
91 Box<
92 dyn std::future::Future<Output = Result<serde_json::Value, PipelineError>>
93 + Send
94 + 'a,
95 >,
96 > {
97 Box::pin(async move { self.execute(database, typeql, transaction_type).await })
98 }
99
100 fn is_connected(&self) -> bool {
101 self.is_connected()
102 }
103}
104
105pub(crate) fn parse_transaction_type(tx_type: &str) -> Result<TransactionType, PipelineError> {
107 match tx_type {
108 "read" => Ok(TransactionType::Read),
109 "write" => Ok(TransactionType::Write),
110 "schema" => Ok(TransactionType::Schema),
111 other => Err(PipelineError::QueryExecution(format!(
112 "Unknown transaction type: {other}"
113 ))),
114 }
115}
116
117pub(crate) fn concept_to_json(concept: &Concept) -> serde_json::Value {
119 let mut obj = serde_json::Map::new();
120
121 obj.insert(
122 "category".to_string(),
123 serde_json::Value::String(concept.get_category().name().to_string()),
124 );
125 obj.insert(
126 "label".to_string(),
127 serde_json::Value::String(concept.get_label().to_string()),
128 );
129
130 if let Some(iid) = concept.try_get_iid() {
131 obj.insert(
132 "iid".to_string(),
133 serde_json::Value::String(iid.to_string()),
134 );
135 }
136
137 if let Some(value) = concept.try_get_value() {
138 obj.insert("value".to_string(), value_to_json(value));
139 }
140
141 if let Some(value_type) = concept.try_get_value_type() {
142 obj.insert(
143 "value_type".to_string(),
144 serde_json::Value::String(value_type.name().to_string()),
145 );
146 }
147
148 serde_json::Value::Object(obj)
149}
150
151#[cfg_attr(coverage_nightly, coverage(off))]
153pub(crate) fn value_to_json(value: &typedb_driver::concept::Value) -> serde_json::Value {
154 if let Some(b) = value.get_boolean() {
155 return serde_json::Value::Bool(b);
156 }
157 if let Some(i) = value.get_integer() {
158 return serde_json::json!(i);
159 }
160 if let Some(d) = value.get_double() {
161 return serde_json::json!(d);
162 }
163 if let Some(s) = value.get_string() {
164 return serde_json::Value::String(s.to_string());
165 }
166 if let Some(date) = value.get_date() {
167 return serde_json::Value::String(date.to_string());
168 }
169 if let Some(dt) = value.get_datetime() {
170 return serde_json::Value::String(dt.to_string());
171 }
172 if let Some(dt_tz) = value.get_datetime_tz() {
173 return serde_json::Value::String(dt_tz.to_string());
174 }
175 if let Some(dec) = value.get_decimal() {
176 return serde_json::Value::String(dec.to_string());
177 }
178 if let Some(dur) = value.get_duration() {
179 return serde_json::Value::String(dur.to_string());
180 }
181 value_to_json_fallback(value)
183}
184
185#[cfg_attr(coverage_nightly, coverage(off))]
186fn value_to_json_fallback(value: &typedb_driver::concept::Value) -> serde_json::Value {
187 serde_json::Value::String(value.to_string())
188}
189
190#[cfg(test)]
191#[cfg_attr(coverage_nightly, coverage(off))]
192mod tests {
193 use std::future::Future;
194 use std::pin::Pin;
195 use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
196 use std::sync::Arc;
197
198 use typedb_driver::IID;
199 use typedb_driver::concept::{Attribute, AttributeType, Concept, Entity, EntityType, Value, ValueType};
200 use typedb_driver::concept::value::{Decimal, Duration, TimeZone};
201 use typedb_driver::TransactionType;
202
203 use super::*;
204 use crate::error::PipelineError;
205 use crate::typedb::backend::TransactionOps;
206
207 struct MockTransaction {
212 query_result: Option<QueryResultKind>,
213 query_error: Option<String>,
214 commit_error: Option<String>,
215 committed: Arc<AtomicBool>,
216 query_called: Arc<AtomicBool>,
217 }
218
219 impl MockTransaction {
220 fn new(result: QueryResultKind) -> Self {
221 Self {
222 query_result: Some(result),
223 query_error: None,
224 commit_error: None,
225 committed: Arc::new(AtomicBool::new(false)),
226 query_called: Arc::new(AtomicBool::new(false)),
227 }
228 }
229
230 fn failing_query(msg: &str) -> Self {
231 Self {
232 query_result: None,
233 query_error: Some(msg.to_string()),
234 commit_error: None,
235 committed: Arc::new(AtomicBool::new(false)),
236 query_called: Arc::new(AtomicBool::new(false)),
237 }
238 }
239
240 fn with_commit_error(mut self, msg: &str) -> Self {
241 self.commit_error = Some(msg.to_string());
242 self
243 }
244 }
245
246 impl TransactionOps for MockTransaction {
247 fn query(
248 &mut self,
249 _typeql: &str,
250 ) -> Pin<Box<dyn Future<Output = Result<QueryResultKind, PipelineError>> + Send + '_>>
251 {
252 self.query_called.store(true, Ordering::SeqCst);
253 let result = self.query_result.take();
254 let error = self.query_error.take();
255 Box::pin(async move {
256 if let Some(msg) = error {
257 return Err(PipelineError::QueryExecution(msg));
258 }
259 Ok(result.expect("MockTransaction::query called more than once"))
260 })
261 }
262
263 fn commit(
264 &mut self,
265 ) -> Pin<Box<dyn Future<Output = Result<(), PipelineError>> + Send + '_>> {
266 self.committed.store(true, Ordering::SeqCst);
267 let error = self.commit_error.take();
268 Box::pin(async move {
269 if let Some(msg) = error {
270 return Err(PipelineError::QueryExecution(msg));
271 }
272 Ok(())
273 })
274 }
275 }
276
277 struct MockBackend {
278 transaction: std::sync::Mutex<Option<MockTransaction>>,
279 open_error: Option<String>,
280 is_open: bool,
281 open_called: Arc<AtomicUsize>,
282 }
283
284 impl MockBackend {
285 fn new(tx: MockTransaction) -> Self {
286 Self {
287 transaction: std::sync::Mutex::new(Some(tx)),
288 open_error: None,
289 is_open: true,
290 open_called: Arc::new(AtomicUsize::new(0)),
291 }
292 }
293
294 fn failing(msg: &str) -> Self {
295 Self {
296 transaction: std::sync::Mutex::new(None),
297 open_error: Some(msg.to_string()),
298 is_open: true,
299 open_called: Arc::new(AtomicUsize::new(0)),
300 }
301 }
302 }
303
304 impl DriverBackend for MockBackend {
305 fn open_transaction(
306 &self,
307 _database: &str,
308 _tx_type: TransactionType,
309 ) -> Pin<
310 Box<
311 dyn Future<Output = Result<Box<dyn TransactionOps>, PipelineError>>
312 + Send
313 + '_,
314 >,
315 > {
316 self.open_called.fetch_add(1, Ordering::SeqCst);
317 let tx = self.transaction.lock().unwrap().take();
318 let error = self.open_error.clone();
319 Box::pin(async move {
320 if let Some(msg) = error {
321 return Err(PipelineError::QueryExecution(msg));
322 }
323 Ok(Box::new(tx.expect("MockBackend: no transaction configured"))
324 as Box<dyn TransactionOps>)
325 })
326 }
327
328 fn is_open(&self) -> bool {
329 self.is_open
330 }
331 }
332
333 fn make_client(backend: MockBackend) -> TypeDBClient {
334 TypeDBClient::with_backend(Box::new(backend))
335 }
336
337 #[test]
342 fn parse_transaction_type_read() {
343 let result = parse_transaction_type("read").unwrap();
344 assert_eq!(result, TransactionType::Read);
345 }
346
347 #[test]
348 fn parse_transaction_type_write() {
349 let result = parse_transaction_type("write").unwrap();
350 assert_eq!(result, TransactionType::Write);
351 }
352
353 #[test]
354 fn parse_transaction_type_schema() {
355 let result = parse_transaction_type("schema").unwrap();
356 assert_eq!(result, TransactionType::Schema);
357 }
358
359 #[test]
360 fn parse_transaction_type_unknown() {
361 let result = parse_transaction_type("unknown");
362 let err = result.unwrap_err();
363 assert!(matches!(&err, PipelineError::QueryExecution(msg) if msg.contains("Unknown transaction type: unknown")));
364 }
365
366 #[test]
367 fn parse_transaction_type_empty() {
368 let result = parse_transaction_type("");
369 assert!(result.is_err());
370 }
371
372 #[test]
373 fn parse_transaction_type_case_sensitive() {
374 let result = parse_transaction_type("Read");
375 assert!(result.is_err());
376 }
377
378 #[tokio::test]
383 async fn execute_ok_read_no_commit() {
384 let tx = MockTransaction::new(QueryResultKind::Ok);
385 let committed = tx.committed.clone();
386 let client = make_client(MockBackend::new(tx));
387
388 let result = client.execute("db", "match $x isa thing;", "read").await.unwrap();
389 assert_eq!(result, serde_json::json!({"ok": true}));
390 assert!(!committed.load(Ordering::SeqCst));
391 }
392
393 #[tokio::test]
394 async fn execute_ok_write_commits() {
395 let tx = MockTransaction::new(QueryResultKind::Ok);
396 let committed = tx.committed.clone();
397 let client = make_client(MockBackend::new(tx));
398
399 let result = client.execute("db", "insert $x isa thing;", "write").await.unwrap();
400 assert_eq!(result, serde_json::json!({"ok": true}));
401 assert!(committed.load(Ordering::SeqCst));
402 }
403
404 #[tokio::test]
405 async fn execute_ok_schema_commits() {
406 let tx = MockTransaction::new(QueryResultKind::Ok);
407 let committed = tx.committed.clone();
408 let client = make_client(MockBackend::new(tx));
409
410 let result = client.execute("db", "define entity thing;", "schema").await.unwrap();
411 assert_eq!(result, serde_json::json!({"ok": true}));
412 assert!(committed.load(Ordering::SeqCst));
413 }
414
415 #[tokio::test]
416 async fn execute_rows_read_no_commit() {
417 let rows = vec![serde_json::json!({"name": "Alice"}), serde_json::json!({"name": "Bob"})];
418 let tx = MockTransaction::new(QueryResultKind::Rows(rows.clone()));
419 let committed = tx.committed.clone();
420 let client = make_client(MockBackend::new(tx));
421
422 let result = client.execute("db", "match $p isa person;", "read").await.unwrap();
423 assert_eq!(result, serde_json::Value::Array(rows));
424 assert!(!committed.load(Ordering::SeqCst));
425 }
426
427 #[tokio::test]
428 async fn execute_rows_write_commits() {
429 let rows = vec![serde_json::json!({"id": 1})];
430 let tx = MockTransaction::new(QueryResultKind::Rows(rows));
431 let committed = tx.committed.clone();
432 let client = make_client(MockBackend::new(tx));
433
434 let result = client.execute("db", "insert $x isa thing;", "write").await.unwrap();
435 assert!(result.is_array());
436 assert!(committed.load(Ordering::SeqCst));
437 }
438
439 #[tokio::test]
440 async fn execute_rows_data_preserved() {
441 let rows = vec![
442 serde_json::json!({"name": "Alice", "age": 30}),
443 serde_json::json!({"name": "Bob", "age": 25}),
444 ];
445 let tx = MockTransaction::new(QueryResultKind::Rows(rows.clone()));
446 let client = make_client(MockBackend::new(tx));
447
448 let result = client.execute("db", "match $p isa person;", "read").await.unwrap();
449 let arr = result.as_array().unwrap();
450 assert_eq!(arr.len(), 2);
451 assert_eq!(arr[0]["name"], "Alice");
452 assert_eq!(arr[1]["age"], 25);
453 }
454
455 #[tokio::test]
456 async fn execute_docs_read_no_commit() {
457 let docs = vec![serde_json::json!({"doc": "data"})];
458 let tx = MockTransaction::new(QueryResultKind::Documents(docs.clone()));
459 let committed = tx.committed.clone();
460 let client = make_client(MockBackend::new(tx));
461
462 let result = client.execute("db", "match $p isa person; fetch {};", "read").await.unwrap();
463 assert_eq!(result, serde_json::Value::Array(docs));
464 assert!(!committed.load(Ordering::SeqCst));
465 }
466
467 #[tokio::test]
468 async fn execute_docs_write_commits() {
469 let docs = vec![serde_json::json!({"doc": "data"})];
470 let tx = MockTransaction::new(QueryResultKind::Documents(docs));
471 let committed = tx.committed.clone();
472 let client = make_client(MockBackend::new(tx));
473
474 let result = client.execute("db", "insert $x isa thing;", "write").await.unwrap();
475 assert!(result.is_array());
476 assert!(committed.load(Ordering::SeqCst));
477 }
478
479 #[tokio::test]
480 async fn execute_docs_commit_error_ignored() {
481 let docs = vec![serde_json::json!({"doc": "data"})];
482 let tx = MockTransaction::new(QueryResultKind::Documents(docs.clone()))
483 .with_commit_error("commit failed");
484 let client = make_client(MockBackend::new(tx));
485
486 let result = client.execute("db", "insert $x isa thing;", "write").await.unwrap();
488 assert_eq!(result, serde_json::Value::Array(docs));
489 }
490
491 #[tokio::test]
492 async fn execute_transaction_open_failure() {
493 let client = make_client(MockBackend::failing("connection refused"));
494
495 let result = client.execute("db", "match $x isa thing;", "read").await;
496 let err = result.unwrap_err();
497 assert!(matches!(&err, PipelineError::QueryExecution(msg) if msg.contains("connection refused")));
498 }
499
500 #[tokio::test]
501 async fn execute_query_failure() {
502 let tx = MockTransaction::failing_query("syntax error");
503 let client = make_client(MockBackend::new(tx));
504
505 let result = client.execute("db", "bad query", "read").await;
506 let err = result.unwrap_err();
507 assert!(matches!(&err, PipelineError::QueryExecution(msg) if msg.contains("syntax error")));
508 }
509
510 #[tokio::test]
511 async fn execute_commit_failure_ok_propagated() {
512 let tx = MockTransaction::new(QueryResultKind::Ok)
513 .with_commit_error("commit failed");
514 let client = make_client(MockBackend::new(tx));
515
516 let result = client.execute("db", "insert $x isa thing;", "write").await;
518 let err = result.unwrap_err();
519 assert!(matches!(&err, PipelineError::QueryExecution(msg) if msg.contains("commit failed")));
520 }
521
522 #[tokio::test]
523 async fn execute_commit_failure_rows_propagated() {
524 let tx = MockTransaction::new(QueryResultKind::Rows(vec![]))
525 .with_commit_error("commit failed");
526 let client = make_client(MockBackend::new(tx));
527
528 let result = client.execute("db", "insert $x isa thing;", "write").await;
530 let err = result.unwrap_err();
531 assert!(matches!(&err, PipelineError::QueryExecution(msg) if msg.contains("commit failed")));
532 }
533
534 #[tokio::test]
535 async fn execute_invalid_transaction_type() {
536 let tx = MockTransaction::new(QueryResultKind::Ok);
537 let backend = MockBackend::new(tx);
538 let open_called = backend.open_called.clone();
539 let client = make_client(backend);
540
541 let result = client.execute("db", "match $x;", "invalid").await;
542 assert!(result.is_err());
543 assert_eq!(open_called.load(Ordering::SeqCst), 0);
545 }
546
547 #[test]
548 fn is_connected_delegates_to_backend() {
549 let mut backend = MockBackend::new(MockTransaction::new(QueryResultKind::Ok));
550 backend.is_open = true;
551 let client = make_client(backend);
552 assert!(client.is_connected());
553 }
554
555 #[test]
556 fn is_connected_false_when_backend_closed() {
557 let mut backend = MockBackend::new(MockTransaction::new(QueryResultKind::Ok));
558 backend.is_open = false;
559 let client = make_client(backend);
560 assert!(!client.is_connected());
561 }
562
563 #[test]
568 fn value_to_json_boolean_true() {
569 let value = Value::Boolean(true);
570 let json = value_to_json(&value);
571 assert_eq!(json, serde_json::Value::Bool(true));
572 }
573
574 #[test]
575 fn value_to_json_boolean_false() {
576 let value = Value::Boolean(false);
577 let json = value_to_json(&value);
578 assert_eq!(json, serde_json::Value::Bool(false));
579 }
580
581 #[test]
582 fn value_to_json_integer() {
583 let value = Value::Integer(42);
584 let json = value_to_json(&value);
585 assert_eq!(json, serde_json::json!(42));
586 }
587
588 #[test]
589 fn value_to_json_integer_negative() {
590 let value = Value::Integer(-100);
591 let json = value_to_json(&value);
592 assert_eq!(json, serde_json::json!(-100));
593 }
594
595 #[test]
596 fn value_to_json_double() {
597 let value = Value::Double(3.15);
598 let json = value_to_json(&value);
599 assert_eq!(json, serde_json::json!(3.15));
600 }
601
602 #[test]
603 fn value_to_json_string() {
604 let value = Value::String("hello".to_string());
605 let json = value_to_json(&value);
606 assert_eq!(json, serde_json::Value::String("hello".to_string()));
607 }
608
609 #[test]
610 fn value_to_json_string_empty() {
611 let value = Value::String(String::new());
612 let json = value_to_json(&value);
613 assert_eq!(json, serde_json::Value::String(String::new()));
614 }
615
616 #[test]
617 fn value_to_json_date() {
618 let date = chrono::NaiveDate::from_ymd_opt(2024, 1, 15).unwrap();
619 let value = Value::Date(date);
620 let json = value_to_json(&value);
621 assert_eq!(json, serde_json::Value::String("2024-01-15".to_string()));
622 }
623
624 #[test]
625 fn value_to_json_datetime() {
626 let dt = chrono::NaiveDate::from_ymd_opt(2024, 1, 15)
627 .unwrap()
628 .and_hms_opt(10, 30, 0)
629 .unwrap();
630 let value = Value::Datetime(dt);
631 let json = value_to_json(&value);
632 let s = json.as_str().unwrap();
633 assert!(s.contains("2024-01-15"));
634 }
635
636 #[test]
637 fn value_to_json_decimal() {
638 let dec = Decimal::new(42, 0);
639 let value = Value::Decimal(dec);
640 let json = value_to_json(&value);
641 assert!(json.is_string());
642 }
643
644 #[test]
645 fn value_to_json_duration() {
646 let dur = Duration::new(1, 2, 3_000_000_000);
647 let value = Value::Duration(dur);
648 let json = value_to_json(&value);
649 assert!(json.is_string());
650 }
651
652 #[test]
653 fn value_to_json_datetime_tz() {
654 use chrono::TimeZone as _;
655 let tz = TimeZone::Fixed(chrono::FixedOffset::east_opt(3600).unwrap());
656 let dt = tz.with_ymd_and_hms(2024, 6, 15, 12, 30, 0).unwrap();
657 let value = Value::DatetimeTZ(dt);
658 let json = value_to_json(&value);
659 let s = json.as_str().unwrap();
660 assert!(s.contains("2024"));
661 }
662
663 #[test]
668 fn concept_to_json_entity_type() {
669 let concept = Concept::EntityType(EntityType {
670 label: "person".to_string(),
671 });
672 let json = concept_to_json(&concept);
673 assert_eq!(json["category"], "EntityType");
674 assert_eq!(json["label"], "person");
675 assert!(json.get("iid").is_none());
676 assert!(json.get("value").is_none());
677 }
678
679 #[test]
680 fn concept_to_json_attribute_type() {
681 let concept = Concept::AttributeType(AttributeType {
682 label: "name".to_string(),
683 value_type: Some(ValueType::String),
684 });
685 let json = concept_to_json(&concept);
686 assert_eq!(json["category"], "AttributeType");
687 assert_eq!(json["label"], "name");
688 assert_eq!(json["value_type"], "string");
689 }
690
691 #[test]
692 fn concept_to_json_value_boolean() {
693 let concept = Concept::Value(Value::Boolean(true));
694 let json = concept_to_json(&concept);
695 assert_eq!(json["category"], "Value");
696 assert_eq!(json["value"], true);
697 }
698
699 #[test]
700 fn concept_to_json_value_integer() {
701 let concept = Concept::Value(Value::Integer(42));
702 let json = concept_to_json(&concept);
703 assert_eq!(json["value"], 42);
704 }
705
706 #[test]
707 fn concept_to_json_value_string() {
708 let concept = Concept::Value(Value::String("hello".to_string()));
709 let json = concept_to_json(&concept);
710 assert_eq!(json["value"], "hello");
711 }
712
713 #[test]
714 fn concept_to_json_entity_with_iid() {
715 let iid: IID = vec![0x01, 0x02, 0x03].into();
716 let concept = Concept::Entity(Entity {
717 iid,
718 type_: Some(EntityType { label: "person".to_string() }),
719 });
720 let json = concept_to_json(&concept);
721 assert_eq!(json["category"], "Entity");
722 assert_eq!(json["label"], "person");
723 let iid_str = json["iid"].as_str().unwrap();
725 assert!(iid_str.starts_with("0x"));
726 }
727
728 #[test]
729 fn concept_to_json_attribute_with_value() {
730 let iid: IID = vec![0xAA, 0xBB].into();
731 let concept = Concept::Attribute(Attribute {
732 iid,
733 value: Value::String("hello".to_string()),
734 type_: Some(AttributeType { label: "name".to_string(), value_type: Some(ValueType::String) }),
735 });
736 let json = concept_to_json(&concept);
737 assert_eq!(json["category"], "Attribute");
738 assert_eq!(json["label"], "name");
739 assert!(json.get("iid").is_none());
741 assert_eq!(json["value"], "hello");
742 assert_eq!(json["value_type"], "string");
743 }
744
745 #[test]
746 fn concept_to_json_attribute_type_without_value_type() {
747 let concept = Concept::AttributeType(AttributeType {
748 label: "abstract_attr".to_string(),
749 value_type: None,
750 });
751 let json = concept_to_json(&concept);
752 assert_eq!(json["label"], "abstract_attr");
753 assert!(json.get("value_type").is_none());
754 }
755
756 #[test]
761 fn type_db_client_implements_query_executor() {
762 fn assert_executor<T: QueryExecutor>() {}
763 assert_executor::<TypeDBClient>();
764 }
765
766 #[tokio::test]
767 async fn query_executor_execute_delegates_to_client() {
768 let tx = MockTransaction::new(QueryResultKind::Rows(vec![serde_json::json!({"x": 1})]));
769 let client = make_client(MockBackend::new(tx));
770 let executor: Box<dyn QueryExecutor> = Box::new(client);
771
772 let result = executor.execute("db", "match $x isa thing;", "read").await.unwrap();
773 assert!(result.is_array());
774 assert_eq!(result.as_array().unwrap().len(), 1);
775 }
776
777 #[test]
778 fn query_executor_is_connected_delegates_to_client() {
779 let mut backend = MockBackend::new(MockTransaction::new(QueryResultKind::Ok));
780 backend.is_open = true;
781 let client = make_client(backend);
782 let executor: Box<dyn QueryExecutor> = Box::new(client);
783 assert!(executor.is_connected());
784 }
785
786 #[tokio::test]
791 #[ignore = "requires running TypeDB server"]
792 #[cfg_attr(coverage_nightly, coverage(off))]
793 async fn integration_connect_invalid_address() {
794 let config = TypeDBSection {
795 address: "localhost:99999".to_string(),
796 database: "test".to_string(),
797 username: "admin".to_string(),
798 password: "password".to_string(),
799 };
800 let result = TypeDBClient::connect(&config).await;
801 assert!(result.is_err());
802 }
803
804 #[tokio::test]
805 #[ignore = "requires running TypeDB server"]
806 #[cfg_attr(coverage_nightly, coverage(off))]
807 async fn integration_connect_success() {
808 let config = TypeDBSection {
809 address: "localhost:1729".to_string(),
810 database: "test".to_string(),
811 username: "admin".to_string(),
812 password: "password".to_string(),
813 };
814 let result = TypeDBClient::connect(&config).await;
815 assert!(result.is_ok());
816 assert!(result.unwrap().is_connected());
817 }
818}