1 package SQL::Translator::Producer::MySQL;
3 # -------------------------------------------------------------------
4 # $Id: MySQL.pm,v 1.54 2007-11-10 03:36:43 mwz444 Exp $
5 # -------------------------------------------------------------------
6 # Copyright (C) 2002-4 SQLFairy Authors
8 # This program is free software; you can redistribute it and/or
9 # modify it under the terms of the GNU General Public License as
10 # published by the Free Software Foundation; version 2.
12 # This program is distributed in the hope that it will be useful, but
13 # WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 # General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with this program; if not, write to the Free Software
19 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
21 # -------------------------------------------------------------------
25 SQL::Translator::Producer::MySQL - MySQL-specific producer for SQL::Translator
29 Use via SQL::Translator:
33 my $t = SQL::Translator->new( parser => '...', producer => 'MySQL', '...' );
38 This module will produce text output of the schema suitable for MySQL.
39 There are still some issues to be worked out with syntax differences
40 between MySQL versions 3 and 4 ("SET foreign_key_checks," character sets
45 Normally the tables will be created without any explicit table type given and
46 so will use the MySQL default.
48 Any tables involved in foreign key constraints automatically get a table type
49 of InnoDB, unless this is overridden by setting the C<mysql_table_type> extra
50 attribute explicitly on the table.
52 =head2 Extra attributes.
54 The producer recognises the following extra attributes on the Schema objects.
60 Set the list of allowed values for Enum fields.
62 =item field.binary, field.unsigned, field.zerofill
64 Set the MySQL field options of the same name.
66 =item field.renamed_from
68 Used when producing diffs to say this column is the new name fo the specified
71 =item table.mysql_table_type
73 Set the type of the table e.g. 'InnoDB', 'MyISAM'. This will be
74 automatically set for tables involved in foreign key constraints if it is
75 not already set explicitly. See L<"Table Types">.
77 =item table.mysql_charset, table.mysql_collate
79 Set the tables default charater set and collation order.
81 =item field.mysql_charset, field.mysql_collate
83 Set the fields charater set and collation order.
91 use vars qw[ $VERSION $DEBUG %used_names ];
92 $VERSION = sprintf "%d.%02d", q$Revision: 1.54 $ =~ /(\d+)\.(\d+)/;
93 $DEBUG = 0 unless defined $DEBUG;
96 use SQL::Translator::Schema::Constants;
97 use SQL::Translator::Utils qw(debug header_comment);
100 # Use only lowercase for the keys (e.g. "long" and not "LONG")
106 varchar2 => 'varchar',
122 'long integer' => 'integer',
124 'datetime' => 'datetime',
128 sub preprocess_schema {
129 my ($class, $schema) = @_;
131 # extra->{mysql_table_type} used to be the type. It belongs in options, so
132 # move it if we find it. Return Engine type if found in extra or options
133 my $mysql_table_type_to_options = sub {
136 my $extra = $table->extra;
138 my $extra_type = delete $extra->{mysql_table_type};
140 # Now just to find if there is already an Engine or Type option...
141 # and lets normalize it to ENGINE since:
143 # The ENGINE table option specifies the storage engine for the table.
144 # TYPE is a synonym, but ENGINE is the preferred option name.
147 # We have to use the hash directly here since otherwise there is no way
149 my $options = ( $table->{options} ||= []);
151 # This assumes that there isn't both a Type and an Engine option.
152 for my $idx ( 0..$#{$options} ) {
153 my ($key, $value) = %{ $options->[$idx] };
155 next unless uc $key eq 'ENGINE' || uc $key eq 'TYPE';
157 # if the extra.mysql_table_type is given, use that
158 delete $options->[$idx]{$key};
159 return $options->[$idx]{ENGINE} = $value || $extra_type;
164 push @$options, { ENGINE => $extra_type };
170 # Names are only specific to a given schema
171 local %used_names = ();
174 # Work out which tables need to be InnoDB to support foreign key
175 # constraints. We do this first as we need InnoDB at both ends.
177 foreach my $table ( $schema->get_tables ) {
179 $mysql_table_type_to_options->($table);
181 foreach my $c( $table->get_constraints ) {
182 next unless $c->type eq FOREIGN_KEY;
184 # Normalize constraint names here.
185 my $c_name = $c->name;
186 # Give the constraint a name if it doesn't have one, so it doens't feel
188 $c_name = $table->name . '_fk' unless length $c_name;
190 $c->name( next_unused_name($c_name) );
192 for my $meth (qw/table reference_table/) {
193 my $table = $schema->get_table($c->$meth) || next;
194 next if $mysql_table_type_to_options->($table);
195 $table->options( { 'ENGINE' => 'InnoDB' } );
202 my $translator = shift;
203 local $DEBUG = $translator->debug;
205 my $no_comments = $translator->no_comments;
206 my $add_drop_table = $translator->add_drop_table;
207 my $schema = $translator->schema;
208 my $show_warnings = $translator->show_warnings || 0;
210 my ($qt, $qf) = ('','');
211 $qt = '`' if $translator->quote_table_names;
212 $qf = '`' if $translator->quote_field_names;
214 debug("PKG: Beginning production\n");
217 $create .= header_comment unless ($no_comments);
218 # \todo Don't set if MySQL 3.x is set on command line
219 $create .= "SET foreign_key_checks=0;\n\n";
221 __PACKAGE__->preprocess_schema($schema);
228 for my $table ( $schema->get_tables ) {
229 # print $table->name, "\n";
230 push @table_defs, create_table($table,
231 { add_drop_table => $add_drop_table,
232 show_warnings => $show_warnings,
233 no_comments => $no_comments,
234 quote_table_names => $qt,
235 quote_field_names => $qf
239 # print "@table_defs\n";
240 push @table_defs, "SET foreign_key_checks=1;\n\n";
242 return wantarray ? ($create, @table_defs) : $create . join ('', @table_defs);
247 my ($table, $options) = @_;
249 my $qt = $options->{quote_table_names} || '';
250 my $qf = $options->{quote_field_names} || '';
252 my $table_name = $table->name;
253 debug("PKG: Looking at table '$table_name'\n");
256 # Header. Should this look like what mysqldump produces?
260 $create .= "--\n-- Table: $qt$table_name$qt\n--\n" unless $options->{no_comments};
261 $drop = qq[DROP TABLE IF EXISTS $qt$table_name$qt;\n] if $options->{add_drop_table};
262 $create .= "CREATE TABLE $qt$table_name$qt (\n";
268 for my $field ( $table->get_fields ) {
269 push @field_defs, create_field($field, $options);
277 for my $index ( $table->get_indices ) {
278 push @index_defs, create_index($index, $options);
279 $indexed_fields{ $_ } = 1 for $index->fields;
283 # Constraints -- need to handle more than just FK. -ky
286 my @constraints = $table->get_constraints;
287 for my $c ( @constraints ) {
288 my $constr = create_constraint($c, $options);
289 push @constraint_defs, $constr if($constr);
291 unless ( $indexed_fields{ ($c->fields())[0] } || $c->type ne FOREIGN_KEY ) {
292 push @index_defs, "INDEX ($qf" . ($c->fields())[0] . "$qf)";
293 $indexed_fields{ ($c->fields())[0] } = 1;
297 $create .= join(",\n", map { " $_" }
298 @field_defs, @index_defs, @constraint_defs
305 $create .= generate_table_options($table) || '';
308 return $drop ? ($drop,$create) : $create;
311 sub generate_table_options
316 my $table_type_defined = 0;
317 for my $t1_option_ref ( $table->options ) {
318 my($key, $value) = %{$t1_option_ref};
319 $table_type_defined = 1
320 if uc $key eq 'ENGINE' or uc $key eq 'TYPE';
321 $create .= " $key=$value";
324 my $mysql_table_type = $table->extra('mysql_table_type');
325 $create .= " ENGINE=$mysql_table_type"
326 if $mysql_table_type && !$table_type_defined;
327 my $charset = $table->extra('mysql_charset');
328 my $collate = $table->extra('mysql_collate');
329 my $comments = $table->comments;
331 $create .= " DEFAULT CHARACTER SET $charset" if $charset;
332 $create .= " COLLATE $collate" if $collate;
333 $create .= qq[ comment='$comments'] if $comments;
339 my ($field, $options) = @_;
341 my $qf = $options->{quote_field_names} ||= '';
343 my $field_name = $field->name;
344 debug("PKG: Looking at field '$field_name'\n");
345 my $field_def = "$qf$field_name$qf";
348 my $data_type = $field->data_type;
349 my @size = $field->size;
350 my %extra = $field->extra;
351 my $list = $extra{'list'} || [];
352 # \todo deal with embedded quotes
353 my $commalist = join( ', ', map { qq['$_'] } @$list );
354 my $charset = $extra{'mysql_charset'};
355 my $collate = $extra{'mysql_collate'};
358 # Oracle "number" type -- figure best MySQL type
360 if ( lc $data_type eq 'number' ) {
362 if ( scalar @size > 1 ) {
363 $data_type = 'double';
365 elsif ( $size[0] && $size[0] >= 12 ) {
366 $data_type = 'bigint';
368 elsif ( $size[0] && $size[0] <= 1 ) {
369 $data_type = 'tinyint';
376 # Convert a large Oracle varchar to "text"
378 elsif ( $data_type =~ /char/i && $size[0] > 255 ) {
382 elsif ( $data_type =~ /char/i && ! $size[0] ) {
385 elsif ( $data_type =~ /boolean/i ) {
387 $commalist = "'0','1'";
389 elsif ( exists $translate{ lc $data_type } ) {
390 $data_type = $translate{ lc $data_type };
393 @size = () if $data_type =~ /(text|blob)/i;
395 if ( $data_type =~ /(double|float)/ && scalar @size == 1 ) {
399 $field_def .= " $data_type";
401 if ( lc $data_type eq 'enum' ) {
402 $field_def .= '(' . $commalist . ')';
404 elsif ( defined $size[0] && $size[0] > 0 ) {
405 $field_def .= '(' . join( ', ', @size ) . ')';
409 $field_def .= " CHARACTER SET $charset" if $charset;
410 $field_def .= " COLLATE $collate" if $collate;
413 for my $qual ( qw[ binary unsigned zerofill ] ) {
414 my $val = $extra{ $qual } || $extra{ uc $qual } or next;
415 $field_def .= " $qual";
417 for my $qual ( 'character set', 'collate', 'on update' ) {
418 my $val = $extra{ $qual } || $extra{ uc $qual } or next;
419 $field_def .= " $qual $val";
423 $field_def .= ' NOT NULL' unless $field->is_nullable;
425 # Default? XXX Need better quoting!
426 my $default = $field->default_value;
427 if ( defined $default ) {
428 if ( uc $default eq 'NULL') {
429 $field_def .= ' DEFAULT NULL';
431 $field_def .= " DEFAULT '$default'";
435 if ( my $comments = $field->comments ) {
436 $field_def .= qq[ comment '$comments'];
440 $field_def .= " auto_increment" if $field->is_auto_increment;
445 sub alter_create_index
447 my ($index, $options) = @_;
449 my $qt = $options->{quote_table_names} || '';
450 my $qf = $options->{quote_field_names} || '';
454 $qt.$index->table->name.$qt,
462 my ($index, $options) = @_;
464 my $qf = $options->{quote_field_names} || '';
467 lc $index->type eq 'normal' ? 'INDEX' : $index->type . ' INDEX',
469 '(' . $qf . join( "$qf, $qf", $index->fields ) . $qf . ')'
476 my ($index, $options) = @_;
478 my $qt = $options->{quote_table_names} || '';
479 my $qf = $options->{quote_field_names} || '';
483 $qt.$index->table->name.$qt,
486 $index->name || $index->fields
491 sub alter_drop_constraint
493 my ($c, $options) = @_;
495 my $qt = $options->{quote_table_names} || '';
496 my $qc = $options->{quote_constraint_names} || '';
498 my $out = sprintf('ALTER TABLE %s DROP %s %s',
501 $qc . $c->name . $qc );
506 sub alter_create_constraint
508 my ($index, $options) = @_;
510 my $qt = $options->{quote_table_names} || '';
513 $qt.$index->table->name.$qt,
515 create_constraint(@_) );
518 sub create_constraint
520 my ($c, $options) = @_;
522 my $qf = $options->{quote_field_names} || '';
523 my $qt = $options->{quote_table_names} || '';
524 my $leave_name = $options->{leave_name} || undef;
526 my @fields = $c->fields or next;
528 if ( $c->type eq PRIMARY_KEY ) {
529 return 'PRIMARY KEY (' . $qf . join("$qf, $qf", @fields). $qf . ')';
531 elsif ( $c->type eq UNIQUE ) {
534 (defined $c->name ? $qf.$c->name.$qf.' ' : '').
535 '(' . $qf . join("$qf, $qf", @fields). $qf . ')';
537 elsif ( $c->type eq FOREIGN_KEY ) {
539 # Make sure FK field is indexed or MySQL complains.
542 my $table = $c->table;
543 my $c_name = $c->name;
553 $def .= ' ('.$qf . join( "$qf, $qf", @fields ) . $qf . ')';
555 $def .= ' REFERENCES ' . $qt . $c->reference_table . $qt;
557 my @rfields = map { $_ || () } $c->reference_fields;
558 unless ( @rfields ) {
559 my $rtable_name = $c->reference_table;
560 if ( my $ref_table = $table->schema->get_table( $rtable_name ) ) {
561 push @rfields, $ref_table->primary_key;
564 warn "Can't find reference table '$rtable_name' " .
565 "in schema\n" if $options->{show_warnings};
570 $def .= ' (' . $qf . join( "$qf, $qf", @rfields ) . $qf . ')';
573 warn "FK constraint on " . $table->name . '.' .
574 join('', @fields) . " has no reference fields\n"
575 if $options->{show_warnings};
578 if ( $c->match_type ) {
580 ( $c->match_type =~ /full/i ) ? 'FULL' : 'PARTIAL';
583 if ( $c->on_delete ) {
584 $def .= ' ON DELETE '.join( ' ', $c->on_delete );
587 if ( $c->on_update ) {
588 $def .= ' ON UPDATE '.join( ' ', $c->on_update );
598 my ($to_table, $options) = @_;
600 my $qt = $options->{quote_table_name} || '';
602 my $table_options = generate_table_options($to_table) || '';
603 my $out = sprintf('ALTER TABLE %s%s',
604 $qt . $to_table->name . $qt,
610 sub rename_field { alter_field(@_) }
613 my ($from_field, $to_field, $options) = @_;
615 my $qf = $options->{quote_field_name} || '';
616 my $qt = $options->{quote_table_name} || '';
618 my $out = sprintf('ALTER TABLE %s CHANGE COLUMN %s %s',
619 $qt . $to_field->table->name . $qt,
620 $qf . $from_field->name . $qf,
621 create_field($to_field, $options));
628 my ($new_field, $options) = @_;
630 my $qt = $options->{quote_table_name} || '';
632 my $out = sprintf('ALTER TABLE %s ADD COLUMN %s',
633 $qt . $new_field->table->name . $qt,
634 create_field($new_field, $options));
642 my ($old_field, $options) = @_;
644 my $qf = $options->{quote_field_name} || '';
645 my $qt = $options->{quote_table_name} || '';
647 my $out = sprintf('ALTER TABLE %s DROP COLUMN %s',
648 $qt . $old_field->table->name . $qt,
649 $qf . $old_field->name . $qf);
655 sub batch_alter_table {
656 my ($table, $diff_hash, $options) = @_;
659 if (@{ $diff_hash->{$_} || [] }) {
660 my $meth = __PACKAGE__->can($_) or die __PACKAGE__ . " cant $_";
661 map { $meth->(ref $_ eq 'ARRAY' ? @$_ : $_) } @{ $diff_hash->{$_} }
663 } qw/alter_drop_constraint
670 alter_create_constraint
673 return unless @stmts;
674 # Just zero or one stmts. return now
675 return "@stmts;" unless @stmts > 1;
677 # Now strip off the 'ALTER TABLE xyz' of all but the first one
679 my $qt = $options->{quote_table_name} || '';
680 my $table_name = $qt . $table->name . $qt;
682 my $first = shift @stmts;
683 my ($alter_table) = $first =~ /^(ALTER TABLE \Q$table_name\E )/;
684 my $re = qr/^$alter_table/;
685 my $padd = " " x length($alter_table);
687 return join( ",\n", $first, map { s/$re//; $padd . $_ } @stmts) . ';';
693 # Drop (foreign key) constraints so table drops cleanly
694 my @sql = batch_alter_table($table, { alter_drop_constraint => [ grep { $_->type eq 'FOREIGN KEY' } $table->get_constraints ] });
696 return join("\n", @sql, "DROP TABLE $table;");
700 sub next_unused_name {
701 my $name = shift || '';
702 if ( !defined($used_names{$name}) ) {
703 $used_names{$name} = $name;
708 while ( defined($used_names{$name . '_' . $i}) ) {
712 $used_names{$name} = $name;
718 # -------------------------------------------------------------------
724 SQL::Translator, http://www.mysql.com/.
728 darren chamberlain E<lt>darren@cpan.orgE<gt>,
729 Ken Y. Clark E<lt>kclark@cpan.orgE<gt>.