1 package SQL::Translator::Producer::MySQL;
3 # -------------------------------------------------------------------
4 # $Id: MySQL.pm,v 1.52 2006-11-27 19:28:04 schiffbruechige 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 table.mysql_table_type
68 Set the type of the table e.g. 'InnoDB', 'MyISAM'. This will be
69 automatically set for tables involved in foreign key constraints if it is
70 not already set explicitly. See L<"Table Types">.
72 =item mysql_character_set
74 MySql-4.1+. Set the tables character set.
75 Run SHOW CHARACTER SET to see list.
79 MySql-4.1+. Set the tables colation order.
81 =item table.mysql_charset, table.mysql_collate
83 Set the tables default charater set and collation order.
85 =item field.mysql_charset, field.mysql_collate
87 Set the fields charater set and collation order.
95 use vars qw[ $VERSION $DEBUG ];
96 $VERSION = sprintf "%d.%02d", q$Revision: 1.52 $ =~ /(\d+)\.(\d+)/;
97 $DEBUG = 0 unless defined $DEBUG;
100 use SQL::Translator::Schema::Constants;
101 use SQL::Translator::Utils qw(debug header_comment);
104 # Use only lowercase for the keys (e.g. "long" and not "LONG")
110 varchar2 => 'varchar',
126 'long integer' => 'integer',
128 'datetime' => 'datetime',
132 my $translator = shift;
133 local $DEBUG = $translator->debug;
134 my $no_comments = $translator->no_comments;
135 my $add_drop_table = $translator->add_drop_table;
136 my $schema = $translator->schema;
137 my $show_warnings = $translator->show_warnings || 0;
139 my ($qt, $qf) = ('','');
140 $qt = '`' if $translator->quote_table_names;
141 $qf = '`' if $translator->quote_field_names;
143 debug("PKG: Beginning production\n");
146 $create .= header_comment unless ($no_comments);
147 # \todo Don't set if MySQL 3.x is set on command line
148 $create .= "SET foreign_key_checks=0;\n\n";
151 # Work out which tables need to be InnoDB to support foreign key
152 # constraints. We do this first as we need InnoDB at both ends.
154 foreach ( map { $_->get_constraints } $schema->get_tables ) {
155 next unless $_->type eq FOREIGN_KEY;
156 foreach my $meth (qw/table reference_table/) {
157 my $table = $schema->get_table($_->$meth) || next;
158 next if $table->extra('mysql_table_type');
159 $table->extra( 'mysql_table_type' => 'InnoDB');
167 for my $table ( $schema->get_tables ) {
168 # print $table->name, "\n";
169 push @table_defs, create_table($table,
170 { add_drop_table => $add_drop_table,
171 show_warnings => $show_warnings,
172 no_comments => $no_comments,
173 quote_table_names => $qt,
174 quote_field_names => $qf
178 # print "@table_defs\n";
179 push @table_defs, "SET foreign_key_checks=1;\n\n";
181 return wantarray ? ($create, @table_defs) : $create . join ('', @table_defs);
186 my ($table, $options) = @_;
188 my $qt = $options->{quote_table_names} || '';
189 my $qf = $options->{quote_field_names} || '';
191 my $table_name = $table->name;
192 debug("PKG: Looking at table '$table_name'\n");
195 # Header. Should this look like what mysqldump produces?
199 $create .= "--\n-- Table: $qt$table_name$qt\n--\n" unless $options->{no_comments};
200 $drop = qq[DROP TABLE IF EXISTS $qt$table_name$qt;\n] if $options->{add_drop_table};
201 $create .= "CREATE TABLE $qt$table_name$qt (\n";
207 for my $field ( $table->get_fields ) {
208 push @field_defs, create_field($field, $options);
216 for my $index ( $table->get_indices ) {
217 push @index_defs, create_index($index, $options);
218 $indexed_fields{ $_ } = 1 for $index->fields;
222 # Constraints -- need to handle more than just FK. -ky
225 my @constraints = $table->get_constraints;
226 for my $c ( @constraints ) {
227 my $constr = create_constraint($c, $options);
228 push @constraint_defs, $constr if($constr);
230 unless ( $indexed_fields{ ($c->fields())[0] } ) {
231 push @index_defs, "INDEX ($qf" . ($c->fields())[0] . "$qf)";
232 $indexed_fields{ ($c->fields())[0] } = 1;
236 $create .= join(",\n", map { " $_" }
237 @field_defs, @index_defs, @constraint_defs
244 my $table_type_defined = 0;
245 for my $t1_option_ref ( $table->options ) {
246 my($key, $value) = %{$t1_option_ref};
247 $table_type_defined = 1
248 if uc $key eq 'ENGINE' or uc $key eq 'TYPE';
249 $create .= " $key=$value";
251 my $mysql_table_type = $table->extra('mysql_table_type');
252 #my $charset = $table->extra('mysql_character_set');
253 #my $collate = $table->extra('mysql_collate');
254 #$create .= " Type=$mysql_table_type" if $mysql_table_type;
255 #$create .= " DEFAULT CHARACTER SET $charset" if $charset;
256 #$create .= " COLLATE $collate" if $collate;
257 $create .= " Type=$mysql_table_type"
258 if $mysql_table_type && !$table_type_defined;
259 my $charset = $table->extra('mysql_charset');
260 my $collate = $table->extra('mysql_collate');
261 my $comments = $table->comments;
263 $create .= " DEFAULT CHARACTER SET $charset" if $charset;
264 $create .= " COLLATE $collate" if $collate;
265 $create .= qq[ comment='$comments'] if $comments;
268 return $drop ? ($drop,$create) : $create;
273 my ($field, $options) = @_;
275 my $qf = $options->{quote_field_names} ||= '';
277 my $field_name = $field->name;
278 debug("PKG: Looking at field '$field_name'\n");
279 my $field_def = "$qf$field_name$qf";
282 my $data_type = $field->data_type;
283 my @size = $field->size;
284 my %extra = $field->extra;
285 my $list = $extra{'list'} || [];
286 # \todo deal with embedded quotes
287 my $commalist = join( ', ', map { qq['$_'] } @$list );
288 my $charset = $extra{'mysql_charset'};
289 my $collate = $extra{'mysql_collate'};
292 # Oracle "number" type -- figure best MySQL type
294 if ( lc $data_type eq 'number' ) {
296 if ( scalar @size > 1 ) {
297 $data_type = 'double';
299 elsif ( $size[0] && $size[0] >= 12 ) {
300 $data_type = 'bigint';
302 elsif ( $size[0] && $size[0] <= 1 ) {
303 $data_type = 'tinyint';
310 # Convert a large Oracle varchar to "text"
312 elsif ( $data_type =~ /char/i && $size[0] > 255 ) {
316 elsif ( $data_type =~ /char/i && ! $size[0] ) {
319 elsif ( $data_type =~ /boolean/i ) {
321 $commalist = "'0','1'";
323 elsif ( exists $translate{ lc $data_type } ) {
324 $data_type = $translate{ lc $data_type };
327 @size = () if $data_type =~ /(text|blob)/i;
329 if ( $data_type =~ /(double|float)/ && scalar @size == 1 ) {
333 $field_def .= " $data_type";
335 if ( lc $data_type eq 'enum' ) {
336 $field_def .= '(' . $commalist . ')';
338 elsif ( defined $size[0] && $size[0] > 0 ) {
339 $field_def .= '(' . join( ', ', @size ) . ')';
343 $field_def .= " CHARACTER SET $charset" if $charset;
344 $field_def .= " COLLATE $collate" if $collate;
347 for my $qual ( qw[ binary unsigned zerofill ] ) {
348 my $val = $extra{ $qual } || $extra{ uc $qual } or next;
349 $field_def .= " $qual";
351 for my $qual ( 'character set', 'collate', 'on update' ) {
352 my $val = $extra{ $qual } || $extra{ uc $qual } or next;
353 $field_def .= " $qual $val";
357 $field_def .= ' NOT NULL' unless $field->is_nullable;
359 # Default? XXX Need better quoting!
360 my $default = $field->default_value;
361 if ( defined $default ) {
362 if ( uc $default eq 'NULL') {
363 $field_def .= ' DEFAULT NULL';
365 $field_def .= " DEFAULT '$default'";
369 if ( my $comments = $field->comments ) {
370 $field_def .= qq[ comment '$comments'];
374 $field_def .= " auto_increment" if $field->is_auto_increment;
381 my ($index, $options) = @_;
383 my $qf = $options->{quote_field_names} || '';
386 lc $index->type eq 'normal' ? 'INDEX' : $index->type,
388 '(' . $qf . join( "$qf, $qf", $index->fields ) . $qf . ')'
393 sub create_constraint
395 my ($c, $options) = @_;
397 my $qf = $options->{quote_field_names} || '';
398 my $qt = $options->{quote_table_names} || '';
399 my $counter = ($options->{fk_name_counter} ||= {});
401 my @fields = $c->fields or next;
403 if ( $c->type eq PRIMARY_KEY ) {
404 return 'PRIMARY KEY (' . $qf . join("$qf, $qf", @fields). $qf . ')';
406 elsif ( $c->type eq UNIQUE ) {
409 (defined $c->name ? $qf.$c->name.$qf.' ' : '').
410 '(' . $qf . join("$qf, $qf", @fields). $qf . ')';
412 elsif ( $c->type eq FOREIGN_KEY ) {
414 # Make sure FK field is indexed or MySQL complains.
417 $counter->{$c->table} ||= {};
421 $qt . join('_', $c->table,
423 ($counter->{$c->table}{$c->name}++ || ())
428 $def .= ' ('.$qf . join( "$qf, $qf", @fields ) . $qf . ')';
430 $def .= ' REFERENCES ' . $qt . $c->reference_table . $qt;
432 my @rfields = map { $_ || () } $c->reference_fields;
433 unless ( @rfields ) {
434 my $rtable_name = $c->reference_table;
435 if ( my $ref_table = $c->table->schema->get_table( $rtable_name ) ) {
436 push @rfields, $ref_table->primary_key;
439 warn "Can't find reference table '$rtable_name' " .
440 "in schema\n" if $options->{show_warnings};
445 $def .= ' (' . $qf . join( "$qf, $qf", @rfields ) . $qf . ')';
448 warn "FK constraint on " . $c->table->name . '.' .
449 join('', @fields) . " has no reference fields\n"
450 if $options->{show_warnings};
453 if ( $c->match_type ) {
455 ( $c->match_type =~ /full/i ) ? 'FULL' : 'PARTIAL';
458 if ( $c->on_delete ) {
459 $def .= ' ON DELETE '.join( ' ', $c->on_delete );
462 if ( $c->on_update ) {
463 $def .= ' ON UPDATE '.join( ' ', $c->on_update );
473 my ($from_field, $to_field, $options) = @_;
475 my $qf = $options->{quote_field_name} || '';
476 my $qt = $options->{quote_table_name} || '';
478 my $out = sprintf('ALTER TABLE %s CHANGE COLUMN %s %s',
479 $qt . $to_field->table->name . $qt,
480 $qf . $to_field->name . $qf,
481 create_field($to_field, $options));
488 my ($new_field, $options) = @_;
490 my $qt = $options->{quote_table_name} || '';
492 my $out = sprintf('ALTER TABLE %s ADD COLUMN %s',
493 $qt . $new_field->table->name . $qt,
494 create_field($new_field, $options));
502 my ($old_field, $options) = @_;
504 my $qf = $options->{quote_field_name} || '';
505 my $qt = $options->{quote_table_name} || '';
507 my $out = sprintf('ALTER TABLE %s DROP COLUMN %s',
508 $qt . $old_field->table->name . $qt,
509 $qf . $old_field->name . $qf);
517 # -------------------------------------------------------------------
523 SQL::Translator, http://www.mysql.com/.
527 darren chamberlain E<lt>darren@cpan.orgE<gt>,
528 Ken Y. Clark E<lt>kclark@cpan.orgE<gt>.