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