take out duplicate docs
[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.
ea93df61 37There are still some issues to be worked out with syntax differences
c855a748 38between MySQL versions 3 and 4 ("SET foreign_key_checks," character sets
39for fields, etc.).
40
ea93df61 41=head1 ARGUMENTS
5d666b31 42
ea93df61 43This producer takes a single optional producer_arg C<mysql_version>, which
5d666b31 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
ea93df61 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;
ea93df61 113use SQL::Translator::Utils qw(debug header_comment
edc4b5da 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 #
ea93df61 173 # The ENGINE table option specifies the storage engine for the table.
9ab59f87 174 # TYPE is a synonym, but ENGINE is the preferred option name.
175 #
176
ea93df61 177 # We have to use the hash directly here since otherwise there is no way
9ab59f87 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] };
ea93df61 186
7725e1e6 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;
ea93df61 204
7725e1e6 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 }
ea93df61 210
9ab59f87 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 ) {
ea93df61 226
7725e1e6 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;
ea93df61 239
1c680eb9 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 = ();
ea93df61 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 =();
ea93df61 295
1c14e9f1 296 for my $table ( $schema->get_tables ) {
cd0ea0fd 297# print $table->name, "\n";
ea93df61 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);
ea93df61 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
ea93df61 477 $create .= join(",\n", map { " $_" }
0013ee25 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
ea93df61 499sub generate_table_options
da5a1bae 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 }
ea93df61 610 elsif (
611 defined $size[0] && $size[0] > 0
612 &&
613 ! grep lc($data_type) eq $_, @no_length_attr
c857e37a 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} || '';
368e3eb7 660 my $table_name = quote_table_name($index->table->name, $qt);
da5a1bae 661 return join( ' ',
662 'ALTER TABLE',
368e3eb7 663 $table_name,
da5a1bae 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} || '';
368e3eb7 695 my $table_name = quote_table_name($index->table->name, $qt);
da5a1bae 696
ea93df61 697 return join( ' ',
da5a1bae 698 'ALTER TABLE',
368e3eb7 699 $table_name,
da5a1bae 700 'DROP',
701 'INDEX',
702 $index->name || $index->fields
703 );
704
705}
706
707sub alter_drop_constraint
708{
709 my ($c, $options) = @_;
710
711 my $qt = $options->{quote_table_names} || '';
7725e1e6 712 my $qc = $options->{quote_field_names} || '';
368e3eb7 713 my $table_name = quote_table_name($c->table->name, $qt);
da5a1bae 714
715 my $out = sprintf('ALTER TABLE %s DROP %s %s',
368e3eb7 716 $table_name,
74ca32ce 717 $c->type eq FOREIGN_KEY ? $c->type : "INDEX",
da5a1bae 718 $qc . $c->name . $qc );
719
720 return $out;
721}
722
723sub alter_create_constraint
724{
725 my ($index, $options) = @_;
726
727 my $qt = $options->{quote_table_names} || '';
368e3eb7 728 my $table_name = quote_table_name($index->table->name, $qt);
da5a1bae 729 return join( ' ',
730 'ALTER TABLE',
368e3eb7 731 $table_name,
da5a1bae 732 'ADD',
733 create_constraint(@_) );
734}
735
0013ee25 736sub create_constraint
737{
738 my ($c, $options) = @_;
739
fb149f81 740 my $qf = $options->{quote_field_names} || '';
741 my $qt = $options->{quote_table_names} || '';
da5a1bae 742 my $leave_name = $options->{leave_name} || undef;
fe0f47d0 743
ee6e2ec3 744 my $reference_table_name = quote_table_name($c->reference_table, $qt);
745
0013ee25 746 my @fields = $c->fields or next;
747
748 if ( $c->type eq PRIMARY_KEY ) {
fe0f47d0 749 return 'PRIMARY KEY (' . $qf . join("$qf, $qf", @fields). $qf . ')';
0013ee25 750 }
751 elsif ( $c->type eq UNIQUE ) {
752 return
ea93df61 753 'UNIQUE '.
f5405d47 754 (defined $c->name ? $qf.truncate_id_uniquely( $c->name, $options->{max_id_length} || $DEFAULT_MAX_ID_LENGTH ).$qf.' ' : '').
fe0f47d0 755 '(' . $qf . join("$qf, $qf", @fields). $qf . ')';
0013ee25 756 }
757 elsif ( $c->type eq FOREIGN_KEY ) {
d529894e 758 #
0013ee25 759 # Make sure FK field is indexed or MySQL complains.
5e56da9a 760 #
0013ee25 761
866d012e 762 my $table = $c->table;
f5405d47 763 my $c_name = truncate_id_uniquely( $c->name, $options->{max_id_length} || $DEFAULT_MAX_ID_LENGTH );
866d012e 764
ea93df61 765 my $def = join(' ',
766 map { $_ || () }
767 'CONSTRAINT',
768 $qf . $c_name . $qf,
fb149f81 769 'FOREIGN KEY'
770 );
0013ee25 771
da5a1bae 772
fe0f47d0 773 $def .= ' ('.$qf . join( "$qf, $qf", @fields ) . $qf . ')';
0013ee25 774
ee6e2ec3 775 $def .= ' REFERENCES ' . $reference_table_name;
0013ee25 776
777 my @rfields = map { $_ || () } $c->reference_fields;
778 unless ( @rfields ) {
779 my $rtable_name = $c->reference_table;
866d012e 780 if ( my $ref_table = $table->schema->get_table( $rtable_name ) ) {
0013ee25 781 push @rfields, $ref_table->primary_key;
1c14e9f1 782 }
0013ee25 783 else {
784 warn "Can't find reference table '$rtable_name' " .
785 "in schema\n" if $options->{show_warnings};
5e56da9a 786 }
787 }
788
0013ee25 789 if ( @rfields ) {
fe0f47d0 790 $def .= ' (' . $qf . join( "$qf, $qf", @rfields ) . $qf . ')';
0013ee25 791 }
792 else {
866d012e 793 warn "FK constraint on " . $table->name . '.' .
ea93df61 794 join('', @fields) . " has no reference fields\n"
0013ee25 795 if $options->{show_warnings};
796 }
5e56da9a 797
0013ee25 798 if ( $c->match_type ) {
ea93df61 799 $def .= ' MATCH ' .
0013ee25 800 ( $c->match_type =~ /full/i ) ? 'FULL' : 'PARTIAL';
801 }
802
803 if ( $c->on_delete ) {
5863ad87 804 $def .= ' ON DELETE '. $c->on_delete;
0013ee25 805 }
806
807 if ( $c->on_update ) {
5863ad87 808 $def .= ' ON UPDATE '. $c->on_update;
0013ee25 809 }
cd0ea0fd 810 return $def;
9398955f 811 }
812
cd0ea0fd 813 return undef;
0013ee25 814}
815
da5a1bae 816sub alter_table
817{
818 my ($to_table, $options) = @_;
819
7467c458 820 my $qt = $options->{quote_table_names} || '';
da5a1bae 821
9a96648f 822 my $table_options = generate_table_options($to_table, $options) || '';
368e3eb7 823 my $table_name = quote_table_name($to_table->name, $qt);
da5a1bae 824 my $out = sprintf('ALTER TABLE %s%s',
368e3eb7 825 $table_name,
da5a1bae 826 $table_options);
827
828 return $out;
829}
830
4d438549 831sub rename_field { alter_field(@_) }
0013ee25 832sub alter_field
833{
fe0f47d0 834 my ($from_field, $to_field, $options) = @_;
835
7467c458 836 my $qf = $options->{quote_field_names} || '';
837 my $qt = $options->{quote_table_names} || '';
368e3eb7 838 my $table_name = quote_table_name($to_field->table->name, $qt);
0013ee25 839
840 my $out = sprintf('ALTER TABLE %s CHANGE COLUMN %s %s',
368e3eb7 841 $table_name,
4d438549 842 $qf . $from_field->name . $qf,
fe0f47d0 843 create_field($to_field, $options));
0013ee25 844
845 return $out;
846}
847
848sub add_field
849{
fe0f47d0 850 my ($new_field, $options) = @_;
851
7467c458 852 my $qt = $options->{quote_table_names} || '';
368e3eb7 853 my $table_name = quote_table_name($new_field->table->name, $qt);
0013ee25 854
855 my $out = sprintf('ALTER TABLE %s ADD COLUMN %s',
368e3eb7 856 $table_name,
fe0f47d0 857 create_field($new_field, $options));
0013ee25 858
859 return $out;
860
861}
862
863sub drop_field
ea93df61 864{
fe0f47d0 865 my ($old_field, $options) = @_;
0013ee25 866
7467c458 867 my $qf = $options->{quote_field_names} || '';
868 my $qt = $options->{quote_table_names} || '';
368e3eb7 869 my $table_name = quote_table_name($old_field->table->name, $qt);
ea93df61 870
0013ee25 871 my $out = sprintf('ALTER TABLE %s DROP COLUMN %s',
368e3eb7 872 $table_name,
fe0f47d0 873 $qf . $old_field->name . $qf);
0013ee25 874
875 return $out;
ea93df61 876
9398955f 877}
878
4d438549 879sub batch_alter_table {
880 my ($table, $diff_hash, $options) = @_;
881
ea93df61 882 # InnoDB has an issue with dropping and re-adding a FK constraint under the
f9ed5d54 883 # name in a single alter statment, see: http://bugs.mysql.com/bug.php?id=13741
884 #
885 # We have to work round this.
886
887 my %fks_to_alter;
888 my %fks_to_drop = map {
ea93df61 889 $_->type eq FOREIGN_KEY
890 ? ( $_->name => $_ )
f9ed5d54 891 : ( )
892 } @{$diff_hash->{alter_drop_constraint} };
893
894 my %fks_to_create = map {
895 if ( $_->type eq FOREIGN_KEY) {
896 $fks_to_alter{$_->name} = $fks_to_drop{$_->name} if $fks_to_drop{$_->name};
897 ( $_->name => $_ );
898 } else { ( ) }
899 } @{$diff_hash->{alter_create_constraint} };
900
24d9fe69 901 my @drop_stmt;
f9ed5d54 902 if (scalar keys %fks_to_alter) {
903 $diff_hash->{alter_drop_constraint} = [
904 grep { !$fks_to_alter{$_->name} } @{ $diff_hash->{alter_drop_constraint} }
905 ];
906
24d9fe69 907 @drop_stmt = batch_alter_table($table, { alter_drop_constraint => [ values %fks_to_alter ] }, $options);
f9ed5d54 908
909 }
910
4d438549 911 my @stmts = map {
912 if (@{ $diff_hash->{$_} || [] }) {
913 my $meth = __PACKAGE__->can($_) or die __PACKAGE__ . " cant $_";
f9ed5d54 914 map { $meth->( (ref $_ eq 'ARRAY' ? @$_ : $_), $options ) } @{ $diff_hash->{$_} }
4d438549 915 } else { () }
46bf5655 916 } qw/rename_table
917 alter_drop_constraint
4d438549 918 alter_drop_index
919 drop_field
920 add_field
921 alter_field
922 rename_field
923 alter_create_index
924 alter_create_constraint
925 alter_table/;
926
368e3eb7 927 #quote
928 my $qt = $options->{quote_table_names} || '';
929
46bf5655 930 # rename_table makes things a bit more complex
931 my $renamed_from = "";
368e3eb7 932 $renamed_from = quote_table_name($diff_hash->{rename_table}[0][0]->name, $qt)
46bf5655 933 if $diff_hash->{rename_table} && @{$diff_hash->{rename_table}};
934
4d438549 935 return unless @stmts;
936 # Just zero or one stmts. return now
24d9fe69 937 return (@drop_stmt,@stmts) unless @stmts > 1;
4d438549 938
939 # Now strip off the 'ALTER TABLE xyz' of all but the first one
940
368e3eb7 941 my $table_name = quote_table_name($table->name, $qt);
4104f82b 942
ea93df61 943 my $re = $renamed_from
368e3eb7 944 ? qr/^ALTER TABLE (?:\Q$table_name\E|\Q$renamed_from\E) /
4104f82b 945 : qr/^ALTER TABLE \Q$table_name\E /;
4d438549 946
947 my $first = shift @stmts;
4104f82b 948 my ($alter_table) = $first =~ /($re)/;
46bf5655 949
4d438549 950 my $padd = " " x length($alter_table);
951
24d9fe69 952 return @drop_stmt, join( ",\n", $first, map { s/$re//; $padd . $_ } @stmts);
4104f82b 953
4d438549 954}
955
956sub drop_table {
46bf5655 957 my ($table, $options) = @_;
958
959 my $qt = $options->{quote_table_names} || '';
4d438549 960
961 # Drop (foreign key) constraints so table drops cleanly
46bf5655 962 my @sql = batch_alter_table($table, { alter_drop_constraint => [ grep { $_->type eq 'FOREIGN KEY' } $table->get_constraints ] }, $options);
963
368e3eb7 964 my $table_name = quote_table_name($table, $qt);
965 return (@sql, "DROP TABLE $table");
46bf5655 966
967}
968
969sub rename_table {
970 my ($old_table, $new_table, $options) = @_;
4d438549 971
46bf5655 972 my $qt = $options->{quote_table_names} || '';
368e3eb7 973 my $old_table_name = quote_table_name($old_table, $qt);
974 my $new_table_name = quote_table_name($new_table, $qt);
4d438549 975
368e3eb7 976 return "ALTER TABLE $old_table_name RENAME TO $new_table_name";
4d438549 977}
978
da5a1bae 979sub next_unused_name {
980 my $name = shift || '';
981 if ( !defined($used_names{$name}) ) {
982 $used_names{$name} = $name;
983 return $name;
984 }
985
986 my $i = 1;
987 while ( defined($used_names{$name . '_' . $i}) ) {
988 ++$i;
989 }
990 $name .= '_' . $i;
991 $used_names{$name} = $name;
992 return $name;
993}
994
9398955f 9951;
9398955f 996
c855a748 997=pod
998
999=head1 SEE ALSO
1000
1001SQL::Translator, http://www.mysql.com/.
9398955f 1002
2d6979da 1003=head1 AUTHORS
9398955f 1004
758ab1cd 1005darren chamberlain E<lt>darren@cpan.orgE<gt>,
f997b9ab 1006Ken Youens-Clark E<lt>kclark@cpan.orgE<gt>.
c855a748 1007
1008=cut