eqemu-server/utils/scripts/db_update.pl
Akkadius 251d4fa3e3 Implemented Automatic Database update and versioning system
Created database revision define, this is located in version.h in common #define CURRENT_BINARY_DATABASE_VERSION 9057
	- This revision define will need to be incremented each time a database update is made
	- Along with a revision define increment, you will need to update the db_update manifest located in:
		- https://raw.githubusercontent.com/EQEmu/Server/master/utils/sql/db_update_manifest.txt
		- An entry needs to be made at the bottom of the manifest, the entry is quite simple
		- Example: 9057|2014_11_13_spells_new_updates.sql|SHOW COLUMNS FROM `spells_new` LIKE 'disallow_sit'|empty|
			- This latest example is checking to see if the spells_new table contains the column 'disallow_sit', if its empty, the update needs to be ran
				- More examples of match types below:
		# Example: Version|Filename.sql|Query_to_Check_Condition_For_Needed_Update|match type|text to match
		#	0 = Database Version
		#	1 = Filename.sql
		#	2 = Query_to_Check_Condition_For_Needed_Update
		#	3 = Match Type - If condition from match type to Value 4 is true, update will flag for needing to be ran
		#		contains = If query results contains text from 4th value
		#		match = If query results matches text from 4th value
		#		missing = If query result is missing text from 4th value
		#		empty = If the query results in no results
		#		not_empty = If the query is not empty
		#	4 = Text to match
	- The manifest contains all database updates 'Required' to be made to the schema, and it will contain a working backport all the way back to SVN -
		currently it is tested and backported through the beginning of our Github repo
	- On world bootup or standalone run of db_update.pl, users will be prompted with a simple menu that we will expand upon later:

		============================================================
			   EQEmu: Automatic Database Upgrade Check
		============================================================
				Operating System is: MSWin32
				(Windows) MySQL is in system path
				Path = C:\Program Files\MariaDB 10.0\bin/mysql
		============================================================
				Binary Database Version: (9057)
				Local Database Version: (9057)

				Database up to Date: Continuing World Bootup...
		============================================================
		Retrieving latest database manifest...
        URL:    https://raw.githubusercontent.com/EQEmu/Server/master/utils/sql/db_update_manifest.txt
        Saved:  db_update/db_update_manifest.txt

Database Management Menu (Please Select):
        1) Backup Database - (Saves to Backups folder)
                Ideal to perform before performing updates
        2) Backup Database Compressed - (Saves to Backups folder)
                Ideal to perform before performing updates
        3) Check for pending Database updates
                Stages updates for automatic upgrade...
        0) Exit

Created db_update.pl, placed in utils/scripts folder, used for the automatic database update routine (Linux/Windows)
	- db_update.pl script created db_version table if not created, if old one is present it will remove it
Created db_dumper.pl, placed in utils/scripts folder, used for the automatic database update routine backups and standalone backups (Linux/Windows)
World will now check the db_update.pl script on bootup, if the db_update.pl script is not present, it will fetch it remotely before running -
	when db_update.pl is done running, world will continue with bootup
world.exe db_version - will report database binary version
2014-11-16 19:26:59 -06:00

372 lines
10 KiB
Perl

#!/usr/bin/perl
###########################################################
#::: Automatic Database Upgrade Script
#::: Author: Akkadius
#::: Purpose: To upgrade databases with ease and maintain versioning
###########################################################
my $confile = "eqemu_config.xml"; #default
open(F, "<$confile") or die "Unable to open config: $confile\n";
my $indb = 0;
while(<F>) {
s/\r//g;
if(/<database>/i) { $indb = 1; }
next unless($indb == 1);
if(/<\/database>/i) { $indb = 0; last; }
if(/<host>(.*)<\/host>/i) { $host = $1; }
elsif(/<username>(.*)<\/username>/i) { $user = $1; }
elsif(/<password>(.*)<\/password>/i) { $pass = $1; }
elsif(/<db>(.*)<\/db>/i) { $db = $1; }
}
print
"============================================================
EQEmu: Automatic Database Upgrade Check
============================================================
";
use Config;
print " Operating System is: $Config{osname}\n";
if($Config{osname}=~/linux/i){ $OS = "Linux"; }
if($Config{osname}=~/Win|MS/i){ $OS = "Windows"; }
if($OS eq "Windows"){
$has_mysql_path = `echo %PATH%`;
if($has_mysql_path=~/MySQL|MariaDB/i){
@mysql = split(';', $has_mysql_path);
foreach my $v (@mysql){
if($v=~/MySQL|MariaDB/i){
$path = $v . "/mysql";
last;
}
}
print " (Windows) MySQL is in system path \n";
print " Path = " . $path . "\n";
print "============================================================\n";
}
}
#::: Linux Check
if($OS eq "Linux"){
$has_mysql_path = `whereis mysql`;
if($has_mysql_path=~/MySQL|MariaDB/i){
@mysql = split(' ', $has_mysql_path);
foreach my $v (@mysql){
if($v=~/MySQL|MariaDB/i){
$path = $v;
last;
}
}
print " (Linux) MySQL is in system path \n";
print " Path = " . $path . "\n";
print "============================================================\n";
}
}
#::: Path not found, error and exit
if($path eq ""){
print "MySQL path not found, please add the path for automatic database upgrading to continue... \n\n";
print "Exiting...\n";
exit;
}
#::: Create db_update working directory if not created
mkdir('db_update');
# print `"$path" --user $user --password="$pass" $db < db_update/db_update_run_file.txt`;
#::: Check if db_version table exists...
if(trim(GetMySQLResult("SHOW COLUMNS FROM `db_version` LIKE 'Revision'")) ne ""){
print GetMySQLResult("DROP TABLE db_version");
print "Old `db_version` table present, dropping...\n\n";
}
if(GetMySQLResult("SHOW TABLES LIKE 'db_version'") eq ""){
print GetMySQLResult("
CREATE TABLE `db_version` (
`version` int(11) DEFAULT '0'
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO `db_version` (`version`) VALUES ('1000');");
print "Table 'db_version' does not exists.... Creating...\n\n";
}
@db_version = split(': ', `world db_version`);
$bin_db_ver = trim($db_version[1]);
$local_db_ver = trim(GetMySQLResult("SELECT `version` FROM `db_version` LIMIT 1"));
print " Binary Database Version: (" . $bin_db_ver . ")\n";
print " Local Database Version: (" . $local_db_ver . ")\n\n";
#::: If World ran this script, and our version is up to date, continue...
if($bin_db_ver == $local_db_ver && $ARGV[0] eq "ran_from_world"){
print " Database up to Date: Continuing World Bootup...\n";
print "============================================================\n";
exit;
}
print "Retrieving latest database manifest...\n";
GetRemoteFile("https://raw.githubusercontent.com/EQEmu/Server/master/utils/sql/db_update_manifest.txt", "db_update/db_update_manifest.txt");
# GetRemoteFile("https://dl.dropboxusercontent.com/u/50023467/dl/db_update_manifest.txt", "db_update/db_update_manifest.txt");
if($local_db_ver < $bin_db_ver && $ARGV[0] eq "ran_from_world"){
print "You have missing database updates, type 1 or 2 to backup your database before running them as recommended...\n\n";
#::: Display Menu
ShowMenuPrompt();
}
else{
#::: Most likely ran standalone
print "\n";
ShowMenuPrompt();
}
sub ShowMenuPrompt {
my %dispatch = (
1 => \&database_dump,
2 => \&database_dump_compress,
3 => \&Run_Database_Check,
0 => \&Exit,
);
while (1) {
{
local $| = 1;
print MenuOptions(), '> ';
}
my $choice = <>;
$choice =~ s/\A\s+//;
$choice =~ s/\s+\z//;
if (defined(my $handler = $dispatch{$choice})) {
my $result = $handler->();
unless (defined $result) {
exit 0;
}
}
else {
if($ARGV[0] ne "ran_from_world"){
warn "\n\nInvalid selection\n\n";
}
}
}
}
sub MenuOptions {
if(defined(@total_updates)){
$option[3] = "Run pending updates... (" . scalar (@total_updates) . ")";
}
else{
$option[3] = "Check for pending Database updates
Stages updates for automatic upgrade...";
}
return <<EO_MENU;
Database Management Menu (Please Select):
1) Backup Database - (Saves to Backups folder)
Ideal to perform before performing updates
2) Backup Database Compressed - (Saves to Backups folder)
Ideal to perform before performing updates
3) $option[3]
0) Exit
EO_MENU
}
sub CheckForDatabaseDumpScript{
if(`perl db_dumper.pl`=~/Need arguments/i){
return;
}
else{
print "db_dumper.pl not found... retrieving...\n\n";
GetRemoteFile("https://raw.githubusercontent.com/EQEmu/Server/master/utils/scripts/db_dumper.pl", "db_dumper.pl");
}
}
sub ran_from_world {
print "Running from world...\n";
}
sub database_dump {
CheckForDatabaseDumpScript();
print "Performing database backup....\n";
print `perl db_dumper.pl database="$db" loc="backups"`;
}
sub database_dump_compress {
CheckForDatabaseDumpScript();
print "Performing database backup....\n";
print `perl db_dumper.pl database="$db" loc="backups" compress`;
}
sub Exit{}
#::: Returns Tab Delimited MySQL Result from Command Line
sub GetMySQLResult{
my $run_query = $_[0];
return `"$path" --user $user --password="$pass" $db -N -B -e "$run_query"`;
}
sub GetMySQLResultFromFile{
my $update_file = $_[0];
return `"$path" --user $user --password="$pass" --force $db < $update_file`;
}
#::: Gets Remote File based on URL (1st Arg), and saves to destination file (2nd Arg)
#::: Example: GetRemoteFile("https://raw.githubusercontent.com/EQEmu/Server/master/utils/sql/db_update_manifest.txt", "db_update/db_update_manifest.txt");
sub GetRemoteFile{
my $URL = $_[0];
my $Dest_File = $_[1];
if($OS eq "Windows"){
require LWP::UserAgent;
my $ua = LWP::UserAgent->new;
$ua->timeout(10);
$ua->env_proxy;
my $response = $ua->get($URL);
if ($response->is_success){
open (FILE, '> ' . $Dest_File . '');
print FILE $response->decoded_content;
close (FILE);
print " URL: " . $URL . "\n";
print " Saved: " . $Dest_File . " \n";
}
else {
print "Error, no connection to the internet...\n\n";
die $response->status_line;
}
}
if($OS eq "Linux"){
#::: wget -O db_update/db_update_manifest.txt https://raw.githubusercontent.com/EQEmu/Server/master/utils/sql/db_update_manifest.txt
$wget = `wget -O $Dest_File $URL`;
print " URL: " . $URL . "\n";
print " Saved: " . $Dest_File . " \n";
if($wget=~/unable to resolve/i){
print "Error, no connection to the internet...\n\n";
die;
}
}
}
#::: Trim Whitespaces
sub trim {
my $string = $_[0];
$string =~ s/^\s+//;
$string =~ s/\s+$//;
return $string;
}
#::: Responsible for Database Upgrade Routines
sub Run_Database_Check{
#::: Run 2 - Running pending updates...
if(defined(@total_updates)){
@total_updates = sort @total_updates;
foreach my $val (@total_updates){
$file_name = trim($m_d{$val}[1]);
print "Running Update: " . $val . " - " . $file_name . "\n";
print GetMySQLResultFromFile("db_update/$file_name");
print GetMySQLResult("UPDATE `db_version` SET `version` = $val WHERE `version` < $val");
}
}
#::: Run 1 - Initial checking of needed updates...
print "Reading manifest...\n\n";
use Data::Dumper;
open (FILE, "db_update/db_update_manifest.txt");
while (<FILE>) {
chomp;
$o = $_;
if($o=~/#/i){ next; }
#print $o . "\n";
@manifest = split('\|', $o);
$m_d{$manifest[0]} = [@manifest];
}
@total_updates = ();
#::: Iterate through Manifest backwards from binary version down to local version...
for($i = $bin_db_ver; $i > 1000; $i--){
if(!defined($m_d{$i}[0])){ next; }
$file_name = trim($m_d{$i}[1]);
$query_check = trim($m_d{$i}[2]);
$match_type = trim($m_d{$i}[3]);
$match_text = trim($m_d{$i}[4]);
#::: Match type update
if($match_type eq "contains"){
if(trim(GetMySQLResult($query_check))=~/$match_text/i){
print "Missing DB Update " . $i . " '" . $file_name . "' \n";
FetchMissingUpdate($i, $file_name);
push(@total_updates, $i);
}
else{
print "DB up to date with: " . $i . " '" . $file_name . "' \n";
}
print_match_debug();
print_break();
}
if($match_type eq "missing"){
if(GetMySQLResult($query_check)=~/$match_text/i){
print "DB up to date with: " . $i . " '" . $file_name . "' \n";
next;
}
else{
print "Missing DB Update " . $i . " '" . $file_name . "' \n";
FetchMissingUpdate($i, $file_name);
push(@total_updates, $i);
}
print_match_debug();
print_break();
}
if($match_type eq "empty"){
if(GetMySQLResult($query_check) eq ""){
print "Missing DB Update " . $i . " '" . $file_name . "' \n";
FetchMissingUpdate($i, $file_name);
push(@total_updates, $i);
}
else{
print "DB up to date with: " . $i . " '" . $file_name . "' \n";
}
print_match_debug();
print_break();
}
if($match_type eq "not_empty"){
if(GetMySQLResult($query_check) ne ""){
print "Missing DB Update " . $i . " '" . $file_name . "' \n";
FetchMissingUpdate($i, $file_name);
push(@total_updates, $i);
}
else{
print "DB up to date with: " . $i . " '" . $file_name . "' \n";
}
print_match_debug();
print_break();
}
}
print "\n\n";
}
sub FetchMissingUpdate{
$db_update = $_[0];
$update_file = $_[1];
if($db_update >= 9000){
GetRemoteFile("https://raw.githubusercontent.com/EQEmu/Server/master/utils/sql/git/required/" . $update_file, "db_update/" . $update_file . "");
}
elsif($db_update >= 5000 && $db_update <= 9000){
GetRemoteFile("https://raw.githubusercontent.com/EQEmu/Server/master/utils/sql/svn/" . $update_file, "db_update/" . $update_file . "");
}
}
sub print_match_debug{
if(!$debug){ return; }
print " Match Type: '" . $match_type . "'\n";
print " Match Text: '" . $match_text . "'\n";
print " Query Check: '" . $query_check . "'\n";
print " Result: '" . trim(GetMySQLResult($query_check)) . "'\n";
}
sub print_break{
if(!$debug){ return; }
print "\n==============================================\n";
}