From 1ee9fd168ccf346e52adc621e6cba04b3c575480 Mon Sep 17 00:00:00 2001 From: Steven Ngesera Date: Mon, 9 Dec 2024 12:36:46 +0300 Subject: [PATCH] feat(database): implement database migrations --- .../20241209010100_create_hm_user_table.sql | 7 + ...209010200_create_hm_user_session_table.sql | 8 + ...09010300_create_hm_user_settings_table.sql | 7 + ...dd_hm_version_to_hm_user_session_table.sql | 1 + ...0300_add_lock_to_hm_user_session_table.sql | 7 + scripts/setup_database.php | 181 +++++++----------- 6 files changed, 99 insertions(+), 112 deletions(-) create mode 100644 database/migrations/20241209010100_create_hm_user_table.sql create mode 100644 database/migrations/20241209010200_create_hm_user_session_table.sql create mode 100644 database/migrations/20241209010300_create_hm_user_settings_table.sql create mode 100644 database/migrations/20241209040200_add_hm_version_to_hm_user_session_table.sql create mode 100644 database/migrations/20241209040300_add_lock_to_hm_user_session_table.sql diff --git a/database/migrations/20241209010100_create_hm_user_table.sql b/database/migrations/20241209010100_create_hm_user_table.sql new file mode 100644 index 000000000..48d6e32a5 --- /dev/null +++ b/database/migrations/20241209010100_create_hm_user_table.sql @@ -0,0 +1,7 @@ +DROP TABLE IF EXISTS hm_user; + +CREATE TABLE IF NOT EXISTS hm_user ( + username VARCHAR(255), + hash VARCHAR(255), + PRIMARY KEY (username) +); diff --git a/database/migrations/20241209010200_create_hm_user_session_table.sql b/database/migrations/20241209010200_create_hm_user_session_table.sql new file mode 100644 index 000000000..3b8b2d494 --- /dev/null +++ b/database/migrations/20241209010200_create_hm_user_session_table.sql @@ -0,0 +1,8 @@ +DROP TABLE IF EXISTS hm_user_session; + +CREATE TABLE IF NOT EXISTS hm_user_session ( + hm_id VARCHAR(255), + data LONGBLOB, + date TIMESTAMP, + PRIMARY KEY (hm_id) +); diff --git a/database/migrations/20241209010300_create_hm_user_settings_table.sql b/database/migrations/20241209010300_create_hm_user_settings_table.sql new file mode 100644 index 000000000..bf5d047e0 --- /dev/null +++ b/database/migrations/20241209010300_create_hm_user_settings_table.sql @@ -0,0 +1,7 @@ +DROP TABLE IF EXISTS hm_user_settings; + +CREATE TABLE IF NOT EXISTS hm_user_settings ( + username VARCHAR(255), + settings LONGBLOB, + PRIMARY KEY (username) +); diff --git a/database/migrations/20241209040200_add_hm_version_to_hm_user_session_table.sql b/database/migrations/20241209040200_add_hm_version_to_hm_user_session_table.sql new file mode 100644 index 000000000..31f76f7fa --- /dev/null +++ b/database/migrations/20241209040200_add_hm_version_to_hm_user_session_table.sql @@ -0,0 +1 @@ +ALTER TABLE hm_user_session ADD COLUMN hm_version INT DEFAULT 1; diff --git a/database/migrations/20241209040300_add_lock_to_hm_user_session_table.sql b/database/migrations/20241209040300_add_lock_to_hm_user_session_table.sql new file mode 100644 index 000000000..0fe9fb9e0 --- /dev/null +++ b/database/migrations/20241209040300_add_lock_to_hm_user_session_table.sql @@ -0,0 +1,7 @@ +PRAGMA foreign_keys=off; +CREATE TEMPORARY TABLE hm_user_session_new AS +SELECT *, NULL AS lock FROM hm_user_session; + +DROP TABLE hm_user_session; +ALTER TABLE hm_user_session_new RENAME TO hm_user_session; +PRAGMA foreign_keys=on; \ No newline at end of file diff --git a/scripts/setup_database.php b/scripts/setup_database.php index 43ff8e2f5..e423d8311 100755 --- a/scripts/setup_database.php +++ b/scripts/setup_database.php @@ -4,6 +4,7 @@ define('APP_PATH', dirname(dirname(__FILE__)).'/'); define('VENDOR_PATH', APP_PATH.'vendor/'); +define('MIGRATIONS_PATH', APP_PATH.'database/migrations'); require VENDOR_PATH.'autoload.php'; require APP_PATH.'lib/framework.php'; @@ -22,23 +23,17 @@ $db_driver = $config->get('db_driver'); $connected = false; -$create_table = "CREATE TABLE IF NOT EXISTS"; -$alter_table = "ALTER TABLE"; -$bad_driver = "Unsupported db driver: {$db_driver}"; - // NOTE: these sql commands could be db agnostic if we change the blobs to text // Check if the required extensions for the configured DB driver are loaded if ($db_driver == 'mysql') { $required_extensions = ['mysqli', 'mysqlnd', 'pdo_mysql']; $missing_extensions = []; - foreach ($required_extensions as $extension) { if (!extension_loaded($extension)) { $missing_extensions[] = $extension; } } - if (!empty($missing_extensions)) { error_log('The following required MySQL extensions are missing: ' . implode(', ', $missing_extensions) . ". Please install them.\n"); exit(1); @@ -84,117 +79,79 @@ } } -function get_existing_columns($conn, $table_name, $db_driver) { - $columns = []; - try { - if ($db_driver == 'mysql') { - $query = "SHOW COLUMNS FROM {$table_name};"; - } elseif ($db_driver == 'pgsql') { - $query = "SELECT column_name FROM information_schema.columns WHERE table_name = '{$table_name}';"; - } elseif ($db_driver == 'sqlite') { - $query = "PRAGMA table_info({$table_name});"; - } else { - throw new Exception("Unsupported DB driver for column retrieval."); - } - - $stmt = $conn->query($query); - while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { - if ($db_driver == 'sqlite') { - $columns[] = $row['name']; - } else { - $columns[] = $row['Field'] ?? $row['column_name']; - } - } - } catch (Exception $e) { - printf("Error retrieving columns: %s\n", $e->getMessage()); +runMigrations($conn, MIGRATIONS_PATH); + +function runMigrations(PDO $pdo, string $migrationDir) { + global $db_driver; + switch ($db_driver) { + case 'mysql': + $createTableSql = " + CREATE TABLE IF NOT EXISTS migrations ( + id INT AUTO_INCREMENT PRIMARY KEY, + migration VARCHAR(255) NOT NULL, + batch INT NOT NULL, + applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + "; + break; + + case 'pgsql': + $createTableSql = " + CREATE TABLE IF NOT EXISTS migrations ( + id SERIAL PRIMARY KEY, + migration VARCHAR(255) NOT NULL, + batch INT NOT NULL, + applied_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP + ); + "; + break; + + case 'sqlite': + $createTableSql = " + CREATE TABLE IF NOT EXISTS migrations ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + migration TEXT NOT NULL, + batch INTEGER NOT NULL, + applied_at TEXT DEFAULT CURRENT_TIMESTAMP + ); + "; + break; + + default: + throw new \Exception("Unsupported database driver: " . $db_driver); } - return $columns; -} + $pdo->exec($createTableSql); + $executed = $pdo->query("SELECT migration FROM migrations") + ->fetchAll(PDO::FETCH_COLUMN); + + $migrationFiles = glob($migrationDir . '/*.sql'); + if ($db_driver !== 'sqlite') { + $migrationFiles = array_filter($migrationFiles, function ($file) { + return basename($file) !== '20241209040300_add_lock_to_hm_user_session_table.sql'; + }); + } + foreach ($migrationFiles as $file) { + if (in_array(basename($file), $executed)) { + continue; + } -function add_missing_columns($conn, $table_name, $required_columns, $db_driver) { - global $alter_table; - $existing_columns = get_existing_columns($conn, $table_name, $db_driver); - - foreach ($required_columns as $column_name => $column_def) { - if (!in_array($column_name, $existing_columns)) { - printf("Adding column %s to table %s ...\n", $column_name, $table_name); - $query = "{$alter_table} {$table_name} ADD COLUMN {$column_name} {$column_def};"; - try { - $conn->exec($query); - } catch (PDOException $e) { - printf("Error adding column %s: %s\n", $column_name, $e->getMessage()); - exit(1); - } + try { + $sql = file_get_contents($file); + $pdo->exec($sql); + + $stmt = $pdo->prepare("INSERT INTO migrations (migration, batch) VALUES (:migration, :batch)"); + $stmt->execute([ + 'migration' => basename($file), + 'batch' => 1 + ]); + echo "Migrated: " . basename($file) . PHP_EOL; + } catch (PDOException $e) { + die("Migration failed: " . $e->getMessage()); } } } +// if (strcasecmp($session_type, 'DB')==0) { -$tables = [ - 'hm_user_session' => [ - 'mysql' => [ - 'hm_id' => 'varchar(255) PRIMARY KEY', - 'data' => 'longblob', - 'hm_version' => 'INT DEFAULT 1', - 'date' => 'timestamp', - ], - 'sqlite' => [ - 'hm_id' => 'varchar(255) PRIMARY KEY', - 'data' => 'longblob', - 'lock' => 'INTEGER DEFAULT 0', - 'hm_version' => 'INT DEFAULT 1', - 'date' => 'timestamp', - ], - 'pgsql' => [ - 'hm_id' => 'varchar(255) PRIMARY KEY', - 'data' => 'text', - 'hm_version' => 'INT DEFAULT 1', - 'date' => 'timestamp', - ], - ], - 'hm_user' => [ - 'mysql' => [ - 'username' => 'varchar(255) PRIMARY KEY', - 'hash' => 'varchar(255)', - ], - 'sqlite' => [ - 'username' => 'varchar(255) PRIMARY KEY', - 'hash' => 'varchar(255)', - ], - 'pgsql' => [ - 'username' => 'varchar(255) PRIMARY KEY', - 'hash' => 'varchar(255)', - ], - ], - 'hm_user_settings' => [ - 'mysql' => [ - 'username' => 'varchar(255) PRIMARY KEY', - 'settings' => 'longblob', - ], - 'sqlite' => [ - 'username' => 'varchar(255) PRIMARY KEY', - 'settings' => 'longblob', - ], - 'pgsql' => [ - 'username' => 'varchar(255) PRIMARY KEY', - 'settings' => 'text', - ], - ], -]; - -if (strcasecmp($session_type, 'DB')==0) { - foreach ($tables as $table_name => $definitions) { - $required_columns = $definitions[$db_driver]; - - // Create table if it doesn't exist - $columns = implode(', ', array_map(fn($col, $def) => "$col $def", array_keys($required_columns), $required_columns)); - $query = "{$create_table} {$table_name} ({$columns});"; - $conn->exec($query); - - // Add any missing columns using ALTER TABLE - add_missing_columns($conn, $table_name, $required_columns, $db_driver); - } - - print("\nDatabase setup and migration finished\n"); -} +// } print("\nDb setup finished\n");