apache/superset · error · SupersetDMLNotAllowedException

DML_NOT_ALLOWED_ERROR

DML_NOT_ALLOWED_ERROR

Error message

This database does not allow for DDL/DML, but the query mutates data. Please contact your administrator for more assistance.

What it means

Raised in EstimateQueryCostCommand preprocessing (superset/commands/sql_lab/estimate.py:150) as SupersetDMLNotAllowedException when parsed_script.has_mutation() is true and the database record has allow_dml = false. Estimation deliberately runs the same DML guard as execution: even though estimating a mutation does not execute it, the gate blocks the request. SupersetErrorType.DML_NOT_ALLOWED_ERROR.

Solutions

  1. Only estimate SELECT queries on this database, or enable 'Allow DML' in the database settings if mutation is intended (operator decision)
  2. Move DML statements to a database connection where allow_dml is true
  3. Check GET /api/v1/database/{id} allow_dml field before offering the estimate action in tooling
Defensive patterns

Strategy: validation

Validate before calling

db = DatabaseDAO.find_by_id(database_id)
if query_mutates(sql) and not db.allow_dml:
    raise ValueError("this database disallows DDL/DML; estimate only SELECTs")

Try / catch

try:
    EstimateQueryCostCommand(params).run()
except SupersetDMLNotAllowedException:
    # route the mutation to a DML-enabled database or drop the estimate request

Prevention

When it happens

Trigger: Submitting INSERT/UPDATE/DELETE/CREATE/ALTER/TRUNCATE (or other mutating statements sqlscriptbox detects) for cost estimation on a database whose settings have 'Allow DML' unchecked.

Common situations: Users trying to preview the cost of a data-change script on a read-only connection; admins disabling DML on production databases and users not realizing the estimate path enforces the same policy.

Related errors


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

Appendix: source

Thrown at superset/commands/sql_lab/estimate.py:150

            resolved_schema = self._database.resolve_query_default_schema(
                self._sql, self._schema, catalog, self._template_params
            )
            # An explicit schema still wins for matching/RLS targeting; otherwise
            # fall back to the runtime-resolved default.
            effective_schema = self._schema or resolved_schema or ""

        if disallowed_tables:
            # Honors schema-qualified denylist entries (e.g.
            # ``information_schema.tables``) and reports only the tables
            # actually referenced by the query.
            found_tables = parsed_script.get_disallowed_tables(
                disallowed_tables, effective_schema
            )
            if found_tables:
                raise SupersetDisallowedSQLTableException(found_tables)

        if parsed_script.has_mutation() and not self._database.allow_dml:
            raise SupersetDMLNotAllowedException()

        if rls_enabled:
            for statement in parsed_script.statements:
                apply_rls(self._database, catalog, effective_schema, statement)
            return parsed_script.format()

        return sql

    def run(
        self,
    ) -> list[dict[str, Any]]:
        self.validate()

        sql = self._sql
        if self._template_params:
            # Access is already checked in validate() before any rendering.
            template_processor = get_template_processor(self._database)
            try:

View on GitHub (pinned to f4587218dd)