Skip to content

Commit f1e03a8

Browse files
committed
Include config in dbt query payload
1 parent 32c82b4 commit f1e03a8

File tree

11 files changed

+94
-84
lines changed

11 files changed

+94
-84
lines changed

sqlmesh/core/model/definition.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -595,6 +595,7 @@ def _statement_renderer(self, expression: exp.Expression) -> ExpressionRenderer:
595595
only_execution_time=False,
596596
default_catalog=self.default_catalog,
597597
model_fqn=self.fqn,
598+
raw_code=self._raw_code,
598599
)
599600
return self._statement_renderer_cache[expression_key]
600601

@@ -1305,6 +1306,10 @@ def _is_time_column_in_partitioned_by(self) -> bool:
13051306
def violated_rules_for_query(self) -> t.Dict[type[Rule], t.Any]:
13061307
return {}
13071308

1309+
@property
1310+
def _raw_code(self) -> t.Optional[str]:
1311+
return None
1312+
13081313

13091314
class SqlModel(_Model):
13101315
"""The model definition which relies on a SQL query to fetch the data.
@@ -1581,6 +1586,7 @@ def _query_renderer(self) -> QueryRenderer:
15811586
default_catalog=self.default_catalog,
15821587
quote_identifiers=not no_quote_identifiers,
15831588
optimize_query=self.optimize_query,
1589+
raw_code=self._raw_code,
15841590
)
15851591

15861592
@property
@@ -1606,6 +1612,11 @@ def violated_rules_for_query(self) -> t.Dict[type[Rule], t.Any]:
16061612
self.render_query()
16071613
return self._query_renderer._violated_rules
16081614

1615+
@property
1616+
def _raw_code(self) -> t.Optional[str]:
1617+
query = self.query
1618+
return query.name if isinstance(query, d.JinjaQuery) else None
1619+
16091620

16101621
class SeedModel(_Model):
16111622
"""The model definition which uses a pre-built static dataset to source the data from.

sqlmesh/core/renderer.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ def __init__(
5353
model_fqn: t.Optional[str] = None,
5454
normalize_identifiers: bool = True,
5555
optimize_query: t.Optional[bool] = True,
56+
raw_code: t.Optional[str] = None,
5657
):
5758
self._expression = expression
5859
self._dialect = dialect
@@ -68,6 +69,7 @@ def __init__(
6869
self._cache: t.List[t.Optional[exp.Expression]] = []
6970
self._model_fqn = model_fqn
7071
self._optimize_query_flag = optimize_query is not False
72+
self._raw_code = raw_code
7173

7274
def update_schema(self, schema: t.Dict[str, t.Any]) -> None:
7375
self.schema = d.normalize_mapping_schema(schema, dialect=self._dialect)
@@ -204,7 +206,7 @@ def _resolve_table(table: str | exp.Table) -> str:
204206
"default_catalog": self._default_catalog,
205207
"runtime_stage": runtime_stage.value,
206208
"resolve_table": _resolve_table,
207-
"raw_code": self._expression.name,
209+
"raw_code": self._raw_code,
208210
}
209211

210212
if this_model:

sqlmesh/dbt/basemodel.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -168,14 +168,6 @@ def _validate_grants(cls, v: t.Dict[str, str]) -> t.Dict[str, t.List[str]]:
168168
},
169169
}
170170

171-
@property
172-
def sql_no_config(self) -> SqlStr:
173-
return SqlStr("")
174-
175-
@property
176-
def sql_embedded_config(self) -> SqlStr:
177-
return SqlStr("")
178-
179171
@property
180172
def table_schema(self) -> str:
181173
"""

sqlmesh/dbt/loader.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ def _to_sqlmesh(config: BMC, context: DbtContext) -> Model:
138138
package_models: t.Dict[str, BaseModelConfig] = {**package.models, **package.seeds}
139139

140140
for model in package_models.values():
141-
if isinstance(model, ModelConfig) and not model.sql_no_config:
141+
if isinstance(model, ModelConfig) and not model.sql.strip():
142142
logger.info(f"Skipping empty model '{model.name}' at path '{model.path}'.")
143143
continue
144144

sqlmesh/dbt/model.py

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
)
2828
from sqlmesh.core.model.kind import SCDType2ByTimeKind, OnDestructiveChange, OnAdditiveChange
2929
from sqlmesh.dbt.basemodel import BaseModelConfig, Materialization, SnapshotStrategy
30-
from sqlmesh.dbt.common import SqlStr, extract_jinja_config, sql_str_validator
30+
from sqlmesh.dbt.common import SqlStr, sql_str_validator
3131
from sqlmesh.utils.errors import ConfigError
3232
from sqlmesh.utils.pydantic import field_validator
3333

@@ -138,10 +138,6 @@ class ModelConfig(BaseModelConfig):
138138
inserts_only: t.Optional[bool] = None
139139
incremental_predicates: t.Optional[t.List[str]] = None
140140

141-
# Private fields
142-
_sql_embedded_config: t.Optional[SqlStr] = None
143-
_sql_no_config: t.Optional[SqlStr] = None
144-
145141
_sql_validator = sql_str_validator
146142

147143
@field_validator(
@@ -432,25 +428,6 @@ def model_kind(self, context: DbtContext) -> ModelKind:
432428

433429
raise ConfigError(f"{materialization.value} materialization not supported.")
434430

435-
@property
436-
def sql_no_config(self) -> SqlStr:
437-
if self._sql_no_config is None:
438-
self._sql_no_config = SqlStr("")
439-
self._extract_sql_config()
440-
return self._sql_no_config
441-
442-
@property
443-
def sql_embedded_config(self) -> SqlStr:
444-
if self._sql_embedded_config is None:
445-
self._sql_embedded_config = SqlStr("")
446-
self._extract_sql_config()
447-
return self._sql_embedded_config
448-
449-
def _extract_sql_config(self) -> None:
450-
no_config, embedded_config = extract_jinja_config(self.sql)
451-
self._sql_no_config = SqlStr(no_config)
452-
self._sql_embedded_config = SqlStr(embedded_config)
453-
454431
def _big_query_partition_by_expr(self, context: DbtContext) -> exp.Expression:
455432
assert isinstance(self.partition_by, dict)
456433
data_type = self.partition_by["data_type"].lower()
@@ -508,7 +485,7 @@ def to_sqlmesh(
508485
) -> Model:
509486
"""Converts the dbt model into a SQLMesh model."""
510487
model_dialect = self.dialect(context)
511-
query = d.jinja_query(self.sql_no_config)
488+
query = d.jinja_query(self.sql)
512489
kind = self.model_kind(context)
513490

514491
optional_kwargs: t.Dict[str, t.Any] = {}

sqlmesh/dbt/test.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
Dependencies,
1313
GeneralConfig,
1414
SqlStr,
15-
extract_jinja_config,
1615
sql_str_validator,
1716
)
1817
from sqlmesh.utils import AttributeDict
@@ -134,9 +133,7 @@ def to_sqlmesh(self, context: DbtContext) -> Audit:
134133
}
135134
)
136135

137-
sql_no_config, _sql_config_only = extract_jinja_config(self.sql)
138-
sql_no_config = sql_no_config.replace("**_dbt_generic_test_kwargs", self._kwargs())
139-
query = d.jinja_query(sql_no_config)
136+
query = d.jinja_query(self.sql.replace("**_dbt_generic_test_kwargs", self._kwargs()))
140137

141138
skip = not self.enabled
142139
blocking = self.severity == Severity.ERROR
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
"""
2+
Warns dbt users about potential diffs due to inclusion of {{ config(...) }} blocks in model SQL.
3+
4+
Prior to this fix, SQLMesh wasn't including the {{ config(...) }} block in the model's SQL payload
5+
when processing dbt models. Now these config blocks are properly included in the raw SQL, which
6+
may cause diffs to appear for existing dbt models even though the actual SQL logic hasn't changed.
7+
8+
This is a one-time diff that will appear after upgrading, and applying a plan will resolve it.
9+
"""
10+
11+
import json
12+
13+
from sqlglot import exp
14+
15+
from sqlmesh.core.console import get_console
16+
17+
SQLMESH_DBT_PACKAGE = "sqlmesh.dbt"
18+
19+
20+
def migrate(state_sync, **kwargs): # type: ignore
21+
engine_adapter = state_sync.engine_adapter
22+
schema = state_sync.schema
23+
snapshots_table = "_snapshots"
24+
if schema:
25+
snapshots_table = f"{schema}.{snapshots_table}"
26+
27+
warning = (
28+
"SQLMesh now includes dbt's {{ config(...) }} blocks in the model's raw SQL when "
29+
"processing dbt models. This change ensures that all model attributes referenced "
30+
"in macros are properly tracked for fingerprinting. As a result, you may see diffs "
31+
"for existing dbt models even though the actual SQL logic hasn't changed. This is "
32+
"a one-time diff that will be resolved after applying a plan. Run 'sqlmesh diff prod' "
33+
"to review any changes, then apply a plan if the diffs look expected."
34+
)
35+
36+
for (snapshot,) in engine_adapter.fetchall(
37+
exp.select("snapshot").from_(snapshots_table), quote_identifiers=True
38+
):
39+
parsed_snapshot = json.loads(snapshot)
40+
node = parsed_snapshot["node"]
41+
42+
jinja_macros = node.get("jinja_macros") or {}
43+
create_builtins_module = jinja_macros.get("create_builtins_module") or ""
44+
45+
if create_builtins_module == SQLMESH_DBT_PACKAGE:
46+
get_console().log_warning(warning)
47+
return

tests/core/test_context.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1534,9 +1534,24 @@ def test_plan_enable_preview_default(sushi_context: Context, sushi_dbt_context:
15341534
assert sushi_dbt_context._plan_preview_enabled
15351535

15361536

1537-
def test_raw_code_missing_from_model_attributes(sushi_test_dbt_context: Context):
1538-
customers_model = sushi_test_dbt_context.models['"memory"."sushi"."simple_model_a"']
1539-
assert "raw_code" not in customers_model.jinja_macros.global_objs["model"] # type: ignore
1537+
@pytest.mark.slow
1538+
def test_raw_code_handling(sushi_test_dbt_context: Context):
1539+
model = sushi_test_dbt_context.models['"memory"."sushi"."model_with_raw_code"']
1540+
assert "raw_code" not in model.jinja_macros.global_objs["model"] # type: ignore
1541+
1542+
# logging "pre-hook" (in dbt_projects.yml) + the actual pre-hook in the model file
1543+
assert len(model.pre_statements) == 2
1544+
1545+
original_file_path = model.jinja_macros.global_objs["model"]["original_file_path"] # type: ignore
1546+
model_file_path = sushi_test_dbt_context.path / original_file_path
1547+
1548+
raw_code_length = len(model_file_path.read_text()) - 1
1549+
1550+
hook = model.render_pre_statements()[0]
1551+
assert (
1552+
hook.sql()
1553+
== f'''CREATE TABLE "t" AS SELECT 'Length is {raw_code_length}' AS "length_col"'''
1554+
)
15401555

15411556

15421557
def test_catalog_name_needs_to_be_quoted():

tests/dbt/test_config.py

Lines changed: 0 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -276,42 +276,6 @@ def test_singular_test_to_standalone_audit(dbt_dummy_postgres_config: PostgresCo
276276
assert standalone_audit.dialect == "bigquery"
277277

278278

279-
def test_model_config_sql_no_config():
280-
assert (
281-
ModelConfig(
282-
sql="""{{
283-
config(
284-
materialized='table',
285-
incremental_strategy='delete+"insert'
286-
)
287-
}}
288-
query"""
289-
).sql_no_config.strip()
290-
== "query"
291-
)
292-
293-
assert (
294-
ModelConfig(
295-
sql="""{{
296-
config(
297-
materialized='table',
298-
incremental_strategy='delete+insert',
299-
post_hook=" '{{ var('new') }}' "
300-
)
301-
}}
302-
query"""
303-
).sql_no_config.strip()
304-
== "query"
305-
)
306-
307-
assert (
308-
ModelConfig(
309-
sql="""before {{config(materialized='table', post_hook=" {{ var('new') }} ")}} after"""
310-
).sql_no_config.strip()
311-
== "before after"
312-
)
313-
314-
315279
@pytest.mark.slow
316280
def test_variables(assert_exp_eq, sushi_test_project):
317281
# Case 1: using an undefined variable without a default value
@@ -350,7 +314,6 @@ def test_variables(assert_exp_eq, sushi_test_project):
350314

351315
# Case 3: using a defined variable with a default value
352316
model_config.sql = "SELECT {{ var('foo', 5) }}"
353-
model_config._sql_no_config = None
354317

355318
assert_exp_eq(model_config.to_sqlmesh(**kwargs).render_query(), 'SELECT 6 AS "6"')
356319

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{{
2+
config(
3+
pre_hook=['CREATE TABLE t AS SELECT \'Length is {{ model.raw_code|length }}\' AS length_col']
4+
)
5+
}}
6+
7+
{{ check_model_is_table(model) }}
8+
{{ check_model_is_table_alt(model) }}
9+
10+
SELECT
11+
1 AS c

0 commit comments

Comments
 (0)