~percona-toolkit-dev/percona-toolkit/release-2.2.2

« back to all changes in this revision

Viewing changes to bin/pt-online-schema-change

  • Committer: Daniel Nichter
  • Date: 2013-03-11 16:51:30 UTC
  • mfrom: (551 2.2)
  • mto: This revision was merged to the branch mainline in revision 552.
  • Revision ID: daniel@percona.com-20130311165130-limzlpx1hj5c8nwz
Merge 2.2 r551.

Show diffs side-by-side

added added

removed removed

Lines of Context:
23
23
      VersionParser
24
24
      DSNParser
25
25
      Daemon
26
 
      ReportFormatter
27
26
      Quoter
28
27
      TableNibbler
29
28
      TableParser
80
79
 
81
80
use List::Util qw(max);
82
81
use Getopt::Long;
 
82
use Data::Dumper;
83
83
 
84
84
my $POD_link_re = '[LC]<"?([^">]+)"?>';
85
85
 
1063
1063
   );
1064
1064
};
1065
1065
 
 
1066
sub set_vars {
 
1067
   my ($self, $file) = @_;
 
1068
   $file ||= $self->{file} || __FILE__;
 
1069
 
 
1070
   my %user_vars;
 
1071
   my $user_vars = $self->has('set-vars') ? $self->get('set-vars') : undef;
 
1072
   if ( $user_vars ) {
 
1073
      foreach my $var_val ( @$user_vars ) {
 
1074
         my ($var, $val) = $var_val =~ m/([^\s=]+)=(\S+)/;
 
1075
         die "Invalid --set-vars value: $var_val\n" unless $var && $val;
 
1076
         $user_vars{$var} = {
 
1077
            val     => $val,
 
1078
            default => 0,
 
1079
         };
 
1080
      }
 
1081
   }
 
1082
 
 
1083
   my %default_vars;
 
1084
   my $default_vars = $self->read_para_after($file, qr/MAGIC_set_vars/);
 
1085
   if ( $default_vars ) {
 
1086
      %default_vars = map {
 
1087
         my $var_val = $_;
 
1088
         my ($var, $val) = $var_val =~ m/([^\s=]+)=(\S+)/;
 
1089
         die "Invalid --set-vars value: $var_val\n" unless $var && $val;
 
1090
         $var => {
 
1091
            val     => $val,
 
1092
            default => 1,
 
1093
         };
 
1094
      } split("\n", $default_vars);
 
1095
   }
 
1096
 
 
1097
   my %vars = (
 
1098
      %default_vars, # first the tool's defaults
 
1099
      %user_vars,    # then the user's which overwrite the defaults
 
1100
   );
 
1101
   PTDEBUG && _d('--set-vars:', Dumper(\%vars));
 
1102
   return \%vars;
 
1103
}
 
1104
 
1066
1105
sub _d {
1067
1106
   my ($package, undef, $line) = caller 0;
1068
1107
   @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
2193
2232
 
2194
2233
      if ( my ($charset) = $cxn_string =~ m/charset=([\w]+)/ ) {
2195
2234
         $sql = qq{/*!40101 SET NAMES "$charset"*/};
2196
 
         PTDEBUG && _d($dbh, ':', $sql);
 
2235
         PTDEBUG && _d($dbh, $sql);
2197
2236
         eval { $dbh->do($sql) };
2198
2237
         if ( $EVAL_ERROR ) {
2199
2238
            die "Error setting NAMES to $charset: $EVAL_ERROR";
2208
2247
         }
2209
2248
      }
2210
2249
 
2211
 
      if ( my $var = $self->prop('set-vars') ) {
2212
 
         $sql = "SET $var";
2213
 
         PTDEBUG && _d($dbh, ':', $sql);
2214
 
         eval { $dbh->do($sql) };
2215
 
         if ( $EVAL_ERROR ) {
2216
 
            die "Error setting $var: $EVAL_ERROR";
2217
 
         }
 
2250
      if ( my $vars = $self->prop('set-vars') ) {
 
2251
         $self->set_vars($dbh, $vars);
2218
2252
      }
2219
2253
 
2220
2254
      $sql = 'SET @@SQL_QUOTE_SHOW_CREATE = 1'
2289
2323
   return \%new_dsn;
2290
2324
}
2291
2325
 
 
2326
sub set_vars {
 
2327
   my ($self, $dbh, $vars) = @_;
 
2328
 
 
2329
   return unless $vars;
 
2330
 
 
2331
   foreach my $var ( sort keys %$vars ) {
 
2332
      my $val = $vars->{$var}->{val};
 
2333
 
 
2334
      (my $quoted_var = $var) =~ s/_/\\_/;
 
2335
      my ($var_exists, $current_val);
 
2336
      eval {
 
2337
         ($var_exists, $current_val) = $dbh->selectrow_array(
 
2338
            "SHOW VARIABLES LIKE '$quoted_var'");
 
2339
      };
 
2340
      my $e = $EVAL_ERROR;
 
2341
      if ( $e ) {
 
2342
         PTDEBUG && _d($e);
 
2343
      }
 
2344
 
 
2345
      if ( $vars->{$var}->{default} && !$var_exists ) {
 
2346
         PTDEBUG && _d('Not setting default var', $var,
 
2347
            'because it does not exist');
 
2348
         next;
 
2349
      }
 
2350
 
 
2351
      if ( $current_val && $current_val eq $val ) {
 
2352
         PTDEBUG && _d('Not setting var', $var, 'because its value',
 
2353
            'is already', $val);
 
2354
         next;
 
2355
      }
 
2356
 
 
2357
      my $sql = "SET SESSION $var=$val";
 
2358
      PTDEBUG && _d($dbh, $sql);
 
2359
      eval { $dbh->do($sql) };
 
2360
      if ( my $set_error = $EVAL_ERROR ) {
 
2361
         chomp($set_error);
 
2362
         $set_error =~ s/ at \S+ line \d+//;
 
2363
         my $msg = "Error setting $var: $set_error";
 
2364
         if ( $current_val ) {
 
2365
            $msg .= "  The current value for $var is $current_val.  "
 
2366
                  . "If the variable is read only (not dynamic), specify "
 
2367
                  . "--set-vars $var=$current_val to avoid this warning, "
 
2368
                  . "else manually set the variable and restart MySQL.";
 
2369
         }
 
2370
         warn $msg . "\n\n";
 
2371
      }
 
2372
   }
 
2373
 
 
2374
   return; 
 
2375
}
 
2376
 
2292
2377
sub _d {
2293
2378
   my ($package, undef, $line) = caller 0;
2294
2379
   @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
2506
2591
# ###########################################################################
2507
2592
 
2508
2593
# ###########################################################################
2509
 
# ReportFormatter package
2510
 
# This package is a copy without comments from the original.  The original
2511
 
# with comments and its test file can be found in the Bazaar repository at,
2512
 
#   lib/ReportFormatter.pm
2513
 
#   t/lib/ReportFormatter.t
2514
 
# See https://launchpad.net/percona-toolkit for more information.
2515
 
# ###########################################################################
2516
 
{
2517
 
package ReportFormatter;
2518
 
 
2519
 
use Lmo;
2520
 
use English qw(-no_match_vars);
2521
 
use constant PTDEBUG => $ENV{PTDEBUG} || 0;
2522
 
 
2523
 
use List::Util qw(min max);
2524
 
use POSIX qw(ceil);
2525
 
 
2526
 
eval { require Term::ReadKey };
2527
 
my $have_term = $EVAL_ERROR ? 0 : 1;
2528
 
 
2529
 
 
2530
 
has underline_header => (
2531
 
   is      => 'ro',
2532
 
   isa     => 'Bool',
2533
 
   default => sub { 1 },
2534
 
);
2535
 
has line_prefix => (
2536
 
   is      => 'ro',
2537
 
   isa     => 'Str',
2538
 
   default => sub { '# ' },
2539
 
);
2540
 
has line_width => (
2541
 
   is      => 'ro',
2542
 
   isa     => 'Int',
2543
 
   default => sub { 78 },
2544
 
);
2545
 
has column_spacing => (
2546
 
   is      => 'ro',
2547
 
   isa     => 'Str',
2548
 
   default => sub { ' ' },
2549
 
);
2550
 
has extend_right => (
2551
 
   is      => 'ro',
2552
 
   isa     => 'Bool',
2553
 
   default => sub { '' },
2554
 
);
2555
 
has truncate_line_mark => (
2556
 
   is      => 'ro',
2557
 
   isa     => 'Str',
2558
 
   default => sub { '...' },
2559
 
);
2560
 
has column_errors => (
2561
 
   is      => 'ro',
2562
 
   isa     => 'Str',
2563
 
   default => sub { 'warn' },
2564
 
);
2565
 
has truncate_header_side => (
2566
 
   is      => 'ro',
2567
 
   isa     => 'Str',
2568
 
   default => sub { 'left' },
2569
 
);
2570
 
has strip_whitespace => (
2571
 
   is      => 'ro',
2572
 
   isa     => 'Bool',
2573
 
   default => sub { 1 },
2574
 
);
2575
 
has title => (
2576
 
   is        => 'rw',
2577
 
   isa       => 'Str',
2578
 
   predicate => 'has_title',
2579
 
);
2580
 
 
2581
 
 
2582
 
has n_cols => (
2583
 
   is      => 'rw',
2584
 
   isa     => 'Int',
2585
 
   default => sub { 0 },
2586
 
   init_arg => undef,
2587
 
);
2588
 
 
2589
 
has cols => (
2590
 
   is       => 'ro',
2591
 
   isa      => 'ArrayRef',
2592
 
   init_arg => undef,
2593
 
   default  => sub { [] },
2594
 
   clearer  => 'clear_cols',
2595
 
);
2596
 
 
2597
 
has lines => (
2598
 
   is       => 'ro',
2599
 
   isa      => 'ArrayRef',
2600
 
   init_arg => undef,
2601
 
   default  => sub { [] },
2602
 
   clearer  => 'clear_lines',
2603
 
);
2604
 
 
2605
 
has truncate_headers => (
2606
 
   is       => 'rw',
2607
 
   isa      => 'Bool',
2608
 
   default  => sub { undef },
2609
 
   init_arg => undef,
2610
 
   clearer  => 'clear_truncate_headers',
2611
 
);
2612
 
 
2613
 
sub BUILDARGS {
2614
 
   my $class = shift;
2615
 
   my $args  = $class->SUPER::BUILDARGS(@_);
2616
 
 
2617
 
   if ( ($args->{line_width} || '') eq 'auto' ) {
2618
 
      die "Cannot auto-detect line width because the Term::ReadKey module "
2619
 
         . "is not installed" unless $have_term;
2620
 
      ($args->{line_width}) = GetTerminalSize();
2621
 
      PTDEBUG && _d('Line width:', $args->{line_width});
2622
 
   }
2623
 
 
2624
 
   return $args;
2625
 
}
2626
 
 
2627
 
sub set_columns {
2628
 
   my ( $self, @cols ) = @_;
2629
 
   my $min_hdr_wid = 0;  # check that header fits on line
2630
 
   my $used_width  = 0;
2631
 
   my @auto_width_cols;
2632
 
 
2633
 
   for my $i ( 0..$#cols ) {
2634
 
      my $col      = $cols[$i];
2635
 
      my $col_name = $col->{name};
2636
 
      my $col_len  = length $col_name;
2637
 
      die "Column does not have a name" unless defined $col_name;
2638
 
 
2639
 
      if ( $col->{width} ) {
2640
 
         $col->{width_pct} = ceil(($col->{width} * 100) / $self->line_width());
2641
 
         PTDEBUG && _d('col:', $col_name, 'width:', $col->{width}, 'chars =',
2642
 
            $col->{width_pct}, '%');
2643
 
      }
2644
 
 
2645
 
      if ( $col->{width_pct} ) {
2646
 
         $used_width += $col->{width_pct};
2647
 
      }
2648
 
      else {
2649
 
         PTDEBUG && _d('Auto width col:', $col_name);
2650
 
         $col->{auto_width} = 1;
2651
 
         push @auto_width_cols, $i;
2652
 
      }
2653
 
 
2654
 
      $col->{truncate}        = 1 unless defined $col->{truncate};
2655
 
      $col->{truncate_mark}   = '...' unless defined $col->{truncate_mark};
2656
 
      $col->{truncate_side} ||= 'right';
2657
 
      $col->{undef_value}     = '' unless defined $col->{undef_value};
2658
 
 
2659
 
      $col->{min_val} = 0;
2660
 
      $col->{max_val} = 0;
2661
 
 
2662
 
      $min_hdr_wid        += $col_len;
2663
 
      $col->{header_width} = $col_len;
2664
 
 
2665
 
      $col->{right_most} = 1 if $i == $#cols;
2666
 
 
2667
 
      push @{$self->cols}, $col;
2668
 
   }
2669
 
 
2670
 
   $self->n_cols( scalar @cols );
2671
 
 
2672
 
   if ( ($used_width || 0) > 100 ) {
2673
 
      die "Total width_pct for all columns is >100%";
2674
 
   }
2675
 
 
2676
 
   if ( @auto_width_cols ) {
2677
 
      my $wid_per_col = int((100 - $used_width) / scalar @auto_width_cols);
2678
 
      PTDEBUG && _d('Line width left:', (100-$used_width), '%;',
2679
 
         'each auto width col:', $wid_per_col, '%');
2680
 
      map { $self->cols->[$_]->{width_pct} = $wid_per_col } @auto_width_cols;
2681
 
   }
2682
 
 
2683
 
   $min_hdr_wid += ($self->n_cols() - 1) * length $self->column_spacing();
2684
 
   PTDEBUG && _d('min header width:', $min_hdr_wid);
2685
 
   if ( $min_hdr_wid > $self->line_width() ) {
2686
 
      PTDEBUG && _d('Will truncate headers because min header width',
2687
 
         $min_hdr_wid, '> line width', $self->line_width());
2688
 
      $self->truncate_headers(1);
2689
 
   }
2690
 
 
2691
 
   return;
2692
 
}
2693
 
 
2694
 
sub add_line {
2695
 
   my ( $self, @vals ) = @_;
2696
 
   my $n_vals = scalar @vals;
2697
 
   if ( $n_vals != $self->n_cols() ) {
2698
 
      $self->_column_error("Number of values $n_vals does not match "
2699
 
         . "number of columns " . $self->n_cols());
2700
 
   }
2701
 
   for my $i ( 0..($n_vals-1) ) {
2702
 
      my $col   = $self->cols->[$i];
2703
 
      my $val   = defined $vals[$i] ? $vals[$i] : $col->{undef_value};
2704
 
      if ( $self->strip_whitespace() ) {
2705
 
         $val =~ s/^\s+//g;
2706
 
         $val =~ s/\s+$//;
2707
 
         $vals[$i] = $val;
2708
 
      }
2709
 
      my $width = length $val;
2710
 
      $col->{min_val} = min($width, ($col->{min_val} || $width));
2711
 
      $col->{max_val} = max($width, ($col->{max_val} || $width));
2712
 
   }
2713
 
   push @{$self->lines}, \@vals;
2714
 
   return;
2715
 
}
2716
 
 
2717
 
sub get_report {
2718
 
   my ( $self, %args ) = @_;
2719
 
 
2720
 
   $self->_calculate_column_widths();
2721
 
   if ( $self->truncate_headers() ) {
2722
 
      $self->_truncate_headers();
2723
 
   }
2724
 
   $self->_truncate_line_values(%args);
2725
 
 
2726
 
   my @col_fmts = $self->_make_column_formats();
2727
 
   my $fmt      = $self->line_prefix()
2728
 
                . join($self->column_spacing(), @col_fmts);
2729
 
   PTDEBUG && _d('Format:', $fmt);
2730
 
 
2731
 
   (my $hdr_fmt = $fmt) =~ s/%([^-])/%-$1/g;
2732
 
 
2733
 
   my @lines;
2734
 
   push @lines, $self->line_prefix() . $self->title() if $self->has_title();
2735
 
   push @lines, $self->_truncate_line(
2736
 
         sprintf($hdr_fmt, map { $_->{name} } @{$self->cols}),
2737
 
         strip => 1,
2738
 
         mark  => '',
2739
 
   );
2740
 
 
2741
 
   if ( $self->underline_header() ) {
2742
 
      my @underlines = map { '=' x $_->{print_width} } @{$self->cols};
2743
 
      push @lines, $self->_truncate_line(
2744
 
         sprintf($fmt, map { $_ || '' } @underlines),
2745
 
         mark  => '',
2746
 
      );
2747
 
   }
2748
 
 
2749
 
   push @lines, map {
2750
 
      my $vals = $_;
2751
 
      my $i    = 0;
2752
 
      my @vals = map {
2753
 
            my $val = defined $_ ? $_ : $self->cols->[$i++]->{undef_value};
2754
 
            $val = '' if !defined $val;
2755
 
            $val =~ s/\n/ /g;
2756
 
            $val;
2757
 
      } @$vals;
2758
 
      my $line = sprintf($fmt, @vals);
2759
 
      if ( $self->extend_right() ) {
2760
 
         $line;
2761
 
      }
2762
 
      else {
2763
 
         $self->_truncate_line($line);
2764
 
      }
2765
 
   } @{$self->lines};
2766
 
 
2767
 
   $self->clear_cols();
2768
 
   $self->clear_lines();
2769
 
   $self->clear_truncate_headers();
2770
 
 
2771
 
   return join("\n", @lines) . "\n";
2772
 
}
2773
 
 
2774
 
sub truncate_value {
2775
 
   my ( $self, $col, $val, $width, $side ) = @_;
2776
 
   return $val if length $val <= $width;
2777
 
   return $val if $col->{right_most} && $self->extend_right();
2778
 
   $side  ||= $col->{truncate_side};
2779
 
   my $mark = $col->{truncate_mark};
2780
 
   if ( $side eq 'right' ) {
2781
 
      $val  = substr($val, 0, $width - length $mark);
2782
 
      $val .= $mark;
2783
 
   }
2784
 
   elsif ( $side eq 'left') {
2785
 
      $val = $mark . substr($val, -1 * $width + length $mark);
2786
 
   }
2787
 
   else {
2788
 
      PTDEBUG && _d("I don't know how to", $side, "truncate values");
2789
 
   }
2790
 
   return $val;
2791
 
}
2792
 
 
2793
 
sub _calculate_column_widths {
2794
 
   my ( $self ) = @_;
2795
 
 
2796
 
   my $extra_space = 0;
2797
 
   foreach my $col ( @{$self->cols} ) {
2798
 
      my $print_width = int($self->line_width() * ($col->{width_pct} / 100));
2799
 
 
2800
 
      PTDEBUG && _d('col:', $col->{name}, 'width pct:', $col->{width_pct},
2801
 
         'char width:', $print_width,
2802
 
         'min val:', $col->{min_val}, 'max val:', $col->{max_val});
2803
 
 
2804
 
      if ( $col->{auto_width} ) {
2805
 
         if ( $col->{min_val} && $print_width < $col->{min_val} ) {
2806
 
            PTDEBUG && _d('Increased to min val width:', $col->{min_val});
2807
 
            $print_width = $col->{min_val};
2808
 
         }
2809
 
         elsif ( $col->{max_val} &&  $print_width > $col->{max_val} ) {
2810
 
            PTDEBUG && _d('Reduced to max val width:', $col->{max_val});
2811
 
            $extra_space += $print_width - $col->{max_val};
2812
 
            $print_width  = $col->{max_val};
2813
 
         }
2814
 
      }
2815
 
 
2816
 
      $col->{print_width} = $print_width;
2817
 
      PTDEBUG && _d('print width:', $col->{print_width});
2818
 
   }
2819
 
 
2820
 
   PTDEBUG && _d('Extra space:', $extra_space);
2821
 
   while ( $extra_space-- ) {
2822
 
      foreach my $col ( @{$self->cols} ) {
2823
 
         if (    $col->{auto_width}
2824
 
              && (    $col->{print_width} < $col->{max_val}
2825
 
                   || $col->{print_width} < $col->{header_width})
2826
 
         ) {
2827
 
            $col->{print_width}++;
2828
 
         }
2829
 
      }
2830
 
   }
2831
 
 
2832
 
   return;
2833
 
}
2834
 
 
2835
 
sub _truncate_headers {
2836
 
   my ( $self, $col ) = @_;
2837
 
   my $side = $self->truncate_header_side();
2838
 
   foreach my $col ( @{$self->cols} ) {
2839
 
      my $col_name    = $col->{name};
2840
 
      my $print_width = $col->{print_width};
2841
 
      next if length $col_name <= $print_width;
2842
 
      $col->{name}  = $self->truncate_value($col, $col_name, $print_width, $side);
2843
 
      PTDEBUG && _d('Truncated hdr', $col_name, 'to', $col->{name},
2844
 
         'max width:', $print_width);
2845
 
   }
2846
 
   return;
2847
 
}
2848
 
 
2849
 
sub _truncate_line_values {
2850
 
   my ( $self, %args ) = @_;
2851
 
   my $n_vals = $self->n_cols() - 1;
2852
 
   foreach my $vals ( @{$self->lines} ) {
2853
 
      for my $i ( 0..$n_vals ) {
2854
 
         my $col   = $self->cols->[$i];
2855
 
         my $val   = defined $vals->[$i] ? $vals->[$i] : $col->{undef_value};
2856
 
         my $width = length $val;
2857
 
 
2858
 
         if ( $col->{print_width} && $width > $col->{print_width} ) {
2859
 
            if ( !$col->{truncate} ) {
2860
 
               $self->_column_error("Value '$val' is too wide for column "
2861
 
                  . $col->{name});
2862
 
            }
2863
 
 
2864
 
            my $callback    = $args{truncate_callback};
2865
 
            my $print_width = $col->{print_width};
2866
 
            $val = $callback ? $callback->($col, $val, $print_width)
2867
 
                 :             $self->truncate_value($col, $val, $print_width);
2868
 
            PTDEBUG && _d('Truncated val', $vals->[$i], 'to', $val,
2869
 
               '; max width:', $print_width);
2870
 
            $vals->[$i] = $val;
2871
 
         }
2872
 
      }
2873
 
   }
2874
 
   return;
2875
 
}
2876
 
 
2877
 
sub _make_column_formats {
2878
 
   my ( $self ) = @_;
2879
 
   my @col_fmts;
2880
 
   my $n_cols = $self->n_cols() - 1;
2881
 
   for my $i ( 0..$n_cols ) {
2882
 
      my $col = $self->cols->[$i];
2883
 
 
2884
 
      my $width = $col->{right_most} && !$col->{right_justify} ? ''
2885
 
                : $col->{print_width};
2886
 
 
2887
 
      my $col_fmt  = '%' . ($col->{right_justify} ? '' : '-') . $width . 's';
2888
 
      push @col_fmts, $col_fmt;
2889
 
   }
2890
 
   return @col_fmts;
2891
 
}
2892
 
 
2893
 
sub _truncate_line {
2894
 
   my ( $self, $line, %args ) = @_;
2895
 
   my $mark = defined $args{mark} ? $args{mark} : $self->truncate_line_mark();
2896
 
   if ( $line ) {
2897
 
      $line =~ s/\s+$// if $args{strip};
2898
 
      my $len  = length($line);
2899
 
      if ( $len > $self->line_width() ) {
2900
 
         $line  = substr($line, 0, $self->line_width() - length $mark);
2901
 
         $line .= $mark if $mark;
2902
 
      }
2903
 
   }
2904
 
   return $line;
2905
 
}
2906
 
 
2907
 
sub _column_error {
2908
 
   my ( $self, $err ) = @_;
2909
 
   my $msg = "Column error: $err";
2910
 
   $self->column_errors() eq 'die' ? die $msg : warn $msg;
2911
 
   return;
2912
 
}
2913
 
 
2914
 
sub _d {
2915
 
   my ($package, undef, $line) = caller 0;
2916
 
   @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
2917
 
        map { defined $_ ? $_ : 'undef' }
2918
 
        @_;
2919
 
   print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
2920
 
}
2921
 
 
2922
 
no Lmo;
2923
 
1;
2924
 
}
2925
 
# ###########################################################################
2926
 
# End ReportFormatter package
2927
 
# ###########################################################################
2928
 
 
2929
 
# ###########################################################################
2930
2594
# Quoter package
2931
2595
# This package is a copy without comments from the original.  The original
2932
2596
# with comments and its test file can be found in the Bazaar repository at,
2942
2606
use English qw(-no_match_vars);
2943
2607
use constant PTDEBUG => $ENV{PTDEBUG} || 0;
2944
2608
 
 
2609
use Data::Dumper;
 
2610
$Data::Dumper::Indent    = 1;
 
2611
$Data::Dumper::Sortkeys  = 1;
 
2612
$Data::Dumper::Quotekeys = 0;
 
2613
 
2945
2614
sub new {
2946
2615
   my ( $class, %args ) = @_;
2947
2616
   return bless {}, $class;
3006
2675
 
3007
2676
sub serialize_list {
3008
2677
   my ( $self, @args ) = @_;
 
2678
   PTDEBUG && _d('Serializing', Dumper(\@args));
3009
2679
   return unless @args;
3010
2680
 
3011
 
   return $args[0] if @args == 1 && !defined $args[0];
3012
 
 
3013
 
   die "Cannot serialize multiple values with undef/NULL"
3014
 
      if grep { !defined $_ } @args;
3015
 
 
3016
 
   return join ',', map { quotemeta } @args;
 
2681
   my @parts;
 
2682
   foreach my $arg  ( @args ) {
 
2683
      if ( defined $arg ) {
 
2684
         $arg =~ s/,/\\,/g;      # escape commas
 
2685
         $arg =~ s/\\N/\\\\N/g;  # escape literal \N
 
2686
         push @parts, $arg;
 
2687
      }
 
2688
      else {
 
2689
         push @parts, '\N';
 
2690
      }
 
2691
   }
 
2692
 
 
2693
   my $string = join(',', @parts);
 
2694
   PTDEBUG && _d('Serialized: <', $string, '>');
 
2695
   return $string;
3017
2696
}
3018
2697
 
3019
2698
sub deserialize_list {
3020
2699
   my ( $self, $string ) = @_;
3021
 
   return $string unless defined $string;
3022
 
   my @escaped_parts = $string =~ /
3023
 
         \G             # Start of string, or end of previous match.
3024
 
         (              # Each of these is an element in the original list.
3025
 
            [^\\,]*     # Anything not a backslash or a comma
3026
 
            (?:         # When we get here, we found one of the above.
3027
 
               \\.      # A backslash followed by something so we can continue
3028
 
               [^\\,]*  # Same as above.
3029
 
            )*          # Repeat zero of more times.
3030
 
         )
3031
 
         ,              # Comma dividing elements
3032
 
      /sxgc;
3033
 
 
3034
 
   push @escaped_parts, pos($string) ? substr( $string, pos($string) ) : $string;
3035
 
 
3036
 
   my @unescaped_parts = map {
3037
 
      my $part = $_;
3038
 
 
3039
 
      my $char_class = utf8::is_utf8($part)  # If it's a UTF-8 string,
3040
 
                     ? qr/(?=\p{ASCII})\W/   # We only care about non-word
3041
 
                     : qr/(?=\p{ASCII})\W|[\x{80}-\x{FF}]/; # Otherwise,
3042
 
      $part =~ s/\\($char_class)/$1/g;
3043
 
      $part;
3044
 
   } @escaped_parts;
3045
 
 
3046
 
   return @unescaped_parts;
 
2700
   PTDEBUG && _d('Deserializing <', $string, '>');
 
2701
   die "Cannot deserialize an undefined string" unless defined $string;
 
2702
 
 
2703
   my @parts;
 
2704
   foreach my $arg ( split(/(?<!\\),/, $string) ) {
 
2705
      if ( $arg eq '\N' ) {
 
2706
         $arg = undef;
 
2707
      }
 
2708
      else {
 
2709
         $arg =~ s/\\,/,/g;
 
2710
         $arg =~ s/\\\\N/\\N/g;
 
2711
      }
 
2712
      push @parts, $arg;
 
2713
   }
 
2714
 
 
2715
   if ( !@parts ) {
 
2716
      my $n_empty_strings = $string =~ tr/,//;
 
2717
      $n_empty_strings++;
 
2718
      PTDEBUG && _d($n_empty_strings, 'empty strings');
 
2719
      map { push @parts, '' } 1..$n_empty_strings;
 
2720
   }
 
2721
   elsif ( $string =~ m/(?<!\\),$/ ) {
 
2722
      PTDEBUG && _d('Last value is an empty string');
 
2723
      push @parts, '';
 
2724
   }
 
2725
 
 
2726
   PTDEBUG && _d('Deserialized', Dumper(\@parts));
 
2727
   return @parts;
 
2728
}
 
2729
 
 
2730
sub _d {
 
2731
   my ($package, undef, $line) = caller 0;
 
2732
   @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
 
2733
        map { defined $_ ? $_ : 'undef' }
 
2734
        @_;
 
2735
   print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
3047
2736
}
3048
2737
 
3049
2738
1;
3889
3578
use English qw(-no_match_vars);
3890
3579
use constant PTDEBUG => $ENV{PTDEBUG} || 0;
3891
3580
 
 
3581
use Time::HiRes qw(sleep);
 
3582
 
3892
3583
sub new {
3893
3584
   my ( $class, %args ) = @_;
3894
3585
   my $self = {
3993
3684
   }
3994
3685
 
3995
3686
   my $self = {
3996
 
      dsn          => $dsn,
3997
 
      dbh          => $args{dbh},
3998
 
      dsn_name     => $dp->as_string($dsn, [qw(h P S)]),
3999
 
      hostname     => '',
4000
 
      set          => $args{set},
4001
 
      NAME_lc      => defined($args{NAME_lc}) ? $args{NAME_lc} : 1,
4002
 
      dbh_set      => 0,
4003
 
      OptionParser => $o,
4004
 
      DSNParser    => $dp,
 
3687
      dsn             => $dsn,
 
3688
      dbh             => $args{dbh},
 
3689
      dsn_name        => $dp->as_string($dsn, [qw(h P S)]),
 
3690
      hostname        => '',
 
3691
      set             => $args{set},
 
3692
      NAME_lc         => defined($args{NAME_lc}) ? $args{NAME_lc} : 1,
 
3693
      dbh_set         => 0,
 
3694
      OptionParser    => $o,
 
3695
      DSNParser       => $dp,
4005
3696
      is_cluster_node => undef,
 
3697
      parent          => $args{parent},
4006
3698
   };
4007
3699
 
4008
3700
   return bless $self, $class;
4009
3701
}
4010
3702
 
4011
3703
sub connect {
4012
 
   my ( $self ) = @_;
 
3704
   my ( $self, %opts ) = @_;
4013
3705
   my $dsn = $self->{dsn};
4014
3706
   my $dp  = $self->{DSNParser};
4015
3707
   my $o   = $self->{OptionParser};
4020
3712
         $dsn->{p} = OptionParser::prompt_noecho("Enter MySQL password: ");
4021
3713
         $self->{asked_for_pass} = 1;
4022
3714
      }
4023
 
      $dbh = $dp->get_dbh($dp->get_cxn_params($dsn),  { AutoCommit => 1 });
 
3715
      $dbh = $dp->get_dbh(
 
3716
         $dp->get_cxn_params($dsn),
 
3717
         {
 
3718
            AutoCommit => 1,
 
3719
            %opts,
 
3720
         },
 
3721
      );
4024
3722
   }
4025
 
   PTDEBUG && _d($dbh, 'Connected dbh to', $self->{name});
4026
3723
 
4027
 
   return $self->set_dbh($dbh);
 
3724
   $dbh = $self->set_dbh($dbh);
 
3725
   PTDEBUG && _d($dbh, 'Connected dbh to', $self->{hostname},$self->{dsn_name});
 
3726
   return $dbh;
4028
3727
}
4029
3728
 
4030
3729
sub set_dbh {
4047
3746
      $self->{hostname} = $hostname;
4048
3747
   }
4049
3748
 
 
3749
   if ( $self->{parent} ) {
 
3750
      PTDEBUG && _d($dbh, 'Setting InactiveDestroy=1 in parent');
 
3751
      $dbh->{InactiveDestroy} = 1;
 
3752
   }
 
3753
 
4050
3754
   if ( my $set = $self->{set}) {
4051
3755
      $set->($dbh);
4052
3756
   }
4056
3760
   return $dbh;
4057
3761
}
4058
3762
 
 
3763
sub lost_connection {
 
3764
   my ($self, $e) = @_;
 
3765
   return 0 unless $e;
 
3766
   return $e =~ m/MySQL server has gone away/
 
3767
       || $e =~ m/Lost connection to MySQL server/;
 
3768
}
 
3769
 
4059
3770
sub dbh {
4060
3771
   my ($self) = @_;
4061
3772
   return $self->{dbh};
4074
3785
 
4075
3786
sub DESTROY {
4076
3787
   my ($self) = @_;
4077
 
   if ( $self->{dbh}
4078
 
         && blessed($self->{dbh})
4079
 
         && $self->{dbh}->can("disconnect") ) {
4080
 
      PTDEBUG && _d('Disconnecting dbh', $self->{dbh}, $self->{name});
 
3788
 
 
3789
   PTDEBUG && _d('Destroying cxn');
 
3790
 
 
3791
   if ( $self->{parent} ) {
 
3792
      PTDEBUG && _d($self->{dbh}, 'Not disconnecting dbh in parent');
 
3793
   }
 
3794
   elsif ( $self->{dbh}
 
3795
           && blessed($self->{dbh})
 
3796
           && $self->{dbh}->can("disconnect") )
 
3797
   {
 
3798
      PTDEBUG && _d($self->{dbh}, 'Disconnecting dbh on', $self->{hostname},
 
3799
         $self->{dsn_name});
4081
3800
      $self->{dbh}->disconnect();
4082
3801
   }
 
3802
 
4083
3803
   return;
4084
3804
}
4085
3805
 
5563
5283
   return $self->{one_nibble};
5564
5284
}
5565
5285
 
5566
 
sub chunk_size {
 
5286
sub limit {
5567
5287
   my ($self) = @_;
5568
 
   return $self->{limit} + 1;
 
5288
   return $self->{limit};
5569
5289
}
5570
5290
 
5571
5291
sub set_chunk_size {
7158
6878
{
7159
6879
package VersionCheck;
7160
6880
 
 
6881
 
7161
6882
use strict;
7162
6883
use warnings FATAL => 'all';
7163
6884
use English qw(-no_match_vars);
7164
6885
 
7165
6886
use constant PTDEBUG => $ENV{PTDEBUG} || 0;
7166
6887
 
7167
 
use Data::Dumper   qw();
7168
 
use Digest::MD5    qw(md5_hex);
7169
 
use Sys::Hostname  qw(hostname);
7170
 
use Fcntl          qw(:DEFAULT);
 
6888
use Data::Dumper;
 
6889
local $Data::Dumper::Indent    = 1;
 
6890
local $Data::Dumper::Sortkeys  = 1;
 
6891
local $Data::Dumper::Quotekeys = 0;
 
6892
 
 
6893
use Digest::MD5 qw(md5_hex);
 
6894
use Sys::Hostname qw(hostname);
7171
6895
use File::Basename qw();
7172
6896
use File::Spec;
7173
 
 
7174
 
my $dir              = File::Spec->tmpdir();
7175
 
my $check_time_file  = File::Spec->catfile($dir,'percona-toolkit-version-check');
7176
 
my $check_time_limit = 60 * 60 * 24;  # one day
7177
 
 
7178
 
sub Dumper {
7179
 
   local $Data::Dumper::Indent    = 1;
7180
 
   local $Data::Dumper::Sortkeys  = 1;
7181
 
   local $Data::Dumper::Quotekeys = 0;
7182
 
 
7183
 
   Data::Dumper::Dumper(@_);
7184
 
}
7185
 
 
7186
 
local $EVAL_ERROR;
 
6897
use FindBin qw();
 
6898
 
7187
6899
eval {
7188
6900
   require Percona::Toolkit;
7189
6901
   require HTTPMicro;
7190
6902
};
7191
6903
 
 
6904
{
 
6905
   my $file    = 'percona-version-check';
 
6906
   my $home    = $ENV{HOME} || $ENV{HOMEPATH} || $ENV{USERPROFILE} || '.';
 
6907
   my @vc_dirs = (
 
6908
      '/etc/percona',
 
6909
      '/etc/percona-toolkit',
 
6910
      '/tmp',
 
6911
      "$home",
 
6912
   );
 
6913
 
 
6914
   sub version_check_file {
 
6915
      foreach my $dir ( @vc_dirs ) {
 
6916
         if ( -d $dir && -w $dir ) {
 
6917
            PTDEBUG && _d('Version check file', $file, 'in', $dir);
 
6918
            return $dir . '/' . $file;
 
6919
         }
 
6920
      }
 
6921
      PTDEBUG && _d('Version check file', $file, 'in', $ENV{PWD});
 
6922
      return $file;  # in the CWD
 
6923
   } 
 
6924
}
 
6925
 
 
6926
sub version_check_time_limit {
 
6927
   return 60 * 60 * 24;  # one day
 
6928
}
 
6929
 
 
6930
 
7192
6931
sub version_check {
7193
 
   my %args      = @_;
7194
 
   my @instances = $args{instances} ? @{ $args{instances} } : ();
7195
 
 
7196
 
   if (exists $ENV{PERCONA_VERSION_CHECK} && !$ENV{PERCONA_VERSION_CHECK}) {
7197
 
      warn '--version-check is disabled by the PERCONA_VERSION_CHECK ',
7198
 
                   "environment variable.\n\n";
7199
 
      return;
 
6932
   my (%args) = @_;
 
6933
 
 
6934
   my $instances = $args{instances} || [];
 
6935
   my $instances_to_check;
 
6936
 
 
6937
   PTDEBUG && _d('FindBin::Bin:', $FindBin::Bin);
 
6938
   if ( !$args{force} ) {
 
6939
      if ( $FindBin::Bin
 
6940
           && (-d "$FindBin::Bin/../.bzr" || -d "$FindBin::Bin/../../.bzr") ) {
 
6941
         PTDEBUG && _d("$FindBin::Bin/../.bzr disables --version-check");
 
6942
         return;
 
6943
      }
7200
6944
   }
7201
6945
 
7202
 
   $args{protocol} ||= 'https';
7203
 
   my @protocols = $args{protocol} eq 'auto'
7204
 
                 ? qw(https http)
7205
 
                 : $args{protocol};
7206
 
   
7207
 
   my $instances_to_check = [];
7208
 
   my $time               = int(time());
7209
6946
   eval {
7210
 
      foreach my $instance ( @instances ) {
7211
 
         my ($name, $id) = _generate_identifier($instance);
 
6947
      foreach my $instance ( @$instances ) {
 
6948
         my ($name, $id) = get_instance_id($instance);
7212
6949
         $instance->{name} = $name;
7213
6950
         $instance->{id}   = $id;
7214
6951
      }
7215
6952
 
7216
 
      my $time_to_check;
7217
 
      ($time_to_check, $instances_to_check)
7218
 
         = time_to_check($check_time_file, \@instances, $time);
7219
 
      if ( !$time_to_check ) {
7220
 
         warn 'It is not time to --version-check again; ',
7221
 
                      "only 1 check per day.\n\n";
7222
 
         return;
7223
 
      }
7224
 
 
7225
 
      my $advice;
7226
 
      my $e;
7227
 
      for my $protocol ( @protocols ) {
7228
 
         $advice = eval { pingback(
7229
 
            url       => $ENV{PERCONA_VERSION_CHECK_URL} || "$protocol://v.percona.com",
7230
 
            instances => $instances_to_check,
7231
 
            protocol  => $protocol,
7232
 
         ) };
7233
 
         last if !$advice && !$EVAL_ERROR;
7234
 
         $e ||= $EVAL_ERROR;
7235
 
      }
 
6953
      push @$instances, { name => 'system', id => 0 };
 
6954
 
 
6955
      $instances_to_check = get_instances_to_check(
 
6956
         instances => $instances,
 
6957
         vc_file   => $args{vc_file},  # testing
 
6958
         now       => $args{now},      # testing
 
6959
      );
 
6960
      PTDEBUG && _d(scalar @$instances_to_check, 'instances to check');
 
6961
      return unless @$instances_to_check;
 
6962
 
 
6963
      my $protocol = 'https';  # optimistic, but...
 
6964
      eval { require IO::Socket::SSL; };
 
6965
      if ( $EVAL_ERROR ) {
 
6966
         PTDEBUG && _d($EVAL_ERROR);
 
6967
         $protocol = 'http';
 
6968
      }
 
6969
      PTDEBUG && _d('Using', $protocol);
 
6970
 
 
6971
      my $advice = pingback(
 
6972
         instances => $instances_to_check,
 
6973
         protocol  => $protocol,
 
6974
         url       => $args{url}                       # testing
 
6975
                   || $ENV{PERCONA_VERSION_CHECK_URL}  # testing
 
6976
                   || "$protocol://v.percona.com",
 
6977
      );
7236
6978
      if ( $advice ) {
7237
 
         print "# Percona suggests these upgrades:\n";
 
6979
         PTDEBUG && _d('Advice:', Dumper($advice));
 
6980
         if ( scalar @$advice > 1) {
 
6981
            print "\n# " . scalar @$advice . " software updates are "
 
6982
               . "available:\n";
 
6983
         }
 
6984
         else {
 
6985
            print "\n# A software update is available:\n";
 
6986
         }
7238
6987
         print join("\n", map { "#   * $_" } @$advice), "\n\n";
7239
6988
      }
 
6989
   };
 
6990
   if ( $EVAL_ERROR ) {
 
6991
      PTDEBUG && _d('Version check failed:', $EVAL_ERROR);
 
6992
   }
 
6993
 
 
6994
   if ( @$instances_to_check ) {
 
6995
      eval {
 
6996
         update_check_times(
 
6997
            instances => $instances_to_check,
 
6998
            vc_file   => $args{vc_file},  # testing
 
6999
            now       => $args{now},      # testing
 
7000
         );
 
7001
      };
 
7002
      if ( $EVAL_ERROR ) {
 
7003
         PTDEBUG && _d('Error updating version check file:', $EVAL_ERROR);
 
7004
      }
 
7005
   }
 
7006
 
 
7007
   if ( $ENV{PTDEBUG_VERSION_CHECK} ) {
 
7008
      warn "Exiting because the PTDEBUG_VERSION_CHECK "
 
7009
         . "environment variable is defined.\n";
 
7010
      exit 255;
 
7011
   }
 
7012
 
 
7013
   return;
 
7014
}
 
7015
 
 
7016
sub get_instances_to_check {
 
7017
   my (%args) = @_;
 
7018
 
 
7019
   my $instances = $args{instances};
 
7020
   my $now       = $args{now}     || int(time);
 
7021
   my $vc_file   = $args{vc_file} || version_check_file();
 
7022
 
 
7023
   if ( !-f $vc_file ) {
 
7024
      PTDEBUG && _d('Version check file', $vc_file, 'does not exist;',
 
7025
         'version checking all instances');
 
7026
      return $instances;
 
7027
   }
 
7028
 
 
7029
   open my $fh, '<', $vc_file or die "Cannot open $vc_file: $OS_ERROR";
 
7030
   chomp(my $file_contents = do { local $/ = undef; <$fh> });
 
7031
   PTDEBUG && _d('Version check file', $vc_file, 'contents:', $file_contents);
 
7032
   close $fh;
 
7033
   my %last_check_time_for = $file_contents =~ /^([^,]+),(.+)$/mg;
 
7034
 
 
7035
   my $check_time_limit = version_check_time_limit();
 
7036
   my @instances_to_check;
 
7037
   foreach my $instance ( @$instances ) {
 
7038
      my $last_check_time = $last_check_time_for{ $instance->{id} };
 
7039
      PTDEBUG && _d('Intsance', $instance->{id}, 'last checked',
 
7040
         $last_check_time, 'now', $now, 'diff', $now - ($last_check_time || 0),
 
7041
         'hours until next check',
 
7042
         sprintf '%.2f',
 
7043
            ($check_time_limit - ($now - ($last_check_time || 0))) / 3600);
 
7044
      if ( !defined $last_check_time
 
7045
           || ($now - $last_check_time) >= $check_time_limit ) {
 
7046
         PTDEBUG && _d('Time to check', Dumper($instance));
 
7047
         push @instances_to_check, $instance;
 
7048
      }
 
7049
   }
 
7050
 
 
7051
   return \@instances_to_check;
 
7052
}
 
7053
 
 
7054
sub update_check_times {
 
7055
   my (%args) = @_;
 
7056
 
 
7057
   my $instances = $args{instances};
 
7058
   my $now       = $args{now}     || int(time);
 
7059
   my $vc_file   = $args{vc_file} || version_check_file();
 
7060
   PTDEBUG && _d('Updating last check time:', $now);
 
7061
 
 
7062
   open my $fh, '>', $vc_file or die "Cannot write to $vc_file: $OS_ERROR";
 
7063
   foreach my $instance ( sort { $a->{id} cmp $b->{id} } @$instances ) {
 
7064
      PTDEBUG && _d('Updated:', Dumper($instance));
 
7065
      print { $fh } $instance->{id} . ',' . $now . "\n";
 
7066
   }
 
7067
   close $fh;
 
7068
 
 
7069
   return;
 
7070
}
 
7071
 
 
7072
sub get_instance_id {
 
7073
   my ($instance) = @_;
 
7074
 
 
7075
   my $dbh = $instance->{dbh};
 
7076
   my $dsn = $instance->{dsn};
 
7077
 
 
7078
   my $sql = q{SELECT CONCAT(@@hostname, @@port)};
 
7079
   PTDEBUG && _d($sql);
 
7080
   my ($name) = eval { $dbh->selectrow_array($sql) };
 
7081
   if ( $EVAL_ERROR ) {
 
7082
      PTDEBUG && _d($EVAL_ERROR);
 
7083
      $sql = q{SELECT @@hostname};
 
7084
      PTDEBUG && _d($sql);
 
7085
      ($name) = eval { $dbh->selectrow_array($sql) };
 
7086
      if ( $EVAL_ERROR ) {
 
7087
         PTDEBUG && _d($EVAL_ERROR);
 
7088
         $name = ($dsn->{h} || 'localhost') . ($dsn->{P} || 3306);
 
7089
      }
7240
7090
      else {
7241
 
         die $e if $e;
7242
 
         print "# No suggestions at this time.\n\n";
7243
 
         ($ENV{PTVCDEBUG} || PTDEBUG )
7244
 
            && _d('--version-check worked, but there were no suggestions');
 
7091
         $sql = q{SHOW VARIABLES LIKE 'port'};
 
7092
         PTDEBUG && _d($sql);
 
7093
         my (undef, $port) = eval { $dbh->selectrow_array($sql) };
 
7094
         PTDEBUG && _d('port:', $port);
 
7095
         $name .= $port || '';
7245
7096
      }
7246
 
   };
7247
 
   if ( $EVAL_ERROR ) {
7248
 
      warn "Error doing --version-check: $EVAL_ERROR";
7249
 
   }
7250
 
   else {
7251
 
      update_checks_file($check_time_file, $instances_to_check, $time);
7252
 
   }
7253
 
   
7254
 
   return;
 
7097
   }
 
7098
   my $id = md5_hex($name);
 
7099
 
 
7100
   PTDEBUG && _d('MySQL instance:', $id, $name, $dsn);
 
7101
 
 
7102
   return $name, $id;
7255
7103
}
7256
7104
 
 
7105
 
7257
7106
sub pingback {
7258
7107
   my (%args) = @_;
7259
 
   my @required_args = qw(url);
 
7108
   my @required_args = qw(url instances);
7260
7109
   foreach my $arg ( @required_args ) {
7261
7110
      die "I need a $arg arugment" unless $args{$arg};
7262
7111
   }
7263
 
   my ($url) = @args{@required_args};
7264
 
 
7265
 
   my ($instances, $ua) = @args{qw(instances ua)};
7266
 
 
7267
 
   $ua ||= HTTPMicro->new( timeout => 5 );
 
7112
   my $url       = $args{url};
 
7113
   my $instances = $args{instances};
 
7114
 
 
7115
   my $ua = $args{ua} || HTTPMicro->new( timeout => 3 );
7268
7116
 
7269
7117
   my $response = $ua->request('GET', $url);
7270
 
   ($ENV{PTVCDEBUG} || PTDEBUG) && _d('Server response:', Dumper($response));
 
7118
   PTDEBUG && _d('Server response:', Dumper($response));
7271
7119
   die "No response from GET $url"
7272
7120
      if !$response;
7273
7121
   die("GET on $url returned HTTP status $response->{status}; expected 200\n",
7275
7123
   die("GET on $url did not return any programs to check")
7276
7124
      if !$response->{content};
7277
7125
 
7278
 
   my $items = __PACKAGE__->parse_server_response(
 
7126
   my $items = parse_server_response(
7279
7127
      response => $response->{content}
7280
7128
   );
7281
7129
   die "Failed to parse server requested programs: $response->{content}"
7282
7130
      if !scalar keys %$items;
7283
7131
      
7284
 
   my $versions = __PACKAGE__->get_versions(
 
7132
   my $versions = get_versions(
7285
7133
      items     => $items,
7286
7134
      instances => $instances,
7287
7135
   );
7298
7146
      headers => { "X-Percona-Toolkit-Tool" => File::Basename::basename($0) },
7299
7147
      content => $client_content,
7300
7148
   };
7301
 
   if ( $ENV{PTVCDEBUG} || PTDEBUG ) {
7302
 
      _d('Client response:', Dumper($client_response));
7303
 
   }
 
7149
   PTDEBUG && _d('Client response:', Dumper($client_response));
7304
7150
 
7305
7151
   $response = $ua->request('POST', $url, $client_response);
7306
7152
   PTDEBUG && _d('Server suggestions:', Dumper($response));
7311
7157
 
7312
7158
   return unless $response->{content};
7313
7159
 
7314
 
   $items = __PACKAGE__->parse_server_response(
 
7160
   $items = parse_server_response(
7315
7161
      response   => $response->{content},
7316
7162
      split_vars => 0,
7317
7163
   );
7324
7170
   return \@suggestions;
7325
7171
}
7326
7172
 
7327
 
sub time_to_check {
7328
 
   my ($file, $instances, $time) = @_;
7329
 
   die "I need a file argument" unless $file;
7330
 
   $time ||= int(time());  # current time
7331
 
 
7332
 
   if ( @$instances ) {
7333
 
      my $instances_to_check = instances_to_check($file, $instances, $time);
7334
 
      return scalar @$instances_to_check, $instances_to_check;
7335
 
   }
7336
 
 
7337
 
   return 1 if !-f $file;
7338
 
   
7339
 
   my $mtime  = (stat $file)[9];
7340
 
   if ( !defined $mtime ) {
7341
 
      PTDEBUG && _d('Error getting modified time of', $file);
7342
 
      return 1;
7343
 
   }
7344
 
   PTDEBUG && _d('time=', $time, 'mtime=', $mtime);
7345
 
   if ( ($time - $mtime) > $check_time_limit ) {
7346
 
      return 1;
7347
 
   }
7348
 
 
7349
 
   return 0;
7350
 
}
7351
 
 
7352
 
sub instances_to_check {
7353
 
   my ($file, $instances, $time, %args) = @_;
7354
 
 
7355
 
   my $file_contents = '';
7356
 
   if (open my $fh, '<', $file) {
7357
 
      chomp($file_contents = do { local $/ = undef; <$fh> });
7358
 
      close $fh;
7359
 
   }
7360
 
   my %cached_instances = $file_contents =~ /^([^,]+),(.+)$/mg;
7361
 
 
7362
 
   my @instances_to_check;
7363
 
   foreach my $instance ( @$instances ) {
7364
 
      my $mtime = $cached_instances{ $instance->{id} };
7365
 
      if ( !$mtime || (($time - $mtime) > $check_time_limit) ) {
7366
 
         if ( $ENV{PTVCDEBUG} || PTDEBUG ) {
7367
 
            _d('Time to check MySQL instance', $instance->{name});
7368
 
         }
7369
 
         push @instances_to_check, $instance;
7370
 
         $cached_instances{ $instance->{id} } = $time;
7371
 
      }
7372
 
   }
7373
 
 
7374
 
   if ( $args{update_file} ) {
7375
 
      open my $fh, '>', $file or die "Cannot open $file for writing: $OS_ERROR";
7376
 
      while ( my ($id, $time) = each %cached_instances ) {
7377
 
         print { $fh } "$id,$time\n";
7378
 
      }
7379
 
      close $fh or die "Cannot close $file: $OS_ERROR";
7380
 
   }
7381
 
 
7382
 
   return \@instances_to_check;
7383
 
}
7384
 
 
7385
 
sub update_checks_file {
7386
 
   my ($file, $instances, $time) = @_;
7387
 
 
7388
 
   if ( !-f $file ) {
7389
 
      if ( $ENV{PTVCDEBUG} || PTDEBUG ) {
7390
 
         _d('Creating time limit file', $file);
7391
 
      }
7392
 
      _touch($file);
7393
 
   }
7394
 
 
7395
 
   if ( $instances && @$instances ) {
7396
 
      instances_to_check($file, $instances, $time, update_file => 1);
7397
 
      return;
7398
 
   }
7399
 
 
7400
 
   my $mtime  = (stat $file)[9];
7401
 
   if ( !defined $mtime ) {
7402
 
      _touch($file);
7403
 
      return;
7404
 
   }
7405
 
   PTDEBUG && _d('time=', $time, 'mtime=', $mtime);
7406
 
   if ( ($time - $mtime) > $check_time_limit ) {
7407
 
      _touch($file);
7408
 
      return;
7409
 
   }
7410
 
 
7411
 
   return;
7412
 
}
7413
 
 
7414
 
sub _touch {
7415
 
   my ($file) = @_;
7416
 
   sysopen my $fh, $file, O_WRONLY|O_CREAT
7417
 
      or die "Cannot create $file : $!";
7418
 
   close $fh or die "Cannot close $file : $!";
7419
 
   utime(undef, undef, $file);
7420
 
}
7421
 
 
7422
 
sub _generate_identifier {
7423
 
   my $instance = shift;
7424
 
   my $dbh      = $instance->{dbh};
7425
 
   my $dsn      = $instance->{dsn};
7426
 
 
7427
 
   my $sql = q{SELECT CONCAT(@@hostname, @@port)};
7428
 
   PTDEBUG && _d($sql);
7429
 
   my ($name) = eval { $dbh->selectrow_array($sql) };
7430
 
   if ( $EVAL_ERROR ) {
7431
 
      PTDEBUG && _d($EVAL_ERROR);
7432
 
      $sql = q{SELECT @@hostname};
7433
 
      PTDEBUG && _d($sql);
7434
 
      ($name) = eval { $dbh->selectrow_array($sql) };
7435
 
      if ( $EVAL_ERROR ) {
7436
 
         PTDEBUG && _d($EVAL_ERROR);
7437
 
         $name = ($dsn->{h} || 'localhost') . ($dsn->{P} || 3306);
7438
 
      }
7439
 
      else {
7440
 
         $sql = q{SHOW VARIABLES LIKE 'port'};
7441
 
         PTDEBUG && _d($sql);
7442
 
         my (undef, $port) = eval { $dbh->selectrow_array($sql) };
7443
 
         PTDEBUG && _d('port:', $port);
7444
 
         $name .= $port || '';
7445
 
      }
7446
 
   }
7447
 
   my $id = md5_hex($name);
7448
 
 
7449
 
   if ( $ENV{PTVCDEBUG} || PTDEBUG ) {
7450
 
      _d('MySQL instance', $name, 'is', $id);
7451
 
   }
7452
 
 
7453
 
   return $name, $id;
7454
 
}
7455
 
 
7456
7173
sub encode_client_response {
7457
7174
   my (%args) = @_;
7458
7175
   my @required_args = qw(items versions general_id);
7479
7196
   return $client_response;
7480
7197
}
7481
7198
 
7482
 
sub validate_options {
7483
 
   my ($o) = @_;
7484
 
 
7485
 
   return if !$o->got('version-check');
7486
 
 
7487
 
   my $value  = $o->get('version-check');
7488
 
   my @values = split /, /,
7489
 
                $o->read_para_after(__FILE__, qr/MAGIC_version_check/);
7490
 
   chomp(@values);
7491
 
                
7492
 
   return if grep { $value eq $_ } @values;
7493
 
   $o->save_error("--version-check invalid value $value.  Accepted values are "
7494
 
                . join(", ", @values[0..$#values-1]) . " and $values[-1]" );
7495
 
}
7496
 
 
7497
7199
sub parse_server_response {
7498
 
   my ($self, %args) = @_;
 
7200
   my (%args) = @_;
7499
7201
   my @required_args = qw(response);
7500
7202
   foreach my $arg ( @required_args ) {
7501
7203
      die "I need a $arg arugment" unless $args{$arg};
7519
7221
   return \%items;
7520
7222
}
7521
7223
 
 
7224
my %sub_for_type = (
 
7225
   os_version          => \&get_os_version,
 
7226
   perl_version        => \&get_perl_version,
 
7227
   perl_module_version => \&get_perl_module_version,
 
7228
   mysql_variable      => \&get_mysql_variable,
 
7229
   bin_version         => \&get_bin_version,
 
7230
);
 
7231
 
 
7232
sub valid_item {
 
7233
   my ($item) = @_;
 
7234
   return unless $item;
 
7235
   if ( !exists $sub_for_type{ $item->{type} } ) {
 
7236
      PTDEBUG && _d('Invalid type:', $item->{type});
 
7237
      return 0;
 
7238
   }
 
7239
   return 1;
 
7240
}
 
7241
 
7522
7242
sub get_versions {
7523
 
   my ($self, %args) = @_;
 
7243
   my (%args) = @_;
7524
7244
   my @required_args = qw(items);
7525
7245
   foreach my $arg ( @required_args ) {
7526
7246
      die "I need a $arg arugment" unless $args{$arg};
7529
7249
 
7530
7250
   my %versions;
7531
7251
   foreach my $item ( values %$items ) {
7532
 
      next unless $self->valid_item($item);
7533
 
 
 
7252
      next unless valid_item($item);
7534
7253
      eval {
7535
 
         my $func    = 'get_' . $item->{type};
7536
 
         my $version = $self->$func(
 
7254
         my $version = $sub_for_type{ $item->{type} }->(
7537
7255
            item      => $item,
7538
7256
            instances => $args{instances},
7539
7257
         );
7550
7268
   return \%versions;
7551
7269
}
7552
7270
 
7553
 
sub valid_item {
7554
 
   my ($self, $item) = @_;
7555
 
   return unless $item;
7556
 
 
7557
 
   if ( ($item->{type} || '') !~ m/
7558
 
         ^(?:
7559
 
             os_version
7560
 
            |perl_version
7561
 
            |perl_module_version
7562
 
            |mysql_variable
7563
 
            |bin_version
7564
 
         )$/x ) {
7565
 
      PTDEBUG && _d('Invalid type:', $item->{type});
7566
 
      return;
7567
 
   }
7568
 
 
7569
 
   return 1;
7570
 
}
7571
7271
 
7572
7272
sub get_os_version {
7573
 
   my ($self) = @_;
7574
 
 
7575
7273
   if ( $OSNAME eq 'MSWin32' ) {
7576
7274
      require Win32;
7577
7275
      return Win32::GetOSDisplayName();
7647
7345
}
7648
7346
 
7649
7347
sub get_perl_version {
7650
 
   my ($self, %args) = @_;
 
7348
   my (%args) = @_;
7651
7349
   my $item = $args{item};
7652
7350
   return unless $item;
7653
7351
 
7657
7355
}
7658
7356
 
7659
7357
sub get_perl_module_version {
7660
 
   my ($self, %args) = @_;
 
7358
   my (%args) = @_;
7661
7359
   my $item = $args{item};
7662
7360
   return unless $item;
7663
7361
 
7664
 
   my $var          = $item->{item} . '::VERSION';
7665
 
   my $version      = _get_scalar($var);
7666
 
   PTDEBUG && _d('Perl version for', $var, '=', "$version");
7667
 
 
7668
 
   return $version ? "$version" : $version;
7669
 
}
7670
 
 
7671
 
sub _get_scalar {
7672
 
   no strict;
7673
 
   return ${*{shift()}};
 
7362
   my $var     = '$' . $item->{item} . '::VERSION';
 
7363
   my $version = eval "use $item->{item}; $var;";
 
7364
   PTDEBUG && _d('Perl version for', $var, '=', $version);
 
7365
   return $version;
7674
7366
}
7675
7367
 
7676
7368
sub get_mysql_variable {
7677
 
   my $self = shift;
7678
 
   return $self->_get_from_mysql(
 
7369
   return get_from_mysql(
7679
7370
      show => 'VARIABLES',
7680
7371
      @_,
7681
7372
   );
7682
7373
}
7683
7374
 
7684
 
sub _get_from_mysql {
7685
 
   my ($self, %args) = @_;
 
7375
sub get_from_mysql {
 
7376
   my (%args) = @_;
7686
7377
   my $show      = $args{show};
7687
7378
   my $item      = $args{item};
7688
7379
   my $instances = $args{instances};
7689
7380
   return unless $show && $item;
7690
7381
 
7691
7382
   if ( !$instances || !@$instances ) {
7692
 
      if ( $ENV{PTVCDEBUG} || PTDEBUG ) {
7693
 
         _d('Cannot check', $item, 'because there are no MySQL instances');
7694
 
      }
 
7383
      PTDEBUG && _d('Cannot check', $item,
 
7384
         'because there are no MySQL instances');
7695
7385
      return;
7696
7386
   }
7697
7387
 
7698
7388
   my @versions;
7699
7389
   my %version_for;
7700
7390
   foreach my $instance ( @$instances ) {
 
7391
      next unless $instance->{id};  # special system instance has id=0
7701
7392
      my $dbh = $instance->{dbh};
7702
7393
      local $dbh->{FetchHashKeyName} = 'NAME_lc';
7703
7394
      my $sql = qq/SHOW $show/;
7712
7403
            'on', $instance->{name});
7713
7404
         push @versions, $version;
7714
7405
      }
7715
 
 
7716
7406
      $version_for{ $instance->{id} } = join(' ', @versions);
7717
7407
   }
7718
7408
 
7720
7410
}
7721
7411
 
7722
7412
sub get_bin_version {
7723
 
   my ($self, %args) = @_;
 
7413
   my (%args) = @_;
7724
7414
   my $item = $args{item};
7725
7415
   my $cmd  = $item->{item};
7726
7416
   return unless $cmd;
7846
7536
use Percona::Toolkit;
7847
7537
use constant PTDEBUG => $ENV{PTDEBUG} || 0;
7848
7538
 
 
7539
use List::Util qw(max);
7849
7540
use Time::HiRes qw(time sleep);
7850
7541
use Data::Dumper;
7851
7542
$Data::Dumper::Indent    = 1;
7881
7572
   $o->get_opts();
7882
7573
 
7883
7574
   my $dp = $o->DSNParser();
7884
 
   $dp->prop('set-vars', $o->get('set-vars'));
 
7575
   $dp->prop('set-vars', $o->set_vars());
7885
7576
 
7886
7577
   # The original table, i.e. the one being altered, must be specified
7887
7578
   # on the command line via the DSN.
7928
7619
         . $n_chunk_index_cols);
7929
7620
   }
7930
7621
 
 
7622
   my $tries = eval {
 
7623
      validate_tries($o);
 
7624
   };
 
7625
   if ( $EVAL_ERROR ) {
 
7626
      $o->save_error($EVAL_ERROR);
 
7627
   }
 
7628
 
7931
7629
   if ( !$o->get('help') ) {
7932
7630
      if ( @ARGV ) {
7933
7631
         $o->save_error('Specify only one DSN on the command line');
7968
7666
      $o->save_error("Invalid --recursion-method: $EVAL_ERROR")
7969
7667
   }
7970
7668
 
7971
 
   VersionCheck::validate_options($o);
7972
 
   
7973
7669
   $o->usage_or_errors(); 
7974
7670
 
7975
7671
   if ( $o->get('quiet') ) {
7988
7684
   # ########################################################################
7989
7685
   my $set_on_connect = sub {
7990
7686
      my ($dbh) = @_;
7991
 
 
7992
 
      # See the same code in pt-table-checksum.
7993
 
      my $lock_wait_timeout = $o->get('lock-wait-timeout');
7994
 
      my $set_lwt = "SET SESSION innodb_lock_wait_timeout=$lock_wait_timeout";
7995
 
      PTDEBUG && _d($set_lwt);
7996
 
      eval {
7997
 
         $dbh->do($set_lwt);
7998
 
      };
7999
 
      if ( $EVAL_ERROR ) {
8000
 
         PTDEBUG && _d($EVAL_ERROR);
8001
 
         # Get the server's current value.
8002
 
         my $sql = "SHOW SESSION VARIABLES LIKE 'innodb_lock_wait_timeout'";
8003
 
         PTDEBUG && _d($dbh, $sql);
8004
 
         my (undef, $curr_lwt) = $dbh->selectrow_array($sql);
8005
 
         PTDEBUG && _d('innodb_lock_wait_timeout on server:', $curr_lwt);
8006
 
         if ( !defined $curr_lwt ) {
8007
 
            PTDEBUG && _d('innodb_lock_wait_timeout does not exist;',
8008
 
               'InnoDB is probably disabled');
8009
 
         }
8010
 
         elsif ( $curr_lwt > $lock_wait_timeout ) { 
8011
 
            warn "Failed to $set_lwt: $EVAL_ERROR\n"
8012
 
               . "The current innodb_lock_wait_timeout value "
8013
 
               . "$curr_lwt is greater than the --lock-wait-timeout "
8014
 
               . "value $lock_wait_timeout and the variable cannot be "
8015
 
               . "changed.  innodb_lock_wait_timeout is only dynamic when "
8016
 
               . "using the InnoDB plugin.  To prevent this warning, either "
8017
 
               . "specify --lock-wait-time=$curr_lwt, or manually set "
8018
 
               . "innodb_lock_wait_timeout to a value less than or equal "
8019
 
               . "to $lock_wait_timeout and restart MySQL.\n";
8020
 
         }
8021
 
      }
 
7687
      return;
8022
7688
   };
8023
7689
 
8024
7690
   # Do not call "new Cxn(" directly; use this sub so that set_on_connect
8029
7695
   # and it might be good to just make a convention of it.
8030
7696
   my $make_cxn = sub {
8031
7697
      my (%args) = @_;
8032
 
      my $cxn = new Cxn(
 
7698
      my $cxn = Cxn->new(
8033
7699
         %args,
8034
7700
         DSNParser    => $dp,
8035
7701
         OptionParser => $o,
8042
7708
      return $cxn;
8043
7709
   };
8044
7710
 
8045
 
   my $cxn = $make_cxn->(dsn => $dsn);
 
7711
   my $cxn     = $make_cxn->(dsn => $dsn);
 
7712
   my $aux_cxn = $make_cxn->(dsn => $dsn, prev_dsn => $dsn);
8046
7713
 
8047
7714
   my $cluster = Percona::XtraDB::Cluster->new;
8048
7715
   if ( $cluster->is_cluster_node($cxn) ) {
8088
7755
   my $lock_in_share_mode = $server_version < '5.1' ? 0 : 1;
8089
7756
 
8090
7757
   # ########################################################################
 
7758
   # Create --plugin.
 
7759
   # ########################################################################   
 
7760
   my $plugin;
 
7761
   if ( my $file = $o->get('plugin') ) {
 
7762
      die "--plugin file $file does not exist\n" unless -f $file;
 
7763
      eval {
 
7764
         require $file;
 
7765
      };
 
7766
      die "Error loading --plugin $file: $EVAL_ERROR" if $EVAL_ERROR;
 
7767
      eval {
 
7768
         $plugin = pt_online_schema_change_plugin->new(
 
7769
            cxn     => $cxn,
 
7770
            aux_cxn => $aux_cxn,
 
7771
            alter   => $o->get('alter'),
 
7772
            execute => $o->get('execute'),
 
7773
            dry_run => $o->get('dry-run'),
 
7774
            print   => $o->get('print'),
 
7775
            quiet   => $o->get('quiet'),
 
7776
            Quoter  => $q,
 
7777
         );
 
7778
      };
 
7779
      die "Error creating --plugin: $EVAL_ERROR" if $EVAL_ERROR;
 
7780
      print "Created plugin from $file.\n";
 
7781
   }
 
7782
 
 
7783
   # ########################################################################
8091
7784
   # Setup lag and load monitors.
8092
7785
   # ########################################################################   
8093
7786
   my $slaves;         # all slaves that are found or specified
8257
7950
   # ########################################################################
8258
7951
   # Do the version-check
8259
7952
   # ########################################################################
8260
 
   if ( $o->get('version-check') ne 'off' && (!$o->has('quiet') || !$o->get('quiet')) ) {
 
7953
   if ( $o->get('version-check') && (!$o->has('quiet') || !$o->get('quiet')) ) {
8261
7954
      VersionCheck::version_check(
8262
 
         instances => [ map({ +{ dbh => $_->dbh(), dsn => $_->dsn() } }
8263
 
            $cxn, ($slaves ? @$slaves : ())) ],
8264
 
         protocol  => $o->get('version-check'),
 
7955
         force     => $o->got('version-check'),
 
7956
         instances => [
 
7957
            map (
 
7958
               { +{ dbh => $_->dbh(), dsn => $_->dsn() } }
 
7959
               $cxn, ($slaves ? @$slaves : ())
 
7960
            )
 
7961
         ],
8265
7962
      );
8266
7963
   }
8267
 
   
 
7964
 
8268
7965
   # ########################################################################
8269
7966
   # Setup and check the original table.
8270
7967
   # ########################################################################
8286
7983
   );
8287
7984
 
8288
7985
   # ########################################################################
 
7986
   # Print --tries.
 
7987
   # ########################################################################
 
7988
   print "Operation, tries, wait:\n";
 
7989
   {
 
7990
      my $fmt = "  %s, %s, %s\n";
 
7991
      foreach my $op ( sort keys %$tries ) {
 
7992
         printf $fmt, $op, $tries->{$op}->{tries}, $tries->{$op}->{wait};
 
7993
      }
 
7994
   }
 
7995
 
 
7996
   # ########################################################################
8289
7997
   # Get child tables of the original table, if necessary.
8290
7998
   # ########################################################################
8291
7999
   my $child_tables;
8426
8134
      return;
8427
8135
   };
8428
8136
 
 
8137
   # The 2nd to last cleanup task is printing the --statistics which
 
8138
   # may reveal something about the failure.
 
8139
   if ( $o->get('statistics') ) {
 
8140
      push @cleanup_tasks, sub {
 
8141
         my $n = max( map { length $_ } keys %stats );
 
8142
         my $fmt = "# %-${n}s %5s\n";
 
8143
         printf $fmt, 'Event', 'Count';
 
8144
         printf $fmt, ('=' x $n),'=====';
 
8145
         foreach my $event ( sort keys %stats ) {
 
8146
            printf $fmt,
 
8147
               $event, (defined $stats{$event} ? $stats{$event} : '?');
 
8148
         }
 
8149
      };
 
8150
   }
 
8151
 
8429
8152
   # ########################################################################
8430
8153
   # Check the --alter statement.
8431
8154
   # ########################################################################
8465
8188
      $daemon->make_PID_file();
8466
8189
   }
8467
8190
 
 
8191
   # ########################################################################
 
8192
   # Init the --plugin.
 
8193
   # ########################################################################   
 
8194
 
 
8195
   # --plugin hook
 
8196
   if ( $plugin && $plugin->can('init') ) {
 
8197
      $plugin->init(
 
8198
         orig_tbl       => $orig_tbl,
 
8199
         child_tables   => $child_tables,
 
8200
         renamed_cols   => $renamed_cols,
 
8201
         slaves         => $slaves,
 
8202
         slave_lag_cxns => $slave_lag_cxns,
 
8203
      );
 
8204
   }
 
8205
 
8468
8206
   # #####################################################################
8469
8207
   # Step 1: Create the new table.
8470
8208
   # #####################################################################
 
8209
 
 
8210
   # --plugin hook
 
8211
   if ( $plugin && $plugin->can('before_create_new_table') ) {
 
8212
      $plugin->before_create_new_table();
 
8213
   }
 
8214
 
8471
8215
   my $new_tbl;
8472
8216
   eval {
8473
8217
      $new_tbl = create_new_table(
8538
8282
                      );
8539
8283
   }
8540
8284
 
 
8285
   # --plugin hook
 
8286
   if ( $plugin && $plugin->can('after_create_new_table') ) {
 
8287
      $plugin->after_create_new_table(
 
8288
         new_tbl => $new_tbl,
 
8289
      );
 
8290
   }
 
8291
 
8541
8292
   # #####################################################################
8542
8293
   # Step 2: Alter the new, empty table.  This should be very quick,
8543
8294
   # or die if the user specified a bad alter statement.
8544
8295
   # #####################################################################
 
8296
 
 
8297
   # --plugin hook
 
8298
   if ( $plugin && $plugin->can('before_alter_new_table') ) {
 
8299
      $plugin->before_alter_new_table(
 
8300
         new_tbl => $new_tbl,
 
8301
      );
 
8302
   }
 
8303
 
8545
8304
   if ( my $alter = $o->get('alter') ) {
8546
8305
      print "Altering new table...\n";
8547
8306
      my $sql = "ALTER TABLE $new_tbl->{name} $alter";
8653
8412
         'columns', @$del_cols);
8654
8413
   }
8655
8414
 
 
8415
   # --plugin hook
 
8416
   if ( $plugin && $plugin->can('after_alter_new_table') ) {
 
8417
      $plugin->after_alter_new_table(
 
8418
         new_tbl => $new_tbl,
 
8419
         del_tbl => $del_tbl,
 
8420
      );
 
8421
   }
 
8422
 
8656
8423
   # ########################################################################
8657
8424
   # Step 3: Create the triggers to capture changes on the original table and
8658
8425
   # apply them to the new table.
8659
8426
   # ########################################################################
8660
8427
 
 
8428
   my $retry = new Retry();
 
8429
 
8661
8430
   # Drop the triggers.  We can save this cleanup task before
8662
8431
   # adding the triggers because if adding them fails, this will be
8663
8432
   # called which will drop whichever triggers were created.
8664
8433
   push @cleanup_tasks, sub {
8665
8434
      PTDEBUG && _d('Clean up triggers');
 
8435
      # --plugin hook
 
8436
      if ( $plugin && $plugin->can('before_drop_triggers') ) {
 
8437
         $plugin->before_drop_triggers(
 
8438
            oktorun           => $oktorun,
 
8439
            drop_trigger_sqls => \@drop_trigger_sqls,
 
8440
         );
 
8441
      }
 
8442
 
8666
8443
      if ( $oktorun ) {
8667
8444
         drop_triggers(
8668
8445
            tbl          => $orig_tbl,
8669
8446
            Cxn          => $cxn,
8670
8447
            Quoter       => $q,
8671
8448
            OptionParser => $o,
 
8449
            Retry        => $retry,
 
8450
            tries        => $tries,
 
8451
            stats        => \%stats,
8672
8452
         );
8673
8453
      }
8674
8454
      else {
8678
8458
      }
8679
8459
   };
8680
8460
 
 
8461
   # --plugin hook
 
8462
   if ( $plugin && $plugin->can('before_create_triggers') ) {
 
8463
      $plugin->before_create_triggers();
 
8464
   }
 
8465
 
8681
8466
   my @trigger_names = eval {
8682
8467
      create_triggers(
8683
8468
         orig_tbl     => $orig_tbl,
8687
8472
         Cxn          => $cxn,
8688
8473
         Quoter       => $q,
8689
8474
         OptionParser => $o,
 
8475
         Retry        => $retry,
 
8476
         tries        => $tries,
 
8477
         stats        => \%stats,
8690
8478
      );
8691
8479
   };
8692
8480
   if ( $EVAL_ERROR ) {
8693
8481
      die "Error creating triggers: $EVAL_ERROR\n";
8694
8482
   };
8695
8483
 
 
8484
   # --plugin hook
 
8485
   if ( $plugin && $plugin->can('after_create_triggers') ) {
 
8486
      $plugin->after_create_triggers();
 
8487
   }
 
8488
 
8696
8489
   # #####################################################################
8697
8490
   # Step 4: Copy rows.
8698
8491
   # #####################################################################
8704
8497
   my $total_rows = 0;
8705
8498
   my $total_time = 0;
8706
8499
   my $avg_rate   = 0;  # rows/second
8707
 
   my $retry      = new Retry();  # for retrying to exec the copy statement
8708
8500
   my $limit      = $o->get('chunk-size-limit');  # brevity
8709
8501
   my $chunk_time = $o->get('chunk-time');        # brevity
8710
8502
 
8824
8616
         my $expl = explain_statement(
8825
8617
            tbl  => $tbl,
8826
8618
            sth  => $sth->{explain_upper_boundary},
8827
 
            vals => [ @{$boundary->{lower}}, $nibble_iter->chunk_size() ],
 
8619
            vals => [ @{$boundary->{lower}}, $nibble_iter->limit() ],
8828
8620
         );
8829
8621
         if (lc($expl->{key} || '') ne lc($nibble_iter->nibble_index() || '')) {
8830
8622
            my $msg
8839
8631
               . $sth->{upper_boundary}->{Statement}
8840
8632
               . " with values "
8841
8633
               . join(", ", map { defined $_ ? $_ : "NULL" }
8842
 
                      (@{$boundary->{lower}}, $nibble_iter->chunk_size()))
 
8634
                      (@{$boundary->{lower}}, $nibble_iter->limit()))
8843
8635
               . "\n";
8844
8636
            die $msg;
8845
8637
         } 
8870
8662
         # Exec and time the chunk checksum query.
8871
8663
         $tbl->{nibble_time} = exec_nibble(
8872
8664
            %args,
8873
 
            Retry        => $retry,
8874
 
            Quoter       => $q,
8875
 
            OptionParser => $o,
8876
 
            stats        => \%stats,
 
8665
            tries   => $tries,
 
8666
            Retry   => $retry,
 
8667
            Quoter  => $q,
 
8668
            stats   => \%stats,
8877
8669
         );
8878
8670
         PTDEBUG && _d('Nibble time:', $tbl->{nibble_time});
8879
8671
 
9005
8797
      );
9006
8798
   }
9007
8799
 
 
8800
   # --plugin hook
 
8801
   if ( $plugin && $plugin->can('before_copy_rows') ) {
 
8802
      $plugin->before_copy_rows();
 
8803
   }
 
8804
 
9008
8805
   # Start copying rows.  This may take awhile, but --progress is on
9009
8806
   # by default so there will be progress updates to stderr.
9010
8807
   eval {
9045
8842
      }
9046
8843
   }
9047
8844
 
 
8845
   # --plugin hook
 
8846
   if ( $plugin && $plugin->can('after_copy_rows') ) {
 
8847
      $plugin->after_copy_rows();
 
8848
   }
 
8849
 
9048
8850
   # #####################################################################
9049
8851
   # XXX
9050
8852
   # Step 5: Rename tables: orig -> old, new -> orig
9053
8855
   # state the tables are left in.
9054
8856
   # XXX
9055
8857
   # #####################################################################
 
8858
 
 
8859
   # --plugin hook
 
8860
   if ( $plugin && $plugin->can('before_swap_tables') ) {
 
8861
      $plugin->before_swap_tables();
 
8862
   }
 
8863
 
9056
8864
   my $old_tbl;
9057
8865
   if ( $o->get('swap-tables') ) {
9058
8866
      eval {
9063
8871
            Cxn          => $cxn,
9064
8872
            Quoter       => $q,
9065
8873
            OptionParser => $o,
 
8874
            Retry        => $retry,
 
8875
            tries        => $tries,
 
8876
            stats        => \%stats,
9066
8877
         );
9067
8878
      };
9068
8879
      if ( $EVAL_ERROR ) {
9069
 
         die "Error swapping the tables: $EVAL_ERROR\n"
9070
 
           . "Verify that the original table $orig_tbl->{name} has not "
9071
 
           . "been modified or renamed to the old table $old_tbl->{name}.  "
9072
 
           . "Then drop the new table $new_tbl->{name} if it exists.\n";
 
8880
         # TODO: one of these values can be undefined
 
8881
         die "Error swapping tables: $EVAL_ERROR\n"
 
8882
           . "To clean up, first verify that the original table "
 
8883
           . "$orig_tbl->{name} has not been modified or renamed, "
 
8884
           . "then drop the new table $new_tbl->{name} if it exists.\n";
9073
8885
      }
9074
8886
   }
9075
8887
   $orig_tbl->{swapped} = 1;  # flag for cleanup tasks
9076
8888
   PTDEBUG && _d('Old table:', Dumper($old_tbl));
9077
8889
 
 
8890
   # --plugin hook
 
8891
   if ( $plugin && $plugin->can('after_swap_tables') ) {
 
8892
      $plugin->after_swap_tables(
 
8893
         old_tbl => $old_tbl,
 
8894
      );
 
8895
   }
 
8896
 
9078
8897
   # #####################################################################
9079
8898
   # Step 6: Update foreign key constraints if there are child tables.
9080
8899
   # #####################################################################
9081
8900
   if ( $child_tables ) {
 
8901
      # --plugin hook
 
8902
      if ( $plugin && $plugin->can('before_update_foreign_keys') ) {
 
8903
         $plugin->before_update_foreign_keys();
 
8904
      }
 
8905
 
9082
8906
      eval {
9083
8907
         if ( $alter_fk_method eq 'none' ) {
9084
8908
            # This shouldn't happen, but in case it does we should know.
9095
8919
               Cxn          => $cxn,
9096
8920
               TableParser  => $tp,
9097
8921
               stats        => \%stats,
 
8922
               Retry        => $retry,
 
8923
               tries        => $tries,
9098
8924
            );
9099
8925
         }
9100
8926
         elsif ( $alter_fk_method eq 'drop_swap' ) {
9103
8929
               new_tbl      => $new_tbl,
9104
8930
               Cxn          => $cxn,
9105
8931
               OptionParser => $o,
 
8932
               stats        => \%stats,
 
8933
               Retry        => $retry,
 
8934
               tries        => $tries,
9106
8935
            );
9107
8936
         }
9108
8937
         elsif ( !$alter_fk_method
9121
8950
         # TODO: improve error message and handling.
9122
8951
         die "Error updating foreign key constraints: $EVAL_ERROR\n";
9123
8952
      }
 
8953
 
 
8954
      # --plugin hook
 
8955
      if ( $plugin && $plugin->can('after_update_foreign_keys') ) {
 
8956
         $plugin->after_update_foreign_keys();
 
8957
      }
9124
8958
   }
9125
8959
 
9126
8960
   # ########################################################################
9134
8968
         print "Not dropping old table because --no-swap-tables was specified.\n";
9135
8969
      }
9136
8970
      else {
 
8971
         # --plugin hook
 
8972
         if ( $plugin && $plugin->can('before_drop_old_table') ) {
 
8973
            $plugin->before_drop_old_table();
 
8974
         }
 
8975
 
9137
8976
         print "Dropping old table...\n";
9138
8977
 
9139
8978
         if ( $alter_fk_method eq 'none' ) {
9156
8995
            die "Error dropping the old table: $EVAL_ERROR\n";
9157
8996
         }
9158
8997
         print "Dropped old table $old_tbl->{name} OK.\n";
 
8998
 
 
8999
         # --plugin hook
 
9000
         if ( $plugin && $plugin->can('after_drop_old_table') ) {
 
9001
            $plugin->after_drop_old_table();
 
9002
         }
9159
9003
      }
9160
9004
   }
9161
 
   
 
9005
 
9162
9006
   # ########################################################################
9163
9007
   # Done.
9164
9008
   # ########################################################################
9165
9009
   $orig_tbl->{success} = 1;  # flag for cleanup tasks
9166
9010
   $cleanup = undef;          # exec cleanup tasks
9167
9011
 
9168
 
   if ( $o->get('statistics') ) {
9169
 
      my $report = new ReportFormatter(
9170
 
         line_width => 74,
9171
 
      );
9172
 
      $report->set_columns(
9173
 
         { name => 'Event',                    },
9174
 
         { name => 'Count', right_justify => 1 },
9175
 
      );
9176
 
 
9177
 
      foreach my $event ( sort keys %stats ) {
9178
 
         $report->add_line(
9179
 
            $event,
9180
 
            $stats{$event},
9181
 
         );
9182
 
      }
9183
 
 
9184
 
      print $report->get_report();
 
9012
   # --plugin hook
 
9013
   if ( $plugin && $plugin->can('before_exit') ) {
 
9014
      $plugin->before_exit(
 
9015
         exit_status => $exit_status,
 
9016
      );
9185
9017
   }
9186
9018
 
9187
9019
   return $exit_status;
9191
9023
# Subroutines.
9192
9024
# ############################################################################
9193
9025
 
 
9026
sub validate_tries {
 
9027
   my ($o) = @_;
 
9028
   my @ops = qw(
 
9029
      create_triggers
 
9030
      drop_triggers
 
9031
      copy_rows
 
9032
      swap_tables 
 
9033
      update_foreign_keys
 
9034
   );
 
9035
   my %user_tries;
 
9036
   my $user_tries = $o->get('tries');
 
9037
   if ( $user_tries ) {
 
9038
      foreach my $var_val ( @$user_tries ) {
 
9039
         my ($op, $tries, $wait) = split(':', $var_val);
 
9040
         die "Invalid --tries value: $var_val\n" unless $op && $tries && $wait;
 
9041
         die "Invalid --tries operation: $op\n" unless grep { $op eq $_ } @ops;
 
9042
         die "Invalid --tries tries: $tries\n" unless $tries > 0;
 
9043
         die "Invalid --tries wait: $wait\n" unless $wait > 0;
 
9044
         $user_tries{$op} = {
 
9045
            tries   => $tries,
 
9046
            wait    => $wait,
 
9047
         };
 
9048
      }
 
9049
   }
 
9050
 
 
9051
   my %default_tries;
 
9052
   my $default_tries = $o->read_para_after(__FILE__, qr/MAGIC_tries/);
 
9053
   if ( $default_tries ) {
 
9054
      %default_tries = map {
 
9055
         my $var_val = $_;
 
9056
         my ($op, $tries, $wait) = $var_val =~ m/(\S+)/g;
 
9057
         die "Invalid --tries value: $var_val\n" unless $op && $tries && $wait;
 
9058
         die "Invalid --tries operation: $op\n" unless grep { $op eq $_ } @ops;
 
9059
         die "Invalid --tries tries: $tries\n" unless $tries > 0;
 
9060
         die "Invalid --tries wait: $wait\n" unless $wait > 0;
 
9061
         $op => {
 
9062
            tries => $tries,
 
9063
            wait  => $wait,
 
9064
         };
 
9065
      } grep { m/^\s+\w+\s+\d+\s+[\d\.]+/ } split("\n", $default_tries);
 
9066
   }
 
9067
 
 
9068
   my %tries = (
 
9069
      %default_tries, # first the tool's defaults
 
9070
      %user_tries,    # then the user's which overwrite the defaults
 
9071
   );
 
9072
   PTDEBUG && _d('--tries:', Dumper(\%tries));
 
9073
   return \%tries;
 
9074
}
 
9075
 
9194
9076
sub check_alter {
9195
9077
   my (%args) = @_;
9196
9078
   my @required_args = qw(alter tbl dry_run Cxn TableParser);
9398
9280
   return 1; # safe
9399
9281
}
9400
9282
 
9401
 
sub create_new_table{
 
9283
sub create_new_table {
9402
9284
   my (%args) = @_;
9403
9285
   my @required_args = qw(orig_tbl Cxn Quoter OptionParser TableParser);
9404
9286
   foreach my $arg ( @required_args ) {
9488
9370
 
9489
9371
sub swap_tables {
9490
9372
   my (%args) = @_;
9491
 
   my @required_args = qw(orig_tbl new_tbl Cxn Quoter OptionParser);
 
9373
   my @required_args = qw(orig_tbl new_tbl Cxn Quoter OptionParser Retry tries stats);
9492
9374
   foreach my $arg ( @required_args ) {
9493
9375
      die "I need a $arg argument" unless $args{$arg};
9494
9376
   }
9495
 
   my ($orig_tbl, $new_tbl, $cxn, $q, $o) = @args{@required_args};
 
9377
   my ($orig_tbl, $new_tbl, $cxn, $q, $o, $retry, $tries, $stats) = @args{@required_args};
9496
9378
 
9497
 
   my $prefix     = '_';
9498
 
   my $table_name = $orig_tbl->{tbl} . ($args{suffix} || '');
9499
 
   my $tries      = 10;  # don't try forever
 
9379
   my $prefix       = '_';
 
9380
   my $table_name   = $orig_tbl->{tbl} . ($args{suffix} || '');
 
9381
   my $name_tries   = 10;  # don't try forever
 
9382
   my $table_exists = qr/table.+?already exists/i;
9500
9383
 
9501
9384
   # This sub only works for --execute.  Since the options are
9502
9385
   # mutually exclusive and we return in the if case, the elsif
9512
9395
   }
9513
9396
   elsif ( $o->get('execute') ) {
9514
9397
      print "Swapping tables...\n";
9515
 
      
9516
 
      while ( $tries-- ) {
 
9398
 
 
9399
      while ( $name_tries-- ) {
9517
9400
         $table_name = $prefix . $table_name;
9518
 
         
 
9401
 
9519
9402
         if ( length($table_name) > 64 ) {
9520
9403
            my $truncated_table_name = substr($table_name, 0, 64);
9521
9404
            PTDEBUG && _d($table_name, 'is over 64 characters long, truncating to',
9526
9409
         my $sql     = "RENAME TABLE $orig_tbl->{name} "
9527
9410
                     . "TO " . $q->quote($orig_tbl->{db}, $table_name)
9528
9411
                     . ", $new_tbl->{name} TO $orig_tbl->{name}"; 
9529
 
         PTDEBUG && _d($sql); 
 
9412
 
9530
9413
         eval {
9531
 
            $cxn->dbh()->do($sql);
 
9414
            osc_retry(
 
9415
               Cxn     => $cxn,
 
9416
               Retry   => $retry,
 
9417
               tries   => $tries->{swap_tables},
 
9418
               stats   => $stats,
 
9419
               code    => sub {
 
9420
                  PTDEBUG && _d($sql);
 
9421
                  $cxn->dbh()->do($sql);
 
9422
               },
 
9423
               ignore_errors => [
 
9424
                  # Ignore this error because if multiple instances of the tool
 
9425
                  # are running, or previous runs failed and weren't cleaned up,
 
9426
                  # then there will be other similarly named tables with fewer
 
9427
                  # leading prefix chars.  Or, in rare cases, the db happens
 
9428
                  # to have a similarly named table created by the user for
 
9429
                  # other purposes.
 
9430
                  $table_exists,
 
9431
               ],
 
9432
            );
9532
9433
         };
9533
 
         if ( $EVAL_ERROR ) {
9534
 
            # Ignore this error because if multiple instances of the tool
9535
 
            # are running, or previous runs failed and weren't cleaned up,
9536
 
            # then there will be other similarly named tables with fewer
9537
 
            # leading prefix chars.  Or, in rarer cases, the db just happens
9538
 
            # to have a similarly named table created by the user for other
9539
 
            # purposes.
9540
 
            next if $EVAL_ERROR =~ m/table.+?already exists/i;
9541
 
 
9542
 
            # Some other error happened.  Let caller catch it.
9543
 
            die $EVAL_ERROR;
 
9434
         if ( my $e = $EVAL_ERROR ) {
 
9435
            if ( $e =~ $table_exists ) {
 
9436
               PTDEBUG && _d($e);
 
9437
               next;
 
9438
            }
 
9439
            die $e;
9544
9440
         }
 
9441
 
9545
9442
         print $sql, "\n" if $o->get('print');
9546
9443
         print "Swapped original and new tables OK.\n";
9547
9444
         return { # success
9553
9450
 
9554
9451
      # This shouldn't happen.
9555
9452
      # Here and in the attempt to find a new table name we probably ought to
9556
 
      # use --retries (and maybe a Retry object?)
 
9453
      # use --tries (and maybe a Retry object?)
9557
9454
      die "Failed to find a unique old table name after serveral attempts.\n";
9558
9455
   }
9559
9456
}
9702
9599
sub rebuild_constraints {
9703
9600
   my ( %args ) = @_;
9704
9601
   my @required_args = qw(orig_tbl old_tbl child_tables stats
9705
 
                          Cxn Quoter OptionParser TableParser);
 
9602
                          Cxn Quoter OptionParser TableParser
 
9603
                          Retry tries);
9706
9604
   foreach my $arg ( @required_args ) {
9707
9605
      die "I need a $arg argument" unless $args{$arg};
9708
9606
   }
9709
 
   my ($orig_tbl, $old_tbl, $child_tables, $stats, $cxn, $q, $o, $tp)
 
9607
   my ($orig_tbl, $old_tbl, $child_tables, $stats, $cxn, $q, $o, $tp, $retry, $tries)
9710
9608
      = @args{@required_args};
9711
9609
 
9712
9610
   # MySQL has a "feature" where if the parent tbl is in the same db,
9781
9679
              . join(', ', @rebuilt_constraints);
9782
9680
      print $sql, "\n" if $o->get('print');
9783
9681
      if ( $o->get('execute') ) {
9784
 
         PTDEBUG && _d($sql);
9785
 
         $cxn->dbh()->do($sql);
9786
 
         $stats->{rebuilt_constraint}++;
 
9682
         osc_retry(
 
9683
            Cxn     => $cxn,
 
9684
            Retry   => $retry,
 
9685
            tries   => $tries->{update_foreign_keys},
 
9686
            stats   => $stats,
 
9687
            code    => sub {
 
9688
               PTDEBUG && _d($sql);
 
9689
               $cxn->dbh()->do($sql);
 
9690
               $stats->{rebuilt_constraint}++;
 
9691
            },
 
9692
         );
9787
9693
      }
9788
9694
   }
9789
9695
 
9796
9702
 
9797
9703
sub drop_swap {
9798
9704
   my ( %args ) = @_;
9799
 
   my @required_args = qw(orig_tbl new_tbl Cxn OptionParser);
 
9705
   my @required_args = qw(orig_tbl new_tbl Cxn OptionParser stats Retry tries);
9800
9706
   foreach my $arg ( @required_args ) {
9801
9707
      die "I need a $arg argument" unless $args{$arg};
9802
9708
   }
9803
 
   my ($orig_tbl, $new_tbl, $cxn, $o) = @args{@required_args};
 
9709
   my ($orig_tbl, $new_tbl, $cxn, $o, $stats, $retry, $tries) = @args{@required_args};
9804
9710
 
9805
9711
   if ( $o->get('dry-run') ) {
9806
9712
      print "Not drop-swapping tables because this is a dry run.\n";
9817
9723
 
9818
9724
   foreach my $sql ( @sqls ) {
9819
9725
      PTDEBUG && _d($sql);
9820
 
      print $sql, "\n"      if $o->get('print');
9821
 
      $cxn->dbh()->do($sql) if $o->get('execute');
 
9726
      print $sql, "\n" if $o->get('print');
 
9727
      if ( $o->get('execute') ) {
 
9728
         osc_retry(
 
9729
            Cxn     => $cxn,
 
9730
            Retry   => $retry,
 
9731
            tries   => $tries->{update_foreign_keys},
 
9732
            stats   => $stats,
 
9733
            code    => sub {
 
9734
               PTDEBUG && _d($sql);
 
9735
               $cxn->dbh()->do($sql);
 
9736
            },
 
9737
         );
 
9738
      }
9822
9739
   }
9823
9740
 
9824
9741
   if ( $o->get('execute') ) {
9830
9747
 
9831
9748
sub create_triggers {
9832
9749
   my ( %args ) = @_;
9833
 
   my @required_args = qw(orig_tbl new_tbl del_tbl columns Cxn Quoter OptionParser);
 
9750
   my @required_args = qw(orig_tbl new_tbl del_tbl columns Cxn Quoter OptionParser Retry tries stats);
9834
9751
   foreach my $arg ( @required_args ) {
9835
9752
      die "I need a $arg argument" unless $args{$arg};
9836
9753
   }
9837
 
   my ($orig_tbl, $new_tbl, $del_tbl, $cols, $cxn, $q, $o) = @args{@required_args};
 
9754
   my ($orig_tbl, $new_tbl, $del_tbl, $cols, $cxn, $q, $o, $retry, $tries, $stats) = @args{@required_args};
9838
9755
 
9839
9756
   # This sub works for --dry-run and --execute.  With --dry-run it's
9840
9757
   # only interesting if --print is specified, too; then the user can
9902
9819
      my ($name, $sql) = @$trg;
9903
9820
      print $sql, "\n" if $o->get('print');
9904
9821
      if ( $o->get('execute') ) {
9905
 
         # Let caller catch errors.
9906
 
         PTDEBUG && _d($sql);
9907
 
         $cxn->dbh()->do($sql);
 
9822
         osc_retry(
 
9823
            Cxn     => $cxn,
 
9824
            Retry   => $retry,
 
9825
            tries   => $tries->{create_triggers},
 
9826
            stats   => $stats,
 
9827
            code    => sub {
 
9828
               PTDEBUG && _d($sql);
 
9829
               $cxn->dbh()->do($sql);
 
9830
            },
 
9831
         );
9908
9832
      }
9909
9833
      # Only save the trigger once it has been created
9910
9834
      # (or faked to be created) so if the 2nd trigger
9924
9848
 
9925
9849
sub drop_triggers {
9926
9850
   my ( %args ) = @_;
9927
 
   my @required_args = qw(tbl Cxn Quoter OptionParser);
 
9851
   my @required_args = qw(tbl Cxn Quoter OptionParser Retry tries stats);
9928
9852
   foreach my $arg ( @required_args ) {
9929
9853
      die "I need a $arg argument" unless $args{$arg};
9930
9854
   }
9931
 
   my ($tbl, $cxn, $q, $o) = @args{@required_args};
 
9855
   my ($tbl, $cxn, $q, $o, $retry, $tries, $stats) = @args{@required_args};
9932
9856
 
9933
9857
   # This sub works for --dry-run and --execute, although --dry-run is
9934
9858
   # only interesting with --print so the user can see the drop trigger
9944
9868
   foreach my $sql ( @drop_trigger_sqls ) {
9945
9869
      print $sql, "\n" if $o->get('print');
9946
9870
      if ( $o->get('execute') ) {
9947
 
         PTDEBUG && _d($sql);
9948
9871
         eval {
9949
 
            $cxn->dbh()->do($sql);
 
9872
            osc_retry(
 
9873
               Cxn     => $cxn,
 
9874
               Retry   => $retry,
 
9875
               tries   => $tries->{drop_triggers},
 
9876
               stats   => $stats,
 
9877
               code    => sub {
 
9878
                  PTDEBUG && _d($sql);
 
9879
                  $cxn->dbh()->do($sql);
 
9880
               },
 
9881
            );
9950
9882
         };
9951
9883
         if ( $EVAL_ERROR ) {
9952
9884
            warn "Error dropping trigger: $EVAL_ERROR\n";
9969
9901
   return;
9970
9902
}
9971
9903
 
 
9904
sub error_event {
 
9905
   my ($error) = @_;
 
9906
   return 'undefined_error' unless $error;
 
9907
   my $event
 
9908
      = $error =~ m/Lock wait timeout/         ? 'lock_wait_timeout'
 
9909
      : $error =~ m/Deadlock found/            ? 'deadlock'
 
9910
      : $error =~ m/execution was interrupted/ ? 'query_killed'
 
9911
      : $error =~ m/server has gone away/      ? 'lost_connection'
 
9912
      : $error =~ m/Lost connection/           ? 'connection_killed'
 
9913
      :                                          'unknown_error';
 
9914
   return $event;
 
9915
}
 
9916
 
 
9917
sub osc_retry {
 
9918
   my (%args) = @_;
 
9919
   my @required_args = qw(Cxn Retry tries code stats);
 
9920
   foreach my $arg ( @required_args ) {
 
9921
      die "I need a $arg argument" unless $args{$arg};
 
9922
   }
 
9923
   my $cxn           = $args{Cxn};
 
9924
   my $retry         = $args{Retry};
 
9925
   my $tries         = $args{tries};
 
9926
   my $code          = $args{code};
 
9927
   my $stats         = $args{stats};
 
9928
   my $ignore_errors = $args{ignore_errors};
 
9929
 
 
9930
   return $retry->retry(
 
9931
      tries => $tries->{tries},
 
9932
      wait  => sub { sleep ($tries->{wait} || 0.25) },
 
9933
      try   => $code,
 
9934
      fail => sub {
 
9935
         my (%args) = @_;
 
9936
         my $error = $args{error};
 
9937
         PTDEBUG && _d('Retry fail:', $error);
 
9938
 
 
9939
         if ( $ignore_errors ) {
 
9940
            return 0 if grep { $error =~ $_ } @$ignore_errors;
 
9941
         }
 
9942
 
 
9943
         # The query failed/caused an error.  If the error is one of these,
 
9944
         # then we can possibly retry.
 
9945
         if (   $error =~ m/Lock wait timeout exceeded/
 
9946
             || $error =~ m/Deadlock found/
 
9947
             || $error =~ m/Query execution was interrupted/
 
9948
         ) {
 
9949
            # These errors/warnings can be retried, so don't print
 
9950
            # a warning yet; do that in final_fail.
 
9951
            $stats->{ error_event($error) }++;
 
9952
            return 1;  # try again
 
9953
         }
 
9954
         elsif (   $error =~ m/MySQL server has gone away/
 
9955
                || $error =~ m/Lost connection to MySQL server/
 
9956
         ) {
 
9957
            # The 1st pattern means that MySQL itself died or was stopped.
 
9958
            # The 2nd pattern means that our cxn was killed (KILL <id>).
 
9959
            $stats->{ error_event($error) }++;
 
9960
            $cxn->connect();  # connect or die trying
 
9961
            return 1;  # reconnected, try again
 
9962
         }
 
9963
 
 
9964
         $stats->{retry_fail}++;
 
9965
 
 
9966
         # At this point, either the error/warning cannot be retried,
 
9967
         # or we failed to reconnect.  Don't retry; call final_fail.
 
9968
         return 0;
 
9969
      },
 
9970
      final_fail => sub {
 
9971
         my (%args) = @_;
 
9972
         my $error = $args{error};
 
9973
         # This die should be caught by the caller.  Copying rows and
 
9974
         # the tool will stop, which is probably good because by this
 
9975
         # point the error or warning indicates that something is wrong.
 
9976
         $stats->{ error_event($error) }++;
 
9977
         die $error;
 
9978
      }
 
9979
   );
 
9980
}
 
9981
 
9972
9982
sub exec_nibble {
9973
9983
   my (%args) = @_;
9974
 
   my @required_args = qw(Cxn tbl stats NibbleIterator Retry Quoter OptionParser);
 
9984
   my @required_args = qw(Cxn tbl stats tries Retry NibbleIterator Quoter);
9975
9985
   foreach my $arg ( @required_args ) {
9976
9986
      die "I need a $arg argument" unless $args{$arg};
9977
9987
   }
9978
 
   my ($cxn, $tbl, $stats, $nibble_iter, $retry, $q, $o)= @args{@required_args};
 
9988
   my ($cxn, $tbl, $stats, $tries, $retry, $nibble_iter, $q)
 
9989
      = @args{@required_args};
9979
9990
 
9980
 
   my $dbh         = $cxn->dbh();
9981
9991
   my $sth         = $nibble_iter->statements();
9982
9992
   my $boundary    = $nibble_iter->boundaries();
9983
9993
   my $lb_quoted   = $q->serialize_list(@{$boundary->{lower}});
10009
10019
      },
10010
10020
   );
10011
10021
 
10012
 
   return $retry->retry(
10013
 
      tries => $o->get('retries'),
10014
 
      wait  => sub { sleep 0.25; return; },
10015
 
      try   => sub {
 
10022
   return osc_retry(
 
10023
      Cxn     => $cxn,
 
10024
      Retry   => $retry,
 
10025
      tries   => $tries->{copy_rows},
 
10026
      stats   => $stats,
 
10027
      code    => sub {
10016
10028
         # ###################################################################
10017
10029
         # Start timing the query.
10018
10030
         # ###################################################################
10041
10053
         # Check if query caused any warnings.
10042
10054
         my $sql_warn = 'SHOW WARNINGS';
10043
10055
         PTDEBUG && _d($sql_warn);
10044
 
         my $warnings = $dbh->selectall_arrayref($sql_warn, { Slice => {} } );
 
10056
         my $warnings = $cxn->dbh->selectall_arrayref($sql_warn, {Slice => {}});
10045
10057
         foreach my $warning ( @$warnings ) {
10046
10058
            my $code    = ($warning->{code} || 0);
10047
10059
            my $message = $warning->{message};
10061
10073
                        ? $warn_code{$code}->{message}
10062
10074
                        : $message)
10063
10075
                     . "\nThis MySQL error is being ignored ";
10064
 
                  if ( $o->get('statistics') ) {
 
10076
                  if ( get('statistics') ) {
10065
10077
                     $err .= "but further occurrences will be reported "
10066
10078
                           . "by --statistics when the tool finishes.\n";
10067
10079
                  }
10088
10100
         # Success: no warnings, no errors.  Return nibble time.
10089
10101
         return $t_end - $t_start;
10090
10102
      },
10091
 
      fail => sub {
10092
 
         my (%args) = @_;
10093
 
         my $error = $args{error};
10094
 
         PTDEBUG && _d('Retry fail:', $error);
10095
 
 
10096
 
         # The query failed/caused an error.  If the error is one of these,
10097
 
         # then we can possibly retry.
10098
 
         if (   $error =~ m/Lock wait timeout exceeded/
10099
 
             || $error =~ m/Deadlock found/
10100
 
             || $error =~ m/Query execution was interrupted/
10101
 
         ) {
10102
 
            # These errors/warnings can be retried, so don't print
10103
 
            # a warning yet; do that in final_fail.
10104
 
            my $event
10105
 
               = $error =~ m/Lock wait timeout/         ? 'lock_wait_timeout'
10106
 
               : $error =~ m/Deadlock found/            ? 'deadlock'
10107
 
               : $error =~ m/execution was interrupted/ ? 'query_killed'
10108
 
               :                                          'unknown1';
10109
 
            $stats->{$event}++;
10110
 
            return 1;  # try again
10111
 
         }
10112
 
         elsif (   $error =~ m/MySQL server has gone away/
10113
 
                || $error =~ m/Lost connection to MySQL server/
10114
 
         ) {
10115
 
            # The 1st pattern means that MySQL itself died or was stopped.
10116
 
            # The 2nd pattern means that our cxn was killed (KILL <id>).
10117
 
            my $event
10118
 
               = $error =~ m/server has gone away/ ? 'lost_connection'
10119
 
               : $error =~ m/Lost connection/      ? 'connection_killed'
10120
 
               :                                     'unknown2';
10121
 
            $stats->{$event}++;
10122
 
            $dbh = $cxn->connect();  # connect or die trying
10123
 
            return 1;  # reconnected, try again
10124
 
         }
10125
 
 
10126
 
         $stats->{retry_fail}++;
10127
 
 
10128
 
         # At this point, either the error/warning cannot be retried,
10129
 
         # or we failed to reconnect.  Don't retry; call final_fail.
10130
 
         return 0;
10131
 
      },
10132
 
      final_fail => sub {
10133
 
         my (%args) = @_;
10134
 
         # This die should be caught by the caller.  Copying rows and
10135
 
         # the tool will stop, which is probably good because by this
10136
 
         # point the error or warning indicates that something is wrong.
10137
 
         die $args{error};
10138
 
      }
10139
10103
   );
10140
10104
}
10141
10105
 
10223
10187
 
10224
10188
=head1 RISKS
10225
10189
 
10226
 
The following section is included to inform users about the potential risks,
10227
 
whether known or unknown, of using this tool.  The two main categories of risks
10228
 
are those created by the nature of the tool (e.g. read-only tools vs. read-write
10229
 
tools) and those created by bugs.
10230
 
 
10231
 
pt-online-schema-change modifies data and structures. You should be careful with
10232
 
it, and test it before using it in production.  You should also ensure that you
10233
 
have recoverable backups before using this tool.
10234
 
 
10235
 
At the time of this release, we know of no bugs that could cause harm to users.
10236
 
 
10237
 
The authoritative source for updated information is always the online issue
10238
 
tracking system.  Issues that affect this tool will be marked as such.  You can
10239
 
see a list of such issues at the following URL:
10240
 
L<http://www.percona.com/bugs/pt-online-schema-change>.
10241
 
 
10242
 
See also L<"BUGS"> for more information on filing bugs and getting help.
 
10190
Percona Toolkit is mature, proven in the real world, and well tested,
 
10191
but all database tools can pose a risk to the system and the database
 
10192
server.  Before using this tool, please:
 
10193
 
 
10194
=over
 
10195
 
 
10196
=item * Read the tool's documentation
 
10197
 
 
10198
=item * Review the tool's known L<"BUGS">
 
10199
 
 
10200
=item * Test the tool on a non-production server
 
10201
 
 
10202
=item * Backup your production server and verify the backups
 
10203
 
 
10204
=back
10243
10205
 
10244
10206
=head1 DESCRIPTION
10245
10207
 
10303
10265
 
10304
10266
=item *
10305
10267
 
10306
 
The tool sets its lock wait timeout to 1 second so that it is more likely to be
10307
 
the victim of any lock contention, and less likely to disrupt other
10308
 
transactions. See L<"--lock-wait-timeout"> for details.
 
10268
The tool sets C<innodb_lock_wait_timeout=1> and (for MySQL 5.5 and newer)
 
10269
C<lock_wait_timeout=60> so that it is more likely to be the victim of any
 
10270
lock contention, and less likely to disrupt other transactions.  These
 
10271
values can be changed by specifying L<"--set-vars">.
10309
10272
 
10310
10273
=item *
10311
10274
 
10383
10346
C<DROP FOREIGN KEY constraint_name> requires specifying C<_constraint_name>
10384
10347
rather than the real C<constraint_name>.  Due to a limitation in MySQL,
10385
10348
pt-online-schema-change adds a leading underscore to foreign key constraint
10386
 
names when creating the new table.  For example, to drop this contraint:
 
10349
names when creating the new table.  For example, to drop this constraint:
10387
10350
 
10388
10351
  CONSTRAINT `fk_foo` FOREIGN KEY (`foo_id`) REFERENCES `bar` (`foo_id`)
10389
10352
 
10503
10466
NAMES UTF8 after connecting to MySQL.  Any other value sets binmode on STDOUT
10504
10467
without the utf8 layer, and runs SET NAMES after connecting to MySQL.
10505
10468
 
10506
 
=item --check-interval
10507
 
 
10508
 
type: time; default: 1
10509
 
 
10510
 
Sleep time between checks for L<"--max-lag">.
10511
 
 
10512
 
 
10513
10469
=item --[no]check-alter
10514
10470
 
10515
10471
default: yes
10541
10497
 
10542
10498
=back
10543
10499
 
 
10500
=item --check-interval
 
10501
 
 
10502
type: time; default: 1
 
10503
 
 
10504
Sleep time between checks for L<"--max-lag">.
 
10505
 
10544
10506
=item --[no]check-plan
10545
10507
 
10546
10508
default: yes
10772
10734
 
10773
10735
Connect to host.
10774
10736
 
10775
 
=item --lock-wait-timeout
10776
 
 
10777
 
type: int; default: 1
10778
 
 
10779
 
Set the session value of C<innodb_lock_wait_timeout>.  This option helps guard
10780
 
against long lock waits if the data-copy queries become slow for some reason.
10781
 
Setting this option dynamically requires the InnoDB plugin, so this works only
10782
 
on newer InnoDB and MySQL versions.  If the setting's current value is greater
10783
 
than the specified value, and the tool cannot set the value as desired, then it
10784
 
prints a warning. If the tool cannot set the value but the current value is less
10785
 
than or equal to the desired value, there is no error.
10786
 
 
10787
10737
=item --max-lag
10788
10738
 
10789
10739
type: time; default: 1s
10842
10792
 
10843
10793
type: string
10844
10794
 
10845
 
Create the given PID file.  The file contains the process ID of the tool's
10846
 
instance.  The PID file is removed when the tool exits.  The tool checks for
10847
 
the existence of the PID file when starting; if it exists and the process with
10848
 
the matching PID exists, the tool exits.
 
10795
Create the given PID file.  The tool won't start if the PID file already
 
10796
exists and the PID it contains is different than the current PID.  However,
 
10797
if the PID file exists and the PID it contains is no longer running, the
 
10798
tool will overwrite the PID file with the current PID.  The PID file is
 
10799
removed automatically when the tool exits.
 
10800
 
 
10801
=item --plugin
 
10802
 
 
10803
type: string
 
10804
 
 
10805
Perl module file that defines a C<pt_online_schema_change_plugin> class.
 
10806
A plugin allows you to write a Perl module that can hook into many parts
 
10807
of pt-online-schema-change.  This requires a good knowledge of Perl and
 
10808
Percona Toolkit conventions, which are beyond this scope of this
 
10809
documentation.  Please contact Percona if you have questions or need help.
 
10810
 
 
10811
See L<"PLUGIN"> for more information.
10849
10812
 
10850
10813
=item --port
10851
10814
 
10920
10883
table. Currently, the DSNs are ordered by id, but id and parent_id are otherwise
10921
10884
ignored.  
10922
10885
 
10923
 
=item --retries
10924
 
 
10925
 
type: int; default: 3
10926
 
 
10927
 
Retry a chunk this many times when there is a nonfatal error.  Nonfatal errors
10928
 
are problems such as a lock wait timeout or the query being killed. This option
10929
 
applies to the data copy operation.
10930
 
 
10931
10886
=item --set-vars
10932
10887
 
10933
 
type: string; default: wait_timeout=10000
10934
 
 
10935
 
Set these MySQL variables.  Immediately after connecting to MySQL, this string
10936
 
will be appended to SET and executed.
 
10888
type: Array
 
10889
 
 
10890
Set the MySQL variables in this comma-separated list of C<variable=value> pairs.
 
10891
 
 
10892
By default, the tool sets:
 
10893
 
 
10894
=for comment ignore-pt-internal-value
 
10895
MAGIC_set_vars
 
10896
 
 
10897
   wait_timeout=10000
 
10898
   innodb_lock_wait_timeout=1
 
10899
   lock_wait_timeout=60
 
10900
 
 
10901
Variables specified on the command line override these defaults.  For
 
10902
example, specifying C<--set-vars wait_timeout=500> overrides the default
 
10903
value of C<10000>.
 
10904
 
 
10905
The tool prints a warning and continues if a variable cannot be set.
10937
10906
 
10938
10907
=item --socket
10939
10908
 
10955
10924
place of the original table.  The original table becomes the "old table," and
10956
10925
the tool drops it unless you disable L<"--[no]drop-old-table">.
10957
10926
 
 
10927
=item --tries
 
10928
 
 
10929
type: array
 
10930
 
 
10931
How many times to try critical operations.  If certain operations fail due
 
10932
to non-fatal, recoverable errors, the tool waits and tries the operation
 
10933
again.  These are the operations that are retried, with their default number
 
10934
of tries and wait time between tries (in seconds):
 
10935
 
 
10936
=for comment ignore-pt-internal-value
 
10937
MAGIC_tries
 
10938
 
 
10939
   OPERATION            TRIES   WAIT
 
10940
   ===================  =====   ====
 
10941
   create_triggers         10      1
 
10942
   drop_triggers           10      1
 
10943
   copy_rows               10   0.25
 
10944
   swap_tables             10      1
 
10945
   update_foreign_keys     10      1
 
10946
 
 
10947
To change the defaults, specify the new values like:
 
10948
 
 
10949
   --tries create_triggers:5:0.5,drop_triggers:5:0.5
 
10950
 
 
10951
That makes the tool try C<create_triggers> and C<drop_triggers> 2 times
 
10952
with a 0.5 second wait between tries.  So the format is:
 
10953
 
 
10954
   operation:tries:wait[,operation:tries:wait]
 
10955
 
 
10956
All three values must be specified.
 
10957
 
 
10958
Note that most operations are affected only in MySQL 5.5 and newer by
 
10959
C<lock_wait_timeout> (see L<"--set-vars">) because of metadata locks.
 
10960
The C<copy_rows> operation is affected in any version of MySQL by
 
10961
C<innodb_lock_wait_timeout>.
 
10962
 
 
10963
For creating and dropping triggers, the number of tries applies to each
 
10964
C<CREATE TRIGGER> and C<DROP TRIGGER> statement for each trigger.
 
10965
For copying rows, the number of tries applies to each chunk, not the
 
10966
entire table.  For swapping tables, the number of tries usually applies
 
10967
once because there is usually only one C<RENAME TABLE> statement.
 
10968
For rebuilding foreign key constraints, the number of tries applies to
 
10969
each statement (C<ALTER> statements for the C<rebuild_constraints>
 
10970
L<"--alter-foreign-keys-method">; other statements for the C<drop_swap>
 
10971
method).
 
10972
 
 
10973
The tool retries each operation if these errors occur:
 
10974
 
 
10975
   Lock wait timeout (innodb_lock_wait_timeout and lock_wait_timeout)
 
10976
   Deadlock found
 
10977
   Query is killed (KILL QUERY <thread_id>)
 
10978
   Connection is killed (KILL CONNECTION <thread_id>)
 
10979
   Lost connection to MySQL
 
10980
 
 
10981
In the case of lost and killed connections, the tool will automatically
 
10982
reconnect.
 
10983
 
 
10984
Failures and retries are recorded in the L<"--statistics">.
 
10985
 
10958
10986
=item --user
10959
10987
 
10960
10988
short form: -u; type: string
10965
10993
 
10966
10994
Show version and exit.
10967
10995
 
10968
 
=item --version-check
10969
 
 
10970
 
type: string; default: off
10971
 
 
10972
 
Send program versions to Percona and print suggested upgrades and problems.
10973
 
Possible values for --version-check:
10974
 
 
10975
 
=for comment ignore-pt-internal-value
10976
 
MAGIC_version_check
10977
 
 
10978
 
https, http, auto, off
10979
 
 
10980
 
C<auto> first tries using C<https>, and resorts to C<http> if that fails.
10981
 
Keep in mind that C<https> might not be available if
10982
 
C<IO::Socket::SSL> is not installed on your system, although
10983
 
C<--version-check http> should work everywhere.
10984
 
 
10985
 
The version check feature causes the tool to send and receive data from
10986
 
Percona over the web.  The data contains program versions from the local
10987
 
machine.  Percona uses the data to focus development on the most widely
10988
 
used versions of programs, and to suggest to customers possible upgrades
10989
 
and known bad versions of programs.
10990
 
 
10991
 
For more information, visit L<http://www.percona.com/version-check>.
 
10996
=item --[no]version-check
 
10997
 
 
10998
default: yes
 
10999
 
 
11000
Check for the latest version of Percona Toolkit, MySQL, and other programs.
 
11001
 
 
11002
This is a standard "check for updates automatically" feature, with two
 
11003
additional features.  First, the tool checks the version of other programs
 
11004
on the local system in addition to its own version.  For example, it checks
 
11005
the version of every MySQL server it connects to, Perl, and the Perl module
 
11006
DBD::mysql.  Second, it checks for and warns about versions with known
 
11007
problems.  For example, MySQL 5.5.25 had a critical bug and was re-released
 
11008
as 5.5.25a.
 
11009
 
 
11010
Any updates or known problems are printed to STDOUT before the tool's normal
 
11011
output.  This feature should never interfere with the normal operation of the
 
11012
tool.  
 
11013
 
 
11014
For more information, visit L<https://www.percona.com/version-check>.
10992
11015
 
10993
11016
=back
10994
11017
 
 
11018
=head1 PLUGIN
 
11019
 
 
11020
The file specified by L<"--plugin"> must define a class (i.e. a package)
 
11021
called C<pt_online_schema_change_plugin> with a C<new()> subroutine.
 
11022
The tool will create an instance of this class and call any hooks that
 
11023
it defines.  No hooks are required, but a plugin isn't very useful without
 
11024
them.
 
11025
 
 
11026
These hooks, in this order, are called if defined:
 
11027
 
 
11028
   init
 
11029
   before_create_new_table
 
11030
   after_create_new_table
 
11031
   before_alter_new_table
 
11032
   after_alter_new_table
 
11033
   before_create_triggers
 
11034
   after_create_triggers
 
11035
   before_copy_rows
 
11036
   after_copy_rows
 
11037
   before_swap_tables
 
11038
   after_swap_tables
 
11039
   before_update_foreign_keys
 
11040
   after_update_foreign_keys
 
11041
   before_drop_old_table
 
11042
   after_drop_old_table
 
11043
   before_drop_triggers
 
11044
   before_exit
 
11045
 
 
11046
Each hook is passed different arguments.  To see which arguments are passed
 
11047
to a hook, search for the hook's name in the tool's source code, like:
 
11048
 
 
11049
   # --plugin hook
 
11050
   if ( $plugin && $plugin->can('init') ) {
 
11051
      $plugin->init(
 
11052
         orig_tbl       => $orig_tbl,
 
11053
         child_tables   => $child_tables,
 
11054
         renamed_cols   => $renamed_cols,
 
11055
         slaves         => $slaves,
 
11056
         slave_lag_cxns => $slave_lag_cxns,
 
11057
      );
 
11058
   }
 
11059
 
 
11060
The comment C<# --plugin hook> precedes every hook call.
 
11061
 
 
11062
Please contact Percona if you have questions or need help.
 
11063
 
10995
11064
=head1 DSN OPTIONS
10996
11065
 
10997
11066
These DSN options are used to create a DSN.  Each option is given like
11134
11203
=head1 ABOUT PERCONA TOOLKIT
11135
11204
 
11136
11205
This tool is part of Percona Toolkit, a collection of advanced command-line
11137
 
tools developed by Percona for MySQL support and consulting.  Percona Toolkit
11138
 
was forked from two projects in June, 2011: Maatkit and Aspersa.  Those
11139
 
projects were created by Baron Schwartz and developed primarily by him and
11140
 
Daniel Nichter, both of whom are employed by Percona.  Visit
11141
 
L<http://www.percona.com/software/> for more software developed by Percona.
 
11206
tools for MySQL developed by Percona.  Percona Toolkit was forked from two
 
11207
projects in June, 2011: Maatkit and Aspersa.  Those projects were created by
 
11208
Baron Schwartz and primarily developed by him and Daniel Nichter.  Visit
 
11209
L<http://www.percona.com/software/> to learn about other free, open-source
 
11210
software from Percona.
11142
11211
 
11143
11212
=head1 COPYRIGHT, LICENSE, AND WARRANTY
11144
11213