diff --git a/migrations/sql/20251007_add_quota_fields_to_usage_storage.sql b/migrations/sql/20251007_add_quota_fields_to_usage_storage.sql index 603ad87..07a8c2b 100644 --- a/migrations/sql/20251007_add_quota_fields_to_usage_storage.sql +++ b/migrations/sql/20251007_add_quota_fields_to_usage_storage.sql @@ -1,43 +1,16 @@ -- Add quota-related fields to usage_storage table -- These fields are used for tier-based quota management and reset scheduling +-- Date: 2025-10-07 --- Add tier_name column if it doesn't exist -SET @col_exists = (SELECT COUNT(*) FROM information_schema.COLUMNS - WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'usage_storage' AND COLUMN_NAME = 'tier_name'); -SET @sql = IF(@col_exists = 0, - 'ALTER TABLE usage_storage ADD COLUMN tier_name VARCHAR(50) DEFAULT ''free'' AFTER account_hash', - 'SELECT 1'); -- No-op if column exists -PREPARE stmt FROM @sql; -EXECUTE stmt; -DEALLOCATE PREPARE stmt; +ALTER TABLE usage_storage +ADD COLUMN tier_name VARCHAR(50) DEFAULT 'free' COMMENT 'Account tier for quota management' AFTER account_hash; --- Add quota_reset_date column if it doesn't exist -SET @col_exists = (SELECT COUNT(*) FROM information_schema.COLUMNS - WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'usage_storage' AND COLUMN_NAME = 'quota_reset_date'); -SET @sql = IF(@col_exists = 0, - 'ALTER TABLE usage_storage ADD COLUMN quota_reset_date DATE DEFAULT (LAST_DAY(CURDATE()) + INTERVAL 1 DAY) AFTER grace_period_until', - 'SELECT 1'); -- No-op if column exists -PREPARE stmt FROM @sql; -EXECUTE stmt; -DEALLOCATE PREPARE stmt; +ALTER TABLE usage_storage +ADD COLUMN quota_reset_date DATE DEFAULT (LAST_DAY(CURDATE()) + INTERVAL 1 DAY) COMMENT 'Next quota reset date' AFTER grace_period_until; --- Add api_calls_count column if it doesn't exist -SET @col_exists = (SELECT COUNT(*) FROM information_schema.COLUMNS - WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'usage_storage' AND COLUMN_NAME = 'api_calls_count'); -SET @sql = IF(@col_exists = 0, - 'ALTER TABLE usage_storage ADD COLUMN api_calls_count INT UNSIGNED DEFAULT 0 AFTER files_count', - 'SELECT 1'); -- No-op if column exists -PREPARE stmt FROM @sql; -EXECUTE stmt; -DEALLOCATE PREPARE stmt; +ALTER TABLE usage_storage +ADD COLUMN api_calls_count INT UNSIGNED DEFAULT 0 COMMENT 'Total API calls count' AFTER files_count; --- Add last_api_call_at column if it doesn't exist -SET @col_exists = (SELECT COUNT(*) FROM information_schema.COLUMNS - WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'usage_storage' AND COLUMN_NAME = 'last_api_call_at'); -SET @sql = IF(@col_exists = 0, - 'ALTER TABLE usage_storage ADD COLUMN last_api_call_at DATETIME NULL AFTER last_warning_at', - 'SELECT 1'); -- No-op if column exists -PREPARE stmt FROM @sql; -EXECUTE stmt; -DEALLOCATE PREPARE stmt; +ALTER TABLE usage_storage +ADD COLUMN last_api_call_at DATETIME NULL COMMENT 'Timestamp of last API call' AFTER last_warning_at; diff --git a/migrations/sql/20251007_add_updated_at_to_transfer_events.sql b/migrations/sql/20251007_add_updated_at_to_transfer_events.sql index efcc70f..c1f737e 100644 --- a/migrations/sql/20251007_add_updated_at_to_transfer_events.sql +++ b/migrations/sql/20251007_add_updated_at_to_transfer_events.sql @@ -1,25 +1,9 @@ -- Add updated_at column to transfer_events table for tracking status updates -- This is useful for monitoring and debugging transfer state changes (pending → success/failed) +-- Date: 2025-10-07 --- Check and add updated_at column if it doesn't exist -SET @col_exists = (SELECT COUNT(*) FROM information_schema.COLUMNS - WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'transfer_events' AND COLUMN_NAME = 'updated_at'); +ALTER TABLE transfer_events +ADD COLUMN updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Last update timestamp' AFTER completed_at; -SET @sql = IF(@col_exists = 0, - 'ALTER TABLE transfer_events ADD COLUMN updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP AFTER completed_at', - 'SELECT 1'); -- No-op if column exists -PREPARE stmt FROM @sql; -EXECUTE stmt; -DEALLOCATE PREPARE stmt; - --- Check and add index if it doesn't exist -SET @idx_exists = (SELECT COUNT(*) FROM information_schema.STATISTICS - WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'transfer_events' AND INDEX_NAME = 'idx_status_updated'); - -SET @sql = IF(@idx_exists = 0, - 'CREATE INDEX idx_status_updated ON transfer_events(status, updated_at)', - 'SELECT 1'); -- No-op if index exists -PREPARE stmt FROM @sql; -EXECUTE stmt; -DEALLOCATE PREPARE stmt; +CREATE INDEX idx_status_updated ON transfer_events(status, updated_at); diff --git a/migrations/sql/20251009_add_unix_permissions_to_files.sql b/migrations/sql/20251009_add_unix_permissions_to_files.sql index 51b4113..c33bda7 100644 --- a/migrations/sql/20251009_add_unix_permissions_to_files.sql +++ b/migrations/sql/20251009_add_unix_permissions_to_files.sql @@ -1,39 +1,10 @@ -- Add unix_permissions column to files table -- This column stores Unix file permissions (e.g., 0o755 = 493, 0o644 = 420) -- NULL values indicate the file is from a non-Unix system (e.g., Windows) +-- Date: 2025-10-09 --- MySQL compatible idempotent migration using prepared statements -SET @column_exists = ( - SELECT COUNT(*) - FROM INFORMATION_SCHEMA.COLUMNS - WHERE TABLE_SCHEMA = DATABASE() - AND TABLE_NAME = 'files' - AND COLUMN_NAME = 'unix_permissions' -); +ALTER TABLE files +ADD COLUMN unix_permissions INT UNSIGNED NULL COMMENT 'Unix file permissions (e.g., 0o755 = 493)' AFTER key_id; -SET @add_column_sql = IF(@column_exists = 0, - 'ALTER TABLE files ADD COLUMN unix_permissions INT UNSIGNED NULL AFTER key_id', - 'SELECT "Column unix_permissions already exists" AS Info' -); - -PREPARE stmt FROM @add_column_sql; -EXECUTE stmt; -DEALLOCATE PREPARE stmt; - --- Add index only if it doesn't exist -SET @index_exists = ( - SELECT COUNT(*) - FROM INFORMATION_SCHEMA.STATISTICS - WHERE TABLE_SCHEMA = DATABASE() - AND TABLE_NAME = 'files' - AND INDEX_NAME = 'idx_unix_permissions' -); - -SET @add_index_sql = IF(@index_exists = 0, - 'CREATE INDEX idx_unix_permissions ON files(unix_permissions)', - 'SELECT "Index idx_unix_permissions already exists" AS Info' -); - -PREPARE stmt FROM @add_index_sql; -EXECUTE stmt; -DEALLOCATE PREPARE stmt; +-- Add index for efficient lookup +CREATE INDEX idx_unix_permissions ON files(unix_permissions); diff --git a/migrations/sql/20251119_add_deleted_file_id.sql b/migrations/sql/20251119_add_deleted_file_id.sql index ef53421..e4a5fe6 100644 --- a/migrations/sql/20251119_add_deleted_file_id.sql +++ b/migrations/sql/20251119_add_deleted_file_id.sql @@ -1,12 +1,11 @@ -- Add deleted_file_id column to files table for tracking deleted file references -- Date: 2025-11-19 -ALTER TABLE files -ADD COLUMN IF NOT EXISTS deleted_file_id BIGINT UNSIGNED NULL COMMENT 'Reference to original file_id when operation_type=DELETE' AFTER revision; +ALTER TABLE files +ADD COLUMN deleted_file_id BIGINT UNSIGNED NULL COMMENT 'Reference to original file_id when operation_type=DELETE' AFTER revision; -- Add index for efficient lookup -ALTER TABLE files -ADD INDEX IF NOT EXISTS idx_deleted_file_id (deleted_file_id); +CREATE INDEX idx_deleted_file_id ON files(deleted_file_id); diff --git a/migrations/sql/20251119_add_encrypted_data_to_files.sql b/migrations/sql/20251119_add_encrypted_data_to_files.sql index 83d2b23..62754f5 100644 --- a/migrations/sql/20251119_add_encrypted_data_to_files.sql +++ b/migrations/sql/20251119_add_encrypted_data_to_files.sql @@ -3,7 +3,7 @@ -- Date: 2025-11-19 ALTER TABLE files -ADD COLUMN IF NOT EXISTS encrypted_data LONGBLOB NULL COMMENT 'Encrypted file data for recovery' AFTER key_id; +ADD COLUMN encrypted_data LONGBLOB NULL COMMENT 'Encrypted file data for recovery' AFTER key_id; diff --git a/migrations/sql/20251119_add_encrypted_data_to_files_down.sql b/migrations/sql/20251119_add_encrypted_data_to_files_down.sql index 5e15a36..1c88c5a 100644 --- a/migrations/sql/20251119_add_encrypted_data_to_files_down.sql +++ b/migrations/sql/20251119_add_encrypted_data_to_files_down.sql @@ -1,21 +1,8 @@ -- Remove encrypted_data column from files table +-- Date: 2025-11-19 -SET @column_exists = ( - SELECT COUNT(*) - FROM INFORMATION_SCHEMA.COLUMNS - WHERE TABLE_SCHEMA = DATABASE() - AND TABLE_NAME = 'files' - AND COLUMN_NAME = 'encrypted_data' -); - -SET @drop_column_sql = IF(@column_exists > 0, - 'ALTER TABLE files DROP COLUMN encrypted_data', - 'SELECT "Column encrypted_data does not exist" AS Info' -); - -PREPARE stmt FROM @drop_column_sql; -EXECUTE stmt; -DEALLOCATE PREPARE stmt; +ALTER TABLE files +DROP COLUMN encrypted_data; diff --git a/src/storage/migrations.rs b/src/storage/migrations.rs index 3835728..954a88e 100644 --- a/src/storage/migrations.rs +++ b/src/storage/migrations.rs @@ -173,14 +173,33 @@ impl MigrationManager for MySqlMigrationManager { match sqlx::query(statement).execute(&mut *tx).await { Ok(_) => debug!("Statement {} executed successfully", i + 1), Err(e) => { - error!("Failed to execute migration statement {}: {}", i + 1, e); - tx.rollback().await.ok(); - return Err(StorageError::Database(format!( - "Migration {} failed at statement {}: {}", - migration.version, - i + 1, - e - ))); + let error_msg = format!("{}", e); + + // Check if this is an idempotency-safe error that we can ignore + let is_idempotent_error = error_msg.contains("1060") || // Duplicate column name + error_msg.contains("1061") || // Duplicate key name + error_msg.contains("1050") || // Table already exists + error_msg.contains("Duplicate column") || + error_msg.contains("Duplicate key") || + error_msg.contains("already exists"); + + if is_idempotent_error { + warn!( + "Idempotent migration warning at statement {}: {} - continuing anyway", + i + 1, + error_msg + ); + debug!("Statement {} skipped (already applied)", i + 1); + } else { + error!("Failed to execute migration statement {}: {}", i + 1, e); + tx.rollback().await.ok(); + return Err(StorageError::Database(format!( + "Migration {} failed at statement {}: {}", + migration.version, + i + 1, + e + ))); + } } } }