mirror of
https://github.com/launchbadge/sqlx.git
synced 2025-12-30 05:11:13 +00:00
feat(mysql): fill out more in MySqlOptions and settle on accessors [#659]
This commit is contained in:
parent
6ed3cb4aad
commit
44c175bb19
10
Cargo.lock
generated
10
Cargo.lock
generated
@ -246,6 +246,12 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
|
||||
|
||||
[[package]]
|
||||
name = "event-listener"
|
||||
version = "2.5.1"
|
||||
@ -771,15 +777,17 @@ dependencies = [
|
||||
"futures-util",
|
||||
"tokio 0.2.24",
|
||||
"tokio 1.0.1",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-mysql"
|
||||
version = "0.6.0-pre"
|
||||
dependencies = [
|
||||
"either",
|
||||
"futures-util",
|
||||
"percent-encoding",
|
||||
"sqlx-core",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@ -39,4 +39,3 @@ _async-std = { version = "1.8.0", optional = true, package = "async-std" }
|
||||
futures-util = { version = "0.3.8", optional = true }
|
||||
_tokio = { version = "1.0.1", optional = true, package = "tokio", features = ["net"] }
|
||||
tokio_02 = { version = "0.2.24", optional = true, package = "tokio", features = ["net"] }
|
||||
url = "2.2.0"
|
||||
|
||||
@ -9,9 +9,8 @@ use crate::DefaultRuntime;
|
||||
pub trait Connection<Rt = DefaultRuntime>: crate::Connection<Rt>
|
||||
where
|
||||
Rt: Runtime,
|
||||
Self::Options: ConnectOptions<Rt>,
|
||||
{
|
||||
type Options: ConnectOptions<Rt, Connection = Self>;
|
||||
|
||||
/// Establish a new database connection.
|
||||
///
|
||||
/// For detailed information, refer to the asynchronous version of
|
||||
@ -21,7 +20,8 @@ where
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
url.parse::<<Self as Connection<Rt>>::Options>()?.connect()
|
||||
url.parse::<<Self as crate::Connection<Rt>>::Options>()?
|
||||
.connect()
|
||||
}
|
||||
|
||||
/// Explicitly close this database connection.
|
||||
|
||||
@ -6,18 +6,18 @@ use crate::DefaultRuntime;
|
||||
/// For detailed information, refer to the asynchronous version of
|
||||
/// this: [`ConnectOptions`][crate::ConnectOptions].
|
||||
///
|
||||
#[allow(clippy::module_name_repetitions)]
|
||||
pub trait ConnectOptions<Rt = DefaultRuntime>: crate::ConnectOptions<Rt>
|
||||
where
|
||||
Rt: Runtime,
|
||||
Self::Connection: crate::Connection<Rt, Options = Self> + Connection<Rt>,
|
||||
{
|
||||
type Connection: Connection<Rt> + ?Sized;
|
||||
|
||||
/// Establish a connection to the database.
|
||||
///
|
||||
/// For detailed information, refer to the asynchronous version of
|
||||
/// this: [`connect()`][crate::ConnectOptions::connect].
|
||||
///
|
||||
fn connect(&self) -> crate::Result<<Self as ConnectOptions<Rt>>::Connection>
|
||||
fn connect(&self) -> crate::Result<Self::Connection>
|
||||
where
|
||||
<Self as ConnectOptions<Rt>>::Connection: Sized;
|
||||
Self::Connection: Sized;
|
||||
}
|
||||
|
||||
@ -26,9 +26,8 @@ where
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// use sqlx::postgres::PgConnection;
|
||||
/// use sqlx::ConnectOptions;
|
||||
///
|
||||
/// let mut conn = PgConnection::connect(
|
||||
/// let mut conn = <PgConnection>::connect(
|
||||
/// "postgres://postgres:password@localhost/database",
|
||||
/// ).await?;
|
||||
/// ```
|
||||
@ -36,10 +35,10 @@ where
|
||||
/// You may alternatively build the connection options imperatively.
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// use sqlx::mysql::{MySqlConnection, MySqlConnectOptions};
|
||||
/// use sqlx::mysql::MySqlConnectOptions;
|
||||
/// use sqlx::ConnectOptions;
|
||||
///
|
||||
/// let mut conn: MySqlConnection = MySqlConnectOptions::builder()
|
||||
/// let mut conn = <MySqlConnectOptions>::new()
|
||||
/// .host("localhost")
|
||||
/// .username("root")
|
||||
/// .password("password")
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
use std::borrow::Cow;
|
||||
use std::error::Error as StdError;
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
@ -6,15 +7,51 @@ pub type Result<T> = std::result::Result<T, Error>;
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum Error {
|
||||
InvalidConnectionUrl(url::ParseError),
|
||||
Configuration {
|
||||
message: Cow<'static, str>,
|
||||
source: Option<Box<dyn StdError + Send + Sync>>,
|
||||
},
|
||||
|
||||
Network(std::io::Error),
|
||||
}
|
||||
|
||||
impl Error {
|
||||
#[doc(hidden)]
|
||||
pub fn configuration(
|
||||
message: impl Into<Cow<'static, str>>,
|
||||
source: impl Into<Box<dyn StdError + Send + Sync>>,
|
||||
) -> Self {
|
||||
Self::Configuration {
|
||||
message: message.into(),
|
||||
source: Some(source.into()),
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn configuration_msg(message: impl Into<Cow<'static, str>>) -> Self {
|
||||
Self::Configuration {
|
||||
message: message.into(),
|
||||
source: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::InvalidConnectionUrl(source) => write!(f, "invalid connection url: {}", source),
|
||||
Self::Network(source) => write!(f, "network: {}", source),
|
||||
|
||||
Self::Configuration {
|
||||
message,
|
||||
source: None,
|
||||
} => write!(f, "configuration: {}", message),
|
||||
|
||||
Self::Configuration {
|
||||
message,
|
||||
source: Some(source),
|
||||
} => {
|
||||
write!(f, "configuration: {}: {}", message, source)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -22,8 +59,14 @@ impl Display for Error {
|
||||
impl StdError for Error {
|
||||
fn source(&self) -> Option<&(dyn StdError + 'static)> {
|
||||
match self {
|
||||
Self::InvalidConnectionUrl(source) => Some(source),
|
||||
Self::Configuration {
|
||||
source: Some(source),
|
||||
..
|
||||
} => Some(&**source),
|
||||
|
||||
Self::Network(source) => Some(source),
|
||||
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,8 +4,9 @@ use std::str::FromStr;
|
||||
use crate::{Connection, DefaultRuntime, Runtime};
|
||||
|
||||
/// Options which can be used to configure how a SQL connection is opened.
|
||||
#[allow(clippy::module_name_repetitions)]
|
||||
pub trait ConnectOptions<Rt = DefaultRuntime>:
|
||||
'static + Send + Sync + Default + Debug + Clone + FromStr<Err = crate::Error>
|
||||
'static + Sized + Send + Sync + Default + Debug + Clone + FromStr<Err = crate::Error>
|
||||
where
|
||||
Rt: Runtime,
|
||||
{
|
||||
|
||||
@ -29,3 +29,6 @@ async = ["futures-util", "sqlx-core/async"]
|
||||
[dependencies]
|
||||
sqlx-core = { version = "0.6.0-pre", path = "../sqlx-core" }
|
||||
futures-util = { version = "0.3.8", optional = true }
|
||||
either = "1.6.1"
|
||||
url = "2.2.0"
|
||||
percent-encoding = "2.1.0"
|
||||
|
||||
@ -1,2 +0,0 @@
|
||||
mod connection;
|
||||
mod options;
|
||||
@ -1,27 +0,0 @@
|
||||
use futures_util::future::BoxFuture;
|
||||
use sqlx_core::{Async, Connection, Result, Runtime};
|
||||
|
||||
use crate::{MySql, MySqlConnectOptions, MySqlConnection};
|
||||
|
||||
impl<Rt> Connection<Rt> for MySqlConnection<Rt>
|
||||
where
|
||||
Rt: Runtime,
|
||||
{
|
||||
type Database = MySql;
|
||||
|
||||
type Options = MySqlConnectOptions<Rt>;
|
||||
|
||||
fn close(self) -> BoxFuture<'static, Result<()>>
|
||||
where
|
||||
Rt: Async,
|
||||
{
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn ping(&mut self) -> BoxFuture<'_, Result<()>>
|
||||
where
|
||||
Rt: Async,
|
||||
{
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
@ -1,23 +0,0 @@
|
||||
use futures_util::{future::BoxFuture, FutureExt};
|
||||
use sqlx_core::{Async, ConnectOptions, Result, Runtime};
|
||||
|
||||
use crate::{MySqlConnectOptions, MySqlConnection};
|
||||
|
||||
impl<Rt> ConnectOptions<Rt> for MySqlConnectOptions<Rt>
|
||||
where
|
||||
Rt: Runtime,
|
||||
{
|
||||
type Connection = MySqlConnection<Rt>;
|
||||
|
||||
fn connect(&self) -> BoxFuture<'_, Result<Self::Connection>>
|
||||
where
|
||||
Self::Connection: Sized,
|
||||
Rt: Async,
|
||||
{
|
||||
FutureExt::boxed(async move {
|
||||
let stream = Rt::connect_tcp(&self.host, self.port).await?;
|
||||
|
||||
Ok(MySqlConnection { stream })
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -1,14 +1,12 @@
|
||||
use sqlx_core::blocking::{Connection, Runtime};
|
||||
use sqlx_core::Result;
|
||||
|
||||
use crate::{MySqlConnectOptions, MySqlConnection};
|
||||
use crate::MySqlConnection;
|
||||
|
||||
impl<Rt> Connection<Rt> for MySqlConnection<Rt>
|
||||
where
|
||||
Rt: Runtime,
|
||||
{
|
||||
type Options = MySqlConnectOptions<Rt>;
|
||||
|
||||
fn close(self) -> Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
use sqlx_core::blocking::{ConnectOptions, Runtime};
|
||||
use sqlx_core::blocking::{ConnectOptions, Connection, Runtime};
|
||||
use sqlx_core::Result;
|
||||
|
||||
use crate::{MySqlConnectOptions, MySqlConnection};
|
||||
@ -6,11 +6,10 @@ use crate::{MySqlConnectOptions, MySqlConnection};
|
||||
impl<Rt> ConnectOptions<Rt> for MySqlConnectOptions<Rt>
|
||||
where
|
||||
Rt: Runtime,
|
||||
Self::Connection: sqlx_core::Connection<Rt, Options = Self> + Connection<Rt>,
|
||||
{
|
||||
type Connection = MySqlConnection<Rt>;
|
||||
|
||||
fn connect(&self) -> Result<MySqlConnection<Rt>> {
|
||||
let stream = <Rt as Runtime>::connect_tcp(&self.host, self.port)?;
|
||||
let stream = <Rt as Runtime>::connect_tcp(self.get_host(), self.get_port())?;
|
||||
|
||||
Ok(MySqlConnection { stream })
|
||||
}
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
|
||||
use sqlx_core::{DefaultRuntime, Runtime};
|
||||
use sqlx_core::{Connection, DefaultRuntime, Runtime};
|
||||
|
||||
use crate::{MySql, MySqlConnectOptions};
|
||||
|
||||
pub struct MySqlConnection<Rt = DefaultRuntime>
|
||||
where
|
||||
@ -17,3 +19,28 @@ where
|
||||
f.debug_struct("MySqlConnection").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Rt> Connection<Rt> for MySqlConnection<Rt>
|
||||
where
|
||||
Rt: Runtime,
|
||||
{
|
||||
type Database = MySql;
|
||||
|
||||
type Options = MySqlConnectOptions<Rt>;
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
fn close(self) -> futures_util::future::BoxFuture<'static, sqlx_core::Result<()>>
|
||||
where
|
||||
Rt: sqlx_core::Async,
|
||||
{
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
fn ping(&mut self) -> futures_util::future::BoxFuture<'_, sqlx_core::Result<()>>
|
||||
where
|
||||
Rt: sqlx_core::Async,
|
||||
{
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
@ -26,9 +26,6 @@ mod options;
|
||||
#[cfg(feature = "blocking")]
|
||||
mod blocking;
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
mod r#async;
|
||||
|
||||
pub use connection::MySqlConnection;
|
||||
pub use database::MySql;
|
||||
pub use options::MySqlConnectOptions;
|
||||
|
||||
@ -1,29 +1,40 @@
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
use std::marker::PhantomData;
|
||||
use std::str::FromStr;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use sqlx_core::{DefaultRuntime, Runtime};
|
||||
use either::Either;
|
||||
use sqlx_core::{ConnectOptions, DefaultRuntime, Runtime};
|
||||
|
||||
use crate::MySqlConnection;
|
||||
|
||||
mod builder;
|
||||
mod default;
|
||||
mod parse;
|
||||
|
||||
/// Options which can be used to configure how a MySQL connection is opened.
|
||||
///
|
||||
/// A value of `MySqlConnectOptions` can be parsed from a connection URL,
|
||||
/// as described by the [MySQL JDBC connector reference](https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-reference-jdbc-url-format.html).
|
||||
///
|
||||
/// ```text
|
||||
/// mysql://[host][/database][?properties]
|
||||
/// ```
|
||||
///
|
||||
/// - The protocol must be `mysql`.
|
||||
///
|
||||
/// - Only a single host is supported.
|
||||
///
|
||||
pub struct MySqlConnectOptions<Rt = DefaultRuntime>
|
||||
where
|
||||
Rt: Runtime,
|
||||
{
|
||||
runtime: PhantomData<Rt>,
|
||||
pub(crate) host: String,
|
||||
pub(crate) port: u16,
|
||||
}
|
||||
|
||||
impl<Rt> Default for MySqlConnectOptions<Rt>
|
||||
where
|
||||
Rt: Runtime,
|
||||
{
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
host: "localhost".to_owned(),
|
||||
runtime: PhantomData,
|
||||
port: 3306,
|
||||
}
|
||||
}
|
||||
address: Either<(String, u16), PathBuf>,
|
||||
username: Option<String>,
|
||||
password: Option<String>,
|
||||
database: Option<String>,
|
||||
timezone: String,
|
||||
charset: String,
|
||||
}
|
||||
|
||||
impl<Rt> Clone for MySqlConnectOptions<Rt>
|
||||
@ -31,7 +42,15 @@ where
|
||||
Rt: Runtime,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
unimplemented!()
|
||||
Self {
|
||||
runtime: PhantomData,
|
||||
address: self.address.clone(),
|
||||
username: self.username.clone(),
|
||||
password: self.password.clone(),
|
||||
database: self.database.clone(),
|
||||
timezone: self.timezone.clone(),
|
||||
charset: self.charset.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -40,21 +59,93 @@ where
|
||||
Rt: Runtime,
|
||||
{
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("MySqlConnectOptions").finish()
|
||||
f.debug_struct("MySqlConnectOptions")
|
||||
.field(
|
||||
"address",
|
||||
&self
|
||||
.address
|
||||
.as_ref()
|
||||
.map_left(|(host, port)| format!("{}:{}", host, port))
|
||||
.map_right(|socket| socket.display()),
|
||||
)
|
||||
.field("username", &self.username)
|
||||
.field("password", &self.password)
|
||||
.field("database", &self.database)
|
||||
.field("timezone", &self.timezone)
|
||||
.field("charset", &self.charset)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Rt> FromStr for MySqlConnectOptions<Rt>
|
||||
impl<Rt> MySqlConnectOptions<Rt>
|
||||
where
|
||||
Rt: Runtime,
|
||||
{
|
||||
type Err = sqlx_core::Error;
|
||||
/// Returns the hostname of the database server.
|
||||
pub fn get_host(&self) -> &str {
|
||||
self.address
|
||||
.as_ref()
|
||||
.left()
|
||||
.map(|(host, _)| &**host)
|
||||
.unwrap_or(default::HOST)
|
||||
}
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(Self {
|
||||
host: "localhost".to_owned(),
|
||||
runtime: PhantomData,
|
||||
port: 3306,
|
||||
/// Returns the TCP port number of the database server.
|
||||
pub fn get_port(&self) -> u16 {
|
||||
self.address
|
||||
.as_ref()
|
||||
.left()
|
||||
.map(|(_, port)| *port)
|
||||
.unwrap_or(default::PORT)
|
||||
}
|
||||
|
||||
/// Returns the path to the Unix domain socket, if one is configured.
|
||||
pub fn get_socket(&self) -> Option<&Path> {
|
||||
self.address.as_ref().right().map(|buf| buf.as_path())
|
||||
}
|
||||
|
||||
/// Returns the default database name.
|
||||
pub fn get_database(&self) -> Option<&str> {
|
||||
self.database.as_deref()
|
||||
}
|
||||
|
||||
/// Returns the username to be used for authentication.
|
||||
pub fn get_username(&self) -> Option<&str> {
|
||||
self.username.as_deref()
|
||||
}
|
||||
|
||||
/// Returns the password to be used for authentication.
|
||||
pub fn get_password(&self) -> Option<&str> {
|
||||
self.password.as_deref()
|
||||
}
|
||||
|
||||
/// Returns the character set for the connection.
|
||||
pub fn get_charset(&self) -> &str {
|
||||
&self.charset
|
||||
}
|
||||
|
||||
/// Returns the timezone for the connection.
|
||||
pub fn get_timezone(&self) -> &str {
|
||||
&self.timezone
|
||||
}
|
||||
}
|
||||
|
||||
impl<Rt> ConnectOptions<Rt> for MySqlConnectOptions<Rt>
|
||||
where
|
||||
Rt: Runtime,
|
||||
{
|
||||
type Connection = MySqlConnection<Rt>;
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
fn connect(&self) -> futures_util::future::BoxFuture<'_, sqlx_core::Result<Self::Connection>>
|
||||
where
|
||||
Self::Connection: Sized,
|
||||
Rt: sqlx_core::Async,
|
||||
{
|
||||
futures_util::FutureExt::boxed(async move {
|
||||
let stream = Rt::connect_tcp(self.get_host(), self.get_port()).await?;
|
||||
|
||||
Ok(MySqlConnection { stream })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
82
sqlx-mysql/src/options/builder.rs
Normal file
82
sqlx-mysql/src/options/builder.rs
Normal file
@ -0,0 +1,82 @@
|
||||
use std::mem;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use either::Either;
|
||||
use sqlx_core::Runtime;
|
||||
|
||||
impl<Rt> super::MySqlConnectOptions<Rt>
|
||||
where
|
||||
Rt: Runtime,
|
||||
{
|
||||
/// Sets the hostname of the database server.
|
||||
///
|
||||
/// If the hostname begins with a slash (`/`), it is interpreted as the absolute path
|
||||
/// to a Unix domain socket file instead of a hostname of a server.
|
||||
///
|
||||
/// Defaults to `localhost`.
|
||||
///
|
||||
pub fn host(&mut self, host: impl AsRef<str>) -> &mut Self {
|
||||
let host = host.as_ref();
|
||||
|
||||
self.address = if host.starts_with('/') {
|
||||
Either::Right(PathBuf::from(&*host))
|
||||
} else {
|
||||
Either::Left((host.into(), self.get_port()))
|
||||
};
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the path of the Unix domain socket to connect to.
|
||||
///
|
||||
/// Overrides [`host()`](#method.host) and [`port()`](#method.port).
|
||||
///
|
||||
pub fn socket(&mut self, socket: impl AsRef<Path>) -> &mut Self {
|
||||
self.address = Either::Right(socket.as_ref().to_owned());
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the TCP port number of the database server.
|
||||
///
|
||||
/// Defaults to `3306`.
|
||||
///
|
||||
pub fn port(&mut self, port: u16) -> &mut Self {
|
||||
self.address = match self.address {
|
||||
Either::Right(_) => Either::Left(("localhost".to_owned(), port)),
|
||||
Either::Left((ref mut host, _)) => Either::Left((mem::take(host), port)),
|
||||
};
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the username to be used for authentication.
|
||||
// FIXME: Specify what happens when you do NOT set this
|
||||
pub fn username(&mut self, username: impl AsRef<str>) -> &mut Self {
|
||||
self.username = Some(username.as_ref().to_owned());
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the password to be used for authentication.
|
||||
pub fn password(&mut self, password: impl AsRef<str>) -> &mut Self {
|
||||
self.password = Some(password.as_ref().to_owned());
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the default database for the connection.
|
||||
pub fn database(&mut self, database: impl AsRef<str>) -> &mut Self {
|
||||
self.database = Some(database.as_ref().to_owned());
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the character set for the connection.
|
||||
pub fn charset(&mut self, charset: impl AsRef<str>) -> &mut Self {
|
||||
self.charset = charset.as_ref().to_owned();
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the timezone for the connection.
|
||||
pub fn timezone(&mut self, timezone: impl AsRef<str>) -> &mut Self {
|
||||
self.timezone = timezone.as_ref().to_owned();
|
||||
self
|
||||
}
|
||||
}
|
||||
37
sqlx-mysql/src/options/default.rs
Normal file
37
sqlx-mysql/src/options/default.rs
Normal file
@ -0,0 +1,37 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use either::Either;
|
||||
use sqlx_core::Runtime;
|
||||
|
||||
use crate::MySqlConnectOptions;
|
||||
|
||||
pub(crate) const HOST: &str = "localhost";
|
||||
pub(crate) const PORT: u16 = 3306;
|
||||
|
||||
impl<Rt> Default for MySqlConnectOptions<Rt>
|
||||
where
|
||||
Rt: Runtime,
|
||||
{
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
runtime: PhantomData,
|
||||
address: Either::Left((HOST.to_owned(), PORT)),
|
||||
username: None,
|
||||
password: None,
|
||||
database: None,
|
||||
charset: "utf8mb4".to_owned(),
|
||||
timezone: "utc".to_owned(),
|
||||
// todo: connect_timeout
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Rt> super::MySqlConnectOptions<Rt>
|
||||
where
|
||||
Rt: Runtime,
|
||||
{
|
||||
/// Creates a default set of options ready for configuration.
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
198
sqlx-mysql/src/options/parse.rs
Normal file
198
sqlx-mysql/src/options/parse.rs
Normal file
@ -0,0 +1,198 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use percent_encoding::percent_decode_str;
|
||||
use sqlx_core::{Error, Runtime};
|
||||
use url::Url;
|
||||
|
||||
use crate::MySqlConnectOptions;
|
||||
|
||||
impl<Rt> FromStr for MySqlConnectOptions<Rt>
|
||||
where
|
||||
Rt: Runtime,
|
||||
{
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let url: Url = s
|
||||
.parse()
|
||||
.map_err(|error| Error::configuration("database url", error))?;
|
||||
|
||||
if !matches!(url.scheme(), "mysql") {
|
||||
return Err(Error::configuration_msg(format!(
|
||||
"unsupported URL scheme {:?} for MySQL",
|
||||
url.scheme()
|
||||
)));
|
||||
}
|
||||
|
||||
let mut options = Self::new();
|
||||
|
||||
if let Some(host) = url.host_str() {
|
||||
options.host(percent_decode_str_utf8(host, "host in database url")?);
|
||||
}
|
||||
|
||||
if let Some(port) = url.port() {
|
||||
options.port(port);
|
||||
}
|
||||
|
||||
let username = url.username();
|
||||
if !username.is_empty() {
|
||||
options.username(percent_decode_str_utf8(
|
||||
username,
|
||||
"username in database url",
|
||||
)?);
|
||||
}
|
||||
|
||||
if let Some(password) = url.password() {
|
||||
options.password(percent_decode_str_utf8(
|
||||
password,
|
||||
"password in database url",
|
||||
)?);
|
||||
}
|
||||
|
||||
let mut path = url.path();
|
||||
|
||||
if path.starts_with('/') {
|
||||
path = &path[1..];
|
||||
}
|
||||
|
||||
if !path.is_empty() {
|
||||
options.database(path);
|
||||
}
|
||||
|
||||
for (key, value) in url.query_pairs().into_iter() {
|
||||
let value =
|
||||
percent_decode_str_utf8(&*value, &format!("parameter {:?} in database url", key))?;
|
||||
|
||||
match &*key {
|
||||
"user" | "username" => {
|
||||
options.password(value);
|
||||
}
|
||||
|
||||
"password" => {
|
||||
options.password(value);
|
||||
}
|
||||
|
||||
// ssl-mode compatibly with SQLx <= 0.5
|
||||
// sslmode compatibly with PostgreSQL
|
||||
// sslMode compatibly with JDBC MySQL
|
||||
// tls compatibly with Go MySQL [preferred]
|
||||
"ssl-mode" | "sslmode" | "sslMode" | "tls" => {
|
||||
todo!()
|
||||
}
|
||||
|
||||
"charset" => {
|
||||
options.charset(value);
|
||||
}
|
||||
|
||||
"timezone" => {
|
||||
options.timezone(value);
|
||||
}
|
||||
|
||||
"socket" => {
|
||||
options.socket(value);
|
||||
}
|
||||
|
||||
_ => {
|
||||
// ignore unknown connection parameters
|
||||
// fixme: should we error or warn here?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(options)
|
||||
}
|
||||
}
|
||||
|
||||
// todo: this should probably go somewhere common
|
||||
fn percent_decode_str_utf8(value: &str, context: &str) -> Result<String, Error> {
|
||||
percent_decode_str(value)
|
||||
.decode_utf8()
|
||||
.map_err(|err| Error::configuration(context.to_owned(), err))
|
||||
.map(|s| (&*s).to_owned())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::MySqlConnectOptions;
|
||||
use std::path::Path;
|
||||
|
||||
#[test]
|
||||
fn it_should_parse() {
|
||||
let url = "mysql://user:password@hostname:5432/database?timezone=system&charset=utf8";
|
||||
let options: MySqlConnectOptions = url.parse().unwrap();
|
||||
|
||||
assert_eq!(options.get_username(), Some("user"));
|
||||
assert_eq!(options.get_password(), Some("password"));
|
||||
assert_eq!(options.get_host(), "hostname");
|
||||
assert_eq!(options.get_port(), 5432);
|
||||
assert_eq!(options.get_database(), Some("database"));
|
||||
assert_eq!(options.get_timezone(), "system");
|
||||
assert_eq!(options.get_charset(), "utf8");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_should_parse_with_defaults() {
|
||||
let url = "mysql://";
|
||||
let options: MySqlConnectOptions = url.parse().unwrap();
|
||||
|
||||
assert_eq!(options.get_username(), None);
|
||||
assert_eq!(options.get_password(), None);
|
||||
assert_eq!(options.get_host(), "localhost");
|
||||
assert_eq!(options.get_port(), 3306);
|
||||
assert_eq!(options.get_database(), None);
|
||||
assert_eq!(options.get_timezone(), "utc");
|
||||
assert_eq!(options.get_charset(), "utf8mb4");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_should_parse_socket_from_query() {
|
||||
let url = "mysql://user:password@localhost/database?socket=/var/run/mysqld/mysqld.sock";
|
||||
let options: MySqlConnectOptions = url.parse().unwrap();
|
||||
|
||||
assert_eq!(options.get_username(), Some("user"));
|
||||
assert_eq!(options.get_password(), Some("password"));
|
||||
assert_eq!(options.get_database(), Some("database"));
|
||||
assert_eq!(
|
||||
options.get_socket(),
|
||||
Some(Path::new("/var/run/mysqld/mysqld.sock"))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_should_parse_socket_from_host() {
|
||||
// socket path in host requires URL encoding – but does work
|
||||
let url = "mysql://user:password@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/database";
|
||||
let options: MySqlConnectOptions = url.parse().unwrap();
|
||||
|
||||
assert_eq!(options.get_username(), Some("user"));
|
||||
assert_eq!(options.get_password(), Some("password"));
|
||||
assert_eq!(options.get_database(), Some("database"));
|
||||
assert_eq!(
|
||||
options.get_socket(),
|
||||
Some(Path::new("/var/run/mysqld/mysqld.sock"))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn it_should_fail_to_parse_non_mysql() {
|
||||
let url = "postgres://user:password@hostname:5432/database?timezone=system&charset=utf8";
|
||||
let _: MySqlConnectOptions = url.parse().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_should_parse_username_with_at_sign() {
|
||||
let url = "mysql://user@hostname:password@hostname:5432/database";
|
||||
let options: MySqlConnectOptions = url.parse().unwrap();
|
||||
|
||||
assert_eq!(options.get_username(), Some("user@hostname"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_should_parse_password_with_non_ascii_chars() {
|
||||
let url = "mysql://username:p@ssw0rd@hostname:5432/database";
|
||||
let options: MySqlConnectOptions = url.parse().unwrap();
|
||||
|
||||
assert_eq!(options.get_password(), Some("p@ssw0rd"));
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user