use sqlx::{Database, Executor, FromRow};
use crate::built_query::BuiltQuery;
use crate::error::FletchError;
#[cfg(any(feature = "sqlite", feature = "postgres", feature = "mysql"))]
use crate::value::Value;
#[derive(Debug, Clone, Copy)]
pub struct ExecuteOutcome {
pub rows_affected: u64,
pub last_insert_id: Option<u64>,
}
#[cfg(any(feature = "sqlite", feature = "postgres"))]
macro_rules! impl_bind {
($db:ty, $mod_name:ident) => {
pub(crate) mod $mod_name {
use super::*;
pub(super) fn bind_values_as<'q, T>(
query: sqlx::query::QueryAs<'q, $db, T, <$db as Database>::Arguments<'q>>,
values: &'q [Value],
) -> sqlx::query::QueryAs<'q, $db, T, <$db as Database>::Arguments<'q>> {
values.iter().fold(query, |q, v| bind_one_as(q, v))
}
pub(super) fn bind_values_query<'q>(
query: sqlx::query::Query<'q, $db, <$db as Database>::Arguments<'q>>,
values: &'q [Value],
) -> sqlx::query::Query<'q, $db, <$db as Database>::Arguments<'q>> {
values.iter().fold(query, |q, v| bind_one_query(q, v))
}
fn bind_one_as<'q, T>(
query: sqlx::query::QueryAs<'q, $db, T, <$db as Database>::Arguments<'q>>,
value: &'q Value,
) -> sqlx::query::QueryAs<'q, $db, T, <$db as Database>::Arguments<'q>> {
match value {
Value::Null => query.bind(None::<String>),
Value::Bool(v) => query.bind(*v),
Value::I32(v) => query.bind(*v),
Value::I64(v) => query.bind(*v),
Value::F32(v) => query.bind(*v),
Value::F64(v) => query.bind(*v),
Value::Text(v) => query.bind(v.as_str()),
Value::Bytes(v) => query.bind(v.as_slice()),
#[cfg(feature = "uuid")]
Value::Uuid(v) => query.bind(*v),
#[cfg(feature = "json")]
Value::Json(v) => query.bind(v),
#[cfg(feature = "chrono")]
Value::Timestamp(v) => query.bind(*v),
}
}
fn bind_one_query<'q>(
query: sqlx::query::Query<'q, $db, <$db as Database>::Arguments<'q>>,
value: &'q Value,
) -> sqlx::query::Query<'q, $db, <$db as Database>::Arguments<'q>> {
match value {
Value::Null => query.bind(None::<String>),
Value::Bool(v) => query.bind(*v),
Value::I32(v) => query.bind(*v),
Value::I64(v) => query.bind(*v),
Value::F32(v) => query.bind(*v),
Value::F64(v) => query.bind(*v),
Value::Text(v) => query.bind(v.as_str()),
Value::Bytes(v) => query.bind(v.as_slice()),
#[cfg(feature = "uuid")]
Value::Uuid(v) => query.bind(*v),
#[cfg(feature = "json")]
Value::Json(v) => query.bind(v),
#[cfg(feature = "chrono")]
Value::Timestamp(v) => query.bind(*v),
}
}
pub async fn fetch_all_rows<'e, T, Ex>(
query: BuiltQuery,
executor: Ex,
) -> Result<Vec<T>, FletchError>
where
T: for<'r> FromRow<'r, <$db as Database>::Row> + Send + Unpin,
Ex: Executor<'e, Database = $db> + Send,
{
let mut q = sqlx::query_as::<$db, T>(&query.sql);
q = bind_values_as(q, &query.values);
q.fetch_all(executor).await.map_err(FletchError::from)
}
pub async fn fetch_one_row<'e, T, Ex>(
query: BuiltQuery,
executor: Ex,
) -> Result<T, FletchError>
where
T: for<'r> FromRow<'r, <$db as Database>::Row> + Send + Unpin,
Ex: Executor<'e, Database = $db> + Send,
{
let mut q = sqlx::query_as::<$db, T>(&query.sql);
q = bind_values_as(q, &query.values);
q.fetch_one(executor).await.map_err(FletchError::from)
}
pub async fn execute_query<'e, Ex>(
query: BuiltQuery,
executor: Ex,
) -> Result<ExecuteOutcome, FletchError>
where
Ex: Executor<'e, Database = $db> + Send,
{
let mut q = sqlx::query::<$db>(&query.sql);
q = bind_values_query(q, &query.values);
let result = q.execute(executor).await.map_err(FletchError::from)?;
Ok(ExecuteOutcome {
rows_affected: result.rows_affected(),
last_insert_id: None,
})
}
}
};
}
#[cfg(feature = "mysql")]
macro_rules! impl_bind_mysql {
($db:ty, $mod_name:ident) => {
pub(crate) mod $mod_name {
use super::*;
pub(super) fn bind_values_as<'q, T>(
query: sqlx::query::QueryAs<'q, $db, T, <$db as Database>::Arguments<'q>>,
values: &'q [Value],
) -> sqlx::query::QueryAs<'q, $db, T, <$db as Database>::Arguments<'q>> {
values.iter().fold(query, |q, v| bind_one_as(q, v))
}
pub(super) fn bind_values_query<'q>(
query: sqlx::query::Query<'q, $db, <$db as Database>::Arguments<'q>>,
values: &'q [Value],
) -> sqlx::query::Query<'q, $db, <$db as Database>::Arguments<'q>> {
values.iter().fold(query, |q, v| bind_one_query(q, v))
}
fn bind_one_as<'q, T>(
query: sqlx::query::QueryAs<'q, $db, T, <$db as Database>::Arguments<'q>>,
value: &'q Value,
) -> sqlx::query::QueryAs<'q, $db, T, <$db as Database>::Arguments<'q>> {
match value {
Value::Null => query.bind(None::<String>),
Value::Bool(v) => query.bind(*v),
Value::I32(v) => query.bind(*v),
Value::I64(v) => query.bind(*v),
Value::F32(v) => query.bind(*v),
Value::F64(v) => query.bind(*v),
Value::Text(v) => query.bind(v.as_str()),
Value::Bytes(v) => query.bind(v.as_slice()),
#[cfg(feature = "uuid")]
Value::Uuid(v) => query.bind(*v),
#[cfg(feature = "json")]
Value::Json(v) => query.bind(v),
#[cfg(feature = "chrono")]
Value::Timestamp(v) => query.bind(*v),
}
}
fn bind_one_query<'q>(
query: sqlx::query::Query<'q, $db, <$db as Database>::Arguments<'q>>,
value: &'q Value,
) -> sqlx::query::Query<'q, $db, <$db as Database>::Arguments<'q>> {
match value {
Value::Null => query.bind(None::<String>),
Value::Bool(v) => query.bind(*v),
Value::I32(v) => query.bind(*v),
Value::I64(v) => query.bind(*v),
Value::F32(v) => query.bind(*v),
Value::F64(v) => query.bind(*v),
Value::Text(v) => query.bind(v.as_str()),
Value::Bytes(v) => query.bind(v.as_slice()),
#[cfg(feature = "uuid")]
Value::Uuid(v) => query.bind(*v),
#[cfg(feature = "json")]
Value::Json(v) => query.bind(v),
#[cfg(feature = "chrono")]
Value::Timestamp(v) => query.bind(*v),
}
}
pub async fn fetch_all_rows<'e, T, Ex>(
query: BuiltQuery,
executor: Ex,
) -> Result<Vec<T>, FletchError>
where
T: for<'r> FromRow<'r, <$db as Database>::Row> + Send + Unpin,
Ex: Executor<'e, Database = $db> + Send,
{
let mut q = sqlx::query_as::<$db, T>(&query.sql);
q = bind_values_as(q, &query.values);
q.fetch_all(executor).await.map_err(FletchError::from)
}
pub async fn fetch_one_row<'e, T, Ex>(
query: BuiltQuery,
executor: Ex,
) -> Result<T, FletchError>
where
T: for<'r> FromRow<'r, <$db as Database>::Row> + Send + Unpin,
Ex: Executor<'e, Database = $db> + Send,
{
let mut q = sqlx::query_as::<$db, T>(&query.sql);
q = bind_values_as(q, &query.values);
q.fetch_one(executor).await.map_err(FletchError::from)
}
pub async fn execute_query<'e, Ex>(
query: BuiltQuery,
executor: Ex,
) -> Result<ExecuteOutcome, FletchError>
where
Ex: Executor<'e, Database = $db> + Send,
{
let mut q = sqlx::query::<$db>(&query.sql);
q = bind_values_query(q, &query.values);
let result = q.execute(executor).await.map_err(FletchError::from)?;
Ok(ExecuteOutcome {
rows_affected: result.rows_affected(),
last_insert_id: Some(result.last_insert_id()),
})
}
}
};
}
#[cfg(feature = "sqlite")]
impl_bind!(sqlx::Sqlite, sqlite_bind);
#[cfg(feature = "postgres")]
impl_bind!(sqlx::Postgres, postgres_bind);
#[cfg(feature = "mysql")]
impl_bind_mysql!(sqlx::MySql, mysql_bind);
pub trait DatabaseBindable: Database {
fn fetch_all_rows<'e, T, Ex>(
query: BuiltQuery,
executor: Ex,
) -> impl std::future::Future<Output = Result<Vec<T>, FletchError>> + Send
where
T: for<'r> FromRow<'r, <Self as Database>::Row> + Send + Unpin,
Ex: Executor<'e, Database = Self> + Send;
fn fetch_one_row<'e, T, Ex>(
query: BuiltQuery,
executor: Ex,
) -> impl std::future::Future<Output = Result<T, FletchError>> + Send
where
T: for<'r> FromRow<'r, <Self as Database>::Row> + Send + Unpin,
Ex: Executor<'e, Database = Self> + Send;
fn execute_query<'e, Ex>(
query: BuiltQuery,
executor: Ex,
) -> impl std::future::Future<Output = Result<ExecuteOutcome, FletchError>> + Send
where
Ex: Executor<'e, Database = Self> + Send;
}
#[cfg(feature = "sqlite")]
impl DatabaseBindable for sqlx::Sqlite {
fn fetch_all_rows<'e, T, Ex>(
query: BuiltQuery,
executor: Ex,
) -> impl std::future::Future<Output = Result<Vec<T>, FletchError>> + Send
where
T: for<'r> FromRow<'r, <Self as Database>::Row> + Send + Unpin,
Ex: Executor<'e, Database = Self> + Send,
{
sqlite_bind::fetch_all_rows::<T, Ex>(query, executor)
}
fn fetch_one_row<'e, T, Ex>(
query: BuiltQuery,
executor: Ex,
) -> impl std::future::Future<Output = Result<T, FletchError>> + Send
where
T: for<'r> FromRow<'r, <Self as Database>::Row> + Send + Unpin,
Ex: Executor<'e, Database = Self> + Send,
{
sqlite_bind::fetch_one_row::<T, Ex>(query, executor)
}
fn execute_query<'e, Ex>(
query: BuiltQuery,
executor: Ex,
) -> impl std::future::Future<Output = Result<ExecuteOutcome, FletchError>> + Send
where
Ex: Executor<'e, Database = Self> + Send,
{
sqlite_bind::execute_query::<Ex>(query, executor)
}
}
#[cfg(feature = "postgres")]
impl DatabaseBindable for sqlx::Postgres {
fn fetch_all_rows<'e, T, Ex>(
query: BuiltQuery,
executor: Ex,
) -> impl std::future::Future<Output = Result<Vec<T>, FletchError>> + Send
where
T: for<'r> FromRow<'r, <Self as Database>::Row> + Send + Unpin,
Ex: Executor<'e, Database = Self> + Send,
{
postgres_bind::fetch_all_rows::<T, Ex>(query, executor)
}
fn fetch_one_row<'e, T, Ex>(
query: BuiltQuery,
executor: Ex,
) -> impl std::future::Future<Output = Result<T, FletchError>> + Send
where
T: for<'r> FromRow<'r, <Self as Database>::Row> + Send + Unpin,
Ex: Executor<'e, Database = Self> + Send,
{
postgres_bind::fetch_one_row::<T, Ex>(query, executor)
}
fn execute_query<'e, Ex>(
query: BuiltQuery,
executor: Ex,
) -> impl std::future::Future<Output = Result<ExecuteOutcome, FletchError>> + Send
where
Ex: Executor<'e, Database = Self> + Send,
{
postgres_bind::execute_query::<Ex>(query, executor)
}
}
#[cfg(feature = "mysql")]
impl DatabaseBindable for sqlx::MySql {
fn fetch_all_rows<'e, T, Ex>(
query: BuiltQuery,
executor: Ex,
) -> impl std::future::Future<Output = Result<Vec<T>, FletchError>> + Send
where
T: for<'r> FromRow<'r, <Self as Database>::Row> + Send + Unpin,
Ex: Executor<'e, Database = Self> + Send,
{
mysql_bind::fetch_all_rows::<T, Ex>(query, executor)
}
fn fetch_one_row<'e, T, Ex>(
query: BuiltQuery,
executor: Ex,
) -> impl std::future::Future<Output = Result<T, FletchError>> + Send
where
T: for<'r> FromRow<'r, <Self as Database>::Row> + Send + Unpin,
Ex: Executor<'e, Database = Self> + Send,
{
mysql_bind::fetch_one_row::<T, Ex>(query, executor)
}
fn execute_query<'e, Ex>(
query: BuiltQuery,
executor: Ex,
) -> impl std::future::Future<Output = Result<ExecuteOutcome, FletchError>> + Send
where
Ex: Executor<'e, Database = Self> + Send,
{
mysql_bind::execute_query::<Ex>(query, executor)
}
}