1
0
mirror of https://github.com/chylex/Lightning-Tracker.git synced 2025-05-30 14:34:09 +02:00

Rewrite update migrations to save progress & remove old migrations

This commit is contained in:
chylex 2020-08-30 09:35:20 +02:00
parent 2da94652ab
commit ddd8f9bd9c
15 changed files with 364 additions and 353 deletions

View File

@ -3,8 +3,6 @@ declare(strict_types = 1);
define('DEBUG', true);
define('INSTALLED_MIGRATION_VERSION', TRACKER_MIGRATION_VERSION);
define('SYS_ENABLE_REGISTRATION', true);
define('BASE_URL', 'http://localhost');

6
dev/version.php Normal file
View File

@ -0,0 +1,6 @@
<?php
declare(strict_types = 1);
define('MIGRATION_VERSION', TRACKER_MIGRATION_VERSION);
define('MIGRATION_TASK', 0);
?>

View File

@ -57,7 +57,7 @@ final class SystemConfig{
$validator->validate();
}
public function generate(int $migration_version = TRACKER_MIGRATION_VERSION): string{
public function generate(): string{
$sys_enable_registration = $this->sys_enable_registration ? 'true' : 'false';
$base_url = addcslashes($this->base_url, '\'\\');
$db_name = addcslashes($this->db_name, '\'\\');
@ -70,8 +70,6 @@ final class SystemConfig{
<?php
declare(strict_types = 1);
define('INSTALLED_MIGRATION_VERSION', $migration_version);
define('SYS_ENABLE_REGISTRATION', $sys_enable_registration);
define('BASE_URL', '$base_url');
@ -85,6 +83,10 @@ PHP;
return $contents;
}
public function write(string $file): bool{
return file_put_contents($file, $this->generate(), LOCK_EX) !== false;
}
}
?>

View File

@ -0,0 +1,34 @@
<?php
declare(strict_types = 1);
namespace Configuration;
final class VersionFile{
private int $migration_version;
private int $migration_task;
public function __construct(int $migration_version, int $migration_task){
$this->migration_version = $migration_version;
$this->migration_task = $migration_task;
}
public function generate(): string{
/** @noinspection ALL */
$contents = <<<PHP
<?php
declare(strict_types = 1);
define('MIGRATION_VERSION', $this->migration_version);
define('MIGRATION_TASK', $this->migration_task);
?>
PHP;
return $contents;
}
public function writeSafe(string $target_file, string $tmp_file): bool{
return file_put_contents($tmp_file, $this->generate(), LOCK_EX) && rename($tmp_file, $target_file);
}
}
?>

View File

@ -120,7 +120,7 @@ HTML;
return false;
}
if (!file_put_contents(CONFIG_FILE, $config->generate(), LOCK_EX)){
if (!$config->write(CONFIG_FILE)){
$this->form->addMessage(FormComponent::MESSAGE_ERROR, Text::blocked('Error updating \'config.php\'.'));
return false;
}

View File

@ -0,0 +1,19 @@
<?php
declare(strict_types = 1);
namespace Update;
use Update\Tasks\SqlTask;
abstract class AbstractMigrationProcess{
protected static final function sql(string $sql): SqlTask{
return new SqlTask($sql);
}
/**
* @return AbstractMigrationTask[]
*/
public abstract function getTasks(): array;
}
?>

View File

@ -0,0 +1,18 @@
<?php
declare(strict_types = 1);
namespace Update;
use PDO;
abstract class AbstractMigrationTask{
public function prepare(PDO $db): void{
}
public abstract function execute(PDO $db): void;
public function finalize(PDO $db): void{
}
}
?>

View File

@ -0,0 +1,53 @@
<?php
declare(strict_types = 1);
namespace Update;
use Configuration\VersionFile;
use Exception;
final class MigrationManager{
private int $current_version;
private int $current_task;
public function __construct(int $current_version, int $current_task){
$this->current_version = $current_version;
$this->current_task = $current_task;
}
public function getCurrentVersion(): int{
return $this->current_version;
}
public function getCurrentTask(): int{
return $this->current_task;
}
/**
* @throws Exception
*/
public function finishVersion(): void{
++$this->current_version;
$this->current_task = 0;
$this->writeFile();
}
/**
* @throws Exception
*/
public function finishTask(): void{
++$this->current_task;
$this->writeFile();
}
/**
* @throws Exception
*/
private function writeFile(): void{
if (!(new VersionFile($this->current_version, $this->current_task))->writeSafe(VERSION_FILE, VERSION_TMP_FILE)){
throw new Exception('Error updating version file (migration '.$this->current_version.', task '.$this->current_task.').');
}
}
}
?>

View File

@ -0,0 +1,95 @@
<?php
declare(strict_types = 1);
namespace Update\Migrations;
use Data\UserId;
use PDO;
use Update\AbstractMigrationProcess;
use Update\AbstractMigrationTask;
use Update\Tasks\DropAllForeignKeysTask;
final class Migration6 extends AbstractMigrationProcess{
public function getTasks(): array{
/** @noinspection SqlResolve, SqlWithoutWhere */
return [
new DropAllForeignKeysTask(),
self::sql('ALTER TABLE users ADD public_id CHAR(9) NOT NULL FIRST'),
self::sql('ALTER TABLE issues CHANGE author_id author_id_old INT NULL'),
self::sql('ALTER TABLE issues CHANGE assignee_id assignee_id_old INT NULL'),
self::sql('ALTER TABLE project_members CHANGE user_id user_id_old INT NOT NULL'),
self::sql('ALTER TABLE projects CHANGE owner_id owner_id_old INT NOT NULL'),
self::sql('ALTER TABLE project_user_settings CHANGE user_id user_id_old INT NOT NULL'),
self::sql('ALTER TABLE user_logins CHANGE id id_old INT NOT NULL'),
self::sql('ALTER TABLE project_members DROP PRIMARY KEY'),
self::sql('ALTER TABLE project_user_settings DROP PRIMARY KEY'),
self::sql('ALTER TABLE user_logins DROP PRIMARY KEY'),
self::sql('ALTER TABLE issues ADD author_id CHAR(9) NULL AFTER author_id_old'),
self::sql('ALTER TABLE issues ADD assignee_id CHAR(9) NULL AFTER assignee_id_old'),
self::sql('ALTER TABLE project_members ADD user_id CHAR(9) NOT NULL AFTER user_id_old'),
self::sql('ALTER TABLE projects ADD owner_id CHAR(9) NOT NULL AFTER owner_id_old'),
self::sql('ALTER TABLE project_user_settings ADD user_id CHAR(9) NOT NULL AFTER user_id_old'),
self::sql('ALTER TABLE user_logins ADD id CHAR(9) NOT NULL AFTER id_old'),
new class extends AbstractMigrationTask{
public function execute(PDO $db): void{
$stmt = $db->query('SELECT id FROM users');
while(($res = $stmt->fetchColumn()) !== false){
/** @noinspection SqlResolve */
$s2 = $db->prepare('UPDATE users SET public_id = ? WHERE id = ?');
$s2->bindValue(1, UserId::generateNew());
$s2->bindValue(2, (int)$res, PDO::PARAM_INT);
$s2->execute();
}
}
},
self::sql('UPDATE issues SET author_id = (SELECT u.public_id FROM users u WHERE u.id = author_id_old)'),
self::sql('UPDATE issues SET assignee_id = (SELECT u.public_id FROM users u WHERE u.id = assignee_id_old)'),
self::sql('UPDATE project_members SET user_id = (SELECT u.public_id FROM users u WHERE u.id = user_id_old)'),
self::sql('UPDATE projects SET owner_id = (SELECT u.public_id FROM users u WHERE u.id = owner_id_old)'),
self::sql('UPDATE project_user_settings SET user_id = (SELECT u.public_id FROM users u WHERE u.id = user_id_old)'),
self::sql('UPDATE user_logins SET id = (SELECT u.public_id FROM users u WHERE u.id = id_old)'),
self::sql('ALTER TABLE users DROP id'),
self::sql('ALTER TABLE users CHANGE public_id id CHAR(9) NOT NULL'),
self::sql('ALTER TABLE users ADD PRIMARY KEY (id)'),
self::sql('ALTER TABLE project_members ADD PRIMARY KEY (project_id, user_id)'),
self::sql('ALTER TABLE project_user_settings ADD PRIMARY KEY (project_id, user_id)'),
self::sql('ALTER TABLE user_logins ADD PRIMARY KEY (id, token)'),
self::sql('ALTER TABLE issues ADD CONSTRAINT fk__issue__project FOREIGN KEY (`project_id`) REFERENCES `projects` (`id`) ON UPDATE CASCADE ON DELETE CASCADE'),
self::sql('ALTER TABLE issues ADD CONSTRAINT fk__issue__author FOREIGN KEY (`author_id`) REFERENCES `users` (`id`) ON UPDATE CASCADE ON DELETE SET NULL'),
self::sql('ALTER TABLE issues ADD CONSTRAINT fk__issue__assignee FOREIGN KEY (`assignee_id`) REFERENCES `users` (`id`) ON UPDATE CASCADE ON DELETE SET NULL'),
self::sql('ALTER TABLE issues ADD CONSTRAINT fk__issue__milestone FOREIGN KEY (`milestone_id`, `project_id`) REFERENCES `milestones` (`milestone_id`, `project_id`) ON UPDATE CASCADE ON DELETE RESTRICT'),
self::sql('ALTER TABLE issues ADD CONSTRAINT fk__issue__scale FOREIGN KEY (`scale`) REFERENCES `issue_weights` (`scale`) ON UPDATE RESTRICT ON DELETE RESTRICT'),
self::sql('ALTER TABLE milestones ADD CONSTRAINT fk__milestone__project FOREIGN KEY (`project_id`) REFERENCES `projects` (`id`) ON UPDATE CASCADE ON DELETE CASCADE'),
self::sql('ALTER TABLE project_members ADD CONSTRAINT fk__project_member__project FOREIGN KEY (`project_id`) REFERENCES `projects` (`id`) ON UPDATE CASCADE ON DELETE CASCADE'),
self::sql('ALTER TABLE project_members ADD CONSTRAINT fk__project_member__user FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON UPDATE CASCADE ON DELETE CASCADE'),
self::sql('ALTER TABLE project_members ADD CONSTRAINT fk__project_member__role FOREIGN KEY (`role_id`, `project_id`) REFERENCES `project_roles` (`role_id`, `project_id`) ON UPDATE CASCADE ON DELETE RESTRICT'),
self::sql('ALTER TABLE project_role_permissions ADD CONSTRAINT fk__project_role_permission__role FOREIGN KEY (`role_id`, `project_id`) REFERENCES `project_roles` (`role_id`, `project_id`) ON UPDATE CASCADE ON DELETE CASCADE'),
self::sql('ALTER TABLE project_roles ADD CONSTRAINT fk__project_role__project FOREIGN KEY (`project_id`) REFERENCES `projects` (`id`) ON UPDATE CASCADE ON DELETE CASCADE '),
self::sql('ALTER TABLE projects ADD CONSTRAINT fk__project__owner FOREIGN KEY (`owner_id`) REFERENCES `users` (`id`) ON UPDATE CASCADE ON DELETE RESTRICT'),
self::sql('ALTER TABLE project_user_settings ADD CONSTRAINT fk__project_user_setting__user FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON UPDATE CASCADE ON DELETE CASCADE'),
self::sql('ALTER TABLE project_user_settings ADD CONSTRAINT fk__project_user_setting__active_milestone FOREIGN KEY (`active_milestone`, `project_id`) REFERENCES `milestones` (`milestone_id`, `project_id`) ON UPDATE CASCADE ON DELETE CASCADE'),
self::sql('ALTER TABLE system_role_permissions ADD CONSTRAINT fk__system_role_permission__role FOREIGN KEY (`role_id`) REFERENCES `system_roles` (`id`) ON UPDATE CASCADE ON DELETE CASCADE'),
self::sql('ALTER TABLE user_logins ADD CONSTRAINT fk__user_login__user FOREIGN KEY (`id`) REFERENCES `users` (`id`) ON UPDATE CASCADE ON DELETE CASCADE'),
self::sql('ALTER TABLE users ADD CONSTRAINT fk__user__role FOREIGN KEY (`role_id`) REFERENCES `system_roles` (`id`) ON UPDATE CASCADE ON DELETE SET NULL'),
self::sql('ALTER TABLE issues DROP author_id_old'),
self::sql('ALTER TABLE issues DROP assignee_id_old'),
self::sql('ALTER TABLE project_members DROP user_id_old'),
self::sql('ALTER TABLE projects DROP owner_id_old'),
self::sql('ALTER TABLE project_user_settings DROP user_id_old'),
self::sql('ALTER TABLE user_logins DROP id_old')
];
}
}
?>

View File

@ -0,0 +1,29 @@
<?php
declare(strict_types = 1);
namespace Update\Tasks;
use PDO;
use Update\AbstractMigrationTask;
final class DropAllForeignKeysTask extends AbstractMigrationTask{
public function execute(PDO $db): void{
$stmt = $db->prepare(<<<SQL
SELECT DISTINCT TABLE_NAME AS tbl, CONSTRAINT_NAME AS constr
FROM information_schema.KEY_COLUMN_USAGE
WHERE TABLE_SCHEMA = :db_name AND REFERENCED_TABLE_SCHEMA = TABLE_SCHEMA
SQL
);
$stmt->bindValue('db_name', DB_NAME);
$stmt->execute();
$rows = $stmt->fetchAll();
foreach($rows as $row){
/** @noinspection SqlResolve */
$db->exec('ALTER TABLE `'.$row['tbl'].'` DROP FOREIGN KEY `'.$row['constr'].'`');
}
}
}
?>

View File

@ -0,0 +1,21 @@
<?php
declare(strict_types = 1);
namespace Update\Tasks;
use PDO;
use Update\AbstractMigrationTask;
final class SqlTask extends AbstractMigrationTask{
private string $sql;
public function __construct(string $sql){
$this->sql = $sql;
}
public function execute(PDO $db): void{
$db->exec($this->sql);
}
}
?>

View File

@ -0,0 +1,34 @@
<?php
declare(strict_types = 1);
namespace Update\Tasks;
use PDO;
use Update\AbstractMigrationTask;
final class SqlTransactionTask extends AbstractMigrationTask{
/**
* @var string[]
*/
private array $statements;
public function __construct(array $statements){
$this->statements = $statements;
}
public function prepare(PDO $db): void{
$db->beginTransaction();
}
public function execute(PDO $db): void{
foreach($this->statements as $sql){
$db->exec($sql);
}
}
public function finalize(PDO $db): void{
$db->commit();
}
}
?>

View File

@ -1,6 +1,8 @@
<?php
declare(strict_types = 1);
use Configuration\SystemConfig;
use Configuration\VersionFile;
use Logging\Log;
use Routing\Request;
use Routing\Router;
@ -15,6 +17,9 @@ define('TRACKER_RESOURCE_VERSION', ''); // autogenerated
define('CONFIG_FILE', __DIR__.'/config.php');
define('CONFIG_BACKUP_FILE', __DIR__.'/config.old.php');
define('VERSION_FILE', __DIR__.'/version.php');
define('VERSION_TMP_FILE', __DIR__.'/version.tmp.php');
setlocale(LC_ALL, 'C');
date_default_timezone_set('UTC');
header_remove('x-powered-by');
@ -31,13 +36,22 @@ spl_autoload_register(static function($class){
require_once 'utils.php';
if (!file_exists('config.php')){
if (!file_exists(CONFIG_FILE)){
require_once 'install.php';
return;
}
/** @noinspection PhpIncludeInspection */
require_once 'config.php';
require_once CONFIG_FILE;
if (!file_exists(VERSION_FILE)){
$version_file = new VersionFile(constant('INSTALLED_MIGRATION_VERSION'), 0);
$version_file->writeSafe(VERSION_FILE, VERSION_TMP_FILE);
SystemConfig::fromCurrentInstallation()->write(CONFIG_FILE);
}
/** @noinspection PhpIncludeInspection */
require_once VERSION_FILE;
if (!defined('DEBUG')){
define('DEBUG', false);
@ -61,7 +75,7 @@ define('BASE_URL_ENC', $base_url_protocol.(new UrlString($base_url_domain_path))
// Migration
if (TRACKER_MIGRATION_VERSION > INSTALLED_MIGRATION_VERSION){
if (TRACKER_MIGRATION_VERSION > MIGRATION_VERSION){
require_once 'update.php';
}

View File

@ -253,7 +253,7 @@ if (!empty($_POST) && $submit_action !== $action_value_conflict_cancel){
// Configuration File
if (empty($errors) && !file_put_contents(__DIR__.'/config.php', $config->generate(), LOCK_EX)){
if (empty($errors) && !$config->write(CONFIG_FILE)){
$errors[] = 'Error creating \'config.php\'.';
}

View File

@ -1,369 +1,57 @@
<?php
declare(strict_types = 1);
use Configuration\SystemConfig;
use Data\UserId;
use Database\DB;
use Logging\Log;
use Update\AbstractMigrationProcess;
use Update\MigrationManager;
use Update\Migrations\Migration6;
function begin_transaction(PDO $db): void{
if (!$db->inTransaction()){
$db->beginTransaction();
function get_migration(int $id): ?AbstractMigrationProcess{
switch($id){
case 6:
return new Migration6();
default:
return null;
}
}
function upgrade_config(PDO $db, int $version): void{
if (!file_put_contents(CONFIG_FILE, SystemConfig::fromCurrentInstallation()->generate($version), LOCK_EX)){
die('Lightning Tracker tried updating to a new version and failed updating the configuration file.');
}
if (isset($db) && $db->inTransaction()){
$db->commit();
}
}
$manager = new MigrationManager(MIGRATION_VERSION, MIGRATION_TASK);
/**
* @param string $path
* @return string
* @throws Exception
*/
function read_sql_file(string $path): string{
$file = __DIR__.'/~database/'.$path;
$contents = file_get_contents($file);
if ($contents === false){
throw new Exception('Error reading file \''.$path.'\'.');
}
return $contents;
try{
$db = DB::get();
}catch(Exception $e){
die('Lightning Tracker tried updating to a new version but could not connect to the database.');
}
try{
if (!copy(CONFIG_FILE, CONFIG_BACKUP_FILE)){
die('Lightning Tracker tried updating to a new version and failed creating a backup configuration file.');
}
$migration_version = INSTALLED_MIGRATION_VERSION;
if ($migration_version === 1){
$db = DB::get();
while(($version = $manager->getCurrentVersion()) < TRACKER_MIGRATION_VERSION){
$migration = get_migration($version);
$db->exec('ALTER TABLE system_roles ADD special BOOL DEFAULT FALSE NOT NULL');
/** @noinspection SqlResolve */
$db->exec('ALTER TABLE tracker_roles ADD special BOOL DEFAULT FALSE NOT NULL');
$stmt = $db->prepare(<<<SQL
SELECT DISTINCT TABLE_NAME AS tbl, CONSTRAINT_NAME AS constr
FROM information_schema.KEY_COLUMN_USAGE
WHERE TABLE_SCHEMA = :db_name AND REFERENCED_TABLE_SCHEMA = TABLE_SCHEMA
AND (
(TABLE_NAME = 'issues' AND COLUMN_NAME = 'milestone_id') OR
(TABLE_NAME = 'tracker_user_settings' AND COLUMN_NAME = 'active_milestone') OR
(TABLE_NAME = 'tracker_user_settings' AND COLUMN_NAME = 'tracker_id')
)
SQL
);
$stmt->bindValue('db_name', DB_NAME);
$stmt->execute();
$rows = $stmt->fetchAll();
foreach($rows as $row){
/** @noinspection SqlResolve */
$db->exec('ALTER TABLE `'.$row['tbl'].'` DROP FOREIGN KEY `'.$row['constr'].'`');
if ($migration === null){
die('Cannot automatically update the installed version.');
}
/** @noinspection SqlResolve */
$db->exec('ALTER TABLE milestones CHANGE id milestone_id INT NOT NULL AFTER tracker_id');
$db->exec('ALTER TABLE milestones DROP PRIMARY KEY');
/** @noinspection SqlResolve */
$db->exec('ALTER TABLE milestones ADD PRIMARY KEY (tracker_id, milestone_id)');
$tasks = $migration->getTasks();
/** @noinspection SqlResolve */
$db->exec(<<<SQL
ALTER TABLE issues
ADD FOREIGN KEY (`milestone_id`, `tracker_id`)
REFERENCES `milestones` (`milestone_id`, `tracker_id`)
ON UPDATE CASCADE
ON DELETE RESTRICT
SQL
);
/** @noinspection SqlResolve */
$db->exec(<<<SQL
ALTER TABLE tracker_user_settings
ADD FOREIGN KEY (`active_milestone`, `tracker_id`)
REFERENCES `milestones` (`milestone_id`, `tracker_id`)
ON UPDATE CASCADE
ON DELETE CASCADE
SQL
);
begin_transaction($db);
/** @noinspection SqlResolve */
$db->exec(<<<SQL
INSERT INTO tracker_roles (tracker_id, title, special)
SELECT tracker_id, 'Owner' AS title, TRUE AS special
FROM tracker_roles
GROUP BY tracker_id
SQL
);
/** @noinspection SqlResolve */
$db->exec(<<<SQL
INSERT INTO tracker_members (tracker_id, user_id, role_id)
SELECT t.id AS tracker_id, t.owner_id AS user_id, tr.id AS role_id
FROM trackers t
JOIN tracker_roles tr ON t.id = tr.tracker_id AND tr.title = 'Owner' AND tr.special = TRUE
SQL
);
/** @noinspection SqlWithoutWhere */
$db->exec('UPDATE milestones SET milestone_id = ordering');
upgrade_config($db, $migration_version = 2);
}
if ($migration_version === 2){
$db = DB::get();
$db->exec('ALTER TABLE milestones MODIFY ordering MEDIUMINT NOT NULL');
$stmt = $db->prepare(<<<SQL
SELECT DISTINCT TABLE_NAME AS tbl, CONSTRAINT_NAME AS constr
FROM information_schema.KEY_COLUMN_USAGE
WHERE TABLE_SCHEMA = :db_name
AND REFERENCED_TABLE_SCHEMA = TABLE_SCHEMA
AND (TABLE_NAME = 'tracker_members' AND REFERENCED_TABLE_NAME = 'tracker_roles')
SQL
);
$stmt->bindValue('db_name', DB_NAME);
$stmt->execute();
$rows = $stmt->fetchAll();
foreach($rows as $row){
/** @noinspection SqlResolve */
$db->exec('ALTER TABLE `'.$row['tbl'].'` DROP FOREIGN KEY `'.$row['constr'].'`');
while(($id = $manager->getCurrentTask()) < count($tasks)){
$task = $tasks[$id];
$task->prepare($db);
$task->execute($db);
$task->finalize($db);
$manager->finishTask();
}
/** @noinspection SqlResolve */
$db->exec('DROP TABLE tracker_role_perms');
/** @noinspection SqlResolve */
$db->exec('DROP TABLE tracker_roles');
$db->exec(read_sql_file('TrackerRoleTable.sql'));
$db->exec(read_sql_file('TrackerRolePermTable.sql'));
/** @noinspection SqlResolve */
$db->exec('UPDATE tracker_members SET role_id = NULL');
/** @noinspection SqlResolve */
$db->exec(<<<SQL
ALTER TABLE tracker_members
ADD FOREIGN KEY (`role_id`, `tracker_id`)
REFERENCES `tracker_roles` (`role_id`, `tracker_id`)
ON UPDATE CASCADE
ON DELETE RESTRICT
SQL
);
begin_transaction($db);
/** @noinspection SqlResolve */
$db->exec(<<<SQL
INSERT INTO tracker_roles (tracker_id, role_id, title, ordering, special)
SELECT t.id, 1 AS role_id, 'Owner' AS title, 0 AS ordering, TRUE AS special
FROM trackers t
GROUP BY t.id
SQL
);
/** @noinspection SqlResolve */
$db->exec(<<<SQL
INSERT INTO tracker_members (tracker_id, user_id, role_id)
SELECT t.id AS tracker_id, t.owner_id AS user_id, tr.role_id AS role_id
FROM trackers t
JOIN tracker_roles tr ON t.id = tr.tracker_id AND tr.title = 'Owner' AND tr.special = TRUE
ON DUPLICATE KEY UPDATE role_id = tr.role_id
SQL
);
upgrade_config($db, $migration_version = 3);
}
if ($migration_version === 3){
$db = DB::get();
/** @noinspection SqlResolve */
$db->exec('ALTER TABLE tracker_role_perms MODIFY permission ENUM (\'settings\', \'members.list\', \'members.manage\', \'milestones.manage\', \'issues.create\', \'issues.fields.all\', \'issues.edit.all\', \'issues.delete.all\') NOT NULL');
begin_transaction($db);
/** @noinspection SqlResolve */
$db->exec(<<<SQL
INSERT IGNORE INTO tracker_role_perms (tracker_id, role_id, permission)
SELECT tr.tracker_id AS tracker_id, tr.role_id AS role_id, 'issues.fields.all' AS permission
FROM tracker_roles tr
WHERE tr.title = 'Developer'
SQL
);
upgrade_config($db, $migration_version = 4);
}
if ($migration_version === 4){
$db = DB::get();
$db->exec('RENAME TABLE trackers TO projects');
$db->exec('RENAME TABLE tracker_roles TO project_roles');
$db->exec('RENAME TABLE tracker_role_perms TO project_role_perms');
$db->exec('RENAME TABLE tracker_members TO project_members');
$db->exec('RENAME TABLE tracker_user_settings TO project_user_settings');
/** @noinspection SqlResolve */
$db->exec('ALTER TABLE project_roles CHANGE tracker_id project_id INT NOT NULL');
/** @noinspection SqlResolve */
$db->exec('ALTER TABLE project_role_perms CHANGE tracker_id project_id INT NOT NULL');
/** @noinspection SqlResolve */
$db->exec('ALTER TABLE project_members CHANGE tracker_id project_id INT NOT NULL');
/** @noinspection SqlResolve */
$db->exec('ALTER TABLE project_user_settings CHANGE tracker_id project_id INT NOT NULL');
/** @noinspection SqlResolve */
$db->exec('ALTER TABLE issues CHANGE tracker_id project_id INT NOT NULL');
/** @noinspection SqlResolve */
$db->exec('ALTER TABLE milestones CHANGE tracker_id project_id INT NOT NULL');
upgrade_config($db, $migration_version = 5);
}
if ($migration_version === 5){
$db = DB::get();
$db->exec('RENAME TABLE project_role_perms TO project_role_permissions');
$db->exec('RENAME TABLE system_role_perms TO system_role_permissions');
$db->exec('ALTER TABLE system_role_permissions MODIFY permission ENUM (\'settings\', \'projects.list\', \'projects.list.all\', \'projects.create\', \'projects.manage\', \'users.list\', \'users.view.emails\', \'users.create\', \'users.manage\') NOT NULL');
upgrade_config($db, $migration_version = 6);
}
if ($migration_version === 6){
$db = DB::get();
$stmt = $db->prepare(<<<SQL
SELECT DISTINCT TABLE_NAME AS tbl, CONSTRAINT_NAME AS constr
FROM information_schema.KEY_COLUMN_USAGE
WHERE TABLE_SCHEMA = :db_name AND REFERENCED_TABLE_SCHEMA = TABLE_SCHEMA
SQL
);
$stmt->bindValue('db_name', DB_NAME);
$stmt->execute();
$rows = $stmt->fetchAll();
foreach($rows as $row){
/** @noinspection SqlResolve */
$db->exec('ALTER TABLE `'.$row['tbl'].'` DROP FOREIGN KEY `'.$row['constr'].'`');
}
$db->exec('ALTER TABLE users ADD public_id CHAR(9) NOT NULL FIRST');
$db->exec('ALTER TABLE issues CHANGE author_id author_id_old INT NULL');
$db->exec('ALTER TABLE issues CHANGE assignee_id assignee_id_old INT NULL');
$db->exec('ALTER TABLE project_members CHANGE user_id user_id_old INT NOT NULL');
$db->exec('ALTER TABLE projects CHANGE owner_id owner_id_old INT NOT NULL');
$db->exec('ALTER TABLE project_user_settings CHANGE user_id user_id_old INT NOT NULL');
$db->exec('ALTER TABLE user_logins CHANGE id id_old INT NOT NULL');
$db->exec('ALTER TABLE project_members DROP PRIMARY KEY');
$db->exec('ALTER TABLE project_user_settings DROP PRIMARY KEY');
$db->exec('ALTER TABLE user_logins DROP PRIMARY KEY');
/** @noinspection SqlResolve */
$db->exec('ALTER TABLE issues ADD author_id CHAR(9) NULL AFTER author_id_old');
/** @noinspection SqlResolve */
$db->exec('ALTER TABLE issues ADD assignee_id CHAR(9) NULL AFTER assignee_id_old');
/** @noinspection SqlResolve */
$db->exec('ALTER TABLE project_members ADD user_id CHAR(9) NOT NULL AFTER user_id_old');
/** @noinspection SqlResolve */
$db->exec('ALTER TABLE projects ADD owner_id CHAR(9) NOT NULL AFTER owner_id_old');
/** @noinspection SqlResolve */
$db->exec('ALTER TABLE project_user_settings ADD user_id CHAR(9) NOT NULL AFTER user_id_old');
/** @noinspection SqlResolve */
$db->exec('ALTER TABLE user_logins ADD id CHAR(9) NOT NULL AFTER id_old');
$stmt = $db->query('SELECT id FROM users');
while(($res = $stmt->fetchColumn()) !== false){
/** @noinspection SqlResolve */
$s2 = $db->prepare('UPDATE users SET public_id = ? WHERE id = ?');
$s2->bindValue(1, UserId::generateNew());
$s2->bindValue(2, (int)$res, PDO::PARAM_INT);
$s2->execute();
}
/** @noinspection SqlResolve, SqlWithoutWhere */
$db->exec('UPDATE issues SET author_id = (SELECT u.public_id FROM users u WHERE u.id = author_id_old)');
/** @noinspection SqlResolve, SqlWithoutWhere */
$db->exec('UPDATE issues SET assignee_id = (SELECT u.public_id FROM users u WHERE u.id = assignee_id_old)');
/** @noinspection SqlResolve, SqlWithoutWhere */
$db->exec('UPDATE project_members SET user_id = (SELECT u.public_id FROM users u WHERE u.id = user_id_old)');
/** @noinspection SqlResolve, SqlWithoutWhere */
$db->exec('UPDATE projects SET owner_id = (SELECT u.public_id FROM users u WHERE u.id = owner_id_old)');
/** @noinspection SqlResolve, SqlWithoutWhere */
$db->exec('UPDATE project_user_settings SET user_id = (SELECT u.public_id FROM users u WHERE u.id = user_id_old)');
/** @noinspection SqlResolve, SqlWithoutWhere */
$db->exec('UPDATE user_logins SET id = (SELECT u.public_id FROM users u WHERE u.id = id_old)');
$db->exec('ALTER TABLE users DROP id');
/** @noinspection SqlResolve */
$db->exec('ALTER TABLE users CHANGE public_id id CHAR(9) NOT NULL');
$db->exec('ALTER TABLE users ADD PRIMARY KEY (id)');
$db->exec('ALTER TABLE project_members ADD PRIMARY KEY (project_id, user_id)');
$db->exec('ALTER TABLE project_user_settings ADD PRIMARY KEY (project_id, user_id)');
$db->exec('ALTER TABLE user_logins ADD PRIMARY KEY (id, token)');
$db->exec('ALTER TABLE issues ADD CONSTRAINT fk__issue__project FOREIGN KEY (`project_id`) REFERENCES `projects` (`id`) ON UPDATE CASCADE ON DELETE CASCADE');
$db->exec('ALTER TABLE issues ADD CONSTRAINT fk__issue__author FOREIGN KEY (`author_id`) REFERENCES `users` (`id`) ON UPDATE CASCADE ON DELETE SET NULL');
$db->exec('ALTER TABLE issues ADD CONSTRAINT fk__issue__assignee FOREIGN KEY (`assignee_id`) REFERENCES `users` (`id`) ON UPDATE CASCADE ON DELETE SET NULL');
$db->exec('ALTER TABLE issues ADD CONSTRAINT fk__issue__milestone FOREIGN KEY (`milestone_id`, `project_id`) REFERENCES `milestones` (`milestone_id`, `project_id`) ON UPDATE CASCADE ON DELETE RESTRICT');
$db->exec('ALTER TABLE issues ADD CONSTRAINT fk__issue__scale FOREIGN KEY (`scale`) REFERENCES `issue_weights` (`scale`) ON UPDATE RESTRICT ON DELETE RESTRICT');
$db->exec('ALTER TABLE milestones ADD CONSTRAINT fk__milestone__project FOREIGN KEY (`project_id`) REFERENCES `projects` (`id`) ON UPDATE CASCADE ON DELETE CASCADE');
$db->exec('ALTER TABLE project_members ADD CONSTRAINT fk__project_member__project FOREIGN KEY (`project_id`) REFERENCES `projects` (`id`) ON UPDATE CASCADE ON DELETE CASCADE');
$db->exec('ALTER TABLE project_members ADD CONSTRAINT fk__project_member__user FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON UPDATE CASCADE ON DELETE CASCADE');
$db->exec('ALTER TABLE project_members ADD CONSTRAINT fk__project_member__role FOREIGN KEY (`role_id`, `project_id`) REFERENCES `project_roles` (`role_id`, `project_id`) ON UPDATE CASCADE ON DELETE RESTRICT');
$db->exec('ALTER TABLE project_role_permissions ADD CONSTRAINT fk__project_role_permission__role FOREIGN KEY (`role_id`, `project_id`) REFERENCES `project_roles` (`role_id`, `project_id`) ON UPDATE CASCADE ON DELETE CASCADE');
$db->exec('ALTER TABLE project_roles ADD CONSTRAINT fk__project_role__project FOREIGN KEY (`project_id`) REFERENCES `projects` (`id`) ON UPDATE CASCADE ON DELETE CASCADE ');
$db->exec('ALTER TABLE projects ADD CONSTRAINT fk__project__owner FOREIGN KEY (`owner_id`) REFERENCES `users` (`id`) ON UPDATE CASCADE ON DELETE RESTRICT');
$db->exec('ALTER TABLE project_user_settings ADD CONSTRAINT fk__project_user_setting__user FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON UPDATE CASCADE ON DELETE CASCADE');
$db->exec('ALTER TABLE project_user_settings ADD CONSTRAINT fk__project_user_setting__active_milestone FOREIGN KEY (`active_milestone`, `project_id`) REFERENCES `milestones` (`milestone_id`, `project_id`) ON UPDATE CASCADE ON DELETE CASCADE');
$db->exec('ALTER TABLE system_role_permissions ADD CONSTRAINT fk__system_role_permission__role FOREIGN KEY (`role_id`) REFERENCES `system_roles` (`id`) ON UPDATE CASCADE ON DELETE CASCADE');
$db->exec('ALTER TABLE user_logins ADD CONSTRAINT fk__user_login__user FOREIGN KEY (`id`) REFERENCES `users` (`id`) ON UPDATE CASCADE ON DELETE CASCADE');
$db->exec('ALTER TABLE users ADD CONSTRAINT fk__user__role FOREIGN KEY (`role_id`) REFERENCES `system_roles` (`id`) ON UPDATE CASCADE ON DELETE SET NULL');
/** @noinspection SqlResolve */
$db->exec('ALTER TABLE issues DROP author_id_old');
/** @noinspection SqlResolve */
$db->exec('ALTER TABLE issues DROP assignee_id_old');
/** @noinspection SqlResolve */
$db->exec('ALTER TABLE project_members DROP user_id_old');
/** @noinspection SqlResolve */
$db->exec('ALTER TABLE projects DROP owner_id_old');
/** @noinspection SqlResolve */
$db->exec('ALTER TABLE project_user_settings DROP user_id_old');
/** @noinspection SqlResolve */
$db->exec('ALTER TABLE user_logins DROP id_old');
upgrade_config($db, $migration_version = 7);
$manager->finishVersion();
}
}catch(Exception $e){
if (isset($db) && $db->inTransaction()){
Log::critical($e);
if ($db->inTransaction()){
$db->rollBack();
}
Log::critical($e);
die('Lightning Tracker tried updating to a new version and encountered an unexpected error. Please check the server logs.');
die('Lightning Tracker tried updating to a new version and encountered an unexpected error (migration '.$manager->getCurrentVersion().', task '.$manager->getCurrentTask().'). Please check the server logs.');
}
?>