Ignore all TT test while TT is broken
[dbsrgits/SQL-Translator.git] / lib / SQL / Translator / Producer / MySQL.pm
CommitLineData
9398955f 1package SQL::Translator::Producer::MySQL;
2
49e1eb70 3# -------------------------------------------------------------------
fb149f81 4# $Id: MySQL.pm,v 1.52 2006-11-27 19:28:04 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 ];
fb149f81 96$VERSION = sprintf "%d.%02d", q$Revision: 1.52 $ =~ /(\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
fb149f81 397 my $qf = $options->{quote_field_names} || '';
398 my $qt = $options->{quote_table_names} || '';
399 my $counter = ($options->{fk_name_counter} ||= {});
fe0f47d0 400
0013ee25 401 my @fields = $c->fields or next;
402
403 if ( $c->type eq PRIMARY_KEY ) {
fe0f47d0 404 return 'PRIMARY KEY (' . $qf . join("$qf, $qf", @fields). $qf . ')';
0013ee25 405 }
406 elsif ( $c->type eq UNIQUE ) {
407 return
fe0f47d0 408 'UNIQUE '.
409 (defined $c->name ? $qf.$c->name.$qf.' ' : '').
410 '(' . $qf . join("$qf, $qf", @fields). $qf . ')';
0013ee25 411 }
412 elsif ( $c->type eq FOREIGN_KEY ) {
d529894e 413 #
0013ee25 414 # Make sure FK field is indexed or MySQL complains.
5e56da9a 415 #
0013ee25 416
fb149f81 417 $counter->{$c->table} ||= {};
0013ee25 418 my $def = join(' ',
fb149f81 419 map { $_ || () }
420 'CONSTRAINT',
421 $qt . join('_', $c->table,
422 $c->name,
423 ($counter->{$c->table}{$c->name}++ || ())
424 ) . $qt,
425 'FOREIGN KEY'
426 );
0013ee25 427
fe0f47d0 428 $def .= ' ('.$qf . join( "$qf, $qf", @fields ) . $qf . ')';
0013ee25 429
fe0f47d0 430 $def .= ' REFERENCES ' . $qt . $c->reference_table . $qt;
0013ee25 431
432 my @rfields = map { $_ || () } $c->reference_fields;
433 unless ( @rfields ) {
434 my $rtable_name = $c->reference_table;
435 if ( my $ref_table = $c->table->schema->get_table( $rtable_name ) ) {
436 push @rfields, $ref_table->primary_key;
1c14e9f1 437 }
0013ee25 438 else {
439 warn "Can't find reference table '$rtable_name' " .
440 "in schema\n" if $options->{show_warnings};
5e56da9a 441 }
442 }
443
0013ee25 444 if ( @rfields ) {
fe0f47d0 445 $def .= ' (' . $qf . join( "$qf, $qf", @rfields ) . $qf . ')';
0013ee25 446 }
447 else {
448 warn "FK constraint on " . $c->table->name . '.' .
449 join('', @fields) . " has no reference fields\n"
450 if $options->{show_warnings};
451 }
5e56da9a 452
0013ee25 453 if ( $c->match_type ) {
454 $def .= ' MATCH ' .
455 ( $c->match_type =~ /full/i ) ? 'FULL' : 'PARTIAL';
456 }
457
458 if ( $c->on_delete ) {
459 $def .= ' ON DELETE '.join( ' ', $c->on_delete );
460 }
461
462 if ( $c->on_update ) {
463 $def .= ' ON UPDATE '.join( ' ', $c->on_update );
464 }
cd0ea0fd 465 return $def;
9398955f 466 }
467
cd0ea0fd 468 return undef;
0013ee25 469}
470
471sub alter_field
472{
fe0f47d0 473 my ($from_field, $to_field, $options) = @_;
474
475 my $qf = $options->{quote_field_name} || '';
476 my $qt = $options->{quote_table_name} || '';
0013ee25 477
478 my $out = sprintf('ALTER TABLE %s CHANGE COLUMN %s %s',
fe0f47d0 479 $qt . $to_field->table->name . $qt,
480 $qf . $to_field->name . $qf,
481 create_field($to_field, $options));
0013ee25 482
483 return $out;
484}
485
486sub add_field
487{
fe0f47d0 488 my ($new_field, $options) = @_;
489
490 my $qt = $options->{quote_table_name} || '';
0013ee25 491
492 my $out = sprintf('ALTER TABLE %s ADD COLUMN %s',
fe0f47d0 493 $qt . $new_field->table->name . $qt,
494 create_field($new_field, $options));
0013ee25 495
496 return $out;
497
498}
499
500sub drop_field
501{
fe0f47d0 502 my ($old_field, $options) = @_;
0013ee25 503
fe0f47d0 504 my $qf = $options->{quote_field_name} || '';
505 my $qt = $options->{quote_table_name} || '';
506
0013ee25 507 my $out = sprintf('ALTER TABLE %s DROP COLUMN %s',
fe0f47d0 508 $qt . $old_field->table->name . $qt,
509 $qf . $old_field->name . $qf);
0013ee25 510
511 return $out;
512
9398955f 513}
514
9398955f 5151;
9398955f 516
c855a748 517# -------------------------------------------------------------------
9398955f 518
c855a748 519=pod
520
521=head1 SEE ALSO
522
523SQL::Translator, http://www.mysql.com/.
9398955f 524
2d6979da 525=head1 AUTHORS
9398955f 526
758ab1cd 527darren chamberlain E<lt>darren@cpan.orgE<gt>,
c855a748 528Ken Y. Clark E<lt>kclark@cpan.orgE<gt>.
529
530=cut