#![cfg_attr(target_arch = "wasm32", allow(unused_imports))]
#![cfg_attr(not(target_arch = "wasm32"), allow(clippy::large_enum_variant))]
use crate::error::{Error, ErrorKind};
use std::{borrow::Cow, fmt};
use url::Url;
#[cfg(feature = "mssql")]
use crate::connector::MssqlUrl;
#[cfg(feature = "mysql")]
use crate::connector::MysqlUrl;
#[cfg(feature = "postgresql")]
use crate::connector::PostgresUrl;
#[cfg(feature = "sqlite")]
use crate::connector::SqliteParams;
#[cfg(feature = "sqlite")]
use std::convert::TryFrom;
use super::ExternalConnectionInfo;
#[cfg(not(target_arch = "wasm32"))]
use super::NativeConnectionInfo;
#[derive(Debug, Clone)]
#[cfg_attr(target_arch = "wasm32", repr(transparent))]
pub enum ConnectionInfo {
#[cfg(not(target_arch = "wasm32"))]
Native(NativeConnectionInfo),
External(ExternalConnectionInfo),
}
impl ConnectionInfo {
#[cfg(not(target_arch = "wasm32"))]
pub fn from_url(url_str: &str) -> crate::Result<Self> {
let url_result: Result<Url, _> = url_str.parse();
match url_str {
#[cfg(feature = "sqlite")]
s if s.starts_with("file") => {
if url_result.is_err() {
let params = SqliteParams::try_from(s)?;
return Ok(ConnectionInfo::Native(NativeConnectionInfo::Sqlite {
file_path: params.file_path,
db_name: params.db_name,
}));
}
}
#[cfg(feature = "mssql")]
s if s.starts_with("jdbc:sqlserver") || s.starts_with("sqlserver") => {
return Ok(ConnectionInfo::Native(NativeConnectionInfo::Mssql(MssqlUrl::new(
url_str,
)?)));
}
_ => (),
}
let url = url_result?;
let sql_family = SqlFamily::from_scheme(url.scheme()).ok_or_else(|| {
let kind =
ErrorKind::DatabaseUrlIsInvalid(format!("{} is not a supported database URL scheme.", url.scheme()));
Error::builder(kind).build()
})?;
match sql_family {
#[cfg(feature = "mysql")]
SqlFamily::Mysql => Ok(ConnectionInfo::Native(NativeConnectionInfo::Mysql(MysqlUrl::new(url)?))),
#[cfg(feature = "sqlite")]
SqlFamily::Sqlite => {
let params = SqliteParams::try_from(url_str)?;
Ok(ConnectionInfo::Native(NativeConnectionInfo::Sqlite {
file_path: params.file_path,
db_name: params.db_name,
}))
}
#[cfg(feature = "postgresql")]
SqlFamily::Postgres => Ok(ConnectionInfo::Native(NativeConnectionInfo::Postgres(
PostgresUrl::new(url)?,
))),
#[allow(unreachable_patterns)]
_ => unreachable!(),
}
}
pub fn dbname(&self) -> Option<&str> {
match self {
#[cfg(not(target_arch = "wasm32"))]
ConnectionInfo::Native(info) => match info {
#[cfg(feature = "postgresql")]
NativeConnectionInfo::Postgres(url) => Some(url.dbname()),
#[cfg(feature = "mysql")]
NativeConnectionInfo::Mysql(url) => Some(url.dbname()),
#[cfg(feature = "mssql")]
NativeConnectionInfo::Mssql(url) => Some(url.dbname()),
#[cfg(feature = "sqlite")]
NativeConnectionInfo::Sqlite { .. } | NativeConnectionInfo::InMemorySqlite { .. } => None,
},
ConnectionInfo::External(_) => None,
}
}
pub fn schema_name(&self) -> &str {
match self {
#[cfg(not(target_arch = "wasm32"))]
ConnectionInfo::Native(info) => match info {
#[cfg(feature = "postgresql")]
NativeConnectionInfo::Postgres(url) => url.schema(),
#[cfg(feature = "mysql")]
NativeConnectionInfo::Mysql(url) => url.dbname(),
#[cfg(feature = "mssql")]
NativeConnectionInfo::Mssql(url) => url.schema(),
#[cfg(feature = "sqlite")]
NativeConnectionInfo::Sqlite { db_name, .. } => db_name,
#[cfg(feature = "sqlite")]
NativeConnectionInfo::InMemorySqlite { db_name } => db_name,
},
ConnectionInfo::External(info) => &info.schema_name,
}
}
pub fn host(&self) -> &str {
match self {
#[cfg(not(target_arch = "wasm32"))]
ConnectionInfo::Native(info) => match info {
#[cfg(feature = "postgresql")]
NativeConnectionInfo::Postgres(url) => url.host(),
#[cfg(feature = "mysql")]
NativeConnectionInfo::Mysql(url) => url.host(),
#[cfg(feature = "mssql")]
NativeConnectionInfo::Mssql(url) => url.host(),
#[cfg(feature = "sqlite")]
NativeConnectionInfo::Sqlite { .. } | NativeConnectionInfo::InMemorySqlite { .. } => "localhost",
},
ConnectionInfo::External(_) => "external",
}
}
pub fn username(&self) -> Option<Cow<str>> {
match self {
#[cfg(not(target_arch = "wasm32"))]
ConnectionInfo::Native(info) => match info {
#[cfg(feature = "postgresql")]
NativeConnectionInfo::Postgres(url) => Some(url.username()),
#[cfg(feature = "mysql")]
NativeConnectionInfo::Mysql(url) => Some(url.username()),
#[cfg(feature = "mssql")]
NativeConnectionInfo::Mssql(url) => url.username().map(Cow::from),
#[cfg(feature = "sqlite")]
NativeConnectionInfo::Sqlite { .. } | NativeConnectionInfo::InMemorySqlite { .. } => None,
},
ConnectionInfo::External(_) => None,
}
}
pub fn file_path(&self) -> Option<&str> {
match self {
#[cfg(not(target_arch = "wasm32"))]
ConnectionInfo::Native(info) => match info {
#[cfg(feature = "postgresql")]
NativeConnectionInfo::Postgres(_) => None,
#[cfg(feature = "mysql")]
NativeConnectionInfo::Mysql(_) => None,
#[cfg(feature = "mssql")]
NativeConnectionInfo::Mssql(_) => None,
#[cfg(feature = "sqlite")]
NativeConnectionInfo::Sqlite { file_path, .. } => Some(file_path),
#[cfg(feature = "sqlite")]
NativeConnectionInfo::InMemorySqlite { .. } => None,
},
ConnectionInfo::External(_) => None,
}
}
pub fn sql_family(&self) -> SqlFamily {
match self {
#[cfg(not(target_arch = "wasm32"))]
ConnectionInfo::Native(info) => match info {
#[cfg(feature = "postgresql")]
NativeConnectionInfo::Postgres(_) => SqlFamily::Postgres,
#[cfg(feature = "mysql")]
NativeConnectionInfo::Mysql(_) => SqlFamily::Mysql,
#[cfg(feature = "mssql")]
NativeConnectionInfo::Mssql(_) => SqlFamily::Mssql,
#[cfg(feature = "sqlite")]
NativeConnectionInfo::Sqlite { .. } | NativeConnectionInfo::InMemorySqlite { .. } => SqlFamily::Sqlite,
},
ConnectionInfo::External(info) => info.sql_family.to_owned(),
}
}
pub fn port(&self) -> Option<u16> {
match self {
#[cfg(not(target_arch = "wasm32"))]
ConnectionInfo::Native(info) => match info {
#[cfg(feature = "postgresql")]
NativeConnectionInfo::Postgres(url) => Some(url.port()),
#[cfg(feature = "mysql")]
NativeConnectionInfo::Mysql(url) => Some(url.port()),
#[cfg(feature = "mssql")]
NativeConnectionInfo::Mssql(url) => Some(url.port()),
#[cfg(feature = "sqlite")]
NativeConnectionInfo::Sqlite { .. } | NativeConnectionInfo::InMemorySqlite { .. } => None,
},
ConnectionInfo::External(_) => None,
}
}
pub fn pg_bouncer(&self) -> bool {
match self {
#[cfg(all(not(target_arch = "wasm32"), feature = "postgresql"))]
ConnectionInfo::Native(NativeConnectionInfo::Postgres(url)) => url.pg_bouncer(),
_ => false,
}
}
pub fn database_location(&self) -> String {
match self {
#[cfg(not(target_arch = "wasm32"))]
ConnectionInfo::Native(info) => match info {
#[cfg(feature = "postgresql")]
NativeConnectionInfo::Postgres(url) => format!("{}:{}", url.host(), url.port()),
#[cfg(feature = "mysql")]
NativeConnectionInfo::Mysql(url) => format!("{}:{}", url.host(), url.port()),
#[cfg(feature = "mssql")]
NativeConnectionInfo::Mssql(url) => format!("{}:{}", url.host(), url.port()),
#[cfg(feature = "sqlite")]
NativeConnectionInfo::Sqlite { file_path, .. } => file_path.clone(),
#[cfg(feature = "sqlite")]
NativeConnectionInfo::InMemorySqlite { .. } => "in-memory".into(),
},
ConnectionInfo::External(_) => "external".into(),
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum SqlFamily {
#[cfg(feature = "postgresql")]
Postgres,
#[cfg(feature = "mysql")]
Mysql,
#[cfg(feature = "sqlite")]
Sqlite,
#[cfg(feature = "mssql")]
Mssql,
}
impl SqlFamily {
pub fn as_str(self) -> &'static str {
match self {
#[cfg(feature = "postgresql")]
SqlFamily::Postgres => "postgresql",
#[cfg(feature = "mysql")]
SqlFamily::Mysql => "mysql",
#[cfg(feature = "sqlite")]
SqlFamily::Sqlite => "sqlite",
#[cfg(feature = "mssql")]
SqlFamily::Mssql => "mssql",
}
}
pub fn from_scheme(url_scheme: &str) -> Option<Self> {
match url_scheme {
#[cfg(feature = "sqlite")]
"file" => Some(SqlFamily::Sqlite),
#[cfg(feature = "postgresql")]
"postgres" | "postgresql" => Some(SqlFamily::Postgres),
#[cfg(feature = "mysql")]
"mysql" => Some(SqlFamily::Mysql),
_ => None,
}
}
pub fn max_insert_rows(&self) -> Option<usize> {
match self {
#[cfg(feature = "postgresql")]
SqlFamily::Postgres => None,
#[cfg(feature = "mysql")]
SqlFamily::Mysql => None,
#[cfg(feature = "sqlite")]
SqlFamily::Sqlite => Some(999),
#[cfg(feature = "mssql")]
SqlFamily::Mssql => Some(1000),
}
}
#[cfg(not(target_arch = "wasm32"))]
pub fn max_bind_values(&self) -> usize {
use std::sync::OnceLock;
static BATCH_SIZE_OVERRIDE: OnceLock<Option<usize>> = OnceLock::new();
BATCH_SIZE_OVERRIDE
.get_or_init(|| {
std::env::var("QUERY_BATCH_SIZE")
.ok()
.map(|size| size.parse().expect("QUERY_BATCH_SIZE: not a valid size"))
})
.unwrap_or(self.default_max_bind_values())
}
#[cfg(target_arch = "wasm32")]
pub fn max_bind_values(&self) -> usize {
self.default_max_bind_values()
}
pub fn default_max_bind_values(&self) -> usize {
match self {
#[cfg(feature = "postgresql")]
SqlFamily::Postgres => 32766,
#[cfg(feature = "mysql")]
SqlFamily::Mysql => 65535,
#[cfg(feature = "sqlite")]
SqlFamily::Sqlite => 999,
#[cfg(feature = "mssql")]
SqlFamily::Mssql => 2099,
}
}
pub fn scheme_is_supported(url_scheme: &str) -> bool {
Self::from_scheme(url_scheme).is_some()
}
#[cfg(feature = "postgresql")]
pub fn is_postgres(&self) -> bool {
matches!(self, SqlFamily::Postgres)
}
#[cfg(not(feature = "postgresql"))]
pub fn is_postgres(&self) -> bool {
false
}
#[cfg(feature = "mysql")]
pub fn is_mysql(&self) -> bool {
matches!(self, SqlFamily::Mysql)
}
#[cfg(not(feature = "mysql"))]
pub fn is_mysql(&self) -> bool {
false
}
#[cfg(feature = "sqlite")]
pub fn is_sqlite(&self) -> bool {
matches!(self, SqlFamily::Sqlite)
}
#[cfg(not(feature = "sqlite"))]
pub fn is_sqlite(&self) -> bool {
false
}
#[cfg(feature = "mssql")]
pub fn is_mssql(&self) -> bool {
matches!(self, SqlFamily::Mssql)
}
#[cfg(not(feature = "mssql"))]
pub fn is_mssql(&self) -> bool {
false
}
}
impl fmt::Display for SqlFamily {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[cfg(test)]
mod tests {
#[cfg(any(feature = "sqlite", feature = "mysql"))]
use super::*;
#[test]
#[cfg(feature = "sqlite")]
fn sqlite_connection_info_from_str_interprets_relative_path_correctly() {
let conn_info = ConnectionInfo::from_url("file:dev.db").unwrap();
#[allow(irrefutable_let_patterns)]
if let ConnectionInfo::Native(NativeConnectionInfo::Sqlite { file_path, db_name: _ }) = conn_info {
assert_eq!(file_path, "dev.db");
} else {
panic!("Wrong type of connection info, should be Sqlite");
}
}
#[test]
#[cfg(feature = "mysql")]
fn mysql_connection_info_from_str() {
let conn_info = ConnectionInfo::from_url("mysql://myuser:my%23pass%23word@lclhst:5432/mydb").unwrap();
#[allow(irrefutable_let_patterns)]
if let ConnectionInfo::Native(NativeConnectionInfo::Mysql(url)) = conn_info {
assert_eq!(url.password().unwrap(), "my#pass#word");
assert_eq!(url.host(), "lclhst");
assert_eq!(url.username(), "myuser");
assert_eq!(url.dbname(), "mydb");
} else {
panic!("Wrong type of connection info, should be Mysql");
}
}
}