Skip to content

feat(postgresql): add forceDrop option to DROP DATABASE#405

Open
nicolasburtey wants to merge 1 commit into
crossplane-contrib:masterfrom
nicolasburtey:feat/postgresql-drop-database-with-force
Open

feat(postgresql): add forceDrop option to DROP DATABASE#405
nicolasburtey wants to merge 1 commit into
crossplane-contrib:masterfrom
nicolasburtey:feat/postgresql-drop-database-with-force

Conversation

@nicolasburtey

@nicolasburtey nicolasburtey commented Jul 1, 2026

Copy link
Copy Markdown

Summary

Adds an opt-in forceDrop field to the PostgreSQL Database managed
resource that makes the controller issue DROP DATABASE IF EXISTS <name> WITH (FORCE) instead of a bare drop. Applies to both the cluster-scoped
(postgresql.sql.crossplane.io) and namespaced
(postgresql.sql.m.crossplane.io) APIs.

WITH (FORCE) (PostgreSQL 13+) terminates every backend connected to
the target database before dropping it. Without it, a Database with
deletionPolicy: Delete cannot be removed while any client holds an
open connection, because the bare drop fails with SQLSTATE 55006
(object_in_use). The managed resource then sticks Terminating
indefinitely — i.e. provider-sql cannot delete what it created whenever
an application maintains a persistent connection pool against the
database.

Today, Database.Delete is:

err := c.db.Exec(ctx, xsql.Query{String: "DROP DATABASE IF EXISTS " + pq.QuoteIdentifier(meta.GetExternalName(mg))})

This adds the WITH (FORCE) escape hatch so a declarative
deletionPolicy: Delete works even with a live pool.

Design

  • New optional boolean spec.forProvider.forceDrop (defaults to
    omitted/false → behaviour unchanged).
  • When true, Delete appends WITH (FORCE).
  • Version-gated on PostgreSQL 13+ (server_version_num >= 130000),
    returning a clear error otherwise. This mirrors the existing
    create-time strategy version guard.
  • forceDrop is a delete-only behaviour toggle, not an observable
    property of the database, so it is added to the upToDate ignore list
    alongside template and strategy (no reconciliation churn).

Open questions for reviewers

  • Error vs. fallback on PG < 13. This PR errors when forceDrop is
    requested on a server older than 13, matching strategy. A gentler
    alternative would be to fall back to a pg_terminate_backend drain
    step (works on all versions) when WITH (FORCE) is unavailable. Happy
    to switch to that if preferred. Note PostgreSQL 13 reached EOL in Nov
    2025, so most deployments are already on 14+.
  • Naming. Went with forceDrop to map directly to the SQL keyword
    and stay consistent with the existing camelCase field style. Open to
    force / forceDelete if there's a preferred convention.

Validation

  • CRDs and deepcopy regenerated via controller-gen.
  • Unit tests added for: default bare drop, WITH (FORCE) drop, and the
    unsupported-version error path (cluster + namespaced).
  • gofmt, go vet, go build ./..., and go test ./... all pass.

Example

apiVersion: postgresql.sql.crossplane.io/v1alpha1
kind: Database
metadata:
  name: force-drop-db
spec:
  forProvider:
    # Optional, only affects deletion, PostgreSQL 13+.
    forceDrop: true

Draft until I get a 👍 on the error-vs-fallback and naming questions above.

Add an opt-in `forceDrop` field to the PostgreSQL Database managed
resource (both cluster-scoped `postgresql.sql.crossplane.io` and
namespaced `postgresql.sql.m.crossplane.io`). When set to true, the
controller issues `DROP DATABASE IF EXISTS <name> WITH (FORCE)` instead
of a bare drop.

`WITH (FORCE)` (PostgreSQL 13+) terminates every backend connected to
the target database before dropping it. This lets a Database with
`deletionPolicy: Delete` be removed even when an application holds a
long-lived connection pool against it, which would otherwise make the
drop fail with SQLSTATE 55006 (object_in_use) and leave the managed
resource stuck Terminating.

The option is gated on the server version (>= 130000), mirroring the
existing create-time `strategy` version guard. It is a delete-only
behaviour toggle, so it is excluded from the up-to-date comparison
(like `template` and `strategy`) and does not cause reconciliation
churn. Default behaviour (bare drop) is unchanged.

Includes regenerated CRDs/deepcopy, unit tests covering the default,
force, and unsupported-version cases, and example manifests.

Signed-off-by: Nicolas Burtey <nb@galoy.io>
@nicolasburtey nicolasburtey force-pushed the feat/postgresql-drop-database-with-force branch from c6867fb to d3b9022 Compare July 1, 2026 16:43
@nicolasburtey nicolasburtey marked this pull request as ready for review July 2, 2026 16:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant