Skip to content

Bug: @resolve_template fails in audits on INCREMENTAL_BY_TIME_RANGE models (this_model is a Subquery) #5927

Description

@nickmuoh

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

  1. Create an INCREMENTAL_BY_TIME_RANGE model with a time_column
  2. 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;
  1. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions