Documetation for the database modeule

This commit is contained in:
Charles Chege 2021-10-29 10:37:10 +03:00
parent 06aa9e3175
commit 91b9e542af
6 changed files with 79 additions and 1 deletions

View File

@ -4,23 +4,34 @@ use crate::{
use futures::Stream;
use std::{future::Future, pin::Pin};
/// Creates constraints for any structure that wants to create a database connection
/// and execute SQL statements
#[async_trait::async_trait]
pub trait ConnectionTrait<'a>: Sync {
/// Create a stream for the [QueryResult]
type Stream: Stream<Item = Result<QueryResult, DbErr>>;
/// Fetch the database backend as specified in [DbBackend].
/// This depends on feature flags enabled.
fn get_database_backend(&self) -> DbBackend;
/// Execute a [Statement]
async fn execute(&self, stmt: Statement) -> Result<ExecResult, DbErr>;
/// Execute a [Statement] and return a query
async fn query_one(&self, stmt: Statement) -> Result<Option<QueryResult>, DbErr>;
/// Execute a [Statement] and return a collection Vec<[QueryResult]> on success
async fn query_all(&self, stmt: Statement) -> Result<Vec<QueryResult>, DbErr>;
/// Execute a [Statement] and return a stream of results
fn stream(
&'a self,
stmt: Statement,
) -> Pin<Box<dyn Future<Output = Result<Self::Stream, DbErr>> + 'a>>;
/// Execute SQL `BEGIN` transaction.
/// Returns a Transaction that can be committed or rolled back
async fn begin(&self) -> Result<DatabaseTransaction, DbErr>;
/// Execute the function inside a transaction.
@ -34,6 +45,7 @@ pub trait ConnectionTrait<'a>: Sync {
T: Send,
E: std::error::Error + Send;
/// Check if the connection is a test connection for the Mock database
fn is_mock_connection(&self) -> bool {
false
}

View File

@ -12,30 +12,43 @@ use sqlx::pool::PoolConnection;
#[cfg(feature = "mock")]
use std::sync::Arc;
/// Handle a database connection depending on the backend
/// enabled by the feature flags. This creates a database pool.
#[cfg_attr(not(feature = "mock"), derive(Clone))]
pub enum DatabaseConnection {
/// Create a MYSQL database connection and pool
#[cfg(feature = "sqlx-mysql")]
SqlxMySqlPoolConnection(crate::SqlxMySqlPoolConnection),
/// Create a PostgreSQL database connection and pool
#[cfg(feature = "sqlx-postgres")]
SqlxPostgresPoolConnection(crate::SqlxPostgresPoolConnection),
/// Create a SQLite database connection and pool
#[cfg(feature = "sqlx-sqlite")]
SqlxSqlitePoolConnection(crate::SqlxSqlitePoolConnection),
/// Create a Mock database connection useful for testing
#[cfg(feature = "mock")]
MockDatabaseConnection(Arc<crate::MockDatabaseConnection>),
/// The connection to the database has been severed
Disconnected,
}
/// The same as a [DatabaseConnection]
pub type DbConn = DatabaseConnection;
/// The type of database backend for real world databases.
/// This is enabled by feature flags as specified in the crate documentation
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum DatabaseBackend {
/// A MySQL backend
MySql,
/// A PostgreSQL backend
Postgres,
/// A SQLite backend
Sqlite,
}
/// The same as [DatabaseBackend] just shorter :)
pub type DbBackend = DatabaseBackend;
pub(crate) enum InnerConnection {
#[cfg(feature = "sqlx-mysql")]
MySql(PoolConnection<sqlx::MySql>),
@ -209,6 +222,7 @@ impl<'a> ConnectionTrait<'a> for DatabaseConnection {
#[cfg(feature = "mock")]
impl DatabaseConnection {
/// Generate a database connection for testing the Mock database
pub fn as_mock_connection(&self) -> &crate::MockDatabaseConnection {
match self {
DatabaseConnection::MockDatabaseConnection(mock_conn) => mock_conn,
@ -216,6 +230,7 @@ impl DatabaseConnection {
}
}
/// Get the transaction log as a collection Vec<[crate::Transaction]>
pub fn into_transaction_log(self) -> Vec<crate::Transaction> {
let mut mocker = self.as_mock_connection().get_mocker_mutex().lock().unwrap();
mocker.drain_transaction_log()
@ -223,6 +238,8 @@ impl DatabaseConnection {
}
impl DbBackend {
/// Check if the URI is the same as the specified database backend.
/// Returns true if they match.
pub fn is_prefix_of(self, base_url: &str) -> bool {
let base_url_parsed = Url::parse(base_url).unwrap();
match self {
@ -234,6 +251,7 @@ impl DbBackend {
}
}
/// Build an SQL [Statement]
pub fn build<S>(&self, statement: &S) -> Statement
where
S: StatementBuilder,
@ -241,6 +259,7 @@ impl DbBackend {
statement.build(self)
}
/// A helper for building SQL queries
pub fn get_query_builder(&self) -> Box<dyn QueryBuilder> {
match self {
Self::MySql => Box::new(MysqlQueryBuilder),

View File

@ -6,6 +6,7 @@ use crate::{
use sea_query::{Value, ValueType, Values};
use std::{collections::BTreeMap, sync::Arc};
/// Defines a Mock database suitable for testing
#[derive(Debug)]
pub struct MockDatabase {
db_backend: DbBackend,
@ -15,33 +16,44 @@ pub struct MockDatabase {
query_results: Vec<Vec<MockRow>>,
}
/// Defines the results obtained from a [MockDatabase]
#[derive(Clone, Debug, Default)]
pub struct MockExecResult {
/// The last inserted id on auto-increment
pub last_insert_id: u64,
/// The number of rows affected by the database operation
pub rows_affected: u64,
}
/// Defines the structure of a test Row for the [MockDatabase]
/// which is just a [BTreeMap]<[String], [Value]>
#[derive(Clone, Debug)]
pub struct MockRow {
values: BTreeMap<String, Value>,
}
/// A trait to get a [MockRow] from a type useful for testing in the [MockDatabase]
pub trait IntoMockRow {
/// The method to perform this operation
fn into_mock_row(self) -> MockRow;
}
/// Defines a transaction that is has not been committed
#[derive(Debug)]
pub struct OpenTransaction {
stmts: Vec<Statement>,
transaction_depth: usize,
}
/// Defines a database transaction as it holds a Vec<[Statement]>
#[derive(Debug, Clone, PartialEq)]
pub struct Transaction {
stmts: Vec<Statement>,
}
impl MockDatabase {
/// Instantiate a mock database with a [DbBackend] to simulate real
/// world SQL databases
pub fn new(db_backend: DbBackend) -> Self {
Self {
db_backend,
@ -52,15 +64,18 @@ impl MockDatabase {
}
}
/// Create a database connection
pub fn into_connection(self) -> DatabaseConnection {
DatabaseConnection::MockDatabaseConnection(Arc::new(MockDatabaseConnection::new(self)))
}
/// Add the [MockExecResult]s to the `exec_results` field for `Self`
pub fn append_exec_results(mut self, mut vec: Vec<MockExecResult>) -> Self {
self.exec_results.append(&mut vec);
self
}
/// Add the [MockExecResult]s to the `exec_results` field for `Self`
pub fn append_query_results<T>(mut self, vec: Vec<Vec<T>>) -> Self
where
T: IntoMockRow,
@ -150,6 +165,7 @@ impl MockDatabaseTrait for MockDatabase {
}
impl MockRow {
/// Try to get the values of a [MockRow] and fail gracefully on error
pub fn try_get<T>(&self, col: &str) -> Result<T, DbErr>
where
T: ValueType,
@ -157,6 +173,7 @@ impl MockRow {
T::try_from(self.values.get(col).unwrap().clone()).map_err(|e| DbErr::Query(e.to_string()))
}
/// An iterator over the keys and values of a mock row
pub fn into_column_value_tuples(self) -> impl Iterator<Item = (String, Value)> {
self.values.into_iter()
}
@ -190,6 +207,7 @@ impl IntoMockRow for BTreeMap<&str, Value> {
}
impl Transaction {
/// Get the [Value]s from s raw SQL statement depending on the [DatabaseBackend](crate::DatabaseBackend)
pub fn from_sql_and_values<I>(db_backend: DbBackend, sql: &str, values: I) -> Self
where
I: IntoIterator<Item = Value>,

View File

@ -18,20 +18,30 @@ pub use transaction::*;
use crate::DbErr;
/// Defines a database
#[derive(Debug, Default)]
pub struct Database;
/// Defines the configuration options of a database
#[derive(Debug)]
pub struct ConnectOptions {
/// The URI of the database
pub(crate) url: String,
/// Maximum number of connections for a pool
pub(crate) max_connections: Option<u32>,
/// Minimum number of connections for a pool
pub(crate) min_connections: Option<u32>,
/// The connection timeout for a packet connection
pub(crate) connect_timeout: Option<Duration>,
/// Maximum idle time for a particular connection to prevent
/// network resource exhaustion
pub(crate) idle_timeout: Option<Duration>,
/// Enables or disables logging
pub(crate) sqlx_logging: bool,
}
impl Database {
/// Method to create a [DatabaseConnection] on a database
pub async fn connect<C>(opt: C) -> Result<DatabaseConnection, DbErr>
where
C: Into<ConnectOptions>,
@ -80,6 +90,7 @@ impl From<String> for ConnectOptions {
}
impl ConnectOptions {
/// Create new [ConnectOptions] for a [Database] by passing in a URI string
pub fn new(url: String) -> Self {
Self {
url,

View File

@ -3,18 +3,25 @@ use sea_query::{inject_parameters, MysqlQueryBuilder, PostgresQueryBuilder, Sqli
pub use sea_query::{Value, Values};
use std::fmt;
/// Defines an SQL statement
#[derive(Debug, Clone, PartialEq)]
pub struct Statement {
/// The SQL query
pub sql: String,
/// The values for the SQL statement
pub values: Option<Values>,
/// The database backend to use
pub db_backend: DbBackend,
}
/// Constraints for building a [Statement]
pub trait StatementBuilder {
/// Method to call in order to build a [Statement]
fn build(&self, db_backend: &DbBackend) -> Statement;
}
impl Statement {
/// Create a [Statement] from a [crate::DatabaseBackend] and a raw SQL statement
pub fn from_string(db_backend: DbBackend, stmt: String) -> Statement {
Statement {
sql: stmt,
@ -23,6 +30,8 @@ impl Statement {
}
}
/// Create a SQL statement from a [crate::DatabaseBackend], a
/// raw SQL statement and defined values
pub fn from_sql_and_values<I>(db_backend: DbBackend, sql: &str, values: I) -> Self
where
I: IntoIterator<Item = Value>,

View File

@ -10,6 +10,8 @@ use sqlx::{pool::PoolConnection, TransactionManager};
use std::{future::Future, pin::Pin, sync::Arc};
// a Transaction is just a sugar for a connection where START TRANSACTION has been executed
/// Defines a database transaction, whether it is an open transaction and the type of
/// backend to use
pub struct DatabaseTransaction {
conn: Arc<Mutex<InnerConnection>>,
backend: DbBackend,
@ -100,6 +102,8 @@ impl DatabaseTransaction {
Ok(res)
}
/// Runs a transaction to completion returning an rolling back the transaction on
/// encountering an error if it fails
pub(crate) async fn run<F, T, E>(self, callback: F) -> Result<T, TransactionError<E>>
where
F: for<'b> FnOnce(
@ -120,6 +124,7 @@ impl DatabaseTransaction {
res
}
/// Commit a transaction atomically
pub async fn commit(mut self) -> Result<(), DbErr> {
self.open = false;
match *self.conn.lock().await {
@ -149,6 +154,7 @@ impl DatabaseTransaction {
Ok(())
}
/// rolls back a transaction in case error are encountered during the operation
pub async fn rollback(mut self) -> Result<(), DbErr> {
self.open = false;
match *self.conn.lock().await {
@ -343,12 +349,15 @@ impl<'a> ConnectionTrait<'a> for DatabaseTransaction {
}
}
/// Defines errors for handling transaction failures
#[derive(Debug)]
pub enum TransactionError<E>
where
E: std::error::Error,
{
/// A Database connection error
Connection(DbErr),
/// An error occurring when doing database transactions
Transaction(E),
}