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