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