mysql_table_type extra data and InnoDB derivation fix.
[dbsrgits/SQL-Translator.git] / lib / SQL / Translator / Producer / MySQL.pm
1 package SQL::Translator::Producer::MySQL;
2
3 # -------------------------------------------------------------------
4 # $Id: MySQL.pm,v 1.42 2005-01-13 11:50:23 grommit Exp $
5 # -------------------------------------------------------------------
6 # Copyright (C) 2002-4 SQLFairy Authors
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
23 =head1 NAME
24
25 SQL::Translator::Producer::MySQL - MySQL-specific producer for SQL::Translator
26
27 =head1 SYNOPSIS
28
29 Use 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
38 This module will produce text output of the schema suitable for MySQL.
39 There are still some issues to be worked out with syntax differences 
40 between MySQL versions 3 and 4 ("SET foreign_key_checks," character sets
41 for fields, etc.).
42
43 =head2 Table Types
44
45 Normally the tables will be created without any explicit table type given and
46 so will use the MySQL default.
47
48 Any tables involved in foreign key constraints automatically get a table type
49 of InnoDB, unless this is overridden by setting the C<mysql_table_type> extra
50 attribute explicitly on the table.
51
52 =head2 Extra attributes.
53
54 The producer recognises the following extra attributes on the Schema objects.
55
56 =over 4
57
58 =item field.list
59
60 Set the list of allowed values for Enum fields.
61
62 =item field.binary field.unsigned field.zerofill
63
64 Set the MySQL field options of the same name.
65
66 =item mysql_table_type
67
68 Set the type of the table e.g. 'InnoDB', 'MyISAM'. This will be
69 automatically set for tables involved in foreign key constraints if it is
70 not already set explicitly. See L<"Table Types">.
71
72 =back
73
74 =cut
75
76 use strict;
77 use vars qw[ $VERSION $DEBUG ];
78 $VERSION = sprintf "%d.%02d", q$Revision: 1.42 $ =~ /(\d+)\.(\d+)/;
79 $DEBUG   = 0 unless defined $DEBUG;
80
81 use Data::Dumper;
82 use SQL::Translator::Schema::Constants;
83 use SQL::Translator::Utils qw(debug header_comment);
84
85 #
86 # Use only lowercase for the keys (e.g. "long" and not "LONG")
87 #
88 my %translate  = (
89     #
90     # Oracle types
91     #
92     varchar2   => 'varchar',
93     long       => 'text',
94     clob       => 'longtext',
95
96     #
97     # Sybase types
98     #
99     int        => 'integer',
100     money      => 'float',
101     real       => 'double',
102     comment    => 'text',
103     bit        => 'tinyint',
104
105     #
106     # Access types
107     #
108     'long integer' => 'integer',
109     'text'         => 'text',
110     'datetime'     => 'datetime',
111 );
112
113 sub produce {
114     my $translator     = shift;
115     local $DEBUG       = $translator->debug;
116     my $no_comments    = $translator->no_comments;
117     my $add_drop_table = $translator->add_drop_table;
118     my $schema         = $translator->schema;
119     my $show_warnings  = $translator->show_warnings || 0;
120
121     debug("PKG: Beginning production\n");
122
123     my $create; 
124     $create .= header_comment unless ($no_comments);
125     # \todo Don't set if MySQL 3.x is set on command line
126     $create .= "SET foreign_key_checks=0;\n\n";
127
128     #
129     # Work out which tables need to be InnoDB to support foreign key
130     # constraints. We do this first as we need InnoDB at both ends.
131     #
132     foreach ( map { $_->get_constraints } $schema->get_tables ) {
133         foreach my $meth (qw/table reference_table/) {
134             my $table = $schema->get_table($_->$meth) || next;
135             next if $table->extra('mysql_table_type');
136             $table->extra( 'mysql_table_type' => 'InnoDB');
137         }
138     }
139
140     #
141     # Generate sql
142     #
143     for my $table ( $schema->get_tables ) {
144         my $table_name = $table->name;
145         debug("PKG: Looking at table '$table_name'\n");
146
147         #
148         # Header.  Should this look like what mysqldump produces?
149         #
150         $create .= "--\n-- Table: $table_name\n--\n" unless $no_comments;
151         $create .= qq[DROP TABLE IF EXISTS $table_name;\n] if $add_drop_table;
152         $create .= "CREATE TABLE $table_name (\n";
153
154         #
155         # Fields
156         #
157         my @field_defs;
158         for my $field ( $table->get_fields ) {
159             my $field_name = $field->name;
160             debug("PKG: Looking at field '$field_name'\n");
161             my $field_def = $field_name;
162
163             # data type and size
164             my $data_type = $field->data_type;
165             my @size      = $field->size;
166             my %extra     = $field->extra;
167             my $list      = $extra{'list'} || [];
168             # \todo deal with embedded quotes
169             my $commalist = join( ', ', map { qq['$_'] } @$list );
170
171             #
172             # Oracle "number" type -- figure best MySQL type
173             #
174             if ( lc $data_type eq 'number' ) {
175                 # not an integer
176                 if ( scalar @size > 1 ) {
177                     $data_type = 'double';
178                 }
179                 elsif ( $size[0] && $size[0] >= 12 ) {
180                     $data_type = 'bigint';
181                 }
182                 elsif ( $size[0] && $size[0] <= 1 ) {
183                     $data_type = 'tinyint';
184                 }
185                 else {
186                     $data_type = 'int';
187                 }
188             }
189             #
190             # Convert a large Oracle varchar to "text"
191             #
192             elsif ( $data_type =~ /char/i && $size[0] > 255 ) {
193                 $data_type = 'text';
194                 @size      = ();
195             }
196             elsif ( $data_type =~ /char/i && ! $size[0] ) {
197                 @size = (255);
198             }
199             elsif ( $data_type =~ /boolean/i ) {
200                 $data_type = 'enum';
201                 $commalist = "'0','1'";
202             }
203             elsif ( exists $translate{ lc $data_type } ) {
204                 $data_type = $translate{ lc $data_type };
205             }
206
207             @size = () if $data_type =~ /(text|blob)/i;
208
209             if ( $data_type =~ /(double|float)/ && scalar @size == 1 ) {
210                 push @size, '0';
211             }
212
213             $field_def .= " $data_type";
214             
215             if ( lc $data_type eq 'enum' ) {
216                 $field_def .= '(' . $commalist . ')';
217                         } 
218             elsif ( defined $size[0] && $size[0] > 0 ) {
219                 $field_def .= '(' . join( ', ', @size ) . ')';
220             }
221
222             # MySQL qualifiers
223             for my $qual ( qw[ binary unsigned zerofill ] ) {
224                 my $val = $extra{ $qual || uc $qual } or next;
225                 $field_def .= " $qual";
226             }
227
228             # Null?
229             $field_def .= ' NOT NULL' unless $field->is_nullable;
230
231             # Default?  XXX Need better quoting!
232             my $default = $field->default_value;
233             if ( defined $default ) {
234                 if ( uc $default eq 'NULL') {
235                     $field_def .= ' DEFAULT NULL';
236                 } else {
237                     $field_def .= " DEFAULT '$default'";
238                 }
239             }
240
241             # auto_increment?
242             $field_def .= " auto_increment" if $field->is_auto_increment;
243             push @field_defs, $field_def;
244                 }
245
246         #
247         # Indices
248         #
249         my @index_defs;
250         my %indexed_fields;
251         for my $index ( $table->get_indices ) {
252             push @index_defs, join( ' ', 
253                 lc $index->type eq 'normal' ? 'INDEX' : $index->type,
254                 $index->name,
255                 '(' . join( ', ', $index->fields ) . ')'
256             );
257             $indexed_fields{ $_ } = 1 for $index->fields;
258         }
259
260         #
261         # Constraints -- need to handle more than just FK. -ky
262         #
263         my @constraint_defs;
264         my @constraints = $table->get_constraints;
265         for my $c ( @constraints ) {
266             my @fields = $c->fields or next;
267
268             if ( $c->type eq PRIMARY_KEY ) {
269                 push @constraint_defs,
270                     'PRIMARY KEY (' . join(', ', @fields). ')';
271             }
272             elsif ( $c->type eq UNIQUE ) {
273                 push @constraint_defs,
274                     'UNIQUE (' . join(', ', @fields). ')';
275             }
276             elsif ( $c->type eq FOREIGN_KEY ) {
277                 #
278                 # Make sure FK field is indexed or MySQL complains.
279                 #
280                 unless ( $indexed_fields{ $fields[0] } ) {
281                     push @index_defs, "INDEX ($fields[0])";
282                     $indexed_fields{ $fields[0] } = 1;
283                 }
284
285                 my $def = join(' ', 
286                     map { $_ || () } 'FOREIGN KEY', $c->name 
287                 );
288
289                 $def .= ' (' . join( ', ', @fields ) . ')';
290
291                 $def .= ' REFERENCES ' . $c->reference_table;
292
293                 my @rfields = map { $_ || () } $c->reference_fields;
294                 unless ( @rfields ) {
295                     my $rtable_name = $c->reference_table;
296                     if ( my $ref_table = $schema->get_table( $rtable_name ) ) {
297                         push @rfields, $ref_table->primary_key;
298                     }
299                     else {
300                         warn "Can't find reference table '$rtable_name' " .
301                             "in schema\n" if $show_warnings;
302                     }
303                 }
304
305                 if ( @rfields ) {
306                     $def .= ' (' . join( ', ', @rfields ) . ')';
307                 }
308                 else {
309                     warn "FK constraint on " . $table->name . '.' .
310                         join('', @fields) . " has no reference fields\n" 
311                         if $show_warnings;
312                 }
313
314                 if ( $c->match_type ) {
315                     $def .= ' MATCH ' . 
316                         ( $c->match_type =~ /full/i ) ? 'FULL' : 'PARTIAL';
317                 }
318
319                 if ( $c->on_delete ) {
320                     $def .= ' ON DELETE '.join( ' ', $c->on_delete );
321                 }
322
323                 if ( $c->on_update ) {
324                     $def .= ' ON UPDATE '.join( ' ', $c->on_update );
325                 }
326
327                 push @constraint_defs, $def;
328             }
329         }
330
331         $create .= join(",\n", map { "  $_" } 
332             @field_defs, @index_defs, @constraint_defs
333         );
334
335         #
336         # Footer
337         #
338         $create .= "\n)";
339         my $mysql_table_type = $table->extra('mysql_table_type');
340         $create .= " Type=$mysql_table_type" if $mysql_table_type;
341         $create .= ";\n\n";
342     }
343
344     return $create;
345 }
346
347 1;
348
349 # -------------------------------------------------------------------
350
351 =pod
352
353 =head1 SEE ALSO
354
355 SQL::Translator, http://www.mysql.com/.
356
357 =head1 AUTHORS
358
359 darren chamberlain E<lt>darren@cpan.orgE<gt>,
360 Ken Y. Clark E<lt>kclark@cpan.orgE<gt>.
361
362 =cut