MySQL producer skips length attribute for columns which do not support that attribute...
[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( ' ',
da5a1bae 622 lc $index->type eq 'normal' ? 'INDEX' : $index->type . ' INDEX',
f5405d47 623 truncate_id_uniquely( $index->name, $options->{max_id_length} || $DEFAULT_MAX_ID_LENGTH ),
fe0f47d0 624 '(' . $qf . join( "$qf, $qf", $index->fields ) . $qf . ')'
0013ee25 625 );
626
627}
628
da5a1bae 629sub alter_drop_index
630{
631 my ($index, $options) = @_;
632
633 my $qt = $options->{quote_table_names} || '';
634 my $qf = $options->{quote_field_names} || '';
635
636 return join( ' ',
637 'ALTER TABLE',
638 $qt.$index->table->name.$qt,
639 'DROP',
640 'INDEX',
641 $index->name || $index->fields
642 );
643
644}
645
646sub alter_drop_constraint
647{
648 my ($c, $options) = @_;
649
650 my $qt = $options->{quote_table_names} || '';
7725e1e6 651 my $qc = $options->{quote_field_names} || '';
da5a1bae 652
653 my $out = sprintf('ALTER TABLE %s DROP %s %s',
7467c458 654 $qt . $c->table->name . $qt,
74ca32ce 655 $c->type eq FOREIGN_KEY ? $c->type : "INDEX",
da5a1bae 656 $qc . $c->name . $qc );
657
658 return $out;
659}
660
661sub alter_create_constraint
662{
663 my ($index, $options) = @_;
664
665 my $qt = $options->{quote_table_names} || '';
666 return join( ' ',
667 'ALTER TABLE',
668 $qt.$index->table->name.$qt,
669 'ADD',
670 create_constraint(@_) );
671}
672
0013ee25 673sub create_constraint
674{
675 my ($c, $options) = @_;
676
fb149f81 677 my $qf = $options->{quote_field_names} || '';
678 my $qt = $options->{quote_table_names} || '';
da5a1bae 679 my $leave_name = $options->{leave_name} || undef;
fe0f47d0 680
0013ee25 681 my @fields = $c->fields or next;
682
683 if ( $c->type eq PRIMARY_KEY ) {
fe0f47d0 684 return 'PRIMARY KEY (' . $qf . join("$qf, $qf", @fields). $qf . ')';
0013ee25 685 }
686 elsif ( $c->type eq UNIQUE ) {
687 return
fe0f47d0 688 'UNIQUE '.
f5405d47 689 (defined $c->name ? $qf.truncate_id_uniquely( $c->name, $options->{max_id_length} || $DEFAULT_MAX_ID_LENGTH ).$qf.' ' : '').
fe0f47d0 690 '(' . $qf . join("$qf, $qf", @fields). $qf . ')';
0013ee25 691 }
692 elsif ( $c->type eq FOREIGN_KEY ) {
d529894e 693 #
0013ee25 694 # Make sure FK field is indexed or MySQL complains.
5e56da9a 695 #
0013ee25 696
866d012e 697 my $table = $c->table;
f5405d47 698 my $c_name = truncate_id_uniquely( $c->name, $options->{max_id_length} || $DEFAULT_MAX_ID_LENGTH );
866d012e 699
0013ee25 700 my $def = join(' ',
fb149f81 701 map { $_ || () }
702 'CONSTRAINT',
7725e1e6 703 $qf . $c_name . $qf,
fb149f81 704 'FOREIGN KEY'
705 );
0013ee25 706
da5a1bae 707
fe0f47d0 708 $def .= ' ('.$qf . join( "$qf, $qf", @fields ) . $qf . ')';
0013ee25 709
fe0f47d0 710 $def .= ' REFERENCES ' . $qt . $c->reference_table . $qt;
0013ee25 711
712 my @rfields = map { $_ || () } $c->reference_fields;
713 unless ( @rfields ) {
714 my $rtable_name = $c->reference_table;
866d012e 715 if ( my $ref_table = $table->schema->get_table( $rtable_name ) ) {
0013ee25 716 push @rfields, $ref_table->primary_key;
1c14e9f1 717 }
0013ee25 718 else {
719 warn "Can't find reference table '$rtable_name' " .
720 "in schema\n" if $options->{show_warnings};
5e56da9a 721 }
722 }
723
0013ee25 724 if ( @rfields ) {
fe0f47d0 725 $def .= ' (' . $qf . join( "$qf, $qf", @rfields ) . $qf . ')';
0013ee25 726 }
727 else {
866d012e 728 warn "FK constraint on " . $table->name . '.' .
0013ee25 729 join('', @fields) . " has no reference fields\n"
730 if $options->{show_warnings};
731 }
5e56da9a 732
0013ee25 733 if ( $c->match_type ) {
734 $def .= ' MATCH ' .
735 ( $c->match_type =~ /full/i ) ? 'FULL' : 'PARTIAL';
736 }
737
738 if ( $c->on_delete ) {
739 $def .= ' ON DELETE '.join( ' ', $c->on_delete );
740 }
741
742 if ( $c->on_update ) {
743 $def .= ' ON UPDATE '.join( ' ', $c->on_update );
744 }
cd0ea0fd 745 return $def;
9398955f 746 }
747
cd0ea0fd 748 return undef;
0013ee25 749}
750
da5a1bae 751sub alter_table
752{
753 my ($to_table, $options) = @_;
754
7467c458 755 my $qt = $options->{quote_table_names} || '';
da5a1bae 756
9a96648f 757 my $table_options = generate_table_options($to_table, $options) || '';
da5a1bae 758 my $out = sprintf('ALTER TABLE %s%s',
759 $qt . $to_table->name . $qt,
760 $table_options);
761
762 return $out;
763}
764
4d438549 765sub rename_field { alter_field(@_) }
0013ee25 766sub alter_field
767{
fe0f47d0 768 my ($from_field, $to_field, $options) = @_;
769
7467c458 770 my $qf = $options->{quote_field_names} || '';
771 my $qt = $options->{quote_table_names} || '';
0013ee25 772
773 my $out = sprintf('ALTER TABLE %s CHANGE COLUMN %s %s',
fe0f47d0 774 $qt . $to_field->table->name . $qt,
4d438549 775 $qf . $from_field->name . $qf,
fe0f47d0 776 create_field($to_field, $options));
0013ee25 777
778 return $out;
779}
780
781sub add_field
782{
fe0f47d0 783 my ($new_field, $options) = @_;
784
7467c458 785 my $qt = $options->{quote_table_names} || '';
0013ee25 786
787 my $out = sprintf('ALTER TABLE %s ADD COLUMN %s',
fe0f47d0 788 $qt . $new_field->table->name . $qt,
789 create_field($new_field, $options));
0013ee25 790
791 return $out;
792
793}
794
795sub drop_field
796{
fe0f47d0 797 my ($old_field, $options) = @_;
0013ee25 798
7467c458 799 my $qf = $options->{quote_field_names} || '';
800 my $qt = $options->{quote_table_names} || '';
fe0f47d0 801
0013ee25 802 my $out = sprintf('ALTER TABLE %s DROP COLUMN %s',
fe0f47d0 803 $qt . $old_field->table->name . $qt,
804 $qf . $old_field->name . $qf);
0013ee25 805
806 return $out;
807
9398955f 808}
809
4d438549 810sub batch_alter_table {
811 my ($table, $diff_hash, $options) = @_;
812
f9ed5d54 813 # InnoDB has an issue with dropping and re-adding a FK constraint under the
814 # name in a single alter statment, see: http://bugs.mysql.com/bug.php?id=13741
815 #
816 # We have to work round this.
817
818 my %fks_to_alter;
819 my %fks_to_drop = map {
820 $_->type eq FOREIGN_KEY
821 ? ( $_->name => $_ )
822 : ( )
823 } @{$diff_hash->{alter_drop_constraint} };
824
825 my %fks_to_create = map {
826 if ( $_->type eq FOREIGN_KEY) {
827 $fks_to_alter{$_->name} = $fks_to_drop{$_->name} if $fks_to_drop{$_->name};
828 ( $_->name => $_ );
829 } else { ( ) }
830 } @{$diff_hash->{alter_create_constraint} };
831
24d9fe69 832 my @drop_stmt;
f9ed5d54 833 if (scalar keys %fks_to_alter) {
834 $diff_hash->{alter_drop_constraint} = [
835 grep { !$fks_to_alter{$_->name} } @{ $diff_hash->{alter_drop_constraint} }
836 ];
837
24d9fe69 838 @drop_stmt = batch_alter_table($table, { alter_drop_constraint => [ values %fks_to_alter ] }, $options);
f9ed5d54 839
840 }
841
4d438549 842 my @stmts = map {
843 if (@{ $diff_hash->{$_} || [] }) {
844 my $meth = __PACKAGE__->can($_) or die __PACKAGE__ . " cant $_";
f9ed5d54 845 map { $meth->( (ref $_ eq 'ARRAY' ? @$_ : $_), $options ) } @{ $diff_hash->{$_} }
4d438549 846 } else { () }
46bf5655 847 } qw/rename_table
848 alter_drop_constraint
4d438549 849 alter_drop_index
850 drop_field
851 add_field
852 alter_field
853 rename_field
854 alter_create_index
855 alter_create_constraint
856 alter_table/;
857
46bf5655 858 # rename_table makes things a bit more complex
859 my $renamed_from = "";
860 $renamed_from = $diff_hash->{rename_table}[0][0]->name
861 if $diff_hash->{rename_table} && @{$diff_hash->{rename_table}};
862
4d438549 863 return unless @stmts;
864 # Just zero or one stmts. return now
24d9fe69 865 return (@drop_stmt,@stmts) unless @stmts > 1;
4d438549 866
867 # Now strip off the 'ALTER TABLE xyz' of all but the first one
868
7467c458 869 my $qt = $options->{quote_table_names} || '';
4104f82b 870 my $table_name = $qt . $table->name . $qt;
871
872
873 my $re = $renamed_from
874 ? qr/^ALTER TABLE (?:\Q$table_name\E|\Q$qt$renamed_from$qt\E) /
875 : qr/^ALTER TABLE \Q$table_name\E /;
4d438549 876
877 my $first = shift @stmts;
4104f82b 878 my ($alter_table) = $first =~ /($re)/;
46bf5655 879
4d438549 880 my $padd = " " x length($alter_table);
881
24d9fe69 882 return @drop_stmt, join( ",\n", $first, map { s/$re//; $padd . $_ } @stmts);
4104f82b 883
4d438549 884}
885
886sub drop_table {
46bf5655 887 my ($table, $options) = @_;
888
889 my $qt = $options->{quote_table_names} || '';
4d438549 890
891 # Drop (foreign key) constraints so table drops cleanly
46bf5655 892 my @sql = batch_alter_table($table, { alter_drop_constraint => [ grep { $_->type eq 'FOREIGN KEY' } $table->get_constraints ] }, $options);
893
24d9fe69 894 return (@sql, "DROP TABLE $qt$table$qt");
895# return join("\n", @sql, "DROP TABLE $qt$table$qt");
46bf5655 896
897}
898
899sub rename_table {
900 my ($old_table, $new_table, $options) = @_;
4d438549 901
46bf5655 902 my $qt = $options->{quote_table_names} || '';
4d438549 903
46bf5655 904 return "ALTER TABLE $qt$old_table$qt RENAME TO $qt$new_table$qt";
4d438549 905}
906
da5a1bae 907sub next_unused_name {
908 my $name = shift || '';
909 if ( !defined($used_names{$name}) ) {
910 $used_names{$name} = $name;
911 return $name;
912 }
913
914 my $i = 1;
915 while ( defined($used_names{$name . '_' . $i}) ) {
916 ++$i;
917 }
918 $name .= '_' . $i;
919 $used_names{$name} = $name;
920 return $name;
921}
922
9398955f 9231;
9398955f 924
c855a748 925# -------------------------------------------------------------------
9398955f 926
c855a748 927=pod
928
929=head1 SEE ALSO
930
931SQL::Translator, http://www.mysql.com/.
9398955f 932
2d6979da 933=head1 AUTHORS
9398955f 934
758ab1cd 935darren chamberlain E<lt>darren@cpan.orgE<gt>,
c855a748 936Ken Y. Clark E<lt>kclark@cpan.orgE<gt>.
937
938=cut