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 my $translator = shift;
129 local $DEBUG = $translator->debug;
131 my $no_comments = $translator->no_comments;
132 my $add_drop_table = $translator->add_drop_table;
133 my $schema = $translator->schema;
134 my $show_warnings = $translator->show_warnings || 0;
136 my ($qt, $qf) = ('','');
137 $qt = '`' if $translator->quote_table_names;
138 $qf = '`' if $translator->quote_field_names;
140 debug("PKG: Beginning production\n");
143 $create .= header_comment unless ($no_comments);
144 # \todo Don't set if MySQL 3.x is set on command line
145 $create .= "SET foreign_key_checks=0;\n\n";
148 # Work out which tables need to be InnoDB to support foreign key
149 # constraints. We do this first as we need InnoDB at both ends.
151 foreach ( map { $_->get_constraints } $schema->get_tables ) {
152 next unless $_->type eq FOREIGN_KEY;
153 foreach my $meth (qw/table reference_table/) {
154 my $table = $schema->get_table($_->$meth) || next;
155 next if $table->extra('mysql_table_type');
156 $table->extra( 'mysql_table_type' => 'InnoDB');
165 for my $table ( $schema->get_tables ) {
166 # print $table->name, "\n";
167 push @table_defs, create_table($table,
168 { add_drop_table => $add_drop_table,
169 show_warnings => $show_warnings,
170 no_comments => $no_comments,
171 quote_table_names => $qt,
172 quote_field_names => $qf
176 # print "@table_defs\n";
177 push @table_defs, "SET foreign_key_checks=1;\n\n";
179 return wantarray ? ($create, @table_defs) : $create . join ('', @table_defs);
184 my ($table, $options) = @_;
186 my $qt = $options->{quote_table_names} || '';
187 my $qf = $options->{quote_field_names} || '';
189 my $table_name = $table->name;
190 debug("PKG: Looking at table '$table_name'\n");
193 # Header. Should this look like what mysqldump produces?
197 $create .= "--\n-- Table: $qt$table_name$qt\n--\n" unless $options->{no_comments};
198 $drop = qq[DROP TABLE IF EXISTS $qt$table_name$qt;\n] if $options->{add_drop_table};
199 $create .= "CREATE TABLE $qt$table_name$qt (\n";
205 for my $field ( $table->get_fields ) {
206 push @field_defs, create_field($field, $options);
214 for my $index ( $table->get_indices ) {
215 push @index_defs, create_index($index, $options);
216 $indexed_fields{ $_ } = 1 for $index->fields;
220 # Constraints -- need to handle more than just FK. -ky
223 my @constraints = $table->get_constraints;
224 for my $c ( @constraints ) {
225 my $constr = create_constraint($c, $options);
226 push @constraint_defs, $constr if($constr);
228 unless ( $indexed_fields{ ($c->fields())[0] } || $c->type ne FOREIGN_KEY ) {
229 push @index_defs, "INDEX ($qf" . ($c->fields())[0] . "$qf)";
230 $indexed_fields{ ($c->fields())[0] } = 1;
234 $create .= join(",\n", map { " $_" }
235 @field_defs, @index_defs, @constraint_defs
242 $create .= generate_table_options($table) || '';
245 return $drop ? ($drop,$create) : $create;
248 sub generate_table_options
253 my $table_type_defined = 0;
254 for my $t1_option_ref ( $table->options ) {
255 my($key, $value) = %{$t1_option_ref};
256 $table_type_defined = 1
257 if uc $key eq 'ENGINE' or uc $key eq 'TYPE';
258 $create .= " $key=$value";
260 my $mysql_table_type = $table->extra('mysql_table_type');
261 $create .= " Type=$mysql_table_type"
262 if $mysql_table_type && !$table_type_defined;
263 my $charset = $table->extra('mysql_charset');
264 my $collate = $table->extra('mysql_collate');
265 my $comments = $table->comments;
267 $create .= " DEFAULT CHARACTER SET $charset" if $charset;
268 $create .= " COLLATE $collate" if $collate;
269 $create .= qq[ comment='$comments'] if $comments;
275 my ($field, $options) = @_;
277 my $qf = $options->{quote_field_names} ||= '';
279 my $field_name = $field->name;
280 debug("PKG: Looking at field '$field_name'\n");
281 my $field_def = "$qf$field_name$qf";
284 my $data_type = $field->data_type;
285 my @size = $field->size;
286 my %extra = $field->extra;
287 my $list = $extra{'list'} || [];
288 # \todo deal with embedded quotes
289 my $commalist = join( ', ', map { qq['$_'] } @$list );
290 my $charset = $extra{'mysql_charset'};
291 my $collate = $extra{'mysql_collate'};
294 # Oracle "number" type -- figure best MySQL type
296 if ( lc $data_type eq 'number' ) {
298 if ( scalar @size > 1 ) {
299 $data_type = 'double';
301 elsif ( $size[0] && $size[0] >= 12 ) {
302 $data_type = 'bigint';
304 elsif ( $size[0] && $size[0] <= 1 ) {
305 $data_type = 'tinyint';
312 # Convert a large Oracle varchar to "text"
314 elsif ( $data_type =~ /char/i && $size[0] > 255 ) {
318 elsif ( $data_type =~ /char/i && ! $size[0] ) {
321 elsif ( $data_type =~ /boolean/i ) {
323 $commalist = "'0','1'";
325 elsif ( exists $translate{ lc $data_type } ) {
326 $data_type = $translate{ lc $data_type };
329 @size = () if $data_type =~ /(text|blob)/i;
331 if ( $data_type =~ /(double|float)/ && scalar @size == 1 ) {
335 $field_def .= " $data_type";
337 if ( lc $data_type eq 'enum' ) {
338 $field_def .= '(' . $commalist . ')';
340 elsif ( defined $size[0] && $size[0] > 0 ) {
341 $field_def .= '(' . join( ', ', @size ) . ')';
345 $field_def .= " CHARACTER SET $charset" if $charset;
346 $field_def .= " COLLATE $collate" if $collate;
349 for my $qual ( qw[ binary unsigned zerofill ] ) {
350 my $val = $extra{ $qual } || $extra{ uc $qual } or next;
351 $field_def .= " $qual";
353 for my $qual ( 'character set', 'collate', 'on update' ) {
354 my $val = $extra{ $qual } || $extra{ uc $qual } or next;
355 $field_def .= " $qual $val";
359 $field_def .= ' NOT NULL' unless $field->is_nullable;
361 # Default? XXX Need better quoting!
362 my $default = $field->default_value;
363 if ( defined $default ) {
364 if ( uc $default eq 'NULL') {
365 $field_def .= ' DEFAULT NULL';
367 $field_def .= " DEFAULT '$default'";
371 if ( my $comments = $field->comments ) {
372 $field_def .= qq[ comment '$comments'];
376 $field_def .= " auto_increment" if $field->is_auto_increment;
381 sub alter_create_index
383 my ($index, $options) = @_;
385 my $qt = $options->{quote_table_names} || '';
386 my $qf = $options->{quote_field_names} || '';
390 $qt.$index->table->name.$qt,
398 my ($index, $options) = @_;
400 my $qf = $options->{quote_field_names} || '';
403 lc $index->type eq 'normal' ? 'INDEX' : $index->type . ' INDEX',
405 '(' . $qf . join( "$qf, $qf", $index->fields ) . $qf . ')'
412 my ($index, $options) = @_;
414 my $qt = $options->{quote_table_names} || '';
415 my $qf = $options->{quote_field_names} || '';
419 $qt.$index->table->name.$qt,
422 $index->name || $index->fields
427 sub alter_drop_constraint
429 my ($c, $options) = @_;
431 my $qt = $options->{quote_table_names} || '';
432 my $qc = $options->{quote_constraint_names} || '';
434 my $out = sprintf('ALTER TABLE %s DROP %s %s',
437 $qc . $c->name . $qc );
442 sub alter_create_constraint
444 my ($index, $options) = @_;
446 my $qt = $options->{quote_table_names} || '';
449 $qt.$index->table->name.$qt,
451 create_constraint(@_) );
454 sub create_constraint
456 my ($c, $options) = @_;
458 my $qf = $options->{quote_field_names} || '';
459 my $qt = $options->{quote_table_names} || '';
460 my $leave_name = $options->{leave_name} || undef;
461 my $counter = ($options->{fk_name_counter} ||= {});
463 my @fields = $c->fields or next;
465 if ( $c->type eq PRIMARY_KEY ) {
466 return 'PRIMARY KEY (' . $qf . join("$qf, $qf", @fields). $qf . ')';
468 elsif ( $c->type eq UNIQUE ) {
471 (defined $c->name ? $qf.$c->name.$qf.' ' : '').
472 '(' . $qf . join("$qf, $qf", @fields). $qf . ')';
474 elsif ( $c->type eq FOREIGN_KEY ) {
476 # Make sure FK field is indexed or MySQL complains.
479 my $table = $c->table;
480 my $c_name = $c->name;
482 # Give the constraint a name if it doesn't have one, so it doens't feel
485 $c_name = $table->name . '_fk';
488 $counter->{$table} ||= {};
492 $qt . join('_', next_unused_name($c_name)
498 $def .= ' ('.$qf . join( "$qf, $qf", @fields ) . $qf . ')';
500 $def .= ' REFERENCES ' . $qt . $c->reference_table . $qt;
502 my @rfields = map { $_ || () } $c->reference_fields;
503 unless ( @rfields ) {
504 my $rtable_name = $c->reference_table;
505 if ( my $ref_table = $table->schema->get_table( $rtable_name ) ) {
506 push @rfields, $ref_table->primary_key;
509 warn "Can't find reference table '$rtable_name' " .
510 "in schema\n" if $options->{show_warnings};
515 $def .= ' (' . $qf . join( "$qf, $qf", @rfields ) . $qf . ')';
518 warn "FK constraint on " . $table->name . '.' .
519 join('', @fields) . " has no reference fields\n"
520 if $options->{show_warnings};
523 if ( $c->match_type ) {
525 ( $c->match_type =~ /full/i ) ? 'FULL' : 'PARTIAL';
528 if ( $c->on_delete ) {
529 $def .= ' ON DELETE '.join( ' ', $c->on_delete );
532 if ( $c->on_update ) {
533 $def .= ' ON UPDATE '.join( ' ', $c->on_update );
543 my ($to_table, $options) = @_;
545 my $qt = $options->{quote_table_name} || '';
547 my $table_options = generate_table_options($to_table) || '';
548 my $out = sprintf('ALTER TABLE %s%s',
549 $qt . $to_table->name . $qt,
555 sub rename_field { alter_field(@_) }
558 my ($from_field, $to_field, $options) = @_;
560 my $qf = $options->{quote_field_name} || '';
561 my $qt = $options->{quote_table_name} || '';
563 my $out = sprintf('ALTER TABLE %s CHANGE COLUMN %s %s',
564 $qt . $to_field->table->name . $qt,
565 $qf . $from_field->name . $qf,
566 create_field($to_field, $options));
573 my ($new_field, $options) = @_;
575 my $qt = $options->{quote_table_name} || '';
577 my $out = sprintf('ALTER TABLE %s ADD COLUMN %s',
578 $qt . $new_field->table->name . $qt,
579 create_field($new_field, $options));
587 my ($old_field, $options) = @_;
589 my $qf = $options->{quote_field_name} || '';
590 my $qt = $options->{quote_table_name} || '';
592 my $out = sprintf('ALTER TABLE %s DROP COLUMN %s',
593 $qt . $old_field->table->name . $qt,
594 $qf . $old_field->name . $qf);
600 sub batch_alter_table {
601 my ($table, $diff_hash, $options) = @_;
604 if (@{ $diff_hash->{$_} || [] }) {
605 my $meth = __PACKAGE__->can($_) or die __PACKAGE__ . " cant $_";
606 map { $meth->(ref $_ eq 'ARRAY' ? @$_ : $_) } @{ $diff_hash->{$_} }
608 } qw/alter_drop_constraint
615 alter_create_constraint
618 return unless @stmts;
619 # Just zero or one stmts. return now
620 return "@stmts;" unless @stmts > 1;
622 # Now strip off the 'ALTER TABLE xyz' of all but the first one
624 my $qt = $options->{quote_table_name} || '';
625 my $table_name = $qt . $table->name . $qt;
627 my $first = shift @stmts;
628 my ($alter_table) = $first =~ /^(ALTER TABLE \Q$table_name\E )/;
629 my $re = qr/^$alter_table/;
630 my $padd = " " x length($alter_table);
632 return join( ",\n", $first, map { s/$re//; $padd . $_ } @stmts) . ';';
638 # Drop (foreign key) constraints so table drops cleanly
640 my @sql = batch_alter_table($table, { alter_drop_constraint => [ grep { $_->type eq 'FOREIGN KEY' } $table->get_constraints ] });
642 return join("\n", @sql, "DROP TABLE $table;");
646 sub next_unused_name {
647 my $name = shift || '';
648 if ( !defined($used_names{$name}) ) {
649 $used_names{$name} = $name;
654 while ( defined($used_names{$name . '_' . $i}) ) {
658 $used_names{$name} = $name;
664 # -------------------------------------------------------------------
670 SQL::Translator, http://www.mysql.com/.
674 darren chamberlain E<lt>darren@cpan.orgE<gt>,
675 Ken Y. Clark E<lt>kclark@cpan.orgE<gt>.