apache/superset · warning · SupersetVirtualTableParseException

Invalid SQL: %(error)s

Error message

Invalid SQL: %(error)s

What it means

Raised by get_virtual_table_metadata() when sqlglot-based SQLScript parsing of the RENDERED SQL fails AND the original dataset SQL contained Jinja markers ({% or {{). This is the softened branch: it raises SupersetVirtualTableParseException instead of a generic DB error because, with Jinja present, parse failure is very likely a rendering artifact (e.g. an empty filter_values('x') rendering to `WHERE col IN ()`) rather than a genuine SQL defect — and the row is already persisted by UpdateDatasetCommand (see #38012).

Source

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

            raise SupersetVirtualTableParseException(
                message=_("Template processing error: %(error)s", error=str(ex)),
            ) from ex
        raise SupersetGenericDBErrorException(
            message=_("Template processing error: %(error)s", error=str(ex)),
        ) from ex
    try:
        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"),

View on GitHub (pinned to f4587218dd)

Solutions

  1. Make the template total: guard macros so output is always valid SQL, e.g. `{% set vals = filter_values('x') %}{% if vals %} WHERE col IN {{ vals }}{% endif %}`.
  2. Supply the missing template params via dataset.template_params_dict so rendering produces complete SQL.
  3. Preview the rendered SQL (SQL Lab with the same Jinja context) to see exactly what string reaches the parser; the parse error's %(error)s names the artifact.
  4. If the rendered SQL is genuinely invalid even with params bound, fix the SQL body itself.

Example fix

-- before
SELECT * FROM orders WHERE region IN {{ filter_values('region') }}

-- after
{% set regions = filter_values('region') %}
SELECT * FROM orders
{% if regions %} WHERE region IN {{ regions }}{% endif %}
Defensive patterns

Strategy: validation

Validate before calling

def renders_valid_sql(dataset) -> bool:
    try:
        rendered = dataset.get_template_processor().process_template(
            dataset.sql, **dataset.template_params_dict
        )
        SQLScript(rendered, engine=dataset.database.db_engine_spec.engine)
        return True
    except Exception:
        return False

Type guard

from superset.exceptions import SupersetVirtualTableParseException  # softened branch marker

Try / catch

try:
    get_virtual_table_metadata(dataset)
except SupersetVirtualTableParseException as ex:
    # rendering artifact: dataset row persists; log and let user supply context
    logger.warning('Dataset %s needs template context: %s', dataset.id, ex)

Prevention

When it happens

Trigger: A virtual dataset uses `{{ filter_values('x') }}` or similar macros and, at refresh/save time, the macro renders empty or partial SQL that sqlglot cannot parse; template output produces a dangling `IN ()`, a bare `AND`, or an empty string. The check is `_has_jinja_markers(original_sql)` — any {% or {{ in the saved SQL selects this branch.

Common situations: Datasets built on Jinja-templated SQL that depend on dashboard context (filter_values, url_param); refreshing such datasets from the API/UI where no filter context exists, so macros render empty; saving a dataset whose SQL only parses when a Jinja variable is bound.

Related errors


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