1
# This program is copyright 2011 Percona Inc.
2
# This program is copyright 2007-2010 Baron Schwartz.
3
# Feedback and improvements are welcome.
5
# THIS PROGRAM IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
6
# WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
7
# MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
9
# This program is free software; you can redistribute it and/or modify it under
10
# the terms of the GNU General Public License as published by the Free Software
11
# Foundation, version 2; OR the Perl Artistic License. On UNIX and similar
12
# systems, you can issue `man perlgpl' or `man perlartistic' to read these
15
# You should have received a copy of the GNU General Public License along with
16
# this program; if not, write to the Free Software Foundation, Inc., 59 Temple
17
# Place, Suite 330, Boston, MA 02111-1307 USA.
18
# ###########################################################################
19
# DSNParser package $Revision: 7388 $
20
# ###########################################################################
23
# DSNParser parses DSNs and creates connections to MySQL using DBI and
29
use warnings FATAL => 'all';
30
use English qw(-no_match_vars);
31
use constant MKDEBUG => $ENV{MKDEBUG} || 0;
34
$Data::Dumper::Indent = 0;
35
$Data::Dumper::Quotekeys = 0;
40
my $have_dbi = $EVAL_ERROR ? 0 : 1;
48
# opts - Hashref of DSN options, usually created in
49
# <OptionParser::get_specs()>
54
my ( $class, %args ) = @_;
55
foreach my $arg ( qw(opts) ) {
56
die "I need a $arg argument" unless $args{$arg};
59
opts => {} # h, P, u, etc. Should come from DSN OPTIONS section in POD.
61
foreach my $opt ( @{$args{opts}} ) {
62
if ( !$opt->{key} || !$opt->{desc} ) {
63
die "Invalid DSN option: ", Dumper($opt);
65
MKDEBUG && _d('DSN option:',
67
map { "$_=" . (defined $opt->{$_} ? ($opt->{$_} || '') : 'undef') }
71
$self->{opts}->{$opt->{key}} = {
74
copy => $opt->{copy} || 0,
77
return bless $self, $class;
80
# Recognized properties:
81
# * dbidriver: which DBI driver to use; assumes mysql, supports Pg.
82
# * required: which parts are required (hashref).
83
# * set-vars: a list of variables to set after connecting
85
my ( $self, $prop, $value ) = @_;
87
MKDEBUG && _d('Setting', $prop, 'property');
88
$self->{$prop} = $value;
90
return $self->{$prop};
94
# Parse a DSN string like "h=host,P=3306".
98
# $prev - Optional DSN hashref with previous DSN values
99
# $defaults - Optional DSN hashref with default DSN values, used if a prop
100
# isn't specified in $dsn or $prev
103
# A DSN hashref like:
109
# p => 'mysql-password',
118
my ( $self, $dsn, $prev, $defaults ) = @_;
120
MKDEBUG && _d('No DSN to parse');
123
MKDEBUG && _d('Parsing', $dsn);
128
my $opts = $self->{opts};
131
foreach my $dsn_part ( split(/,/, $dsn) ) {
132
if ( my ($prop_key, $prop_val) = $dsn_part =~ m/^(.)=(.*)$/ ) {
133
# Handle the typical DSN parts like h=host, P=3306, etc.
134
$given_props{$prop_key} = $prop_val;
138
MKDEBUG && _d('Interpreting', $dsn_part, 'as h=', $dsn_part);
139
$given_props{h} = $dsn_part;
143
# Fill in final props from given, previous, and/or default props
144
foreach my $key ( keys %$opts ) {
145
MKDEBUG && _d('Finding value for', $key);
146
$final_props{$key} = $given_props{$key};
147
if ( !defined $final_props{$key}
148
&& defined $prev->{$key} && $opts->{$key}->{copy} )
150
$final_props{$key} = $prev->{$key};
151
MKDEBUG && _d('Copying value for', $key, 'from previous DSN');
153
if ( !defined $final_props{$key} ) {
154
$final_props{$key} = $defaults->{$key};
155
MKDEBUG && _d('Copying value for', $key, 'from defaults');
160
foreach my $key ( keys %given_props ) {
161
die "Unknown DSN option '$key' in '$dsn'. For more details, "
162
. "please use the --help option, or try 'perldoc $PROGRAM_NAME' "
163
. "for complete documentation."
164
unless exists $opts->{$key};
166
if ( (my $required = $self->prop('required')) ) {
167
foreach my $key ( keys %$required ) {
168
die "Missing required DSN option '$key' in '$dsn'. For more details, "
169
. "please use the --help option, or try 'perldoc $PROGRAM_NAME' "
170
. "for complete documentation."
171
unless $final_props{$key};
175
return \%final_props;
178
# Like parse() above but takes an OptionParser object instead of
181
my ( $self, $o ) = @_;
182
die 'I need an OptionParser object' unless ref $o eq 'OptionParser';
185
map { "$_=".$o->get($_); }
186
grep { $o->has($_) && $o->get($_) }
187
keys %{$self->{opts}}
189
MKDEBUG && _d('DSN string made from options:', $dsn_string);
190
return $self->parse($dsn_string);
193
# $props is an optional arrayref of allowed DSN parts to
194
# include in the string. So if you only want to stringify
195
# h and P, then pass [qw(h P)].
197
my ( $self, $dsn, $props ) = @_;
198
return $dsn unless ref $dsn;
199
my %allowed = $props ? map { $_=>1 } @$props : ();
201
map { "$_=" . ($_ eq 'p' ? '...' : $dsn->{$_}) }
202
grep { defined $dsn->{$_} && $self->{opts}->{$_} }
203
grep { !$props || $allowed{$_} }
210
= "DSN syntax is key=value[,key=value...] Allowable DSN keys:\n\n"
211
. " KEY COPY MEANING\n"
212
. " === ==== =============================================\n";
213
my %opts = %{$self->{opts}};
214
foreach my $key ( sort keys %opts ) {
216
. ($opts{$key}->{copy} ? 'yes ' : 'no ')
217
. ($opts{$key}->{desc} || '[No description]')
220
$usage .= "\n If the DSN is a bareword, the word is treated as the 'h' key.\n";
224
# Supports PostgreSQL via the dbidriver element of $info, but assumes MySQL by
227
my ( $self, $info ) = @_;
229
my %opts = %{$self->{opts}};
230
my $driver = $self->prop('dbidriver') || '';
231
if ( $driver eq 'Pg' ) {
232
$dsn = 'DBI:Pg:dbname=' . ( $info->{D} || '' ) . ';'
233
. join(';', map { "$opts{$_}->{dsn}=$info->{$_}" }
234
grep { defined $info->{$_} }
238
$dsn = 'DBI:mysql:' . ( $info->{D} || '' ) . ';'
239
. join(';', map { "$opts{$_}->{dsn}=$info->{$_}" }
240
grep { defined $info->{$_} }
242
. ';mysql_read_default_group=client';
245
return ($dsn, $info->{u}, $info->{p});
248
# Fills in missing info from a DSN after successfully connecting to the server.
250
my ( $self, $dbh, $dsn ) = @_;
251
my $vars = $dbh->selectall_hashref('SHOW VARIABLES', 'Variable_name');
252
my ($user, $db) = $dbh->selectrow_array('SELECT USER(), DATABASE()');
254
$dsn->{h} ||= $vars->{hostname}->{Value};
255
$dsn->{S} ||= $vars->{'socket'}->{Value};
256
$dsn->{P} ||= $vars->{port}->{Value};
261
# Actually opens a connection, then sets some things on the connection so it is
262
# the way the Maatkit tools will expect. Tools should NEVER open their own
263
# connection or use $dbh->reconnect, or these things will not take place!
265
my ( $self, $cxn_string, $user, $pass, $opts ) = @_;
271
ShowErrorStatement => 1,
272
mysql_enable_utf8 => ($cxn_string =~ m/charset=utf8/i ? 1 : 0),
274
@{$defaults}{ keys %$opts } = values %$opts;
276
# Only add this if explicitly set because we're not sure if
277
# mysql_use_result=0 would leave default mysql_store_result
279
if ( $opts->{mysql_use_result} ) {
280
$defaults->{mysql_use_result} = 1;
284
die "Cannot connect to MySQL because the Perl DBI module is not "
285
. "installed or not found. Run 'perl -MDBI' to see the directories "
286
. "that Perl searches for DBI. If DBI is not installed, try:\n"
287
. " Debian/Ubuntu apt-get install libdbi-perl\n"
288
. " RHEL/CentOS yum install perl-DBI\n"
289
. " OpenSolaris pgk install pkg:/SUNWpmdbi\n";
293
# Try twice to open the $dbh and set it up as desired.
296
while ( !$dbh && $tries-- ) {
297
MKDEBUG && _d($cxn_string, ' ', $user, ' ', $pass,
298
join(', ', map { "$_=>$defaults->{$_}" } keys %$defaults ));
301
$dbh = DBI->connect($cxn_string, $user, $pass, $defaults);
303
# If it's a MySQL connection, set some options.
304
if ( $cxn_string =~ m/mysql/i ) {
307
# Set SQL_MODE and options for SHOW CREATE TABLE.
308
# Get current, server SQL mode. Don't clobber this;
309
# append our SQL mode to whatever is already set.
310
# http://code.google.com/p/maatkit/issues/detail?id=801
311
$sql = 'SELECT @@SQL_MODE';
312
MKDEBUG && _d($dbh, $sql);
313
my ($sql_mode) = $dbh->selectrow_array($sql);
315
$sql = 'SET @@SQL_QUOTE_SHOW_CREATE = 1'
316
. '/*!40101, @@SQL_MODE=\'NO_AUTO_VALUE_ON_ZERO'
317
. ($sql_mode ? ",$sql_mode" : '')
319
MKDEBUG && _d($dbh, $sql);
322
# Set character set and binmode on STDOUT.
323
if ( my ($charset) = $cxn_string =~ m/charset=(\w+)/ ) {
324
$sql = "/*!40101 SET NAMES $charset*/";
325
MKDEBUG && _d($dbh, ':', $sql);
327
MKDEBUG && _d('Enabling charset for STDOUT');
328
if ( $charset eq 'utf8' ) {
329
binmode(STDOUT, ':utf8')
330
or die "Can't binmode(STDOUT, ':utf8'): $OS_ERROR";
333
binmode(STDOUT) or die "Can't binmode(STDOUT): $OS_ERROR";
337
if ( $self->prop('set-vars') ) {
338
$sql = "SET " . $self->prop('set-vars');
339
MKDEBUG && _d($dbh, ':', $sql);
344
if ( !$dbh && $EVAL_ERROR ) {
345
MKDEBUG && _d($EVAL_ERROR);
346
if ( $EVAL_ERROR =~ m/not a compiled character set|character set utf8/ ) {
347
MKDEBUG && _d('Going to try again without utf8 support');
348
delete $defaults->{mysql_enable_utf8};
350
elsif ( $EVAL_ERROR =~ m/locate DBD\/mysql/i ) {
351
die "Cannot connect to MySQL because the Perl DBD::mysql module is "
352
. "not installed or not found. Run 'perl -MDBD::mysql' to see "
353
. "the directories that Perl searches for DBD::mysql. If "
354
. "DBD::mysql is not installed, try:\n"
355
. " Debian/Ubuntu apt-get install libdbd-mysql-perl\n"
356
. " RHEL/CentOS yum install perl-DBD-MySQL\n"
357
. " OpenSolaris pgk install pkg:/SUNWapu13dbd-mysql\n";
365
MKDEBUG && _d('DBH info: ',
367
Dumper($dbh->selectrow_hashref(
368
'SELECT DATABASE(), CONNECTION_ID(), VERSION()/*!50038 , @@hostname*/')),
369
'Connection info:', $dbh->{mysql_hostinfo},
370
'Character set info:', Dumper($dbh->selectall_arrayref(
371
'SHOW VARIABLES LIKE "character_set%"', { Slice => {}})),
372
'$DBD::mysql::VERSION:', $DBD::mysql::VERSION,
373
'$DBI::VERSION:', $DBI::VERSION,
379
# Tries to figure out a hostname for the connection.
381
my ( $self, $dbh ) = @_;
382
if ( my ($host) = ($dbh->{mysql_hostinfo} || '') =~ m/^(\w+) via/ ) {
385
my ( $hostname, $one ) = $dbh->selectrow_array(
386
'SELECT /*!50038 @@hostname, */ 1');
390
# Disconnects a database handle, but complains verbosely if there are any active
391
# children. These are usually $sth handles that haven't been finish()ed.
393
my ( $self, $dbh ) = @_;
394
MKDEBUG && $self->print_active_handles($dbh);
398
sub print_active_handles {
399
my ( $self, $thing, $level ) = @_;
401
printf("# Active %sh: %s %s %s\n", ($thing->{Type} || 'undef'), "\t" x $level,
402
$thing, (($thing->{Type} || '') eq 'st' ? $thing->{Statement} || '' : ''))
403
or die "Cannot print: $OS_ERROR";
404
foreach my $handle ( grep {defined} @{ $thing->{ChildHandles} } ) {
405
$self->print_active_handles( $handle, $level + 1 );
409
# Copy all set vals in dsn_1 to dsn_2. Existing val in dsn_2 are not
410
# overwritten unless overwrite=>1 is given, but undef never overwrites a
413
my ( $self, $dsn_1, $dsn_2, %args ) = @_;
414
die 'I need a dsn_1 argument' unless $dsn_1;
415
die 'I need a dsn_2 argument' unless $dsn_2;
419
if ( $args{overwrite} ) {
420
$val = defined $dsn_1->{$key} ? $dsn_1->{$key} : $dsn_2->{$key};
423
$val = defined $dsn_2->{$key} ? $dsn_2->{$key} : $dsn_1->{$key};
426
} keys %{$self->{opts}};
431
my ($package, undef, $line) = caller 0;
432
@_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
433
map { defined $_ ? $_ : 'undef' }
435
print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
440
# ###########################################################################
441
# End DSNParser package
442
# ###########################################################################