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