3 See the NOTICE file distributed with
this work
for additional information
4 regarding copyright ownership.
6 Licensed under the Apache License, Version 2.0 (the
"License");
7 you may not use
this file except in compliance with the License.
8 You may obtain a copy of the License at
12 Unless required by applicable law or agreed to in writing, software
13 distributed under the License is distributed on an
"AS IS" BASIS,
14 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 See the License
for the specific language governing permissions and
16 limitations under the License.
20 # Module to ease mysql schema conversion
23 # Make a schema_converter with new ( source_dbh, target_dbh )
24 # source database should be filled, target is empty schema
27 # For each target table there will be a transfer
28 # Either with a self specified transfer function
29 # with a custom select
30 # from a renamed source table
31 # from the same name source table
33 # configure the transfer (and the order) with
35 # table_rename( "oldname", "newname" )
36 # table_skip( "tablename" )
37 # do_first( "newtable1", "newtable2", "newtable3" )
40 # Each standard table transfer
41 # Either do a custom select statement
42 # or transfer columns with same name or renamed into each other
43 # specify columns to omit in target or get error
45 # column_rename( "tablename", "oldcolname", "newcolname" )
46 # column_skip( $targetdb, "table", "column" )
47 # custom_select( $targetdb, "tablename",
49 # Each row may be modified (custom select or standard select)
50 # specify a row_modifier function for the target table
51 # It takes a list ref and returns a list ref with the modified values
52 # ( you have to know the order of columns which come in have to go out db )
54 # set_row_modifier( "tablename", function_reference )
56 # potentially large (target) tables should be declared with
57 # big_table( "newtablename" );
59 package SchemaConverter;
67 my ( $class, @args ) = @_;
72 $self->source_dbh( $args[0] );
73 $self->target_dbh( $args[1] );
81 my ( $self, $arg ) = @_;
84 ( $self->{
'tmp_dir'} = $arg );
86 return $self->{
'tmp_dir'};
90 my ( $self, $arg ) = @_;
93 ( $self->{
'source_dbh'} = $arg );
95 return $self->{
'source_dbh'};
99 my ( $self, $arg ) = @_;
102 ( $self->{
'target_dbh'} = $arg );
104 return $self->{
'target_dbh'};
110 $self->source_dbh()->disconnect();
111 $self->target_dbh()->disconnect();
121 if( ! defined $self->tmp_dir() ) {
123 die(
"No tmp_dir specified" );
125 $tmpdir = $self->tmp_dir();
128 # first we should check if all standard trnasfers can go
129 # otherwise testing this is a pain
130 $self->check_possible_transfer();
131 my @ordered_tables = $self->{targetdb}{dofirst};
132 my %all_tables = %{$self->{targetdb}{tables}};
133 for my $tablename ( @ordered_tables ) {
134 delete $all_tables{$tablename};
136 for my $tablename ( keys %all_tables ) {
137 push( @ordered_tables, $tablename );
140 for my $tablename ( @ordered_tables ) {
142 print STDERR
"Transfer $tablename ";
144 open( FH,
">$tmpdir/$tablename.txt" ) or die "cant open dumpfile";
146 if( exists $self->{targetdb}{tables}{$tablename}{transfer} ) {
147 my $transfunc = $self->{targetdb}{tables}{$tablename}{transfer};
148 &$transfunc( $self->source_dbh(), $self->target_dbh(), $tablename, \*FH );
151 if( exists $self->{targetdb}{tables}{$tablename}{link} ) {
152 $sourcetable = $self->{targetdb}{tables}{$tablename}{link};
153 if( $sourcetable eq
"" ) {
158 # find the sourcetable
159 if( exists $self->{targetdb}{tables}{$tablename}{select} ) {
161 # if we have custom select, sourcetable doesnt make sense
162 $sourcetable = undef;
164 } elsif( ! exists $self->{sourcedb}{tables}{$tablename} ) {
166 die
"Couldnt find source for $tablename. Enter empty sourcetable.";
169 $sourcetable = $tablename;
173 $self->standard_table_transfer( $sourcetable, $tablename, \*FH );
180 $self->target_dbh->do(
"load data infile '$tmpdir/$tablename.txt' into table $tablename" );
184 unlink
"$tmpdir/$tablename.txt";
185 print STDERR
" finished\n";
193 # this function checks if a standard transfer is possible.
194 # custom selects and custom transfer functions are not checked
195 # it will only return if it can do the transfer otherwise die
197 sub check_possible_transfer {
200 for my $tablename ( keys %{$self->{targetdb}{tables}} ) {
203 if( exists $self->{targetdb}{tables}{$tablename}{transfer} ) {
207 if( exists $self->{targetdb}{tables}{$tablename}{link} ) {
208 $sourcetable = $self->{targetdb}{tables}{$tablename}{link};
209 if( $sourcetable eq
"" ) {
214 # find the sourcetable
215 if( exists $self->{targetdb}{tables}{$tablename}{select} ) {
217 # custom select, no check
219 } elsif( ! exists $self->{sourcedb}{tables}{$tablename} ) {
221 die
"Couldnt find source for $tablename. Enter empty sourcetable.";
224 $sourcetable = $tablename;
228 my @newcols = @{$self->{targetdb}{tables}{$tablename}{columns}};
229 my @oldcols = @{$self->{sourcedb}{tables}{$sourcetable}{columns}};
233 if( exists $self->{targetdb}{tables}{$tablename}{columnrename} ) {
234 %rename = %{$self->{targetdb}{tables}{$tablename}{columnrename}};
239 # find all source columns and build select statement
241 for my $colname ( @newcols ) {
244 if( exists $rename{$colname} ) {
245 $selname = $rename{$colname};
246 if( $selname eq
"" ) {
251 for my $oldcol ( @oldcols ) {
252 if( $oldcol eq $colname ) {
259 die
"Couldnt fill $tablename.$colname\n";
267 sub standard_table_transfer {
268 my ( $self, $sourcetable, $targettable, $tmpfile ) = @_;
270 my $sourcedb = $self->source_dbh();
271 my $targetdb = $self->target_dbh();
273 # look for custom select
275 if( exists $self->{targetdb}{tables}{$targettable}{select} ) {
276 $select = $self->{targetdb}{tables}{$targettable}{select};
278 # check if all columns have matching names
279 my @newcols = @{$self->{targetdb}{tables}{$targettable}{columns}};
280 my @oldcols = @{$self->{sourcedb}{tables}{$sourcetable}{columns}};
284 if( exists $self->{targetdb}{tables}{$targettable}{columnrename} ) {
285 %rename = %{$self->{targetdb}{tables}{$targettable}{columnrename}};
290 # find all source columns and build select statement
292 for my $colname ( @newcols ) {
295 if( exists $rename{$colname} ) {
296 $selname = $rename{$colname};
297 if( $selname eq
"" ) {
302 for my $oldcol ( @oldcols ) {
303 if( $oldcol eq $colname ) {
310 die
"Couldnt fill $targettable.$colname\n";
314 $select .=
" $selname,";
317 $select =
"SELECT $select from $sourcetable";
321 # DBD doesn't use cursors; loads whole table during execute()
322 # problem for large tables - "mysql_use_result" gets around this
324 if (defined $self->{targetdb}{tables}{$targettable}{its_a_big_un}) {
325 $sth = $self->source_dbh()->prepare( $select , { mysql_use_result => 1 } );
328 $sth = $self->source_dbh()->prepare( $select );
333 if( exists $self->{targetdb}{tables}{$targettable}{row_modify} ) {
334 my $rowmod = $self->{targetdb}{tables}{$targettable}{row_modify};
336 while( my $arref = $sth->fetchrow_arrayref() ) {
338 $row = &{$rowmod}($arref);
340 print $tmpfile ( join(
"\t",@{$row} ),
"\n" );
343 while( my $arref = $sth->fetchrow_arrayref() ) {
344 $row = join(
"\t", @$arref );
345 print $tmpfile
"$row\n";
357 for my $db_name (
'targetdb',
'sourcedb' ) {
358 if( $db_name eq
'targetdb' ) {
359 $dbh = $self->target_dbh();
361 $dbh = $self->source_dbh();
364 my $sth = $dbh->prepare(
"show tables" );
367 while( my $arref = $sth->fetchrow_arrayref() ) {
368 $self->{$db_name}{tables}{$arref->[0]} = {};
371 my @tables = keys %{$self->{$db_name}{tables}};
372 for my $table ( @tables ) {
373 $sth = $dbh->prepare(
"show columns from $table" );
375 while( my $arref = $sth->fetchrow_arrayref () ) {
376 push( @{$self->{$db_name}{tables}{$table}{columns}}, $arref->[0] );
384 my ( $self, $oldtable, $newtable ) = @_;
385 $self->{targetdb}{tables}{$newtable}{link} = $oldtable;
389 my ( $self, $newtable ) = @_;
390 $self->{targetdb}{tables}{$newtable}{link} =
"";
394 my ( $self, $newtable, $oldcol, $newcol ) = @_;
395 $self->{targetdb}{tables}{$newtable}{columnrename}{$newcol} = $oldcol;
399 my ( $self, $newtable, $newcol ) = @_;
400 $self->{targetdb}{tables}{$newtable}{columnrename}{$newcol} =
"";
404 my ( $self, $newtable, $select ) = @_;
405 $self->{targetdb}{tables}{$newtable}{select} = $select;
408 sub set_row_modifier {
409 my ( $self, $newtable, $row_modifier ) = @_;
410 $self->{targetdb}{tables}{$newtable}{row_modify} = $row_modifier;
415 for my $tablename ( keys %{$self->{targetdb}{tables}} ) {
416 $self->target_dbh()->do(
"delete from $tablename" );
421 my ( $self, $table ) = @_;
423 $self->{targetdb}{tables}{$table}{its_a_big_un} = 1;
429 my @ordered_table_list= @_;
430 $self->{targetdb}{dofirst} = \@ordered_table_list;