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