Skip to content

Commit d8a11f1

Browse files
committed
Fix: disable query validations for dbt models
1 parent cef3859 commit d8a11f1

File tree

5 files changed

+79
-11
lines changed

5 files changed

+79
-11
lines changed

sqlmesh/core/context.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@
8181
from sqlmesh.core.linter.rules import BUILTIN_RULES
8282
from sqlmesh.core.macros import ExecutableOrMacro, macro
8383
from sqlmesh.core.metric import Metric, rewrite
84-
from sqlmesh.core.model import Model, update_model_schemas
84+
from sqlmesh.core.model import Model, SqlModel, update_model_schemas
8585
from sqlmesh.core.config.model import ModelDefaultsConfig
8686
from sqlmesh.core.notification_target import (
8787
NotificationEvent,
@@ -680,10 +680,13 @@ def load(self, update_schemas: bool = True) -> GenericContext[C]:
680680
cache_dir=self.cache_dir,
681681
)
682682

683-
models = self.models.values()
684-
for model in models:
685-
# The model definition can be validated correctly only after the schema is set.
686-
model.validate_definition()
683+
# The model definition can be validated correctly only after the schema is set.
684+
for model in self.models.values():
685+
if isinstance(model, SqlModel):
686+
# Validates only the model metadata; SQL validations are handled by the linter
687+
super(SqlModel, model).validate_definition() # type: ignore
688+
else:
689+
model.validate_definition()
687690

688691
duplicates = set(self._models) & set(self._standalone_audits)
689692
if duplicates:

sqlmesh/core/linter/rules/builtin.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
)
2626
from sqlmesh.core.linter.definition import RuleSet
2727
from sqlmesh.core.model import Model, SqlModel, ExternalModel
28+
from sqlmesh.utils.errors import ConfigError
2829
from sqlmesh.utils.lineage import extract_references_from_query, ExternalModelReference
2930

3031

@@ -274,4 +275,22 @@ def create_fix(self, model_name: str) -> t.Optional[Fix]:
274275
)
275276

276277

278+
class ValidateModelDefinition(Rule):
279+
"""
280+
Checks whether a model satisfies certain properties, such as (but not limited to):
281+
282+
- If SQL-based, it contains at least one projection & projection names are unique
283+
- Its kind is configured correctly (e.g., the VIEW kind is not supported for Python models)
284+
- Other metadata properties are well-formed (e.g., incremental-by-time models require a time column)
285+
"""
286+
287+
def check_model(self, model: Model) -> t.Optional[RuleViolation]:
288+
try:
289+
model.validate_definition()
290+
except ConfigError as ex:
291+
return self.violation(str(ex))
292+
293+
return None
294+
295+
277296
BUILTIN_RULES = RuleSet(subclasses(__name__, Rule, (Rule,)))

sqlmesh/core/model/definition.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1417,12 +1417,20 @@ def columns_to_types(self) -> t.Optional[t.Dict[str, exp.DataType]]:
14171417

14181418
unknown = exp.DataType.build("unknown")
14191419

1420-
self._columns_to_types = {
1420+
columns_to_types = {}
1421+
for select in query.selects:
1422+
output_name = select.output_name
1423+
1424+
# If model validation is disabled, we cannot assume that projections
1425+
# will have inferrable output names or even that they will be unique
1426+
if not output_name or output_name in columns_to_types:
1427+
return None
1428+
14211429
# copy data type because it is used in the engine to build CTAS and other queries
14221430
# this can change the parent which will mess up the diffing algo
1423-
select.output_name: (select.type or unknown).copy()
1424-
for select in query.selects
1425-
}
1431+
columns_to_types[output_name] = (select.type or unknown).copy()
1432+
1433+
self._columns_to_types = columns_to_types
14261434

14271435
if "*" in self._columns_to_types:
14281436
return None
@@ -1846,8 +1854,9 @@ def validate_definition(self) -> None:
18461854
super().validate_definition()
18471855

18481856
if self.kind and not self.kind.supports_python_models:
1849-
raise SQLMeshError(
1850-
f"Cannot create Python model '{self.name}' as the '{self.kind.name}' kind doesn't support Python models"
1857+
raise_config_error(
1858+
f"Cannot create Python model '{self.name}' as the '{self.kind.name}' kind doesn't support Python models",
1859+
self._path,
18511860
)
18521861

18531862
def render(

tests/core/test_context.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1554,6 +1554,38 @@ def test_raw_code_handling(sushi_test_dbt_context: Context):
15541554
)
15551555

15561556

1557+
@pytest.mark.slow
1558+
def test_dbt_models_are_not_validated(sushi_test_dbt_context: Context):
1559+
model = sushi_test_dbt_context.models['"memory"."sushi"."non_validated_model"']
1560+
1561+
assert model.render_query_or_raise().sql(comments=False) == 'SELECT 1 AS "c", 2 AS "c"'
1562+
assert sushi_test_dbt_context.fetchdf(
1563+
'SELECT * FROM "memory"."sushi"."non_validated_model"'
1564+
).to_dict() == {"c": {0: 1}, "c_1": {0: 2}}
1565+
1566+
# Write a new incremental model file that should fail validation
1567+
models_dir = sushi_test_dbt_context.path / "models"
1568+
incremental_model_path = models_dir / "invalid_incremental.sql"
1569+
incremental_model_content = """{{
1570+
config(
1571+
materialized='incremental',
1572+
incremental_strategy='delete+insert',
1573+
)
1574+
}}
1575+
1576+
SELECT
1577+
1 AS c"""
1578+
1579+
incremental_model_path.write_text(incremental_model_content)
1580+
1581+
# Reload the context - this should raise a validation error for the incremental model
1582+
with pytest.raises(
1583+
ConfigError,
1584+
match="Unmanaged incremental models with insert / overwrite enabled must specify the partitioned_by field",
1585+
):
1586+
Context(paths=sushi_test_dbt_context.path, config="test_config")
1587+
1588+
15571589
def test_catalog_name_needs_to_be_quoted():
15581590
config = Config(
15591591
model_defaults=ModelDefaultsConfig(dialect="duckdb"),
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{{ config(materialized='table') }}
2+
3+
SELECT
4+
1 AS c,
5+
2 AS c,

0 commit comments

Comments
 (0)