Remove all expansion $XX tags (isolated commit, easily revertable)
[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
48Valid version specifiers for C<mysql_parser_version> are listed L<here|SQL::Translator::Utils/parse_mysql_version>
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 ];
103$VERSION = '1.99';
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
0013ee25 372 my $table_name = $table->name;
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;
fe0f47d0 380 $create .= "--\n-- Table: $qt$table_name$qt\n--\n" unless $options->{no_comments};
24d9fe69 381 $drop = qq[DROP TABLE IF EXISTS $qt$table_name$qt] if $options->{add_drop_table};
fe0f47d0 382 $create .= "CREATE TABLE $qt$table_name$qt (\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
da5a1bae 431sub generate_table_options
432{
9a96648f 433 my ($table, $options) = @_;
da5a1bae 434 my $create;
435
436 my $table_type_defined = 0;
9a96648f 437 my $qf = $options->{quote_field_names} ||= '';
7725e1e6 438 my $charset = $table->extra('mysql_charset');
439 my $collate = $table->extra('mysql_collate');
9a96648f 440 my $union = undef;
da5a1bae 441 for my $t1_option_ref ( $table->options ) {
442 my($key, $value) = %{$t1_option_ref};
443 $table_type_defined = 1
444 if uc $key eq 'ENGINE' or uc $key eq 'TYPE';
7725e1e6 445 if (uc $key eq 'CHARACTER SET') {
446 $charset = $value;
447 next;
448 } elsif (uc $key eq 'COLLATE') {
449 $collate = $value;
450 next;
9a96648f 451 } elsif (uc $key eq 'UNION') {
452 $union = "($qf". join("$qf, $qf", @$value) ."$qf)";
453 next;
7725e1e6 454 }
da5a1bae 455 $create .= " $key=$value";
456 }
9ab59f87 457
da5a1bae 458 my $mysql_table_type = $table->extra('mysql_table_type');
9ab59f87 459 $create .= " ENGINE=$mysql_table_type"
da5a1bae 460 if $mysql_table_type && !$table_type_defined;
da5a1bae 461 my $comments = $table->comments;
462
463 $create .= " DEFAULT CHARACTER SET $charset" if $charset;
464 $create .= " COLLATE $collate" if $collate;
9a96648f 465 $create .= " UNION=$union" if $union;
da5a1bae 466 $create .= qq[ comment='$comments'] if $comments;
467 return $create;
468}
469
0013ee25 470sub create_field
471{
fe0f47d0 472 my ($field, $options) = @_;
473
474 my $qf = $options->{quote_field_names} ||= '';
9398955f 475
0013ee25 476 my $field_name = $field->name;
477 debug("PKG: Looking at field '$field_name'\n");
fe0f47d0 478 my $field_def = "$qf$field_name$qf";
0013ee25 479
480 # data type and size
481 my $data_type = $field->data_type;
482 my @size = $field->size;
483 my %extra = $field->extra;
484 my $list = $extra{'list'} || [];
485 # \todo deal with embedded quotes
486 my $commalist = join( ', ', map { qq['$_'] } @$list );
487 my $charset = $extra{'mysql_charset'};
488 my $collate = $extra{'mysql_collate'};
489
5d666b31 490 my $mysql_version = $options->{mysql_version} || 0;
0013ee25 491 #
492 # Oracle "number" type -- figure best MySQL type
493 #
494 if ( lc $data_type eq 'number' ) {
495 # not an integer
496 if ( scalar @size > 1 ) {
497 $data_type = 'double';
d529894e 498 }
0013ee25 499 elsif ( $size[0] && $size[0] >= 12 ) {
500 $data_type = 'bigint';
501 }
502 elsif ( $size[0] && $size[0] <= 1 ) {
503 $data_type = 'tinyint';
504 }
505 else {
506 $data_type = 'int';
507 }
508 }
509 #
510 # Convert a large Oracle varchar to "text"
5d666b31 511 # (not necessary as of 5.0.3 http://dev.mysql.com/doc/refman/5.0/en/char.html)
0013ee25 512 #
513 elsif ( $data_type =~ /char/i && $size[0] > 255 ) {
5d666b31 514 unless ($size[0] <= 65535 && $mysql_version >= 5.000003 ) {
515 $data_type = 'text';
516 @size = ();
517 }
0013ee25 518 }
0013ee25 519 elsif ( $data_type =~ /boolean/i ) {
ca1f9923 520 if ($mysql_version >= 4) {
521 $data_type = 'boolean';
522 } else {
523 $data_type = 'enum';
524 $commalist = "'0','1'";
525 }
0013ee25 526 }
527 elsif ( exists $translate{ lc $data_type } ) {
528 $data_type = $translate{ lc $data_type };
529 }
530
531 @size = () if $data_type =~ /(text|blob)/i;
532
533 if ( $data_type =~ /(double|float)/ && scalar @size == 1 ) {
534 push @size, '0';
535 }
d529894e 536
0013ee25 537 $field_def .= " $data_type";
538
7c1aae02 539 if ( lc($data_type) eq 'enum' || lc($data_type) eq 'set') {
0013ee25 540 $field_def .= '(' . $commalist . ')';
7c1aae02 541 }
0013ee25 542 elsif ( defined $size[0] && $size[0] > 0 ) {
543 $field_def .= '(' . join( ', ', @size ) . ')';
544 }
545
546 # char sets
547 $field_def .= " CHARACTER SET $charset" if $charset;
548 $field_def .= " COLLATE $collate" if $collate;
549
550 # MySQL qualifiers
551 for my $qual ( qw[ binary unsigned zerofill ] ) {
552 my $val = $extra{ $qual } || $extra{ uc $qual } or next;
553 $field_def .= " $qual";
554 }
555 for my $qual ( 'character set', 'collate', 'on update' ) {
556 my $val = $extra{ $qual } || $extra{ uc $qual } or next;
557 $field_def .= " $qual $val";
558 }
559
560 # Null?
561 $field_def .= ' NOT NULL' unless $field->is_nullable;
562
563 # Default? XXX Need better quoting!
564 my $default = $field->default_value;
565 if ( defined $default ) {
bc8e2aa1 566 SQL::Translator::Producer->_apply_default_value(
567 \$field_def,
568 $default,
569 [
570 'NULL' => \'NULL',
571 ],
572 );
0013ee25 573 }
574
575 if ( my $comments = $field->comments ) {
576 $field_def .= qq[ comment '$comments'];
577 }
578
579 # auto_increment?
580 $field_def .= " auto_increment" if $field->is_auto_increment;
581
582 return $field_def;
583}
584
da5a1bae 585sub alter_create_index
586{
587 my ($index, $options) = @_;
588
589 my $qt = $options->{quote_table_names} || '';
590 my $qf = $options->{quote_field_names} || '';
591
592 return join( ' ',
593 'ALTER TABLE',
594 $qt.$index->table->name.$qt,
595 'ADD',
596 create_index(@_)
597 );
598}
599
0013ee25 600sub create_index
601{
fe0f47d0 602 my ($index, $options) = @_;
603
604 my $qf = $options->{quote_field_names} || '';
0013ee25 605
606 return join( ' ',
da5a1bae 607 lc $index->type eq 'normal' ? 'INDEX' : $index->type . ' INDEX',
f5405d47 608 truncate_id_uniquely( $index->name, $options->{max_id_length} || $DEFAULT_MAX_ID_LENGTH ),
fe0f47d0 609 '(' . $qf . join( "$qf, $qf", $index->fields ) . $qf . ')'
0013ee25 610 );
611
612}
613
da5a1bae 614sub alter_drop_index
615{
616 my ($index, $options) = @_;
617
618 my $qt = $options->{quote_table_names} || '';
619 my $qf = $options->{quote_field_names} || '';
620
621 return join( ' ',
622 'ALTER TABLE',
623 $qt.$index->table->name.$qt,
624 'DROP',
625 'INDEX',
626 $index->name || $index->fields
627 );
628
629}
630
631sub alter_drop_constraint
632{
633 my ($c, $options) = @_;
634
635 my $qt = $options->{quote_table_names} || '';
7725e1e6 636 my $qc = $options->{quote_field_names} || '';
da5a1bae 637
638 my $out = sprintf('ALTER TABLE %s DROP %s %s',
7467c458 639 $qt . $c->table->name . $qt,
74ca32ce 640 $c->type eq FOREIGN_KEY ? $c->type : "INDEX",
da5a1bae 641 $qc . $c->name . $qc );
642
643 return $out;
644}
645
646sub alter_create_constraint
647{
648 my ($index, $options) = @_;
649
650 my $qt = $options->{quote_table_names} || '';
651 return join( ' ',
652 'ALTER TABLE',
653 $qt.$index->table->name.$qt,
654 'ADD',
655 create_constraint(@_) );
656}
657
0013ee25 658sub create_constraint
659{
660 my ($c, $options) = @_;
661
fb149f81 662 my $qf = $options->{quote_field_names} || '';
663 my $qt = $options->{quote_table_names} || '';
da5a1bae 664 my $leave_name = $options->{leave_name} || undef;
fe0f47d0 665
0013ee25 666 my @fields = $c->fields or next;
667
668 if ( $c->type eq PRIMARY_KEY ) {
fe0f47d0 669 return 'PRIMARY KEY (' . $qf . join("$qf, $qf", @fields). $qf . ')';
0013ee25 670 }
671 elsif ( $c->type eq UNIQUE ) {
672 return
fe0f47d0 673 'UNIQUE '.
f5405d47 674 (defined $c->name ? $qf.truncate_id_uniquely( $c->name, $options->{max_id_length} || $DEFAULT_MAX_ID_LENGTH ).$qf.' ' : '').
fe0f47d0 675 '(' . $qf . join("$qf, $qf", @fields). $qf . ')';
0013ee25 676 }
677 elsif ( $c->type eq FOREIGN_KEY ) {
d529894e 678 #
0013ee25 679 # Make sure FK field is indexed or MySQL complains.
5e56da9a 680 #
0013ee25 681
866d012e 682 my $table = $c->table;
f5405d47 683 my $c_name = truncate_id_uniquely( $c->name, $options->{max_id_length} || $DEFAULT_MAX_ID_LENGTH );
866d012e 684
0013ee25 685 my $def = join(' ',
fb149f81 686 map { $_ || () }
687 'CONSTRAINT',
7725e1e6 688 $qf . $c_name . $qf,
fb149f81 689 'FOREIGN KEY'
690 );
0013ee25 691
da5a1bae 692
fe0f47d0 693 $def .= ' ('.$qf . join( "$qf, $qf", @fields ) . $qf . ')';
0013ee25 694
fe0f47d0 695 $def .= ' REFERENCES ' . $qt . $c->reference_table . $qt;
0013ee25 696
697 my @rfields = map { $_ || () } $c->reference_fields;
698 unless ( @rfields ) {
699 my $rtable_name = $c->reference_table;
866d012e 700 if ( my $ref_table = $table->schema->get_table( $rtable_name ) ) {
0013ee25 701 push @rfields, $ref_table->primary_key;
1c14e9f1 702 }
0013ee25 703 else {
704 warn "Can't find reference table '$rtable_name' " .
705 "in schema\n" if $options->{show_warnings};
5e56da9a 706 }
707 }
708
0013ee25 709 if ( @rfields ) {
fe0f47d0 710 $def .= ' (' . $qf . join( "$qf, $qf", @rfields ) . $qf . ')';
0013ee25 711 }
712 else {
866d012e 713 warn "FK constraint on " . $table->name . '.' .
0013ee25 714 join('', @fields) . " has no reference fields\n"
715 if $options->{show_warnings};
716 }
5e56da9a 717
0013ee25 718 if ( $c->match_type ) {
719 $def .= ' MATCH ' .
720 ( $c->match_type =~ /full/i ) ? 'FULL' : 'PARTIAL';
721 }
722
723 if ( $c->on_delete ) {
724 $def .= ' ON DELETE '.join( ' ', $c->on_delete );
725 }
726
727 if ( $c->on_update ) {
728 $def .= ' ON UPDATE '.join( ' ', $c->on_update );
729 }
cd0ea0fd 730 return $def;
9398955f 731 }
732
cd0ea0fd 733 return undef;
0013ee25 734}
735
da5a1bae 736sub alter_table
737{
738 my ($to_table, $options) = @_;
739
7467c458 740 my $qt = $options->{quote_table_names} || '';
da5a1bae 741
9a96648f 742 my $table_options = generate_table_options($to_table, $options) || '';
da5a1bae 743 my $out = sprintf('ALTER TABLE %s%s',
744 $qt . $to_table->name . $qt,
745 $table_options);
746
747 return $out;
748}
749
4d438549 750sub rename_field { alter_field(@_) }
0013ee25 751sub alter_field
752{
fe0f47d0 753 my ($from_field, $to_field, $options) = @_;
754
7467c458 755 my $qf = $options->{quote_field_names} || '';
756 my $qt = $options->{quote_table_names} || '';
0013ee25 757
758 my $out = sprintf('ALTER TABLE %s CHANGE COLUMN %s %s',
fe0f47d0 759 $qt . $to_field->table->name . $qt,
4d438549 760 $qf . $from_field->name . $qf,
fe0f47d0 761 create_field($to_field, $options));
0013ee25 762
763 return $out;
764}
765
766sub add_field
767{
fe0f47d0 768 my ($new_field, $options) = @_;
769
7467c458 770 my $qt = $options->{quote_table_names} || '';
0013ee25 771
772 my $out = sprintf('ALTER TABLE %s ADD COLUMN %s',
fe0f47d0 773 $qt . $new_field->table->name . $qt,
774 create_field($new_field, $options));
0013ee25 775
776 return $out;
777
778}
779
780sub drop_field
781{
fe0f47d0 782 my ($old_field, $options) = @_;
0013ee25 783
7467c458 784 my $qf = $options->{quote_field_names} || '';
785 my $qt = $options->{quote_table_names} || '';
fe0f47d0 786
0013ee25 787 my $out = sprintf('ALTER TABLE %s DROP COLUMN %s',
fe0f47d0 788 $qt . $old_field->table->name . $qt,
789 $qf . $old_field->name . $qf);
0013ee25 790
791 return $out;
792
9398955f 793}
794
4d438549 795sub batch_alter_table {
796 my ($table, $diff_hash, $options) = @_;
797
f9ed5d54 798 # InnoDB has an issue with dropping and re-adding a FK constraint under the
799 # name in a single alter statment, see: http://bugs.mysql.com/bug.php?id=13741
800 #
801 # We have to work round this.
802
803 my %fks_to_alter;
804 my %fks_to_drop = map {
805 $_->type eq FOREIGN_KEY
806 ? ( $_->name => $_ )
807 : ( )
808 } @{$diff_hash->{alter_drop_constraint} };
809
810 my %fks_to_create = map {
811 if ( $_->type eq FOREIGN_KEY) {
812 $fks_to_alter{$_->name} = $fks_to_drop{$_->name} if $fks_to_drop{$_->name};
813 ( $_->name => $_ );
814 } else { ( ) }
815 } @{$diff_hash->{alter_create_constraint} };
816
24d9fe69 817 my @drop_stmt;
f9ed5d54 818 if (scalar keys %fks_to_alter) {
819 $diff_hash->{alter_drop_constraint} = [
820 grep { !$fks_to_alter{$_->name} } @{ $diff_hash->{alter_drop_constraint} }
821 ];
822
24d9fe69 823 @drop_stmt = batch_alter_table($table, { alter_drop_constraint => [ values %fks_to_alter ] }, $options);
f9ed5d54 824
825 }
826
4d438549 827 my @stmts = map {
828 if (@{ $diff_hash->{$_} || [] }) {
829 my $meth = __PACKAGE__->can($_) or die __PACKAGE__ . " cant $_";
f9ed5d54 830 map { $meth->( (ref $_ eq 'ARRAY' ? @$_ : $_), $options ) } @{ $diff_hash->{$_} }
4d438549 831 } else { () }
46bf5655 832 } qw/rename_table
833 alter_drop_constraint
4d438549 834 alter_drop_index
835 drop_field
836 add_field
837 alter_field
838 rename_field
839 alter_create_index
840 alter_create_constraint
841 alter_table/;
842
46bf5655 843 # rename_table makes things a bit more complex
844 my $renamed_from = "";
845 $renamed_from = $diff_hash->{rename_table}[0][0]->name
846 if $diff_hash->{rename_table} && @{$diff_hash->{rename_table}};
847
4d438549 848 return unless @stmts;
849 # Just zero or one stmts. return now
24d9fe69 850 return (@drop_stmt,@stmts) unless @stmts > 1;
4d438549 851
852 # Now strip off the 'ALTER TABLE xyz' of all but the first one
853
7467c458 854 my $qt = $options->{quote_table_names} || '';
4104f82b 855 my $table_name = $qt . $table->name . $qt;
856
857
858 my $re = $renamed_from
859 ? qr/^ALTER TABLE (?:\Q$table_name\E|\Q$qt$renamed_from$qt\E) /
860 : qr/^ALTER TABLE \Q$table_name\E /;
4d438549 861
862 my $first = shift @stmts;
4104f82b 863 my ($alter_table) = $first =~ /($re)/;
46bf5655 864
4d438549 865 my $padd = " " x length($alter_table);
866
24d9fe69 867 return @drop_stmt, join( ",\n", $first, map { s/$re//; $padd . $_ } @stmts);
4104f82b 868
4d438549 869}
870
871sub drop_table {
46bf5655 872 my ($table, $options) = @_;
873
874 my $qt = $options->{quote_table_names} || '';
4d438549 875
876 # Drop (foreign key) constraints so table drops cleanly
46bf5655 877 my @sql = batch_alter_table($table, { alter_drop_constraint => [ grep { $_->type eq 'FOREIGN KEY' } $table->get_constraints ] }, $options);
878
24d9fe69 879 return (@sql, "DROP TABLE $qt$table$qt");
880# return join("\n", @sql, "DROP TABLE $qt$table$qt");
46bf5655 881
882}
883
884sub rename_table {
885 my ($old_table, $new_table, $options) = @_;
4d438549 886
46bf5655 887 my $qt = $options->{quote_table_names} || '';
4d438549 888
46bf5655 889 return "ALTER TABLE $qt$old_table$qt RENAME TO $qt$new_table$qt";
4d438549 890}
891
da5a1bae 892sub next_unused_name {
893 my $name = shift || '';
894 if ( !defined($used_names{$name}) ) {
895 $used_names{$name} = $name;
896 return $name;
897 }
898
899 my $i = 1;
900 while ( defined($used_names{$name . '_' . $i}) ) {
901 ++$i;
902 }
903 $name .= '_' . $i;
904 $used_names{$name} = $name;
905 return $name;
906}
907
9398955f 9081;
9398955f 909
c855a748 910# -------------------------------------------------------------------
9398955f 911
c855a748 912=pod
913
914=head1 SEE ALSO
915
916SQL::Translator, http://www.mysql.com/.
9398955f 917
2d6979da 918=head1 AUTHORS
9398955f 919
758ab1cd 920darren chamberlain E<lt>darren@cpan.orgE<gt>,
c855a748 921Ken Y. Clark E<lt>kclark@cpan.orgE<gt>.
922
923=cut