Skip to content

Commit 4be895f

Browse files
committed
refactor(db): 从 URL scheme 自动推断驱动类型,重命名数据库用户名配置为user,修复 PostgreSQL 查询逻辑
1 parent 91e7b7a commit 4be895f

9 files changed

Lines changed: 75 additions & 44 deletions

File tree

crates/webr-db/src/config.rs

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,10 @@ use serde::Deserialize;
33
/// Database connection pool configuration.
44
#[derive(Debug, Clone, Deserialize)]
55
pub struct DatasourceConfig {
6-
/// Driver: "mysql", "postgres", or "sqlite"
7-
pub driver: String,
8-
/// Full connection URL, e.g. "postgres://host:5432/mydb"
6+
/// Full connection URL, e.g. "postgres://host:5432/db"
97
pub url: String,
10-
/// Optional username injected into the URL (replaces any embedded credentials)
11-
pub username: Option<String>,
8+
/// Optional user injected into the URL (replaces any embedded credentials)
9+
pub user: Option<String>,
1210
/// Optional password injected into the URL
1311
pub password: Option<String>,
1412
/// Pool tuning parameters
@@ -51,17 +49,35 @@ fn default_idle_timeout() -> u64 {
5149
}
5250

5351
impl DatasourceConfig {
52+
/// Infer the driver from the URL scheme.
53+
///
54+
/// Supported schemes: `postgres`, `mysql`, `sqlite`.
55+
pub fn resolve_driver(&self) -> Result<&str, crate::DbError> {
56+
let scheme = self.url.split_once(':').map(|(s, _)| s).ok_or_else(|| {
57+
crate::DbError::Config(format!(
58+
"cannot infer driver: url has no scheme: '{}'",
59+
self.url
60+
))
61+
})?;
62+
match scheme {
63+
"postgres" | "mysql" | "sqlite" => Ok(scheme),
64+
other => Err(crate::DbError::Config(format!(
65+
"unsupported driver '{other}' in url scheme"
66+
))),
67+
}
68+
}
69+
5470
/// Resolve the final connection URL.
5571
///
56-
/// Merges `username`/`password` into the URL's authority section when configured.
72+
/// Merges `user`/`password` into the URL's authority section when configured.
5773
pub fn resolve_url(&self) -> Result<String, crate::DbError> {
5874
Ok(self.merge_credentials(&self.url))
5975
}
6076

61-
/// Inject `username`/`password` into an existing URL, replacing any embedded credentials.
77+
/// Inject `user`/`password` into an existing URL, replacing any embedded credentials.
6278
/// Returns the URL unchanged if neither field is configured.
6379
fn merge_credentials(&self, url: &str) -> String {
64-
let user = self.username.as_deref();
80+
let user = self.user.as_deref();
6581
let pass = self.password.as_deref();
6682
if user.is_none() && pass.is_none() {
6783
return url.to_string();

crates/webr-db/src/executor.rs

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,15 @@ impl DbPool {
365365
match ins {
366366
#[cfg(feature = "postgres")]
367367
ExecutionBinder::Postgres(q) => {
368-
q.fetch_one(self.as_pg()).await.map_err(DbError::Sqlx)
368+
q.execute(self.as_pg()).await.map_err(DbError::Sqlx)?;
369+
let fq = self.query_as::<R>(fetch_sql);
370+
match fq {
371+
QueryBinder::Postgres(q) => {
372+
q.fetch_one(self.as_pg()).await.map_err(DbError::Sqlx)
373+
}
374+
#[allow(unreachable_patterns)]
375+
_ => unreachable!(),
376+
}
369377
}
370378
#[cfg(feature = "mysql")]
371379
ExecutionBinder::MySql(q) => {
@@ -599,10 +607,18 @@ impl DbTransaction {
599607
let mut __g = self.lock().await;
600608
match ins {
601609
#[cfg(feature = "postgres")]
602-
ExecutionBinder::Postgres(q) => q
603-
.fetch_one(Self::as_pg(&mut __g))
604-
.await
605-
.map_err(DbError::Sqlx),
610+
ExecutionBinder::Postgres(q) => {
611+
q.execute(Self::as_pg(&mut __g)).await.map_err(DbError::Sqlx)?;
612+
let fq = self.query_as::<R>(fetch_sql);
613+
match fq {
614+
QueryBinder::Postgres(q) => q
615+
.fetch_one(Self::as_pg(&mut __g))
616+
.await
617+
.map_err(DbError::Sqlx),
618+
#[allow(unreachable_patterns)]
619+
_ => unreachable!(),
620+
}
621+
}
606622
#[cfg(feature = "mysql")]
607623
ExecutionBinder::MySql(q) => {
608624
let result = q

crates/webr-db/src/pool.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ impl DbPool {
7171
#[allow(unused_variables)]
7272
let idle_timeout = Duration::from_secs(pool_cfg.idle_timeout_secs);
7373

74-
match config.driver.as_str() {
74+
match config.resolve_driver()? {
7575
#[cfg(feature = "postgres")]
7676
"postgres" => {
7777
let pg_pool = sqlx::postgres::PgPoolOptions::new()
@@ -136,7 +136,7 @@ impl DbPool {
136136
/// # Panics
137137
/// Panics if the driver is not PostgreSQL.
138138
#[cfg(feature = "postgres")]
139-
pub fn as_pg(&self) -> &sqlx::PgPool {
139+
pub(crate) fn as_pg(&self) -> &sqlx::PgPool {
140140
#[allow(unreachable_patterns)]
141141
match &self.inner {
142142
PoolInner::Postgres(p) => p,
@@ -148,7 +148,7 @@ impl DbPool {
148148
/// # Panics
149149
/// Panics if the driver is not MySQL.
150150
#[cfg(feature = "mysql")]
151-
pub fn as_my(&self) -> &sqlx::MySqlPool {
151+
pub(crate) fn as_my(&self) -> &sqlx::MySqlPool {
152152
#[allow(unreachable_patterns)]
153153
match &self.inner {
154154
PoolInner::MySql(p) => p,
@@ -160,7 +160,7 @@ impl DbPool {
160160
/// # Panics
161161
/// Panics if the driver is not SQLite.
162162
#[cfg(feature = "sqlite")]
163-
pub fn as_sq(&self) -> &sqlx::SqlitePool {
163+
pub(crate) fn as_sq(&self) -> &sqlx::SqlitePool {
164164
#[allow(unreachable_patterns)]
165165
match &self.inner {
166166
PoolInner::Sqlite(p) => p,

crates/webr-db/src/transaction.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ impl DbTransaction {
121121
/// Panics if the driver is not PostgreSQL.
122122
#[cfg(feature = "postgres")]
123123
#[allow(clippy::wrong_self_convention)]
124-
pub fn as_pg(guard: &mut TxnInner) -> &mut sqlx::PgConnection {
124+
pub(crate) fn as_pg(guard: &mut TxnInner) -> &mut sqlx::PgConnection {
125125
#[allow(unreachable_patterns)]
126126
match guard {
127127
TxnInner::Postgres(tx) => &mut **tx,
@@ -134,7 +134,7 @@ impl DbTransaction {
134134
/// Panics if the driver is not MySQL.
135135
#[cfg(feature = "mysql")]
136136
#[allow(clippy::wrong_self_convention)]
137-
pub fn as_my(guard: &mut TxnInner) -> &mut sqlx::MySqlConnection {
137+
pub(crate) fn as_my(guard: &mut TxnInner) -> &mut sqlx::MySqlConnection {
138138
#[allow(unreachable_patterns)]
139139
match guard {
140140
TxnInner::MySql(tx) => &mut **tx,
@@ -147,7 +147,7 @@ impl DbTransaction {
147147
/// Panics if the driver is not SQLite.
148148
#[cfg(feature = "sqlite")]
149149
#[allow(clippy::wrong_self_convention)]
150-
pub fn as_sq(guard: &mut TxnInner) -> &mut sqlx::SqliteConnection {
150+
pub(crate) fn as_sq(guard: &mut TxnInner) -> &mut sqlx::SqliteConnection {
151151
#[allow(unreachable_patterns)]
152152
match guard {
153153
TxnInner::Sqlite(tx) => &mut **tx,

crates/webr-db/tests/db_tests.rs

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,21 +18,19 @@ fn pool_config_default_values() {
1818
#[test]
1919
fn resolve_url_returns_url_unchanged_when_no_credentials() {
2020
let cfg = DatasourceConfig {
21-
driver: "sqlite".into(),
2221
url: "sqlite:///tmp/explicit.db".into(),
23-
username: None,
22+
user: None,
2423
password: None,
2524
pool: PoolConfig::default(),
2625
};
2726
assert_eq!(cfg.resolve_url().unwrap(), "sqlite:///tmp/explicit.db");
2827
}
2928

3029
#[test]
31-
fn resolve_url_merges_username_and_password() {
30+
fn resolve_url_merges_user_and_password() {
3231
let cfg = DatasourceConfig {
33-
driver: "postgres".into(),
3432
url: "postgres://host:5432/mydb".into(),
35-
username: Some("admin".into()),
33+
user: Some("admin".into()),
3634
password: Some("secret".into()),
3735
pool: PoolConfig::default(),
3836
};
@@ -43,11 +41,10 @@ fn resolve_url_merges_username_and_password() {
4341
}
4442

4543
#[test]
46-
fn resolve_url_merges_username_only() {
44+
fn resolve_url_merges_user_only() {
4745
let cfg = DatasourceConfig {
48-
driver: "postgres".into(),
4946
url: "postgres://host:5432/mydb".into(),
50-
username: Some("admin".into()),
47+
user: Some("admin".into()),
5148
password: None,
5249
pool: PoolConfig::default(),
5350
};
@@ -60,9 +57,8 @@ fn resolve_url_merges_username_only() {
6057
#[test]
6158
fn resolve_url_replaces_existing_credentials() {
6259
let cfg = DatasourceConfig {
63-
driver: "postgres".into(),
6460
url: "postgres://old:oldpass@host:5432/mydb?sslmode=require".into(),
65-
username: Some("new".into()),
61+
user: Some("new".into()),
6662
password: Some("newpass".into()),
6763
pool: PoolConfig::default(),
6864
};
@@ -75,9 +71,8 @@ fn resolve_url_replaces_existing_credentials() {
7571
#[test]
7672
fn resolve_url_returns_url_without_scheme_unchanged() {
7773
let cfg = DatasourceConfig {
78-
driver: "sqlite".into(),
7974
url: "just-a-path.db".into(),
80-
username: Some("user".into()),
75+
user: Some("user".into()),
8176
password: Some("pass".into()),
8277
pool: PoolConfig::default(),
8378
};

crates/webr-db/tests/pool_tests.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,8 @@ struct Todo {
1717
/// Build a SQLite in-memory pool.
1818
async fn test_pool() -> DbPool {
1919
let cfg = DatasourceConfig {
20-
driver: "sqlite".into(),
2120
url: "sqlite::memory:".into(),
22-
username: None,
21+
user: None,
2322
password: None,
2423
pool: PoolConfig::default(),
2524
};
@@ -336,9 +335,8 @@ async fn scope_txn_exposes_transaction_via_try_get_txn() {
336335
#[tokio::test]
337336
async fn from_config_unsupported_driver_returns_error() {
338337
let cfg = DatasourceConfig {
339-
driver: "oracle".into(),
340-
url: "jdbc:oracle:thin:@localhost:1521/xe".into(),
341-
username: None,
338+
url: "oracle://localhost/xe".into(),
339+
user: None,
342340
password: None,
343341
pool: PoolConfig::default(),
344342
};

examples/orm/config/application.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,4 @@ host = "0.0.0.0"
66
level = "debug"
77

88
[datasource]
9-
driver = "sqlite"
109
url = "sqlite://todos.db?mode=rwc"

src/auto_init.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,21 @@ pub async fn auto_init(app: &mut AppBuilder) -> Result<(), Error> {
1919
#[cfg(feature = "auto-init")]
2020
{
2121
// Auto-initialize cache if configured
22-
#[cfg(any(feature = "cache-memory", feature = "cache-sled", feature = "cache-redis"))]
22+
#[cfg(any(
23+
feature = "cache-memory",
24+
feature = "cache-sled",
25+
feature = "cache-redis"
26+
))]
2327
{
2428
if let Ok(cache_config) = app.config().get::<webr_cache::CacheConfig>("cache") {
2529
let cache = crate::cache_adapter::Cache::from_config(&cache_config)
2630
.await
2731
.map_err(crate::__cache_error)?;
2832
app.provide(cache)?;
29-
tracing::info!("Cache auto-initialized with backend: {}", cache_config.backend);
33+
tracing::info!(
34+
"Cache auto-initialized with backend: {}",
35+
cache_config.backend
36+
);
3037
}
3138
}
3239

@@ -42,7 +49,7 @@ pub async fn auto_init(app: &mut AppBuilder) -> Result<(), Error> {
4249
app.provide(pool)?;
4350
tracing::info!(
4451
"Database pool auto-initialized with driver: {}",
45-
ds_config.driver
52+
ds_config.resolve_driver().unwrap_or("unknown")
4653
);
4754
}
4855
}

src/db_adapter.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ use webr_core::component::Component;
1313
/// Implements [`Component`] so the pool can be registered with
1414
/// `app.provide(pool)` and injected via `Inject<DbPool>`.
1515
///
16-
/// Derefs to the inner [`webr_db::DbPool`], so all pool methods
17-
/// (`as_pg`, `as_my`, `as_sq`, `driver`, `placeholder`, …) are
18-
/// available without explicit delegation.
16+
/// Derefs to the inner [`webr_db::DbPool`], so the public pool API
17+
/// (`driver`, `placeholder`, `query_as`, `fetch_all`, …) is available
18+
/// without explicit delegation.
1919
pub struct DbPool(webr_db::DbPool);
2020

2121
impl DbPool {

0 commit comments

Comments
 (0)