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;
93 use base qw(SQL::Translator::Producer);
95 use SQL::Translator::Schema::Constants;
96 use SQL::Translator::Generator::DDL::MySQL;
97 use SQL::Translator::Utils qw(debug header_comment
98 truncate_id_uniquely parse_mysql_version
99 batch_alter_table_statements
100 normalize_quote_options
104 # Use only lowercase for the keys (e.g. "long" and not "LONG")
110 varchar2 => 'varchar',
126 'long integer' => 'integer',
128 'datetime' => 'datetime',
137 # Column types that do not support length attribute
139 my @no_length_attr = qw/
140 date time timestamp datetime year
144 sub preprocess_schema {
147 # extra->{mysql_table_type} used to be the type. It belongs in options, so
148 # move it if we find it. Return Engine type if found in extra or options
149 # Similarly for mysql_charset and mysql_collate
150 my $extra_to_options = sub {
151 my ($table, $extra_name, $opt_name) = @_;
153 my $extra = $table->extra;
155 my $extra_type = delete $extra->{$extra_name};
157 # Now just to find if there is already an Engine or Type option...
158 # and lets normalize it to ENGINE since:
160 # The ENGINE table option specifies the storage engine for the table.
161 # TYPE is a synonym, but ENGINE is the preferred option name.
164 my $options = $table->options;
166 # If multiple option names, normalize to the first one
168 OPT_NAME: for ( @$opt_name[1..$#$opt_name] ) {
169 for my $idx ( 0..$#{$options} ) {
170 my ($key, $value) = %{ $options->[$idx] };
173 $options->[$idx] = { $opt_name->[0] => $value };
178 $opt_name = $opt_name->[0];
183 # This assumes that there isn't both a Type and an Engine option.
185 for my $idx ( 0..$#{$options} ) {
186 my ($key, $value) = %{ $options->[$idx] };
188 next unless uc $key eq $opt_name;
190 # make sure case is right on option name
191 delete $options->[$idx]{$key};
192 return $options->[$idx]{$opt_name} = $value || $extra_type;
197 push @$options, { $opt_name => $extra_type };
203 # Names are only specific to a given schema
204 local %used_names = ();
207 # Work out which tables need to be InnoDB to support foreign key
208 # constraints. We do this first as we need InnoDB at both ends.
210 foreach my $table ( $schema->get_tables ) {
212 $extra_to_options->($table, 'mysql_table_type', ['ENGINE', 'TYPE'] );
213 $extra_to_options->($table, 'mysql_charset', 'CHARACTER SET' );
214 $extra_to_options->($table, 'mysql_collate', 'COLLATE' );
216 foreach my $c ( $table->get_constraints ) {
217 next unless $c->type eq FOREIGN_KEY;
219 # Normalize constraint names here.
220 my $c_name = $c->name;
221 # Give the constraint a name if it doesn't have one, so it doesn't feel
223 $c_name = $table->name . '_fk' unless length $c_name;
225 $c->name( next_unused_name($c_name) );
227 for my $meth (qw/table reference_table/) {
228 my $table = $schema->get_table($c->$meth) || next;
229 # This normalizes the types to ENGINE and returns the value if its there
230 next if $extra_to_options->($table, 'mysql_table_type', ['ENGINE', 'TYPE']);
231 $table->options( { 'ENGINE' => 'InnoDB' } );
233 } # foreach constraints
235 my %map = ( mysql_collate => 'collate', mysql_charset => 'character set');
236 foreach my $f ( $table->get_fields ) {
237 my $extra = $f->extra;
239 $extra->{$map{$_}} = delete $extra->{$_} if exists $extra->{$_};
243 if ( !$size[0] && $f->data_type =~ /char$/ ) {
252 my ($quoting_generator, $nonquoting_generator);
255 return $options->{generator} if exists $options->{generator};
257 return normalize_quote_options($options)
258 ? $quoting_generator ||= SQL::Translator::Generator::DDL::MySQL->new()
259 : $nonquoting_generator ||= SQL::Translator::Generator::DDL::MySQL->new(
266 my $translator = shift;
267 local $DEBUG = $translator->debug;
269 my $no_comments = $translator->no_comments;
270 my $add_drop_table = $translator->add_drop_table;
271 my $schema = $translator->schema;
272 my $show_warnings = $translator->show_warnings || 0;
273 my $producer_args = $translator->producer_args;
274 my $mysql_version = parse_mysql_version ($producer_args->{mysql_version}, 'perl') || 0;
275 my $max_id_length = $producer_args->{mysql_max_id_length} || $DEFAULT_MAX_ID_LENGTH;
277 my $generator = _generator({ quote_identifiers => $translator->quote_identifiers });
279 debug("PKG: Beginning production\n");
282 $create .= header_comment unless ($no_comments);
283 # \todo Don't set if MySQL 3.x is set on command line
284 my @create = "SET foreign_key_checks=0";
286 preprocess_schema($schema);
293 for my $table ( $schema->get_tables ) {
294 # print $table->name, "\n";
295 push @table_defs, create_table($table,
296 { add_drop_table => $add_drop_table,
297 show_warnings => $show_warnings,
298 no_comments => $no_comments,
299 generator => $generator,
300 max_id_length => $max_id_length,
301 mysql_version => $mysql_version
305 if ($mysql_version >= 5.000001) {
306 for my $view ( $schema->get_views ) {
307 push @table_defs, create_view($view,
308 { add_replace_view => $add_drop_table,
309 show_warnings => $show_warnings,
310 no_comments => $no_comments,
311 generator => $generator,
312 max_id_length => $max_id_length,
313 mysql_version => $mysql_version
318 if ($mysql_version >= 5.000002) {
319 for my $trigger ( $schema->get_triggers ) {
320 push @table_defs, create_trigger($trigger,
321 { add_drop_trigger => $add_drop_table,
322 show_warnings => $show_warnings,
323 no_comments => $no_comments,
324 generator => $generator,
325 max_id_length => $max_id_length,
326 mysql_version => $mysql_version
332 # print "@table_defs\n";
333 push @table_defs, "SET foreign_key_checks=1";
335 return wantarray ? ($create ? $create : (), @create, @table_defs) : ($create . join('', map { $_ ? "$_;\n\n" : () } (@create, @table_defs)));
339 my ($trigger, $options) = @_;
340 my $generator = _generator($options);
342 my $trigger_name = $trigger->name;
343 debug("PKG: Looking at trigger '${trigger_name}'\n");
347 my $events = $trigger->database_events;
348 for my $event ( @$events ) {
349 my $name = $trigger_name;
353 warn "Multiple database events supplied for trigger '${trigger_name}', ",
354 "creating trigger '${name}' for the '${event}' event\n"
355 if $options->{show_warnings};
358 my $action = $trigger->action;
359 if($action !~ /^ \s* BEGIN [\s\;] .*? [\s\;] END [\s\;]* $/six) {
360 $action .= ";" unless $action =~ /;\s*\z/;
361 $action = "BEGIN $action END";
364 push @statements, "DROP TRIGGER IF EXISTS " . $generator->quote($name) if $options->{add_drop_trigger};
365 push @statements, sprintf(
366 "CREATE TRIGGER %s %s %s ON %s\n FOR EACH ROW %s",
367 $generator->quote($name), $trigger->perform_action_when, $event,
368 $generator->quote($trigger->on_table), $action,
372 # Tack the comment onto the first statement
373 $statements[0] = "--\n-- Trigger " . $generator->quote($trigger_name) . "\n--\n" . $statements[0] unless $options->{no_comments};
378 my ($view, $options) = @_;
379 my $generator = _generator($options);
381 my $view_name = $view->name;
382 my $view_name_qt = $generator->quote($view_name);
384 debug("PKG: Looking at view '${view_name}'\n");
386 # Header. Should this look like what mysqldump produces?
388 $create .= "--\n-- View: $view_name_qt\n--\n" unless $options->{no_comments};
390 $create .= ' OR REPLACE' if $options->{add_replace_view};
393 my $extra = $view->extra;
395 if( exists($extra->{mysql_algorithm}) && defined(my $algorithm = $extra->{mysql_algorithm}) ){
396 $create .= " ALGORITHM = ${algorithm}\n" if $algorithm =~ /(?:UNDEFINED|MERGE|TEMPTABLE)/i;
399 if( exists($extra->{mysql_definer}) && defined(my $user = $extra->{mysql_definer}) ){
400 $create .= " DEFINER = ${user}\n";
403 if( exists($extra->{mysql_security}) && defined(my $security = $extra->{mysql_security}) ){
404 $create .= " SQL SECURITY ${security}\n" if $security =~ /(?:DEFINER|INVOKER)/i;
408 $create .= " VIEW $view_name_qt";
410 if( my @fields = $view->fields ){
411 my $list = join ', ', map { $generator->quote($_) } @fields;
412 $create .= " ( ${list} )";
414 if( my $sql = $view->sql ){
415 # do not wrap parenthesis around the selector, mysql doesn't like this
416 # http://bugs.mysql.com/bug.php?id=9198
417 $create .= " AS\n ${sql}\n";
425 my ($table, $options) = @_;
426 my $generator = _generator($options);
428 my $table_name = $generator->quote($table->name);
429 debug("PKG: Looking at table '$table_name'\n");
432 # Header. Should this look like what mysqldump produces?
436 $create .= "--\n-- Table: $table_name\n--\n" unless $options->{no_comments};
437 $drop = qq[DROP TABLE IF EXISTS $table_name] if $options->{add_drop_table};
438 $create .= "CREATE TABLE $table_name (\n";
444 for my $field ( $table->get_fields ) {
445 push @field_defs, create_field($field, $options);
453 for my $index ( $table->get_indices ) {
454 push @index_defs, create_index($index, $options);
455 $indexed_fields{ $_ } = 1 for $index->fields;
459 # Constraints -- need to handle more than just FK. -ky
462 my @constraints = $table->get_constraints;
463 for my $c ( @constraints ) {
464 my $constr = create_constraint($c, $options);
465 push @constraint_defs, $constr if($constr);
467 unless ( $indexed_fields{ ($c->fields())[0] } || $c->type ne FOREIGN_KEY ) {
468 push @index_defs, "INDEX (" . $generator->quote(($c->fields())[0]) . ")";
469 $indexed_fields{ ($c->fields())[0] } = 1;
473 $create .= join(",\n", map { " $_" }
474 @field_defs, @index_defs, @constraint_defs
481 $create .= generate_table_options($table, $options) || '';
482 # $create .= ";\n\n";
484 return $drop ? ($drop,$create) : $create;
487 sub generate_table_options
489 my ($table, $options) = @_;
492 my $table_type_defined = 0;
493 my $generator = _generator($options);
494 my $charset = $table->extra('mysql_charset');
495 my $collate = $table->extra('mysql_collate');
497 for my $t1_option_ref ( $table->options ) {
498 my($key, $value) = %{$t1_option_ref};
499 $table_type_defined = 1
500 if uc $key eq 'ENGINE' or uc $key eq 'TYPE';
501 if (uc $key eq 'CHARACTER SET') {
504 } elsif (uc $key eq 'COLLATE') {
507 } elsif (uc $key eq 'UNION') {
508 $union = '(' . join(', ', map { $generator->quote($_) } @$value) . ')';
511 $create .= " $key=$value";
514 my $mysql_table_type = $table->extra('mysql_table_type');
515 $create .= " ENGINE=$mysql_table_type"
516 if $mysql_table_type && !$table_type_defined;
517 my $comments = $table->comments;
519 $create .= " DEFAULT CHARACTER SET $charset" if $charset;
520 $create .= " COLLATE $collate" if $collate;
521 $create .= " UNION=$union" if $union;
522 $create .= qq[ comment='$comments'] if $comments;
528 my ($field, $options) = @_;
530 my $generator = _generator($options);
532 my $field_name = $field->name;
533 debug("PKG: Looking at field '$field_name'\n");
534 my $field_def = $generator->quote($field_name);
537 my $data_type = $field->data_type;
538 my @size = $field->size;
539 my %extra = $field->extra;
540 my $list = $extra{'list'} || [];
541 my $commalist = join( ', ', map { __PACKAGE__->_quote_string($_) } @$list );
542 my $charset = $extra{'mysql_charset'};
543 my $collate = $extra{'mysql_collate'};
545 my $mysql_version = $options->{mysql_version} || 0;
547 # Oracle "number" type -- figure best MySQL type
549 if ( lc $data_type eq 'number' ) {
551 if ( scalar @size > 1 ) {
552 $data_type = 'double';
554 elsif ( $size[0] && $size[0] >= 12 ) {
555 $data_type = 'bigint';
557 elsif ( $size[0] && $size[0] <= 1 ) {
558 $data_type = 'tinyint';
565 # Convert a large Oracle varchar to "text"
566 # (not necessary as of 5.0.3 http://dev.mysql.com/doc/refman/5.0/en/char.html)
568 elsif ( $data_type =~ /char/i && $size[0] > 255 ) {
569 unless ($size[0] <= 65535 && $mysql_version >= 5.000003 ) {
574 elsif ( $data_type =~ /boolean/i ) {
575 if ($mysql_version >= 4) {
576 $data_type = 'boolean';
579 $commalist = "'0','1'";
582 elsif ( exists $translate{ lc $data_type } ) {
583 $data_type = $translate{ lc $data_type };
586 @size = () if $data_type =~ /(text|blob)/i;
588 if ( $data_type =~ /(double|float)/ && scalar @size == 1 ) {
592 $field_def .= " $data_type";
594 if ( lc($data_type) eq 'enum' || lc($data_type) eq 'set') {
595 $field_def .= '(' . $commalist . ')';
598 defined $size[0] && $size[0] > 0
600 ! grep lc($data_type) eq $_, @no_length_attr
602 $field_def .= '(' . join( ', ', @size ) . ')';
606 $field_def .= " CHARACTER SET $charset" if $charset;
607 $field_def .= " COLLATE $collate" if $collate;
610 for my $qual ( qw[ binary unsigned zerofill ] ) {
611 my $val = $extra{ $qual } || $extra{ uc $qual } or next;
612 $field_def .= " $qual";
614 for my $qual ( 'character set', 'collate', 'on update' ) {
615 my $val = $extra{ $qual } || $extra{ uc $qual } or next;
617 $field_def .= " $qual ${$val}";
620 $field_def .= " $qual $val";
625 if ( $field->is_nullable ) {
626 $field_def .= ' NULL';
629 $field_def .= ' NOT NULL';
633 __PACKAGE__->_apply_default_value(
641 if ( my $comments = $field->comments ) {
642 $comments = __PACKAGE__->_quote_string($comments);
643 $field_def .= qq[ comment $comments];
647 $field_def .= " auto_increment" if $field->is_auto_increment;
653 my ($self, $string) = @_;
655 $string =~ s/([\\'])/$1$1/g;
656 return qq{'$string'};
659 sub alter_create_index
661 my ($index, $options) = @_;
663 my $table_name = _generator($options)->quote($index->table->name);
674 my ( $index, $options ) = @_;
675 my $generator = _generator($options);
680 lc $index->type eq 'normal' ? 'INDEX' : $index->type . ' INDEX',
682 ? $generator->quote(truncate_id_uniquely(
684 $options->{max_id_length} || $DEFAULT_MAX_ID_LENGTH
687 '(' . join( ', ', map { $generator->quote($_) } $index->fields ) . ')'
693 my ($index, $options) = @_;
695 my $table_name = _generator($options)->quote($index->table->name);
702 $index->name || $index->fields
707 sub alter_drop_constraint
709 my ($c, $options) = @_;
711 my $generator = _generator($options);
712 my $table_name = $generator->quote($c->table->name);
714 my @out = ('ALTER','TABLE',$table_name,'DROP');
715 if($c->type eq PRIMARY_KEY) {
719 push @out, ($c->type eq FOREIGN_KEY ? $c->type : "INDEX"),
720 $generator->quote($c->name);
722 return join(' ',@out);
725 sub alter_create_constraint
727 my ($index, $options) = @_;
729 my $table_name = _generator($options)->quote($index->table->name);
734 create_constraint(@_) );
737 sub create_constraint
739 my ($c, $options) = @_;
741 my $generator = _generator($options);
742 my $leave_name = $options->{leave_name} || undef;
744 my $reference_table_name = $generator->quote($c->reference_table);
746 my @fields = $c->fields or return;
748 if ( $c->type eq PRIMARY_KEY ) {
749 return 'PRIMARY KEY (' . join(", ", map { $generator->quote($_) } @fields) . ')';
751 elsif ( $c->type eq UNIQUE ) {
752 return sprintf 'UNIQUE %s(%s)',
753 ((defined $c->name && $c->name)
755 truncate_id_uniquely( $c->name, $options->{max_id_length} || $DEFAULT_MAX_ID_LENGTH ),
759 ( join ', ', map { $generator->quote($_) } @fields ),
762 elsif ( $c->type eq FOREIGN_KEY ) {
764 # Make sure FK field is indexed or MySQL complains.
767 my $table = $c->table;
768 my $c_name = truncate_id_uniquely( $c->name, $options->{max_id_length} || $DEFAULT_MAX_ID_LENGTH );
772 ($c_name ? $generator->quote($c_name) : () ),
777 $def .= ' ('. join( ', ', map { $generator->quote($_) } @fields ) . ')';
779 $def .= ' REFERENCES ' . $reference_table_name;
781 my @rfields = map { $_ || () } $c->reference_fields;
782 unless ( @rfields ) {
783 my $rtable_name = $c->reference_table;
784 if ( my $ref_table = $table->schema->get_table( $rtable_name ) ) {
785 push @rfields, $ref_table->primary_key;
788 warn "Can't find reference table '$rtable_name' " .
789 "in schema\n" if $options->{show_warnings};
794 $def .= ' (' . join( ', ', map { $generator->quote($_) } @rfields ) . ')';
797 warn "FK constraint on " . $table->name . '.' .
798 join('', @fields) . " has no reference fields\n"
799 if $options->{show_warnings};
802 if ( $c->match_type ) {
804 ( $c->match_type =~ /full/i ) ? 'FULL' : 'PARTIAL';
807 if ( $c->on_delete ) {
808 $def .= ' ON DELETE '. $c->on_delete;
811 if ( $c->on_update ) {
812 $def .= ' ON UPDATE '. $c->on_update;
822 my ($to_table, $options) = @_;
824 my $table_options = generate_table_options($to_table, $options) || '';
825 my $table_name = _generator($options)->quote($to_table->name);
826 my $out = sprintf('ALTER TABLE %s%s',
833 sub rename_field { alter_field(@_) }
836 my ($from_field, $to_field, $options) = @_;
838 my $generator = _generator($options);
839 my $table_name = $generator->quote($to_field->table->name);
841 my $out = sprintf('ALTER TABLE %s CHANGE COLUMN %s %s',
843 $generator->quote($from_field->name),
844 create_field($to_field, $options));
851 my ($new_field, $options) = @_;
853 my $table_name = _generator($options)->quote($new_field->table->name);
855 my $out = sprintf('ALTER TABLE %s ADD COLUMN %s',
857 create_field($new_field, $options));
865 my ($old_field, $options) = @_;
867 my $generator = _generator($options);
868 my $table_name = $generator->quote($old_field->table->name);
870 my $out = sprintf('ALTER TABLE %s DROP COLUMN %s',
872 $generator->quote($old_field->name));
878 sub batch_alter_table {
879 my ($table, $diff_hash, $options) = @_;
881 # InnoDB has an issue with dropping and re-adding a FK constraint under the
882 # name in a single alter statement, see: http://bugs.mysql.com/bug.php?id=13741
884 # We have to work round this.
887 my %fks_to_drop = map {
888 $_->type eq FOREIGN_KEY
891 } @{$diff_hash->{alter_drop_constraint} };
893 my %fks_to_create = map {
894 if ( $_->type eq FOREIGN_KEY) {
895 $fks_to_alter{$_->name} = $fks_to_drop{$_->name} if $fks_to_drop{$_->name};
898 } @{$diff_hash->{alter_create_constraint} };
901 if (scalar keys %fks_to_alter) {
902 $diff_hash->{alter_drop_constraint} = [
903 grep { !$fks_to_alter{$_->name} } @{ $diff_hash->{alter_drop_constraint} }
906 @drop_stmt = batch_alter_table($table, { alter_drop_constraint => [ values %fks_to_alter ] }, $options);
910 my @stmts = batch_alter_table_statements($diff_hash, $options);
913 my $generator = _generator($options);
915 # rename_table makes things a bit more complex
916 my $renamed_from = "";
917 $renamed_from = $generator->quote($diff_hash->{rename_table}[0][0]->name)
918 if $diff_hash->{rename_table} && @{$diff_hash->{rename_table}};
920 return unless @stmts;
921 # Just zero or one stmts. return now
922 return (@drop_stmt,@stmts) unless @stmts > 1;
924 # Now strip off the 'ALTER TABLE xyz' of all but the first one
926 my $table_name = $generator->quote($table->name);
928 my $re = $renamed_from
929 ? qr/^ALTER TABLE (?:\Q$table_name\E|\Q$renamed_from\E) /
930 : qr/^ALTER TABLE \Q$table_name\E /;
932 my $first = shift @stmts;
933 my ($alter_table) = $first =~ /($re)/;
935 my $padd = " " x length($alter_table);
937 return @drop_stmt, join( ",\n", $first, map { s/$re//; $padd . $_ } @stmts);
942 my ($table, $options) = @_;
945 # Drop (foreign key) constraints so table drops cleanly
947 $table, { alter_drop_constraint => [ grep { $_->type eq 'FOREIGN KEY' } $table->get_constraints ] }, $options
949 'DROP TABLE ' . _generator($options)->quote($table),
954 my ($old_table, $new_table, $options) = @_;
956 my $generator = _generator($options);
957 my $old_table_name = $generator->quote($old_table);
958 my $new_table_name = $generator->quote($new_table);
960 return "ALTER TABLE $old_table_name RENAME TO $new_table_name";
963 sub next_unused_name {
964 my $name = shift || '';
965 if ( !defined($used_names{$name}) ) {
966 $used_names{$name} = $name;
971 while ( defined($used_names{$name . '_' . $i}) ) {
975 $used_names{$name} = $name;
985 SQL::Translator, http://www.mysql.com/.
989 darren chamberlain E<lt>darren@cpan.orgE<gt>,
990 Ken Youens-Clark E<lt>kclark@cpan.orgE<gt>.