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