mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 19:03:43 +00:00
Cars and rockets built before builds were recorded were saved with subkey 0 and have no ugc_modular_build row, so the client has no blueprint id to ask for their icon with. The runtime fix only reaches characters that load again. Migrations mysql 98 and sqlite 81 (98/81 because the guilds work takes 97/80) run ModularBuildIdMigration after the SQL: every saved item with modules (x@ma) and no subkey gets what a new build gets, as the character-load fix does: a persistent id from object_id_tracker (with the character bit) as its subkey and a ugc_modular_build row with its modules and the character as owner. The XML is written with UpdateCharacterXml. Tried on a copy of a server's database with a couple of thousand such items: every one got an id and a build row, in seconds. Check: after the migration, old cars and rockets show their icons in the backpack (the UGC server makes them like any build) and still work (equip, race, launch). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
181 lines
6.8 KiB
C++
181 lines
6.8 KiB
C++
#include "MigrationRunner.h"
|
|
|
|
#include "BrickByBrickFix.h"
|
|
#include "CDClientDatabase.h"
|
|
#include "Database.h"
|
|
#include "Game.h"
|
|
#include "GeneralUtils.h"
|
|
#include "Logger.h"
|
|
#include "BinaryPathFinder.h"
|
|
#include "ModelNormalizeMigration.h"
|
|
#include "ModularBuildIdMigration.h"
|
|
|
|
#include <fstream>
|
|
|
|
Migration LoadMigration(std::string folder, std::string path) {
|
|
Migration migration{};
|
|
std::ifstream file(BinaryPathFinder::GetBinaryDir() / "migrations/" / folder / path);
|
|
|
|
if (file.is_open()) {
|
|
std::string line;
|
|
std::string total = "";
|
|
|
|
// Keep the line ends: a -- comment runs to the end of its line, so without them it would swallow every
|
|
// statement after it (and, as the migrations are joined, every later migration too)
|
|
while (std::getline(file, line)) {
|
|
total += line;
|
|
total += '\n';
|
|
}
|
|
|
|
file.close();
|
|
|
|
migration.name = path;
|
|
migration.data = total;
|
|
}
|
|
|
|
return migration;
|
|
}
|
|
|
|
void MigrationRunner::RunMigrations() {
|
|
Database::Get()->CreateMigrationHistoryTable();
|
|
|
|
// has to be here because when moving the files to the new folder, the migration_history table is not updated so it will run them all again.
|
|
|
|
const auto migrationFolder = Database::GetMigrationFolder();
|
|
if (!Database::Get()->IsMigrationRun("17_migration_for_migrations.sql") && migrationFolder == "mysql") {
|
|
LOG("Running migration: 17_migration_for_migrations.sql");
|
|
Database::Get()->ExecuteCustomQuery("UPDATE `migration_history` SET `name` = SUBSTR(`name`, 5) WHERE `name` LIKE \"dlu%\";");
|
|
Database::Get()->InsertMigration("17_migration_for_migrations.sql");
|
|
}
|
|
|
|
std::string finalSQL = "";
|
|
bool runSd0Migrations = false;
|
|
bool runNormalizeMigrations = false;
|
|
bool runNormalizeAfterFirstPartMigrations = false;
|
|
bool runBrickBuildsNotOnGrid = false;
|
|
bool runModularBuildIds = false;
|
|
for (const auto& entry : GeneralUtils::GetSqlFileNamesFromFolder((BinaryPathFinder::GetBinaryDir() / "./migrations/dlu/" / migrationFolder).string())) {
|
|
auto migration = LoadMigration("dlu/" + migrationFolder + "/", entry);
|
|
|
|
if (migration.data.empty()) {
|
|
continue;
|
|
}
|
|
|
|
if (Database::Get()->IsMigrationRun(migration.name)) continue;
|
|
|
|
LOG("Running migration: %s", migration.name.c_str());
|
|
if (migration.name == "5_brick_model_sd0.sql") {
|
|
runSd0Migrations = true;
|
|
} else if (migration.name.ends_with("_normalize_model_positions.sql")) {
|
|
runNormalizeMigrations = true;
|
|
} else if (migration.name.ends_with("_normalize_model_positions_after_first_part.sql")) {
|
|
runNormalizeAfterFirstPartMigrations = true;
|
|
} else if (migration.name.ends_with("_brickbuilds_not_on_grid.sql")) {
|
|
runBrickBuildsNotOnGrid = true;
|
|
} else if (migration.name.ends_with("_modular_build_ids.sql")) {
|
|
runModularBuildIds = true;
|
|
} else {
|
|
finalSQL.append(migration.data.c_str());
|
|
}
|
|
|
|
Database::Get()->InsertMigration(migration.name);
|
|
}
|
|
|
|
if (finalSQL.empty() && !runSd0Migrations && !runNormalizeMigrations && !runNormalizeAfterFirstPartMigrations && !runBrickBuildsNotOnGrid && !runModularBuildIds) {
|
|
LOG("Server database is up to date.");
|
|
return;
|
|
}
|
|
|
|
if (!finalSQL.empty()) {
|
|
auto migration = SplitStatements(finalSQL, migrationFolder == "mysql");
|
|
for (auto& query : migration) {
|
|
try {
|
|
if (query.empty()) continue;
|
|
Database::Get()->ExecuteCustomQuery(query);
|
|
} catch (std::exception& e) {
|
|
LOG("Encountered error running migration: %s", e.what());
|
|
}
|
|
}
|
|
}
|
|
|
|
// Do this last on the off chance none of the other migrations have been run yet.
|
|
if (runSd0Migrations) {
|
|
uint32_t numberOfUpdatedModels = BrickByBrickFix::UpdateBrickByBrickModelsToSd0();
|
|
LOG("%i models were updated from zlib to sd0.", numberOfUpdatedModels);
|
|
uint32_t numberOfTruncatedModels = BrickByBrickFix::TruncateBrokenBrickByBrickXml();
|
|
LOG("%i models were truncated from the database.", numberOfTruncatedModels);
|
|
}
|
|
|
|
if (runNormalizeMigrations) {
|
|
ModelNormalizeMigration::Run();
|
|
}
|
|
|
|
if (runNormalizeAfterFirstPartMigrations) {
|
|
ModelNormalizeMigration::RunAfterFirstPart();
|
|
}
|
|
|
|
if (runBrickBuildsNotOnGrid) {
|
|
ModelNormalizeMigration::RunBrickBuildGrid();
|
|
}
|
|
|
|
// After the SQL, which makes ugc_modular_build and object_id_tracker on a new database
|
|
if (runModularBuildIds) {
|
|
ModularBuildIdMigration::Run();
|
|
}
|
|
}
|
|
|
|
void MigrationRunner::RunSQLiteMigrations() {
|
|
auto cdstmt = CDClientDatabase::CreatePreppedStmt("CREATE TABLE IF NOT EXISTS migration_history (name TEXT NOT NULL, date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP);");
|
|
cdstmt.execQuery().finalize();
|
|
cdstmt.finalize();
|
|
|
|
if (CDClientDatabase::ExecuteQuery("select * from migration_history where name = \"7_migration_for_migrations.sql\";").eof()) {
|
|
LOG("Running migration: 7_migration_for_migrations.sql");
|
|
CDClientDatabase::ExecuteQuery("UPDATE `migration_history` SET `name` = SUBSTR(`name`, 10) WHERE `name` LIKE \"cdserver%\";");
|
|
CDClientDatabase::ExecuteQuery("INSERT INTO migration_history (name) VALUES (\"7_migration_for_migrations.sql\");");
|
|
}
|
|
|
|
for (const auto& entry : GeneralUtils::GetSqlFileNamesFromFolder((BinaryPathFinder::GetBinaryDir() / "migrations/cdserver/").string())) {
|
|
auto migration = LoadMigration("cdserver/", entry);
|
|
|
|
if (migration.data.empty()) continue;
|
|
|
|
// Check if there is an entry in the migration history table on the cdclient database.
|
|
cdstmt = CDClientDatabase::CreatePreppedStmt("SELECT name FROM migration_history WHERE name = ?;");
|
|
cdstmt.bind(static_cast<int32_t>(1), migration.name.c_str());
|
|
auto cdres = cdstmt.execQuery();
|
|
|
|
if (!cdres.eof()) continue;
|
|
|
|
// Check first if there is entry in the migration history table on the main database.
|
|
if (Database::Get()->IsMigrationRun(migration.name)) {
|
|
// Insert into cdclient database if there is an entry in the main database but not the cdclient database.
|
|
cdstmt = CDClientDatabase::CreatePreppedStmt("INSERT INTO migration_history (name) VALUES (?);");
|
|
cdstmt.bind(static_cast<int32_t>(1), migration.name.c_str());
|
|
cdstmt.execQuery();
|
|
continue;
|
|
}
|
|
|
|
// Doing these 1 migration at a time since one takes a long time and some may think it is crashing.
|
|
// This will at the least guarentee that the full migration needs to be run in order to be counted as "migrated".
|
|
LOG("Executing migration: %s. This may take a while. Do not shut down server.", migration.name.c_str());
|
|
CDClientDatabase::ExecuteQuery("BEGIN TRANSACTION;");
|
|
for (const auto& dml : SplitStatements(migration.data, false)) {
|
|
if (dml.empty()) continue;
|
|
try {
|
|
CDClientDatabase::ExecuteDML(dml.c_str());
|
|
} catch (CppSQLite3Exception& e) {
|
|
LOG("Encountered error running DML command: (%i) : %s", e.errorCode(), e.errorMessage());
|
|
}
|
|
}
|
|
|
|
// Insert into cdclient database.
|
|
cdstmt = CDClientDatabase::CreatePreppedStmt("INSERT INTO migration_history (name) VALUES (?);");
|
|
cdstmt.bind(static_cast<int32_t>(1), migration.name.c_str());
|
|
cdstmt.execQuery();
|
|
CDClientDatabase::ExecuteQuery("COMMIT;");
|
|
}
|
|
|
|
LOG("CDServer database is up to date.");
|
|
}
|