~percona-toolkit-dev/percona-toolkit/fix-osc-quoting-bug-873598

« back to all changes in this revision

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

  • Committer: Daniel Nichter
  • Date: 2012-02-21 16:06:08 UTC
  • Revision ID: daniel@percona.com-20120221160608-k1i8ltncshcwbvzo
Test, update modules, and fix bug 873598 in pt-online-schema-change.

Show diffs side-by-side

added added

removed removed

Lines of Context:
959
959
      $opt->{value} = ($pre || '') . $num;
960
960
   }
961
961
   else {
962
 
      $self->save_error("Invalid size for --$opt->{long}");
 
962
      $self->save_error("Invalid size for --$opt->{long}: $val");
963
963
   }
964
964
   return;
965
965
}
1249
1249
sub as_string {
1250
1250
   my ( $self, $dsn, $props ) = @_;
1251
1251
   return $dsn unless ref $dsn;
1252
 
   my %allowed = $props ? map { $_=>1 } @$props : ();
 
1252
   my @keys = $props ? @$props : sort keys %$dsn;
1253
1253
   return join(',',
1254
 
      map  { "$_=" . ($_ eq 'p' ? '...' : $dsn->{$_})  }
1255
 
      grep { defined $dsn->{$_} && $self->{opts}->{$_} }
1256
 
      grep { !$props || $allowed{$_}                   }
1257
 
      sort keys %$dsn );
 
1254
      map  { "$_=" . ($_ eq 'p' ? '...' : $dsn->{$_}) }
 
1255
      grep {
 
1256
         exists $self->{opts}->{$_}
 
1257
         && exists $dsn->{$_}
 
1258
         && defined $dsn->{$_}
 
1259
      } @keys);
1258
1260
}
1259
1261
 
1260
1262
sub usage {
1729
1731
   return $db ? "$db.$tbl" : $tbl;
1730
1732
}
1731
1733
 
 
1734
sub serialize_list {
 
1735
   my ( $self, @args ) = @_;
 
1736
   return unless @args;
 
1737
 
 
1738
   return $args[0] if @args == 1 && !defined $args[0];
 
1739
 
 
1740
   die "Cannot serialize multiple values with undef/NULL"
 
1741
      if grep { !defined $_ } @args;
 
1742
 
 
1743
   return join ',', map { quotemeta } @args;
 
1744
}
 
1745
 
 
1746
sub deserialize_list {
 
1747
   my ( $self, $string ) = @_;
 
1748
   return $string unless defined $string;
 
1749
   my @escaped_parts = $string =~ /
 
1750
         \G             # Start of string, or end of previous match.
 
1751
         (              # Each of these is an element in the original list.
 
1752
            [^\\,]*     # Anything not a backslash or a comma
 
1753
            (?:         # When we get here, we found one of the above.
 
1754
               \\.      # A backslash followed by something so we can continue
 
1755
               [^\\,]*  # Same as above.
 
1756
            )*          # Repeat zero of more times.
 
1757
         )
 
1758
         ,              # Comma dividing elements
 
1759
      /sxgc;
 
1760
 
 
1761
   push @escaped_parts, pos($string) ? substr( $string, pos($string) ) : $string;
 
1762
 
 
1763
   my @unescaped_parts = map {
 
1764
      my $part = $_;
 
1765
 
 
1766
      my $char_class = utf8::is_utf8($part)  # If it's a UTF-8 string,
 
1767
                     ? qr/(?=\p{ASCII})\W/   # We only care about non-word
 
1768
                     : qr/(?=\p{ASCII})\W|[\x{80}-\x{FF}]/; # Otherwise,
 
1769
      $part =~ s/\\($char_class)/$1/g;
 
1770
      $part;
 
1771
   } @escaped_parts;
 
1772
 
 
1773
   return @unescaped_parts;
 
1774
}
 
1775
 
1732
1776
1;
1733
1777
}
1734
1778
# ###########################################################################
2022
2066
   return bless $self, $class;
2023
2067
}
2024
2068
 
 
2069
sub get_create_table {
 
2070
   my ( $self, $dbh, $db, $tbl ) = @_;
 
2071
   die "I need a dbh parameter" unless $dbh;
 
2072
   die "I need a db parameter"  unless $db;
 
2073
   die "I need a tbl parameter" unless $tbl;
 
2074
   my $q = $self->{Quoter};
 
2075
 
 
2076
   my $sql = '/*!40101 SET @OLD_SQL_MODE := @@SQL_MODE, '
 
2077
           . q{@@SQL_MODE := REPLACE(REPLACE(@@SQL_MODE, 'ANSI_QUOTES', ''), ',,', ','), }
 
2078
           . '@OLD_QUOTE := @@SQL_QUOTE_SHOW_CREATE, '
 
2079
           . '@@SQL_QUOTE_SHOW_CREATE := 1 */';
 
2080
   PTDEBUG && _d($sql);
 
2081
   eval { $dbh->do($sql); };
 
2082
   PTDEBUG && $EVAL_ERROR && _d($EVAL_ERROR);
 
2083
 
 
2084
   $sql = 'USE ' . $q->quote($db);
 
2085
   PTDEBUG && _d($dbh, $sql);
 
2086
   $dbh->do($sql);
 
2087
 
 
2088
   $sql = "SHOW CREATE TABLE " . $q->quote($db, $tbl);
 
2089
   PTDEBUG && _d($sql);
 
2090
   my $href;
 
2091
   eval { $href = $dbh->selectrow_hashref($sql); };
 
2092
   if ( $EVAL_ERROR ) {
 
2093
      PTDEBUG && _d($EVAL_ERROR);
 
2094
      return;
 
2095
   }
 
2096
 
 
2097
   $sql = '/*!40101 SET @@SQL_MODE := @OLD_SQL_MODE, '
 
2098
        . '@@SQL_QUOTE_SHOW_CREATE := @OLD_QUOTE */';
 
2099
   PTDEBUG && _d($sql);
 
2100
   $dbh->do($sql);
 
2101
 
 
2102
   my ($key) = grep { m/create table/i } keys %$href;
 
2103
   if ( $key ) {
 
2104
      PTDEBUG && _d('This table is a base table');
 
2105
      $href->{$key}  =~ s/\b[ ]{2,}/ /g;
 
2106
      $href->{$key} .= "\n";
 
2107
   }
 
2108
   else {
 
2109
      PTDEBUG && _d('This table is a view');
 
2110
      ($key) = grep { m/create view/i } keys %$href;
 
2111
   }
 
2112
 
 
2113
   return $href->{$key};
 
2114
}
 
2115
 
2025
2116
sub parse {
2026
2117
   my ( $self, $ddl, $opts ) = @_;
2027
2118
   return unless $ddl;
2028
 
   if ( ref $ddl eq 'ARRAY' ) {
2029
 
      if ( lc $ddl->[0] eq 'table' ) {
2030
 
         $ddl = $ddl->[1];
2031
 
      }
2032
 
      else {
2033
 
         return {
2034
 
            engine => 'VIEW',
2035
 
         };
2036
 
      }
2037
 
   }
2038
2119
 
2039
2120
   if ( $ddl !~ m/CREATE (?:TEMPORARY )?TABLE `/ ) {
2040
2121
      die "Cannot parse table definition; is ANSI quoting "
2341
2422
   return $ddl;
2342
2423
}
2343
2424
 
2344
 
sub remove_secondary_indexes {
2345
 
   my ( $self, $ddl ) = @_;
2346
 
   my $sec_indexes_ddl;
2347
 
   my $tbl_struct = $self->parse($ddl);
2348
 
 
2349
 
   if ( ($tbl_struct->{engine} || '') =~ m/InnoDB/i ) {
2350
 
      my $clustered_key = $tbl_struct->{clustered_key};
2351
 
      $clustered_key  ||= '';
2352
 
 
2353
 
      my @sec_indexes   = map {
2354
 
         my $key_def = $_->{ddl};
2355
 
         $key_def =~ s/([\(\)])/\\$1/g;
2356
 
         $ddl =~ s/\s+$key_def//i;
2357
 
 
2358
 
         my $key_ddl = "ADD $_->{ddl}";
2359
 
         $key_ddl   .= ',' unless $key_ddl =~ m/,$/;
2360
 
         $key_ddl;
2361
 
      }
2362
 
      grep { $_->{name} ne $clustered_key }
2363
 
      values %{$tbl_struct->{keys}};
2364
 
      PTDEBUG && _d('Secondary indexes:', Dumper(\@sec_indexes));
2365
 
 
2366
 
      if ( @sec_indexes ) {
2367
 
         $sec_indexes_ddl = join(' ', @sec_indexes);
2368
 
         $sec_indexes_ddl =~ s/,$//;
2369
 
      }
2370
 
 
2371
 
      $ddl =~ s/,(\n\) )/$1/s;
2372
 
   }
2373
 
   else {
2374
 
      PTDEBUG && _d('Not removing secondary indexes from',
2375
 
         $tbl_struct->{engine}, 'table');
2376
 
   }
2377
 
 
2378
 
   return $ddl, $sec_indexes_ddl, $tbl_struct;
 
2425
sub get_table_status {
 
2426
   my ( $self, $dbh, $db, $like ) = @_;
 
2427
   my $q = $self->{Quoter};
 
2428
   my $sql = "SHOW TABLE STATUS FROM " . $q->quote($db);
 
2429
   my @params;
 
2430
   if ( $like ) {
 
2431
      $sql .= ' LIKE ?';
 
2432
      push @params, $like;
 
2433
   }
 
2434
   PTDEBUG && _d($sql, @params);
 
2435
   my $sth = $dbh->prepare($sql);
 
2436
   eval { $sth->execute(@params); };
 
2437
   if ($EVAL_ERROR) {
 
2438
      PTDEBUG && _d($EVAL_ERROR);
 
2439
      return;
 
2440
   }
 
2441
   my @tables = @{$sth->fetchall_arrayref({})};
 
2442
   @tables = map {
 
2443
      my %tbl; # Make a copy with lowercased keys
 
2444
      @tbl{ map { lc $_ } keys %$_ } = values %$_;
 
2445
      $tbl{engine} ||= $tbl{type} || $tbl{comment};
 
2446
      delete $tbl{type};
 
2447
      \%tbl;
 
2448
   } @tables;
 
2449
   return @tables;
2379
2450
}
2380
2451
 
2381
2452
sub _d {
2393
2464
# ###########################################################################
2394
2465
 
2395
2466
# ###########################################################################
2396
 
# MySQLDump package
2397
 
# This package is a copy without comments from the original.  The original
2398
 
# with comments and its test file can be found in the Bazaar repository at,
2399
 
#   lib/MySQLDump.pm
2400
 
#   t/lib/MySQLDump.t
2401
 
# See https://launchpad.net/percona-toolkit for more information.
2402
 
# ###########################################################################
2403
 
{
2404
 
package MySQLDump;
2405
 
 
2406
 
use strict;
2407
 
use warnings FATAL => 'all';
2408
 
use English qw(-no_match_vars);
2409
 
use constant PTDEBUG => $ENV{PTDEBUG} || 0;
2410
 
 
2411
 
( our $before = <<'EOF') =~ s/^   //gm;
2412
 
   /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
2413
 
   /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
2414
 
   /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
2415
 
   /*!40101 SET NAMES utf8 */;
2416
 
   /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
2417
 
   /*!40103 SET TIME_ZONE='+00:00' */;
2418
 
   /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
2419
 
   /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
2420
 
   /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
2421
 
   /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
2422
 
EOF
2423
 
 
2424
 
( our $after = <<'EOF') =~ s/^   //gm;
2425
 
   /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
2426
 
   /*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
2427
 
   /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
2428
 
   /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
2429
 
   /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
2430
 
   /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
2431
 
   /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
2432
 
   /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
2433
 
EOF
2434
 
 
2435
 
sub new {
2436
 
   my ( $class, %args ) = @_;
2437
 
   my $self = {
2438
 
      cache => 0,  # Afaik no script uses this cache any longer because
2439
 
   };
2440
 
   return bless $self, $class;
2441
 
}
2442
 
 
2443
 
sub dump {
2444
 
   my ( $self, $dbh, $quoter, $db, $tbl, $what ) = @_;
2445
 
 
2446
 
   if ( $what eq 'table' ) {
2447
 
      my $ddl = $self->get_create_table($dbh, $quoter, $db, $tbl);
2448
 
      return unless $ddl;
2449
 
      if ( $ddl->[0] eq 'table' ) {
2450
 
         return $before
2451
 
            . 'DROP TABLE IF EXISTS ' . $quoter->quote($tbl) . ";\n"
2452
 
            . $ddl->[1] . ";\n";
2453
 
      }
2454
 
      else {
2455
 
         return 'DROP TABLE IF EXISTS ' . $quoter->quote($tbl) . ";\n"
2456
 
            . '/*!50001 DROP VIEW IF EXISTS '
2457
 
            . $quoter->quote($tbl) . "*/;\n/*!50001 "
2458
 
            . $self->get_tmp_table($dbh, $quoter, $db, $tbl) . "*/;\n";
2459
 
      }
2460
 
   }
2461
 
   elsif ( $what eq 'triggers' ) {
2462
 
      my $trgs = $self->get_triggers($dbh, $quoter, $db, $tbl);
2463
 
      if ( $trgs && @$trgs ) {
2464
 
         my $result = $before . "\nDELIMITER ;;\n";
2465
 
         foreach my $trg ( @$trgs ) {
2466
 
            if ( $trg->{sql_mode} ) {
2467
 
               $result .= qq{/*!50003 SET SESSION SQL_MODE='$trg->{sql_mode}' */;;\n};
2468
 
            }
2469
 
            $result .= "/*!50003 CREATE */ ";
2470
 
            if ( $trg->{definer} ) {
2471
 
               my ( $user, $host )
2472
 
                  = map { s/'/''/g; "'$_'"; }
2473
 
                    split('@', $trg->{definer}, 2);
2474
 
               $result .= "/*!50017 DEFINER=$user\@$host */ ";
2475
 
            }
2476
 
            $result .= sprintf("/*!50003 TRIGGER %s %s %s ON %s\nFOR EACH ROW %s */;;\n\n",
2477
 
               $quoter->quote($trg->{trigger}),
2478
 
               @{$trg}{qw(timing event)},
2479
 
               $quoter->quote($trg->{table}),
2480
 
               $trg->{statement});
2481
 
         }
2482
 
         $result .= "DELIMITER ;\n\n/*!50003 SET SESSION SQL_MODE=\@OLD_SQL_MODE */;\n\n";
2483
 
         return $result;
2484
 
      }
2485
 
      else {
2486
 
         return undef;
2487
 
      }
2488
 
   }
2489
 
   elsif ( $what eq 'view' ) {
2490
 
      my $ddl = $self->get_create_table($dbh, $quoter, $db, $tbl);
2491
 
      return '/*!50001 DROP TABLE IF EXISTS ' . $quoter->quote($tbl) . "*/;\n"
2492
 
         . '/*!50001 DROP VIEW IF EXISTS ' . $quoter->quote($tbl) . "*/;\n"
2493
 
         . '/*!50001 ' . $ddl->[1] . "*/;\n";
2494
 
   }
2495
 
   else {
2496
 
      die "You didn't say what to dump.";
2497
 
   }
2498
 
}
2499
 
 
2500
 
sub _use_db {
2501
 
   my ( $self, $dbh, $quoter, $new ) = @_;
2502
 
   if ( !$new ) {
2503
 
      PTDEBUG && _d('No new DB to use');
2504
 
      return;
2505
 
   }
2506
 
   my $sql = 'USE ' . $quoter->quote($new);
2507
 
   PTDEBUG && _d($dbh, $sql);
2508
 
   $dbh->do($sql);
2509
 
   return;
2510
 
}
2511
 
 
2512
 
sub get_create_table {
2513
 
   my ( $self, $dbh, $quoter, $db, $tbl ) = @_;
2514
 
   if ( !$self->{cache} || !$self->{tables}->{$db}->{$tbl} ) {
2515
 
      my $sql = '/*!40101 SET @OLD_SQL_MODE := @@SQL_MODE, '
2516
 
         . q{@@SQL_MODE := REPLACE(REPLACE(@@SQL_MODE, 'ANSI_QUOTES', ''), ',,', ','), }
2517
 
         . '@OLD_QUOTE := @@SQL_QUOTE_SHOW_CREATE, '
2518
 
         . '@@SQL_QUOTE_SHOW_CREATE := 1 */';
2519
 
      PTDEBUG && _d($sql);
2520
 
      eval { $dbh->do($sql); };
2521
 
      PTDEBUG && $EVAL_ERROR && _d($EVAL_ERROR);
2522
 
      $self->_use_db($dbh, $quoter, $db);
2523
 
      $sql = "SHOW CREATE TABLE " . $quoter->quote($db, $tbl);
2524
 
      PTDEBUG && _d($sql);
2525
 
      my $href;
2526
 
      eval { $href = $dbh->selectrow_hashref($sql); };
2527
 
      if ( $EVAL_ERROR ) {
2528
 
         warn "Failed to $sql.  The table may be damaged.\nError: $EVAL_ERROR";
2529
 
         return;
2530
 
      }
2531
 
 
2532
 
      $sql = '/*!40101 SET @@SQL_MODE := @OLD_SQL_MODE, '
2533
 
         . '@@SQL_QUOTE_SHOW_CREATE := @OLD_QUOTE */';
2534
 
      PTDEBUG && _d($sql);
2535
 
      $dbh->do($sql);
2536
 
      my ($key) = grep { m/create table/i } keys %$href;
2537
 
      if ( $key ) {
2538
 
         PTDEBUG && _d('This table is a base table');
2539
 
         $self->{tables}->{$db}->{$tbl} = [ 'table', $href->{$key} ];
2540
 
      }
2541
 
      else {
2542
 
         PTDEBUG && _d('This table is a view');
2543
 
         ($key) = grep { m/create view/i } keys %$href;
2544
 
         $self->{tables}->{$db}->{$tbl} = [ 'view', $href->{$key} ];
2545
 
      }
2546
 
   }
2547
 
   return $self->{tables}->{$db}->{$tbl};
2548
 
}
2549
 
 
2550
 
sub get_columns {
2551
 
   my ( $self, $dbh, $quoter, $db, $tbl ) = @_;
2552
 
   PTDEBUG && _d('Get columns for', $db, $tbl);
2553
 
   if ( !$self->{cache} || !$self->{columns}->{$db}->{$tbl} ) {
2554
 
      $self->_use_db($dbh, $quoter, $db);
2555
 
      my $sql = "SHOW COLUMNS FROM " . $quoter->quote($db, $tbl);
2556
 
      PTDEBUG && _d($sql);
2557
 
      my $cols = $dbh->selectall_arrayref($sql, { Slice => {} });
2558
 
 
2559
 
      $self->{columns}->{$db}->{$tbl} = [
2560
 
         map {
2561
 
            my %row;
2562
 
            @row{ map { lc $_ } keys %$_ } = values %$_;
2563
 
            \%row;
2564
 
         } @$cols
2565
 
      ];
2566
 
   }
2567
 
   return $self->{columns}->{$db}->{$tbl};
2568
 
}
2569
 
 
2570
 
sub get_tmp_table {
2571
 
   my ( $self, $dbh, $quoter, $db, $tbl ) = @_;
2572
 
   my $result = 'CREATE TABLE ' . $quoter->quote($tbl) . " (\n";
2573
 
   $result .= join(",\n",
2574
 
      map { '  ' . $quoter->quote($_->{field}) . ' ' . $_->{type} }
2575
 
      @{$self->get_columns($dbh, $quoter, $db, $tbl)});
2576
 
   $result .= "\n)";
2577
 
   PTDEBUG && _d($result);
2578
 
   return $result;
2579
 
}
2580
 
 
2581
 
sub get_triggers {
2582
 
   my ( $self, $dbh, $quoter, $db, $tbl ) = @_;
2583
 
   if ( !$self->{cache} || !$self->{triggers}->{$db} ) {
2584
 
      $self->{triggers}->{$db} = {};
2585
 
      my $sql = '/*!40101 SET @OLD_SQL_MODE := @@SQL_MODE, '
2586
 
         . q{@@SQL_MODE := REPLACE(REPLACE(@@SQL_MODE, 'ANSI_QUOTES', ''), ',,', ','), }
2587
 
         . '@OLD_QUOTE := @@SQL_QUOTE_SHOW_CREATE, '
2588
 
         . '@@SQL_QUOTE_SHOW_CREATE := 1 */';
2589
 
      PTDEBUG && _d($sql);
2590
 
      eval { $dbh->do($sql); };
2591
 
      PTDEBUG && $EVAL_ERROR && _d($EVAL_ERROR);
2592
 
      $sql = "SHOW TRIGGERS FROM " . $quoter->quote($db);
2593
 
      PTDEBUG && _d($sql);
2594
 
      my $sth = $dbh->prepare($sql);
2595
 
      $sth->execute();
2596
 
      if ( $sth->rows ) {
2597
 
         my $trgs = $sth->fetchall_arrayref({});
2598
 
         foreach my $trg (@$trgs) {
2599
 
            my %trg;
2600
 
            @trg{ map { lc $_ } keys %$trg } = values %$trg;
2601
 
            push @{ $self->{triggers}->{$db}->{ $trg{table} } }, \%trg;
2602
 
         }
2603
 
      }
2604
 
      $sql = '/*!40101 SET @@SQL_MODE := @OLD_SQL_MODE, '
2605
 
         . '@@SQL_QUOTE_SHOW_CREATE := @OLD_QUOTE */';
2606
 
      PTDEBUG && _d($sql);
2607
 
      $dbh->do($sql);
2608
 
   }
2609
 
   if ( $tbl ) {
2610
 
      return $self->{triggers}->{$db}->{$tbl};
2611
 
   }
2612
 
   return values %{$self->{triggers}->{$db}};
2613
 
}
2614
 
 
2615
 
sub get_databases {
2616
 
   my ( $self, $dbh, $quoter, $like ) = @_;
2617
 
   if ( !$self->{cache} || !$self->{databases} || $like ) {
2618
 
      my $sql = 'SHOW DATABASES';
2619
 
      my @params;
2620
 
      if ( $like ) {
2621
 
         $sql .= ' LIKE ?';
2622
 
         push @params, $like;
2623
 
      }
2624
 
      my $sth = $dbh->prepare($sql);
2625
 
      PTDEBUG && _d($sql, @params);
2626
 
      $sth->execute( @params );
2627
 
      my @dbs = map { $_->[0] } @{$sth->fetchall_arrayref()};
2628
 
      $self->{databases} = \@dbs unless $like;
2629
 
      return @dbs;
2630
 
   }
2631
 
   return @{$self->{databases}};
2632
 
}
2633
 
 
2634
 
sub get_table_status {
2635
 
   my ( $self, $dbh, $quoter, $db, $like ) = @_;
2636
 
   if ( !$self->{cache} || !$self->{table_status}->{$db} || $like ) {
2637
 
      my $sql = "SHOW TABLE STATUS FROM " . $quoter->quote($db);
2638
 
      my @params;
2639
 
      if ( $like ) {
2640
 
         $sql .= ' LIKE ?';
2641
 
         push @params, $like;
2642
 
      }
2643
 
      PTDEBUG && _d($sql, @params);
2644
 
      my $sth = $dbh->prepare($sql);
2645
 
      $sth->execute(@params);
2646
 
      my @tables = @{$sth->fetchall_arrayref({})};
2647
 
      @tables = map {
2648
 
         my %tbl; # Make a copy with lowercased keys
2649
 
         @tbl{ map { lc $_ } keys %$_ } = values %$_;
2650
 
         $tbl{engine} ||= $tbl{type} || $tbl{comment};
2651
 
         delete $tbl{type};
2652
 
         \%tbl;
2653
 
      } @tables;
2654
 
      $self->{table_status}->{$db} = \@tables unless $like;
2655
 
      return @tables;
2656
 
   }
2657
 
   return @{$self->{table_status}->{$db}};
2658
 
}
2659
 
 
2660
 
sub get_table_list {
2661
 
   my ( $self, $dbh, $quoter, $db, $like ) = @_;
2662
 
   if ( !$self->{cache} || !$self->{table_list}->{$db} || $like ) {
2663
 
      my $sql = "SHOW /*!50002 FULL*/ TABLES FROM " . $quoter->quote($db);
2664
 
      my @params;
2665
 
      if ( $like ) {
2666
 
         $sql .= ' LIKE ?';
2667
 
         push @params, $like;
2668
 
      }
2669
 
      PTDEBUG && _d($sql, @params);
2670
 
      my $sth = $dbh->prepare($sql);
2671
 
      $sth->execute(@params);
2672
 
      my @tables = @{$sth->fetchall_arrayref()};
2673
 
      @tables = map {
2674
 
         my %tbl = (
2675
 
            name   => $_->[0],
2676
 
            engine => ($_->[1] || '') eq 'VIEW' ? 'VIEW' : '',
2677
 
         );
2678
 
         \%tbl;
2679
 
      } @tables;
2680
 
      $self->{table_list}->{$db} = \@tables unless $like;
2681
 
      return @tables;
2682
 
   }
2683
 
   return @{$self->{table_list}->{$db}};
2684
 
}
2685
 
 
2686
 
sub _d {
2687
 
   my ($package, undef, $line) = caller 0;
2688
 
   @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
2689
 
        map { defined $_ ? $_ : 'undef' }
2690
 
        @_;
2691
 
   print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
2692
 
}
2693
 
 
2694
 
1;
2695
 
}
2696
 
# ###########################################################################
2697
 
# End MySQLDump package
2698
 
# ###########################################################################
2699
 
 
2700
 
# ###########################################################################
2701
2467
# TableChunker package
2702
2468
# This package is a copy without comments from the original.  The original
2703
2469
# with comments and its test file can be found in the Bazaar repository at,
3698
3464
sub start {
3699
3465
   my ( $self, $start ) = @_;
3700
3466
   $self->{start} = $self->{last_reported} = $start || time();
 
3467
   $self->{first_report} = 0;
3701
3468
}
3702
3469
 
3703
3470
sub update {
3704
 
   my ( $self, $callback, $now ) = @_;
 
3471
   my ( $self, $callback, %args ) = @_;
3705
3472
   my $jobsize   = $self->{jobsize};
3706
 
   $now        ||= time();
 
3473
   my $now    ||= $args{now} || time;
 
3474
 
3707
3475
   $self->{iterations}++; # How many updates have happened;
3708
3476
 
 
3477
   if ( !$self->{first_report} && $args{first_report} ) {
 
3478
      $args{first_report}->();
 
3479
      $self->{first_report} = 1;
 
3480
   }
 
3481
 
3709
3482
   if ( $self->{report} eq 'time'
3710
3483
         && $self->{interval} > $now - $self->{last_reported}
3711
3484
   ) {
3786
3559
 
3787
3560
sub new {
3788
3561
   my ( $class, %args ) = @_;
3789
 
   my @required_args = qw();
 
3562
   my @required_args = qw(Quoter);
3790
3563
   foreach my $arg ( @required_args ) {
3791
3564
      die "I need a $arg argument" unless $args{$arg};
3792
3565
   }
3793
3566
 
3794
3567
   my $self = {
3795
 
      %args,
 
3568
      Quoter => $args{Quoter},
3796
3569
   };
3797
3570
 
3798
3571
   return bless $self, $class;
3822
3595
      die "I need a $arg argument" unless $args{$arg};
3823
3596
   }
3824
3597
   my ($db, $tbl, $tmp_tbl, $chunk_column) = @args{@required_args};
3825
 
 
3826
 
   my $old_table  = "`$db`.`$tbl`";
3827
 
   my $new_table  = "`$db`.`$tmp_tbl`";
3828
 
   my $new_values = join(', ', map { "NEW.$_" } @{$args{columns}});
3829
 
   my $columns    = join(', ', @{$args{columns}});
 
3598
   my $q = $self->{Quoter};
 
3599
 
 
3600
   $chunk_column = $q->quote($chunk_column);
 
3601
 
 
3602
   my $old_table  = $q->quote($db, $tbl);
 
3603
   my $new_table  = $q->quote($db, $tmp_tbl);
 
3604
   my $new_values = join(', ', map { "NEW.".$q->quote($_) } @{$args{columns}});
 
3605
   my $columns    = join(', ', map { $q->quote($_) }        @{$args{columns}});
3830
3606
 
3831
3607
   my $delete_trigger = "CREATE TRIGGER mk_osc_del AFTER DELETE ON $old_table "
3832
3608
                      . "FOR EACH ROW "
3862
3638
      die "I need a $arg argument" unless $args{$arg};
3863
3639
   }
3864
3640
   my ($dbh, $db, $msg) = @args{@required_args};
 
3641
   my $q = $self->{Quoter};
3865
3642
 
3866
3643
   foreach my $trigger ( qw(del ins upd) ) {
3867
 
      my $sql = "DROP TRIGGER IF EXISTS `$db`.`mk_osc_$trigger`";
 
3644
      my $sql = "DROP TRIGGER IF EXISTS " . $q->quote($db, "mk_osc_$trigger");
3868
3645
      $msg->($sql);
3869
3646
      $dbh->do($sql) unless $args{print};
3870
3647
   }
3904
3681
 
3905
3682
sub new {
3906
3683
   my ( $class, %args ) = @_;
3907
 
   my @required_args = qw(Retry);
 
3684
   my @required_args = qw(Retry Quoter);
3908
3685
   foreach my $arg ( @required_args ) {
3909
3686
      die "I need a $arg argument" unless $args{$arg};
3910
3687
   }
3911
3688
 
3912
3689
   my $self = {
3913
 
      %args,
 
3690
      Retry  => $args{Retry},
 
3691
      Quoter => $args{Quoter},
3914
3692
   };
3915
3693
 
3916
3694
   return bless $self, $class;
3923
3701
      die "I need a $arg argument" unless $args{$arg};
3924
3702
   }
3925
3703
   my ($dbh, $msg, $from_table, $to_table, $chunks) = @args{@required_args};
 
3704
   my $q        = $self->{Quoter};
3926
3705
   my $pr       = $args{Progress};
3927
3706
   my $sleep    = $args{sleep};
3928
 
   my $columns  = join(', ', @{$args{columns}});
 
3707
   my $columns  = join(', ', map { $q->quote($_) } @{$args{columns}});
3929
3708
   my $n_chunks = @$chunks - 1;
3930
3709
 
3931
3710
   for my $chunkno ( 0..$n_chunks ) {
3950
3729
            wait  => sub { sleep 1; },
3951
3730
            tries => 3,
3952
3731
            try   => sub {
3953
 
               my ( %args ) = @_;
3954
 
                  eval {
3955
 
                     $dbh->do($sql);
3956
 
                  };
3957
 
                  if ( $EVAL_ERROR ) {
3958
 
                     PTDEBUG && _d($EVAL_ERROR);
3959
 
                     if ( $EVAL_ERROR =~ m/Lock wait timeout exceeded/ ) {
3960
 
                        $error = $EVAL_ERROR;
3961
 
                        if ( $args{tryno} > 1 ) {
3962
 
                           $msg->("Lock wait timeout exceeded; retrying $sql");
3963
 
                        }
3964
 
                        return;
3965
 
                     }
3966
 
                     die $EVAL_ERROR;
3967
 
                  }
3968
 
                  return 1;
3969
 
            },
3970
 
            on_failure => sub { die $error; },
 
3732
               $dbh->do($sql);
 
3733
               return;
 
3734
            },
 
3735
            fail => sub {
 
3736
               my (%args) = @_;
 
3737
               my $error = $args{error};
 
3738
               PTDEBUG && _d($error);
 
3739
               if ( $error =~ m/Lock wait timeout exceeded/ ) {
 
3740
                  $msg->("Lock wait timeout exceeded; retrying $sql");
 
3741
                  return 1; # call wait, call try
 
3742
               }
 
3743
               return 0; # call final_fail
 
3744
            },
 
3745
            final_fail => sub {
 
3746
               my (%args) = @_;
 
3747
               die $args{error};
 
3748
            },
3971
3749
         );
3972
3750
      }
3973
3751
 
4024
3802
 
4025
3803
sub retry {
4026
3804
   my ( $self, %args ) = @_;
4027
 
   my @required_args = qw(try wait);
 
3805
   my @required_args = qw(try fail final_fail);
4028
3806
   foreach my $arg ( @required_args ) {
4029
3807
      die "I need a $arg argument" unless $args{$arg};
4030
3808
   };
4031
 
   my ($try, $wait) = @args{@required_args};
 
3809
   my ($try, $fail, $final_fail) = @args{@required_args};
 
3810
   my $wait  = $args{wait}  || sub { sleep 1; };
4032
3811
   my $tries = $args{tries} || 3;
4033
3812
 
 
3813
   my $last_error;
4034
3814
   my $tryno = 0;
 
3815
   TRY:
4035
3816
   while ( ++$tryno <= $tries ) {
4036
 
      PTDEBUG && _d("Retry", $tryno, "of", $tries);
 
3817
      PTDEBUG && _d("Try", $tryno, "of", $tries);
4037
3818
      my $result;
4038
3819
      eval {
4039
3820
         $result = $try->(tryno=>$tryno);
4040
3821
      };
 
3822
      if ( $EVAL_ERROR ) {
 
3823
         PTDEBUG && _d("Try code failed:", $EVAL_ERROR);
 
3824
         $last_error = $EVAL_ERROR;
4041
3825
 
4042
 
      if ( defined $result ) {
 
3826
         if ( $tryno < $tries ) {   # more retries
 
3827
            my $retry = $fail->(tryno=>$tryno, error=>$last_error);
 
3828
            last TRY unless $retry;
 
3829
            PTDEBUG && _d("Calling wait code");
 
3830
            $wait->(tryno=>$tryno);
 
3831
         }
 
3832
      }
 
3833
      else {
4043
3834
         PTDEBUG && _d("Try code succeeded");
4044
 
         if ( my $on_success = $args{on_success} ) {
4045
 
            PTDEBUG && _d("Calling on_success code");
4046
 
            $on_success->(tryno=>$tryno, result=>$result);
4047
 
         }
4048
3835
         return $result;
4049
3836
      }
4050
 
 
4051
 
      if ( $EVAL_ERROR ) {
4052
 
         PTDEBUG && _d("Try code died:", $EVAL_ERROR);
4053
 
         die $EVAL_ERROR unless $args{retry_on_die};
4054
 
      }
4055
 
 
4056
 
      if ( $tryno < $tries ) {
4057
 
         PTDEBUG && _d("Try code failed, calling wait code");
4058
 
         $wait->(tryno=>$tryno);
4059
 
      }
4060
 
   }
4061
 
 
4062
 
   PTDEBUG && _d("Try code did not succeed");
4063
 
   if ( my $on_failure = $args{on_failure} ) {
4064
 
      PTDEBUG && _d("Calling on_failure code");
4065
 
      $on_failure->();
4066
 
   }
4067
 
 
4068
 
   return;
 
3837
   }
 
3838
 
 
3839
   PTDEBUG && _d('Try code did not succeed');
 
3840
   return $final_fail->(error=>$last_error);
4069
3841
}
4070
3842
 
4071
3843
sub _d {
4110
3882
   my $vp      = new VersionParser();
4111
3883
   my $q       = new Quoter();
4112
3884
   my $tp      = new TableParser(Quoter => $q);
4113
 
   my $du      = new MySQLDump();
4114
3885
   my $chunker = new TableChunker(Quoter => $q, TableParser => $tp);
4115
3886
 
4116
3887
   # ########################################################################
4225
3996
      Quoter        => $q,
4226
3997
      TableParser   => $tp,
4227
3998
      TableChunker  => $chunker,
4228
 
      MySQLDump     => $du,
4229
3999
      VersionParser => $vp,
4230
4000
   );
4231
4001
 
4233
4003
   # Create the capture-sync and copy-rows plugins.  Currently, we just have
4234
4004
   # one method for each.
4235
4005
   # ########################################################################
4236
 
   my $capture_sync = new OSCCaptureSync();
4237
 
   my $copy_rows    = new CopyRowsInsertSelect(Retry => new Retry());
 
4006
   my $capture_sync = new OSCCaptureSync(Quoter => $q);
 
4007
   my $copy_rows    = new CopyRowsInsertSelect(
 
4008
      Retry  => new Retry(),
 
4009
      Quoter => $q,
 
4010
   );
4238
4011
 
4239
4012
   # More values are added later.  These are the minimum need to do --cleanup.
4240
4013
   my %plugin_args = (
4379
4152
      # it manually.
4380
4153
      if ( !$o->get('print') ) {
4381
4154
         my $tmp_tbl_struct = $tp->parse(
4382
 
               $du->get_create_table($dbh, $q, $db, $tmp_tbl));
 
4155
            $tp->get_create_table($dbh, $db, $tmp_tbl));
4383
4156
 
4384
4157
         @columns = intersection([
4385
4158
            $plugin_args{tbl_struct}->{is_col},
4489
4262
# ############################################################################
4490
4263
sub check_tables {
4491
4264
   my ( %args ) = @_;
4492
 
   my @required_args = qw(dbh db tbl tmp_tbl old_tbl VersionParser Quoter TableParser OptionParser TableChunker MySQLDump);
 
4265
   my @required_args = qw(dbh db tbl tmp_tbl old_tbl VersionParser Quoter TableParser OptionParser TableChunker);
4493
4266
   foreach my $arg ( @required_args ) {
4494
4267
      die "I need a $arg argument" unless $args{$arg};
4495
4268
   }
4528
4301
 
4529
4302
   # For now, we require that the old table has an exact-chunkable
4530
4303
   # column (i.e. unique single-column).
4531
 
   $tbl_info{tbl_struct} = $tp->parse(
4532
 
      $args{MySQLDump}->get_create_table($dbh, $args{Quoter}, $db, $tbl));
 
4304
   $tbl_info{tbl_struct} = $tp->parse($tp->get_create_table($dbh, $db, $tbl));
4533
4305
   my ($exact, @chunkable_cols) = $args{TableChunker}->find_chunk_columns(
4534
4306
      tbl_struct => $tbl_info{tbl_struct},
4535
4307
      exact      => 1,