Applied patch from Ryan to uniqify index names sanely for the mysql producer
[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
f5405d47 99# Maximum length for most identifiers is 64, according to:
100# http://dev.mysql.com/doc/refman/4.1/en/identifiers.html
101# http://dev.mysql.com/doc/refman/5.0/en/identifiers.html
102my $DEFAULT_MAX_ID_LENGTH = 64;
103
9398955f 104use Data::Dumper;
1c14e9f1 105use SQL::Translator::Schema::Constants;
f5405d47 106use SQL::Translator::Utils qw(debug header_comment truncate_id_uniquely);
9398955f 107
d2344c83 108#
109# Use only lowercase for the keys (e.g. "long" and not "LONG")
110#
2620fc1c 111my %translate = (
112 #
113 # Oracle types
114 #
115 varchar2 => 'varchar',
116 long => 'text',
d2344c83 117 clob => 'longtext',
2620fc1c 118
119 #
120 # Sybase types
121 #
122 int => 'integer',
123 money => 'float',
124 real => 'double',
125 comment => 'text',
126 bit => 'tinyint',
87779799 127
128 #
129 # Access types
130 #
131 'long integer' => 'integer',
132 'text' => 'text',
133 'datetime' => 'datetime',
2620fc1c 134);
135
9ab59f87 136
137sub preprocess_schema {
934e1b39 138 my ($schema) = @_;
9ab59f87 139
140 # extra->{mysql_table_type} used to be the type. It belongs in options, so
141 # move it if we find it. Return Engine type if found in extra or options
7725e1e6 142 # Similarly for mysql_charset and mysql_collate
143 my $extra_to_options = sub {
144 my ($table, $extra_name, $opt_name) = @_;
9ab59f87 145
146 my $extra = $table->extra;
147
7725e1e6 148 my $extra_type = delete $extra->{$extra_name};
9ab59f87 149
150 # Now just to find if there is already an Engine or Type option...
151 # and lets normalize it to ENGINE since:
152 #
153 # The ENGINE table option specifies the storage engine for the table.
154 # TYPE is a synonym, but ENGINE is the preferred option name.
155 #
156
157 # We have to use the hash directly here since otherwise there is no way
158 # to remove options.
159 my $options = ( $table->{options} ||= []);
160
7725e1e6 161 # If multiple option names, normalize to the first one
162 if (ref $opt_name) {
163 OPT_NAME: for ( @$opt_name[1..$#$opt_name] ) {
164 for my $idx ( 0..$#{$options} ) {
165 my ($key, $value) = %{ $options->[$idx] };
166
167 if (uc $key eq $_) {
168 $options->[$idx] = { $opt_name->[0] => $value };
169 last OPT_NAME;
170 }
171 }
172 }
173 $opt_name = $opt_name->[0];
174
175 }
176
177
9ab59f87 178 # This assumes that there isn't both a Type and an Engine option.
7725e1e6 179 OPTION:
9ab59f87 180 for my $idx ( 0..$#{$options} ) {
181 my ($key, $value) = %{ $options->[$idx] };
182
7725e1e6 183 next unless uc $key eq $opt_name;
184
185 # make sure case is right on option name
9ab59f87 186 delete $options->[$idx]{$key};
7725e1e6 187 return $options->[$idx]{$opt_name} = $value || $extra_type;
9ab59f87 188
189 }
190
191 if ($extra_type) {
7725e1e6 192 push @$options, { $opt_name => $extra_type };
9ab59f87 193 return $extra_type;
194 }
195
196 };
197
1c680eb9 198 # Names are only specific to a given schema
199 local %used_names = ();
200
9ab59f87 201 #
202 # Work out which tables need to be InnoDB to support foreign key
203 # constraints. We do this first as we need InnoDB at both ends.
204 #
205 foreach my $table ( $schema->get_tables ) {
7725e1e6 206
207 $extra_to_options->($table, 'mysql_table_type', ['ENGINE', 'TYPE'] );
208 $extra_to_options->($table, 'mysql_charset', 'CHARACTER SET' );
209 $extra_to_options->($table, 'mysql_collate', 'COLLATE' );
9ab59f87 210
929ef265 211 foreach my $c ( $table->get_constraints ) {
1c680eb9 212 next unless $c->type eq FOREIGN_KEY;
213
214 # Normalize constraint names here.
215 my $c_name = $c->name;
216 # Give the constraint a name if it doesn't have one, so it doens't feel
217 # left out
218 $c_name = $table->name . '_fk' unless length $c_name;
219
220 $c->name( next_unused_name($c_name) );
221
9ab59f87 222 for my $meth (qw/table reference_table/) {
1c680eb9 223 my $table = $schema->get_table($c->$meth) || next;
7725e1e6 224 # This normalizes the types to ENGINE and returns the value if its there
225 next if $extra_to_options->($table, 'mysql_table_type', ['ENGINE', 'TYPE']);
9ab59f87 226 $table->options( { 'ENGINE' => 'InnoDB' } );
227 }
929ef265 228 } # foreach constraints
229
2c6be67a 230 my %map = ( mysql_collate => 'collate', mysql_charset => 'character set');
929ef265 231 foreach my $f ( $table->get_fields ) {
2c6be67a 232 my $extra = $f->extra;
233 for (keys %map) {
234 $extra->{$map{$_}} = delete $extra->{$_} if exists $extra->{$_};
235 }
236
929ef265 237 my @size = $f->size;
238 if ( !$size[0] && $f->data_type =~ /char$/ ) {
239 $f->size( (255) );
240 }
9ab59f87 241 }
929ef265 242
9ab59f87 243 }
244}
245
9398955f 246sub produce {
a1d94525 247 my $translator = shift;
248 local $DEBUG = $translator->debug;
da5a1bae 249 local %used_names;
a1d94525 250 my $no_comments = $translator->no_comments;
251 my $add_drop_table = $translator->add_drop_table;
252 my $schema = $translator->schema;
2bc23e82 253 my $show_warnings = $translator->show_warnings || 0;
ca1f9923 254 my $producer_args = $translator->producer_args;
255 my $mysql_version = $producer_args->{mysql_version} || 0;
f5405d47 256 my $max_id_length = $producer_args->{mysql_max_id_length} || $DEFAULT_MAX_ID_LENGTH;
d529894e 257
7725e1e6 258 my ($qt, $qf, $qc) = ('','', '');
fe0f47d0 259 $qt = '`' if $translator->quote_table_names;
260 $qf = '`' if $translator->quote_field_names;
261
1a24938d 262 debug("PKG: Beginning production\n");
da5a1bae 263 %used_names = ();
d529894e 264 my $create;
5ee19df8 265 $create .= header_comment unless ($no_comments);
0823773d 266 # \todo Don't set if MySQL 3.x is set on command line
da147d03 267 $create .= "SET foreign_key_checks=0;\n\n";
9398955f 268
934e1b39 269 preprocess_schema($schema);
5a0c7b43 270
271 #
272 # Generate sql
273 #
cd0ea0fd 274 my @table_defs =();
4d438549 275
1c14e9f1 276 for my $table ( $schema->get_tables ) {
cd0ea0fd 277# print $table->name, "\n";
0013ee25 278 push @table_defs, create_table($table,
fe0f47d0 279 { add_drop_table => $add_drop_table,
280 show_warnings => $show_warnings,
281 no_comments => $no_comments,
282 quote_table_names => $qt,
ca1f9923 283 quote_field_names => $qf,
f5405d47 284 max_id_length => $max_id_length,
ca1f9923 285 mysql_version => $mysql_version
0013ee25 286 });
287 }
9398955f 288
cd0ea0fd 289# print "@table_defs\n";
fa94b25f 290 push @table_defs, "SET foreign_key_checks=1;\n\n";
cd0ea0fd 291
fa94b25f 292 return wantarray ? ($create, @table_defs) : $create . join ('', @table_defs);
0013ee25 293}
9398955f 294
0013ee25 295sub create_table
296{
297 my ($table, $options) = @_;
2620fc1c 298
fe0f47d0 299 my $qt = $options->{quote_table_names} || '';
300 my $qf = $options->{quote_field_names} || '';
301
0013ee25 302 my $table_name = $table->name;
303 debug("PKG: Looking at table '$table_name'\n");
6d3f6379 304
0013ee25 305 #
306 # Header. Should this look like what mysqldump produces?
307 #
cd0ea0fd 308 my $create = '';
fa94b25f 309 my $drop;
fe0f47d0 310 $create .= "--\n-- Table: $qt$table_name$qt\n--\n" unless $options->{no_comments};
fa94b25f 311 $drop = qq[DROP TABLE IF EXISTS $qt$table_name$qt;\n] if $options->{add_drop_table};
fe0f47d0 312 $create .= "CREATE TABLE $qt$table_name$qt (\n";
2b695517 313
0013ee25 314 #
315 # Fields
316 #
317 my @field_defs;
318 for my $field ( $table->get_fields ) {
fe0f47d0 319 push @field_defs, create_field($field, $options);
0013ee25 320 }
1ded8513 321
0013ee25 322 #
323 # Indices
324 #
325 my @index_defs;
326 my %indexed_fields;
327 for my $index ( $table->get_indices ) {
fe0f47d0 328 push @index_defs, create_index($index, $options);
0013ee25 329 $indexed_fields{ $_ } = 1 for $index->fields;
330 }
d529894e 331
0013ee25 332 #
333 # Constraints -- need to handle more than just FK. -ky
334 #
335 my @constraint_defs;
336 my @constraints = $table->get_constraints;
337 for my $c ( @constraints ) {
cd0ea0fd 338 my $constr = create_constraint($c, $options);
339 push @constraint_defs, $constr if($constr);
0013ee25 340
da5a1bae 341 unless ( $indexed_fields{ ($c->fields())[0] } || $c->type ne FOREIGN_KEY ) {
342 push @index_defs, "INDEX ($qf" . ($c->fields())[0] . "$qf)";
343 $indexed_fields{ ($c->fields())[0] } = 1;
344 }
0013ee25 345 }
1ded8513 346
0013ee25 347 $create .= join(",\n", map { " $_" }
348 @field_defs, @index_defs, @constraint_defs
349 );
9398955f 350
0013ee25 351 #
352 # Footer
353 #
354 $create .= "\n)";
da5a1bae 355 $create .= generate_table_options($table) || '';
0013ee25 356 $create .= ";\n\n";
9398955f 357
fa94b25f 358 return $drop ? ($drop,$create) : $create;
0013ee25 359}
f8b6e804 360
da5a1bae 361sub generate_table_options
362{
363 my ($table) = @_;
364 my $create;
365
366 my $table_type_defined = 0;
7725e1e6 367 my $charset = $table->extra('mysql_charset');
368 my $collate = $table->extra('mysql_collate');
da5a1bae 369 for my $t1_option_ref ( $table->options ) {
370 my($key, $value) = %{$t1_option_ref};
371 $table_type_defined = 1
372 if uc $key eq 'ENGINE' or uc $key eq 'TYPE';
7725e1e6 373 if (uc $key eq 'CHARACTER SET') {
374 $charset = $value;
375 next;
376 } elsif (uc $key eq 'COLLATE') {
377 $collate = $value;
378 next;
379 }
da5a1bae 380 $create .= " $key=$value";
381 }
9ab59f87 382
da5a1bae 383 my $mysql_table_type = $table->extra('mysql_table_type');
9ab59f87 384 $create .= " ENGINE=$mysql_table_type"
da5a1bae 385 if $mysql_table_type && !$table_type_defined;
da5a1bae 386 my $comments = $table->comments;
387
388 $create .= " DEFAULT CHARACTER SET $charset" if $charset;
389 $create .= " COLLATE $collate" if $collate;
390 $create .= qq[ comment='$comments'] if $comments;
391 return $create;
392}
393
0013ee25 394sub create_field
395{
fe0f47d0 396 my ($field, $options) = @_;
397
398 my $qf = $options->{quote_field_names} ||= '';
9398955f 399
0013ee25 400 my $field_name = $field->name;
401 debug("PKG: Looking at field '$field_name'\n");
fe0f47d0 402 my $field_def = "$qf$field_name$qf";
0013ee25 403
404 # data type and size
405 my $data_type = $field->data_type;
406 my @size = $field->size;
407 my %extra = $field->extra;
408 my $list = $extra{'list'} || [];
409 # \todo deal with embedded quotes
410 my $commalist = join( ', ', map { qq['$_'] } @$list );
411 my $charset = $extra{'mysql_charset'};
412 my $collate = $extra{'mysql_collate'};
413
414 #
415 # Oracle "number" type -- figure best MySQL type
416 #
417 if ( lc $data_type eq 'number' ) {
418 # not an integer
419 if ( scalar @size > 1 ) {
420 $data_type = 'double';
d529894e 421 }
0013ee25 422 elsif ( $size[0] && $size[0] >= 12 ) {
423 $data_type = 'bigint';
424 }
425 elsif ( $size[0] && $size[0] <= 1 ) {
426 $data_type = 'tinyint';
427 }
428 else {
429 $data_type = 'int';
430 }
431 }
432 #
433 # Convert a large Oracle varchar to "text"
434 #
435 elsif ( $data_type =~ /char/i && $size[0] > 255 ) {
436 $data_type = 'text';
437 @size = ();
438 }
0013ee25 439 elsif ( $data_type =~ /boolean/i ) {
ca1f9923 440 my $mysql_version = $options->{mysql_version} || 0;
441 if ($mysql_version >= 4) {
442 $data_type = 'boolean';
443 } else {
444 $data_type = 'enum';
445 $commalist = "'0','1'";
446 }
0013ee25 447 }
448 elsif ( exists $translate{ lc $data_type } ) {
449 $data_type = $translate{ lc $data_type };
450 }
451
452 @size = () if $data_type =~ /(text|blob)/i;
453
454 if ( $data_type =~ /(double|float)/ && scalar @size == 1 ) {
455 push @size, '0';
456 }
d529894e 457
0013ee25 458 $field_def .= " $data_type";
459
460 if ( lc $data_type eq 'enum' ) {
461 $field_def .= '(' . $commalist . ')';
462 }
463 elsif ( defined $size[0] && $size[0] > 0 ) {
464 $field_def .= '(' . join( ', ', @size ) . ')';
465 }
466
467 # char sets
468 $field_def .= " CHARACTER SET $charset" if $charset;
469 $field_def .= " COLLATE $collate" if $collate;
470
471 # MySQL qualifiers
472 for my $qual ( qw[ binary unsigned zerofill ] ) {
473 my $val = $extra{ $qual } || $extra{ uc $qual } or next;
474 $field_def .= " $qual";
475 }
476 for my $qual ( 'character set', 'collate', 'on update' ) {
477 my $val = $extra{ $qual } || $extra{ uc $qual } or next;
478 $field_def .= " $qual $val";
479 }
480
481 # Null?
482 $field_def .= ' NOT NULL' unless $field->is_nullable;
483
484 # Default? XXX Need better quoting!
485 my $default = $field->default_value;
486 if ( defined $default ) {
487 if ( uc $default eq 'NULL') {
488 $field_def .= ' DEFAULT NULL';
489 } else {
490 $field_def .= " DEFAULT '$default'";
491 }
492 }
493
494 if ( my $comments = $field->comments ) {
495 $field_def .= qq[ comment '$comments'];
496 }
497
498 # auto_increment?
499 $field_def .= " auto_increment" if $field->is_auto_increment;
500
501 return $field_def;
502}
503
da5a1bae 504sub alter_create_index
505{
506 my ($index, $options) = @_;
507
508 my $qt = $options->{quote_table_names} || '';
509 my $qf = $options->{quote_field_names} || '';
510
511 return join( ' ',
512 'ALTER TABLE',
513 $qt.$index->table->name.$qt,
514 'ADD',
515 create_index(@_)
516 );
517}
518
0013ee25 519sub create_index
520{
fe0f47d0 521 my ($index, $options) = @_;
522
523 my $qf = $options->{quote_field_names} || '';
0013ee25 524
525 return join( ' ',
da5a1bae 526 lc $index->type eq 'normal' ? 'INDEX' : $index->type . ' INDEX',
f5405d47 527 truncate_id_uniquely( $index->name, $options->{max_id_length} || $DEFAULT_MAX_ID_LENGTH ),
fe0f47d0 528 '(' . $qf . join( "$qf, $qf", $index->fields ) . $qf . ')'
0013ee25 529 );
530
531}
532
da5a1bae 533sub alter_drop_index
534{
535 my ($index, $options) = @_;
536
537 my $qt = $options->{quote_table_names} || '';
538 my $qf = $options->{quote_field_names} || '';
539
540 return join( ' ',
541 'ALTER TABLE',
542 $qt.$index->table->name.$qt,
543 'DROP',
544 'INDEX',
545 $index->name || $index->fields
546 );
547
548}
549
550sub alter_drop_constraint
551{
552 my ($c, $options) = @_;
553
554 my $qt = $options->{quote_table_names} || '';
7725e1e6 555 my $qc = $options->{quote_field_names} || '';
da5a1bae 556
557 my $out = sprintf('ALTER TABLE %s DROP %s %s',
7467c458 558 $qt . $c->table->name . $qt,
74ca32ce 559 $c->type eq FOREIGN_KEY ? $c->type : "INDEX",
da5a1bae 560 $qc . $c->name . $qc );
561
562 return $out;
563}
564
565sub alter_create_constraint
566{
567 my ($index, $options) = @_;
568
569 my $qt = $options->{quote_table_names} || '';
570 return join( ' ',
571 'ALTER TABLE',
572 $qt.$index->table->name.$qt,
573 'ADD',
574 create_constraint(@_) );
575}
576
0013ee25 577sub create_constraint
578{
579 my ($c, $options) = @_;
580
fb149f81 581 my $qf = $options->{quote_field_names} || '';
582 my $qt = $options->{quote_table_names} || '';
da5a1bae 583 my $leave_name = $options->{leave_name} || undef;
fe0f47d0 584
0013ee25 585 my @fields = $c->fields or next;
586
587 if ( $c->type eq PRIMARY_KEY ) {
fe0f47d0 588 return 'PRIMARY KEY (' . $qf . join("$qf, $qf", @fields). $qf . ')';
0013ee25 589 }
590 elsif ( $c->type eq UNIQUE ) {
591 return
fe0f47d0 592 'UNIQUE '.
f5405d47 593 (defined $c->name ? $qf.truncate_id_uniquely( $c->name, $options->{max_id_length} || $DEFAULT_MAX_ID_LENGTH ).$qf.' ' : '').
fe0f47d0 594 '(' . $qf . join("$qf, $qf", @fields). $qf . ')';
0013ee25 595 }
596 elsif ( $c->type eq FOREIGN_KEY ) {
d529894e 597 #
0013ee25 598 # Make sure FK field is indexed or MySQL complains.
5e56da9a 599 #
0013ee25 600
866d012e 601 my $table = $c->table;
f5405d47 602 my $c_name = truncate_id_uniquely( $c->name, $options->{max_id_length} || $DEFAULT_MAX_ID_LENGTH );
866d012e 603
0013ee25 604 my $def = join(' ',
fb149f81 605 map { $_ || () }
606 'CONSTRAINT',
7725e1e6 607 $qf . $c_name . $qf,
fb149f81 608 'FOREIGN KEY'
609 );
0013ee25 610
da5a1bae 611
fe0f47d0 612 $def .= ' ('.$qf . join( "$qf, $qf", @fields ) . $qf . ')';
0013ee25 613
fe0f47d0 614 $def .= ' REFERENCES ' . $qt . $c->reference_table . $qt;
0013ee25 615
616 my @rfields = map { $_ || () } $c->reference_fields;
617 unless ( @rfields ) {
618 my $rtable_name = $c->reference_table;
866d012e 619 if ( my $ref_table = $table->schema->get_table( $rtable_name ) ) {
0013ee25 620 push @rfields, $ref_table->primary_key;
1c14e9f1 621 }
0013ee25 622 else {
623 warn "Can't find reference table '$rtable_name' " .
624 "in schema\n" if $options->{show_warnings};
5e56da9a 625 }
626 }
627
0013ee25 628 if ( @rfields ) {
fe0f47d0 629 $def .= ' (' . $qf . join( "$qf, $qf", @rfields ) . $qf . ')';
0013ee25 630 }
631 else {
866d012e 632 warn "FK constraint on " . $table->name . '.' .
0013ee25 633 join('', @fields) . " has no reference fields\n"
634 if $options->{show_warnings};
635 }
5e56da9a 636
0013ee25 637 if ( $c->match_type ) {
638 $def .= ' MATCH ' .
639 ( $c->match_type =~ /full/i ) ? 'FULL' : 'PARTIAL';
640 }
641
642 if ( $c->on_delete ) {
643 $def .= ' ON DELETE '.join( ' ', $c->on_delete );
644 }
645
646 if ( $c->on_update ) {
647 $def .= ' ON UPDATE '.join( ' ', $c->on_update );
648 }
cd0ea0fd 649 return $def;
9398955f 650 }
651
cd0ea0fd 652 return undef;
0013ee25 653}
654
da5a1bae 655sub alter_table
656{
657 my ($to_table, $options) = @_;
658
7467c458 659 my $qt = $options->{quote_table_names} || '';
da5a1bae 660
661 my $table_options = generate_table_options($to_table) || '';
662 my $out = sprintf('ALTER TABLE %s%s',
663 $qt . $to_table->name . $qt,
664 $table_options);
665
666 return $out;
667}
668
4d438549 669sub rename_field { alter_field(@_) }
0013ee25 670sub alter_field
671{
fe0f47d0 672 my ($from_field, $to_field, $options) = @_;
673
7467c458 674 my $qf = $options->{quote_field_names} || '';
675 my $qt = $options->{quote_table_names} || '';
0013ee25 676
677 my $out = sprintf('ALTER TABLE %s CHANGE COLUMN %s %s',
fe0f47d0 678 $qt . $to_field->table->name . $qt,
4d438549 679 $qf . $from_field->name . $qf,
fe0f47d0 680 create_field($to_field, $options));
0013ee25 681
682 return $out;
683}
684
685sub add_field
686{
fe0f47d0 687 my ($new_field, $options) = @_;
688
7467c458 689 my $qt = $options->{quote_table_names} || '';
0013ee25 690
691 my $out = sprintf('ALTER TABLE %s ADD COLUMN %s',
fe0f47d0 692 $qt . $new_field->table->name . $qt,
693 create_field($new_field, $options));
0013ee25 694
695 return $out;
696
697}
698
699sub drop_field
700{
fe0f47d0 701 my ($old_field, $options) = @_;
0013ee25 702
7467c458 703 my $qf = $options->{quote_field_names} || '';
704 my $qt = $options->{quote_table_names} || '';
fe0f47d0 705
0013ee25 706 my $out = sprintf('ALTER TABLE %s DROP COLUMN %s',
fe0f47d0 707 $qt . $old_field->table->name . $qt,
708 $qf . $old_field->name . $qf);
0013ee25 709
710 return $out;
711
9398955f 712}
713
4d438549 714sub batch_alter_table {
715 my ($table, $diff_hash, $options) = @_;
716
f9ed5d54 717 # InnoDB has an issue with dropping and re-adding a FK constraint under the
718 # name in a single alter statment, see: http://bugs.mysql.com/bug.php?id=13741
719 #
720 # We have to work round this.
721
722 my %fks_to_alter;
723 my %fks_to_drop = map {
724 $_->type eq FOREIGN_KEY
725 ? ( $_->name => $_ )
726 : ( )
727 } @{$diff_hash->{alter_drop_constraint} };
728
729 my %fks_to_create = map {
730 if ( $_->type eq FOREIGN_KEY) {
731 $fks_to_alter{$_->name} = $fks_to_drop{$_->name} if $fks_to_drop{$_->name};
732 ( $_->name => $_ );
733 } else { ( ) }
734 } @{$diff_hash->{alter_create_constraint} };
735
736 my $drop_stmt = '';
737 if (scalar keys %fks_to_alter) {
738 $diff_hash->{alter_drop_constraint} = [
739 grep { !$fks_to_alter{$_->name} } @{ $diff_hash->{alter_drop_constraint} }
740 ];
741
742 $drop_stmt = batch_alter_table($table, { alter_drop_constraint => [ values %fks_to_alter ] }, $options)
743 . "\n";
744
745 }
746
4d438549 747 my @stmts = map {
748 if (@{ $diff_hash->{$_} || [] }) {
749 my $meth = __PACKAGE__->can($_) or die __PACKAGE__ . " cant $_";
f9ed5d54 750 map { $meth->( (ref $_ eq 'ARRAY' ? @$_ : $_), $options ) } @{ $diff_hash->{$_} }
4d438549 751 } else { () }
46bf5655 752 } qw/rename_table
753 alter_drop_constraint
4d438549 754 alter_drop_index
755 drop_field
756 add_field
757 alter_field
758 rename_field
759 alter_create_index
760 alter_create_constraint
761 alter_table/;
762
46bf5655 763 # rename_table makes things a bit more complex
764 my $renamed_from = "";
765 $renamed_from = $diff_hash->{rename_table}[0][0]->name
766 if $diff_hash->{rename_table} && @{$diff_hash->{rename_table}};
767
4d438549 768 return unless @stmts;
769 # Just zero or one stmts. return now
f9ed5d54 770 return "$drop_stmt@stmts;" unless @stmts > 1;
4d438549 771
772 # Now strip off the 'ALTER TABLE xyz' of all but the first one
773
7467c458 774 my $qt = $options->{quote_table_names} || '';
4104f82b 775 my $table_name = $qt . $table->name . $qt;
776
777
778 my $re = $renamed_from
779 ? qr/^ALTER TABLE (?:\Q$table_name\E|\Q$qt$renamed_from$qt\E) /
780 : qr/^ALTER TABLE \Q$table_name\E /;
4d438549 781
782 my $first = shift @stmts;
4104f82b 783 my ($alter_table) = $first =~ /($re)/;
46bf5655 784
4d438549 785 my $padd = " " x length($alter_table);
786
f9ed5d54 787 return $drop_stmt . join( ",\n", $first, map { s/$re//; $padd . $_ } @stmts) . ';';
4104f82b 788
4d438549 789}
790
791sub drop_table {
46bf5655 792 my ($table, $options) = @_;
793
794 my $qt = $options->{quote_table_names} || '';
4d438549 795
796 # Drop (foreign key) constraints so table drops cleanly
46bf5655 797 my @sql = batch_alter_table($table, { alter_drop_constraint => [ grep { $_->type eq 'FOREIGN KEY' } $table->get_constraints ] }, $options);
798
799 return join("\n", @sql, "DROP TABLE $qt$table$qt;");
800
801}
802
803sub rename_table {
804 my ($old_table, $new_table, $options) = @_;
4d438549 805
46bf5655 806 my $qt = $options->{quote_table_names} || '';
4d438549 807
46bf5655 808 return "ALTER TABLE $qt$old_table$qt RENAME TO $qt$new_table$qt";
4d438549 809}
810
da5a1bae 811sub next_unused_name {
812 my $name = shift || '';
813 if ( !defined($used_names{$name}) ) {
814 $used_names{$name} = $name;
815 return $name;
816 }
817
818 my $i = 1;
819 while ( defined($used_names{$name . '_' . $i}) ) {
820 ++$i;
821 }
822 $name .= '_' . $i;
823 $used_names{$name} = $name;
824 return $name;
825}
826
9398955f 8271;
9398955f 828
c855a748 829# -------------------------------------------------------------------
9398955f 830
c855a748 831=pod
832
833=head1 SEE ALSO
834
835SQL::Translator, http://www.mysql.com/.
9398955f 836
2d6979da 837=head1 AUTHORS
9398955f 838
758ab1cd 839darren chamberlain E<lt>darren@cpan.orgE<gt>,
c855a748 840Ken Y. Clark E<lt>kclark@cpan.orgE<gt>.
841
842=cut