Browse Source

Adds support for automatic SQL updates
* SQL updates are checked at individual server start up and applied based on several unique factors.
* Adds a master sql_update_db.yml database that houses everything in one.
* Progress is saved into a local flat file for each available schema.
* Removes usage of individual SQL files.

aleos 5 years ago
parent
commit
920844bf49

+ 6 - 0
sql-files/saves/char-sql_update_db.yml

@@ -0,0 +1,6 @@
+# THIS FILE IS USED AS TRACKER FOR SQL UPDATES.
+# DO NOT TOUCH THE CONTENT OF THIS FILE AS IT IS AUTOMATICALLY GENERATED.
+
+Header:
+  Type: SQL_UPDATE_DB
+  Version: 1

+ 6 - 0
sql-files/saves/log-sql_update_db.yml

@@ -0,0 +1,6 @@
+# THIS FILE IS USED AS TRACKER FOR SQL UPDATES.
+# DO NOT TOUCH THE CONTENT OF THIS FILE AS IT IS AUTOMATICALLY GENERATED.
+
+Header:
+  Type: SQL_UPDATE_DB
+  Version: 1

+ 6 - 0
sql-files/saves/login-sql_update_db.yml

@@ -0,0 +1,6 @@
+# THIS FILE IS USED AS TRACKER FOR SQL UPDATES.
+# DO NOT TOUCH THE CONTENT OF THIS FILE AS IT IS AUTOMATICALLY GENERATED.
+
+Header:
+  Type: SQL_UPDATE_DB
+  Version: 1

+ 6 - 0
sql-files/saves/map-sql_update_db.yml

@@ -0,0 +1,6 @@
+# THIS FILE IS USED AS TRACKER FOR SQL UPDATES.
+# DO NOT TOUCH THE CONTENT OF THIS FILE AS IT IS AUTOMATICALLY GENERATED.
+
+Header:
+  Type: SQL_UPDATE_DB
+  Version: 1

+ 119 - 0
sql-files/sql_update_db.yml

@@ -0,0 +1,119 @@
+# This file is a part of rAthena.
+#   Copyright(C) 2020 rAthena Development Team
+#   https://rathena.org - https://github.com/rathena
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+###########################################################################
+# SQL Update Database
+###########################################################################
+#
+# SQL Update Settings
+#
+###########################################################################
+# - Id                       Patch ID.
+#   Script                   Individual SQL script to execute.
+#   Mode                     Server mode the update applies to. (Default: Both)
+#   Database                 Database the update applies to. (Default: Normal)
+#   Skip                     Skip the update. (Default: false)
+#   PatchDate                Timestamp of when succesfully patched. Automatically generated by the server.
+###########################################################################
+
+Header:
+  Type: SQL_UPDATE_DB
+  Version: 1
+
+Body:
+  # 14.3 Flaming Basin - Dimensional Travel Quest variable fix
+  - Id: 1
+    Script: >
+      UPDATE `char_reg_num` SET `key` = 'ep14_3_newerabs' WHERE `key` = 'ep14_3_dimensional_travel' AND `index` = 0 AND `value` < 2;
+    Mode: Renewal
+  - Id: 2
+    Script: >
+      UPDATE `char_reg_num` SET `key` = 'ep14_3_newerabs', `value` = 3 WHERE `key` = 'ep14_3_dimensional_travel' AND `index` = 0 AND `value` = 2;
+    Mode: Renewal
+  - Id: 3
+    Script: >
+      UPDATE `char_reg_num` SET `key` = 'ep14_3_newerabs', `value` = `value` + 2 WHERE `key` = 'ep14_3_dimensional_travel' AND `index` = 0 AND `value` < 8;
+    Mode: Renewal
+  - Id: 4
+    Script: >
+      UPDATE `char_reg_num` SET `key` = 'ep14_3_newerabs', `value` = `value` + 7 WHERE `key` = 'ep14_3_dimensional_travel' AND `index` = 0 AND `value` > 7;
+    Mode: Renewal
+
+  # CR_CULTIVATION removal from Renewal
+  - Id: 5
+    Script: >
+      UPDATE `char` c, `skill` s SET `c`.skill_point = `c`.skill_point + `s`.lv WHERE `s`.id = 491 AND `c`.char_id = `s`.char_id;
+    Mode: Renewal
+  - Id: 6
+    Script: >
+      DELETE FROM `skill` WHERE `id` = 491;
+    Mode: Renewal
+
+  # AB_EUCHARISTICA removal
+  - Id: 7
+    Script: >
+      UPDATE `char` c, `skill` s SET `c`.skill_point = `c`.skill_point + `s`.lv WHERE `s`.id = 2049 AND `c`.char_id = `s`.char_id;
+  - Id: 8 
+    Script: >
+      DELETE FROM `skill` WHERE `id` = 2049;
+
+  # GN_SLINGITEM removal
+  - Id: 9
+    Script: >
+      UPDATE `char` c, `skill` s SET `c`.skill_point = `c`.skill_point + `s`.lv WHERE `s`.id = 2493 AND `c`.char_id = `s`.char_id;
+  - Id: 10
+    Script: >
+      DELETE FROM `skill` WHERE `id` = 2493;
+
+  # GN_MAKEBOMB removal
+  - Id: 11
+    Script: >
+      UPDATE `char` c, `skill` s SET `c`.skill_point = `c`.skill_point + `s`.lv WHERE `s`.id = 2496 AND `c`.char_id = `s`.char_id;
+  - Id: 12
+    Script: >
+      DELETE FROM `skill` WHERE `id` = 2496;
+
+  # HT_SANDMAN removal from Super Novice Expanded
+  - Id: 13
+    Script: >
+      UPDATE `char` c, `skill` s SET `c`.skill_point = `c`.skill_point + `s`.lv WHERE (`c`.class = 4190 OR `c`.class = 4191) AND `s`.id = 119 AND `c`.char_id = `s`.char_id;
+  - Id: 14
+    Script: >
+      DELETE FROM `skill` USING `skill`, `char` WHERE (`char`.class = 4190 OR `char`.class = 4191) AND `skill`.id = 119 AND `char`.char_id = `skill`.char_id;
+
+  # HT_FLASHER removal from Super Novice Expanded
+  - Id: 15
+    Script: >
+      UPDATE `char` c, `skill` s SET `c`.skill_point = `c`.skill_point + `s`.lv WHERE (`c`.class = 4190 OR `c`.class = 4191) AND `s`.id = 120 AND `c`.char_id = `s`.char_id;
+  - Id: 16
+    Script: >
+      DELETE FROM `skill` USING `skill`, `char` WHERE (`char`.class = 4190 OR `char`.class = 4191) AND `skill`.id = 120 AND `char`.char_id = `skill`.char_id;
+
+  # HT_FREEZINGTRAP removal from Super Novice Expanded
+  - Id: 17
+    Script: >
+      UPDATE `char` c, `skill` s SET `c`.skill_point = `c`.skill_point + `s`.lv WHERE (`c`.class = 4190 OR `c`.class = 4191) AND `s`.id = 121 AND `c`.char_id = `s`.char_id;
+  - Id: 18
+    Script: >
+      DELETE FROM `skill` USING `skill`, `char` WHERE (`char`.class = 4190 OR `char`.class = 4191) AND `skill`.id = 121 AND `char`.char_id = `skill`.char_id;
+
+Footer:
+  Imports:
+  - Path: sql-files/saves/login-sql_update_db.yml
+  - Path: sql-files/saves/char-sql_update_db.yml
+  - Path: sql-files/saves/map-sql_update_db.yml
+  - Path: sql-files/saves/log-sql_update_db.yml

+ 0 - 38
sql-files/upgrades/premium_storage.sql

@@ -1,38 +0,0 @@
---
--- Table structure for table `storage_1`
---
-
-CREATE TABLE IF NOT EXISTS `storage_1` (
-  `id` int(11) unsigned NOT NULL auto_increment,
-  `account_id` int(11) unsigned NOT NULL default '0',
-  `nameid` smallint(5) unsigned NOT NULL default '0',
-  `amount` smallint(11) unsigned NOT NULL default '0',
-  `equip` int(11) unsigned NOT NULL default '0',
-  `identify` smallint(6) unsigned NOT NULL default '0',
-  `refine` tinyint(3) unsigned NOT NULL default '0',
-  `attribute` tinyint(4) unsigned NOT NULL default '0',
-  `card0` smallint(5) unsigned NOT NULL default '0',
-  `card1` smallint(5) unsigned NOT NULL default '0',
-  `card2` smallint(5) unsigned NOT NULL default '0',
-  `card3` smallint(5) unsigned NOT NULL default '0',
-  `option_id0` smallint(5) unsigned NOT NULL default '0',
-  `option_val0` smallint(5) unsigned NOT NULL default '0',
-  `option_parm0` tinyint(3) unsigned NOT NULL default '0',
-  `option_id1` smallint(5) unsigned NOT NULL default '0',
-  `option_val1` smallint(5) unsigned NOT NULL default '0',
-  `option_parm1` tinyint(3) unsigned NOT NULL default '0',
-  `option_id2` smallint(5) unsigned NOT NULL default '0',
-  `option_val2` smallint(5) unsigned NOT NULL default '0',
-  `option_parm2` tinyint(3) unsigned NOT NULL default '0',
-  `option_id3` smallint(5) unsigned NOT NULL default '0',
-  `option_val3` smallint(5) unsigned NOT NULL default '0',
-  `option_parm3` tinyint(3) unsigned NOT NULL default '0',
-  `option_id4` smallint(5) unsigned NOT NULL default '0',
-  `option_val4` smallint(5) unsigned NOT NULL default '0',
-  `option_parm4` tinyint(3) unsigned NOT NULL default '0',
-  `expire_time` int(11) unsigned NOT NULL default '0',
-  `bound` tinyint(3) unsigned NOT NULL default '0',
-  `unique_id` bigint(20) unsigned NOT NULL default '0',
-  PRIMARY KEY  (`id`),
-  KEY `account_id` (`account_id`)
-) ENGINE=MyISAM;

+ 0 - 41
sql-files/upgrades/upgrade_20190309.sql

@@ -1,41 +0,0 @@
-ALTER TABLE `pet`
-	ADD COLUMN `autofeed` tinyint(2) NOT NULL default '0' AFTER `incubate`;
-
-UPDATE `inventory` `i`
-INNER JOIN `char` `c`
-ON `i`.`char_id` = `c`.`char_id` AND `c`.`pet_id` <> '0'
-SET `i`.`attribute` = '1'
-WHERE
-	`i`.`card0` = '256'
-AND
-	( `i`.`card1` | ( `i`.`card2` << 16 ) ) = `c`.`pet_id`
-;
-
-INSERT INTO `inventory`( `char_id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `card0`, `card1`, `card2`, `card3` )
-SELECT
-	`p`.`char_id`,						-- Character ID
-	`p`.`egg_id`,						-- Egg Item ID
-	'1',								-- Amount
-	'0',								-- Equip
-	'1',								-- Identify
-	'0',								-- Refine
-	'1',								-- Attribute
-	'256',								-- Card0
-	( `p`.`pet_id` & 0xFFFF ),			-- Card1
-	( ( `p`.`pet_id` >> 16 ) & 0xFFFF ),	-- Card2
-	'0'									-- Card3
-FROM `pet` `p`
-LEFT JOIN `inventory` `i`
-ON
-	`i`.`char_id` = `p`.`char_id`
-AND
-	`i`.`nameid` = `p`.`egg_id`
-AND
-	`i`.`card0` = '256'
-AND
-	( `i`.`card1` | ( `i`.`card2` << 16 ) ) = `p`.`pet_id`
-WHERE
-	`p`.`incubate` = '0'
-AND
-	`i`.`id` IS NULL
-;

+ 0 - 19
sql-files/upgrades/upgrade_20190628.sql

@@ -1,19 +0,0 @@
-ALTER TABLE `mob_db`
-	MODIFY `Sprite` varchar(24) NOT NULL,
-	ADD UNIQUE KEY (`Sprite`)
-;
-
-ALTER TABLE `mob_db_re`
-	MODIFY `Sprite` varchar(24) NOT NULL,
-	ADD UNIQUE KEY (`Sprite`)
-;
-
-ALTER TABLE `mob_db2`
-	MODIFY `Sprite` varchar(24) NOT NULL,
-	ADD UNIQUE KEY (`Sprite`)
-;
-
-ALTER TABLE `mob_db2_re`
-	MODIFY `Sprite` varchar(24) NOT NULL,
-	ADD UNIQUE KEY (`Sprite`)
-;

+ 0 - 2
sql-files/upgrades/upgrade_20190814.sql

@@ -1,2 +0,0 @@
-ALTER TABLE `ipbanlist`
-	CHANGE COLUMN `list` `list` VARCHAR(15) NOT NULL DEFAULT '' FIRST;

+ 0 - 1
sql-files/upgrades/upgrade_20190815.sql

@@ -1 +0,0 @@
-DROP TABLE `ragsrvinfo`;

+ 0 - 33
sql-files/upgrades/upgrade_20191222.sql

@@ -1,33 +0,0 @@
-ALTER TABLE `bonus_script`
-    ADD PRIMARY KEY (`char_id`, `type`);
-
-ALTER TABLE `buyingstore_items`
-    ADD PRIMARY KEY (`buyingstore_id`, `index`);
-
-ALTER TABLE `friends`
-    DROP INDEX `char_id`,
-    ADD PRIMARY KEY (`char_id`, `friend_id`);
-
-ALTER TABLE `interlog`
-    ADD COLUMN `id` INT NOT NULL AUTO_INCREMENT FIRST,
-    ADD PRIMARY KEY (`id`),
-    ADD INDEX `time` (`time`);
-
-ALTER TABLE `ipbanlist`
-    DROP INDEX `list`,
-    ADD PRIMARY KEY (`list`, `btime`);
-
-ALTER TABLE `sc_data`
-    DROP INDEX `account_id`,
-    DROP INDEX `char_id`,
-    ADD PRIMARY KEY (`char_id`, `type`);
-
-ALTER TABLE `skillcooldown`
-    DROP INDEX `account_id`,
-    DROP INDEX `char_id`,
-    ADD PRIMARY KEY (`char_id`, `skill`);
-
-ALTER TABLE `vending_items`
-    ADD PRIMARY KEY (`vending_id`, `index`);
-
-DROP TABLE `sstatus`;

+ 0 - 5
sql-files/upgrades/upgrade_20200108.sql

@@ -1,5 +0,0 @@
-ALTER TABLE `charlog`
-    DROP PRIMARY KEY, -- comment if primary key has not been created yet
-    ADD COLUMN `id` bigint(20) unsigned NOT NULL auto_increment first,
-    ADD PRIMARY KEY (`id`),
-    ADD KEY `account_id` (`account_id`);

+ 0 - 8
sql-files/upgrades/upgrade_20200109.sql

@@ -1,8 +0,0 @@
-ALTER TABLE `acc_reg_num`
-	MODIFY `value` bigint(11) NOT NULL default '0';
-
-ALTER TABLE `global_acc_reg_num`
-	MODIFY `value` bigint(11) NOT NULL default '0';
-
-ALTER TABLE `char_reg_num`
-	MODIFY `value` bigint(11) NOT NULL default '0';

+ 0 - 11
sql-files/upgrades/upgrade_20200126.sql

@@ -1,11 +0,0 @@
-ALTER TABLE `achievement`
-	MODIFY `count1` int unsigned NOT NULL default '0',
-	MODIFY `count2` int unsigned NOT NULL default '0',
-	MODIFY `count3` int unsigned NOT NULL default '0',
-	MODIFY `count4` int unsigned NOT NULL default '0',
-	MODIFY `count5` int unsigned NOT NULL default '0',
-	MODIFY `count6` int unsigned NOT NULL default '0',
-	MODIFY `count7` int unsigned NOT NULL default '0',
-	MODIFY `count8` int unsigned NOT NULL default '0',
-	MODIFY `count9` int unsigned NOT NULL default '0',
-	MODIFY `count10` int unsigned NOT NULL default '0';

+ 0 - 13
sql-files/upgrades/upgrade_20200204.sql

@@ -1,13 +0,0 @@
-ALTER TABLE `guild_member`
-	DROP COLUMN `account_id`,
-	DROP COLUMN `hair`,
-	DROP COLUMN `hair_color`,
-	DROP COLUMN `gender`,
-	DROP COLUMN `class`,
-	DROP COLUMN `lv`,
-	DROP COLUMN `exp_payper`,
-	DROP COLUMN `online`,
-	DROP COLUMN `name`;
-
-ALTER TABLE `friends`
-	DROP COLUMN `friend_account`;

+ 0 - 4
sql-files/upgrades/upgrade_20200303.sql

@@ -1,4 +0,0 @@
-UPDATE `char_reg_num` SET `key` = 'ep14_3_newerabs' WHERE `key` = 'ep14_3_dimensional_travel' AND `index` = 0 AND `value` < 2;
-UPDATE `char_reg_num` SET `key` = 'ep14_3_newerabs', `value` = 3 WHERE `key` = 'ep14_3_dimensional_travel' AND `index` = 0 AND `value` = 2;
-UPDATE `char_reg_num` SET `key` = 'ep14_3_newerabs', `value` = `value` + 2 WHERE `key` = 'ep14_3_dimensional_travel' AND `index` = 0 AND `value` < 8;
-UPDATE `char_reg_num` SET `key` = 'ep14_3_newerabs', `value` = `value` + 7 WHERE `key` = 'ep14_3_dimensional_travel' AND `index` = 0 AND `value` > 7;

+ 0 - 1
sql-files/upgrades/upgrade_20200327.sql

@@ -1 +0,0 @@
-DROP TABLE `interreg`;

+ 0 - 16
sql-files/upgrades/upgrade_20200402.sql

@@ -1,16 +0,0 @@
--- AB_EUCHARISTICA
-UPDATE `char` c, `skill` s SET `c`.skill_point = `c`.skill_point + `s`.lv WHERE `s`.id = 2049 AND `c`.char_id = `s`.char_id;
-DELETE FROM `skill` WHERE `id` = 2049;
-
--- GN_SLINGITEM
-UPDATE `char` c, `skill` s SET `c`.skill_point = `c`.skill_point + `s`.lv WHERE `s`.id = 2493 AND `c`.char_id = `s`.char_id;
-DELETE FROM `skill` WHERE `id` = 2493;
-
--- GN_MAKEBOMB
-UPDATE `char` c, `skill` s SET `c`.skill_point = `c`.skill_point + `s`.lv WHERE `s`.id = 2496 AND `c`.char_id = `s`.char_id;
-DELETE FROM `skill` WHERE `id` = 2496;
-
--- ONLY RUN THE BELOW QUERIES IF YOU ARE ON RENEWAL
--- CR_CULTIVATION
-UPDATE `char` c, `skill` s SET `c`.skill_point = `c`.skill_point + `s`.lv WHERE `s`.id = 491 AND `c`.char_id = `s`.char_id;
-DELETE FROM `skill` WHERE `id` = 491;

+ 0 - 11
sql-files/upgrades/upgrade_20200506.sql

@@ -1,11 +0,0 @@
--- HT_SANDMAN
-UPDATE `char` c, `skill` s SET `c`.skill_point = `c`.skill_point + `s`.lv WHERE (`c`.class = 4190 OR `c`.class = 4191) AND `s`.id = 119 AND `c`.char_id = `s`.char_id;
-DELETE FROM `skill` USING `skill`, `char` WHERE (`char`.class = 4190 OR `char`.class = 4191) AND `skill`.id = 119 AND `char`.char_id = `skill`.char_id;
-
--- HT_FLASHER
-UPDATE `char` c, `skill` s SET `c`.skill_point = `c`.skill_point + `s`.lv WHERE (`c`.class = 4190 OR `c`.class = 4191) AND `s`.id = 120 AND `c`.char_id = `s`.char_id;
-DELETE FROM `skill` USING `skill`, `char` WHERE (`char`.class = 4190 OR `char`.class = 4191) AND `skill`.id = 120 AND `char`.char_id = `skill`.char_id;
-
--- HT_FREEZINGTRAP
-UPDATE `char` c, `skill` s SET `c`.skill_point = `c`.skill_point + `s`.lv WHERE (`c`.class = 4190 OR `c`.class = 4191) AND `s`.id = 121 AND `c`.char_id = `s`.char_id;
-DELETE FROM `skill` USING `skill`, `char` WHERE (`char`.class = 4190 OR `char`.class = 4191) AND `skill`.id = 121 AND `char`.char_id = `skill`.char_id;

+ 2 - 0
src/char/inter.cpp

@@ -949,6 +949,8 @@ int inter_init_sql(const char *file)
 			Sql_ShowDebug(sql_handle);
 	}
 
+	Sql_UpgradesChecker(sql_handle, SQLDB_CHAR);
+
 	wis_db = idb_alloc(DB_OPT_RELEASE_DATA);
 	interServerDb.load();
 	inter_guild_sql_init();

+ 228 - 0
src/common/sql.cpp

@@ -7,6 +7,8 @@
 #include "winapi.hpp"
 #endif
 
+#include <fstream>
+#include <iomanip>
 #include <mysql.h>
 #include <stdlib.h>// strtoul
 
@@ -1045,6 +1047,232 @@ void Sql_inter_server_read(const char* cfgName, bool first) {
 	return;
 }
 
+/**
+ * Automatically upgrade the SQL tables.
+ * @param sql_handle: SQL Connection
+ * @param schema: Schema
+ */
+void Sql_UpgradesChecker(Sql *sql_handle, e_sql_database schema) {
+	if (sql_handle == nullptr) {
+		ShowError("Sql_UpgradesChecker: The SQL connection has not been made yet.\n");
+		return;
+	}
+
+	if (sql_update_db.empty())
+		sql_update_db.load();
+
+	std::vector<int32> new_updates, skipped_updates;
+
+	for (const auto &updateIt : sql_update_db) {
+		std::shared_ptr<s_sql_update_db> update = updateIt.second;
+
+		if (!update->patchdate.empty()) // Already applied
+			continue;
+
+		if (update->mode != MODE_BOTH) { // Only apply to specific mode
+#ifdef RENEWAL
+			uint8 compiledMode = MODE_RENEWAL;
+			std::string mode = "Renewal";
+#else
+			uint8 compiledMode = MODE_PRERENEWAL;
+			std::string mode = "Prerenewal"
+#endif
+
+			if (compiledMode != update->mode) {
+				ShowError("Sql_UpgradesChecker: The server's current mode of '%s' differs from the mode '%s' provided by the update. Skipping.\n", mode.c_str(), update->mode == MODE_RENEWAL ? "Renewal" : "Prerenewal");
+				continue;
+			}
+		}
+
+		if (!(update->database & schema)) // Only apply to the specific schema
+			continue;
+
+		if (update->skip) { // Skip an update if flagged to do so
+			skipped_updates.push_back(update->id);
+			continue;
+		}
+
+		if (SQL_ERROR == Sql_QueryStr(sql_handle, update->script.c_str())) { // Execute SQL update
+			Sql_ShowDebug(sql_handle);
+			continue;
+		}
+
+		new_updates.push_back(update->id);
+	}
+
+	if (!(schema & SQLDB_LOG) && !skipped_updates.empty()) {
+		size_t count = skipped_updates.size();
+
+		ShowSQL("Detected %zu skipped " CL_WHITE "SQL update%s" CL_RESET "\n", count, count > 1 ? "s" : "");
+		for (const auto &skipIt : skipped_updates)
+			ShowSQL("-- '" CL_WHITE "Update %d" CL_RESET "' has been skipped.\n", skipIt);
+	}
+
+	if (!new_updates.empty()) {
+		std::string save_file;
+
+		if (schema & SQLDB_LOGIN)
+			save_file = "sql-files/saves/login-update_db.yml";
+		else if (schema & SQLDB_CHAR)
+			save_file = "sql-files/saves/char-update_db.yml";
+		else if (schema & SQLDB_MAP)
+			save_file = "sql-files/saves/map-update_db.yml";
+		else
+			save_file = "sql-files/saves/log-update_db.yml";
+
+		std::ofstream save;
+
+		save.open(save_file);
+
+		if (!save.is_open()) {
+			ShowError("Sql_UpgradesChecker: Failed to open '%s'.\n", save_file.c_str());
+			return;
+		}
+
+		size_t count = new_updates.size();
+		YAML::Emitter output(save);
+
+		output << YAML::Comment("THIS FILE IS USED AS A LOG FOR SQL UPDATES.") << YAML::Newline;
+		output << YAML::Comment("DO NOT TOUCH THE CONTENT OF THIS FILE AS IT IS AUTOMATICALLY GENERATED.") << YAML::Newline;
+		output << YAML::BeginMap;
+		output << YAML::Key << "Header";
+		output << YAML::BeginMap;
+		output << YAML::Key << "Type" << YAML::Value << "SQL_UPDATE_DB";
+		output << YAML::Key << "Version" << YAML::Value << 1;
+		output << YAML::EndMap;
+		output << YAML::Newline;
+		output << YAML::Key << "Body";
+		output << YAML::BeginSeq;
+
+		ShowSQL("Detected %zu new " CL_WHITE "SQL update%s" CL_RESET "\n", count, count > 1 ? "s" : "");
+		for (const auto &newIt : new_updates) {
+			ShowSQL("-- '" CL_WHITE "Update %d" CL_RESET "' has been applied.\n", newIt);
+
+			time_t now = time(nullptr);
+
+			output << YAML::BeginMap;
+			output << YAML::Key << "Id" << YAML::Value << newIt;
+			output << YAML::Key << "PatchDate" << YAML::Value << trim(asctime(localtime(&now)));
+			output << YAML::EndMap;
+		}
+
+		output << YAML::EndSeq;
+		output << YAML::EndMap;
+		output << YAML::Newline;
+		save.close();
+	}
+
+	new_updates.clear();
+	skipped_updates.clear();
+}
+
+const std::string SqlUpdateDatabase::getDefaultLocation() {
+	return "sql-files/sql_update_db.yml";
+}
+
+/**
+ * Reads and parses an entry from the sql-files/sql_update_db.
+ * @param node: YAML node containing the entry.
+ * @param handle: SQL handle
+ * @return count of successfully parsed rows
+ */
+uint64 SqlUpdateDatabase::parseBodyNode(const YAML::Node &node) {
+	int32 id;
+
+	if (!this->asInt32(node, "Id", id))
+		return 0;
+
+	std::shared_ptr<s_sql_update_db> update = this->find(id);
+	bool exists = update != nullptr;
+
+	if (!exists) {
+		if (!this->nodesExist(node, { "Script" }))
+			return 0;
+
+		update = std::make_shared<s_sql_update_db>();
+		update->id = id;
+	}
+
+	if (this->nodeExists(node, "Script")) {
+		std::string script;
+
+		if (!this->asString(node, "Script", script))
+			return 0;
+
+		update->script = script;
+	}
+
+	if (this->nodeExists(node, "Mode")) {
+		std::string mode;
+
+		if (!this->asString(node, "Mode", mode))
+			return 0;
+
+		if (mode.compare("Prerenewal") == 0)
+			update->mode = MODE_PRERENEWAL;
+		else if (mode.compare("Renewal") == 0)
+			update->mode = MODE_RENEWAL;
+		else {
+			this->invalidWarning(node["Mode"], "Invalid mode '%s' given. Skipping.\n", mode.c_str());
+			return 0;
+		}
+	} else {
+		if (!exists)
+			update->mode = MODE_BOTH;
+	}
+
+	if (this->nodeExists(node, "Database")) {
+		std::string database;
+
+		if (!this->asString(node, "Database", database))
+			return 0;
+
+		if (database.compare("Login") == 0)
+			update->database = SQLDB_LOGIN;
+		else if (database.compare("Char") == 0)
+			update->database = SQLDB_CHAR;
+		else if (database.compare("Map") == 0)
+			update->database = SQLDB_MAP;
+		else if (database.compare("Log") == 0)
+			update->database = SQLDB_LOG;
+		else {
+			this->invalidWarning(node["Database"], "Invalid database '%s' given. Skipping.\n", database.c_str());
+			return 0;
+		}
+	} else {
+		if (!exists)
+			update->database = SQLDB_NORMAL;
+	}
+
+	if (this->nodeExists(node, "Skip")) {
+		bool skip;
+
+		if (!this->asBool(node, "Skip", skip))
+			return 0;
+
+		update->skip = skip;
+	} else {
+		if (!exists)
+			update->skip = false;
+	}
+
+	if (this->nodeExists(node, "PatchDate")) {
+		std::string patchdate;
+
+		if (!this->asString(node, "PatchDate", patchdate))
+			return 0;
+
+		update->patchdate = patchdate;
+	}
+
+	if (!exists)
+		this->put(id, update);
+
+	return 1;
+}
+
+SqlUpdateDatabase sql_update_db;
+
 void Sql_Init(void) {
 	Sql_inter_server_read(SQL_CONF_NAME,true);
 }

+ 38 - 0
src/common/sql.hpp

@@ -7,6 +7,7 @@
 #include <stdarg.h>// va_list
 
 #include "cbasetypes.hpp"
+#include "database.hpp"
 
 // Return codes
 #define SQL_ERROR -1
@@ -62,6 +63,43 @@ typedef enum SqlDataType SqlDataType;
 typedef struct Sql Sql;
 typedef struct SqlStmt SqlStmt;
 
+enum e_server_mode : uint8 {
+	MODE_PRERENEWAL = 0,
+	MODE_RENEWAL,
+	MODE_BOTH,
+};
+
+enum e_sql_database : uint8 {
+	SQLDB_LOGIN = 0x1,
+	SQLDB_CHAR = 0x2,
+	SQLDB_MAP = 0x4,
+	SQLDB_LOG = 0x8,
+
+	SQLDB_NORMAL = SQLDB_LOGIN | SQLDB_CHAR | SQLDB_MAP, // All except log
+};
+
+struct s_sql_update_db {
+	int32 id;
+	std::string script;
+	e_server_mode mode;
+	uint8 database;
+	bool skip;
+	std::string patchdate;
+};
+
+class SqlUpdateDatabase : public TypesafeYamlDatabase<int32, s_sql_update_db> {
+public:
+	SqlUpdateDatabase() : TypesafeYamlDatabase("SQL_UPDATE_DB", 1) {
+
+	}
+
+	const std::string getDefaultLocation();
+	uint64 parseBodyNode(const YAML::Node &node);
+};
+
+extern SqlUpdateDatabase sql_update_db;
+
+void Sql_UpgradesChecker(Sql *sql_handle, e_sql_database schema);
 
 /// Allocates and initializes a new Sql handle.
 struct Sql* Sql_Malloc(void);

+ 2 - 0
src/login/account.cpp

@@ -134,6 +134,8 @@ static bool account_db_sql_init(AccountDB* self) {
 	if( codepage[0] != '\0' && SQL_ERROR == Sql_SetEncoding(sql_handle, codepage) )
 		Sql_ShowDebug(sql_handle);
 
+	Sql_UpgradesChecker(sql_handle, SQLDB_LOGIN);
+
 	return true;
 }
 

+ 5 - 0
src/map/map.cpp

@@ -4298,6 +4298,9 @@ int map_sql_init(void)
 		if ( SQL_ERROR == Sql_SetEncoding(qsmysql_handle, default_codepage) )
 			Sql_ShowDebug(qsmysql_handle);
 	}
+
+	Sql_UpgradesChecker(mmysql_handle, SQLDB_MAP);
+
 	return 0;
 }
 
@@ -4338,6 +4341,8 @@ int log_sql_init(void)
 		if ( SQL_ERROR == Sql_SetEncoding(logmysql_handle, default_codepage) )
 			Sql_ShowDebug(logmysql_handle);
 
+	Sql_UpgradesChecker(logmysql_handle, SQLDB_LOG);
+
 	return 0;
 }