SqlNullComparedWithEquality
warning native on by defaultGroup: Probable bugs
SQL equality comparisons do not test whether a value is null. Use IS NULL or IS NOT NULL instead.
Example
php
<?php
function bad(PDO $p){$p->query("SELECT * FROM t WHERE x = NULL");$p->exec("UPDATE t SET x=1 WHERE NULL = y");}Reported:
- line 2: Use IS NULL or IS NOT NULL for SQL null checks.
Detection
- Resolved PDO query/exec/prepare or mysqli query/prepare receives literal SQL containing unquoted = NULL, <> NULL, != NULL or NULL = expression inside a WHERE, HAVING or JOIN ON predicate, outside comments. Exclude UPDATE SET assignments, INSERT values, column defaults and expressions outside a proven predicate context. Highlight entire SQL argument. SQL lexer distinguishes strings/quoted identifiers/comments.
- Predicate context is tracked per parenthesis level. An opening parenthesis inherits the context of the enclosing level; clause keywords met inside the parentheses (a subquery's SELECT/FROM, the FROM of EXTRACT(... FROM ...)) only change that inner level, and the closing parenthesis restores the enclosing one. A FROM that directly follows DISTINCT belongs to the IS [NOT] DISTINCT FROM operator and does not end the predicate.
- The receiver's method is looked up without a target-version filter, so PDO::query() is recognized for every supported target version; an override declared in a user subclass is still not the builtin.
- Resolve builtin functions, classes, constants and methods; user-defined lookalikes are excluded. Follow only bounded local proof in one lexical scope.
- Emit one finding per violating operation, not per supporting evidence statement. When the proof is inconclusive, nothing is reported.
Exceptions (no report)
- Do not report unresolved or shadowed builtin names, unknown values, or incomplete receiver/type provenance. Unknown calls, aliases escaping, and intervening writes invalidate local proof. Similar code outside the stated detection contract is not a finding.
- A NULL that closes an IS NULL or IS NOT NULL operator is not an operand of a following
=:x IS NOT NULL = 1compares the boolean result and is not reported. A NULL on the right of the=is still reported (x IS NOT NULL = NULL), and so is a bareNOT NULL = exprwithout IS. - Both standard suppression forms suppress this native ID.
Fix
None. Choosing a repair requires intent or additional runtime information.
PHP versions
Available throughout supported PHP 5.3–8.5 where the referenced builtin/extension is available. Respect syntax/version-specific receiver contracts; unavailable APIs are not this rule’s concern.
Configure
In custos.json:
json
{
"rules": {
"SqlNullComparedWithEquality": {
"enabled": false
}
}
}Suppress
Before the statement or declaration (or the first statement of the file):
php
// @custos-ignore SqlNullComparedWithEquality