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