1use crate::UtilsError;
7use scirs2_core::ndarray::{Array1, Array2};
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10use std::fmt;
11use std::sync::{Arc, Mutex};
12use std::time::Duration;
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct DatabaseConfig {
17 pub host: String,
18 pub port: u16,
19 pub database: String,
20 pub username: String,
21 pub password: String,
22 pub pool_size: usize,
23 pub connection_timeout: Duration,
24 pub query_timeout: Duration,
25 pub ssl_mode: SslMode,
26 pub additional_params: HashMap<String, String>,
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
30pub enum SslMode {
31 Disable,
32 Prefer,
33 Require,
34}
35
36impl Default for DatabaseConfig {
37 fn default() -> Self {
38 Self {
39 host: "localhost".to_string(),
40 port: 5432,
41 database: "postgres".to_string(),
42 username: "postgres".to_string(),
43 password: String::new(),
44 pool_size: 10,
45 connection_timeout: Duration::from_secs(30),
46 query_timeout: Duration::from_secs(60),
47 ssl_mode: SslMode::Prefer,
48 additional_params: HashMap::new(),
49 }
50 }
51}
52
53impl DatabaseConfig {
54 pub fn new(host: String, database: String, username: String, password: String) -> Self {
55 Self {
56 host,
57 database,
58 username,
59 password,
60 ..Default::default()
61 }
62 }
63
64 pub fn with_port(mut self, port: u16) -> Self {
65 self.port = port;
66 self
67 }
68
69 pub fn with_pool_size(mut self, pool_size: usize) -> Self {
70 self.pool_size = pool_size;
71 self
72 }
73
74 pub fn with_timeout(mut self, timeout: Duration) -> Self {
75 self.connection_timeout = timeout;
76 self.query_timeout = timeout;
77 self
78 }
79
80 pub fn connection_string(&self) -> String {
81 let ssl_param = match self.ssl_mode {
82 SslMode::Disable => "sslmode=disable",
83 SslMode::Prefer => "sslmode=prefer",
84 SslMode::Require => "sslmode=require",
85 };
86
87 let mut params = vec![
88 format!("host={}", self.host),
89 format!("port={}", self.port),
90 format!("dbname={}", self.database),
91 format!("user={}", self.username),
92 ssl_param.to_string(),
93 ];
94
95 if !self.password.is_empty() {
96 params.push(format!("password={}", self.password));
97 }
98
99 for (key, value) in &self.additional_params {
100 params.push(format!("{key}={value}"));
101 }
102
103 params.join(" ")
104 }
105}
106
107#[derive(thiserror::Error, Debug, Clone)]
109pub enum DatabaseError {
110 #[error("Connection failed: {0}")]
111 ConnectionFailed(String),
112 #[error("Query execution failed: {0}")]
113 QueryFailed(String),
114 #[error("Transaction failed: {0}")]
115 TransactionFailed(String),
116 #[error("Data conversion failed: {0}")]
117 ConversionFailed(String),
118 #[error("Connection pool exhausted")]
119 PoolExhausted,
120 #[error("Invalid configuration: {0}")]
121 InvalidConfig(String),
122}
123
124impl From<DatabaseError> for UtilsError {
125 fn from(err: DatabaseError) -> Self {
126 UtilsError::InvalidParameter(err.to_string())
127 }
128}
129
130#[derive(Debug, Clone)]
132pub struct Row {
133 columns: HashMap<String, Value>,
134 column_order: Vec<String>,
135}
136
137impl Row {
138 pub fn new() -> Self {
139 Self {
140 columns: HashMap::new(),
141 column_order: Vec::new(),
142 }
143 }
144
145 pub fn insert<T: Into<Value>>(&mut self, column: String, value: T) {
146 if !self.columns.contains_key(&column) {
147 self.column_order.push(column.clone());
148 }
149 self.columns.insert(column, value.into());
150 }
151
152 pub fn get(&self, column: &str) -> Option<&Value> {
153 self.columns.get(column)
154 }
155
156 pub fn get_string(&self, column: &str) -> Option<String> {
157 self.get(column)?.as_string()
158 }
159
160 pub fn get_f64(&self, column: &str) -> Option<f64> {
161 self.get(column)?.as_f64()
162 }
163
164 pub fn get_i64(&self, column: &str) -> Option<i64> {
165 self.get(column)?.as_i64()
166 }
167
168 pub fn columns(&self) -> &[String] {
169 &self.column_order
170 }
171}
172
173impl Default for Row {
174 fn default() -> Self {
175 Self::new()
176 }
177}
178
179#[derive(Debug, Clone, PartialEq)]
181pub enum Value {
182 Null,
183 Bool(bool),
184 Int(i64),
185 Float(f64),
186 String(String),
187 Bytes(Vec<u8>),
188}
189
190impl Value {
191 pub fn as_string(&self) -> Option<String> {
192 match self {
193 Value::String(s) => Some(s.clone()),
194 Value::Int(i) => Some(i.to_string()),
195 Value::Float(f) => Some(f.to_string()),
196 Value::Bool(b) => Some(b.to_string()),
197 _ => None,
198 }
199 }
200
201 pub fn as_f64(&self) -> Option<f64> {
202 match self {
203 Value::Float(f) => Some(*f),
204 Value::Int(i) => Some(*i as f64),
205 Value::String(s) => s.parse().ok(),
206 _ => None,
207 }
208 }
209
210 pub fn as_i64(&self) -> Option<i64> {
211 match self {
212 Value::Int(i) => Some(*i),
213 Value::Float(f) => Some(*f as i64),
214 Value::String(s) => s.parse().ok(),
215 _ => None,
216 }
217 }
218
219 pub fn is_null(&self) -> bool {
220 matches!(self, Value::Null)
221 }
222}
223
224impl From<String> for Value {
225 fn from(s: String) -> Self {
226 Value::String(s)
227 }
228}
229
230impl From<&str> for Value {
231 fn from(s: &str) -> Self {
232 Value::String(s.to_string())
233 }
234}
235
236impl From<i64> for Value {
237 fn from(i: i64) -> Self {
238 Value::Int(i)
239 }
240}
241
242impl From<i32> for Value {
243 fn from(i: i32) -> Self {
244 Value::Int(i as i64)
245 }
246}
247
248impl From<f64> for Value {
249 fn from(f: f64) -> Self {
250 Value::Float(f)
251 }
252}
253
254impl From<f32> for Value {
255 fn from(f: f32) -> Self {
256 Value::Float(f as f64)
257 }
258}
259
260impl From<bool> for Value {
261 fn from(b: bool) -> Self {
262 Value::Bool(b)
263 }
264}
265
266pub trait Connection {
268 fn execute(&self, query: &Query) -> Result<QueryResult, DatabaseError>;
269 fn query(&self, query: &Query) -> Result<ResultSet, DatabaseError>;
270 fn begin_transaction(&self) -> Result<Transaction, DatabaseError>;
271 fn close(&self) -> Result<(), DatabaseError>;
272 fn is_connected(&self) -> bool;
273}
274
275pub struct MockConnection {
277 connected: bool,
278 mock_data: HashMap<String, Vec<Row>>,
279}
280
281impl MockConnection {
282 pub fn new() -> Self {
283 Self {
284 connected: true,
285 mock_data: HashMap::new(),
286 }
287 }
288
289 pub fn add_mock_data(&mut self, table: String, rows: Vec<Row>) {
290 self.mock_data.insert(table, rows);
291 }
292}
293
294impl Default for MockConnection {
295 fn default() -> Self {
296 Self::new()
297 }
298}
299
300impl Connection for MockConnection {
301 fn execute(&self, _query: &Query) -> Result<QueryResult, DatabaseError> {
302 if !self.connected {
303 return Err(DatabaseError::ConnectionFailed("Not connected".to_string()));
304 }
305
306 Ok(QueryResult {
307 rows_affected: 1,
308 execution_time: Duration::from_millis(10),
309 })
310 }
311
312 fn query(&self, _query: &Query) -> Result<ResultSet, DatabaseError> {
313 if !self.connected {
314 return Err(DatabaseError::ConnectionFailed("Not connected".to_string()));
315 }
316
317 let mut result = ResultSet::new(vec!["id".to_string(), "value".to_string()]);
319 result.set_execution_time(Duration::from_millis(5));
320 Ok(result)
321 }
322
323 fn begin_transaction(&self) -> Result<Transaction, DatabaseError> {
324 if !self.connected {
325 return Err(DatabaseError::ConnectionFailed("Not connected".to_string()));
326 }
327 Ok(Transaction::new())
328 }
329
330 fn close(&self) -> Result<(), DatabaseError> {
331 Ok(())
332 }
333
334 fn is_connected(&self) -> bool {
335 self.connected
336 }
337}
338
339pub struct DatabasePool {
341 #[allow(dead_code)]
342 config: DatabaseConfig,
343 connections: Arc<Mutex<Vec<Box<dyn Connection + Send + Sync>>>>,
344 max_size: usize,
345}
346
347impl DatabasePool {
348 pub fn new(config: DatabaseConfig) -> Self {
349 let max_size = config.pool_size;
350 Self {
351 config,
352 connections: Arc::new(Mutex::new(Vec::new())),
353 max_size,
354 }
355 }
356
357 pub fn get_connection(&self) -> Result<Box<dyn Connection + Send + Sync>, DatabaseError> {
358 Ok(Box::new(MockConnection::new()))
361 }
362
363 pub fn return_connection(&self, _connection: Box<dyn Connection + Send + Sync>) {
364 }
366
367 pub fn size(&self) -> usize {
368 self.connections
369 .lock()
370 .expect("operation should succeed")
371 .len()
372 }
373
374 pub fn max_size(&self) -> usize {
375 self.max_size
376 }
377}
378
379pub struct QueryBuilder {
381 query_type: QueryType,
382 table: Option<String>,
383 columns: Vec<String>,
384 conditions: Vec<String>,
385 joins: Vec<String>,
386 order_by: Vec<String>,
387 group_by: Vec<String>,
388 having: Vec<String>,
389 limit: Option<usize>,
390 offset: Option<usize>,
391 parameters: Vec<Value>,
392}
393
394#[derive(Debug, Clone)]
395#[allow(dead_code)]
396enum QueryType {
397 Select,
398 Insert,
399 Update,
400 Delete,
401}
402
403impl QueryBuilder {
404 pub fn select() -> Self {
405 Self {
406 query_type: QueryType::Select,
407 table: None,
408 columns: Vec::new(),
409 conditions: Vec::new(),
410 joins: Vec::new(),
411 order_by: Vec::new(),
412 group_by: Vec::new(),
413 having: Vec::new(),
414 limit: None,
415 offset: None,
416 parameters: Vec::new(),
417 }
418 }
419
420 pub fn from(mut self, table: &str) -> Self {
421 self.table = Some(table.to_string());
422 self
423 }
424
425 pub fn columns(mut self, columns: &[&str]) -> Self {
426 self.columns = columns.iter().map(|s| s.to_string()).collect();
427 self
428 }
429
430 pub fn where_clause(mut self, condition: &str) -> Self {
431 self.conditions.push(condition.to_string());
432 self
433 }
434
435 pub fn join(mut self, join_clause: &str) -> Self {
436 self.joins.push(join_clause.to_string());
437 self
438 }
439
440 pub fn order_by(mut self, column: &str, ascending: bool) -> Self {
441 let direction = if ascending { "ASC" } else { "DESC" };
442 self.order_by.push(format!("{column} {direction}"));
443 self
444 }
445
446 pub fn group_by(mut self, columns: &[&str]) -> Self {
447 self.group_by = columns.iter().map(|s| s.to_string()).collect();
448 self
449 }
450
451 pub fn limit(mut self, limit: usize) -> Self {
452 self.limit = Some(limit);
453 self
454 }
455
456 pub fn offset(mut self, offset: usize) -> Self {
457 self.offset = Some(offset);
458 self
459 }
460
461 pub fn parameter<T: Into<Value>>(mut self, value: T) -> Self {
462 self.parameters.push(value.into());
463 self
464 }
465
466 pub fn build(self) -> Query {
467 let sql = self.build_sql();
468 Query::new(sql, self.parameters)
469 }
470
471 fn build_sql(&self) -> String {
472 match self.query_type {
473 QueryType::Select => self.build_select(),
474 _ => "".to_string(), }
476 }
477
478 fn build_select(&self) -> String {
479 let mut query = String::new();
480
481 query.push_str("SELECT ");
483 if self.columns.is_empty() {
484 query.push('*');
485 } else {
486 query.push_str(&self.columns.join(", "));
487 }
488
489 if let Some(table) = &self.table {
491 query.push_str(&format!(" FROM {table}"));
492 }
493
494 for join in &self.joins {
496 query.push_str(&format!(" {join}"));
497 }
498
499 if !self.conditions.is_empty() {
501 query.push_str(&format!(" WHERE {}", self.conditions.join(" AND ")));
502 }
503
504 if !self.group_by.is_empty() {
506 query.push_str(&format!(" GROUP BY {}", self.group_by.join(", ")));
507 }
508
509 if !self.having.is_empty() {
511 query.push_str(&format!(" HAVING {}", self.having.join(" AND ")));
512 }
513
514 if !self.order_by.is_empty() {
516 query.push_str(&format!(" ORDER BY {}", self.order_by.join(", ")));
517 }
518
519 if let Some(limit) = self.limit {
521 query.push_str(&format!(" LIMIT {limit}"));
522 }
523
524 if let Some(offset) = self.offset {
526 query.push_str(&format!(" OFFSET {offset}"));
527 }
528
529 query
530 }
531}
532
533#[derive(Debug, Clone)]
535pub struct Query {
536 sql: String,
537 parameters: Vec<Value>,
538}
539
540impl Query {
541 pub fn new(sql: String, parameters: Vec<Value>) -> Self {
542 Self { sql, parameters }
543 }
544
545 pub fn sql(&self) -> &str {
546 &self.sql
547 }
548
549 pub fn parameters(&self) -> &[Value] {
550 &self.parameters
551 }
552}
553
554#[derive(Debug, Clone)]
556pub struct QueryResult {
557 pub rows_affected: usize,
558 pub execution_time: Duration,
559}
560
561#[derive(Debug, Clone)]
563pub struct ResultSet {
564 rows: Vec<Row>,
565 columns: Vec<String>,
566 execution_time: Duration,
567 #[allow(dead_code)]
568 rows_affected: Option<usize>,
569}
570
571impl ResultSet {
572 pub fn new(columns: Vec<String>) -> Self {
573 Self {
574 rows: Vec::new(),
575 columns,
576 execution_time: Duration::from_secs(0),
577 rows_affected: None,
578 }
579 }
580
581 pub fn add_row(&mut self, row: Row) {
582 self.rows.push(row);
583 }
584
585 pub fn rows(&self) -> &[Row] {
586 &self.rows
587 }
588
589 pub fn columns(&self) -> &[String] {
590 &self.columns
591 }
592
593 pub fn len(&self) -> usize {
594 self.rows.len()
595 }
596
597 pub fn is_empty(&self) -> bool {
598 self.rows.is_empty()
599 }
600
601 pub fn execution_time(&self) -> Duration {
602 self.execution_time
603 }
604
605 pub fn set_execution_time(&mut self, time: Duration) {
606 self.execution_time = time;
607 }
608
609 pub fn to_array2(&self) -> Result<Array2<f64>, DatabaseError> {
611 if self.rows.is_empty() {
612 return Err(DatabaseError::ConversionFailed(
613 "Cannot convert empty result set to array".to_string(),
614 ));
615 }
616
617 let n_rows = self.rows.len();
618 let n_cols = self.columns.len();
619 let mut data = Array2::zeros((n_rows, n_cols));
620
621 for (row_idx, row) in self.rows.iter().enumerate() {
622 for (col_idx, col_name) in self.columns.iter().enumerate() {
623 let value = row.get(col_name).ok_or_else(|| {
624 DatabaseError::ConversionFailed(format!("Column '{col_name}' not found in row"))
625 })?;
626
627 let numeric_value = value.as_f64().ok_or_else(|| {
628 DatabaseError::ConversionFailed(format!(
629 "Cannot convert value to f64: {value:?}"
630 ))
631 })?;
632
633 data[[row_idx, col_idx]] = numeric_value;
634 }
635 }
636
637 Ok(data)
638 }
639
640 pub fn column_to_array1(&self, column: &str) -> Result<Array1<f64>, DatabaseError> {
642 if !self.columns.contains(&column.to_string()) {
643 return Err(DatabaseError::ConversionFailed(format!(
644 "Column '{column}' not found"
645 )));
646 }
647
648 let mut data = Array1::zeros(self.rows.len());
649 for (idx, row) in self.rows.iter().enumerate() {
650 let value = row.get(column).ok_or_else(|| {
651 DatabaseError::ConversionFailed(format!("Column '{column}' not found in row"))
652 })?;
653
654 let numeric_value = value.as_f64().ok_or_else(|| {
655 DatabaseError::ConversionFailed(format!("Cannot convert value to f64: {value:?}"))
656 })?;
657
658 data[idx] = numeric_value;
659 }
660
661 Ok(data)
662 }
663
664 pub fn unique_values(&self, column: &str) -> Result<Vec<Value>, DatabaseError> {
666 if !self.columns.contains(&column.to_string()) {
667 return Err(DatabaseError::ConversionFailed(format!(
668 "Column '{column}' not found"
669 )));
670 }
671
672 let mut unique_values = Vec::new();
673 for row in &self.rows {
674 if let Some(value) = row.get(column) {
675 if !unique_values.contains(value) {
676 unique_values.push(value.clone());
677 }
678 }
679 }
680
681 Ok(unique_values)
682 }
683}
684
685pub struct Transaction {
687 committed: bool,
688 rolled_back: bool,
689}
690
691impl Transaction {
692 pub fn new() -> Self {
693 Self {
694 committed: false,
695 rolled_back: false,
696 }
697 }
698
699 pub fn commit(&mut self) -> Result<(), DatabaseError> {
700 if self.rolled_back {
701 return Err(DatabaseError::TransactionFailed(
702 "Transaction already rolled back".to_string(),
703 ));
704 }
705 self.committed = true;
706 Ok(())
707 }
708
709 pub fn rollback(&mut self) -> Result<(), DatabaseError> {
710 if self.committed {
711 return Err(DatabaseError::TransactionFailed(
712 "Transaction already committed".to_string(),
713 ));
714 }
715 self.rolled_back = true;
716 Ok(())
717 }
718
719 pub fn is_committed(&self) -> bool {
720 self.committed
721 }
722
723 pub fn is_rolled_back(&self) -> bool {
724 self.rolled_back
725 }
726}
727
728impl Default for Transaction {
729 fn default() -> Self {
730 Self::new()
731 }
732}
733
734impl fmt::Display for DatabaseConfig {
735 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
736 write!(
737 f,
738 "{}@{}:{}/{}",
739 self.username, self.host, self.port, self.database
740 )
741 }
742}
743
744#[allow(non_snake_case)]
745#[cfg(test)]
746mod tests {
747 use super::*;
748
749 #[test]
750 fn test_database_config() {
751 let config = DatabaseConfig::new(
752 "localhost".to_string(),
753 "testdb".to_string(),
754 "user".to_string(),
755 "pass".to_string(),
756 )
757 .with_port(3306)
758 .with_pool_size(5);
759
760 assert_eq!(config.host, "localhost");
761 assert_eq!(config.port, 3306);
762 assert_eq!(config.pool_size, 5);
763
764 let conn_str = config.connection_string();
765 assert!(conn_str.contains("host=localhost"));
766 assert!(conn_str.contains("port=3306"));
767 }
768
769 #[test]
770 fn test_value_conversions() {
771 let int_val = Value::from(42i64);
772 assert_eq!(int_val.as_i64(), Some(42));
773 assert_eq!(int_val.as_f64(), Some(42.0));
774
775 let float_val = Value::from(std::f64::consts::PI);
776 assert_eq!(float_val.as_f64(), Some(std::f64::consts::PI));
777
778 let string_val = Value::from("hello");
779 assert_eq!(string_val.as_string(), Some("hello".to_string()));
780 }
781
782 #[test]
783 fn test_row_operations() {
784 let mut row = Row::new();
785 row.insert("id".to_string(), 1i64);
786 row.insert("name".to_string(), "test");
787 row.insert("score".to_string(), 95.5f64);
788
789 assert_eq!(row.get_i64("id"), Some(1));
790 assert_eq!(row.get_string("name"), Some("test".to_string()));
791 assert_eq!(row.get_f64("score"), Some(95.5));
792 assert_eq!(row.columns().len(), 3);
793 }
794
795 #[test]
796 fn test_result_set_array_conversion() {
797 let mut result_set = ResultSet::new(vec!["a".to_string(), "b".to_string()]);
798
799 let mut row1 = Row::new();
800 row1.insert("a".to_string(), 1.0f64);
801 row1.insert("b".to_string(), 2.0f64);
802 result_set.add_row(row1);
803
804 let mut row2 = Row::new();
805 row2.insert("a".to_string(), 3.0f64);
806 row2.insert("b".to_string(), 4.0f64);
807 result_set.add_row(row2);
808
809 let array = result_set.to_array2().expect("operation should succeed");
810 assert_eq!(array.shape(), &[2, 2]);
811 assert_eq!(array[[0, 0]], 1.0);
812 assert_eq!(array[[1, 1]], 4.0);
813 }
814
815 #[test]
816 fn test_query_builder() {
817 let query = QueryBuilder::select()
818 .columns(&["id", "name", "score"])
819 .from("users")
820 .where_clause("score > 80")
821 .order_by("score", false)
822 .limit(10)
823 .build();
824
825 let sql = query.sql();
826 assert!(sql.contains("SELECT id, name, score"));
827 assert!(sql.contains("FROM users"));
828 assert!(sql.contains("WHERE score > 80"));
829 assert!(sql.contains("ORDER BY score DESC"));
830 assert!(sql.contains("LIMIT 10"));
831 }
832
833 #[test]
834 fn test_mock_connection() {
835 let connection = MockConnection::new();
836 assert!(connection.is_connected());
837
838 let query = Query::new("SELECT 1".to_string(), vec![]);
839 let result = connection
840 .execute(&query)
841 .expect("operation should succeed");
842 assert_eq!(result.rows_affected, 1);
843
844 let result_set = connection.query(&query).expect("operation should succeed");
845 assert_eq!(result_set.columns().len(), 2);
846 }
847
848 #[test]
849 fn test_transaction() {
850 let mut transaction = Transaction::new();
851 assert!(!transaction.is_committed());
852 assert!(!transaction.is_rolled_back());
853
854 transaction.commit().expect("operation should succeed");
855 assert!(transaction.is_committed());
856
857 assert!(transaction.rollback().is_err());
859 }
860
861 #[test]
862 fn test_database_pool() {
863 let config = DatabaseConfig::default();
864 let pool = DatabasePool::new(config);
865
866 assert_eq!(pool.max_size(), 10);
867
868 let connection = pool.get_connection().expect("operation should succeed");
869 assert!(connection.is_connected());
870 }
871
872 #[test]
873 fn test_result_set_unique_values() {
874 let mut result_set = ResultSet::new(vec!["category".to_string()]);
875
876 let mut row1 = Row::new();
877 row1.insert("category".to_string(), "A");
878 result_set.add_row(row1);
879
880 let mut row2 = Row::new();
881 row2.insert("category".to_string(), "B");
882 result_set.add_row(row2);
883
884 let mut row3 = Row::new();
885 row3.insert("category".to_string(), "A");
886 result_set.add_row(row3);
887
888 let unique_values = result_set
889 .unique_values("category")
890 .expect("operation should succeed");
891 assert_eq!(unique_values.len(), 2);
892 assert!(unique_values.contains(&Value::String("A".to_string())));
893 assert!(unique_values.contains(&Value::String("B".to_string())));
894 }
895}