Check for >255 field size for all char fields (not just varchar); turn a
[dbsrgits/SQL-Translator.git] / lib / SQL / Translator / Producer / MySQL.pm
1 package SQL::Translator::Producer::MySQL;
2
3 # -------------------------------------------------------------------
4 # $Id: MySQL.pm,v 1.37 2004-08-11 21:55:34 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.37 $ =~ /(\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
89     debug("PKG: Beginning production\n");
90
91     my $create; 
92     $create .= header_comment unless ($no_comments);
93     # \todo Don't set if MySQL 3.x is set on command line
94     $create .= "SET foreign_key_checks=0;\n\n";
95
96     for my $table ( $schema->get_tables ) {
97         my $table_name = $table->name;
98         debug("PKG: Looking at table '$table_name'\n");
99
100         #
101         # Header.  Should this look like what mysqldump produces?
102         #
103         $create .= "--\n-- Table: $table_name\n--\n" unless $no_comments;
104         $create .= qq[DROP TABLE IF EXISTS $table_name;\n] if $add_drop_table;
105         $create .= "CREATE TABLE $table_name (\n";
106
107         #
108         # Fields
109         #
110         my @field_defs;
111         for my $field ( $table->get_fields ) {
112             my $field_name = $field->name;
113             debug("PKG: Looking at field '$field_name'\n");
114             my $field_def = $field_name;
115
116             # data type and size
117             my $data_type = $field->data_type;
118             my @size      = $field->size;
119             my %extra     = $field->extra;
120             my $list      = $extra{'list'} || [];
121             # \todo deal with embedded quotes
122             my $commalist = join( ', ', map { qq['$_'] } @$list );
123
124             #
125             # Oracle "number" type -- figure best MySQL type
126             #
127             if ( lc $data_type eq 'number' ) {
128                 # not an integer
129                 if ( scalar @size > 1 ) {
130                     $data_type = 'double';
131                 }
132                 elsif ( $size[0] >= 12 ) {
133                     $data_type = 'bigint';
134                 }
135                 elsif ( $size[0] <= 1 ) {
136                     $data_type = 'tinyint';
137                 }
138                 else {
139                     $data_type = 'int';
140                 }
141             }
142             #
143             # Convert a large Oracle varchar to "text"
144             #
145             elsif ( $data_type =~ /char/i && $size[0] > 255 ) {
146                 $data_type = 'text';
147                 @size      = ();
148             }
149             elsif ( $data_type =~ /char/i && ! $size[0] ) {
150                 @size = (255);
151             }
152             elsif ( $data_type =~ /boolean/i ) {
153                 $data_type = 'enum';
154                 $commalist = "'0','1'";
155             }
156             elsif ( exists $translate{ lc $data_type } ) {
157                 $data_type = $translate{ lc $data_type };
158             }
159
160             @size = () if $data_type =~ /(text|blob)/i;
161
162             if ( $data_type =~ /(double|float)/ && scalar @size == 1 ) {
163                 push @size, '0';
164             }
165
166             $field_def .= " $data_type";
167             
168             if ( lc $data_type eq 'enum' ) {
169                 $field_def .= '(' . $commalist . ')';
170                         } 
171             elsif ( defined $size[0] && $size[0] > 0 ) {
172                 $field_def .= '(' . join( ', ', @size ) . ')';
173             }
174
175             # MySQL qualifiers
176             for my $qual ( qw[ binary unsigned zerofill ] ) {
177                 my $val = $extra{ $qual || uc $qual } or next;
178                 $field_def .= " $qual";
179             }
180
181             # Null?
182             $field_def .= ' NOT NULL' unless $field->is_nullable;
183
184             # Default?  XXX Need better quoting!
185             my $default = $field->default_value;
186             if ( defined $default ) {
187                 if ( uc $default eq 'NULL') {
188                     $field_def .= ' DEFAULT NULL';
189                 } else {
190                     $field_def .= " DEFAULT '$default'";
191                 }
192             }
193
194             # auto_increment?
195             $field_def .= " auto_increment" if $field->is_auto_increment;
196             push @field_defs, $field_def;
197                 }
198
199         #
200         # Indices
201         #
202         my @index_defs;
203         my %indexed_fields;
204         for my $index ( $table->get_indices ) {
205             push @index_defs, join( ' ', 
206                 lc $index->type eq 'normal' ? 'INDEX' : $index->type,
207                 $index->name,
208                 '(' . join( ', ', $index->fields ) . ')'
209             );
210             $indexed_fields{ $_ } = 1 for $index->fields;
211         }
212
213         #
214         # Constraints -- need to handle more than just FK. -ky
215         #
216         my @constraint_defs;
217         my $has_fk;
218         for my $c ( $table->get_constraints ) {
219             my @fields = $c->fields or next;
220
221             if ( $c->type eq PRIMARY_KEY ) {
222                 push @constraint_defs,
223                     'PRIMARY KEY (' . join(', ', @fields). ')';
224             }
225             elsif ( $c->type eq UNIQUE ) {
226                 push @constraint_defs,
227                     'UNIQUE (' . join(', ', @fields). ')';
228             }
229             elsif ( $c->type eq FOREIGN_KEY ) {
230                 $has_fk = 1;
231                 
232                 #
233                 # Make sure FK field is indexed or MySQL complains.
234                 #
235                 unless ( $indexed_fields{ $fields[0] } ) {
236                     push @index_defs, "INDEX ($fields[0])";
237                 }
238
239                 my $def = join(' ', 
240                     map { $_ || () } 'FOREIGN KEY', $c->name 
241                 );
242
243                 $def .= ' (' . join( ', ', @fields ) . ')';
244
245                 $def .= ' REFERENCES ' . $c->reference_table;
246
247                 if ( my @rfields = $c->reference_fields ) {
248                     $def .= ' (' . join( ', ', @rfields ) . ')';
249                 }
250
251                 if ( $c->match_type ) {
252                     $def .= ' MATCH ' . 
253                         ( $c->match_type =~ /full/i ) ? 'FULL' : 'PARTIAL';
254                 }
255
256                 if ( $c->on_delete ) {
257                     $def .= ' ON DELETE '.join( ' ', $c->on_delete );
258                 }
259
260                 if ( $c->on_update ) {
261                     $def .= ' ON UPDATE '.join( ' ', $c->on_update );
262                 }
263
264                 push @constraint_defs, $def;
265             }
266         }
267
268         $create .= join(",\n", map { "  $_" } 
269             @field_defs, @index_defs, @constraint_defs
270         );
271
272         #
273         # Footer
274         #
275         $create .= "\n)";
276         if ( $has_fk ) {
277             $create .= " Type=InnoDB";
278         }
279         $create .= ";\n\n";
280     }
281
282     return $create;
283 }
284
285 1;
286
287 # -------------------------------------------------------------------
288
289 =pod
290
291 =head1 SEE ALSO
292
293 SQL::Translator, http://www.mysql.com/.
294
295 =head1 AUTHORS
296
297 darren chamberlain E<lt>darren@cpan.orgE<gt>,
298 Ken Y. Clark E<lt>kclark@cpan.orgE<gt>.
299
300 =cut