222 lines
7.6 KiB
Perl
222 lines
7.6 KiB
Perl
#!/usr/bin/perl -w
|
|
|
|
use strict;
|
|
use warnings;
|
|
use DBI;
|
|
use Data::Dumper;
|
|
use JSON;
|
|
use Getopt::Long;
|
|
use File::Slurp qw(:std);
|
|
use Archive::Zip qw( :ERROR_CODES :CONSTANTS );
|
|
use POSIX qw(strftime);
|
|
use IO::Handle;
|
|
use File::Path qw(make_path);
|
|
STDOUT->autoflush(1);
|
|
|
|
#https://www.easysoft.com/developer/languages/perl/dbd_odbc_tutorial_part_2.html
|
|
|
|
# use Term::ANSIColor;
|
|
|
|
my $db = ""; # Datenbank
|
|
my $user = ""; # Datenbankuser
|
|
my $pass = ""; # Datenbankpasswort
|
|
my $server = ""; # Datenbankserver
|
|
my $zip = "0"; #komprimieren und oder löschen
|
|
my $maxlength = 0;
|
|
my $list = "";
|
|
my $dateien = "1";
|
|
my $rem = "0";
|
|
|
|
my %h = ('db' => \$db, 'user' => \$user, 'pass' => \$pass, 'server' => \$server, 'zip' => \$zip, 'rem' => \$rem, 'max' => \$maxlength, 'list' => \$list, 'files' => \$dateien);
|
|
GetOptions (\%h, 'db=s', 'user=s', 'pass=s', 'server=s', 'zip=s', 'rem=s', 'max=s', 'list=s', 'files=s');
|
|
|
|
if ( $db eq "" || $user eq "" || $user eq "" || $server eq "" ) {
|
|
print "\nWillkommen zum Backup einer Mysqldatenbank\n";
|
|
print "es fehlen noch folgende Parameter damit es losgehen kann.\n";
|
|
print "\n";
|
|
print "--server -s = Datenbankserver\n" if ($server eq "");
|
|
print "--db -d = Datenbankname\n" if ($db eq "");
|
|
print "--user -u = Datenbankuser\n" if ($user eq "");
|
|
print "--pass -p = Datenbankpasswort\n" if ($pass eq "");
|
|
print "--max -m = maximale Insertlänge in Bytes (optional)\n";
|
|
print "--list -l = Liste der Tabellen mit ' ' getrennt (optional)\n";
|
|
print "--files -f = 1 für jede Tabelle in eine Datei (optional)\n";
|
|
print "--zip -z = 1 zum packen, 2 packen und Sql Dateien löschen (optional) \n";
|
|
print "--rem -r = 0 vorhande Tabelle vorher löschen (optional) \n";
|
|
|
|
exit;
|
|
}
|
|
|
|
my $tref; # dient zur analyse welcher Datentyp die Spalte hat
|
|
my $text; # wird mit den Daten zum schreiben in die datei gefüllt
|
|
my @files;
|
|
my %tabellen;
|
|
|
|
my $dbh = DBI->connect("DBI:mysql:$db:$server", $user, $pass) or die "Couldn't connect to database: " . DBI->errstr;
|
|
|
|
print "Get tables from Database $db\n";
|
|
my $s1 = $dbh->selectall_arrayref("SHOW FULL TABLES FROM $db;", { Slice => {} });
|
|
|
|
# Ordner erstellen wenn nicht bekannt
|
|
if (!-d "$db") {
|
|
make_path "$db";
|
|
}
|
|
|
|
if ( $list ne "" ) {
|
|
if ( $list !~ / / ) {
|
|
$tabellen{$list} = 1;
|
|
}
|
|
}
|
|
for my $l ( split( ' ', $list ) ) {
|
|
$tabellen{$l} = 1;
|
|
}
|
|
|
|
if ( $dateien eq '0' ) {
|
|
push @files, "$server/$db/$db.sql";
|
|
}
|
|
|
|
my $firsttableend = 0;
|
|
|
|
for my $tab ( @{$s1} ) {
|
|
my $n = $tab->{"Tables_in_$db"};
|
|
my $datei = "$db/$n.sql";
|
|
if ( $dateien eq '0' ) {
|
|
$datei = "$db/$db.sql";
|
|
} else {
|
|
write_file($datei, '' );
|
|
}
|
|
|
|
if ( $list ne "" ) {
|
|
next if ( !$tabellen{$n} );
|
|
}
|
|
|
|
$text = "\n/*!40101 SET \@OLD_CHARACTER_SET_CLIENT=@\@CHARACTER_SET_CLIENT */;\n/*!40101 SET NAMES utf8 */;\n/*!50503 SET NAMES utf8mb4 */;\n/*!40014 SET \@OLD_FOREIGN_KEY_CHECKS=@\@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;\n/*!40101 SET \@OLD_SQL_MODE=@\@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;\n\n";
|
|
|
|
my $create = $dbh->selectrow_hashref("SHOW CREATE TABLE $db.$n" , { Slice => {} });
|
|
|
|
if ( $rem eq "1" ) {
|
|
$text .= "DROP TABLE IF EXISTS `$n`;\n";
|
|
}
|
|
|
|
# Createstatement beschaffen
|
|
$text .= $create->{'Create Table'};
|
|
$text =~ s/CREATE TABLE/CREATE TABLE IF NOT EXISTS/g;
|
|
my $s2 = $dbh->selectall_arrayref("DESCRIBE $db.$n" , { Slice => {} });
|
|
my @tabelle;
|
|
|
|
# hier wird sich der Datentyp einer Spalte gemerkt
|
|
# und ein Array mit den Spaltenamen
|
|
for my $s ( @{$s2} ) {
|
|
my $f = $s->{Field};
|
|
$tref->{ $n }->{$f} = $s->{Type};
|
|
push @tabelle, $s->{Field};
|
|
}
|
|
$text .= ";\n\n/*!40000 ALTER TABLE `$n` DISABLE KEYS */;\n";
|
|
|
|
if ( $firsttableend == 1 ) {
|
|
append_file($datei, $text );
|
|
} else {
|
|
write_file($datei, $text );
|
|
}
|
|
|
|
# Insert Kopf erstellen
|
|
my $inserttext .= "INSERT INTO $n (";
|
|
for my $col( @tabelle ) {
|
|
$inserttext .= "`".$col."`,"
|
|
}
|
|
$inserttext =~ s/,$/) VALUES/;
|
|
|
|
print "create Inserts for $n\n";
|
|
|
|
my $cnt = 0;
|
|
my $len = 0; # Zähler für die maximale Länge in Bytes eines Insert
|
|
|
|
my $sth = $dbh->prepare("SELECT * from $db.$n"); # prepare the query
|
|
# in der Schleife werden die einzelnen inserts direkt in die Datei geschrieben, geht schneller als wenn man die inserts sammelt und dann erst schreibt
|
|
$sth->execute(); # execute the query with parameter
|
|
my $max = $sth->rows;
|
|
# Holen der Daten
|
|
while (my $d = $sth->fetchrow_hashref) { # retrieve one row
|
|
$cnt += 1;
|
|
|
|
if ( $len == 0 ) { # bei 0 wird ein neues Create statement erzeugt
|
|
append_file($datei, $inserttext );
|
|
}
|
|
|
|
$text = "\n("; # Klammer für insert erstellen
|
|
|
|
# hier wird entschieden wie die einzelnen Daten in abhängigkeit des Datentyps geschrieben werden
|
|
# es kommen die gemerkten Spalten (@tabelle) und Datentypen ($tref) zum Einsatz
|
|
# zb: mit Strings mit '' integer ohne '', umgang mit zeilenumbrüchen, blobs
|
|
# bei einem unbekannten typ kommt der Dumper
|
|
for my $col( @tabelle ) {
|
|
if ( defined $d->{$col} ) {
|
|
if ( $tref->{ $n }->{$col} =~ /int|year|double|decimal|float/ ) {
|
|
$text .= $d->{$col}.","
|
|
} elsif ($tref->{ $n }->{$col} =~ /char|date|time|enum|text/ ) {
|
|
$d->{$col} =~ s/\\/\\\\/g;
|
|
$d->{$col} =~ s/\n/\\n/g;
|
|
$d->{$col} =~ s/\r/\\n/g;
|
|
$d->{$col} =~ s/\r\n\\/\\n/g;
|
|
$d->{$col} =~ s/\'/\\'/g;
|
|
|
|
$text .= "'".$d->{$col}."',"
|
|
} elsif ($tref->{ $n }->{$col} =~ /blob/ ) {
|
|
$text .= "_binary 0x". uc unpack("H*",$d->{$col}).",";
|
|
} else {
|
|
print Dumper($tref->{ $n }->{$col});
|
|
}
|
|
} else {
|
|
$text .= "NULL,";
|
|
}
|
|
}
|
|
|
|
$len += length($text); # zählen der Länge der inserts es werden auch die , _binary
|
|
$text =~ s/,$//; # letzes , insert entfernen
|
|
$text .= "),"; # und Klammer wieder schließen
|
|
|
|
if ( $cnt == $max or ($len > $maxlength and $maxlength != 0 ) ) { # wenn maximale Länge eines insert oder Ende der Tabelle erreicht , gegen ; tauschen und Zähler auf 0
|
|
$len = 0;
|
|
$text =~ s/,$/;\n/;
|
|
}
|
|
append_file($datei, $text );
|
|
|
|
print "$cnt - $max\r";
|
|
}
|
|
print " ---> ready\n";
|
|
|
|
$text = "/*!40000 ALTER TABLE `$n` ENABLE KEYS */;\n\n/*!40101 SET SQL_MODE=IFNULL(\@OLD_SQL_MODE, '') */;\n/*!40014 SET FOREIGN_KEY_CHECKS=IF(\@OLD_FOREIGN_KEY_CHECKS IS NULL, 1, \@OLD_FOREIGN_KEY_CHECKS) */;\n/*!40101 SET CHARACTER_SET_CLIENT=\@OLD_CHARACTER_SET_CLIENT */;\n";
|
|
append_file($datei, $text );
|
|
|
|
if ( $dateien == 1 ) {
|
|
push @files, "$db/$n.sql";
|
|
}
|
|
$firsttableend = 1;
|
|
}
|
|
$dbh->disconnect;
|
|
|
|
if ( $zip > 0 ) {
|
|
print "Compress $db into zip file ";
|
|
my $now_string = strftime "%Y-%m-%d %H.%M", localtime;
|
|
# Creating a new zip file
|
|
my $zip = Archive::Zip->new();
|
|
my $zipfile = "$db-$now_string.zip";
|
|
# Trying to read the existing zip structure, when zip archive already exists
|
|
$zip->read( $zipfile ) if -s $zipfile;
|
|
|
|
foreach my $file ( @files ) {
|
|
# remove if the current file was already in the zip:
|
|
$zip->removeMember( $file );
|
|
# add a new file to zip object in memory, using best compressionLevel = 9
|
|
$zip->addFile( $file, $file, 5 );
|
|
}
|
|
if ( $zip->numberOfMembers ) {
|
|
# Save to a zip file $zipfile
|
|
$zip->overwriteAs( $zipfile );
|
|
}
|
|
if ( $zip > 1 ) {
|
|
unlink @files;
|
|
}
|
|
print "---> ready\n";
|
|
}
|
|
1; |