From e4184e5294140ffe8f89aac8d76774af1518637e Mon Sep 17 00:00:00 2001 From: jkomyno Date: Tue, 26 Dec 2023 17:43:48 +0100 Subject: [PATCH 01/11] feat(query-engine-wasm): start excluding native-drivers only errors from wasm32 target --- libs/user-facing-errors/Cargo.toml | 1 + libs/user-facing-errors/src/quaint.rs | 397 +++++++++--------- quaint/src/connector.rs | 6 + quaint/src/connector/connection_info.rs | 226 +++++----- quaint/src/connector/mssql/native/error.rs | 10 +- quaint/src/connector/mysql/native/error.rs | 12 +- quaint/src/connector/native.rs | 31 ++ quaint/src/connector/postgres/native/error.rs | 21 +- quaint/src/connector/postgres/native/mod.rs | 29 +- quaint/src/connector/postgres/url.rs | 4 +- quaint/src/connector/timeout.rs | 6 +- quaint/src/{error.rs => error/mod.rs} | 105 ++--- quaint/src/error/name.rs | 32 ++ quaint/src/error/native.rs | 40 ++ quaint/src/pooled.rs | 11 +- quaint/src/single.rs | 10 +- .../sql-query-connector/src/error.rs | 83 ++-- 17 files changed, 581 insertions(+), 443 deletions(-) create mode 100644 quaint/src/connector/native.rs rename quaint/src/{error.rs => error/mod.rs} (82%) create mode 100644 quaint/src/error/name.rs create mode 100644 quaint/src/error/native.rs diff --git a/libs/user-facing-errors/Cargo.toml b/libs/user-facing-errors/Cargo.toml index 8e715e7d66c9..f43de70afdef 100644 --- a/libs/user-facing-errors/Cargo.toml +++ b/libs/user-facing-errors/Cargo.toml @@ -15,4 +15,5 @@ quaint = { path = "../../quaint", optional = true } [features] default = [] +# native = ["quaint/native"] sql = ["quaint"] diff --git a/libs/user-facing-errors/src/quaint.rs b/libs/user-facing-errors/src/quaint.rs index aab6598d81bc..a82d048af05c 100644 --- a/libs/user-facing-errors/src/quaint.rs +++ b/libs/user-facing-errors/src/quaint.rs @@ -1,8 +1,10 @@ use crate::{common, query_engine, KnownError}; -use common::ModelKind; use indoc::formatdoc; use quaint::{error::ErrorKind, prelude::ConnectionInfo}; +#[cfg(not(target_arch = "wasm32"))] +use quaint::{connector::NativeConnectionInfo, error::NativeErrorKind}; + impl From<&quaint::error::DatabaseConstraint> for query_engine::DatabaseConstraint { fn from(other: &quaint::error::DatabaseConstraint) -> Self { match other { @@ -36,92 +38,168 @@ pub fn invalid_connection_string_description(error_details: &str) -> String { details.replace('\n', " ") } +// The following errors may arise from both native or external connections (TODO, check if this is true): +// - `DatabaseDoesNotExist` +// - `DatabaseAccessDenied` +// - `DatabaseAlreadyExists` +// - `TableDoesNotExist` +// - `DatabaseUrlIsInvalid` +// +// TODO: try replacing `unreachable!` with `default_value` +// +// TODO: try handling `connection_info == ConnectionInfo::External(_)` right away pub fn render_quaint_error(kind: &ErrorKind, connection_info: &ConnectionInfo) -> Option { - match (kind, connection_info) { - (ErrorKind::DatabaseDoesNotExist { .. }, ConnectionInfo::Sqlite { .. }) => { - unreachable!(); // quaint implicitly creates sqlite databases - } - - (ErrorKind::DatabaseDoesNotExist { db_name }, ConnectionInfo::Postgres(url)) => { - Some(KnownError::new(common::DatabaseDoesNotExist::Postgres { - database_name: db_name.to_string(), - database_host: url.host().to_owned(), - database_port: url.port(), - })) - } - - (ErrorKind::DatabaseDoesNotExist { .. }, ConnectionInfo::Mysql(url)) => { - Some(KnownError::new(common::DatabaseDoesNotExist::Mysql { - database_name: url.dbname().to_owned(), - database_host: url.host().to_owned(), - database_port: url.port(), - })) - } - (ErrorKind::DatabaseDoesNotExist { .. }, ConnectionInfo::Mssql(url)) => { - Some(KnownError::new(common::DatabaseDoesNotExist::Mssql { - database_name: url.dbname().to_owned(), - database_host: url.host().to_owned(), - database_port: url.port(), - })) - } - - (ErrorKind::DatabaseAccessDenied { .. }, ConnectionInfo::Postgres(url)) => { - Some(KnownError::new(common::DatabaseAccessDenied { - database_user: url.username().into_owned(), - database_name: format!("{}.{}", url.dbname(), url.schema()), - })) - } - - (ErrorKind::DatabaseAccessDenied { .. }, ConnectionInfo::Mysql(url)) => { - Some(KnownError::new(common::DatabaseAccessDenied { - database_user: url.username().into_owned(), - database_name: url.dbname().to_owned(), - })) - } - - (ErrorKind::DatabaseAlreadyExists { db_name }, ConnectionInfo::Postgres(url)) => { - Some(KnownError::new(common::DatabaseAlreadyExists { - database_name: format!("{db_name}"), - database_host: url.host().to_owned(), - database_port: url.port(), - })) - } + let default_value: Option = None; - (ErrorKind::DatabaseAlreadyExists { db_name }, ConnectionInfo::Mysql(url)) => { - Some(KnownError::new(common::DatabaseAlreadyExists { - database_name: format!("{db_name}"), - database_host: url.host().to_owned(), - database_port: url.port(), - })) - } - - (ErrorKind::AuthenticationFailed { user }, ConnectionInfo::Postgres(url)) => { - Some(KnownError::new(common::IncorrectDatabaseCredentials { - database_user: format!("{user}"), - database_host: url.host().to_owned(), - })) - } - - (ErrorKind::AuthenticationFailed { user }, ConnectionInfo::Mysql(url)) => { - Some(KnownError::new(common::IncorrectDatabaseCredentials { - database_user: format!("{user}"), - database_host: url.host().to_owned(), - })) - } - - (ErrorKind::ConnectionError(_), ConnectionInfo::Postgres(url)) => { - Some(KnownError::new(common::DatabaseNotReachable { - database_port: url.port(), - database_host: url.host().to_owned(), - })) - } - - (ErrorKind::ConnectionError(_), ConnectionInfo::Mysql(url)) => { - Some(KnownError::new(common::DatabaseNotReachable { - database_port: url.port(), - database_host: url.host().to_owned(), - })) - } + match (kind, connection_info) { + (ErrorKind::DatabaseDoesNotExist { .. }, ConnectionInfo::External(_)) => default_value, + #[cfg(not(target_arch = "wasm32"))] + (ErrorKind::DatabaseDoesNotExist { db_name }, _) => match connection_info { + ConnectionInfo::Native(NativeConnectionInfo::Mysql(url)) => { + Some(KnownError::new(common::DatabaseDoesNotExist::Mysql { + database_name: db_name.to_string(), + database_host: url.host().to_owned(), + database_port: url.port(), + })) + } + ConnectionInfo::Native(NativeConnectionInfo::Postgres(url)) => { + Some(KnownError::new(common::DatabaseDoesNotExist::Postgres { + database_name: db_name.to_string(), + database_host: url.host().to_owned(), + database_port: url.port(), + })) + } + ConnectionInfo::Native(NativeConnectionInfo::Mssql(url)) => { + Some(KnownError::new(common::DatabaseDoesNotExist::Mssql { + database_name: db_name.to_string(), + database_host: url.host().to_owned(), + database_port: url.port(), + })) + } + _ => unreachable!(), // quaint implicitly creates sqlite databases + }, + + (ErrorKind::DatabaseAccessDenied { .. }, ConnectionInfo::External(_)) => default_value, + #[cfg(not(target_arch = "wasm32"))] + (ErrorKind::DatabaseAccessDenied { .. }, _) => match connection_info { + ConnectionInfo::Native(NativeConnectionInfo::Postgres(url)) => { + Some(KnownError::new(common::DatabaseAccessDenied { + database_user: url.username().into_owned(), + database_name: format!("{}.{}", url.dbname(), url.schema()), + })) + } + ConnectionInfo::Native(NativeConnectionInfo::Mysql(url)) => { + Some(KnownError::new(common::DatabaseAccessDenied { + database_user: url.username().into_owned(), + database_name: url.dbname().to_owned(), + })) + } + _ => unreachable!(), + }, + + (ErrorKind::DatabaseAlreadyExists { .. }, ConnectionInfo::External(_)) => default_value, + #[cfg(not(target_arch = "wasm32"))] + (ErrorKind::DatabaseAlreadyExists { db_name }, _) => match connection_info { + ConnectionInfo::Native(NativeConnectionInfo::Postgres(url)) => { + Some(KnownError::new(common::DatabaseAlreadyExists { + database_name: format!("{db_name}"), + database_host: url.host().to_owned(), + database_port: url.port(), + })) + } + ConnectionInfo::Native(NativeConnectionInfo::Mysql(url)) => { + Some(KnownError::new(common::DatabaseAlreadyExists { + database_name: format!("{db_name}"), + database_host: url.host().to_owned(), + database_port: url.port(), + })) + } + _ => unreachable!(), + }, + + (ErrorKind::AuthenticationFailed { .. }, ConnectionInfo::External(_)) => default_value, + #[cfg(not(target_arch = "wasm32"))] + (ErrorKind::AuthenticationFailed { user }, _) => match connection_info { + ConnectionInfo::Native(NativeConnectionInfo::Postgres(url)) => { + Some(KnownError::new(common::IncorrectDatabaseCredentials { + database_user: format!("{user}"), + database_host: url.host().to_owned(), + })) + } + ConnectionInfo::Native(NativeConnectionInfo::Mysql(url)) => { + Some(KnownError::new(common::IncorrectDatabaseCredentials { + database_user: format!("{user}"), + database_host: url.host().to_owned(), + })) + } + _ => unreachable!(), + }, + + (ErrorKind::SocketTimeout { .. }, ConnectionInfo::External(_)) => default_value, + #[cfg(not(target_arch = "wasm32"))] + (ErrorKind::SocketTimeout, _) => match connection_info { + ConnectionInfo::Native(NativeConnectionInfo::Postgres(url)) => { + let time = match url.socket_timeout() { + Some(dur) => format!("{}s", dur.as_secs()), + None => String::from("N/A"), + }; + + Some(KnownError::new(common::DatabaseOperationTimeout { + time, + context: "Socket timeout (the database failed to respond to a query within the configured timeout — see https://pris.ly/d/mssql-connector for more details.)." + .into(), + })) + } + ConnectionInfo::Native(NativeConnectionInfo::Mysql(url)) => { + let time = match url.socket_timeout() { + Some(dur) => format!("{}s", dur.as_secs()), + None => String::from("N/A"), + }; + + Some(KnownError::new(common::DatabaseOperationTimeout { + time, + context: "Socket timeout (the database failed to respond to a query within the configured timeout — see https://pris.ly/d/mysql-connector for more details.)." + .into(), + })) + } + ConnectionInfo::Native(NativeConnectionInfo::Mssql(url)) => { + let time = match url.socket_timeout() { + Some(dur) => format!("{}s", dur.as_secs()), + None => String::from("N/A"), + }; + + Some(KnownError::new(common::DatabaseOperationTimeout { + time, + context: "Socket timeout (the database failed to respond to a query within the configured timeout — see https://pris.ly/d/postgres-connector for more details.)." + .into(), + })) + } + _ => unreachable!(), + }, + + (ErrorKind::TableDoesNotExist { .. }, ConnectionInfo::External(_)) => default_value, + #[cfg(not(target_arch = "wasm32"))] + (ErrorKind::TableDoesNotExist { table: model }, _) => match connection_info { + ConnectionInfo::Native(NativeConnectionInfo::Postgres(_)) => Some(KnownError::new(common::InvalidModel { + model: format!("{model}"), + kind: common::ModelKind::Table, + })), + ConnectionInfo::Native(NativeConnectionInfo::Mysql(_)) => Some(KnownError::new(common::InvalidModel { + model: format!("{model}"), + kind: common::ModelKind::Table, + })), + ConnectionInfo::Native(NativeConnectionInfo::Sqlite { .. }) => { + Some(KnownError::new(common::InvalidModel { + model: format!("{model}"), + kind: common::ModelKind::Table, + })) + } + ConnectionInfo::Native(NativeConnectionInfo::Mssql(_)) => Some(KnownError::new(common::InvalidModel { + model: format!("{model}"), + kind: common::ModelKind::Table, + })), + _ => unreachable!(), + }, (ErrorKind::UniqueConstraintViolation { constraint }, _) => { Some(KnownError::new(query_engine::UniqueKeyViolation { @@ -129,75 +207,6 @@ pub fn render_quaint_error(kind: &ErrorKind, connection_info: &ConnectionInfo) - })) } - (ErrorKind::TlsError { message }, _) => Some(KnownError::new(common::TlsConnectionError { - message: message.into(), - })), - - (ErrorKind::ConnectTimeout, ConnectionInfo::Mysql(url)) => { - Some(KnownError::new(common::DatabaseNotReachable { - database_host: url.host().to_owned(), - database_port: url.port(), - })) - } - - (ErrorKind::ConnectTimeout, ConnectionInfo::Postgres(url)) => { - Some(KnownError::new(common::DatabaseNotReachable { - database_host: url.host().to_owned(), - database_port: url.port(), - })) - } - - (ErrorKind::ConnectTimeout, ConnectionInfo::Mssql(url)) => { - Some(KnownError::new(common::DatabaseNotReachable { - database_host: url.host().to_owned(), - database_port: url.port(), - })) - } - - (ErrorKind::SocketTimeout, ConnectionInfo::Mysql(url)) => { - let time = match url.socket_timeout() { - Some(dur) => format!("{}s", dur.as_secs()), - None => String::from("N/A"), - }; - - Some(KnownError::new(common::DatabaseOperationTimeout { - time, - context: "Socket timeout (the database failed to respond to a query within the configured timeout — see https://pris.ly/d/mysql-connector for more details.)." - .into(), - })) - } - - (ErrorKind::SocketTimeout, ConnectionInfo::Postgres(url)) => { - let time = match url.socket_timeout() { - Some(dur) => format!("{}s", dur.as_secs()), - None => String::from("N/A"), - }; - - Some(KnownError::new(common::DatabaseOperationTimeout { - time, - context: "Socket timeout (the database failed to respond to a query within the configured timeout — see https://pris.ly/d/mssql-connector for more details.)." - .into(), - })) - } - - (ErrorKind::SocketTimeout, ConnectionInfo::Mssql(url)) => { - let time = match url.socket_timeout() { - Some(dur) => format!("{}s", dur.as_secs()), - None => String::from("N/A"), - }; - - Some(KnownError::new(common::DatabaseOperationTimeout { - time, - context: "Socket timeout (the database failed to respond to a query within the configured timeout — see https://pris.ly/d/postgres-connector for more details.)." - .into(), - })) - } - - (ErrorKind::PoolTimeout { max_open, timeout, .. }, _) => Some(KnownError::new(query_engine::PoolTimeout { - connection_limit: *max_open, - timeout: *timeout, - })), - (ErrorKind::DatabaseUrlIsInvalid(details), _connection_info) => { Some(KnownError::new(common::InvalidConnectionString { details: details.to_owned(), @@ -216,42 +225,50 @@ pub fn render_quaint_error(kind: &ErrorKind, connection_info: &ConnectionInfo) - })) } - (ErrorKind::TableDoesNotExist { table: model }, ConnectionInfo::Mysql(_)) => { - Some(KnownError::new(common::InvalidModel { - model: format!("{model}"), - kind: ModelKind::Table, - })) - } - - (ErrorKind::TableDoesNotExist { table: model }, ConnectionInfo::Postgres(_)) => { - Some(KnownError::new(common::InvalidModel { - model: format!("{model}"), - kind: ModelKind::Table, - })) - } - - (ErrorKind::TableDoesNotExist { table: model }, ConnectionInfo::Sqlite { .. }) => { - Some(KnownError::new(common::InvalidModel { - model: format!("{model}"), - kind: ModelKind::Table, - })) - } - - (ErrorKind::TableDoesNotExist { table: model }, ConnectionInfo::Mssql(_)) => { - Some(KnownError::new(common::InvalidModel { - model: format!("{model}"), - kind: ModelKind::Table, - })) - } - - (ErrorKind::IncorrectNumberOfParameters { expected, actual }, ConnectionInfo::Mssql(_)) => { - Some(KnownError::new(common::IncorrectNumberOfParameters { - expected: *expected, - actual: *actual, - })) - } - - (ErrorKind::ConnectionClosed, _) => Some(KnownError::new(common::ConnectionClosed)), + #[cfg(not(target_arch = "wasm32"))] + (ErrorKind::Native(native_error_kind), _) => match (native_error_kind, connection_info) { + (NativeErrorKind::ConnectionError(_), ConnectionInfo::Native(NativeConnectionInfo::Postgres(url))) => { + Some(KnownError::new(common::DatabaseNotReachable { + database_port: url.port(), + database_host: url.host().to_owned(), + })) + } + (NativeErrorKind::ConnectionError(_), ConnectionInfo::Native(NativeConnectionInfo::Mysql(url))) => { + Some(KnownError::new(common::DatabaseNotReachable { + database_port: url.port(), + database_host: url.host().to_owned(), + })) + } + (NativeErrorKind::TlsError { message }, _) => Some(KnownError::new(common::TlsConnectionError { + message: message.into(), + })), + (NativeErrorKind::ConnectTimeout, ConnectionInfo::Native(NativeConnectionInfo::Postgres(url))) => { + Some(KnownError::new(common::DatabaseNotReachable { + database_host: url.host().to_owned(), + database_port: url.port(), + })) + } + (NativeErrorKind::ConnectTimeout, ConnectionInfo::Native(NativeConnectionInfo::Mysql(url))) => { + Some(KnownError::new(common::DatabaseNotReachable { + database_host: url.host().to_owned(), + database_port: url.port(), + })) + } + (NativeErrorKind::ConnectTimeout, ConnectionInfo::Native(NativeConnectionInfo::Mssql(url))) => { + Some(KnownError::new(common::DatabaseNotReachable { + database_host: url.host().to_owned(), + database_port: url.port(), + })) + } + (NativeErrorKind::PoolTimeout { max_open, timeout, .. }, _) => { + Some(KnownError::new(query_engine::PoolTimeout { + connection_limit: *max_open, + timeout: *timeout, + })) + } + (NativeErrorKind::ConnectionClosed, _) => Some(KnownError::new(common::ConnectionClosed)), + _ => unreachable!(), + }, _ => None, } diff --git a/quaint/src/connector.rs b/quaint/src/connector.rs index d56ca737bd80..475856936566 100644 --- a/quaint/src/connector.rs +++ b/quaint/src/connector.rs @@ -13,6 +13,8 @@ mod connection_info; pub mod external; pub mod metrics; +#[cfg(feature = "native")] +pub mod native; mod queryable; mod result_set; #[cfg(any(feature = "mssql-native", feature = "postgresql-native", feature = "mysql-native"))] @@ -22,6 +24,10 @@ mod type_identifier; pub use self::result_set::*; pub use connection_info::*; + +#[cfg(feature = "native")] +pub use native::*; + pub use external::*; pub use queryable::*; pub use transaction::*; diff --git a/quaint/src/connector/connection_info.rs b/quaint/src/connector/connection_info.rs index 0abe9adb28f3..bef28248f9e0 100644 --- a/quaint/src/connector/connection_info.rs +++ b/quaint/src/connector/connection_info.rs @@ -1,3 +1,5 @@ +#![cfg_attr(target_arch = "wasm32", allow(unused_imports))] + use crate::error::{Error, ErrorKind}; use std::{borrow::Cow, fmt}; use url::Url; @@ -15,30 +17,14 @@ use std::convert::TryFrom; use super::ExternalConnectionInfo; +#[cfg(not(target_arch = "wasm32"))] +use super::NativeConnectionInfo; + /// General information about a SQL connection. #[derive(Debug, Clone)] pub enum ConnectionInfo { - /// A PostgreSQL connection URL. - #[cfg(feature = "postgresql")] - Postgres(PostgresUrl), - /// A MySQL connection URL. - #[cfg(feature = "mysql")] - Mysql(MysqlUrl), - /// A SQL Server connection URL. - #[cfg(feature = "mssql")] - Mssql(MssqlUrl), - /// A SQLite connection URL. - #[cfg(feature = "sqlite")] - Sqlite { - /// The filesystem path of the SQLite database. - file_path: String, - /// The name the database is bound to - Always "main" - db_name: String, - }, - #[cfg(feature = "sqlite")] - InMemorySqlite { - db_name: String, - }, + #[cfg(not(target_arch = "wasm32"))] + Native(NativeConnectionInfo), External(ExternalConnectionInfo), } @@ -47,6 +33,7 @@ impl ConnectionInfo { /// /// Will fail if URI is invalid or the scheme points to an unsupported /// database. + #[cfg(not(target_arch = "wasm32"))] pub fn from_url(url_str: &str) -> crate::Result { let url_result: Result = url_str.parse(); @@ -57,15 +44,17 @@ impl ConnectionInfo { if url_result.is_err() { let params = SqliteParams::try_from(s)?; - return Ok(ConnectionInfo::Sqlite { + 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::Mssql(MssqlUrl::new(url_str)?)); + return Ok(ConnectionInfo::Native(NativeConnectionInfo::Mssql(MssqlUrl::new( + url_str, + )?))); } _ => (), } @@ -81,18 +70,20 @@ impl ConnectionInfo { match sql_family { #[cfg(feature = "mysql")] - SqlFamily::Mysql => Ok(ConnectionInfo::Mysql(MysqlUrl::new(url)?)), + SqlFamily::Mysql => Ok(ConnectionInfo::Native(NativeConnectionInfo::Mysql(MysqlUrl::new(url)?))), #[cfg(feature = "sqlite")] SqlFamily::Sqlite => { let params = SqliteParams::try_from(url_str)?; - Ok(ConnectionInfo::Sqlite { + Ok(ConnectionInfo::Native(NativeConnectionInfo::Sqlite { file_path: params.file_path, db_name: params.db_name, - }) + })) } #[cfg(feature = "postgresql")] - SqlFamily::Postgres => Ok(ConnectionInfo::Postgres(PostgresUrl::new(url)?)), + SqlFamily::Postgres => Ok(ConnectionInfo::Native(NativeConnectionInfo::Postgres( + PostgresUrl::new(url)?, + ))), #[allow(unreachable_patterns)] _ => unreachable!(), } @@ -101,14 +92,17 @@ impl ConnectionInfo { /// The provided database name. This will be `None` on SQLite. pub fn dbname(&self) -> Option<&str> { match self { - #[cfg(feature = "postgresql")] - ConnectionInfo::Postgres(url) => Some(url.dbname()), - #[cfg(feature = "mysql")] - ConnectionInfo::Mysql(url) => Some(url.dbname()), - #[cfg(feature = "mssql")] - ConnectionInfo::Mssql(url) => Some(url.dbname()), - #[cfg(feature = "sqlite")] - ConnectionInfo::Sqlite { .. } | ConnectionInfo::InMemorySqlite { .. } => None, + #[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, } } @@ -120,16 +114,19 @@ impl ConnectionInfo { /// - In MySQL, it is the database name. pub fn schema_name(&self) -> &str { match self { - #[cfg(feature = "postgresql")] - ConnectionInfo::Postgres(url) => url.schema(), - #[cfg(feature = "mysql")] - ConnectionInfo::Mysql(url) => url.dbname(), - #[cfg(feature = "mssql")] - ConnectionInfo::Mssql(url) => url.schema(), - #[cfg(feature = "sqlite")] - ConnectionInfo::Sqlite { db_name, .. } => db_name, - #[cfg(feature = "sqlite")] - ConnectionInfo::InMemorySqlite { db_name } => db_name, + #[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, } } @@ -137,30 +134,36 @@ impl ConnectionInfo { /// The provided database host. This will be `"localhost"` on SQLite. pub fn host(&self) -> &str { match self { - #[cfg(feature = "postgresql")] - ConnectionInfo::Postgres(url) => url.host(), - #[cfg(feature = "mysql")] - ConnectionInfo::Mysql(url) => url.host(), - #[cfg(feature = "mssql")] - ConnectionInfo::Mssql(url) => url.host(), - #[cfg(feature = "sqlite")] - ConnectionInfo::Sqlite { .. } | ConnectionInfo::InMemorySqlite { .. } => "localhost", - + #[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", } } /// The provided database user name. This will be `None` on SQLite. pub fn username(&self) -> Option> { + // TODO: why do some of the native `.username()` methods return an `Option<&str>` and others a `Cow`? match self { - #[cfg(feature = "postgresql")] - ConnectionInfo::Postgres(url) => Some(url.username()), - #[cfg(feature = "mysql")] - ConnectionInfo::Mysql(url) => Some(url.username()), - #[cfg(feature = "mssql")] - ConnectionInfo::Mssql(url) => url.username().map(Cow::from), - #[cfg(feature = "sqlite")] - ConnectionInfo::Sqlite { .. } | ConnectionInfo::InMemorySqlite { .. } => None, + #[cfg(not(target_arch = "wasm32"))] + ConnectionInfo::Native(info) => match info { + #[cfg(feature = "postgresql")] + NativeConnectionInfo::Postgres(url) => Some(url.username()).map(Cow::from), + #[cfg(feature = "mysql")] + NativeConnectionInfo::Mysql(url) => Some(url.username()).map(Cow::from), + #[cfg(feature = "mssql")] + NativeConnectionInfo::Mssql(url) => url.username().map(Cow::from), + #[cfg(feature = "sqlite")] + NativeConnectionInfo::Sqlite { .. } | NativeConnectionInfo::InMemorySqlite { .. } => None, + }, ConnectionInfo::External(_) => None, } } @@ -168,16 +171,19 @@ impl ConnectionInfo { /// The database file for SQLite, otherwise `None`. pub fn file_path(&self) -> Option<&str> { match self { - #[cfg(feature = "postgresql")] - ConnectionInfo::Postgres(_) => None, - #[cfg(feature = "mysql")] - ConnectionInfo::Mysql(_) => None, - #[cfg(feature = "mssql")] - ConnectionInfo::Mssql(_) => None, - #[cfg(feature = "sqlite")] - ConnectionInfo::Sqlite { file_path, .. } => Some(file_path), - #[cfg(feature = "sqlite")] - ConnectionInfo::InMemorySqlite { .. } => None, + #[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, } } @@ -185,14 +191,17 @@ impl ConnectionInfo { /// The family of databases connected. pub fn sql_family(&self) -> SqlFamily { match self { - #[cfg(feature = "postgresql")] - ConnectionInfo::Postgres(_) => SqlFamily::Postgres, - #[cfg(feature = "mysql")] - ConnectionInfo::Mysql(_) => SqlFamily::Mysql, - #[cfg(feature = "mssql")] - ConnectionInfo::Mssql(_) => SqlFamily::Mssql, - #[cfg(feature = "sqlite")] - ConnectionInfo::Sqlite { .. } | ConnectionInfo::InMemorySqlite { .. } => SqlFamily::Sqlite, + #[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(), } } @@ -200,24 +209,30 @@ impl ConnectionInfo { /// The provided database port, if applicable. pub fn port(&self) -> Option { match self { - #[cfg(feature = "postgresql")] - ConnectionInfo::Postgres(url) => Some(url.port()), - #[cfg(feature = "mysql")] - ConnectionInfo::Mysql(url) => Some(url.port()), - #[cfg(feature = "mssql")] - ConnectionInfo::Mssql(url) => Some(url.port()), - #[cfg(feature = "sqlite")] - ConnectionInfo::Sqlite { .. } | ConnectionInfo::InMemorySqlite { .. } => None, + #[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, } } /// Whether the pgbouncer mode is enabled. pub fn pg_bouncer(&self) -> bool { - #[allow(unreachable_patterns)] match self { - #[cfg(feature = "postgresql")] - ConnectionInfo::Postgres(url) => url.pg_bouncer(), + #[cfg(not(target_arch = "wasm32"))] + ConnectionInfo::Native(info) => match info { + #[cfg(feature = "postgresql")] + NativeConnectionInfo::Postgres(url) => url.pg_bouncer(), + _ => false, + }, _ => false, } } @@ -226,16 +241,19 @@ impl ConnectionInfo { /// and port on MySQL/Postgres, and the file path on SQLite. pub fn database_location(&self) -> String { match self { - #[cfg(feature = "postgresql")] - ConnectionInfo::Postgres(url) => format!("{}:{}", url.host(), url.port()), - #[cfg(feature = "mysql")] - ConnectionInfo::Mysql(url) => format!("{}:{}", url.host(), url.port()), - #[cfg(feature = "mssql")] - ConnectionInfo::Mssql(url) => format!("{}:{}", url.host(), url.port()), - #[cfg(feature = "sqlite")] - ConnectionInfo::Sqlite { file_path, .. } => file_path.clone(), - #[cfg(feature = "sqlite")] - ConnectionInfo::InMemorySqlite { .. } => "in-memory".into(), + #[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(), } } @@ -353,7 +371,7 @@ mod tests { let conn_info = ConnectionInfo::from_url("file:dev.db").unwrap(); #[allow(irrefutable_let_patterns)] - if let ConnectionInfo::Sqlite { file_path, db_name: _ } = conn_info { + 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"); @@ -366,7 +384,7 @@ mod tests { let conn_info = ConnectionInfo::from_url("mysql://myuser:my%23pass%23word@lclhst:5432/mydb").unwrap(); #[allow(irrefutable_let_patterns)] - if let ConnectionInfo::Mysql(url) = conn_info { + 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"); diff --git a/quaint/src/connector/mssql/native/error.rs b/quaint/src/connector/mssql/native/error.rs index f9b6f5e95ab6..9c16bf9f2952 100644 --- a/quaint/src/connector/mssql/native/error.rs +++ b/quaint/src/connector/mssql/native/error.rs @@ -1,4 +1,4 @@ -use crate::error::{DatabaseConstraint, Error, ErrorKind}; +use crate::error::{DatabaseConstraint, Error, ErrorKind, NativeErrorKind}; use tiberius::error::IoErrorKind; impl From for Error { @@ -8,17 +8,19 @@ impl From for Error { kind: IoErrorKind::UnexpectedEof, message, } => { - let mut builder = Error::builder(ErrorKind::ConnectionClosed); + let mut builder = Error::builder(ErrorKind::Native(NativeErrorKind::ConnectionClosed)); builder.set_original_message(message); builder.build() } - e @ tiberius::error::Error::Io { .. } => Error::builder(ErrorKind::ConnectionError(e.into())).build(), + e @ tiberius::error::Error::Io { .. } => { + Error::builder(ErrorKind::Native(NativeErrorKind::ConnectionError(e.into()))).build() + } tiberius::error::Error::Tls(message) => { let message = format!( "The TLS settings didn't allow the connection to be established. Please review your connection string. (error: {message})" ); - Error::builder(ErrorKind::TlsError { message }).build() + Error::builder(ErrorKind::Native(NativeErrorKind::TlsError { message })).build() } tiberius::error::Error::Server(e) if [3902u32, 3903u32, 3971u32].iter().any(|code| e.code() == *code) => { let kind = ErrorKind::TransactionAlreadyClosed(e.message().to_string()); diff --git a/quaint/src/connector/mysql/native/error.rs b/quaint/src/connector/mysql/native/error.rs index 89c21fb706f6..0d9e58ccd9dc 100644 --- a/quaint/src/connector/mysql/native/error.rs +++ b/quaint/src/connector/mysql/native/error.rs @@ -1,6 +1,6 @@ use crate::{ connector::mysql::error::MysqlError, - error::{Error, ErrorKind}, + error::{Error, ErrorKind, NativeErrorKind}, }; use mysql_async as my; @@ -17,14 +17,16 @@ impl From<&my::ServerError> for MysqlError { impl From for Error { fn from(e: my::Error) -> Error { match e { - my::Error::Io(my::IoError::Tls(err)) => Error::builder(ErrorKind::TlsError { + my::Error::Io(my::IoError::Tls(err)) => Error::builder(ErrorKind::Native(NativeErrorKind::TlsError { message: err.to_string(), - }) + })) .build(), my::Error::Io(my::IoError::Io(err)) if err.kind() == std::io::ErrorKind::UnexpectedEof => { - Error::builder(ErrorKind::ConnectionClosed).build() + Error::builder(ErrorKind::Native(NativeErrorKind::ConnectionClosed)).build() + } + my::Error::Io(io_error) => { + Error::builder(ErrorKind::Native(NativeErrorKind::ConnectionError(io_error.into()))).build() } - my::Error::Io(io_error) => Error::builder(ErrorKind::ConnectionError(io_error.into())).build(), my::Error::Driver(e) => Error::builder(ErrorKind::QueryError(e.into())).build(), my::Error::Server(ref server_error) => { let mysql_error: MysqlError = server_error.into(); diff --git a/quaint/src/connector/native.rs b/quaint/src/connector/native.rs new file mode 100644 index 000000000000..b9cf4b9858e6 --- /dev/null +++ b/quaint/src/connector/native.rs @@ -0,0 +1,31 @@ +#[cfg(feature = "mssql")] +use crate::connector::MssqlUrl; +#[cfg(feature = "mysql")] +use crate::connector::MysqlUrl; +#[cfg(feature = "postgresql")] +use crate::connector::PostgresUrl; + +/// General information about a SQL connection, provided by native Rust drivers. +#[cfg(not(target_arch = "wasm32"))] +#[derive(Debug, Clone)] +pub enum NativeConnectionInfo { + /// A PostgreSQL connection URL. + #[cfg(feature = "postgresql")] + Postgres(PostgresUrl), + /// A MySQL connection URL. + #[cfg(feature = "mysql")] + Mysql(MysqlUrl), + /// A SQL Server connection URL. + #[cfg(feature = "mssql")] + Mssql(MssqlUrl), + /// A SQLite connection URL. + #[cfg(feature = "sqlite")] + Sqlite { + /// The filesystem path of the SQLite database. + file_path: String, + /// The name the database is bound to - Always "main" + db_name: String, + }, + #[cfg(feature = "sqlite")] + InMemorySqlite { db_name: String }, +} diff --git a/quaint/src/connector/postgres/native/error.rs b/quaint/src/connector/postgres/native/error.rs index c353e397705c..6ceb26299691 100644 --- a/quaint/src/connector/postgres/native/error.rs +++ b/quaint/src/connector/postgres/native/error.rs @@ -2,7 +2,7 @@ use tokio_postgres::error::DbError; use crate::{ connector::postgres::error::PostgresError, - error::{Error, ErrorKind}, + error::{Error, ErrorKind, NativeErrorKind}, }; impl From<&DbError> for PostgresError { @@ -21,7 +21,7 @@ impl From<&DbError> for PostgresError { impl From for Error { fn from(e: tokio_postgres::error::Error) -> Error { if e.is_closed() { - return Error::builder(ErrorKind::ConnectionClosed).build(); + return Error::builder(ErrorKind::Native(NativeErrorKind::ConnectionClosed)).build(); } if let Some(db_error) = e.as_db_error() { @@ -46,7 +46,7 @@ impl From for Error { match reason.as_str() { "error connecting to server: timed out" => { - let mut builder = Error::builder(ErrorKind::ConnectTimeout); + let mut builder = Error::builder(ErrorKind::Native(NativeErrorKind::ConnectTimeout)); if let Some(code) = code { builder.set_original_code(code); @@ -57,9 +57,9 @@ impl From for Error { } // sigh... // https://github.com/sfackler/rust-postgres/blob/0c84ed9f8201f4e5b4803199a24afa2c9f3723b2/tokio-postgres/src/connect_tls.rs#L37 "error performing TLS handshake: server does not support TLS" => { - let mut builder = Error::builder(ErrorKind::TlsError { + let mut builder = Error::builder(ErrorKind::Native(NativeErrorKind::TlsError { message: reason.clone(), - }); + })); if let Some(code) = code { builder.set_original_code(code); @@ -105,7 +105,12 @@ fn try_extracting_io_error(err: &tokio_postgres::error::Error) -> Option err.source() .and_then(|err| err.downcast_ref::()) - .map(|err| ErrorKind::ConnectionError(Box::new(std::io::Error::new(err.kind(), format!("{err}"))))) + .map(|err| { + ErrorKind::Native(NativeErrorKind::ConnectionError(Box::new(std::io::Error::new( + err.kind(), + format!("{err}"), + )))) + }) .map(|kind| Error::builder(kind).build()) } @@ -117,9 +122,9 @@ impl From for Error { impl From<&native_tls::Error> for Error { fn from(e: &native_tls::Error) -> Error { - let kind = ErrorKind::TlsError { + let kind = ErrorKind::Native(NativeErrorKind::TlsError { message: format!("{e}"), - }; + }); Error::builder(kind).build() } diff --git a/quaint/src/connector/postgres/native/mod.rs b/quaint/src/connector/postgres/native/mod.rs index 30f34e7002be..2601a709487b 100644 --- a/quaint/src/connector/postgres/native/mod.rs +++ b/quaint/src/connector/postgres/native/mod.rs @@ -7,6 +7,7 @@ mod error; pub(crate) use crate::connector::postgres::url::PostgresUrl; use crate::connector::postgres::url::{Hidden, SslAcceptMode, SslParams}; use crate::connector::{timeout, IsolationLevel, Transaction}; +use crate::error::NativeErrorKind; use crate::{ ast::{Query, Value}, @@ -93,9 +94,9 @@ impl SslParams { if let Some(ref cert_file) = self.certificate_file { let cert = fs::read(cert_file).map_err(|err| { - Error::builder(ErrorKind::TlsError { + Error::builder(ErrorKind::Native(NativeErrorKind::TlsError { message: format!("cert file not found ({err})"), - }) + })) .build() })?; @@ -104,9 +105,9 @@ impl SslParams { if let Some(ref identity_file) = self.identity_file { let db = fs::read(identity_file).map_err(|err| { - Error::builder(ErrorKind::TlsError { + Error::builder(ErrorKind::Native(NativeErrorKind::TlsError { message: format!("identity file not found ({err})"), - }) + })) .build() })?; let password = self.identity_password.0.as_deref().unwrap_or(""); @@ -305,11 +306,11 @@ impl PostgreSql { if params.len() > i16::MAX as usize { // tokio_postgres would return an error here. Let's avoid calling the driver // and return an error early. - let kind = ErrorKind::QueryInvalidInput(format!( + let kind = ErrorKind::Native(NativeErrorKind::QueryInvalidInput(format!( "too many bind variables in prepared statement, expected maximum of {}, received {}", i16::MAX, params.len() - )); + ))); Err(Error::builder(kind).build()) } else { Ok(()) @@ -371,10 +372,10 @@ impl Queryable for PostgreSql { let stmt = self.fetch_cached(sql, &[]).await?; if stmt.params().len() != params.len() { - let kind = ErrorKind::IncorrectNumberOfParameters { + let kind = ErrorKind::Native(NativeErrorKind::IncorrectNumberOfParameters { expected: stmt.params().len(), actual: params.len(), - }; + }); return Err(Error::builder(kind).build()); } @@ -401,10 +402,10 @@ impl Queryable for PostgreSql { let stmt = self.fetch_cached(sql, params).await?; if stmt.params().len() != params.len() { - let kind = ErrorKind::IncorrectNumberOfParameters { + let kind = ErrorKind::Native(NativeErrorKind::IncorrectNumberOfParameters { expected: stmt.params().len(), actual: params.len(), - }; + }); return Err(Error::builder(kind).build()); } @@ -437,10 +438,10 @@ impl Queryable for PostgreSql { let stmt = self.fetch_cached(sql, &[]).await?; if stmt.params().len() != params.len() { - let kind = ErrorKind::IncorrectNumberOfParameters { + let kind = ErrorKind::Native(NativeErrorKind::IncorrectNumberOfParameters { expected: stmt.params().len(), actual: params.len(), - }; + }); return Err(Error::builder(kind).build()); } @@ -461,10 +462,10 @@ impl Queryable for PostgreSql { let stmt = self.fetch_cached(sql, params).await?; if stmt.params().len() != params.len() { - let kind = ErrorKind::IncorrectNumberOfParameters { + let kind = ErrorKind::Native(NativeErrorKind::IncorrectNumberOfParameters { expected: stmt.params().len(), actual: params.len(), - }; + }); return Err(Error::builder(kind).build()); } diff --git a/quaint/src/connector/postgres/url.rs b/quaint/src/connector/postgres/url.rs index 4970ae6b1a9d..cab76e58a90d 100644 --- a/quaint/src/connector/postgres/url.rs +++ b/quaint/src/connector/postgres/url.rs @@ -608,7 +608,7 @@ mod tests { match res { Ok(_) => unreachable!(), Err(e) => match e.kind() { - ErrorKind::TlsError { .. } => (), + ErrorKind::Native(NativeErrorKind::TlsError { .. }) => (), other => panic!("{:#?}", other), }, } @@ -626,7 +626,7 @@ mod tests { match res { Ok(_) => unreachable!(), Err(e) => match e.kind() { - ErrorKind::IncorrectNumberOfParameters { expected, actual } => { + ErrorKind::Native(NativeErrorKind::IncorrectNumberOfParameters { expected, actual }) => { assert_eq!(1, *expected); assert_eq!(2, *actual); } diff --git a/quaint/src/connector/timeout.rs b/quaint/src/connector/timeout.rs index 7eec9dcd0506..a0445c4c7a26 100644 --- a/quaint/src/connector/timeout.rs +++ b/quaint/src/connector/timeout.rs @@ -2,12 +2,16 @@ use crate::error::{Error, ErrorKind}; use futures::Future; use std::time::Duration; +#[cfg(feature = "native")] pub async fn connect(duration: Option, f: F) -> crate::Result where F: Future>, E: Into, { - timeout(duration, f, || Error::builder(ErrorKind::ConnectTimeout).build()).await + timeout(duration, f, || { + Error::builder(ErrorKind::Native(crate::error::NativeErrorKind::ConnectTimeout)).build() + }) + .await } pub async fn socket(duration: Option, f: F) -> crate::Result diff --git a/quaint/src/error.rs b/quaint/src/error/mod.rs similarity index 82% rename from quaint/src/error.rs rename to quaint/src/error/mod.rs index a77513876726..ccbb132291f7 100644 --- a/quaint/src/error.rs +++ b/quaint/src/error/mod.rs @@ -1,14 +1,24 @@ //! Error module + +#[cfg(not(target_arch = "wasm32"))] +pub mod native; + +pub(crate) mod name; + use crate::connector::IsolationLevel; -use std::{borrow::Cow, fmt, io, num}; +use std::{borrow::Cow, fmt, num}; use thiserror::Error; #[cfg(feature = "pooled")] use std::time::Duration; +#[cfg(not(target_arch = "wasm32"))] +pub use native::NativeErrorKind; + pub use crate::connector::mysql::MysqlError; pub use crate::connector::postgres::PostgresError; pub use crate::connector::sqlite::SqliteError; +pub(crate) use name::Name; #[derive(Debug, PartialEq, Eq)] pub enum DatabaseConstraint { @@ -41,39 +51,6 @@ impl fmt::Display for DatabaseConstraint { } } -#[derive(Debug, PartialEq, Eq)] -pub enum Name { - Available(String), - Unavailable, -} - -impl Name { - pub fn available(name: impl ToString) -> Self { - Self::Available(name.to_string()) - } -} - -impl fmt::Display for Name { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Available(name) => name.fmt(f), - Self::Unavailable => write!(f, "(not available)"), - } - } -} - -impl From> for Name -where - T: ToString, -{ - fn from(name: Option) -> Self { - match name { - Some(name) => Self::available(name), - None => Self::Unavailable, - } - } -} - #[derive(Debug, Error)] /// The error types for database I/O, connection and query parameter /// construction. @@ -134,8 +111,9 @@ impl Error { } /// Determines if the error was associated with closed connection. + #[cfg(not(target_arch = "wasm32"))] pub fn is_closed(&self) -> bool { - matches!(self.kind, ErrorKind::ConnectionClosed) + matches!(self.kind, ErrorKind::Native(NativeErrorKind::ConnectionClosed)) } // Builds an error from a raw error coming from the connector @@ -157,15 +135,16 @@ impl fmt::Display for Error { #[derive(Debug, Error)] pub enum ErrorKind { + #[cfg(not(target_arch = "wasm32"))] + #[error("Error in the underlying connector")] + Native(NativeErrorKind), + #[error("Error in the underlying connector ({}): {}", status, reason)] RawConnectorError { status: String, reason: String }, #[error("Error querying the database: {}", _0)] QueryError(Box), - #[error("Invalid input provided to query: {}", _0)] - QueryInvalidInput(String), - #[error("Database does not exist: {}", db_name)] DatabaseDoesNotExist { db_name: Name }, @@ -193,9 +172,6 @@ pub enum ErrorKind { #[error("Foreign key constraint failed: {}", constraint)] ForeignKeyConstraintViolation { constraint: DatabaseConstraint }, - #[error("Error creating a database connection.")] - ConnectionError(Box), - #[error("Error reading the column value: {}", _0)] ColumnReadFailure(Box), @@ -220,42 +196,12 @@ pub enum ErrorKind { #[error("The provided arguments are not supported")] InvalidConnectionArguments, - #[error("Error in an I/O operation: {0}")] - IoError(io::Error), - - #[error("Timed out when connecting to the database.")] - ConnectTimeout, - - #[error("The server terminated the connection.")] - ConnectionClosed, - - #[error( - "Timed out fetching a connection from the pool (connection limit: {}, in use: {}, pool timeout {})", - max_open, - in_use, - timeout - )] - PoolTimeout { max_open: u64, in_use: u64, timeout: u64 }, - - #[error("The connection pool has been closed")] - PoolClosed {}, - #[error("Timed out during query execution.")] SocketTimeout, - #[error("Error opening a TLS connection. {}", message)] - TlsError { message: String }, - #[error("Value out of range error. {}", message)] ValueOutOfRange { message: String }, - #[error( - "Incorrect number of parameters given to a statement. Expected {}: got: {}.", - expected, - actual - )] - IncorrectNumberOfParameters { expected: usize, actual: usize }, - #[error("Transaction was already closed: {}", _0)] TransactionAlreadyClosed(String), @@ -281,6 +227,13 @@ pub enum ErrorKind { ExternalError(i32), } +#[cfg(not(target_arch = "wasm32"))] +impl From for Error { + fn from(e: std::io::Error) -> Error { + Error::builder(ErrorKind::Native(NativeErrorKind::IoError(e))).build() + } +} + impl ErrorKind { #[cfg(feature = "mysql-native")] pub(crate) fn value_out_of_range(msg: impl Into) -> Self { @@ -298,11 +251,11 @@ impl ErrorKind { #[cfg(feature = "pooled")] pub(crate) fn pool_timeout(max_open: u64, in_use: u64, timeout: Duration) -> Self { - Self::PoolTimeout { + Self::Native(NativeErrorKind::PoolTimeout { max_open, in_use, timeout: timeout.as_secs(), - } + }) } pub fn invalid_isolation_level(isolation_level: &IsolationLevel) -> Self { @@ -357,12 +310,6 @@ impl From for Error { } } -impl From for Error { - fn from(e: io::Error) -> Error { - Error::builder(ErrorKind::IoError(e)).build() - } -} - impl From for Error { fn from(_e: std::num::ParseIntError) -> Error { Error::builder(ErrorKind::conversion("Couldn't convert data to an integer")).build() diff --git a/quaint/src/error/name.rs b/quaint/src/error/name.rs new file mode 100644 index 000000000000..f4d48a47331b --- /dev/null +++ b/quaint/src/error/name.rs @@ -0,0 +1,32 @@ +#[derive(Debug, PartialEq, Eq)] +pub enum Name { + Available(String), + Unavailable, +} + +impl Name { + pub fn available(name: impl ToString) -> Self { + Self::Available(name.to_string()) + } +} + +impl std::fmt::Display for Name { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Available(name) => name.fmt(f), + Self::Unavailable => write!(f, "(not available)"), + } + } +} + +impl From> for Name +where + T: ToString, +{ + fn from(name: Option) -> Self { + match name { + Some(name) => Self::available(name), + None => Self::Unavailable, + } + } +} diff --git a/quaint/src/error/native.rs b/quaint/src/error/native.rs new file mode 100644 index 000000000000..a787ffead560 --- /dev/null +++ b/quaint/src/error/native.rs @@ -0,0 +1,40 @@ +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum NativeErrorKind { + #[error("Invalid input provided to query: {}", _0)] + QueryInvalidInput(String), + + #[error("Error creating a database connection.")] + ConnectionError(Box), + + #[error("The server terminated the connection.")] + ConnectionClosed, + + #[error("The connection pool has been closed")] + PoolClosed {}, + + #[error( + "Timed out fetching a connection from the pool (connection limit: {}, in use: {}, pool timeout {})", + max_open, + in_use, + timeout + )] + PoolTimeout { max_open: u64, in_use: u64, timeout: u64 }, + + #[error("Error in an I/O operation: {0}")] + IoError(std::io::Error), + + #[error("Timed out when connecting to the database.")] + ConnectTimeout, + + #[error("Error opening a TLS connection. {}", message)] + TlsError { message: String }, + + #[error( + "Incorrect number of parameters given to a statement. Expected {}: got: {}.", + expected, + actual + )] + IncorrectNumberOfParameters { expected: usize, actual: usize }, +} diff --git a/quaint/src/pooled.rs b/quaint/src/pooled.rs index 4c4152923377..2dc1a843eba1 100644 --- a/quaint/src/pooled.rs +++ b/quaint/src/pooled.rs @@ -152,6 +152,9 @@ mod manager; pub use manager::*; +#[cfg(feature = "native")] +use crate::{connector::NativeConnectionInfo, error::NativeErrorKind}; + use crate::{ connector::{ConnectionInfo, PostgresFlavour}, error::{Error, ErrorKind}, @@ -303,7 +306,7 @@ impl Builder { /// /// - Defaults to `PostgresFlavour::Unknown`. pub fn set_postgres_flavour(&mut self, flavour: PostgresFlavour) { - if let ConnectionInfo::Postgres(ref mut url) = self.connection_info { + if let ConnectionInfo::Native(NativeConnectionInfo::Postgres(ref mut url)) = self.connection_info { url.set_flavour(flavour); } @@ -484,7 +487,9 @@ impl Quaint { let inner = match res { Ok(conn) => conn, - Err(mobc::Error::PoolClosed) => return Err(Error::builder(ErrorKind::PoolClosed {}).build()), + Err(mobc::Error::PoolClosed) => { + return Err(Error::builder(ErrorKind::Native(NativeErrorKind::PoolClosed {})).build()) + } Err(mobc::Error::Timeout) => { let state = self.inner.state().await; // We can use unwrap here because a pool timeout has to be set to use a connection pool @@ -495,7 +500,7 @@ impl Quaint { } Err(mobc::Error::Inner(e)) => return Err(e), Err(e @ mobc::Error::BadConn) => { - let error = Error::builder(ErrorKind::ConnectionError(Box::new(e))).build(); + let error = Error::builder(ErrorKind::Native(NativeErrorKind::ConnectionError(Box::new(e)))).build(); return Err(error); } }; diff --git a/quaint/src/single.rs b/quaint/src/single.rs index b819259d81c7..653ac990b2e2 100644 --- a/quaint/src/single.rs +++ b/quaint/src/single.rs @@ -10,6 +10,9 @@ use std::{fmt, sync::Arc}; #[cfg(feature = "sqlite-native")] use std::convert::TryFrom; +#[cfg(feature = "native")] +use crate::connector::NativeConnectionInfo; + /// The main entry point and an abstraction over a database connection. #[derive(Clone)] pub struct Quaint { @@ -125,7 +128,7 @@ impl Quaint { /// - `isolationLevel` the transaction isolation level. Possible values: /// `READ UNCOMMITTED`, `READ COMMITTED`, `REPEATABLE READ`, `SNAPSHOT`, /// `SERIALIZABLE`. - #[cfg_attr(target_arch = "wasm32", allow(unused_variables))] + #[cfg(feature = "native")] #[allow(unreachable_code)] pub async fn new(url_str: &str) -> crate::Result { let inner = match url_str { @@ -172,9 +175,9 @@ impl Quaint { Ok(Quaint { inner: Arc::new(connector::Sqlite::new_in_memory()?), - connection_info: Arc::new(ConnectionInfo::InMemorySqlite { + connection_info: Arc::new(ConnectionInfo::Native(NativeConnectionInfo::InMemorySqlite { db_name: DEFAULT_SQLITE_DATABASE.to_owned(), - }), + })), }) } @@ -183,6 +186,7 @@ impl Quaint { &self.connection_info } + #[cfg(feature = "native")] fn log_start(info: &ConnectionInfo) { let family = info.sql_family(); let pg_bouncer = if info.pg_bouncer() { " in PgBouncer mode" } else { "" }; diff --git a/query-engine/connectors/sql-query-connector/src/error.rs b/query-engine/connectors/sql-query-connector/src/error.rs index a7770879c510..0296806ebbfc 100644 --- a/query-engine/connectors/sql-query-connector/src/error.rs +++ b/query-engine/connectors/sql-query-connector/src/error.rs @@ -5,13 +5,19 @@ use std::{any::Any, string::FromUtf8Error}; use thiserror::Error; use user_facing_errors::query_engine::DatabaseConstraint; -pub(crate) enum RawError { - IncorrectNumberOfParameters { - expected: usize, - actual: usize, - }, +#[cfg(not(target_arch = "wasm32"))] +use quaint::error::NativeErrorKind; + +#[cfg(not(target_arch = "wasm32"))] +pub(crate) enum NativeRawError { + IncorrectNumberOfParameters { expected: usize, actual: usize }, QueryInvalidInput(String), ConnectionClosed, +} + +pub(crate) enum RawError { + #[cfg(not(target_arch = "wasm32"))] + Native(NativeRawError), Database { code: Option, message: Option, @@ -27,17 +33,20 @@ pub(crate) enum RawError { impl From for SqlError { fn from(re: RawError) -> SqlError { match re { - RawError::IncorrectNumberOfParameters { expected, actual } => { - Self::IncorrectNumberOfParameters { expected, actual } - } - RawError::QueryInvalidInput(message) => Self::QueryInvalidInput(message), + #[cfg(not(target_arch = "wasm32"))] + RawError::Native(native) => match native { + NativeRawError::IncorrectNumberOfParameters { expected, actual } => { + SqlError::IncorrectNumberOfParameters { expected, actual } + } + NativeRawError::QueryInvalidInput(message) => SqlError::QueryInvalidInput(message), + NativeRawError::ConnectionClosed => SqlError::ConnectionClosed, + }, RawError::UnsupportedColumnType { column_type } => Self::RawError { code: String::from("N/A"), message: format!( r#"Failed to deserialize column of type '{column_type}'. If you're using $queryRaw and this column is explicitly marked as `Unsupported` in your Prisma schema, try casting this column to any supported Prisma type such as `String`."# ), }, - RawError::ConnectionClosed => Self::ConnectionClosed, RawError::Database { code, message } => Self::RawError { code: code.unwrap_or_else(|| String::from("N/A")), message: message.unwrap_or_else(|| String::from("N/A")), @@ -49,23 +58,32 @@ impl From for SqlError { impl From for RawError { fn from(e: quaint::error::Error) -> Self { + let default_value: RawError = Self::Database { + code: e.original_code().map(ToString::to_string), + message: e.original_message().map(ToString::to_string), + }; + match e.kind() { - quaint::error::ErrorKind::IncorrectNumberOfParameters { expected, actual } => { - Self::IncorrectNumberOfParameters { - expected: *expected, - actual: *actual, + #[cfg(not(target_arch = "wasm32"))] + quaint::error::ErrorKind::Native(native_error_kind) => match native_error_kind { + NativeErrorKind::IncorrectNumberOfParameters { expected, actual } => { + Self::Native(NativeRawError::IncorrectNumberOfParameters { + expected: *expected, + actual: *actual, + }) } - } - quaint::error::ErrorKind::ConnectionClosed => Self::ConnectionClosed, + NativeErrorKind::ConnectionClosed => Self::Native(NativeRawError::ConnectionClosed), + NativeErrorKind::QueryInvalidInput(message) => { + Self::Native(NativeRawError::QueryInvalidInput(message.to_owned())) + } + _ => default_value, + }, + quaint::error::ErrorKind::UnsupportedColumnType { column_type } => Self::UnsupportedColumnType { column_type: column_type.to_owned(), }, - quaint::error::ErrorKind::QueryInvalidInput(message) => Self::QueryInvalidInput(message.to_owned()), quaint::error::ErrorKind::ExternalError(id) => Self::External { id: *id }, - _ => Self::Database { - code: e.original_code().map(ToString::to_string), - message: e.original_message().map(ToString::to_string), - }, + _ => default_value, } } } @@ -276,13 +294,24 @@ impl From for SqlError { impl From for SqlError { fn from(e: quaint::error::Error) -> Self { match QuaintKind::from(e) { + #[cfg(not(target_arch = "wasm32"))] + e @ QuaintKind::Native(native_error_kind) => match native_error_kind { + NativeErrorKind::QueryInvalidInput(qe) => Self::QueryInvalidInput(qe), + NativeErrorKind::IoError(_) | NativeErrorKind::ConnectionError(_) => Self::ConnectionError(e), + NativeErrorKind::ConnectionClosed => SqlError::ConnectionClosed, + ee @ NativeErrorKind::IncorrectNumberOfParameters { .. } => SqlError::QueryError(ee.into()), + NativeErrorKind::ConnectTimeout => SqlError::ConnectionError(e), + NativeErrorKind::PoolTimeout { .. } => SqlError::ConnectionError(e), + NativeErrorKind::PoolClosed { .. } => SqlError::ConnectionError(e), + NativeErrorKind::TlsError { .. } => Self::ConnectionError(e), + _ => unreachable!(), + }, + QuaintKind::RawConnectorError { status, reason } => Self::RawError { code: status, message: reason, }, QuaintKind::QueryError(qe) => Self::QueryError(qe), - QuaintKind::QueryInvalidInput(qe) => Self::QueryInvalidInput(qe), - e @ QuaintKind::IoError(_) => Self::ConnectionError(e), QuaintKind::NotFound => Self::RecordDoesNotExist, QuaintKind::UniqueConstraintViolation { constraint } => Self::UniqueConstraintViolation { constraint: constraint.into(), @@ -296,18 +325,16 @@ impl From for SqlError { constraint: constraint.into(), }, QuaintKind::MissingFullTextSearchIndex => Self::MissingFullTextSearchIndex, - e @ QuaintKind::ConnectionError(_) => Self::ConnectionError(e), QuaintKind::ColumnReadFailure(e) => Self::ColumnReadFailure(e), QuaintKind::ColumnNotFound { column } => SqlError::ColumnDoesNotExist(format!("{column}")), QuaintKind::TableDoesNotExist { table } => SqlError::TableDoesNotExist(format!("{table}")), - QuaintKind::ConnectionClosed => SqlError::ConnectionClosed, + QuaintKind::InvalidIsolationLevel(msg) => Self::InvalidIsolationLevel(msg), QuaintKind::TransactionWriteConflict => Self::TransactionWriteConflict, QuaintKind::RollbackWithoutBegin => Self::RollbackWithoutBegin, QuaintKind::ExternalError(error_id) => Self::ExternalError(error_id), e @ QuaintKind::UnsupportedColumnType { .. } => SqlError::ConversionError(e.into()), e @ QuaintKind::TransactionAlreadyClosed(_) => SqlError::TransactionAlreadyClosed(format!("{e}")), - e @ QuaintKind::IncorrectNumberOfParameters { .. } => SqlError::QueryError(e.into()), e @ QuaintKind::ConversionError(_) => SqlError::ConversionError(e.into()), e @ QuaintKind::ResultIndexOutOfBounds { .. } => SqlError::QueryError(e.into()), e @ QuaintKind::ResultTypeMismatch { .. } => SqlError::QueryError(e.into()), @@ -320,11 +347,7 @@ impl From for SqlError { e @ QuaintKind::DatabaseAccessDenied { .. } => SqlError::ConnectionError(e), e @ QuaintKind::DatabaseAlreadyExists { .. } => SqlError::ConnectionError(e), e @ QuaintKind::InvalidConnectionArguments => SqlError::ConnectionError(e), - e @ QuaintKind::ConnectTimeout => SqlError::ConnectionError(e), e @ QuaintKind::SocketTimeout => SqlError::ConnectionError(e), - e @ QuaintKind::PoolTimeout { .. } => SqlError::ConnectionError(e), - e @ QuaintKind::PoolClosed { .. } => SqlError::ConnectionError(e), - e @ QuaintKind::TlsError { .. } => Self::ConnectionError(e), } } } From efb02c16ecad11269c34a07320fab26b68955cab Mon Sep 17 00:00:00 2001 From: jkomyno Date: Wed, 27 Dec 2023 13:02:27 +0100 Subject: [PATCH 02/11] feat(query-engine-wasm): fix compilation on native targets --- quaint/src/prelude.rs | 3 +++ .../sql-query-connector/src/error.rs | 23 ++++++++++--------- .../src/flavour/mssql/connection.rs | 9 ++++++-- .../src/flavour/mysql/connection.rs | 9 ++++++-- .../src/flavour/postgres/connection.rs | 9 ++++++-- .../src/multi_engine_test_api.rs | 14 ++++++----- 6 files changed, 44 insertions(+), 23 deletions(-) diff --git a/quaint/src/prelude.rs b/quaint/src/prelude.rs index adaa701210f1..6b28926a5f43 100644 --- a/quaint/src/prelude.rs +++ b/quaint/src/prelude.rs @@ -5,3 +5,6 @@ pub use crate::connector::{ TransactionCapable, }; pub use crate::{col, val, values}; + +#[cfg(feature = "native")] +pub use crate::connector::NativeConnectionInfo; diff --git a/query-engine/connectors/sql-query-connector/src/error.rs b/query-engine/connectors/sql-query-connector/src/error.rs index 0296806ebbfc..4d6604f7a0cd 100644 --- a/query-engine/connectors/sql-query-connector/src/error.rs +++ b/query-engine/connectors/sql-query-connector/src/error.rs @@ -292,19 +292,20 @@ impl From for SqlError { } impl From for SqlError { - fn from(e: quaint::error::Error) -> Self { - match QuaintKind::from(e) { + fn from(error: quaint::error::Error) -> Self { + let quaint_kind = QuaintKind::from(error); + + match quaint_kind { #[cfg(not(target_arch = "wasm32"))] - e @ QuaintKind::Native(native_error_kind) => match native_error_kind { - NativeErrorKind::QueryInvalidInput(qe) => Self::QueryInvalidInput(qe), - NativeErrorKind::IoError(_) | NativeErrorKind::ConnectionError(_) => Self::ConnectionError(e), + QuaintKind::Native(ref native_error_kind) => match native_error_kind { + NativeErrorKind::QueryInvalidInput(ref qe) => Self::QueryInvalidInput(qe.to_owned()), + NativeErrorKind::IoError(_) | NativeErrorKind::ConnectionError(_) => Self::ConnectionError(quaint_kind), NativeErrorKind::ConnectionClosed => SqlError::ConnectionClosed, - ee @ NativeErrorKind::IncorrectNumberOfParameters { .. } => SqlError::QueryError(ee.into()), - NativeErrorKind::ConnectTimeout => SqlError::ConnectionError(e), - NativeErrorKind::PoolTimeout { .. } => SqlError::ConnectionError(e), - NativeErrorKind::PoolClosed { .. } => SqlError::ConnectionError(e), - NativeErrorKind::TlsError { .. } => Self::ConnectionError(e), - _ => unreachable!(), + NativeErrorKind::IncorrectNumberOfParameters { .. } => SqlError::QueryError(quaint_kind.into()), + NativeErrorKind::ConnectTimeout => SqlError::ConnectionError(quaint_kind), + NativeErrorKind::PoolTimeout { .. } => SqlError::ConnectionError(quaint_kind), + NativeErrorKind::PoolClosed { .. } => SqlError::ConnectionError(quaint_kind), + NativeErrorKind::TlsError { .. } => Self::ConnectionError(quaint_kind), }, QuaintKind::RawConnectorError { status, reason } => Self::RawError { diff --git a/schema-engine/connectors/sql-schema-connector/src/flavour/mssql/connection.rs b/schema-engine/connectors/sql-schema-connector/src/flavour/mssql/connection.rs index 88094a0c3b4d..580c3a186381 100644 --- a/schema-engine/connectors/sql-schema-connector/src/flavour/mssql/connection.rs +++ b/schema-engine/connectors/sql-schema-connector/src/flavour/mssql/connection.rs @@ -2,7 +2,7 @@ use quaint::{ connector::{self, MssqlUrl}, - prelude::{ConnectionInfo, Queryable}, + prelude::{ConnectionInfo, NativeConnectionInfo, Queryable}, }; use schema_connector::{ConnectorError, ConnectorResult, Namespaces}; use sql_schema_describer::{mssql as describer, DescriberErrorKind, SqlSchema, SqlSchemaDescriberBackend}; @@ -104,5 +104,10 @@ fn quaint_err(params: &super::Params) -> impl (Fn(quaint::error::Error) -> Conne } fn quaint_err_url(url: &MssqlUrl) -> impl (Fn(quaint::error::Error) -> ConnectorError) + '_ { - |err| crate::flavour::quaint_error_to_connector_error(err, &ConnectionInfo::Mssql(url.clone())) + |err| { + crate::flavour::quaint_error_to_connector_error( + err, + &ConnectionInfo::Native(NativeConnectionInfo::Mssql(url.clone())), + ) + } } diff --git a/schema-engine/connectors/sql-schema-connector/src/flavour/mysql/connection.rs b/schema-engine/connectors/sql-schema-connector/src/flavour/mysql/connection.rs index f52dc91aff93..c0d216ef4a4d 100644 --- a/schema-engine/connectors/sql-schema-connector/src/flavour/mysql/connection.rs +++ b/schema-engine/connectors/sql-schema-connector/src/flavour/mysql/connection.rs @@ -7,7 +7,7 @@ use quaint::{ mysql_async::{self as my, prelude::Query}, MysqlUrl, }, - prelude::{ConnectionInfo, Queryable}, + prelude::{ConnectionInfo, NativeConnectionInfo, Queryable}, }; use schema_connector::{ConnectorError, ConnectorResult}; use sql_schema_describer::{DescriberErrorKind, SqlSchema}; @@ -160,7 +160,12 @@ impl Connection { } fn quaint_err(url: &MysqlUrl) -> impl (Fn(quaint::error::Error) -> ConnectorError) + '_ { - |err| crate::flavour::quaint_error_to_connector_error(err, &ConnectionInfo::Mysql(url.clone())) + |err| { + crate::flavour::quaint_error_to_connector_error( + err, + &ConnectionInfo::Native(NativeConnectionInfo::Mysql(url.clone())), + ) + } } fn convert_server_error(circumstances: BitFlags, error: &my::Error) -> Option { diff --git a/schema-engine/connectors/sql-schema-connector/src/flavour/postgres/connection.rs b/schema-engine/connectors/sql-schema-connector/src/flavour/postgres/connection.rs index 9db4ed56b859..c5f4c645916e 100644 --- a/schema-engine/connectors/sql-schema-connector/src/flavour/postgres/connection.rs +++ b/schema-engine/connectors/sql-schema-connector/src/flavour/postgres/connection.rs @@ -5,7 +5,7 @@ use indoc::indoc; use psl::PreviewFeature; use quaint::{ connector::{self, tokio_postgres::error::ErrorPosition, PostgresUrl}, - prelude::{ConnectionInfo, Queryable}, + prelude::{ConnectionInfo, NativeConnectionInfo, Queryable}, }; use schema_connector::{ConnectorError, ConnectorResult, Namespaces}; use sql_schema_describer::{postgres::PostgresSchemaExt, SqlSchema}; @@ -202,5 +202,10 @@ fn normalize_sql_schema(schema: &mut SqlSchema, preview_features: BitFlags impl (Fn(quaint::error::Error) -> ConnectorError) + '_ { - |err| crate::flavour::quaint_error_to_connector_error(err, &ConnectionInfo::Postgres(url.clone())) + |err| { + crate::flavour::quaint_error_to_connector_error( + err, + &ConnectionInfo::Native(NativeConnectionInfo::Postgres(url.clone())), + ) + } } diff --git a/schema-engine/sql-migration-tests/src/multi_engine_test_api.rs b/schema-engine/sql-migration-tests/src/multi_engine_test_api.rs index aeaa059bccfd..79c745aa86d3 100644 --- a/schema-engine/sql-migration-tests/src/multi_engine_test_api.rs +++ b/schema-engine/sql-migration-tests/src/multi_engine_test_api.rs @@ -14,7 +14,7 @@ use crate::{ }; use psl::PreviewFeature; use quaint::{ - prelude::{ConnectionInfo, Queryable, ResultSet}, + prelude::{ConnectionInfo, NativeConnectionInfo, Queryable, ResultSet}, single::Quaint, }; use schema_core::schema_connector::{ConnectorParams, SchemaConnector}; @@ -196,17 +196,19 @@ impl TestApi { }; let mut connector = match &connection_info { - ConnectionInfo::Postgres(_) => { + ConnectionInfo::Native(NativeConnectionInfo::Postgres(_)) => { if self.args.provider() == "cockroachdb" { SqlSchemaConnector::new_cockroach() } else { SqlSchemaConnector::new_postgres() } } - ConnectionInfo::Mysql(_) => SqlSchemaConnector::new_mysql(), - ConnectionInfo::Mssql(_) => SqlSchemaConnector::new_mssql(), - ConnectionInfo::Sqlite { .. } => SqlSchemaConnector::new_sqlite(), - ConnectionInfo::InMemorySqlite { .. } | ConnectionInfo::External(_) => unreachable!(), + ConnectionInfo::Native(NativeConnectionInfo::Mysql(_)) => SqlSchemaConnector::new_mysql(), + ConnectionInfo::Native(NativeConnectionInfo::Mssql(_)) => SqlSchemaConnector::new_mssql(), + ConnectionInfo::Native(NativeConnectionInfo::Sqlite { .. }) => SqlSchemaConnector::new_sqlite(), + ConnectionInfo::Native(NativeConnectionInfo::InMemorySqlite { .. }) | ConnectionInfo::External(_) => { + unreachable!() + } }; connector.set_params(params).unwrap(); From b914e6477fb8a759970c03508b51e5a6558af817 Mon Sep 17 00:00:00 2001 From: jkomyno Date: Wed, 27 Dec 2023 13:21:40 +0100 Subject: [PATCH 03/11] chore(query-engine-wasm): update comment --- libs/user-facing-errors/src/quaint.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/libs/user-facing-errors/src/quaint.rs b/libs/user-facing-errors/src/quaint.rs index a82d048af05c..44b3785a5a93 100644 --- a/libs/user-facing-errors/src/quaint.rs +++ b/libs/user-facing-errors/src/quaint.rs @@ -44,10 +44,6 @@ pub fn invalid_connection_string_description(error_details: &str) -> String { // - `DatabaseAlreadyExists` // - `TableDoesNotExist` // - `DatabaseUrlIsInvalid` -// -// TODO: try replacing `unreachable!` with `default_value` -// -// TODO: try handling `connection_info == ConnectionInfo::External(_)` right away pub fn render_quaint_error(kind: &ErrorKind, connection_info: &ConnectionInfo) -> Option { let default_value: Option = None; From b04e2d1c2d82d09ec2de7c21b3a7506eb269cd83 Mon Sep 17 00:00:00 2001 From: jkomyno Date: Wed, 27 Dec 2023 13:22:24 +0100 Subject: [PATCH 04/11] chore(query-engine-wasm): remove comment --- libs/user-facing-errors/Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/libs/user-facing-errors/Cargo.toml b/libs/user-facing-errors/Cargo.toml index f43de70afdef..8e715e7d66c9 100644 --- a/libs/user-facing-errors/Cargo.toml +++ b/libs/user-facing-errors/Cargo.toml @@ -15,5 +15,4 @@ quaint = { path = "../../quaint", optional = true } [features] default = [] -# native = ["quaint/native"] sql = ["quaint"] From 2a324a8100015b5fb6e77ec29b5907f005e96f94 Mon Sep 17 00:00:00 2001 From: jkomyno Date: Wed, 27 Dec 2023 15:06:24 +0100 Subject: [PATCH 05/11] chore(query-engine-wasm): fix clippy --- quaint/src/connector/connection_info.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/quaint/src/connector/connection_info.rs b/quaint/src/connector/connection_info.rs index bef28248f9e0..74af501bbca2 100644 --- a/quaint/src/connector/connection_info.rs +++ b/quaint/src/connector/connection_info.rs @@ -1,4 +1,5 @@ #![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}; @@ -156,9 +157,9 @@ impl ConnectionInfo { #[cfg(not(target_arch = "wasm32"))] ConnectionInfo::Native(info) => match info { #[cfg(feature = "postgresql")] - NativeConnectionInfo::Postgres(url) => Some(url.username()).map(Cow::from), + NativeConnectionInfo::Postgres(url) => Some(url.username()), #[cfg(feature = "mysql")] - NativeConnectionInfo::Mysql(url) => Some(url.username()).map(Cow::from), + NativeConnectionInfo::Mysql(url) => Some(url.username()), #[cfg(feature = "mssql")] NativeConnectionInfo::Mssql(url) => url.username().map(Cow::from), #[cfg(feature = "sqlite")] @@ -227,12 +228,8 @@ impl ConnectionInfo { /// Whether the pgbouncer mode is enabled. pub fn pg_bouncer(&self) -> bool { match self { - #[cfg(not(target_arch = "wasm32"))] - ConnectionInfo::Native(info) => match info { - #[cfg(feature = "postgresql")] - NativeConnectionInfo::Postgres(url) => url.pg_bouncer(), - _ => false, - }, + #[cfg(all(not(target_arch = "wasm32"), feature = "postgresql"))] + ConnectionInfo::Native(NativeConnectionInfo::Postgres(url)) => url.pg_bouncer(), _ => false, } } From a7f342e8573d5cb90eee7f229c1c2a22e4efe375 Mon Sep 17 00:00:00 2001 From: jkomyno Date: Wed, 27 Dec 2023 16:15:16 +0100 Subject: [PATCH 06/11] chore(query-engine-wasm): apply "repr(transparent)" to "ConnectionInfo" on Wasm, to shove off a few bytes --- quaint/src/connector/connection_info.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/quaint/src/connector/connection_info.rs b/quaint/src/connector/connection_info.rs index 74af501bbca2..50f2301e443e 100644 --- a/quaint/src/connector/connection_info.rs +++ b/quaint/src/connector/connection_info.rs @@ -23,6 +23,7 @@ use super::NativeConnectionInfo; /// General information about a SQL connection. #[derive(Debug, Clone)] +#[cfg_attr(target_arch = "wasm32", repr(transparent))] pub enum ConnectionInfo { #[cfg(not(target_arch = "wasm32"))] Native(NativeConnectionInfo), From b39a1cdeaf2e162449cc7975259f72c70cb50475 Mon Sep 17 00:00:00 2001 From: jkomyno Date: Wed, 27 Dec 2023 17:01:30 +0100 Subject: [PATCH 07/11] chore(query-engine-wasm): remove comments --- libs/user-facing-errors/src/quaint.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/libs/user-facing-errors/src/quaint.rs b/libs/user-facing-errors/src/quaint.rs index 44b3785a5a93..67515abbf66e 100644 --- a/libs/user-facing-errors/src/quaint.rs +++ b/libs/user-facing-errors/src/quaint.rs @@ -38,12 +38,6 @@ pub fn invalid_connection_string_description(error_details: &str) -> String { details.replace('\n', " ") } -// The following errors may arise from both native or external connections (TODO, check if this is true): -// - `DatabaseDoesNotExist` -// - `DatabaseAccessDenied` -// - `DatabaseAlreadyExists` -// - `TableDoesNotExist` -// - `DatabaseUrlIsInvalid` pub fn render_quaint_error(kind: &ErrorKind, connection_info: &ConnectionInfo) -> Option { let default_value: Option = None; From 968a715abf9ebe040cf9d2aca39d4a5a91a035d3 Mon Sep 17 00:00:00 2001 From: jkomyno Date: Thu, 11 Jan 2024 14:31:19 +0100 Subject: [PATCH 08/11] feat: address review comments --- quaint/src/connector/postgres/native/mod.rs | 20 +++++------ quaint/src/error/mod.rs | 10 ++++++ quaint/src/error/native.rs | 10 ------ .../sql-query-connector/src/error.rs | 35 ++++++++++--------- 4 files changed, 38 insertions(+), 37 deletions(-) diff --git a/quaint/src/connector/postgres/native/mod.rs b/quaint/src/connector/postgres/native/mod.rs index f403205c5d5c..2f8496d40ff5 100644 --- a/quaint/src/connector/postgres/native/mod.rs +++ b/quaint/src/connector/postgres/native/mod.rs @@ -306,11 +306,11 @@ impl PostgreSql { if params.len() > i16::MAX as usize { // tokio_postgres would return an error here. Let's avoid calling the driver // and return an error early. - let kind = ErrorKind::Native(NativeErrorKind::QueryInvalidInput(format!( + let kind = ErrorKind::QueryInvalidInput(format!( "too many bind variables in prepared statement, expected maximum of {}, received {}", i16::MAX, params.len() - ))); + )); Err(Error::builder(kind).build()) } else { Ok(()) @@ -372,10 +372,10 @@ impl Queryable for PostgreSql { let stmt = self.fetch_cached(sql, &[]).await?; if stmt.params().len() != params.len() { - let kind = ErrorKind::Native(NativeErrorKind::IncorrectNumberOfParameters { + let kind = ErrorKind::IncorrectNumberOfParameters { expected: stmt.params().len(), actual: params.len(), - }); + }; return Err(Error::builder(kind).build()); } @@ -402,10 +402,10 @@ impl Queryable for PostgreSql { let stmt = self.fetch_cached(sql, params).await?; if stmt.params().len() != params.len() { - let kind = ErrorKind::Native(NativeErrorKind::IncorrectNumberOfParameters { + let kind = ErrorKind::IncorrectNumberOfParameters { expected: stmt.params().len(), actual: params.len(), - }); + }; return Err(Error::builder(kind).build()); } @@ -438,10 +438,10 @@ impl Queryable for PostgreSql { let stmt = self.fetch_cached(sql, &[]).await?; if stmt.params().len() != params.len() { - let kind = ErrorKind::Native(NativeErrorKind::IncorrectNumberOfParameters { + let kind = ErrorKind::IncorrectNumberOfParameters { expected: stmt.params().len(), actual: params.len(), - }); + }; return Err(Error::builder(kind).build()); } @@ -462,10 +462,10 @@ impl Queryable for PostgreSql { let stmt = self.fetch_cached(sql, params).await?; if stmt.params().len() != params.len() { - let kind = ErrorKind::Native(NativeErrorKind::IncorrectNumberOfParameters { + let kind = ErrorKind::IncorrectNumberOfParameters { expected: stmt.params().len(), actual: params.len(), - }); + }; return Err(Error::builder(kind).build()); } diff --git a/quaint/src/error/mod.rs b/quaint/src/error/mod.rs index ccbb132291f7..987d134dc19c 100644 --- a/quaint/src/error/mod.rs +++ b/quaint/src/error/mod.rs @@ -145,6 +145,9 @@ pub enum ErrorKind { #[error("Error querying the database: {}", _0)] QueryError(Box), + #[error("Invalid input provided to query: {}", _0)] + QueryInvalidInput(String), + #[error("Database does not exist: {}", db_name)] DatabaseDoesNotExist { db_name: Name }, @@ -202,6 +205,13 @@ pub enum ErrorKind { #[error("Value out of range error. {}", message)] ValueOutOfRange { message: String }, + #[error( + "Incorrect number of parameters given to a statement. Expected {}: got: {}.", + expected, + actual + )] + IncorrectNumberOfParameters { expected: usize, actual: usize }, + #[error("Transaction was already closed: {}", _0)] TransactionAlreadyClosed(String), diff --git a/quaint/src/error/native.rs b/quaint/src/error/native.rs index a787ffead560..e9479b64f200 100644 --- a/quaint/src/error/native.rs +++ b/quaint/src/error/native.rs @@ -2,9 +2,6 @@ use thiserror::Error; #[derive(Debug, Error)] pub enum NativeErrorKind { - #[error("Invalid input provided to query: {}", _0)] - QueryInvalidInput(String), - #[error("Error creating a database connection.")] ConnectionError(Box), @@ -30,11 +27,4 @@ pub enum NativeErrorKind { #[error("Error opening a TLS connection. {}", message)] TlsError { message: String }, - - #[error( - "Incorrect number of parameters given to a statement. Expected {}: got: {}.", - expected, - actual - )] - IncorrectNumberOfParameters { expected: usize, actual: usize }, } diff --git a/query-engine/connectors/sql-query-connector/src/error.rs b/query-engine/connectors/sql-query-connector/src/error.rs index 4d6604f7a0cd..e284c4f59218 100644 --- a/query-engine/connectors/sql-query-connector/src/error.rs +++ b/query-engine/connectors/sql-query-connector/src/error.rs @@ -10,8 +10,6 @@ use quaint::error::NativeErrorKind; #[cfg(not(target_arch = "wasm32"))] pub(crate) enum NativeRawError { - IncorrectNumberOfParameters { expected: usize, actual: usize }, - QueryInvalidInput(String), ConnectionClosed, } @@ -25,6 +23,11 @@ pub(crate) enum RawError { UnsupportedColumnType { column_type: String, }, + IncorrectNumberOfParameters { + expected: usize, + actual: usize, + }, + QueryInvalidInput(String), External { id: i32, }, @@ -35,12 +38,12 @@ impl From for SqlError { match re { #[cfg(not(target_arch = "wasm32"))] RawError::Native(native) => match native { - NativeRawError::IncorrectNumberOfParameters { expected, actual } => { - SqlError::IncorrectNumberOfParameters { expected, actual } - } - NativeRawError::QueryInvalidInput(message) => SqlError::QueryInvalidInput(message), NativeRawError::ConnectionClosed => SqlError::ConnectionClosed, }, + RawError::IncorrectNumberOfParameters { expected, actual } => { + Self::IncorrectNumberOfParameters { expected, actual } + } + RawError::QueryInvalidInput(message) => Self::QueryInvalidInput(message), RawError::UnsupportedColumnType { column_type } => Self::RawError { code: String::from("N/A"), message: format!( @@ -66,22 +69,20 @@ impl From for RawError { match e.kind() { #[cfg(not(target_arch = "wasm32"))] quaint::error::ErrorKind::Native(native_error_kind) => match native_error_kind { - NativeErrorKind::IncorrectNumberOfParameters { expected, actual } => { - Self::Native(NativeRawError::IncorrectNumberOfParameters { - expected: *expected, - actual: *actual, - }) - } NativeErrorKind::ConnectionClosed => Self::Native(NativeRawError::ConnectionClosed), - NativeErrorKind::QueryInvalidInput(message) => { - Self::Native(NativeRawError::QueryInvalidInput(message.to_owned())) - } _ => default_value, }, + quaint::error::ErrorKind::IncorrectNumberOfParameters { expected, actual } => { + Self::IncorrectNumberOfParameters { + expected: *expected, + actual: *actual, + } + } quaint::error::ErrorKind::UnsupportedColumnType { column_type } => Self::UnsupportedColumnType { column_type: column_type.to_owned(), }, + quaint::error::ErrorKind::QueryInvalidInput(message) => Self::QueryInvalidInput(message.to_owned()), quaint::error::ErrorKind::ExternalError(id) => Self::External { id: *id }, _ => default_value, } @@ -298,10 +299,8 @@ impl From for SqlError { match quaint_kind { #[cfg(not(target_arch = "wasm32"))] QuaintKind::Native(ref native_error_kind) => match native_error_kind { - NativeErrorKind::QueryInvalidInput(ref qe) => Self::QueryInvalidInput(qe.to_owned()), NativeErrorKind::IoError(_) | NativeErrorKind::ConnectionError(_) => Self::ConnectionError(quaint_kind), NativeErrorKind::ConnectionClosed => SqlError::ConnectionClosed, - NativeErrorKind::IncorrectNumberOfParameters { .. } => SqlError::QueryError(quaint_kind.into()), NativeErrorKind::ConnectTimeout => SqlError::ConnectionError(quaint_kind), NativeErrorKind::PoolTimeout { .. } => SqlError::ConnectionError(quaint_kind), NativeErrorKind::PoolClosed { .. } => SqlError::ConnectionError(quaint_kind), @@ -313,6 +312,7 @@ impl From for SqlError { message: reason, }, QuaintKind::QueryError(qe) => Self::QueryError(qe), + QuaintKind::QueryInvalidInput(qe) => Self::QueryInvalidInput(qe), QuaintKind::NotFound => Self::RecordDoesNotExist, QuaintKind::UniqueConstraintViolation { constraint } => Self::UniqueConstraintViolation { constraint: constraint.into(), @@ -336,6 +336,7 @@ impl From for SqlError { QuaintKind::ExternalError(error_id) => Self::ExternalError(error_id), e @ QuaintKind::UnsupportedColumnType { .. } => SqlError::ConversionError(e.into()), e @ QuaintKind::TransactionAlreadyClosed(_) => SqlError::TransactionAlreadyClosed(format!("{e}")), + e @ QuaintKind::IncorrectNumberOfParameters { .. } => SqlError::QueryError(e.into()), e @ QuaintKind::ConversionError(_) => SqlError::ConversionError(e.into()), e @ QuaintKind::ResultIndexOutOfBounds { .. } => SqlError::QueryError(e.into()), e @ QuaintKind::ResultTypeMismatch { .. } => SqlError::QueryError(e.into()), From 428b1699ae8e49337324b3f0bed6ee521aa50086 Mon Sep 17 00:00:00 2001 From: jkomyno Date: Thu, 11 Jan 2024 15:57:48 +0100 Subject: [PATCH 09/11] chore: clippy --- query-engine/connectors/sql-query-connector/src/error.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/query-engine/connectors/sql-query-connector/src/error.rs b/query-engine/connectors/sql-query-connector/src/error.rs index e284c4f59218..d3c81b602733 100644 --- a/query-engine/connectors/sql-query-connector/src/error.rs +++ b/query-engine/connectors/sql-query-connector/src/error.rs @@ -68,11 +68,9 @@ impl From for RawError { match e.kind() { #[cfg(not(target_arch = "wasm32"))] - quaint::error::ErrorKind::Native(native_error_kind) => match native_error_kind { - NativeErrorKind::ConnectionClosed => Self::Native(NativeRawError::ConnectionClosed), - _ => default_value, - }, - + quaint::error::ErrorKind::Native(NativeErrorKind::ConnectionClosed) => { + Self::Native(NativeRawError::ConnectionClosed) + } quaint::error::ErrorKind::IncorrectNumberOfParameters { expected, actual } => { Self::IncorrectNumberOfParameters { expected: *expected, From 57da5fde6dfd65f0f355016c462a8816bcb7fdd6 Mon Sep 17 00:00:00 2001 From: jkomyno Date: Thu, 11 Jan 2024 16:13:56 +0100 Subject: [PATCH 10/11] chore: fix quaint test --- quaint/src/connector/postgres/url.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quaint/src/connector/postgres/url.rs b/quaint/src/connector/postgres/url.rs index cab76e58a90d..3d8c803e0954 100644 --- a/quaint/src/connector/postgres/url.rs +++ b/quaint/src/connector/postgres/url.rs @@ -626,7 +626,7 @@ mod tests { match res { Ok(_) => unreachable!(), Err(e) => match e.kind() { - ErrorKind::Native(NativeErrorKind::IncorrectNumberOfParameters { expected, actual }) => { + ErrorKind::IncorrectNumberOfParameters { expected, actual } => { assert_eq!(1, *expected); assert_eq!(2, *actual); } From 5ad7d0b4d5c6ff29c0bb281d22c05963f7118fcd Mon Sep 17 00:00:00 2001 From: jkomyno Date: Tue, 16 Jan 2024 15:10:18 +0100 Subject: [PATCH 11/11] fix(driver-adapters): fix condition that made Vitess fail --- libs/user-facing-errors/src/quaint.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/libs/user-facing-errors/src/quaint.rs b/libs/user-facing-errors/src/quaint.rs index 67515abbf66e..c2e73948dcfb 100644 --- a/libs/user-facing-errors/src/quaint.rs +++ b/libs/user-facing-errors/src/quaint.rs @@ -45,23 +45,23 @@ pub fn render_quaint_error(kind: &ErrorKind, connection_info: &ConnectionInfo) - (ErrorKind::DatabaseDoesNotExist { .. }, ConnectionInfo::External(_)) => default_value, #[cfg(not(target_arch = "wasm32"))] (ErrorKind::DatabaseDoesNotExist { db_name }, _) => match connection_info { - ConnectionInfo::Native(NativeConnectionInfo::Mysql(url)) => { - Some(KnownError::new(common::DatabaseDoesNotExist::Mysql { + ConnectionInfo::Native(NativeConnectionInfo::Postgres(url)) => { + Some(KnownError::new(common::DatabaseDoesNotExist::Postgres { database_name: db_name.to_string(), database_host: url.host().to_owned(), database_port: url.port(), })) } - ConnectionInfo::Native(NativeConnectionInfo::Postgres(url)) => { - Some(KnownError::new(common::DatabaseDoesNotExist::Postgres { - database_name: db_name.to_string(), + ConnectionInfo::Native(NativeConnectionInfo::Mysql(url)) => { + Some(KnownError::new(common::DatabaseDoesNotExist::Mysql { + database_name: url.dbname().to_owned(), database_host: url.host().to_owned(), database_port: url.port(), })) } ConnectionInfo::Native(NativeConnectionInfo::Mssql(url)) => { Some(KnownError::new(common::DatabaseDoesNotExist::Mssql { - database_name: db_name.to_string(), + database_name: url.dbname().to_owned(), database_host: url.host().to_owned(), database_port: url.port(), }))