mysql_table_type extra data and InnoDB derivation fix.
[dbsrgits/SQL-Translator.git] / lib / SQL / Translator / Producer / MySQL.pm
CommitLineData
9398955f 1package SQL::Translator::Producer::MySQL;
2
49e1eb70 3# -------------------------------------------------------------------
5a0c7b43 4# $Id: MySQL.pm,v 1.42 2005-01-13 11:50:23 grommit Exp $
49e1eb70 5# -------------------------------------------------------------------
977651a5 6# Copyright (C) 2002-4 SQLFairy Authors
9398955f 7#
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.
11#
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.
16#
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
20# 02111-1307 USA
21# -------------------------------------------------------------------
22
c855a748 23=head1 NAME
24
25SQL::Translator::Producer::MySQL - MySQL-specific producer for SQL::Translator
26
27=head1 SYNOPSIS
28
29Use via SQL::Translator:
30
31 use SQL::Translator;
32
33 my $t = SQL::Translator->new( parser => '...', producer => 'MySQL', '...' );
34 $t->translate;
35
36=head1 DESCRIPTION
37
38This module will produce text output of the schema suitable for MySQL.
39There are still some issues to be worked out with syntax differences
40between MySQL versions 3 and 4 ("SET foreign_key_checks," character sets
41for fields, etc.).
42
5a0c7b43 43=head2 Table Types
44
45Normally the tables will be created without any explicit table type given and
46so will use the MySQL default.
47
48Any tables involved in foreign key constraints automatically get a table type
49of InnoDB, unless this is overridden by setting the C<mysql_table_type> extra
50attribute explicitly on the table.
51
52=head2 Extra attributes.
53
54The producer recognises the following extra attributes on the Schema objects.
55
56=over 4
57
58=item field.list
59
60Set the list of allowed values for Enum fields.
61
62=item field.binary field.unsigned field.zerofill
63
64Set the MySQL field options of the same name.
65
66=item mysql_table_type
67
68Set the type of the table e.g. 'InnoDB', 'MyISAM'. This will be
69automatically set for tables involved in foreign key constraints if it is
70not already set explicitly. See L<"Table Types">.
71
72=back
73
c855a748 74=cut
75
9398955f 76use strict;
d529894e 77use vars qw[ $VERSION $DEBUG ];
5a0c7b43 78$VERSION = sprintf "%d.%02d", q$Revision: 1.42 $ =~ /(\d+)\.(\d+)/;
5636ed00 79$DEBUG = 0 unless defined $DEBUG;
9398955f 80
81use Data::Dumper;
1c14e9f1 82use SQL::Translator::Schema::Constants;
5ee19df8 83use SQL::Translator::Utils qw(debug header_comment);
9398955f 84
d2344c83 85#
86# Use only lowercase for the keys (e.g. "long" and not "LONG")
87#
2620fc1c 88my %translate = (
89 #
90 # Oracle types
91 #
92 varchar2 => 'varchar',
93 long => 'text',
d2344c83 94 clob => 'longtext',
2620fc1c 95
96 #
97 # Sybase types
98 #
99 int => 'integer',
100 money => 'float',
101 real => 'double',
102 comment => 'text',
103 bit => 'tinyint',
87779799 104
105 #
106 # Access types
107 #
108 'long integer' => 'integer',
109 'text' => 'text',
110 'datetime' => 'datetime',
2620fc1c 111);
112
9398955f 113sub produce {
a1d94525 114 my $translator = shift;
115 local $DEBUG = $translator->debug;
116 my $no_comments = $translator->no_comments;
117 my $add_drop_table = $translator->add_drop_table;
118 my $schema = $translator->schema;
2bc23e82 119 my $show_warnings = $translator->show_warnings || 0;
d529894e 120
1a24938d 121 debug("PKG: Beginning production\n");
d529894e 122
123 my $create;
5ee19df8 124 $create .= header_comment unless ($no_comments);
0823773d 125 # \todo Don't set if MySQL 3.x is set on command line
da147d03 126 $create .= "SET foreign_key_checks=0;\n\n";
9398955f 127
5a0c7b43 128 #
129 # Work out which tables need to be InnoDB to support foreign key
130 # constraints. We do this first as we need InnoDB at both ends.
131 #
132 foreach ( map { $_->get_constraints } $schema->get_tables ) {
133 foreach my $meth (qw/table reference_table/) {
134 my $table = $schema->get_table($_->$meth) || next;
135 next if $table->extra('mysql_table_type');
136 $table->extra( 'mysql_table_type' => 'InnoDB');
137 }
138 }
139
140 #
141 # Generate sql
142 #
1c14e9f1 143 for my $table ( $schema->get_tables ) {
144 my $table_name = $table->name;
145 debug("PKG: Looking at table '$table_name'\n");
9398955f 146
d529894e 147 #
9398955f 148 # Header. Should this look like what mysqldump produces?
d529894e 149 #
1c14e9f1 150 $create .= "--\n-- Table: $table_name\n--\n" unless $no_comments;
151 $create .= qq[DROP TABLE IF EXISTS $table_name;\n] if $add_drop_table;
152 $create .= "CREATE TABLE $table_name (\n";
9398955f 153
d529894e 154 #
9398955f 155 # Fields
d529894e 156 #
1c14e9f1 157 my @field_defs;
158 for my $field ( $table->get_fields ) {
159 my $field_name = $field->name;
160 debug("PKG: Looking at field '$field_name'\n");
161 my $field_def = $field_name;
9398955f 162
163 # data type and size
1c14e9f1 164 my $data_type = $field->data_type;
165 my @size = $field->size;
35ed60b5 166 my %extra = $field->extra;
167 my $list = $extra{'list'} || [];
0823773d 168 # \todo deal with embedded quotes
4524cf01 169 my $commalist = join( ', ', map { qq['$_'] } @$list );
2620fc1c 170
1c14e9f1 171 #
172 # Oracle "number" type -- figure best MySQL type
173 #
174 if ( lc $data_type eq 'number' ) {
aa0a4c42 175 # not an integer
176 if ( scalar @size > 1 ) {
177 $data_type = 'double';
178 }
be42bce8 179 elsif ( $size[0] && $size[0] >= 12 ) {
aa0a4c42 180 $data_type = 'bigint';
181 }
be42bce8 182 elsif ( $size[0] && $size[0] <= 1 ) {
aa0a4c42 183 $data_type = 'tinyint';
184 }
185 else {
186 $data_type = 'int';
2620fc1c 187 }
188 }
49758c5d 189 #
190 # Convert a large Oracle varchar to "text"
191 #
2b695517 192 elsif ( $data_type =~ /char/i && $size[0] > 255 ) {
49758c5d 193 $data_type = 'text';
194 @size = ();
195 }
4167a803 196 elsif ( $data_type =~ /char/i && ! $size[0] ) {
197 @size = (255);
198 }
2b695517 199 elsif ( $data_type =~ /boolean/i ) {
200 $data_type = 'enum';
201 $commalist = "'0','1'";
202 }
d2344c83 203 elsif ( exists $translate{ lc $data_type } ) {
204 $data_type = $translate{ lc $data_type };
2620fc1c 205 }
206
472b787e 207 @size = () if $data_type =~ /(text|blob)/i;
6d3f6379 208
2b695517 209 if ( $data_type =~ /(double|float)/ && scalar @size == 1 ) {
210 push @size, '0';
211 }
212
1c14e9f1 213 $field_def .= " $data_type";
35ed60b5 214
215 if ( lc $data_type eq 'enum' ) {
216 $field_def .= '(' . $commalist . ')';
6d3f6379 217 }
218 elsif ( defined $size[0] && $size[0] > 0 ) {
1c14e9f1 219 $field_def .= '(' . join( ', ', @size ) . ')';
220 }
d529894e 221
222 # MySQL qualifiers
223 for my $qual ( qw[ binary unsigned zerofill ] ) {
1c14e9f1 224 my $val = $extra{ $qual || uc $qual } or next;
0823773d 225 $field_def .= " $qual";
d529894e 226 }
9398955f 227
228 # Null?
1c14e9f1 229 $field_def .= ' NOT NULL' unless $field->is_nullable;
9398955f 230
231 # Default? XXX Need better quoting!
1c14e9f1 232 my $default = $field->default_value;
d529894e 233 if ( defined $default ) {
234 if ( uc $default eq 'NULL') {
1c14e9f1 235 $field_def .= ' DEFAULT NULL';
9398955f 236 } else {
1c14e9f1 237 $field_def .= " DEFAULT '$default'";
9398955f 238 }
239 }
240
241 # auto_increment?
1c14e9f1 242 $field_def .= " auto_increment" if $field->is_auto_increment;
243 push @field_defs, $field_def;
56120730 244 }
9398955f 245
d529894e 246 #
247 # Indices
248 #
1c14e9f1 249 my @index_defs;
bc60004d 250 my %indexed_fields;
1c14e9f1 251 for my $index ( $table->get_indices ) {
252 push @index_defs, join( ' ',
35ed60b5 253 lc $index->type eq 'normal' ? 'INDEX' : $index->type,
1c14e9f1 254 $index->name,
255 '(' . join( ', ', $index->fields ) . ')'
256 );
bc60004d 257 $indexed_fields{ $_ } = 1 for $index->fields;
d529894e 258 }
259
260 #
5e56da9a 261 # Constraints -- need to handle more than just FK. -ky
262 #
2620fc1c 263 my @constraint_defs;
2bc23e82 264 my @constraints = $table->get_constraints;
265 for my $c ( @constraints ) {
1c14e9f1 266 my @fields = $c->fields or next;
5e56da9a 267
1c14e9f1 268 if ( $c->type eq PRIMARY_KEY ) {
269 push @constraint_defs,
270 'PRIMARY KEY (' . join(', ', @fields). ')';
271 }
272 elsif ( $c->type eq UNIQUE ) {
273 push @constraint_defs,
274 'UNIQUE (' . join(', ', @fields). ')';
275 }
276 elsif ( $c->type eq FOREIGN_KEY ) {
bc60004d 277 #
278 # Make sure FK field is indexed or MySQL complains.
279 #
280 unless ( $indexed_fields{ $fields[0] } ) {
281 push @index_defs, "INDEX ($fields[0])";
2bc23e82 282 $indexed_fields{ $fields[0] } = 1;
bc60004d 283 }
284
1c14e9f1 285 my $def = join(' ',
286 map { $_ || () } 'FOREIGN KEY', $c->name
287 );
288
289 $def .= ' (' . join( ', ', @fields ) . ')';
290
291 $def .= ' REFERENCES ' . $c->reference_table;
292
2bc23e82 293 my @rfields = map { $_ || () } $c->reference_fields;
294 unless ( @rfields ) {
295 my $rtable_name = $c->reference_table;
296 if ( my $ref_table = $schema->get_table( $rtable_name ) ) {
297 push @rfields, $ref_table->primary_key;
298 }
299 else {
300 warn "Can't find reference table '$rtable_name' " .
301 "in schema\n" if $show_warnings;
302 }
303 }
304
305 if ( @rfields ) {
1c14e9f1 306 $def .= ' (' . join( ', ', @rfields ) . ')';
5e56da9a 307 }
2bc23e82 308 else {
309 warn "FK constraint on " . $table->name . '.' .
310 join('', @fields) . " has no reference fields\n"
311 if $show_warnings;
312 }
5e56da9a 313
1c14e9f1 314 if ( $c->match_type ) {
5e56da9a 315 $def .= ' MATCH ' .
1c14e9f1 316 ( $c->match_type =~ /full/i ) ? 'FULL' : 'PARTIAL';
5e56da9a 317 }
318
1c14e9f1 319 if ( $c->on_delete ) {
320 $def .= ' ON DELETE '.join( ' ', $c->on_delete );
586809da 321 }
322
1c14e9f1 323 if ( $c->on_update ) {
324 $def .= ' ON UPDATE '.join( ' ', $c->on_update );
586809da 325 }
5e56da9a 326
2620fc1c 327 push @constraint_defs, $def;
5e56da9a 328 }
329 }
330
1c14e9f1 331 $create .= join(",\n", map { " $_" }
332 @field_defs, @index_defs, @constraint_defs
333 );
5e56da9a 334
335 #
9398955f 336 # Footer
d529894e 337 #
c45c546e 338 $create .= "\n)";
5a0c7b43 339 my $mysql_table_type = $table->extra('mysql_table_type');
340 $create .= " Type=$mysql_table_type" if $mysql_table_type;
9398955f 341 $create .= ";\n\n";
342 }
343
9398955f 344 return $create;
345}
346
9398955f 3471;
9398955f 348
c855a748 349# -------------------------------------------------------------------
9398955f 350
c855a748 351=pod
352
353=head1 SEE ALSO
354
355SQL::Translator, http://www.mysql.com/.
9398955f 356
2d6979da 357=head1 AUTHORS
9398955f 358
758ab1cd 359darren chamberlain E<lt>darren@cpan.orgE<gt>,
c855a748 360Ken Y. Clark E<lt>kclark@cpan.orgE<gt>.
361
362=cut