# 18 rules

Rendered from `packages/gripes`. Each line is the finding at
`warning`, the default of four attitude levels; the technical content
is the same at every level and only the register changes.

| Rule | Severity | Needs | Engines | At warning |
| --- | --- | --- | --- | --- |
| `delete.no-where` | blocker | statement | all engines | Delete with no where clause. That is the table, not the row. |
| `grant.default-privileges-untrusted` | blocker | access | postgres | Default privileges grant every new {objects} to {role}. Nothing yet exists. |
| `grant.definer-public-reach` | blocker | access | postgres | Security definer, and {role} can call it. It runs as {owner}, not them. |
| `grant.public-execute` | blocker | access | postgres | PUBLIC can execute {routine}, so {role} can call it. That is the default. |
| `grant.untrusted-write` | blocker | access | postgres | {role} can {privileges} {relation}. Anonymous callers, writing. |
| `join.no-condition` | blocker | statement | all engines | This join has no condition. I'll allow it. I won't forget it. |
| `routine.definer-no-search-path` | blocker | object | all engines | Security definer, no search_path. Anyone who can add a schema owns this. |
| `update.no-where` | blocker | statement | all engines | Update with no where clause. Every row gets this, not just yours. |
| `column.nullable-inequality` | warning | statement, schema | all engines | {column} can be null, and null is not <> anything. Those rows vanish here. |
| `grant.rls-no-policy` | warning | access | postgres | RLS on {relation}, zero policies. Every row is denied, silently. |
| `grant.untrusted-read-no-rls` | warning | access | postgres | {role} reads every row of {relation}. Row-level security is off. |
| `grant.view-owner-bypass` | warning | access | postgres | {view} is not security_invoker. {role} reads {relation} as {owner}. |
| `index.not-concurrent` | warning | statement | postgres | No concurrently. Writes to this table queue up until the index is built. |
| `subquery.not-in` | warning | statement | all engines | not in against a subquery. One null in there and you get zero rows, silently. |
| `table.no-primary-key` | warning | object | all engines | No primary key on {table}. Every row is indistinguishable from its twin. |
| `view.select-star` | warning | statement | all engines | The star in this view is expanded once, at creation. New columns never appear. |
| `index.duplicate` | style | object | all engines | {index} is already covered by {covering}. It costs writes and buys nothing. |
| `routine.volatile-but-readonly` | style | object | all engines | {routine} only reads, yet claims volatile. The planner takes you at your word. |

## Not built yet

- `index.missing` — Nothing knows which indexes a relation has. The completion catalog does not carry them and the object describe result is not wired to the runner.
- `execution.truncated` — It cannot tell you that it stopped reading. Needs the server-side runner, which is being written.
- `statement.over-ddl` — A view created with `select *` is a finding the catalogue already has, and it fires in a query document but never on the view itself, which is the one place anyone would look.
- `plan.seq-scan` — Nothing reads an execution plan, so a sequential scan over forty million rows reads exactly like one over forty.
- `select.unqualified-star` — `select * from t limit 100` is the single most common legitimate query there is. Griping at it is precisely the style filler this catalogue exists to avoid.

See also: [gripe levels](/docs/gripe-levels.md), [dismissing gripes](/docs/dismissing-gripes.md), [writing a rule](/docs/writing-a-rule.md).
