From f0b5b080babcc5d2989d309d33c59cd5b4fe58af Mon Sep 17 00:00:00 2001 From: Crypto137 Date: Wed, 28 Aug 2024 18:33:57 +0300 Subject: [PATCH] Implement SQLite database file initialization and migration --- src/MHServerEmu.Core/Helpers/FileHelper.cs | 8 ++ .../Data/Account.db | Bin 94208 -> 0 bytes .../Data/SQLite/InitializeDatabase.sql | 72 ++++++++++ .../Data/SQLite/Migrations/0.sql | 60 ++++++++ src/MHServerEmu.DatabaseAccess/IDBManager.cs | 2 +- .../MHServerEmu.DatabaseAccess.csproj | 5 +- .../SQLite/SQLiteDBManager.cs | 132 ++++++++++++++++-- .../SQLite/SQLiteScripts.cs | 28 ++++ 8 files changed, 296 insertions(+), 11 deletions(-) delete mode 100644 src/MHServerEmu.DatabaseAccess/Data/Account.db create mode 100644 src/MHServerEmu.DatabaseAccess/Data/SQLite/InitializeDatabase.sql create mode 100644 src/MHServerEmu.DatabaseAccess/Data/SQLite/Migrations/0.sql create mode 100644 src/MHServerEmu.DatabaseAccess/SQLite/SQLiteScripts.cs diff --git a/src/MHServerEmu.Core/Helpers/FileHelper.cs b/src/MHServerEmu.Core/Helpers/FileHelper.cs index 3c92b572..a21f7dcb 100644 --- a/src/MHServerEmu.Core/Helpers/FileHelper.cs +++ b/src/MHServerEmu.Core/Helpers/FileHelper.cs @@ -11,6 +11,14 @@ namespace MHServerEmu.Core.Helpers public static readonly string ServerRoot = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); public static readonly string DataDirectory = Path.Combine(ServerRoot, "Data"); + /// + /// Returns a path relative to server root directory. + /// + public static string GetRelativePath(string filePath) + { + return Path.GetRelativePath(ServerRoot, filePath); + } + /// /// Deserializes a from a JSON file located at the specified path. /// diff --git a/src/MHServerEmu.DatabaseAccess/Data/Account.db b/src/MHServerEmu.DatabaseAccess/Data/Account.db deleted file mode 100644 index 7c1a76f3cfa498e43f6e0b53c76de8b610e9142b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 94208 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 e@q?h?cwoT|@OaeK6IBS - /// Updates the Player and Avatar tables in the database with the data from the provided . + /// Updates persistent game data for the provided . /// public bool UpdateAccountData(DBAccount account); diff --git a/src/MHServerEmu.DatabaseAccess/MHServerEmu.DatabaseAccess.csproj b/src/MHServerEmu.DatabaseAccess/MHServerEmu.DatabaseAccess.csproj index 4be7efcd..7f01e441 100644 --- a/src/MHServerEmu.DatabaseAccess/MHServerEmu.DatabaseAccess.csproj +++ b/src/MHServerEmu.DatabaseAccess/MHServerEmu.DatabaseAccess.csproj @@ -27,7 +27,10 @@ - + + PreserveNewest + + PreserveNewest diff --git a/src/MHServerEmu.DatabaseAccess/SQLite/SQLiteDBManager.cs b/src/MHServerEmu.DatabaseAccess/SQLite/SQLiteDBManager.cs index f61b6068..8893ef3c 100644 --- a/src/MHServerEmu.DatabaseAccess/SQLite/SQLiteDBManager.cs +++ b/src/MHServerEmu.DatabaseAccess/SQLite/SQLiteDBManager.cs @@ -8,6 +8,8 @@ namespace MHServerEmu.DatabaseAccess.SQLite { public class SQLiteDBManager : IDBManager { + private const int CurrentSchemaVersion = 1; + private static readonly Logger Logger = LogManager.CreateLogger(); private string _connectionString; @@ -19,10 +21,20 @@ namespace MHServerEmu.DatabaseAccess.SQLite public bool Initialize() { string dbPath = Path.Combine(FileHelper.DataDirectory, "Account.db"); - if (File.Exists(dbPath) == false) return Logger.FatalReturn(false, $"Initialize(): {dbPath} not found"); - _connectionString = $"Data Source={dbPath}"; - Logger.Info("Established database connection"); + + if (File.Exists(dbPath) == false) + { + if (InitializeDatabaseFile(dbPath) == false) + return false; + } + else + { + if (MigrateDatabaseFileToCurrentSchema(dbPath) == false) + return false; + } + + Logger.Info($"Initialized database"); return true; } @@ -44,7 +56,8 @@ namespace MHServerEmu.DatabaseAccess.SQLite public bool QueryIsPlayerNameTaken(string playerName) { - using SQLiteConnection connection = new(_connectionString); + using SQLiteConnection connection = GetConnection(); + // This check is case insensitive (COLLATE NOCASE) var results = connection.Query("SELECT PlayerName FROM Account WHERE PlayerName = @PlayerName COLLATE NOCASE", new { PlayerName = playerName }); return results.Any(); @@ -52,8 +65,7 @@ namespace MHServerEmu.DatabaseAccess.SQLite public bool InsertAccount(DBAccount account) { - using SQLiteConnection connection = new(_connectionString); - connection.Open(); + using SQLiteConnection connection = GetConnection(); try { @@ -70,7 +82,7 @@ namespace MHServerEmu.DatabaseAccess.SQLite public bool UpdateAccount(DBAccount account) { - using SQLiteConnection connection = new(_connectionString); + using SQLiteConnection connection = GetConnection(); try { @@ -87,8 +99,7 @@ namespace MHServerEmu.DatabaseAccess.SQLite public bool UpdateAccountData(DBAccount account) { - using SQLiteConnection connection = new(_connectionString); - connection.Open(); + using SQLiteConnection connection = GetConnection(); // Use a transaction to make sure all data is saved using (var transaction = connection.BeginTransaction()) @@ -143,6 +154,103 @@ namespace MHServerEmu.DatabaseAccess.SQLite } } + /// + /// Creates and opens a new . + /// + private SQLiteConnection GetConnection() + { + SQLiteConnection connection = new(_connectionString); + connection.Open(); + return connection; + } + + private bool InitializeDatabaseFile(string dbPath) + { + // Create a new database file if it does not exist + string initializationScript = SQLiteScripts.GetInitializationScript(); + if (initializationScript == string.Empty) + return Logger.ErrorReturn(false, "InitializeDatabaseFile(): Failed to get database initialization script"); + + SQLiteConnection.CreateFile(dbPath); + using SQLiteConnection connection = GetConnection(); + connection.Execute(initializationScript); + + Logger.Info($"Initialized a new database file at {Path.GetRelativePath(FileHelper.ServerRoot, dbPath)} using schema version {CurrentSchemaVersion}"); + return true; + } + + private bool MigrateDatabaseFileToCurrentSchema(string dbPath) + { + // Migrate existing database if needed + int schemaVersion = GetSchemaVersion(); + if (schemaVersion > CurrentSchemaVersion) + return Logger.ErrorReturn(false, $"Initialize(): Existing database file uses unsupported schema version {schemaVersion} (current = {CurrentSchemaVersion})"); + + Logger.Info($"Found existing database file with schema version {schemaVersion} (current = {CurrentSchemaVersion})"); + + if (schemaVersion == CurrentSchemaVersion) + return true; + + // Create a back to fall back to if something goes wrong + string backupDbPath = $"{dbPath}.backup"; + File.Copy(dbPath, backupDbPath); + + using SQLiteConnection connection = GetConnection(); + bool success = true; + + while (schemaVersion < CurrentSchemaVersion) + { + Logger.Info($"Migrating version {schemaVersion} => {schemaVersion + 1}..."); + + string migrationScript = SQLiteScripts.GetMigrationScript(schemaVersion); + if (migrationScript == string.Empty) + { + Logger.Error($"MigrateDatabaseFileToCurrentSchema(): Failed to get database migration script for version {schemaVersion}"); + success = false; + break; + } + + connection.Execute(migrationScript); + connection.Execute($"PRAGMA user_version = {++schemaVersion}"); + } + + success &= GetSchemaVersion() == CurrentSchemaVersion; + + if (success == false) + { + // Restore backup + File.Delete(dbPath); + File.Move(backupDbPath, dbPath); + return Logger.ErrorReturn(false, "MigrateDatabaseFileToCurrentSchema(): Migration failed, backup restored"); + } + + Logger.Info($"Successfully migrated to schema version {CurrentSchemaVersion}"); + return true; + } + + /// + /// Returns the user_version value of the current database. + /// + private int GetSchemaVersion() + { + using SQLiteConnection connection = GetConnection(); + + var queryResult = connection.Query("PRAGMA user_version"); + if (queryResult.Any()) + return queryResult.First(); + + return Logger.WarnReturn(-1, "GetSchemaVersion(): Failed to query user_version from the DB"); + } + + /// + /// Sets the user_version value of the current database. + /// + private void SetSchemaVersion(int version) + { + using SQLiteConnection connection = GetConnection(); + connection.Execute($"PRAGMA user_version = {version}"); + } + /// /// Loads account data for the specified and maps relations. /// @@ -177,12 +285,18 @@ namespace MHServerEmu.DatabaseAccess.SQLite } } + /// + /// Loads instances belonging to the specified container from the specified table. + /// private static IEnumerable LoadEntitiesFromTable(SQLiteConnection connection, string tableName, long containerDbGuid) { var @params = new { ContainerDbGuid = containerDbGuid }; return connection.Query($"SELECT * FROM {tableName} WHERE ContainerDbGuid = @ContainerDbGuid", @params); } + /// + /// Updates instances belonging to the specified container in the specified table using the provided . + /// private static void UpdateEntityTable(SQLiteConnection connection, SQLiteTransaction transaction, string tableName, long containerDbGuid, DBEntityCollection dbEntityCollection) { diff --git a/src/MHServerEmu.DatabaseAccess/SQLite/SQLiteScripts.cs b/src/MHServerEmu.DatabaseAccess/SQLite/SQLiteScripts.cs new file mode 100644 index 00000000..585e611f --- /dev/null +++ b/src/MHServerEmu.DatabaseAccess/SQLite/SQLiteScripts.cs @@ -0,0 +1,28 @@ +using MHServerEmu.Core.Helpers; +using MHServerEmu.Core.Logging; + +namespace MHServerEmu.DatabaseAccess.SQLite +{ + public static class SQLiteScripts + { + private static readonly Logger Logger = LogManager.CreateLogger(); + + public static string GetInitializationScript() + { + string filePath = Path.Combine(FileHelper.DataDirectory, "SQLite", "InitializeDatabase.sql"); + if (File.Exists(filePath) == false) + return Logger.WarnReturn(string.Empty, $"GetDatabaseInitializationScript(): Initialization script file not found at {FileHelper.GetRelativePath(filePath)}"); + + return File.ReadAllText(filePath); + } + + public static string GetMigrationScript(int currentVersion) + { + string filePath = Path.Combine(FileHelper.DataDirectory, "SQLite", "Migrations", $"{currentVersion}.sql"); + if (File.Exists(filePath) == false) + return Logger.WarnReturn(string.Empty, $"GetMigrationScript(): Migration script for version {currentVersion} not found at {FileHelper.GetRelativePath(filePath)}"); + + return File.ReadAllText(filePath); + } + } +}