Skip to main content

exarrow_rs/adbc/
database.rs

1//! ADBC Database implementation.
2//!
3//! This module provides the `Database` type which acts as a factory for
4//! creating database connections.
5
6use crate::adbc::Connection;
7use crate::connection::params::ConnectionParams;
8use crate::error::ConnectionError;
9use std::str::FromStr;
10
11/// ADBC Database connection factory.
12///
13/// The `Database` type represents a database connection configuration and
14/// serves as a factory for creating `Connection` instances. It encapsulates
15/// the connection parameters and provides methods to establish connections.
16///
17/// # Example
18///
19#[derive(Debug, Clone)]
20pub struct Database {
21    /// Connection parameters
22    params: ConnectionParams,
23    /// Original connection string (for display purposes)
24    connection_string: String,
25}
26
27impl Database {
28    /// Create a new Database instance from connection parameters.
29    ///
30    /// # Arguments
31    ///
32    /// * `params` - The connection parameters
33    ///
34    /// # Example
35    ///
36    pub fn new(params: ConnectionParams) -> Self {
37        // Reconstruct a safe connection string for display (without password)
38        let connection_string = format!(
39            "exasol://{}@{}:{}{}",
40            params.username,
41            params.host,
42            params.port,
43            params
44                .schema
45                .as_ref()
46                .map(|s| format!("/{}", s))
47                .unwrap_or_default()
48        );
49
50        Self {
51            params,
52            connection_string,
53        }
54    }
55
56    /// Get the connection parameters.
57    ///
58    /// # Returns
59    ///
60    /// A reference to the connection parameters.
61    pub fn params(&self) -> &ConnectionParams {
62        &self.params
63    }
64
65    /// Get the connection string (without password).
66    ///
67    /// # Returns
68    ///
69    /// A safe display version of the connection string.
70    pub fn connection_string(&self) -> &str {
71        &self.connection_string
72    }
73
74    /// Establish a connection to the database.
75    ///
76    /// This creates a new `Connection` instance and establishes a connection
77    /// to the Exasol database using the configured parameters.
78    ///
79    /// # Returns
80    ///
81    /// A connected `Connection` instance.
82    ///
83    /// # Errors
84    ///
85    /// Returns `ConnectionError` if the connection fails.
86    ///
87    /// # Example
88    ///
89    pub async fn connect(&self) -> Result<Connection, ConnectionError> {
90        Connection::from_params(self.params.clone()).await
91    }
92
93    /// Test the connection without establishing a persistent connection.
94    ///
95    /// This attempts to connect to the database and immediately closes the
96    /// connection, verifying that the connection parameters are valid.
97    ///
98    /// # Returns
99    ///
100    /// `Ok(())` if the connection test succeeds.
101    ///
102    /// # Errors
103    ///
104    /// Returns `ConnectionError` if the connection test fails.
105    ///
106    /// # Example
107    ///
108    pub async fn test_connection(&self) -> Result<(), ConnectionError> {
109        let connection = self.connect().await?;
110        connection.close().await?;
111        Ok(())
112    }
113}
114
115impl FromStr for Database {
116    type Err = ConnectionError;
117
118    /// Parse a connection string to create a Database instance.
119    ///
120    /// # Arguments
121    ///
122    /// * `s` - Connection string in the format:
123    ///   `exasol://[username[:password]@]host[:port][/schema][?param=value&...]`
124    ///
125    /// # Returns
126    ///
127    /// A `Database` instance configured with the parsed parameters.
128    ///
129    /// # Errors
130    ///
131    /// Returns `ConnectionError` if the connection string is invalid.
132    ///
133    /// # Example
134    ///
135    fn from_str(s: &str) -> Result<Self, Self::Err> {
136        let params = ConnectionParams::from_str(s)?;
137        Ok(Self::new(params))
138    }
139}
140
141impl std::fmt::Display for Database {
142    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
143        write!(f, "Database({})", self.connection_string)
144    }
145}
146
147#[cfg(test)]
148mod tests {
149    use super::*;
150    use crate::connection::ConnectionBuilder;
151
152    #[test]
153    fn test_database_creation() {
154        let params = ConnectionBuilder::new()
155            .host("localhost")
156            .port(8563)
157            .username("test")
158            .password("secret")
159            .build()
160            .unwrap();
161
162        let database = Database::new(params);
163        assert!(database.connection_string().contains("localhost"));
164        assert!(database.connection_string().contains("test"));
165        // Password should not appear in connection string
166        assert!(!database.connection_string().contains("secret"));
167    }
168
169    #[test]
170    fn test_database_from_str_basic() {
171        let database = Database::from_str("exasol://user@localhost").unwrap();
172        assert_eq!(database.params().host, "localhost");
173        assert_eq!(database.params().port, 8563);
174        assert_eq!(database.params().username, "user");
175    }
176
177    #[test]
178    fn test_database_from_str_with_port() {
179        let database = Database::from_str("exasol://user@localhost:9000").unwrap();
180        assert_eq!(database.params().port, 9000);
181    }
182
183    #[test]
184    fn test_database_from_str_with_password() {
185        let database = Database::from_str("exasol://user:pass@localhost").unwrap();
186        assert_eq!(database.params().username, "user");
187        // Password should be set internally but not exposed
188        assert!(database.connection_string().contains("user"));
189        assert!(!database.connection_string().contains("pass"));
190    }
191
192    #[test]
193    fn test_database_from_str_with_schema() {
194        let database = Database::from_str("exasol://user@localhost/MY_SCHEMA").unwrap();
195        assert_eq!(database.params().schema, Some("MY_SCHEMA".to_string()));
196        assert!(database.connection_string().contains("MY_SCHEMA"));
197    }
198
199    #[test]
200    fn test_database_from_str_full() {
201        let database =
202            Database::from_str("exasol://admin:secret@db.example.com:9000/PROD?timeout=30")
203                .unwrap();
204
205        assert_eq!(database.params().host, "db.example.com");
206        assert_eq!(database.params().port, 9000);
207        assert_eq!(database.params().username, "admin");
208        assert_eq!(database.params().schema, Some("PROD".to_string()));
209    }
210
211    #[test]
212    fn test_database_from_str_invalid() {
213        let result = Database::from_str("invalid://connection");
214        assert!(result.is_err());
215
216        let result = Database::from_str("");
217        assert!(result.is_err());
218
219        let result = Database::from_str("postgres://user@host");
220        assert!(result.is_err());
221    }
222
223    #[test]
224    fn test_database_display() {
225        let database = Database::from_str("exasol://user@localhost/SCHEMA").unwrap();
226        let display = format!("{}", database);
227        assert!(display.contains("Database"));
228        assert!(display.contains("localhost"));
229        assert!(display.contains("SCHEMA"));
230    }
231
232    #[test]
233    fn test_database_debug() {
234        let database = Database::from_str("exasol://user@localhost").unwrap();
235        let debug = format!("{:?}", database);
236        assert!(debug.contains("Database"));
237        assert!(debug.contains("params"));
238    }
239}