sqlx-utils 1.1.3

Utilities for working with SQLx in a structured and efficient way, both when developing and running
Documentation
#![allow(dead_code)]
#![allow(clippy::extra_unused_lifetimes)]
#![allow(clippy::needless_lifetimes)]

use parking_lot::ArcMutexGuard;
use sqlx::prelude::FromRow;
use sqlx::{QueryBuilder, Transaction};
use sqlx_utils::prelude::*;
use std::sync::{Arc, LazyLock};
use std::time::Duration;

pub static DATABASE_URL: LazyLock<String> =
    LazyLock::new(|| std::env::var("DATABASE_URL").expect("failed to get DATABASE_URL"));

sql_filter! {
    pub struct UserFilter<UserRepo> {
        SELECT id, name, email as username FROM users WHERE (?id = i64 AND name LIKE String)
    }
}

#[derive(Clone, FromRow)]
pub struct User {
    id: i64,
    name: String,
}

impl Model for User {
    type Id = i64;

    fn get_id(&self) -> Option<i64> {
        Some(self.id)
    }
}

repository! {
    pub UserRepo<User>;
}

repository! {
    !zst
    pub UserRepo2<User>;
}

repository_insert! {
    UserRepo<User>;

    insert_query(model) {
        sqlx::query("INSERT INTO users (name) VALUES (?)").bind(&model.name)
    }
}

repository_update! {
    UserRepo<User>;

    update_query(model) {
        sqlx::query("UPDATE users SET name = ? where id = ?").bind(model.id).bind(&model.name)
    }
}

repository_delete! {
    UserRepo<User>;

    delete_by_id_query(id) {
        sqlx::query("DELETE FROM users WHERE id = ?").bind(id)
    }

    delete_by_filter_query(filter) {
        let mut builder = QueryBuilder::new("DELETE FROM users WHERE ");

        filter.apply_filter(&mut builder);

        builder
    }
}

pub enum UserContext {
    System,
    UnAuthenticated,
}

#[derive(Debug)]
pub enum DbError {
    SqlxError(sqlx::Error),
    SqlxUtils(sqlx_utils::Error),
    NotAllowed,
}

impl From<sqlx::Error> for DbError {
    fn from(e: sqlx::Error) -> Self {
        DbError::SqlxError(e)
    }
}

impl From<sqlx_utils::Error> for DbError {
    fn from(e: sqlx_utils::Error) -> Self {
        DbError::SqlxUtils(e)
    }
}

impl UserRepo {
    pub async fn save_with_context(
        &self,
        model: User,
        context: UserContext,
    ) -> Result<User, DbError> {
        self.with_transaction(move |mut tx| async move {
            let res = match context {
                UserContext::System => self
                    .save_with_executor(&mut *tx, model)
                    .await
                    .map_err(Into::into),
                UserContext::UnAuthenticated => Err(DbError::NotAllowed),
            };

            (res, tx)
        })
        .await
    }

    pub async fn save_with_tx<'a, 'b>(&'a self, model: User) -> Result<Vec<User>, DbError> {
        self.transaction_sequential::<'a, 'b>([
            move |mut tx: Transaction<'b, Database>| async move {
                let res = self.save_with_executor(&mut *tx, model).await;

                (res, tx)
            },
        ])
        .await
        .map_err(Into::into)
    }

    pub async fn save_with_rx_concurrent<'a, 'b>(
        &'a self,
        model: User,
    ) -> Result<Vec<User>, DbError>
    where
        'b: 'a,
    {
        self.transaction_concurrent::<'a, 'b>([
            |tx: Arc<parking_lot::Mutex<Transaction<'b, Database>>>| async move {
                let mut tx = match tx.try_lock_arc() {
                    Some(tx) => tx,
                    None => return Err(Error::MutexLockError),
                };

                let res = USER_REPO.save_with_executor(&mut **tx, model).await;

                ArcMutexGuard::<parking_lot::RawMutex, Transaction<'b, sqlx::Any>>::unlock_fair(tx);

                res
            },
        ])
        .await
        .map_err(Into::into)
    }

    pub async fn try_save_with_tx<'a, 'b>(
        &'a self,
        model: User,
    ) -> Result<Vec<User>, Vec<DbError>> {
        self.try_transaction::<'a, 'b>([move |mut tx: Transaction<'b, Database>| async move {
            let res = self.save_with_executor(&mut *tx, model).await;

            (res, tx)
        }])
        .await
        .map_err(|errors| errors.into_iter().map(Into::into).collect())
    }
}

async fn action<'b>(
    _: Transaction<'b, Database>,
) -> (Result<User, DbError>, Transaction<'b, Database>) {
    unimplemented!()
}

#[tokio::main]
async fn main() {
    install_default_drivers();

    initialize_db_pool(
        PoolOptions::new()
            .max_connections(21)
            .min_connections(5)
            .idle_timeout(Duration::from_secs(60 * 10))
            .max_lifetime(Duration::from_secs(60 * 60 * 24))
            .acquire_timeout(Duration::from_secs(20))
            .connect(&DATABASE_URL)
            .await
            .expect("Failed to connect to database"),
    );

    let user = User {
        id: 1,
        name: String::new(),
    };

    USER_REPO.save_ref(&user).await.unwrap();

    USER_REPO.save_in_transaction(user.clone()).await.unwrap();

    USER_REPO
        .save_with_context(user.clone(), UserContext::System)
        .await
        .unwrap();

    USER_REPO.with_transaction(action).await.unwrap();

    USER_REPO
        .delete_by_values_in_transaction("id", [1, 2, 3, 11, 22])
        .await
        .unwrap();

    USER_REPO
        .get_one_by_filter(UserFilter::new("name").id(1))
        .await
        .unwrap();
}