diff --git a/aurweb/routers/packages.py b/aurweb/routers/packages.py index 23f44ee3..eab75e5a 100644 --- a/aurweb/routers/packages.py +++ b/aurweb/routers/packages.py @@ -908,8 +908,7 @@ async def pkgbase_vote(request: Request, name: str): VoteTS=now) # Update NumVotes/Popularity. - conn = db.ConnectionExecutor(db.get_engine().raw_connection()) - popupdate.run_single(conn, pkgbase) + popupdate.run_single(pkgbase) return RedirectResponse(f"/pkgbase/{name}", status_code=HTTPStatus.SEE_OTHER) @@ -929,8 +928,7 @@ async def pkgbase_unvote(request: Request, name: str): db.delete(vote) # Update NumVotes/Popularity. - conn = db.ConnectionExecutor(db.get_engine().raw_connection()) - popupdate.run_single(conn, pkgbase) + popupdate.run_single(pkgbase) return RedirectResponse(f"/pkgbase/{name}", status_code=HTTPStatus.SEE_OTHER) @@ -1473,8 +1471,7 @@ async def pkgbase_merge_post(request: Request, name: str, pkgbase_merge_instance(request, pkgbase, target) # Run popupdate on the target. - conn = db.ConnectionExecutor(db.get_engine().raw_connection()) - popupdate.run_single(conn, target) + popupdate.run_single(target) if not next: next = f"/pkgbase/{target.Name}" diff --git a/aurweb/scripts/popupdate.py b/aurweb/scripts/popupdate.py index db4ba170..e2d008f2 100755 --- a/aurweb/scripts/popupdate.py +++ b/aurweb/scripts/popupdate.py @@ -1,51 +1,71 @@ #!/usr/bin/env python3 from datetime import datetime +from typing import List -import aurweb.db +from sqlalchemy import and_, func +from sqlalchemy.sql.functions import coalesce +from sqlalchemy.sql.functions import sum as _sum + +from aurweb import db +from aurweb.models import PackageBase, PackageVote -def run_single(conn, pkgbase): +def run_variable(pkgbases: List[PackageBase] = []) -> None: + """ + Update popularity on a list of PackageBases. + + If no PackageBase is included, we update the popularity + of every PackageBase in the database. + + :param pkgbases: List of PackageBase instances + """ + now = int(datetime.utcnow().timestamp()) + + # NumVotes subquery. + votes_subq = db.get_session().query( + func.count("*") + ).select_from(PackageVote).filter( + PackageVote.PackageBaseID == PackageBase.ID + ) + + # Popularity subquery. + pop_subq = db.get_session().query( + coalesce(_sum(func.pow(0.98, (now - PackageVote.VoteTS) / 86400)), 0.0), + ).select_from(PackageVote).filter( + and_(PackageVote.PackageBaseID == PackageBase.ID, + PackageVote.VoteTS.isnot(None)) + ) + + with db.begin(): + query = db.query(PackageBase) + + ids = set() + if pkgbases: + ids = {pkgbase.ID for pkgbase in pkgbases} + query = query.filter(PackageBase.ID.in_(ids)) + + query.update({ + "NumVotes": votes_subq.scalar_subquery(), + "Popularity": pop_subq.scalar_subquery() + }) + + +def run_single(pkgbase: PackageBase) -> None: """ A single popupdate. The given pkgbase instance will be refreshed after the database update is done. NOTE: This function is compatible only with aurweb FastAPI. - :param conn: db.Connection[Executor] :param pkgbase: Instance of db.PackageBase """ - - conn.execute("UPDATE PackageBases SET NumVotes = (" - "SELECT COUNT(*) FROM PackageVotes " - "WHERE PackageVotes.PackageBaseID = PackageBases.ID) " - "WHERE PackageBases.ID = ?", [pkgbase.ID]) - - now = int(datetime.utcnow().timestamp()) - conn.execute("UPDATE PackageBases SET Popularity = (" - "SELECT COALESCE(SUM(POWER(0.98, (? - VoteTS) / 86400)), 0.0) " - "FROM PackageVotes WHERE PackageVotes.PackageBaseID = " - "PackageBases.ID AND NOT VoteTS IS NULL) WHERE " - "PackageBases.ID = ?", [now, pkgbase.ID]) - - conn.commit() - conn.close() - aurweb.db.refresh(pkgbase) + run_variable([pkgbase]) + db.refresh(pkgbase) def main(): - conn = aurweb.db.Connection() - conn.execute("UPDATE PackageBases SET NumVotes = (" - "SELECT COUNT(*) FROM PackageVotes " - "WHERE PackageVotes.PackageBaseID = PackageBases.ID)") - - now = int(datetime.utcnow().timestamp()) - conn.execute("UPDATE PackageBases SET Popularity = (" - "SELECT COALESCE(SUM(POWER(0.98, (? - VoteTS) / 86400)), 0.0) " - "FROM PackageVotes WHERE PackageVotes.PackageBaseID = " - "PackageBases.ID AND NOT VoteTS IS NULL)", [now]) - - conn.commit() - conn.close() + db.get_engine() + run_variable() if __name__ == '__main__': diff --git a/test/test_rpc.py b/test/test_rpc.py index a4cdb5da..b61a7e4e 100644 --- a/test/test_rpc.py +++ b/test/test_rpc.py @@ -9,7 +9,7 @@ import pytest from fastapi.testclient import TestClient from redis.client import Pipeline -from aurweb import asgi, config, db, scripts +from aurweb import asgi, config, scripts from aurweb.db import begin, create, query from aurweb.models.account_type import AccountType from aurweb.models.dependency_type import DependencyType @@ -187,8 +187,7 @@ def setup(db_test): PackageBase=pkgbase1, VoteTS=5000) - conn = db.ConnectionExecutor(db.get_engine().raw_connection()) - scripts.popupdate.run_single(conn, pkgbase1) + scripts.popupdate.run_single(pkgbase1) @pytest.fixture