Skip to main content

PG.L1.DROP_SCHEMA — Dropping a schema is every table in it at once

  • Category: safety
  • Level: 1
  • Confidence: deterministic
  • Downtime class: online
  • Stability: stable
  • Suites: lint
  • Applies to: PostgreSQL 18

A schema is the namespace your tables live in. Dropping it is not a bigger version of dropping a table — it is dropping every table, view, sequence and routine inside it in one statement, with no list of what went and no WHERE clause to undo.

CASCADE is the difference between bad and unbounded

Without it, PostgreSQL refuses a schema that still contains anything. The statement is then a safety check that failed loudly, and the schema was already empty.

With it, the refusal is waived — and the reach does not stop at the schema boundary. A view in reporting that selects from a table in app, a foreign key pointing into it from anywhere: those are dependent objects, and CASCADE takes them too. The blast radius of the statement is therefore larger than the namespace it names.

Why this rule exists at all

It was measured rather than assumed. Across every shipped rule, DROP TABLE invoices produced two findings and DROP SCHEMA public CASCADE produced none. The tool reported dropping one table and said nothing about dropping the namespace that contains every table.

Nothing was suppressing it. A schema statement was classified into the generic DDL bucket, which no rule reads as a catch-all — so there was no statement kind for a rule to fire on. The fix was in the capture layer first and the rule second.

Why level 1

Level 1 is the destructive band, the lowest one above zero, and the one a project sees as soon as it turns the linter on. The rule does not judge whether the schema is still needed — it cannot know that. It judges that the change is irreversible and unbounded, and asks for that to be stated rather than assumed.

The downtime class is online, and that is not a contradiction

The statement takes locks briefly and does not scan or rewrite. Its cost is not deploy time, it is deploy permanence. The class prices the first; level 1 prices the second.

Flagged

DB::statement('DROP SCHEMA reporting CASCADE');

Laravel's schema builder cannot emit this — there is no Schema::dropSchema(). It reaches a migration through a hand-written DB::statement(), which is precisely the kind of statement no framework reviews on your behalf.

Preferred

// Retire the contents first, then let RESTRICT (PostgreSQL's default) be a real
// safety check instead of a waived one:
DB::statement('ALTER TABLE reporting.daily_rollups SET SCHEMA archive');
DB::statement('DROP SCHEMA reporting RESTRICT');

RESTRICT is PostgreSQL's default. Naming it makes the intent explicit: this schema should be empty by now, and if it is not, stop.

Saying yes on purpose

A drop that is genuinely intended is not a finding to silence — it is a statement to make:

#[SqlensAllowDestructive('the reporting schema moved to the warehouse in 3.1')]
final class DropReportingSchema extends Migration { /* … */ }

The annotation carries a reason, which is the difference between a suppression and a decision. The finding is still produced and shown; the opt-in changes the presentation, not the fact.

What the rule does not claim

Two limits, stated rather than left for you to discover:

  • A schema this same migration created is reported like any other. The table rule stays quiet when a migration drops a table it just created, because that table never held data a deploy would miss. CREATE SCHEMA is not captured as its own statement kind, so this rule cannot make the same distinction — and adding that kind purely to answer the question would put a branch into the classifier that nothing else reads.
  • What the schema contained is not read. This is a statement-level rule, so it describes the reach of the statement, not the rows actually lost. The catalog side of the tool is where the contents are known.

Sources

The fix material this rule carries

A finding from this rule carries a payload whose strategy is none: this rule has looked, and there is no safe standard sequence. That is a conclusion rather than an omission — a finding with no payload at all says only that nobody wrote one.

The payload is material for you or an agent to apply. SQLens writes no migration and runs no DDL. See the remediation payload for every field, the placeholder semantics, and the version rules a consumer has to follow.