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