typo fixes
[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#
132# Column types that do not support lenth attribute
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;
216 # Give the constraint a name if it doesn't have one, so it doens't feel
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;
609 $field_def .= " $qual $val";
610 }
611
612 # Null?
ad071409 613 if ( $field->is_nullable ) {
614 $field_def .= ' NULL';
615 }
616 else {
617 $field_def .= ' NOT NULL';
618 }
0013ee25 619
06baeb21 620 # Default?
621 SQL::Translator::Producer->_apply_default_value(
622 $field,
623 \$field_def,
624 [
625 'NULL' => \'NULL',
626 ],
627 );
0013ee25 628
629 if ( my $comments = $field->comments ) {
630 $field_def .= qq[ comment '$comments'];
631 }
632
633 # auto_increment?
634 $field_def .= " auto_increment" if $field->is_auto_increment;
635
636 return $field_def;
637}
638
da5a1bae 639sub alter_create_index
640{
641 my ($index, $options) = @_;
642
643 my $qt = $options->{quote_table_names} || '';
644 my $qf = $options->{quote_field_names} || '';
368e3eb7 645 my $table_name = quote_table_name($index->table->name, $qt);
da5a1bae 646 return join( ' ',
647 'ALTER TABLE',
368e3eb7 648 $table_name,
da5a1bae 649 'ADD',
650 create_index(@_)
651 );
652}
653
0013ee25 654sub create_index
655{
edc4b5da 656 my ( $index, $options ) = @_;
fe0f47d0 657
658 my $qf = $options->{quote_field_names} || '';
0013ee25 659
edc4b5da 660 return join(
661 ' ',
4ec6e692 662 map { $_ || () }
edc4b5da 663 lc $index->type eq 'normal' ? 'INDEX' : $index->type . ' INDEX',
664 $index->name
20476859 665 ? $qf . truncate_id_uniquely(
edc4b5da 666 $index->name,
667 $options->{max_id_length} || $DEFAULT_MAX_ID_LENGTH
20476859 668 ) . $qf
edc4b5da 669 : '',
670 '(' . $qf . join( "$qf, $qf", $index->fields ) . $qf . ')'
671 );
0013ee25 672}
673
da5a1bae 674sub alter_drop_index
675{
676 my ($index, $options) = @_;
677
678 my $qt = $options->{quote_table_names} || '';
679 my $qf = $options->{quote_field_names} || '';
368e3eb7 680 my $table_name = quote_table_name($index->table->name, $qt);
da5a1bae 681
ea93df61 682 return join( ' ',
da5a1bae 683 'ALTER TABLE',
368e3eb7 684 $table_name,
da5a1bae 685 'DROP',
686 'INDEX',
687 $index->name || $index->fields
688 );
689
690}
691
692sub alter_drop_constraint
693{
694 my ($c, $options) = @_;
695
696 my $qt = $options->{quote_table_names} || '';
7725e1e6 697 my $qc = $options->{quote_field_names} || '';
368e3eb7 698 my $table_name = quote_table_name($c->table->name, $qt);
da5a1bae 699
b62fa492 700 my @out = ('ALTER','TABLE',$table_name,'DROP');
701 if($c->type eq PRIMARY_KEY) {
702 push @out, $c->type;
703 }
704 else {
705 push @out, ($c->type eq FOREIGN_KEY ? $c->type : "INDEX"),
706 $qc . $c->name . $qc;
707 }
708 return join(' ',@out);
da5a1bae 709}
710
711sub alter_create_constraint
712{
713 my ($index, $options) = @_;
714
715 my $qt = $options->{quote_table_names} || '';
368e3eb7 716 my $table_name = quote_table_name($index->table->name, $qt);
da5a1bae 717 return join( ' ',
718 'ALTER TABLE',
368e3eb7 719 $table_name,
da5a1bae 720 'ADD',
721 create_constraint(@_) );
722}
723
0013ee25 724sub create_constraint
725{
726 my ($c, $options) = @_;
727
fb149f81 728 my $qf = $options->{quote_field_names} || '';
729 my $qt = $options->{quote_table_names} || '';
da5a1bae 730 my $leave_name = $options->{leave_name} || undef;
fe0f47d0 731
ee6e2ec3 732 my $reference_table_name = quote_table_name($c->reference_table, $qt);
733
0013ee25 734 my @fields = $c->fields or next;
735
736 if ( $c->type eq PRIMARY_KEY ) {
fe0f47d0 737 return 'PRIMARY KEY (' . $qf . join("$qf, $qf", @fields). $qf . ')';
0013ee25 738 }
739 elsif ( $c->type eq UNIQUE ) {
55da13f2 740 return sprintf 'UNIQUE %s(%s)',
741 ((defined $c->name && $c->name)
742 ? join ('',
743 $qf,
744 truncate_id_uniquely( $c->name, $options->{max_id_length} || $DEFAULT_MAX_ID_LENGTH ),
745 $qf,
746 ' '
747 )
748 : ''
749 ),
750 ( join ', ', map { "${qf}${_}${qf}" } @fields ),
751 ;
0013ee25 752 }
753 elsif ( $c->type eq FOREIGN_KEY ) {
d529894e 754 #
0013ee25 755 # Make sure FK field is indexed or MySQL complains.
5e56da9a 756 #
0013ee25 757
866d012e 758 my $table = $c->table;
f5405d47 759 my $c_name = truncate_id_uniquely( $c->name, $options->{max_id_length} || $DEFAULT_MAX_ID_LENGTH );
866d012e 760
ea93df61 761 my $def = join(' ',
762 map { $_ || () }
763 'CONSTRAINT',
764 $qf . $c_name . $qf,
fb149f81 765 'FOREIGN KEY'
766 );
0013ee25 767
da5a1bae 768
fe0f47d0 769 $def .= ' ('.$qf . join( "$qf, $qf", @fields ) . $qf . ')';
0013ee25 770
ee6e2ec3 771 $def .= ' REFERENCES ' . $reference_table_name;
0013ee25 772
773 my @rfields = map { $_ || () } $c->reference_fields;
774 unless ( @rfields ) {
775 my $rtable_name = $c->reference_table;
866d012e 776 if ( my $ref_table = $table->schema->get_table( $rtable_name ) ) {
0013ee25 777 push @rfields, $ref_table->primary_key;
1c14e9f1 778 }
0013ee25 779 else {
780 warn "Can't find reference table '$rtable_name' " .
781 "in schema\n" if $options->{show_warnings};
5e56da9a 782 }
783 }
784
0013ee25 785 if ( @rfields ) {
fe0f47d0 786 $def .= ' (' . $qf . join( "$qf, $qf", @rfields ) . $qf . ')';
0013ee25 787 }
788 else {
866d012e 789 warn "FK constraint on " . $table->name . '.' .
ea93df61 790 join('', @fields) . " has no reference fields\n"
0013ee25 791 if $options->{show_warnings};
792 }
5e56da9a 793
0013ee25 794 if ( $c->match_type ) {
ea93df61 795 $def .= ' MATCH ' .
0013ee25 796 ( $c->match_type =~ /full/i ) ? 'FULL' : 'PARTIAL';
797 }
798
799 if ( $c->on_delete ) {
5863ad87 800 $def .= ' ON DELETE '. $c->on_delete;
0013ee25 801 }
802
803 if ( $c->on_update ) {
5863ad87 804 $def .= ' ON UPDATE '. $c->on_update;
0013ee25 805 }
cd0ea0fd 806 return $def;
9398955f 807 }
808
cd0ea0fd 809 return undef;
0013ee25 810}
811
da5a1bae 812sub alter_table
813{
814 my ($to_table, $options) = @_;
815
7467c458 816 my $qt = $options->{quote_table_names} || '';
da5a1bae 817
9a96648f 818 my $table_options = generate_table_options($to_table, $options) || '';
368e3eb7 819 my $table_name = quote_table_name($to_table->name, $qt);
da5a1bae 820 my $out = sprintf('ALTER TABLE %s%s',
368e3eb7 821 $table_name,
da5a1bae 822 $table_options);
823
824 return $out;
825}
826
4d438549 827sub rename_field { alter_field(@_) }
0013ee25 828sub alter_field
829{
fe0f47d0 830 my ($from_field, $to_field, $options) = @_;
831
7467c458 832 my $qf = $options->{quote_field_names} || '';
833 my $qt = $options->{quote_table_names} || '';
368e3eb7 834 my $table_name = quote_table_name($to_field->table->name, $qt);
0013ee25 835
836 my $out = sprintf('ALTER TABLE %s CHANGE COLUMN %s %s',
368e3eb7 837 $table_name,
4d438549 838 $qf . $from_field->name . $qf,
fe0f47d0 839 create_field($to_field, $options));
0013ee25 840
841 return $out;
842}
843
844sub add_field
845{
fe0f47d0 846 my ($new_field, $options) = @_;
847
7467c458 848 my $qt = $options->{quote_table_names} || '';
368e3eb7 849 my $table_name = quote_table_name($new_field->table->name, $qt);
0013ee25 850
851 my $out = sprintf('ALTER TABLE %s ADD COLUMN %s',
368e3eb7 852 $table_name,
fe0f47d0 853 create_field($new_field, $options));
0013ee25 854
855 return $out;
856
857}
858
859sub drop_field
ea93df61 860{
fe0f47d0 861 my ($old_field, $options) = @_;
0013ee25 862
7467c458 863 my $qf = $options->{quote_field_names} || '';
864 my $qt = $options->{quote_table_names} || '';
368e3eb7 865 my $table_name = quote_table_name($old_field->table->name, $qt);
ea93df61 866
0013ee25 867 my $out = sprintf('ALTER TABLE %s DROP COLUMN %s',
368e3eb7 868 $table_name,
fe0f47d0 869 $qf . $old_field->name . $qf);
0013ee25 870
871 return $out;
ea93df61 872
9398955f 873}
874
4d438549 875sub batch_alter_table {
876 my ($table, $diff_hash, $options) = @_;
877
ea93df61 878 # InnoDB has an issue with dropping and re-adding a FK constraint under the
f9ed5d54 879 # name in a single alter statment, see: http://bugs.mysql.com/bug.php?id=13741
880 #
881 # We have to work round this.
882
883 my %fks_to_alter;
884 my %fks_to_drop = map {
ea93df61 885 $_->type eq FOREIGN_KEY
886 ? ( $_->name => $_ )
f9ed5d54 887 : ( )
888 } @{$diff_hash->{alter_drop_constraint} };
889
890 my %fks_to_create = map {
891 if ( $_->type eq FOREIGN_KEY) {
892 $fks_to_alter{$_->name} = $fks_to_drop{$_->name} if $fks_to_drop{$_->name};
893 ( $_->name => $_ );
894 } else { ( ) }
895 } @{$diff_hash->{alter_create_constraint} };
896
24d9fe69 897 my @drop_stmt;
f9ed5d54 898 if (scalar keys %fks_to_alter) {
899 $diff_hash->{alter_drop_constraint} = [
900 grep { !$fks_to_alter{$_->name} } @{ $diff_hash->{alter_drop_constraint} }
901 ];
902
24d9fe69 903 @drop_stmt = batch_alter_table($table, { alter_drop_constraint => [ values %fks_to_alter ] }, $options);
f9ed5d54 904
905 }
906
4d438549 907 my @stmts = map {
908 if (@{ $diff_hash->{$_} || [] }) {
909 my $meth = __PACKAGE__->can($_) or die __PACKAGE__ . " cant $_";
f9ed5d54 910 map { $meth->( (ref $_ eq 'ARRAY' ? @$_ : $_), $options ) } @{ $diff_hash->{$_} }
4d438549 911 } else { () }
46bf5655 912 } qw/rename_table
913 alter_drop_constraint
4d438549 914 alter_drop_index
915 drop_field
916 add_field
917 alter_field
918 rename_field
919 alter_create_index
920 alter_create_constraint
921 alter_table/;
922
368e3eb7 923 #quote
924 my $qt = $options->{quote_table_names} || '';
925
46bf5655 926 # rename_table makes things a bit more complex
927 my $renamed_from = "";
368e3eb7 928 $renamed_from = quote_table_name($diff_hash->{rename_table}[0][0]->name, $qt)
46bf5655 929 if $diff_hash->{rename_table} && @{$diff_hash->{rename_table}};
930
4d438549 931 return unless @stmts;
932 # Just zero or one stmts. return now
24d9fe69 933 return (@drop_stmt,@stmts) unless @stmts > 1;
4d438549 934
935 # Now strip off the 'ALTER TABLE xyz' of all but the first one
936
368e3eb7 937 my $table_name = quote_table_name($table->name, $qt);
4104f82b 938
ea93df61 939 my $re = $renamed_from
368e3eb7 940 ? qr/^ALTER TABLE (?:\Q$table_name\E|\Q$renamed_from\E) /
4104f82b 941 : qr/^ALTER TABLE \Q$table_name\E /;
4d438549 942
943 my $first = shift @stmts;
4104f82b 944 my ($alter_table) = $first =~ /($re)/;
46bf5655 945
4d438549 946 my $padd = " " x length($alter_table);
947
24d9fe69 948 return @drop_stmt, join( ",\n", $first, map { s/$re//; $padd . $_ } @stmts);
4104f82b 949
4d438549 950}
951
952sub drop_table {
46bf5655 953 my ($table, $options) = @_;
954
955 my $qt = $options->{quote_table_names} || '';
4d438549 956
957 # Drop (foreign key) constraints so table drops cleanly
46bf5655 958 my @sql = batch_alter_table($table, { alter_drop_constraint => [ grep { $_->type eq 'FOREIGN KEY' } $table->get_constraints ] }, $options);
959
368e3eb7 960 my $table_name = quote_table_name($table, $qt);
961 return (@sql, "DROP TABLE $table");
46bf5655 962
963}
964
965sub rename_table {
966 my ($old_table, $new_table, $options) = @_;
4d438549 967
46bf5655 968 my $qt = $options->{quote_table_names} || '';
368e3eb7 969 my $old_table_name = quote_table_name($old_table, $qt);
970 my $new_table_name = quote_table_name($new_table, $qt);
4d438549 971
368e3eb7 972 return "ALTER TABLE $old_table_name RENAME TO $new_table_name";
4d438549 973}
974
da5a1bae 975sub next_unused_name {
976 my $name = shift || '';
977 if ( !defined($used_names{$name}) ) {
978 $used_names{$name} = $name;
979 return $name;
980 }
981
982 my $i = 1;
983 while ( defined($used_names{$name . '_' . $i}) ) {
984 ++$i;
985 }
986 $name .= '_' . $i;
987 $used_names{$name} = $name;
988 return $name;
989}
990
9398955f 9911;
9398955f 992
c855a748 993=pod
994
995=head1 SEE ALSO
996
997SQL::Translator, http://www.mysql.com/.
9398955f 998
2d6979da 999=head1 AUTHORS
9398955f 1000
758ab1cd 1001darren chamberlain E<lt>darren@cpan.orgE<gt>,
f997b9ab 1002Ken Youens-Clark E<lt>kclark@cpan.orgE<gt>.
c855a748 1003
1004=cut