package SQL::Translator::Producer::MySQL;
# -------------------------------------------------------------------
-# $Id: MySQL.pm,v 1.54 2007-11-10 03:36:43 mwz444 Exp $
-# -------------------------------------------------------------------
-# Copyright (C) 2002-4 SQLFairy Authors
+# Copyright (C) 2002-2009 SQLFairy Authors
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
between MySQL versions 3 and 4 ("SET foreign_key_checks," character sets
for fields, etc.).
+=head1 ARGUMENTS
+
+This producer takes a single optional producer_arg C<mysql_version>, which
+provides the desired version for the target database. By default MySQL v3 is
+assumed, and statements pertaining to any features introduced in later versions
+(e.g. CREATE VIEW) are not produced.
+
+Valid version specifiers for C<mysql_version> are listed L<here|SQL::Translator::Utils/parse_mysql_version>
+
=head2 Table Types
Normally the tables will be created without any explicit table type given and
automatically set for tables involved in foreign key constraints if it is
not already set explicitly. See L<"Table Types">.
-Please note that the C<ENGINE> option is the prefered method of specifying
+Please note that the C<ENGINE> option is the preferred method of specifying
the MySQL storage engine to use, but this method still works for backwards
-compatability.
+compatibility.
=item B<table.mysql_charset>, B<table.mysql_collate>
use strict;
use warnings;
use vars qw[ $VERSION $DEBUG %used_names ];
-$VERSION = sprintf "%d.%02d", q$Revision: 1.54 $ =~ /(\d+)\.(\d+)/;
+$VERSION = '1.59';
$DEBUG = 0 unless defined $DEBUG;
# Maximum length for most identifiers is 64, according to:
use Data::Dumper;
use SQL::Translator::Schema::Constants;
-use SQL::Translator::Utils qw(debug header_comment truncate_id_uniquely);
+use SQL::Translator::Utils qw(debug header_comment
+ truncate_id_uniquely parse_mysql_version);
#
# Use only lowercase for the keys (e.g. "long" and not "LONG")
'long integer' => 'integer',
'text' => 'text',
'datetime' => 'datetime',
+
+ #
+ # PostgreSQL types
+ #
+ bytea => 'BLOB',
);
+#
+# Column types that do not support lenth attribute
+#
+my @no_length_attr = qw/
+ date time timestamp datetime year
+ /;
+
sub preprocess_schema {
my ($schema) = @_;
my $schema = $translator->schema;
my $show_warnings = $translator->show_warnings || 0;
my $producer_args = $translator->producer_args;
- my $mysql_version = $producer_args->{mysql_version} || 0;
+ my $mysql_version = parse_mysql_version ($producer_args->{mysql_version}, 'perl') || 0;
my $max_id_length = $producer_args->{mysql_max_id_length} || $DEFAULT_MAX_ID_LENGTH;
my ($qt, $qf, $qc) = ('','', '');
debug("PKG: Beginning production\n");
%used_names = ();
- my $create;
+ my $create = '';
$create .= header_comment unless ($no_comments);
# \todo Don't set if MySQL 3.x is set on command line
- $create .= "SET foreign_key_checks=0;\n\n";
+ my @create = "SET foreign_key_checks=0";
preprocess_schema($schema);
});
}
- if ($mysql_version > 5.0) {
+ if ($mysql_version >= 5.000001) {
for my $view ( $schema->get_views ) {
push @table_defs, create_view($view,
{ add_replace_view => $add_drop_table,
}
}
+ if ($mysql_version >= 5.000002) {
+ for my $trigger ( $schema->get_triggers ) {
+ push @table_defs, create_trigger($trigger,
+ { add_drop_trigger => $add_drop_table,
+ show_warnings => $show_warnings,
+ no_comments => $no_comments,
+ quote_table_names => $qt,
+ quote_field_names => $qf,
+ max_id_length => $max_id_length,
+ mysql_version => $mysql_version
+ });
+ }
+ }
+
# print "@table_defs\n";
- push @table_defs, "SET foreign_key_checks=1;\n\n";
+ push @table_defs, "SET foreign_key_checks=1";
+
+ return wantarray ? ($create ? $create : (), @create, @table_defs) : ($create . join('', map { $_ ? "$_;\n\n" : () } (@create, @table_defs)));
+}
+
+sub create_trigger {
+ my ($trigger, $options) = @_;
+ my $qt = $options->{quote_table_names} || '';
+ my $qf = $options->{quote_field_names} || '';
+
+ my $trigger_name = $trigger->name;
+ debug("PKG: Looking at trigger '${trigger_name}'\n");
+
+ my @statements;
- return wantarray ? ($create, @table_defs) : $create . join ('', @table_defs);
+ my $events = $trigger->database_events;
+ for my $event ( @$events ) {
+ my $name = $trigger_name;
+ if (@$events > 1) {
+ $name .= "_$event";
+
+ warn "Multiple database events supplied for trigger '${trigger_name}', ",
+ "creating trigger '${name}' for the '${event}' event\n"
+ if $options->{show_warnings};
+ }
+
+ my $action = $trigger->action;
+ $action .= ";" unless $action =~ /;\s*\z/;
+
+ push @statements, "DROP TRIGGER IF EXISTS ${qt}${name}${qt}" if $options->{add_drop_trigger};
+ push @statements, sprintf(
+ "CREATE TRIGGER ${qt}%s${qt} %s %s ON ${qt}%s${qt}\n FOR EACH ROW BEGIN %s END",
+ $name, $trigger->perform_action_when, $event, $trigger->on_table, $action,
+ );
+
+ }
+ # Tack the comment onto the first statement
+ $statements[0] = "--\n-- Trigger ${qt}${trigger_name}${qt}\n--\n" . $statements[0] unless $options->{no_comments};
+ return @statements;
}
sub create_view {
$create .= " ( ${list} )";
}
if( my $sql = $view->sql ){
- $create .= " AS (\n ${sql}\n )";
+ # do not wrap parenthesis around the selector, mysql doesn't like this
+ # http://bugs.mysql.com/bug.php?id=9198
+ $create .= " AS\n ${sql}\n";
}
- $create .= ";\n\n";
+# $create .= "";
return $create;
}
my $qt = $options->{quote_table_names} || '';
my $qf = $options->{quote_field_names} || '';
- my $table_name = $table->name;
+ my $table_name = quote_table_name($table->name, $qt);
debug("PKG: Looking at table '$table_name'\n");
#
#
my $create = '';
my $drop;
- $create .= "--\n-- Table: $qt$table_name$qt\n--\n" unless $options->{no_comments};
- $drop = qq[DROP TABLE IF EXISTS $qt$table_name$qt;\n] if $options->{add_drop_table};
- $create .= "CREATE TABLE $qt$table_name$qt (\n";
+ $create .= "--\n-- Table: $table_name\n--\n" unless $options->{no_comments};
+ $drop = qq[DROP TABLE IF EXISTS $table_name] if $options->{add_drop_table};
+ $create .= "CREATE TABLE $table_name (\n";
#
# Fields
# Footer
#
$create .= "\n)";
- $create .= generate_table_options($table) || '';
- $create .= ";\n\n";
+ $create .= generate_table_options($table, $options) || '';
+# $create .= ";\n\n";
return $drop ? ($drop,$create) : $create;
}
+sub quote_table_name {
+ my ($table_name, $qt) = @_;
+
+ $table_name =~ s/\./$qt.$qt/g;
+
+ return "$qt$table_name$qt";
+}
+
sub generate_table_options
{
- my ($table) = @_;
+ my ($table, $options) = @_;
my $create;
my $table_type_defined = 0;
+ my $qf = $options->{quote_field_names} ||= '';
my $charset = $table->extra('mysql_charset');
my $collate = $table->extra('mysql_collate');
+ my $union = undef;
for my $t1_option_ref ( $table->options ) {
my($key, $value) = %{$t1_option_ref};
$table_type_defined = 1
} elsif (uc $key eq 'COLLATE') {
$collate = $value;
next;
+ } elsif (uc $key eq 'UNION') {
+ $union = "($qf". join("$qf, $qf", @$value) ."$qf)";
+ next;
}
$create .= " $key=$value";
}
$create .= " DEFAULT CHARACTER SET $charset" if $charset;
$create .= " COLLATE $collate" if $collate;
+ $create .= " UNION=$union" if $union;
$create .= qq[ comment='$comments'] if $comments;
return $create;
}
my $charset = $extra{'mysql_charset'};
my $collate = $extra{'mysql_collate'};
+ my $mysql_version = $options->{mysql_version} || 0;
#
# Oracle "number" type -- figure best MySQL type
#
}
#
# Convert a large Oracle varchar to "text"
+ # (not necessary as of 5.0.3 http://dev.mysql.com/doc/refman/5.0/en/char.html)
#
elsif ( $data_type =~ /char/i && $size[0] > 255 ) {
- $data_type = 'text';
- @size = ();
+ unless ($size[0] <= 65535 && $mysql_version >= 5.000003 ) {
+ $data_type = 'text';
+ @size = ();
+ }
}
elsif ( $data_type =~ /boolean/i ) {
- my $mysql_version = $options->{mysql_version} || 0;
if ($mysql_version >= 4) {
$data_type = 'boolean';
} else {
if ( lc($data_type) eq 'enum' || lc($data_type) eq 'set') {
$field_def .= '(' . $commalist . ')';
}
- elsif ( defined $size[0] && $size[0] > 0 ) {
+ elsif (
+ defined $size[0] && $size[0] > 0
+ &&
+ ! grep lc($data_type) eq $_, @no_length_attr
+ ) {
$field_def .= '(' . join( ', ', @size ) . ')';
}
# Null?
$field_def .= ' NOT NULL' unless $field->is_nullable;
- # Default? XXX Need better quoting!
- my $default = $field->default_value;
- if ( defined $default ) {
- if ( uc $default eq 'NULL') {
- $field_def .= ' DEFAULT NULL';
- } else {
- $field_def .= " DEFAULT '$default'";
- }
- }
+ # Default?
+ SQL::Translator::Producer->_apply_default_value(
+ $field,
+ \$field_def,
+ [
+ 'NULL' => \'NULL',
+ ],
+ );
if ( my $comments = $field->comments ) {
$field_def .= qq[ comment '$comments'];
sub create_index
{
- my ($index, $options) = @_;
+ my ( $index, $options ) = @_;
my $qf = $options->{quote_field_names} || '';
- return join( ' ',
- lc $index->type eq 'normal' ? 'INDEX' : $index->type . ' INDEX',
- truncate_id_uniquely( $index->name, $options->{max_id_length} || $DEFAULT_MAX_ID_LENGTH ),
- '(' . $qf . join( "$qf, $qf", $index->fields ) . $qf . ')'
- );
-
+ return join(
+ ' ',
+ map { $_ || () }
+ lc $index->type eq 'normal' ? 'INDEX' : $index->type . ' INDEX',
+ $index->name
+ ? $qf . truncate_id_uniquely(
+ $index->name,
+ $options->{max_id_length} || $DEFAULT_MAX_ID_LENGTH
+ ) . $qf
+ : '',
+ '(' . $qf . join( "$qf, $qf", $index->fields ) . $qf . ')'
+ );
}
sub alter_drop_index
my $qt = $options->{quote_table_names} || '';
- my $table_options = generate_table_options($to_table) || '';
+ my $table_options = generate_table_options($to_table, $options) || '';
my $out = sprintf('ALTER TABLE %s%s',
$qt . $to_table->name . $qt,
$table_options);
} else { ( ) }
} @{$diff_hash->{alter_create_constraint} };
- my $drop_stmt = '';
+ my @drop_stmt;
if (scalar keys %fks_to_alter) {
$diff_hash->{alter_drop_constraint} = [
grep { !$fks_to_alter{$_->name} } @{ $diff_hash->{alter_drop_constraint} }
];
- $drop_stmt = batch_alter_table($table, { alter_drop_constraint => [ values %fks_to_alter ] }, $options)
- . "\n";
+ @drop_stmt = batch_alter_table($table, { alter_drop_constraint => [ values %fks_to_alter ] }, $options);
}
return unless @stmts;
# Just zero or one stmts. return now
- return "$drop_stmt@stmts;" unless @stmts > 1;
+ return (@drop_stmt,@stmts) unless @stmts > 1;
# Now strip off the 'ALTER TABLE xyz' of all but the first one
my $padd = " " x length($alter_table);
- return $drop_stmt . join( ",\n", $first, map { s/$re//; $padd . $_ } @stmts) . ';';
+ return @drop_stmt, join( ",\n", $first, map { s/$re//; $padd . $_ } @stmts);
}
# Drop (foreign key) constraints so table drops cleanly
my @sql = batch_alter_table($table, { alter_drop_constraint => [ grep { $_->type eq 'FOREIGN KEY' } $table->get_constraints ] }, $options);
- return join("\n", @sql, "DROP TABLE $qt$table$qt;");
+ return (@sql, "DROP TABLE $qt$table$qt");
+# return join("\n", @sql, "DROP TABLE $qt$table$qt");
}
=head1 AUTHORS
darren chamberlain E<lt>darren@cpan.orgE<gt>,
-Ken Y. Clark E<lt>kclark@cpan.orgE<gt>.
+Ken Youens-Clark E<lt>kclark@cpan.orgE<gt>.
=cut