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
- 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 %}`.
- Supply the missing template params via dataset.template_params_dict so rendering produces complete SQL.
- 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.
- 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
- Wrap every filter_values/url_param macro in {% if %} guards so output is valid SQL with or without context.
- Bind template_params on the dataset instead of relying on dashboard context at refresh time.
- Treat SupersetVirtualTableParseException as 'needs context', not 'broken SQL' — don't delete the dataset.
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
- Error in jinja expression in column expression: %(msg)s
- Error in jinja expression in datetime column: %(msg)s
- Error in jinja expression in metric expression: %(msg)s
- Fetch values predicate failed SQL validation: %(msg)s
- Only `SELECT` statements are allowed
AI-assisted analysis of apache/superset@f4587218dd (2026-08-14).
Data as JSON: /api/errors/205d58da0ef2b1f5.
Report an issue: GitHub.