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:
- Cascade delete: remove the parent and all children (with appropriate warnings or confirmation)
- Return
409 Conflict with a clear message listing the dependent resources that must be deleted first
- 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
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 |
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 Errorwith a raw PostgreSQL foreign key violation message instead of handling the cascading relationship or returning a proper409 Conflictresponse.Affected operation:
Expected behavior:
One of:
409 Conflictwith a clear message listing the dependent resources that must be deleted first400 Bad Requestexplaining that the resource has active referencesActual behavior:
Returns
500 Internal Server Errorwith a raw PostgreSQL error: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:
--clean/ teardown operations in bootstrap scripts become fragile and order-dependentProposed Solutions
Option A: Cascade deletes for owned children (Recommended)
Add
ON DELETE CASCADEto 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 proper409 Conflictresponse 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
Return
409for 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
Acceptance Criteria
409error (not500)Dependencies
Blocked by: Nothing
Blocks: Nothing
References
ON DELETE CASCADEvsRESTRICToptions