1use crate::error::{Error, ErrorKind};
2use std::{borrow::Cow, fmt};
3use url::Url;
4
5#[cfg(feature = "mssql")]
6use crate::connector::MssqlUrl;
7#[cfg(feature = "mysql")]
8use crate::connector::MysqlUrl;
9#[cfg(feature = "postgresql")]
10use crate::connector::PostgresUrl;
11#[cfg(feature = "sqlite")]
12use crate::connector::SqliteParams;
13#[cfg(feature = "sqlite")]
14use std::convert::TryFrom;
15
16#[derive(Debug, Clone)]
18pub enum ConnectionInfo {
19 #[cfg(feature = "postgresql")]
21 #[cfg_attr(feature = "docs", doc(cfg(feature = "postgresql")))]
22 Postgres(PostgresUrl),
23 #[cfg(feature = "mysql")]
25 #[cfg_attr(feature = "docs", doc(cfg(feature = "mysql")))]
26 Mysql(MysqlUrl),
27 #[cfg(feature = "mssql")]
29 #[cfg_attr(feature = "docs", doc(cfg(feature = "mssql")))]
30 Mssql(MssqlUrl),
31 #[cfg(feature = "sqlite")]
33 #[cfg_attr(feature = "docs", doc(cfg(feature = "sqlite")))]
34 Sqlite {
35 file_path: String,
37 db_name: String,
39 },
40 #[cfg(feature = "sqlite")]
41 #[cfg_attr(feature = "docs", doc(cfg(feature = "sqlite")))]
42 InMemorySqlite { db_name: String },
43}
44
45impl ConnectionInfo {
46 pub fn from_url(url_str: &str) -> crate::Result<Self> {
51 let url_result: Result<Url, _> = url_str.parse();
52
53 match url_str {
55 #[cfg(feature = "sqlite")]
56 s if s.starts_with("file") => {
57 if url_result.is_err() {
58 let params = SqliteParams::try_from(s)?;
59
60 return Ok(ConnectionInfo::Sqlite { file_path: params.file_path, db_name: params.db_name });
61 }
62 }
63 #[cfg(feature = "mssql")]
64 s if s.starts_with("jdbc:sqlserver") || s.starts_with("sqlserver") => {
65 return Ok(ConnectionInfo::Mssql(MssqlUrl::new(url_str)?));
66 }
67 _ => (),
68 }
69
70 let url = url_result?;
71
72 let sql_family = SqlFamily::from_scheme(url.scheme()).ok_or_else(|| {
73 let kind =
74 ErrorKind::DatabaseUrlIsInvalid(format!("{} is not a supported database URL scheme.", url.scheme()));
75 Error::builder(kind).build()
76 })?;
77
78 match sql_family {
79 #[cfg(feature = "mysql")]
80 SqlFamily::Mysql => Ok(ConnectionInfo::Mysql(MysqlUrl::new(url)?)),
81 #[cfg(feature = "sqlite")]
82 SqlFamily::Sqlite => {
83 let params = SqliteParams::try_from(url_str)?;
84
85 Ok(ConnectionInfo::Sqlite { file_path: params.file_path, db_name: params.db_name })
86 }
87 #[cfg(feature = "postgresql")]
88 SqlFamily::Postgres => Ok(ConnectionInfo::Postgres(PostgresUrl::new(url)?)),
89 #[allow(unreachable_patterns)]
90 _ => unreachable!(),
91 }
92 }
93
94 pub fn dbname(&self) -> Option<&str> {
96 match self {
97 #[cfg(feature = "postgresql")]
98 ConnectionInfo::Postgres(url) => Some(url.dbname()),
99 #[cfg(feature = "mysql")]
100 ConnectionInfo::Mysql(url) => Some(url.dbname()),
101 #[cfg(feature = "mssql")]
102 ConnectionInfo::Mssql(url) => Some(url.dbname()),
103 #[cfg(feature = "sqlite")]
104 ConnectionInfo::Sqlite { .. } | ConnectionInfo::InMemorySqlite { .. } => None,
105 }
106 }
107
108 pub fn schema_name(&self) -> &str {
114 match self {
115 #[cfg(feature = "postgresql")]
116 ConnectionInfo::Postgres(url) => url.schema(),
117 #[cfg(feature = "mysql")]
118 ConnectionInfo::Mysql(url) => url.dbname(),
119 #[cfg(feature = "mssql")]
120 ConnectionInfo::Mssql(url) => url.schema(),
121 #[cfg(feature = "sqlite")]
122 ConnectionInfo::Sqlite { db_name, .. } => db_name,
123 #[cfg(feature = "sqlite")]
124 ConnectionInfo::InMemorySqlite { db_name } => db_name,
125 }
126 }
127
128 pub fn host(&self) -> &str {
130 match self {
131 #[cfg(feature = "postgresql")]
132 ConnectionInfo::Postgres(url) => url.host(),
133 #[cfg(feature = "mysql")]
134 ConnectionInfo::Mysql(url) => url.host(),
135 #[cfg(feature = "mssql")]
136 ConnectionInfo::Mssql(url) => url.host(),
137 #[cfg(feature = "sqlite")]
138 ConnectionInfo::Sqlite { .. } | ConnectionInfo::InMemorySqlite { .. } => "localhost",
139 }
140 }
141
142 pub fn username(&self) -> Option<Cow<str>> {
144 match self {
145 #[cfg(feature = "postgresql")]
146 ConnectionInfo::Postgres(url) => Some(url.username()),
147 #[cfg(feature = "mysql")]
148 ConnectionInfo::Mysql(url) => Some(url.username()),
149 #[cfg(feature = "mssql")]
150 ConnectionInfo::Mssql(url) => url.username().map(Cow::from),
151 #[cfg(feature = "sqlite")]
152 ConnectionInfo::Sqlite { .. } | ConnectionInfo::InMemorySqlite { .. } => None,
153 }
154 }
155
156 pub fn file_path(&self) -> Option<&str> {
158 match self {
159 #[cfg(feature = "postgresql")]
160 ConnectionInfo::Postgres(_) => None,
161 #[cfg(feature = "mysql")]
162 ConnectionInfo::Mysql(_) => None,
163 #[cfg(feature = "mssql")]
164 ConnectionInfo::Mssql(_) => None,
165 #[cfg(feature = "sqlite")]
166 ConnectionInfo::Sqlite { file_path, .. } => Some(file_path),
167 #[cfg(feature = "sqlite")]
168 ConnectionInfo::InMemorySqlite { .. } => None,
169 }
170 }
171
172 pub fn sql_family(&self) -> SqlFamily {
174 match self {
175 #[cfg(feature = "postgresql")]
176 ConnectionInfo::Postgres(_) => SqlFamily::Postgres,
177 #[cfg(feature = "mysql")]
178 ConnectionInfo::Mysql(_) => SqlFamily::Mysql,
179 #[cfg(feature = "mssql")]
180 ConnectionInfo::Mssql(_) => SqlFamily::Mssql,
181 #[cfg(feature = "sqlite")]
182 ConnectionInfo::Sqlite { .. } | ConnectionInfo::InMemorySqlite { .. } => SqlFamily::Sqlite,
183 }
184 }
185
186 pub fn port(&self) -> Option<u16> {
188 match self {
189 #[cfg(feature = "postgresql")]
190 ConnectionInfo::Postgres(url) => Some(url.port()),
191 #[cfg(feature = "mysql")]
192 ConnectionInfo::Mysql(url) => Some(url.port()),
193 #[cfg(feature = "mssql")]
194 ConnectionInfo::Mssql(url) => Some(url.port()),
195 #[cfg(feature = "sqlite")]
196 ConnectionInfo::Sqlite { .. } | ConnectionInfo::InMemorySqlite { .. } => None,
197 }
198 }
199
200 pub fn pg_bouncer(&self) -> bool {
202 #[allow(unreachable_patterns)]
203 match self {
204 #[cfg(feature = "postgresql")]
205 ConnectionInfo::Postgres(url) => url.pg_bouncer(),
206 _ => false,
207 }
208 }
209
210 pub fn database_location(&self) -> String {
213 match self {
214 #[cfg(feature = "postgresql")]
215 ConnectionInfo::Postgres(url) => format!("{}:{}", url.host(), url.port()),
216 #[cfg(feature = "mysql")]
217 ConnectionInfo::Mysql(url) => format!("{}:{}", url.host(), url.port()),
218 #[cfg(feature = "mssql")]
219 ConnectionInfo::Mssql(url) => format!("{}:{}", url.host(), url.port()),
220 #[cfg(feature = "sqlite")]
221 ConnectionInfo::Sqlite { file_path, .. } => file_path.clone(),
222 #[cfg(feature = "sqlite")]
223 ConnectionInfo::InMemorySqlite { .. } => "in-memory".into(),
224 }
225 }
226}
227
228#[derive(Debug, PartialEq, Eq, Clone, Copy)]
230pub enum SqlFamily {
231 #[cfg(feature = "postgresql")]
232 #[cfg_attr(feature = "docs", doc(cfg(feature = "postgresql")))]
233 Postgres,
234 #[cfg(feature = "mysql")]
235 #[cfg_attr(feature = "docs", doc(cfg(feature = "mysql")))]
236 Mysql,
237 #[cfg(feature = "sqlite")]
238 #[cfg_attr(feature = "docs", doc(cfg(feature = "sqlite")))]
239 Sqlite,
240 #[cfg(feature = "mssql")]
241 #[cfg_attr(feature = "docs", doc(cfg(feature = "mssql")))]
242 Mssql,
243}
244
245impl SqlFamily {
246 pub fn as_str(self) -> &'static str {
248 match self {
249 #[cfg(feature = "postgresql")]
250 SqlFamily::Postgres => "postgresql",
251 #[cfg(feature = "mysql")]
252 SqlFamily::Mysql => "mysql",
253 #[cfg(feature = "sqlite")]
254 SqlFamily::Sqlite => "sqlite",
255 #[cfg(feature = "mssql")]
256 SqlFamily::Mssql => "mssql",
257 }
258 }
259
260 pub fn from_scheme(url_scheme: &str) -> Option<Self> {
262 match url_scheme {
263 #[cfg(feature = "sqlite")]
264 "file" => Some(SqlFamily::Sqlite),
265 #[cfg(feature = "postgresql")]
266 "postgres" | "postgresql" => Some(SqlFamily::Postgres),
267 #[cfg(feature = "mysql")]
268 "mysql" => Some(SqlFamily::Mysql),
269 _ => None,
270 }
271 }
272
273 pub fn scheme_is_supported(url_scheme: &str) -> bool {
275 Self::from_scheme(url_scheme).is_some()
276 }
277
278 #[cfg(feature = "postgresql")]
280 pub fn is_postgres(&self) -> bool {
281 matches!(self, SqlFamily::Postgres)
282 }
283
284 #[cfg(not(feature = "postgresql"))]
286 pub fn is_postgres(&self) -> bool {
287 false
288 }
289
290 #[cfg(feature = "mysql")]
292 pub fn is_mysql(&self) -> bool {
293 matches!(self, SqlFamily::Mysql)
294 }
295
296 #[cfg(not(feature = "mysql"))]
298 pub fn is_mysql(&self) -> bool {
299 false
300 }
301
302 #[cfg(feature = "sqlite")]
304 pub fn is_sqlite(&self) -> bool {
305 matches!(self, SqlFamily::Sqlite)
306 }
307
308 #[cfg(not(feature = "sqlite"))]
310 pub fn is_sqlite(&self) -> bool {
311 false
312 }
313
314 #[cfg(feature = "mssql")]
316 pub fn is_mssql(&self) -> bool {
317 matches!(self, SqlFamily::Mssql)
318 }
319
320 #[cfg(not(feature = "mssql"))]
322 pub fn is_mssql(&self) -> bool {
323 false
324 }
325}
326
327impl fmt::Display for SqlFamily {
328 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
329 write!(f, "{}", self.as_str())
330 }
331}
332
333#[cfg(test)]
334mod tests {
335 #[cfg(any(feature = "sqlite", feature = "mysql"))]
336 use super::*;
337
338 #[test]
339 #[cfg(feature = "sqlite")]
340 fn sqlite_connection_info_from_str_interprets_relative_path_correctly() {
341 let conn_info = ConnectionInfo::from_url("file:dev.db").unwrap();
342
343 #[allow(irrefutable_let_patterns)]
344 if let ConnectionInfo::Sqlite { file_path, db_name: _ } = conn_info {
345 assert_eq!(file_path, "dev.db");
346 } else {
347 panic!("Wrong type of connection info, should be Sqlite");
348 }
349 }
350
351 #[test]
352 #[cfg(feature = "mysql")]
353 fn mysql_connection_info_from_str() {
354 let conn_info = ConnectionInfo::from_url("mysql://myuser:my%23pass%23word@lclhst:5432/mydb").unwrap();
355
356 #[allow(irrefutable_let_patterns)]
357 if let ConnectionInfo::Mysql(url) = conn_info {
358 assert_eq!(url.password().unwrap(), "my#pass#word");
359 assert_eq!(url.host(), "lclhst");
360 assert_eq!(url.username(), "myuser");
361 assert_eq!(url.dbname(), "mydb");
362 } else {
363 panic!("Wrong type of connection info, should be Mysql");
364 }
365 }
366}