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