Added the header to the change file.
[dbsrgits/SQL-Translator.git] / lib / SQL / Translator / Producer / MySQL.pm
CommitLineData
9398955f 1package SQL::Translator::Producer::MySQL;
2
49e1eb70 3# -------------------------------------------------------------------
1ded8513 4# $Id: MySQL.pm,v 1.43 2005-06-08 14:44:07 grommit Exp $
49e1eb70 5# -------------------------------------------------------------------
977651a5 6# Copyright (C) 2002-4 SQLFairy Authors
9398955f 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
c855a748 23=head1 NAME
24
25SQL::Translator::Producer::MySQL - MySQL-specific producer for SQL::Translator
26
27=head1 SYNOPSIS
28
29Use 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
38This module will produce text output of the schema suitable for MySQL.
39There are still some issues to be worked out with syntax differences
40between MySQL versions 3 and 4 ("SET foreign_key_checks," character sets
41for fields, etc.).
42
5a0c7b43 43=head2 Table Types
44
45Normally the tables will be created without any explicit table type given and
46so will use the MySQL default.
47
48Any tables involved in foreign key constraints automatically get a table type
49of InnoDB, unless this is overridden by setting the C<mysql_table_type> extra
50attribute explicitly on the table.
51
52=head2 Extra attributes.
53
54The producer recognises the following extra attributes on the Schema objects.
55
56=over 4
57
58=item field.list
59
60Set the list of allowed values for Enum fields.
61
62=item field.binary field.unsigned field.zerofill
63
64Set the MySQL field options of the same name.
65
1ded8513 66=item table.mysql_table_type
5a0c7b43 67
68Set the type of the table e.g. 'InnoDB', 'MyISAM'. This will be
69automatically set for tables involved in foreign key constraints if it is
70not already set explicitly. See L<"Table Types">.
71
1ded8513 72=item table.mysql_charset table.mysql_collate
73
74Set the tables default charater set and collation order.
75
76=item field.mysql_charset field.mysql_collate
77
78Set the fields charater set and collation order.
79
5a0c7b43 80=back
81
c855a748 82=cut
83
9398955f 84use strict;
d529894e 85use vars qw[ $VERSION $DEBUG ];
1ded8513 86$VERSION = sprintf "%d.%02d", q$Revision: 1.43 $ =~ /(\d+)\.(\d+)/;
5636ed00 87$DEBUG = 0 unless defined $DEBUG;
9398955f 88
89use Data::Dumper;
1c14e9f1 90use SQL::Translator::Schema::Constants;
5ee19df8 91use SQL::Translator::Utils qw(debug header_comment);
9398955f 92
d2344c83 93#
94# Use only lowercase for the keys (e.g. "long" and not "LONG")
95#
2620fc1c 96my %translate = (
97 #
98 # Oracle types
99 #
100 varchar2 => 'varchar',
101 long => 'text',
d2344c83 102 clob => 'longtext',
2620fc1c 103
104 #
105 # Sybase types
106 #
107 int => 'integer',
108 money => 'float',
109 real => 'double',
110 comment => 'text',
111 bit => 'tinyint',
87779799 112
113 #
114 # Access types
115 #
116 'long integer' => 'integer',
117 'text' => 'text',
118 'datetime' => 'datetime',
2620fc1c 119);
120
9398955f 121sub produce {
a1d94525 122 my $translator = shift;
123 local $DEBUG = $translator->debug;
124 my $no_comments = $translator->no_comments;
125 my $add_drop_table = $translator->add_drop_table;
126 my $schema = $translator->schema;
2bc23e82 127 my $show_warnings = $translator->show_warnings || 0;
d529894e 128
1a24938d 129 debug("PKG: Beginning production\n");
d529894e 130
131 my $create;
5ee19df8 132 $create .= header_comment unless ($no_comments);
0823773d 133 # \todo Don't set if MySQL 3.x is set on command line
da147d03 134 $create .= "SET foreign_key_checks=0;\n\n";
9398955f 135
5a0c7b43 136 #
137 # Work out which tables need to be InnoDB to support foreign key
138 # constraints. We do this first as we need InnoDB at both ends.
139 #
140 foreach ( map { $_->get_constraints } $schema->get_tables ) {
141 foreach my $meth (qw/table reference_table/) {
142 my $table = $schema->get_table($_->$meth) || next;
143 next if $table->extra('mysql_table_type');
144 $table->extra( 'mysql_table_type' => 'InnoDB');
145 }
146 }
147
148 #
149 # Generate sql
150 #
1c14e9f1 151 for my $table ( $schema->get_tables ) {
152 my $table_name = $table->name;
153 debug("PKG: Looking at table '$table_name'\n");
9398955f 154
d529894e 155 #
9398955f 156 # Header. Should this look like what mysqldump produces?
d529894e 157 #
1c14e9f1 158 $create .= "--\n-- Table: $table_name\n--\n" unless $no_comments;
159 $create .= qq[DROP TABLE IF EXISTS $table_name;\n] if $add_drop_table;
160 $create .= "CREATE TABLE $table_name (\n";
9398955f 161
d529894e 162 #
9398955f 163 # Fields
d529894e 164 #
1c14e9f1 165 my @field_defs;
166 for my $field ( $table->get_fields ) {
167 my $field_name = $field->name;
168 debug("PKG: Looking at field '$field_name'\n");
169 my $field_def = $field_name;
9398955f 170
171 # data type and size
1c14e9f1 172 my $data_type = $field->data_type;
173 my @size = $field->size;
35ed60b5 174 my %extra = $field->extra;
175 my $list = $extra{'list'} || [];
0823773d 176 # \todo deal with embedded quotes
4524cf01 177 my $commalist = join( ', ', map { qq['$_'] } @$list );
1ded8513 178 my $charset = $extra{'mysql_charset'};
179 my $collate = $extra{'mysql_collate'};
2620fc1c 180
1c14e9f1 181 #
182 # Oracle "number" type -- figure best MySQL type
183 #
184 if ( lc $data_type eq 'number' ) {
aa0a4c42 185 # not an integer
186 if ( scalar @size > 1 ) {
187 $data_type = 'double';
188 }
be42bce8 189 elsif ( $size[0] && $size[0] >= 12 ) {
aa0a4c42 190 $data_type = 'bigint';
191 }
be42bce8 192 elsif ( $size[0] && $size[0] <= 1 ) {
aa0a4c42 193 $data_type = 'tinyint';
194 }
195 else {
196 $data_type = 'int';
2620fc1c 197 }
198 }
49758c5d 199 #
200 # Convert a large Oracle varchar to "text"
201 #
2b695517 202 elsif ( $data_type =~ /char/i && $size[0] > 255 ) {
49758c5d 203 $data_type = 'text';
204 @size = ();
205 }
4167a803 206 elsif ( $data_type =~ /char/i && ! $size[0] ) {
207 @size = (255);
208 }
2b695517 209 elsif ( $data_type =~ /boolean/i ) {
210 $data_type = 'enum';
211 $commalist = "'0','1'";
212 }
d2344c83 213 elsif ( exists $translate{ lc $data_type } ) {
214 $data_type = $translate{ lc $data_type };
2620fc1c 215 }
216
472b787e 217 @size = () if $data_type =~ /(text|blob)/i;
6d3f6379 218
2b695517 219 if ( $data_type =~ /(double|float)/ && scalar @size == 1 ) {
220 push @size, '0';
221 }
222
1c14e9f1 223 $field_def .= " $data_type";
1ded8513 224
35ed60b5 225 if ( lc $data_type eq 'enum' ) {
226 $field_def .= '(' . $commalist . ')';
6d3f6379 227 }
228 elsif ( defined $size[0] && $size[0] > 0 ) {
1c14e9f1 229 $field_def .= '(' . join( ', ', @size ) . ')';
230 }
d529894e 231
1ded8513 232 # char sets
233 $field_def .= " CHARACTER SET $charset" if $charset;
234 $field_def .= " COLLATE $collate" if $collate;
235
d529894e 236 # MySQL qualifiers
237 for my $qual ( qw[ binary unsigned zerofill ] ) {
1c14e9f1 238 my $val = $extra{ $qual || uc $qual } or next;
0823773d 239 $field_def .= " $qual";
d529894e 240 }
9398955f 241
242 # Null?
1c14e9f1 243 $field_def .= ' NOT NULL' unless $field->is_nullable;
9398955f 244
245 # Default? XXX Need better quoting!
1c14e9f1 246 my $default = $field->default_value;
d529894e 247 if ( defined $default ) {
248 if ( uc $default eq 'NULL') {
1c14e9f1 249 $field_def .= ' DEFAULT NULL';
9398955f 250 } else {
1c14e9f1 251 $field_def .= " DEFAULT '$default'";
9398955f 252 }
253 }
254
255 # auto_increment?
1c14e9f1 256 $field_def .= " auto_increment" if $field->is_auto_increment;
257 push @field_defs, $field_def;
56120730 258 }
9398955f 259
d529894e 260 #
261 # Indices
262 #
1c14e9f1 263 my @index_defs;
bc60004d 264 my %indexed_fields;
1c14e9f1 265 for my $index ( $table->get_indices ) {
266 push @index_defs, join( ' ',
35ed60b5 267 lc $index->type eq 'normal' ? 'INDEX' : $index->type,
1c14e9f1 268 $index->name,
269 '(' . join( ', ', $index->fields ) . ')'
270 );
bc60004d 271 $indexed_fields{ $_ } = 1 for $index->fields;
d529894e 272 }
273
274 #
5e56da9a 275 # Constraints -- need to handle more than just FK. -ky
276 #
2620fc1c 277 my @constraint_defs;
2bc23e82 278 my @constraints = $table->get_constraints;
279 for my $c ( @constraints ) {
1c14e9f1 280 my @fields = $c->fields or next;
5e56da9a 281
1c14e9f1 282 if ( $c->type eq PRIMARY_KEY ) {
283 push @constraint_defs,
284 'PRIMARY KEY (' . join(', ', @fields). ')';
285 }
286 elsif ( $c->type eq UNIQUE ) {
287 push @constraint_defs,
288 'UNIQUE (' . join(', ', @fields). ')';
289 }
290 elsif ( $c->type eq FOREIGN_KEY ) {
bc60004d 291 #
292 # Make sure FK field is indexed or MySQL complains.
293 #
294 unless ( $indexed_fields{ $fields[0] } ) {
295 push @index_defs, "INDEX ($fields[0])";
2bc23e82 296 $indexed_fields{ $fields[0] } = 1;
bc60004d 297 }
298
1c14e9f1 299 my $def = join(' ',
300 map { $_ || () } 'FOREIGN KEY', $c->name
301 );
302
303 $def .= ' (' . join( ', ', @fields ) . ')';
304
305 $def .= ' REFERENCES ' . $c->reference_table;
306
2bc23e82 307 my @rfields = map { $_ || () } $c->reference_fields;
308 unless ( @rfields ) {
309 my $rtable_name = $c->reference_table;
310 if ( my $ref_table = $schema->get_table( $rtable_name ) ) {
311 push @rfields, $ref_table->primary_key;
312 }
313 else {
314 warn "Can't find reference table '$rtable_name' " .
315 "in schema\n" if $show_warnings;
316 }
317 }
318
319 if ( @rfields ) {
1c14e9f1 320 $def .= ' (' . join( ', ', @rfields ) . ')';
5e56da9a 321 }
2bc23e82 322 else {
323 warn "FK constraint on " . $table->name . '.' .
324 join('', @fields) . " has no reference fields\n"
325 if $show_warnings;
326 }
5e56da9a 327
1c14e9f1 328 if ( $c->match_type ) {
5e56da9a 329 $def .= ' MATCH ' .
1c14e9f1 330 ( $c->match_type =~ /full/i ) ? 'FULL' : 'PARTIAL';
5e56da9a 331 }
332
1c14e9f1 333 if ( $c->on_delete ) {
334 $def .= ' ON DELETE '.join( ' ', $c->on_delete );
586809da 335 }
336
1c14e9f1 337 if ( $c->on_update ) {
338 $def .= ' ON UPDATE '.join( ' ', $c->on_update );
586809da 339 }
5e56da9a 340
2620fc1c 341 push @constraint_defs, $def;
5e56da9a 342 }
343 }
344
1c14e9f1 345 $create .= join(",\n", map { " $_" }
346 @field_defs, @index_defs, @constraint_defs
347 );
5e56da9a 348
349 #
9398955f 350 # Footer
d529894e 351 #
c45c546e 352 $create .= "\n)";
5a0c7b43 353 my $mysql_table_type = $table->extra('mysql_table_type');
1ded8513 354 my $charset = $table->extra('mysql_charset');
355 my $collate = $table->extra('mysql_collate');
5a0c7b43 356 $create .= " Type=$mysql_table_type" if $mysql_table_type;
1ded8513 357 $create .= " DEFAULT CHARACTER SET $charset" if $charset;
358 $create .= " COLLATE $collate" if $collate;
9398955f 359 $create .= ";\n\n";
360 }
361
9398955f 362 return $create;
363}
364
9398955f 3651;
9398955f 366
c855a748 367# -------------------------------------------------------------------
9398955f 368
c855a748 369=pod
370
371=head1 SEE ALSO
372
373SQL::Translator, http://www.mysql.com/.
9398955f 374
2d6979da 375=head1 AUTHORS
9398955f 376
758ab1cd 377darren chamberlain E<lt>darren@cpan.orgE<gt>,
c855a748 378Ken Y. Clark E<lt>kclark@cpan.orgE<gt>.
379
380=cut