a5f9cf6b1f3167f802ec10134ee87a07f465b66f
[dbsrgits/SQL-Translator.git] / lib / SQL / Translator / Producer / MySQL.pm
1 package SQL::Translator::Producer::MySQL;
2
3 # -------------------------------------------------------------------
4 # $Id: MySQL.pm,v 1.35 2004-08-05 18:15:12 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.35 $ =~ /(\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 =~ /varchar/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 ( exists $translate{ lc $data_type } ) {
153                 $data_type = $translate{ lc $data_type };
154             }
155
156             @size = () if $data_type =~ /(text|blob)/i;
157
158             $field_def .= " $data_type";
159             
160             if ( lc $data_type eq 'enum' ) {
161                 $field_def .= '(' . $commalist . ')';
162                         } 
163             elsif ( defined $size[0] && $size[0] > 0 ) {
164                 $field_def .= '(' . join( ', ', @size ) . ')';
165             }
166
167             # MySQL qualifiers
168             for my $qual ( qw[ binary unsigned zerofill ] ) {
169                 my $val = $extra{ $qual || uc $qual } or next;
170                 $field_def .= " $qual";
171             }
172
173             # Null?
174             $field_def .= ' NOT NULL' unless $field->is_nullable;
175
176             # Default?  XXX Need better quoting!
177             my $default = $field->default_value;
178             if ( defined $default ) {
179                 if ( uc $default eq 'NULL') {
180                     $field_def .= ' DEFAULT NULL';
181                 } else {
182                     $field_def .= " DEFAULT '$default'";
183                 }
184             }
185
186             # auto_increment?
187             $field_def .= " auto_increment" if $field->is_auto_increment;
188             push @field_defs, $field_def;
189                 }
190
191         #
192         # Indices
193         #
194         my @index_defs;
195         for my $index ( $table->get_indices ) {
196             push @index_defs, join( ' ', 
197                 lc $index->type eq 'normal' ? 'INDEX' : $index->type,
198                 $index->name,
199                 '(' . join( ', ', $index->fields ) . ')'
200             );
201         }
202
203         #
204         # Constraints -- need to handle more than just FK. -ky
205         #
206         my @constraint_defs;
207         my $has_fk;
208         for my $c ( $table->get_constraints ) {
209             my @fields = $c->fields or next;
210
211             if ( $c->type eq PRIMARY_KEY ) {
212                 push @constraint_defs,
213                     'PRIMARY KEY (' . join(', ', @fields). ')';
214             }
215             elsif ( $c->type eq UNIQUE ) {
216                 push @constraint_defs,
217                     'UNIQUE (' . join(', ', @fields). ')';
218             }
219             elsif ( $c->type eq FOREIGN_KEY ) {
220                 $has_fk = 1;
221                 my $def = join(' ', 
222                     map { $_ || () } 'FOREIGN KEY', $c->name 
223                 );
224
225                 $def .= ' (' . join( ', ', @fields ) . ')';
226
227                 $def .= ' REFERENCES ' . $c->reference_table;
228
229                 if ( my @rfields = $c->reference_fields ) {
230                     $def .= ' (' . join( ', ', @rfields ) . ')';
231                 }
232
233                 if ( $c->match_type ) {
234                     $def .= ' MATCH ' . 
235                         ( $c->match_type =~ /full/i ) ? 'FULL' : 'PARTIAL';
236                 }
237
238                 if ( $c->on_delete ) {
239                     $def .= ' ON DELETE '.join( ' ', $c->on_delete );
240                 }
241
242                 if ( $c->on_update ) {
243                     $def .= ' ON UPDATE '.join( ' ', $c->on_update );
244                 }
245
246                 push @constraint_defs, $def;
247             }
248         }
249
250         $create .= join(",\n", map { "  $_" } 
251             @field_defs, @index_defs, @constraint_defs
252         );
253
254         #
255         # Footer
256         #
257         $create .= "\n)";
258         if ( $has_fk ) {
259             $create .= " Type=InnoDB";
260         }
261         $create .= ";\n\n";
262     }
263
264     return $create;
265 }
266
267 1;
268
269 # -------------------------------------------------------------------
270
271 =pod
272
273 =head1 SEE ALSO
274
275 SQL::Translator, http://www.mysql.com/.
276
277 =head1 AUTHORS
278
279 darren chamberlain E<lt>darren@cpan.orgE<gt>,
280 Ken Y. Clark E<lt>kclark@cpan.orgE<gt>.
281
282 =cut