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