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 futures::Stream;
use std::{future::Future, pin::Pin}; 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] #[async_trait::async_trait]
pub trait ConnectionTrait<'a>: Sync { pub trait ConnectionTrait<'a>: Sync {
/// Create a stream for the [QueryResult]
type Stream: Stream<Item = Result<QueryResult, DbErr>>; 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; fn get_database_backend(&self) -> DbBackend;
/// Execute a [Statement]
async fn execute(&self, stmt: Statement) -> Result<ExecResult, DbErr>; 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>; 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>; async fn query_all(&self, stmt: Statement) -> Result<Vec<QueryResult>, DbErr>;
/// Execute a [Statement] and return a stream of results
fn stream( fn stream(
&'a self, &'a self,
stmt: Statement, stmt: Statement,
) -> Pin<Box<dyn Future<Output = Result<Self::Stream, DbErr>> + 'a>>; ) -> 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>; async fn begin(&self) -> Result<DatabaseTransaction, DbErr>;
/// Execute the function inside a transaction. /// Execute the function inside a transaction.
@ -34,6 +45,7 @@ pub trait ConnectionTrait<'a>: Sync {
T: Send, T: Send,
E: std::error::Error + Send; E: std::error::Error + Send;
/// Check if the connection is a test connection for the Mock database
fn is_mock_connection(&self) -> bool { fn is_mock_connection(&self) -> bool {
false false
} }

View File

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

View File

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

View File

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

View File

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

View File

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