Skip to content

Commit

Permalink
feat(database): implement database migrations
Browse files Browse the repository at this point in the history
  • Loading branch information
Shadow243 committed Dec 14, 2024
1 parent 5f024a8 commit cd0b746
Show file tree
Hide file tree
Showing 15 changed files with 198 additions and 114 deletions.
4 changes: 2 additions & 2 deletions config/database.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,10 @@
| defined above, and the db user must have read-write access to it:
|
| Postgresql:
| CREATE TABLE hm_user_session (hm_id varchar(250) primary key not null, data text, date timestamp);
| CREATE TABLE hm_user_session (hm_id varchar(250) primary key not null, data text, hm_version INTEGEF DEFAULT 1, date timestamp);
|
| MySQL or SQLite:
| CREATE TABLE hm_user_session (hm_id varchar(180), data longblob, lock INTEGER DEFAULT 0, date timestamp, primary key (hm_id));
| CREATE TABLE hm_user_session (hm_id varchar(180), data longblob, hm_version INTEGEF DEFAULT 1, lock INTEGER DEFAULT 0, date timestamp, primary key (hm_id));
|
|
| DB Authentication
Expand Down
Original file line number Diff line number Diff line change
@@ -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)
);
Original file line number Diff line number Diff line change
@@ -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)
);
Original file line number Diff line number Diff line change
@@ -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)
);
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE hm_user_session ADD COLUMN hm_version INT DEFAULT 1;
12 changes: 12 additions & 0 deletions database/migrations/pgsql/20241209010100_create_hm_user_table.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
DROP TABLE IF EXISTS hm_user;

DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM information_schema.tables
WHERE table_name = 'hm_user') THEN
CREATE TABLE hm_user (
username VARCHAR(255) PRIMARY KEY,
hash VARCHAR(255)
);
END IF;
END $$;
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
DROP TABLE IF EXISTS hm_user_session;

DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM information_schema.tables
WHERE table_name = 'hm_user_session') THEN
CREATE TABLE hm_user_session (
hm_id VARCHAR(255) PRIMARY KEY,
data BYTEA,
date TIMESTAMP
);
END IF;
END $$;
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
DROP TABLE IF EXISTS hm_user_settings;

DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM information_schema.tables
WHERE table_name = 'hm_user_settings') THEN
CREATE TABLE hm_user_settings (
username VARCHAR(255) PRIMARY KEY,
settings BYTEA
);
END IF;
END $$;
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1
FROM information_schema.columns
WHERE table_name = 'hm_user_session'
AND column_name = 'hm_version'
) THEN
ALTER TABLE hm_user_session
ADD COLUMN hm_version INT DEFAULT 1;
END IF;
END $$;
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
DROP TABLE IF EXISTS hm_user;

CREATE TABLE IF NOT EXISTS hm_user (
username TEXT NOT NULL,
hash TEXT NOT NULL,
PRIMARY KEY (username)
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
DROP TABLE IF EXISTS hm_user_session;

CREATE TABLE IF NOT EXISTS hm_user_session (
hm_id TEXT NOT NULL,
data BLOB,
date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (hm_id)
);

Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
DROP TABLE IF EXISTS hm_user_settings;

CREATE TABLE IF NOT EXISTS hm_user_settings (
username TEXT NOT NULL,
settings BLOB,
PRIMARY KEY (username)
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
PRAGMA foreign_keys=off;

CREATE TABLE hm_user_session_new (
hm_id TEXT NOT NULL,
data BLOB,
date TIMESTAMP,
hm_version INTEGER DEFAULT 1,
PRIMARY KEY (hm_id)
);

INSERT INTO hm_user_session_new (hm_id, data, date)
SELECT hm_id, data, date
FROM hm_user_session;

DROP TABLE hm_user_session;
ALTER TABLE hm_user_session_new RENAME TO hm_user_session;

PRAGMA foreign_keys=on;
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
PRAGMA foreign_keys=off;

CREATE TABLE hm_user_session_new (
hm_id TEXT PRIMARY KEY,
data BLOB,
date TIMESTAMP,
hm_version INTEGER DEFAULT 1,
lock INTEGER DEFAULT 0
);

INSERT INTO hm_user_session_new (hm_id, data, date, hm_version)
SELECT hm_id, data, date, hm_version
FROM hm_user_session;

DROP TABLE hm_user_session;

ALTER TABLE hm_user_session_new RENAME TO hm_user_session;

PRAGMA foreign_keys=on;
176 changes: 64 additions & 112 deletions scripts/setup_database.php
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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);
Expand Down Expand Up @@ -84,117 +79,74 @@
}
}

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 .'/'.$db_driver.'/*.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");

0 comments on commit cd0b746

Please sign in to comment.