{"record":{"id":"decf3589a147e7bc","repo":"apache/superset","slug":"custom-sql-fields-cannot-contain-set-operations","errorCode":null,"errorMessage":"Custom SQL fields cannot contain set operations.","messagePattern":"Custom SQL fields cannot contain set operations\\.","errorType":"http","errorClass":"SupersetSecurityException","httpStatus":403,"severity":"error","filePath":"superset/connectors/sqla/models.py","lineNumber":1015,"sourceCode":"    wrapped = f\"SELECT {skeleton}\"\n\n    try:\n        parsed = SQLStatement(wrapped, engine)\n    except SupersetParseError as ex:\n        if contains_jinja:\n            return\n        raise SupersetSecurityException(\n            SupersetError(\n                error_type=SupersetErrorType.ADHOC_SUBQUERY_NOT_ALLOWED_ERROR,\n                message=_(\n                    \"Custom SQL fields cannot be parsed as a single SQL statement.\"\n                ),\n                level=ErrorLevel.ERROR,\n            )\n        ) from ex\n\n    if parsed.is_set_operation():\n        raise SupersetSecurityException(\n            SupersetError(\n                error_type=SupersetErrorType.ADHOC_SUBQUERY_NOT_ALLOWED_ERROR,\n                message=_(\"Custom SQL fields cannot contain set operations.\"),\n                level=ErrorLevel.ERROR,\n            )\n        )\n\n    validate_adhoc_subquery(\n        wrapped,\n        database,\n        catalog,\n        schema or \"\",\n        engine,\n    )\n    sanitize_clause(wrapped, engine)\n\n\nclass TableColumn(AuditMixinNullable, ImportExportMixin, CertificationMixin, Model):","sourceCodeStart":997,"sourceCodeEnd":1033,"githubUrl":"https://github.com/apache/superset/blob/f4587218dd19d046c3e4d00063e7d27f8a2ed354/superset/connectors/sqla/models.py#L997-L1033","documentation":"SupersetSecurityException (ADHOC_SUBQUERY_NOT_ALLOWED_ERROR) raised in validate_adhoc_subquery (models.py:1015) when sqlglot's is_set_operation() reports that the wrapped expression contains a set operation (UNION/INTERSECT/EXCEPT [ALL]). Custom SQL fields for metrics/columns/filters must be single scalar expressions; set operations would allow smuggling subquery-like behavior, so Superset blocks them outright.","triggerScenarios":"Putting e.g. 'SELECT a FROM t UNION SELECT b FROM t2' or an expression whose parse tree contains UNION/INTERSECT/EXCEPT into a Custom SQL metric, column, or filter value field in Explore or the dataset editor.","commonSituations":"Users pasting union-based queries into adhoc metric SQL; attempts to simulate subqueries via set operators; Jinja that renders to SQL containing UNION (Jinja skeletons replace {% %}/{{ }} blocks, but fully rendered UNIONs are caught).","solutions":["Remove the set operation from the custom SQL field; compute the unioned data in a virtual dataset (its SQL supports full queries) and reference that dataset instead.","Restructure the metric as a single expression, e.g. use SUM/CASE aggregation over joined rows rather than UNION.","If you genuinely need set-operation SQL, put it in the dataset's virtual SQL or a derived view in the database, not in an adhoc field."],"exampleFix":"-- before (adhoc metric SQL — rejected)\nSELECT SUM(a) FROM t1 UNION SELECT SUM(b) FROM t2\n\n-- after\n-- create a virtual dataset with the union, then use a simple metric on it\nSUM(value)  -- on dataset: SELECT a AS value FROM t1 UNION ALL SELECT b FROM t1","handlingStrategy":"validation","validationCode":"from superset.db_engine_specs.presto import SQLStatement # illustrative\nfrom sqlglot import parse_one\n\ndef contains_set_operation(expr: str) -> bool:\n    tree = parse_one(f\"SELECT {expr}\")\n    return any(node.key in {\"union\", \"intersect\", \"except\", \"union_all\", \"union_distinct\"} for node in tree.walk())","typeGuard":null,"tryCatchPattern":"from superset.exceptions import SupersetSecurityException\n\ntry:\n    validate_adhoc_subquery(expr, database, catalog, schema)\nexcept SupersetSecurityException as ex:\n    if \"set operations\" in str(ex):\n        guide_user_to_virtual_dataset()\n    raise","preventionTips":["Never put UNION/INTERSECT/EXCEPT in adhoc fields; move that SQL into a virtual dataset.","Teach users the distinction: adhoc field = scalar expression, virtual dataset = full query.","CI-lint stored expressions for set-operation tokens."],"tags":["adhoc","sql","security","set-operations","validation"],"backgroundTag":null,"analyzedSha":"f4587218dd19d046c3e4d00063e7d27f8a2ed354","analyzedAt":"2026-08-14T22:39:27.425Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}