Files
whatsapp/scripts/migrate_add_conversation_fields.php
2026-01-24 00:55:33 -05:00

167 lines
5.9 KiB
PHP

<?php
/**
* Migration: Add conversation fields and indexes
* - Creates a backup SQL file for `conversations` (CREATE TABLE + INSERTs)
* - Adds columns if missing
* - Adds indexes if missing
*
* Usage (CLI): php scripts/migrate_add_conversation_fields.php --yes
* If --yes is not passed the script will prompt for confirmation.
*/
require_once __DIR__ . '/../config/config.php';
$options = getopt('', ['yes']);
$force = isset($options['yes']);
$dsn = "mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=" . (defined('DB_CHARSET') ? DB_CHARSET : 'utf8mb4');
try {
$pdo = new PDO($dsn, DB_USER, DB_PASS, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
} catch (Throwable $t) {
echo "Error connecting to DB: " . $t->getMessage() . PHP_EOL;
exit(1);
}
echo "This script will backup table `conversations` and apply schema changes.\n";
if (!$force) {
fwrite(STDOUT, "Proceed? (yes/no): ");
$answer = trim(fgets(STDIN));
if (strtolower($answer) !== 'yes') {
echo "Aborted by user.\n";
exit(0);
}
}
// 1) Backup: get CREATE TABLE and all rows
$timestamp = date('Ymd_His');
$backupDir = __DIR__ . '/../backups';
if (!is_dir($backupDir)) mkdir($backupDir, 0755, true);
$backupFile = $backupDir . "/conversations_backup_{$timestamp}.sql";
try {
// Show create
$row = $pdo->query("SHOW CREATE TABLE `conversations`")->fetch(PDO::FETCH_ASSOC);
$createSql = $row['Create Table'] ?? null;
if (!$createSql) throw new Exception('Could not obtain CREATE TABLE for conversations');
$fh = fopen($backupFile, 'w');
fwrite($fh, "-- Backup of table `conversations` generated at {$timestamp}\n\n");
fwrite($fh, "DROP TABLE IF EXISTS `conversations`;\n");
fwrite($fh, $createSql . ";\n\n");
// Dump rows as INSERTs in batches
$stmt = $pdo->query("SELECT * FROM `conversations`");
$cols = [];
$insertCount = 0;
$batchSize = 200;
$rows = [];
while ($r = $stmt->fetch(PDO::FETCH_ASSOC)) {
if (empty($cols)) $cols = array_keys($r);
$values = array_map(function($v) use ($pdo) {
if (is_null($v)) return 'NULL';
// Ensure non-binary scalars are properly quoted as strings
return $pdo->quote((string)$v);
}, array_values($r));
$rows[] = '(' . implode(',', $values) . ')';
if (count($rows) >= $batchSize) {
$line = "INSERT INTO `conversations` (`" . implode('`,`', $cols) . "`) VALUES\n" . implode(",\n", $rows) . ";\n";
fwrite($fh, $line);
$insertCount += count($rows);
$rows = [];
}
}
if (count($rows)) {
$line = "INSERT INTO `conversations` (`" . implode('`,`', $cols) . "`) VALUES\n" . implode(",\n", $rows) . ";\n";
fwrite($fh, $line);
$insertCount += count($rows);
}
fclose($fh);
echo "Backup written to: {$backupFile} (rows: {$insertCount})\n";
} catch (Throwable $t) {
echo "Backup failed: " . $t->getMessage() . PHP_EOL;
exit(1);
}
// 2) Prepare ALTER statements (only add if missing)
$columnsToAdd = [
'message_id' => "VARCHAR(255) DEFAULT NULL",
'message_type' => "VARCHAR(32) DEFAULT 'text'",
'media_url' => "TEXT DEFAULT NULL",
'local_file' => "VARCHAR(255) DEFAULT NULL",
'local_thumb' => "VARCHAR(255) DEFAULT NULL",
'filename' => "VARCHAR(255) DEFAULT NULL",
'mime_type' => "VARCHAR(100) DEFAULT NULL",
'reply_to_message_id' => "VARCHAR(255) DEFAULT NULL",
'reaction_emoji' => "VARCHAR(64) DEFAULT NULL",
'reaction_to_message_id' => "VARCHAR(255) DEFAULT NULL",
'status' => "VARCHAR(32) DEFAULT 'received'"
];
// Gather existing columns and their types
$existingColsStmt = $pdo->query("SHOW COLUMNS FROM `conversations`");
$existingCols = [];
$existingTypes = []; // field -> Type
foreach ($existingColsStmt->fetchAll(PDO::FETCH_ASSOC) as $c) {
$existingCols[] = $c['Field'];
$existingTypes[$c['Field']] = strtolower($c['Type']);
}
$alters = [];
foreach ($columnsToAdd as $col => $type) {
if (!in_array($col, $existingCols)) {
$alters[] = "ADD COLUMN `{$col}` {$type}";
} else {
// If the column exists but its type doesn't match the desired one, schedule a MODIFY
$desiredType = strtolower(preg_replace('/\s+default\s+.*/i', '', $type)); // e.g. 'varchar(255)'
if (!str_starts_with($existingTypes[$col], $desiredType)) {
$alters[] = "MODIFY COLUMN `{$col}` {$type}";
echo "Will modify column type for {$col}: found {$existingTypes[$col]} -> desired {$desiredType}\n";
}
}
}
try {
if (count($alters)) {
$sql = "ALTER TABLE `conversations` " . implode(', ', $alters) . ";";
$pdo->exec($sql);
echo "Applied ALTER TABLE to add columns: " . implode(', ', array_keys($columnsToAdd)) . "\n";
} else {
echo "No new columns to add.\n";
}
} catch (Throwable $t) {
echo "ALTER TABLE failed: " . $t->getMessage() . PHP_EOL;
echo "You can manually inspect {$backupFile} and run the appropriate ALTERs.\n";
exit(1);
}
// 3) Add indexes if missing
$indexes = [
'idx_message_id' => ['cols' => ['message_id'], 'unique' => false],
'idx_reply' => ['cols' => ['reply_to_message_id'], 'unique' => false],
];
$existingIndexes = [];
$rs = $pdo->query("SHOW INDEX FROM `conversations`");
foreach ($rs->fetchAll(PDO::FETCH_ASSOC) as $row) {
$existingIndexes[$row['Key_name']][] = $row['Column_name'];
}
foreach ($indexes as $name => $info) {
if (!isset($existingIndexes[$name])) {
$colsSql = implode('`,`', $info['cols']);
$sql = "CREATE INDEX `{$name}` ON `conversations` (`{$colsSql}`);";
try {
$pdo->exec($sql);
echo "Added index {$name} (" . implode(',', $info['cols']) . ")\n";
} catch (Throwable $t) {
echo "Failed to add index {$name}: " . $t->getMessage() . PHP_EOL;
}
} else {
echo "Index {$name} already exists.\n";
}
}
echo "Migration complete. Backup file: {$backupFile}\n";
return 0;