Skip to content

Commit 9f4e204

Browse files
committed
Allow creating a sea_orm::Databse from sqlx::ConnectOptions
1 parent 96a776a commit 9f4e204

File tree

6 files changed

+318
-78
lines changed

6 files changed

+318
-78
lines changed

Cargo.toml

+3-2
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ sea-query = { version = "^0.20.0", features = ["thread-safe"] }
3434
sea-strum = { version = "^0.21", features = ["derive", "sea-orm"] }
3535
serde = { version = "^1.0", features = ["derive"] }
3636
serde_json = { version = "^1", optional = true }
37-
sqlx = { version = "^0.5", optional = true }
37+
sqlx = { version = "^0.5.11", optional = true }
3838
uuid = { version = "0.8", features = ["serde", "v4"], optional = true }
3939
ouroboros = "0.14"
4040
url = "^2.2"
@@ -68,13 +68,14 @@ with-json = ["serde_json", "sea-query/with-json", "chrono/serde"]
6868
with-chrono = ["chrono", "sea-query/with-chrono"]
6969
with-rust_decimal = ["rust_decimal", "sea-query/with-rust_decimal"]
7070
with-uuid = ["uuid", "sea-query/with-uuid"]
71-
sqlx-all = ["sqlx-mysql", "sqlx-postgres", "sqlx-sqlite"]
71+
sqlx-all = ["sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", "sqlx-any"]
7272
sqlx-dep = ["sqlx-json", "sqlx-chrono", "sqlx-decimal", "sqlx-uuid"]
7373
sqlx-json = ["sqlx/json", "with-json"]
7474
sqlx-chrono = ["sqlx/chrono", "with-chrono"]
7575
sqlx-decimal = ["sqlx/decimal", "with-rust_decimal"]
7676
sqlx-uuid = ["sqlx/uuid", "with-uuid"]
7777
sqlx-mysql = ["sqlx-dep", "sea-query/sqlx-mysql", "sqlx/mysql"]
78+
sqlx-any = ["sqlx-dep", "sqlx/any"]
7879
sqlx-postgres = ["sqlx-dep", "sea-query/sqlx-postgres", "sqlx/postgres"]
7980
sqlx-sqlite = ["sqlx-dep", "sea-query/sqlx-sqlite", "sqlx/sqlite"]
8081
runtime-async-std = []

src/database/mod.rs

+261-37
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
#[cfg(feature = "sqlx-any")]
2+
use sqlx::any::AnyKind;
3+
#[cfg(feature = "sqlx-mysql")]
4+
use sqlx::mysql::MySqlConnectOptions;
5+
#[cfg(feature = "sqlx-postgres")]
6+
use sqlx::postgres::PgConnectOptions;
7+
#[cfg(feature = "sqlx-sqlite")]
8+
use sqlx::sqlite::SqliteConnectOptions;
9+
use std::fmt::Debug;
110
use std::time::Duration;
211

312
mod connection;
@@ -23,11 +32,55 @@ use crate::DbErr;
2332
#[derive(Debug, Default)]
2433
pub struct Database;
2534

35+
/// Supported database kinds of [sqlx::ConnectOptions]'.
36+
#[derive(Debug, Clone)]
37+
pub enum SqlxConnectOptions {
38+
#[cfg(feature = "sqlx-mysql")]
39+
MySql(MySqlConnectOptions),
40+
#[cfg(feature = "sqlx-postgres")]
41+
Postgres(PgConnectOptions),
42+
#[cfg(feature = "sqlx-sqlite")]
43+
Sqlite(SqliteConnectOptions),
44+
#[cfg(feature = "mock")]
45+
Mock(DbBackend),
46+
}
47+
48+
impl SqlxConnectOptions {
49+
/// The database backend type
50+
pub fn get_db_backend_type(&self) -> DbBackend {
51+
match self {
52+
#[cfg(feature = "sqlx-mysql")]
53+
SqlxConnectOptions::MySql(_) => DbBackend::MySql,
54+
#[cfg(feature = "sqlx-postgres")]
55+
SqlxConnectOptions::Postgres(_) => DbBackend::Postgres,
56+
#[cfg(feature = "sqlx-sqlite")]
57+
SqlxConnectOptions::Sqlite(_) => DbBackend::Sqlite,
58+
#[cfg(feature = "mock")]
59+
SqlxConnectOptions::Mock(db_backend) => db_backend.clone(),
60+
}
61+
}
62+
63+
#[cfg(feature = "mock")]
64+
pub fn mock(db_backend: DbBackend) -> SqlxConnectOptions {
65+
Self::Mock(db_backend)
66+
}
67+
68+
#[cfg(feature = "mock")]
69+
pub fn is_mock(&self) -> bool {
70+
match self {
71+
SqlxConnectOptions::Mock(_) => true,
72+
_ => false,
73+
}
74+
}
75+
}
76+
2677
/// Defines the configuration options of a database
2778
#[derive(Debug)]
2879
pub struct ConnectOptions {
29-
/// The URI of the database
30-
pub(crate) url: String,
80+
/// The database sqlx::ConnectOptions used to connect to the database.
81+
pub(crate) connect_options: SqlxConnectOptions,
82+
/// The URI of the database, if this struct was created from an URI string, otherwise None
83+
pub(crate) url: Option<String>,
3184
/// Maximum number of connections for a pool
3285
pub(crate) max_connections: Option<u32>,
3386
/// Minimum number of connections for a pool
@@ -44,58 +97,165 @@ pub struct ConnectOptions {
4497
impl Database {
4598
/// Method to create a [DatabaseConnection] on a database
4699
#[instrument(level = "trace", skip(opt))]
47-
pub async fn connect<C>(opt: C) -> Result<DatabaseConnection, DbErr>
100+
pub async fn connect<C, E>(opt: C) -> Result<DatabaseConnection, DbErr>
48101
where
49-
C: Into<ConnectOptions>,
102+
C: TryInto<ConnectOptions, Error = E> + Debug,
103+
E: std::error::Error,
50104
{
51-
let opt: ConnectOptions = opt.into();
105+
let describe = format!("{:?}", opt);
106+
let opt: ConnectOptions = opt
107+
.try_into()
108+
.map_err(|e| DbErr::Conn(format!("Couldn't parse connection options {}", e)))?;
52109

53-
#[cfg(feature = "sqlx-mysql")]
54-
if DbBackend::MySql.is_prefix_of(&opt.url) {
55-
return crate::SqlxMySqlConnector::connect(opt).await;
56-
}
57-
#[cfg(feature = "sqlx-postgres")]
58-
if DbBackend::Postgres.is_prefix_of(&opt.url) {
59-
return crate::SqlxPostgresConnector::connect(opt).await;
60-
}
61-
#[cfg(feature = "sqlx-sqlite")]
62-
if DbBackend::Sqlite.is_prefix_of(&opt.url) {
63-
return crate::SqlxSqliteConnector::connect(opt).await;
64-
}
65110
#[cfg(feature = "mock")]
66-
if crate::MockDatabaseConnector::accepts(&opt.url) {
67-
return crate::MockDatabaseConnector::connect(&opt.url).await;
111+
if opt.connect_options.is_mock() {
112+
return crate::MockDatabaseConnector::connect(opt).await;
113+
}
114+
115+
let backend = opt.connect_options.get_db_backend_type();
116+
117+
match backend {
118+
#[cfg(feature = "sqlx-mysql")]
119+
DbBackend::MySql => crate::SqlxMySqlConnector::connect(opt).await,
120+
#[cfg(feature = "sqlx-postgres")]
121+
DbBackend::Postgres => crate::SqlxPostgresConnector::connect(opt).await,
122+
#[cfg(feature = "sqlx-sqlite")]
123+
DbBackend::Sqlite => crate::SqlxSqliteConnector::connect(opt).await,
124+
_ => {
125+
return Err(DbErr::Conn(format!(
126+
"The connection option {} has no supporting driver.",
127+
describe
128+
)));
129+
}
68130
}
69-
Err(DbErr::Conn(format!(
70-
"The connection string '{}' has no supporting driver.",
71-
opt.url
72-
)))
73131
}
74132
}
75133

76-
impl From<&str> for ConnectOptions {
77-
fn from(string: &str) -> ConnectOptions {
134+
impl TryFrom<&str> for ConnectOptions {
135+
type Error = DbErr;
136+
137+
fn try_from(string: &str) -> Result<Self, Self::Error> {
78138
ConnectOptions::from_str(string)
79139
}
80140
}
81141

82-
impl From<&String> for ConnectOptions {
83-
fn from(string: &String) -> ConnectOptions {
142+
impl TryFrom<&String> for ConnectOptions {
143+
type Error = DbErr;
144+
145+
fn try_from(string: &String) -> Result<Self, Self::Error> {
84146
ConnectOptions::from_str(string.as_str())
85147
}
86148
}
87149

88-
impl From<String> for ConnectOptions {
89-
fn from(string: String) -> ConnectOptions {
90-
ConnectOptions::new(string)
150+
impl TryFrom<String> for ConnectOptions {
151+
type Error = DbErr;
152+
153+
fn try_from(string: String) -> Result<Self, Self::Error> {
154+
ConnectOptions::new_from_url(string)
155+
}
156+
}
157+
158+
#[cfg(feature = "sqlx-mysql")]
159+
impl TryFrom<MySqlConnectOptions> for ConnectOptions {
160+
type Error = DbErr;
161+
162+
fn try_from(connect_options: MySqlConnectOptions) -> Result<Self, Self::Error> {
163+
Ok(ConnectOptions::new(SqlxConnectOptions::MySql(
164+
connect_options,
165+
)))
166+
}
167+
}
168+
169+
#[cfg(feature = "sqlx-postgres")]
170+
impl TryFrom<PgConnectOptions> for ConnectOptions {
171+
type Error = DbErr;
172+
173+
fn try_from(connect_options: PgConnectOptions) -> Result<Self, Self::Error> {
174+
Ok(ConnectOptions::new(SqlxConnectOptions::Postgres(
175+
connect_options,
176+
)))
177+
}
178+
}
179+
180+
#[cfg(feature = "sqlx-sqlite")]
181+
impl TryFrom<SqliteConnectOptions> for ConnectOptions {
182+
type Error = DbErr;
183+
184+
fn try_from(connect_options: SqliteConnectOptions) -> Result<Self, Self::Error> {
185+
Ok(ConnectOptions::new(SqlxConnectOptions::Sqlite(
186+
connect_options,
187+
)))
188+
}
189+
}
190+
191+
#[cfg(feature = "sqlx-any")]
192+
impl TryFrom<sqlx::any::AnyConnectOptions> for ConnectOptions {
193+
type Error = DbErr;
194+
195+
fn try_from(connect_options: sqlx::any::AnyConnectOptions) -> Result<Self, Self::Error> {
196+
Ok(ConnectOptions::new(connect_options.try_into()?))
197+
}
198+
}
199+
200+
#[cfg(feature = "sqlx-mysql")]
201+
impl TryFrom<MySqlConnectOptions> for SqlxConnectOptions {
202+
type Error = DbErr;
203+
204+
fn try_from(connect_options: MySqlConnectOptions) -> Result<Self, Self::Error> {
205+
Ok(SqlxConnectOptions::MySql(connect_options))
206+
}
207+
}
208+
209+
#[cfg(feature = "sqlx-postgres")]
210+
impl TryFrom<PgConnectOptions> for SqlxConnectOptions {
211+
type Error = DbErr;
212+
213+
fn try_from(connect_options: PgConnectOptions) -> Result<Self, Self::Error> {
214+
Ok(SqlxConnectOptions::Postgres(connect_options))
215+
}
216+
}
217+
218+
#[cfg(feature = "sqlx-sqlite")]
219+
impl TryFrom<SqliteConnectOptions> for SqlxConnectOptions {
220+
type Error = DbErr;
221+
222+
fn try_from(connect_options: SqliteConnectOptions) -> Result<Self, Self::Error> {
223+
Ok(SqlxConnectOptions::Sqlite(connect_options))
224+
}
225+
}
226+
227+
#[cfg(feature = "sqlx-any")]
228+
impl TryFrom<sqlx::any::AnyConnectOptions> for SqlxConnectOptions {
229+
type Error = DbErr;
230+
231+
fn try_from(connect_options: sqlx::any::AnyConnectOptions) -> Result<Self, Self::Error> {
232+
match connect_options.kind() {
233+
#[cfg(feature = "sqlx-postgres")]
234+
AnyKind::Postgres => Ok(SqlxConnectOptions::Postgres(
235+
connect_options.as_postgres_cloned().unwrap(),
236+
)),
237+
#[cfg(feature = "sqlx-mysql")]
238+
AnyKind::MySql => Ok(SqlxConnectOptions::MySql(
239+
connect_options.as_mysql_cloned().unwrap(),
240+
)),
241+
#[cfg(feature = "sqlx-sqlite")]
242+
AnyKind::Sqlite => Ok(SqlxConnectOptions::Sqlite(
243+
connect_options.as_sqlite_cloned().unwrap(),
244+
)),
245+
_ => Err(DbErr::Conn(format!(
246+
"Sea orm doesn't support sqlx database kind: {:?}!",
247+
connect_options.kind()
248+
))),
249+
}
91250
}
92251
}
93252

94253
impl ConnectOptions {
95-
/// Create new [ConnectOptions] for a [Database] by passing in a URI string
96-
pub fn new(url: String) -> Self {
254+
/// Create new [ConnectOptions] for a [Database] by passing in a [sqlx::ConnectOptions]
255+
pub fn new(connect_options: SqlxConnectOptions) -> Self {
97256
Self {
98-
url,
257+
connect_options,
258+
url: None,
99259
max_connections: None,
100260
min_connections: None,
101261
connect_timeout: None,
@@ -104,8 +264,64 @@ impl ConnectOptions {
104264
}
105265
}
106266

107-
fn from_str(url: &str) -> Self {
108-
Self::new(url.to_owned())
267+
/// Create new [ConnectOptions] for a [Database] by passing in a URI string
268+
pub fn new_from_url(url: String) -> Result<Self, DbErr> {
269+
Ok(Self {
270+
connect_options: Self::url_to_sqlx_connect_options(url.clone())?,
271+
url: Some(url),
272+
max_connections: None,
273+
min_connections: None,
274+
connect_timeout: None,
275+
idle_timeout: None,
276+
sqlx_logging: true,
277+
})
278+
}
279+
280+
fn url_to_sqlx_connect_options(url: String) -> Result<SqlxConnectOptions, DbErr> {
281+
#[cfg(feature = "sqlx-mysql")]
282+
if DbBackend::MySql.is_prefix_of(&url) {
283+
return Ok(url
284+
.parse::<MySqlConnectOptions>()
285+
.map_err(|e| DbErr::Conn(e.to_string()))?
286+
.try_into()?);
287+
}
288+
#[cfg(feature = "sqlx-postgres")]
289+
if DbBackend::Postgres.is_prefix_of(&url) {
290+
return Ok(url
291+
.parse::<PgConnectOptions>()
292+
.map_err(|e| DbErr::Conn(e.to_string()))?
293+
.try_into()?);
294+
}
295+
#[cfg(feature = "sqlx-sqlite")]
296+
if DbBackend::Sqlite.is_prefix_of(&url) {
297+
return Ok(url
298+
.parse::<SqliteConnectOptions>()
299+
.map_err(|e| DbErr::Conn(e.to_string()))?
300+
.try_into()?);
301+
}
302+
#[cfg(feature = "mock")]
303+
if crate::MockDatabaseConnector::accepts(&url) {
304+
if DbBackend::MySql.is_prefix_of(&url) {
305+
return Ok(SqlxConnectOptions::Mock(DbBackend::MySql));
306+
}
307+
#[cfg(feature = "sqlx-postgres")]
308+
if DbBackend::Postgres.is_prefix_of(&url) {
309+
return Ok(SqlxConnectOptions::Mock(DbBackend::Postgres));
310+
}
311+
#[cfg(feature = "sqlx-sqlite")]
312+
if DbBackend::Sqlite.is_prefix_of(&url) {
313+
return Ok(SqlxConnectOptions::Mock(DbBackend::Sqlite));
314+
}
315+
return Ok(SqlxConnectOptions::Mock(DbBackend::Postgres));
316+
}
317+
Err(DbErr::Conn(format!(
318+
"The connection string '{}' has no supporting driver.",
319+
url
320+
)))
321+
}
322+
323+
fn from_str(url: &str) -> Result<Self, DbErr> {
324+
Self::new_from_url(url.to_owned())
109325
}
110326

111327
#[cfg(feature = "sqlx-dep")]
@@ -130,11 +346,19 @@ impl ConnectOptions {
130346
opt
131347
}
132348

133-
/// Get the database URL of the pool
134-
pub fn get_url(&self) -> &str {
349+
/// Get the database URL of the pool. This is only present if the pool was created from an URL.
350+
/// If it was created from some sqlx::ConnectOptions then this method returns None.
351+
///
352+
/// To get the actual ConnectOptions used to connect to the database see: [Self::get_connect_options].
353+
pub fn get_url(&self) -> &Option<String> {
135354
&self.url
136355
}
137356

357+
/// Get the ConnectOptions used to connect to the database
358+
pub fn get_connect_options(&self) -> &SqlxConnectOptions {
359+
&self.connect_options
360+
}
361+
138362
/// Set the maximum number of connections of the pool
139363
pub fn max_connections(&mut self, value: u32) -> &mut Self {
140364
self.max_connections = Some(value);

0 commit comments

Comments
 (0)