Lint Rules #

SELECT lints your SQL as you type. Not with a list of regexes. The statement is parsed into a syntax tree and checked against the schema SELECT introspected from your database, so the linter knows that orders.custmer_id is not a column, that id is ambiguous across two joined tables, and that the CTE you renamed at the top is now unused.

Findings appear inline in the editor and in the problems list. Nothing runs against the database to produce them.

Rules that resolve names and types need the schema. If SELECT has not introspected the database yet, or the connection is closed, those rules stay quiet rather than guessing. You will still get the ones that read the statement alone.

Severities #

Severity Means
error The statement is wrong. It will fail, or it will run and not do what it says.
warning Almost certainly a mistake, but there is a reading where you meant it.
hint Style and habit. Correct SQL that the next reader will thank you for changing.

Every severity is adjustable per workspace, including down to off, in the .lint file.

Names and references #

These need the schema.

Rule Severity Fires on
unknown-table warning A table or view that is not in the schema. Not the target of a CREATE, which is a definition and not a reference.
table-already-exists warning A CREATE of a name the schema already carries, which the database refuses. Quiet under IF NOT EXISTS, OR REPLACE, TEMPORARY, and when an earlier statement in the file drops the name.
unknown-column warning A column that is not on any table in scope.
ambiguous-column warning An unqualified column that exists on more than one of the tables read at that point.
unused-cte hint A CTE that is defined and never referenced.
unknown-function warning A function the dialect does not define.

Types #

These need the schema too.

Rule Severity Fires on
type-mismatch-comparison warning Comparing two expressions of incompatible types.
type-mismatch-arithmetic error Arithmetic on a non-numeric column.
type-mismatch-argument warning A function argument whose type is not what the function takes.
in-list-type-mismatch warning An IN list value that cannot match the column’s type.
coalesce-type-mismatch warning COALESCE over arguments of inconsistent types.
enum-value-mismatch warning Comparing an enum column to a value outside its allowed set.
missing-argument error A function called with no arguments where it requires one.

NULL #

The rules that catch the mistake everyone makes at least once.

Rule Severity Fires on
null-equality error = NULL, which is never true. Use IS NULL.
null-inequality error <> NULL, same problem.
null-in-not-in warning NOT IN over a list containing NULL, which returns no rows.
null-in-list warning NULL inside an IN list, where it can never match.
coalesce-single-arg warning COALESCE with one argument, which does nothing.

Structure and aggregation #

Rule Severity Fires on
agg-in-where error An aggregate in WHERE. Use HAVING.
agg-in-join error An aggregate in a JOIN ... ON condition.
having-without-group-by error HAVING with no GROUP BY.
window-in-where error A window function in WHERE.
duplicate-column warning Two output columns with the same name.
ordinal-out-of-range error GROUP BY 4 when the select list has three columns.
offset-without-limit error OFFSET with no LIMIT.
limit-without-order-by hint LIMIT with no ORDER BY: the rows you get are arbitrary.
subquery-order-by hint ORDER BY inside a subquery, where it is discarded.

Predicates and style #

Rule Severity Fires on
contradictory-predicate error A condition that can never be true.
tautological-predicate warning A condition that is always true.
division-by-zero error Division by a literal zero.
reversed-between error BETWEEN 10 AND 1, which matches nothing.
empty-in-list error IN ().
duplicate-in-value warning The same value twice in an IN list.
like-no-wildcard warning LIKE with no % or _, which is just =.
like-leading-wildcard hint LIKE '%foo', which cannot use an index.
trailing-comma error A trailing comma in the select list.
unquoted-uppercase warning An unquoted uppercase identifier, which the database will fold to lowercase.

Turning a rule off #

Set a severity to off in the .lint file to change it for the whole workspace, or for a set of paths:

.lint
[
  {
    "files": ["migrations/**"],
    "rules": { "unquoted-uppercase": "off" }
  }
]

For a single statement, a comment does it:

-- lint-disable-next-line like-leading-wildcard
SELECT * FROM customers WHERE email LIKE '%@example.com';
  • -- lint-disable-next-line <rule-id> suppresses the rule on the following line.
  • -- lint-disable <rule-id> anywhere in the file suppresses it for the whole file.

Your own rules #

The built-in rules are about SQL being correct. Rules about what your team allows are yours to write, as regex patterns in the .lint file: no DROP TABLE outside migrations/, no SELECT * in a committed query. That file is committed with the workspace, so the rule your team agreed on is the rule everyone gets.