Updated change file
[dbsrgits/SQL-Translator.git] / lib / SQL / Translator / Producer / MySQL.pm
CommitLineData
9398955f 1package SQL::Translator::Producer::MySQL;
2
49e1eb70 3# -------------------------------------------------------------------
866d012e 4# $Id: MySQL.pm,v 1.54 2007-11-10 03:36:43 mwz444 Exp $
49e1eb70 5# -------------------------------------------------------------------
977651a5 6# Copyright (C) 2002-4 SQLFairy Authors
9398955f 7#
8# This program is free software; you can redistribute it and/or
9# modify it under the terms of the GNU General Public License as
10# published by the Free Software Foundation; version 2.
11#
12# This program is distributed in the hope that it will be useful, but
13# WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15# General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program; if not, write to the Free Software
19# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
20# 02111-1307 USA
21# -------------------------------------------------------------------
22
c855a748 23=head1 NAME
24
25SQL::Translator::Producer::MySQL - MySQL-specific producer for SQL::Translator
26
27=head1 SYNOPSIS
28
29Use via SQL::Translator:
30
31 use SQL::Translator;
32
33 my $t = SQL::Translator->new( parser => '...', producer => 'MySQL', '...' );
34 $t->translate;
35
36=head1 DESCRIPTION
37
38This module will produce text output of the schema suitable for MySQL.
39There are still some issues to be worked out with syntax differences
40between MySQL versions 3 and 4 ("SET foreign_key_checks," character sets
41for fields, etc.).
42
5a0c7b43 43=head2 Table Types
44
45Normally the tables will be created without any explicit table type given and
46so will use the MySQL default.
47
48Any tables involved in foreign key constraints automatically get a table type
49of InnoDB, unless this is overridden by setting the C<mysql_table_type> extra
50attribute explicitly on the table.
51
52=head2 Extra attributes.
53
54The producer recognises the following extra attributes on the Schema objects.
55
56=over 4
57
7467c458 58=item B<field.list>
5a0c7b43 59
60Set the list of allowed values for Enum fields.
61
7467c458 62=item B<field.binary>, B<field.unsigned>, B<field.zerofill>
5a0c7b43 63
64Set the MySQL field options of the same name.
65
7467c458 66=item B<field.renamed_from>, B<table.renamed_from>
4d438549 67
7467c458 68Use when producing diffs to indicate that the current table/field has been
69renamed from the old name as given in the attribute value.
4d438549 70
7467c458 71=item B<table.mysql_table_type>
5a0c7b43 72
73Set the type of the table e.g. 'InnoDB', 'MyISAM'. This will be
74automatically set for tables involved in foreign key constraints if it is
75not already set explicitly. See L<"Table Types">.
76
7467c458 77Please note that the C<ENGINE> option is the prefered method of specifying
78the MySQL storage engine to use, but this method still works for backwards
79compatability.
80
81=item B<table.mysql_charset>, B<table.mysql_collate>
1ded8513 82
83Set the tables default charater set and collation order.
84
7467c458 85=item B<field.mysql_charset>, B<field.mysql_collate>
1ded8513 86
87Set the fields charater set and collation order.
88
5a0c7b43 89=back
90
c855a748 91=cut
92
9398955f 93use strict;
cd0ea0fd 94use warnings;
da5a1bae 95use vars qw[ $VERSION $DEBUG %used_names ];
866d012e 96$VERSION = sprintf "%d.%02d", q$Revision: 1.54 $ =~ /(\d+)\.(\d+)/;
5636ed00 97$DEBUG = 0 unless defined $DEBUG;
9398955f 98
99use Data::Dumper;
1c14e9f1 100use SQL::Translator::Schema::Constants;
5ee19df8 101use SQL::Translator::Utils qw(debug header_comment);
9398955f 102
d2344c83 103#
104# Use only lowercase for the keys (e.g. "long" and not "LONG")
105#
2620fc1c 106my %translate = (
107 #
108 # Oracle types
109 #
110 varchar2 => 'varchar',
111 long => 'text',
d2344c83 112 clob => 'longtext',
2620fc1c 113
114 #
115 # Sybase types
116 #
117 int => 'integer',
118 money => 'float',
119 real => 'double',
120 comment => 'text',
121 bit => 'tinyint',
87779799 122
123 #
124 # Access types
125 #
126 'long integer' => 'integer',
127 'text' => 'text',
128 'datetime' => 'datetime',
2620fc1c 129);
130
9ab59f87 131
132sub preprocess_schema {
934e1b39 133 my ($schema) = @_;
9ab59f87 134
135 # extra->{mysql_table_type} used to be the type. It belongs in options, so
136 # move it if we find it. Return Engine type if found in extra or options
137 my $mysql_table_type_to_options = sub {
138 my ($table) = @_;
139
140 my $extra = $table->extra;
141
142 my $extra_type = delete $extra->{mysql_table_type};
143
144 # Now just to find if there is already an Engine or Type option...
145 # and lets normalize it to ENGINE since:
146 #
147 # The ENGINE table option specifies the storage engine for the table.
148 # TYPE is a synonym, but ENGINE is the preferred option name.
149 #
150
151 # We have to use the hash directly here since otherwise there is no way
152 # to remove options.
153 my $options = ( $table->{options} ||= []);
154
155 # This assumes that there isn't both a Type and an Engine option.
156 for my $idx ( 0..$#{$options} ) {
157 my ($key, $value) = %{ $options->[$idx] };
158
159 next unless uc $key eq 'ENGINE' || uc $key eq 'TYPE';
160
161 # if the extra.mysql_table_type is given, use that
162 delete $options->[$idx]{$key};
163 return $options->[$idx]{ENGINE} = $value || $extra_type;
164
165 }
166
167 if ($extra_type) {
168 push @$options, { ENGINE => $extra_type };
169 return $extra_type;
170 }
171
172 };
173
1c680eb9 174 # Names are only specific to a given schema
175 local %used_names = ();
176
9ab59f87 177 #
178 # Work out which tables need to be InnoDB to support foreign key
179 # constraints. We do this first as we need InnoDB at both ends.
180 #
181 foreach my $table ( $schema->get_tables ) {
182
183 $mysql_table_type_to_options->($table);
184
929ef265 185 foreach my $c ( $table->get_constraints ) {
1c680eb9 186 next unless $c->type eq FOREIGN_KEY;
187
188 # Normalize constraint names here.
189 my $c_name = $c->name;
190 # Give the constraint a name if it doesn't have one, so it doens't feel
191 # left out
192 $c_name = $table->name . '_fk' unless length $c_name;
193
194 $c->name( next_unused_name($c_name) );
195
9ab59f87 196 for my $meth (qw/table reference_table/) {
1c680eb9 197 my $table = $schema->get_table($c->$meth) || next;
9ab59f87 198 next if $mysql_table_type_to_options->($table);
199 $table->options( { 'ENGINE' => 'InnoDB' } );
200 }
929ef265 201 } # foreach constraints
202
203 foreach my $f ( $table->get_fields ) {
204 my @size = $f->size;
205 if ( !$size[0] && $f->data_type =~ /char$/ ) {
206 $f->size( (255) );
207 }
9ab59f87 208 }
929ef265 209
9ab59f87 210 }
211}
212
9398955f 213sub produce {
a1d94525 214 my $translator = shift;
215 local $DEBUG = $translator->debug;
da5a1bae 216 local %used_names;
a1d94525 217 my $no_comments = $translator->no_comments;
218 my $add_drop_table = $translator->add_drop_table;
219 my $schema = $translator->schema;
2bc23e82 220 my $show_warnings = $translator->show_warnings || 0;
d529894e 221
fe0f47d0 222 my ($qt, $qf) = ('','');
223 $qt = '`' if $translator->quote_table_names;
224 $qf = '`' if $translator->quote_field_names;
225
1a24938d 226 debug("PKG: Beginning production\n");
da5a1bae 227 %used_names = ();
d529894e 228 my $create;
5ee19df8 229 $create .= header_comment unless ($no_comments);
0823773d 230 # \todo Don't set if MySQL 3.x is set on command line
da147d03 231 $create .= "SET foreign_key_checks=0;\n\n";
9398955f 232
934e1b39 233 preprocess_schema($schema);
5a0c7b43 234
235 #
236 # Generate sql
237 #
cd0ea0fd 238 my @table_defs =();
4d438549 239
1c14e9f1 240 for my $table ( $schema->get_tables ) {
cd0ea0fd 241# print $table->name, "\n";
0013ee25 242 push @table_defs, create_table($table,
fe0f47d0 243 { add_drop_table => $add_drop_table,
244 show_warnings => $show_warnings,
245 no_comments => $no_comments,
246 quote_table_names => $qt,
247 quote_field_names => $qf
0013ee25 248 });
249 }
9398955f 250
cd0ea0fd 251# print "@table_defs\n";
fa94b25f 252 push @table_defs, "SET foreign_key_checks=1;\n\n";
cd0ea0fd 253
fa94b25f 254 return wantarray ? ($create, @table_defs) : $create . join ('', @table_defs);
0013ee25 255}
9398955f 256
0013ee25 257sub create_table
258{
259 my ($table, $options) = @_;
2620fc1c 260
fe0f47d0 261 my $qt = $options->{quote_table_names} || '';
262 my $qf = $options->{quote_field_names} || '';
263
0013ee25 264 my $table_name = $table->name;
265 debug("PKG: Looking at table '$table_name'\n");
6d3f6379 266
0013ee25 267 #
268 # Header. Should this look like what mysqldump produces?
269 #
cd0ea0fd 270 my $create = '';
fa94b25f 271 my $drop;
fe0f47d0 272 $create .= "--\n-- Table: $qt$table_name$qt\n--\n" unless $options->{no_comments};
fa94b25f 273 $drop = qq[DROP TABLE IF EXISTS $qt$table_name$qt;\n] if $options->{add_drop_table};
fe0f47d0 274 $create .= "CREATE TABLE $qt$table_name$qt (\n";
2b695517 275
0013ee25 276 #
277 # Fields
278 #
279 my @field_defs;
280 for my $field ( $table->get_fields ) {
fe0f47d0 281 push @field_defs, create_field($field, $options);
0013ee25 282 }
1ded8513 283
0013ee25 284 #
285 # Indices
286 #
287 my @index_defs;
288 my %indexed_fields;
289 for my $index ( $table->get_indices ) {
fe0f47d0 290 push @index_defs, create_index($index, $options);
0013ee25 291 $indexed_fields{ $_ } = 1 for $index->fields;
292 }
d529894e 293
0013ee25 294 #
295 # Constraints -- need to handle more than just FK. -ky
296 #
297 my @constraint_defs;
298 my @constraints = $table->get_constraints;
299 for my $c ( @constraints ) {
cd0ea0fd 300 my $constr = create_constraint($c, $options);
301 push @constraint_defs, $constr if($constr);
0013ee25 302
da5a1bae 303 unless ( $indexed_fields{ ($c->fields())[0] } || $c->type ne FOREIGN_KEY ) {
304 push @index_defs, "INDEX ($qf" . ($c->fields())[0] . "$qf)";
305 $indexed_fields{ ($c->fields())[0] } = 1;
306 }
0013ee25 307 }
1ded8513 308
0013ee25 309 $create .= join(",\n", map { " $_" }
310 @field_defs, @index_defs, @constraint_defs
311 );
9398955f 312
0013ee25 313 #
314 # Footer
315 #
316 $create .= "\n)";
da5a1bae 317 $create .= generate_table_options($table) || '';
0013ee25 318 $create .= ";\n\n";
9398955f 319
fa94b25f 320 return $drop ? ($drop,$create) : $create;
0013ee25 321}
f8b6e804 322
da5a1bae 323sub generate_table_options
324{
325 my ($table) = @_;
326 my $create;
327
328 my $table_type_defined = 0;
329 for my $t1_option_ref ( $table->options ) {
330 my($key, $value) = %{$t1_option_ref};
331 $table_type_defined = 1
332 if uc $key eq 'ENGINE' or uc $key eq 'TYPE';
333 $create .= " $key=$value";
334 }
9ab59f87 335
da5a1bae 336 my $mysql_table_type = $table->extra('mysql_table_type');
9ab59f87 337 $create .= " ENGINE=$mysql_table_type"
da5a1bae 338 if $mysql_table_type && !$table_type_defined;
339 my $charset = $table->extra('mysql_charset');
340 my $collate = $table->extra('mysql_collate');
341 my $comments = $table->comments;
342
343 $create .= " DEFAULT CHARACTER SET $charset" if $charset;
344 $create .= " COLLATE $collate" if $collate;
345 $create .= qq[ comment='$comments'] if $comments;
346 return $create;
347}
348
0013ee25 349sub create_field
350{
fe0f47d0 351 my ($field, $options) = @_;
352
353 my $qf = $options->{quote_field_names} ||= '';
9398955f 354
0013ee25 355 my $field_name = $field->name;
356 debug("PKG: Looking at field '$field_name'\n");
fe0f47d0 357 my $field_def = "$qf$field_name$qf";
0013ee25 358
359 # data type and size
360 my $data_type = $field->data_type;
361 my @size = $field->size;
362 my %extra = $field->extra;
363 my $list = $extra{'list'} || [];
364 # \todo deal with embedded quotes
365 my $commalist = join( ', ', map { qq['$_'] } @$list );
366 my $charset = $extra{'mysql_charset'};
367 my $collate = $extra{'mysql_collate'};
368
369 #
370 # Oracle "number" type -- figure best MySQL type
371 #
372 if ( lc $data_type eq 'number' ) {
373 # not an integer
374 if ( scalar @size > 1 ) {
375 $data_type = 'double';
d529894e 376 }
0013ee25 377 elsif ( $size[0] && $size[0] >= 12 ) {
378 $data_type = 'bigint';
379 }
380 elsif ( $size[0] && $size[0] <= 1 ) {
381 $data_type = 'tinyint';
382 }
383 else {
384 $data_type = 'int';
385 }
386 }
387 #
388 # Convert a large Oracle varchar to "text"
389 #
390 elsif ( $data_type =~ /char/i && $size[0] > 255 ) {
391 $data_type = 'text';
392 @size = ();
393 }
0013ee25 394 elsif ( $data_type =~ /boolean/i ) {
395 $data_type = 'enum';
396 $commalist = "'0','1'";
397 }
398 elsif ( exists $translate{ lc $data_type } ) {
399 $data_type = $translate{ lc $data_type };
400 }
401
402 @size = () if $data_type =~ /(text|blob)/i;
403
404 if ( $data_type =~ /(double|float)/ && scalar @size == 1 ) {
405 push @size, '0';
406 }
d529894e 407
0013ee25 408 $field_def .= " $data_type";
409
410 if ( lc $data_type eq 'enum' ) {
411 $field_def .= '(' . $commalist . ')';
412 }
413 elsif ( defined $size[0] && $size[0] > 0 ) {
414 $field_def .= '(' . join( ', ', @size ) . ')';
415 }
416
417 # char sets
418 $field_def .= " CHARACTER SET $charset" if $charset;
419 $field_def .= " COLLATE $collate" if $collate;
420
421 # MySQL qualifiers
422 for my $qual ( qw[ binary unsigned zerofill ] ) {
423 my $val = $extra{ $qual } || $extra{ uc $qual } or next;
424 $field_def .= " $qual";
425 }
426 for my $qual ( 'character set', 'collate', 'on update' ) {
427 my $val = $extra{ $qual } || $extra{ uc $qual } or next;
428 $field_def .= " $qual $val";
429 }
430
431 # Null?
432 $field_def .= ' NOT NULL' unless $field->is_nullable;
433
434 # Default? XXX Need better quoting!
435 my $default = $field->default_value;
436 if ( defined $default ) {
437 if ( uc $default eq 'NULL') {
438 $field_def .= ' DEFAULT NULL';
439 } else {
440 $field_def .= " DEFAULT '$default'";
441 }
442 }
443
444 if ( my $comments = $field->comments ) {
445 $field_def .= qq[ comment '$comments'];
446 }
447
448 # auto_increment?
449 $field_def .= " auto_increment" if $field->is_auto_increment;
450
451 return $field_def;
452}
453
da5a1bae 454sub alter_create_index
455{
456 my ($index, $options) = @_;
457
458 my $qt = $options->{quote_table_names} || '';
459 my $qf = $options->{quote_field_names} || '';
460
461 return join( ' ',
462 'ALTER TABLE',
463 $qt.$index->table->name.$qt,
464 'ADD',
465 create_index(@_)
466 );
467}
468
0013ee25 469sub create_index
470{
fe0f47d0 471 my ($index, $options) = @_;
472
473 my $qf = $options->{quote_field_names} || '';
0013ee25 474
475 return join( ' ',
da5a1bae 476 lc $index->type eq 'normal' ? 'INDEX' : $index->type . ' INDEX',
0013ee25 477 $index->name,
fe0f47d0 478 '(' . $qf . join( "$qf, $qf", $index->fields ) . $qf . ')'
0013ee25 479 );
480
481}
482
da5a1bae 483sub alter_drop_index
484{
485 my ($index, $options) = @_;
486
487 my $qt = $options->{quote_table_names} || '';
488 my $qf = $options->{quote_field_names} || '';
489
490 return join( ' ',
491 'ALTER TABLE',
492 $qt.$index->table->name.$qt,
493 'DROP',
494 'INDEX',
495 $index->name || $index->fields
496 );
497
498}
499
500sub alter_drop_constraint
501{
502 my ($c, $options) = @_;
503
504 my $qt = $options->{quote_table_names} || '';
505 my $qc = $options->{quote_constraint_names} || '';
506
507 my $out = sprintf('ALTER TABLE %s DROP %s %s',
7467c458 508 $qt . $c->table->name . $qt,
74ca32ce 509 $c->type eq FOREIGN_KEY ? $c->type : "INDEX",
da5a1bae 510 $qc . $c->name . $qc );
511
512 return $out;
513}
514
515sub alter_create_constraint
516{
517 my ($index, $options) = @_;
518
519 my $qt = $options->{quote_table_names} || '';
520 return join( ' ',
521 'ALTER TABLE',
522 $qt.$index->table->name.$qt,
523 'ADD',
524 create_constraint(@_) );
525}
526
0013ee25 527sub create_constraint
528{
529 my ($c, $options) = @_;
530
fb149f81 531 my $qf = $options->{quote_field_names} || '';
532 my $qt = $options->{quote_table_names} || '';
7467c458 533 my $qc = $options->{quote_constraint_names} || '';
da5a1bae 534 my $leave_name = $options->{leave_name} || undef;
fe0f47d0 535
0013ee25 536 my @fields = $c->fields or next;
537
538 if ( $c->type eq PRIMARY_KEY ) {
fe0f47d0 539 return 'PRIMARY KEY (' . $qf . join("$qf, $qf", @fields). $qf . ')';
0013ee25 540 }
541 elsif ( $c->type eq UNIQUE ) {
542 return
fe0f47d0 543 'UNIQUE '.
544 (defined $c->name ? $qf.$c->name.$qf.' ' : '').
545 '(' . $qf . join("$qf, $qf", @fields). $qf . ')';
0013ee25 546 }
547 elsif ( $c->type eq FOREIGN_KEY ) {
d529894e 548 #
0013ee25 549 # Make sure FK field is indexed or MySQL complains.
5e56da9a 550 #
0013ee25 551
866d012e 552 my $table = $c->table;
da5a1bae 553 my $c_name = $c->name;
866d012e 554
0013ee25 555 my $def = join(' ',
fb149f81 556 map { $_ || () }
557 'CONSTRAINT',
7467c458 558 $qc . $c_name . $qc,
fb149f81 559 'FOREIGN KEY'
560 );
0013ee25 561
da5a1bae 562
fe0f47d0 563 $def .= ' ('.$qf . join( "$qf, $qf", @fields ) . $qf . ')';
0013ee25 564
fe0f47d0 565 $def .= ' REFERENCES ' . $qt . $c->reference_table . $qt;
0013ee25 566
567 my @rfields = map { $_ || () } $c->reference_fields;
568 unless ( @rfields ) {
569 my $rtable_name = $c->reference_table;
866d012e 570 if ( my $ref_table = $table->schema->get_table( $rtable_name ) ) {
0013ee25 571 push @rfields, $ref_table->primary_key;
1c14e9f1 572 }
0013ee25 573 else {
574 warn "Can't find reference table '$rtable_name' " .
575 "in schema\n" if $options->{show_warnings};
5e56da9a 576 }
577 }
578
0013ee25 579 if ( @rfields ) {
fe0f47d0 580 $def .= ' (' . $qf . join( "$qf, $qf", @rfields ) . $qf . ')';
0013ee25 581 }
582 else {
866d012e 583 warn "FK constraint on " . $table->name . '.' .
0013ee25 584 join('', @fields) . " has no reference fields\n"
585 if $options->{show_warnings};
586 }
5e56da9a 587
0013ee25 588 if ( $c->match_type ) {
589 $def .= ' MATCH ' .
590 ( $c->match_type =~ /full/i ) ? 'FULL' : 'PARTIAL';
591 }
592
593 if ( $c->on_delete ) {
594 $def .= ' ON DELETE '.join( ' ', $c->on_delete );
595 }
596
597 if ( $c->on_update ) {
598 $def .= ' ON UPDATE '.join( ' ', $c->on_update );
599 }
cd0ea0fd 600 return $def;
9398955f 601 }
602
cd0ea0fd 603 return undef;
0013ee25 604}
605
da5a1bae 606sub alter_table
607{
608 my ($to_table, $options) = @_;
609
7467c458 610 my $qt = $options->{quote_table_names} || '';
da5a1bae 611
612 my $table_options = generate_table_options($to_table) || '';
613 my $out = sprintf('ALTER TABLE %s%s',
614 $qt . $to_table->name . $qt,
615 $table_options);
616
617 return $out;
618}
619
4d438549 620sub rename_field { alter_field(@_) }
0013ee25 621sub alter_field
622{
fe0f47d0 623 my ($from_field, $to_field, $options) = @_;
624
7467c458 625 my $qf = $options->{quote_field_names} || '';
626 my $qt = $options->{quote_table_names} || '';
0013ee25 627
628 my $out = sprintf('ALTER TABLE %s CHANGE COLUMN %s %s',
fe0f47d0 629 $qt . $to_field->table->name . $qt,
4d438549 630 $qf . $from_field->name . $qf,
fe0f47d0 631 create_field($to_field, $options));
0013ee25 632
633 return $out;
634}
635
636sub add_field
637{
fe0f47d0 638 my ($new_field, $options) = @_;
639
7467c458 640 my $qt = $options->{quote_table_names} || '';
0013ee25 641
642 my $out = sprintf('ALTER TABLE %s ADD COLUMN %s',
fe0f47d0 643 $qt . $new_field->table->name . $qt,
644 create_field($new_field, $options));
0013ee25 645
646 return $out;
647
648}
649
650sub drop_field
651{
fe0f47d0 652 my ($old_field, $options) = @_;
0013ee25 653
7467c458 654 my $qf = $options->{quote_field_names} || '';
655 my $qt = $options->{quote_table_names} || '';
fe0f47d0 656
0013ee25 657 my $out = sprintf('ALTER TABLE %s DROP COLUMN %s',
fe0f47d0 658 $qt . $old_field->table->name . $qt,
659 $qf . $old_field->name . $qf);
0013ee25 660
661 return $out;
662
9398955f 663}
664
4d438549 665sub batch_alter_table {
666 my ($table, $diff_hash, $options) = @_;
667
f9ed5d54 668 # InnoDB has an issue with dropping and re-adding a FK constraint under the
669 # name in a single alter statment, see: http://bugs.mysql.com/bug.php?id=13741
670 #
671 # We have to work round this.
672
673 my %fks_to_alter;
674 my %fks_to_drop = map {
675 $_->type eq FOREIGN_KEY
676 ? ( $_->name => $_ )
677 : ( )
678 } @{$diff_hash->{alter_drop_constraint} };
679
680 my %fks_to_create = map {
681 if ( $_->type eq FOREIGN_KEY) {
682 $fks_to_alter{$_->name} = $fks_to_drop{$_->name} if $fks_to_drop{$_->name};
683 ( $_->name => $_ );
684 } else { ( ) }
685 } @{$diff_hash->{alter_create_constraint} };
686
687 my $drop_stmt = '';
688 if (scalar keys %fks_to_alter) {
689 $diff_hash->{alter_drop_constraint} = [
690 grep { !$fks_to_alter{$_->name} } @{ $diff_hash->{alter_drop_constraint} }
691 ];
692
693 $drop_stmt = batch_alter_table($table, { alter_drop_constraint => [ values %fks_to_alter ] }, $options)
694 . "\n";
695
696 }
697
4d438549 698 my @stmts = map {
699 if (@{ $diff_hash->{$_} || [] }) {
700 my $meth = __PACKAGE__->can($_) or die __PACKAGE__ . " cant $_";
f9ed5d54 701 map { $meth->( (ref $_ eq 'ARRAY' ? @$_ : $_), $options ) } @{ $diff_hash->{$_} }
4d438549 702 } else { () }
46bf5655 703 } qw/rename_table
704 alter_drop_constraint
4d438549 705 alter_drop_index
706 drop_field
707 add_field
708 alter_field
709 rename_field
710 alter_create_index
711 alter_create_constraint
712 alter_table/;
713
46bf5655 714 # rename_table makes things a bit more complex
715 my $renamed_from = "";
716 $renamed_from = $diff_hash->{rename_table}[0][0]->name
717 if $diff_hash->{rename_table} && @{$diff_hash->{rename_table}};
718
4d438549 719 return unless @stmts;
720 # Just zero or one stmts. return now
f9ed5d54 721 return "$drop_stmt@stmts;" unless @stmts > 1;
4d438549 722
723 # Now strip off the 'ALTER TABLE xyz' of all but the first one
724
7467c458 725 my $qt = $options->{quote_table_names} || '';
4104f82b 726 my $table_name = $qt . $table->name . $qt;
727
728
729 my $re = $renamed_from
730 ? qr/^ALTER TABLE (?:\Q$table_name\E|\Q$qt$renamed_from$qt\E) /
731 : qr/^ALTER TABLE \Q$table_name\E /;
4d438549 732
733 my $first = shift @stmts;
4104f82b 734 my ($alter_table) = $first =~ /($re)/;
46bf5655 735
4d438549 736 my $padd = " " x length($alter_table);
737
f9ed5d54 738 return $drop_stmt . join( ",\n", $first, map { s/$re//; $padd . $_ } @stmts) . ';';
4104f82b 739
4d438549 740}
741
742sub drop_table {
46bf5655 743 my ($table, $options) = @_;
744
745 my $qt = $options->{quote_table_names} || '';
4d438549 746
747 # Drop (foreign key) constraints so table drops cleanly
46bf5655 748 my @sql = batch_alter_table($table, { alter_drop_constraint => [ grep { $_->type eq 'FOREIGN KEY' } $table->get_constraints ] }, $options);
749
750 return join("\n", @sql, "DROP TABLE $qt$table$qt;");
751
752}
753
754sub rename_table {
755 my ($old_table, $new_table, $options) = @_;
4d438549 756
46bf5655 757 my $qt = $options->{quote_table_names} || '';
4d438549 758
46bf5655 759 return "ALTER TABLE $qt$old_table$qt RENAME TO $qt$new_table$qt";
4d438549 760}
761
da5a1bae 762sub next_unused_name {
763 my $name = shift || '';
764 if ( !defined($used_names{$name}) ) {
765 $used_names{$name} = $name;
766 return $name;
767 }
768
769 my $i = 1;
770 while ( defined($used_names{$name . '_' . $i}) ) {
771 ++$i;
772 }
773 $name .= '_' . $i;
774 $used_names{$name} = $name;
775 return $name;
776}
777
9398955f 7781;
9398955f 779
c855a748 780# -------------------------------------------------------------------
9398955f 781
c855a748 782=pod
783
784=head1 SEE ALSO
785
786SQL::Translator, http://www.mysql.com/.
9398955f 787
2d6979da 788=head1 AUTHORS
9398955f 789
758ab1cd 790darren chamberlain E<lt>darren@cpan.orgE<gt>,
c855a748 791Ken Y. Clark E<lt>kclark@cpan.orgE<gt>.
792
793=cut