Branch for diff refactoring
[dbsrgits/SQL-Translator.git] / lib / SQL / Translator / Producer / MySQL.pm
CommitLineData
9398955f 1package SQL::Translator::Producer::MySQL;
2
49e1eb70 3# -------------------------------------------------------------------
866d012e 4# $Id: MySQL.pm,v 1.54 2007-11-10 03:36:43 mwz444 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;
da5a1bae 95use vars qw[ $VERSION $DEBUG %used_names ];
866d012e 96$VERSION = sprintf "%d.%02d", q$Revision: 1.54 $ =~ /(\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;
da5a1bae 134 local %used_names;
a1d94525 135 my $no_comments = $translator->no_comments;
136 my $add_drop_table = $translator->add_drop_table;
137 my $schema = $translator->schema;
2bc23e82 138 my $show_warnings = $translator->show_warnings || 0;
d529894e 139
fe0f47d0 140 my ($qt, $qf) = ('','');
141 $qt = '`' if $translator->quote_table_names;
142 $qf = '`' if $translator->quote_field_names;
143
1a24938d 144 debug("PKG: Beginning production\n");
da5a1bae 145 %used_names = ();
d529894e 146 my $create;
5ee19df8 147 $create .= header_comment unless ($no_comments);
0823773d 148 # \todo Don't set if MySQL 3.x is set on command line
da147d03 149 $create .= "SET foreign_key_checks=0;\n\n";
9398955f 150
5a0c7b43 151 #
152 # Work out which tables need to be InnoDB to support foreign key
153 # constraints. We do this first as we need InnoDB at both ends.
154 #
155 foreach ( map { $_->get_constraints } $schema->get_tables ) {
cd0ea0fd 156 next unless $_->type eq FOREIGN_KEY;
5a0c7b43 157 foreach my $meth (qw/table reference_table/) {
158 my $table = $schema->get_table($_->$meth) || next;
159 next if $table->extra('mysql_table_type');
160 $table->extra( 'mysql_table_type' => 'InnoDB');
161 }
162 }
163
164 #
165 # Generate sql
166 #
cd0ea0fd 167 my @table_defs =();
1c14e9f1 168 for my $table ( $schema->get_tables ) {
cd0ea0fd 169# print $table->name, "\n";
0013ee25 170 push @table_defs, create_table($table,
fe0f47d0 171 { add_drop_table => $add_drop_table,
172 show_warnings => $show_warnings,
173 no_comments => $no_comments,
174 quote_table_names => $qt,
175 quote_field_names => $qf
0013ee25 176 });
177 }
9398955f 178
cd0ea0fd 179# print "@table_defs\n";
fa94b25f 180 push @table_defs, "SET foreign_key_checks=1;\n\n";
cd0ea0fd 181
fa94b25f 182 return wantarray ? ($create, @table_defs) : $create . join ('', @table_defs);
0013ee25 183}
9398955f 184
0013ee25 185sub create_table
186{
187 my ($table, $options) = @_;
2620fc1c 188
fe0f47d0 189 my $qt = $options->{quote_table_names} || '';
190 my $qf = $options->{quote_field_names} || '';
191
0013ee25 192 my $table_name = $table->name;
193 debug("PKG: Looking at table '$table_name'\n");
6d3f6379 194
0013ee25 195 #
196 # Header. Should this look like what mysqldump produces?
197 #
cd0ea0fd 198 my $create = '';
fa94b25f 199 my $drop;
fe0f47d0 200 $create .= "--\n-- Table: $qt$table_name$qt\n--\n" unless $options->{no_comments};
fa94b25f 201 $drop = qq[DROP TABLE IF EXISTS $qt$table_name$qt;\n] if $options->{add_drop_table};
fe0f47d0 202 $create .= "CREATE TABLE $qt$table_name$qt (\n";
2b695517 203
0013ee25 204 #
205 # Fields
206 #
207 my @field_defs;
208 for my $field ( $table->get_fields ) {
fe0f47d0 209 push @field_defs, create_field($field, $options);
0013ee25 210 }
1ded8513 211
0013ee25 212 #
213 # Indices
214 #
215 my @index_defs;
216 my %indexed_fields;
217 for my $index ( $table->get_indices ) {
fe0f47d0 218 push @index_defs, create_index($index, $options);
0013ee25 219 $indexed_fields{ $_ } = 1 for $index->fields;
220 }
d529894e 221
0013ee25 222 #
223 # Constraints -- need to handle more than just FK. -ky
224 #
225 my @constraint_defs;
226 my @constraints = $table->get_constraints;
227 for my $c ( @constraints ) {
cd0ea0fd 228 my $constr = create_constraint($c, $options);
229 push @constraint_defs, $constr if($constr);
0013ee25 230
da5a1bae 231 unless ( $indexed_fields{ ($c->fields())[0] } || $c->type ne FOREIGN_KEY ) {
232 push @index_defs, "INDEX ($qf" . ($c->fields())[0] . "$qf)";
233 $indexed_fields{ ($c->fields())[0] } = 1;
234 }
0013ee25 235 }
1ded8513 236
0013ee25 237 $create .= join(",\n", map { " $_" }
238 @field_defs, @index_defs, @constraint_defs
239 );
9398955f 240
0013ee25 241 #
242 # Footer
243 #
244 $create .= "\n)";
da5a1bae 245 $create .= generate_table_options($table) || '';
0013ee25 246 $create .= ";\n\n";
9398955f 247
fa94b25f 248 return $drop ? ($drop,$create) : $create;
0013ee25 249}
f8b6e804 250
da5a1bae 251sub generate_table_options
252{
253 my ($table) = @_;
254 my $create;
255
256 my $table_type_defined = 0;
257 for my $t1_option_ref ( $table->options ) {
258 my($key, $value) = %{$t1_option_ref};
259 $table_type_defined = 1
260 if uc $key eq 'ENGINE' or uc $key eq 'TYPE';
261 $create .= " $key=$value";
262 }
263 my $mysql_table_type = $table->extra('mysql_table_type');
264 #my $charset = $table->extra('mysql_character_set');
265 #my $collate = $table->extra('mysql_collate');
266 #$create .= " Type=$mysql_table_type" if $mysql_table_type;
267 #$create .= " DEFAULT CHARACTER SET $charset" if $charset;
268 #$create .= " COLLATE $collate" if $collate;
269 $create .= " Type=$mysql_table_type"
270 if $mysql_table_type && !$table_type_defined;
271 my $charset = $table->extra('mysql_charset');
272 my $collate = $table->extra('mysql_collate');
273 my $comments = $table->comments;
274
275 $create .= " DEFAULT CHARACTER SET $charset" if $charset;
276 $create .= " COLLATE $collate" if $collate;
277 $create .= qq[ comment='$comments'] if $comments;
278 return $create;
279}
280
0013ee25 281sub create_field
282{
fe0f47d0 283 my ($field, $options) = @_;
284
285 my $qf = $options->{quote_field_names} ||= '';
9398955f 286
0013ee25 287 my $field_name = $field->name;
288 debug("PKG: Looking at field '$field_name'\n");
fe0f47d0 289 my $field_def = "$qf$field_name$qf";
0013ee25 290
291 # data type and size
292 my $data_type = $field->data_type;
293 my @size = $field->size;
294 my %extra = $field->extra;
295 my $list = $extra{'list'} || [];
296 # \todo deal with embedded quotes
297 my $commalist = join( ', ', map { qq['$_'] } @$list );
298 my $charset = $extra{'mysql_charset'};
299 my $collate = $extra{'mysql_collate'};
300
301 #
302 # Oracle "number" type -- figure best MySQL type
303 #
304 if ( lc $data_type eq 'number' ) {
305 # not an integer
306 if ( scalar @size > 1 ) {
307 $data_type = 'double';
d529894e 308 }
0013ee25 309 elsif ( $size[0] && $size[0] >= 12 ) {
310 $data_type = 'bigint';
311 }
312 elsif ( $size[0] && $size[0] <= 1 ) {
313 $data_type = 'tinyint';
314 }
315 else {
316 $data_type = 'int';
317 }
318 }
319 #
320 # Convert a large Oracle varchar to "text"
321 #
322 elsif ( $data_type =~ /char/i && $size[0] > 255 ) {
323 $data_type = 'text';
324 @size = ();
325 }
326 elsif ( $data_type =~ /char/i && ! $size[0] ) {
327 @size = (255);
328 }
329 elsif ( $data_type =~ /boolean/i ) {
330 $data_type = 'enum';
331 $commalist = "'0','1'";
332 }
333 elsif ( exists $translate{ lc $data_type } ) {
334 $data_type = $translate{ lc $data_type };
335 }
336
337 @size = () if $data_type =~ /(text|blob)/i;
338
339 if ( $data_type =~ /(double|float)/ && scalar @size == 1 ) {
340 push @size, '0';
341 }
d529894e 342
0013ee25 343 $field_def .= " $data_type";
344
345 if ( lc $data_type eq 'enum' ) {
346 $field_def .= '(' . $commalist . ')';
347 }
348 elsif ( defined $size[0] && $size[0] > 0 ) {
349 $field_def .= '(' . join( ', ', @size ) . ')';
350 }
351
352 # char sets
353 $field_def .= " CHARACTER SET $charset" if $charset;
354 $field_def .= " COLLATE $collate" if $collate;
355
356 # MySQL qualifiers
357 for my $qual ( qw[ binary unsigned zerofill ] ) {
358 my $val = $extra{ $qual } || $extra{ uc $qual } or next;
359 $field_def .= " $qual";
360 }
361 for my $qual ( 'character set', 'collate', 'on update' ) {
362 my $val = $extra{ $qual } || $extra{ uc $qual } or next;
363 $field_def .= " $qual $val";
364 }
365
366 # Null?
367 $field_def .= ' NOT NULL' unless $field->is_nullable;
368
369 # Default? XXX Need better quoting!
370 my $default = $field->default_value;
371 if ( defined $default ) {
372 if ( uc $default eq 'NULL') {
373 $field_def .= ' DEFAULT NULL';
374 } else {
375 $field_def .= " DEFAULT '$default'";
376 }
377 }
378
379 if ( my $comments = $field->comments ) {
380 $field_def .= qq[ comment '$comments'];
381 }
382
383 # auto_increment?
384 $field_def .= " auto_increment" if $field->is_auto_increment;
385
386 return $field_def;
387}
388
da5a1bae 389sub alter_create_index
390{
391 my ($index, $options) = @_;
392
393 my $qt = $options->{quote_table_names} || '';
394 my $qf = $options->{quote_field_names} || '';
395
396 return join( ' ',
397 'ALTER TABLE',
398 $qt.$index->table->name.$qt,
399 'ADD',
400 create_index(@_)
401 );
402}
403
0013ee25 404sub create_index
405{
fe0f47d0 406 my ($index, $options) = @_;
407
408 my $qf = $options->{quote_field_names} || '';
0013ee25 409
410 return join( ' ',
da5a1bae 411 lc $index->type eq 'normal' ? 'INDEX' : $index->type . ' INDEX',
0013ee25 412 $index->name,
fe0f47d0 413 '(' . $qf . join( "$qf, $qf", $index->fields ) . $qf . ')'
0013ee25 414 );
415
416}
417
da5a1bae 418sub alter_drop_index
419{
420 my ($index, $options) = @_;
421
422 my $qt = $options->{quote_table_names} || '';
423 my $qf = $options->{quote_field_names} || '';
424
425 return join( ' ',
426 'ALTER TABLE',
427 $qt.$index->table->name.$qt,
428 'DROP',
429 'INDEX',
430 $index->name || $index->fields
431 );
432
433}
434
435sub alter_drop_constraint
436{
437 my ($c, $options) = @_;
438
439 my $qt = $options->{quote_table_names} || '';
440 my $qc = $options->{quote_constraint_names} || '';
441
442 my $out = sprintf('ALTER TABLE %s DROP %s %s',
443 $c->table->name,
444 $c->type,
445 $qc . $c->name . $qc );
446
447 return $out;
448}
449
450sub alter_create_constraint
451{
452 my ($index, $options) = @_;
453
454 my $qt = $options->{quote_table_names} || '';
455 return join( ' ',
456 'ALTER TABLE',
457 $qt.$index->table->name.$qt,
458 'ADD',
459 create_constraint(@_) );
460}
461
0013ee25 462sub create_constraint
463{
464 my ($c, $options) = @_;
465
fb149f81 466 my $qf = $options->{quote_field_names} || '';
467 my $qt = $options->{quote_table_names} || '';
da5a1bae 468 my $leave_name = $options->{leave_name} || undef;
fb149f81 469 my $counter = ($options->{fk_name_counter} ||= {});
fe0f47d0 470
0013ee25 471 my @fields = $c->fields or next;
472
473 if ( $c->type eq PRIMARY_KEY ) {
fe0f47d0 474 return 'PRIMARY KEY (' . $qf . join("$qf, $qf", @fields). $qf . ')';
0013ee25 475 }
476 elsif ( $c->type eq UNIQUE ) {
477 return
fe0f47d0 478 'UNIQUE '.
479 (defined $c->name ? $qf.$c->name.$qf.' ' : '').
480 '(' . $qf . join("$qf, $qf", @fields). $qf . ')';
0013ee25 481 }
482 elsif ( $c->type eq FOREIGN_KEY ) {
d529894e 483 #
0013ee25 484 # Make sure FK field is indexed or MySQL complains.
5e56da9a 485 #
0013ee25 486
866d012e 487 my $table = $c->table;
da5a1bae 488 my $c_name = $c->name;
866d012e 489
490 # Give the constraint a name if it doesn't have one, so it doens't feel
491 # left out
492 unless ( $c_name ){
493 $c_name = $table->name . '_fk';
494 }
495
496 $counter->{$table} ||= {};
0013ee25 497 my $def = join(' ',
fb149f81 498 map { $_ || () }
499 'CONSTRAINT',
da5a1bae 500 $qt . join('_', next_unused_name($c_name)
fb149f81 501 ) . $qt,
502 'FOREIGN KEY'
503 );
0013ee25 504
da5a1bae 505
fe0f47d0 506 $def .= ' ('.$qf . join( "$qf, $qf", @fields ) . $qf . ')';
0013ee25 507
fe0f47d0 508 $def .= ' REFERENCES ' . $qt . $c->reference_table . $qt;
0013ee25 509
510 my @rfields = map { $_ || () } $c->reference_fields;
511 unless ( @rfields ) {
512 my $rtable_name = $c->reference_table;
866d012e 513 if ( my $ref_table = $table->schema->get_table( $rtable_name ) ) {
0013ee25 514 push @rfields, $ref_table->primary_key;
1c14e9f1 515 }
0013ee25 516 else {
517 warn "Can't find reference table '$rtable_name' " .
518 "in schema\n" if $options->{show_warnings};
5e56da9a 519 }
520 }
521
0013ee25 522 if ( @rfields ) {
fe0f47d0 523 $def .= ' (' . $qf . join( "$qf, $qf", @rfields ) . $qf . ')';
0013ee25 524 }
525 else {
866d012e 526 warn "FK constraint on " . $table->name . '.' .
0013ee25 527 join('', @fields) . " has no reference fields\n"
528 if $options->{show_warnings};
529 }
5e56da9a 530
0013ee25 531 if ( $c->match_type ) {
532 $def .= ' MATCH ' .
533 ( $c->match_type =~ /full/i ) ? 'FULL' : 'PARTIAL';
534 }
535
536 if ( $c->on_delete ) {
537 $def .= ' ON DELETE '.join( ' ', $c->on_delete );
538 }
539
540 if ( $c->on_update ) {
541 $def .= ' ON UPDATE '.join( ' ', $c->on_update );
542 }
cd0ea0fd 543 return $def;
9398955f 544 }
545
cd0ea0fd 546 return undef;
0013ee25 547}
548
da5a1bae 549sub alter_table
550{
551 my ($to_table, $options) = @_;
552
553 my $qt = $options->{quote_table_name} || '';
554
555 my $table_options = generate_table_options($to_table) || '';
556 my $out = sprintf('ALTER TABLE %s%s',
557 $qt . $to_table->name . $qt,
558 $table_options);
559
560 return $out;
561}
562
0013ee25 563sub alter_field
564{
fe0f47d0 565 my ($from_field, $to_field, $options) = @_;
566
567 my $qf = $options->{quote_field_name} || '';
568 my $qt = $options->{quote_table_name} || '';
0013ee25 569
570 my $out = sprintf('ALTER TABLE %s CHANGE COLUMN %s %s',
fe0f47d0 571 $qt . $to_field->table->name . $qt,
572 $qf . $to_field->name . $qf,
573 create_field($to_field, $options));
0013ee25 574
575 return $out;
576}
577
578sub add_field
579{
fe0f47d0 580 my ($new_field, $options) = @_;
581
582 my $qt = $options->{quote_table_name} || '';
0013ee25 583
584 my $out = sprintf('ALTER TABLE %s ADD COLUMN %s',
fe0f47d0 585 $qt . $new_field->table->name . $qt,
586 create_field($new_field, $options));
0013ee25 587
588 return $out;
589
590}
591
592sub drop_field
593{
fe0f47d0 594 my ($old_field, $options) = @_;
0013ee25 595
fe0f47d0 596 my $qf = $options->{quote_field_name} || '';
597 my $qt = $options->{quote_table_name} || '';
598
0013ee25 599 my $out = sprintf('ALTER TABLE %s DROP COLUMN %s',
fe0f47d0 600 $qt . $old_field->table->name . $qt,
601 $qf . $old_field->name . $qf);
0013ee25 602
603 return $out;
604
9398955f 605}
606
da5a1bae 607sub next_unused_name {
608 my $name = shift || '';
609 if ( !defined($used_names{$name}) ) {
610 $used_names{$name} = $name;
611 return $name;
612 }
613
614 my $i = 1;
615 while ( defined($used_names{$name . '_' . $i}) ) {
616 ++$i;
617 }
618 $name .= '_' . $i;
619 $used_names{$name} = $name;
620 return $name;
621}
622
9398955f 6231;
9398955f 624
c855a748 625# -------------------------------------------------------------------
9398955f 626
c855a748 627=pod
628
629=head1 SEE ALSO
630
631SQL::Translator, http://www.mysql.com/.
9398955f 632
2d6979da 633=head1 AUTHORS
9398955f 634
758ab1cd 635darren chamberlain E<lt>darren@cpan.orgE<gt>,
c855a748 636Ken Y. Clark E<lt>kclark@cpan.orgE<gt>.
637
638=cut