1 package SQL::Translator::Producer::MySQL;
5 SQL::Translator::Producer::MySQL - MySQL-specific producer for SQL::Translator
9 Use via SQL::Translator:
13 my $t = SQL::Translator->new( parser => '...', producer => 'MySQL', '...' );
18 This module will produce text output of the schema suitable for MySQL.
19 There are still some issues to be worked out with syntax differences
20 between MySQL versions 3 and 4 ("SET foreign_key_checks," character sets
25 This producer takes a single optional producer_arg C<mysql_version>, which
26 provides the desired version for the target database. By default MySQL v3 is
27 assumed, and statements pertaining to any features introduced in later versions
28 (e.g. CREATE VIEW) are not produced.
30 Valid version specifiers for C<mysql_version> are listed L<here|SQL::Translator::Utils/parse_mysql_version>
34 Normally the tables will be created without any explicit table type given and
35 so will use the MySQL default.
37 Any tables involved in foreign key constraints automatically get a table type
38 of InnoDB, unless this is overridden by setting the C<mysql_table_type> extra
39 attribute explicitly on the table.
41 =head2 Extra attributes.
43 The producer recognises the following extra attributes on the Schema objects.
49 Set the list of allowed values for Enum fields.
51 =item B<field.binary>, B<field.unsigned>, B<field.zerofill>
53 Set the MySQL field options of the same name.
55 =item B<field.renamed_from>, B<table.renamed_from>
57 Use when producing diffs to indicate that the current table/field has been
58 renamed from the old name as given in the attribute value.
60 =item B<table.mysql_table_type>
62 Set the type of the table e.g. 'InnoDB', 'MyISAM'. This will be
63 automatically set for tables involved in foreign key constraints if it is
64 not already set explicitly. See L<"Table Types">.
66 Please note that the C<ENGINE> option is the preferred method of specifying
67 the MySQL storage engine to use, but this method still works for backwards
70 =item B<table.mysql_charset>, B<table.mysql_collate>
72 Set the tables default charater set and collation order.
74 =item B<field.mysql_charset>, B<field.mysql_collate>
76 Set the fields charater set and collation order.
84 our ( $DEBUG, %used_names );
85 our $VERSION = '1.59';
86 $DEBUG = 0 unless defined $DEBUG;
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
91 my $DEFAULT_MAX_ID_LENGTH = 64;
94 use SQL::Translator::Schema::Constants;
95 use SQL::Translator::Utils qw(debug header_comment
96 truncate_id_uniquely parse_mysql_version);
99 # Use only lowercase for the keys (e.g. "long" and not "LONG")
105 varchar2 => 'varchar',
121 'long integer' => 'integer',
123 'datetime' => 'datetime',
132 # Column types that do not support lenth attribute
134 my @no_length_attr = qw/
135 date time timestamp datetime year
139 sub preprocess_schema {
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
144 # Similarly for mysql_charset and mysql_collate
145 my $extra_to_options = sub {
146 my ($table, $extra_name, $opt_name) = @_;
148 my $extra = $table->extra;
150 my $extra_type = delete $extra->{$extra_name};
152 # Now just to find if there is already an Engine or Type option...
153 # and lets normalize it to ENGINE since:
155 # The ENGINE table option specifies the storage engine for the table.
156 # TYPE is a synonym, but ENGINE is the preferred option name.
159 # We have to use the hash directly here since otherwise there is no way
161 my $options = ( $table->{options} ||= []);
163 # If multiple option names, normalize to the first one
165 OPT_NAME: for ( @$opt_name[1..$#$opt_name] ) {
166 for my $idx ( 0..$#{$options} ) {
167 my ($key, $value) = %{ $options->[$idx] };
170 $options->[$idx] = { $opt_name->[0] => $value };
175 $opt_name = $opt_name->[0];
180 # This assumes that there isn't both a Type and an Engine option.
182 for my $idx ( 0..$#{$options} ) {
183 my ($key, $value) = %{ $options->[$idx] };
185 next unless uc $key eq $opt_name;
187 # make sure case is right on option name
188 delete $options->[$idx]{$key};
189 return $options->[$idx]{$opt_name} = $value || $extra_type;
194 push @$options, { $opt_name => $extra_type };
200 # Names are only specific to a given schema
201 local %used_names = ();
204 # Work out which tables need to be InnoDB to support foreign key
205 # constraints. We do this first as we need InnoDB at both ends.
207 foreach my $table ( $schema->get_tables ) {
209 $extra_to_options->($table, 'mysql_table_type', ['ENGINE', 'TYPE'] );
210 $extra_to_options->($table, 'mysql_charset', 'CHARACTER SET' );
211 $extra_to_options->($table, 'mysql_collate', 'COLLATE' );
213 foreach my $c ( $table->get_constraints ) {
214 next unless $c->type eq FOREIGN_KEY;
216 # Normalize constraint names here.
217 my $c_name = $c->name;
218 # Give the constraint a name if it doesn't have one, so it doens't feel
220 $c_name = $table->name . '_fk' unless length $c_name;
222 $c->name( next_unused_name($c_name) );
224 for my $meth (qw/table reference_table/) {
225 my $table = $schema->get_table($c->$meth) || next;
226 # This normalizes the types to ENGINE and returns the value if its there
227 next if $extra_to_options->($table, 'mysql_table_type', ['ENGINE', 'TYPE']);
228 $table->options( { 'ENGINE' => 'InnoDB' } );
230 } # foreach constraints
232 my %map = ( mysql_collate => 'collate', mysql_charset => 'character set');
233 foreach my $f ( $table->get_fields ) {
234 my $extra = $f->extra;
236 $extra->{$map{$_}} = delete $extra->{$_} if exists $extra->{$_};
240 if ( !$size[0] && $f->data_type =~ /char$/ ) {
249 my $translator = shift;
250 local $DEBUG = $translator->debug;
252 my $no_comments = $translator->no_comments;
253 my $add_drop_table = $translator->add_drop_table;
254 my $schema = $translator->schema;
255 my $show_warnings = $translator->show_warnings || 0;
256 my $producer_args = $translator->producer_args;
257 my $mysql_version = parse_mysql_version ($producer_args->{mysql_version}, 'perl') || 0;
258 my $max_id_length = $producer_args->{mysql_max_id_length} || $DEFAULT_MAX_ID_LENGTH;
260 my ($qt, $qf, $qc) = ('','', '');
261 $qt = '`' if $translator->quote_table_names;
262 $qf = '`' if $translator->quote_field_names;
264 debug("PKG: Beginning production\n");
267 $create .= header_comment unless ($no_comments);
268 # \todo Don't set if MySQL 3.x is set on command line
269 my @create = "SET foreign_key_checks=0";
271 preprocess_schema($schema);
278 for my $table ( $schema->get_tables ) {
279 # print $table->name, "\n";
280 push @table_defs, create_table($table,
281 { add_drop_table => $add_drop_table,
282 show_warnings => $show_warnings,
283 no_comments => $no_comments,
284 quote_table_names => $qt,
285 quote_field_names => $qf,
286 max_id_length => $max_id_length,
287 mysql_version => $mysql_version
291 if ($mysql_version >= 5.000001) {
292 for my $view ( $schema->get_views ) {
293 push @table_defs, create_view($view,
294 { add_replace_view => $add_drop_table,
295 show_warnings => $show_warnings,
296 no_comments => $no_comments,
297 quote_table_names => $qt,
298 quote_field_names => $qf,
299 max_id_length => $max_id_length,
300 mysql_version => $mysql_version
305 if ($mysql_version >= 5.000002) {
306 for my $trigger ( $schema->get_triggers ) {
307 push @table_defs, create_trigger($trigger,
308 { add_drop_trigger => $add_drop_table,
309 show_warnings => $show_warnings,
310 no_comments => $no_comments,
311 quote_table_names => $qt,
312 quote_field_names => $qf,
313 max_id_length => $max_id_length,
314 mysql_version => $mysql_version
320 # print "@table_defs\n";
321 push @table_defs, "SET foreign_key_checks=1";
323 return wantarray ? ($create ? $create : (), @create, @table_defs) : ($create . join('', map { $_ ? "$_;\n\n" : () } (@create, @table_defs)));
327 my ($trigger, $options) = @_;
328 my $qt = $options->{quote_table_names} || '';
329 my $qf = $options->{quote_field_names} || '';
331 my $trigger_name = $trigger->name;
332 debug("PKG: Looking at trigger '${trigger_name}'\n");
336 my $events = $trigger->database_events;
337 for my $event ( @$events ) {
338 my $name = $trigger_name;
342 warn "Multiple database events supplied for trigger '${trigger_name}', ",
343 "creating trigger '${name}' for the '${event}' event\n"
344 if $options->{show_warnings};
347 my $action = $trigger->action;
348 $action .= ";" unless $action =~ /;\s*\z/;
350 push @statements, "DROP TRIGGER IF EXISTS ${qt}${name}${qt}" if $options->{add_drop_trigger};
351 push @statements, sprintf(
352 "CREATE TRIGGER ${qt}%s${qt} %s %s ON ${qt}%s${qt}\n FOR EACH ROW BEGIN %s END",
353 $name, $trigger->perform_action_when, $event, $trigger->on_table, $action,
357 # Tack the comment onto the first statement
358 $statements[0] = "--\n-- Trigger ${qt}${trigger_name}${qt}\n--\n" . $statements[0] unless $options->{no_comments};
363 my ($view, $options) = @_;
364 my $qt = $options->{quote_table_names} || '';
365 my $qf = $options->{quote_field_names} || '';
367 my $view_name = $view->name;
368 debug("PKG: Looking at view '${view_name}'\n");
370 # Header. Should this look like what mysqldump produces?
372 $create .= "--\n-- View: ${qt}${view_name}${qt}\n--\n" unless $options->{no_comments};
374 $create .= ' OR REPLACE' if $options->{add_replace_view};
377 my $extra = $view->extra;
379 if( exists($extra->{mysql_algorithm}) && defined(my $algorithm = $extra->{mysql_algorithm}) ){
380 $create .= " ALGORITHM = ${algorithm}\n" if $algorithm =~ /(?:UNDEFINED|MERGE|TEMPTABLE)/i;
383 if( exists($extra->{mysql_definer}) && defined(my $user = $extra->{mysql_definer}) ){
384 $create .= " DEFINER = ${user}\n";
387 if( exists($extra->{mysql_security}) && defined(my $security = $extra->{mysql_security}) ){
388 $create .= " SQL SECURITY ${security}\n" if $security =~ /(?:DEFINER|INVOKER)/i;
392 $create .= " VIEW ${qt}${view_name}${qt}";
394 if( my @fields = $view->fields ){
395 my $list = join ', ', map { "${qf}${_}${qf}"} @fields;
396 $create .= " ( ${list} )";
398 if( my $sql = $view->sql ){
399 # do not wrap parenthesis around the selector, mysql doesn't like this
400 # http://bugs.mysql.com/bug.php?id=9198
401 $create .= " AS\n ${sql}\n";
409 my ($table, $options) = @_;
411 my $qt = $options->{quote_table_names} || '';
412 my $qf = $options->{quote_field_names} || '';
414 my $table_name = quote_table_name($table->name, $qt);
415 debug("PKG: Looking at table '$table_name'\n");
418 # Header. Should this look like what mysqldump produces?
422 $create .= "--\n-- Table: $table_name\n--\n" unless $options->{no_comments};
423 $drop = qq[DROP TABLE IF EXISTS $table_name] if $options->{add_drop_table};
424 $create .= "CREATE TABLE $table_name (\n";
430 for my $field ( $table->get_fields ) {
431 push @field_defs, create_field($field, $options);
439 for my $index ( $table->get_indices ) {
440 push @index_defs, create_index($index, $options);
441 $indexed_fields{ $_ } = 1 for $index->fields;
445 # Constraints -- need to handle more than just FK. -ky
448 my @constraints = $table->get_constraints;
449 for my $c ( @constraints ) {
450 my $constr = create_constraint($c, $options);
451 push @constraint_defs, $constr if($constr);
453 unless ( $indexed_fields{ ($c->fields())[0] } || $c->type ne FOREIGN_KEY ) {
454 push @index_defs, "INDEX ($qf" . ($c->fields())[0] . "$qf)";
455 $indexed_fields{ ($c->fields())[0] } = 1;
459 $create .= join(",\n", map { " $_" }
460 @field_defs, @index_defs, @constraint_defs
467 $create .= generate_table_options($table, $options) || '';
468 # $create .= ";\n\n";
470 return $drop ? ($drop,$create) : $create;
473 sub quote_table_name {
474 my ($table_name, $qt) = @_;
476 $table_name =~ s/\./$qt.$qt/g;
478 return "$qt$table_name$qt";
481 sub generate_table_options
483 my ($table, $options) = @_;
486 my $table_type_defined = 0;
487 my $qf = $options->{quote_field_names} ||= '';
488 my $charset = $table->extra('mysql_charset');
489 my $collate = $table->extra('mysql_collate');
491 for my $t1_option_ref ( $table->options ) {
492 my($key, $value) = %{$t1_option_ref};
493 $table_type_defined = 1
494 if uc $key eq 'ENGINE' or uc $key eq 'TYPE';
495 if (uc $key eq 'CHARACTER SET') {
498 } elsif (uc $key eq 'COLLATE') {
501 } elsif (uc $key eq 'UNION') {
502 $union = "($qf". join("$qf, $qf", @$value) ."$qf)";
505 $create .= " $key=$value";
508 my $mysql_table_type = $table->extra('mysql_table_type');
509 $create .= " ENGINE=$mysql_table_type"
510 if $mysql_table_type && !$table_type_defined;
511 my $comments = $table->comments;
513 $create .= " DEFAULT CHARACTER SET $charset" if $charset;
514 $create .= " COLLATE $collate" if $collate;
515 $create .= " UNION=$union" if $union;
516 $create .= qq[ comment='$comments'] if $comments;
522 my ($field, $options) = @_;
524 my $qf = $options->{quote_field_names} ||= '';
526 my $field_name = $field->name;
527 debug("PKG: Looking at field '$field_name'\n");
528 my $field_def = "$qf$field_name$qf";
531 my $data_type = $field->data_type;
532 my @size = $field->size;
533 my %extra = $field->extra;
534 my $list = $extra{'list'} || [];
535 # \todo deal with embedded quotes
536 my $commalist = join( ', ', map { qq['$_'] } @$list );
537 my $charset = $extra{'mysql_charset'};
538 my $collate = $extra{'mysql_collate'};
540 my $mysql_version = $options->{mysql_version} || 0;
542 # Oracle "number" type -- figure best MySQL type
544 if ( lc $data_type eq 'number' ) {
546 if ( scalar @size > 1 ) {
547 $data_type = 'double';
549 elsif ( $size[0] && $size[0] >= 12 ) {
550 $data_type = 'bigint';
552 elsif ( $size[0] && $size[0] <= 1 ) {
553 $data_type = 'tinyint';
560 # Convert a large Oracle varchar to "text"
561 # (not necessary as of 5.0.3 http://dev.mysql.com/doc/refman/5.0/en/char.html)
563 elsif ( $data_type =~ /char/i && $size[0] > 255 ) {
564 unless ($size[0] <= 65535 && $mysql_version >= 5.000003 ) {
569 elsif ( $data_type =~ /boolean/i ) {
570 if ($mysql_version >= 4) {
571 $data_type = 'boolean';
574 $commalist = "'0','1'";
577 elsif ( exists $translate{ lc $data_type } ) {
578 $data_type = $translate{ lc $data_type };
581 @size = () if $data_type =~ /(text|blob)/i;
583 if ( $data_type =~ /(double|float)/ && scalar @size == 1 ) {
587 $field_def .= " $data_type";
589 if ( lc($data_type) eq 'enum' || lc($data_type) eq 'set') {
590 $field_def .= '(' . $commalist . ')';
593 defined $size[0] && $size[0] > 0
595 ! grep lc($data_type) eq $_, @no_length_attr
597 $field_def .= '(' . join( ', ', @size ) . ')';
601 $field_def .= " CHARACTER SET $charset" if $charset;
602 $field_def .= " COLLATE $collate" if $collate;
605 for my $qual ( qw[ binary unsigned zerofill ] ) {
606 my $val = $extra{ $qual } || $extra{ uc $qual } or next;
607 $field_def .= " $qual";
609 for my $qual ( 'character set', 'collate', 'on update' ) {
610 my $val = $extra{ $qual } || $extra{ uc $qual } or next;
611 $field_def .= " $qual $val";
615 $field_def .= ' NOT NULL' unless $field->is_nullable;
618 SQL::Translator::Producer->_apply_default_value(
626 if ( my $comments = $field->comments ) {
627 $field_def .= qq[ comment '$comments'];
631 $field_def .= " auto_increment" if $field->is_auto_increment;
636 sub alter_create_index
638 my ($index, $options) = @_;
640 my $qt = $options->{quote_table_names} || '';
641 my $qf = $options->{quote_field_names} || '';
642 my $table_name = quote_table_name($index->table->name, $qt);
653 my ( $index, $options ) = @_;
655 my $qf = $options->{quote_field_names} || '';
660 lc $index->type eq 'normal' ? 'INDEX' : $index->type . ' INDEX',
662 ? $qf . truncate_id_uniquely(
664 $options->{max_id_length} || $DEFAULT_MAX_ID_LENGTH
667 '(' . $qf . join( "$qf, $qf", $index->fields ) . $qf . ')'
673 my ($index, $options) = @_;
675 my $qt = $options->{quote_table_names} || '';
676 my $qf = $options->{quote_field_names} || '';
677 my $table_name = quote_table_name($index->table->name, $qt);
684 $index->name || $index->fields
689 sub alter_drop_constraint
691 my ($c, $options) = @_;
693 my $qt = $options->{quote_table_names} || '';
694 my $qc = $options->{quote_field_names} || '';
695 my $table_name = quote_table_name($c->table->name, $qt);
697 my $out = sprintf('ALTER TABLE %s DROP %s %s',
699 $c->type eq FOREIGN_KEY ? $c->type : "INDEX",
700 $qc . $c->name . $qc );
705 sub alter_create_constraint
707 my ($index, $options) = @_;
709 my $qt = $options->{quote_table_names} || '';
710 my $table_name = quote_table_name($index->table->name, $qt);
715 create_constraint(@_) );
718 sub create_constraint
720 my ($c, $options) = @_;
722 my $qf = $options->{quote_field_names} || '';
723 my $qt = $options->{quote_table_names} || '';
724 my $leave_name = $options->{leave_name} || undef;
726 my $reference_table_name = quote_table_name($c->reference_table, $qt);
728 my @fields = $c->fields or next;
730 if ( $c->type eq PRIMARY_KEY ) {
731 return 'PRIMARY KEY (' . $qf . join("$qf, $qf", @fields). $qf . ')';
733 elsif ( $c->type eq UNIQUE ) {
734 return sprintf 'UNIQUE %s(%s)',
735 ((defined $c->name && $c->name)
738 truncate_id_uniquely( $c->name, $options->{max_id_length} || $DEFAULT_MAX_ID_LENGTH ),
744 ( join ', ', map { "${qf}${_}${qf}" } @fields ),
747 elsif ( $c->type eq FOREIGN_KEY ) {
749 # Make sure FK field is indexed or MySQL complains.
752 my $table = $c->table;
753 my $c_name = truncate_id_uniquely( $c->name, $options->{max_id_length} || $DEFAULT_MAX_ID_LENGTH );
763 $def .= ' ('.$qf . join( "$qf, $qf", @fields ) . $qf . ')';
765 $def .= ' REFERENCES ' . $reference_table_name;
767 my @rfields = map { $_ || () } $c->reference_fields;
768 unless ( @rfields ) {
769 my $rtable_name = $c->reference_table;
770 if ( my $ref_table = $table->schema->get_table( $rtable_name ) ) {
771 push @rfields, $ref_table->primary_key;
774 warn "Can't find reference table '$rtable_name' " .
775 "in schema\n" if $options->{show_warnings};
780 $def .= ' (' . $qf . join( "$qf, $qf", @rfields ) . $qf . ')';
783 warn "FK constraint on " . $table->name . '.' .
784 join('', @fields) . " has no reference fields\n"
785 if $options->{show_warnings};
788 if ( $c->match_type ) {
790 ( $c->match_type =~ /full/i ) ? 'FULL' : 'PARTIAL';
793 if ( $c->on_delete ) {
794 $def .= ' ON DELETE '. $c->on_delete;
797 if ( $c->on_update ) {
798 $def .= ' ON UPDATE '. $c->on_update;
808 my ($to_table, $options) = @_;
810 my $qt = $options->{quote_table_names} || '';
812 my $table_options = generate_table_options($to_table, $options) || '';
813 my $table_name = quote_table_name($to_table->name, $qt);
814 my $out = sprintf('ALTER TABLE %s%s',
821 sub rename_field { alter_field(@_) }
824 my ($from_field, $to_field, $options) = @_;
826 my $qf = $options->{quote_field_names} || '';
827 my $qt = $options->{quote_table_names} || '';
828 my $table_name = quote_table_name($to_field->table->name, $qt);
830 my $out = sprintf('ALTER TABLE %s CHANGE COLUMN %s %s',
832 $qf . $from_field->name . $qf,
833 create_field($to_field, $options));
840 my ($new_field, $options) = @_;
842 my $qt = $options->{quote_table_names} || '';
843 my $table_name = quote_table_name($new_field->table->name, $qt);
845 my $out = sprintf('ALTER TABLE %s ADD COLUMN %s',
847 create_field($new_field, $options));
855 my ($old_field, $options) = @_;
857 my $qf = $options->{quote_field_names} || '';
858 my $qt = $options->{quote_table_names} || '';
859 my $table_name = quote_table_name($old_field->table->name, $qt);
861 my $out = sprintf('ALTER TABLE %s DROP COLUMN %s',
863 $qf . $old_field->name . $qf);
869 sub batch_alter_table {
870 my ($table, $diff_hash, $options) = @_;
872 # InnoDB has an issue with dropping and re-adding a FK constraint under the
873 # name in a single alter statment, see: http://bugs.mysql.com/bug.php?id=13741
875 # We have to work round this.
878 my %fks_to_drop = map {
879 $_->type eq FOREIGN_KEY
882 } @{$diff_hash->{alter_drop_constraint} };
884 my %fks_to_create = map {
885 if ( $_->type eq FOREIGN_KEY) {
886 $fks_to_alter{$_->name} = $fks_to_drop{$_->name} if $fks_to_drop{$_->name};
889 } @{$diff_hash->{alter_create_constraint} };
892 if (scalar keys %fks_to_alter) {
893 $diff_hash->{alter_drop_constraint} = [
894 grep { !$fks_to_alter{$_->name} } @{ $diff_hash->{alter_drop_constraint} }
897 @drop_stmt = batch_alter_table($table, { alter_drop_constraint => [ values %fks_to_alter ] }, $options);
902 if (@{ $diff_hash->{$_} || [] }) {
903 my $meth = __PACKAGE__->can($_) or die __PACKAGE__ . " cant $_";
904 map { $meth->( (ref $_ eq 'ARRAY' ? @$_ : $_), $options ) } @{ $diff_hash->{$_} }
907 alter_drop_constraint
914 alter_create_constraint
918 my $qt = $options->{quote_table_names} || '';
920 # rename_table makes things a bit more complex
921 my $renamed_from = "";
922 $renamed_from = quote_table_name($diff_hash->{rename_table}[0][0]->name, $qt)
923 if $diff_hash->{rename_table} && @{$diff_hash->{rename_table}};
925 return unless @stmts;
926 # Just zero or one stmts. return now
927 return (@drop_stmt,@stmts) unless @stmts > 1;
929 # Now strip off the 'ALTER TABLE xyz' of all but the first one
931 my $table_name = quote_table_name($table->name, $qt);
933 my $re = $renamed_from
934 ? qr/^ALTER TABLE (?:\Q$table_name\E|\Q$renamed_from\E) /
935 : qr/^ALTER TABLE \Q$table_name\E /;
937 my $first = shift @stmts;
938 my ($alter_table) = $first =~ /($re)/;
940 my $padd = " " x length($alter_table);
942 return @drop_stmt, join( ",\n", $first, map { s/$re//; $padd . $_ } @stmts);
947 my ($table, $options) = @_;
949 my $qt = $options->{quote_table_names} || '';
951 # Drop (foreign key) constraints so table drops cleanly
952 my @sql = batch_alter_table($table, { alter_drop_constraint => [ grep { $_->type eq 'FOREIGN KEY' } $table->get_constraints ] }, $options);
954 my $table_name = quote_table_name($table, $qt);
955 return (@sql, "DROP TABLE $table");
960 my ($old_table, $new_table, $options) = @_;
962 my $qt = $options->{quote_table_names} || '';
963 my $old_table_name = quote_table_name($old_table, $qt);
964 my $new_table_name = quote_table_name($new_table, $qt);
966 return "ALTER TABLE $old_table_name RENAME TO $new_table_name";
969 sub next_unused_name {
970 my $name = shift || '';
971 if ( !defined($used_names{$name}) ) {
972 $used_names{$name} = $name;
977 while ( defined($used_names{$name . '_' . $i}) ) {
981 $used_names{$name} = $name;
991 SQL::Translator, http://www.mysql.com/.
995 darren chamberlain E<lt>darren@cpan.orgE<gt>,
996 Ken Youens-Clark E<lt>kclark@cpan.orgE<gt>.