-- Calculate the severity of a vulnerability based on its CVSSv2 base score.
CREATE OR REPLACE FUNCTION "CVSSV2_TO_SEVERITY"(
    "base_score" NUMERIC
) RETURNS VARCHAR
    LANGUAGE "plpgsql"
AS
$$
BEGIN
    RETURN CASE
               WHEN "base_score" >= 7 THEN 'HIGH'
               WHEN "base_score" >= 4 THEN 'MEDIUM'
               WHEN "base_score" > 0 THEN 'LOW'
               ELSE 'UNASSIGNED'
        END;
END;
$$;

-- Calculate the severity of a vulnerability based on its CVSSv3 base score.
CREATE OR REPLACE FUNCTION "CVSSV3_TO_SEVERITY"(
    "base_score" NUMERIC
) RETURNS VARCHAR
    LANGUAGE "plpgsql"
AS
$$
BEGIN
    RETURN CASE
               WHEN "base_score" >= 9 THEN 'CRITICAL'
               WHEN "base_score" >= 7 THEN 'HIGH'
               WHEN "base_score" >= 4 THEN 'MEDIUM'
               WHEN "base_score" > 0 THEN 'LOW'
               ELSE 'UNASSIGNED'
        END;
END;
$$;

-- Calculate the severity of a vulnerability based on:
--   * a pre-set severity
--   * a CVSSv3 base score
--   * a CVSSv2 base score
-- The behavior of this function is identical to Vulnerability#getSeverity
-- in the API server Java code base.
-- https://github.com/DependencyTrack/dependency-track/blob/1976be1f5cc9d027900f09aed9d1539595aeda3a/src/main/java/org/dependencytrack/model/Vulnerability.java#L338-L340
CREATE OR REPLACE FUNCTION "CALC_SEVERITY"(
    "severity" VARCHAR,
    "cvssv3_base_score" NUMERIC,
    "cvssv2_base_score" NUMERIC
) RETURNS VARCHAR
    LANGUAGE "plpgsql"
AS
$$
BEGIN
    IF "cvssv3_base_score" IS NOT NULL THEN
        RETURN "CVSSV3_TO_SEVERITY"("cvssv3_base_score");
    ELSEIF "cvssv2_base_score" IS NOT NULL THEN
        RETURN "CVSSV2_TO_SEVERITY"("cvssv2_base_score");
    ELSEIF "severity" IS NOT NULL THEN
        RETURN "severity";
    ELSE
        RETURN 'UNASSIGNED';
    END IF;
END;
$$;

-- Calculate the inherited risk score of a component, based on the number
-- of vulnerabilities per severity.
-- The behavior of this function is identical to Metrics#inheritedRiskScore
-- in the API server Java code base.
-- https://github.com/DependencyTrack/dependency-track/blob/1976be1f5cc9d027900f09aed9d1539595aeda3a/src/main/java/org/dependencytrack/metrics/Metrics.java#L31-L33
CREATE OR REPLACE FUNCTION "CALC_RISK_SCORE"(
    "critical" INT,
    "high" INT,
    "medium" INT,
    "low" INT,
    "unassigned" INT
) RETURNS NUMERIC
    LANGUAGE "plpgsql"
AS
$$
BEGIN
    RETURN ("critical" * 10) + ("high" * 5) + ("medium" * 3) + ("low" * 1) + ("unassigned" * 5);
END;
$$;

CREATE OR REPLACE PROCEDURE "UPDATE_COMPONENT_METRICS"(
    "component_uuid" VARCHAR
)
    LANGUAGE "plpgsql"
AS
$$
DECLARE
    "v_component"                               RECORD; -- The component to update metrics for
    "v_vulnerability"                           RECORD; -- Loop variable for iterating over vulnerabilities the component is affected by
    "v_alias"                                   RECORD; -- Loop variable for iterating over aliases of a vulnerability
    "v_aliases_seen"                            TEXT[]; -- Array of aliases encountered while iterating over vulnerabilities
    "v_severity"                                VARCHAR; -- Loop variable for the current vulnerability's severity
    "v_policy_violation"                        RECORD; -- Loop variable for iterating over policy violations assigned to the component
    "v_vulnerabilities"                         INT     := 0; -- Total number of vulnerabilities
    "v_critical"                                INT     := 0; -- Number of vulnerabilities with critical severity
    "v_high"                                    INT     := 0; -- Number of vulnerabilities with high severity
    "v_medium"                                  INT     := 0; -- Number of vulnerabilities with medium severity
    "v_low"                                     INT     := 0; -- Number of vulnerabilities with low severity
    "v_unassigned"                              INT     := 0; -- Number of vulnerabilities with unassigned severity
    "v_risk_score"                              NUMERIC := 0; -- Inherited risk score
    "v_findings_total"                          INT     := 0; -- Total number of findings
    "v_findings_audited"                        INT     := 0; -- Number of audited findings
    "v_findings_unaudited"                      INT     := 0; -- Number of unaudited findings
    "v_findings_suppressed"                     INT     := 0; -- Number of suppressed findings
    "v_policy_violations_total"                 INT     := 0; -- Total number of policy violations
    "v_policy_violations_fail"                  INT     := 0; -- Number of policy violations with level fail
    "v_policy_violations_warn"                  INT     := 0; -- Number of policy violations with level warn
    "v_policy_violations_info"                  INT     := 0; -- Number of policy violations with level info
    "v_policy_violations_audited"               INT     := 0; -- Number of audited policy violations
    "v_policy_violations_unaudited"             INT     := 0; -- Number of unaudited policy violations
    "v_policy_violations_license_total"         INT     := 0; -- Total number of policy violations of type license
    "v_policy_violations_license_audited"       INT     := 0; -- Number of audited policy violations of type license
    "v_policy_violations_license_unaudited"     INT     := 0; -- Number of unaudited policy violations of type license
    "v_policy_violations_operational_total"     INT     := 0; -- Total number of policy violations of type operational
    "v_policy_violations_operational_audited"   INT     := 0; -- Number of audited policy violations of type operational
    "v_policy_violations_operational_unaudited" INT     := 0; -- Number of unaudited policy violations of type operational
    "v_policy_violations_security_total"        INT     := 0; -- Total number of policy violations of type security
    "v_policy_violations_security_audited"      INT     := 0; -- Number of audited policy violations of type security
    "v_policy_violations_security_unaudited"    INT     := 0; -- Number of unaudited policy violations of type security
    "v_existing_id"                             BIGINT; -- ID of the existing row that matches the data point calculated in this procedure
BEGIN
    SELECT "ID", "PROJECT_ID" INTO "v_component" FROM "COMPONENT" WHERE "UUID" = "component_uuid";
    IF "v_component" IS NULL THEN
        RAISE EXCEPTION 'Component with UUID % does not exist', "component_uuid";
    END IF;

    FOR "v_vulnerability" IN SELECT "VULNID", "SOURCE", "SEVERITY", "CVSSV2BASESCORE", "CVSSV3BASESCORE"
                             FROM "VULNERABILITY" AS "V"
                                      INNER JOIN "COMPONENTS_VULNERABILITIES" AS "CV"
                                                 ON "CV"."COMPONENT_ID" = "v_component"."ID"
                                                     AND "CV"."VULNERABILITY_ID" = "V"."ID"
                             WHERE NOT EXISTS(SELECT 1
                                              FROM "ANALYSIS" AS "A"
                                              WHERE "A"."COMPONENT_ID" = "v_component"."ID"
                                                AND "A"."VULNERABILITY_ID" = "CV"."VULNERABILITY_ID"
                                                AND "A"."SUPPRESSED" = TRUE)
        LOOP
            CONTINUE WHEN ("v_vulnerability"."SOURCE" || '|' || "v_vulnerability"."VULNID") = ANY ("v_aliases_seen");

            FOR "v_alias" IN SELECT *
                             FROM "VULNERABILITYALIAS" AS "VA"
                             WHERE ("v_vulnerability"."SOURCE" = 'GITHUB' AND
                                    "VA"."GHSA_ID" = "v_vulnerability"."VULNID")
                                OR ("v_vulnerability"."SOURCE" = 'INTERNAL' AND
                                    "VA"."INTERNAL_ID" = "v_vulnerability"."VULNID")
                                OR ("v_vulnerability"."SOURCE" = 'NVD' AND
                                    "VA"."CVE_ID" = "v_vulnerability"."VULNID")
                                OR ("v_vulnerability"."SOURCE" = 'OSSINDEX' AND
                                    "VA"."SONATYPE_ID" = "v_vulnerability"."VULNID")
                                OR ("v_vulnerability"."SOURCE" = 'OSV' AND
                                    "VA"."OSV_ID" = "v_vulnerability"."VULNID")
                                OR ("v_vulnerability"."SOURCE" = 'SNYK' AND
                                    "VA"."SNYK_ID" = "v_vulnerability"."VULNID")
                                OR ("v_vulnerability"."SOURCE" = 'VULNDB' AND
                                    "VA"."VULNDB_ID" = "v_vulnerability"."VULNID")
                LOOP
                    IF "v_alias"."GHSA_ID" IS NOT NULL THEN
                        "v_aliases_seen" = array_append("v_aliases_seen", 'GITHUB|' || "v_alias"."GHSA_ID");
                    END IF;
                    IF "v_alias"."INTERNAL_ID" IS NOT NULL THEN
                        "v_aliases_seen" = array_append("v_aliases_seen", 'INTERNAL|' || "v_alias"."INTERNAL_ID");
                    END IF;
                    IF "v_alias"."CVE_ID" IS NOT NULL THEN
                        "v_aliases_seen" = array_append("v_aliases_seen", 'NVD|' || "v_alias"."CVE_ID");
                    END IF;
                    IF "v_alias"."SONATYPE_ID" IS NOT NULL THEN
                        "v_aliases_seen" = array_append("v_aliases_seen", 'OSSINDEX|' || "v_alias"."SONATYPE_ID");
                    END IF;
                    IF "v_alias"."OSV_ID" IS NOT NULL THEN
                        "v_aliases_seen" = array_append("v_aliases_seen", 'OSV|' || "v_alias"."OSV_ID");
                    END IF;
                    IF "v_alias"."SNYK_ID" IS NOT NULL THEN
                        "v_aliases_seen" = array_append("v_aliases_seen", 'SNYK|' || "v_alias"."SNYK_ID");
                    END IF;
                    IF "v_alias"."VULNDB_ID" IS NOT NULL THEN
                        "v_aliases_seen" = array_append("v_aliases_seen", 'VULNDB|' || "v_alias"."VULNDB_ID");
                    END IF;
                END LOOP;

            "v_vulnerabilities" := "v_vulnerabilities" + 1;

            SELECT "CALC_SEVERITY"(
                           "v_vulnerability"."SEVERITY",
                           "v_vulnerability"."CVSSV3BASESCORE",
                           "v_vulnerability"."CVSSV2BASESCORE")
            INTO "v_severity";

            IF "v_severity" = 'CRITICAL' THEN
                "v_critical" := "v_critical" + 1;
            ELSEIF "v_severity" = 'HIGH' THEN
                "v_high" := "v_high" + 1;
            ELSEIF "v_severity" = 'MEDIUM' THEN
                "v_medium" := "v_medium" + 1;
            ELSEIF "v_severity" = 'LOW' THEN
                "v_low" := "v_low" + 1;
            ELSE
                "v_unassigned" := "v_unassigned" + 1;
            END IF;

        END LOOP;

    "v_risk_score" = "CALC_RISK_SCORE"("v_critical", "v_high", "v_medium", "v_low", "v_unassigned");

    SELECT COUNT(*)
    FROM "ANALYSIS" AS "A"
    WHERE "A"."COMPONENT_ID" = "v_component"."ID"
      AND "A"."SUPPRESSED" = FALSE
      AND "A"."STATE" != 'NOT_SET'
      AND "A"."STATE" != 'IN_TRIAGE'
    INTO "v_findings_audited";

    "v_findings_total" = "v_vulnerabilities";
    "v_findings_unaudited" = "v_findings_total" - "v_findings_audited";

    SELECT COUNT(*)
    FROM "ANALYSIS" AS "A"
    WHERE "A"."COMPONENT_ID" = "v_component"."ID"
      AND "A"."SUPPRESSED" = TRUE
    INTO "v_findings_suppressed";

    FOR "v_policy_violation" IN SELECT "PV"."TYPE", "P"."VIOLATIONSTATE"
                                FROM "POLICYVIOLATION" AS "PV"
                                         INNER JOIN "POLICYCONDITION" AS "PC" ON "PV"."POLICYCONDITION_ID" = "PC"."ID"
                                         INNER JOIN "POLICY" AS "P" ON "PC"."POLICY_ID" = "P"."ID"
                                         LEFT JOIN "VIOLATIONANALYSIS" AS "VA"
                                                   ON "VA"."COMPONENT_ID" = "v_component"."ID" AND
                                                      "VA"."POLICYVIOLATION_ID" = "PV"."ID"
                                WHERE "PV"."COMPONENT_ID" = "v_component"."ID"
                                  AND ("VA" IS NULL OR "VA"."SUPPRESSED" = FALSE)
        LOOP
            "v_policy_violations_total" := "v_policy_violations_total" + 1;

            IF "v_policy_violation"."TYPE" = 'LICENSE' THEN
                "v_policy_violations_license_total" := "v_policy_violations_license_total" + 1;
            ELSEIF "v_policy_violation"."TYPE" = 'OPERATIONAL' THEN
                "v_policy_violations_operational_total" := "v_policy_violations_operational_total" + 1;
            ELSEIF "v_policy_violation"."TYPE" = 'SECURITY' THEN
                "v_policy_violations_security_total" := "v_policy_violations_security_total" + 1;
            ELSE
                RAISE EXCEPTION 'Encountered invalid policy violation type %', "v_policy_violation"."TYPE";
            END IF;

            IF "v_policy_violation"."VIOLATIONSTATE" = 'FAIL' THEN
                "v_policy_violations_fail" := "v_policy_violations_fail" + 1;
            ELSEIF "v_policy_violation"."VIOLATIONSTATE" = 'WARN' THEN
                "v_policy_violations_warn" := "v_policy_violations_warn" + 1;
            ELSEIF "v_policy_violation"."VIOLATIONSTATE" = 'INFO' THEN
                "v_policy_violations_info" := "v_policy_violations_info" + 1;
            ELSE
                RAISE EXCEPTION 'Encountered invalid violation state %', "v_policy_violation"."VIOLATIONSTATE";
            end if;
        END LOOP;

    SELECT COUNT(*)
    FROM "VIOLATIONANALYSIS" AS "VA"
             INNER JOIN "POLICYVIOLATION" AS "PV" ON "PV"."ID" = "VA"."POLICYVIOLATION_ID"
    WHERE "VA"."COMPONENT_ID" = "v_component"."ID"
      AND "PV"."TYPE" = 'LICENSE'
      AND "VA"."SUPPRESSED" = FALSE
      AND "VA"."STATE" != 'NOT_SET'
    INTO "v_policy_violations_license_audited";
    "v_policy_violations_license_unaudited" =
                "v_policy_violations_license_total" - "v_policy_violations_license_audited";

    SELECT COUNT(*)
    FROM "VIOLATIONANALYSIS" AS "VA"
             INNER JOIN "POLICYVIOLATION" AS "PV" ON "PV"."ID" = "VA"."POLICYVIOLATION_ID"
    WHERE "VA"."COMPONENT_ID" = "v_component"."ID"
      AND "PV"."TYPE" = 'OPERATIONAL'
      AND "VA"."SUPPRESSED" = FALSE
      AND "VA"."STATE" != 'NOT_SET'
    INTO "v_policy_violations_operational_audited";
    "v_policy_violations_operational_unaudited" =
                "v_policy_violations_operational_total" - "v_policy_violations_operational_audited";

    SELECT COUNT(*)
    FROM "VIOLATIONANALYSIS" AS "VA"
             INNER JOIN "POLICYVIOLATION" AS "PV" ON "PV"."ID" = "VA"."POLICYVIOLATION_ID"
    WHERE "VA"."COMPONENT_ID" = "v_component"."ID"
      AND "PV"."TYPE" = 'SECURITY'
      AND "VA"."SUPPRESSED" = FALSE
      AND "VA"."STATE" != 'NOT_SET'
    INTO "v_policy_violations_security_audited";
    "v_policy_violations_security_unaudited" =
                "v_policy_violations_security_total" - "v_policy_violations_security_audited";

    "v_policy_violations_audited" = "v_policy_violations_license_audited"
        + "v_policy_violations_operational_audited"
        + "v_policy_violations_security_audited";
    "v_policy_violations_unaudited" = "v_policy_violations_total" - "v_policy_violations_audited";

    SELECT "ID"
    FROM "DEPENDENCYMETRICS"
    WHERE "COMPONENT_ID" = "v_component"."ID"
      AND "VULNERABILITIES" = "v_vulnerabilities"
      AND "CRITICAL" = "v_critical"
      AND "HIGH" = "v_high"
      AND "MEDIUM" = "v_medium"
      AND "LOW" = "v_low"
      AND "UNASSIGNED_SEVERITY" = "v_unassigned"
      AND "RISKSCORE" = "v_risk_score"
      AND "FINDINGS_TOTAL" = "v_findings_total"
      AND "FINDINGS_AUDITED" = "v_findings_audited"
      AND "FINDINGS_UNAUDITED" = "v_findings_unaudited"
      AND "SUPPRESSED" = "v_findings_suppressed"
      AND "POLICYVIOLATIONS_TOTAL" = "v_policy_violations_total"
      AND "POLICYVIOLATIONS_FAIL" = "v_policy_violations_fail"
      AND "POLICYVIOLATIONS_WARN" = "v_policy_violations_warn"
      AND "POLICYVIOLATIONS_INFO" = "v_policy_violations_info"
      AND "POLICYVIOLATIONS_AUDITED" = "v_policy_violations_audited"
      AND "POLICYVIOLATIONS_UNAUDITED" = "v_policy_violations_unaudited"
      AND "POLICYVIOLATIONS_LICENSE_TOTAL" = "v_policy_violations_license_total"
      AND "POLICYVIOLATIONS_LICENSE_AUDITED" = "v_policy_violations_license_audited"
      AND "POLICYVIOLATIONS_LICENSE_UNAUDITED" = "v_policy_violations_license_unaudited"
      AND "POLICYVIOLATIONS_OPERATIONAL_TOTAL" = "v_policy_violations_operational_total"
      AND "POLICYVIOLATIONS_OPERATIONAL_AUDITED" = "v_policy_violations_operational_audited"
      AND "POLICYVIOLATIONS_OPERATIONAL_UNAUDITED" = "v_policy_violations_operational_unaudited"
      AND "POLICYVIOLATIONS_SECURITY_TOTAL" = "v_policy_violations_security_total"
      AND "POLICYVIOLATIONS_SECURITY_AUDITED" = "v_policy_violations_security_audited"
      AND "POLICYVIOLATIONS_SECURITY_UNAUDITED" = "v_policy_violations_security_unaudited"
    ORDER BY "LAST_OCCURRENCE" DESC
    LIMIT 1
    INTO "v_existing_id";

    IF "v_existing_id" IS NOT NULL THEN
        UPDATE "DEPENDENCYMETRICS" SET "LAST_OCCURRENCE" = NOW() WHERE "ID" = "v_existing_id";
    ELSE
        INSERT INTO "DEPENDENCYMETRICS" ("COMPONENT_ID",
                                         "PROJECT_ID",
                                         "VULNERABILITIES",
                                         "CRITICAL",
                                         "HIGH",
                                         "MEDIUM",
                                         "LOW",
                                         "UNASSIGNED_SEVERITY",
                                         "RISKSCORE",
                                         "FINDINGS_TOTAL",
                                         "FINDINGS_AUDITED",
                                         "FINDINGS_UNAUDITED",
                                         "SUPPRESSED",
                                         "POLICYVIOLATIONS_TOTAL",
                                         "POLICYVIOLATIONS_FAIL",
                                         "POLICYVIOLATIONS_WARN",
                                         "POLICYVIOLATIONS_INFO",
                                         "POLICYVIOLATIONS_AUDITED",
                                         "POLICYVIOLATIONS_UNAUDITED",
                                         "POLICYVIOLATIONS_LICENSE_TOTAL",
                                         "POLICYVIOLATIONS_LICENSE_AUDITED",
                                         "POLICYVIOLATIONS_LICENSE_UNAUDITED",
                                         "POLICYVIOLATIONS_OPERATIONAL_TOTAL",
                                         "POLICYVIOLATIONS_OPERATIONAL_AUDITED",
                                         "POLICYVIOLATIONS_OPERATIONAL_UNAUDITED",
                                         "POLICYVIOLATIONS_SECURITY_TOTAL",
                                         "POLICYVIOLATIONS_SECURITY_AUDITED",
                                         "POLICYVIOLATIONS_SECURITY_UNAUDITED",
                                         "FIRST_OCCURRENCE",
                                         "LAST_OCCURRENCE")
        VALUES ("v_component"."ID",
                "v_component"."PROJECT_ID",
                "v_vulnerabilities",
                "v_critical",
                "v_high",
                "v_medium",
                "v_low",
                "v_unassigned",
                "v_risk_score",
                "v_findings_total",
                "v_findings_audited",
                "v_findings_unaudited",
                "v_findings_suppressed",
                "v_policy_violations_total",
                "v_policy_violations_fail",
                "v_policy_violations_warn",
                "v_policy_violations_info",
                "v_policy_violations_audited",
                "v_policy_violations_unaudited",
                "v_policy_violations_license_total",
                "v_policy_violations_license_audited",
                "v_policy_violations_license_unaudited",
                "v_policy_violations_operational_total",
                "v_policy_violations_operational_audited",
                "v_policy_violations_operational_unaudited",
                "v_policy_violations_security_total",
                "v_policy_violations_security_audited",
                "v_policy_violations_security_unaudited",
                NOW(),
                NOW());

        UPDATE "COMPONENT" SET "LAST_RISKSCORE" = "v_risk_score" WHERE "ID" = "v_component"."ID";
    END IF;
END;
$$;

CREATE OR REPLACE PROCEDURE "UPDATE_PROJECT_METRICS"(
    "project_uuid" VARCHAR(36)
)
    LANGUAGE "plpgsql"
AS
$$
DECLARE
    "v_project_id"                              BIGINT;
    "v_components"                              INT; -- Total number of components in the project
    "v_vulnerable_components"                   INT; -- Number of vulnerable components in the project
    "v_vulnerabilities"                         INT; -- Total number of vulnerabilities
    "v_critical"                                INT; -- Number of vulnerabilities with critical severity
    "v_high"                                    INT; -- Number of vulnerabilities with high severity
    "v_medium"                                  INT; -- Number of vulnerabilities with medium severity
    "v_low"                                     INT; -- Number of vulnerabilities with low severity
    "v_unassigned"                              INT; -- Number of vulnerabilities with unassigned severity
    "v_risk_score"                              NUMERIC; -- Inherited risk score
    "v_findings_total"                          INT; -- Total number of findings
    "v_findings_audited"                        INT; -- Number of audited findings
    "v_findings_unaudited"                      INT; -- Number of unaudited findings
    "v_findings_suppressed"                     INT; -- Number of suppressed findings
    "v_policy_violations_total"                 INT; -- Total number of policy violations
    "v_policy_violations_fail"                  INT; -- Number of policy violations with level fail
    "v_policy_violations_warn"                  INT; -- Number of policy violations with level warn
    "v_policy_violations_info"                  INT; -- Number of policy violations with level info
    "v_policy_violations_audited"               INT; -- Number of audited policy violations
    "v_policy_violations_unaudited"             INT; -- Number of unaudited policy violations
    "v_policy_violations_license_total"         INT; -- Total number of policy violations of type license
    "v_policy_violations_license_audited"       INT; -- Number of audited policy violations of type license
    "v_policy_violations_license_unaudited"     INT; -- Number of unaudited policy violations of type license
    "v_policy_violations_operational_total"     INT; -- Total number of policy violations of type operational
    "v_policy_violations_operational_audited"   INT; -- Number of audited policy violations of type operational
    "v_policy_violations_operational_unaudited" INT; -- Number of unaudited policy violations of type operational
    "v_policy_violations_security_total"        INT; -- Total number of policy violations of type security
    "v_policy_violations_security_audited"      INT; -- Number of audited policy violations of type security
    "v_policy_violations_security_unaudited"    INT; -- Number of unaudited policy violations of type security
    "v_existing_id"                             BIGINT; -- ID of the existing row that matches the data point calculated in this procedure
BEGIN
    SELECT "ID" FROM "PROJECT" WHERE "UUID" = "project_uuid" INTO "v_project_id";
    IF "v_project_id" IS NULL THEN
        RAISE EXCEPTION 'Project with UUID % does not exist', "project_uuid";
    END IF;

    -- Aggregate over all most recent DEPENDENCYMETRICS.
    -- NOTE: SUM returns NULL when no rows match the query, but COUNT returns 0.
    -- For nullable result columns, use COALESCE(..., 0) to have a default value.
    SELECT COUNT(*)::INT,
           COALESCE(SUM(CASE WHEN "VULNERABILITIES" > 0 THEN 1 ELSE 0 END)::INT, 0),
           COALESCE(SUM("VULNERABILITIES")::INT, 0),
           COALESCE(SUM("CRITICAL")::INT, 0),
           COALESCE(SUM("HIGH")::INT, 0),
           COALESCE(SUM("MEDIUM")::INT, 0),
           COALESCE(SUM("LOW")::INT, 0),
           COALESCE(SUM("UNASSIGNED_SEVERITY")::INT, 0),
           COALESCE(SUM("FINDINGS_TOTAL")::INT, 0),
           COALESCE(SUM("FINDINGS_AUDITED")::INT, 0),
           COALESCE(SUM("FINDINGS_UNAUDITED")::INT, 0),
           COALESCE(SUM("SUPPRESSED")::INT, 0),
           COALESCE(SUM("POLICYVIOLATIONS_TOTAL")::INT, 0),
           COALESCE(SUM("POLICYVIOLATIONS_FAIL")::INT, 0),
           COALESCE(SUM("POLICYVIOLATIONS_WARN")::INT, 0),
           COALESCE(SUM("POLICYVIOLATIONS_INFO")::INT, 0),
           COALESCE(SUM("POLICYVIOLATIONS_AUDITED")::INT, 0),
           COALESCE(SUM("POLICYVIOLATIONS_UNAUDITED")::INT, 0),
           COALESCE(SUM("POLICYVIOLATIONS_LICENSE_TOTAL")::INT, 0),
           COALESCE(SUM("POLICYVIOLATIONS_LICENSE_AUDITED")::INT, 0),
           COALESCE(SUM("POLICYVIOLATIONS_LICENSE_UNAUDITED")::INT, 0),
           COALESCE(SUM("POLICYVIOLATIONS_OPERATIONAL_TOTAL")::INT, 0),
           COALESCE(SUM("POLICYVIOLATIONS_OPERATIONAL_AUDITED")::INT, 0),
           COALESCE(SUM("POLICYVIOLATIONS_OPERATIONAL_UNAUDITED")::INT, 0),
           COALESCE(SUM("POLICYVIOLATIONS_SECURITY_TOTAL")::INT, 0),
           COALESCE(SUM("POLICYVIOLATIONS_SECURITY_AUDITED")::INT, 0),
           COALESCE(SUM("POLICYVIOLATIONS_SECURITY_UNAUDITED")::INT, 0)
    FROM (SELECT DISTINCT ON ("DM"."COMPONENT_ID") *
          FROM "DEPENDENCYMETRICS" AS "DM"
          WHERE "PROJECT_ID" = "v_project_id"
          ORDER BY "DM"."COMPONENT_ID", "DM"."LAST_OCCURRENCE" DESC) AS "LATEST_COMPONENT_METRICS"
    INTO
        "v_components",
        "v_vulnerable_components",
        "v_vulnerabilities",
        "v_critical",
        "v_high",
        "v_medium",
        "v_low",
        "v_unassigned",
        "v_findings_total",
        "v_findings_audited",
        "v_findings_unaudited",
        "v_findings_suppressed",
        "v_policy_violations_total",
        "v_policy_violations_fail",
        "v_policy_violations_warn",
        "v_policy_violations_info",
        "v_policy_violations_audited",
        "v_policy_violations_unaudited",
        "v_policy_violations_license_total",
        "v_policy_violations_license_audited",
        "v_policy_violations_license_unaudited",
        "v_policy_violations_operational_total",
        "v_policy_violations_operational_audited",
        "v_policy_violations_operational_unaudited",
        "v_policy_violations_security_total",
        "v_policy_violations_security_audited",
        "v_policy_violations_security_unaudited";

    "v_risk_score" = "CALC_RISK_SCORE"("v_critical", "v_high", "v_medium", "v_low", "v_unassigned");

    SELECT "ID"
    FROM "PROJECTMETRICS"
    WHERE "PROJECT_ID" = "v_project_id"
      AND "COMPONENTS" = "v_components"
      AND "VULNERABLECOMPONENTS" = "v_vulnerable_components"
      AND "VULNERABILITIES" = "v_vulnerabilities"
      AND "CRITICAL" = "v_critical"
      AND "HIGH" = "v_high"
      AND "MEDIUM" = "v_medium"
      AND "LOW" = "v_low"
      AND "UNASSIGNED_SEVERITY" = "v_unassigned"
      AND "RISKSCORE" = "v_risk_score"
      AND "FINDINGS_TOTAL" = "v_findings_total"
      AND "FINDINGS_AUDITED" = "v_findings_audited"
      AND "FINDINGS_UNAUDITED" = "v_findings_unaudited"
      AND "SUPPRESSED" = "v_findings_suppressed"
      AND "POLICYVIOLATIONS_TOTAL" = "v_policy_violations_total"
      AND "POLICYVIOLATIONS_FAIL" = "v_policy_violations_fail"
      AND "POLICYVIOLATIONS_WARN" = "v_policy_violations_warn"
      AND "POLICYVIOLATIONS_INFO" = "v_policy_violations_info"
      AND "POLICYVIOLATIONS_AUDITED" = "v_policy_violations_audited"
      AND "POLICYVIOLATIONS_UNAUDITED" = "v_policy_violations_unaudited"
      AND "POLICYVIOLATIONS_LICENSE_TOTAL" = "v_policy_violations_license_total"
      AND "POLICYVIOLATIONS_LICENSE_AUDITED" = "v_policy_violations_license_audited"
      AND "POLICYVIOLATIONS_LICENSE_UNAUDITED" = "v_policy_violations_license_unaudited"
      AND "POLICYVIOLATIONS_OPERATIONAL_TOTAL" = "v_policy_violations_operational_total"
      AND "POLICYVIOLATIONS_OPERATIONAL_AUDITED" = "v_policy_violations_operational_audited"
      AND "POLICYVIOLATIONS_OPERATIONAL_UNAUDITED" = "v_policy_violations_operational_unaudited"
      AND "POLICYVIOLATIONS_SECURITY_TOTAL" = "v_policy_violations_security_total"
      AND "POLICYVIOLATIONS_SECURITY_AUDITED" = "v_policy_violations_security_audited"
      AND "POLICYVIOLATIONS_SECURITY_UNAUDITED" = "v_policy_violations_security_unaudited"
    ORDER BY "LAST_OCCURRENCE" DESC
    LIMIT 1
    INTO "v_existing_id";

    IF "v_existing_id" IS NOT NULL THEN
        UPDATE "PROJECTMETRICS" SET "LAST_OCCURRENCE" = NOW() WHERE "ID" = "v_existing_id";
    ELSE
        INSERT INTO "PROJECTMETRICS" ("PROJECT_ID",
                                      "COMPONENTS",
                                      "VULNERABLECOMPONENTS",
                                      "VULNERABILITIES",
                                      "CRITICAL",
                                      "HIGH",
                                      "MEDIUM",
                                      "LOW",
                                      "UNASSIGNED_SEVERITY",
                                      "RISKSCORE",
                                      "FINDINGS_TOTAL",
                                      "FINDINGS_AUDITED",
                                      "FINDINGS_UNAUDITED",
                                      "SUPPRESSED",
                                      "POLICYVIOLATIONS_TOTAL",
                                      "POLICYVIOLATIONS_FAIL",
                                      "POLICYVIOLATIONS_WARN",
                                      "POLICYVIOLATIONS_INFO",
                                      "POLICYVIOLATIONS_AUDITED",
                                      "POLICYVIOLATIONS_UNAUDITED",
                                      "POLICYVIOLATIONS_LICENSE_TOTAL",
                                      "POLICYVIOLATIONS_LICENSE_AUDITED",
                                      "POLICYVIOLATIONS_LICENSE_UNAUDITED",
                                      "POLICYVIOLATIONS_OPERATIONAL_TOTAL",
                                      "POLICYVIOLATIONS_OPERATIONAL_AUDITED",
                                      "POLICYVIOLATIONS_OPERATIONAL_UNAUDITED",
                                      "POLICYVIOLATIONS_SECURITY_TOTAL",
                                      "POLICYVIOLATIONS_SECURITY_AUDITED",
                                      "POLICYVIOLATIONS_SECURITY_UNAUDITED",
                                      "FIRST_OCCURRENCE",
                                      "LAST_OCCURRENCE")
        VALUES ("v_project_id",
                "v_components",
                "v_vulnerable_components",
                "v_vulnerabilities",
                "v_critical",
                "v_high",
                "v_medium",
                "v_low",
                "v_unassigned",
                "v_risk_score",
                "v_findings_total",
                "v_findings_audited",
                "v_findings_unaudited",
                "v_findings_suppressed",
                "v_policy_violations_total",
                "v_policy_violations_fail",
                "v_policy_violations_warn",
                "v_policy_violations_info",
                "v_policy_violations_audited",
                "v_policy_violations_unaudited",
                "v_policy_violations_license_total",
                "v_policy_violations_license_audited",
                "v_policy_violations_license_unaudited",
                "v_policy_violations_operational_total",
                "v_policy_violations_operational_audited",
                "v_policy_violations_operational_unaudited",
                "v_policy_violations_security_total",
                "v_policy_violations_security_audited",
                "v_policy_violations_security_unaudited",
                NOW(),
                NOW());

        UPDATE "PROJECT" SET "LAST_RISKSCORE" = "v_risk_score" WHERE "ID" = "v_project_id";
    END IF;
end;
$$;

--
CREATE OR REPLACE PROCEDURE "UPDATE_PORTFOLIO_METRICS"()
    LANGUAGE "plpgsql"
AS
$$
DECLARE
    "v_projects"                                INT; -- Total number of projects in the portfolio
    "v_vulnerable_projects"                     INT; -- Number of vulnerable projects in the portfolio
    "v_components"                              INT; -- Total number of components in the portfolio
    "v_vulnerable_components"                   INT; -- Number of vulnerable components in the portfolio
    "v_vulnerabilities"                         INT; -- Total number of vulnerabilities
    "v_critical"                                INT; -- Number of vulnerabilities with critical severity
    "v_high"                                    INT; -- Number of vulnerabilities with high severity
    "v_medium"                                  INT; -- Number of vulnerabilities with medium severity
    "v_low"                                     INT; -- Number of vulnerabilities with low severity
    "v_unassigned"                              INT; -- Number of vulnerabilities with unassigned severity
    "v_risk_score"                              NUMERIC; -- Inherited risk score
    "v_findings_total"                          INT; -- Total number of findings
    "v_findings_audited"                        INT; -- Number of audited findings
    "v_findings_unaudited"                      INT; -- Number of unaudited findings
    "v_findings_suppressed"                     INT; -- Number of suppressed findings
    "v_policy_violations_total"                 INT; -- Total number of policy violations
    "v_policy_violations_fail"                  INT; -- Number of policy violations with level fail
    "v_policy_violations_warn"                  INT; -- Number of policy violations with level warn
    "v_policy_violations_info"                  INT; -- Number of policy violations with level info
    "v_policy_violations_audited"               INT; -- Number of audited policy violations
    "v_policy_violations_unaudited"             INT; -- Number of unaudited policy violations
    "v_policy_violations_license_total"         INT; -- Total number of policy violations of type license
    "v_policy_violations_license_audited"       INT; -- Number of audited policy violations of type license
    "v_policy_violations_license_unaudited"     INT; -- Number of unaudited policy violations of type license
    "v_policy_violations_operational_total"     INT; -- Total number of policy violations of type operational
    "v_policy_violations_operational_audited"   INT; -- Number of audited policy violations of type operational
    "v_policy_violations_operational_unaudited" INT; -- Number of unaudited policy violations of type operational
    "v_policy_violations_security_total"        INT; -- Total number of policy violations of type security
    "v_policy_violations_security_audited"      INT; -- Number of audited policy violations of type security
    "v_policy_violations_security_unaudited"    INT; -- Number of unaudited policy violations of type security
    "v_existing_id"                             BIGINT; -- ID of the existing row that matches the data point calculated in this procedure
BEGIN
    -- Aggregate over all most recent DEPENDENCYMETRICS.
    -- NOTE: SUM returns NULL when no rows match the query, but COUNT returns 0.
    -- For nullable result columns, use COALESCE(..., 0) to have a default value.
    SELECT COUNT(*)::INT,
           COALESCE(SUM(CASE WHEN "VULNERABILITIES" > 0 THEN 1 ELSE 0 END)::INT, 0),
           COALESCE(SUM("COMPONENTS")::INT, 0),
           COALESCE(SUM("VULNERABLECOMPONENTS")::INT, 0),
           COALESCE(SUM("VULNERABILITIES")::INT, 0),
           COALESCE(SUM("CRITICAL")::INT, 0),
           COALESCE(SUM("HIGH")::INT, 0),
           COALESCE(SUM("MEDIUM")::INT, 0),
           COALESCE(SUM("LOW")::INT, 0),
           COALESCE(SUM("UNASSIGNED_SEVERITY")::INT, 0),
           COALESCE(SUM("FINDINGS_TOTAL")::INT, 0),
           COALESCE(SUM("FINDINGS_AUDITED")::INT, 0),
           COALESCE(SUM("FINDINGS_UNAUDITED")::INT, 0),
           COALESCE(SUM("SUPPRESSED")::INT, 0),
           COALESCE(SUM("POLICYVIOLATIONS_TOTAL")::INT, 0),
           COALESCE(SUM("POLICYVIOLATIONS_FAIL")::INT, 0),
           COALESCE(SUM("POLICYVIOLATIONS_WARN")::INT, 0),
           COALESCE(SUM("POLICYVIOLATIONS_INFO")::INT, 0),
           COALESCE(SUM("POLICYVIOLATIONS_AUDITED")::INT, 0),
           COALESCE(SUM("POLICYVIOLATIONS_UNAUDITED")::INT, 0),
           COALESCE(SUM("POLICYVIOLATIONS_LICENSE_TOTAL")::INT, 0),
           COALESCE(SUM("POLICYVIOLATIONS_LICENSE_AUDITED")::INT, 0),
           COALESCE(SUM("POLICYVIOLATIONS_LICENSE_UNAUDITED")::INT, 0),
           COALESCE(SUM("POLICYVIOLATIONS_OPERATIONAL_TOTAL")::INT, 0),
           COALESCE(SUM("POLICYVIOLATIONS_OPERATIONAL_AUDITED")::INT, 0),
           COALESCE(SUM("POLICYVIOLATIONS_OPERATIONAL_UNAUDITED")::INT, 0),
           COALESCE(SUM("POLICYVIOLATIONS_SECURITY_TOTAL")::INT, 0),
           COALESCE(SUM("POLICYVIOLATIONS_SECURITY_AUDITED")::INT, 0),
           COALESCE(SUM("POLICYVIOLATIONS_SECURITY_UNAUDITED")::INT, 0)
    FROM (SELECT DISTINCT ON ("PM"."PROJECT_ID") *
          FROM "PROJECTMETRICS" AS "PM"
                   INNER JOIN "PROJECT" AS "P" ON "P"."ID" = "PM"."PROJECT_ID"
          WHERE "P"."ACTIVE" = TRUE  -- Only consider active projects
             OR "P"."ACTIVE" IS NULL -- ACTIVE is nullable, assume TRUE per default
          ORDER BY "PM"."PROJECT_ID", "PM"."LAST_OCCURRENCE" DESC) AS "LATEST_PROJECT_METRICS"
    INTO
        "v_projects",
        "v_vulnerable_projects",
        "v_components",
        "v_vulnerable_components",
        "v_vulnerabilities",
        "v_critical",
        "v_high",
        "v_medium",
        "v_low",
        "v_unassigned",
        "v_findings_total",
        "v_findings_audited",
        "v_findings_unaudited",
        "v_findings_suppressed",
        "v_policy_violations_total",
        "v_policy_violations_fail",
        "v_policy_violations_warn",
        "v_policy_violations_info",
        "v_policy_violations_audited",
        "v_policy_violations_unaudited",
        "v_policy_violations_license_total",
        "v_policy_violations_license_audited",
        "v_policy_violations_license_unaudited",
        "v_policy_violations_operational_total",
        "v_policy_violations_operational_audited",
        "v_policy_violations_operational_unaudited",
        "v_policy_violations_security_total",
        "v_policy_violations_security_audited",
        "v_policy_violations_security_unaudited";

    "v_risk_score" = "CALC_RISK_SCORE"("v_critical", "v_high", "v_medium", "v_low", "v_unassigned");

    SELECT "ID"
    FROM "PORTFOLIOMETRICS"
    WHERE "PROJECTS" = "v_projects"
      AND "VULNERABLEPROJECTS" = "v_vulnerable_projects"
      AND "COMPONENTS" = "v_components"
      AND "VULNERABLECOMPONENTS" = "v_vulnerable_components"
      AND "VULNERABILITIES" = "v_vulnerabilities"
      AND "CRITICAL" = "v_critical"
      AND "HIGH" = "v_high"
      AND "MEDIUM" = "v_medium"
      AND "LOW" = "v_low"
      AND "UNASSIGNED_SEVERITY" = "v_unassigned"
      AND "RISKSCORE" = "v_risk_score"
      AND "FINDINGS_TOTAL" = "v_findings_total"
      AND "FINDINGS_AUDITED" = "v_findings_audited"
      AND "FINDINGS_UNAUDITED" = "v_findings_unaudited"
      AND "SUPPRESSED" = "v_findings_suppressed"
      AND "POLICYVIOLATIONS_TOTAL" = "v_policy_violations_total"
      AND "POLICYVIOLATIONS_FAIL" = "v_policy_violations_fail"
      AND "POLICYVIOLATIONS_WARN" = "v_policy_violations_warn"
      AND "POLICYVIOLATIONS_INFO" = "v_policy_violations_info"
      AND "POLICYVIOLATIONS_AUDITED" = "v_policy_violations_audited"
      AND "POLICYVIOLATIONS_UNAUDITED" = "v_policy_violations_unaudited"
      AND "POLICYVIOLATIONS_LICENSE_TOTAL" = "v_policy_violations_license_total"
      AND "POLICYVIOLATIONS_LICENSE_AUDITED" = "v_policy_violations_license_audited"
      AND "POLICYVIOLATIONS_LICENSE_UNAUDITED" = "v_policy_violations_license_unaudited"
      AND "POLICYVIOLATIONS_OPERATIONAL_TOTAL" = "v_policy_violations_operational_total"
      AND "POLICYVIOLATIONS_OPERATIONAL_AUDITED" = "v_policy_violations_operational_audited"
      AND "POLICYVIOLATIONS_OPERATIONAL_UNAUDITED" = "v_policy_violations_operational_unaudited"
      AND "POLICYVIOLATIONS_SECURITY_TOTAL" = "v_policy_violations_security_total"
      AND "POLICYVIOLATIONS_SECURITY_AUDITED" = "v_policy_violations_security_audited"
      AND "POLICYVIOLATIONS_SECURITY_UNAUDITED" = "v_policy_violations_security_unaudited"
    ORDER BY "LAST_OCCURRENCE" DESC
    LIMIT 1
    INTO "v_existing_id";

    IF "v_existing_id" IS NOT NULL THEN
        UPDATE "PORTFOLIOMETRICS" SET "LAST_OCCURRENCE" = NOW() WHERE "ID" = "v_existing_id";
    ELSE
        INSERT INTO "PORTFOLIOMETRICS" ("PROJECTS",
                                        "VULNERABLEPROJECTS",
                                        "COMPONENTS",
                                        "VULNERABLECOMPONENTS",
                                        "VULNERABILITIES",
                                        "CRITICAL",
                                        "HIGH",
                                        "MEDIUM",
                                        "LOW",
                                        "UNASSIGNED_SEVERITY",
                                        "RISKSCORE",
                                        "FINDINGS_TOTAL",
                                        "FINDINGS_AUDITED",
                                        "FINDINGS_UNAUDITED",
                                        "SUPPRESSED",
                                        "POLICYVIOLATIONS_TOTAL",
                                        "POLICYVIOLATIONS_FAIL",
                                        "POLICYVIOLATIONS_WARN",
                                        "POLICYVIOLATIONS_INFO",
                                        "POLICYVIOLATIONS_AUDITED",
                                        "POLICYVIOLATIONS_UNAUDITED",
                                        "POLICYVIOLATIONS_LICENSE_TOTAL",
                                        "POLICYVIOLATIONS_LICENSE_AUDITED",
                                        "POLICYVIOLATIONS_LICENSE_UNAUDITED",
                                        "POLICYVIOLATIONS_OPERATIONAL_TOTAL",
                                        "POLICYVIOLATIONS_OPERATIONAL_AUDITED",
                                        "POLICYVIOLATIONS_OPERATIONAL_UNAUDITED",
                                        "POLICYVIOLATIONS_SECURITY_TOTAL",
                                        "POLICYVIOLATIONS_SECURITY_AUDITED",
                                        "POLICYVIOLATIONS_SECURITY_UNAUDITED",
                                        "FIRST_OCCURRENCE",
                                        "LAST_OCCURRENCE")
        VALUES ("v_projects",
                "v_vulnerable_projects",
                "v_components",
                "v_vulnerable_components",
                "v_vulnerabilities",
                "v_critical",
                "v_high",
                "v_medium",
                "v_low",
                "v_unassigned",
                "v_risk_score",
                "v_findings_total",
                "v_findings_audited",
                "v_findings_unaudited",
                "v_findings_suppressed",
                "v_policy_violations_total",
                "v_policy_violations_fail",
                "v_policy_violations_warn",
                "v_policy_violations_info",
                "v_policy_violations_audited",
                "v_policy_violations_unaudited",
                "v_policy_violations_license_total",
                "v_policy_violations_license_audited",
                "v_policy_violations_license_unaudited",
                "v_policy_violations_operational_total",
                "v_policy_violations_operational_audited",
                "v_policy_violations_operational_unaudited",
                "v_policy_violations_security_total",
                "v_policy_violations_security_audited",
                "v_policy_violations_security_unaudited",
                NOW(),
                NOW());
    END IF;
END;
$$;