Skip to content

feat(rules)!: check audit, test and hook SQL and enforce SQL quality rules - #910

Merged
kvlonge merged 7 commits into
mainfrom
feat/rules-batch
Oct 2, 2026
Merged

kvlonge merged 7 commits into
mainfrom
feat/rules-batch

Conversation

@kvlonge

@kvlonge kvlonge commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

Why

SQL in audits, SQL tests and hooks was never type- or reference-checked, several Rule findings pointed at the statement start, and CHI-385 asked for enforceable SQL quality: no dead CTE columns, bounded ranking sorts, proven-deterministic rankings and no sprawling literals (CHI-459, CHI-382, CHI-413, CHI-385).

Changes

  • Breaking: the compile checks models get now run on audits (per attached model), SQL tests and hooks; parse failures there are errors. TEST/AUDIT accept sql_analysis false for unparseable SQL; --no-sql-analysis skips them.
  • Cast, literal and set-operation findings point at the offending expression; identical position-less findings are no longer merged.
  • New Rules: SQBRSQL042 unused CTE output columns (auto-fix), SQBRSQL043 ranking sort cap (max_ranking_order_by, default 5), SQBRSQL044 long literals (help leads with the narrowest constant scope and lists duplicates). SQBRSQL018 requires a proven key (tie-breaker, unique + not_null audit, or merge/cursor-less delete_insert unique_key).
  • sqb format --fix applies only row-neutral fixes, verifies each file (no new diagnostics; outputs, types, nullability, lineage and dependencies unchanged) and works on projects that already have errors.
  • [settings] require_sql_analysis: sql_analysis false only where SQL cannot be parsed.
  • Help that names a setting shows the exact TOML.

Verification

  • Unit, integration and DuckDB E2E per change, including a DuckDB execution test proving applied fixes keep row counts and values.
  • Real ~1,350-file project (scratch copy): format --fix removed ~4,800 dead lines with all 1,022 model outputs identical; second run no-op; new test/audit errors reviewed as genuine.
  • make test, make check-ci; one independent review plus a follow-up.

…ooks

Bind singular and generic audits (including measurement and evidence queries),
SQL-test bodies and inline and named SQL hooks against the same closed relation
shapes as models, so unknown columns, unknown functions (B101) and semantic type
errors fail compile instead of the warehouse. Audit and SQL-test bodies the
parser rejects are P001 syntax errors rather than silent passes.

Every finding names its owner: the SQL test, or the audit with the model and
column it is attached to. Set-operation, comparison and unknown-column findings
show the explicit fix, such as CAST('1' AS NUMBER) in a test's expected rows or
TO_DATE(@date_column) in a generic audit. Refs CHI-459.
…ejects

A DuckDB lambda compiles but the project Rules parser cannot read it; the
model is reported as rules-unevaluated, fails rules run and compile, and is
not counted as evaluated. Refs CHI-382.
…nding expression

Native cast, coercible-literal and set-operation findings carry no span, so
they landed on the statement start and identical messages were merged. Match
each position-less finding to its own cast, literal, set-operation operator or
branch projection in source order, and stop merging position-less duplicates:
two failing casts are two findings at their own casts. Refs CHI-413.
…tputs and ranking proofs

SQBRSQL042 reports non-final CTE output columns no later step reads, with a
verified removal fix. SQBRSQL043 caps ranking window sorts at
rules.thresholds.max_ranking_order_by (default 5). SQBRSQL044 reports authored
string literals longer than rules.thresholds.max_literal_length and points at a
constant in the narrowest scope; when other files repeat the identical literal
the help lists them and names the nearest common folder's constants directory.
Literals generated by @const or macros are not reported. Both threshold
remediations show the exact sqlbuild_project.toml setting and current value.
SQBRSQL018 now requires ROW_NUMBER, FIRST_VALUE and LAST_VALUE windows to cover
a declared unique key, propagated through CTEs, renames, one-to-one joins,
GROUP BY, DISTINCT, UNION and QUALIFY deduplication.

Rules findings with a fix that format --fix applies now say so: fixable: true
in rules JSON, a note in text and compile output. Refs CHI-385.
…g help

[settings] require_sql_analysis = true (project file only, default false)
accepts sql_analysis false from a MODEL header or a [path_defaults] entry only
on SQL the parser cannot read. On a parseable model it is a P009 error at the
header key or path_defaults entry that counts the findings it was hiding.
sqlbuild_local.toml cannot turn SQL analysis off project-wide under the policy
and cannot set require_sql_analysis; --no-sql-analysis still works for one run.

Every error and Rule remediation that names a setting now shows the exact
file, section and key = value to write and states the current value, and the
parser-rejection error shows the exact MODEL header entry. A catalogue scan
test keeps loosely named settings out of Python and Rust diagnostics.
… errors

format --fix required a clean compile, so on a project with any existing
compile error every fix was refused, after recompiling the whole project once
per edited file. Verification now compares the fixed compile with the baseline
file by file: an edited file may lose diagnostics but not gain any (compared by
code, resource and message), its models must keep columns, types, nullability,
dependencies and lineage (absent lineage on both sides matches), and nothing
else may change. One compile of all edits attributes failures to the file that
caused them; files are checked one by one only when a difference is
unattributable. Refusals give the specific reason. A file whose SQL cannot be
expanded is skipped with a format-fix-skipped warning instead of failing the run.
@kvlonge
kvlonge enabled auto-merge (squash) October 2, 2026 14:45
Built-in not_null and unique audits on a column their closed target shape
contains cannot produce a finding, so they skip native binding; any other
case is still bound and reported unchanged. Identical normalized SQL with the
same relations is bound once and its result shared, and SQL test bodies are
prepared once per distinct body.
@kvlonge
kvlonge merged commit 723a25a into main Oct 2, 2026
36 checks passed
@kvlonge
kvlonge deleted the feat/rules-batch branch October 2, 2026 17:07
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.

1 participant