]> git.ipfire.org Git - pbs.git/commitdiff
repos: Create CTEs to easily fetch all packages and builds
authorMichael Tremer <michael.tremer@ipfire.org>
Wed, 22 Jan 2025 16:59:22 +0000 (16:59 +0000)
committerMichael Tremer <michael.tremer@ipfire.org>
Wed, 22 Jan 2025 17:00:00 +0000 (17:00 +0000)
Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
src/buildservice/repos.py

index 8e1b7c627c1fee007a3e07cddf6134eefef280d7..219b1ea96219a4eb48b9a57e103b9efe37bb9299 100644 (file)
@@ -4,6 +4,7 @@ import asyncio
 import configparser
 import contextlib
 import datetime
+import functools
 import io
 import logging
 import os.path
@@ -544,82 +545,125 @@ class Repo(database.Base, database.BackendMixin, database.SoftDeleteMixin):
 
                return await self.db.fetch_one(stmt)
 
-       async def get_total_builds(self):
+       @functools.cached_property
+       def builds(self):
                """
-                       Return the total number of builds in this repository
+                       Returns a CTE with all builds in this repository
                """
-               stmt = (
+               return (
                        sqlalchemy
                        .select(
-                               sqlalchemy.func.count().label("total_builds"),
+                               builds.Build,
+                       )
+                       .select_from(
+                               RepoBuild,
                        )
-                       .select_from(RepoBuild)
                        .join(
                                builds.Build,
                                builds.Build.id == RepoBuild.build_id,
                        )
                        .where(
+                               # Builds should not be deleted
                                builds.Build.deleted_at == None,
 
+                               # Builds must be in this repository
                                RepoBuild.repo == self,
                                RepoBuild.removed_at == None,
                        )
+                       .cte("_builds")
+               )
+
+       @functools.cached_property
+       def packages(self):
+               """
+                       Returns a CTE with all packages in this repository
+               """
+               # Source packages
+               source_packages = (
+                       sqlalchemy
+                       .select(
+                               packages.Package,
+                       )
+                       .select_from(
+                               self.builds,
+                       )
+                       .join(
+                               packages.Package,
+                               packages.Package.id == self.builds.c.pkg_id,
+                       )
+                       .where(
+                               packages.Package.deleted_at == None,
+                       )
+               )
+
+               # Binary Packages
+               binary_packages = (
+                       sqlalchemy
+                       .select(
+                               packages.Package,
+                       )
+                       .select_from(
+                               self.builds,
+                       )
+                       .join(
+                               jobs.Job,
+                               jobs.Job.build_id == self.builds.c.id,
+                       )
+                       .join(
+                               jobs.JobPackage,
+                               jobs.JobPackage.c.job_id == jobs.Job.id,
+                       )
+                       .join(
+                               packages.Package,
+                               packages.Package.id == jobs.JobPackage.c.pkg_id,
+                       )
+                       .where(
+                               packages.Package.deleted_at == None,
+                               jobs.Job.deleted_at == None,
+                       )
+               )
+
+               return sqlalchemy.union_all(source_packages, binary_packages).cte("_packages")
+
+       async def get_total_builds(self):
+               """
+                       Return the total number of builds in this repository
+               """
+               stmt = (
+                       sqlalchemy
+                       .select(
+                               sqlalchemy.func.count().label("total_builds"),
+                       )
+                       .select_from(self.builds)
                )
 
                return await self.db.select_one(stmt, "total_builds")
 
-       async def get_packages(self, arch):
-               if arch == "src":
-                       packages = await self.backend.packages._get_packages("""
-                               SELECT
-                                       packages.*
-                               FROM
-                                       repository_builds
-                               LEFT JOIN
-                                       builds ON repository_builds.build_id = builds.id
-                               LEFT JOIN
-                                       packages ON builds.pkg_id = packages.id
-                               WHERE
-                                       builds.deleted_at IS NULL
-                               AND
-                                       packages.deleted_at IS NULL
-                               AND
-                                       repository_builds.repo_id = %s
-                               AND
-                                       repository_builds.removed_at IS NULL
-                               """, self.id,
+       def get_packages(self, arch):
+               """
+                       Returns an iterator over all packages that match the given arch
+               """
+               arches = [arch]
+
+               # Include noarch packages unless we are looking for source packages only
+               if not arch == "src":
+                       arches.append("noarch")
+
+               # Alias the CTE
+               all_packages = sqlalchemy.orm.aliased(packages.Package, self.packages)
+
+               # Fetch all packages that match the architecture
+               stmt = (
+                       sqlalchemy
+                       .select(
+                               all_packages,
                        )
-               else:
-                       packages = await self.backend.packages._get_packages("""
-                               SELECT
-                                       packages.*
-                               FROM
-                                       repository_builds
-                               LEFT JOIN
-                                       builds ON repository_builds.build_id = builds.id
-                               LEFT JOIN
-                                       jobs ON builds.id = jobs.build_id
-                               LEFT JOIN
-                                       job_packages ON jobs.id = job_packages.job_id
-                               LEFT JOIN
-                                       packages ON job_packages.pkg_id = packages.id
-                               WHERE
-                                       builds.deleted_at IS NULL
-                               AND
-                                       jobs.deleted_at IS NULL
-                               AND
-                                       packages.deleted_at IS NULL
-                               AND
-                                       repository_builds.repo_id = %s
-                               AND
-                                       repository_builds.removed_at IS NULL
-                               AND
-                                       packages.arch = ANY(%s)
-                               """,
-                               self.id, [arch, "noarch"],
+                       .where(
+                               all_packages.arch.in_(arches),
                        )
+               )
 
-               return list(packages)
+               return self.db.fetch(stmt)
 
        @property
        async def pending_jobs(self):
@@ -657,113 +701,23 @@ class Repo(database.Base, database.BackendMixin, database.SoftDeleteMixin):
 
        # Stats
 
-       @lazy_property
-       async def size(self):
-               res = await self.db.query("""
-                       SELECT
-                               packages.arch AS arch,
-                               SUM(packages.filesize) AS size
-                       FROM
-                               repository_builds
-                       LEFT JOIN
-                               builds ON repository_builds.build_id = builds.id
-                       LEFT JOIN
-                               jobs ON builds.id = jobs.build_id
-                       LEFT JOIN
-                               job_packages ON jobs.id = job_packages.job_id
-                       LEFT JOIN
-                               packages ON job_packages.pkg_id = packages.id
-                       WHERE
-                               builds.deleted_at IS NULL
-                       AND
-                               jobs.deleted_at IS NULL
-                       AND
-                               packages.deleted_at IS NULL
-                       AND
-                               repository_builds.repo_id = %s
-                       AND
-                               repository_builds.removed_at IS NULL
-                       GROUP BY
-                               packages.arch
-                       """, self.id,
-               )
-
-               return { row.arch : row.size for row in res if row.arch in self.distro.arches }
-
        async def get_total_size(self):
                """
                        Returns the total size of the repository
                """
-               source_packages = (
-                       sqlalchemy
-                       .select(
-                               packages.Package.filesize.label("size")
-                       )
-                       .select_from(RepoBuild)
-                       .join(
-                               builds.Build,
-                               builds.Build.id == RepoBuild.build_id,
-                       )
-                       .join(
-                               packages.Package,
-                               packages.Package.id == builds.Build.pkg_id,
-                       )
-                       .where(
-                               packages.Package.deleted_at == None,
-                               builds.Build.deleted_at == None,
-                               RepoBuild.removed_at == None,
-                               RepoBuild.repo == self,
-                       )
-               )
-
-               binary_packages = (
-                       sqlalchemy
-                       .select(
-                               packages.Package.filesize.label("size")
-                       )
-                       .select_from(RepoBuild)
-                       .join(
-                               builds.Build,
-                               builds.Build.id == RepoBuild.build_id,
-                       )
-                       .join(
-                               jobs.Job,
-                               jobs.Job.build_id == builds.Build.id,
-                       )
-                       .join(
-                               packages.Package,
-                               packages.Package.id == builds.Build.pkg_id,
-                       )
-                       .where(
-                               packages.Package.deleted_at == None,
-                               builds.Build.deleted_at == None,
-                               RepoBuild.removed_at == None,
-                               RepoBuild.repo == self,
-                       )
-               )
-
-               all_packages = (
-                       sqlalchemy
-                       .union_all(
-                               source_packages,
-                               binary_packages,
-                       )
-                       .cte("all_packages")
-               )
-
                stmt = (
                        sqlalchemy
                        .select(
                                sqlalchemy.func.sum(
-                                       all_packages.c.size,
+                                       self.packages.c.size,
                                ).label("total_size"),
                        )
                        .select_from(
-                               all_packages,
+                               self.packages,
                        )
                )
 
-               return await self.db.select_one(stmt, "total_size")
+               return await self.db.select_one(stmt, "total_size") or 0
 
        # Pakfire
 
@@ -932,7 +886,7 @@ class Repo(database.Base, database.BackendMixin, database.SoftDeleteMixin):
 
                                        # Make filelist (if they exist)
                                        files = [
-                                               p.path for p in packages \
+                                               p.path async for p in packages \
                                                        if p.path and await self.backend.exists(p.path)
                                        ]