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