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