apache/superset · error · SupersetSecurityException

Only `SELECT` statements are allowed

Error message

Only `SELECT` statements are allowed

What it means

Raised by get_virtual_table_metadata() when SQLScript.has_mutation() detects a mutating statement (INSERT/UPDATE/DELETE/CREATE/ALTER/DROP etc.) in a virtual dataset's SQL. Virtual datasets are metadata-only views; mutating SQL is rejected with a SupersetSecurityException of type DATASOURCE_SECURITY_ACCESS_ERROR before anything is executed.

Source

Thrown at superset/connectors/sqla/utils.py:154

        parsed_script = SQLScript(sql, engine=db_engine_spec.engine)
    except SupersetParseError as ex:
        # ``SQLScript`` fails on any invalid SQL, including static SQL
        # with no template dependency. Only soften when the input
        # contained Jinja markers — in that case an "Invalid SQL"
        # outcome is very likely a rendering artifact (e.g. an empty
        # ``filter_values('x')`` producing ``WHERE col IN ()``) rather
        # than a genuine defect in the user's SQL, and the row is
        # already persisted by ``UpdateDatasetCommand``. Genuinely
        # invalid static SQL must still hard-error. See #38012.
        if _has_jinja_markers(original_sql):
            raise SupersetVirtualTableParseException(
                message=_("Invalid SQL: %(error)s", error=ex.error.message),
            ) from ex
        raise SupersetGenericDBErrorException(
            message=_("Invalid SQL: %(error)s", error=ex.error.message),
        ) from ex
    if parsed_script.has_mutation():
        raise SupersetSecurityException(
            SupersetError(
                error_type=SupersetErrorType.DATASOURCE_SECURITY_ACCESS_ERROR,
                message=_("Only `SELECT` statements are allowed"),
                level=ErrorLevel.ERROR,
            )
        )
    if len(parsed_script.statements) > 1:
        raise SupersetSecurityException(
            SupersetError(
                error_type=SupersetErrorType.DATASOURCE_SECURITY_ACCESS_ERROR,
                message=_("Only single queries supported"),
                level=ErrorLevel.ERROR,
            )
        )
    return get_columns_description(
        dataset.database,
        dataset.catalog,
        dataset.schema,

View on GitHub (pinned to f4587218dd)

Solutions

  1. Make the dataset SQL a single pure SELECT; move mutations to a proper pipeline/job.
  2. If flagged falsely, rename non-mutating constructs that the parser mistakes for DML, or restructure the query (e.g. avoid a function named like an update call).
  3. Run the mutation outside Superset, then build the virtual dataset on top of the resulting table.

Example fix

-- before
DELETE FROM audit_log WHERE ts < '2020-01-01';
SELECT * FROM audit_log;

-- after
SELECT * FROM audit_log WHERE ts >= '2020-01-01'
Defensive patterns

Strategy: validation

Validate before calling

def is_single_select(sql: str, engine: str) -> bool:
    try:
        script = SQLScript(sql, engine=engine)
        return not script.has_mutation()
    except SupersetParseError:
        return False

Try / catch

try:
    get_virtual_table_metadata(dataset)
except SupersetSecurityException:
    # mutation rejected before execution: rewrite SQL as pure SELECT
    raise

Prevention

When it happens

Trigger: Saving a dataset whose SQL is `INSERT INTO ...`, `UPDATE ...`, `DELETE FROM ...`, or DDL like `CREATE TABLE ...` / `DROP VIEW ...`; also `SELECT ...; DELETE ...` where the mutation check trips on any statement. The check runs on the rendered SQL during save/refresh of the dataset.

Common situations: Trying to smuggle a mutation through the dataset layer because direct DML via SQL Lab is restricted; pasting a whole script (including cleanup DDL) into a dataset definition; a CTE or function name that sqlglot's engine dialect misclassifies as a mutation.

Related errors


AI-assisted analysis of apache/superset@f4587218dd (2026-08-14). Data as JSON: /api/errors/cb3b633ae2299c94. Report an issue: GitHub.