Skip to content

Commit 31757e4

Browse files
authored
Better error messages & some clippy fixes (#197)
* better info * better error messages and clippy fixes * wip * capturing error * improving logging for panic and error * wip: trying to figure out how to fail immediately if connection config is not right * better error messages on various driver IO errors * much better error messages * fmt * refactor * postgres pool fix * refactoring everything to lazyCell under sync * lazy static to lazy cell, which is type safe * clean up and remove lazy static * fix * postgres errors * fix * introduce connection timeout * fix * fix * clippy fix * hrmm * hmm, * remove postgres * clippy * remove wrong clippy config * fix * fix * fix * fixed * clippy * remove println * fix loop * remove println * fmt
1 parent 066bc62 commit 31757e4

File tree

17 files changed

+233
-113
lines changed

17 files changed

+233
-113
lines changed

Cargo.lock

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ serde_json = { version = "1.0.127" }
2626
sqlparser = { version = "0.45.0" }
2727
regex = { version = "1.10.6" }
2828
convert_case = "0.6.0"
29-
lazy_static = { version = "1.5.0" }
3029
openssl = { version = "0.10.66", features = ["vendored"] }
3130
colored = "2.1.0"
3231
mysql_async = { version = "0.34.2", features = ["minimal"] }

book/src/api/1.3.configs-file-based.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ Example `.sqlxrc.json`
3939
"DB_PASS": "password",
4040
"DB_HOST": "127.0.0.1",
4141
"DB_PORT": 3307,
42-
"POOL_SIZE": 20
42+
"POOL_SIZE": 20,
43+
"CONNECTION_TIMEOUT": 10
4344
}
4445
}
4546
}
@@ -75,6 +76,7 @@ Supported fields of each connection include
7576
- `DB_PORT`: database port (e.g. 4321)
7677
- `PG_SEARCH_PATH`: PostgreSQL schema search path (default is "$user,public") [https://www.postgresql.org/docs/current/ddl-schemas.html#DDL-SCHEMAS-PATH](https://www.postgresql.org/docs/current/ddl-schemas.html#DDL-SCHEMAS-PATH)
7778
- `POOL_SIZE`: Size of the connection pool to establish per connection type
79+
- `CONNECTION_TIMEOUT`: Timeout in second of Database connection attempt
7880

7981
### generate_types
8082

src/common/config.rs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,19 +54,24 @@ pub struct DbConnectionConfig {
5454
pub pg_search_path: Option<String>,
5555
#[serde(rename = "POOL_SIZE", default = "default_pool_size")]
5656
pub pool_size: u32,
57+
#[serde(rename = "CONNECTION_TIMEOUT", default = "default_connection_timeout")]
58+
pub connection_timeout: u64,
5759
}
5860

5961
fn default_pool_size() -> u32 {
6062
10
6163
}
6264

65+
fn default_connection_timeout() -> u64 {
66+
5
67+
}
68+
6369
/// Config is used to determine connection configurations for primary target Database
6470
/// It uses 2 sources of config and they are used in following priorities
6571
/// 1. any configuration via CLI options
6672
/// 2. any dotenv configured options
6773
#[derive(Clone, Debug)]
6874
pub struct Config {
69-
pub dotenv: Dotenv,
7075
pub generate_types_config: Option<GenerateTypesConfig>,
7176
pub connections: HashMap<String, DbConnectionConfig>,
7277
pub ignore_patterns: Vec<String>,
@@ -94,7 +99,6 @@ impl Config {
9499
let ignore_patterns = Self::get_ignore_patterns(&default_ignore_config_path);
95100
let log_level = Self::get_log_level(file_config_path);
96101
Config {
97-
dotenv,
98102
connections,
99103
generate_types_config,
100104
ignore_patterns,
@@ -169,7 +173,7 @@ impl Config {
169173
"Empty or invalid JSON provided for file based configuration - config file: {:?}, error: {:?}",
170174
file_config_path, result,
171175
)
172-
)
176+
);
173177
}
174178

175179
result.unwrap()
@@ -271,6 +275,11 @@ impl Config {
271275
.or_else(|| Some(default_pool_size()))
272276
.unwrap();
273277

278+
let connection_timeout = default_config
279+
.map(|x| x.connection_timeout)
280+
.or_else(|| Some(default_connection_timeout()))
281+
.unwrap();
282+
274283
DbConnectionConfig {
275284
db_type: db_type.to_owned(),
276285
db_host: db_host.to_owned(),
@@ -280,6 +289,7 @@ impl Config {
280289
db_name: db_name.to_owned(),
281290
pg_search_path: pg_search_path.to_owned(),
282291
pool_size,
292+
connection_timeout,
283293
}
284294
}
285295

src/common/lazy.rs

Lines changed: 83 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -6,84 +6,99 @@ use crate::core::mysql::pool::MySqlConnectionManager;
66
use crate::core::postgres::pool::PostgresConnectionManager;
77
use crate::ts_generator::information_schema::DBSchema;
88
use clap::Parser;
9-
use lazy_static::lazy_static;
9+
use std::sync::LazyLock;
1010
use std::{collections::HashMap, sync::Arc};
1111
use tokio::{runtime::Handle, sync::Mutex, task};
1212

1313
// The file contains all implicitly dependent variables or state that files need for the logic
1414
// We have a lot of states that we need to drill down into each methods
15-
lazy_static! {
16-
pub static ref CLI_ARGS: Cli = Cli::parse();
17-
pub static ref CONFIG: Config = Config::new();
15+
pub static CLI_ARGS: LazyLock<Cli> = LazyLock::new(Cli::parse);
16+
pub static CONFIG: LazyLock<Config> = LazyLock::new(Config::new);
1817

19-
// This is a holder for shared DBSChema used to fetch information for information_schema table
20-
// By having a singleton, we can think about caching the result if we are fetching a query too many times
21-
pub static ref DB_SCHEMA: Arc<Mutex<DBSchema>> = Arc::new(Mutex::new(DBSchema::new()));
22-
pub static ref ERR_DB_CONNECTION_ISSUE: String = "Unable to connect to the database, please check the connection configuration again https://jasonshin.github.io/sqlx-ts/api/1.connecting-to-db.html".to_string();
18+
// This is a holder for shared DBSChema used to fetch information for information_schema table
19+
// By having a singleton, we can think about caching the result if we are fetching a query too many times
20+
pub static DB_SCHEMA: LazyLock<Arc<Mutex<DBSchema>>> = LazyLock::new(|| Arc::new(Mutex::new(DBSchema::new())));
21+
// TODO: move this to errors.rs
22+
pub static ERR_DB_CONNECTION_ISSUE: LazyLock<String> = LazyLock::new(|| {
23+
"Unable to connect to the database, please check the connection configuration again https://jasonshin.github.io/sqlx-ts/api/1.connecting-to-db.html".to_string()
24+
});
2325

24-
// This variable holds database connections for each connection name that is defined in the config
25-
// We are using lazy_static to initialize the connections once and use them throughout the application
26-
static ref DB_CONN_CACHE: HashMap<String, Arc<Mutex<DBConn>>> = {
27-
let mut cache = HashMap::new();
28-
for connection in CONFIG.connections.keys() {
29-
let connection_config = CONFIG.connections.get(connection)
30-
.unwrap_or_else(|| panic!("Failed to find a correct connection from the configuration - {connection}"));
31-
let db_type = connection_config.db_type.to_owned();
32-
let conn = match db_type {
33-
DatabaseType::Mysql => {
34-
task::block_in_place(|| Handle::current().block_on(async {
35-
let mysql_cred = CONFIG.get_mysql_cred_str(connection_config);
36-
let mysql_cred = mysql_cred.as_str();
37-
let manager = MySqlConnectionManager::new(mysql_cred.to_string());
38-
let pool = bb8::Pool::builder().max_size(connection_config.pool_size).build(manager)
39-
.await
40-
.expect(&ERR_DB_CONNECTION_ISSUE);
41-
DBConn::MySQLPooledConn(Mutex::new(pool))
42-
}))
43-
}
44-
DatabaseType::Postgres => {
45-
task::block_in_place(|| Handle::current().block_on(async {
46-
let postgres_cred = CONFIG.get_postgres_cred(connection_config);
47-
let manager = PostgresConnectionManager::new(postgres_cred);
48-
let pool = bb8::Pool::builder().max_size(10).build(manager)
49-
.await
50-
.expect(&ERR_DB_CONNECTION_ISSUE);
51-
let db_conn = DBConn::PostgresConn(Mutex::new(pool));
26+
// This variable holds database connections for each connection name that is defined in the config
27+
// We are using lazy_static to initialize the connections once and use them throughout the application
28+
pub static DB_CONN_CACHE: LazyLock<HashMap<String, Arc<Mutex<DBConn>>>> = LazyLock::new(|| {
29+
let mut cache = HashMap::new();
30+
for connection in CONFIG.connections.keys() {
31+
let connection_config = CONFIG
32+
.connections
33+
.get(connection)
34+
.unwrap_or_else(|| panic!("Invalid connection name - {connection}"));
35+
let db_type = connection_config.db_type.to_owned();
36+
let conn = match db_type {
37+
DatabaseType::Mysql => task::block_in_place(|| {
38+
Handle::current().block_on(async {
39+
let mysql_cred = CONFIG.get_mysql_cred_str(connection_config);
40+
let mysql_cred = mysql_cred.as_str();
41+
let manager = MySqlConnectionManager::new(mysql_cred.to_string(), connection.to_string());
42+
let pool = bb8::Pool::builder()
43+
.max_size(connection_config.pool_size)
44+
.connection_timeout(std::time::Duration::from_secs(connection_config.connection_timeout))
45+
.build(manager)
46+
.await
47+
.expect(&ERR_DB_CONNECTION_ISSUE);
5248

53-
let conn = match &db_conn {
54-
DBConn::PostgresConn(conn) => conn,
55-
_ => panic!("Invalid connection type"),
56-
};
49+
DBConn::MySQLPooledConn(Mutex::new(pool))
50+
})
51+
}),
52+
DatabaseType::Postgres => task::block_in_place(|| {
53+
Handle::current().block_on(async {
54+
let postgres_cred = CONFIG.get_postgres_cred(connection_config);
55+
let manager = PostgresConnectionManager::new(postgres_cred);
56+
let pool = bb8::Pool::builder()
57+
.max_size(connection_config.pool_size)
58+
.connection_timeout(std::time::Duration::from_secs(connection_config.connection_timeout))
59+
.build(manager)
60+
.await
61+
.expect(&ERR_DB_CONNECTION_ISSUE);
5762

58-
if connection_config.pg_search_path.is_some() {
59-
let search_path_query = format!("SET search_path TO {}", &connection_config.pg_search_path.clone().unwrap().as_str());
60-
{
61-
let conn = conn.lock().await;
62-
let conn = conn
63-
.get()
64-
.await
65-
.expect(&ERR_DB_CONNECTION_ISSUE);
63+
let db_conn = DBConn::PostgresConn(Mutex::new(pool));
6664

67-
let ERR_SEARCH_PATH_QUERY = format!("Failed to execute the search_path query {:?}", search_path_query.as_str());
68-
let ERR_SEARCH_PATH_QUERY = ERR_SEARCH_PATH_QUERY.as_str();
69-
conn.execute(&search_path_query, &[]).await
70-
.expect(ERR_SEARCH_PATH_QUERY);
71-
}
72-
}
73-
db_conn
74-
}))
65+
let conn = match &db_conn {
66+
DBConn::PostgresConn(conn) => conn,
67+
_ => panic!("Invalid connection type"),
68+
};
7569

76-
}
77-
};
78-
cache.insert(connection.to_owned(), Arc::new(Mutex::new(conn)));
79-
};
80-
cache
81-
};
70+
if connection_config.pg_search_path.is_some() {
71+
let search_path_query = format!(
72+
"SET search_path TO {}",
73+
&connection_config.pg_search_path.clone().unwrap().as_str()
74+
);
75+
{
76+
let conn = conn.lock().await;
77+
let conn = conn.get().await.expect(&ERR_DB_CONNECTION_ISSUE);
8278

83-
// This variable holds a singleton of DBConnections that is used to get a DBConn from the cache
84-
// DBConn is used to access the raw connection to the database or run `prepare` statement against each connection
85-
pub static ref DB_CONNECTIONS: Arc<Mutex<DBConnections<'static>>> = {
86-
let db_connections = DBConnections::new(&DB_CONN_CACHE);
87-
Arc::new(Mutex::new(db_connections))
79+
let err_search_path_query = format!(
80+
"Failed to execute the search_path query {:?}",
81+
search_path_query.as_str()
82+
);
83+
let err_search_path_query = err_search_path_query.as_str();
84+
conn
85+
.execute(&search_path_query, &[])
86+
.await
87+
.expect(err_search_path_query);
88+
}
89+
}
90+
db_conn
91+
})
92+
}),
8893
};
89-
}
94+
cache.insert(connection.to_owned(), Arc::new(Mutex::new(conn)));
95+
}
96+
cache
97+
});
98+
99+
// This variable holds a singleton of DBConnections that is used to get a DBConn from the cache
100+
// DBConn is used to access the raw connection to the database or run `prepare` statement against each connection
101+
pub static DB_CONNECTIONS: LazyLock<Arc<Mutex<DBConnections<'static>>>> = LazyLock::new(|| {
102+
let db_connections = DBConnections::new(&DB_CONN_CACHE);
103+
Arc::new(Mutex::new(db_connections))
104+
});

src/common/logger.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// TODO: Add documentation including examples
22
// TODO: Use SQLX_TS_LOG env var to set log level
33

4+
#[allow(unused_macros)]
45
macro_rules! debug {
56
($arg:tt) => ({
67
use crate::common::lazy::CONFIG;
@@ -90,6 +91,7 @@ macro_rules! error {
9091
($arg:tt, $($arg2:tt)*) => ({
9192
use crate::common::lazy::CONFIG;
9293
use crate::common::types::LogLevel;
94+
9395
if CONFIG.log_level.gte(&LogLevel::Error) {
9496
use colored::*;
9597
let level = "[ERROR]".red();
@@ -98,3 +100,5 @@ macro_rules! error {
98100
}
99101
});
100102
}
103+
104+
pub(crate) use error;

src/core/mysql/pool.rs

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
11
use async_trait::async_trait;
2-
use mysql_async::{prelude::*, Conn, Error, Opts};
2+
use mysql_async::{prelude::*, Conn, Error, IoError, Opts};
33
use tokio::{runtime::Handle, task};
44

55
#[derive(Clone, Debug)]
66
pub struct MySqlConnectionManager {
77
conn_url: String,
8+
// The connection name that user has defined in .sqlxrc.json
9+
connection_name: String,
810
}
911

1012
impl MySqlConnectionManager {
11-
pub fn new(conn_url: String) -> Self {
12-
Self { conn_url }
13+
pub fn new(conn_url: String, connection_name: String) -> Self {
14+
Self {
15+
conn_url,
16+
connection_name,
17+
}
1318
}
1419
}
1520

@@ -19,12 +24,50 @@ impl bb8::ManageConnection for MySqlConnectionManager {
1924
type Error = Error;
2025

2126
async fn connect(&self) -> Result<Self::Connection, Self::Error> {
27+
let connection_name = &self.connection_name;
2228
let conn_opts = Opts::from_url(self.conn_url.as_str())?;
23-
Conn::new(conn_opts).await
29+
30+
let conn = Conn::new(conn_opts).await.map_err(|err| {
31+
match err {
32+
Error::Driver(driver_error) => {
33+
panic!("Driver error occurred while connecting to MySQL database - connection: {connection_name}, error: {driver_error}");
34+
}
35+
Error::Io(io_err) => {
36+
match io_err {
37+
IoError::Io(io_err) => {
38+
if io_err.kind() == std::io::ErrorKind::ConnectionRefused {
39+
panic!("Connection Refused - check your connection config for MySQL database - connection: {connection_name}")
40+
} else {
41+
panic!("I/O error occurred while connection to MySQL database - connection: {connection_name}, error: {io_err}")
42+
}
43+
}
44+
IoError::Tls(tls_err) => {
45+
panic!("TLS error occurred while connecting to MySQL database - connection: {connection_name}, error: {tls_err}");
46+
}
47+
}
48+
}
49+
Error::Other(other_err) => {
50+
panic!("An unexpected error occurred while connecting to MySQL database - connection: {connection_name}, error: {other_err}");
51+
}
52+
Error::Server(server_err) => {
53+
panic!("Server error occurred while connecting to MySQL database - connection: {connection_name}, error: {server_err}");
54+
}
55+
Error::Url(_) => {
56+
panic!("Invalid URL format for MySQL connection string - connection: {connection_name}");
57+
}
58+
}
59+
}).unwrap();
60+
61+
Ok(conn)
2462
}
2563

2664
async fn is_valid(&self, conn: &mut Self::Connection) -> Result<(), Self::Error> {
27-
conn.query("SELECT version()").await.map(|_: Vec<String>| ())
65+
let connection_name = &self.connection_name;
66+
conn
67+
.query("SELECT 1")
68+
.await
69+
.map(|_: Vec<String>| ())
70+
.map_err(|err| panic!("Failed to validate MySQL connection for connection: {connection_name}. Error: {err}"))
2871
}
2972

3073
fn has_broken(&self, conn: &mut Self::Connection) -> bool {

src/core/mysql/prepare.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,7 @@ pub async fn prepare(
2727
let explain_query = format!("PREPARE stmt FROM \"{}\"", sql.query);
2828
let span = sql.span.to_owned();
2929
let conn = conn.lock().await;
30-
let mut conn = conn
31-
.get()
32-
.await
33-
.expect("Failed to retrieve a connection from the pool. Consider increasing the connection pool size");
30+
let mut conn = conn.get().await?;
3431
let result = conn.query::<Row, String>(explain_query).await;
3532

3633
if let Err(err) = result {

0 commit comments

Comments
 (0)