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