Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 9 additions & 36 deletions migrations/sql/20251007_add_quota_fields_to_usage_storage.sql
Original file line number Diff line number Diff line change
@@ -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;

24 changes: 4 additions & 20 deletions migrations/sql/20251007_add_updated_at_to_transfer_events.sql
Original file line number Diff line number Diff line change
@@ -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);

39 changes: 5 additions & 34 deletions migrations/sql/20251009_add_unix_permissions_to_files.sql
Original file line number Diff line number Diff line change
@@ -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);
7 changes: 3 additions & 4 deletions migrations/sql/20251119_add_deleted_file_id.sql
Original file line number Diff line number Diff line change
@@ -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);



Expand Down
2 changes: 1 addition & 1 deletion migrations/sql/20251119_add_encrypted_data_to_files.sql
Original file line number Diff line number Diff line change
Expand Up @@ -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;



Expand Down
19 changes: 3 additions & 16 deletions migrations/sql/20251119_add_encrypted_data_to_files_down.sql
Original file line number Diff line number Diff line change
@@ -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;



Expand Down
35 changes: 27 additions & 8 deletions src/storage/migrations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
)));
}
}
}
}
Expand Down