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