dd41858f18ae118349fd6a9bd80b8bce40836c8f
[dbsrgits/SQL-Translator.git] / lib / SQL / Translator / Producer / MySQL.pm
1 package SQL::Translator::Producer::MySQL;
2
3 # -------------------------------------------------------------------
4 # $Id: MySQL.pm,v 1.41 2004-09-20 20:48:19 kycl4rk 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 =cut
44
45 use strict;
46 use vars qw[ $VERSION $DEBUG ];
47 $VERSION = sprintf "%d.%02d", q$Revision: 1.41 $ =~ /(\d+)\.(\d+)/;
48 $DEBUG   = 0 unless defined $DEBUG;
49
50 use Data::Dumper;
51 use SQL::Translator::Schema::Constants;
52 use SQL::Translator::Utils qw(debug header_comment);
53
54 #
55 # Use only lowercase for the keys (e.g. "long" and not "LONG")
56 #
57 my %translate  = (
58     #
59     # Oracle types
60     #
61     varchar2   => 'varchar',
62     long       => 'text',
63     clob       => 'longtext',
64
65     #
66     # Sybase types
67     #
68     int        => 'integer',
69     money      => 'float',
70     real       => 'double',
71     comment    => 'text',
72     bit        => 'tinyint',
73
74     #
75     # Access types
76     #
77     'long integer' => 'integer',
78     'text'         => 'text',
79     'datetime'     => 'datetime',
80 );
81
82 sub produce {
83     my $translator     = shift;
84     local $DEBUG       = $translator->debug;
85     my $no_comments    = $translator->no_comments;
86     my $add_drop_table = $translator->add_drop_table;
87     my $schema         = $translator->schema;
88     my $show_warnings  = $translator->show_warnings || 0;
89
90     debug("PKG: Beginning production\n");
91
92     my $create; 
93     $create .= header_comment unless ($no_comments);
94     # \todo Don't set if MySQL 3.x is set on command line
95     $create .= "SET foreign_key_checks=0;\n\n";
96
97     for my $table ( $schema->get_tables ) {
98         my $table_name = $table->name;
99         debug("PKG: Looking at table '$table_name'\n");
100
101         #
102         # Header.  Should this look like what mysqldump produces?
103         #
104         $create .= "--\n-- Table: $table_name\n--\n" unless $no_comments;
105         $create .= qq[DROP TABLE IF EXISTS $table_name;\n] if $add_drop_table;
106         $create .= "CREATE TABLE $table_name (\n";
107
108         #
109         # Fields
110         #
111         my @field_defs;
112         for my $field ( $table->get_fields ) {
113             my $field_name = $field->name;
114             debug("PKG: Looking at field '$field_name'\n");
115             my $field_def = $field_name;
116
117             # data type and size
118             my $data_type = $field->data_type;
119             my @size      = $field->size;
120             my %extra     = $field->extra;
121             my $list      = $extra{'list'} || [];
122             # \todo deal with embedded quotes
123             my $commalist = join( ', ', map { qq['$_'] } @$list );
124
125             #
126             # Oracle "number" type -- figure best MySQL type
127             #
128             if ( lc $data_type eq 'number' ) {
129                 # not an integer
130                 if ( scalar @size > 1 ) {
131                     $data_type = 'double';
132                 }
133                 elsif ( $size[0] && $size[0] >= 12 ) {
134                     $data_type = 'bigint';
135                 }
136                 elsif ( $size[0] && $size[0] <= 1 ) {
137                     $data_type = 'tinyint';
138                 }
139                 else {
140                     $data_type = 'int';
141                 }
142             }
143             #
144             # Convert a large Oracle varchar to "text"
145             #
146             elsif ( $data_type =~ /char/i && $size[0] > 255 ) {
147                 $data_type = 'text';
148                 @size      = ();
149             }
150             elsif ( $data_type =~ /char/i && ! $size[0] ) {
151                 @size = (255);
152             }
153             elsif ( $data_type =~ /boolean/i ) {
154                 $data_type = 'enum';
155                 $commalist = "'0','1'";
156             }
157             elsif ( exists $translate{ lc $data_type } ) {
158                 $data_type = $translate{ lc $data_type };
159             }
160
161             @size = () if $data_type =~ /(text|blob)/i;
162
163             if ( $data_type =~ /(double|float)/ && scalar @size == 1 ) {
164                 push @size, '0';
165             }
166
167             $field_def .= " $data_type";
168             
169             if ( lc $data_type eq 'enum' ) {
170                 $field_def .= '(' . $commalist . ')';
171                         } 
172             elsif ( defined $size[0] && $size[0] > 0 ) {
173                 $field_def .= '(' . join( ', ', @size ) . ')';
174             }
175
176             # MySQL qualifiers
177             for my $qual ( qw[ binary unsigned zerofill ] ) {
178                 my $val = $extra{ $qual || uc $qual } or next;
179                 $field_def .= " $qual";
180             }
181
182             # Null?
183             $field_def .= ' NOT NULL' unless $field->is_nullable;
184
185             # Default?  XXX Need better quoting!
186             my $default = $field->default_value;
187             if ( defined $default ) {
188                 if ( uc $default eq 'NULL') {
189                     $field_def .= ' DEFAULT NULL';
190                 } else {
191                     $field_def .= " DEFAULT '$default'";
192                 }
193             }
194
195             # auto_increment?
196             $field_def .= " auto_increment" if $field->is_auto_increment;
197             push @field_defs, $field_def;
198                 }
199
200         #
201         # Indices
202         #
203         my @index_defs;
204         my %indexed_fields;
205         for my $index ( $table->get_indices ) {
206             push @index_defs, join( ' ', 
207                 lc $index->type eq 'normal' ? 'INDEX' : $index->type,
208                 $index->name,
209                 '(' . join( ', ', $index->fields ) . ')'
210             );
211             $indexed_fields{ $_ } = 1 for $index->fields;
212         }
213
214         #
215         # Constraints -- need to handle more than just FK. -ky
216         #
217         my @constraint_defs;
218         my $has_fk;
219         my @constraints = $table->get_constraints;
220         for my $c ( @constraints ) {
221             my @fields = $c->fields or next;
222
223             if ( $c->type eq PRIMARY_KEY ) {
224                 push @constraint_defs,
225                     'PRIMARY KEY (' . join(', ', @fields). ')';
226             }
227             elsif ( $c->type eq UNIQUE ) {
228                 push @constraint_defs,
229                     'UNIQUE (' . join(', ', @fields). ')';
230             }
231             elsif ( $c->type eq FOREIGN_KEY ) {
232                 $has_fk = 1;
233                 
234                 #
235                 # Make sure FK field is indexed or MySQL complains.
236                 #
237                 unless ( $indexed_fields{ $fields[0] } ) {
238                     push @index_defs, "INDEX ($fields[0])";
239                     $indexed_fields{ $fields[0] } = 1;
240                 }
241
242                 my $def = join(' ', 
243                     map { $_ || () } 'FOREIGN KEY', $c->name 
244                 );
245
246                 $def .= ' (' . join( ', ', @fields ) . ')';
247
248                 $def .= ' REFERENCES ' . $c->reference_table;
249
250                 my @rfields = map { $_ || () } $c->reference_fields;
251                 unless ( @rfields ) {
252                     my $rtable_name = $c->reference_table;
253                     if ( my $ref_table = $schema->get_table( $rtable_name ) ) {
254                         push @rfields, $ref_table->primary_key;
255                     }
256                     else {
257                         warn "Can't find reference table '$rtable_name' " .
258                             "in schema\n" if $show_warnings;
259                     }
260                 }
261
262                 if ( @rfields ) {
263                     $def .= ' (' . join( ', ', @rfields ) . ')';
264                 }
265                 else {
266                     warn "FK constraint on " . $table->name . '.' .
267                         join('', @fields) . " has no reference fields\n" 
268                         if $show_warnings;
269                 }
270
271                 if ( $c->match_type ) {
272                     $def .= ' MATCH ' . 
273                         ( $c->match_type =~ /full/i ) ? 'FULL' : 'PARTIAL';
274                 }
275
276                 if ( $c->on_delete ) {
277                     $def .= ' ON DELETE '.join( ' ', $c->on_delete );
278                 }
279
280                 if ( $c->on_update ) {
281                     $def .= ' ON UPDATE '.join( ' ', $c->on_update );
282                 }
283
284                 push @constraint_defs, $def;
285             }
286         }
287
288         $create .= join(",\n", map { "  $_" } 
289             @field_defs, @index_defs, @constraint_defs
290         );
291
292         #
293         # Footer
294         #
295         $create .= "\n)";
296         if ( $has_fk ) {
297             $create .= " Type=InnoDB";
298         }
299         $create .= ";\n\n";
300     }
301
302     return $create;
303 }
304
305 1;
306
307 # -------------------------------------------------------------------
308
309 =pod
310
311 =head1 SEE ALSO
312
313 SQL::Translator, http://www.mysql.com/.
314
315 =head1 AUTHORS
316
317 darren chamberlain E<lt>darren@cpan.orgE<gt>,
318 Ken Y. Clark E<lt>kclark@cpan.orgE<gt>.
319
320 =cut