Produce either a list of statements or a string
[dbsrgits/SQL-Translator.git] / lib / SQL / Translator / Producer / MySQL.pm
CommitLineData
9398955f 1package SQL::Translator::Producer::MySQL;
2
49e1eb70 3# -------------------------------------------------------------------
0013ee25 4# $Id: MySQL.pm,v 1.47 2006-06-07 16:32:11 schiffbruechige 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
4257646e 72=item mysql_character_set
73
74MySql-4.1+. Set the tables character set.
75Run SHOW CHARACTER SET to see list.
76
77=item mysql_collate
78
79MySql-4.1+. Set the tables colation order.
80
1ded8513 81=item table.mysql_charset table.mysql_collate
82
83Set the tables default charater set and collation order.
84
85=item field.mysql_charset field.mysql_collate
86
87Set the fields charater set and collation order.
88
5a0c7b43 89=back
90
c855a748 91=cut
92
9398955f 93use strict;
d529894e 94use vars qw[ $VERSION $DEBUG ];
0013ee25 95$VERSION = sprintf "%d.%02d", q$Revision: 1.47 $ =~ /(\d+)\.(\d+)/;
5636ed00 96$DEBUG = 0 unless defined $DEBUG;
9398955f 97
98use Data::Dumper;
1c14e9f1 99use SQL::Translator::Schema::Constants;
5ee19df8 100use SQL::Translator::Utils qw(debug header_comment);
9398955f 101
d2344c83 102#
103# Use only lowercase for the keys (e.g. "long" and not "LONG")
104#
2620fc1c 105my %translate = (
106 #
107 # Oracle types
108 #
109 varchar2 => 'varchar',
110 long => 'text',
d2344c83 111 clob => 'longtext',
2620fc1c 112
113 #
114 # Sybase types
115 #
116 int => 'integer',
117 money => 'float',
118 real => 'double',
119 comment => 'text',
120 bit => 'tinyint',
87779799 121
122 #
123 # Access types
124 #
125 'long integer' => 'integer',
126 'text' => 'text',
127 'datetime' => 'datetime',
2620fc1c 128);
129
9398955f 130sub produce {
a1d94525 131 my $translator = shift;
132 local $DEBUG = $translator->debug;
133 my $no_comments = $translator->no_comments;
134 my $add_drop_table = $translator->add_drop_table;
135 my $schema = $translator->schema;
2bc23e82 136 my $show_warnings = $translator->show_warnings || 0;
d529894e 137
1a24938d 138 debug("PKG: Beginning production\n");
d529894e 139
140 my $create;
5ee19df8 141 $create .= header_comment unless ($no_comments);
0823773d 142 # \todo Don't set if MySQL 3.x is set on command line
da147d03 143 $create .= "SET foreign_key_checks=0;\n\n";
9398955f 144
5a0c7b43 145 #
146 # Work out which tables need to be InnoDB to support foreign key
147 # constraints. We do this first as we need InnoDB at both ends.
148 #
149 foreach ( map { $_->get_constraints } $schema->get_tables ) {
150 foreach my $meth (qw/table reference_table/) {
151 my $table = $schema->get_table($_->$meth) || next;
152 next if $table->extra('mysql_table_type');
153 $table->extra( 'mysql_table_type' => 'InnoDB');
154 }
155 }
156
157 #
158 # Generate sql
159 #
0013ee25 160 my (@table_defs);
1c14e9f1 161 for my $table ( $schema->get_tables ) {
0013ee25 162 push @table_defs, create_table($table,
163 { add_drop_table => $add_drop_table,
164 show_warnings => $show_warnings,
165 no_comments => $no_comments
166 });
167 }
9398955f 168
0013ee25 169 return wantarray ? @table_defs : $create . join ('', @table_defs);
170}
9398955f 171
0013ee25 172sub create_table
173{
174 my ($table, $options) = @_;
2620fc1c 175
0013ee25 176 my $table_name = $table->name;
177 debug("PKG: Looking at table '$table_name'\n");
6d3f6379 178
0013ee25 179 #
180 # Header. Should this look like what mysqldump produces?
181 #
182 my $create .= "--\n-- Table: $table_name\n--\n" unless $options->{no_comments};
183 $create .= qq[DROP TABLE IF EXISTS $table_name;\n] if $options->{add_drop_table};
184 $create .= "CREATE TABLE $table_name (\n";
2b695517 185
0013ee25 186 #
187 # Fields
188 #
189 my @field_defs;
190 for my $field ( $table->get_fields ) {
191 push @field_defs, create_field($field);
192 }
1ded8513 193
0013ee25 194 #
195 # Indices
196 #
197 my @index_defs;
198 my %indexed_fields;
199 for my $index ( $table->get_indices ) {
200 push @index_defs, create_index($index);
201 $indexed_fields{ $_ } = 1 for $index->fields;
202 }
d529894e 203
0013ee25 204 #
205 # Constraints -- need to handle more than just FK. -ky
206 #
207 my @constraint_defs;
208 my @constraints = $table->get_constraints;
209 for my $c ( @constraints ) {
210 push @constraint_defs, create_constraint($c, $options);
211
212 unless ( $indexed_fields{ ($c->fields())[0] } ) {
213 push @index_defs, "INDEX (" . ($c->fields())[0] . ")";
214 $indexed_fields{ ($c->fields())[0] } = 1;
215 }
216 }
1ded8513 217
0013ee25 218 $create .= join(",\n", map { " $_" }
219 @field_defs, @index_defs, @constraint_defs
220 );
9398955f 221
0013ee25 222 #
223 # Footer
224 #
225 $create .= "\n)";
226 my $table_type_defined = 0;
227 for my $t1_option_ref ( $table->options ) {
228 my($key, $value) = %{$t1_option_ref};
229 $table_type_defined = 1
230 if uc $key eq 'ENGINE' or uc $key eq 'TYPE';
231 $create .= " $key=$value";
232 }
233 my $mysql_table_type = $table->extra('mysql_table_type');
234 #my $charset = $table->extra('mysql_character_set');
235 #my $collate = $table->extra('mysql_collate');
236 #$create .= " Type=$mysql_table_type" if $mysql_table_type;
237 #$create .= " DEFAULT CHARACTER SET $charset" if $charset;
238 #$create .= " COLLATE $collate" if $collate;
239 $create .= " Type=$mysql_table_type"
240 if $mysql_table_type && !$table_type_defined;
241 my $charset = $table->extra('mysql_charset');
242 my $collate = $table->extra('mysql_collate');
243 my $comments = $table->comments;
244
245 $create .= " DEFAULT CHARACTER SET $charset" if $charset;
246 $create .= " COLLATE $collate" if $collate;
247 $create .= qq[ comment='$comments'] if $comments;
248 $create .= ";\n\n";
9398955f 249
0013ee25 250 return $create;
251}
f8b6e804 252
0013ee25 253sub create_field
254{
255 my ($field) = @_;
9398955f 256
0013ee25 257 my $field_name = $field->name;
258 debug("PKG: Looking at field '$field_name'\n");
259 my $field_def = $field_name;
260
261 # data type and size
262 my $data_type = $field->data_type;
263 my @size = $field->size;
264 my %extra = $field->extra;
265 my $list = $extra{'list'} || [];
266 # \todo deal with embedded quotes
267 my $commalist = join( ', ', map { qq['$_'] } @$list );
268 my $charset = $extra{'mysql_charset'};
269 my $collate = $extra{'mysql_collate'};
270
271 #
272 # Oracle "number" type -- figure best MySQL type
273 #
274 if ( lc $data_type eq 'number' ) {
275 # not an integer
276 if ( scalar @size > 1 ) {
277 $data_type = 'double';
d529894e 278 }
0013ee25 279 elsif ( $size[0] && $size[0] >= 12 ) {
280 $data_type = 'bigint';
281 }
282 elsif ( $size[0] && $size[0] <= 1 ) {
283 $data_type = 'tinyint';
284 }
285 else {
286 $data_type = 'int';
287 }
288 }
289 #
290 # Convert a large Oracle varchar to "text"
291 #
292 elsif ( $data_type =~ /char/i && $size[0] > 255 ) {
293 $data_type = 'text';
294 @size = ();
295 }
296 elsif ( $data_type =~ /char/i && ! $size[0] ) {
297 @size = (255);
298 }
299 elsif ( $data_type =~ /boolean/i ) {
300 $data_type = 'enum';
301 $commalist = "'0','1'";
302 }
303 elsif ( exists $translate{ lc $data_type } ) {
304 $data_type = $translate{ lc $data_type };
305 }
306
307 @size = () if $data_type =~ /(text|blob)/i;
308
309 if ( $data_type =~ /(double|float)/ && scalar @size == 1 ) {
310 push @size, '0';
311 }
d529894e 312
0013ee25 313 $field_def .= " $data_type";
314
315 if ( lc $data_type eq 'enum' ) {
316 $field_def .= '(' . $commalist . ')';
317 }
318 elsif ( defined $size[0] && $size[0] > 0 ) {
319 $field_def .= '(' . join( ', ', @size ) . ')';
320 }
321
322 # char sets
323 $field_def .= " CHARACTER SET $charset" if $charset;
324 $field_def .= " COLLATE $collate" if $collate;
325
326 # MySQL qualifiers
327 for my $qual ( qw[ binary unsigned zerofill ] ) {
328 my $val = $extra{ $qual } || $extra{ uc $qual } or next;
329 $field_def .= " $qual";
330 }
331 for my $qual ( 'character set', 'collate', 'on update' ) {
332 my $val = $extra{ $qual } || $extra{ uc $qual } or next;
333 $field_def .= " $qual $val";
334 }
335
336 # Null?
337 $field_def .= ' NOT NULL' unless $field->is_nullable;
338
339 # Default? XXX Need better quoting!
340 my $default = $field->default_value;
341 if ( defined $default ) {
342 if ( uc $default eq 'NULL') {
343 $field_def .= ' DEFAULT NULL';
344 } else {
345 $field_def .= " DEFAULT '$default'";
346 }
347 }
348
349 if ( my $comments = $field->comments ) {
350 $field_def .= qq[ comment '$comments'];
351 }
352
353 # auto_increment?
354 $field_def .= " auto_increment" if $field->is_auto_increment;
355
356 return $field_def;
357}
358
359sub create_index
360{
361 my ($index) = @_;
362
363 return join( ' ',
364 lc $index->type eq 'normal' ? 'INDEX' : $index->type,
365 $index->name,
366 '(' . join( ', ', $index->fields ) . ')'
367 );
368
369}
370
371sub create_constraint
372{
373 my ($c, $options) = @_;
374
375 my @fields = $c->fields or next;
376
377 if ( $c->type eq PRIMARY_KEY ) {
378 return 'PRIMARY KEY (' . join(', ', @fields). ')';
379 }
380 elsif ( $c->type eq UNIQUE ) {
381 return
382 'UNIQUE '.
383 (defined $c->name ? $c->name.' ' : '').
384 '(' . join(', ', @fields). ')';
385 }
386 elsif ( $c->type eq FOREIGN_KEY ) {
d529894e 387 #
0013ee25 388 # Make sure FK field is indexed or MySQL complains.
5e56da9a 389 #
0013ee25 390
391 my $def = join(' ',
392 map { $_ || () } 'CONSTRAINT', $c->name, 'FOREIGN KEY'
393 );
394
395 $def .= ' (' . join( ', ', @fields ) . ')';
396
397 $def .= ' REFERENCES ' . $c->reference_table;
398
399 my @rfields = map { $_ || () } $c->reference_fields;
400 unless ( @rfields ) {
401 my $rtable_name = $c->reference_table;
402 if ( my $ref_table = $c->table->schema->get_table( $rtable_name ) ) {
403 push @rfields, $ref_table->primary_key;
1c14e9f1 404 }
0013ee25 405 else {
406 warn "Can't find reference table '$rtable_name' " .
407 "in schema\n" if $options->{show_warnings};
5e56da9a 408 }
409 }
410
0013ee25 411 if ( @rfields ) {
412 $def .= ' (' . join( ', ', @rfields ) . ')';
413 }
414 else {
415 warn "FK constraint on " . $c->table->name . '.' .
416 join('', @fields) . " has no reference fields\n"
417 if $options->{show_warnings};
418 }
5e56da9a 419
0013ee25 420 if ( $c->match_type ) {
421 $def .= ' MATCH ' .
422 ( $c->match_type =~ /full/i ) ? 'FULL' : 'PARTIAL';
423 }
424
425 if ( $c->on_delete ) {
426 $def .= ' ON DELETE '.join( ' ', $c->on_delete );
427 }
428
429 if ( $c->on_update ) {
430 $def .= ' ON UPDATE '.join( ' ', $c->on_update );
431 }
9398955f 432 }
433
0013ee25 434}
435
436sub alter_field
437{
438 my ($from_field, $to_field) = @_;
439
440 my $out = sprintf('ALTER TABLE %s CHANGE COLUMN %s %s',
441 $to_field->table->name,
442 $to_field->name,
443 create_field($to_field));
444
445 return $out;
446}
447
448sub add_field
449{
450 my ($new_field) = @_;
451
452 my $out = sprintf('ALTER TABLE %s ADD COLUMN %s',
453 $new_field->table->name,
454 create_field($new_field));
455
456 return $out;
457
458}
459
460sub drop_field
461{
462 my ($old_field) = @_;
463
464 my $out = sprintf('ALTER TABLE %s DROP COLUMN %s',
465 $old_field->table->name,
466 $old_field->name);
467
468 return $out;
469
9398955f 470}
471
9398955f 4721;
9398955f 473
c855a748 474# -------------------------------------------------------------------
9398955f 475
c855a748 476=pod
477
478=head1 SEE ALSO
479
480SQL::Translator, http://www.mysql.com/.
9398955f 481
2d6979da 482=head1 AUTHORS
9398955f 483
758ab1cd 484darren chamberlain E<lt>darren@cpan.orgE<gt>,
c855a748 485Ken Y. Clark E<lt>kclark@cpan.orgE<gt>.
486
487=cut