Put field and table comments into proper MySQL syntax.
[dbsrgits/SQL-Translator.git] / lib / SQL / Translator / Producer / MySQL.pm
CommitLineData
9398955f 1package SQL::Translator::Producer::MySQL;
2
49e1eb70 3# -------------------------------------------------------------------
f8b6e804 4# $Id: MySQL.pm,v 1.44 2005-06-15 18:05:07 kycl4rk 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
1ded8513 66=item table.mysql_table_type
5a0c7b43 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
1ded8513 72=item table.mysql_charset table.mysql_collate
73
74Set the tables default charater set and collation order.
75
76=item field.mysql_charset field.mysql_collate
77
78Set the fields charater set and collation order.
79
5a0c7b43 80=back
81
c855a748 82=cut
83
9398955f 84use strict;
d529894e 85use vars qw[ $VERSION $DEBUG ];
f8b6e804 86$VERSION = sprintf "%d.%02d", q$Revision: 1.44 $ =~ /(\d+)\.(\d+)/;
5636ed00 87$DEBUG = 0 unless defined $DEBUG;
9398955f 88
89use Data::Dumper;
1c14e9f1 90use SQL::Translator::Schema::Constants;
5ee19df8 91use SQL::Translator::Utils qw(debug header_comment);
9398955f 92
d2344c83 93#
94# Use only lowercase for the keys (e.g. "long" and not "LONG")
95#
2620fc1c 96my %translate = (
97 #
98 # Oracle types
99 #
100 varchar2 => 'varchar',
101 long => 'text',
d2344c83 102 clob => 'longtext',
2620fc1c 103
104 #
105 # Sybase types
106 #
107 int => 'integer',
108 money => 'float',
109 real => 'double',
110 comment => 'text',
111 bit => 'tinyint',
87779799 112
113 #
114 # Access types
115 #
116 'long integer' => 'integer',
117 'text' => 'text',
118 'datetime' => 'datetime',
2620fc1c 119);
120
9398955f 121sub produce {
a1d94525 122 my $translator = shift;
123 local $DEBUG = $translator->debug;
124 my $no_comments = $translator->no_comments;
125 my $add_drop_table = $translator->add_drop_table;
126 my $schema = $translator->schema;
2bc23e82 127 my $show_warnings = $translator->show_warnings || 0;
d529894e 128
1a24938d 129 debug("PKG: Beginning production\n");
d529894e 130
131 my $create;
5ee19df8 132 $create .= header_comment unless ($no_comments);
0823773d 133 # \todo Don't set if MySQL 3.x is set on command line
da147d03 134 $create .= "SET foreign_key_checks=0;\n\n";
9398955f 135
5a0c7b43 136 #
137 # Work out which tables need to be InnoDB to support foreign key
138 # constraints. We do this first as we need InnoDB at both ends.
139 #
140 foreach ( map { $_->get_constraints } $schema->get_tables ) {
141 foreach my $meth (qw/table reference_table/) {
142 my $table = $schema->get_table($_->$meth) || next;
143 next if $table->extra('mysql_table_type');
144 $table->extra( 'mysql_table_type' => 'InnoDB');
145 }
146 }
147
148 #
149 # Generate sql
150 #
1c14e9f1 151 for my $table ( $schema->get_tables ) {
152 my $table_name = $table->name;
153 debug("PKG: Looking at table '$table_name'\n");
9398955f 154
d529894e 155 #
9398955f 156 # Header. Should this look like what mysqldump produces?
d529894e 157 #
1c14e9f1 158 $create .= "--\n-- Table: $table_name\n--\n" unless $no_comments;
159 $create .= qq[DROP TABLE IF EXISTS $table_name;\n] if $add_drop_table;
160 $create .= "CREATE TABLE $table_name (\n";
9398955f 161
d529894e 162 #
9398955f 163 # Fields
d529894e 164 #
1c14e9f1 165 my @field_defs;
166 for my $field ( $table->get_fields ) {
167 my $field_name = $field->name;
168 debug("PKG: Looking at field '$field_name'\n");
169 my $field_def = $field_name;
9398955f 170
171 # data type and size
1c14e9f1 172 my $data_type = $field->data_type;
173 my @size = $field->size;
35ed60b5 174 my %extra = $field->extra;
175 my $list = $extra{'list'} || [];
0823773d 176 # \todo deal with embedded quotes
4524cf01 177 my $commalist = join( ', ', map { qq['$_'] } @$list );
1ded8513 178 my $charset = $extra{'mysql_charset'};
179 my $collate = $extra{'mysql_collate'};
2620fc1c 180
1c14e9f1 181 #
182 # Oracle "number" type -- figure best MySQL type
183 #
184 if ( lc $data_type eq 'number' ) {
aa0a4c42 185 # not an integer
186 if ( scalar @size > 1 ) {
187 $data_type = 'double';
188 }
be42bce8 189 elsif ( $size[0] && $size[0] >= 12 ) {
aa0a4c42 190 $data_type = 'bigint';
191 }
be42bce8 192 elsif ( $size[0] && $size[0] <= 1 ) {
aa0a4c42 193 $data_type = 'tinyint';
194 }
195 else {
196 $data_type = 'int';
2620fc1c 197 }
198 }
49758c5d 199 #
200 # Convert a large Oracle varchar to "text"
201 #
2b695517 202 elsif ( $data_type =~ /char/i && $size[0] > 255 ) {
49758c5d 203 $data_type = 'text';
204 @size = ();
205 }
4167a803 206 elsif ( $data_type =~ /char/i && ! $size[0] ) {
207 @size = (255);
208 }
2b695517 209 elsif ( $data_type =~ /boolean/i ) {
210 $data_type = 'enum';
211 $commalist = "'0','1'";
212 }
d2344c83 213 elsif ( exists $translate{ lc $data_type } ) {
214 $data_type = $translate{ lc $data_type };
2620fc1c 215 }
216
472b787e 217 @size = () if $data_type =~ /(text|blob)/i;
6d3f6379 218
2b695517 219 if ( $data_type =~ /(double|float)/ && scalar @size == 1 ) {
220 push @size, '0';
221 }
222
1c14e9f1 223 $field_def .= " $data_type";
1ded8513 224
35ed60b5 225 if ( lc $data_type eq 'enum' ) {
226 $field_def .= '(' . $commalist . ')';
6d3f6379 227 }
228 elsif ( defined $size[0] && $size[0] > 0 ) {
1c14e9f1 229 $field_def .= '(' . join( ', ', @size ) . ')';
230 }
d529894e 231
1ded8513 232 # char sets
233 $field_def .= " CHARACTER SET $charset" if $charset;
234 $field_def .= " COLLATE $collate" if $collate;
235
d529894e 236 # MySQL qualifiers
237 for my $qual ( qw[ binary unsigned zerofill ] ) {
1c14e9f1 238 my $val = $extra{ $qual || uc $qual } or next;
0823773d 239 $field_def .= " $qual";
d529894e 240 }
9398955f 241
242 # Null?
1c14e9f1 243 $field_def .= ' NOT NULL' unless $field->is_nullable;
9398955f 244
245 # Default? XXX Need better quoting!
1c14e9f1 246 my $default = $field->default_value;
d529894e 247 if ( defined $default ) {
248 if ( uc $default eq 'NULL') {
1c14e9f1 249 $field_def .= ' DEFAULT NULL';
9398955f 250 } else {
1c14e9f1 251 $field_def .= " DEFAULT '$default'";
9398955f 252 }
253 }
254
f8b6e804 255 if ( my $comments = $field->comments ) {
256 $field_def .= qq[ comment '$comments'];
257 }
258
9398955f 259 # auto_increment?
1c14e9f1 260 $field_def .= " auto_increment" if $field->is_auto_increment;
261 push @field_defs, $field_def;
56120730 262 }
9398955f 263
d529894e 264 #
265 # Indices
266 #
1c14e9f1 267 my @index_defs;
bc60004d 268 my %indexed_fields;
1c14e9f1 269 for my $index ( $table->get_indices ) {
270 push @index_defs, join( ' ',
35ed60b5 271 lc $index->type eq 'normal' ? 'INDEX' : $index->type,
1c14e9f1 272 $index->name,
273 '(' . join( ', ', $index->fields ) . ')'
274 );
bc60004d 275 $indexed_fields{ $_ } = 1 for $index->fields;
d529894e 276 }
277
278 #
5e56da9a 279 # Constraints -- need to handle more than just FK. -ky
280 #
2620fc1c 281 my @constraint_defs;
2bc23e82 282 my @constraints = $table->get_constraints;
283 for my $c ( @constraints ) {
1c14e9f1 284 my @fields = $c->fields or next;
5e56da9a 285
1c14e9f1 286 if ( $c->type eq PRIMARY_KEY ) {
287 push @constraint_defs,
288 'PRIMARY KEY (' . join(', ', @fields). ')';
289 }
290 elsif ( $c->type eq UNIQUE ) {
291 push @constraint_defs,
292 'UNIQUE (' . join(', ', @fields). ')';
293 }
294 elsif ( $c->type eq FOREIGN_KEY ) {
bc60004d 295 #
296 # Make sure FK field is indexed or MySQL complains.
297 #
298 unless ( $indexed_fields{ $fields[0] } ) {
299 push @index_defs, "INDEX ($fields[0])";
2bc23e82 300 $indexed_fields{ $fields[0] } = 1;
bc60004d 301 }
302
1c14e9f1 303 my $def = join(' ',
304 map { $_ || () } 'FOREIGN KEY', $c->name
305 );
306
307 $def .= ' (' . join( ', ', @fields ) . ')';
308
309 $def .= ' REFERENCES ' . $c->reference_table;
310
2bc23e82 311 my @rfields = map { $_ || () } $c->reference_fields;
312 unless ( @rfields ) {
313 my $rtable_name = $c->reference_table;
314 if ( my $ref_table = $schema->get_table( $rtable_name ) ) {
315 push @rfields, $ref_table->primary_key;
316 }
317 else {
318 warn "Can't find reference table '$rtable_name' " .
319 "in schema\n" if $show_warnings;
320 }
321 }
322
323 if ( @rfields ) {
1c14e9f1 324 $def .= ' (' . join( ', ', @rfields ) . ')';
5e56da9a 325 }
2bc23e82 326 else {
327 warn "FK constraint on " . $table->name . '.' .
328 join('', @fields) . " has no reference fields\n"
329 if $show_warnings;
330 }
5e56da9a 331
1c14e9f1 332 if ( $c->match_type ) {
5e56da9a 333 $def .= ' MATCH ' .
1c14e9f1 334 ( $c->match_type =~ /full/i ) ? 'FULL' : 'PARTIAL';
5e56da9a 335 }
336
1c14e9f1 337 if ( $c->on_delete ) {
338 $def .= ' ON DELETE '.join( ' ', $c->on_delete );
586809da 339 }
340
1c14e9f1 341 if ( $c->on_update ) {
342 $def .= ' ON UPDATE '.join( ' ', $c->on_update );
586809da 343 }
5e56da9a 344
2620fc1c 345 push @constraint_defs, $def;
5e56da9a 346 }
347 }
348
1c14e9f1 349 $create .= join(",\n", map { " $_" }
350 @field_defs, @index_defs, @constraint_defs
351 );
5e56da9a 352
353 #
9398955f 354 # Footer
d529894e 355 #
c45c546e 356 $create .= "\n)";
5a0c7b43 357 my $mysql_table_type = $table->extra('mysql_table_type');
1ded8513 358 my $charset = $table->extra('mysql_charset');
359 my $collate = $table->extra('mysql_collate');
f8b6e804 360 my $comments = $table->comments;
361
5a0c7b43 362 $create .= " Type=$mysql_table_type" if $mysql_table_type;
1ded8513 363 $create .= " DEFAULT CHARACTER SET $charset" if $charset;
364 $create .= " COLLATE $collate" if $collate;
f8b6e804 365 $create .= qq[ comment='$comments'] if $comments;
9398955f 366 $create .= ";\n\n";
367 }
368
9398955f 369 return $create;
370}
371
9398955f 3721;
9398955f 373
c855a748 374# -------------------------------------------------------------------
9398955f 375
c855a748 376=pod
377
378=head1 SEE ALSO
379
380SQL::Translator, http://www.mysql.com/.
9398955f 381
2d6979da 382=head1 AUTHORS
9398955f 383
758ab1cd 384darren chamberlain E<lt>darren@cpan.orgE<gt>,
c855a748 385Ken Y. Clark E<lt>kclark@cpan.orgE<gt>.
386
387=cut