#!/usr/bin/env php getMessage() . "\n"; exit(1); } // Create migrations tracking table if it doesn't exist $createTable = "CREATE TABLE IF NOT EXISTS migrations ( id INT AUTO_INCREMENT PRIMARY KEY, filename VARCHAR(255) NOT NULL UNIQUE, applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, INDEX idx_filename (filename) )"; if (!$conn->query($createTable)) { echo "Error: Could not create migrations table: " . $conn->error . "\n"; exit(1); } // Get list of completed migrations $completed = []; $result = $conn->query("SELECT filename FROM migrations ORDER BY id"); while ($row = $result->fetch_assoc()) { $completed[] = $row['filename']; } // Get list of migration files $migrationsDir = __DIR__; $files = glob($migrationsDir . '/*.sql'); sort($files); if (empty($files)) { echo "No migration files found.\n"; exit(0); } if ($statusOnly) { echo "Migration Status:\n"; echo str_repeat('-', 60) . "\n"; foreach ($files as $file) { $filename = basename($file); $status = in_array($filename, $completed) ? '[DONE]' : '[PENDING]'; echo sprintf(" %s %s\n", $status, $filename); } exit(0); } // Find pending migrations $pending = []; foreach ($files as $file) { $filename = basename($file); if (!in_array($filename, $completed)) { $pending[] = $file; } } if (empty($pending)) { echo "All migrations are up to date.\n"; exit(0); } echo sprintf("Found %d pending migration(s):\n", count($pending)); foreach ($pending as $file) { echo " - " . basename($file) . "\n"; } echo "\n"; if ($dryRun) { echo "[DRY RUN] No changes made.\n"; exit(0); } // Run pending migrations $success = 0; $failed = 0; foreach ($pending as $file) { $filename = basename($file); echo "Running: $filename... "; $sql = file_get_contents($file); if ($sql === false) { echo "FAILED (could not read file)\n"; $failed++; continue; } // Execute migration - handle multiple statements $conn->begin_transaction(); try { // Split by semicolon but respect statements properly // Note: This doesn't handle semicolons in strings, but our migrations are simple $statements = array_filter( array_map('trim', explode(';', $sql)), function($stmt) { // Remove comments and check if there's actual SQL $cleaned = preg_replace('/--.*$/m', '', $stmt); return !empty(trim($cleaned)); } ); foreach ($statements as $statement) { if (!$conn->query($statement)) { // Some "errors" are acceptable (like "index already exists") $error = $conn->error; if (strpos($error, 'Duplicate key name') !== false || strpos($error, 'already exists') !== false) { // Index already exists, that's fine continue; } throw new Exception($error); } } // Record the migration $stmt = $conn->prepare("INSERT INTO migrations (filename) VALUES (?)"); $stmt->bind_param('s', $filename); if (!$stmt->execute()) { throw new Exception("Could not record migration: " . $conn->error); } $conn->commit(); echo "OK\n"; $success++; } catch (Exception $e) { $conn->rollback(); echo "FAILED (" . $e->getMessage() . ")\n"; $failed++; } } echo "\n"; echo "=== Migration Complete ===\n"; echo sprintf(" Success: %d\n", $success); echo sprintf(" Failed: %d\n", $failed); exit($failed > 0 ? 1 : 0);