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);
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 length 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 my $options = $table->options;
161 # If multiple option names, normalize to the first one
163 OPT_NAME: for ( @$opt_name[1..$#$opt_name] ) {
164 for my $idx ( 0..$#{$options} ) {
165 my ($key, $value) = %{ $options->[$idx] };
168 $options->[$idx] = { $opt_name->[0] => $value };
173 $opt_name = $opt_name->[0];
178 # This assumes that there isn't both a Type and an Engine option.
180 for my $idx ( 0..$#{$options} ) {
181 my ($key, $value) = %{ $options->[$idx] };
183 next unless uc $key eq $opt_name;
185 # make sure case is right on option name
186 delete $options->[$idx]{$key};
187 return $options->[$idx]{$opt_name} = $value || $extra_type;
192 push @$options, { $opt_name => $extra_type };
198 # Names are only specific to a given schema
199 local %used_names = ();
202 # Work out which tables need to be InnoDB to support foreign key
203 # constraints. We do this first as we need InnoDB at both ends.
205 foreach my $table ( $schema->get_tables ) {
207 $extra_to_options->($table, 'mysql_table_type', ['ENGINE', 'TYPE'] );
208 $extra_to_options->($table, 'mysql_charset', 'CHARACTER SET' );
209 $extra_to_options->($table, 'mysql_collate', 'COLLATE' );
211 foreach my $c ( $table->get_constraints ) {
212 next unless $c->type eq FOREIGN_KEY;
214 # Normalize constraint names here.
215 my $c_name = $c->name;
216 # Give the constraint a name if it doesn't have one, so it doesn't feel
218 $c_name = $table->name . '_fk' unless length $c_name;
220 $c->name( next_unused_name($c_name) );
222 for my $meth (qw/table reference_table/) {
223 my $table = $schema->get_table($c->$meth) || next;
224 # This normalizes the types to ENGINE and returns the value if its there
225 next if $extra_to_options->($table, 'mysql_table_type', ['ENGINE', 'TYPE']);
226 $table->options( { 'ENGINE' => 'InnoDB' } );
228 } # foreach constraints
230 my %map = ( mysql_collate => 'collate', mysql_charset => 'character set');
231 foreach my $f ( $table->get_fields ) {
232 my $extra = $f->extra;
234 $extra->{$map{$_}} = delete $extra->{$_} if exists $extra->{$_};
238 if ( !$size[0] && $f->data_type =~ /char$/ ) {
247 my $translator = shift;
248 local $DEBUG = $translator->debug;
250 my $no_comments = $translator->no_comments;
251 my $add_drop_table = $translator->add_drop_table;
252 my $schema = $translator->schema;
253 my $show_warnings = $translator->show_warnings || 0;
254 my $producer_args = $translator->producer_args;
255 my $mysql_version = parse_mysql_version ($producer_args->{mysql_version}, 'perl') || 0;
256 my $max_id_length = $producer_args->{mysql_max_id_length} || $DEFAULT_MAX_ID_LENGTH;
258 my ($qt, $qf, $qc) = ('','', '');
259 $qt = '`' if $translator->quote_table_names;
260 $qf = '`' if $translator->quote_field_names;
262 debug("PKG: Beginning production\n");
265 $create .= header_comment unless ($no_comments);
266 # \todo Don't set if MySQL 3.x is set on command line
267 my @create = "SET foreign_key_checks=0";
269 preprocess_schema($schema);
276 for my $table ( $schema->get_tables ) {
277 # print $table->name, "\n";
278 push @table_defs, create_table($table,
279 { add_drop_table => $add_drop_table,
280 show_warnings => $show_warnings,
281 no_comments => $no_comments,
282 quote_table_names => $qt,
283 quote_field_names => $qf,
284 max_id_length => $max_id_length,
285 mysql_version => $mysql_version
289 if ($mysql_version >= 5.000001) {
290 for my $view ( $schema->get_views ) {
291 push @table_defs, create_view($view,
292 { add_replace_view => $add_drop_table,
293 show_warnings => $show_warnings,
294 no_comments => $no_comments,
295 quote_table_names => $qt,
296 quote_field_names => $qf,
297 max_id_length => $max_id_length,
298 mysql_version => $mysql_version
303 if ($mysql_version >= 5.000002) {
304 for my $trigger ( $schema->get_triggers ) {
305 push @table_defs, create_trigger($trigger,
306 { add_drop_trigger => $add_drop_table,
307 show_warnings => $show_warnings,
308 no_comments => $no_comments,
309 quote_table_names => $qt,
310 quote_field_names => $qf,
311 max_id_length => $max_id_length,
312 mysql_version => $mysql_version
318 # print "@table_defs\n";
319 push @table_defs, "SET foreign_key_checks=1";
321 return wantarray ? ($create ? $create : (), @create, @table_defs) : ($create . join('', map { $_ ? "$_;\n\n" : () } (@create, @table_defs)));
325 my ($trigger, $options) = @_;
326 my $qt = $options->{quote_table_names} || '';
327 my $qf = $options->{quote_field_names} || '';
329 my $trigger_name = $trigger->name;
330 debug("PKG: Looking at trigger '${trigger_name}'\n");
334 my $events = $trigger->database_events;
335 for my $event ( @$events ) {
336 my $name = $trigger_name;
340 warn "Multiple database events supplied for trigger '${trigger_name}', ",
341 "creating trigger '${name}' for the '${event}' event\n"
342 if $options->{show_warnings};
345 my $action = $trigger->action;
346 $action .= ";" unless $action =~ /;\s*\z/;
348 push @statements, "DROP TRIGGER IF EXISTS ${qt}${name}${qt}" if $options->{add_drop_trigger};
349 push @statements, sprintf(
350 "CREATE TRIGGER ${qt}%s${qt} %s %s ON ${qt}%s${qt}\n FOR EACH ROW BEGIN %s END",
351 $name, $trigger->perform_action_when, $event, $trigger->on_table, $action,
355 # Tack the comment onto the first statement
356 $statements[0] = "--\n-- Trigger ${qt}${trigger_name}${qt}\n--\n" . $statements[0] unless $options->{no_comments};
361 my ($view, $options) = @_;
362 my $qt = $options->{quote_table_names} || '';
363 my $qf = $options->{quote_field_names} || '';
365 my $view_name = $view->name;
366 debug("PKG: Looking at view '${view_name}'\n");
368 # Header. Should this look like what mysqldump produces?
370 $create .= "--\n-- View: ${qt}${view_name}${qt}\n--\n" unless $options->{no_comments};
372 $create .= ' OR REPLACE' if $options->{add_replace_view};
375 my $extra = $view->extra;
377 if( exists($extra->{mysql_algorithm}) && defined(my $algorithm = $extra->{mysql_algorithm}) ){
378 $create .= " ALGORITHM = ${algorithm}\n" if $algorithm =~ /(?:UNDEFINED|MERGE|TEMPTABLE)/i;
381 if( exists($extra->{mysql_definer}) && defined(my $user = $extra->{mysql_definer}) ){
382 $create .= " DEFINER = ${user}\n";
385 if( exists($extra->{mysql_security}) && defined(my $security = $extra->{mysql_security}) ){
386 $create .= " SQL SECURITY ${security}\n" if $security =~ /(?:DEFINER|INVOKER)/i;
390 $create .= " VIEW ${qt}${view_name}${qt}";
392 if( my @fields = $view->fields ){
393 my $list = join ', ', map { "${qf}${_}${qf}"} @fields;
394 $create .= " ( ${list} )";
396 if( my $sql = $view->sql ){
397 # do not wrap parenthesis around the selector, mysql doesn't like this
398 # http://bugs.mysql.com/bug.php?id=9198
399 $create .= " AS\n ${sql}\n";
407 my ($table, $options) = @_;
409 my $qt = $options->{quote_table_names} || '';
410 my $qf = $options->{quote_field_names} || '';
412 my $table_name = quote_table_name($table->name, $qt);
413 debug("PKG: Looking at table '$table_name'\n");
416 # Header. Should this look like what mysqldump produces?
420 $create .= "--\n-- Table: $table_name\n--\n" unless $options->{no_comments};
421 $drop = qq[DROP TABLE IF EXISTS $table_name] if $options->{add_drop_table};
422 $create .= "CREATE TABLE $table_name (\n";
428 for my $field ( $table->get_fields ) {
429 push @field_defs, create_field($field, $options);
437 for my $index ( $table->get_indices ) {
438 push @index_defs, create_index($index, $options);
439 $indexed_fields{ $_ } = 1 for $index->fields;
443 # Constraints -- need to handle more than just FK. -ky
446 my @constraints = $table->get_constraints;
447 for my $c ( @constraints ) {
448 my $constr = create_constraint($c, $options);
449 push @constraint_defs, $constr if($constr);
451 unless ( $indexed_fields{ ($c->fields())[0] } || $c->type ne FOREIGN_KEY ) {
452 push @index_defs, "INDEX ($qf" . ($c->fields())[0] . "$qf)";
453 $indexed_fields{ ($c->fields())[0] } = 1;
457 $create .= join(",\n", map { " $_" }
458 @field_defs, @index_defs, @constraint_defs
465 $create .= generate_table_options($table, $options) || '';
466 # $create .= ";\n\n";
468 return $drop ? ($drop,$create) : $create;
471 sub quote_table_name {
472 my ($table_name, $qt) = @_;
474 $table_name =~ s/\./$qt.$qt/g;
476 return "$qt$table_name$qt";
479 sub generate_table_options
481 my ($table, $options) = @_;
484 my $table_type_defined = 0;
485 my $qf = $options->{quote_field_names} ||= '';
486 my $charset = $table->extra('mysql_charset');
487 my $collate = $table->extra('mysql_collate');
489 for my $t1_option_ref ( $table->options ) {
490 my($key, $value) = %{$t1_option_ref};
491 $table_type_defined = 1
492 if uc $key eq 'ENGINE' or uc $key eq 'TYPE';
493 if (uc $key eq 'CHARACTER SET') {
496 } elsif (uc $key eq 'COLLATE') {
499 } elsif (uc $key eq 'UNION') {
500 $union = "($qf". join("$qf, $qf", @$value) ."$qf)";
503 $create .= " $key=$value";
506 my $mysql_table_type = $table->extra('mysql_table_type');
507 $create .= " ENGINE=$mysql_table_type"
508 if $mysql_table_type && !$table_type_defined;
509 my $comments = $table->comments;
511 $create .= " DEFAULT CHARACTER SET $charset" if $charset;
512 $create .= " COLLATE $collate" if $collate;
513 $create .= " UNION=$union" if $union;
514 $create .= qq[ comment='$comments'] if $comments;
520 my ($field, $options) = @_;
522 my $qf = $options->{quote_field_names} ||= '';
524 my $field_name = $field->name;
525 debug("PKG: Looking at field '$field_name'\n");
526 my $field_def = "$qf$field_name$qf";
529 my $data_type = $field->data_type;
530 my @size = $field->size;
531 my %extra = $field->extra;
532 my $list = $extra{'list'} || [];
533 # \todo deal with embedded quotes
534 my $commalist = join( ', ', map { qq['$_'] } @$list );
535 my $charset = $extra{'mysql_charset'};
536 my $collate = $extra{'mysql_collate'};
538 my $mysql_version = $options->{mysql_version} || 0;
540 # Oracle "number" type -- figure best MySQL type
542 if ( lc $data_type eq 'number' ) {
544 if ( scalar @size > 1 ) {
545 $data_type = 'double';
547 elsif ( $size[0] && $size[0] >= 12 ) {
548 $data_type = 'bigint';
550 elsif ( $size[0] && $size[0] <= 1 ) {
551 $data_type = 'tinyint';
558 # Convert a large Oracle varchar to "text"
559 # (not necessary as of 5.0.3 http://dev.mysql.com/doc/refman/5.0/en/char.html)
561 elsif ( $data_type =~ /char/i && $size[0] > 255 ) {
562 unless ($size[0] <= 65535 && $mysql_version >= 5.000003 ) {
567 elsif ( $data_type =~ /boolean/i ) {
568 if ($mysql_version >= 4) {
569 $data_type = 'boolean';
572 $commalist = "'0','1'";
575 elsif ( exists $translate{ lc $data_type } ) {
576 $data_type = $translate{ lc $data_type };
579 @size = () if $data_type =~ /(text|blob)/i;
581 if ( $data_type =~ /(double|float)/ && scalar @size == 1 ) {
585 $field_def .= " $data_type";
587 if ( lc($data_type) eq 'enum' || lc($data_type) eq 'set') {
588 $field_def .= '(' . $commalist . ')';
591 defined $size[0] && $size[0] > 0
593 ! grep lc($data_type) eq $_, @no_length_attr
595 $field_def .= '(' . join( ', ', @size ) . ')';
599 $field_def .= " CHARACTER SET $charset" if $charset;
600 $field_def .= " COLLATE $collate" if $collate;
603 for my $qual ( qw[ binary unsigned zerofill ] ) {
604 my $val = $extra{ $qual } || $extra{ uc $qual } or next;
605 $field_def .= " $qual";
607 for my $qual ( 'character set', 'collate', 'on update' ) {
608 my $val = $extra{ $qual } || $extra{ uc $qual } or next;
609 $field_def .= " $qual $val";
613 if ( $field->is_nullable ) {
614 $field_def .= ' NULL';
617 $field_def .= ' NOT NULL';
621 SQL::Translator::Producer->_apply_default_value(
629 if ( my $comments = $field->comments ) {
630 $field_def .= qq[ comment '$comments'];
634 $field_def .= " auto_increment" if $field->is_auto_increment;
639 sub alter_create_index
641 my ($index, $options) = @_;
643 my $qt = $options->{quote_table_names} || '';
644 my $qf = $options->{quote_field_names} || '';
645 my $table_name = quote_table_name($index->table->name, $qt);
656 my ( $index, $options ) = @_;
658 my $qf = $options->{quote_field_names} || '';
663 lc $index->type eq 'normal' ? 'INDEX' : $index->type . ' INDEX',
665 ? $qf . truncate_id_uniquely(
667 $options->{max_id_length} || $DEFAULT_MAX_ID_LENGTH
670 '(' . $qf . join( "$qf, $qf", $index->fields ) . $qf . ')'
676 my ($index, $options) = @_;
678 my $qt = $options->{quote_table_names} || '';
679 my $qf = $options->{quote_field_names} || '';
680 my $table_name = quote_table_name($index->table->name, $qt);
687 $index->name || $index->fields
692 sub alter_drop_constraint
694 my ($c, $options) = @_;
696 my $qt = $options->{quote_table_names} || '';
697 my $qc = $options->{quote_field_names} || '';
698 my $table_name = quote_table_name($c->table->name, $qt);
700 my @out = ('ALTER','TABLE',$table_name,'DROP');
701 if($c->type eq PRIMARY_KEY) {
705 push @out, ($c->type eq FOREIGN_KEY ? $c->type : "INDEX"),
706 $qc . $c->name . $qc;
708 return join(' ',@out);
711 sub alter_create_constraint
713 my ($index, $options) = @_;
715 my $qt = $options->{quote_table_names} || '';
716 my $table_name = quote_table_name($index->table->name, $qt);
721 create_constraint(@_) );
724 sub create_constraint
726 my ($c, $options) = @_;
728 my $qf = $options->{quote_field_names} || '';
729 my $qt = $options->{quote_table_names} || '';
730 my $leave_name = $options->{leave_name} || undef;
732 my $reference_table_name = quote_table_name($c->reference_table, $qt);
734 my @fields = $c->fields or next;
736 if ( $c->type eq PRIMARY_KEY ) {
737 return 'PRIMARY KEY (' . $qf . join("$qf, $qf", @fields). $qf . ')';
739 elsif ( $c->type eq UNIQUE ) {
740 return sprintf 'UNIQUE %s(%s)',
741 ((defined $c->name && $c->name)
744 truncate_id_uniquely( $c->name, $options->{max_id_length} || $DEFAULT_MAX_ID_LENGTH ),
750 ( join ', ', map { "${qf}${_}${qf}" } @fields ),
753 elsif ( $c->type eq FOREIGN_KEY ) {
755 # Make sure FK field is indexed or MySQL complains.
758 my $table = $c->table;
759 my $c_name = truncate_id_uniquely( $c->name, $options->{max_id_length} || $DEFAULT_MAX_ID_LENGTH );
769 $def .= ' ('.$qf . join( "$qf, $qf", @fields ) . $qf . ')';
771 $def .= ' REFERENCES ' . $reference_table_name;
773 my @rfields = map { $_ || () } $c->reference_fields;
774 unless ( @rfields ) {
775 my $rtable_name = $c->reference_table;
776 if ( my $ref_table = $table->schema->get_table( $rtable_name ) ) {
777 push @rfields, $ref_table->primary_key;
780 warn "Can't find reference table '$rtable_name' " .
781 "in schema\n" if $options->{show_warnings};
786 $def .= ' (' . $qf . join( "$qf, $qf", @rfields ) . $qf . ')';
789 warn "FK constraint on " . $table->name . '.' .
790 join('', @fields) . " has no reference fields\n"
791 if $options->{show_warnings};
794 if ( $c->match_type ) {
796 ( $c->match_type =~ /full/i ) ? 'FULL' : 'PARTIAL';
799 if ( $c->on_delete ) {
800 $def .= ' ON DELETE '. $c->on_delete;
803 if ( $c->on_update ) {
804 $def .= ' ON UPDATE '. $c->on_update;
814 my ($to_table, $options) = @_;
816 my $qt = $options->{quote_table_names} || '';
818 my $table_options = generate_table_options($to_table, $options) || '';
819 my $table_name = quote_table_name($to_table->name, $qt);
820 my $out = sprintf('ALTER TABLE %s%s',
827 sub rename_field { alter_field(@_) }
830 my ($from_field, $to_field, $options) = @_;
832 my $qf = $options->{quote_field_names} || '';
833 my $qt = $options->{quote_table_names} || '';
834 my $table_name = quote_table_name($to_field->table->name, $qt);
836 my $out = sprintf('ALTER TABLE %s CHANGE COLUMN %s %s',
838 $qf . $from_field->name . $qf,
839 create_field($to_field, $options));
846 my ($new_field, $options) = @_;
848 my $qt = $options->{quote_table_names} || '';
849 my $table_name = quote_table_name($new_field->table->name, $qt);
851 my $out = sprintf('ALTER TABLE %s ADD COLUMN %s',
853 create_field($new_field, $options));
861 my ($old_field, $options) = @_;
863 my $qf = $options->{quote_field_names} || '';
864 my $qt = $options->{quote_table_names} || '';
865 my $table_name = quote_table_name($old_field->table->name, $qt);
867 my $out = sprintf('ALTER TABLE %s DROP COLUMN %s',
869 $qf . $old_field->name . $qf);
875 sub batch_alter_table {
876 my ($table, $diff_hash, $options) = @_;
878 # InnoDB has an issue with dropping and re-adding a FK constraint under the
879 # name in a single alter statement, see: http://bugs.mysql.com/bug.php?id=13741
881 # We have to work round this.
884 my %fks_to_drop = map {
885 $_->type eq FOREIGN_KEY
888 } @{$diff_hash->{alter_drop_constraint} };
890 my %fks_to_create = map {
891 if ( $_->type eq FOREIGN_KEY) {
892 $fks_to_alter{$_->name} = $fks_to_drop{$_->name} if $fks_to_drop{$_->name};
895 } @{$diff_hash->{alter_create_constraint} };
898 if (scalar keys %fks_to_alter) {
899 $diff_hash->{alter_drop_constraint} = [
900 grep { !$fks_to_alter{$_->name} } @{ $diff_hash->{alter_drop_constraint} }
903 @drop_stmt = batch_alter_table($table, { alter_drop_constraint => [ values %fks_to_alter ] }, $options);
908 if (@{ $diff_hash->{$_} || [] }) {
909 my $meth = __PACKAGE__->can($_) or die __PACKAGE__ . " cant $_";
910 map { $meth->( (ref $_ eq 'ARRAY' ? @$_ : $_), $options ) } @{ $diff_hash->{$_} }
913 alter_drop_constraint
920 alter_create_constraint
924 my $qt = $options->{quote_table_names} || '';
926 # rename_table makes things a bit more complex
927 my $renamed_from = "";
928 $renamed_from = quote_table_name($diff_hash->{rename_table}[0][0]->name, $qt)
929 if $diff_hash->{rename_table} && @{$diff_hash->{rename_table}};
931 return unless @stmts;
932 # Just zero or one stmts. return now
933 return (@drop_stmt,@stmts) unless @stmts > 1;
935 # Now strip off the 'ALTER TABLE xyz' of all but the first one
937 my $table_name = quote_table_name($table->name, $qt);
939 my $re = $renamed_from
940 ? qr/^ALTER TABLE (?:\Q$table_name\E|\Q$renamed_from\E) /
941 : qr/^ALTER TABLE \Q$table_name\E /;
943 my $first = shift @stmts;
944 my ($alter_table) = $first =~ /($re)/;
946 my $padd = " " x length($alter_table);
948 return @drop_stmt, join( ",\n", $first, map { s/$re//; $padd . $_ } @stmts);
953 my ($table, $options) = @_;
955 my $qt = $options->{quote_table_names} || '';
957 # Drop (foreign key) constraints so table drops cleanly
958 my @sql = batch_alter_table($table, { alter_drop_constraint => [ grep { $_->type eq 'FOREIGN KEY' } $table->get_constraints ] }, $options);
960 my $table_name = quote_table_name($table, $qt);
961 return (@sql, "DROP TABLE $table");
966 my ($old_table, $new_table, $options) = @_;
968 my $qt = $options->{quote_table_names} || '';
969 my $old_table_name = quote_table_name($old_table, $qt);
970 my $new_table_name = quote_table_name($new_table, $qt);
972 return "ALTER TABLE $old_table_name RENAME TO $new_table_name";
975 sub next_unused_name {
976 my $name = shift || '';
977 if ( !defined($used_names{$name}) ) {
978 $used_names{$name} = $name;
983 while ( defined($used_names{$name . '_' . $i}) ) {
987 $used_names{$name} = $name;
997 SQL::Translator, http://www.mysql.com/.
1001 darren chamberlain E<lt>darren@cpan.orgE<gt>,
1002 Ken Youens-Clark E<lt>kclark@cpan.orgE<gt>.