Added Access data types.
[dbsrgits/SQL-Translator.git] / lib / SQL / Translator / Producer / MySQL.pm
1 package SQL::Translator::Producer::MySQL;
2
3 # -------------------------------------------------------------------
4 # $Id: MySQL.pm,v 1.33 2004-04-19 16:38:37 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.33 $ =~ /(\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         for my $c ( $table->get_constraints ) {
205             my @fields = $c->fields or next;
206
207             if ( $c->type eq PRIMARY_KEY ) {
208                 push @constraint_defs,
209                     'PRIMARY KEY (' . join(', ', @fields). ')';
210             }
211             elsif ( $c->type eq UNIQUE ) {
212                 push @constraint_defs,
213                     'UNIQUE (' . join(', ', @fields). ')';
214             }
215             elsif ( $c->type eq FOREIGN_KEY ) {
216                 my $def = join(' ', 
217                     map { $_ || () } 'FOREIGN KEY', $c->name 
218                 );
219
220                 $def .= ' (' . join( ', ', @fields ) . ')';
221
222                 $def .= ' REFERENCES ' . $c->reference_table;
223
224                 if ( my @rfields = $c->reference_fields ) {
225                     $def .= ' (' . join( ', ', @rfields ) . ')';
226                 }
227
228                 if ( $c->match_type ) {
229                     $def .= ' MATCH ' . 
230                         ( $c->match_type =~ /full/i ) ? 'FULL' : 'PARTIAL';
231                 }
232
233                 if ( $c->on_delete ) {
234                     $def .= ' ON DELETE '.join( ' ', $c->on_delete );
235                 }
236
237                 if ( $c->on_update ) {
238                     $def .= ' ON UPDATE '.join( ' ', $c->on_update );
239                 }
240
241                 push @constraint_defs, $def;
242             }
243         }
244
245         $create .= join(",\n", map { "  $_" } 
246             @field_defs, @index_defs, @constraint_defs
247         );
248
249         #
250         # Footer
251         #
252         $create .= "\n)";
253 #        while ( 
254 #            my ( $key, $val ) = each %{ $table->options }
255 #        ) {
256 #            $create .= " $key=$val" 
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