{"record":{"id":"6e00e8430afb00ad","repo":"apache/superset","slug":"adhoc-metric-expressiontype-is-invalid","errorCode":null,"errorMessage":"Adhoc metric expressionType is invalid","messagePattern":"Adhoc metric expressionType is invalid","errorType":"validation","errorClass":"QueryObjectValidationError","httpStatus":400,"severity":"error","filePath":"superset/connectors/sqla/models.py","lineNumber":1931,"sourceCode":"                raise QueryObjectValidationError(\n                    _(\"Adhoc metric SQL expression is invalid\")\n                )\n\n            if not processed:\n                try:\n                    expression = self._process_select_expression(\n                        expression=expression,\n                        database_id=self.database_id,\n                        engine=self.database.backend,\n                        schema=self.schema,\n                        template_processor=template_processor,\n                    )\n                except SupersetSecurityException as ex:\n                    raise QueryObjectValidationError(ex.message) from ex\n\n            sqla_metric = literal_column(expression)\n        else:\n            raise QueryObjectValidationError(\"Adhoc metric expressionType is invalid\")\n\n        return self.make_sqla_column_compatible(sqla_metric, label)\n\n    def _render_adhoc_expression_for_metadata_lookup(\n        self,\n        sql_expression: str,\n        template_processor: BaseTemplateProcessor | None,\n    ) -> str:\n        \"\"\"Render Jinja in *sql_expression* so the result can be matched against\n        column metadata.  Without this, a templated expression such as\n        ``{{ filter_values('x')[0] }}`` is passed raw to ``get_column``, never\n        matches, and falls back to ``literal_column`` — which breaks for virtual\n        datasets because the rendered name isn't present in the FROM subquery.\"\"\"\n        if not template_processor:\n            return sql_expression\n        try:\n            return template_processor.process_template(sql_expression)\n        except SupersetSyntaxErrorException as ex:","sourceCodeStart":1913,"sourceCodeEnd":1949,"githubUrl":"https://github.com/apache/superset/blob/f4587218dd19d046c3e4d00063e7d27f8a2ed354/superset/connectors/sqla/models.py#L1913-L1949","documentation":"QueryObjectValidationError raised in SqlaTable.adhoc_metric_to_sqla (models.py:1931) when the adhoc metric's expressionType is neither 'SIMPLE' nor 'SQL'. Superset dispatches metric construction on this discriminator; any other value (typo, null, legacy value) falls to the else branch. Note this raise is the one place without _() translation, but the semantics are identical.","triggerScenarios":"Adhoc metric dict like {expressionType: 'simple' | 'Custom' | null | 'Api'}, or a payload from a different Superset version/plugin using an unsupported expressionType enum value.","commonSituations":"Case mismatch ('simple' vs 'SIMPLE') from hand-written payloads; dashboard/chart export-import between versions whose AdhocMetricExpressionType enum differs; custom chart plugins emitting their own metric shapes.","solutions":["Set expressionType to exactly 'SIMPLE' or 'SQL' (see superset/common/utils/query_utils.py AdhocMetricExpressionType).","For simple aggregation: provide column + aggregate; for SQL: provide sqlExpression.","Re-create the metric through the Explore UI to get canonical JSON."],"exampleFix":"// before\n{ expressionType: 'simple', column: { column_name: 'sales' }, aggregate: 'SUM' }\n\n// after\n{ expressionType: 'SIMPLE', column: { column_name: 'sales' }, aggregate: 'SUM' }","handlingStrategy":"type-guard","validationCode":"VALID_EXPRESSION_TYPES = {\"SIMPLE\", \"SQL\"}\n\ndef validate_metric_shape(metric: dict) -> None:\n    et = metric.get(\"expressionType\")\n    if et not in VALID_EXPRESSION_TYPES:\n        raise ValueError(f\"expressionType must be one of {VALID_EXPRESSION_TYPES}, got {et!r}\")","typeGuard":"from typing import TypedDict, Literal, NotRequired\n\nclass AdhocMetric(TypedDict):\n    expressionType: Literal[\"SIMPLE\", \"SQL\"]\n    column: NotRequired[dict]\n    aggregate: NotRequired[str]\n    sqlExpression: NotRequired[str]\n\ndef is_adhoc_metric_typed(metric: object) -> bool:\n    return (\n        isinstance(metric, dict)\n        and metric.get(\"expressionType\") in {\"SIMPLE\", \"SQL\"}\n    )","tryCatchPattern":"from superset.exceptions import QueryObjectValidationError\n\ntry:\n    col = table.adhoc_metric_to_sqla(metric, columns_by_name)\nexcept QueryObjectValidationError as ex:\n    if \"expressionType is invalid\" in str(ex):\n        metric = {**metric, \"expressionType\": metric.get(\"expressionType\", \"\").upper()}\n        col = table.adhoc_metric_to_sqla(metric, columns_by_name)\n    else:\n        raise","preventionTips":["Use the AdhocMetricExpressionType enum values verbatim; never lowercase variants.","Type chart formData metric fields in TypeScript with a discriminated union.","Re-save imported dashboards in Explore to normalize metric JSON."],"tags":["adhoc","metric","expression-type","validation"],"backgroundTag":null,"analyzedSha":"f4587218dd19d046c3e4d00063e7d27f8a2ed354","analyzedAt":"2026-08-14T22:39:27.425Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}