# Copyright 1999-2016. Parallels IP Holdings GmbH. All Rights Reserved. package Db::MysqlShellBackend; use strict; use Db::ShellBackend; use AgentConfig; use Db::MysqlUtils; use File::Temp qw/ tempfile tempdir /; use vars qw|@ISA|; @ISA = qw|Db::ShellBackend|; # # 'name', 'user', 'password'[, 'host'][, 'socket'][, 'port'][, 'preload_dirs'][, 'utf8names'] # sub _init { my ($self, %params) = @_; $self->SUPER::_init(%params, 'type'=>'mysql'); if (defined $params{host} and defined $params{socket}) { return "socket & host are mutually exclusive"; } $self->{socket} = $params{socket} if defined $params{socket}; $self->{preload_dirs} = $params{preload_dirs} if defined $params{preload_dirs}; $self->{utf8names} = $params{utf8names} if defined $params{utf8names}; $self->{not_compatible_mysql_323} = $params{not_compatible_mysql_323} if defined $params{not_compatible_mysql_323}; $self->{tables} = join(' ', @{$params{tables}}) if defined $params{tables}; $self->{replace_and_no_create_info} = 1 if defined $params{replace_and_no_create_info}; $self->{dump_charset} = $params{dump_charset} if defined $params{dump_charset}; $self->{extra_dump_options} = '--no-data' if defined $params{dummyContent}; my @version = Db::MysqlUtils::getVersionArray(); $self->{mysql_major_version} = $version[0]; $self->{mysql_minor_version} = $version[1]; $self->{mysql_build_version} = $version[2]; } sub description { my ($self) = @_; return "mysql shell connection. " . $self->SUPER::description(); } sub _getCmd { my ($self, $cmd, $additionalOptions) = @_; my $addTables = $cmd eq AgentConfig::mysqldumpBin(); if ($self->{password}) { my ($fh, $fileName) = tempfile(UNLINK => 1); if ($cmd eq AgentConfig::mysqldumpBin()) { print $fh "[mysqldump]\npassword="; } else { print $fh "[mysql]\npassword="; } if (!$self->_mysql41OrNewer()) { print $fh $self->{password}; } else { print $fh '"' . $self->{password} . '"'; } close $fh; $cmd .= " --defaults-extra-file=" . $fileName; $self->_setFileToRemove($fileName); } else { $self->_set_errstr("Can't create temporary file"); } $cmd .= " --socket='$self->{socket}'" if $self->{socket}; $cmd .= " -h '$self->{host}'" if $self->{host}; $cmd .= " -u '$self->{user}'"; $cmd .= " -P '$self->{port}'" if $self->{port}; $cmd .= " $additionalOptions" if $additionalOptions; if ($self->{name} =~ /^-.*/) { $cmd .= " -- "; } $cmd .= " '$self->{name}'"; $cmd .= " --tables $self->{tables} " if $self->{tables} and $addTables; $cmd .= " --no-create-info --insert-ignore" if $self->{replace_and_no_create_info} and $addTables; $cmd = "LD_PRELOAD=$self->{preload_dirs} $cmd" if $self->{preload_dirs}; # ~/.my.cnf is the top priority file for Linux and can interfere with # DB dumps, so we cheat here pretending HOME directory is empty # and there is no ~/.my.cnf my $fakeHomeDir = tempdir(); $self->_setFileToRemove($fakeHomeDir); $cmd = "HOME=\"$fakeHomeDir\" $cmd"; return $cmd; } sub _mysql41OrNewer { my ($self) = @_; if (defined $self->{mysql_major_version} and defined $self->{mysql_minor_version}) { return $self->{mysql_major_version} > 4 || ($self->{mysql_major_version} == 4 and $self->{mysql_minor_version} >= 1); } } sub _mysql512OrNewer { my ($self) = @_; return if (!$self->{mysql_major_version} || !$self->{mysql_minor_version}); return 1 if ($self->{mysql_major_version} > 5); if ($self->{mysql_major_version} == 5) { return 1 if ($self->{mysql_minor_version} > 1 || ($self->{mysql_minor_version} == 1 and $self->{mysql_build_version} >= 2)); } } sub connect { my ($self) = @_; if (!AgentConfig::mysqlBin()) { $self->_set_errstr("Unable to find 'mysql'"); return; } my $version = Db::MysqlUtils::getVersion(); $self->{cmdline} = $self->_getCmd(AgentConfig::mysqlBin(), "-B" . ($version && $version eq "3.23" ? "" : " -b")); if ($self->{utf8names} && Db::MysqlUtils::doesSupportCharacterSets($self)) { $self->{sql_prefix} = Db::MysqlUtils::getCharacterSetsSupportSql() . ";"; $self->{sql_prefix} .= Db::MysqlUtils::getNamesCharacterSetsSupportSql() . ";"; } if (!AgentConfig::mysqldumpBin()) { $self->_set_errstr("Unable to find 'mysqldump'"); return; } my $dumpoptions = "--quick --quote-names --add-drop-table"; if( $self->_mysql41OrNewer() ){ # $dumpoptions .= " --default-character-set=utf8 --set-charset" if $self->_mysql41OrNewer(); # $dumpoptions .= " --compatible=mysql323 " if !$self->{not_compatible_mysql_323} && $self->_mysql41OrNewer(); if ($self->{utf8names}) { $dumpoptions .= " --default-character-set=utf8 --set-charset"; }else{ my $dumpCharset = $self->{dump_charset} || 'utf8'; $dumpoptions .= " --default-character-set=$dumpCharset --set-charset" if $dumpCharset; } if ($self->_mysql512OrNewer()) { $dumpoptions .= " --routines"; } } if ($self->{extra_dump_options}) { $dumpoptions .= ' '.$self->{extra_dump_options}; } my $dumpcmd = $self->_getCmd(AgentConfig::mysqldumpBin(), $dumpoptions); if (!$self->_mysql41OrNewer()) { $dumpcmd .= " | grep -v '^---'"; # http://bugs.mysql.com/bug.php?id=1056 } $self->_setDumpCmdline($dumpcmd); my $res = $self->execute("SELECT 1"); if (!$self->rownum()) { $res = 0; } $self->finish(); return $res; } # # unquotes \n \t \\ in the string # used in parsing mysql batch output # sub _unquote { my ($str) = @_; my $len = length($str); my $res = ""; for (my $i = 0; $i <= $len; ++$i) { my $s = substr($str, $i, 1); if ($i != $len and $s eq "\\") { my $s = substr($str, ++$i, 1); $res .= "\n" if $s eq "n"; $res .= "\t" if $s eq "t"; $res .= "\\" if $s eq "\\"; } else { $res .= $s; } } return $res; } sub execute { my ($self, $sql, $quiet, @params) = @_; my $resultSql = $self->_getSql($sql, @params); if (!defined($resultSql)) { $self->_set_errstr(sprintf("Unable to replace placeholders in SQL query: invalid parameters number (%d)\n%s", scalar(@params), $sql)); return; } $sql = $resultSql; if (defined $self->{sql_prefix}) { $sql = $self->{sql_prefix} . $sql; } # quote ` from the shell and ' from the string $sql =~ s/'/'\\''/g; my @out = `echo '$sql' | $self->{cmdline}`; chomp @out; if ($#out > 0 and $out[0] =~ /^error/i) { $self->_set_errstr("Error: unable to execute $sql:\n" . (join "\n", @out)); return; } my @res = map {[map {$_ eq 'NULL' ? undef : _unquote($_)} split (/\t/, $_, -1)]} @out; $self->_set_execute_result(\@res); return 1; } 1;