Skip to content

Commit f7c1e76

Browse files
authored
feat(rules): run SQL Rules through the SQLClean engine (#1283)
* feat(rules): run SQL Rules through the SQLClean engine (work in progress) Checkpoint of the SQLClean integration: SQLCLEAN Rule codes, default rule selection with the human-only notice, shared expansion inputs for early lint, and SQL Rules skipped under --no-sql-analysis. e2e fixture classification and Fensu faults are still open. * feat(rules): finish the SQLClean engine integration Pin sqlclean to v0.4.1, lint with the compile target and vars, skip audit and SQL test files that opt out of SQL analysis, skip the row-rule fact build for SQL-only selections, align fixtures and DuckShop with the new findings, and update the docs. * fix(rules): lint with the compiled target and vars, keep format fix eligibility SQL Rules now expand with the compiled project effective target and vars on every compile path instead of falling back to the default target when the early-lint reuse is missing. Commands without target flags use the project default explicitly. Restore the eight-code format --fix allowlist and its documented decisions, and cover --target and --vars end to end. * test(compile): split perf guards into default Rules and compiler-only Default SQL Rules lint every compiled model, a measured 1.14x to 1.19x cold wall cost over select = []. Compiler-only cases keep the previous budgets and the 1.10 same-runner ratio; default-Rules cases scale the cold budgets by the measured cost. Also pin sqlclean to the PIVOT USING fix and keep two fixtures' derived tables out of SQLCLEAN039. * test(compile): keep compiler-only ratios valid after default Rules land Pass compiler_only to the base generator when the base supports it, so the compiler-only job keeps both sides without Rules once main runs them. Mark the default-Rules cold allowance as temporary, reject --compiler-only with the dense project at parse time, assert that default-Rules guard cases run Rules, and pin sqlclean to the v0.4.2 release commit. * test(compile): split the 5,000- and 10,000-model guards for default Rules Default SQL Rules cost 1.09x on the 10,000-model compile and 1.20x and 1.25x on the 5,000- and 10,000-model cache guards' cold runs, measured against select = [] on the same machine. Scale those cold budgets and keep the previous budgets as compiler-only cases, as for 3,000 models. * test(compile): keep one compiler-only guard and calibrate Rules budgets on CI Replace the compiler-only twin at every guard size with one paired guard: the 3,000-model cache guard runs default Rules and select = [] in the same job and covers cold, warm, edit and project-config edit compiles at the previous budgets. Drop the compiler-only same-runner ratio job and the --compiler-only option of the ratio script. Scale each default Rules budget by the larger of the CI same-job and local alternating medians, excluding pairs whose Rules-independent phases exceed twice their median. Give the project-config edit its own budget, since it reruns every rule. * docs(compile): correct the 5,000-model project-config budget and cold budget scope The stated method gives 1.24x for the 5,000-model project-config edit, so its budget is 18.0 s. The cold budget bounds the cold run as well as the cache-disabled timeout.
1 parent c2ccbad commit f7c1e76

296 files changed

Lines changed: 2225 additions & 22938 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/workflows/verify.yml‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -671,8 +671,15 @@ jobs:
671671
max_ratio: "1.10"
672672
edit_max_ratio: "1.20"
673673
edit_max_cpu_ratio: "1.75"
674+
# Temporary migration allowance; restore 1.10 once the base also runs default Rules.
675+
# Default SQL Rules add a cold cost over select = [] (1.19x to 1.20x wall, 1.32x to
676+
# 1.33x CPU in this job), so only the cold limits scale; warm and edit keep 1.10.
677+
# The compiler-only cache guard at 3,000 models keeps the select = [] budgets.
674678
- kind: fresh
679+
label: fresh default Rules
675680
max_ratio: "1.10"
681+
cold_max_ratio: "1.25"
682+
cold_max_cpu_ratio: "1.41"
676683
- kind: dense
677684
label: contracts CPU vs base
678685
gate_phase: contracts_cpu_ms
@@ -746,12 +753,16 @@ jobs:
746753
env:
747754
KIND: ${{ matrix.kind }}
748755
MAX_RATIO: ${{ matrix.max_ratio }}
756+
COLD_MAX_RATIO: ${{ matrix.cold_max_ratio }}
757+
COLD_MAX_CPU_RATIO: ${{ matrix.cold_max_cpu_ratio }}
749758
EDIT_MAX_RATIO: ${{ matrix.edit_max_ratio }}
750759
EDIT_MAX_CPU_RATIO: ${{ matrix.edit_max_cpu_ratio }}
751760
run: |
752761
uv run python -m scripts.compare_compile_performance \
753762
--kind "${KIND}" \
754763
--modes cold warm edit \
764+
${COLD_MAX_RATIO:+--cold-max-ratio "$COLD_MAX_RATIO"} \
765+
${COLD_MAX_CPU_RATIO:+--cold-max-cpu-ratio "$COLD_MAX_CPU_RATIO"} \
755766
${EDIT_MAX_RATIO:+--edit-max-ratio "$EDIT_MAX_RATIO"} \
756767
${EDIT_MAX_CPU_RATIO:+--edit-max-cpu-ratio "$EDIT_MAX_CPU_RATIO"} \
757768
--max-ratio "${MAX_RATIO}" \

‎Cargo.lock‎

Lines changed: 17 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Cargo.toml‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,8 @@ sqlbuild-recall = { path = "crates/sqlbuild-recall" }
139139
sqlbuild-scopes = { path = "crates/sqlbuild-scopes" }
140140
sqlbuild-sqltext = { path = "crates/sqlbuild-sqltext" }
141141
sqlbuild-state = { path = "crates/sqlbuild-state" }
142+
# SQL lint Rules, quality Rules, the formatter and the canonical token stream.
143+
sqlclean = { git = "https://github.com/sqlstack-io/sqlclean.git", rev = "571e40a746b3d8d20d6118620f05428eca45243c" }
142144
stacker = "0.1"
143145
syn = { version = "2", features = ["full", "visit", "extra-traits"] }
144146
tempfile = "3"

‎benchmarks/dbt-compare/README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ average.
3434
| Python macros | Jinja macros producing the same SQL |
3535
| `audits [not_null]` | `data_tests: [not_null]` |
3636
| SQL tests with fixture CTEs | `unit_tests` with the same input and expected rows |
37-
| `[rules] select = ["SQBR", "XSQBR"]` (built-in Rules plus one custom Rule) | none |
37+
| `[rules] select = ["SQBR", "SQLCLEAN", "XSQBR"]` (built-in Rules, SQLClean Rules and one custom Rule) | none |
3838

3939
`sqlbuild-norules/` is the same project with `[rules] select = []`. That turns off configurable Rules;
4040
column binding, type inference and contract checks still run.

‎benchmarks/dbt-compare/write_dbt_dense.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ def write_norules(project: Path) -> Path:
6666
shutil.copytree(project, target)
6767
toml = target / "sqlbuild_project.toml"
6868
text = toml.read_text()
69-
new = text.replace('select = ["SQBR", "XSQBR"]', "select = []")
69+
new = text.replace('select = ["SQBR", "SQLCLEAN", "XSQBR"]', "select = []")
7070
assert new != text
7171
toml.write_text(new)
7272
return target
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
generator 2 files 10874 sha256 7dd651a573bce90f9816889cdeece75b9f837137813a033125daafa28b7b70eb
1+
generator 3 files 10874 sha256 5f3d30b82ebf52f2eb869494f52f77d1699f9a08743febd6ee9ca0cb22ed0611

‎benchmarks/duckshop-10k/duckshop10k/__init__.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@
44
regenerate checksum.txt, whenever a change alters any generated file.
55
"""
66

7-
GENERATOR_VERSION = "2"
7+
GENERATOR_VERSION = "3"
88
SEED = 20261024
99
PROJECT_NAME = "duckshop_10k"

‎benchmarks/duckshop-10k/duckshop10k/declarations.py‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -321,23 +321,23 @@ def union_relations(relations: list[SqlResourceRef], columns: list[str]) -> str:
321321

322322
MACRO_EXCEPTIONS: tuple[tuple[str, str, str], ...] = (
323323
(
324-
"SQBRSQL018",
324+
"SQLCLEAN018",
325325
"cdc_latest",
326326
"""
327327
The macro cannot see its callers' keys on its own. Every call site is still checked, against the
328328
unique, non-null _seq that each change-log source declares.
329329
""",
330330
),
331331
(
332-
"SQBRSQL018",
332+
"SQLCLEAN018",
333333
"cdc_history",
334334
"""
335335
The macro cannot see its callers' keys on its own. _seq is unique and non-null in every change-log
336336
source, so ordering by it within a key and load is deterministic.
337337
""",
338338
),
339339
(
340-
"SQBRSQL034",
340+
"SQLCLEAN034",
341341
"load_metadata",
342342
"""
343343
A column-list macro body is a select list by construction. Its columns are inserted into the

‎benchmarks/duckshop-10k/duckshop10k/project.py‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -192,12 +192,12 @@ def project_toml() -> str:
192192
lookup_customer_id = "C000001"
193193
194194
[rules]
195-
select = ["SQBRSQL", "SQBRGRAPH", "SQBRCONTRACT", "SQBRTEST", "XSQBRDUCK"]
196-
# SQBRSQL021: `SELECT * FROM final` and the change-log macro's pass-through CTEs are the house
195+
select = ["SQLCLEAN", "SQBRGRAPH", "SQBRCONTRACT", "SQBRTEST", "XSQBRDUCK"]
196+
# SQLCLEAN021: `SELECT * FROM final` and the change-log macro's pass-through CTEs are the house
197197
# style; each model's columns block, not its terminal SELECT, owns the output shape.
198198
# SQBRCONTRACT101: exact contracts are reserved for published dim_ and fct_ models.
199199
# SQBRTEST202: a sample of models carries unit tests, as in a project of this size.
200-
ignore = ["SQBRSQL021", "SQBRCONTRACT101", "SQBRTEST202"]
200+
ignore = ["SQLCLEAN021", "SQBRCONTRACT101", "SQBRTEST202"]
201201
'''
202202

203203

‎benchmarks/duckshop/README.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ The core project, without the [edge-case area](#edge-cases):
4848
- 5 seeds, 2 SQL functions (a scalar UDF and a table function) and a Python UDF.
4949
- SQL and Python macros, folder-scoped constants and enums, generic and singular audits, model
5050
schemas, hooks, incremental models with cursor windows, and snapshots with both strategies.
51-
- Rules select `SQBRSQL`, `SQBRGRAPH`, `SQBRCONTRACT`, `SQBRTEST` and the project's own
51+
- Rules select `SQLCLEAN`, `SQBRGRAPH`, `SQBRCONTRACT`, `SQBRTEST` and the project's own
5252
`XSQBRDUCK` rules, with reviewed exceptions in `sqlbuild_project.toml`. Every model must have at least one unit test (`SQBRTEST202`).
5353

5454
## Tests and scenarios
@@ -225,7 +225,7 @@ noted here:
225225
static types of the columns it reads from a CTE through a table alias (`FROM listings AS l`), so
226226
`rpt_edge_partner_listing_export` declares only the name and audits of `"listingId"`, without
227227
types.
228-
- `SQBRSQL018` can only take proof from single-column keys. Append-only feeds redeliver rows, so
228+
- `SQLCLEAN018` can only take proof from single-column keys. Append-only feeds redeliver rows, so
229229
their id is unique only together with `_loaded_at` (the `unique_per_delivery` audit), and the
230230
staging models that keep the latest delivery carry reviewed exceptions.
231231

0 commit comments

Comments
 (0)