#!/usr/bin/env perl

BEGIN {
   die "The PERCONA_TOOLKIT_BRANCH environment variable is not set.\n"
      unless $ENV{PERCONA_TOOLKIT_BRANCH} && -d $ENV{PERCONA_TOOLKIT_BRANCH};
   unshift @INC, "$ENV{PERCONA_TOOLKIT_BRANCH}/lib";
};

use strict;
use warnings FATAL => 'all';
use English qw(-no_match_vars);
use Test::More;

use PerconaTest;
use Sandbox;
require "$trunk/bin/pt-online-schema-change";
require VersionParser;

use Time::HiRes qw(sleep);
use Data::Dumper;
$Data::Dumper::Indent    = 1;
$Data::Dumper::Sortkeys  = 1;
$Data::Dumper::Quotekeys = 0;

my $dp         = new DSNParser(opts=>$dsn_opts);
my $sb         = new Sandbox(basedir => '/tmp', DSNParser => $dp);
my $master_dbh = $sb->get_dbh_for('master');
my $slave_dbh  = $sb->get_dbh_for('slave1');

if ( !$master_dbh ) {
   plan skip_all => 'Cannot connect to sandbox master';
}
elsif ( !$slave_dbh ) {
   plan skip_all => 'Cannot connect to sandbox slave';
}

my $output;
my $master_dsn = "h=127.1,P=12345,u=msandbox,p=msandbox";
my $sample     = "t/pt-online-schema-change/samples";
my $exit;
my $rows;

my $query_table_stop   = "/tmp/query_table.$PID.stop";
my $query_table_pid    = "/tmp/query_table.$PID.pid";
my $query_table_output = "/tmp/query_table.$PID.output";

sub start_query_table {
   my ($db, $tbl, $pkcol) = @_;

   diag(`rm -rf $query_table_stop`);
   diag(`echo > $query_table_output`);

   my $cmd = "$trunk/$sample/query_table.pl";
   system("$cmd 127.1 12345 $db $tbl $pkcol $query_table_stop $query_table_pid >$query_table_output &");
   wait_until(sub{-e $query_table_pid});

   return;
}

sub stop_query_table {
   diag(`touch $query_table_stop`);
   open my $fh, '<', $query_table_pid or die $OS_ERROR;
   my ($p) = <$fh>;
   close $fh;
   chomp $p;
   wait_until(sub{!kill 0, $p});
   return;
}

sub get_ids {
   open my $fh, '<', $query_table_output
      or die "Cannot open $query_table_output: $OS_ERROR";
   my @lines = <$fh>;
   close $fh;

   my %ids = (
      updated  => '',
      deleted  => '',
      inserted => '',
   );
   foreach my $line ( @lines ) {
      my ($stmt, $ids) = split(':', $line);
      chomp $ids;
      $ids{$stmt} = $ids || '';
   }

   return \%ids;
}

sub check_ids {
   my ( $db, $tbl, $pkcol, $ids, $test ) = @_;
   my $rows;

   my $n_updated  = $ids->{updated} ? ($ids->{updated}  =~ tr/,//) : 0;
   my $n_deleted  = $ids->{deleted} ? ($ids->{deleted}  =~ tr/,//) : 0;
   my $n_inserted = $ids->{inserted} ? ($ids->{inserted} =~ tr/,//) : 0;

   # "1,1"=~tr/,// returns 1 but is 2 values
   $n_updated++ if $ids->{updated};
   $n_deleted++ if $ids->{deleted};
   $n_inserted++ if $ids->{inserted};

   $rows = $master_dbh->selectrow_arrayref(
      "SELECT COUNT($pkcol) FROM $db.$tbl");
   is(
      $rows->[0],
      500 + $n_inserted - $n_deleted,
      "$test: new table rows: 500 original + $n_inserted inserted - $n_deleted deleted"
   ) or diag(Dumper($rows));

   my $where_ids = $ids->{inserted} ? $ids->{inserted} : '0';
   $rows = $master_dbh->selectall_arrayref(
      "SELECT $pkcol FROM $db.$tbl WHERE $pkcol > 500 AND $pkcol NOT IN ($where_ids)");
   is_deeply(
      $rows,
      [],
      "$test: no extra rows inserted in new table"
   ) or die(Dumper($rows));

   if ( $n_deleted ) {
      $rows = $master_dbh->selectall_arrayref(
         "SELECT $pkcol FROM $db.$tbl WHERE $pkcol IN ($ids->{deleted})");
      is_deeply(
         $rows,
         [],
         "$test: no deleted rows present in new table"
      ) or diag(Dumper($rows));
   }
   else {
      ok(
         1,
         "$test: no rows deleted"
      );
   };

   if ( $n_updated ) {
      my $sql = "SELECT $pkcol FROM $db.$tbl WHERE $pkcol IN ($ids->{updated}) "
              . "AND c NOT LIKE 'updated%'";
      $rows = $master_dbh->selectall_arrayref($sql);
      is_deeply(
         $rows,
         [],
         "$test: updated rows correct in new table"
      ) or diag(Dumper($rows));
   }
   else {
      ok(
         1,
         "$test: no rows updated"
      );
   }

   return;
}
   
# #############################################################################
# Attempt to alter a table while another process is changing it.
# #############################################################################

my $db_flavor = VersionParser->new($master_dbh)->flavor();
if ( $db_flavor =~ m/XtraDB Cluster/ ) {
   $sb->load_file('master', "$sample/basic_no_fks_innodb.sql");
}
else {
   $sb->load_file('master', "$sample/basic_no_fks.sql");
}
$master_dbh->do("USE pt_osc");
$master_dbh->do("TRUNCATE TABLE t");
$master_dbh->do("LOAD DATA INFILE '$trunk/t/pt-online-schema-change/samples/basic_no_fks.data' INTO TABLE t");
$master_dbh->do("ANALYZE TABLE t");
$sb->wait_for_slaves();

# Start inserting, updating, and deleting rows at random.
start_query_table(qw(pt_osc t id));

# While that's ^ happening, alter the table.
($output, $exit) = full_output(
   sub { pt_online_schema_change::main(
      "$master_dsn,D=pt_osc,t=t",
      qw(--set-vars innodb_lock_wait_timeout=5),
      qw(--print --execute --chunk-size 100 --alter ENGINE=InnoDB --no-check-plan)) },
   stderr => 1,
);

# Stop changing the table's data.
stop_query_table();

like(
   $output,
   qr/Successfully altered `pt_osc`.`t`/,
   'Change engine: altered OK'
);

$rows = $master_dbh->selectall_hashref('SHOW TABLE STATUS FROM pt_osc', 'name');
is(
   $rows->{t}->{engine},
   'InnoDB',
   "Change engine: new table ENGINE=InnoDB"
) or warn Dumper($rows);

is(
   scalar keys %$rows,
   1,
   "Change engine: dropped old table"
);

is(
   $exit,
   0,
   "Change engine: exit status 0"
);

# #############################################################################
# Check that triggers work when renaming a column
# #############################################################################

$master_dbh->do("USE pt_osc");
$master_dbh->do("TRUNCATE TABLE t");
$master_dbh->do("LOAD DATA INFILE '$trunk/t/pt-online-schema-change/samples/basic_no_fks.data' INTO TABLE t");
$master_dbh->do("ANALYZE TABLE t");
$sb->wait_for_slaves();

# Start inserting, updating, and deleting rows at random.
start_query_table(qw(pt_osc t id));

# While that's ^ happening, alter the table.
($output, $exit) = full_output(
   sub { pt_online_schema_change::main(
      "$master_dsn,D=pt_osc,t=t",
      qw(--set-vars innodb_lock_wait_timeout=5),
      qw(--print --execute --chunk-size 100 --no-check-alter),
      '--alter', 'CHANGE COLUMN d q date',
   ) },
   stderr => 1,
);

# Stop changing the table's data.
stop_query_table();

like(
   $output,
   qr/Successfully altered `pt_osc`.`t`/,
   'Rename column: altered OK'
);

is(
   $exit,
   0,
   "Rename columnn: exit status 0"
);

$sb->wait_for_slaves();

check_ids(qw(pt_osc t id), get_ids(), "Rename column");

# #############################################################################
# Done.
# #############################################################################
unlink $query_table_stop or warn $OS_ERROR;
unlink $query_table_output or warn $OS_ERROR;
unlink $query_table_pid or warn $OS_ERROR;
$sb->wipe_clean($master_dbh);
ok($sb->ok(), "Sandbox servers") or BAIL_OUT(__FILE__ . " broke the sandbox");
done_testing;
exit;
