~percona-toolkit-dev/percona-toolkit/mysql-5.6-test-fixes

« back to all changes in this revision

Viewing changes to bin/pt-duplicate-key-checker

  • Committer: Daniel Nichter
  • Date: 2011-06-24 22:02:05 UTC
  • Revision ID: daniel@percona.com-20110624220205-e779cao9hcwyly1w
Add forked Maatkit tools in bin/ and their tests in t/.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env perl
 
2
 
 
3
# This program is copyright 2011-@CURRENTYEAR@ Percona Inc.
 
4
# This program is copyright 2007-2011 Baron Schwartz.
 
5
# Feedback and improvements are welcome.
 
6
#
 
7
# THIS PROGRAM IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
 
8
# WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
 
9
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 
10
#
 
11
# This program is free software; you can redistribute it and/or modify it under
 
12
# the terms of the GNU General Public License as published by the Free Software
 
13
# Foundation, version 2; OR the Perl Artistic License.  On UNIX and similar
 
14
# systems, you can issue `man perlgpl' or `man perlartistic' to read these
 
15
# licenses.
 
16
#
 
17
# You should have received a copy of the GNU General Public License along with
 
18
# this program; if not, write to the Free Software Foundation, Inc., 59 Temple
 
19
# Place, Suite 330, Boston, MA  02111-1307  USA.
 
20
 
 
21
use strict;
 
22
use warnings FATAL => 'all';
 
23
 
 
24
our $VERSION = '@VERSION@';
 
25
our $DISTRIB = '@DISTRIB@';
 
26
our $SVN_REV = sprintf("%d", (q$Revision: 7477 $ =~ m/(\d+)/g, 0));
 
27
 
 
28
# ###########################################################################
 
29
# VersionParser package 6667
 
30
# This package is a copy without comments from the original.  The original
 
31
# with comments and its test file can be found in the SVN repository at,
 
32
#   trunk/common/VersionParser.pm
 
33
#   trunk/common/t/VersionParser.t
 
34
# See http://code.google.com/p/maatkit/wiki/Developers for more information.
 
35
# ###########################################################################
 
36
package VersionParser;
 
37
 
 
38
use strict;
 
39
use warnings FATAL => 'all';
 
40
 
 
41
use English qw(-no_match_vars);
 
42
 
 
43
use constant MKDEBUG => $ENV{MKDEBUG} || 0;
 
44
 
 
45
sub new {
 
46
   my ( $class ) = @_;
 
47
   bless {}, $class;
 
48
}
 
49
 
 
50
sub parse {
 
51
   my ( $self, $str ) = @_;
 
52
   my $result = sprintf('%03d%03d%03d', $str =~ m/(\d+)/g);
 
53
   MKDEBUG && _d($str, 'parses to', $result);
 
54
   return $result;
 
55
}
 
56
 
 
57
sub version_ge {
 
58
   my ( $self, $dbh, $target ) = @_;
 
59
   if ( !$self->{$dbh} ) {
 
60
      $self->{$dbh} = $self->parse(
 
61
         $dbh->selectrow_array('SELECT VERSION()'));
 
62
   }
 
63
   my $result = $self->{$dbh} ge $self->parse($target) ? 1 : 0;
 
64
   MKDEBUG && _d($self->{$dbh}, 'ge', $target, ':', $result);
 
65
   return $result;
 
66
}
 
67
 
 
68
sub innodb_version {
 
69
   my ( $self, $dbh ) = @_;
 
70
   return unless $dbh;
 
71
   my $innodb_version = "NO";
 
72
 
 
73
   my ($innodb) =
 
74
      grep { $_->{engine} =~ m/InnoDB/i }
 
75
      map  {
 
76
         my %hash;
 
77
         @hash{ map { lc $_ } keys %$_ } = values %$_;
 
78
         \%hash;
 
79
      }
 
80
      @{ $dbh->selectall_arrayref("SHOW ENGINES", {Slice=>{}}) };
 
81
   if ( $innodb ) {
 
82
      MKDEBUG && _d("InnoDB support:", $innodb->{support});
 
83
      if ( $innodb->{support} =~ m/YES|DEFAULT/i ) {
 
84
         my $vars = $dbh->selectrow_hashref(
 
85
            "SHOW VARIABLES LIKE 'innodb_version'");
 
86
         $innodb_version = !$vars ? "BUILTIN"
 
87
                         :          ($vars->{Value} || $vars->{value});
 
88
      }
 
89
      else {
 
90
         $innodb_version = $innodb->{support};  # probably DISABLED or NO
 
91
      }
 
92
   }
 
93
 
 
94
   MKDEBUG && _d("InnoDB version:", $innodb_version);
 
95
   return $innodb_version;
 
96
}
 
97
 
 
98
sub _d {
 
99
   my ($package, undef, $line) = caller 0;
 
100
   @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
 
101
        map { defined $_ ? $_ : 'undef' }
 
102
        @_;
 
103
   print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
 
104
}
 
105
 
 
106
1;
 
107
 
 
108
# ###########################################################################
 
109
# End VersionParser package
 
110
# ###########################################################################
 
111
 
 
112
# ###########################################################################
 
113
# Quoter package 6850
 
114
# This package is a copy without comments from the original.  The original
 
115
# with comments and its test file can be found in the SVN repository at,
 
116
#   trunk/common/Quoter.pm
 
117
#   trunk/common/t/Quoter.t
 
118
# See http://code.google.com/p/maatkit/wiki/Developers for more information.
 
119
# ###########################################################################
 
120
 
 
121
package Quoter;
 
122
 
 
123
use strict;
 
124
use warnings FATAL => 'all';
 
125
use English qw(-no_match_vars);
 
126
 
 
127
use constant MKDEBUG => $ENV{MKDEBUG} || 0;
 
128
 
 
129
sub new {
 
130
   my ( $class, %args ) = @_;
 
131
   return bless {}, $class;
 
132
}
 
133
 
 
134
sub quote {
 
135
   my ( $self, @vals ) = @_;
 
136
   foreach my $val ( @vals ) {
 
137
      $val =~ s/`/``/g;
 
138
   }
 
139
   return join('.', map { '`' . $_ . '`' } @vals);
 
140
}
 
141
 
 
142
sub quote_val {
 
143
   my ( $self, $val ) = @_;
 
144
 
 
145
   return 'NULL' unless defined $val;          # undef = NULL
 
146
   return "''" if $val eq '';                  # blank string = ''
 
147
   return $val if $val =~ m/^0x[0-9a-fA-F]+$/;  # hex data
 
148
 
 
149
   $val =~ s/(['\\])/\\$1/g;
 
150
   return "'$val'";
 
151
}
 
152
 
 
153
sub split_unquote {
 
154
   my ( $self, $db_tbl, $default_db ) = @_;
 
155
   $db_tbl =~ s/`//g;
 
156
   my ( $db, $tbl ) = split(/[.]/, $db_tbl);
 
157
   if ( !$tbl ) {
 
158
      $tbl = $db;
 
159
      $db  = $default_db;
 
160
   }
 
161
   return ($db, $tbl);
 
162
}
 
163
 
 
164
sub literal_like {
 
165
   my ( $self, $like ) = @_;
 
166
   return unless $like;
 
167
   $like =~ s/([%_])/\\$1/g;
 
168
   return "'$like'";
 
169
}
 
170
 
 
171
sub join_quote {
 
172
   my ( $self, $default_db, $db_tbl ) = @_;
 
173
   return unless $db_tbl;
 
174
   my ($db, $tbl) = split(/[.]/, $db_tbl);
 
175
   if ( !$tbl ) {
 
176
      $tbl = $db;
 
177
      $db  = $default_db;
 
178
   }
 
179
   $db  = "`$db`"  if $db  && $db  !~ m/^`/;
 
180
   $tbl = "`$tbl`" if $tbl && $tbl !~ m/^`/;
 
181
   return $db ? "$db.$tbl" : $tbl;
 
182
}
 
183
 
 
184
1;
 
185
 
 
186
# ###########################################################################
 
187
# End Quoter package
 
188
# ###########################################################################
 
189
 
 
190
# ###########################################################################
 
191
# TableParser package 7156
 
192
# This package is a copy without comments from the original.  The original
 
193
# with comments and its test file can be found in the SVN repository at,
 
194
#   trunk/common/TableParser.pm
 
195
#   trunk/common/t/TableParser.t
 
196
# See http://code.google.com/p/maatkit/wiki/Developers for more information.
 
197
# ###########################################################################
 
198
 
 
199
package TableParser;
 
200
 
 
201
use strict;
 
202
use warnings FATAL => 'all';
 
203
use English qw(-no_match_vars);
 
204
use Data::Dumper;
 
205
$Data::Dumper::Indent    = 1;
 
206
$Data::Dumper::Sortkeys  = 1;
 
207
$Data::Dumper::Quotekeys = 0;
 
208
 
 
209
use constant MKDEBUG => $ENV{MKDEBUG} || 0;
 
210
 
 
211
sub new {
 
212
   my ( $class, %args ) = @_;
 
213
   my @required_args = qw(Quoter);
 
214
   foreach my $arg ( @required_args ) {
 
215
      die "I need a $arg argument" unless $args{$arg};
 
216
   }
 
217
   my $self = { %args };
 
218
   return bless $self, $class;
 
219
}
 
220
 
 
221
sub parse {
 
222
   my ( $self, $ddl, $opts ) = @_;
 
223
   return unless $ddl;
 
224
   if ( ref $ddl eq 'ARRAY' ) {
 
225
      if ( lc $ddl->[0] eq 'table' ) {
 
226
         $ddl = $ddl->[1];
 
227
      }
 
228
      else {
 
229
         return {
 
230
            engine => 'VIEW',
 
231
         };
 
232
      }
 
233
   }
 
234
 
 
235
   if ( $ddl !~ m/CREATE (?:TEMPORARY )?TABLE `/ ) {
 
236
      die "Cannot parse table definition; is ANSI quoting "
 
237
         . "enabled or SQL_QUOTE_SHOW_CREATE disabled?";
 
238
   }
 
239
 
 
240
   my ($name)     = $ddl =~ m/CREATE (?:TEMPORARY )?TABLE\s+(`.+?`)/;
 
241
   (undef, $name) = $self->{Quoter}->split_unquote($name) if $name;
 
242
 
 
243
   $ddl =~ s/(`[^`]+`)/\L$1/g;
 
244
 
 
245
   my $engine = $self->get_engine($ddl);
 
246
 
 
247
   my @defs   = $ddl =~ m/^(\s+`.*?),?$/gm;
 
248
   my @cols   = map { $_ =~ m/`([^`]+)`/ } @defs;
 
249
   MKDEBUG && _d('Table cols:', join(', ', map { "`$_`" } @cols));
 
250
 
 
251
   my %def_for;
 
252
   @def_for{@cols} = @defs;
 
253
 
 
254
   my (@nums, @null);
 
255
   my (%type_for, %is_nullable, %is_numeric, %is_autoinc);
 
256
   foreach my $col ( @cols ) {
 
257
      my $def = $def_for{$col};
 
258
      my ( $type ) = $def =~ m/`[^`]+`\s([a-z]+)/;
 
259
      die "Can't determine column type for $def" unless $type;
 
260
      $type_for{$col} = $type;
 
261
      if ( $type =~ m/(?:(?:tiny|big|medium|small)?int|float|double|decimal|year)/ ) {
 
262
         push @nums, $col;
 
263
         $is_numeric{$col} = 1;
 
264
      }
 
265
      if ( $def !~ m/NOT NULL/ ) {
 
266
         push @null, $col;
 
267
         $is_nullable{$col} = 1;
 
268
      }
 
269
      $is_autoinc{$col} = $def =~ m/AUTO_INCREMENT/i ? 1 : 0;
 
270
   }
 
271
 
 
272
   my ($keys, $clustered_key) = $self->get_keys($ddl, $opts, \%is_nullable);
 
273
 
 
274
   my ($charset) = $ddl =~ m/DEFAULT CHARSET=(\w+)/;
 
275
 
 
276
   return {
 
277
      name           => $name,
 
278
      cols           => \@cols,
 
279
      col_posn       => { map { $cols[$_] => $_ } 0..$#cols },
 
280
      is_col         => { map { $_ => 1 } @cols },
 
281
      null_cols      => \@null,
 
282
      is_nullable    => \%is_nullable,
 
283
      is_autoinc     => \%is_autoinc,
 
284
      clustered_key  => $clustered_key,
 
285
      keys           => $keys,
 
286
      defs           => \%def_for,
 
287
      numeric_cols   => \@nums,
 
288
      is_numeric     => \%is_numeric,
 
289
      engine         => $engine,
 
290
      type_for       => \%type_for,
 
291
      charset        => $charset,
 
292
   };
 
293
}
 
294
 
 
295
sub sort_indexes {
 
296
   my ( $self, $tbl ) = @_;
 
297
 
 
298
   my @indexes
 
299
      = sort {
 
300
         (($a ne 'PRIMARY') <=> ($b ne 'PRIMARY'))
 
301
         || ( !$tbl->{keys}->{$a}->{is_unique} <=> !$tbl->{keys}->{$b}->{is_unique} )
 
302
         || ( $tbl->{keys}->{$a}->{is_nullable} <=> $tbl->{keys}->{$b}->{is_nullable} )
 
303
         || ( scalar(@{$tbl->{keys}->{$a}->{cols}}) <=> scalar(@{$tbl->{keys}->{$b}->{cols}}) )
 
304
      }
 
305
      grep {
 
306
         $tbl->{keys}->{$_}->{type} eq 'BTREE'
 
307
      }
 
308
      sort keys %{$tbl->{keys}};
 
309
 
 
310
   MKDEBUG && _d('Indexes sorted best-first:', join(', ', @indexes));
 
311
   return @indexes;
 
312
}
 
313
 
 
314
sub find_best_index {
 
315
   my ( $self, $tbl, $index ) = @_;
 
316
   my $best;
 
317
   if ( $index ) {
 
318
      ($best) = grep { uc $_ eq uc $index } keys %{$tbl->{keys}};
 
319
   }
 
320
   if ( !$best ) {
 
321
      if ( $index ) {
 
322
         die "Index '$index' does not exist in table";
 
323
      }
 
324
      else {
 
325
         ($best) = $self->sort_indexes($tbl);
 
326
      }
 
327
   }
 
328
   MKDEBUG && _d('Best index found is', $best);
 
329
   return $best;
 
330
}
 
331
 
 
332
sub find_possible_keys {
 
333
   my ( $self, $dbh, $database, $table, $quoter, $where ) = @_;
 
334
   return () unless $where;
 
335
   my $sql = 'EXPLAIN SELECT * FROM ' . $quoter->quote($database, $table)
 
336
      . ' WHERE ' . $where;
 
337
   MKDEBUG && _d($sql);
 
338
   my $expl = $dbh->selectrow_hashref($sql);
 
339
   $expl = { map { lc($_) => $expl->{$_} } keys %$expl };
 
340
   if ( $expl->{possible_keys} ) {
 
341
      MKDEBUG && _d('possible_keys =', $expl->{possible_keys});
 
342
      my @candidates = split(',', $expl->{possible_keys});
 
343
      my %possible   = map { $_ => 1 } @candidates;
 
344
      if ( $expl->{key} ) {
 
345
         MKDEBUG && _d('MySQL chose', $expl->{key});
 
346
         unshift @candidates, grep { $possible{$_} } split(',', $expl->{key});
 
347
         MKDEBUG && _d('Before deduping:', join(', ', @candidates));
 
348
         my %seen;
 
349
         @candidates = grep { !$seen{$_}++ } @candidates;
 
350
      }
 
351
      MKDEBUG && _d('Final list:', join(', ', @candidates));
 
352
      return @candidates;
 
353
   }
 
354
   else {
 
355
      MKDEBUG && _d('No keys in possible_keys');
 
356
      return ();
 
357
   }
 
358
}
 
359
 
 
360
sub check_table {
 
361
   my ( $self, %args ) = @_;
 
362
   my @required_args = qw(dbh db tbl);
 
363
   foreach my $arg ( @required_args ) {
 
364
      die "I need a $arg argument" unless $args{$arg};
 
365
   }
 
366
   my ($dbh, $db, $tbl) = @args{@required_args};
 
367
   my $q      = $self->{Quoter};
 
368
   my $db_tbl = $q->quote($db, $tbl);
 
369
   MKDEBUG && _d('Checking', $db_tbl);
 
370
 
 
371
   my $sql = "SHOW TABLES FROM " . $q->quote($db)
 
372
           . ' LIKE ' . $q->literal_like($tbl);
 
373
   MKDEBUG && _d($sql);
 
374
   my $row;
 
375
   eval {
 
376
      $row = $dbh->selectrow_arrayref($sql);
 
377
   };
 
378
   if ( $EVAL_ERROR ) {
 
379
      MKDEBUG && _d($EVAL_ERROR);
 
380
      return 0;
 
381
   }
 
382
   if ( !$row->[0] || $row->[0] ne $tbl ) {
 
383
      MKDEBUG && _d('Table does not exist');
 
384
      return 0;
 
385
   }
 
386
 
 
387
   MKDEBUG && _d('Table exists; no privs to check');
 
388
   return 1 unless $args{all_privs};
 
389
 
 
390
   $sql = "SHOW FULL COLUMNS FROM $db_tbl";
 
391
   MKDEBUG && _d($sql);
 
392
   eval {
 
393
      $row = $dbh->selectrow_hashref($sql);
 
394
   };
 
395
   if ( $EVAL_ERROR ) {
 
396
      MKDEBUG && _d($EVAL_ERROR);
 
397
      return 0;
 
398
   }
 
399
   if ( !scalar keys %$row ) {
 
400
      MKDEBUG && _d('Table has no columns:', Dumper($row));
 
401
      return 0;
 
402
   }
 
403
   my $privs = $row->{privileges} || $row->{Privileges};
 
404
 
 
405
   $sql = "DELETE FROM $db_tbl LIMIT 0";
 
406
   MKDEBUG && _d($sql);
 
407
   eval {
 
408
      $dbh->do($sql);
 
409
   };
 
410
   my $can_delete = $EVAL_ERROR ? 0 : 1;
 
411
 
 
412
   MKDEBUG && _d('User privs on', $db_tbl, ':', $privs,
 
413
      ($can_delete ? 'delete' : ''));
 
414
 
 
415
   if ( !($privs =~ m/select/ && $privs =~ m/insert/ && $privs =~ m/update/
 
416
          && $can_delete) ) {
 
417
      MKDEBUG && _d('User does not have all privs');
 
418
      return 0;
 
419
   }
 
420
 
 
421
   MKDEBUG && _d('User has all privs');
 
422
   return 1;
 
423
}
 
424
 
 
425
sub get_engine {
 
426
   my ( $self, $ddl, $opts ) = @_;
 
427
   my ( $engine ) = $ddl =~ m/\).*?(?:ENGINE|TYPE)=(\w+)/;
 
428
   MKDEBUG && _d('Storage engine:', $engine);
 
429
   return $engine || undef;
 
430
}
 
431
 
 
432
sub get_keys {
 
433
   my ( $self, $ddl, $opts, $is_nullable ) = @_;
 
434
   my $engine        = $self->get_engine($ddl);
 
435
   my $keys          = {};
 
436
   my $clustered_key = undef;
 
437
 
 
438
   KEY:
 
439
   foreach my $key ( $ddl =~ m/^  ((?:[A-Z]+ )?KEY .*)$/gm ) {
 
440
 
 
441
      next KEY if $key =~ m/FOREIGN/;
 
442
 
 
443
      my $key_ddl = $key;
 
444
      MKDEBUG && _d('Parsed key:', $key_ddl);
 
445
 
 
446
      if ( $engine !~ m/MEMORY|HEAP/ ) {
 
447
         $key =~ s/USING HASH/USING BTREE/;
 
448
      }
 
449
 
 
450
      my ( $type, $cols ) = $key =~ m/(?:USING (\w+))? \((.+)\)/;
 
451
      my ( $special ) = $key =~ m/(FULLTEXT|SPATIAL)/;
 
452
      $type = $type || $special || 'BTREE';
 
453
      if ( $opts->{mysql_version} && $opts->{mysql_version} lt '004001000'
 
454
         && $engine =~ m/HEAP|MEMORY/i )
 
455
      {
 
456
         $type = 'HASH'; # MySQL pre-4.1 supports only HASH indexes on HEAP
 
457
      }
 
458
 
 
459
      my ($name) = $key =~ m/(PRIMARY|`[^`]*`)/;
 
460
      my $unique = $key =~ m/PRIMARY|UNIQUE/ ? 1 : 0;
 
461
      my @cols;
 
462
      my @col_prefixes;
 
463
      foreach my $col_def ( $cols =~ m/`[^`]+`(?:\(\d+\))?/g ) {
 
464
         my ($name, $prefix) = $col_def =~ m/`([^`]+)`(?:\((\d+)\))?/;
 
465
         push @cols, $name;
 
466
         push @col_prefixes, $prefix;
 
467
      }
 
468
      $name =~ s/`//g;
 
469
 
 
470
      MKDEBUG && _d( $name, 'key cols:', join(', ', map { "`$_`" } @cols));
 
471
 
 
472
      $keys->{$name} = {
 
473
         name         => $name,
 
474
         type         => $type,
 
475
         colnames     => $cols,
 
476
         cols         => \@cols,
 
477
         col_prefixes => \@col_prefixes,
 
478
         is_unique    => $unique,
 
479
         is_nullable  => scalar(grep { $is_nullable->{$_} } @cols),
 
480
         is_col       => { map { $_ => 1 } @cols },
 
481
         ddl          => $key_ddl,
 
482
      };
 
483
 
 
484
      if ( $engine =~ m/InnoDB/i && !$clustered_key ) {
 
485
         my $this_key = $keys->{$name};
 
486
         if ( $this_key->{name} eq 'PRIMARY' ) {
 
487
            $clustered_key = 'PRIMARY';
 
488
         }
 
489
         elsif ( $this_key->{is_unique} && !$this_key->{is_nullable} ) {
 
490
            $clustered_key = $this_key->{name};
 
491
         }
 
492
         MKDEBUG && $clustered_key && _d('This key is the clustered key');
 
493
      }
 
494
   }
 
495
 
 
496
   return $keys, $clustered_key;
 
497
}
 
498
 
 
499
sub get_fks {
 
500
   my ( $self, $ddl, $opts ) = @_;
 
501
   my $fks = {};
 
502
 
 
503
   foreach my $fk (
 
504
      $ddl =~ m/CONSTRAINT .* FOREIGN KEY .* REFERENCES [^\)]*\)/mg )
 
505
   {
 
506
      my ( $name ) = $fk =~ m/CONSTRAINT `(.*?)`/;
 
507
      my ( $cols ) = $fk =~ m/FOREIGN KEY \(([^\)]+)\)/;
 
508
      my ( $parent, $parent_cols ) = $fk =~ m/REFERENCES (\S+) \(([^\)]+)\)/;
 
509
 
 
510
      if ( $parent !~ m/\./ && $opts->{database} ) {
 
511
         $parent = "`$opts->{database}`.$parent";
 
512
      }
 
513
 
 
514
      $fks->{$name} = {
 
515
         name           => $name,
 
516
         colnames       => $cols,
 
517
         cols           => [ map { s/[ `]+//g; $_; } split(',', $cols) ],
 
518
         parent_tbl     => $parent,
 
519
         parent_colnames=> $parent_cols,
 
520
         parent_cols    => [ map { s/[ `]+//g; $_; } split(',', $parent_cols) ],
 
521
         ddl            => $fk,
 
522
      };
 
523
   }
 
524
 
 
525
   return $fks;
 
526
}
 
527
 
 
528
sub remove_auto_increment {
 
529
   my ( $self, $ddl ) = @_;
 
530
   $ddl =~ s/(^\).*?) AUTO_INCREMENT=\d+\b/$1/m;
 
531
   return $ddl;
 
532
}
 
533
 
 
534
sub remove_secondary_indexes {
 
535
   my ( $self, $ddl ) = @_;
 
536
   my $sec_indexes_ddl;
 
537
   my $tbl_struct = $self->parse($ddl);
 
538
 
 
539
   if ( ($tbl_struct->{engine} || '') =~ m/InnoDB/i ) {
 
540
      my $clustered_key = $tbl_struct->{clustered_key};
 
541
      $clustered_key  ||= '';
 
542
 
 
543
      my @sec_indexes   = map {
 
544
         my $key_def = $_->{ddl};
 
545
         $key_def =~ s/([\(\)])/\\$1/g;
 
546
         $ddl =~ s/\s+$key_def//i;
 
547
 
 
548
         my $key_ddl = "ADD $_->{ddl}";
 
549
         $key_ddl   .= ',' unless $key_ddl =~ m/,$/;
 
550
         $key_ddl;
 
551
      }
 
552
      grep { $_->{name} ne $clustered_key }
 
553
      values %{$tbl_struct->{keys}};
 
554
      MKDEBUG && _d('Secondary indexes:', Dumper(\@sec_indexes));
 
555
 
 
556
      if ( @sec_indexes ) {
 
557
         $sec_indexes_ddl = join(' ', @sec_indexes);
 
558
         $sec_indexes_ddl =~ s/,$//;
 
559
      }
 
560
 
 
561
      $ddl =~ s/,(\n\) )/$1/s;
 
562
   }
 
563
   else {
 
564
      MKDEBUG && _d('Not removing secondary indexes from',
 
565
         $tbl_struct->{engine}, 'table');
 
566
   }
 
567
 
 
568
   return $ddl, $sec_indexes_ddl, $tbl_struct;
 
569
}
 
570
 
 
571
sub _d {
 
572
   my ($package, undef, $line) = caller 0;
 
573
   @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
 
574
        map { defined $_ ? $_ : 'undef' }
 
575
        @_;
 
576
   print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
 
577
}
 
578
 
 
579
1;
 
580
 
 
581
# ###########################################################################
 
582
# End TableParser package
 
583
# ###########################################################################
 
584
 
 
585
# ###########################################################################
 
586
# MySQLDump package 6345
 
587
# This package is a copy without comments from the original.  The original
 
588
# with comments and its test file can be found in the SVN repository at,
 
589
#   trunk/common/MySQLDump.pm
 
590
#   trunk/common/t/MySQLDump.t
 
591
# See http://code.google.com/p/maatkit/wiki/Developers for more information.
 
592
# ###########################################################################
 
593
package MySQLDump;
 
594
 
 
595
use strict;
 
596
use warnings FATAL => 'all';
 
597
 
 
598
use English qw(-no_match_vars);
 
599
 
 
600
use constant MKDEBUG => $ENV{MKDEBUG} || 0;
 
601
 
 
602
( our $before = <<'EOF') =~ s/^   //gm;
 
603
   /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
 
604
   /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
 
605
   /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
 
606
   /*!40101 SET NAMES utf8 */;
 
607
   /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
 
608
   /*!40103 SET TIME_ZONE='+00:00' */;
 
609
   /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
 
610
   /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
 
611
   /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
 
612
   /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
 
613
EOF
 
614
 
 
615
( our $after = <<'EOF') =~ s/^   //gm;
 
616
   /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
 
617
   /*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
 
618
   /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
 
619
   /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
 
620
   /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
 
621
   /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
 
622
   /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
 
623
   /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
 
624
EOF
 
625
 
 
626
sub new {
 
627
   my ( $class, %args ) = @_;
 
628
   my $self = {
 
629
      cache => 0,  # Afaik no script uses this cache any longer because
 
630
   };
 
631
   return bless $self, $class;
 
632
}
 
633
 
 
634
sub dump {
 
635
   my ( $self, $dbh, $quoter, $db, $tbl, $what ) = @_;
 
636
 
 
637
   if ( $what eq 'table' ) {
 
638
      my $ddl = $self->get_create_table($dbh, $quoter, $db, $tbl);
 
639
      return unless $ddl;
 
640
      if ( $ddl->[0] eq 'table' ) {
 
641
         return $before
 
642
            . 'DROP TABLE IF EXISTS ' . $quoter->quote($tbl) . ";\n"
 
643
            . $ddl->[1] . ";\n";
 
644
      }
 
645
      else {
 
646
         return 'DROP TABLE IF EXISTS ' . $quoter->quote($tbl) . ";\n"
 
647
            . '/*!50001 DROP VIEW IF EXISTS '
 
648
            . $quoter->quote($tbl) . "*/;\n/*!50001 "
 
649
            . $self->get_tmp_table($dbh, $quoter, $db, $tbl) . "*/;\n";
 
650
      }
 
651
   }
 
652
   elsif ( $what eq 'triggers' ) {
 
653
      my $trgs = $self->get_triggers($dbh, $quoter, $db, $tbl);
 
654
      if ( $trgs && @$trgs ) {
 
655
         my $result = $before . "\nDELIMITER ;;\n";
 
656
         foreach my $trg ( @$trgs ) {
 
657
            if ( $trg->{sql_mode} ) {
 
658
               $result .= qq{/*!50003 SET SESSION SQL_MODE='$trg->{sql_mode}' */;;\n};
 
659
            }
 
660
            $result .= "/*!50003 CREATE */ ";
 
661
            if ( $trg->{definer} ) {
 
662
               my ( $user, $host )
 
663
                  = map { s/'/''/g; "'$_'"; }
 
664
                    split('@', $trg->{definer}, 2);
 
665
               $result .= "/*!50017 DEFINER=$user\@$host */ ";
 
666
            }
 
667
            $result .= sprintf("/*!50003 TRIGGER %s %s %s ON %s\nFOR EACH ROW %s */;;\n\n",
 
668
               $quoter->quote($trg->{trigger}),
 
669
               @{$trg}{qw(timing event)},
 
670
               $quoter->quote($trg->{table}),
 
671
               $trg->{statement});
 
672
         }
 
673
         $result .= "DELIMITER ;\n\n/*!50003 SET SESSION SQL_MODE=\@OLD_SQL_MODE */;\n\n";
 
674
         return $result;
 
675
      }
 
676
      else {
 
677
         return undef;
 
678
      }
 
679
   }
 
680
   elsif ( $what eq 'view' ) {
 
681
      my $ddl = $self->get_create_table($dbh, $quoter, $db, $tbl);
 
682
      return '/*!50001 DROP TABLE IF EXISTS ' . $quoter->quote($tbl) . "*/;\n"
 
683
         . '/*!50001 DROP VIEW IF EXISTS ' . $quoter->quote($tbl) . "*/;\n"
 
684
         . '/*!50001 ' . $ddl->[1] . "*/;\n";
 
685
   }
 
686
   else {
 
687
      die "You didn't say what to dump.";
 
688
   }
 
689
}
 
690
 
 
691
sub _use_db {
 
692
   my ( $self, $dbh, $quoter, $new ) = @_;
 
693
   if ( !$new ) {
 
694
      MKDEBUG && _d('No new DB to use');
 
695
      return;
 
696
   }
 
697
   my $sql = 'USE ' . $quoter->quote($new);
 
698
   MKDEBUG && _d($dbh, $sql);
 
699
   $dbh->do($sql);
 
700
   return;
 
701
}
 
702
 
 
703
sub get_create_table {
 
704
   my ( $self, $dbh, $quoter, $db, $tbl ) = @_;
 
705
   if ( !$self->{cache} || !$self->{tables}->{$db}->{$tbl} ) {
 
706
      my $sql = '/*!40101 SET @OLD_SQL_MODE := @@SQL_MODE, '
 
707
         . q{@@SQL_MODE := REPLACE(REPLACE(@@SQL_MODE, 'ANSI_QUOTES', ''), ',,', ','), }
 
708
         . '@OLD_QUOTE := @@SQL_QUOTE_SHOW_CREATE, '
 
709
         . '@@SQL_QUOTE_SHOW_CREATE := 1 */';
 
710
      MKDEBUG && _d($sql);
 
711
      eval { $dbh->do($sql); };
 
712
      MKDEBUG && $EVAL_ERROR && _d($EVAL_ERROR);
 
713
      $self->_use_db($dbh, $quoter, $db);
 
714
      $sql = "SHOW CREATE TABLE " . $quoter->quote($db, $tbl);
 
715
      MKDEBUG && _d($sql);
 
716
      my $href;
 
717
      eval { $href = $dbh->selectrow_hashref($sql); };
 
718
      if ( $EVAL_ERROR ) {
 
719
         warn "Failed to $sql.  The table may be damaged.\nError: $EVAL_ERROR";
 
720
         return;
 
721
      }
 
722
 
 
723
      $sql = '/*!40101 SET @@SQL_MODE := @OLD_SQL_MODE, '
 
724
         . '@@SQL_QUOTE_SHOW_CREATE := @OLD_QUOTE */';
 
725
      MKDEBUG && _d($sql);
 
726
      $dbh->do($sql);
 
727
      my ($key) = grep { m/create table/i } keys %$href;
 
728
      if ( $key ) {
 
729
         MKDEBUG && _d('This table is a base table');
 
730
         $self->{tables}->{$db}->{$tbl} = [ 'table', $href->{$key} ];
 
731
      }
 
732
      else {
 
733
         MKDEBUG && _d('This table is a view');
 
734
         ($key) = grep { m/create view/i } keys %$href;
 
735
         $self->{tables}->{$db}->{$tbl} = [ 'view', $href->{$key} ];
 
736
      }
 
737
   }
 
738
   return $self->{tables}->{$db}->{$tbl};
 
739
}
 
740
 
 
741
sub get_columns {
 
742
   my ( $self, $dbh, $quoter, $db, $tbl ) = @_;
 
743
   MKDEBUG && _d('Get columns for', $db, $tbl);
 
744
   if ( !$self->{cache} || !$self->{columns}->{$db}->{$tbl} ) {
 
745
      $self->_use_db($dbh, $quoter, $db);
 
746
      my $sql = "SHOW COLUMNS FROM " . $quoter->quote($db, $tbl);
 
747
      MKDEBUG && _d($sql);
 
748
      my $cols = $dbh->selectall_arrayref($sql, { Slice => {} });
 
749
 
 
750
      $self->{columns}->{$db}->{$tbl} = [
 
751
         map {
 
752
            my %row;
 
753
            @row{ map { lc $_ } keys %$_ } = values %$_;
 
754
            \%row;
 
755
         } @$cols
 
756
      ];
 
757
   }
 
758
   return $self->{columns}->{$db}->{$tbl};
 
759
}
 
760
 
 
761
sub get_tmp_table {
 
762
   my ( $self, $dbh, $quoter, $db, $tbl ) = @_;
 
763
   my $result = 'CREATE TABLE ' . $quoter->quote($tbl) . " (\n";
 
764
   $result .= join(",\n",
 
765
      map { '  ' . $quoter->quote($_->{field}) . ' ' . $_->{type} }
 
766
      @{$self->get_columns($dbh, $quoter, $db, $tbl)});
 
767
   $result .= "\n)";
 
768
   MKDEBUG && _d($result);
 
769
   return $result;
 
770
}
 
771
 
 
772
sub get_triggers {
 
773
   my ( $self, $dbh, $quoter, $db, $tbl ) = @_;
 
774
   if ( !$self->{cache} || !$self->{triggers}->{$db} ) {
 
775
      $self->{triggers}->{$db} = {};
 
776
      my $sql = '/*!40101 SET @OLD_SQL_MODE := @@SQL_MODE, '
 
777
         . q{@@SQL_MODE := REPLACE(REPLACE(@@SQL_MODE, 'ANSI_QUOTES', ''), ',,', ','), }
 
778
         . '@OLD_QUOTE := @@SQL_QUOTE_SHOW_CREATE, '
 
779
         . '@@SQL_QUOTE_SHOW_CREATE := 1 */';
 
780
      MKDEBUG && _d($sql);
 
781
      eval { $dbh->do($sql); };
 
782
      MKDEBUG && $EVAL_ERROR && _d($EVAL_ERROR);
 
783
      $sql = "SHOW TRIGGERS FROM " . $quoter->quote($db);
 
784
      MKDEBUG && _d($sql);
 
785
      my $sth = $dbh->prepare($sql);
 
786
      $sth->execute();
 
787
      if ( $sth->rows ) {
 
788
         my $trgs = $sth->fetchall_arrayref({});
 
789
         foreach my $trg (@$trgs) {
 
790
            my %trg;
 
791
            @trg{ map { lc $_ } keys %$trg } = values %$trg;
 
792
            push @{ $self->{triggers}->{$db}->{ $trg{table} } }, \%trg;
 
793
         }
 
794
      }
 
795
      $sql = '/*!40101 SET @@SQL_MODE := @OLD_SQL_MODE, '
 
796
         . '@@SQL_QUOTE_SHOW_CREATE := @OLD_QUOTE */';
 
797
      MKDEBUG && _d($sql);
 
798
      $dbh->do($sql);
 
799
   }
 
800
   if ( $tbl ) {
 
801
      return $self->{triggers}->{$db}->{$tbl};
 
802
   }
 
803
   return values %{$self->{triggers}->{$db}};
 
804
}
 
805
 
 
806
sub get_databases {
 
807
   my ( $self, $dbh, $quoter, $like ) = @_;
 
808
   if ( !$self->{cache} || !$self->{databases} || $like ) {
 
809
      my $sql = 'SHOW DATABASES';
 
810
      my @params;
 
811
      if ( $like ) {
 
812
         $sql .= ' LIKE ?';
 
813
         push @params, $like;
 
814
      }
 
815
      my $sth = $dbh->prepare($sql);
 
816
      MKDEBUG && _d($sql, @params);
 
817
      $sth->execute( @params );
 
818
      my @dbs = map { $_->[0] } @{$sth->fetchall_arrayref()};
 
819
      $self->{databases} = \@dbs unless $like;
 
820
      return @dbs;
 
821
   }
 
822
   return @{$self->{databases}};
 
823
}
 
824
 
 
825
sub get_table_status {
 
826
   my ( $self, $dbh, $quoter, $db, $like ) = @_;
 
827
   if ( !$self->{cache} || !$self->{table_status}->{$db} || $like ) {
 
828
      my $sql = "SHOW TABLE STATUS FROM " . $quoter->quote($db);
 
829
      my @params;
 
830
      if ( $like ) {
 
831
         $sql .= ' LIKE ?';
 
832
         push @params, $like;
 
833
      }
 
834
      MKDEBUG && _d($sql, @params);
 
835
      my $sth = $dbh->prepare($sql);
 
836
      $sth->execute(@params);
 
837
      my @tables = @{$sth->fetchall_arrayref({})};
 
838
      @tables = map {
 
839
         my %tbl; # Make a copy with lowercased keys
 
840
         @tbl{ map { lc $_ } keys %$_ } = values %$_;
 
841
         $tbl{engine} ||= $tbl{type} || $tbl{comment};
 
842
         delete $tbl{type};
 
843
         \%tbl;
 
844
      } @tables;
 
845
      $self->{table_status}->{$db} = \@tables unless $like;
 
846
      return @tables;
 
847
   }
 
848
   return @{$self->{table_status}->{$db}};
 
849
}
 
850
 
 
851
sub get_table_list {
 
852
   my ( $self, $dbh, $quoter, $db, $like ) = @_;
 
853
   if ( !$self->{cache} || !$self->{table_list}->{$db} || $like ) {
 
854
      my $sql = "SHOW /*!50002 FULL*/ TABLES FROM " . $quoter->quote($db);
 
855
      my @params;
 
856
      if ( $like ) {
 
857
         $sql .= ' LIKE ?';
 
858
         push @params, $like;
 
859
      }
 
860
      MKDEBUG && _d($sql, @params);
 
861
      my $sth = $dbh->prepare($sql);
 
862
      $sth->execute(@params);
 
863
      my @tables = @{$sth->fetchall_arrayref()};
 
864
      @tables = map {
 
865
         my %tbl = (
 
866
            name   => $_->[0],
 
867
            engine => ($_->[1] || '') eq 'VIEW' ? 'VIEW' : '',
 
868
         );
 
869
         \%tbl;
 
870
      } @tables;
 
871
      $self->{table_list}->{$db} = \@tables unless $like;
 
872
      return @tables;
 
873
   }
 
874
   return @{$self->{table_list}->{$db}};
 
875
}
 
876
 
 
877
sub _d {
 
878
   my ($package, undef, $line) = caller 0;
 
879
   @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
 
880
        map { defined $_ ? $_ : 'undef' }
 
881
        @_;
 
882
   print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
 
883
}
 
884
 
 
885
1;
 
886
 
 
887
# ###########################################################################
 
888
# End MySQLDump package
 
889
# ###########################################################################
 
890
 
 
891
# ###########################################################################
 
892
# DSNParser package 7388
 
893
# This package is a copy without comments from the original.  The original
 
894
# with comments and its test file can be found in the SVN repository at,
 
895
#   trunk/common/DSNParser.pm
 
896
#   trunk/common/t/DSNParser.t
 
897
# See http://code.google.com/p/maatkit/wiki/Developers for more information.
 
898
# ###########################################################################
 
899
 
 
900
package DSNParser;
 
901
 
 
902
use strict;
 
903
use warnings FATAL => 'all';
 
904
use English qw(-no_match_vars);
 
905
use constant MKDEBUG => $ENV{MKDEBUG} || 0;
 
906
 
 
907
use Data::Dumper;
 
908
$Data::Dumper::Indent    = 0;
 
909
$Data::Dumper::Quotekeys = 0;
 
910
 
 
911
eval {
 
912
   require DBI;
 
913
};
 
914
my $have_dbi = $EVAL_ERROR ? 0 : 1;
 
915
 
 
916
 
 
917
sub new {
 
918
   my ( $class, %args ) = @_;
 
919
   foreach my $arg ( qw(opts) ) {
 
920
      die "I need a $arg argument" unless $args{$arg};
 
921
   }
 
922
   my $self = {
 
923
      opts => {}  # h, P, u, etc.  Should come from DSN OPTIONS section in POD.
 
924
   };
 
925
   foreach my $opt ( @{$args{opts}} ) {
 
926
      if ( !$opt->{key} || !$opt->{desc} ) {
 
927
         die "Invalid DSN option: ", Dumper($opt);
 
928
      }
 
929
      MKDEBUG && _d('DSN option:',
 
930
         join(', ',
 
931
            map { "$_=" . (defined $opt->{$_} ? ($opt->{$_} || '') : 'undef') }
 
932
               keys %$opt
 
933
         )
 
934
      );
 
935
      $self->{opts}->{$opt->{key}} = {
 
936
         dsn  => $opt->{dsn},
 
937
         desc => $opt->{desc},
 
938
         copy => $opt->{copy} || 0,
 
939
      };
 
940
   }
 
941
   return bless $self, $class;
 
942
}
 
943
 
 
944
sub prop {
 
945
   my ( $self, $prop, $value ) = @_;
 
946
   if ( @_ > 2 ) {
 
947
      MKDEBUG && _d('Setting', $prop, 'property');
 
948
      $self->{$prop} = $value;
 
949
   }
 
950
   return $self->{$prop};
 
951
}
 
952
 
 
953
sub parse {
 
954
   my ( $self, $dsn, $prev, $defaults ) = @_;
 
955
   if ( !$dsn ) {
 
956
      MKDEBUG && _d('No DSN to parse');
 
957
      return;
 
958
   }
 
959
   MKDEBUG && _d('Parsing', $dsn);
 
960
   $prev     ||= {};
 
961
   $defaults ||= {};
 
962
   my %given_props;
 
963
   my %final_props;
 
964
   my $opts = $self->{opts};
 
965
 
 
966
   foreach my $dsn_part ( split(/,/, $dsn) ) {
 
967
      if ( my ($prop_key, $prop_val) = $dsn_part =~  m/^(.)=(.*)$/ ) {
 
968
         $given_props{$prop_key} = $prop_val;
 
969
      }
 
970
      else {
 
971
         MKDEBUG && _d('Interpreting', $dsn_part, 'as h=', $dsn_part);
 
972
         $given_props{h} = $dsn_part;
 
973
      }
 
974
   }
 
975
 
 
976
   foreach my $key ( keys %$opts ) {
 
977
      MKDEBUG && _d('Finding value for', $key);
 
978
      $final_props{$key} = $given_props{$key};
 
979
      if (   !defined $final_props{$key}
 
980
           && defined $prev->{$key} && $opts->{$key}->{copy} )
 
981
      {
 
982
         $final_props{$key} = $prev->{$key};
 
983
         MKDEBUG && _d('Copying value for', $key, 'from previous DSN');
 
984
      }
 
985
      if ( !defined $final_props{$key} ) {
 
986
         $final_props{$key} = $defaults->{$key};
 
987
         MKDEBUG && _d('Copying value for', $key, 'from defaults');
 
988
      }
 
989
   }
 
990
 
 
991
   foreach my $key ( keys %given_props ) {
 
992
      die "Unknown DSN option '$key' in '$dsn'.  For more details, "
 
993
            . "please use the --help option, or try 'perldoc $PROGRAM_NAME' "
 
994
            . "for complete documentation."
 
995
         unless exists $opts->{$key};
 
996
   }
 
997
   if ( (my $required = $self->prop('required')) ) {
 
998
      foreach my $key ( keys %$required ) {
 
999
         die "Missing required DSN option '$key' in '$dsn'.  For more details, "
 
1000
               . "please use the --help option, or try 'perldoc $PROGRAM_NAME' "
 
1001
               . "for complete documentation."
 
1002
            unless $final_props{$key};
 
1003
      }
 
1004
   }
 
1005
 
 
1006
   return \%final_props;
 
1007
}
 
1008
 
 
1009
sub parse_options {
 
1010
   my ( $self, $o ) = @_;
 
1011
   die 'I need an OptionParser object' unless ref $o eq 'OptionParser';
 
1012
   my $dsn_string
 
1013
      = join(',',
 
1014
          map  { "$_=".$o->get($_); }
 
1015
          grep { $o->has($_) && $o->get($_) }
 
1016
          keys %{$self->{opts}}
 
1017
        );
 
1018
   MKDEBUG && _d('DSN string made from options:', $dsn_string);
 
1019
   return $self->parse($dsn_string);
 
1020
}
 
1021
 
 
1022
sub as_string {
 
1023
   my ( $self, $dsn, $props ) = @_;
 
1024
   return $dsn unless ref $dsn;
 
1025
   my %allowed = $props ? map { $_=>1 } @$props : ();
 
1026
   return join(',',
 
1027
      map  { "$_=" . ($_ eq 'p' ? '...' : $dsn->{$_})  }
 
1028
      grep { defined $dsn->{$_} && $self->{opts}->{$_} }
 
1029
      grep { !$props || $allowed{$_}                   }
 
1030
      sort keys %$dsn );
 
1031
}
 
1032
 
 
1033
sub usage {
 
1034
   my ( $self ) = @_;
 
1035
   my $usage
 
1036
      = "DSN syntax is key=value[,key=value...]  Allowable DSN keys:\n\n"
 
1037
      . "  KEY  COPY  MEANING\n"
 
1038
      . "  ===  ====  =============================================\n";
 
1039
   my %opts = %{$self->{opts}};
 
1040
   foreach my $key ( sort keys %opts ) {
 
1041
      $usage .= "  $key    "
 
1042
             .  ($opts{$key}->{copy} ? 'yes   ' : 'no    ')
 
1043
             .  ($opts{$key}->{desc} || '[No description]')
 
1044
             . "\n";
 
1045
   }
 
1046
   $usage .= "\n  If the DSN is a bareword, the word is treated as the 'h' key.\n";
 
1047
   return $usage;
 
1048
}
 
1049
 
 
1050
sub get_cxn_params {
 
1051
   my ( $self, $info ) = @_;
 
1052
   my $dsn;
 
1053
   my %opts = %{$self->{opts}};
 
1054
   my $driver = $self->prop('dbidriver') || '';
 
1055
   if ( $driver eq 'Pg' ) {
 
1056
      $dsn = 'DBI:Pg:dbname=' . ( $info->{D} || '' ) . ';'
 
1057
         . join(';', map  { "$opts{$_}->{dsn}=$info->{$_}" }
 
1058
                     grep { defined $info->{$_} }
 
1059
                     qw(h P));
 
1060
   }
 
1061
   else {
 
1062
      $dsn = 'DBI:mysql:' . ( $info->{D} || '' ) . ';'
 
1063
         . join(';', map  { "$opts{$_}->{dsn}=$info->{$_}" }
 
1064
                     grep { defined $info->{$_} }
 
1065
                     qw(F h P S A))
 
1066
         . ';mysql_read_default_group=client';
 
1067
   }
 
1068
   MKDEBUG && _d($dsn);
 
1069
   return ($dsn, $info->{u}, $info->{p});
 
1070
}
 
1071
 
 
1072
sub fill_in_dsn {
 
1073
   my ( $self, $dbh, $dsn ) = @_;
 
1074
   my $vars = $dbh->selectall_hashref('SHOW VARIABLES', 'Variable_name');
 
1075
   my ($user, $db) = $dbh->selectrow_array('SELECT USER(), DATABASE()');
 
1076
   $user =~ s/@.*//;
 
1077
   $dsn->{h} ||= $vars->{hostname}->{Value};
 
1078
   $dsn->{S} ||= $vars->{'socket'}->{Value};
 
1079
   $dsn->{P} ||= $vars->{port}->{Value};
 
1080
   $dsn->{u} ||= $user;
 
1081
   $dsn->{D} ||= $db;
 
1082
}
 
1083
 
 
1084
sub get_dbh {
 
1085
   my ( $self, $cxn_string, $user, $pass, $opts ) = @_;
 
1086
   $opts ||= {};
 
1087
   my $defaults = {
 
1088
      AutoCommit         => 0,
 
1089
      RaiseError         => 1,
 
1090
      PrintError         => 0,
 
1091
      ShowErrorStatement => 1,
 
1092
      mysql_enable_utf8 => ($cxn_string =~ m/charset=utf8/i ? 1 : 0),
 
1093
   };
 
1094
   @{$defaults}{ keys %$opts } = values %$opts;
 
1095
 
 
1096
   if ( $opts->{mysql_use_result} ) {
 
1097
      $defaults->{mysql_use_result} = 1;
 
1098
   }
 
1099
 
 
1100
   if ( !$have_dbi ) {
 
1101
      die "Cannot connect to MySQL because the Perl DBI module is not "
 
1102
         . "installed or not found.  Run 'perl -MDBI' to see the directories "
 
1103
         . "that Perl searches for DBI.  If DBI is not installed, try:\n"
 
1104
         . "  Debian/Ubuntu  apt-get install libdbi-perl\n"
 
1105
         . "  RHEL/CentOS    yum install perl-DBI\n"
 
1106
         . "  OpenSolaris    pgk install pkg:/SUNWpmdbi\n";
 
1107
 
 
1108
   }
 
1109
 
 
1110
   my $dbh;
 
1111
   my $tries = 2;
 
1112
   while ( !$dbh && $tries-- ) {
 
1113
      MKDEBUG && _d($cxn_string, ' ', $user, ' ', $pass, ' {',
 
1114
         join(', ', map { "$_=>$defaults->{$_}" } keys %$defaults ), '}');
 
1115
 
 
1116
      eval {
 
1117
         $dbh = DBI->connect($cxn_string, $user, $pass, $defaults);
 
1118
 
 
1119
         if ( $cxn_string =~ m/mysql/i ) {
 
1120
            my $sql;
 
1121
 
 
1122
            $sql = 'SELECT @@SQL_MODE';
 
1123
            MKDEBUG && _d($dbh, $sql);
 
1124
            my ($sql_mode) = $dbh->selectrow_array($sql);
 
1125
 
 
1126
            $sql = 'SET @@SQL_QUOTE_SHOW_CREATE = 1'
 
1127
                 . '/*!40101, @@SQL_MODE=\'NO_AUTO_VALUE_ON_ZERO'
 
1128
                 . ($sql_mode ? ",$sql_mode" : '')
 
1129
                 . '\'*/';
 
1130
            MKDEBUG && _d($dbh, $sql);
 
1131
            $dbh->do($sql);
 
1132
 
 
1133
            if ( my ($charset) = $cxn_string =~ m/charset=(\w+)/ ) {
 
1134
               $sql = "/*!40101 SET NAMES $charset*/";
 
1135
               MKDEBUG && _d($dbh, ':', $sql);
 
1136
               $dbh->do($sql);
 
1137
               MKDEBUG && _d('Enabling charset for STDOUT');
 
1138
               if ( $charset eq 'utf8' ) {
 
1139
                  binmode(STDOUT, ':utf8')
 
1140
                     or die "Can't binmode(STDOUT, ':utf8'): $OS_ERROR";
 
1141
               }
 
1142
               else {
 
1143
                  binmode(STDOUT) or die "Can't binmode(STDOUT): $OS_ERROR";
 
1144
               }
 
1145
            }
 
1146
 
 
1147
            if ( $self->prop('set-vars') ) {
 
1148
               $sql = "SET " . $self->prop('set-vars');
 
1149
               MKDEBUG && _d($dbh, ':', $sql);
 
1150
               $dbh->do($sql);
 
1151
            }
 
1152
         }
 
1153
      };
 
1154
      if ( !$dbh && $EVAL_ERROR ) {
 
1155
         MKDEBUG && _d($EVAL_ERROR);
 
1156
         if ( $EVAL_ERROR =~ m/not a compiled character set|character set utf8/ ) {
 
1157
            MKDEBUG && _d('Going to try again without utf8 support');
 
1158
            delete $defaults->{mysql_enable_utf8};
 
1159
         }
 
1160
         elsif ( $EVAL_ERROR =~ m/locate DBD\/mysql/i ) {
 
1161
            die "Cannot connect to MySQL because the Perl DBD::mysql module is "
 
1162
               . "not installed or not found.  Run 'perl -MDBD::mysql' to see "
 
1163
               . "the directories that Perl searches for DBD::mysql.  If "
 
1164
               . "DBD::mysql is not installed, try:\n"
 
1165
               . "  Debian/Ubuntu  apt-get install libdbd-mysql-perl\n"
 
1166
               . "  RHEL/CentOS    yum install perl-DBD-MySQL\n"
 
1167
               . "  OpenSolaris    pgk install pkg:/SUNWapu13dbd-mysql\n";
 
1168
         }
 
1169
         if ( !$tries ) {
 
1170
            die $EVAL_ERROR;
 
1171
         }
 
1172
      }
 
1173
   }
 
1174
 
 
1175
   MKDEBUG && _d('DBH info: ',
 
1176
      $dbh,
 
1177
      Dumper($dbh->selectrow_hashref(
 
1178
         'SELECT DATABASE(), CONNECTION_ID(), VERSION()/*!50038 , @@hostname*/')),
 
1179
      'Connection info:',      $dbh->{mysql_hostinfo},
 
1180
      'Character set info:',   Dumper($dbh->selectall_arrayref(
 
1181
                     'SHOW VARIABLES LIKE "character_set%"', { Slice => {}})),
 
1182
      '$DBD::mysql::VERSION:', $DBD::mysql::VERSION,
 
1183
      '$DBI::VERSION:',        $DBI::VERSION,
 
1184
   );
 
1185
 
 
1186
   return $dbh;
 
1187
}
 
1188
 
 
1189
sub get_hostname {
 
1190
   my ( $self, $dbh ) = @_;
 
1191
   if ( my ($host) = ($dbh->{mysql_hostinfo} || '') =~ m/^(\w+) via/ ) {
 
1192
      return $host;
 
1193
   }
 
1194
   my ( $hostname, $one ) = $dbh->selectrow_array(
 
1195
      'SELECT /*!50038 @@hostname, */ 1');
 
1196
   return $hostname;
 
1197
}
 
1198
 
 
1199
sub disconnect {
 
1200
   my ( $self, $dbh ) = @_;
 
1201
   MKDEBUG && $self->print_active_handles($dbh);
 
1202
   $dbh->disconnect;
 
1203
}
 
1204
 
 
1205
sub print_active_handles {
 
1206
   my ( $self, $thing, $level ) = @_;
 
1207
   $level ||= 0;
 
1208
   printf("# Active %sh: %s %s %s\n", ($thing->{Type} || 'undef'), "\t" x $level,
 
1209
      $thing, (($thing->{Type} || '') eq 'st' ? $thing->{Statement} || '' : ''))
 
1210
      or die "Cannot print: $OS_ERROR";
 
1211
   foreach my $handle ( grep {defined} @{ $thing->{ChildHandles} } ) {
 
1212
      $self->print_active_handles( $handle, $level + 1 );
 
1213
   }
 
1214
}
 
1215
 
 
1216
sub copy {
 
1217
   my ( $self, $dsn_1, $dsn_2, %args ) = @_;
 
1218
   die 'I need a dsn_1 argument' unless $dsn_1;
 
1219
   die 'I need a dsn_2 argument' unless $dsn_2;
 
1220
   my %new_dsn = map {
 
1221
      my $key = $_;
 
1222
      my $val;
 
1223
      if ( $args{overwrite} ) {
 
1224
         $val = defined $dsn_1->{$key} ? $dsn_1->{$key} : $dsn_2->{$key};
 
1225
      }
 
1226
      else {
 
1227
         $val = defined $dsn_2->{$key} ? $dsn_2->{$key} : $dsn_1->{$key};
 
1228
      }
 
1229
      $key => $val;
 
1230
   } keys %{$self->{opts}};
 
1231
   return \%new_dsn;
 
1232
}
 
1233
 
 
1234
sub _d {
 
1235
   my ($package, undef, $line) = caller 0;
 
1236
   @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
 
1237
        map { defined $_ ? $_ : 'undef' }
 
1238
        @_;
 
1239
   print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
 
1240
}
 
1241
 
 
1242
1;
 
1243
 
 
1244
# ###########################################################################
 
1245
# End DSNParser package
 
1246
# ###########################################################################
 
1247
 
 
1248
# ###########################################################################
 
1249
# OptionParser package 7102
 
1250
# This package is a copy without comments from the original.  The original
 
1251
# with comments and its test file can be found in the SVN repository at,
 
1252
#   trunk/common/OptionParser.pm
 
1253
#   trunk/common/t/OptionParser.t
 
1254
# See http://code.google.com/p/maatkit/wiki/Developers for more information.
 
1255
# ###########################################################################
 
1256
 
 
1257
package OptionParser;
 
1258
 
 
1259
use strict;
 
1260
use warnings FATAL => 'all';
 
1261
use List::Util qw(max);
 
1262
use English qw(-no_match_vars);
 
1263
use constant MKDEBUG => $ENV{MKDEBUG} || 0;
 
1264
 
 
1265
use Getopt::Long;
 
1266
 
 
1267
my $POD_link_re = '[LC]<"?([^">]+)"?>';
 
1268
 
 
1269
sub new {
 
1270
   my ( $class, %args ) = @_;
 
1271
   my @required_args = qw();
 
1272
   foreach my $arg ( @required_args ) {
 
1273
      die "I need a $arg argument" unless $args{$arg};
 
1274
   }
 
1275
 
 
1276
   my ($program_name) = $PROGRAM_NAME =~ m/([.A-Za-z-]+)$/;
 
1277
   $program_name ||= $PROGRAM_NAME;
 
1278
   my $home = $ENV{HOME} || $ENV{HOMEPATH} || $ENV{USERPROFILE} || '.';
 
1279
 
 
1280
   my %attributes = (
 
1281
      'type'       => 1,
 
1282
      'short form' => 1,
 
1283
      'group'      => 1,
 
1284
      'default'    => 1,
 
1285
      'cumulative' => 1,
 
1286
      'negatable'  => 1,
 
1287
   );
 
1288
 
 
1289
   my $self = {
 
1290
      head1             => 'OPTIONS',        # These args are used internally
 
1291
      skip_rules        => 0,                # to instantiate another Option-
 
1292
      item              => '--(.*)',         # Parser obj that parses the
 
1293
      attributes        => \%attributes,     # DSN OPTIONS section.  Tools
 
1294
      parse_attributes  => \&_parse_attribs, # don't tinker with these args.
 
1295
 
 
1296
      %args,
 
1297
 
 
1298
      strict            => 1,  # disabled by a special rule
 
1299
      program_name      => $program_name,
 
1300
      opts              => {},
 
1301
      got_opts          => 0,
 
1302
      short_opts        => {},
 
1303
      defaults          => {},
 
1304
      groups            => {},
 
1305
      allowed_groups    => {},
 
1306
      errors            => [],
 
1307
      rules             => [],  # desc of rules for --help
 
1308
      mutex             => [],  # rule: opts are mutually exclusive
 
1309
      atleast1          => [],  # rule: at least one opt is required
 
1310
      disables          => {},  # rule: opt disables other opts 
 
1311
      defaults_to       => {},  # rule: opt defaults to value of other opt
 
1312
      DSNParser         => undef,
 
1313
      default_files     => [
 
1314
         "/etc/maatkit/maatkit.conf",
 
1315
         "/etc/maatkit/$program_name.conf",
 
1316
         "$home/.maatkit.conf",
 
1317
         "$home/.$program_name.conf",
 
1318
      ],
 
1319
      types             => {
 
1320
         string => 's', # standard Getopt type
 
1321
         int    => 'i', # standard Getopt type
 
1322
         float  => 'f', # standard Getopt type
 
1323
         Hash   => 'H', # hash, formed from a comma-separated list
 
1324
         hash   => 'h', # hash as above, but only if a value is given
 
1325
         Array  => 'A', # array, similar to Hash
 
1326
         array  => 'a', # array, similar to hash
 
1327
         DSN    => 'd', # DSN
 
1328
         size   => 'z', # size with kMG suffix (powers of 2^10)
 
1329
         time   => 'm', # time, with an optional suffix of s/h/m/d
 
1330
      },
 
1331
   };
 
1332
 
 
1333
   return bless $self, $class;
 
1334
}
 
1335
 
 
1336
sub get_specs {
 
1337
   my ( $self, $file ) = @_;
 
1338
   $file ||= $self->{file} || __FILE__;
 
1339
   my @specs = $self->_pod_to_specs($file);
 
1340
   $self->_parse_specs(@specs);
 
1341
 
 
1342
   open my $fh, "<", $file or die "Cannot open $file: $OS_ERROR";
 
1343
   my $contents = do { local $/ = undef; <$fh> };
 
1344
   close $fh;
 
1345
   if ( $contents =~ m/^=head1 DSN OPTIONS/m ) {
 
1346
      MKDEBUG && _d('Parsing DSN OPTIONS');
 
1347
      my $dsn_attribs = {
 
1348
         dsn  => 1,
 
1349
         copy => 1,
 
1350
      };
 
1351
      my $parse_dsn_attribs = sub {
 
1352
         my ( $self, $option, $attribs ) = @_;
 
1353
         map {
 
1354
            my $val = $attribs->{$_};
 
1355
            if ( $val ) {
 
1356
               $val    = $val eq 'yes' ? 1
 
1357
                       : $val eq 'no'  ? 0
 
1358
                       :                 $val;
 
1359
               $attribs->{$_} = $val;
 
1360
            }
 
1361
         } keys %$attribs;
 
1362
         return {
 
1363
            key => $option,
 
1364
            %$attribs,
 
1365
         };
 
1366
      };
 
1367
      my $dsn_o = new OptionParser(
 
1368
         description       => 'DSN OPTIONS',
 
1369
         head1             => 'DSN OPTIONS',
 
1370
         dsn               => 0,         # XXX don't infinitely recurse!
 
1371
         item              => '\* (.)',  # key opts are a single character
 
1372
         skip_rules        => 1,         # no rules before opts
 
1373
         attributes        => $dsn_attribs,
 
1374
         parse_attributes  => $parse_dsn_attribs,
 
1375
      );
 
1376
      my @dsn_opts = map {
 
1377
         my $opts = {
 
1378
            key  => $_->{spec}->{key},
 
1379
            dsn  => $_->{spec}->{dsn},
 
1380
            copy => $_->{spec}->{copy},
 
1381
            desc => $_->{desc},
 
1382
         };
 
1383
         $opts;
 
1384
      } $dsn_o->_pod_to_specs($file);
 
1385
      $self->{DSNParser} = DSNParser->new(opts => \@dsn_opts);
 
1386
   }
 
1387
 
 
1388
   return;
 
1389
}
 
1390
 
 
1391
sub DSNParser {
 
1392
   my ( $self ) = @_;
 
1393
   return $self->{DSNParser};
 
1394
};
 
1395
 
 
1396
sub get_defaults_files {
 
1397
   my ( $self ) = @_;
 
1398
   return @{$self->{default_files}};
 
1399
}
 
1400
 
 
1401
sub _pod_to_specs {
 
1402
   my ( $self, $file ) = @_;
 
1403
   $file ||= $self->{file} || __FILE__;
 
1404
   open my $fh, '<', $file or die "Cannot open $file: $OS_ERROR";
 
1405
 
 
1406
   my @specs = ();
 
1407
   my @rules = ();
 
1408
   my $para;
 
1409
 
 
1410
   local $INPUT_RECORD_SEPARATOR = '';
 
1411
   while ( $para = <$fh> ) {
 
1412
      next unless $para =~ m/^=head1 $self->{head1}/;
 
1413
      last;
 
1414
   }
 
1415
 
 
1416
   while ( $para = <$fh> ) {
 
1417
      last if $para =~ m/^=over/;
 
1418
      next if $self->{skip_rules};
 
1419
      chomp $para;
 
1420
      $para =~ s/\s+/ /g;
 
1421
      $para =~ s/$POD_link_re/$1/go;
 
1422
      MKDEBUG && _d('Option rule:', $para);
 
1423
      push @rules, $para;
 
1424
   }
 
1425
 
 
1426
   die "POD has no $self->{head1} section" unless $para;
 
1427
 
 
1428
   do {
 
1429
      if ( my ($option) = $para =~ m/^=item $self->{item}/ ) {
 
1430
         chomp $para;
 
1431
         MKDEBUG && _d($para);
 
1432
         my %attribs;
 
1433
 
 
1434
         $para = <$fh>; # read next paragraph, possibly attributes
 
1435
 
 
1436
         if ( $para =~ m/: / ) { # attributes
 
1437
            $para =~ s/\s+\Z//g;
 
1438
            %attribs = map {
 
1439
                  my ( $attrib, $val) = split(/: /, $_);
 
1440
                  die "Unrecognized attribute for --$option: $attrib"
 
1441
                     unless $self->{attributes}->{$attrib};
 
1442
                  ($attrib, $val);
 
1443
               } split(/; /, $para);
 
1444
            if ( $attribs{'short form'} ) {
 
1445
               $attribs{'short form'} =~ s/-//;
 
1446
            }
 
1447
            $para = <$fh>; # read next paragraph, probably short help desc
 
1448
         }
 
1449
         else {
 
1450
            MKDEBUG && _d('Option has no attributes');
 
1451
         }
 
1452
 
 
1453
         $para =~ s/\s+\Z//g;
 
1454
         $para =~ s/\s+/ /g;
 
1455
         $para =~ s/$POD_link_re/$1/go;
 
1456
 
 
1457
         $para =~ s/\.(?:\n.*| [A-Z].*|\Z)//s;
 
1458
         MKDEBUG && _d('Short help:', $para);
 
1459
 
 
1460
         die "No description after option spec $option" if $para =~ m/^=item/;
 
1461
 
 
1462
         if ( my ($base_option) =  $option =~ m/^\[no\](.*)/ ) {
 
1463
            $option = $base_option;
 
1464
            $attribs{'negatable'} = 1;
 
1465
         }
 
1466
 
 
1467
         push @specs, {
 
1468
            spec  => $self->{parse_attributes}->($self, $option, \%attribs), 
 
1469
            desc  => $para
 
1470
               . (defined $attribs{default} ? " (default $attribs{default})" : ''),
 
1471
            group => ($attribs{'group'} ? $attribs{'group'} : 'default'),
 
1472
         };
 
1473
      }
 
1474
      while ( $para = <$fh> ) {
 
1475
         last unless $para;
 
1476
         if ( $para =~ m/^=head1/ ) {
 
1477
            $para = undef; # Can't 'last' out of a do {} block.
 
1478
            last;
 
1479
         }
 
1480
         last if $para =~ m/^=item /;
 
1481
      }
 
1482
   } while ( $para );
 
1483
 
 
1484
   die "No valid specs in $self->{head1}" unless @specs;
 
1485
 
 
1486
   close $fh;
 
1487
   return @specs, @rules;
 
1488
}
 
1489
 
 
1490
sub _parse_specs {
 
1491
   my ( $self, @specs ) = @_;
 
1492
   my %disables; # special rule that requires deferred checking
 
1493
 
 
1494
   foreach my $opt ( @specs ) {
 
1495
      if ( ref $opt ) { # It's an option spec, not a rule.
 
1496
         MKDEBUG && _d('Parsing opt spec:',
 
1497
            map { ($_, '=>', $opt->{$_}) } keys %$opt);
 
1498
 
 
1499
         my ( $long, $short ) = $opt->{spec} =~ m/^([\w-]+)(?:\|([^!+=]*))?/;
 
1500
         if ( !$long ) {
 
1501
            die "Cannot parse long option from spec $opt->{spec}";
 
1502
         }
 
1503
         $opt->{long} = $long;
 
1504
 
 
1505
         die "Duplicate long option --$long" if exists $self->{opts}->{$long};
 
1506
         $self->{opts}->{$long} = $opt;
 
1507
 
 
1508
         if ( length $long == 1 ) {
 
1509
            MKDEBUG && _d('Long opt', $long, 'looks like short opt');
 
1510
            $self->{short_opts}->{$long} = $long;
 
1511
         }
 
1512
 
 
1513
         if ( $short ) {
 
1514
            die "Duplicate short option -$short"
 
1515
               if exists $self->{short_opts}->{$short};
 
1516
            $self->{short_opts}->{$short} = $long;
 
1517
            $opt->{short} = $short;
 
1518
         }
 
1519
         else {
 
1520
            $opt->{short} = undef;
 
1521
         }
 
1522
 
 
1523
         $opt->{is_negatable}  = $opt->{spec} =~ m/!/        ? 1 : 0;
 
1524
         $opt->{is_cumulative} = $opt->{spec} =~ m/\+/       ? 1 : 0;
 
1525
         $opt->{is_required}   = $opt->{desc} =~ m/required/ ? 1 : 0;
 
1526
 
 
1527
         $opt->{group} ||= 'default';
 
1528
         $self->{groups}->{ $opt->{group} }->{$long} = 1;
 
1529
 
 
1530
         $opt->{value} = undef;
 
1531
         $opt->{got}   = 0;
 
1532
 
 
1533
         my ( $type ) = $opt->{spec} =~ m/=(.)/;
 
1534
         $opt->{type} = $type;
 
1535
         MKDEBUG && _d($long, 'type:', $type);
 
1536
 
 
1537
 
 
1538
         $opt->{spec} =~ s/=./=s/ if ( $type && $type =~ m/[HhAadzm]/ );
 
1539
 
 
1540
         if ( (my ($def) = $opt->{desc} =~ m/default\b(?: ([^)]+))?/) ) {
 
1541
            $self->{defaults}->{$long} = defined $def ? $def : 1;
 
1542
            MKDEBUG && _d($long, 'default:', $def);
 
1543
         }
 
1544
 
 
1545
         if ( $long eq 'config' ) {
 
1546
            $self->{defaults}->{$long} = join(',', $self->get_defaults_files());
 
1547
         }
 
1548
 
 
1549
         if ( (my ($dis) = $opt->{desc} =~ m/(disables .*)/) ) {
 
1550
            $disables{$long} = $dis;
 
1551
            MKDEBUG && _d('Deferring check of disables rule for', $opt, $dis);
 
1552
         }
 
1553
 
 
1554
         $self->{opts}->{$long} = $opt;
 
1555
      }
 
1556
      else { # It's an option rule, not a spec.
 
1557
         MKDEBUG && _d('Parsing rule:', $opt); 
 
1558
         push @{$self->{rules}}, $opt;
 
1559
         my @participants = $self->_get_participants($opt);
 
1560
         my $rule_ok = 0;
 
1561
 
 
1562
         if ( $opt =~ m/mutually exclusive|one and only one/ ) {
 
1563
            $rule_ok = 1;
 
1564
            push @{$self->{mutex}}, \@participants;
 
1565
            MKDEBUG && _d(@participants, 'are mutually exclusive');
 
1566
         }
 
1567
         if ( $opt =~ m/at least one|one and only one/ ) {
 
1568
            $rule_ok = 1;
 
1569
            push @{$self->{atleast1}}, \@participants;
 
1570
            MKDEBUG && _d(@participants, 'require at least one');
 
1571
         }
 
1572
         if ( $opt =~ m/default to/ ) {
 
1573
            $rule_ok = 1;
 
1574
            $self->{defaults_to}->{$participants[0]} = $participants[1];
 
1575
            MKDEBUG && _d($participants[0], 'defaults to', $participants[1]);
 
1576
         }
 
1577
         if ( $opt =~ m/restricted to option groups/ ) {
 
1578
            $rule_ok = 1;
 
1579
            my ($groups) = $opt =~ m/groups ([\w\s\,]+)/;
 
1580
            my @groups = split(',', $groups);
 
1581
            %{$self->{allowed_groups}->{$participants[0]}} = map {
 
1582
               s/\s+//;
 
1583
               $_ => 1;
 
1584
            } @groups;
 
1585
         }
 
1586
         if( $opt =~ m/accepts additional command-line arguments/ ) {
 
1587
            $rule_ok = 1;
 
1588
            $self->{strict} = 0;
 
1589
            MKDEBUG && _d("Strict mode disabled by rule");
 
1590
         }
 
1591
 
 
1592
         die "Unrecognized option rule: $opt" unless $rule_ok;
 
1593
      }
 
1594
   }
 
1595
 
 
1596
   foreach my $long ( keys %disables ) {
 
1597
      my @participants = $self->_get_participants($disables{$long});
 
1598
      $self->{disables}->{$long} = \@participants;
 
1599
      MKDEBUG && _d('Option', $long, 'disables', @participants);
 
1600
   }
 
1601
 
 
1602
   return; 
 
1603
}
 
1604
 
 
1605
sub _get_participants {
 
1606
   my ( $self, $str ) = @_;
 
1607
   my @participants;
 
1608
   foreach my $long ( $str =~ m/--(?:\[no\])?([\w-]+)/g ) {
 
1609
      die "Option --$long does not exist while processing rule $str"
 
1610
         unless exists $self->{opts}->{$long};
 
1611
      push @participants, $long;
 
1612
   }
 
1613
   MKDEBUG && _d('Participants for', $str, ':', @participants);
 
1614
   return @participants;
 
1615
}
 
1616
 
 
1617
sub opts {
 
1618
   my ( $self ) = @_;
 
1619
   my %opts = %{$self->{opts}};
 
1620
   return %opts;
 
1621
}
 
1622
 
 
1623
sub short_opts {
 
1624
   my ( $self ) = @_;
 
1625
   my %short_opts = %{$self->{short_opts}};
 
1626
   return %short_opts;
 
1627
}
 
1628
 
 
1629
sub set_defaults {
 
1630
   my ( $self, %defaults ) = @_;
 
1631
   $self->{defaults} = {};
 
1632
   foreach my $long ( keys %defaults ) {
 
1633
      die "Cannot set default for nonexistent option $long"
 
1634
         unless exists $self->{opts}->{$long};
 
1635
      $self->{defaults}->{$long} = $defaults{$long};
 
1636
      MKDEBUG && _d('Default val for', $long, ':', $defaults{$long});
 
1637
   }
 
1638
   return;
 
1639
}
 
1640
 
 
1641
sub get_defaults {
 
1642
   my ( $self ) = @_;
 
1643
   return $self->{defaults};
 
1644
}
 
1645
 
 
1646
sub get_groups {
 
1647
   my ( $self ) = @_;
 
1648
   return $self->{groups};
 
1649
}
 
1650
 
 
1651
sub _set_option {
 
1652
   my ( $self, $opt, $val ) = @_;
 
1653
   my $long = exists $self->{opts}->{$opt}       ? $opt
 
1654
            : exists $self->{short_opts}->{$opt} ? $self->{short_opts}->{$opt}
 
1655
            : die "Getopt::Long gave a nonexistent option: $opt";
 
1656
 
 
1657
   $opt = $self->{opts}->{$long};
 
1658
   if ( $opt->{is_cumulative} ) {
 
1659
      $opt->{value}++;
 
1660
   }
 
1661
   else {
 
1662
      $opt->{value} = $val;
 
1663
   }
 
1664
   $opt->{got} = 1;
 
1665
   MKDEBUG && _d('Got option', $long, '=', $val);
 
1666
}
 
1667
 
 
1668
sub get_opts {
 
1669
   my ( $self ) = @_; 
 
1670
 
 
1671
   foreach my $long ( keys %{$self->{opts}} ) {
 
1672
      $self->{opts}->{$long}->{got} = 0;
 
1673
      $self->{opts}->{$long}->{value}
 
1674
         = exists $self->{defaults}->{$long}       ? $self->{defaults}->{$long}
 
1675
         : $self->{opts}->{$long}->{is_cumulative} ? 0
 
1676
         : undef;
 
1677
   }
 
1678
   $self->{got_opts} = 0;
 
1679
 
 
1680
   $self->{errors} = [];
 
1681
 
 
1682
   if ( @ARGV && $ARGV[0] eq "--config" ) {
 
1683
      shift @ARGV;
 
1684
      $self->_set_option('config', shift @ARGV);
 
1685
   }
 
1686
   if ( $self->has('config') ) {
 
1687
      my @extra_args;
 
1688
      foreach my $filename ( split(',', $self->get('config')) ) {
 
1689
         eval {
 
1690
            push @extra_args, $self->_read_config_file($filename);
 
1691
         };
 
1692
         if ( $EVAL_ERROR ) {
 
1693
            if ( $self->got('config') ) {
 
1694
               die $EVAL_ERROR;
 
1695
            }
 
1696
            elsif ( MKDEBUG ) {
 
1697
               _d($EVAL_ERROR);
 
1698
            }
 
1699
         }
 
1700
      }
 
1701
      unshift @ARGV, @extra_args;
 
1702
   }
 
1703
 
 
1704
   Getopt::Long::Configure('no_ignore_case', 'bundling');
 
1705
   GetOptions(
 
1706
      map    { $_->{spec} => sub { $self->_set_option(@_); } }
 
1707
      grep   { $_->{long} ne 'config' } # --config is handled specially above.
 
1708
      values %{$self->{opts}}
 
1709
   ) or $self->save_error('Error parsing options');
 
1710
 
 
1711
   if ( exists $self->{opts}->{version} && $self->{opts}->{version}->{got} ) {
 
1712
      printf("%s  Ver %s Distrib %s Changeset %s\n",
 
1713
         $self->{program_name}, $main::VERSION, $main::DISTRIB, $main::SVN_REV)
 
1714
            or die "Cannot print: $OS_ERROR";
 
1715
      exit 0;
 
1716
   }
 
1717
 
 
1718
   if ( @ARGV && $self->{strict} ) {
 
1719
      $self->save_error("Unrecognized command-line options @ARGV");
 
1720
   }
 
1721
 
 
1722
   foreach my $mutex ( @{$self->{mutex}} ) {
 
1723
      my @set = grep { $self->{opts}->{$_}->{got} } @$mutex;
 
1724
      if ( @set > 1 ) {
 
1725
         my $err = join(', ', map { "--$self->{opts}->{$_}->{long}" }
 
1726
                      @{$mutex}[ 0 .. scalar(@$mutex) - 2] )
 
1727
                 . ' and --'.$self->{opts}->{$mutex->[-1]}->{long}
 
1728
                 . ' are mutually exclusive.';
 
1729
         $self->save_error($err);
 
1730
      }
 
1731
   }
 
1732
 
 
1733
   foreach my $required ( @{$self->{atleast1}} ) {
 
1734
      my @set = grep { $self->{opts}->{$_}->{got} } @$required;
 
1735
      if ( @set == 0 ) {
 
1736
         my $err = join(', ', map { "--$self->{opts}->{$_}->{long}" }
 
1737
                      @{$required}[ 0 .. scalar(@$required) - 2] )
 
1738
                 .' or --'.$self->{opts}->{$required->[-1]}->{long};
 
1739
         $self->save_error("Specify at least one of $err");
 
1740
      }
 
1741
   }
 
1742
 
 
1743
   $self->_check_opts( keys %{$self->{opts}} );
 
1744
   $self->{got_opts} = 1;
 
1745
   return;
 
1746
}
 
1747
 
 
1748
sub _check_opts {
 
1749
   my ( $self, @long ) = @_;
 
1750
   my $long_last = scalar @long;
 
1751
   while ( @long ) {
 
1752
      foreach my $i ( 0..$#long ) {
 
1753
         my $long = $long[$i];
 
1754
         next unless $long;
 
1755
         my $opt  = $self->{opts}->{$long};
 
1756
         if ( $opt->{got} ) {
 
1757
            if ( exists $self->{disables}->{$long} ) {
 
1758
               my @disable_opts = @{$self->{disables}->{$long}};
 
1759
               map { $self->{opts}->{$_}->{value} = undef; } @disable_opts;
 
1760
               MKDEBUG && _d('Unset options', @disable_opts,
 
1761
                  'because', $long,'disables them');
 
1762
            }
 
1763
 
 
1764
            if ( exists $self->{allowed_groups}->{$long} ) {
 
1765
 
 
1766
               my @restricted_groups = grep {
 
1767
                  !exists $self->{allowed_groups}->{$long}->{$_}
 
1768
               } keys %{$self->{groups}};
 
1769
 
 
1770
               my @restricted_opts;
 
1771
               foreach my $restricted_group ( @restricted_groups ) {
 
1772
                  RESTRICTED_OPT:
 
1773
                  foreach my $restricted_opt (
 
1774
                     keys %{$self->{groups}->{$restricted_group}} )
 
1775
                  {
 
1776
                     next RESTRICTED_OPT if $restricted_opt eq $long;
 
1777
                     push @restricted_opts, $restricted_opt
 
1778
                        if $self->{opts}->{$restricted_opt}->{got};
 
1779
                  }
 
1780
               }
 
1781
 
 
1782
               if ( @restricted_opts ) {
 
1783
                  my $err;
 
1784
                  if ( @restricted_opts == 1 ) {
 
1785
                     $err = "--$restricted_opts[0]";
 
1786
                  }
 
1787
                  else {
 
1788
                     $err = join(', ',
 
1789
                               map { "--$self->{opts}->{$_}->{long}" }
 
1790
                               grep { $_ } 
 
1791
                               @restricted_opts[0..scalar(@restricted_opts) - 2]
 
1792
                            )
 
1793
                          . ' or --'.$self->{opts}->{$restricted_opts[-1]}->{long};
 
1794
                  }
 
1795
                  $self->save_error("--$long is not allowed with $err");
 
1796
               }
 
1797
            }
 
1798
 
 
1799
         }
 
1800
         elsif ( $opt->{is_required} ) { 
 
1801
            $self->save_error("Required option --$long must be specified");
 
1802
         }
 
1803
 
 
1804
         $self->_validate_type($opt);
 
1805
         if ( $opt->{parsed} ) {
 
1806
            delete $long[$i];
 
1807
         }
 
1808
         else {
 
1809
            MKDEBUG && _d('Temporarily failed to parse', $long);
 
1810
         }
 
1811
      }
 
1812
 
 
1813
      die "Failed to parse options, possibly due to circular dependencies"
 
1814
         if @long == $long_last;
 
1815
      $long_last = @long;
 
1816
   }
 
1817
 
 
1818
   return;
 
1819
}
 
1820
 
 
1821
sub _validate_type {
 
1822
   my ( $self, $opt ) = @_;
 
1823
   return unless $opt;
 
1824
 
 
1825
   if ( !$opt->{type} ) {
 
1826
      $opt->{parsed} = 1;
 
1827
      return;
 
1828
   }
 
1829
 
 
1830
   my $val = $opt->{value};
 
1831
 
 
1832
   if ( $val && $opt->{type} eq 'm' ) {  # type time
 
1833
      MKDEBUG && _d('Parsing option', $opt->{long}, 'as a time value');
 
1834
      my ( $prefix, $num, $suffix ) = $val =~ m/([+-]?)(\d+)([a-z])?$/;
 
1835
      if ( !$suffix ) {
 
1836
         my ( $s ) = $opt->{desc} =~ m/\(suffix (.)\)/;
 
1837
         $suffix = $s || 's';
 
1838
         MKDEBUG && _d('No suffix given; using', $suffix, 'for',
 
1839
            $opt->{long}, '(value:', $val, ')');
 
1840
      }
 
1841
      if ( $suffix =~ m/[smhd]/ ) {
 
1842
         $val = $suffix eq 's' ? $num            # Seconds
 
1843
              : $suffix eq 'm' ? $num * 60       # Minutes
 
1844
              : $suffix eq 'h' ? $num * 3600     # Hours
 
1845
              :                  $num * 86400;   # Days
 
1846
         $opt->{value} = ($prefix || '') . $val;
 
1847
         MKDEBUG && _d('Setting option', $opt->{long}, 'to', $val);
 
1848
      }
 
1849
      else {
 
1850
         $self->save_error("Invalid time suffix for --$opt->{long}");
 
1851
      }
 
1852
   }
 
1853
   elsif ( $val && $opt->{type} eq 'd' ) {  # type DSN
 
1854
      MKDEBUG && _d('Parsing option', $opt->{long}, 'as a DSN');
 
1855
      my $prev = {};
 
1856
      my $from_key = $self->{defaults_to}->{ $opt->{long} };
 
1857
      if ( $from_key ) {
 
1858
         MKDEBUG && _d($opt->{long}, 'DSN copies from', $from_key, 'DSN');
 
1859
         if ( $self->{opts}->{$from_key}->{parsed} ) {
 
1860
            $prev = $self->{opts}->{$from_key}->{value};
 
1861
         }
 
1862
         else {
 
1863
            MKDEBUG && _d('Cannot parse', $opt->{long}, 'until',
 
1864
               $from_key, 'parsed');
 
1865
            return;
 
1866
         }
 
1867
      }
 
1868
      my $defaults = $self->{DSNParser}->parse_options($self);
 
1869
      $opt->{value} = $self->{DSNParser}->parse($val, $prev, $defaults);
 
1870
   }
 
1871
   elsif ( $val && $opt->{type} eq 'z' ) {  # type size
 
1872
      MKDEBUG && _d('Parsing option', $opt->{long}, 'as a size value');
 
1873
      $self->_parse_size($opt, $val);
 
1874
   }
 
1875
   elsif ( $opt->{type} eq 'H' || (defined $val && $opt->{type} eq 'h') ) {
 
1876
      $opt->{value} = { map { $_ => 1 } split(/(?<!\\),\s*/, ($val || '')) };
 
1877
   }
 
1878
   elsif ( $opt->{type} eq 'A' || (defined $val && $opt->{type} eq 'a') ) {
 
1879
      $opt->{value} = [ split(/(?<!\\),\s*/, ($val || '')) ];
 
1880
   }
 
1881
   else {
 
1882
      MKDEBUG && _d('Nothing to validate for option',
 
1883
         $opt->{long}, 'type', $opt->{type}, 'value', $val);
 
1884
   }
 
1885
 
 
1886
   $opt->{parsed} = 1;
 
1887
   return;
 
1888
}
 
1889
 
 
1890
sub get {
 
1891
   my ( $self, $opt ) = @_;
 
1892
   my $long = (length $opt == 1 ? $self->{short_opts}->{$opt} : $opt);
 
1893
   die "Option $opt does not exist"
 
1894
      unless $long && exists $self->{opts}->{$long};
 
1895
   return $self->{opts}->{$long}->{value};
 
1896
}
 
1897
 
 
1898
sub got {
 
1899
   my ( $self, $opt ) = @_;
 
1900
   my $long = (length $opt == 1 ? $self->{short_opts}->{$opt} : $opt);
 
1901
   die "Option $opt does not exist"
 
1902
      unless $long && exists $self->{opts}->{$long};
 
1903
   return $self->{opts}->{$long}->{got};
 
1904
}
 
1905
 
 
1906
sub has {
 
1907
   my ( $self, $opt ) = @_;
 
1908
   my $long = (length $opt == 1 ? $self->{short_opts}->{$opt} : $opt);
 
1909
   return defined $long ? exists $self->{opts}->{$long} : 0;
 
1910
}
 
1911
 
 
1912
sub set {
 
1913
   my ( $self, $opt, $val ) = @_;
 
1914
   my $long = (length $opt == 1 ? $self->{short_opts}->{$opt} : $opt);
 
1915
   die "Option $opt does not exist"
 
1916
      unless $long && exists $self->{opts}->{$long};
 
1917
   $self->{opts}->{$long}->{value} = $val;
 
1918
   return;
 
1919
}
 
1920
 
 
1921
sub save_error {
 
1922
   my ( $self, $error ) = @_;
 
1923
   push @{$self->{errors}}, $error;
 
1924
   return;
 
1925
}
 
1926
 
 
1927
sub errors {
 
1928
   my ( $self ) = @_;
 
1929
   return $self->{errors};
 
1930
}
 
1931
 
 
1932
sub usage {
 
1933
   my ( $self ) = @_;
 
1934
   warn "No usage string is set" unless $self->{usage}; # XXX
 
1935
   return "Usage: " . ($self->{usage} || '') . "\n";
 
1936
}
 
1937
 
 
1938
sub descr {
 
1939
   my ( $self ) = @_;
 
1940
   warn "No description string is set" unless $self->{description}; # XXX
 
1941
   my $descr  = ($self->{description} || $self->{program_name} || '')
 
1942
              . "  For more details, please use the --help option, "
 
1943
              . "or try 'perldoc $PROGRAM_NAME' "
 
1944
              . "for complete documentation.";
 
1945
   $descr = join("\n", $descr =~ m/(.{0,80})(?:\s+|$)/g)
 
1946
      unless $ENV{DONT_BREAK_LINES};
 
1947
   $descr =~ s/ +$//mg;
 
1948
   return $descr;
 
1949
}
 
1950
 
 
1951
sub usage_or_errors {
 
1952
   my ( $self, $file, $return ) = @_;
 
1953
   $file ||= $self->{file} || __FILE__;
 
1954
 
 
1955
   if ( !$self->{description} || !$self->{usage} ) {
 
1956
      MKDEBUG && _d("Getting description and usage from SYNOPSIS in", $file);
 
1957
      my %synop = $self->_parse_synopsis($file);
 
1958
      $self->{description} ||= $synop{description};
 
1959
      $self->{usage}       ||= $synop{usage};
 
1960
      MKDEBUG && _d("Description:", $self->{description},
 
1961
         "\nUsage:", $self->{usage});
 
1962
   }
 
1963
 
 
1964
   if ( $self->{opts}->{help}->{got} ) {
 
1965
      print $self->print_usage() or die "Cannot print usage: $OS_ERROR";
 
1966
      exit 0 unless $return;
 
1967
   }
 
1968
   elsif ( scalar @{$self->{errors}} ) {
 
1969
      print $self->print_errors() or die "Cannot print errors: $OS_ERROR";
 
1970
      exit 0 unless $return;
 
1971
   }
 
1972
 
 
1973
   return;
 
1974
}
 
1975
 
 
1976
sub print_errors {
 
1977
   my ( $self ) = @_;
 
1978
   my $usage = $self->usage() . "\n";
 
1979
   if ( (my @errors = @{$self->{errors}}) ) {
 
1980
      $usage .= join("\n  * ", 'Errors in command-line arguments:', @errors)
 
1981
              . "\n";
 
1982
   }
 
1983
   return $usage . "\n" . $self->descr();
 
1984
}
 
1985
 
 
1986
sub print_usage {
 
1987
   my ( $self ) = @_;
 
1988
   die "Run get_opts() before print_usage()" unless $self->{got_opts};
 
1989
   my @opts = values %{$self->{opts}};
 
1990
 
 
1991
   my $maxl = max(
 
1992
      map {
 
1993
         length($_->{long})               # option long name
 
1994
         + ($_->{is_negatable} ? 4 : 0)   # "[no]" if opt is negatable
 
1995
         + ($_->{type} ? 2 : 0)           # "=x" where x is the opt type
 
1996
      }
 
1997
      @opts);
 
1998
 
 
1999
   my $maxs = max(0,
 
2000
      map {
 
2001
         length($_)
 
2002
         + ($self->{opts}->{$_}->{is_negatable} ? 4 : 0)
 
2003
         + ($self->{opts}->{$_}->{type} ? 2 : 0)
 
2004
      }
 
2005
      values %{$self->{short_opts}});
 
2006
 
 
2007
   my $lcol = max($maxl, ($maxs + 3));
 
2008
   my $rcol = 80 - $lcol - 6;
 
2009
   my $rpad = ' ' x ( 80 - $rcol );
 
2010
 
 
2011
   $maxs = max($lcol - 3, $maxs);
 
2012
 
 
2013
   my $usage = $self->descr() . "\n" . $self->usage();
 
2014
 
 
2015
   my @groups = reverse sort grep { $_ ne 'default'; } keys %{$self->{groups}};
 
2016
   push @groups, 'default';
 
2017
 
 
2018
   foreach my $group ( reverse @groups ) {
 
2019
      $usage .= "\n".($group eq 'default' ? 'Options' : $group).":\n\n";
 
2020
      foreach my $opt (
 
2021
         sort { $a->{long} cmp $b->{long} }
 
2022
         grep { $_->{group} eq $group }
 
2023
         @opts )
 
2024
      {
 
2025
         my $long  = $opt->{is_negatable} ? "[no]$opt->{long}" : $opt->{long};
 
2026
         my $short = $opt->{short};
 
2027
         my $desc  = $opt->{desc};
 
2028
 
 
2029
         $long .= $opt->{type} ? "=$opt->{type}" : "";
 
2030
 
 
2031
         if ( $opt->{type} && $opt->{type} eq 'm' ) {
 
2032
            my ($s) = $desc =~ m/\(suffix (.)\)/;
 
2033
            $s    ||= 's';
 
2034
            $desc =~ s/\s+\(suffix .\)//;
 
2035
            $desc .= ".  Optional suffix s=seconds, m=minutes, h=hours, "
 
2036
                   . "d=days; if no suffix, $s is used.";
 
2037
         }
 
2038
         $desc = join("\n$rpad", grep { $_ } $desc =~ m/(.{0,$rcol})(?:\s+|$)/g);
 
2039
         $desc =~ s/ +$//mg;
 
2040
         if ( $short ) {
 
2041
            $usage .= sprintf("  --%-${maxs}s -%s  %s\n", $long, $short, $desc);
 
2042
         }
 
2043
         else {
 
2044
            $usage .= sprintf("  --%-${lcol}s  %s\n", $long, $desc);
 
2045
         }
 
2046
      }
 
2047
   }
 
2048
 
 
2049
   $usage .= "\nOption types: s=string, i=integer, f=float, h/H/a/A=comma-separated list, d=DSN, z=size, m=time\n";
 
2050
 
 
2051
   if ( (my @rules = @{$self->{rules}}) ) {
 
2052
      $usage .= "\nRules:\n\n";
 
2053
      $usage .= join("\n", map { "  $_" } @rules) . "\n";
 
2054
   }
 
2055
   if ( $self->{DSNParser} ) {
 
2056
      $usage .= "\n" . $self->{DSNParser}->usage();
 
2057
   }
 
2058
   $usage .= "\nOptions and values after processing arguments:\n\n";
 
2059
   foreach my $opt ( sort { $a->{long} cmp $b->{long} } @opts ) {
 
2060
      my $val   = $opt->{value};
 
2061
      my $type  = $opt->{type} || '';
 
2062
      my $bool  = $opt->{spec} =~ m/^[\w-]+(?:\|[\w-])?!?$/;
 
2063
      $val      = $bool              ? ( $val ? 'TRUE' : 'FALSE' )
 
2064
                : !defined $val      ? '(No value)'
 
2065
                : $type eq 'd'       ? $self->{DSNParser}->as_string($val)
 
2066
                : $type =~ m/H|h/    ? join(',', sort keys %$val)
 
2067
                : $type =~ m/A|a/    ? join(',', @$val)
 
2068
                :                    $val;
 
2069
      $usage .= sprintf("  --%-${lcol}s  %s\n", $opt->{long}, $val);
 
2070
   }
 
2071
   return $usage;
 
2072
}
 
2073
 
 
2074
sub prompt_noecho {
 
2075
   shift @_ if ref $_[0] eq __PACKAGE__;
 
2076
   my ( $prompt ) = @_;
 
2077
   local $OUTPUT_AUTOFLUSH = 1;
 
2078
   print $prompt
 
2079
      or die "Cannot print: $OS_ERROR";
 
2080
   my $response;
 
2081
   eval {
 
2082
      require Term::ReadKey;
 
2083
      Term::ReadKey::ReadMode('noecho');
 
2084
      chomp($response = <STDIN>);
 
2085
      Term::ReadKey::ReadMode('normal');
 
2086
      print "\n"
 
2087
         or die "Cannot print: $OS_ERROR";
 
2088
   };
 
2089
   if ( $EVAL_ERROR ) {
 
2090
      die "Cannot read response; is Term::ReadKey installed? $EVAL_ERROR";
 
2091
   }
 
2092
   return $response;
 
2093
}
 
2094
 
 
2095
if ( MKDEBUG ) {
 
2096
   print '# ', $^X, ' ', $], "\n";
 
2097
   my $uname = `uname -a`;
 
2098
   if ( $uname ) {
 
2099
      $uname =~ s/\s+/ /g;
 
2100
      print "# $uname\n";
 
2101
   }
 
2102
   printf("# %s  Ver %s Distrib %s Changeset %s line %d\n",
 
2103
      $PROGRAM_NAME, ($main::VERSION || ''), ($main::DISTRIB || ''),
 
2104
      ($main::SVN_REV || ''), __LINE__);
 
2105
   print('# Arguments: ',
 
2106
      join(' ', map { my $a = "_[$_]_"; $a =~ s/\n/\n# /g; $a; } @ARGV), "\n");
 
2107
}
 
2108
 
 
2109
sub _read_config_file {
 
2110
   my ( $self, $filename ) = @_;
 
2111
   open my $fh, "<", $filename or die "Cannot open $filename: $OS_ERROR\n";
 
2112
   my @args;
 
2113
   my $prefix = '--';
 
2114
   my $parse  = 1;
 
2115
 
 
2116
   LINE:
 
2117
   while ( my $line = <$fh> ) {
 
2118
      chomp $line;
 
2119
      next LINE if $line =~ m/^\s*(?:\#|\;|$)/;
 
2120
      $line =~ s/\s+#.*$//g;
 
2121
      $line =~ s/^\s+|\s+$//g;
 
2122
      if ( $line eq '--' ) {
 
2123
         $prefix = '';
 
2124
         $parse  = 0;
 
2125
         next LINE;
 
2126
      }
 
2127
      if ( $parse
 
2128
         && (my($opt, $arg) = $line =~ m/^\s*([^=\s]+?)(?:\s*=\s*(.*?)\s*)?$/)
 
2129
      ) {
 
2130
         push @args, grep { defined $_ } ("$prefix$opt", $arg);
 
2131
      }
 
2132
      elsif ( $line =~ m/./ ) {
 
2133
         push @args, $line;
 
2134
      }
 
2135
      else {
 
2136
         die "Syntax error in file $filename at line $INPUT_LINE_NUMBER";
 
2137
      }
 
2138
   }
 
2139
   close $fh;
 
2140
   return @args;
 
2141
}
 
2142
 
 
2143
sub read_para_after {
 
2144
   my ( $self, $file, $regex ) = @_;
 
2145
   open my $fh, "<", $file or die "Can't open $file: $OS_ERROR";
 
2146
   local $INPUT_RECORD_SEPARATOR = '';
 
2147
   my $para;
 
2148
   while ( $para = <$fh> ) {
 
2149
      next unless $para =~ m/^=pod$/m;
 
2150
      last;
 
2151
   }
 
2152
   while ( $para = <$fh> ) {
 
2153
      next unless $para =~ m/$regex/;
 
2154
      last;
 
2155
   }
 
2156
   $para = <$fh>;
 
2157
   chomp($para);
 
2158
   close $fh or die "Can't close $file: $OS_ERROR";
 
2159
   return $para;
 
2160
}
 
2161
 
 
2162
sub clone {
 
2163
   my ( $self ) = @_;
 
2164
 
 
2165
   my %clone = map {
 
2166
      my $hashref  = $self->{$_};
 
2167
      my $val_copy = {};
 
2168
      foreach my $key ( keys %$hashref ) {
 
2169
         my $ref = ref $hashref->{$key};
 
2170
         $val_copy->{$key} = !$ref           ? $hashref->{$key}
 
2171
                           : $ref eq 'HASH'  ? { %{$hashref->{$key}} }
 
2172
                           : $ref eq 'ARRAY' ? [ @{$hashref->{$key}} ]
 
2173
                           : $hashref->{$key};
 
2174
      }
 
2175
      $_ => $val_copy;
 
2176
   } qw(opts short_opts defaults);
 
2177
 
 
2178
   foreach my $scalar ( qw(got_opts) ) {
 
2179
      $clone{$scalar} = $self->{$scalar};
 
2180
   }
 
2181
 
 
2182
   return bless \%clone;     
 
2183
}
 
2184
 
 
2185
sub _parse_size {
 
2186
   my ( $self, $opt, $val ) = @_;
 
2187
 
 
2188
   if ( lc($val || '') eq 'null' ) {
 
2189
      MKDEBUG && _d('NULL size for', $opt->{long});
 
2190
      $opt->{value} = 'null';
 
2191
      return;
 
2192
   }
 
2193
 
 
2194
   my %factor_for = (k => 1_024, M => 1_048_576, G => 1_073_741_824);
 
2195
   my ($pre, $num, $factor) = $val =~ m/^([+-])?(\d+)([kMG])?$/;
 
2196
   if ( defined $num ) {
 
2197
      if ( $factor ) {
 
2198
         $num *= $factor_for{$factor};
 
2199
         MKDEBUG && _d('Setting option', $opt->{y},
 
2200
            'to num', $num, '* factor', $factor);
 
2201
      }
 
2202
      $opt->{value} = ($pre || '') . $num;
 
2203
   }
 
2204
   else {
 
2205
      $self->save_error("Invalid size for --$opt->{long}");
 
2206
   }
 
2207
   return;
 
2208
}
 
2209
 
 
2210
sub _parse_attribs {
 
2211
   my ( $self, $option, $attribs ) = @_;
 
2212
   my $types = $self->{types};
 
2213
   return $option
 
2214
      . ($attribs->{'short form'} ? '|' . $attribs->{'short form'}   : '' )
 
2215
      . ($attribs->{'negatable'}  ? '!'                              : '' )
 
2216
      . ($attribs->{'cumulative'} ? '+'                              : '' )
 
2217
      . ($attribs->{'type'}       ? '=' . $types->{$attribs->{type}} : '' );
 
2218
}
 
2219
 
 
2220
sub _parse_synopsis {
 
2221
   my ( $self, $file ) = @_;
 
2222
   $file ||= $self->{file} || __FILE__;
 
2223
   MKDEBUG && _d("Parsing SYNOPSIS in", $file);
 
2224
 
 
2225
   local $INPUT_RECORD_SEPARATOR = '';  # read paragraphs
 
2226
   open my $fh, "<", $file or die "Cannot open $file: $OS_ERROR";
 
2227
   my $para;
 
2228
   1 while defined($para = <$fh>) && $para !~ m/^=head1 SYNOPSIS/;
 
2229
   die "$file does not contain a SYNOPSIS section" unless $para;
 
2230
   my @synop;
 
2231
   for ( 1..2 ) {  # 1 for the usage, 2 for the description
 
2232
      my $para = <$fh>;
 
2233
      push @synop, $para;
 
2234
   }
 
2235
   close $fh;
 
2236
   MKDEBUG && _d("Raw SYNOPSIS text:", @synop);
 
2237
   my ($usage, $desc) = @synop;
 
2238
   die "The SYNOPSIS section in $file is not formatted properly"
 
2239
      unless $usage && $desc;
 
2240
 
 
2241
   $usage =~ s/^\s*Usage:\s+(.+)/$1/;
 
2242
   chomp $usage;
 
2243
 
 
2244
   $desc =~ s/\n/ /g;
 
2245
   $desc =~ s/\s{2,}/ /g;
 
2246
   $desc =~ s/\. ([A-Z][a-z])/.  $1/g;
 
2247
   $desc =~ s/\s+$//;
 
2248
 
 
2249
   return (
 
2250
      description => $desc,
 
2251
      usage       => $usage,
 
2252
   );
 
2253
};
 
2254
 
 
2255
sub _d {
 
2256
   my ($package, undef, $line) = caller 0;
 
2257
   @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
 
2258
        map { defined $_ ? $_ : 'undef' }
 
2259
        @_;
 
2260
   print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
 
2261
}
 
2262
 
 
2263
1;
 
2264
 
 
2265
# ###########################################################################
 
2266
# End OptionParser package
 
2267
# ###########################################################################
 
2268
 
 
2269
# ###########################################################################
 
2270
# KeySize package 7096
 
2271
# This package is a copy without comments from the original.  The original
 
2272
# with comments and its test file can be found in the SVN repository at,
 
2273
#   trunk/common/KeySize.pm
 
2274
#   trunk/common/t/KeySize.t
 
2275
# See http://code.google.com/p/maatkit/wiki/Developers for more information.
 
2276
# ###########################################################################
 
2277
package KeySize;
 
2278
 
 
2279
use strict;
 
2280
use warnings FATAL => 'all';
 
2281
use English qw(-no_match_vars);
 
2282
 
 
2283
use constant MKDEBUG => $ENV{MKDEBUG} || 0;
 
2284
 
 
2285
sub new {
 
2286
   my ( $class, %args ) = @_;
 
2287
   my $self = { %args };
 
2288
   return bless $self, $class;
 
2289
}
 
2290
 
 
2291
sub get_key_size {
 
2292
   my ( $self, %args ) = @_;
 
2293
   foreach my $arg ( qw(name cols tbl_name tbl_struct dbh) ) {
 
2294
      die "I need a $arg argument" unless $args{$arg};
 
2295
   }
 
2296
   my $name = $args{name};
 
2297
   my @cols = @{$args{cols}};
 
2298
   my $dbh  = $args{dbh};
 
2299
 
 
2300
   $self->{explain} = '';
 
2301
   $self->{query}   = '';
 
2302
   $self->{error}   = '';
 
2303
 
 
2304
   if ( @cols == 0 ) {
 
2305
      $self->{error} = "No columns for key $name";
 
2306
      return;
 
2307
   }
 
2308
 
 
2309
   my $key_exists = $self->_key_exists(%args);
 
2310
   MKDEBUG && _d('Key', $name, 'exists in', $args{tbl_name}, ':',
 
2311
      $key_exists ? 'yes': 'no');
 
2312
 
 
2313
   my $sql = 'EXPLAIN SELECT ' . join(', ', @cols)
 
2314
           . ' FROM ' . $args{tbl_name}
 
2315
           . ($key_exists ? " FORCE INDEX (`$name`)" : '')
 
2316
           . ' WHERE ';
 
2317
   my @where_cols;
 
2318
   foreach my $col ( @cols ) {
 
2319
      push @where_cols, "$col=1";
 
2320
   }
 
2321
   if ( scalar @cols == 1 ) {
 
2322
      push @where_cols, "$cols[0]<>1";
 
2323
   }
 
2324
   $sql .= join(' OR ', @where_cols);
 
2325
   $self->{query} = $sql;
 
2326
   MKDEBUG && _d('sql:', $sql);
 
2327
 
 
2328
   my $explain;
 
2329
   my $sth = $dbh->prepare($sql);
 
2330
   eval { $sth->execute(); };
 
2331
   if ( $EVAL_ERROR ) {
 
2332
      chomp $EVAL_ERROR;
 
2333
      $self->{error} = "Cannot get size of $name key: $EVAL_ERROR";
 
2334
      return;
 
2335
   }
 
2336
   $explain = $sth->fetchrow_hashref();
 
2337
 
 
2338
   $self->{explain} = $explain;
 
2339
   my $key_len      = $explain->{key_len};
 
2340
   my $rows         = $explain->{rows};
 
2341
   my $chosen_key   = $explain->{key};  # May differ from $name
 
2342
   MKDEBUG && _d('MySQL chose key:', $chosen_key, 'len:', $key_len,
 
2343
      'rows:', $rows);
 
2344
 
 
2345
   my $key_size = 0;
 
2346
   if ( $key_len && $rows ) {
 
2347
      if ( $chosen_key =~ m/,/ && $key_len =~ m/,/ ) {
 
2348
         $self->{error} = "MySQL chose multiple keys: $chosen_key";
 
2349
         return;
 
2350
      }
 
2351
      $key_size = $key_len * $rows;
 
2352
   }
 
2353
   else {
 
2354
      $self->{error} = "key_len or rows NULL in EXPLAIN:\n"
 
2355
                     . _explain_to_text($explain);
 
2356
      return;
 
2357
   }
 
2358
 
 
2359
   return $key_size, $chosen_key;
 
2360
}
 
2361
 
 
2362
sub query {
 
2363
   my ( $self ) = @_;
 
2364
   return $self->{query};
 
2365
}
 
2366
 
 
2367
sub explain {
 
2368
   my ( $self ) = @_;
 
2369
   return _explain_to_text($self->{explain});
 
2370
}
 
2371
 
 
2372
sub error {
 
2373
   my ( $self ) = @_;
 
2374
   return $self->{error};
 
2375
}
 
2376
 
 
2377
sub _key_exists {
 
2378
   my ( $self, %args ) = @_;
 
2379
   return exists $args{tbl_struct}->{keys}->{ lc $args{name} } ? 1 : 0;
 
2380
}
 
2381
 
 
2382
sub _explain_to_text {
 
2383
   my ( $explain ) = @_;
 
2384
   return join("\n",
 
2385
      map { "$_: ".($explain->{$_} ? $explain->{$_} : 'NULL') }
 
2386
      sort keys %$explain
 
2387
   );
 
2388
}
 
2389
 
 
2390
sub _d {
 
2391
   my ($package, undef, $line) = caller 0;
 
2392
   @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
 
2393
        map { defined $_ ? $_ : 'undef' }
 
2394
        @_;
 
2395
   print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
 
2396
}
 
2397
 
 
2398
1;
 
2399
 
 
2400
# ###########################################################################
 
2401
# End KeySize package
 
2402
# ###########################################################################
 
2403
 
 
2404
# ###########################################################################
 
2405
# DuplicateKeyFinder package 7147
 
2406
# This package is a copy without comments from the original.  The original
 
2407
# with comments and its test file can be found in the SVN repository at,
 
2408
#   trunk/common/DuplicateKeyFinder.pm
 
2409
#   trunk/common/t/DuplicateKeyFinder.t
 
2410
# See http://code.google.com/p/maatkit/wiki/Developers for more information.
 
2411
# ###########################################################################
 
2412
package DuplicateKeyFinder;
 
2413
 
 
2414
use strict;
 
2415
use warnings FATAL => 'all';
 
2416
use English qw(-no_match_vars);
 
2417
 
 
2418
use List::Util qw(min);
 
2419
 
 
2420
use constant MKDEBUG => $ENV{MKDEBUG} || 0;
 
2421
 
 
2422
sub new {
 
2423
   my ( $class, %args ) = @_;
 
2424
   my $self = {};
 
2425
   return bless $self, $class;
 
2426
}
 
2427
 
 
2428
sub get_duplicate_keys {
 
2429
   my ( $self, $keys,  %args ) = @_;
 
2430
   die "I need a keys argument" unless $keys;
 
2431
   my %keys = %$keys;  # Copy keys because we remove non-duplicates.
 
2432
   my $primary_key;
 
2433
   my @unique_keys;
 
2434
   my @normal_keys;
 
2435
   my @fulltext_keys;
 
2436
   my @dupes;
 
2437
 
 
2438
   KEY:
 
2439
   foreach my $key ( values %keys ) {
 
2440
      $key->{real_cols} = [ @{$key->{cols}} ];
 
2441
 
 
2442
      $key->{len_cols}  = length $key->{colnames};
 
2443
 
 
2444
      if ( $key->{name} eq 'PRIMARY'
 
2445
           || ($args{clustered_key} && $key->{name} eq $args{clustered_key}) ) {
 
2446
         $primary_key = $key;
 
2447
         MKDEBUG && _d('primary key:', $key->{name});
 
2448
         next KEY;
 
2449
      }
 
2450
 
 
2451
      my $is_fulltext = $key->{type} eq 'FULLTEXT' ? 1 : 0;
 
2452
      if ( $args{ignore_order} || $is_fulltext  ) {
 
2453
         my $ordered_cols = join(',', sort(split(/,/, $key->{colnames})));
 
2454
         MKDEBUG && _d('Reordered', $key->{name}, 'cols from',
 
2455
            $key->{colnames}, 'to', $ordered_cols); 
 
2456
         $key->{colnames} = $ordered_cols;
 
2457
      }
 
2458
 
 
2459
      my $push_to = $key->{is_unique} ? \@unique_keys : \@normal_keys;
 
2460
      if ( !$args{ignore_structure} ) {
 
2461
         $push_to = \@fulltext_keys if $is_fulltext;
 
2462
      }
 
2463
      push @$push_to, $key; 
 
2464
   }
 
2465
 
 
2466
   push @normal_keys, $self->unconstrain_keys($primary_key, \@unique_keys);
 
2467
 
 
2468
   if ( $primary_key ) {
 
2469
      MKDEBUG && _d('Comparing PRIMARY KEY to UNIQUE keys');
 
2470
      push @dupes,
 
2471
         $self->remove_prefix_duplicates([$primary_key], \@unique_keys, %args);
 
2472
 
 
2473
      MKDEBUG && _d('Comparing PRIMARY KEY to normal keys');
 
2474
      push @dupes,
 
2475
         $self->remove_prefix_duplicates([$primary_key], \@normal_keys, %args);
 
2476
   }
 
2477
 
 
2478
   MKDEBUG && _d('Comparing UNIQUE keys to normal keys');
 
2479
   push @dupes,
 
2480
      $self->remove_prefix_duplicates(\@unique_keys, \@normal_keys, %args);
 
2481
 
 
2482
   MKDEBUG && _d('Comparing normal keys');
 
2483
   push @dupes,
 
2484
      $self->remove_prefix_duplicates(\@normal_keys, \@normal_keys, %args);
 
2485
 
 
2486
   MKDEBUG && _d('Comparing FULLTEXT keys');
 
2487
   push @dupes,
 
2488
      $self->remove_prefix_duplicates(\@fulltext_keys, \@fulltext_keys, %args, exact_duplicates => 1);
 
2489
 
 
2490
 
 
2491
   my $clustered_key = $args{clustered_key} ? $keys{$args{clustered_key}}
 
2492
                     : undef;
 
2493
   MKDEBUG && _d('clustered key:', $clustered_key->{name},
 
2494
      $clustered_key->{colnames});
 
2495
   if ( $clustered_key
 
2496
        && $args{clustered}
 
2497
        && $args{tbl_info}->{engine}
 
2498
        && $args{tbl_info}->{engine} =~ m/InnoDB/i )
 
2499
   {
 
2500
      MKDEBUG && _d('Removing UNIQUE dupes of clustered key');
 
2501
      push @dupes,
 
2502
         $self->remove_clustered_duplicates($clustered_key, \@unique_keys, %args);
 
2503
 
 
2504
      MKDEBUG && _d('Removing ordinary dupes of clustered key');
 
2505
      push @dupes,
 
2506
         $self->remove_clustered_duplicates($clustered_key, \@normal_keys, %args);
 
2507
   }
 
2508
 
 
2509
   return \@dupes;
 
2510
}
 
2511
 
 
2512
sub get_duplicate_fks {
 
2513
   my ( $self, $fks, %args ) = @_;
 
2514
   die "I need a fks argument" unless $fks;
 
2515
   my @fks = values %$fks;
 
2516
   my @dupes;
 
2517
 
 
2518
   foreach my $i ( 0..$#fks - 1 ) {
 
2519
      next unless $fks[$i];
 
2520
      foreach my $j ( $i+1..$#fks ) {
 
2521
         next unless $fks[$j];
 
2522
 
 
2523
         my $i_cols  = join(',', sort @{$fks[$i]->{cols}} );
 
2524
         my $j_cols  = join(',', sort @{$fks[$j]->{cols}} );
 
2525
         my $i_pcols = join(',', sort @{$fks[$i]->{parent_cols}} );
 
2526
         my $j_pcols = join(',', sort @{$fks[$j]->{parent_cols}} );
 
2527
 
 
2528
         if ( $fks[$i]->{parent_tbl} eq $fks[$j]->{parent_tbl}
 
2529
              && $i_cols  eq $j_cols
 
2530
              && $i_pcols eq $j_pcols ) {
 
2531
            my $dupe = {
 
2532
               key               => $fks[$j]->{name},
 
2533
               cols              => [ @{$fks[$j]->{cols}} ],
 
2534
               ddl               => $fks[$j]->{ddl},
 
2535
               duplicate_of      => $fks[$i]->{name},
 
2536
               duplicate_of_cols => [ @{$fks[$i]->{cols}} ],
 
2537
               duplicate_of_ddl  => $fks[$i]->{ddl},
 
2538
               reason            =>
 
2539
                    "FOREIGN KEY $fks[$j]->{name} ($fks[$j]->{colnames}) "
 
2540
                  . "REFERENCES $fks[$j]->{parent_tbl} "
 
2541
                  . "($fks[$j]->{parent_colnames}) "
 
2542
                  . 'is a duplicate of '
 
2543
                  . "FOREIGN KEY $fks[$i]->{name} ($fks[$i]->{colnames}) "
 
2544
                  . "REFERENCES $fks[$i]->{parent_tbl} "
 
2545
                  ."($fks[$i]->{parent_colnames})",
 
2546
               dupe_type         => 'fk',
 
2547
            };
 
2548
            push @dupes, $dupe;
 
2549
            delete $fks[$j];
 
2550
            $args{callback}->($dupe, %args) if $args{callback};
 
2551
         }
 
2552
      }
 
2553
   }
 
2554
   return \@dupes;
 
2555
}
 
2556
 
 
2557
sub remove_prefix_duplicates {
 
2558
   my ( $self, $left_keys, $right_keys, %args ) = @_;
 
2559
   my @dupes;
 
2560
   my $right_offset;
 
2561
   my $last_left_key;
 
2562
   my $last_right_key = scalar(@$right_keys) - 1;
 
2563
 
 
2564
 
 
2565
   if ( $right_keys != $left_keys ) {
 
2566
 
 
2567
      @$left_keys = sort { lc($a->{colnames}) cmp lc($b->{colnames}) }
 
2568
                    grep { defined $_; }
 
2569
                    @$left_keys;
 
2570
      @$right_keys = sort { lc($a->{colnames}) cmp lc($b->{colnames}) }
 
2571
                     grep { defined $_; }
 
2572
                    @$right_keys;
 
2573
 
 
2574
      $last_left_key = scalar(@$left_keys) - 1;
 
2575
 
 
2576
      $right_offset = 0;
 
2577
   }
 
2578
   else {
 
2579
 
 
2580
      @$left_keys = reverse sort { lc($a->{colnames}) cmp lc($b->{colnames}) }
 
2581
                    grep { defined $_; }
 
2582
                    @$left_keys;
 
2583
      
 
2584
      $last_left_key = scalar(@$left_keys) - 2;
 
2585
 
 
2586
      $right_offset = 1;
 
2587
   }
 
2588
 
 
2589
   LEFT_KEY:
 
2590
   foreach my $left_index ( 0..$last_left_key ) {
 
2591
      next LEFT_KEY unless defined $left_keys->[$left_index];
 
2592
 
 
2593
      RIGHT_KEY:
 
2594
      foreach my $right_index ( $left_index+$right_offset..$last_right_key ) {
 
2595
         next RIGHT_KEY unless defined $right_keys->[$right_index];
 
2596
 
 
2597
         my $left_name      = $left_keys->[$left_index]->{name};
 
2598
         my $left_cols      = $left_keys->[$left_index]->{colnames};
 
2599
         my $left_len_cols  = $left_keys->[$left_index]->{len_cols};
 
2600
         my $right_name     = $right_keys->[$right_index]->{name};
 
2601
         my $right_cols     = $right_keys->[$right_index]->{colnames};
 
2602
         my $right_len_cols = $right_keys->[$right_index]->{len_cols};
 
2603
 
 
2604
         MKDEBUG && _d('Comparing left', $left_name, '(',$left_cols,')',
 
2605
            'to right', $right_name, '(',$right_cols,')');
 
2606
 
 
2607
         if (    substr($left_cols,  0, $right_len_cols)
 
2608
              eq substr($right_cols, 0, $right_len_cols) ) {
 
2609
 
 
2610
            if ( $args{exact_duplicates} && ($right_len_cols<$left_len_cols) ) {
 
2611
               MKDEBUG && _d($right_name, 'not exact duplicate of', $left_name);
 
2612
               next RIGHT_KEY;
 
2613
            }
 
2614
 
 
2615
            if ( exists $right_keys->[$right_index]->{unique_col} ) {
 
2616
               MKDEBUG && _d('Cannot remove', $right_name,
 
2617
                  'because is constrains col',
 
2618
                  $right_keys->[$right_index]->{cols}->[0]);
 
2619
               next RIGHT_KEY;
 
2620
            }
 
2621
 
 
2622
            MKDEBUG && _d('Remove', $right_name);
 
2623
            my $reason;
 
2624
            if ( $right_keys->[$right_index]->{unconstrained} ) {
 
2625
               $reason .= "Uniqueness of $right_name ignored because "
 
2626
                  . $right_keys->[$right_index]->{constraining_key}->{name}
 
2627
                  . " is a stronger constraint\n"; 
 
2628
            }
 
2629
            my $exact_dupe = $right_len_cols < $left_len_cols ? 0 : 1;
 
2630
            $reason .= $right_name
 
2631
                     . ($exact_dupe ? ' is a duplicate of '
 
2632
                                    : ' is a left-prefix of ')
 
2633
                     . $left_name;
 
2634
            my $dupe = {
 
2635
               key               => $right_name,
 
2636
               cols              => $right_keys->[$right_index]->{real_cols},
 
2637
               ddl               => $right_keys->[$right_index]->{ddl},
 
2638
               duplicate_of      => $left_name,
 
2639
               duplicate_of_cols => $left_keys->[$left_index]->{real_cols},
 
2640
               duplicate_of_ddl  => $left_keys->[$left_index]->{ddl},
 
2641
               reason            => $reason,
 
2642
               dupe_type         => $exact_dupe ? 'exact' : 'prefix',
 
2643
            };
 
2644
            push @dupes, $dupe;
 
2645
            delete $right_keys->[$right_index];
 
2646
 
 
2647
            $args{callback}->($dupe, %args) if $args{callback};
 
2648
         }
 
2649
         else {
 
2650
            MKDEBUG && _d($right_name, 'not left-prefix of', $left_name);
 
2651
            next LEFT_KEY;
 
2652
         }
 
2653
      } # RIGHT_KEY
 
2654
   } # LEFT_KEY
 
2655
   MKDEBUG && _d('No more keys');
 
2656
 
 
2657
   @$left_keys  = grep { defined $_; } @$left_keys;
 
2658
   @$right_keys = grep { defined $_; } @$right_keys;
 
2659
 
 
2660
   return @dupes;
 
2661
}
 
2662
 
 
2663
sub remove_clustered_duplicates {
 
2664
   my ( $self, $ck, $keys, %args ) = @_;
 
2665
   die "I need a ck argument"   unless $ck;
 
2666
   die "I need a keys argument" unless $keys;
 
2667
   my $ck_cols = $ck->{colnames};
 
2668
 
 
2669
   my @dupes;
 
2670
   KEY:
 
2671
   for my $i ( 0 .. @$keys - 1 ) {
 
2672
      my $key = $keys->[$i]->{colnames};
 
2673
      if ( $key =~ m/$ck_cols$/ ) {
 
2674
         MKDEBUG && _d("clustered key dupe:", $keys->[$i]->{name},
 
2675
            $keys->[$i]->{colnames});
 
2676
         my $dupe = {
 
2677
            key               => $keys->[$i]->{name},
 
2678
            cols              => $keys->[$i]->{real_cols},
 
2679
            ddl               => $keys->[$i]->{ddl},
 
2680
            duplicate_of      => $ck->{name},
 
2681
            duplicate_of_cols => $ck->{real_cols},
 
2682
            duplicate_of_ddl  => $ck->{ddl},
 
2683
            reason            => "Key $keys->[$i]->{name} ends with a "
 
2684
                               . "prefix of the clustered index",
 
2685
            dupe_type         => 'clustered',
 
2686
            short_key         => $self->shorten_clustered_duplicate(
 
2687
                                    $ck_cols,
 
2688
                                    join(',', map { "`$_`" }
 
2689
                                       @{$keys->[$i]->{real_cols}})
 
2690
                                 ),
 
2691
         };
 
2692
         push @dupes, $dupe;
 
2693
         delete $keys->[$i];
 
2694
         $args{callback}->($dupe, %args) if $args{callback};
 
2695
      }
 
2696
   }
 
2697
   MKDEBUG && _d('No more keys');
 
2698
 
 
2699
   @$keys = grep { defined $_; } @$keys;
 
2700
 
 
2701
   return @dupes;
 
2702
}
 
2703
 
 
2704
sub shorten_clustered_duplicate {
 
2705
   my ( $self, $ck_cols, $dupe_key_cols ) = @_;
 
2706
   return $ck_cols if $ck_cols eq $dupe_key_cols;
 
2707
   $dupe_key_cols =~ s/$ck_cols$//;
 
2708
   $dupe_key_cols =~ s/,+$//;
 
2709
   return $dupe_key_cols;
 
2710
}
 
2711
 
 
2712
sub unconstrain_keys {
 
2713
   my ( $self, $primary_key, $unique_keys ) = @_;
 
2714
   die "I need a unique_keys argument" unless $unique_keys;
 
2715
   my %unique_cols;
 
2716
   my @unique_sets;
 
2717
   my %unconstrain;
 
2718
   my @unconstrained_keys;
 
2719
 
 
2720
   MKDEBUG && _d('Unconstraining redundantly unique keys');
 
2721
 
 
2722
   UNIQUE_KEY:
 
2723
   foreach my $unique_key ( $primary_key, @$unique_keys ) {
 
2724
      next unless $unique_key; # primary key may be undefined
 
2725
      my $cols = $unique_key->{cols};
 
2726
      if ( @$cols == 1 ) {
 
2727
         MKDEBUG && _d($unique_key->{name},'defines unique column:',$cols->[0]);
 
2728
         if ( !exists $unique_cols{$cols->[0]} ) {
 
2729
            $unique_cols{$cols->[0]}  = $unique_key;
 
2730
            $unique_key->{unique_col} = 1;
 
2731
         }
 
2732
      }
 
2733
      else {
 
2734
         local $LIST_SEPARATOR = '-';
 
2735
         MKDEBUG && _d($unique_key->{name}, 'defines unique set:', @$cols);
 
2736
         push @unique_sets, { cols => $cols, key => $unique_key };
 
2737
      }
 
2738
   }
 
2739
 
 
2740
   UNIQUE_SET:
 
2741
   foreach my $unique_set ( @unique_sets ) {
 
2742
      my $n_unique_cols = 0;
 
2743
      COL:
 
2744
      foreach my $col ( @{$unique_set->{cols}} ) {
 
2745
         if ( exists $unique_cols{$col} ) {
 
2746
            MKDEBUG && _d('Unique set', $unique_set->{key}->{name},
 
2747
               'has unique col', $col);
 
2748
            last COL if ++$n_unique_cols > 1;
 
2749
            $unique_set->{constraining_key} = $unique_cols{$col};
 
2750
         }
 
2751
      }
 
2752
      if ( $n_unique_cols && $unique_set->{key}->{name} ne 'PRIMARY' ) {
 
2753
         MKDEBUG && _d('Will unconstrain unique set',
 
2754
            $unique_set->{key}->{name},
 
2755
            'because it is redundantly constrained by key',
 
2756
            $unique_set->{constraining_key}->{name},
 
2757
            '(',$unique_set->{constraining_key}->{colnames},')');
 
2758
         $unconstrain{$unique_set->{key}->{name}}
 
2759
            = $unique_set->{constraining_key};
 
2760
      }
 
2761
   }
 
2762
 
 
2763
   for my $i ( 0..(scalar @$unique_keys-1) ) {
 
2764
      if ( exists $unconstrain{$unique_keys->[$i]->{name}} ) {
 
2765
         MKDEBUG && _d('Unconstraining', $unique_keys->[$i]->{name});
 
2766
         $unique_keys->[$i]->{unconstrained} = 1;
 
2767
         $unique_keys->[$i]->{constraining_key}
 
2768
            = $unconstrain{$unique_keys->[$i]->{name}};
 
2769
         push @unconstrained_keys, $unique_keys->[$i];
 
2770
         delete $unique_keys->[$i];
 
2771
      }
 
2772
   }
 
2773
 
 
2774
   MKDEBUG && _d('No more keys');
 
2775
   return @unconstrained_keys;
 
2776
}
 
2777
 
 
2778
sub _d {
 
2779
   my ($package, undef, $line) = caller 0;
 
2780
   @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
 
2781
        map { defined $_ ? $_ : 'undef' }
 
2782
        @_;
 
2783
   print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
 
2784
}
 
2785
 
 
2786
1;
 
2787
# ###########################################################################
 
2788
# End DuplicateKeyFinder package
 
2789
# ###########################################################################
 
2790
 
 
2791
# ###########################################################################
 
2792
# Transformers package 7226
 
2793
# This package is a copy without comments from the original.  The original
 
2794
# with comments and its test file can be found in the SVN repository at,
 
2795
#   trunk/common/Transformers.pm
 
2796
#   trunk/common/t/Transformers.t
 
2797
# See http://code.google.com/p/maatkit/wiki/Developers for more information.
 
2798
# ###########################################################################
 
2799
 
 
2800
package Transformers;
 
2801
 
 
2802
use strict;
 
2803
use warnings FATAL => 'all';
 
2804
use English qw(-no_match_vars);
 
2805
use Time::Local qw(timegm timelocal);
 
2806
use Digest::MD5 qw(md5_hex);
 
2807
 
 
2808
use constant MKDEBUG => $ENV{MKDEBUG} || 0;
 
2809
 
 
2810
require Exporter;
 
2811
our @ISA         = qw(Exporter);
 
2812
our %EXPORT_TAGS = ();
 
2813
our @EXPORT      = ();
 
2814
our @EXPORT_OK   = qw(
 
2815
   micro_t
 
2816
   percentage_of
 
2817
   secs_to_time
 
2818
   time_to_secs
 
2819
   shorten
 
2820
   ts
 
2821
   parse_timestamp
 
2822
   unix_timestamp
 
2823
   any_unix_timestamp
 
2824
   make_checksum
 
2825
   crc32
 
2826
);
 
2827
 
 
2828
our $mysql_ts  = qr/(\d\d)(\d\d)(\d\d) +(\d+):(\d+):(\d+)(\.\d+)?/;
 
2829
our $proper_ts = qr/(\d\d\d\d)-(\d\d)-(\d\d)[T ](\d\d):(\d\d):(\d\d)(\.\d+)?/;
 
2830
our $n_ts      = qr/(\d{1,5})([shmd]?)/; # Limit \d{1,5} because \d{6} looks
 
2831
 
 
2832
sub micro_t {
 
2833
   my ( $t, %args ) = @_;
 
2834
   my $p_ms = defined $args{p_ms} ? $args{p_ms} : 0;  # precision for ms vals
 
2835
   my $p_s  = defined $args{p_s}  ? $args{p_s}  : 0;  # precision for s vals
 
2836
   my $f;
 
2837
 
 
2838
   $t = 0 if $t < 0;
 
2839
 
 
2840
   $t = sprintf('%.17f', $t) if $t =~ /e/;
 
2841
 
 
2842
   $t =~ s/\.(\d{1,6})\d*/\.$1/;
 
2843
 
 
2844
   if ($t > 0 && $t <= 0.000999) {
 
2845
      $f = ($t * 1000000) . 'us';
 
2846
   }
 
2847
   elsif ($t >= 0.001000 && $t <= 0.999999) {
 
2848
      $f = sprintf("%.${p_ms}f", $t * 1000);
 
2849
      $f = ($f * 1) . 'ms'; # * 1 to remove insignificant zeros
 
2850
   }
 
2851
   elsif ($t >= 1) {
 
2852
      $f = sprintf("%.${p_s}f", $t);
 
2853
      $f = ($f * 1) . 's'; # * 1 to remove insignificant zeros
 
2854
   }
 
2855
   else {
 
2856
      $f = 0;  # $t should = 0 at this point
 
2857
   }
 
2858
 
 
2859
   return $f;
 
2860
}
 
2861
 
 
2862
sub percentage_of {
 
2863
   my ( $is, $of, %args ) = @_;
 
2864
   my $p   = $args{p} || 0; # float precision
 
2865
   my $fmt = $p ? "%.${p}f" : "%d";
 
2866
   return sprintf $fmt, ($is * 100) / ($of ||= 1);
 
2867
}
 
2868
 
 
2869
sub secs_to_time {
 
2870
   my ( $secs, $fmt ) = @_;
 
2871
   $secs ||= 0;
 
2872
   return '00:00' unless $secs;
 
2873
 
 
2874
   $fmt ||= $secs >= 86_400 ? 'd'
 
2875
          : $secs >= 3_600  ? 'h'
 
2876
          :                   'm';
 
2877
 
 
2878
   return
 
2879
      $fmt eq 'd' ? sprintf(
 
2880
         "%d+%02d:%02d:%02d",
 
2881
         int($secs / 86_400),
 
2882
         int(($secs % 86_400) / 3_600),
 
2883
         int(($secs % 3_600) / 60),
 
2884
         $secs % 60)
 
2885
      : $fmt eq 'h' ? sprintf(
 
2886
         "%02d:%02d:%02d",
 
2887
         int(($secs % 86_400) / 3_600),
 
2888
         int(($secs % 3_600) / 60),
 
2889
         $secs % 60)
 
2890
      : sprintf(
 
2891
         "%02d:%02d",
 
2892
         int(($secs % 3_600) / 60),
 
2893
         $secs % 60);
 
2894
}
 
2895
 
 
2896
sub time_to_secs {
 
2897
   my ( $val, $default_suffix ) = @_;
 
2898
   die "I need a val argument" unless defined $val;
 
2899
   my $t = 0;
 
2900
   my ( $prefix, $num, $suffix ) = $val =~ m/([+-]?)(\d+)([a-z])?$/;
 
2901
   $suffix = $suffix || $default_suffix || 's';
 
2902
   if ( $suffix =~ m/[smhd]/ ) {
 
2903
      $t = $suffix eq 's' ? $num * 1        # Seconds
 
2904
         : $suffix eq 'm' ? $num * 60       # Minutes
 
2905
         : $suffix eq 'h' ? $num * 3600     # Hours
 
2906
         :                  $num * 86400;   # Days
 
2907
 
 
2908
      $t *= -1 if $prefix && $prefix eq '-';
 
2909
   }
 
2910
   else {
 
2911
      die "Invalid suffix for $val: $suffix";
 
2912
   }
 
2913
   return $t;
 
2914
}
 
2915
 
 
2916
sub shorten {
 
2917
   my ( $num, %args ) = @_;
 
2918
   my $p = defined $args{p} ? $args{p} : 2;     # float precision
 
2919
   my $d = defined $args{d} ? $args{d} : 1_024; # divisor
 
2920
   my $n = 0;
 
2921
   my @units = ('', qw(k M G T P E Z Y));
 
2922
   while ( $num >= $d && $n < @units - 1 ) {
 
2923
      $num /= $d;
 
2924
      ++$n;
 
2925
   }
 
2926
   return sprintf(
 
2927
      $num =~ m/\./ || $n
 
2928
         ? "%.${p}f%s"
 
2929
         : '%d',
 
2930
      $num, $units[$n]);
 
2931
}
 
2932
 
 
2933
sub ts {
 
2934
   my ( $time, $gmt ) = @_;
 
2935
   my ( $sec, $min, $hour, $mday, $mon, $year )
 
2936
      = $gmt ? gmtime($time) : localtime($time);
 
2937
   $mon  += 1;
 
2938
   $year += 1900;
 
2939
   my $val = sprintf("%d-%02d-%02dT%02d:%02d:%02d",
 
2940
      $year, $mon, $mday, $hour, $min, $sec);
 
2941
   if ( my ($us) = $time =~ m/(\.\d+)$/ ) {
 
2942
      $us = sprintf("%.6f", $us);
 
2943
      $us =~ s/^0\././;
 
2944
      $val .= $us;
 
2945
   }
 
2946
   return $val;
 
2947
}
 
2948
 
 
2949
sub parse_timestamp {
 
2950
   my ( $val ) = @_;
 
2951
   if ( my($y, $m, $d, $h, $i, $s, $f)
 
2952
         = $val =~ m/^$mysql_ts$/ )
 
2953
   {
 
2954
      return sprintf "%d-%02d-%02d %02d:%02d:"
 
2955
                     . (defined $f ? '%09.6f' : '%02d'),
 
2956
                     $y + 2000, $m, $d, $h, $i, (defined $f ? $s + $f : $s);
 
2957
   }
 
2958
   return $val;
 
2959
}
 
2960
 
 
2961
sub unix_timestamp {
 
2962
   my ( $val, $gmt ) = @_;
 
2963
   if ( my($y, $m, $d, $h, $i, $s, $us) = $val =~ m/^$proper_ts$/ ) {
 
2964
      $val = $gmt
 
2965
         ? timegm($s, $i, $h, $d, $m - 1, $y)
 
2966
         : timelocal($s, $i, $h, $d, $m - 1, $y);
 
2967
      if ( defined $us ) {
 
2968
         $us = sprintf('%.6f', $us);
 
2969
         $us =~ s/^0\././;
 
2970
         $val .= $us;
 
2971
      }
 
2972
   }
 
2973
   return $val;
 
2974
}
 
2975
 
 
2976
sub any_unix_timestamp {
 
2977
   my ( $val, $callback ) = @_;
 
2978
 
 
2979
   if ( my ($n, $suffix) = $val =~ m/^$n_ts$/ ) {
 
2980
      $n = $suffix eq 's' ? $n            # Seconds
 
2981
         : $suffix eq 'm' ? $n * 60       # Minutes
 
2982
         : $suffix eq 'h' ? $n * 3600     # Hours
 
2983
         : $suffix eq 'd' ? $n * 86400    # Days
 
2984
         :                  $n;           # default: Seconds
 
2985
      MKDEBUG && _d('ts is now - N[shmd]:', $n);
 
2986
      return time - $n;
 
2987
   }
 
2988
   elsif ( $val =~ m/^\d{9,}/ ) {
 
2989
      MKDEBUG && _d('ts is already a unix timestamp');
 
2990
      return $val;
 
2991
   }
 
2992
   elsif ( my ($ymd, $hms) = $val =~ m/^(\d{6})(?:\s+(\d+:\d+:\d+))?/ ) {
 
2993
      MKDEBUG && _d('ts is MySQL slow log timestamp');
 
2994
      $val .= ' 00:00:00' unless $hms;
 
2995
      return unix_timestamp(parse_timestamp($val));
 
2996
   }
 
2997
   elsif ( ($ymd, $hms) = $val =~ m/^(\d{4}-\d\d-\d\d)(?:[T ](\d+:\d+:\d+))?/) {
 
2998
      MKDEBUG && _d('ts is properly formatted timestamp');
 
2999
      $val .= ' 00:00:00' unless $hms;
 
3000
      return unix_timestamp($val);
 
3001
   }
 
3002
   else {
 
3003
      MKDEBUG && _d('ts is MySQL expression');
 
3004
      return $callback->($val) if $callback && ref $callback eq 'CODE';
 
3005
   }
 
3006
 
 
3007
   MKDEBUG && _d('Unknown ts type:', $val);
 
3008
   return;
 
3009
}
 
3010
 
 
3011
sub make_checksum {
 
3012
   my ( $val ) = @_;
 
3013
   my $checksum = uc substr(md5_hex($val), -16);
 
3014
   MKDEBUG && _d($checksum, 'checksum for', $val);
 
3015
   return $checksum;
 
3016
}
 
3017
 
 
3018
sub crc32 {
 
3019
   my ( $string ) = @_;
 
3020
   return unless $string;
 
3021
   my $poly = 0xEDB88320;
 
3022
   my $crc  = 0xFFFFFFFF;
 
3023
   foreach my $char ( split(//, $string) ) {
 
3024
      my $comp = ($crc ^ ord($char)) & 0xFF;
 
3025
      for ( 1 .. 8 ) {
 
3026
         $comp = $comp & 1 ? $poly ^ ($comp >> 1) : $comp >> 1;
 
3027
      }
 
3028
      $crc = (($crc >> 8) & 0x00FFFFFF) ^ $comp;
 
3029
   }
 
3030
   return $crc ^ 0xFFFFFFFF;
 
3031
}
 
3032
 
 
3033
sub _d {
 
3034
   my ($package, undef, $line) = caller 0;
 
3035
   @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
 
3036
        map { defined $_ ? $_ : 'undef' }
 
3037
        @_;
 
3038
   print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
 
3039
}
 
3040
 
 
3041
1;
 
3042
 
 
3043
# ###########################################################################
 
3044
# End Transformers package
 
3045
# ###########################################################################
 
3046
 
 
3047
# ###########################################################################
 
3048
# Daemon package 6255
 
3049
# This package is a copy without comments from the original.  The original
 
3050
# with comments and its test file can be found in the SVN repository at,
 
3051
#   trunk/common/Daemon.pm
 
3052
#   trunk/common/t/Daemon.t
 
3053
# See http://code.google.com/p/maatkit/wiki/Developers for more information.
 
3054
# ###########################################################################
 
3055
 
 
3056
package Daemon;
 
3057
 
 
3058
use strict;
 
3059
use warnings FATAL => 'all';
 
3060
 
 
3061
use POSIX qw(setsid);
 
3062
use English qw(-no_match_vars);
 
3063
 
 
3064
use constant MKDEBUG => $ENV{MKDEBUG} || 0;
 
3065
 
 
3066
sub new {
 
3067
   my ( $class, %args ) = @_;
 
3068
   foreach my $arg ( qw(o) ) {
 
3069
      die "I need a $arg argument" unless $args{$arg};
 
3070
   }
 
3071
   my $o = $args{o};
 
3072
   my $self = {
 
3073
      o        => $o,
 
3074
      log_file => $o->has('log') ? $o->get('log') : undef,
 
3075
      PID_file => $o->has('pid') ? $o->get('pid') : undef,
 
3076
   };
 
3077
 
 
3078
   check_PID_file(undef, $self->{PID_file});
 
3079
 
 
3080
   MKDEBUG && _d('Daemonized child will log to', $self->{log_file});
 
3081
   return bless $self, $class;
 
3082
}
 
3083
 
 
3084
sub daemonize {
 
3085
   my ( $self ) = @_;
 
3086
 
 
3087
   MKDEBUG && _d('About to fork and daemonize');
 
3088
   defined (my $pid = fork()) or die "Cannot fork: $OS_ERROR";
 
3089
   if ( $pid ) {
 
3090
      MKDEBUG && _d('I am the parent and now I die');
 
3091
      exit;
 
3092
   }
 
3093
 
 
3094
   $self->{PID_owner} = $PID;
 
3095
   $self->{child}     = 1;
 
3096
 
 
3097
   POSIX::setsid() or die "Cannot start a new session: $OS_ERROR";
 
3098
   chdir '/'       or die "Cannot chdir to /: $OS_ERROR";
 
3099
 
 
3100
   $self->_make_PID_file();
 
3101
 
 
3102
   $OUTPUT_AUTOFLUSH = 1;
 
3103
 
 
3104
   if ( -t STDIN ) {
 
3105
      close STDIN;
 
3106
      open  STDIN, '/dev/null'
 
3107
         or die "Cannot reopen STDIN to /dev/null: $OS_ERROR";
 
3108
   }
 
3109
 
 
3110
   if ( $self->{log_file} ) {
 
3111
      close STDOUT;
 
3112
      open  STDOUT, '>>', $self->{log_file}
 
3113
         or die "Cannot open log file $self->{log_file}: $OS_ERROR";
 
3114
 
 
3115
      close STDERR;
 
3116
      open  STDERR, ">&STDOUT"
 
3117
         or die "Cannot dupe STDERR to STDOUT: $OS_ERROR"; 
 
3118
   }
 
3119
   else {
 
3120
      if ( -t STDOUT ) {
 
3121
         close STDOUT;
 
3122
         open  STDOUT, '>', '/dev/null'
 
3123
            or die "Cannot reopen STDOUT to /dev/null: $OS_ERROR";
 
3124
      }
 
3125
      if ( -t STDERR ) {
 
3126
         close STDERR;
 
3127
         open  STDERR, '>', '/dev/null'
 
3128
            or die "Cannot reopen STDERR to /dev/null: $OS_ERROR";
 
3129
      }
 
3130
   }
 
3131
 
 
3132
   MKDEBUG && _d('I am the child and now I live daemonized');
 
3133
   return;
 
3134
}
 
3135
 
 
3136
sub check_PID_file {
 
3137
   my ( $self, $file ) = @_;
 
3138
   my $PID_file = $self ? $self->{PID_file} : $file;
 
3139
   MKDEBUG && _d('Checking PID file', $PID_file);
 
3140
   if ( $PID_file && -f $PID_file ) {
 
3141
      my $pid;
 
3142
      eval { chomp($pid = `cat $PID_file`); };
 
3143
      die "Cannot cat $PID_file: $OS_ERROR" if $EVAL_ERROR;
 
3144
      MKDEBUG && _d('PID file exists; it contains PID', $pid);
 
3145
      if ( $pid ) {
 
3146
         my $pid_is_alive = kill 0, $pid;
 
3147
         if ( $pid_is_alive ) {
 
3148
            die "The PID file $PID_file already exists "
 
3149
               . " and the PID that it contains, $pid, is running";
 
3150
         }
 
3151
         else {
 
3152
            warn "Overwriting PID file $PID_file because the PID that it "
 
3153
               . "contains, $pid, is not running";
 
3154
         }
 
3155
      }
 
3156
      else {
 
3157
         die "The PID file $PID_file already exists but it does not "
 
3158
            . "contain a PID";
 
3159
      }
 
3160
   }
 
3161
   else {
 
3162
      MKDEBUG && _d('No PID file');
 
3163
   }
 
3164
   return;
 
3165
}
 
3166
 
 
3167
sub make_PID_file {
 
3168
   my ( $self ) = @_;
 
3169
   if ( exists $self->{child} ) {
 
3170
      die "Do not call Daemon::make_PID_file() for daemonized scripts";
 
3171
   }
 
3172
   $self->_make_PID_file();
 
3173
   $self->{PID_owner} = $PID;
 
3174
   return;
 
3175
}
 
3176
 
 
3177
sub _make_PID_file {
 
3178
   my ( $self ) = @_;
 
3179
 
 
3180
   my $PID_file = $self->{PID_file};
 
3181
   if ( !$PID_file ) {
 
3182
      MKDEBUG && _d('No PID file to create');
 
3183
      return;
 
3184
   }
 
3185
 
 
3186
   $self->check_PID_file();
 
3187
 
 
3188
   open my $PID_FH, '>', $PID_file
 
3189
      or die "Cannot open PID file $PID_file: $OS_ERROR";
 
3190
   print $PID_FH $PID
 
3191
      or die "Cannot print to PID file $PID_file: $OS_ERROR";
 
3192
   close $PID_FH
 
3193
      or die "Cannot close PID file $PID_file: $OS_ERROR";
 
3194
 
 
3195
   MKDEBUG && _d('Created PID file:', $self->{PID_file});
 
3196
   return;
 
3197
}
 
3198
 
 
3199
sub _remove_PID_file {
 
3200
   my ( $self ) = @_;
 
3201
   if ( $self->{PID_file} && -f $self->{PID_file} ) {
 
3202
      unlink $self->{PID_file}
 
3203
         or warn "Cannot remove PID file $self->{PID_file}: $OS_ERROR";
 
3204
      MKDEBUG && _d('Removed PID file');
 
3205
   }
 
3206
   else {
 
3207
      MKDEBUG && _d('No PID to remove');
 
3208
   }
 
3209
   return;
 
3210
}
 
3211
 
 
3212
sub DESTROY {
 
3213
   my ( $self ) = @_;
 
3214
 
 
3215
   $self->_remove_PID_file() if ($self->{PID_owner} || 0) == $PID;
 
3216
 
 
3217
   return;
 
3218
}
 
3219
 
 
3220
sub _d {
 
3221
   my ($package, undef, $line) = caller 0;
 
3222
   @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
 
3223
        map { defined $_ ? $_ : 'undef' }
 
3224
        @_;
 
3225
   print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
 
3226
}
 
3227
 
 
3228
1;
 
3229
 
 
3230
# ###########################################################################
 
3231
# End Daemon package
 
3232
# ###########################################################################
 
3233
 
 
3234
# ###########################################################################
 
3235
# SchemaIterator package 7141
 
3236
# This package is a copy without comments from the original.  The original
 
3237
# with comments and its test file can be found in the SVN repository at,
 
3238
#   trunk/common/SchemaIterator.pm
 
3239
#   trunk/common/t/SchemaIterator.t
 
3240
# See http://code.google.com/p/maatkit/wiki/Developers for more information.
 
3241
# ###########################################################################
 
3242
package SchemaIterator;
 
3243
 
 
3244
use strict;
 
3245
use warnings FATAL => 'all';
 
3246
 
 
3247
use English qw(-no_match_vars);
 
3248
use Data::Dumper;
 
3249
$Data::Dumper::Indent    = 1;
 
3250
$Data::Dumper::Sortkeys  = 1;
 
3251
$Data::Dumper::Quotekeys = 0;
 
3252
 
 
3253
use constant MKDEBUG => $ENV{MKDEBUG} || 0;
 
3254
 
 
3255
sub new {
 
3256
   my ( $class, %args ) = @_;
 
3257
   foreach my $arg ( qw(Quoter) ) {
 
3258
      die "I need a $arg argument" unless $args{$arg};
 
3259
   }
 
3260
   my $self = {
 
3261
      %args,
 
3262
      filter => undef,
 
3263
      dbs    => [],
 
3264
   };
 
3265
   return bless $self, $class;
 
3266
}
 
3267
 
 
3268
sub make_filter {
 
3269
   my ( $self, $o ) = @_;
 
3270
   my @lines = (
 
3271
      'sub {',
 
3272
      '   my ( $dbh, $db, $tbl ) = @_;',
 
3273
      '   my $engine = undef;',
 
3274
   );
 
3275
 
 
3276
 
 
3277
   my @permit_dbs = _make_filter('unless', '$db', $o->get('databases'))
 
3278
      if $o->has('databases');
 
3279
   my @reject_dbs = _make_filter('if', '$db', $o->get('ignore-databases'))
 
3280
      if $o->has('ignore-databases');
 
3281
   my @dbs_regex;
 
3282
   if ( $o->has('databases-regex') && (my $p = $o->get('databases-regex')) ) {
 
3283
      push @dbs_regex, "      return 0 unless \$db && (\$db =~ m/$p/o);";
 
3284
   }
 
3285
   my @reject_dbs_regex;
 
3286
   if ( $o->has('ignore-databases-regex')
 
3287
        && (my $p = $o->get('ignore-databases-regex')) ) {
 
3288
      push @reject_dbs_regex, "      return 0 if \$db && (\$db =~ m/$p/o);";
 
3289
   }
 
3290
   if ( @permit_dbs || @reject_dbs || @dbs_regex || @reject_dbs_regex ) {
 
3291
      push @lines,
 
3292
         '   if ( $db ) {',
 
3293
            (@permit_dbs        ? @permit_dbs       : ()),
 
3294
            (@reject_dbs        ? @reject_dbs       : ()),
 
3295
            (@dbs_regex         ? @dbs_regex        : ()),
 
3296
            (@reject_dbs_regex  ? @reject_dbs_regex : ()),
 
3297
         '   }';
 
3298
   }
 
3299
 
 
3300
   if ( $o->has('tables') || $o->has('ignore-tables')
 
3301
        || $o->has('ignore-tables-regex') ) {
 
3302
 
 
3303
      my $have_qtbl       = 0;
 
3304
      my $have_only_qtbls = 0;
 
3305
      my %qtbls;
 
3306
 
 
3307
      my @permit_tbls;
 
3308
      my @permit_qtbls;
 
3309
      my %permit_qtbls;
 
3310
      if ( $o->get('tables') ) {
 
3311
         my %tbls;
 
3312
         map {
 
3313
            if ( $_ =~ m/\./ ) {
 
3314
               $permit_qtbls{$_} = 1;
 
3315
            }
 
3316
            else {
 
3317
               $tbls{$_} = 1;
 
3318
            }
 
3319
         } keys %{ $o->get('tables') };
 
3320
         @permit_tbls  = _make_filter('unless', '$tbl', \%tbls);
 
3321
         @permit_qtbls = _make_filter('unless', '$qtbl', \%permit_qtbls);
 
3322
 
 
3323
         if ( @permit_qtbls ) {
 
3324
            push @lines,
 
3325
               '   my $qtbl   = ($db ? "$db." : "") . ($tbl ? $tbl : "");';
 
3326
            $have_qtbl = 1;
 
3327
         }
 
3328
      }
 
3329
 
 
3330
      my @reject_tbls;
 
3331
      my @reject_qtbls;
 
3332
      my %reject_qtbls;
 
3333
      if ( $o->get('ignore-tables') ) {
 
3334
         my %tbls;
 
3335
         map {
 
3336
            if ( $_ =~ m/\./ ) {
 
3337
               $reject_qtbls{$_} = 1;
 
3338
            }
 
3339
            else {
 
3340
               $tbls{$_} = 1;
 
3341
            }
 
3342
         } keys %{ $o->get('ignore-tables') };
 
3343
         @reject_tbls= _make_filter('if', '$tbl', \%tbls);
 
3344
         @reject_qtbls = _make_filter('if', '$qtbl', \%reject_qtbls);
 
3345
 
 
3346
         if ( @reject_qtbls && !$have_qtbl ) {
 
3347
            push @lines,
 
3348
               '   my $qtbl   = ($db ? "$db." : "") . ($tbl ? $tbl : "");';
 
3349
         }
 
3350
      }
 
3351
 
 
3352
      if ( keys %permit_qtbls  && !@permit_dbs ) {
 
3353
         my $dbs = {};
 
3354
         map {
 
3355
            my ($db, undef) = split(/\./, $_);
 
3356
            $dbs->{$db} = 1;
 
3357
         } keys %permit_qtbls;
 
3358
         MKDEBUG && _d('Adding restriction "--databases',
 
3359
               (join(',', keys %$dbs) . '"'));
 
3360
         if ( keys %$dbs ) {
 
3361
            $o->set('databases', $dbs);
 
3362
            return $self->make_filter($o);
 
3363
         }
 
3364
      }
 
3365
 
 
3366
      my @tbls_regex;
 
3367
      if ( $o->has('tables-regex') && (my $p = $o->get('tables-regex')) ) {
 
3368
         push @tbls_regex, "      return 0 unless \$tbl && (\$tbl =~ m/$p/o);";
 
3369
      }
 
3370
      my @reject_tbls_regex;
 
3371
      if ( $o->has('ignore-tables-regex')
 
3372
           && (my $p = $o->get('ignore-tables-regex')) ) {
 
3373
         push @reject_tbls_regex,
 
3374
            "      return 0 if \$tbl && (\$tbl =~ m/$p/o);";
 
3375
      }
 
3376
 
 
3377
      my @get_eng;
 
3378
      my @permit_engs;
 
3379
      my @reject_engs;
 
3380
      if ( ($o->has('engines') && $o->get('engines'))
 
3381
           || ($o->has('ignore-engines') && $o->get('ignore-engines')) ) {
 
3382
         push @get_eng,
 
3383
            '      my $sql = "SHOW TABLE STATUS "',
 
3384
            '              . ($db ? "FROM `$db`" : "")',
 
3385
            '              . " LIKE \'$tbl\'";',
 
3386
            '      MKDEBUG && _d($sql);',
 
3387
            '      eval {',
 
3388
            '         $engine = $dbh->selectrow_hashref($sql)->{engine};',
 
3389
            '      };',
 
3390
            '      MKDEBUG && $EVAL_ERROR && _d($EVAL_ERROR);',
 
3391
            '      MKDEBUG && _d($tbl, "uses engine", $engine);',
 
3392
            '      $engine = lc $engine if $engine;',
 
3393
         @permit_engs
 
3394
            = _make_filter('unless', '$engine', $o->get('engines'), 1);
 
3395
         @reject_engs
 
3396
            = _make_filter('if', '$engine', $o->get('ignore-engines'), 1)
 
3397
      }
 
3398
 
 
3399
      if ( @permit_tbls || @permit_qtbls || @reject_tbls || @tbls_regex
 
3400
           || @reject_tbls_regex || @permit_engs || @reject_engs ) {
 
3401
         push @lines,
 
3402
            '   if ( $tbl ) {',
 
3403
               (@permit_tbls       ? @permit_tbls        : ()),
 
3404
               (@reject_tbls       ? @reject_tbls        : ()),
 
3405
               (@tbls_regex        ? @tbls_regex         : ()),
 
3406
               (@reject_tbls_regex ? @reject_tbls_regex  : ()),
 
3407
               (@permit_qtbls      ? @permit_qtbls       : ()),
 
3408
               (@reject_qtbls      ? @reject_qtbls       : ()),
 
3409
               (@get_eng           ? @get_eng            : ()),
 
3410
               (@permit_engs       ? @permit_engs        : ()),
 
3411
               (@reject_engs       ? @reject_engs        : ()),
 
3412
            '   }';
 
3413
      }
 
3414
   }
 
3415
 
 
3416
   push @lines,
 
3417
      '   MKDEBUG && _d(\'Passes filters:\', $db, $tbl, $engine, $dbh);',
 
3418
      '   return 1;',  '}';
 
3419
 
 
3420
   my $code = join("\n", @lines);
 
3421
   MKDEBUG && _d('filter sub:', $code);
 
3422
   my $filter_sub= eval $code
 
3423
      or die "Error compiling subroutine code:\n$code\n$EVAL_ERROR";
 
3424
 
 
3425
   return $filter_sub;
 
3426
}
 
3427
 
 
3428
sub set_filter {
 
3429
   my ( $self, $filter_sub ) = @_;
 
3430
   $self->{filter} = $filter_sub;
 
3431
   MKDEBUG && _d('Set filter sub');
 
3432
   return;
 
3433
}
 
3434
 
 
3435
sub get_db_itr {
 
3436
   my ( $self, %args ) = @_;
 
3437
   my @required_args = qw(dbh);
 
3438
   foreach my $arg ( @required_args ) {
 
3439
      die "I need a $arg argument" unless $args{$arg};
 
3440
   }
 
3441
   my ($dbh) = @args{@required_args};
 
3442
 
 
3443
   my $filter = $self->{filter};
 
3444
   my @dbs;
 
3445
   eval {
 
3446
      my $sql = 'SHOW DATABASES';
 
3447
      MKDEBUG && _d($sql);
 
3448
      @dbs =  grep {
 
3449
         my $ok = $filter ? $filter->($dbh, $_, undef) : 1;
 
3450
         $ok = 0 if $_ =~ m/information_schema|performance_schema|lost\+found/;
 
3451
         $ok;
 
3452
      } @{ $dbh->selectcol_arrayref($sql) };
 
3453
      MKDEBUG && _d('Found', scalar @dbs, 'databases');
 
3454
   };
 
3455
 
 
3456
   MKDEBUG && $EVAL_ERROR && _d($EVAL_ERROR);
 
3457
   my $iterator = sub {
 
3458
      return shift @dbs;
 
3459
   };
 
3460
 
 
3461
   if (wantarray) {
 
3462
      return ($iterator, scalar @dbs);
 
3463
   }
 
3464
   else {
 
3465
      return $iterator;
 
3466
   }
 
3467
}
 
3468
 
 
3469
sub get_tbl_itr {
 
3470
   my ( $self, %args ) = @_;
 
3471
   my @required_args = qw(dbh db);
 
3472
   foreach my $arg ( @required_args ) {
 
3473
      die "I need a $arg argument" unless $args{$arg};
 
3474
   }
 
3475
   my ($dbh, $db, $views) = @args{@required_args, 'views'};
 
3476
 
 
3477
   my $filter = $self->{filter};
 
3478
   my @tbls;
 
3479
   if ( $db ) {
 
3480
      eval {
 
3481
         my $sql = 'SHOW /*!50002 FULL*/ TABLES FROM '
 
3482
                 . $self->{Quoter}->quote($db);
 
3483
         MKDEBUG && _d($sql);
 
3484
         @tbls = map {
 
3485
            $_->[0]
 
3486
         }
 
3487
         grep {
 
3488
            my ($tbl, $type) = @$_;
 
3489
            my $ok = $filter ? $filter->($dbh, $db, $tbl) : 1;
 
3490
            if ( !$views ) {
 
3491
               $ok = 0 if ($type || '') eq 'VIEW';
 
3492
            }
 
3493
            $ok;
 
3494
         }
 
3495
         @{ $dbh->selectall_arrayref($sql) };
 
3496
         MKDEBUG && _d('Found', scalar @tbls, 'tables in', $db);
 
3497
      };
 
3498
      MKDEBUG && $EVAL_ERROR && _d($EVAL_ERROR);
 
3499
   }
 
3500
   else {
 
3501
      MKDEBUG && _d('No db given so no tables');
 
3502
   }
 
3503
 
 
3504
   my $iterator = sub {
 
3505
      return shift @tbls;
 
3506
   };
 
3507
 
 
3508
   if ( wantarray ) {
 
3509
      return ($iterator, scalar @tbls);
 
3510
   }
 
3511
   else {
 
3512
      return $iterator;
 
3513
   }
 
3514
}
 
3515
 
 
3516
sub _make_filter {
 
3517
   my ( $cond, $var_name, $objs, $lc ) = @_;
 
3518
   my @lines;
 
3519
   if ( scalar keys %$objs ) {
 
3520
      my $test = join(' || ',
 
3521
         map { "$var_name eq '" . ($lc ? lc $_ : $_) ."'" } keys %$objs);
 
3522
      push @lines, "      return 0 $cond $var_name && ($test);",
 
3523
   }
 
3524
   return @lines;
 
3525
}
 
3526
 
 
3527
sub _d {
 
3528
   my ($package, undef, $line) = caller 0;
 
3529
   @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
 
3530
        map { defined $_ ? $_ : 'undef' }
 
3531
        @_;
 
3532
   print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
 
3533
}
 
3534
 
 
3535
1;
 
3536
 
 
3537
# ###########################################################################
 
3538
# End SchemaIterator package
 
3539
# ###########################################################################
 
3540
 
 
3541
# #############################################################################
 
3542
# This is a combination of modules and programs in one -- a runnable module.
 
3543
# http://www.perl.com/pub/a/2006/07/13/lightning-articles.html?page=last
 
3544
# Or, look it up in the Camel book on pages 642 and 643 in the 3rd edition.
 
3545
#
 
3546
# Check at the end of this package for the call to main() which actually runs
 
3547
# the program.
 
3548
# #############################################################################
 
3549
package mk_duplicate_key_checker;
 
3550
 
 
3551
use English qw(-no_match_vars);
 
3552
use Getopt::Long;
 
3553
use List::Util qw(max);
 
3554
 
 
3555
Transformers->import(qw(shorten));
 
3556
 
 
3557
use constant MKDEBUG => $ENV{MKDEBUG} || 0;
 
3558
 
 
3559
$OUTPUT_AUTOFLUSH = 1;
 
3560
 
 
3561
my $max_width = 74;
 
3562
my $hdr_width = $max_width - 2;  # for '# '
 
3563
my $hdr_fmt   = "# %-${hdr_width}s\n";
 
3564
 
 
3565
sub main {
 
3566
   @ARGV = @_;  # set global ARGV for this package
 
3567
 
 
3568
   my %summary = ( 'Total Indexes' => 0 );
 
3569
   my %seen_tbl;
 
3570
 
 
3571
   my $q  = new Quoter();
 
3572
   my $tp = new TableParser(Quoter => $q);
 
3573
 
 
3574
   # #######################################################################
 
3575
   # Get configuration information and parse command line options.
 
3576
   # #######################################################################
 
3577
   my $o = new OptionParser();
 
3578
   $o->get_specs();
 
3579
   $o->get_opts();
 
3580
 
 
3581
   my $dp = $o->DSNParser();
 
3582
   $dp->prop('set-vars', $o->get('set-vars'));
 
3583
 
 
3584
   $o->usage_or_errors();
 
3585
 
 
3586
   # ########################################################################
 
3587
   # If --pid, check it first since we'll die if it already exits.
 
3588
   # ########################################################################
 
3589
   my $daemon;
 
3590
   if ( $o->get('pid') ) {
 
3591
      # We're not daemoninzing, it just handles PID stuff.  Keep $daemon
 
3592
      # in the the scope of main() because when it's destroyed it automatically
 
3593
      # removes the PID file.
 
3594
      $daemon = new Daemon(o=>$o);
 
3595
      $daemon->make_PID_file();
 
3596
   }
 
3597
 
 
3598
   # #######################################################################
 
3599
   # Get ready to do the main work.
 
3600
   # #######################################################################
 
3601
   my $get_keys = $o->get('key-types') =~ m/k/ ? 1 : 0;
 
3602
   my $get_fks  = $o->get('key-types') =~ m/f/ ? 1 : 0;
 
3603
 
 
3604
   # Connect to the database
 
3605
   if ( $o->got('ask-pass') ) {
 
3606
      $o->set('password', OptionParser::prompt_noecho("Enter password: "));
 
3607
   }
 
3608
   my $dsn_defaults = $dp->parse_options($o);
 
3609
   my $dsn          = @ARGV ? $dp->parse(shift @ARGV, $dsn_defaults)
 
3610
                    :         $dsn_defaults;
 
3611
   my $dbh          = $dp->get_dbh($dp->get_cxn_params($dsn),
 
3612
                              { AutoCommit => 1, });
 
3613
 
 
3614
   my $vp = new VersionParser();
 
3615
   my $version = $vp->parse($dbh->selectrow_array('SELECT VERSION()'));
 
3616
 
 
3617
   my $ks = $o->get('summary') ? new KeySize(q=>$q) : undef;
 
3618
   my $dk = new DuplicateKeyFinder();
 
3619
   my $du = new MySQLDump();
 
3620
 
 
3621
   my %tp_opts = (
 
3622
      ignore_type  => $o->get('all-structs'),
 
3623
      ignore_order => $o->get('ignore-order'),
 
3624
      clustered    => $o->get('clustered'),
 
3625
   );
 
3626
 
 
3627
   # #######################################################################
 
3628
   # Do the main work.
 
3629
   # #######################################################################
 
3630
 
 
3631
   my $si = new SchemaIterator(
 
3632
      Quoter => $q,
 
3633
   );
 
3634
   $si->set_filter($si->make_filter($o));
 
3635
   my $next_db = $si->get_db_itr(dbh => $dbh);
 
3636
   DATABASE:
 
3637
   while ( my $database = $next_db->() ) {
 
3638
      MKDEBUG && _d('Getting tables from', $database);
 
3639
      my $next_tbl = $si->get_tbl_itr(
 
3640
         dbh   => $dbh,
 
3641
         db    => $database,
 
3642
         views => 0,
 
3643
      );
 
3644
      TABLE:
 
3645
      while ( my $table = $next_tbl->() ) {
 
3646
         MKDEBUG && _d('Got table', $table);
 
3647
 
 
3648
         # If get_create_table() fails, it will throw a warning and return
 
3649
         # undef.  So we can just move on to the next table.
 
3650
         my $ddl = $du->get_create_table($dbh, $q, $database, $table);
 
3651
         next TABLE unless $ddl;
 
3652
         $ddl = $ddl->[1];  # retval is an arrayref: [table|view, SHOW CREATE]
 
3653
 
 
3654
         my $engine   = $tp->get_engine($ddl) || next TABLE;
 
3655
         my $tbl_info = {
 
3656
            db     => $database,
 
3657
            tbl    => $table,
 
3658
            engine => $engine,
 
3659
            ddl    => $ddl,
 
3660
         };
 
3661
 
 
3662
         my ($keys, $clustered_key)
 
3663
                  = $tp->get_keys($ddl, {version => $version })  if $get_keys;
 
3664
         my $fks  = $tp->get_fks($ddl,  {database => $database}) if $get_fks;
 
3665
 
 
3666
         next TABLE unless %$keys || %$fks;
 
3667
 
 
3668
         if ( $o->got('verbose') ) {
 
3669
            print_all_keys($keys, $tbl_info, \%seen_tbl) if $keys;
 
3670
            print_all_keys($fks,  $tbl_info, \%seen_tbl) if $fks;
 
3671
         }
 
3672
         else {
 
3673
            MKDEBUG && _d('Getting duplicate keys on', $database, $table);
 
3674
            eval {
 
3675
               $dk->get_duplicate_keys(
 
3676
                  $keys,
 
3677
                  clustered_key => $clustered_key,
 
3678
                  tbl_info      => $tbl_info,
 
3679
                  callback      => \&print_duplicate_key,
 
3680
                  %tp_opts,
 
3681
                  # get_duplicate_keys() ignores these args but passes them
 
3682
                  # to the callback:
 
3683
                     dbh      => $dbh,
 
3684
                     is_fk    => 0,
 
3685
                     o        => $o,
 
3686
                     ks       => $ks,
 
3687
                     tp       => $tp,
 
3688
                     q        => $q,
 
3689
                     seen_tbl => \%seen_tbl,
 
3690
                     summary  => \%summary,
 
3691
               ) if $keys;
 
3692
 
 
3693
               $dk->get_duplicate_fks(
 
3694
                  $fks,
 
3695
                  tbl_info => $tbl_info,
 
3696
                  callback => \&print_duplicate_key,
 
3697
                  %tp_opts,
 
3698
                  # get_duplicate_fks() ignores these args but passes them
 
3699
                  # to the callback:
 
3700
                     dbh   => $dbh,
 
3701
                     is_fk => 1,
 
3702
                     o     => $o,
 
3703
                     ks    => $ks,
 
3704
                     tp       => $tp,
 
3705
                     q        => $q,
 
3706
                     seen_tbl => \%seen_tbl,
 
3707
                     summary  => \%summary,
 
3708
               ) if $fks;
 
3709
            };
 
3710
            if ( $EVAL_ERROR ) {
 
3711
               warn "Error checking `$database`.`$table` for duplicate keys: "
 
3712
                  . $EVAL_ERROR;
 
3713
               next TABLE;
 
3714
            }
 
3715
         }
 
3716
 
 
3717
         # Always count Total Keys so print_key_summary won't die
 
3718
         # because %summary is empty.
 
3719
         $summary{'Total Indexes'} += (scalar keys %$keys) + (scalar keys %$fks)
 
3720
      }  # TABLE
 
3721
   }  # DATABASE
 
3722
 
 
3723
   print_key_summary(%summary) if $o->get('summary');
 
3724
 
 
3725
   return 0;
 
3726
}
 
3727
 
 
3728
# ##########################################################################
 
3729
# Subroutines
 
3730
# ##########################################################################
 
3731
 
 
3732
sub print_all_keys {
 
3733
   my ( $keys, $tbl_info, $seen_tbl ) = @_;
 
3734
   return unless $keys;
 
3735
   my $db  = $tbl_info->{db};
 
3736
   my $tbl = $tbl_info->{tbl};
 
3737
   if ( !$seen_tbl->{"$db$tbl"}++ ) {
 
3738
      printf $hdr_fmt, ('#' x $hdr_width);
 
3739
      printf $hdr_fmt, "$db.$tbl";
 
3740
      printf $hdr_fmt, ('#' x $hdr_width);
 
3741
   }
 
3742
   foreach my $key ( values %$keys ) {
 
3743
      print "\n# $key->{name} ($key->{colnames})";
 
3744
   }
 
3745
   print "\n";
 
3746
   return;
 
3747
}
 
3748
 
 
3749
sub print_duplicate_key {
 
3750
   my ( $dupe, %args ) = @_;
 
3751
   return unless $dupe;
 
3752
   foreach my $arg ( qw(tbl_info dbh is_fk o ks q tp seen_tbl) ) {
 
3753
      die "I need a $arg argument" unless exists $args{$arg};
 
3754
   }
 
3755
   MKDEBUG && _d('Printing duplicate key', $dupe->{key});
 
3756
   my $db       = $args{tbl_info}->{db};
 
3757
   my $tbl      = $args{tbl_info}->{tbl};
 
3758
   my $dbh      = $args{dbh};
 
3759
   my $o        = $args{o};
 
3760
   my $ks       = $args{ks};
 
3761
   my $seen_tbl = $args{seen_tbl};
 
3762
   my $q        = $args{q};
 
3763
   my $tp       = $args{tp};
 
3764
   my $summary  = $args{summary};
 
3765
   my $struct   = $tp->parse($args{tbl_info}->{ddl});
 
3766
 
 
3767
   if ( !$seen_tbl->{"$db$tbl"}++ ) {
 
3768
      printf $hdr_fmt, ('#' x $hdr_width);
 
3769
      printf $hdr_fmt, "$db.$tbl";
 
3770
      printf $hdr_fmt, ('#' x $hdr_width);
 
3771
      print "\n";
 
3772
   }
 
3773
 
 
3774
   $dupe->{reason} =~ s/\n/\n# /g;
 
3775
   print "# $dupe->{reason}\n";
 
3776
 
 
3777
   print "# Key definitions:\n";
 
3778
   print "#   " . ($dupe->{ddl} || '') . "\n";
 
3779
   print "#   " . ($dupe->{duplicate_of_ddl} || '') . "\n";
 
3780
 
 
3781
   print "# Column types:\n";
 
3782
   my %seen;  # print each column only once
 
3783
   foreach my $col ( @{$dupe->{cols}}, @{$dupe->{duplicate_of_cols}} ) {
 
3784
      next if $seen{$col}++;
 
3785
      MKDEBUG && _d('col', $col);
 
3786
      print "#\t" . lc($struct->{defs}->{lc $col}) . "\n";
 
3787
   }
 
3788
 
 
3789
   if ( $o->get('sql') ) {
 
3790
      if ( $dupe->{dupe_type} ne 'clustered' ) {
 
3791
         print "# To remove this duplicate "
 
3792
            . ($args{is_fk} ? 'foreign key' : 'index')
 
3793
            . ", execute:\n"
 
3794
            . 'ALTER TABLE ' . $q->quote($db, $tbl)
 
3795
            . ($args{is_fk} ? ' DROP FOREIGN KEY ' : ' DROP INDEX ')
 
3796
            . "`$dupe->{key}`;\n";
 
3797
      }
 
3798
      else {
 
3799
         # Suggest shortening clustered dupes instead of
 
3800
         # removing them (issue 295).
 
3801
         print "# To shorten this duplicate clustered index, execute:\n"
 
3802
            . 'ALTER TABLE '.$q->quote($db, $tbl)." DROP INDEX `$dupe->{key}`, "
 
3803
            . "ADD INDEX `$dupe->{key}` ($dupe->{short_key});\n";
 
3804
      }
 
3805
   }
 
3806
   print "\n";
 
3807
 
 
3808
   if ( $o->get('summary') && $summary ) {
 
3809
      $summary->{'Total Duplicate Indexes'} += 1;
 
3810
      my ($size, $chosen_key) = $ks->get_key_size(
 
3811
         name        => $dupe->{key},
 
3812
         cols        => $dupe->{cols},
 
3813
         tbl_name    => $q->quote($db, $tbl),
 
3814
         tbl_struct  => $struct,
 
3815
         dbh         => $dbh,
 
3816
      );
 
3817
      if ( $args{is_fk} ) {
 
3818
         # Foreign keys have no size because they're just constraints.
 
3819
         print "# MySQL uses the $chosen_key index for this "
 
3820
            . "foreign key constraint\n\n";
 
3821
      }
 
3822
      else {
 
3823
         $size ||= 0;
 
3824
 
 
3825
         # Create Size Duplicate Keys summary even if there's no valid keys.
 
3826
         $summary->{'Size Duplicate Indexes'} += $size;
 
3827
 
 
3828
         if ( $size ) {
 
3829
            if ( $chosen_key && $chosen_key ne $dupe->{key} ) {
 
3830
               # This shouldn't happen. But in case it does, we should know.
 
3831
               print "# MySQL chose the $chosen_key index despite FORCE INDEX\n\n";
 
3832
            }
 
3833
         }
 
3834
      }
 
3835
   }
 
3836
   return;
 
3837
}
 
3838
 
 
3839
sub print_key_summary {
 
3840
   my ( %summary ) = @_;
 
3841
   printf $hdr_fmt, ('#' x $hdr_width);
 
3842
   printf $hdr_fmt, 'Summary of indexes';
 
3843
   printf $hdr_fmt, ('#' x $hdr_width);
 
3844
   print "\n";
 
3845
   my $max_item = max(map { length($_) } keys %summary);
 
3846
   my $line_fmt = "# %-${max_item}s  %-s\n";
 
3847
   foreach my $item ( sort keys %summary ) {
 
3848
      printf $line_fmt, $item, $summary{$item};
 
3849
   }
 
3850
   return;
 
3851
}
 
3852
 
 
3853
sub _d {
 
3854
   my ($package, undef, $line) = caller 0;
 
3855
   @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
 
3856
        map { defined $_ ? $_ : 'undef' }
 
3857
        @_;
 
3858
   print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
 
3859
}
 
3860
 
 
3861
# ############################################################################
 
3862
# Run the program.
 
3863
# ############################################################################
 
3864
if ( !caller ) { exit main(@ARGV); }
 
3865
 
 
3866
1; # Because this is a module as well as a script.
 
3867
 
 
3868
# ############################################################################
 
3869
# Documentation
 
3870
# ############################################################################
 
3871
 
 
3872
=pod
 
3873
 
 
3874
=head1 NAME
 
3875
 
 
3876
mk-duplicate-key-checker - Find duplicate indexes and foreign keys on MySQL tables.
 
3877
 
 
3878
=head1 SYNOPSIS
 
3879
 
 
3880
Usage: mk-duplicate-key-checker [OPTION...] [DSN]
 
3881
 
 
3882
mk-duplicate-key-checker examines MySQL tables for duplicate or redundant
 
3883
indexes and foreign keys.  Connection options are read from MySQL option files.
 
3884
 
 
3885
   mk-duplicate-key-checker --host host1
 
3886
 
 
3887
=head1 RISKS
 
3888
 
 
3889
The following section is included to inform users about the potential risks,
 
3890
whether known or unknown, of using this tool.  The two main categories of risks
 
3891
are those created by the nature of the tool (e.g. read-only tools vs. read-write
 
3892
tools) and those created by bugs.
 
3893
 
 
3894
mk-duplicate-key-checker is a read-only tool that executes SHOW CREATE TABLE and
 
3895
related queries to inspect table structures, and thus is very low-risk.
 
3896
 
 
3897
At the time of this release, there is an unconfirmed bug that causes the tool
 
3898
to crash.
 
3899
 
 
3900
The authoritative source for updated information is always the online issue
 
3901
tracking system.  Issues that affect this tool will be marked as such.  You can
 
3902
see a list of such issues at the following URL:
 
3903
L<http://www.maatkit.org/bugs/mk-duplicate-key-checker>.
 
3904
 
 
3905
See also L<"BUGS"> for more information on filing bugs and getting help.
 
3906
 
 
3907
=head1 DESCRIPTION
 
3908
 
 
3909
This program examines the output of SHOW CREATE TABLE on MySQL tables, and if
 
3910
it finds indexes that cover the same columns as another index in the same
 
3911
order, or cover an exact leftmost prefix of another index, it prints out
 
3912
the suspicious indexes.  By default, indexes must be of the same type, so a
 
3913
BTREE index is not a duplicate of a FULLTEXT index, even if they have the same
 
3914
columns.  You can override this.
 
3915
 
 
3916
It also looks for duplicate foreign keys.  A duplicate foreign key covers the
 
3917
same columns as another in the same table, and references the same parent
 
3918
table.
 
3919
 
 
3920
=head1 OPTIONS
 
3921
 
 
3922
This tool accepts additional command-line arguments.  Refer to the
 
3923
L<"SYNOPSIS"> and usage information for details.
 
3924
 
 
3925
=over
 
3926
 
 
3927
=item --all-structs
 
3928
 
 
3929
Compare indexes with different structs (BTREE, HASH, etc).
 
3930
 
 
3931
By default this is disabled, because a BTREE index that covers the same columns
 
3932
as a FULLTEXT index is not really a duplicate, for example.
 
3933
 
 
3934
=item --ask-pass
 
3935
 
 
3936
Prompt for a password when connecting to MySQL.
 
3937
 
 
3938
=item --charset
 
3939
 
 
3940
short form: -A; type: string
 
3941
 
 
3942
Default character set.  If the value is utf8, sets Perl's binmode on
 
3943
STDOUT to utf8, passes the mysql_enable_utf8 option to DBD::mysql, and runs SET
 
3944
NAMES UTF8 after connecting to MySQL.  Any other value sets binmode on STDOUT
 
3945
without the utf8 layer, and runs SET NAMES after connecting to MySQL.
 
3946
 
 
3947
=item --[no]clustered
 
3948
 
 
3949
default: yes
 
3950
 
 
3951
PK columns appended to secondary key is duplicate.
 
3952
 
 
3953
Detects when a suffix of a secondary key is a leftmost prefix of the primary
 
3954
key, and treats it as a duplicate key.  Only detects this condition on storage
 
3955
engines whose primary keys are clustered (currently InnoDB and solidDB).
 
3956
 
 
3957
Clustered storage engines append the primary key columns to the leaf nodes of
 
3958
all secondary keys anyway, so you might consider it redundant to have them
 
3959
appear in the internal nodes as well.  Of course, you may also want them in the
 
3960
internal nodes, because just having them at the leaf nodes won't help for some
 
3961
queries.  It does help for covering index queries, however.
 
3962
 
 
3963
Here's an example of a key that is considered redundant with this option:
 
3964
 
 
3965
  PRIMARY KEY  (`a`)
 
3966
  KEY `b` (`b`,`a`)
 
3967
 
 
3968
The use of such indexes is rather subtle.  For example, suppose you have the
 
3969
following query:
 
3970
 
 
3971
  SELECT ... WHERE b=1 ORDER BY a;
 
3972
 
 
3973
This query will do a filesort if we remove the index on C<b,a>.  But if we
 
3974
shorten the index on C<b,a> to just C<b> and also remove the ORDER BY, the query
 
3975
should return the same results.
 
3976
 
 
3977
The tool suggests shortening duplicate clustered keys by dropping the key
 
3978
and re-adding it without the primary key prefix.  The shortened clustered
 
3979
key may still duplicate another key, but the tool cannot currently detect
 
3980
when this happens without being ran a second time to re-check the newly
 
3981
shortened clustered keys.  Therefore, if you shorten any duplicate clustered
 
3982
keys, you should run the tool again.
 
3983
 
 
3984
=item --config
 
3985
 
 
3986
type: Array
 
3987
 
 
3988
Read this comma-separated list of config files; if specified, this must be the
 
3989
first option on the command line.
 
3990
 
 
3991
=item --databases
 
3992
 
 
3993
short form: -d; type: hash
 
3994
 
 
3995
Check only this comma-separated list of databases.
 
3996
 
 
3997
=item --defaults-file
 
3998
 
 
3999
short form: -F; type: string
 
4000
 
 
4001
Only read mysql options from the given file.  You must give an absolute pathname.
 
4002
 
 
4003
=item --engines
 
4004
 
 
4005
short form: -e; type: hash
 
4006
 
 
4007
Check only tables whose storage engine is in this comma-separated list.
 
4008
 
 
4009
=item --help
 
4010
 
 
4011
Show help and exit.
 
4012
 
 
4013
=item --host
 
4014
 
 
4015
short form: -h; type: string
 
4016
 
 
4017
Connect to host.
 
4018
 
 
4019
=item --ignore-databases
 
4020
 
 
4021
type: Hash
 
4022
 
 
4023
Ignore this comma-separated list of databases.
 
4024
 
 
4025
=item --ignore-engines
 
4026
 
 
4027
type: Hash
 
4028
 
 
4029
Ignore this comma-separated list of storage engines.
 
4030
 
 
4031
=item --ignore-order
 
4032
 
 
4033
Ignore index order so KEY(a,b) duplicates KEY(b,a).
 
4034
 
 
4035
=item --ignore-tables
 
4036
 
 
4037
type: Hash
 
4038
 
 
4039
Ignore this comma-separated list of tables.  Table names may be qualified with
 
4040
the database name.
 
4041
 
 
4042
=item --key-types
 
4043
 
 
4044
type: string; default: fk
 
4045
 
 
4046
Check for duplicate f=foreign keys, k=keys or fk=both.
 
4047
 
 
4048
=item --password
 
4049
 
 
4050
short form: -p; type: string
 
4051
 
 
4052
Password to use when connecting.
 
4053
 
 
4054
=item --pid
 
4055
 
 
4056
type: string
 
4057
 
 
4058
Create the given PID file.  The file contains the process ID of the script.
 
4059
The PID file is removed when the script exits.  Before starting, the script
 
4060
checks if the PID file already exists.  If it does not, then the script creates
 
4061
and writes its own PID to it.  If it does, then the script checks the following:
 
4062
if the file contains a PID and a process is running with that PID, then
 
4063
the script dies; or, if there is no process running with that PID, then the
 
4064
script overwrites the file with its own PID and starts; else, if the file
 
4065
contains no PID, then the script dies.
 
4066
 
 
4067
=item --port
 
4068
 
 
4069
short form: -P; type: int
 
4070
 
 
4071
Port number to use for connection.
 
4072
 
 
4073
=item --set-vars
 
4074
 
 
4075
type: string; default: wait_timeout=10000
 
4076
 
 
4077
Set these MySQL variables.  Immediately after connecting to MySQL, this string
 
4078
will be appended to SET and executed.
 
4079
 
 
4080
=item --socket
 
4081
 
 
4082
short form: -S; type: string
 
4083
 
 
4084
Socket file to use for connection.
 
4085
 
 
4086
=item --[no]sql
 
4087
 
 
4088
default: yes
 
4089
 
 
4090
Print DROP KEY statement for each duplicate key.  By default an ALTER TABLE
 
4091
DROP KEY statement is printed below each duplicate key so that, if you want to
 
4092
remove the duplicate key, you can copy-paste the statement into MySQL.
 
4093
 
 
4094
To disable printing these statements, specify --nosql.
 
4095
 
 
4096
=item --[no]summary
 
4097
 
 
4098
default: yes
 
4099
 
 
4100
Print summary of indexes at end of output.
 
4101
 
 
4102
=item --tables
 
4103
 
 
4104
short form: -t; type: hash
 
4105
 
 
4106
Check only this comma-separated list of tables.
 
4107
 
 
4108
Table names may be qualified with the database name.
 
4109
 
 
4110
=item --user
 
4111
 
 
4112
short form: -u; type: string
 
4113
 
 
4114
User for login if not current user.
 
4115
 
 
4116
=item --verbose
 
4117
 
 
4118
short form: -v
 
4119
 
 
4120
Output all keys and/or foreign keys found, not just redundant ones.
 
4121
 
 
4122
=item --version
 
4123
 
 
4124
Show version and exit.
 
4125
 
 
4126
=back
 
4127
 
 
4128
=head1 DSN OPTIONS
 
4129
 
 
4130
These DSN options are used to create a DSN.  Each option is given like
 
4131
C<option=value>.  The options are case-sensitive, so P and p are not the
 
4132
same option.  There cannot be whitespace before or after the C<=> and
 
4133
if the value contains whitespace it must be quoted.  DSN options are
 
4134
comma-separated.  See the L<maatkit> manpage for full details.
 
4135
 
 
4136
=over
 
4137
 
 
4138
=item * A
 
4139
 
 
4140
dsn: charset; copy: yes
 
4141
 
 
4142
Default character set.
 
4143
 
 
4144
=item * D
 
4145
 
 
4146
dsn: database; copy: yes
 
4147
 
 
4148
Default database.
 
4149
 
 
4150
=item * F
 
4151
 
 
4152
dsn: mysql_read_default_file; copy: yes
 
4153
 
 
4154
Only read default options from the given file
 
4155
 
 
4156
=item * h
 
4157
 
 
4158
dsn: host; copy: yes
 
4159
 
 
4160
Connect to host.
 
4161
 
 
4162
=item * p
 
4163
 
 
4164
dsn: password; copy: yes
 
4165
 
 
4166
Password to use when connecting.
 
4167
 
 
4168
=item * P
 
4169
 
 
4170
dsn: port; copy: yes
 
4171
 
 
4172
Port number to use for connection.
 
4173
 
 
4174
=item * S
 
4175
 
 
4176
dsn: mysql_socket; copy: yes
 
4177
 
 
4178
Socket file to use for connection.
 
4179
 
 
4180
=item * u
 
4181
 
 
4182
dsn: user; copy: yes
 
4183
 
 
4184
User for login if not current user.
 
4185
 
 
4186
=back
 
4187
 
 
4188
=head1 DOWNLOADING
 
4189
 
 
4190
You can download Maatkit from Google Code at
 
4191
L<http://code.google.com/p/maatkit/>, or you can get any of the tools
 
4192
easily with a command like the following:
 
4193
 
 
4194
   wget http://www.maatkit.org/get/toolname
 
4195
   or
 
4196
   wget http://www.maatkit.org/trunk/toolname
 
4197
 
 
4198
Where C<toolname> can be replaced with the name (or fragment of a name) of any
 
4199
of the Maatkit tools.  Once downloaded, they're ready to run; no installation is
 
4200
needed.  The first URL gets the latest released version of the tool, and the
 
4201
second gets the latest trunk code from Subversion.
 
4202
 
 
4203
=head1 ENVIRONMENT
 
4204
 
 
4205
The environment variable C<MKDEBUG> enables verbose debugging output in all of
 
4206
the Maatkit tools:
 
4207
 
 
4208
   MKDEBUG=1 mk-....
 
4209
 
 
4210
=head1 SYSTEM REQUIREMENTS
 
4211
 
 
4212
You need the following Perl modules: DBI and DBD::mysql.
 
4213
 
 
4214
=head1 BUGS
 
4215
 
 
4216
For a list of known bugs see L<http://www.maatkit.org/bugs/mk-duplicate-key-checker>.
 
4217
 
 
4218
Please use Google Code Issues and Groups to report bugs or request support:
 
4219
L<http://code.google.com/p/maatkit/>.  You can also join #maatkit on Freenode to
 
4220
discuss Maatkit.
 
4221
 
 
4222
Please include the complete command-line used to reproduce the problem you are
 
4223
seeing, the version of all MySQL servers involved, the complete output of the
 
4224
tool when run with L<"--version">, and if possible, debugging output produced by
 
4225
running with the C<MKDEBUG=1> environment variable.
 
4226
 
 
4227
=head1 COPYRIGHT, LICENSE AND WARRANTY
 
4228
 
 
4229
This program is copyright 2007-@CURRENTYEAR@ Baron Schwartz.
 
4230
Feedback and improvements are welcome.
 
4231
 
 
4232
THIS PROGRAM IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
 
4233
WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
 
4234
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 
4235
 
 
4236
This program is free software; you can redistribute it and/or modify it under
 
4237
the terms of the GNU General Public License as published by the Free Software
 
4238
Foundation, version 2; OR the Perl Artistic License.  On UNIX and similar
 
4239
systems, you can issue `man perlgpl' or `man perlartistic' to read these
 
4240
licenses.
 
4241
 
 
4242
You should have received a copy of the GNU General Public License along with
 
4243
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
 
4244
Place, Suite 330, Boston, MA  02111-1307  USA.
 
4245
 
 
4246
=head1 AUTHOR
 
4247
 
 
4248
Baron Schwartz, Daniel Nichter
 
4249
 
 
4250
=head1 ABOUT MAATKIT
 
4251
 
 
4252
This tool is part of Maatkit, a toolkit for power users of MySQL.  Maatkit
 
4253
was created by Baron Schwartz; Baron and Daniel Nichter are the primary
 
4254
code contributors.  Both are employed by Percona.  Financial support for
 
4255
Maatkit development is primarily provided by Percona and its clients. 
 
4256
 
 
4257
=head1 VERSION
 
4258
 
 
4259
This manual page documents Ver @VERSION@ Distrib @DISTRIB@ $Revision: 7477 $.
 
4260
 
 
4261
=cut