#!/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;