Skip to content

Commit c629851

Browse files
committed
sqlite
1 parent a2b0bce commit c629851

16 files changed

Lines changed: 641 additions & 29 deletions

File tree

Cargo.lock

Lines changed: 50 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ tokio-postgres = "0.7.16"
3131
tokio = { version = "1.50.0", features = ["rt-multi-thread", "macros", "default"]}
3232
async-recursion = "1.1.1"
3333
bb8 = "0.9.1"
34+
rusqlite = { version = "0.31", features = ["bundled"] }
3435
log = "0.4.29"
3536

3637
[dev-dependencies]
@@ -39,3 +40,4 @@ predicates = "3.1.4"
3940
tempfile = "3.27.0"
4041
test_utils = { path="test-utils" }
4142
pretty_assertions = "1.4.1"
43+
rusqlite = { version = "0.31", features = ["bundled"] }

src/common/config.rs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,9 @@ impl Config {
234234
.or_else(|| default_config.map(|x| x.db_host.clone()))
235235
};
236236

237-
let db_host = match (db_url.is_some(), db_host_chain()) {
237+
let is_sqlite = matches!(db_type, DatabaseType::Sqlite);
238+
239+
let db_host = match (db_url.is_some() || is_sqlite, db_host_chain()) {
238240
(true, Some(v)) => v,
239241
(true, None) => String::new(),
240242
(false, Some(v)) => v,
@@ -254,7 +256,7 @@ impl Config {
254256
.or_else(|| default_config.map(|x| x.db_port))
255257
};
256258

257-
let db_port = match (db_url.is_some(), db_port_chain()) {
259+
let db_port = match (db_url.is_some() || is_sqlite, db_port_chain()) {
258260
(true, Some(v)) => v,
259261
(true, None) => 0,
260262
(false, Some(v)) => v,
@@ -275,7 +277,7 @@ impl Config {
275277
.or_else(|| default_config.map(|x| x.db_user.clone()))
276278
};
277279

278-
let db_user = match (db_url.is_some(), db_user_chain()) {
280+
let db_user = match (db_url.is_some() || is_sqlite, db_user_chain()) {
279281
(true, Some(v)) => v,
280282
(true, None) => String::new(),
281283
(false, Some(v)) => v,
@@ -381,6 +383,19 @@ impl Config {
381383
.to_string()
382384
}
383385

386+
/// Returns the file path for a SQLite database connection.
387+
/// If DB_URL is provided, it's used directly. Otherwise DB_NAME is used as the file path.
388+
pub fn get_sqlite_path(&self, conn: &DbConnectionConfig) -> String {
389+
if let Some(db_url) = &conn.db_url {
390+
return db_url.to_owned();
391+
}
392+
393+
conn
394+
.db_name
395+
.clone()
396+
.unwrap_or_else(|| panic!("DB_NAME (file path) is required for SQLite connections"))
397+
}
398+
384399
pub fn get_postgres_cred(&self, conn: &DbConnectionConfig) -> String {
385400
// If custom DB_URL is provided, use it directly
386401
if let Some(db_url) = &conn.db_url {

src/common/dotenv.rs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,10 @@ impl Dotenv {
3232
Dotenv {
3333
db_type: match Self::get_var("DB_TYPE") {
3434
None => None,
35-
Some(val) => {
36-
if val == "mysql" {
37-
Some(DatabaseType::Mysql)
38-
} else {
39-
Some(DatabaseType::Postgres)
40-
}
35+
Some(val) => match val.as_str() {
36+
"mysql" => Some(DatabaseType::Mysql),
37+
"sqlite" => Some(DatabaseType::Sqlite),
38+
_ => Some(DatabaseType::Postgres),
4139
}
4240
},
4341
db_user: Self::get_var("DB_USER"),

src/common/lazy.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use crate::common::types::DatabaseType;
44
use crate::core::connection::{DBConn, DBConnections};
55
use crate::core::mysql::pool::MySqlConnectionManager;
66
use crate::core::postgres::pool::PostgresConnectionManager;
7+
use crate::core::sqlite::pool::SqliteConnectionManager;
78
use crate::ts_generator::information_schema::DBSchema;
89
use clap::Parser;
910
use std::sync::LazyLock;
@@ -49,6 +50,20 @@ pub static DB_CONN_CACHE: LazyLock<HashMap<String, Arc<Mutex<DBConn>>>> = LazyLo
4950
DBConn::MySQLPooledConn(Mutex::new(pool))
5051
})
5152
}),
53+
DatabaseType::Sqlite => task::block_in_place(|| {
54+
Handle::current().block_on(async {
55+
let sqlite_path = CONFIG.get_sqlite_path(connection_config);
56+
let manager = SqliteConnectionManager::new(sqlite_path, connection.to_string());
57+
let pool = bb8::Pool::builder()
58+
.max_size(connection_config.pool_size)
59+
.connection_timeout(std::time::Duration::from_secs(connection_config.connection_timeout))
60+
.build(manager)
61+
.await
62+
.expect(&ERR_DB_CONNECTION_ISSUE);
63+
64+
DBConn::SqliteConn(Mutex::new(pool))
65+
})
66+
}),
5267
DatabaseType::Postgres => task::block_in_place(|| {
5368
Handle::current().block_on(async {
5469
let postgres_cred = CONFIG.get_postgres_cred(connection_config);

src/common/types.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ pub enum FileExtension {
1818
pub enum DatabaseType {
1919
Postgres,
2020
Mysql,
21+
Sqlite,
2122
}
2223

2324
#[derive(ValueEnum, Debug, Clone, Serialize, Deserialize)]

src/core/connection.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use crate::common::types::DatabaseType;
33
use crate::common::SQL;
44
use crate::core::mysql::prepare as mysql_explain;
55
use crate::core::postgres::prepare as postgres_explain;
6+
use crate::core::sqlite::prepare as sqlite_explain;
67
use crate::ts_generator::types::ts_query::TsQuery;
78
use bb8::Pool;
89
use std::collections::HashMap;
@@ -11,6 +12,7 @@ use tokio::sync::Mutex;
1112

1213
use super::mysql::pool::MySqlConnectionManager;
1314
use super::postgres::pool::PostgresConnectionManager;
15+
use super::sqlite::pool::SqliteConnectionManager;
1416
use crate::common::errors::DB_CONN_FROM_LOCAL_CACHE_ERROR;
1517
use color_eyre::Result;
1618
use swc_common::errors::Handler;
@@ -19,6 +21,7 @@ use swc_common::errors::Handler;
1921
pub enum DBConn {
2022
MySQLPooledConn(Mutex<Pool<MySqlConnectionManager>>),
2123
PostgresConn(Mutex<Pool<PostgresConnectionManager>>),
24+
SqliteConn(Mutex<Pool<SqliteConnectionManager>>),
2225
}
2326

2427
impl DBConn {
@@ -31,6 +34,7 @@ impl DBConn {
3134
let (explain_failed, ts_query) = match &self {
3235
DBConn::MySQLPooledConn(_conn) => mysql_explain::prepare(self, sql, should_generate_types, handler).await?,
3336
DBConn::PostgresConn(_conn) => postgres_explain::prepare(self, sql, should_generate_types, handler).await?,
37+
DBConn::SqliteConn(_conn) => sqlite_explain::prepare(self, sql, should_generate_types, handler).await?,
3438
};
3539

3640
Ok((explain_failed, ts_query))
@@ -41,6 +45,7 @@ impl DBConn {
4145
match self {
4246
DBConn::MySQLPooledConn(_) => DatabaseType::Mysql,
4347
DBConn::PostgresConn(_) => DatabaseType::Postgres,
48+
DBConn::SqliteConn(_) => DatabaseType::Sqlite,
4449
}
4550
}
4651
}

src/core/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ pub mod connection;
22
pub mod execute;
33
pub mod mysql;
44
pub mod postgres;
5+
pub mod sqlite;

src/core/sqlite/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
pub mod pool;
2+
pub mod prepare;

src/core/sqlite/pool.rs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
use rusqlite::Connection;
2+
use std::sync::{Arc, Mutex};
3+
use tokio::task;
4+
5+
/// A connection manager for SQLite that wraps rusqlite's synchronous Connection
6+
/// behind an Arc<Mutex<>> for thread-safe access with bb8 connection pooling.
7+
#[derive(Clone, Debug)]
8+
pub struct SqliteConnectionManager {
9+
db_path: String,
10+
connection_name: String,
11+
}
12+
13+
/// Wrapper around rusqlite::Connection to make it Send + Sync for bb8
14+
pub struct SqliteConnection {
15+
pub conn: Arc<Mutex<Connection>>,
16+
}
17+
18+
// Safety: rusqlite::Connection is not Send by default, but we protect it with Mutex
19+
// and only access it via spawn_blocking
20+
unsafe impl Send for SqliteConnection {}
21+
unsafe impl Sync for SqliteConnection {}
22+
23+
impl SqliteConnectionManager {
24+
pub fn new(db_path: String, connection_name: String) -> Self {
25+
Self {
26+
db_path,
27+
connection_name,
28+
}
29+
}
30+
}
31+
32+
#[derive(Debug)]
33+
pub struct SqlitePoolError(pub String);
34+
35+
impl std::fmt::Display for SqlitePoolError {
36+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37+
write!(f, "SQLite pool error: {}", self.0)
38+
}
39+
}
40+
41+
impl std::error::Error for SqlitePoolError {}
42+
43+
impl bb8::ManageConnection for SqliteConnectionManager {
44+
type Connection = SqliteConnection;
45+
type Error = SqlitePoolError;
46+
47+
async fn connect(&self) -> Result<Self::Connection, Self::Error> {
48+
let db_path = self.db_path.clone();
49+
let connection_name = self.connection_name.clone();
50+
51+
let conn = task::spawn_blocking(move || {
52+
Connection::open(&db_path).unwrap_or_else(|err| {
53+
panic!(
54+
"Failed to open SQLite database at '{}' for connection '{}': {}",
55+
db_path, connection_name, err
56+
)
57+
})
58+
})
59+
.await
60+
.map_err(|e| SqlitePoolError(format!("Failed to spawn blocking task: {e}")))?;
61+
62+
Ok(SqliteConnection {
63+
conn: Arc::new(Mutex::new(conn)),
64+
})
65+
}
66+
67+
async fn is_valid(&self, conn: &mut Self::Connection) -> Result<(), Self::Error> {
68+
let inner = conn.conn.clone();
69+
task::spawn_blocking(move || {
70+
let conn = inner.lock().unwrap();
71+
conn
72+
.execute_batch("SELECT 1")
73+
.map_err(|e| SqlitePoolError(format!("SQLite connection validation failed: {e}")))
74+
})
75+
.await
76+
.map_err(|e| SqlitePoolError(format!("Failed to spawn blocking task: {e}")))?
77+
}
78+
79+
fn has_broken(&self, _conn: &mut Self::Connection) -> bool {
80+
false
81+
}
82+
}

0 commit comments

Comments
 (0)