fletch-orm 1.0.0

Lightweight database toolkit built on SQLx
Documentation
//! Bind [`Value`] parameters to SQLx queries for each supported database.

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;

/// Result of executing a built statement.
#[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);

/// Databases supported by fletch value binding and query execution.
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)
    }
}