sqlite-rwc 0.4.0

Reader Writer Concurrency Setup for Sqlite3
Documentation
use crate::drivers::{Driver, DriverMutConnectionDeref};
use diesel::connection::{AnsiTransactionManager, SimpleConnection, TransactionManager};
use diesel::prelude::*;
use diesel::result;
use std::path::Path;

pub type DieselConnectionPool = crate::SyncConnectionPool<DieselDriver>;
pub type DieselPooledConnection = crate::SyncPooledConnection<DieselDriver>;
pub type DieselConnectionPoolError = crate::ConnectionPoolError<<DieselDriver as Driver>::Error>;
pub type DieselTransaction<'t> = crate::Transaction<'t, DieselDriver>;
pub type DieselReadTransaction<'t> = crate::ReadTransaction<'t, DieselDriver>;

#[cfg(feature = "async")]
pub type DieselAsyncConnectionPool = crate::AsyncConnectionPool<DieselDriver>;
#[cfg(feature = "async")]
pub type DieselAsyncPooledConnection = crate::AsyncPooledConnection<DieselDriver>;

#[cfg(feature = "async")]
pub type DieselAsyncTransaction<'t> = crate::AsyncTransaction<'t, DieselDriver>;
#[cfg(feature = "async")]
pub type DieselAsyncReadTransaction<'t> = crate::AsyncReadTransaction<'t, DieselDriver>;
#[cfg(feature = "async")]
pub type DieselAsyncConnectionError = crate::AsyncConnectionError<<DieselDriver as Driver>::Error>;
#[derive(Debug)]
pub struct DieselDriver;

impl Driver for DieselDriver {
    type Connection = SqliteConnection;
    type Error = result::Error;

    type ConnectionError = ConnectionError;

    type ConnectionRef<'c> = &'c mut Self::Connection;

    fn new_read_connection(path: &Path) -> Result<Self::Connection, Self::ConnectionError> {
        SqliteConnection::establish(&Self::file_uri_read_only(path))
    }

    fn new_write_connection(path: &Path) -> Result<Self::Connection, Self::ConnectionError> {
        let mut connection = SqliteConnection::establish(&Self::file_uri(path))?;
        Self::apply_pragmas(&mut connection)
            .map_err(Self::ConnectionError::CouldntSetupConfiguration)?;
        Ok(connection)
    }

    fn begin_transaction(connection: &mut Self::Connection, sql: &str) -> Result<(), Self::Error> {
        AnsiTransactionManager::begin_transaction_sql(connection, sql)
    }

    fn commit_transaction(connection: &mut Self::Connection) -> Result<(), Self::Error> {
        AnsiTransactionManager::commit_transaction(connection)
    }

    fn rollback_transaction(connection: &mut Self::Connection) -> Result<(), Self::Error> {
        AnsiTransactionManager::rollback_transaction(connection)
    }
}

impl DriverMutConnectionDeref for DieselDriver {}

impl DieselDriver {
    fn file_uri(path: &Path) -> String {
        format!("file://{}", path.to_string_lossy())
    }

    fn file_uri_read_only(path: &Path) -> String {
        format!("file://{}?mode=ro", path.to_string_lossy())
    }

    fn apply_pragmas(conn: &mut SqliteConnection) -> QueryResult<()> {
        conn.batch_execute("PRAGMA journal_mode = WAL;")?;
        conn.batch_execute("PRAGMA foreign_keys = ON;")?;
        conn.batch_execute("PRAGMA synchronous= FULL;")?;
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::{
        ConnectionPool, ConnectionPoolConfig, ReadTransaction, SyncConnectionPool, Transaction,
    };
    use diesel::result::Error;
    use diesel::{QueryResult, RunQueryDsl, Selectable, insert_into, sql_query};
    use sqlite_watcher::watcher::Watcher;
    use std::sync::Arc;
    use tempdir::TempDir;

    #[derive(Insertable, Queryable, Selectable)]
    #[diesel(table_name = foo)]
    #[diesel(check_for_backend(diesel::sqlite::Sqlite))]
    struct Foo {
        pub id: i32,
    }

    fn create_table(tx: &mut Transaction<'_, DieselDriver>) -> QueryResult<()> {
        sql_query("CREATE TABLE foo (id INTEGER PRIMARY KEY)").execute(&mut **tx)?;
        insert_into(foo::table)
            .values(Foo { id: 1 })
            .execute(&mut **tx)?;
        Ok(())
    }

    fn read_value(conn: &mut ReadTransaction<'_, DieselDriver>) -> QueryResult<Foo> {
        foo::dsl::foo
            .find(1)
            .select(Foo::as_select())
            .first(&mut **conn)
    }

    #[test]
    fn read_scope_query() {
        let (pool, _dir) = new_pool();
        let mut conn = pool.connection().unwrap();
        conn.transaction_closure(create_table).unwrap();

        let foo = conn.read_transaction_closure(read_value).unwrap();
        assert_eq!(foo.id, 1);
    }

    #[test]
    #[should_panic(
        expected = "called `Result::unwrap()` on an `Err` value: DatabaseError(Unknown, \"attempt to write a readonly database\")"
    )]
    fn panic_on_write_in_read_scope() {
        let (pool, _dir) = new_pool();
        let mut conn = pool.connection().unwrap();

        conn.transaction_closure(|tx| {
            sql_query("CREATE TABLE foo (id INTEGER PRIMARY KEY)")
                .execute(&mut **tx)
                .unwrap();
            Ok::<_, Error>(())
        })
        .unwrap();
        conn.read_transaction_closure(|conn| {
            insert_into(foo::table)
                .values(Foo { id: 1 })
                .execute(&mut **conn)
        })
        .unwrap();
    }

    fn new_pool() -> (Arc<SyncConnectionPool<DieselDriver>>, TempDir) {
        let dir = tempdir::TempDir::new("diesel-test").unwrap();
        let pool = ConnectionPool::new(ConnectionPoolConfig {
            max_read_connection_count: 4,
            file_path: dir.path().join("sqlite.db"),
            connection_acquire_timeout: None,
            #[cfg(feature = "watcher")]
            watcher: Watcher::new().unwrap(),
        })
        .unwrap();
        (pool, dir)
    }

    diesel::table! {
        foo (id) {
            id -> Integer,
        }
    }
}