{"record":{"id":"85879a085ecc2a66","repo":"apache/superset","slug":"adhoc-metric-sql-expression-is-invalid","errorCode":null,"errorMessage":"Adhoc metric SQL expression is invalid","messagePattern":"Adhoc metric SQL expression is invalid","errorType":"validation","errorClass":"QueryObjectValidationError","httpStatus":400,"severity":"error","filePath":"superset/connectors/sqla/models.py","lineNumber":1913,"sourceCode":"            if (\n                not isinstance(aggregate, str)\n                or aggregate not in self.sqla_aggregations\n            ):\n                raise QueryObjectValidationError(_(\"Adhoc metric aggregate is invalid\"))\n            metric_column = metric.get(\"column\") or {}\n            column_name = cast(str, metric_column.get(\"column_name\"))\n            table_column: TableColumn | None = columns_by_name.get(column_name)\n            if table_column:\n                sqla_column = table_column.get_sqla_col(\n                    template_processor=template_processor\n                )\n            else:\n                sqla_column = column(column_name)\n            sqla_metric = self.sqla_aggregations[aggregate](sqla_column)\n        elif expression_type == utils.AdhocMetricExpressionType.SQL:\n            expression: str | None = metric.get(\"sqlExpression\")\n            if not isinstance(expression, str) or not expression.strip():\n                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\")","sourceCodeStart":1895,"sourceCodeEnd":1931,"githubUrl":"https://github.com/apache/superset/blob/f4587218dd19d046c3e4d00063e7d27f8a2ed354/superset/connectors/sqla/models.py#L1895-L1931","documentation":"QueryObjectValidationError raised in SqlaTable.adhoc_metric_to_sqla (models.py:1913) when an adhoc metric with expressionType 'SQL' has a sqlExpression that is not a non-empty string after strip. The SQL expression is the entire definition of such a metric, so blank/missing/whitespace-only values are rejected before Jinja processing and security validation.","triggerScenarios":"Chart payload with {expressionType:'SQL', sqlExpression:''} or sqlExpression: null/undefined, or a string of only spaces. Typical when the metric JSON was constructed programmatically or the UI saved an unfinished custom-SQL metric.","commonSituations":"Programmatic chart creation copying a template metric without filling sqlExpression; frontend state desync leaving the custom SQL box empty; migration/import of dashboards losing the expression field.","solutions":["Provide a non-empty SQL expression for the metric, e.g. 'SUM(sales)'.","If the metric was meant to be a simple aggregation, switch expressionType to 'SIMPLE' with column+aggregate instead of an empty SQL expression.","Re-open the chart in Explore and re-enter the custom SQL, then save to regenerate clean JSON."],"exampleFix":"// before\n{ expressionType: 'SQL', sqlExpression: '   ' }\n\n// after\n{ expressionType: 'SQL', sqlExpression: 'SUM(sales)' }","handlingStrategy":"type-guard","validationCode":"def validate_sql_metric(metric: dict) -> None:\n    if metric.get(\"expressionType\") == \"SQL\":\n        expr = metric.get(\"sqlExpression\")\n        if not isinstance(expr, str) or not expr.strip():\n            raise ValueError(\"expressionType 'SQL' requires a non-empty sqlExpression\")","typeGuard":"def is_valid_sql_metric(metric: dict) -> bool:\n    return (\n        metric.get(\"expressionType\") == \"SQL\"\n        and isinstance(metric.get(\"sqlExpression\"), str)\n        and bool(metric[\"sqlExpression\"].strip())\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 \"SQL expression is invalid\" in str(ex):\n        raise ValueError(\"Metric is missing its custom SQL; re-add it in Explore\") from ex\n    raise","preventionTips":["Require sqlExpression in form validation before a metric can be saved.","Diff dashboard import/export payloads for metrics with empty sqlExpression.","Never construct adhoc SQL metrics by hand without the expression string."],"tags":["adhoc","metric","sql-expression","validation"],"backgroundTag":null,"analyzedSha":"f4587218dd19d046c3e4d00063e7d27f8a2ed354","analyzedAt":"2026-08-14T22:39:27.425Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}