Documetation for the database modeule
This commit is contained in:
parent
06aa9e3175
commit
91b9e542af
@ -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
|
||||
}
|
||||
|
@ -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),
|
||||
|
@ -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>,
|
||||
|
@ -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,
|
||||
|
@ -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>,
|
||||
|
@ -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),
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user