kellnr-db 7.2.0

Kellnr is a self-hosted registry for Rust crates with support for rustdocs and crates.io caching.
Documentation
use kellnr_common::canonical_name::CanonicalName;
use kellnr_common::version::Version;
use kellnr_entity::{crate_meta, cratesio_crate, cratesio_meta, krate};
use kellnr_migration::iden::{CrateIden, CrateMetaIden, CratesIoIden, CratesIoMetaIden};
use sea_orm::sea_query::{Cond, Expr, Order, Query};
use sea_orm::{ColumnTrait, EntityTrait, ExprTrait, FromQueryResult, QueryFilter};

use super::{Database, saturating_i64};
use crate::error::DbError;
use crate::provider::{CrateVisibility, DbResult};

impl Database {
    pub(super) async fn get_total_unique_cached_crates(&self) -> DbResult<u64> {
        self.count(
            CratesIoIden::Table,
            CratesIoIden::Id,
            DbError::FailedToCountCrates,
        )
        .await
    }

    pub(super) async fn get_total_cached_crate_versions(&self) -> DbResult<u64> {
        self.count(
            CratesIoMetaIden::Table,
            CratesIoMetaIden::Id,
            DbError::FailedToCountCrateVersions,
        )
        .await
    }

    pub(super) async fn get_total_cached_downloads(&self) -> DbResult<u64> {
        self.sum(
            CratesIoIden::Table,
            CratesIoIden::TotalDownloads,
            DbError::FailedToCountTotalDownloads,
        )
        .await
    }

    pub(super) async fn increase_download_counter(
        &self,
        crate_name: &CanonicalName,
        crate_version: &Version,
    ) -> DbResult<()> {
        self.in_transaction(async |txn| {
            krate::Entity::update_many()
                .col_expr(
                    krate::Column::TotalDownloads,
                    Expr::col(krate::Column::TotalDownloads).add(1),
                )
                .filter(krate::Column::Name.eq(crate_name))
                .exec(txn)
                .await?;

            let crate_id = krate::Entity::find()
                .filter(krate::Column::Name.eq(crate_name))
                .one(txn)
                .await?
                .ok_or_else(|| DbError::CrateNotFound(crate_name.to_string()))?
                .id;

            crate_meta::Entity::update_many()
                .col_expr(
                    crate_meta::Column::Downloads,
                    Expr::col(crate_meta::Column::Downloads).add(1),
                )
                .filter(
                    Cond::all()
                        .add(crate_meta::Column::Version.eq(crate_version))
                        .add(crate_meta::Column::CrateFk.eq(crate_id)),
                )
                .exec(txn)
                .await?;

            Ok(())
        })
        .await
    }

    pub(super) async fn increase_cached_download_counter(
        &self,
        crate_name: &CanonicalName,
        crate_version: &Version,
    ) -> DbResult<()> {
        self.in_transaction(async |txn| {
            cratesio_crate::Entity::update_many()
                .col_expr(
                    cratesio_crate::Column::TotalDownloads,
                    Expr::col(cratesio_crate::Column::TotalDownloads).add(1),
                )
                .filter(cratesio_crate::Column::Name.eq(crate_name))
                .exec(txn)
                .await?;

            let crate_id = cratesio_crate::Entity::find()
                .filter(cratesio_crate::Column::Name.eq(crate_name))
                .one(txn)
                .await?
                .ok_or_else(|| DbError::CrateNotFound(crate_name.to_string()))?
                .id;

            cratesio_meta::Entity::update_many()
                .col_expr(
                    cratesio_meta::Column::Downloads,
                    Expr::col(cratesio_meta::Column::Downloads).add(1),
                )
                .filter(
                    Cond::all()
                        .add(cratesio_meta::Column::Version.eq(crate_version))
                        .add(cratesio_meta::Column::CratesIoFk.eq(crate_id)),
                )
                .exec(txn)
                .await?;

            Ok(())
        })
        .await
    }

    pub(super) async fn increase_download_counter_by(
        &self,
        crate_name: &CanonicalName,
        crate_version: &Version,
        count: u64,
    ) -> DbResult<()> {
        self.in_transaction(async |txn| {
            krate::Entity::update_many()
                .col_expr(
                    krate::Column::TotalDownloads,
                    Expr::col(krate::Column::TotalDownloads).add(saturating_i64(count)),
                )
                .filter(krate::Column::Name.eq(crate_name))
                .exec(txn)
                .await?;

            let crate_id = krate::Entity::find()
                .filter(krate::Column::Name.eq(crate_name))
                .one(txn)
                .await?
                .ok_or_else(|| DbError::CrateNotFound(crate_name.to_string()))?
                .id;

            crate_meta::Entity::update_many()
                .col_expr(
                    crate_meta::Column::Downloads,
                    Expr::col(crate_meta::Column::Downloads).add(saturating_i64(count)),
                )
                .filter(
                    Cond::all()
                        .add(crate_meta::Column::Version.eq(crate_version))
                        .add(crate_meta::Column::CrateFk.eq(crate_id)),
                )
                .exec(txn)
                .await?;

            Ok(())
        })
        .await
    }

    pub(super) async fn increase_cached_download_counter_by(
        &self,
        crate_name: &CanonicalName,
        crate_version: &Version,
        count: u64,
    ) -> DbResult<()> {
        self.in_transaction(async |txn| {
            cratesio_crate::Entity::update_many()
                .col_expr(
                    cratesio_crate::Column::TotalDownloads,
                    Expr::col(cratesio_crate::Column::TotalDownloads).add(saturating_i64(count)),
                )
                .filter(cratesio_crate::Column::Name.eq(crate_name))
                .exec(txn)
                .await?;

            let crate_id = cratesio_crate::Entity::find()
                .filter(cratesio_crate::Column::Name.eq(crate_name))
                .one(txn)
                .await?
                .ok_or_else(|| DbError::CrateNotFound(crate_name.to_string()))?
                .id;

            cratesio_meta::Entity::update_many()
                .col_expr(
                    cratesio_meta::Column::Downloads,
                    Expr::col(cratesio_meta::Column::Downloads).add(saturating_i64(count)),
                )
                .filter(
                    Cond::all()
                        .add(cratesio_meta::Column::Version.eq(crate_version))
                        .add(cratesio_meta::Column::CratesIoFk.eq(crate_id)),
                )
                .exec(txn)
                .await?;

            Ok(())
        })
        .await
    }

    pub(super) async fn get_total_unique_crates(&self) -> DbResult<u64> {
        self.count(
            CrateIden::Table,
            CrateIden::Id,
            DbError::FailedToCountCrates,
        )
        .await
    }

    pub(super) async fn get_total_crate_versions(&self) -> DbResult<u64> {
        self.count(
            CrateMetaIden::Table,
            CrateMetaIden::Id,
            DbError::FailedToCountCrateVersions,
        )
        .await
    }

    pub(super) async fn get_total_downloads(&self) -> DbResult<u64> {
        self.sum(
            CrateIden::Table,
            CrateIden::TotalDownloads,
            DbError::FailedToCountTotalDownloads,
        )
        .await
    }

    pub(super) async fn get_top_crates_downloads(
        &self,
        top: u64,
        visibility: &CrateVisibility,
    ) -> DbResult<Vec<(String, u64)>> {
        #[derive(Debug, PartialEq, FromQueryResult)]
        struct SelectResult {
            original_name: String,
            total_downloads: i64,
        }

        let mut query = Query::select()
            .columns(vec![CrateIden::OriginalName, CrateIden::TotalDownloads])
            .from(CrateIden::Table)
            .order_by(CrateIden::TotalDownloads, Order::Desc)
            .limit(top)
            .to_owned();
        if let Some(condition) = Self::crate_visibility_condition(visibility) {
            query.cond_where(condition);
        }
        let stmt = self.db_con.get_database_backend().build(&query);

        Ok(SelectResult::find_by_statement(stmt)
            .all(&self.db_con)
            .await?
            .into_iter()
            .map(|x| {
                (
                    x.original_name,
                    u64::try_from(x.total_downloads).unwrap_or(0),
                )
            })
            .collect())
    }
}