mod fields;
#[cfg(feature = "mysql")]
pub mod impl_mysql;
#[cfg(feature = "postgres")]
pub mod impl_postgres;
#[cfg(feature = "sqlite")]
pub mod impl_sqlite;
pub mod migrations;
pub mod query;
mod relations;
mod sea_query_db;
use std::fmt::{Display, Formatter};
use std::hash::Hash;
use std::str::FromStr;
use std::sync::Arc;
use async_trait::async_trait;
use cot_core::error::impl_into_cot_error;
pub use cot_macros::model;
pub use cot_macros::query;
use derive_more::{Debug, Deref, Display};
#[cfg(test)]
use mockall::automock;
use query::Query;
pub use relations::{ForeignKey, ForeignKeyOnDeletePolicy, ForeignKeyOnUpdatePolicy};
use sea_query::{
ColumnRef, ExprTrait, Iden, IntoColumnRef, OnConflict, ReturningClause, SchemaStatementBuilder,
SimpleExpr,
};
use sea_query_sqlx::{SqlxBinder, SqlxValues};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use sqlx::{Type, TypeInfo};
use thiserror::Error;
use tracing::{Instrument, Level, span, trace};
#[cfg(feature = "mysql")]
use crate::db::impl_mysql::{DatabaseMySql, MySqlRow, MySqlValueRef};
#[cfg(feature = "postgres")]
use crate::db::impl_postgres::{DatabasePostgres, PostgresRow, PostgresValueRef};
#[cfg(feature = "sqlite")]
use crate::db::impl_sqlite::{DatabaseSqlite, SqliteRow, SqliteValueRef};
use crate::db::migrations::ColumnTypeMapper;
const ERROR_PREFIX: &str = "database error:";
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum DatabaseError {
#[error("{ERROR_PREFIX} database engine error: {0}")]
DatabaseEngineError(#[from] sqlx::Error),
#[error("{ERROR_PREFIX} error when building query: {0}")]
QueryBuildingError(#[from] sea_query::error::Error),
#[error(
"{ERROR_PREFIX} type mismatch in database value: expected `{expected}`, found `{found}`. \
Perhaps a migration is needed"
)]
TypeMismatch {
expected: String,
found: String,
},
#[error("{ERROR_PREFIX} error when decoding database value: {0}")]
ValueDecode(Box<dyn std::error::Error + 'static + Send + Sync>),
#[error("{ERROR_PREFIX} error when applying migrations: {0}")]
MigrationError(#[from] migrations::MigrationEngineError),
#[error("{ERROR_PREFIX} record with primary key `{primary_key}` not found in the database")]
RecordNotFound {
primary_key: DbValue,
},
#[error("{ERROR_PREFIX} error retrieving a Foreign Key from the database: record not found")]
ForeignKeyNotFound,
#[error("{ERROR_PREFIX} unique constraint violation")]
UniqueViolation,
#[error(
"{ERROR_PREFIX} model has {field_count} fields which exceeds the database parameter limit \
of {limit}"
)]
BulkInsertModelTooLarge {
field_count: usize,
limit: usize,
},
#[error(
"{ERROR_PREFIX} bulk_insert requires at least one non-auto field. Models with only \
auto-generated fields should be created individually using insert()"
)]
BulkInsertNoValueColumns,
#[error(
"{ERROR_PREFIX} bulk insert: expected {expected} rows, but got {actual}. This may be \
due to concurrent inserts or gaps in auto-increment IDs if using MySQL backend, or \
might be a general database misbehavior otherwise."
)]
BulkInsertReturnDataInvalid {
expected: usize,
actual: usize,
},
}
impl_into_cot_error!(DatabaseError, INTERNAL_SERVER_ERROR);
impl DatabaseError {
#[must_use]
pub fn value_decode(error: impl std::error::Error + 'static + Send + Sync) -> Self {
Self::ValueDecode(Box::new(error))
}
}
pub type Result<T> = std::result::Result<T, DatabaseError>;
#[async_trait]
#[diagnostic::on_unimplemented(
message = "`{Self}` is not marked as a database model",
label = "`{Self}` is not annotated with `#[cot::db::model]`",
note = "annotate `{Self}` with the `#[cot::db::model]` attribute"
)]
pub trait Model: Sized + Send + 'static {
#[allow(
clippy::allow_attributes,
clippy::doc_markdown,
reason = "UPPER_SNAKE_CASE"
)]
type Fields;
type PrimaryKey: PrimaryKey;
const APP_NAME: &'static str;
const TABLE_NAME: Identifier;
const PRIMARY_KEY_NAME: Identifier;
const COLUMNS: &'static [Column];
fn from_db(db_row: Row) -> Result<Self>;
fn update_from_db(&mut self, db_row: Row, columns: &[usize]) -> Result<()>;
fn primary_key(&self) -> &Self::PrimaryKey;
fn set_primary_key(&mut self, primary_key: Self::PrimaryKey);
fn get_values(&self, columns: &[usize]) -> Vec<&dyn ToDbFieldValue>;
#[must_use]
fn objects() -> Query<Self> {
Query::new()
}
async fn get_by_primary_key<DB: DatabaseBackend>(
db: &DB,
pk: Self::PrimaryKey,
) -> Result<Option<Self>>;
async fn save<DB: DatabaseBackend>(&mut self, db: &DB) -> Result<()> {
db.insert_or_update(self).await?;
Ok(())
}
async fn insert<DB: DatabaseBackend>(&mut self, db: &DB) -> Result<()> {
db.insert(self).await?;
Ok(())
}
async fn update<DB: DatabaseBackend>(&mut self, db: &DB) -> Result<()> {
db.update(self).await?;
Ok(())
}
async fn bulk_insert<DB: DatabaseBackend>(db: &DB, instances: &mut [Self]) -> Result<()> {
db.bulk_insert(instances).await?;
Ok(())
}
async fn bulk_insert_or_update<DB: DatabaseBackend>(
db: &DB,
instances: &mut [Self],
) -> Result<()> {
db.bulk_insert_or_update(instances).await?;
Ok(())
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Display, Deref)]
pub struct Identifier(&'static str);
impl Identifier {
#[must_use]
pub const fn new(s: &'static str) -> Self {
Self(s)
}
#[must_use]
pub fn as_str(&self) -> &str {
self.0
}
}
impl From<&'static str> for Identifier {
fn from(s: &'static str) -> Self {
Self::new(s)
}
}
impl Iden for Identifier {
fn unquoted(&self) -> &str {
self.as_str()
}
}
impl Iden for &Identifier {
fn unquoted(&self) -> &str {
self.as_str()
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Column {
name: Identifier,
}
impl Column {
#[must_use]
pub const fn new(name: Identifier) -> Self {
Self { name }
}
}
pub trait PrimaryKey: DatabaseField + Clone {}
#[non_exhaustive]
#[derive(Debug)]
pub enum Row {
#[cfg(feature = "sqlite")]
Sqlite(SqliteRow),
#[cfg(feature = "postgres")]
Postgres(PostgresRow),
#[cfg(feature = "mysql")]
MySql(MySqlRow),
}
impl Row {
pub fn get<T: FromDbValue>(&self, index: usize) -> Result<T> {
let result = match self {
#[cfg(feature = "sqlite")]
Row::Sqlite(sqlite_row) => sqlite_row
.get_raw(index)
.and_then(|value| T::from_sqlite(value))?,
#[cfg(feature = "postgres")]
Row::Postgres(postgres_row) => postgres_row
.get_raw(index)
.and_then(|value| T::from_postgres(value))?,
#[cfg(feature = "mysql")]
Row::MySql(mysql_row) => mysql_row
.get_raw(index)
.and_then(|value| T::from_mysql(value))?,
};
Ok(result)
}
}
pub trait DatabaseField: FromDbValue + ToDbFieldValue {
const NULLABLE: bool = false;
const TYPE: ColumnType;
}
pub trait FromDbValue {
#[cfg(feature = "sqlite")]
fn from_sqlite(value: SqliteValueRef<'_>) -> Result<Self>
where
Self: Sized;
#[cfg(feature = "postgres")]
fn from_postgres(value: PostgresValueRef<'_>) -> Result<Self>
where
Self: Sized;
#[cfg(feature = "mysql")]
fn from_mysql(value: MySqlValueRef<'_>) -> Result<Self>
where
Self: Sized;
}
pub type DbValue = sea_query::Value;
pub trait ToDbValue: Send + Sync {
fn to_db_value(&self) -> DbValue;
}
pub trait ToDbFieldValue {
fn to_db_field_value(&self) -> DbFieldValue;
}
#[derive(Debug, Clone, PartialEq)]
pub enum DbFieldValue {
Auto,
Value(DbValue),
}
impl DbFieldValue {
#[must_use]
pub fn is_auto(&self) -> bool {
matches!(self, Self::Auto)
}
#[must_use]
pub fn is_value(&self) -> bool {
matches!(self, Self::Value(_))
}
#[must_use]
#[track_caller]
pub fn unwrap_value(self) -> DbValue {
self.expect_value("called DbValue::unwrap_value() on a wrong DbValue variant")
}
#[must_use]
#[track_caller]
pub fn expect_value(self, message: &str) -> DbValue {
match self {
Self::Value(value) => value,
Self::Auto => panic!("{message}"),
}
}
}
impl<T: ToDbValue> ToDbFieldValue for T {
fn to_db_field_value(&self) -> DbFieldValue {
DbFieldValue::Value(self.to_db_value())
}
}
impl<T: Into<DbValue>> From<T> for DbFieldValue {
fn from(value: T) -> Self {
Self::Value(value.into())
}
}
impl<T: ToDbValue + ?Sized> ToDbValue for &T {
fn to_db_value(&self) -> DbValue {
(*self).to_db_value()
}
}
trait SqlxRowRef {
type ValueRef<'r>: SqlxValueRef<'r>
where
Self: 'r;
fn get_raw(&self, index: usize) -> Result<Self::ValueRef<'_>>;
}
pub trait SqlxValueRef<'r>: Sized {
type DB: sqlx::Database;
fn get_raw(self) -> <Self::DB as sqlx::Database>::ValueRef<'r>;
fn get<T: sqlx::decode::Decode<'r, Self::DB> + Type<Self::DB>>(self) -> Result<T> {
use sqlx::ValueRef;
let value = self.get_raw();
if !value.is_null() {
let ty = value.type_info();
if !ty.is_null() && !T::compatible(&ty) {
return Err(DatabaseError::TypeMismatch {
expected: T::type_info().to_string(),
found: ty.to_string(),
});
}
}
T::decode(value).map_err(DatabaseError::ValueDecode)
}
}
#[derive(Debug, Clone)]
pub struct Database {
inner: Arc<DatabaseImpl>,
}
#[derive(Debug)]
enum DatabaseImpl {
#[cfg(feature = "sqlite")]
Sqlite(DatabaseSqlite),
#[cfg(feature = "postgres")]
Postgres(DatabasePostgres),
#[cfg(feature = "mysql")]
MySql(DatabaseMySql),
}
impl Database {
pub async fn new<T: Into<String>>(url: T) -> Result<Self> {
let url = url.into();
#[cfg(feature = "sqlite")]
if url.starts_with("sqlite:") {
let inner = DatabaseSqlite::new(&url).await?;
return Ok(Self {
inner: Arc::new(DatabaseImpl::Sqlite(inner)),
});
}
#[cfg(feature = "postgres")]
if url.starts_with("postgresql:") {
let inner = DatabasePostgres::new(&url).await?;
return Ok(Self {
inner: Arc::new(DatabaseImpl::Postgres(inner)),
});
}
#[cfg(feature = "mysql")]
if url.starts_with("mysql:") {
let inner = DatabaseMySql::new(&url).await?;
return Ok(Self {
inner: Arc::new(DatabaseImpl::MySql(inner)),
});
}
panic!("Unsupported database URL: {url}");
}
pub async fn close(&self) -> Result<()> {
match &*self.inner {
#[cfg(feature = "sqlite")]
DatabaseImpl::Sqlite(inner) => inner.close().await,
#[cfg(feature = "postgres")]
DatabaseImpl::Postgres(inner) => inner.close().await,
#[cfg(feature = "mysql")]
DatabaseImpl::MySql(inner) => inner.close().await,
}
}
pub async fn insert<T: Model>(&self, data: &mut T) -> Result<()> {
let span = span!(Level::TRACE, "insert", table = %T::TABLE_NAME);
Self::insert_or_update_impl(self, data, false)
.instrument(span)
.await
}
pub async fn insert_or_update<T: Model>(&self, data: &mut T) -> Result<()> {
let span = span!(
Level::TRACE,
"insert_or_update",
table = %T::TABLE_NAME
);
Self::insert_or_update_impl(self, data, true)
.instrument(span)
.await
}
async fn insert_or_update_impl<T: Model>(&self, data: &mut T, update: bool) -> Result<()> {
let column_identifiers = T::COLUMNS
.iter()
.map(|column| Identifier::from(column.name.as_str()));
let value_indices: Vec<_> = T::COLUMNS
.iter()
.enumerate()
.map(|(i, _column)| i)
.collect();
let values = data
.get_values(&value_indices)
.into_iter()
.map(ToDbFieldValue::to_db_field_value);
let mut auto_col_ids = Vec::new();
let mut auto_col_identifiers = Vec::new();
let mut value_identifiers = Vec::new();
let mut filtered_values = Vec::new();
std::iter::zip(std::iter::zip(value_indices, column_identifiers), values).for_each(
|((index, identifier), value)| match value {
DbFieldValue::Auto => {
auto_col_ids.push(index);
auto_col_identifiers.push(identifier.into_column_ref());
}
DbFieldValue::Value(value) => {
value_identifiers.push(identifier);
filtered_values.push(value);
}
},
);
let mut insert_statement = sea_query::Query::insert()
.into_table(T::TABLE_NAME)
.columns(value_identifiers.iter().copied())
.values(
filtered_values
.into_iter()
.map(SimpleExpr::Value)
.collect::<Vec<_>>(),
)?
.or_default_values()
.to_owned();
if update && !value_identifiers.is_empty() {
insert_statement.on_conflict(
OnConflict::column(T::PRIMARY_KEY_NAME)
.update_columns(value_identifiers)
.to_owned(),
);
}
if auto_col_ids.is_empty() {
self.execute_statement(&insert_statement).await?;
} else {
let row = if self.supports_returning() {
insert_statement.returning(ReturningClause::Columns(auto_col_identifiers));
self.fetch_option(&insert_statement)
.await?
.expect("query should return the primary key")
} else {
let result = self.execute_statement(&insert_statement).await?;
let row_id = result
.last_inserted_row_id
.expect("expected last inserted row ID if RETURNING clause is not supported");
let query = sea_query::Query::select()
.from(T::TABLE_NAME)
.columns(auto_col_identifiers)
.and_where(sea_query::Expr::col(T::PRIMARY_KEY_NAME).eq(row_id))
.to_owned();
self.fetch_option(&query).await?.expect(
"expected a row returned from a SELECT if RETURNING clause is not supported",
)
};
data.update_from_db(row, &auto_col_ids)?;
}
if update {
trace!(primary_key = ?data.primary_key().to_db_field_value(), "Inserted or updated row");
} else {
trace!(primary_key = ?data.primary_key().to_db_field_value(), "Inserted row");
}
Ok(())
}
pub async fn update<T: Model>(&self, data: &mut T) -> Result<()> {
let span = span!(
Level::TRACE,
"update",
table = %T::TABLE_NAME,
primary_key = ?data.primary_key().to_db_field_value(),
);
Self::update_impl(self, data).instrument(span).await
}
async fn update_impl<T: Model>(&self, data: &mut T) -> Result<()> {
let column_identifiers = T::COLUMNS
.iter()
.map(|column| Identifier::from(column.name.as_str()));
let value_indices: Vec<_> = T::COLUMNS
.iter()
.enumerate()
.map(|(i, _column)| i)
.collect();
let values = data
.get_values(&value_indices)
.into_iter()
.map(ToDbFieldValue::to_db_field_value);
let mut statement_values = Vec::new();
std::iter::zip(column_identifiers, values).for_each(|(identifier, value)| match value {
DbFieldValue::Auto => {
panic!("Auto values are not supported in update queries");
}
DbFieldValue::Value(value) => {
statement_values.push((identifier, SimpleExpr::Value(value)));
}
});
let primary_key = data
.primary_key()
.to_db_field_value()
.expect_value("primary key cannot be auto when updating");
let update_statement = sea_query::Query::update()
.table(T::TABLE_NAME)
.values(statement_values)
.and_where(sea_query::Expr::col(T::PRIMARY_KEY_NAME).eq(primary_key.clone()))
.to_owned();
let result = self.execute_statement(&update_statement).await?;
if result.rows_affected == RowsNum(0) {
return Err(DatabaseError::RecordNotFound { primary_key });
}
trace!("Updated row");
Ok(())
}
pub async fn bulk_insert<T: Model>(&self, data: &mut [T]) -> Result<()> {
let span = span!(Level::TRACE, "bulk_insert", table = %T::TABLE_NAME, count = data.len());
Self::bulk_insert_impl(self, data, false)
.instrument(span)
.await
}
pub async fn bulk_insert_or_update<T: Model>(&self, data: &mut [T]) -> Result<()> {
let span = span!(
Level::TRACE,
"bulk_insert_or_update",
table = %T::TABLE_NAME,
count = data.len()
);
Self::bulk_insert_impl(self, data, true)
.instrument(span)
.await
}
async fn bulk_insert_impl<T: Model>(&self, data: &mut [T], update: bool) -> Result<()> {
if data.is_empty() {
return Ok(());
}
let max_params = match &*self.inner {
#[cfg(feature = "sqlite")]
DatabaseImpl::Sqlite(_) => 32766,
#[cfg(feature = "postgres")]
DatabaseImpl::Postgres(_) => 65535,
#[cfg(feature = "mysql")]
DatabaseImpl::MySql(_) => 65535,
};
let column_identifiers: Vec<_> = T::COLUMNS
.iter()
.map(|column| Identifier::from(column.name.as_str()))
.collect();
let value_indices: Vec<_> = (0..T::COLUMNS.len()).collect();
let first_values = data[0]
.get_values(&value_indices)
.into_iter()
.map(ToDbFieldValue::to_db_field_value)
.collect::<Vec<_>>();
let mut auto_col_ids = Vec::new();
let mut auto_col_identifiers = Vec::new();
let mut value_column_indices = Vec::new();
let mut value_identifiers = Vec::new();
for (index, (identifier, value)) in
std::iter::zip(column_identifiers.iter(), first_values.iter()).enumerate()
{
match value {
DbFieldValue::Auto => {
auto_col_ids.push(index);
auto_col_identifiers.push((*identifier).into_column_ref());
}
DbFieldValue::Value(_) => {
value_column_indices.push(index);
value_identifiers.push(*identifier);
}
}
}
let num_value_fields = value_identifiers.len();
if num_value_fields > max_params {
return Err(DatabaseError::BulkInsertModelTooLarge {
field_count: num_value_fields,
limit: max_params,
});
}
let batch_size = max_params
.checked_div(num_value_fields)
.ok_or(DatabaseError::BulkInsertNoValueColumns)?;
for chunk in data.chunks_mut(batch_size) {
self.bulk_insert_chunk(
chunk,
update,
&value_identifiers,
&value_column_indices,
&auto_col_ids,
&auto_col_identifiers,
)
.await?;
}
Ok(())
}
async fn bulk_insert_chunk<T: Model>(
&self,
chunk: &mut [T],
update: bool,
value_identifiers: &[Identifier],
value_column_indices: &[usize],
auto_col_ids: &[usize],
auto_col_identifiers: &[ColumnRef],
) -> Result<()> {
let mut insert_statement = sea_query::Query::insert()
.into_table(T::TABLE_NAME)
.columns(value_identifiers.iter().copied())
.to_owned();
for instance in chunk.iter() {
let values = instance.get_values(value_column_indices);
let db_values: Vec<_> = values
.into_iter()
.map(|v| match v.to_db_field_value() {
DbFieldValue::Value(val) => val,
DbFieldValue::Auto => {
unreachable!(
"Expected `Value` for a value column, but found `Auto` when running \
a bulk insert"
)
}
})
.map(SimpleExpr::Value)
.collect();
debug_assert!(!db_values.is_empty(), "expected at least 1 value field");
insert_statement.values(db_values)?;
}
if update {
let update_cols: Vec<_> = value_identifiers
.iter()
.filter(|id| **id != T::PRIMARY_KEY_NAME)
.copied()
.collect();
insert_statement.on_conflict(
OnConflict::column(T::PRIMARY_KEY_NAME)
.update_columns(update_cols)
.to_owned(),
);
}
if auto_col_ids.is_empty() {
self.execute_statement(&insert_statement).await?;
} else if self.supports_returning() {
insert_statement.returning(ReturningClause::Columns(auto_col_identifiers.to_vec()));
let rows = self.fetch_all(&insert_statement).await?;
if rows.len() != chunk.len() {
return Err(DatabaseError::BulkInsertReturnDataInvalid {
expected: chunk.len(),
actual: rows.len(),
});
}
for (instance, row) in chunk.iter_mut().zip(rows) {
instance.update_from_db(row, auto_col_ids)?;
}
} else {
let result = self.execute_statement(&insert_statement).await?;
let first_id = result.last_inserted_row_id.ok_or_else(|| {
DatabaseError::BulkInsertReturnDataInvalid {
expected: chunk.len(),
actual: 0,
}
})?;
let query = sea_query::Query::select()
.from(T::TABLE_NAME)
.columns(auto_col_identifiers.iter().cloned())
.and_where(
sea_query::Expr::col(T::PRIMARY_KEY_NAME).gte(first_id).and(
sea_query::Expr::col(T::PRIMARY_KEY_NAME).lt(first_id
+ <u64 as TryFrom<usize>>::try_from(chunk.len())
.expect("chunk length fits in u64")),
),
)
.order_by(T::PRIMARY_KEY_NAME, sea_query::Order::Asc)
.to_owned();
let rows = self.fetch_all(&query).await?;
if rows.len() != chunk.len() {
return Err(DatabaseError::BulkInsertReturnDataInvalid {
expected: chunk.len(),
actual: rows.len(),
});
}
for (instance, row) in chunk.iter_mut().zip(rows) {
instance.update_from_db(row, auto_col_ids)?;
}
}
if update {
trace!(count = chunk.len(), "Inserted or updated rows");
} else {
trace!(count = chunk.len(), "Inserted rows");
}
Ok(())
}
pub async fn query<T: Model>(&self, query: &Query<T>) -> Result<Vec<T>> {
let columns_to_get: Vec<_> = T::COLUMNS.iter().map(|column| column.name).collect();
let mut select = sea_query::Query::select();
select.columns(columns_to_get).from(T::TABLE_NAME);
query.add_filter_to_statement(&mut select);
query.add_limit_to_statement(&mut select);
query.add_offset_to_statement(&mut select);
let rows = self.fetch_all(&select).await?;
let result = rows.into_iter().map(T::from_db).collect::<Result<_>>()?;
Ok(result)
}
pub async fn get<T: Model>(&self, query: &Query<T>) -> Result<Option<T>> {
let columns_to_get: Vec<_> = T::COLUMNS.iter().map(|column| column.name).collect();
let mut select = sea_query::Query::select();
select.columns(columns_to_get).from(T::TABLE_NAME);
query.add_filter_to_statement(&mut select);
select.limit(1);
let row = self.fetch_option(&select).await?;
let result = match row {
Some(row) => Some(T::from_db(row)?),
None => None,
};
Ok(result)
}
pub async fn exists<T: Model>(&self, query: &Query<T>) -> Result<bool> {
let mut select = sea_query::Query::select();
select.expr(sea_query::Expr::value(1)).from(T::TABLE_NAME);
query.add_filter_to_statement(&mut select);
select.limit(1);
let rows = self.fetch_option(&select).await?;
Ok(rows.is_some())
}
pub async fn delete<T: Model>(&self, query: &Query<T>) -> Result<StatementResult> {
let mut delete = sea_query::Query::delete();
delete.from_table(T::TABLE_NAME);
query.add_filter_to_statement(&mut delete);
self.execute_statement(&delete).await
}
pub async fn raw(&self, query: &str) -> Result<StatementResult> {
self.raw_with(query, &[]).await
}
pub async fn raw_with(
&self,
query: &str,
values: &[&dyn ToDbValue],
) -> Result<StatementResult> {
let values = values
.iter()
.map(ToDbValue::to_db_value)
.collect::<Vec<_>>();
let values = SqlxValues(sea_query::Values(values));
let result = match &*self.inner {
#[cfg(feature = "sqlite")]
DatabaseImpl::Sqlite(inner) => inner.raw_with(query, values).await?,
#[cfg(feature = "postgres")]
DatabaseImpl::Postgres(inner) => inner.raw_with(query, values).await?,
#[cfg(feature = "mysql")]
DatabaseImpl::MySql(inner) => inner.raw_with(query, values).await?,
};
Ok(result)
}
async fn fetch_option<T>(&self, statement: &T) -> Result<Option<Row>>
where
T: SqlxBinder + Send + Sync,
{
let result = match &*self.inner {
#[cfg(feature = "sqlite")]
DatabaseImpl::Sqlite(inner) => inner.fetch_option(statement).await?.map(Row::Sqlite),
#[cfg(feature = "postgres")]
DatabaseImpl::Postgres(inner) => {
inner.fetch_option(statement).await?.map(Row::Postgres)
}
#[cfg(feature = "mysql")]
DatabaseImpl::MySql(inner) => inner.fetch_option(statement).await?.map(Row::MySql),
};
Ok(result)
}
fn supports_returning(&self) -> bool {
match &*self.inner {
#[cfg(feature = "sqlite")]
DatabaseImpl::Sqlite(_) => true,
#[cfg(feature = "postgres")]
DatabaseImpl::Postgres(_) => true,
#[cfg(feature = "mysql")]
DatabaseImpl::MySql(_) => false,
}
}
async fn fetch_all<T>(&self, statement: &T) -> Result<Vec<Row>>
where
T: SqlxBinder + Send + Sync,
{
let result = match &*self.inner {
#[cfg(feature = "sqlite")]
DatabaseImpl::Sqlite(inner) => inner
.fetch_all(statement)
.await?
.into_iter()
.map(Row::Sqlite)
.collect(),
#[cfg(feature = "postgres")]
DatabaseImpl::Postgres(inner) => inner
.fetch_all(statement)
.await?
.into_iter()
.map(Row::Postgres)
.collect(),
#[cfg(feature = "mysql")]
DatabaseImpl::MySql(inner) => inner
.fetch_all(statement)
.await?
.into_iter()
.map(Row::MySql)
.collect(),
};
Ok(result)
}
async fn execute_statement<T>(&self, statement: &T) -> Result<StatementResult>
where
T: SqlxBinder + Send + Sync,
{
let result = match &*self.inner {
#[cfg(feature = "sqlite")]
DatabaseImpl::Sqlite(inner) => inner.execute_statement(statement).await?,
#[cfg(feature = "postgres")]
DatabaseImpl::Postgres(inner) => inner.execute_statement(statement).await?,
#[cfg(feature = "mysql")]
DatabaseImpl::MySql(inner) => inner.execute_statement(statement).await?,
};
Ok(result)
}
async fn execute_schema<T: SchemaStatementBuilder>(
&self,
statement: T,
) -> Result<StatementResult> {
let result = match &*self.inner {
#[cfg(feature = "sqlite")]
DatabaseImpl::Sqlite(inner) => inner.execute_schema(statement).await?,
#[cfg(feature = "postgres")]
DatabaseImpl::Postgres(inner) => inner.execute_schema(statement).await?,
#[cfg(feature = "mysql")]
DatabaseImpl::MySql(inner) => inner.execute_schema(statement).await?,
};
Ok(result)
}
}
impl ColumnTypeMapper for Database {
fn sea_query_column_type_for(&self, column_type: ColumnType) -> sea_query::ColumnType {
match &*self.inner {
#[cfg(feature = "sqlite")]
DatabaseImpl::Sqlite(inner) => inner.sea_query_column_type_for(column_type),
#[cfg(feature = "postgres")]
DatabaseImpl::Postgres(inner) => inner.sea_query_column_type_for(column_type),
#[cfg(feature = "mysql")]
DatabaseImpl::MySql(inner) => inner.sea_query_column_type_for(column_type),
}
}
}
#[cfg_attr(test, automock)]
#[async_trait]
pub trait DatabaseBackend: Send + Sync {
async fn insert_or_update<T: Model>(&self, data: &mut T) -> Result<()>;
async fn insert<T: Model>(&self, data: &mut T) -> Result<()>;
async fn update<T: Model>(&self, data: &mut T) -> Result<()>;
async fn bulk_insert<T: Model>(&self, data: &mut [T]) -> Result<()>;
async fn bulk_insert_or_update<T: Model>(&self, data: &mut [T]) -> Result<()>;
async fn query<T: Model>(&self, query: &Query<T>) -> Result<Vec<T>>;
async fn get<T: Model>(&self, query: &Query<T>) -> Result<Option<T>>;
async fn exists<T: Model>(&self, query: &Query<T>) -> Result<bool>;
async fn delete<T: Model>(&self, query: &Query<T>) -> Result<StatementResult>;
}
#[async_trait]
impl DatabaseBackend for Database {
async fn insert_or_update<T: Model>(&self, data: &mut T) -> Result<()> {
Database::insert_or_update(self, data).await
}
async fn insert<T: Model>(&self, data: &mut T) -> Result<()> {
Database::insert(self, data).await
}
async fn update<T: Model>(&self, data: &mut T) -> Result<()> {
Database::update(self, data).await
}
async fn bulk_insert<T: Model>(&self, data: &mut [T]) -> Result<()> {
Database::bulk_insert(self, data).await
}
async fn bulk_insert_or_update<T: Model>(&self, data: &mut [T]) -> Result<()> {
Database::bulk_insert_or_update(self, data).await
}
async fn query<T: Model>(&self, query: &Query<T>) -> Result<Vec<T>> {
Database::query(self, query).await
}
async fn get<T: Model>(&self, query: &Query<T>) -> Result<Option<T>> {
Database::get(self, query).await
}
async fn exists<T: Model>(&self, query: &Query<T>) -> Result<bool> {
Database::exists(self, query).await
}
async fn delete<T: Model>(&self, query: &Query<T>) -> Result<StatementResult> {
Database::delete(self, query).await
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct StatementResult {
rows_affected: RowsNum,
last_inserted_row_id: Option<u64>,
}
impl StatementResult {
#[must_use]
#[cfg(test)]
fn new(rows_affected: RowsNum) -> Self {
Self {
rows_affected,
last_inserted_row_id: None,
}
}
#[must_use]
pub fn rows_affected(&self) -> RowsNum {
self.rows_affected
}
#[must_use]
pub fn last_inserted_row_id(&self) -> Option<u64> {
self.last_inserted_row_id
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Deref, Display)]
pub struct RowsNum(pub u64);
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Default, Ord, PartialOrd)]
pub enum Auto<T> {
Fixed(T),
#[default]
Auto,
}
impl<T> Auto<T> {
#[must_use]
#[expect(clippy::self_named_constructors)]
pub const fn auto() -> Self {
Self::Auto
}
#[must_use]
pub const fn fixed(value: T) -> Self {
Self::Fixed(value)
}
#[must_use]
#[track_caller]
pub fn unwrap(self) -> T {
self.expect("called `Auto::unwrap()` on a `Auto::Auto` value")
}
#[must_use]
pub fn expect(self, message: &str) -> T {
match self {
Self::Fixed(value) => value,
Self::Auto => panic!("{}", message),
}
}
}
impl<T> From<T> for Auto<T> {
fn from(value: T) -> Self {
Self::fixed(value)
}
}
impl<T: FromStr> FromStr for Auto<T> {
type Err = T::Err;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
T::from_str(s).map(Self::fixed)
}
}
impl<T: Display> Display for Auto<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::Fixed(value) => Display::fmt(value, f),
Self::Auto => panic!("Auto values cannot be displayed"),
}
}
}
impl<T: Serialize> Serialize for Auto<T> {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
Self::Fixed(value) => value.serialize(serializer),
Self::Auto => Err(serde::ser::Error::custom(
"Auto::Auto values cannot be serialized",
)),
}
}
}
impl<'de, T: Deserialize<'de>> Deserialize<'de> for Auto<T> {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
T::deserialize(deserializer).map(Self::Fixed)
}
}
#[derive(
Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Deref, Display, Serialize, Deserialize,
)]
pub struct LimitedString<const LIMIT: u32>(String);
impl<const LIMIT: u32> PartialEq<&str> for LimitedString<LIMIT> {
fn eq(&self, other: &&str) -> bool {
self.0 == *other
}
}
impl<const LIMIT: u32> PartialEq<String> for LimitedString<LIMIT> {
fn eq(&self, other: &String) -> bool {
self.0 == *other
}
}
impl<const LIMIT: u32> PartialEq<LimitedString<LIMIT>> for &str {
fn eq(&self, other: &LimitedString<LIMIT>) -> bool {
*self == other.0
}
}
impl<const LIMIT: u32> PartialEq<LimitedString<LIMIT>> for String {
fn eq(&self, other: &LimitedString<LIMIT>) -> bool {
*self == other.0
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Error)]
#[error("string is too long ({length} > {LIMIT})")]
pub struct NewLimitedStringError<const LIMIT: u32> {
pub(crate) length: usize,
}
impl<const LIMIT: u32> LimitedString<LIMIT> {
pub fn new(
value: impl Into<String>,
) -> std::result::Result<Self, NewLimitedStringError<LIMIT>> {
let value = value.into();
let length = value.len();
if length > LIMIT as usize {
return Err(NewLimitedStringError { length });
}
Ok(Self(value))
}
}
#[cfg(feature = "fake")]
impl<const LIMIT: u32> fake::Dummy<usize> for LimitedString<LIMIT> {
fn dummy_with_rng<R: fake::rand::Rng + ?Sized>(len: &usize, rng: &mut R) -> Self {
use fake::rand::distr::SampleString;
assert!(
*len <= LIMIT as usize,
concat!(
"len must be less than or equal to LIMIT (",
stringify!(LIMIT),
")"
)
);
let str: String = fake::rand::distr::Alphanumeric.sample_string(rng, *len);
Self::new(str).unwrap()
}
}
#[cfg(feature = "fake")]
impl<const LIMIT: u32> fake::Dummy<fake::Faker> for LimitedString<LIMIT> {
fn dummy_with_rng<R: fake::rand::Rng + ?Sized>(_: &fake::Faker, rng: &mut R) -> Self {
use fake::Fake;
let len: usize = (0..LIMIT as usize).fake_with_rng(rng);
len.fake_with_rng(rng)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[non_exhaustive]
pub enum ColumnType {
Boolean,
TinyInteger,
SmallInteger,
Integer,
BigInteger,
TinyUnsignedInteger,
SmallUnsignedInteger,
UnsignedInteger,
BigUnsignedInteger,
Float,
Double,
Time,
Date,
DateTime,
DateTimeWithTimeZone,
Text,
Blob,
String(u32),
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn identifier() {
let id = Identifier::new("test");
assert_eq!(id.as_str(), "test");
}
#[test]
fn column() {
let column = Column::new(Identifier::new("test"));
assert_eq!(column.name.as_str(), "test");
}
#[test]
fn limited_string_new_within_limit() {
let limited_string = LimitedString::<10>::new("short");
assert!(limited_string.is_ok());
assert_eq!(limited_string.unwrap(), "short");
}
#[test]
fn limited_string_new_exceeds_limit() {
let limited_string = LimitedString::<5>::new("too long");
assert!(limited_string.is_err());
let error = limited_string.unwrap_err();
assert_eq!(error.to_string(), "string is too long (8 > 5)");
}
#[test]
fn limited_string_new_exact_limit() {
let limited_string = LimitedString::<5>::new("exact");
assert!(limited_string.is_ok());
assert_eq!(limited_string.unwrap(), "exact");
}
#[test]
fn limited_string_eq() {
assert_eq!(LimitedString::<5>::new("test").unwrap(), "test");
assert_eq!("test", LimitedString::<5>::new("test").unwrap());
assert_eq!(
LimitedString::<5>::new("test").unwrap(),
String::from("test"),
);
assert_eq!(
String::from("test"),
LimitedString::<5>::new("test").unwrap(),
);
}
#[test]
fn db_field_value_is_auto() {
let auto_value = DbFieldValue::Auto;
assert!(auto_value.is_auto());
assert!(!auto_value.is_value());
}
#[test]
fn db_field_value_is_value() {
let value = DbFieldValue::Value(42.into());
assert!(value.is_value());
assert!(!value.is_auto());
}
#[test]
fn db_field_value_unwrap() {
let value = DbFieldValue::Value(42.into());
assert_eq!(value.unwrap_value(), 42.into());
}
#[test]
#[should_panic(expected = "called DbValue::unwrap_value() on a wrong DbValue variant")]
fn db_field_value_unwrap_panic() {
let auto_value = DbFieldValue::Auto;
let _ = auto_value.unwrap_value();
}
#[test]
fn db_field_value_expect() {
let value = DbFieldValue::Value(42.into());
assert_eq!(value.expect_value("expected a value"), 42.into());
}
#[test]
#[should_panic(expected = "expected a value")]
fn db_field_value_expect_panic() {
let auto_value = DbFieldValue::Auto;
let _ = auto_value.expect_value("expected a value");
}
#[test]
fn auto_serialize_fixed() {
let auto = Auto::fixed(42i32);
let serialized = serde_json::to_string(&auto).unwrap();
assert_eq!(serialized, "42");
}
#[test]
fn auto_serialize_auto() {
let auto = Auto::<i32>::Auto;
let value = serde_json::to_string(&auto);
assert!(value.is_err());
}
#[test]
fn auto_deserialize_fixed() {
let deserialized: Auto<i32> = serde_json::from_str("42").unwrap();
assert_eq!(deserialized, Auto::fixed(42));
}
#[test]
fn auto_deserialize_auto() {
let deserialized: std::result::Result<Auto<i32>, serde_json::Error> =
serde_json::from_str("null");
assert!(deserialized.is_err());
}
}