bzdbcopy.pl code style

hinted-selects
Vitaliy Filippov 2014-10-27 18:35:15 +03:00
parent 904055ea47
commit cf849b33a7
1 changed files with 60 additions and 55 deletions

View File

@ -1,5 +1,4 @@
#!/usr/bin/perl -w #!/usr/bin/perl -w
#
# The contents of this file are subject to the Mozilla Public # The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file # License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of # except in compliance with the License. You may obtain a copy of
@ -48,24 +47,26 @@ use constant TARGET_DB_HOST => 'localhost';
# MAIN SCRIPT # MAIN SCRIPT
##################################################################### #####################################################################
print "Connecting to the '" . SOURCE_DB_NAME . "' source database on " print "Connecting to the '" . SOURCE_DB_NAME . "' source database on " . SOURCE_DB_TYPE . "...\n";
. SOURCE_DB_TYPE . "...\n"; my $source_db = Bugzilla::DB::_connect(
my $source_db = Bugzilla::DB::_connect(SOURCE_DB_TYPE, SOURCE_DB_HOST, SOURCE_DB_TYPE, SOURCE_DB_HOST, SOURCE_DB_NAME,
SOURCE_DB_NAME, undef, undef, SOURCE_DB_USER, SOURCE_DB_PASSWORD); undef, undef, SOURCE_DB_USER, SOURCE_DB_PASSWORD
);
# Don't read entire tables into memory. # Don't read entire tables into memory.
if (SOURCE_DB_TYPE eq 'Mysql') { if (SOURCE_DB_TYPE eq 'Mysql')
$source_db->{'mysql_use_result'}=1; {
$source_db->{mysql_use_result} = 1;
# MySQL cannot have two queries running at the same time. Ensure the schema # MySQL cannot have two queries running at the same time. Ensure the schema
# is loaded from the database so bz_column_info will not execute a query # is loaded from the database so bz_column_info will not execute a query
$source_db->_bz_real_schema; $source_db->_bz_real_schema;
} }
print "Connecting to the '" . TARGET_DB_NAME . "' target database on " print "Connecting to the '" . TARGET_DB_NAME . "' target database on " . TARGET_DB_TYPE . "...\n";
. TARGET_DB_TYPE . "...\n"; my $target_db = Bugzilla::DB::_connect(
my $target_db = Bugzilla::DB::_connect(TARGET_DB_TYPE, TARGET_DB_HOST, TARGET_DB_TYPE, TARGET_DB_HOST, TARGET_DB_NAME,
TARGET_DB_NAME, undef, undef, TARGET_DB_USER, TARGET_DB_PASSWORD); undef, undef, TARGET_DB_USER, TARGET_DB_PASSWORD
my $ident_char = $target_db->get_info( 29 ); # SQL_IDENTIFIER_QUOTE_CHAR );
my $ident_char = $target_db->get_info(29); # SQL_IDENTIFIER_QUOTE_CHAR
# We use the table list from the target DB, because if somebody # We use the table list from the target DB, because if somebody
# has customized their source DB, we still want the script to work, # has customized their source DB, we still want the script to work,
@ -75,7 +76,7 @@ my @table_list = $target_db->bz_table_list_real();
# We don't want to copy over the bz_schema table's contents. # We don't want to copy over the bz_schema table's contents.
my $bz_schema_location = lsearch(\@table_list, 'bz_schema'); my $bz_schema_location = lsearch(\@table_list, 'bz_schema');
splice(@table_list, $bz_schema_location, 1) if $bz_schema_location > 0; splice @table_list, $bz_schema_location, 1 if $bz_schema_location > 0;
# Instead of figuring out some fancy algorithm to insert data in the right # Instead of figuring out some fancy algorithm to insert data in the right
# order and not break FK integrity, we just drop them all. # order and not break FK integrity, we just drop them all.
@ -83,40 +84,40 @@ $target_db->bz_drop_foreign_keys();
# We start a transaction on the target DB, which helps when we're doing # We start a transaction on the target DB, which helps when we're doing
# so many inserts. # so many inserts.
$target_db->bz_start_transaction(); $target_db->bz_start_transaction();
foreach my $table (@table_list) { foreach my $table (@table_list)
{
my @serial_cols; my @serial_cols;
print "Reading data from the source '$table' table on " print "Reading data from the source '$table' table on " . SOURCE_DB_TYPE . "...\n";
. SOURCE_DB_TYPE . "...\n";
my @table_columns = $target_db->bz_table_columns_real($table); my @table_columns = $target_db->bz_table_columns_real($table);
# The column names could be quoted using the quote identifier char # The column names could be quoted using the quote identifier char
# Remove these chars as different databases use different quote chars # Remove these chars as different databases use different quote chars
@table_columns = map { s/^\Q$ident_char\E?(.*?)\Q$ident_char\E?$/$1/; $_ } @table_columns = map { s/^\Q$ident_char\E?(.*?)\Q$ident_char\E?$/$1/; $_ } @table_columns;
@table_columns;
my ($total) = $source_db->selectrow_array("SELECT COUNT(*) FROM $table"); my ($total) = $source_db->selectrow_array("SELECT COUNT(*) FROM $table");
my $select_query = "SELECT " . join(',', @table_columns) . " FROM $table"; my $select_query = "SELECT " . join(',', @table_columns) . " FROM $table";
my $select_sth = $source_db->prepare($select_query); my $select_sth = $source_db->prepare($select_query);
$select_sth->execute(); $select_sth->execute();
my $insert_query = "INSERT INTO $table ( " . join(',', @table_columns) my $insert_query = "INSERT INTO $table ( " . join(',', @table_columns) . " ) VALUES (";
. " ) VALUES ("; $insert_query .= '?,' foreach @table_columns;
$insert_query .= '?,' foreach (@table_columns);
# Remove the last comma. # Remove the last comma.
chop($insert_query); chop($insert_query);
$insert_query .= ")"; $insert_query .= ")";
my $insert_sth = $target_db->prepare($insert_query); my $insert_sth = $target_db->prepare($insert_query);
print "Clearing out the target '$table' table on " print "Clearing out the target '$table' table on " . TARGET_DB_TYPE . "...\n";
. TARGET_DB_TYPE . "...\n";
$target_db->do("DELETE FROM $table"); $target_db->do("DELETE FROM $table");
# Oracle doesn't like us manually inserting into tables that have # Oracle doesn't like us manually inserting into tables that have
# auto-increment PKs set, because of the way we made auto-increment # auto-increment PKs set, because of the way we made auto-increment
# fields work. # fields work.
if ($target_db->isa('Bugzilla::DB::Oracle')) { if ($target_db->isa('Bugzilla::DB::Oracle'))
foreach my $column (@table_columns) { {
foreach my $column (@table_columns)
{
my $col_info = $source_db->bz_column_info($table, $column); my $col_info = $source_db->bz_column_info($table, $column);
if ($col_info && $col_info->{TYPE} =~ /SERIAL/i) { if ($col_info && $col_info->{TYPE} =~ /SERIAL/i)
{
print "Dropping the sequence + trigger on $table.$column...\n"; print "Dropping the sequence + trigger on $table.$column...\n";
$target_db->do("DROP TRIGGER ${table}_${column}_TR"); $target_db->do("DROP TRIGGER ${table}_${column}_TR");
$target_db->do("DROP SEQUENCE ${table}_${column}_SEQ"); $target_db->do("DROP SEQUENCE ${table}_${column}_SEQ");
@ -124,32 +125,35 @@ foreach my $table (@table_list) {
} }
} }
print "Writing data to the target '$table' table on " print "Writing data to the target '$table' table on " . TARGET_DB_TYPE . "...\n";
. TARGET_DB_TYPE . "...\n";
my $count = 0; my $count = 0;
while (my $row = $select_sth->fetchrow_arrayref) { while (my $row = $select_sth->fetchrow_arrayref)
{
# Each column needs to be bound separately, because # Each column needs to be bound separately, because
# many columns need to be dealt with specially. # many columns need to be dealt with specially.
my $colnum = 0; my $colnum = 0;
foreach my $column (@table_columns) { foreach my $column (@table_columns)
{
# bind_param args start at 1, but arrays start at 0. # bind_param args start at 1, but arrays start at 0.
my $param_num = $colnum + 1; my $param_num = $colnum + 1;
my $already_bound; my $already_bound;
# Certain types of columns need special handling. # Certain types of columns need special handling.
my $col_info = $source_db->bz_column_info($table, $column); my $col_info = $source_db->bz_column_info($table, $column);
if ($col_info && $col_info->{TYPE} eq 'LONGBLOB') { if ($col_info && $col_info->{TYPE} eq 'LONGBLOB')
$insert_sth->bind_param($param_num, {
$row->[$colnum], $target_db->BLOB_TYPE); $insert_sth->bind_param($param_num, $row->[$colnum], $target_db->BLOB_TYPE);
$already_bound = 1; $already_bound = 1;
} }
elsif ($col_info && $col_info->{TYPE} =~ /decimal/) { elsif ($col_info && $col_info->{TYPE} =~ /decimal/)
{
# In MySQL, decimal cols can be too long. # In MySQL, decimal cols can be too long.
my $col_type = $col_info->{TYPE}; my $col_type = $col_info->{TYPE};
$col_type =~ /decimal\((\d+),(\d+)\)/; $col_type =~ /decimal\((\d+),(\d+)\)/;
my ($precision, $decimals) = ($1, $2); my ($precision, $decimals) = ($1, $2);
# If it's longer than precision + decimal point # If it's longer than precision + decimal point
if ( length($row->[$colnum]) > ($precision + 1) ) { if (length $row->[$colnum] > $precision + 1)
{
# Truncate it to the highest allowed value. # Truncate it to the highest allowed value.
my $orig_value = $row->[$colnum]; my $orig_value = $row->[$colnum];
$row->[$colnum] = ''; $row->[$colnum] = '';
@ -157,19 +161,20 @@ foreach my $table (@table_list) {
$row->[$colnum] .= '9' while ($non_decimal--); $row->[$colnum] .= '9' while ($non_decimal--);
$row->[$colnum] .= '.'; $row->[$colnum] .= '.';
$row->[$colnum] .= '9' while ($decimals--); $row->[$colnum] .= '9' while ($decimals--);
print "Truncated value $orig_value to " . $row->[$colnum] print "Truncated value $orig_value to " . $row->[$colnum] . " for $table.$column.\n";
. " for $table.$column.\n";
} }
} }
elsif ($col_info && $col_info->{TYPE} =~ /DATETIME/i) { elsif ($col_info && $col_info->{TYPE} =~ /DATETIME/i)
{
my $date = $row->[$colnum]; my $date = $row->[$colnum];
# MySQL can have strange invalid values for Datetimes. # MySQL can have strange invalid values for Datetimes.
$row->[$colnum] = '1901-01-01 00:00:00' if ($date && $date eq '0000-00-00 00:00:00')
if $date && $date eq '0000-00-00 00:00:00'; {
$row->[$colnum] = '1901-01-01 00:00:00';
}
} }
$insert_sth->bind_param($param_num, $row->[$colnum]) $insert_sth->bind_param($param_num, $row->[$colnum]) unless $already_bound;
unless $already_bound;
$colnum++; $colnum++;
} }
@ -179,31 +184,32 @@ foreach my $table (@table_list) {
} }
# For some DBs, we have to do clever things with auto-increment fields. # For some DBs, we have to do clever things with auto-increment fields.
foreach my $column (@table_columns) { foreach my $column (@table_columns)
{
next if $target_db->isa('Bugzilla::DB::Mysql'); next if $target_db->isa('Bugzilla::DB::Mysql');
my $col_info = $source_db->bz_column_info($table, $column); my $col_info = $source_db->bz_column_info($table, $column);
if ($col_info && $col_info->{TYPE} =~ /SERIAL/i) { if ($col_info && $col_info->{TYPE} =~ /SERIAL/i)
my ($max_val) = $target_db->selectrow_array( {
"SELECT MAX($column) FROM $table"); my ($max_val) = $target_db->selectrow_array("SELECT MAX($column) FROM $table");
# Set the sequence to the current max value + 1. # Set the sequence to the current max value + 1.
$max_val = 0 if !defined $max_val; $max_val = 0 if !defined $max_val;
$max_val++; $max_val++;
print "\nSetting the next value for $table.$column to $max_val."; print "\nSetting the next value for $table.$column to $max_val.";
if ($target_db->isa('Bugzilla::DB::Pg')) { if ($target_db->isa('Bugzilla::DB::Pg'))
{
# PostgreSQL doesn't like it when you insert values into # PostgreSQL doesn't like it when you insert values into
# a serial field; it doesn't increment the counter # a serial field; it doesn't increment the counter automatically.
# automatically.
$target_db->bz_set_next_serial_value($table, $column); $target_db->bz_set_next_serial_value($table, $column);
} }
elsif ($target_db->isa('Bugzilla::DB::Oracle')) { elsif ($target_db->isa('Bugzilla::DB::Oracle'))
{
# Oracle increments the counter on every insert, and *always* # Oracle increments the counter on every insert, and *always*
# sets the field, even if you gave it a value. So if there # sets the field, even if you gave it a value. So if there
# were already rows in the target DB (like the default rows # were already rows in the target DB (like the default rows
# created by checksetup), you'll get crazy values in your # created by checksetup), you'll get crazy values in your
# id columns. So we just dropped the sequences above and # id columns. So we just dropped the sequences above and
# we re-create them here, starting with the right number. # we re-create them here, starting with the right number.
my @sql = $target_db->_bz_real_schema->_get_create_seq_ddl( my @sql = $target_db->_bz_real_schema->_get_create_seq_ddl($table, $column, $max_val);
$table, $column, $max_val);
$target_db->do($_) foreach @sql; $target_db->do($_) foreach @sql;
} }
} }
@ -253,4 +259,3 @@ in the "User-Configurable Settings" section at the top of the script.
The C<SOURCE> settings are for the database you're copying from, and The C<SOURCE> settings are for the database you're copying from, and
the C<TARGET> settings are for the database you're copying to. The the C<TARGET> settings are for the database you're copying to. The
C<DB_TYPE> is the name of a DB driver from the F<Bugzilla/DB/> directory. C<DB_TYPE> is the name of a DB driver from the F<Bugzilla/DB/> directory.