use crate::config::SqliteConfig;
use crate::error::SqliteError;
use crate::types::{Param, Row, Rows, SqlValue};
use parking_lot::Mutex;
use rusqlite::Connection as RawConnection;
use std::sync::Arc;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConnectionState {
Open,
Closed,
}
pub struct Connection {
inner: Arc<Mutex<Option<RawConnection>>>,
config: SqliteConfig,
state: Arc<Mutex<ConnectionState>>,
}
impl Connection {
pub fn open(config: SqliteConfig) -> Result<Self, SqliteError> {
let path = config.path_string();
let conn = if config.read_only {
RawConnection::open_with_flags(
&path,
rusqlite::OpenFlags::SQLITE_OPEN_READ_ONLY | rusqlite::OpenFlags::SQLITE_OPEN_URI,
)
} else if config.create_if_missing {
RawConnection::open(&path)
} else {
RawConnection::open_with_flags(
&path,
rusqlite::OpenFlags::SQLITE_OPEN_READ_WRITE | rusqlite::OpenFlags::SQLITE_OPEN_URI,
)
}
.map_err(|e| SqliteError::OpenFailed(e.to_string()))?;
for pragma in config.to_pragmas() {
conn.execute_batch(&pragma)
.map_err(|e| SqliteError::OpenFailed(format!("PRAGMA failed: {}", e)))?;
}
Ok(Self {
inner: Arc::new(Mutex::new(Some(conn))),
config,
state: Arc::new(Mutex::new(ConnectionState::Open)),
})
}
pub fn open_in_memory() -> Result<Self, SqliteError> {
Self::open(SqliteConfig::memory())
}
pub fn state(&self) -> ConnectionState {
*self.state.lock()
}
pub fn is_open(&self) -> bool {
self.state() == ConnectionState::Open
}
pub fn config(&self) -> &SqliteConfig {
&self.config
}
pub fn execute(&self, sql: &str, params: &[Param]) -> Result<usize, SqliteError> {
let guard = self.inner.lock();
let conn = guard.as_ref().ok_or(SqliteError::DatabaseClosed)?;
let params_refs: Vec<&dyn rusqlite::ToSql> =
params.iter().map(|p| p as &dyn rusqlite::ToSql).collect();
conn.execute(sql, params_refs.as_slice())
.map_err(SqliteError::from)
}
pub fn execute_batch(&self, sql: &str) -> Result<(), SqliteError> {
let guard = self.inner.lock();
let conn = guard.as_ref().ok_or(SqliteError::DatabaseClosed)?;
conn.execute_batch(sql).map_err(SqliteError::from)
}
pub fn query_row(&self, sql: &str, params: &[Param]) -> Result<Option<Row>, SqliteError> {
let guard = self.inner.lock();
let conn = guard.as_ref().ok_or(SqliteError::DatabaseClosed)?;
let params_refs: Vec<&dyn rusqlite::ToSql> =
params.iter().map(|p| p as &dyn rusqlite::ToSql).collect();
let mut stmt = conn.prepare(sql).map_err(SqliteError::from)?;
let columns: Vec<String> = stmt.column_names().iter().map(|s: &&str| s.to_string()).collect();
let result = stmt.query_row(params_refs.as_slice(), |row: &rusqlite::Row| {
let mut r = Row::new();
for (i, col) in columns.iter().enumerate() {
let value: SqlValue = row.get(i)?;
r.push(col.to_string(), value);
}
Ok(r)
});
match result {
Ok(row) => Ok(Some(row)),
Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
Err(e) => Err(SqliteError::from(e)),
}
}
pub fn query(&self, sql: &str, params: &[Param]) -> Result<Rows, SqliteError> {
let guard = self.inner.lock();
let conn = guard.as_ref().ok_or(SqliteError::DatabaseClosed)?;
let params_refs: Vec<&dyn rusqlite::ToSql> =
params.iter().map(|p| p as &dyn rusqlite::ToSql).collect();
let mut stmt = conn.prepare(sql).map_err(SqliteError::from)?;
let columns: Vec<String> = stmt.column_names().iter().map(|s: &&str| s.to_string()).collect();
let rows = stmt
.query_map(params_refs.as_slice(), |row: &rusqlite::Row| {
let mut r = Row::new();
for (i, col) in columns.iter().enumerate() {
let value: SqlValue = row.get(i)?;
r.push(col.to_string(), value);
}
Ok(r)
})
.map_err(SqliteError::from)?;
let mut result: Vec<Row> = Vec::new();
for row in rows {
result.push(row.map_err(SqliteError::from)?);
}
Ok(result)
}
pub fn last_insert_rowid(&self) -> Result<i64, SqliteError> {
let guard = self.inner.lock();
let conn = guard.as_ref().ok_or(SqliteError::DatabaseClosed)?;
Ok(conn.last_insert_rowid())
}
pub fn changes(&self) -> Result<usize, SqliteError> {
let guard = self.inner.lock();
let conn = guard.as_ref().ok_or(SqliteError::DatabaseClosed)?;
Ok(conn.changes() as usize)
}
pub fn close(&self) -> Result<(), SqliteError> {
let mut guard = self.inner.lock();
let mut state = self.state.lock();
if let Some(conn) = guard.take() {
drop(conn);
}
*state = ConnectionState::Closed;
Ok(())
}
pub fn with_raw<F, R>(&self, f: F) -> Result<R, SqliteError>
where
F: FnOnce(&RawConnection) -> Result<R, SqliteError>,
{
let guard = self.inner.lock();
let conn = guard.as_ref().ok_or(SqliteError::DatabaseClosed)?;
f(conn)
}
}
impl Clone for Connection {
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
config: self.config.clone(),
state: self.state.clone(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_open_memory() {
let conn = Connection::open_in_memory().unwrap();
assert!(conn.is_open());
}
#[test]
fn test_execute_and_query() {
let conn = Connection::open_in_memory().unwrap();
conn.execute_batch("CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)")
.unwrap();
conn.execute("INSERT INTO test (name) VALUES (?)", &["Alice".into()])
.unwrap();
conn.execute("INSERT INTO test (name) VALUES (?)", &["Bob".into()])
.unwrap();
let id = conn.last_insert_rowid().unwrap();
assert_eq!(id, 2);
let rows = conn.query("SELECT * FROM test", &[]).unwrap();
assert_eq!(rows.len(), 2);
assert_eq!(rows[0].get_str("name"), Some("Alice"));
assert_eq!(rows[1].get_str("name"), Some("Bob"));
}
#[test]
fn test_query_row() {
let conn = Connection::open_in_memory().unwrap();
conn.execute_batch("CREATE TABLE test (id INTEGER PRIMARY KEY, value INTEGER)")
.unwrap();
conn.execute("INSERT INTO test (value) VALUES (?)", &[42i32.into()])
.unwrap();
let row = conn
.query_row("SELECT * FROM test WHERE id = ?", &[1i32.into()])
.unwrap();
assert!(row.is_some());
assert_eq!(row.unwrap().get_i64("value"), Some(42));
let row = conn
.query_row("SELECT * FROM test WHERE id = ?", &[999i32.into()])
.unwrap();
assert!(row.is_none());
}
#[test]
fn test_close() {
let conn = Connection::open_in_memory().unwrap();
assert!(conn.is_open());
conn.close().unwrap();
assert!(!conn.is_open());
let result = conn.execute("SELECT 1", &[]);
assert!(result.is_err());
}
}