{"record":{"id":"cefa348c73f75120","repo":"octobercms/october","slug":"the-aggregate-function-is-not-supported","errorCode":null,"errorMessage":"The aggregate function is not supported: ","messagePattern":"The aggregate function is not supported: ","errorType":"exception","errorClass":"SystemException","httpStatus":null,"severity":"error","filePath":"modules/dashboard/classes/ReportMetric.php","lineNumber":93,"sourceCode":"        }\n\n        if (!strlen($aggregateFunction)) {\n            throw new SystemException('The aggregate function cannot be empty.');\n        }\n\n        $knownAggregateFunctions = [\n            self::AGGREGATE_SUM,\n            self::AGGREGATE_AVG,\n            self::AGGREGATE_MIN,\n            self::AGGREGATE_MAX,\n            self::AGGREGATE_COUNT,\n            self::AGGREGATE_NONE,\n            self::AGGREGATE_COUNT_DISTINCT,\n            self::AGGREGATE_COUNT_DISTINCT_NOT_NULL\n        ];\n\n        if (!in_array($aggregateFunction, $knownAggregateFunctions)) {\n            throw new SystemException('The aggregate function is not supported: ' . $aggregateFunction);\n        }\n\n        $this->code = $code;\n        $this->databaseColumnName = $databaseColumnName;\n        $this->displayName = $displayName;\n        $this->aggregateFunction = $aggregateFunction;\n        $this->intlFormatOptions = $intlFormatOptions;\n    }\n\n    /**\n     * Returns the metric code.\n     * @return string Returns the metric code.\n     */\n    public function getCode(): string\n    {\n        return $this->code;\n    }\n","sourceCodeStart":75,"sourceCodeEnd":111,"githubUrl":"https://github.com/octobercms/october/blob/b608633a7e8922487d91a8161499020121c3b3bf/modules/dashboard/classes/ReportMetric.php#L75-L111","documentation":"After the empty-string checks, the ReportMetric constructor validates $aggregateFunction against a fixed whitelist: sum, avg, min, max, count, none, count_distinct, count_distinct_not_null (the AGGREGATE_* constants). Any other non-empty string throws SystemException with the offending value appended. This is the enforcement point for the closed set of SQL aggregates the report query builder knows how to render; there is no extension mechanism here, so custom functions cannot be registered.","triggerScenarios":"Passing a raw SQL-ish string like 'COUNT(DISTINCT x)', 'SUM', 'Average', 'distinct_count', or a typo like 'cont' as the 4th constructor argument. Case matters: the constants are lowercase, so 'SUM' fails in_array without strict comparison only by luck — stick to constants.","commonSituations":"Copy-pasting SQL into the metric definition; using a hypothetical constant name that does not exist; mapping a REST/UI payload field with different naming ('countDistinct' vs 'count_distinct') straight into the constructor.","solutions":["Use the class constants: ReportMetric::AGGREGATE_SUM, AGGREGATE_AVG, AGGREGATE_MIN, AGGREGATE_MAX, AGGREGATE_COUNT, AGGREGATE_NONE, AGGREGATE_COUNT_DISTINCT, AGGREGATE_COUNT_DISTINCT_NOT_NULL.","Normalize incoming config values: strtolower + str_replace(' ', '_', ...) mapped to the constants before constructing.","If you need COUNT(DISTINCT col), use AGGREGATE_COUNT_DISTINCT — the column goes in the databaseColumnName argument, not the function string.","If you truly need an unsupported aggregate, compute it in the data source query/view and expose the result with AGGREGATE_NONE."],"exampleFix":"// before\nnew ReportMetric('customers', 'customer_id', 'Customers', 'distinct_count');\n\n// after\nnew ReportMetric('customers', 'customer_id', 'Customers', ReportMetric::AGGREGATE_COUNT_DISTINCT);","handlingStrategy":"validation","validationCode":"const AGGREGATES = [\n    ReportMetric::AGGREGATE_SUM, ReportMetric::AGGREGATE_AVG,\n    ReportMetric::AGGREGATE_MIN, ReportMetric::AGGREGATE_MAX,\n    ReportMetric::AGGREGATE_COUNT, ReportMetric::AGGREGATE_NONE,\n    ReportMetric::AGGREGATE_COUNT_DISTINCT, ReportMetric::AGGREGATE_COUNT_DISTINCT_NOT_NULL,\n];\n\nfunction normalizeAggregate(string $raw): string {\n    $map = ['distinct_count' => 'count_distinct', 'countdistinct' => 'count_distinct'];\n    $fn = $map[strtolower(trim($raw))] ?? strtolower(trim($raw));\n    if (!in_array($fn, AGGREGATES, true)) {\n        throw new InvalidArgumentException(\"Unsupported aggregate function: {$raw}\");\n    }\n    return $fn;\n}","typeGuard":"function isValidAggregate(string $fn): bool\n{\n    return in_array($fn, [\n        ReportMetric::AGGREGATE_SUM, ReportMetric::AGGREGATE_AVG,\n        ReportMetric::AGGREGATE_MIN, ReportMetric::AGGREGATE_MAX,\n        ReportMetric::AGGREGATE_COUNT, ReportMetric::AGGREGATE_NONE,\n        ReportMetric::AGGREGATE_COUNT_DISTINCT, ReportMetric::AGGREGATE_COUNT_DISTINCT_NOT_NULL,\n    ], true);\n}","tryCatchPattern":null,"preventionTips":["Reference AGGREGATE_* constants only; they are the single source of truth.","Normalize external aggregate names (API/UI payloads) to the constants at the boundary.","Watch for COUNT DISTINCT needs: use AGGREGATE_COUNT_DISTINCT rather than composing SQL text."],"tags":["dashboard","report-metric","aggregate-functions","whitelist-validation"],"backgroundTag":"enum-value-not-allowed","analyzedSha":"b608633a7e8922487d91a8161499020121c3b3bf","analyzedAt":"2026-08-21T04:24:57.515Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}