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 character set and collation order.
74 =item B<field.mysql_charset>, B<field.mysql_collate>
76 Set the fields character 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
97 batch_alter_table_statements
101 # Use only lowercase for the keys (e.g. "long" and not "LONG")
107 varchar2 => 'varchar',
123 'long integer' => 'integer',
125 'datetime' => 'datetime',
134 # Column types that do not support length attribute
136 my @no_length_attr = qw/
137 date time timestamp datetime year
141 sub preprocess_schema {
144 # extra->{mysql_table_type} used to be the type. It belongs in options, so
145 # move it if we find it. Return Engine type if found in extra or options
146 # Similarly for mysql_charset and mysql_collate
147 my $extra_to_options = sub {
148 my ($table, $extra_name, $opt_name) = @_;
150 my $extra = $table->extra;
152 my $extra_type = delete $extra->{$extra_name};
154 # Now just to find if there is already an Engine or Type option...
155 # and lets normalize it to ENGINE since:
157 # The ENGINE table option specifies the storage engine for the table.
158 # TYPE is a synonym, but ENGINE is the preferred option name.
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 doesn'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;
612 $field_def .= " $qual ${$val}";
615 $field_def .= " $qual $val";
620 if ( $field->is_nullable ) {
621 $field_def .= ' NULL';
624 $field_def .= ' NOT NULL';
628 SQL::Translator::Producer->_apply_default_value(
636 if ( my $comments = $field->comments ) {
637 $field_def .= qq[ comment '$comments'];
641 $field_def .= " auto_increment" if $field->is_auto_increment;
646 sub alter_create_index
648 my ($index, $options) = @_;
650 my $qt = $options->{quote_table_names} || '';
651 my $qf = $options->{quote_field_names} || '';
652 my $table_name = quote_table_name($index->table->name, $qt);
663 my ( $index, $options ) = @_;
665 my $qf = $options->{quote_field_names} || '';
670 lc $index->type eq 'normal' ? 'INDEX' : $index->type . ' INDEX',
672 ? $qf . truncate_id_uniquely(
674 $options->{max_id_length} || $DEFAULT_MAX_ID_LENGTH
677 '(' . $qf . join( "$qf, $qf", $index->fields ) . $qf . ')'
683 my ($index, $options) = @_;
685 my $qt = $options->{quote_table_names} || '';
686 my $qf = $options->{quote_field_names} || '';
687 my $table_name = quote_table_name($index->table->name, $qt);
694 $index->name || $index->fields
699 sub alter_drop_constraint
701 my ($c, $options) = @_;
703 my $qt = $options->{quote_table_names} || '';
704 my $qc = $options->{quote_field_names} || '';
705 my $table_name = quote_table_name($c->table->name, $qt);
707 my @out = ('ALTER','TABLE',$table_name,'DROP');
708 if($c->type eq PRIMARY_KEY) {
712 push @out, ($c->type eq FOREIGN_KEY ? $c->type : "INDEX"),
713 $qc . $c->name . $qc;
715 return join(' ',@out);
718 sub alter_create_constraint
720 my ($index, $options) = @_;
722 my $qt = $options->{quote_table_names} || '';
723 my $table_name = quote_table_name($index->table->name, $qt);
728 create_constraint(@_) );
731 sub create_constraint
733 my ($c, $options) = @_;
735 my $qf = $options->{quote_field_names} || '';
736 my $qt = $options->{quote_table_names} || '';
737 my $leave_name = $options->{leave_name} || undef;
739 my $reference_table_name = quote_table_name($c->reference_table, $qt);
741 my @fields = $c->fields or return;
743 if ( $c->type eq PRIMARY_KEY ) {
744 return 'PRIMARY KEY (' . $qf . join("$qf, $qf", @fields). $qf . ')';
746 elsif ( $c->type eq UNIQUE ) {
747 return sprintf 'UNIQUE %s(%s)',
748 ((defined $c->name && $c->name)
751 truncate_id_uniquely( $c->name, $options->{max_id_length} || $DEFAULT_MAX_ID_LENGTH ),
757 ( join ', ', map { "${qf}${_}${qf}" } @fields ),
760 elsif ( $c->type eq FOREIGN_KEY ) {
762 # Make sure FK field is indexed or MySQL complains.
765 my $table = $c->table;
766 my $c_name = truncate_id_uniquely( $c->name, $options->{max_id_length} || $DEFAULT_MAX_ID_LENGTH );
776 $def .= ' ('.$qf . join( "$qf, $qf", @fields ) . $qf . ')';
778 $def .= ' REFERENCES ' . $reference_table_name;
780 my @rfields = map { $_ || () } $c->reference_fields;
781 unless ( @rfields ) {
782 my $rtable_name = $c->reference_table;
783 if ( my $ref_table = $table->schema->get_table( $rtable_name ) ) {
784 push @rfields, $ref_table->primary_key;
787 warn "Can't find reference table '$rtable_name' " .
788 "in schema\n" if $options->{show_warnings};
793 $def .= ' (' . $qf . join( "$qf, $qf", @rfields ) . $qf . ')';
796 warn "FK constraint on " . $table->name . '.' .
797 join('', @fields) . " has no reference fields\n"
798 if $options->{show_warnings};
801 if ( $c->match_type ) {
803 ( $c->match_type =~ /full/i ) ? 'FULL' : 'PARTIAL';
806 if ( $c->on_delete ) {
807 $def .= ' ON DELETE '. $c->on_delete;
810 if ( $c->on_update ) {
811 $def .= ' ON UPDATE '. $c->on_update;
821 my ($to_table, $options) = @_;
823 my $qt = $options->{quote_table_names} || '';
825 my $table_options = generate_table_options($to_table, $options) || '';
826 my $table_name = quote_table_name($to_table->name, $qt);
827 my $out = sprintf('ALTER TABLE %s%s',
834 sub rename_field { alter_field(@_) }
837 my ($from_field, $to_field, $options) = @_;
839 my $qf = $options->{quote_field_names} || '';
840 my $qt = $options->{quote_table_names} || '';
841 my $table_name = quote_table_name($to_field->table->name, $qt);
843 my $out = sprintf('ALTER TABLE %s CHANGE COLUMN %s %s',
845 $qf . $from_field->name . $qf,
846 create_field($to_field, $options));
853 my ($new_field, $options) = @_;
855 my $qt = $options->{quote_table_names} || '';
856 my $table_name = quote_table_name($new_field->table->name, $qt);
858 my $out = sprintf('ALTER TABLE %s ADD COLUMN %s',
860 create_field($new_field, $options));
868 my ($old_field, $options) = @_;
870 my $qf = $options->{quote_field_names} || '';
871 my $qt = $options->{quote_table_names} || '';
872 my $table_name = quote_table_name($old_field->table->name, $qt);
874 my $out = sprintf('ALTER TABLE %s DROP COLUMN %s',
876 $qf . $old_field->name . $qf);
882 sub batch_alter_table {
883 my ($table, $diff_hash, $options) = @_;
885 # InnoDB has an issue with dropping and re-adding a FK constraint under the
886 # name in a single alter statement, see: http://bugs.mysql.com/bug.php?id=13741
888 # We have to work round this.
891 my %fks_to_drop = map {
892 $_->type eq FOREIGN_KEY
895 } @{$diff_hash->{alter_drop_constraint} };
897 my %fks_to_create = map {
898 if ( $_->type eq FOREIGN_KEY) {
899 $fks_to_alter{$_->name} = $fks_to_drop{$_->name} if $fks_to_drop{$_->name};
902 } @{$diff_hash->{alter_create_constraint} };
905 if (scalar keys %fks_to_alter) {
906 $diff_hash->{alter_drop_constraint} = [
907 grep { !$fks_to_alter{$_->name} } @{ $diff_hash->{alter_drop_constraint} }
910 @drop_stmt = batch_alter_table($table, { alter_drop_constraint => [ values %fks_to_alter ] }, $options);
914 my @stmts = batch_alter_table_statements($diff_hash, $options);
917 my $qt = $options->{quote_table_names} || '';
919 # rename_table makes things a bit more complex
920 my $renamed_from = "";
921 $renamed_from = quote_table_name($diff_hash->{rename_table}[0][0]->name, $qt)
922 if $diff_hash->{rename_table} && @{$diff_hash->{rename_table}};
924 return unless @stmts;
925 # Just zero or one stmts. return now
926 return (@drop_stmt,@stmts) unless @stmts > 1;
928 # Now strip off the 'ALTER TABLE xyz' of all but the first one
930 my $table_name = quote_table_name($table->name, $qt);
932 my $re = $renamed_from
933 ? qr/^ALTER TABLE (?:\Q$table_name\E|\Q$renamed_from\E) /
934 : qr/^ALTER TABLE \Q$table_name\E /;
936 my $first = shift @stmts;
937 my ($alter_table) = $first =~ /($re)/;
939 my $padd = " " x length($alter_table);
941 return @drop_stmt, join( ",\n", $first, map { s/$re//; $padd . $_ } @stmts);
946 my ($table, $options) = @_;
948 my $qt = $options->{quote_table_names} || '';
950 # Drop (foreign key) constraints so table drops cleanly
951 my @sql = batch_alter_table($table, { alter_drop_constraint => [ grep { $_->type eq 'FOREIGN KEY' } $table->get_constraints ] }, $options);
953 my $table_name = quote_table_name($table, $qt);
954 return (@sql, "DROP TABLE $table");
959 my ($old_table, $new_table, $options) = @_;
961 my $qt = $options->{quote_table_names} || '';
962 my $old_table_name = quote_table_name($old_table, $qt);
963 my $new_table_name = quote_table_name($new_table, $qt);
965 return "ALTER TABLE $old_table_name RENAME TO $new_table_name";
968 sub next_unused_name {
969 my $name = shift || '';
970 if ( !defined($used_names{$name}) ) {
971 $used_names{$name} = $name;
976 while ( defined($used_names{$name . '_' . $i}) ) {
980 $used_names{$name} = $name;
990 SQL::Translator, http://www.mysql.com/.
994 darren chamberlain E<lt>darren@cpan.orgE<gt>,
995 Ken Youens-Clark E<lt>kclark@cpan.orgE<gt>.