Skip to content

Commit 956e993

Browse files
authored
fix: align configured rules with selected analysis (#752)
Co-authored-by: Kevin Longe <273747162+kvlonge@users.noreply.github.com>
1 parent 0e6ed74 commit 956e993

4 files changed

Lines changed: 111 additions & 2 deletions

File tree

‎src/sqlbuild/compiler/pipeline/_helpers/analysis_selection.py‎

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,21 @@
22

33
from __future__ import annotations
44

5-
from sqlbuild.compiler.compile.models import CompileAnalysisSelection
5+
from sqlbuild.compiler.compile.models import (
6+
CompileAnalysisSelection,
7+
CompiledObjectKey,
8+
CompiledProject,
9+
)
610
from sqlbuild.compiler.discovery.models import DiscoveredProjectInputs
711
from sqlbuild.compiler.pipeline.models import CompilePipelineOptions
12+
from sqlbuild.compiler.planner.main.selection._resolve_planner_scopes import (
13+
resolve_planner_scopes,
14+
)
15+
from sqlbuild.compiler.planner.models import (
16+
PlannerPolicies,
17+
PlannerScopeResolution,
18+
PlannerSelection,
19+
)
820

921

1022
def resolve_compile_analysis_selection(
@@ -28,3 +40,30 @@ def resolve_compile_analysis_selection(
2840
auto_load_sources=options.auto_load_sources,
2941
no_cache=options.no_cache,
3042
)
43+
44+
45+
def resolve_configured_rule_selection(
46+
*,
47+
options: CompilePipelineOptions,
48+
discovered_inputs: DiscoveredProjectInputs,
49+
project: CompiledProject,
50+
) -> frozenset[CompiledObjectKey] | None:
51+
"""Align configured-rule subjects with models that received deep SQL analysis."""
52+
53+
analysis_selection: CompileAnalysisSelection | None = resolve_compile_analysis_selection(
54+
options=options,
55+
discovered_inputs=discovered_inputs,
56+
)
57+
if analysis_selection is None or (
58+
not analysis_selection.select and not analysis_selection.exclude
59+
):
60+
return None
61+
scopes: PlannerScopeResolution = resolve_planner_scopes(
62+
project=project,
63+
selection=PlannerSelection(
64+
select=analysis_selection.select,
65+
exclude=analysis_selection.exclude,
66+
),
67+
policies=PlannerPolicies(auto_load_sources=analysis_selection.auto_load_sources),
68+
)
69+
return frozenset(model.key for model in scopes.stale_warning_scope.models_by_name.values())

‎src/sqlbuild/compiler/pipeline/main/compile.py‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
from sqlbuild.compiler.graph.main._build_lineage_upstream_deps import (
2323
build_lineage_upstream_deps,
2424
)
25+
from sqlbuild.compiler.pipeline._helpers.analysis_selection import (
26+
resolve_configured_rule_selection,
27+
)
2528
from sqlbuild.compiler.pipeline._helpers.deferred_locations import (
2629
build_deferred_locations,
2730
gather_deferred_relations,
@@ -106,7 +109,11 @@ def run_compile_pipeline(
106109
config=rules_config,
107110
project_dir=project_dir,
108111
dialect=adapter.sql_analysis_dialect() or "generic",
109-
selected_keys=None,
112+
selected_keys=resolve_configured_rule_selection(
113+
options=resolved_options,
114+
discovered_inputs=discovered_inputs,
115+
project=project,
116+
),
110117
)
111118
if on_progress is not None:
112119
on_progress(

‎tests/integration/src/sqlbuild/cli/commands/main/test_rules.py‎

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1687,6 +1687,66 @@ def test_given_exception_for_other_model_when_compiling_selection_then_exception
16871687
assert payload["diagnostics"] == []
16881688

16891689

1690+
@pytest.mark.parametrize(
1691+
"test_case",
1692+
[RulesIntegrationTestCase("typed unselected model stays outside rule scope", 0, "")],
1693+
ids=lambda case: case.description,
1694+
)
1695+
def test_given_typed_unselected_model_when_planning_selection_then_rules_use_selected_scope(
1696+
test_case: RulesIntegrationTestCase,
1697+
tmp_path: Path,
1698+
capsys: pytest.CaptureFixture[str],
1699+
) -> None:
1700+
(tmp_path / "sqlbuild_project.toml").write_text(
1701+
'name = "orders"\nadapter = "duckdb"\n\n[rules]\nselect = ["SQBRCONTRACT105"]\n',
1702+
encoding="utf-8",
1703+
)
1704+
sources: Path = tmp_path / "sources"
1705+
sources.mkdir()
1706+
(sources / "customers.yml").write_text(
1707+
"""sources:
1708+
- name: raw_customers
1709+
schema: raw
1710+
table: customers
1711+
columns:
1712+
- name: customer_id
1713+
type: VARCHAR
1714+
""",
1715+
encoding="utf-8",
1716+
)
1717+
models: Path = tmp_path / "models"
1718+
models.mkdir()
1719+
(models / "orders.sql").write_text(
1720+
"MODEL (contract enforced, columns (order_id (type INTEGER)));\n"
1721+
"SELECT CAST(1 AS INTEGER) AS order_id\n",
1722+
encoding="utf-8",
1723+
)
1724+
(models / "customers.sql").write_text(
1725+
"""MODEL (
1726+
contract enforced,
1727+
columns (customer_id (type VARCHAR)),
1728+
);
1729+
1730+
WITH source_rows AS (
1731+
SELECT customer_id FROM __source("raw_customers")
1732+
), final AS (
1733+
SELECT customer_id FROM source_rows
1734+
)
1735+
SELECT customer_id FROM final
1736+
""",
1737+
encoding="utf-8",
1738+
)
1739+
1740+
exit_code: int = main(["--project-dir", str(tmp_path), "plan", "--json", "--select", "orders"])
1741+
payload: dict[str, object] = json.loads(capsys.readouterr().out)
1742+
1743+
assert exit_code == test_case.expected_exit_code
1744+
assert payload["selected_count"] == 1
1745+
assert [model["name"] for model in cast(list[dict[str, object]], payload["models"])] == [
1746+
"orders"
1747+
]
1748+
1749+
16901750
@pytest.mark.parametrize(
16911751
"test_case",
16921752
[RulesIntegrationTestCase("subject folder precedes model layer", 1, "SQBRPROJECT102")],

‎tests/unit/src/sqlbuild/compiler/pipeline/main/test_compile_phase_timings.py‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ def test_given_slow_planning_connection_when_compiling_then_direct_phases_are_di
5454
monkeypatch.setattr(compile_module, "open_connection_with_hooks", Mock(return_value=object()))
5555
monkeypatch.setattr(compile_module, "_build_result", Mock(return_value=pipeline_result))
5656
monkeypatch.setattr(compile_module, "load_rules_config", Mock(return_value=RulesConfig()))
57+
monkeypatch.setattr(
58+
compile_module, "resolve_configured_rule_selection", Mock(return_value=None)
59+
)
5760
monkeypatch.setattr(
5861
compile_module,
5962
"run_rules",

0 commit comments

Comments
 (0)