Summary
@resolve_template raises AttributeError: 'Subquery' object has no attribute 'catalog' when used in an audit attached to an INCREMENTAL_BY_TIME_RANGE model. The root cause is not in the audit itself but in how this_model is passed to the macro evaluator during the audit rendering phase for incremental models.
Root Cause
In sqlmesh/core/model/definition.py (render_audit_query), when the model has a time_column, this_model is set to a Subquery:
"this_model": exp.select("*").from_(quoted_model_name).where(where).subquery()
if where is not None
else quoted_model_name,
This means for any model with a time_column (i.e. all INCREMENTAL_BY_TIME_RANGE models), this_model in evaluator.locals is always a Subquery, not a Table.
Then in sqlmesh/core/macros.py (resolve_template), the macro assumes this_model is always convertible to a Table:
this_model = exp.to_table(evaluator.locals["this_model"], dialect=evaluator.dialect)
template_str: str = template.this
result = (
template_str.replace("@{catalog_name}", this_model.catalog)
.replace("@{schema_name}", this_model.db)
.replace("@{table_name}", this_model.name)
)
exp.to_table() on a Subquery does not produce an object with .catalog/.db/.name attributes, so it raises:
AttributeError: 'Subquery' object has no attribute 'catalog'
How to Reproduce
- Create an
INCREMENTAL_BY_TIME_RANGE model with a time_column
- Attach an audit that uses
@resolve_template with mode := 'table'
-- Model
MODEL (
name my_schema.my_model,
kind INCREMENTAL_BY_TIME_RANGE (
time_column (event_date, '%Y-%m-%d')
),
audits (my_audit),
);
SELECT event_date, app_name, user_id FROM upstream.source
WHERE event_date BETWEEN @start_date AND @end_date;
-- Audit
AUDIT (name my_audit, dialect snowflake);
SELECT app_name, COUNT(*) as row_count
FROM @resolve_template('@{catalog_name}.@{schema_name}.@{table_name}', mode := 'table')
WHERE event_date >= DATEADD('day', -30, @end_date::date)
GROUP BY app_name
HAVING row_count = 0;
- Run
sqlmesh run — the audit fails with the AttributeError.
Use Case
@resolve_template(..., mode := 'table') is useful in audits that need to query a broader time range than the current batch. For example, an audit that checks "no app has gone completely silent in the last 30 days" needs access to the full materialized table — not just the current 7-day batch that @this_model provides. The mode := 'table' parameter was designed for exactly this purpose, but it doesn't work in the audit phase for incremental models.
Suggested Fix
@resolve_template should handle the case where this_model is a Subquery by extracting the underlying Table from it. For example:
this_model_expr = evaluator.locals["this_model"]
if isinstance(this_model_expr, exp.Subquery):
# Extract the Table from: (SELECT * FROM <table> WHERE ...) AS _q_0
table = this_model_expr.find(exp.Table)
else:
table = exp.to_table(this_model_expr, dialect=evaluator.dialect)
Alternatively, render_audit_query could pass the raw table name as a separate key (e.g., this_model_table) alongside the subquery version so that macros like @resolve_template can access it without needing to unwrap the subquery.
Related
Environment
- SQLMesh version: 0.236.0
- Dialect: Snowflake
Summary
@resolve_templateraisesAttributeError: 'Subquery' object has no attribute 'catalog'when used in an audit attached to anINCREMENTAL_BY_TIME_RANGEmodel. The root cause is not in the audit itself but in howthis_modelis passed to the macro evaluator during the audit rendering phase for incremental models.Root Cause
In
sqlmesh/core/model/definition.py(render_audit_query), when the model has atime_column,this_modelis set to aSubquery:This means for any model with a
time_column(i.e. allINCREMENTAL_BY_TIME_RANGEmodels),this_modelinevaluator.localsis always aSubquery, not aTable.Then in
sqlmesh/core/macros.py(resolve_template), the macro assumesthis_modelis always convertible to aTable:exp.to_table()on aSubquerydoes not produce an object with.catalog/.db/.nameattributes, so it raises:How to Reproduce
INCREMENTAL_BY_TIME_RANGEmodel with atime_column@resolve_templatewithmode := 'table'sqlmesh run— the audit fails with theAttributeError.Use Case
@resolve_template(..., mode := 'table')is useful in audits that need to query a broader time range than the current batch. For example, an audit that checks "no app has gone completely silent in the last 30 days" needs access to the full materialized table — not just the current 7-day batch that@this_modelprovides. Themode := 'table'parameter was designed for exactly this purpose, but it doesn't work in the audit phase for incremental models.Suggested Fix
@resolve_templateshould handle the case wherethis_modelis aSubqueryby extracting the underlyingTablefrom it. For example:Alternatively,
render_audit_querycould pass the raw table name as a separate key (e.g.,this_model_table) alongside the subquery version so that macros like@resolve_templatecan access it without needing to unwrap the subquery.Related
where is Nonebranch (when there's notime_column), but thewhere is not Nonepath still produces the Subquery that breaks@resolve_template.Environment