Skip to content

Rework SQLMesh templater and add SM01-03 linting rules#7722

Open
barradasCouto wants to merge 5 commits into
sqlfluff:mainfrom
barradasCouto:sqlmesh-linter
Open

Rework SQLMesh templater and add SM01-03 linting rules#7722
barradasCouto wants to merge 5 commits into
sqlfluff:mainfrom
barradasCouto:sqlmesh-linter

Conversation

@barradasCouto
Copy link
Copy Markdown

Brief summary of the change made

Templater: Replaces _build_source_mapping, _find_model_block_end, and _coalesce_diff_opcodes with Context.render() + slice_file(). Sets templates_in_worker=False for deterministic context lifecycle. Adds proper teardown via _update_runtime_context() / _clear_cached_sqlmesh_context(). Lazy %s logging, narrowed exceptions, full type annotations.

Rules (new):

  • SM01 – Forbid 1=1 tautology in WHERE clauses
  • SM02 – Require explicit CAST for aliased SELECT expressions
  • SM03 – Forbid AD_HOC catalog references

Registered via get_rules() hookimpl in __init__.py.

Tests: Rewritten templater tests, full parametrized coverage for SM01–SM03, CV09 integration test, new python_model.py fixture.

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:

    • .yml rule test cases in test/fixtures/rules/std_rule_cases.
    • .sql/.yml parser test cases in test/fixtures/dialects (note YML files can be auto generated with tox -e generate-fixture-yml).
    • Full autofix test cases in test/fixtures/linter/autofix.
    • Other: plugin-scoped tests in plugins/sqlfluff-templater-sqlmesh/test/ (40 tests, all passing).
  • Added appropriate documentation for the change.

Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_HOC catalog 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.

Comment thread plugins/sqlfluff-templater-sqlmesh/sqlfluff_templater_sqlmesh/templater.py Outdated
Comment on lines +351 to +362
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,
)
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot apply changes based on this feedback

Comment thread plugins/sqlfluff-templater-sqlmesh/sqlfluff_templater_sqlmesh/templater.py Outdated
Comment on lines +39 to +56
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
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +151 to +159
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

Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot apply changes based on this feedback

Comment thread plugins/sqlfluff-templater-sqlmesh/sqlfluff_templater_sqlmesh/rules.py Outdated
Comment on lines +14 to +27
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
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot apply changes based on this feedback

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@barradasCouto I believe you need individual Copilot subscription or repo maintaner role for this

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 11, 2026

Coverage Results ✅

Name    Stmts   Miss  Cover   Missing
-------------------------------------
TOTAL   21600      0   100%

263 files skipped due to complete coverage.

@peterbud
Copy link
Copy Markdown
Contributor

Hi @barradasCouto - thanks for opening this PR and welcome.
Are you planning to address the feedback from Copilot?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants