From 700048337f3a1f4c26b40ad23b360f14d0e4eea1 Mon Sep 17 00:00:00 2001 From: Jared Vititoe Date: Tue, 8 Sep 2026 11:56:33 -0400 Subject: [PATCH] Fix inconsistent FK ON DELETE behavior on bulk_operations/ticket_templates (#40) bulk_operations.performed_by and ticket_templates.created_by had no ON DELETE clause (defaulting to RESTRICT), unlike every other user-reference FK in the schema (tickets.*, ticket_attachments, ticket_dependencies, recurring_tickets, api_keys), which all use SET NULL. Deleting a user who ever ran a bulk operation or created a template hard-failed at the DB level instead of nulling the reference, breaking the pattern used everywhere else. performed_by was NOT NULL, so it had to become nullable to support SET NULL, matching how every other SET NULL column is defined. - Fixed 000_baseline.sql for fresh installs. - Added 003_fk_on_delete_set_null.sql for existing deployments. Verified against a local MariaDB instance: reproduced the old RESTRICT schema, ran the migration (twice, for idempotency), then confirmed deleting a user with rows in both tables now nulls the references instead of failing. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01MGDKHiU5RJdo3dqQUDow3X --- migrations/000_baseline.sql | 6 ++--- migrations/003_fk_on_delete_set_null.sql | 32 ++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 migrations/003_fk_on_delete_set_null.sql diff --git a/migrations/000_baseline.sql b/migrations/000_baseline.sql index 3eeee7d..f5ffec9 100644 --- a/migrations/000_baseline.sql +++ b/migrations/000_baseline.sql @@ -57,7 +57,7 @@ CREATE TABLE IF NOT EXISTS `bulk_operations` ( `operation_id` int(11) NOT NULL AUTO_INCREMENT, `operation_type` varchar(50) NOT NULL, `ticket_ids` text NOT NULL, - `performed_by` int(11) NOT NULL, + `performed_by` int(11) DEFAULT NULL, `parameters` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`parameters`)), -- 32, not 20: 'completed_with_errors' is 21 chars (see 001_widen_bulk_operations_status.sql) `status` varchar(32) DEFAULT 'pending', @@ -69,7 +69,7 @@ CREATE TABLE IF NOT EXISTS `bulk_operations` ( PRIMARY KEY (`operation_id`), KEY `idx_performed_by` (`performed_by`), KEY `idx_created_at` (`created_at`), - CONSTRAINT `bulk_operations_ibfk_1` FOREIGN KEY (`performed_by`) REFERENCES `users` (`user_id`) + CONSTRAINT `bulk_operations_ibfk_1` FOREIGN KEY (`performed_by`) REFERENCES `users` (`user_id`) ON DELETE SET NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; -- ============ custom_field_definitions ============ @@ -238,7 +238,7 @@ CREATE TABLE IF NOT EXISTS `ticket_templates` ( PRIMARY KEY (`template_id`), KEY `created_by` (`created_by`), KEY `idx_template_name` (`template_name`), - CONSTRAINT `ticket_templates_ibfk_1` FOREIGN KEY (`created_by`) REFERENCES `users` (`user_id`) + CONSTRAINT `ticket_templates_ibfk_1` FOREIGN KEY (`created_by`) REFERENCES `users` (`user_id`) ON DELETE SET NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; -- ============ ticket_watchers ============ diff --git a/migrations/003_fk_on_delete_set_null.sql b/migrations/003_fk_on_delete_set_null.sql new file mode 100644 index 0000000..9e8021b --- /dev/null +++ b/migrations/003_fk_on_delete_set_null.sql @@ -0,0 +1,32 @@ +-- Fix inconsistent FK ON DELETE behavior on bulk_operations.performed_by and +-- ticket_templates.created_by +-- +-- Every other user-reference FK in the schema (tickets.created_by/updated_by/ +-- assigned_to, ticket_attachments.uploaded_by, ticket_dependencies.created_by, +-- recurring_tickets.created_by/assigned_to, api_keys.created_by, etc.) uses +-- ON DELETE SET NULL. These two had no ON DELETE clause at all, which +-- defaults to RESTRICT — so deleting a user who ever ran a bulk operation or +-- created a template hard-fails at the DB level instead of nulling the +-- reference, breaking the pattern used everywhere else and potentially +-- blocking legitimate user offboarding/cleanup. +-- +-- bulk_operations.performed_by is NOT NULL today; it must become nullable to +-- support SET NULL, matching how every other SET NULL column in the schema +-- is defined. +-- +-- Safe to re-run. + +ALTER TABLE `bulk_operations` + MODIFY COLUMN `performed_by` int(11) DEFAULT NULL; + +ALTER TABLE `bulk_operations` + DROP FOREIGN KEY IF EXISTS `bulk_operations_ibfk_1`; + +ALTER TABLE `bulk_operations` + ADD CONSTRAINT `bulk_operations_ibfk_1` FOREIGN KEY (`performed_by`) REFERENCES `users` (`user_id`) ON DELETE SET NULL; + +ALTER TABLE `ticket_templates` + DROP FOREIGN KEY IF EXISTS `ticket_templates_ibfk_1`; + +ALTER TABLE `ticket_templates` + ADD CONSTRAINT `ticket_templates_ibfk_1` FOREIGN KEY (`created_by`) REFERENCES `users` (`user_id`) ON DELETE SET NULL;