feat(rules): add W023 scalar-udf-in-where#34
Merged
Pawansingh3889 merged 1 commit intoPawansingh3889:mainfrom Apr 29, 2026
Merged
feat(rules): add W023 scalar-udf-in-where#34Pawansingh3889 merged 1 commit intoPawansingh3889:mainfrom
Pawansingh3889 merged 1 commit intoPawansingh3889:mainfrom
Conversation
T-SQL scalar UDFs in WHERE/HAVING/ON clauses force row-by-row evaluation and prevent index seeks. Add W023 that flags <schema>.<name>(...) calls inside predicate clauses. Built-ins (LEN, UPPER, SUBSTRING, etc.) lack a schema prefix and are not affected. Mirrors W019 / CountDistinctUnbounded in shape and test layout.
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Owner
|
Schema-prefix heuristic is the right call. Cleanly separates user UDFs from built-ins, no allow-list to maintain. Test coverage hits the cases I'd worry about: HAVING, JOIN ON, EXISTS subquery inner predicate, SELECT-list-only false positive. |
This was referenced Apr 29, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #30. Part of the v0.7 Performance Rules Pack.
Adds W023
scalar-udf-in-where, mirroring W019 /CountDistinctUnboundedin shape and test layout (the issue itself points at W019 as the canonical reference).What it catches
The rule extracts WHERE / HAVING / ON predicate bodies and looks for any
<schema>.<name>(call inside them. The schema-prefix requirement is the key heuristic: built-ins do not carry adbo./<schema>.prefix in T-SQL, soLEN,UPPER,SUBSTRING,CONVERT,YEAR, etc. naturally don't match. SELECT-list calls don't match either because the clause-body extraction excludes them.Edge cases covered (per the issue + tests)
LEN,UPPER,SUBSTRING-- no fire (no schema prefix).EXISTS (... WHERE dbo.fn_X(col) = 1)correlated subquery -- fires on inner WHERE.HAVING dbo.fn_X(col) > 0-- fires.JOIN b ON dbo.fn_X(a.id) = b.id-- fires.WHERE x.y = 1(table.column reference) -- no fire (the regex requires a trailing().Files
sql_guard/rules/warnings.py:ScalarUdfInWhereclass.sql_guard/rules/__init__.py: import +ALL_RULESregistration.tests/test_new_rules.py: 10 new tests covering positive + negative cases.tests/fixtures/warnings.sql: W023 fixture line.tests/test_rules.py: bumpedtest_all_rules_loaded(34 -> 35) andtest_24_warnings(24 -> 25).README.md: rule table row + contributors entry update.CHANGELOG.md:### Addedline under[Unreleased].Verification
pytest tests/test_new_rules.py -k w023-- 10 passed.pytest tests/test_rules.py -k "all_rules_loaded or 24_warnings"-- 2 passed.test_deep_nesting_detectedfailure intests/test_structural.pyis pre-existing onmain(verified by stashing my changes) and unrelated to this PR.Out of scope
VOLATILEfunction detection (the issue notes this as v0.8).T006if the maintainer prefers T-SQL-only rules to use aTprefix; happy to rename in a follow-up.MyFunc(col)). T-SQL convention is to prefix them; tracking the trade-off in the issue's edge-case 1 + 2 discussion.