Postgres Language Server — the optional schema linter
The Postgres Language Server ships
a schema linter, dblint. SQLens can run it and report its security checks alongside its own —
row-level security enabled with no policy, a policy on a table where row-level security is off,
functions with a mutable search_path, extensions installed in public.
It is an amplifier, never a requirement. Every SQLens rule runs without it, and a run that lacks it says so rather than quietly checking less.
What a run does to your database
This is the one adapter whose tool opens its own connection, so the question was measured rather
than reasoned about. With statement logging on, one run issues sixteen statements: none of them
mutating, none taking a lock — catalog reads, inside one transaction, with search_path emptied.
That is a property of somebody else's binary, which is exactly the kind of fact that changes without telling you. So it is not a promise in prose: a test in this package's own suite runs the tool as a role with no write privilege at all, and a version that started wanting one turns it red.
Install it, or do not
The version matters more than it usually does. This tool is below 1.0, where a version number promises nothing about compatibility, so SQLens accepts one MINOR range — the range its adapter was measured against. A build outside it is reported as unsupported rather than trusted, because a report in a shape nobody measured does not fail to parse: it parses into the wrong thing.
Configuration
// config/sqlens.php
'tools' => [
'pgls' => [
'path' => null, // or an absolute path to pin a specific binary
'enabled' => true,
'timeout' => 30,
],
],
There is no fast_path key, and the absence is the statement: the single-file route judges one
migration with a sub-second budget, and this tool answers by connecting to a database. There is no
version of that route it belongs in.
A pinned path that does not run is reported rather than worked around. Naming a path is a
statement about which binary, so falling back to $PATH would honor the configuration in
appearance and ignore it in fact.
What its findings look like here
They carry SQLens rule ids under a PGLS. prefix, at high, medium or low — translated from
the tool's own ranking rather than passed through, because the two scales measure different things.
critical is deliberately unreachable from an external check: a re-rating upstream must not fail a
build nobody changed.
Every finding names the tool and the exact version that produced it, plus that tool's own documentation for the rule. The link a report renders as "read about this" stays a SQLens page, because it has to explain what SQLens did with the finding — which the upstream page cannot.
Where it overlaps with SQLens's own rules
Two of the tool's six security rules check ground SQLens already covers:
| PGLS rule | SQLens rule |
|---|---|
policyExistsRlsDisabled | SEC.RLS.DISABLED |
rlsEnabledNoPolicy | SEC.RLS.NO_POLICY |
Where both fire on the same object you see one finding, and it is SQLens's. Not because it is better — because it carries this package's documentation, severity and remediation, and because a finding whose id changed depending on whether an optional binary is installed could not be baselined.
The tool's agreement is not thrown away. It is recorded on the finding as a confirmation naming the build that agreed, so a report still shows two sources reaching the same conclusion.
The other four rules are pure addition — extensions in public, outdated extension versions,
mutable function search paths, unsupported reg* types. SQLens has no rule for any of them, so
installing the tool genuinely adds four checks.
That table is an allowlist of known overlaps, never a filter: a rule missing from it passes through untouched. The opposite reading would silently drop whatever nobody thought to list, and the checks most worth having are the ones nobody thought about.
When it cannot run
A missing binary, a database it cannot reach, or a report it cannot read all end as an
undetermined naming which of those happened. Never as "no findings": a run that never reached your
database has checked nothing, and saying otherwise is the one failure this package is built to
refuse.
Under a strict-tools profile, a fixable absence ends the run as a misconfiguration instead.
Related
- Understanding
undetermined CAP.L0.MISSING_TOOL— the notice a run reports when an amplifier did not answer