167 lines
5.9 KiB
PHP
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;
|