Merge 'trunk' into 'roundtrip'
[dbsrgits/SQL-Translator.git] / lib / SQL / Translator / Producer / MySQL.pm
CommitLineData
9398955f 1package SQL::Translator::Producer::MySQL;
2
49e1eb70 3# -------------------------------------------------------------------
478f608d 4# Copyright (C) 2002-2009 SQLFairy Authors
9398955f 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
c855a748 21=head1 NAME
22
23SQL::Translator::Producer::MySQL - MySQL-specific producer for SQL::Translator
24
25=head1 SYNOPSIS
26
27Use 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
36This module will produce text output of the schema suitable for MySQL.
37There are still some issues to be worked out with syntax differences
38between MySQL versions 3 and 4 ("SET foreign_key_checks," character sets
39for fields, etc.).
40
5d666b31 41=head1 ARGUMENTS
42
43This producer takes a single optional producer_arg C<mysql_version>, which
44provides the desired version for the target database. By default MySQL v3 is
45assumed, and statements pertaining to any features introduced in later versions
46(e.g. CREATE VIEW) are not produced.
47
94929d12 48Valid version specifiers for C<mysql_version> are listed L<here|SQL::Translator::Utils/parse_mysql_version>
5d666b31 49
5a0c7b43 50=head2 Table Types
51
52Normally the tables will be created without any explicit table type given and
53so will use the MySQL default.
54
55Any tables involved in foreign key constraints automatically get a table type
56of InnoDB, unless this is overridden by setting the C<mysql_table_type> extra
57attribute explicitly on the table.
58
59=head2 Extra attributes.
60
61The producer recognises the following extra attributes on the Schema objects.
62
63=over 4
64
7467c458 65=item B<field.list>
5a0c7b43 66
67Set the list of allowed values for Enum fields.
68
7467c458 69=item B<field.binary>, B<field.unsigned>, B<field.zerofill>
5a0c7b43 70
71Set the MySQL field options of the same name.
72
7467c458 73=item B<field.renamed_from>, B<table.renamed_from>
4d438549 74
7467c458 75Use when producing diffs to indicate that the current table/field has been
76renamed from the old name as given in the attribute value.
4d438549 77
7467c458 78=item B<table.mysql_table_type>
5a0c7b43 79
80Set the type of the table e.g. 'InnoDB', 'MyISAM'. This will be
81automatically set for tables involved in foreign key constraints if it is
82not already set explicitly. See L<"Table Types">.
83
7467c458 84Please note that the C<ENGINE> option is the prefered method of specifying
85the MySQL storage engine to use, but this method still works for backwards
86compatability.
87
88=item B<table.mysql_charset>, B<table.mysql_collate>
1ded8513 89
90Set the tables default charater set and collation order.
91
7467c458 92=item B<field.mysql_charset>, B<field.mysql_collate>
1ded8513 93
94Set the fields charater set and collation order.
95
5a0c7b43 96=back
97
c855a748 98=cut
99
9398955f 100use strict;
cd0ea0fd 101use warnings;
da06ac74 102use vars qw[ $VERSION $DEBUG %used_names ];
4ab3763d 103$VERSION = '1.59';
5636ed00 104$DEBUG = 0 unless defined $DEBUG;
9398955f 105
f5405d47 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
109my $DEFAULT_MAX_ID_LENGTH = 64;
110
9398955f 111use Data::Dumper;
1c14e9f1 112use SQL::Translator::Schema::Constants;
5d666b31 113use SQL::Translator::Utils qw(debug header_comment truncate_id_uniquely parse_mysql_version);
9398955f 114
d2344c83 115#
116# Use only lowercase for the keys (e.g. "long" and not "LONG")
117#
2620fc1c 118my %translate = (
119 #
120 # Oracle types
121 #
122 varchar2 => 'varchar',
123 long => 'text',
d2344c83 124 clob => 'longtext',
2620fc1c 125
126 #
127 # Sybase types
128 #
129 int => 'integer',
130 money => 'float',
131 real => 'double',
132 comment => 'text',
133 bit => 'tinyint',
87779799 134
135 #
136 # Access types
137 #
138 'long integer' => 'integer',
139 'text' => 'text',
140 'datetime' => 'datetime',
08dd6593 141
142 #
143 # PostgreSQL types
144 #
145 bytea => 'BLOB',
2620fc1c 146);
147
9ab59f87 148
149sub preprocess_schema {
934e1b39 150 my ($schema) = @_;
9ab59f87 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
7725e1e6 154 # Similarly for mysql_charset and mysql_collate
155 my $extra_to_options = sub {
156 my ($table, $extra_name, $opt_name) = @_;
9ab59f87 157
158 my $extra = $table->extra;
159
7725e1e6 160 my $extra_type = delete $extra->{$extra_name};
9ab59f87 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
7725e1e6 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
9ab59f87 190 # This assumes that there isn't both a Type and an Engine option.
7725e1e6 191 OPTION:
9ab59f87 192 for my $idx ( 0..$#{$options} ) {
193 my ($key, $value) = %{ $options->[$idx] };
194
7725e1e6 195 next unless uc $key eq $opt_name;
196
197 # make sure case is right on option name
9ab59f87 198 delete $options->[$idx]{$key};
7725e1e6 199 return $options->[$idx]{$opt_name} = $value || $extra_type;
9ab59f87 200
201 }
202
203 if ($extra_type) {
7725e1e6 204 push @$options, { $opt_name => $extra_type };
9ab59f87 205 return $extra_type;
206 }
207
208 };
209
1c680eb9 210 # Names are only specific to a given schema
211 local %used_names = ();
212
9ab59f87 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 ) {
7725e1e6 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' );
9ab59f87 222
929ef265 223 foreach my $c ( $table->get_constraints ) {
1c680eb9 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
9ab59f87 234 for my $meth (qw/table reference_table/) {
1c680eb9 235 my $table = $schema->get_table($c->$meth) || next;
7725e1e6 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']);
9ab59f87 238 $table->options( { 'ENGINE' => 'InnoDB' } );
239 }
929ef265 240 } # foreach constraints
241
2c6be67a 242 my %map = ( mysql_collate => 'collate', mysql_charset => 'character set');
929ef265 243 foreach my $f ( $table->get_fields ) {
2c6be67a 244 my $extra = $f->extra;
245 for (keys %map) {
246 $extra->{$map{$_}} = delete $extra->{$_} if exists $extra->{$_};
247 }
248
929ef265 249 my @size = $f->size;
250 if ( !$size[0] && $f->data_type =~ /char$/ ) {
251 $f->size( (255) );
252 }
9ab59f87 253 }
929ef265 254
9ab59f87 255 }
256}
257
9398955f 258sub produce {
a1d94525 259 my $translator = shift;
260 local $DEBUG = $translator->debug;
da5a1bae 261 local %used_names;
a1d94525 262 my $no_comments = $translator->no_comments;
263 my $add_drop_table = $translator->add_drop_table;
264 my $schema = $translator->schema;
2bc23e82 265 my $show_warnings = $translator->show_warnings || 0;
ca1f9923 266 my $producer_args = $translator->producer_args;
5d666b31 267 my $mysql_version = parse_mysql_version ($producer_args->{mysql_version}, 'perl') || 0;
f5405d47 268 my $max_id_length = $producer_args->{mysql_max_id_length} || $DEFAULT_MAX_ID_LENGTH;
d529894e 269
7725e1e6 270 my ($qt, $qf, $qc) = ('','', '');
fe0f47d0 271 $qt = '`' if $translator->quote_table_names;
272 $qf = '`' if $translator->quote_field_names;
273
1a24938d 274 debug("PKG: Beginning production\n");
da5a1bae 275 %used_names = ();
24d9fe69 276 my $create = '';
5ee19df8 277 $create .= header_comment unless ($no_comments);
0823773d 278 # \todo Don't set if MySQL 3.x is set on command line
24d9fe69 279 my @create = "SET foreign_key_checks=0";
9398955f 280
934e1b39 281 preprocess_schema($schema);
5a0c7b43 282
283 #
284 # Generate sql
285 #
cd0ea0fd 286 my @table_defs =();
4d438549 287
1c14e9f1 288 for my $table ( $schema->get_tables ) {
cd0ea0fd 289# print $table->name, "\n";
0013ee25 290 push @table_defs, create_table($table,
fe0f47d0 291 { add_drop_table => $add_drop_table,
292 show_warnings => $show_warnings,
293 no_comments => $no_comments,
294 quote_table_names => $qt,
ca1f9923 295 quote_field_names => $qf,
f5405d47 296 max_id_length => $max_id_length,
ca1f9923 297 mysql_version => $mysql_version
0013ee25 298 });
299 }
9398955f 300
e30b71b8 301 if ($mysql_version >= 5.000001) {
83ddfeac 302 for my $view ( $schema->get_views ) {
d28afa66 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 });
83ddfeac 312 }
d28afa66 313 }
314
315
cd0ea0fd 316# print "@table_defs\n";
24d9fe69 317 push @table_defs, "SET foreign_key_checks=1";
cd0ea0fd 318
24d9fe69 319 return wantarray ? ($create ? $create : (), @create, @table_defs) : ($create . join('', map { $_ ? "$_;\n\n" : () } (@create, @table_defs)));
0013ee25 320}
9398955f 321
d28afa66 322sub 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 }
24d9fe69 361# $create .= "";
d28afa66 362 return $create;
363}
364
0013ee25 365sub create_table
366{
367 my ($table, $options) = @_;
2620fc1c 368
fe0f47d0 369 my $qt = $options->{quote_table_names} || '';
370 my $qf = $options->{quote_field_names} || '';
371
8d693a85 372 my $table_name = quote_table_name($table->name, $qt);
0013ee25 373 debug("PKG: Looking at table '$table_name'\n");
6d3f6379 374
0013ee25 375 #
376 # Header. Should this look like what mysqldump produces?
377 #
cd0ea0fd 378 my $create = '';
fa94b25f 379 my $drop;
8d693a85 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";
2b695517 383
0013ee25 384 #
385 # Fields
386 #
387 my @field_defs;
388 for my $field ( $table->get_fields ) {
fe0f47d0 389 push @field_defs, create_field($field, $options);
0013ee25 390 }
1ded8513 391
0013ee25 392 #
393 # Indices
394 #
395 my @index_defs;
396 my %indexed_fields;
397 for my $index ( $table->get_indices ) {
fe0f47d0 398 push @index_defs, create_index($index, $options);
0013ee25 399 $indexed_fields{ $_ } = 1 for $index->fields;
400 }
d529894e 401
0013ee25 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 ) {
cd0ea0fd 408 my $constr = create_constraint($c, $options);
409 push @constraint_defs, $constr if($constr);
0013ee25 410
da5a1bae 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 }
0013ee25 415 }
1ded8513 416
0013ee25 417 $create .= join(",\n", map { " $_" }
418 @field_defs, @index_defs, @constraint_defs
419 );
9398955f 420
0013ee25 421 #
422 # Footer
423 #
424 $create .= "\n)";
9a96648f 425 $create .= generate_table_options($table, $options) || '';
24d9fe69 426# $create .= ";\n\n";
9398955f 427
fa94b25f 428 return $drop ? ($drop,$create) : $create;
0013ee25 429}
f8b6e804 430
8d693a85 431sub 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
da5a1bae 439sub generate_table_options
440{
9a96648f 441 my ($table, $options) = @_;
da5a1bae 442 my $create;
443
444 my $table_type_defined = 0;
9a96648f 445 my $qf = $options->{quote_field_names} ||= '';
7725e1e6 446 my $charset = $table->extra('mysql_charset');
447 my $collate = $table->extra('mysql_collate');
9a96648f 448 my $union = undef;
da5a1bae 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';
7725e1e6 453 if (uc $key eq 'CHARACTER SET') {
454 $charset = $value;
455 next;
456 } elsif (uc $key eq 'COLLATE') {
457 $collate = $value;
458 next;
9a96648f 459 } elsif (uc $key eq 'UNION') {
460 $union = "($qf". join("$qf, $qf", @$value) ."$qf)";
461 next;
7725e1e6 462 }
da5a1bae 463 $create .= " $key=$value";
464 }
9ab59f87 465
da5a1bae 466 my $mysql_table_type = $table->extra('mysql_table_type');
9ab59f87 467 $create .= " ENGINE=$mysql_table_type"
da5a1bae 468 if $mysql_table_type && !$table_type_defined;
da5a1bae 469 my $comments = $table->comments;
470
471 $create .= " DEFAULT CHARACTER SET $charset" if $charset;
472 $create .= " COLLATE $collate" if $collate;
9a96648f 473 $create .= " UNION=$union" if $union;
da5a1bae 474 $create .= qq[ comment='$comments'] if $comments;
475 return $create;
476}
477
0013ee25 478sub create_field
479{
fe0f47d0 480 my ($field, $options) = @_;
481
482 my $qf = $options->{quote_field_names} ||= '';
9398955f 483
0013ee25 484 my $field_name = $field->name;
485 debug("PKG: Looking at field '$field_name'\n");
fe0f47d0 486 my $field_def = "$qf$field_name$qf";
0013ee25 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
5d666b31 498 my $mysql_version = $options->{mysql_version} || 0;
0013ee25 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';
d529894e 506 }
0013ee25 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"
5d666b31 519 # (not necessary as of 5.0.3 http://dev.mysql.com/doc/refman/5.0/en/char.html)
0013ee25 520 #
521 elsif ( $data_type =~ /char/i && $size[0] > 255 ) {
5d666b31 522 unless ($size[0] <= 65535 && $mysql_version >= 5.000003 ) {
523 $data_type = 'text';
524 @size = ();
525 }
0013ee25 526 }
0013ee25 527 elsif ( $data_type =~ /boolean/i ) {
ca1f9923 528 if ($mysql_version >= 4) {
529 $data_type = 'boolean';
530 } else {
531 $data_type = 'enum';
532 $commalist = "'0','1'";
533 }
0013ee25 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 }
d529894e 544
0013ee25 545 $field_def .= " $data_type";
546
7c1aae02 547 if ( lc($data_type) eq 'enum' || lc($data_type) eq 'set') {
0013ee25 548 $field_def .= '(' . $commalist . ')';
7c1aae02 549 }
0013ee25 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 ) {
bc8e2aa1 574 SQL::Translator::Producer->_apply_default_value(
575 \$field_def,
576 $default,
577 [
578 'NULL' => \'NULL',
579 ],
580 );
0013ee25 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
da5a1bae 593sub 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
0013ee25 608sub create_index
609{
fe0f47d0 610 my ($index, $options) = @_;
611
612 my $qf = $options->{quote_field_names} || '';
0013ee25 613
614 return join( ' ',
daaf8bca 615 lc $index->type eq 'normal'
616 ? 'INDEX'
617 : $index->type . ' INDEX'
618 ,
619 $index->name
620 ? (truncate_id_uniquely( $index->name, $options->{max_id_length} || $DEFAULT_MAX_ID_LENGTH ) )
621 : ()
622 ,
623 '(' . $qf . join( "$qf, $qf", $index->fields ) . $qf . ')'
0013ee25 624 );
625
626}
627
da5a1bae 628sub alter_drop_index
629{
630 my ($index, $options) = @_;
631
632 my $qt = $options->{quote_table_names} || '';
633 my $qf = $options->{quote_field_names} || '';
634
635 return join( ' ',
636 'ALTER TABLE',
637 $qt.$index->table->name.$qt,
638 'DROP',
639 'INDEX',
640 $index->name || $index->fields
641 );
642
643}
644
645sub alter_drop_constraint
646{
647 my ($c, $options) = @_;
648
649 my $qt = $options->{quote_table_names} || '';
7725e1e6 650 my $qc = $options->{quote_field_names} || '';
da5a1bae 651
652 my $out = sprintf('ALTER TABLE %s DROP %s %s',
7467c458 653 $qt . $c->table->name . $qt,
74ca32ce 654 $c->type eq FOREIGN_KEY ? $c->type : "INDEX",
da5a1bae 655 $qc . $c->name . $qc );
656
657 return $out;
658}
659
660sub alter_create_constraint
661{
662 my ($index, $options) = @_;
663
664 my $qt = $options->{quote_table_names} || '';
665 return join( ' ',
666 'ALTER TABLE',
667 $qt.$index->table->name.$qt,
668 'ADD',
669 create_constraint(@_) );
670}
671
0013ee25 672sub create_constraint
673{
674 my ($c, $options) = @_;
675
fb149f81 676 my $qf = $options->{quote_field_names} || '';
677 my $qt = $options->{quote_table_names} || '';
da5a1bae 678 my $leave_name = $options->{leave_name} || undef;
fe0f47d0 679
0013ee25 680 my @fields = $c->fields or next;
681
682 if ( $c->type eq PRIMARY_KEY ) {
fe0f47d0 683 return 'PRIMARY KEY (' . $qf . join("$qf, $qf", @fields). $qf . ')';
0013ee25 684 }
685 elsif ( $c->type eq UNIQUE ) {
686 return
fe0f47d0 687 'UNIQUE '.
f5405d47 688 (defined $c->name ? $qf.truncate_id_uniquely( $c->name, $options->{max_id_length} || $DEFAULT_MAX_ID_LENGTH ).$qf.' ' : '').
fe0f47d0 689 '(' . $qf . join("$qf, $qf", @fields). $qf . ')';
0013ee25 690 }
691 elsif ( $c->type eq FOREIGN_KEY ) {
d529894e 692 #
0013ee25 693 # Make sure FK field is indexed or MySQL complains.
5e56da9a 694 #
0013ee25 695
866d012e 696 my $table = $c->table;
f5405d47 697 my $c_name = truncate_id_uniquely( $c->name, $options->{max_id_length} || $DEFAULT_MAX_ID_LENGTH );
866d012e 698
0013ee25 699 my $def = join(' ',
fb149f81 700 map { $_ || () }
701 'CONSTRAINT',
7725e1e6 702 $qf . $c_name . $qf,
fb149f81 703 'FOREIGN KEY'
704 );
0013ee25 705
da5a1bae 706
fe0f47d0 707 $def .= ' ('.$qf . join( "$qf, $qf", @fields ) . $qf . ')';
0013ee25 708
fe0f47d0 709 $def .= ' REFERENCES ' . $qt . $c->reference_table . $qt;
0013ee25 710
711 my @rfields = map { $_ || () } $c->reference_fields;
712 unless ( @rfields ) {
713 my $rtable_name = $c->reference_table;
866d012e 714 if ( my $ref_table = $table->schema->get_table( $rtable_name ) ) {
0013ee25 715 push @rfields, $ref_table->primary_key;
1c14e9f1 716 }
0013ee25 717 else {
718 warn "Can't find reference table '$rtable_name' " .
719 "in schema\n" if $options->{show_warnings};
5e56da9a 720 }
721 }
722
0013ee25 723 if ( @rfields ) {
fe0f47d0 724 $def .= ' (' . $qf . join( "$qf, $qf", @rfields ) . $qf . ')';
0013ee25 725 }
726 else {
866d012e 727 warn "FK constraint on " . $table->name . '.' .
0013ee25 728 join('', @fields) . " has no reference fields\n"
729 if $options->{show_warnings};
730 }
5e56da9a 731
0013ee25 732 if ( $c->match_type ) {
733 $def .= ' MATCH ' .
734 ( $c->match_type =~ /full/i ) ? 'FULL' : 'PARTIAL';
735 }
736
737 if ( $c->on_delete ) {
738 $def .= ' ON DELETE '.join( ' ', $c->on_delete );
739 }
740
741 if ( $c->on_update ) {
742 $def .= ' ON UPDATE '.join( ' ', $c->on_update );
743 }
cd0ea0fd 744 return $def;
9398955f 745 }
746
cd0ea0fd 747 return undef;
0013ee25 748}
749
da5a1bae 750sub alter_table
751{
752 my ($to_table, $options) = @_;
753
7467c458 754 my $qt = $options->{quote_table_names} || '';
da5a1bae 755
9a96648f 756 my $table_options = generate_table_options($to_table, $options) || '';
da5a1bae 757 my $out = sprintf('ALTER TABLE %s%s',
758 $qt . $to_table->name . $qt,
759 $table_options);
760
761 return $out;
762}
763
4d438549 764sub rename_field { alter_field(@_) }
0013ee25 765sub alter_field
766{
fe0f47d0 767 my ($from_field, $to_field, $options) = @_;
768
7467c458 769 my $qf = $options->{quote_field_names} || '';
770 my $qt = $options->{quote_table_names} || '';
0013ee25 771
772 my $out = sprintf('ALTER TABLE %s CHANGE COLUMN %s %s',
fe0f47d0 773 $qt . $to_field->table->name . $qt,
4d438549 774 $qf . $from_field->name . $qf,
fe0f47d0 775 create_field($to_field, $options));
0013ee25 776
777 return $out;
778}
779
780sub add_field
781{
fe0f47d0 782 my ($new_field, $options) = @_;
783
7467c458 784 my $qt = $options->{quote_table_names} || '';
0013ee25 785
786 my $out = sprintf('ALTER TABLE %s ADD COLUMN %s',
fe0f47d0 787 $qt . $new_field->table->name . $qt,
788 create_field($new_field, $options));
0013ee25 789
790 return $out;
791
792}
793
794sub drop_field
795{
fe0f47d0 796 my ($old_field, $options) = @_;
0013ee25 797
7467c458 798 my $qf = $options->{quote_field_names} || '';
799 my $qt = $options->{quote_table_names} || '';
fe0f47d0 800
0013ee25 801 my $out = sprintf('ALTER TABLE %s DROP COLUMN %s',
fe0f47d0 802 $qt . $old_field->table->name . $qt,
803 $qf . $old_field->name . $qf);
0013ee25 804
805 return $out;
806
9398955f 807}
808
4d438549 809sub batch_alter_table {
810 my ($table, $diff_hash, $options) = @_;
811
f9ed5d54 812 # InnoDB has an issue with dropping and re-adding a FK constraint under the
813 # name in a single alter statment, see: http://bugs.mysql.com/bug.php?id=13741
814 #
815 # We have to work round this.
816
817 my %fks_to_alter;
818 my %fks_to_drop = map {
819 $_->type eq FOREIGN_KEY
820 ? ( $_->name => $_ )
821 : ( )
822 } @{$diff_hash->{alter_drop_constraint} };
823
824 my %fks_to_create = map {
825 if ( $_->type eq FOREIGN_KEY) {
826 $fks_to_alter{$_->name} = $fks_to_drop{$_->name} if $fks_to_drop{$_->name};
827 ( $_->name => $_ );
828 } else { ( ) }
829 } @{$diff_hash->{alter_create_constraint} };
830
24d9fe69 831 my @drop_stmt;
f9ed5d54 832 if (scalar keys %fks_to_alter) {
833 $diff_hash->{alter_drop_constraint} = [
834 grep { !$fks_to_alter{$_->name} } @{ $diff_hash->{alter_drop_constraint} }
835 ];
836
24d9fe69 837 @drop_stmt = batch_alter_table($table, { alter_drop_constraint => [ values %fks_to_alter ] }, $options);
f9ed5d54 838
839 }
840
4d438549 841 my @stmts = map {
842 if (@{ $diff_hash->{$_} || [] }) {
843 my $meth = __PACKAGE__->can($_) or die __PACKAGE__ . " cant $_";
f9ed5d54 844 map { $meth->( (ref $_ eq 'ARRAY' ? @$_ : $_), $options ) } @{ $diff_hash->{$_} }
4d438549 845 } else { () }
46bf5655 846 } qw/rename_table
847 alter_drop_constraint
4d438549 848 alter_drop_index
849 drop_field
850 add_field
851 alter_field
852 rename_field
853 alter_create_index
854 alter_create_constraint
855 alter_table/;
856
46bf5655 857 # rename_table makes things a bit more complex
858 my $renamed_from = "";
859 $renamed_from = $diff_hash->{rename_table}[0][0]->name
860 if $diff_hash->{rename_table} && @{$diff_hash->{rename_table}};
861
4d438549 862 return unless @stmts;
863 # Just zero or one stmts. return now
24d9fe69 864 return (@drop_stmt,@stmts) unless @stmts > 1;
4d438549 865
866 # Now strip off the 'ALTER TABLE xyz' of all but the first one
867
7467c458 868 my $qt = $options->{quote_table_names} || '';
4104f82b 869 my $table_name = $qt . $table->name . $qt;
870
871
872 my $re = $renamed_from
873 ? qr/^ALTER TABLE (?:\Q$table_name\E|\Q$qt$renamed_from$qt\E) /
874 : qr/^ALTER TABLE \Q$table_name\E /;
4d438549 875
876 my $first = shift @stmts;
4104f82b 877 my ($alter_table) = $first =~ /($re)/;
46bf5655 878
4d438549 879 my $padd = " " x length($alter_table);
880
24d9fe69 881 return @drop_stmt, join( ",\n", $first, map { s/$re//; $padd . $_ } @stmts);
4104f82b 882
4d438549 883}
884
885sub drop_table {
46bf5655 886 my ($table, $options) = @_;
887
888 my $qt = $options->{quote_table_names} || '';
4d438549 889
890 # Drop (foreign key) constraints so table drops cleanly
46bf5655 891 my @sql = batch_alter_table($table, { alter_drop_constraint => [ grep { $_->type eq 'FOREIGN KEY' } $table->get_constraints ] }, $options);
892
24d9fe69 893 return (@sql, "DROP TABLE $qt$table$qt");
894# return join("\n", @sql, "DROP TABLE $qt$table$qt");
46bf5655 895
896}
897
898sub rename_table {
899 my ($old_table, $new_table, $options) = @_;
4d438549 900
46bf5655 901 my $qt = $options->{quote_table_names} || '';
4d438549 902
46bf5655 903 return "ALTER TABLE $qt$old_table$qt RENAME TO $qt$new_table$qt";
4d438549 904}
905
da5a1bae 906sub next_unused_name {
907 my $name = shift || '';
908 if ( !defined($used_names{$name}) ) {
909 $used_names{$name} = $name;
910 return $name;
911 }
912
913 my $i = 1;
914 while ( defined($used_names{$name . '_' . $i}) ) {
915 ++$i;
916 }
917 $name .= '_' . $i;
918 $used_names{$name} = $name;
919 return $name;
920}
921
9398955f 9221;
9398955f 923
c855a748 924# -------------------------------------------------------------------
9398955f 925
c855a748 926=pod
927
928=head1 SEE ALSO
929
930SQL::Translator, http://www.mysql.com/.
9398955f 931
2d6979da 932=head1 AUTHORS
9398955f 933
758ab1cd 934darren chamberlain E<lt>darren@cpan.orgE<gt>,
c855a748 935Ken Y. Clark E<lt>kclark@cpan.orgE<gt>.
936
937=cut