Rework SQLMesh templater and add SM01-03 linting rules#7722
Rework SQLMesh templater and add SM01-03 linting rules#7722barradasCouto wants to merge 5 commits into
Conversation
There was a problem hiding this comment.
Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.
There was a problem hiding this comment.
Pull request overview
This PR updates the sqlfluff-templater-sqlmesh plugin to use SQLMesh’s Context.render() + SQLFluff slicing for templated/source mapping, and introduces three SQLMesh-specific linting rules (SM01–SM03) with accompanying fixtures and integration tests.
Changes:
- Reworks the SQLMesh templater implementation and runtime context lifecycle (including deterministic
templates_in_worker=False). - Adds new rules SM01 (1=1), SM02 (CAST required for aliased select expressions), SM03 (forbid
AD_HOCcatalog references) and registers them via plugin hooks. - Expands plugin-scoped test suite with integration tests and SQLMesh fixture projects/output.
Reviewed changes
Copilot reviewed 22 out of 22 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| pyproject.toml | Adds sqlmesh keyword for package metadata discoverability. |
| plugins/sqlfluff-templater-sqlmesh/README.md | Adds plugin README entry. |
| plugins/sqlfluff-templater-sqlmesh/pyproject.toml | Bumps plugin version and declares sqlmesh dependency. |
| plugins/sqlfluff-templater-sqlmesh/sqlfluff_templater_sqlmesh/init.py | Registers templater + SM01–SM03 rules via plugin hooks. |
| plugins/sqlfluff-templater-sqlmesh/sqlfluff_templater_sqlmesh/templater.py | New templater implementation using Context.render() + slice_file() and cached context handling. |
| plugins/sqlfluff-templater-sqlmesh/sqlfluff_templater_sqlmesh/rules.py | Adds SM01–SM03 rule implementations. |
| plugins/sqlfluff-templater-sqlmesh/test/init.py | Adds test package marker. |
| plugins/sqlfluff-templater-sqlmesh/test/basic_test.py | Basic sanity tests for templater properties and version reporting. |
| plugins/sqlfluff-templater-sqlmesh/test/fixtures_test.py | Adds fixture-structure and (optional) SQLMesh render tests. |
| plugins/sqlfluff-templater-sqlmesh/test/integration_test.py | Adds integration tests for model name extraction, literal fallback, and context creation. |
| plugins/sqlfluff-templater-sqlmesh/test/rules_test.py | Adds unit tests for SM01–SM03 and rule execution with SQLMesh templating. |
| plugins/sqlfluff-templater-sqlmesh/test/templater_integration_test.py | Adds end-to-end linter/templater integration and slice integrity tests. |
| plugins/sqlfluff-templater-sqlmesh/test/fixtures/sqlmesh/.sqlfluff | Adds fixture-local SQLFluff config selecting sqlmesh templater. |
| plugins/sqlfluff-templater-sqlmesh/test/fixtures/sqlmesh/config.py | Adds SQLMesh project config fixture (python config). |
| plugins/sqlfluff-templater-sqlmesh/test/fixtures/sqlmesh/macros/custom_macros.sql | Adds SQLMesh macro fixtures used by models. |
| plugins/sqlfluff-templater-sqlmesh/test/fixtures/sqlmesh/models/simple_model.sql | Adds SQLMesh model fixture. |
| plugins/sqlfluff-templater-sqlmesh/test/fixtures/sqlmesh/models/model_with_macros.sql | Adds SQLMesh model fixture exercising macros/variables. |
| plugins/sqlfluff-templater-sqlmesh/test/fixtures/sqlmesh/models/incremental_model.sql | Adds SQLMesh incremental model fixture. |
| plugins/sqlfluff-templater-sqlmesh/test/fixtures/sqlmesh/models/python_model.py | Adds SQLMesh Python model fixture. |
| plugins/sqlfluff-templater-sqlmesh/test/fixtures/sqlmesh/templated_output/simple_model.sql | Adds expected rendered SQL fixture output. |
| plugins/sqlfluff-templater-sqlmesh/test/fixtures/sqlmesh/templated_output/model_with_macros.sql | Adds expected rendered SQL fixture output. |
| plugins/sqlfluff-templater-sqlmesh/test/fixtures/sqlmesh/templated_output/incremental_model.sql | Adds expected rendered SQL fixture output. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| except (SQLFluffSkipFile, SQLTemplaterError, ValueError) as err: | ||
| templater_logger.warning( | ||
| "Failed to create slice mapping for %s: %s. Using literal mapping.", | ||
| fname, | ||
| err, | ||
| ) | ||
| return self._create_literal_templated_file( | ||
| fname, | ||
| rendered_sql, | ||
| source_content=in_str, | ||
| was_rendered=False, | ||
| ) |
There was a problem hiding this comment.
When slice_file() fails after successful SQLMesh rendering, the fallback calls _create_literal_templated_file(..., templated_content=rendered_sql, source_content=in_str, was_rendered=False). Because source and templated content differ, treating this as a literal slice risks incorrect source↔templated position mapping and potentially unsafe/misapplied fixes. Consider marking this fallback as rendered/templated so SQLFluff won’t treat the whole file as safely literal-mappable.
| crawl_behaviour = SegmentSeekerCrawler({"comparison_operator"}) | ||
| is_fix_compatible = False | ||
|
|
||
| def _eval(self, context: RuleContext) -> Optional[LintResult]: | ||
| """Flag constant true ``1 = 1`` comparisons.""" | ||
| if context.segment.raw != "=": | ||
| return None | ||
|
|
||
| expression = next( | ||
| ( | ||
| segment | ||
| for segment in reversed(context.parent_stack) | ||
| if segment.is_type("expression") | ||
| ), | ||
| None, | ||
| ) | ||
| if expression is None: | ||
| return None |
There was a problem hiding this comment.
PR description says SM01 forbids the 1=1 tautology specifically in WHERE clauses, but the implementation crawls all comparison_operator segments and will flag 1 = 1 anywhere in an expression (e.g., JOIN conditions, CASE expressions). Either update the rule to confirm the comparison is within a where_clause (or similar) in parent_stack, or adjust the rule description/docs/tests to match the broader scope.
| if segment.get_child("wildcard_expression") is not None: | ||
| return None | ||
|
|
||
| if any(segment.recursive_crawl("cast_expression")): | ||
| return None | ||
|
|
||
| if self._has_cast_like_function(segment): | ||
| return None | ||
|
|
There was a problem hiding this comment.
SM02 currently treats an aliased select element as compliant if it contains any cast_expression anywhere within the element. This will allow cases where only a sub-expression is cast (e.g., casting an argument inside a function) rather than explicitly casting the final projected expression. If the intent is to require the outermost expression to be cast, tighten the check to validate the top-level expression before the alias is a cast (or a cast-like function wrapping the expression).
| def execute( | ||
| context: ExecutionContext, | ||
| start: t.Optional[str] = None, | ||
| end: t.Optional[str] = None, | ||
| **kwargs: t.Any, | ||
| ) -> t.Dict[str, t.Any]: | ||
| """Execute the Python model.""" | ||
| # Fetch data from upstream model | ||
| df = context.fetchdf("SELECT * FROM source_table") | ||
|
|
||
| # Add computed column | ||
| df["computed_value"] = df["id"] * 2.5 | ||
|
|
||
| return df |
There was a problem hiding this comment.
The execute() function return type annotation doesn’t match what the function returns: it is annotated as Dict[str, Any] but returns df (a DataFrame from context.fetchdf()). Update the annotation (and/or the return value) so the fixture is internally consistent and matches SQLMesh’s expected Python model contract.
There was a problem hiding this comment.
@barradasCouto I believe you need individual Copilot subscription or repo maintaner role for this
Coverage Results ✅ |
|
Hi @barradasCouto - thanks for opening this PR and welcome. |
e83d31b to
9ea3864
Compare
Brief summary of the change made
Templater: Replaces
_build_source_mapping,_find_model_block_end, and_coalesce_diff_opcodeswithContext.render()+slice_file(). Setstemplates_in_worker=Falsefor deterministic context lifecycle. Adds proper teardown via_update_runtime_context()/_clear_cached_sqlmesh_context(). Lazy%slogging, narrowed exceptions, full type annotations.Rules (new):
1=1tautology in WHERE clausesAD_HOCcatalog referencesRegistered via
get_rules()hookimpl in__init__.py.Tests: Rewritten templater tests, full parametrized coverage for SM01–SM03, CV09 integration test, new
python_model.pyfixture.Bumps plugin version 3.5.0 → 4.1.0.
Are there any other side effects of this change that we should be aware of?
None. All changes are scoped to the
plugins/sqlfluff-templater-sqlmesh/directory.Pull Request checklist
Please confirm you have completed any of the necessary steps below.
Included test cases to demonstrate any code changes, which may be one or more of the following:
.ymlrule test cases intest/fixtures/rules/std_rule_cases..sql/.ymlparser test cases intest/fixtures/dialects(note YML files can be auto generated withtox -e generate-fixture-yml).test/fixtures/linter/autofix.plugins/sqlfluff-templater-sqlmesh/test/(40 tests, all passing).Added appropriate documentation for the change.