Skip to content

DELETE on parent resources fails with raw PostgreSQL FK constraint error instead of cascading or returning structured error #2

@Sam-Bolling

Description

@Sam-Bolling

Finding

DELETE operations on parent resources that have child references (e.g., deleting a deployment that has linked systems, or a system that has datastreams) fail with a PostgreSQL foreign key constraint violation instead of cascading the delete or returning a meaningful API error.

Review Source: Live integration testing from ogc-csapi-explorer and OSHConnect-Python publishers
Severity: P1-Critical
Category: API Design / Error Handling
Ownership: connected-systems-go


Problem Statement

When a client sends a DELETE request for a parent resource that has foreign key references from child resources, the server returns a 500 Internal Server Error with a raw PostgreSQL foreign key violation message instead of handling the cascading relationship or returning a proper 409 Conflict response.

Affected operation:

DELETE /api/deployments/<uuid>

Expected behavior:

One of:

  1. Cascade delete: remove the parent and all children (with appropriate warnings or confirmation)
  2. Return 409 Conflict with a clear message listing the dependent resources that must be deleted first
  3. Return 400 Bad Request explaining that the resource has active references

Actual behavior:

Returns 500 Internal Server Error with a raw PostgreSQL error:

ERROR: update or delete on table "..." violates foreign key constraint "fk_..." on table "..."

Workaround: Clients must manually delete all child resources (observations, datastreams, systems) in reverse dependency order before deleting the parent. During our testing, we had to delete resources bottom-up to avoid the constraint errors.

Impact:

  • Makes cleanup and resource management difficult for API consumers
  • Exposes internal database implementation details in error responses
  • Forces clients to know the internal schema's FK relationships to perform deletes
  • --clean / teardown operations in bootstrap scripts become fragile and order-dependent

Proposed Solutions

Option A: Cascade deletes for owned children (Recommended)

Add ON DELETE CASCADE to foreign key constraints for owned relationships (e.g., system→datastreams, datastream→observations). This matches the behavior most clients expect — deleting a system removes its datastreams and their observations.

Pros: Most intuitive behavior; simplifies client code; standard pattern for resource ownership hierarchies
Cons: Destructive — no undo; must be careful about which relationships cascade vs. restrict
Effort: Medium | Risk: Low (but must audit all FK relationships)

Option B: Return 409 Conflict with dependent resource list

Keep the FK constraints as RESTRICT, but catch the PostgreSQL error and return a proper 409 Conflict response with a body listing the dependent resources.

{
  "status": 409,
  "message": "Cannot delete deployment: 3 systems and 2 subdeployments reference this resource",
  "dependents": [
    { "type": "systems", "id": "..." },
    { "type": "deployments", "id": "..." }
  ]
}

Pros: Non-destructive; gives clients full control; explicit about what blocks the delete
Cons: More work for clients to implement cleanup; requires additional query to enumerate dependents
Effort: Medium | Risk: None

Option C: Hybrid — cascade owned, restrict referenced

  • Cascade for ownership: system→datastreams→observations, deployment→subdeployments
  • Restrict for references: system↔deployment links, procedure references

Return 409 for restrict-blocked deletes with a clear message.

Pros: Best of both worlds; matches OGC resource ownership semantics
Cons: More complex to implement; must define which relationships are "owned" vs "referenced"
Effort: Large | Risk: Low

Scope — What NOT to Touch

  • ❌ Do NOT change the creation or update behavior for any resource type
  • ❌ Do NOT expose other internal database errors in API responses

Acceptance Criteria

  • Deleting a deployment with linked systems either succeeds (cascade) or returns a structured 409 error (not 500)
  • Deleting a system with datastreams either succeeds (cascade) or returns a structured error
  • Deleting a datastream with observations either succeeds (cascade) or returns a structured error
  • No raw PostgreSQL error messages are exposed in API responses
  • Delete of a resource with no dependents continues to work as before

Dependencies

Blocked by: Nothing
Blocks: Nothing


References

# Document What It Provides
1 OGC 23-001 §7.4 Resource lifecycle and delete semantics
2 OSHConnect-Python bootstrap_helpers.py Client code that must work around the issue with ordered deletes
3 PostgreSQL FK constraint documentation ON DELETE CASCADE vs RESTRICT options

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions